feat(llm): improve model-specific patches

This commit is contained in:
Simon
2025-10-20 22:36:36 +08:00
parent 5162056625
commit e8bd1ee31b
3 changed files with 54 additions and 45 deletions

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -192,3 +192,23 @@ export function lenientParseMacroToolCall(
)
}
}
export function modelPatch(body: Record<string, any>) {
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
}