refactor(ai): separate conversation and generation lowering

This commit is contained in:
Shoubhit Dash 2026-08-31 20:51:17 +05:30
parent eb083cce63
commit ffb9728068
3 changed files with 58 additions and 23 deletions

View file

@ -689,13 +689,30 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
return input
})
const lowerOptions = (request: LLMRequest) => {
const options = OpenResponsesOptions.resolve(request)
export const lowerConversation = Effect.fn("OpenResponses.lowerConversation")(function* (
request: LLMRequest,
extension: Extension,
) {
const instructions = ProviderShared.joinText(request.system)
return {
model: request.model.id,
input: yield* lowerMessages(request, extension),
...(instructions ? { instructions } : {}),
}
})
export const lowerGeneration = (request: LLMRequest) => {
const options = OpenResponsesOptions.resolve(request)
const generation = request.generation
const cacheKey = ProviderShared.promptCacheKey(request)
const parallelToolCalls = resolveParallelToolCalls(request)
return {
...(instructions ? { instructions } : {}),
stream: true as const,
max_output_tokens: generation?.maxTokens,
temperature: generation?.temperature,
top_p: generation?.topP,
presence_penalty: generation?.presencePenalty,
frequency_penalty: generation?.frequencyPenalty,
...(options.store !== undefined ? { store: options.store } : {}),
...(options.metadata ? { metadata: options.metadata } : {}),
...(options.safetyIdentifier ? { safety_identifier: options.safetyIdentifier } : {}),
@ -723,7 +740,7 @@ export const resolveParallelToolCalls = (request: LLMRequest) => {
return disabled === undefined ? undefined : !disabled
}
const allowedToolChoice = (request: LLMRequest) => {
export const allowedToolChoice = (request: LLMRequest) => {
const allowed = OpenResponsesOptions.resolve(request).allowedTools
if (!allowed) return undefined
return {
@ -737,11 +754,10 @@ export const fromRequestWithExtension = Effect.fn("OpenResponses.fromRequestWith
request: LLMRequest,
extension: Extension,
) {
const generation = request.generation
const toolSchemaCompatibility = request.model.compatibility?.toolSchema
return {
model: request.model.id,
input: yield* lowerMessages(request, extension),
...(yield* lowerConversation(request, extension)),
...lowerGeneration(request),
tools:
request.tools.length === 0
? undefined
@ -755,13 +771,6 @@ export const fromRequestWithExtension = Effect.fn("OpenResponses.fromRequestWith
tool_choice:
allowedToolChoice(request) ??
(request.toolChoice ? yield* lowerToolChoice(extension.name, request.toolChoice) : undefined),
stream: true as const,
max_output_tokens: generation?.maxTokens,
temperature: generation?.temperature,
top_p: generation?.topP,
presence_penalty: generation?.presencePenalty,
frequency_penalty: generation?.frequencyPenalty,
...lowerOptions(request),
}
})

View file

@ -5,7 +5,7 @@ import { Auth } from "../route/auth.js"
import { Endpoint } from "../route/endpoint.js"
import { Protocol } from "../route/protocol.js"
import { HttpTransport } from "../route/transport/index.js"
import { LLMRequest, type JsonSchema, type ToolDefinition } from "../schema/index.js"
import type { LLMRequest, JsonSchema, ToolDefinition } from "../schema/index.js"
import { OpenResponses } from "./open-responses.js"
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js"
import { OpenAIImage } from "./utils/openai-image.js"
@ -125,15 +125,10 @@ const lowerToolChoice = (toolChoice: NonNullable<LLMRequest["toolChoice"]>, tool
const decodeBody = ProviderShared.validateWith(Schema.decodeUnknownEffect(OpenAIResponsesBody))
const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request: LLMRequest) {
const body = yield* OpenResponses.fromRequestWithExtension(
LLMRequest.update(request, { tools: [], toolChoice: undefined }),
extension,
)
const toolSchemaCompatibility = request.model.compatibility?.toolSchema
const parallelToolCalls = OpenResponses.resolveParallelToolCalls(request)
return yield* decodeBody({
...body,
...(parallelToolCalls === undefined ? {} : { parallel_tool_calls: parallelToolCalls }),
...(yield* OpenResponses.lowerConversation(request, extension)),
...OpenResponses.lowerGeneration(request),
tools:
request.tools.length === 0
? undefined
@ -141,7 +136,8 @@ const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request:
lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility)),
),
tool_choice:
body.tool_choice ?? (request.toolChoice ? yield* lowerToolChoice(request.toolChoice, request.tools) : undefined),
OpenResponses.allowedToolChoice(request) ??
(request.toolChoice ? yield* lowerToolChoice(request.toolChoice, request.tools) : undefined),
})
})

View file

@ -0,0 +1,30 @@
import { expect } from "bun:test"
import { Effect } from "effect"
import { LLM, Message } from "../../src/index.js"
import { OpenAI } from "../../src/providers.js"
import { OpenResponses } from "../../src/protocols/open-responses.js"
import { it } from "../lib/effect.js"
it.effect("conversation lowering excludes generation settings and tool definitions", () =>
Effect.gen(function* () {
const body = yield* OpenResponses.lowerConversation(
LLM.request({
model: OpenAI.configure({ apiKey: "test" }).responses("fixture"),
system: "Keep the context",
messages: [Message.user("hello"), Message.assistant("hi")],
generation: { maxTokens: 100, temperature: 0.5 },
providerOptions: { store: false },
tools: [{ name: "unsupported", description: "Generation only", inputSchema: {}, native: { unsupported: {} } }],
}),
{ id: "open-responses", name: "Open Responses" },
)
expect(body).toEqual({
model: "fixture",
instructions: "Keep the context",
input: [
{ role: "user", content: [{ type: "input_text", text: "hello" }] },
{ type: "message", role: "assistant", content: [{ type: "output_text", text: "hi" }] },
],
})
}),
)