mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 07:04:45 +00:00
fix(ai): map groq qwen3 reasoning effort values closes #1745
This commit is contained in:
parent
42579dd923
commit
7b96041068
5 changed files with 91 additions and 3 deletions
|
|
@ -428,7 +428,7 @@ function buildParams(model: Model<"openai-completions">, context: Context, optio
|
||||||
(params as any).enable_thinking = !!options?.reasoningEffort;
|
(params as any).enable_thinking = !!options?.reasoningEffort;
|
||||||
} else if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) {
|
} else if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) {
|
||||||
// OpenAI-style reasoning_effort
|
// OpenAI-style reasoning_effort
|
||||||
params.reasoning_effort = options.reasoningEffort;
|
(params as any).reasoning_effort = mapReasoningEffort(options.reasoningEffort, compat.reasoningEffortMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenRouter provider routing preferences
|
// OpenRouter provider routing preferences
|
||||||
|
|
@ -450,6 +450,13 @@ function buildParams(model: Model<"openai-completions">, context: Context, optio
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapReasoningEffort(
|
||||||
|
effort: NonNullable<OpenAICompletionsOptions["reasoningEffort"]>,
|
||||||
|
reasoningEffortMap: Partial<Record<NonNullable<OpenAICompletionsOptions["reasoningEffort"]>, string>>,
|
||||||
|
): string {
|
||||||
|
return reasoningEffortMap[effort] ?? effort;
|
||||||
|
}
|
||||||
|
|
||||||
function maybeAddOpenRouterAnthropicCacheControl(
|
function maybeAddOpenRouterAnthropicCacheControl(
|
||||||
model: Model<"openai-completions">,
|
model: Model<"openai-completions">,
|
||||||
messages: ChatCompletionMessageParam[],
|
messages: ChatCompletionMessageParam[],
|
||||||
|
|
@ -777,13 +784,26 @@ function detectCompat(model: Model<"openai-completions">): Required<OpenAIComple
|
||||||
const useMaxTokens = provider === "mistral" || baseUrl.includes("mistral.ai") || baseUrl.includes("chutes.ai");
|
const useMaxTokens = provider === "mistral" || baseUrl.includes("mistral.ai") || baseUrl.includes("chutes.ai");
|
||||||
|
|
||||||
const isGrok = provider === "xai" || baseUrl.includes("api.x.ai");
|
const isGrok = provider === "xai" || baseUrl.includes("api.x.ai");
|
||||||
|
const isGroq = provider === "groq" || baseUrl.includes("groq.com");
|
||||||
|
|
||||||
const isMistral = provider === "mistral" || baseUrl.includes("mistral.ai");
|
const isMistral = provider === "mistral" || baseUrl.includes("mistral.ai");
|
||||||
|
|
||||||
|
const reasoningEffortMap =
|
||||||
|
isGroq && model.id === "qwen/qwen3-32b"
|
||||||
|
? {
|
||||||
|
minimal: "default",
|
||||||
|
low: "default",
|
||||||
|
medium: "default",
|
||||||
|
high: "default",
|
||||||
|
xhigh: "default",
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
supportsStore: !isNonStandard,
|
supportsStore: !isNonStandard,
|
||||||
supportsDeveloperRole: !isNonStandard,
|
supportsDeveloperRole: !isNonStandard,
|
||||||
supportsReasoningEffort: !isGrok && !isZai,
|
supportsReasoningEffort: !isGrok && !isZai,
|
||||||
|
reasoningEffortMap,
|
||||||
supportsUsageInStreaming: true,
|
supportsUsageInStreaming: true,
|
||||||
maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",
|
maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",
|
||||||
requiresToolResultName: isMistral,
|
requiresToolResultName: isMistral,
|
||||||
|
|
@ -809,6 +829,7 @@ function getCompat(model: Model<"openai-completions">): Required<OpenAICompletio
|
||||||
supportsStore: model.compat.supportsStore ?? detected.supportsStore,
|
supportsStore: model.compat.supportsStore ?? detected.supportsStore,
|
||||||
supportsDeveloperRole: model.compat.supportsDeveloperRole ?? detected.supportsDeveloperRole,
|
supportsDeveloperRole: model.compat.supportsDeveloperRole ?? detected.supportsDeveloperRole,
|
||||||
supportsReasoningEffort: model.compat.supportsReasoningEffort ?? detected.supportsReasoningEffort,
|
supportsReasoningEffort: model.compat.supportsReasoningEffort ?? detected.supportsReasoningEffort,
|
||||||
|
reasoningEffortMap: model.compat.reasoningEffortMap ?? detected.reasoningEffortMap,
|
||||||
supportsUsageInStreaming: model.compat.supportsUsageInStreaming ?? detected.supportsUsageInStreaming,
|
supportsUsageInStreaming: model.compat.supportsUsageInStreaming ?? detected.supportsUsageInStreaming,
|
||||||
maxTokensField: model.compat.maxTokensField ?? detected.maxTokensField,
|
maxTokensField: model.compat.maxTokensField ?? detected.maxTokensField,
|
||||||
requiresToolResultName: model.compat.requiresToolResultName ?? detected.requiresToolResultName,
|
requiresToolResultName: model.compat.requiresToolResultName ?? detected.requiresToolResultName,
|
||||||
|
|
|
||||||
|
|
@ -235,6 +235,8 @@ export interface OpenAICompletionsCompat {
|
||||||
supportsDeveloperRole?: boolean;
|
supportsDeveloperRole?: boolean;
|
||||||
/** Whether the provider supports `reasoning_effort`. Default: auto-detected from URL. */
|
/** Whether the provider supports `reasoning_effort`. Default: auto-detected from URL. */
|
||||||
supportsReasoningEffort?: boolean;
|
supportsReasoningEffort?: boolean;
|
||||||
|
/** Optional mapping from pi-ai reasoning levels to provider/model-specific `reasoning_effort` values. */
|
||||||
|
reasoningEffortMap?: Partial<Record<ThinkingLevel, string>>;
|
||||||
/** Whether the provider supports `stream_options: { include_usage: true }` for token usage in streaming responses. Default: true. */
|
/** Whether the provider supports `stream_options: { include_usage: true }` for token usage in streaming responses. Default: true. */
|
||||||
supportsUsageInStreaming?: boolean;
|
supportsUsageInStreaming?: boolean;
|
||||||
/** Which field to use for max tokens. Default: auto-detected from URL. */
|
/** Which field to use for max tokens. Default: auto-detected from URL. */
|
||||||
|
|
|
||||||
|
|
@ -119,4 +119,60 @@ describe("openai-completions tool_choice", () => {
|
||||||
expect(tool?.strict).toBeUndefined();
|
expect(tool?.strict).toBeUndefined();
|
||||||
expect("strict" in (tool ?? {})).toBe(false);
|
expect("strict" in (tool ?? {})).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("maps groq qwen3 reasoning levels to default reasoning_effort", async () => {
|
||||||
|
const model = getModel("groq", "qwen/qwen3-32b")!;
|
||||||
|
let payload: unknown;
|
||||||
|
|
||||||
|
await streamSimple(
|
||||||
|
model,
|
||||||
|
{
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: "Hi",
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiKey: "test",
|
||||||
|
reasoning: "medium",
|
||||||
|
onPayload: (params: unknown) => {
|
||||||
|
payload = params;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).result();
|
||||||
|
|
||||||
|
const params = (payload ?? mockState.lastParams) as { reasoning_effort?: string };
|
||||||
|
expect(params.reasoning_effort).toBe("default");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps normal reasoning_effort for groq models without compat mapping", async () => {
|
||||||
|
const model = getModel("groq", "openai/gpt-oss-20b")!;
|
||||||
|
let payload: unknown;
|
||||||
|
|
||||||
|
await streamSimple(
|
||||||
|
model,
|
||||||
|
{
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: "Hi",
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiKey: "test",
|
||||||
|
reasoning: "medium",
|
||||||
|
onPayload: (params: unknown) => {
|
||||||
|
payload = params;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).result();
|
||||||
|
|
||||||
|
const params = (payload ?? mockState.lastParams) as { reasoning_effort?: string };
|
||||||
|
expect(params.reasoning_effort).toBe("medium");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ const compat: Required<OpenAICompletionsCompat> = {
|
||||||
supportsStore: true,
|
supportsStore: true,
|
||||||
supportsDeveloperRole: true,
|
supportsDeveloperRole: true,
|
||||||
supportsReasoningEffort: true,
|
supportsReasoningEffort: true,
|
||||||
|
reasoningEffortMap: {},
|
||||||
supportsUsageInStreaming: true,
|
supportsUsageInStreaming: true,
|
||||||
maxTokensField: "max_completion_tokens",
|
maxTokensField: "max_completion_tokens",
|
||||||
requiresToolResultName: false,
|
requiresToolResultName: false,
|
||||||
|
|
|
||||||
|
|
@ -172,10 +172,17 @@ models: [{
|
||||||
// ...
|
// ...
|
||||||
compat: {
|
compat: {
|
||||||
supportsDeveloperRole: false, // use "system" instead of "developer"
|
supportsDeveloperRole: false, // use "system" instead of "developer"
|
||||||
supportsReasoningEffort: false, // disable reasoning_effort param
|
supportsReasoningEffort: true,
|
||||||
|
reasoningEffortMap: { // map pi-ai levels to provider values
|
||||||
|
minimal: "default",
|
||||||
|
low: "default",
|
||||||
|
medium: "default",
|
||||||
|
high: "default",
|
||||||
|
xhigh: "default"
|
||||||
|
},
|
||||||
maxTokensField: "max_tokens", // instead of "max_completion_tokens"
|
maxTokensField: "max_tokens", // instead of "max_completion_tokens"
|
||||||
requiresToolResultName: true, // tool results need name field
|
requiresToolResultName: true, // tool results need name field
|
||||||
requiresMistralToolIds: true // tool IDs must be 9 alphanumeric chars
|
requiresMistralToolIds: true,
|
||||||
thinkingFormat: "qwen" // uses enable_thinking: true
|
thinkingFormat: "qwen" // uses enable_thinking: true
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
@ -568,6 +575,7 @@ interface ProviderModelConfig {
|
||||||
supportsStore?: boolean;
|
supportsStore?: boolean;
|
||||||
supportsDeveloperRole?: boolean;
|
supportsDeveloperRole?: boolean;
|
||||||
supportsReasoningEffort?: boolean;
|
supportsReasoningEffort?: boolean;
|
||||||
|
reasoningEffortMap?: Partial<Record<"minimal" | "low" | "medium" | "high" | "xhigh", string>>;
|
||||||
supportsUsageInStreaming?: boolean;
|
supportsUsageInStreaming?: boolean;
|
||||||
maxTokensField?: "max_completion_tokens" | "max_tokens";
|
maxTokensField?: "max_completion_tokens" | "max_tokens";
|
||||||
requiresToolResultName?: boolean;
|
requiresToolResultName?: boolean;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue