refactor(llm): share tool choice lowering
This commit is contained in:
@@ -209,15 +209,13 @@ const lowerTool = (tool: ToolDefinition): AnthropicTool => ({
|
||||
input_schema: tool.inputSchema,
|
||||
})
|
||||
|
||||
const lowerToolChoice = Effect.fn("AnthropicMessages.lowerToolChoice")(function* (
|
||||
toolChoice: NonNullable<LLMRequest["toolChoice"]>,
|
||||
) {
|
||||
if (toolChoice.type === "none") return undefined
|
||||
if (toolChoice.type === "required") return { type: "any" as const }
|
||||
if (toolChoice.type !== "tool") return { type: "auto" as const }
|
||||
if (!toolChoice.name) return yield* invalid("Anthropic Messages tool choice requires a tool name")
|
||||
return { type: "tool" as const, name: toolChoice.name }
|
||||
})
|
||||
const lowerToolChoice = (toolChoice: NonNullable<LLMRequest["toolChoice"]>) =>
|
||||
ProviderShared.matchToolChoice("Anthropic Messages", toolChoice, {
|
||||
auto: () => ({ type: "auto" as const }),
|
||||
none: () => undefined,
|
||||
required: () => ({ type: "any" as const }),
|
||||
tool: (name) => ({ type: "tool" as const, name }),
|
||||
})
|
||||
|
||||
const lowerToolCall = (part: ToolCallPart): AnthropicToolUseBlock => ({
|
||||
type: "tool_use",
|
||||
|
||||
@@ -232,15 +232,13 @@ const textWithCache = (text: string, cache: CacheHint | undefined): Array<Bedroc
|
||||
return cachePoint ? [{ text }, cachePoint] : [{ text }]
|
||||
}
|
||||
|
||||
const lowerToolChoice = Effect.fn("BedrockConverse.lowerToolChoice")(function* (
|
||||
toolChoice: NonNullable<LLMRequest["toolChoice"]>,
|
||||
) {
|
||||
if (toolChoice.type === "none") return undefined
|
||||
if (toolChoice.type === "required") return { any: {} } as const
|
||||
if (toolChoice.type !== "tool") return { auto: {} } as const
|
||||
if (!toolChoice.name) return yield* invalid("Bedrock Converse tool choice requires a tool name")
|
||||
return { tool: { name: toolChoice.name } } as const
|
||||
})
|
||||
const lowerToolChoice = (toolChoice: NonNullable<LLMRequest["toolChoice"]>) =>
|
||||
ProviderShared.matchToolChoice("Bedrock Converse", toolChoice, {
|
||||
auto: () => ({ auto: {} }) as const,
|
||||
none: () => undefined,
|
||||
required: () => ({ any: {} }) as const,
|
||||
tool: (name) => ({ tool: { name } }) as const,
|
||||
})
|
||||
|
||||
const lowerToolCall = (part: ToolCallPart): BedrockToolUseBlock => ({
|
||||
toolUse: {
|
||||
|
||||
@@ -170,17 +170,13 @@ const lowerTool = (tool: ToolDefinition) => ({
|
||||
parameters: GeminiToolSchema.convert(tool.inputSchema),
|
||||
})
|
||||
|
||||
const lowerToolConfig = Effect.fn("Gemini.lowerToolConfig")(function* (
|
||||
toolChoice: NonNullable<LLMRequest["toolChoice"]>,
|
||||
) {
|
||||
if (toolChoice.type === "required") return { functionCallingConfig: { mode: "ANY" as const } }
|
||||
if (toolChoice.type === "none") return { functionCallingConfig: { mode: "NONE" as const } }
|
||||
if (toolChoice.type !== "tool") return { functionCallingConfig: { mode: "AUTO" as const } }
|
||||
if (!toolChoice.name) return yield* invalid("Gemini tool choice requires a tool name")
|
||||
return {
|
||||
functionCallingConfig: { mode: "ANY" as const, allowedFunctionNames: [toolChoice.name] },
|
||||
}
|
||||
})
|
||||
const lowerToolConfig = (toolChoice: NonNullable<LLMRequest["toolChoice"]>) =>
|
||||
ProviderShared.matchToolChoice("Gemini", toolChoice, {
|
||||
auto: () => ({ functionCallingConfig: { mode: "AUTO" as const } }),
|
||||
none: () => ({ functionCallingConfig: { mode: "NONE" as const } }),
|
||||
required: () => ({ functionCallingConfig: { mode: "ANY" as const } }),
|
||||
tool: (name) => ({ functionCallingConfig: { mode: "ANY" as const, allowedFunctionNames: [name] } }),
|
||||
})
|
||||
|
||||
const lowerUserPart = (part: TextPart | MediaPart) =>
|
||||
part.type === "text"
|
||||
|
||||
@@ -166,13 +166,13 @@ const lowerTool = (tool: ToolDefinition): OpenAIChatTool => ({
|
||||
},
|
||||
})
|
||||
|
||||
const lowerToolChoice = Effect.fn("OpenAIChat.lowerToolChoice")(function* (
|
||||
toolChoice: NonNullable<LLMRequest["toolChoice"]>,
|
||||
) {
|
||||
if (toolChoice.type !== "tool") return toolChoice.type
|
||||
if (!toolChoice.name) return yield* invalid("OpenAI Chat tool choice requires a tool name")
|
||||
return { type: "function" as const, function: { name: toolChoice.name } }
|
||||
})
|
||||
const lowerToolChoice = (toolChoice: NonNullable<LLMRequest["toolChoice"]>) =>
|
||||
ProviderShared.matchToolChoice("OpenAI Chat", toolChoice, {
|
||||
auto: () => "auto" as const,
|
||||
none: () => "none" as const,
|
||||
required: () => "required" as const,
|
||||
tool: (name) => ({ type: "function" as const, function: { name } }),
|
||||
})
|
||||
|
||||
const lowerToolCall = (part: ToolCallPart): OpenAIChatAssistantToolCall => ({
|
||||
id: part.id,
|
||||
|
||||
@@ -158,13 +158,13 @@ const lowerTool = (tool: ToolDefinition): OpenAIResponsesTool => ({
|
||||
parameters: tool.inputSchema,
|
||||
})
|
||||
|
||||
const lowerToolChoice = Effect.fn("OpenAIResponses.lowerToolChoice")(function* (
|
||||
toolChoice: NonNullable<LLMRequest["toolChoice"]>,
|
||||
) {
|
||||
if (toolChoice.type !== "tool") return toolChoice.type
|
||||
if (!toolChoice.name) return yield* invalid("OpenAI Responses tool choice requires a tool name")
|
||||
return { type: "function" as const, name: toolChoice.name }
|
||||
})
|
||||
const lowerToolChoice = (toolChoice: NonNullable<LLMRequest["toolChoice"]>) =>
|
||||
ProviderShared.matchToolChoice("OpenAI Responses", toolChoice, {
|
||||
auto: () => "auto" as const,
|
||||
none: () => "none" as const,
|
||||
required: () => "required" as const,
|
||||
tool: (name) => ({ type: "function" as const, name }),
|
||||
})
|
||||
|
||||
const lowerToolCall = (part: ToolCallPart): OpenAIResponsesInputItem => ({
|
||||
type: "function_call",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Buffer } from "node:buffer"
|
||||
import { Cause, Effect, Schema, Stream } from "effect"
|
||||
import * as Sse from "effect/unstable/encoding/Sse"
|
||||
import { Headers, HttpClientRequest, type HttpClientResponse } from "effect/unstable/http"
|
||||
import { InvalidRequestError, ProviderChunkError, type MediaPart, type ToolResultPart } from "../schema"
|
||||
import { InvalidRequestError, ProviderChunkError, type LLMRequest, type MediaPart, type ToolResultPart } from "../schema"
|
||||
|
||||
export const Json = Schema.fromJsonString(Schema.Unknown)
|
||||
export const decodeJson = Schema.decodeUnknownSync(Json)
|
||||
@@ -168,6 +168,24 @@ export const sseFraming = (
|
||||
*/
|
||||
export const invalidRequest = (message: string) => new InvalidRequestError({ message })
|
||||
|
||||
export const matchToolChoice = <Auto, None, Required, Tool>(
|
||||
adapter: string,
|
||||
toolChoice: NonNullable<LLMRequest["toolChoice"]>,
|
||||
cases: {
|
||||
readonly auto: () => Auto
|
||||
readonly none: () => None
|
||||
readonly required: () => Required
|
||||
readonly tool: (name: string) => Tool
|
||||
},
|
||||
) =>
|
||||
Effect.gen(function* () {
|
||||
if (toolChoice.type === "auto") return cases.auto()
|
||||
if (toolChoice.type === "none") return cases.none()
|
||||
if (toolChoice.type === "required") return cases.required()
|
||||
if (!toolChoice.name) return yield* invalidRequest(`${adapter} tool choice requires a tool name`)
|
||||
return cases.tool(toolChoice.name)
|
||||
})
|
||||
|
||||
/**
|
||||
* Build a `validate` step from a Schema decoder. Replaces the per-adapter
|
||||
* lambda body `(payload) => decode(payload).pipe(Effect.mapError((e) =>
|
||||
|
||||
Reference in New Issue
Block a user