Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 73 additions & 69 deletions js/plugins/anthropic/src/runner/beta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { logger } from 'genkit/logging';

import { KNOWN_CLAUDE_MODELS, extractVersion } from '../models.js';
import { AnthropicConfigSchema, type ClaudeRunnerParams } from '../types.js';
import { removeUndefinedProperties } from '../utils.js';
import { BaseRunner } from './base.js';
import { RunnerTypes } from './types.js';

Expand Down Expand Up @@ -299,48 +300,49 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
: system;
}

const thinkingConfig = this.toAnthropicThinkingConfig(
request.config?.thinking
) as BetaMessageCreateParams['thinking'] | undefined;

// Need to extract topP and topK from request.config to avoid duplicate properties being added to the body
// This happens because topP and topK have different property names (top_p and top_k) in the Anthropic API.
// Thinking is extracted separately to avoid type issues.
// ApiVersion is extracted separately as it's not a valid property for the Anthropic API.
const {
topP,
topK,
apiVersion: _1,
thinking: _2,
...restConfig
} = request.config ?? {};

const body: BetaMessageCreateParamsNonStreaming = {
model: mappedModelName,
max_tokens:
request.config?.maxOutputTokens ?? this.DEFAULT_MAX_OUTPUT_TOKENS,
messages,
betas: BETA_APIS,
system: betaSystem,
stop_sequences: request.config?.stopSequences,
temperature: request.config?.temperature,
top_k: topK,
top_p: topP,
tool_choice: request.config?.tool_choice,
metadata: request.config?.metadata,
tools: request.tools?.map((tool) => this.toAnthropicTool(tool)),
thinking: thinkingConfig,
output_format: this.isStructuredOutputEnabled(request)
? {
type: 'json_schema',
schema: toAnthropicSchema(request.output!.schema!),
}
: undefined,
betas: Array.isArray(request.config?.betas)
? [...(request.config?.betas ?? [])]
: [...BETA_APIS],
...restConfig,
};

if (betaSystem !== undefined) body.system = betaSystem;
if (request.config?.stopSequences !== undefined)
body.stop_sequences = request.config.stopSequences;
if (request.config?.temperature !== undefined)
body.temperature = request.config.temperature;
if (request.config?.topK !== undefined) body.top_k = request.config.topK;
if (request.config?.topP !== undefined) body.top_p = request.config.topP;
if (request.config?.tool_choice !== undefined) {
body.tool_choice = request.config
.tool_choice as BetaMessageCreateParams['tool_choice'];
}
if (request.config?.metadata !== undefined) {
body.metadata = request.config
.metadata as BetaMessageCreateParams['metadata'];
}
if (request.tools) {
body.tools = request.tools.map((tool) => this.toAnthropicTool(tool));
}
const thinkingConfig = this.toAnthropicThinkingConfig(
request.config?.thinking
);
if (thinkingConfig) {
body.thinking = thinkingConfig as BetaMessageCreateParams['thinking'];
}

// Apply structured output when model supports it and constrained output is requested
if (this.isStructuredOutputEnabled(request)) {
body.output_format = {
type: 'json_schema',
schema: toAnthropicSchema(request.output!.schema!),
};
}

return body;
return removeUndefinedProperties(body);
}

/**
Expand Down Expand Up @@ -369,48 +371,50 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
]
: system;

const thinkingConfig = this.toAnthropicThinkingConfig(
request.config?.thinking
) as BetaMessageCreateParams['thinking'] | undefined;

// Need to extract topP and topK from request.config to avoid duplicate properties being added to the body
// This happens because topP and topK have different property names (top_p and top_k) in the Anthropic API.
// Thinking is extracted separately to avoid type issues.
// ApiVersion is extracted separately as it's not a valid property for the Anthropic API.
const {
topP,
topK,
apiVersion: _1,
thinking: _2,
...restConfig
} = request.config ?? {};

const body: BetaMessageCreateParamsStreaming = {
model: mappedModelName,
max_tokens:
request.config?.maxOutputTokens ?? this.DEFAULT_MAX_OUTPUT_TOKENS,
messages,
stream: true,
betas: BETA_APIS,
system: betaSystem,
stop_sequences: request.config?.stopSequences,
temperature: request.config?.temperature,
top_k: topK,
top_p: topP,
tool_choice: request.config?.tool_choice,
metadata: request.config?.metadata,
tools: request.tools?.map((tool) => this.toAnthropicTool(tool)),
thinking: thinkingConfig,
output_format: this.isStructuredOutputEnabled(request)
? {
type: 'json_schema',
schema: toAnthropicSchema(request.output!.schema!),
}
: undefined,
betas: Array.isArray(request.config?.betas)
? [...(request.config?.betas ?? [])]
: [...BETA_APIS],
...restConfig,
};

if (betaSystem !== undefined) body.system = betaSystem;
if (request.config?.stopSequences !== undefined)
body.stop_sequences = request.config.stopSequences;
if (request.config?.temperature !== undefined)
body.temperature = request.config.temperature;
if (request.config?.topK !== undefined) body.top_k = request.config.topK;
if (request.config?.topP !== undefined) body.top_p = request.config.topP;
if (request.config?.tool_choice !== undefined) {
body.tool_choice = request.config
.tool_choice as BetaMessageCreateParams['tool_choice'];
}
if (request.config?.metadata !== undefined) {
body.metadata = request.config
.metadata as BetaMessageCreateParams['metadata'];
}
if (request.tools) {
body.tools = request.tools.map((tool) => this.toAnthropicTool(tool));
}
const thinkingConfig = this.toAnthropicThinkingConfig(
request.config?.thinking
);
if (thinkingConfig) {
body.thinking = thinkingConfig as BetaMessageCreateParams['thinking'];
}

// Apply structured output when model supports it and constrained output is requested
if (this.isStructuredOutputEnabled(request)) {
body.output_format = {
type: 'json_schema',
schema: toAnthropicSchema(request.output!.schema!),
};
}
return body;
return removeUndefinedProperties(body);
}

protected toGenkitResponse(message: BetaMessage): GenerateResponseData {
Expand Down
145 changes: 68 additions & 77 deletions js/plugins/anthropic/src/runner/stable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ import { logger } from 'genkit/logging';

import { KNOWN_CLAUDE_MODELS, extractVersion } from '../models.js';
import { AnthropicConfigSchema, type ClaudeRunnerParams } from '../types.js';
import { removeUndefinedProperties } from '../utils.js';
import { BaseRunner } from './base.js';
import { RunnerTypes as BaseRunnerTypes } from './types.js';

interface RunnerTypes extends BaseRunnerTypes {
Message: Message;
Stream: MessageStream;
Expand Down Expand Up @@ -179,6 +181,12 @@ export class Runner extends BaseRunner<RunnerTypes> {
request: GenerateRequest<typeof AnthropicConfigSchema>,
cacheSystemPrompt?: boolean
): MessageCreateParamsNonStreaming {
if (request.output?.format && request.output.format !== 'text') {
throw new Error(
`Only text output format is supported for Claude models currently`
);
}

const model = KNOWN_CLAUDE_MODELS[modelName];
const { system, messages } = this.toAnthropicMessages(request.messages);
const mappedModelName =
Expand All @@ -197,58 +205,53 @@ export class Runner extends BaseRunner<RunnerTypes> {
]
: system;

const thinkingConfig = this.toAnthropicThinkingConfig(
request.config?.thinking
) as MessageCreateParams['thinking'] | undefined;

// Need to extract topP and topK from request.config to avoid duplicate properties being added to the body
// This happens because topP and topK have different property names (top_p and top_k) in the Anthropic API.
// Thinking is extracted separately to avoid type issues.
// ApiVersion is extracted separately as it's not a valid property for the Anthropic API.
const {
topP,
topK,
apiVersion: _1,
thinking: _2,
...restConfig
} = request.config ?? {};

const body: MessageCreateParamsNonStreaming = {
model: mappedModelName,
max_tokens:
request.config?.maxOutputTokens ?? this.DEFAULT_MAX_OUTPUT_TOKENS,
messages,
system: systemValue,
stop_sequences: request.config?.stopSequences,
temperature: request.config?.temperature,
top_k: topK,
top_p: topP,
tool_choice: request.config?.tool_choice,
metadata: request.config?.metadata,
tools: request.tools?.map((tool) => this.toAnthropicTool(tool)),
thinking: thinkingConfig,
...restConfig,
};

if (systemValue !== undefined) {
body.system = systemValue;
}

if (request.tools) {
body.tools = request.tools.map((tool) => this.toAnthropicTool(tool));
}
if (request.config?.topK !== undefined) {
body.top_k = request.config.topK;
}
if (request.config?.topP !== undefined) {
body.top_p = request.config.topP;
}
if (request.config?.temperature !== undefined) {
body.temperature = request.config.temperature;
}
if (request.config?.stopSequences !== undefined) {
body.stop_sequences = request.config.stopSequences;
}
if (request.config?.metadata !== undefined) {
body.metadata = request.config.metadata;
}
if (request.config?.tool_choice !== undefined) {
body.tool_choice = request.config.tool_choice;
}
const thinkingConfig = this.toAnthropicThinkingConfig(
request.config?.thinking
);
if (thinkingConfig) {
body.thinking = thinkingConfig as MessageCreateParams['thinking'];
}

if (request.output?.format && request.output.format !== 'text') {
throw new Error(
`Only text output format is supported for Claude models currently`
);
}
return body;
return removeUndefinedProperties(body);
}

protected toAnthropicStreamingRequestBody(
modelName: string,
request: GenerateRequest<typeof AnthropicConfigSchema>,
cacheSystemPrompt?: boolean
): MessageCreateParamsStreaming {
if (request.output?.format && request.output.format !== 'text') {
throw new Error(
`Only text output format is supported for Claude models currently`
);
}

const model = KNOWN_CLAUDE_MODELS[modelName];
const { system, messages } = this.toAnthropicMessages(request.messages);
const mappedModelName =
Expand All @@ -267,53 +270,41 @@ export class Runner extends BaseRunner<RunnerTypes> {
]
: system;

const thinkingConfig = this.toAnthropicThinkingConfig(
request.config?.thinking
) as MessageCreateParams['thinking'] | undefined;

// Need to extract topP and topK from request.config to avoid duplicate properties being added to the body
// This happens because topP and topK have different property names (top_p and top_k) in the Anthropic API.
// Thinking is extracted separately to avoid type issues.
// ApiVersion is extracted separately as it's not a valid property for the Anthropic API.
const {
topP,
topK,
apiVersion: _1,
thinking: _2,
...restConfig
} = request.config ?? {};

const body: MessageCreateParamsStreaming = {
model: mappedModelName,
max_tokens:
request.config?.maxOutputTokens ?? this.DEFAULT_MAX_OUTPUT_TOKENS,
messages,
stream: true,
system: systemValue,
stop_sequences: request.config?.stopSequences,
temperature: request.config?.temperature,
top_k: topK,
top_p: topP,
tool_choice: request.config?.tool_choice,
metadata: request.config?.metadata,
tools: request.tools?.map((tool) => this.toAnthropicTool(tool)),
thinking: thinkingConfig,
...restConfig,
};

if (systemValue !== undefined) {
body.system = systemValue;
}

if (request.tools) {
body.tools = request.tools.map((tool) => this.toAnthropicTool(tool));
}
if (request.config?.topK !== undefined) {
body.top_k = request.config.topK;
}
if (request.config?.topP !== undefined) {
body.top_p = request.config.topP;
}
if (request.config?.temperature !== undefined) {
body.temperature = request.config.temperature;
}
if (request.config?.stopSequences !== undefined) {
body.stop_sequences = request.config.stopSequences;
}
if (request.config?.metadata !== undefined) {
body.metadata = request.config.metadata;
}
if (request.config?.tool_choice !== undefined) {
body.tool_choice = request.config.tool_choice;
}
const thinkingConfig = this.toAnthropicThinkingConfig(
request.config?.thinking
);
if (thinkingConfig) {
body.thinking =
thinkingConfig as MessageCreateParamsStreaming['thinking'];
}

if (request.output?.format && request.output.format !== 'text') {
throw new Error(
`Only text output format is supported for Claude models currently`
);
}
return body;
return removeUndefinedProperties(body);
}

protected async createMessage(
Expand Down
Loading