From e8bd1ee31b92589e70a8ab2f4d56a42733154282 Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:36:36 +0800 Subject: [PATCH] feat(llm): improve model-specific patches --- src/llms/OpenAIClient.ts | 41 +++++++++++++++------------------ src/llms/OpenAILenientClient.ts | 38 +++++++++++++----------------- src/llms/utils.ts | 20 ++++++++++++++++ 3 files changed, 54 insertions(+), 45 deletions(-) diff --git a/src/llms/OpenAIClient.ts b/src/llms/OpenAIClient.ts index bf3f72e..c631011 100644 --- a/src/llms/OpenAIClient.ts +++ b/src/llms/OpenAIClient.ts @@ -3,13 +3,7 @@ */ import { InvokeError, InvokeErrorType } from './errors' import type { InvokeResult, LLMClient, Message, OpenAIClientConfig, Tool } from './types' -import { zodToOpenAITool } from './utils' - -// Claude's openAI-API has different format for some fields -const CLAUDE_PATCH = { - tool_choice: { type: 'tool', name: 'AgentOutput' }, - thinking: { type: 'disabled' }, -} +import { modelPatch, zodToOpenAITool } from './utils' export class OpenAIClient implements LLMClient { config: OpenAIClientConfig @@ -26,9 +20,10 @@ export class OpenAIClient implements LLMClient { // 1. Convert tools to OpenAI format const openaiTools = Object.entries(tools).map(([name, tool]) => zodToOpenAITool(name, tool)) - // 2. Detect if Claude (auto-compatibility) + // 2. Detect patch (auto-compatibility) // TODO: Gemini also uses slightly different format than OpenAI const isClaude = this.config.model.toLowerCase().startsWith('claude') + const isGrok = this.config.model.toLowerCase().startsWith('grok') // 3. Call API let response: Response @@ -39,24 +34,24 @@ export class OpenAIClient implements LLMClient { 'Content-Type': 'application/json', Authorization: `Bearer ${this.config.apiKey}`, }, - body: JSON.stringify({ - model: this.config.model, - temperature: this.config.temperature, - max_tokens: this.config.maxTokens, - messages, + body: JSON.stringify( + modelPatch({ + model: this.config.model, + temperature: this.config.temperature, + max_tokens: this.config.maxTokens, + messages, - tools: openaiTools, - // tool_choice: 'required', - tool_choice: { type: 'function', function: { name: 'AgentOutput' } }, + tools: openaiTools, + // tool_choice: 'required', + tool_choice: { type: 'function', function: { name: 'AgentOutput' } }, - // model specific params + // model specific params - // reasoning_effort: 'minimal', - // verbosity: 'low', - parallel_tool_calls: false, - - ...(isClaude ? CLAUDE_PATCH : {}), - }), + // reasoning_effort: 'minimal', + // verbosity: 'low', + parallel_tool_calls: false, + }) + ), signal: abortSignal, }) } catch (error: unknown) { diff --git a/src/llms/OpenAILenientClient.ts b/src/llms/OpenAILenientClient.ts index 655c44c..b92b11f 100644 --- a/src/llms/OpenAILenientClient.ts +++ b/src/llms/OpenAILenientClient.ts @@ -5,13 +5,7 @@ import type { MacroToolInput } from '@/PageAgent' import { InvokeError, InvokeErrorType } from './errors' import type { InvokeResult, LLMClient, Message, OpenAIClientConfig, Tool } from './types' -import { lenientParseMacroToolCall, zodToOpenAITool } from './utils' - -// Claude's openAI-API has different format for some fields -const CLAUDE_PATCH = { - tool_choice: { type: 'tool', name: 'AgentOutput' }, - thinking: { type: 'disabled' }, -} +import { lenientParseMacroToolCall, modelPatch, zodToOpenAITool } from './utils' export class OpenAIClient implements LLMClient { config: OpenAIClientConfig @@ -41,24 +35,24 @@ export class OpenAIClient implements LLMClient { 'Content-Type': 'application/json', Authorization: `Bearer ${this.config.apiKey}`, }, - body: JSON.stringify({ - model: this.config.model, - temperature: this.config.temperature, - max_tokens: this.config.maxTokens, - messages, + body: JSON.stringify( + modelPatch({ + model: this.config.model, + temperature: this.config.temperature, + max_tokens: this.config.maxTokens, + messages, - tools: openaiTools, - // tool_choice: 'required', - tool_choice: { type: 'function', function: { name: 'AgentOutput' } }, + tools: openaiTools, + // tool_choice: 'required', + tool_choice: { type: 'function', function: { name: 'AgentOutput' } }, - // model specific params + // model specific params - // reasoning_effort: 'minimal', - // verbosity: 'low', - parallel_tool_calls: false, - - ...(isClaude ? CLAUDE_PATCH : {}), - }), + // reasoning_effort: 'minimal', + // verbosity: 'low', + parallel_tool_calls: false, + }) + ), signal: abortSignal, }) } catch (error: unknown) { diff --git a/src/llms/utils.ts b/src/llms/utils.ts index ba3e805..3a264b0 100644 --- a/src/llms/utils.ts +++ b/src/llms/utils.ts @@ -192,3 +192,23 @@ export function lenientParseMacroToolCall( ) } } + +export function modelPatch(body: Record) { + const model: string = body.model || '' + + if (model.toLowerCase().startsWith('claude')) { + body.tool_choice = { type: 'tool', name: 'AgentOutput' } + body.thinking = { type: 'disabled' } + // body.reasoning = { enabled: 'disabled' } + } + + if (model.toLowerCase().includes('grok')) { + console.log('Applying Grok patch: removing tool_choice') + delete body.tool_choice + console.log('Applying Grok patch: disable reasoning and thinking') + body.thinking = { type: 'disabled', effort: 'minimal' } + body.reasoning = { enabled: false, effort: 'low' } + } + + return body +}