Compare commits

...
Author SHA1 Message Date
Aiden Cline 39d57b052f fix(ai): derive maxTokensField from provider for OpenAI Chat
Derive max_tokens vs max_completion_tokens from provider/baseURL when
explicit compatibility is not set, aligned with models.dev provider
naming: DeepSeek, Moonshot AI, Together AI, ZAI (including coding-plan
and Zhipu variants), Nvidia, Cerebras, Chutes, and Cloudflare AI
Gateway still require max_tokens. Explicit compatibility.maxTokensField
still wins.
2026-08-24 00:23:31 -05:00
4 changed files with 39 additions and 4 deletions
+36 -1
View File
@@ -528,6 +528,40 @@ const hasToolHistory = (messages: ReadonlyArray<LLMRequest["messages"][number]>)
return false
}
// Derive `max_tokens` vs `max_completion_tokens` from provider/baseURL when
// explicit `compatibility.maxTokensField` is not set. Aligned with
// models.dev provider naming: DeepSeek, Moonshot AI, Together AI, ZAI
// (Zhipu + Coding Plan variants), Nvidia, Cerebras, Chutes, etc. still
// require `max_tokens`.
const detectMaxTokensField = (provider: string, baseURL: string | undefined): "max_tokens" | "max_completion_tokens" => {
const p = provider.toLowerCase()
const url = (baseURL ?? "").toLowerCase()
if (
p === "deepseek" ||
url.includes("deepseek.com") ||
p === "moonshotai" ||
url.includes("api.moonshot.ai") ||
p === "togetherai" ||
url.includes("api.together.") ||
p === "zai" ||
p === "zai-coding-plan" ||
p === "zhipuai" ||
p === "zhipuai-coding-plan" ||
url.includes("api.z.ai") ||
url.includes("open.bigmodel.cn") ||
p === "nvidia" ||
url.includes("integrate.api.nvidia.com") ||
p === "cerebras" ||
url.includes("cerebras.ai") ||
url.includes("llm.chutes.ai") ||
p === "chutes" ||
p === "cloudflare-ai-gateway" ||
url.includes("gateway.ai.cloudflare.com")
)
return "max_tokens"
return "max_completion_tokens"
}
const lowerOptions = (request: LLMRequest) => {
const options = OpenAIOptions.resolve(request)
const cacheKey = ProviderShared.clampPromptCacheKey(request.promptCacheKey)
@@ -551,7 +585,8 @@ export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* (
)
const generation = request.generation
const toolSchemaCompatibility = request.model.compatibility?.toolSchema
const maxTokensField = request.model.compatibility?.maxTokensField ?? "max_tokens"
const detectedMaxTokensField = detectMaxTokensField(String(request.model.provider), request.model.route.endpoint.baseURL)
const maxTokensField = request.model.compatibility?.maxTokensField ?? detectedMaxTokensField
const hasHistory = hasToolHistory(request.messages)
return {
model: request.model.id,
+1 -1
View File
@@ -66,7 +66,7 @@ describe("request option precedence", () => {
expect(prepared.body).toMatchObject({
model: "gpt-4o-mini",
stream: true,
max_tokens: 30,
max_completion_tokens: 30,
temperature: 0.5,
top_p: 0.9,
frequency_penalty: 0.25,
@@ -55,7 +55,7 @@ describe("OpenAI Chat route", () => {
],
stream: true,
stream_options: { include_usage: true },
max_tokens: 20,
max_completion_tokens: 20,
temperature: 0,
})
}),
+1 -1
View File
@@ -101,7 +101,7 @@ describe("LLMClient tools", () => {
const messages = Reflect.get(second, "messages")
const tools = Reflect.get(second, "tools")
expect(Reflect.get(second, "max_tokens")).toBe(50)
expect(Reflect.get(second, "max_completion_tokens")).toBe(50)
expect(Reflect.get(second, "tool_choice")).toBe("auto")
expect(tools).toHaveLength(1)
expect(