Merge pull request #322 from alibaba/feat/optional-toolchoice

This commit is contained in:
Simon
2026-03-20 17:43:44 +08:00
committed by GitHub
6 changed files with 40 additions and 5 deletions

View File

@@ -21,6 +21,7 @@ export interface AdvancedConfig {
maxSteps?: number maxSteps?: number
systemInstruction?: string systemInstruction?: string
experimentalLlmsTxt?: boolean experimentalLlmsTxt?: boolean
disableNamedToolChoice?: boolean
} }
export interface ExtConfig extends LLMConfig, AdvancedConfig { export interface ExtConfig extends LLMConfig, AdvancedConfig {
@@ -124,6 +125,7 @@ export function useAgent(): UseAgentResult {
maxSteps, maxSteps,
systemInstruction, systemInstruction,
experimentalLlmsTxt, experimentalLlmsTxt,
disableNamedToolChoice,
...llmConfig ...llmConfig
}: ExtConfig) => { }: ExtConfig) => {
await chrome.storage.local.set({ llmConfig }) await chrome.storage.local.set({ llmConfig })
@@ -132,7 +134,12 @@ export function useAgent(): UseAgentResult {
} else { } else {
await chrome.storage.local.remove('language') await chrome.storage.local.remove('language')
} }
const advancedConfig: AdvancedConfig = { maxSteps, systemInstruction, experimentalLlmsTxt } const advancedConfig: AdvancedConfig = {
maxSteps,
systemInstruction,
experimentalLlmsTxt,
disableNamedToolChoice,
}
await chrome.storage.local.set({ advancedConfig }) await chrome.storage.local.set({ advancedConfig })
setConfig({ ...llmConfig, ...advancedConfig, language }) setConfig({ ...llmConfig, ...advancedConfig, language })
}, },

View File

@@ -36,6 +36,9 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
const [experimentalLlmsTxt, setExperimentalLlmsTxt] = useState( const [experimentalLlmsTxt, setExperimentalLlmsTxt] = useState(
config?.experimentalLlmsTxt ?? false config?.experimentalLlmsTxt ?? false
) )
const [disableNamedToolChoice, setDisableNamedToolChoice] = useState(
config?.disableNamedToolChoice ?? false
)
const [advancedOpen, setAdvancedOpen] = useState(false) const [advancedOpen, setAdvancedOpen] = useState(false)
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [userAuthToken, setUserAuthToken] = useState<string>('') const [userAuthToken, setUserAuthToken] = useState<string>('')
@@ -51,6 +54,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
setMaxSteps(config?.maxSteps) setMaxSteps(config?.maxSteps)
setSystemInstruction(config?.systemInstruction ?? '') setSystemInstruction(config?.systemInstruction ?? '')
setExperimentalLlmsTxt(config?.experimentalLlmsTxt ?? false) setExperimentalLlmsTxt(config?.experimentalLlmsTxt ?? false)
setDisableNamedToolChoice(config?.disableNamedToolChoice ?? false)
}, [config]) }, [config])
// Poll for user auth token every second until found // Poll for user auth token every second until found
@@ -96,6 +100,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
maxSteps: maxSteps || undefined, maxSteps: maxSteps || undefined,
systemInstruction: systemInstruction || undefined, systemInstruction: systemInstruction || undefined,
experimentalLlmsTxt, experimentalLlmsTxt,
disableNamedToolChoice,
}) })
} finally { } finally {
setSaving(false) setSaving(false)
@@ -271,6 +276,11 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
/> />
</div> </div>
<label className="flex items-center justify-between cursor-pointer">
<span className="text-xs text-muted-foreground">Disable named tool_choice</span>
<Switch checked={disableNamedToolChoice} onCheckedChange={setDisableNamedToolChoice} />
</label>
<label className="flex items-center justify-between cursor-pointer"> <label className="flex items-center justify-between cursor-pointer">
<span className="text-xs text-muted-foreground">Experimental llms.txt support</span> <span className="text-xs text-muted-foreground">Experimental llms.txt support</span>
<Switch checked={experimentalLlmsTxt} onCheckedChange={setExperimentalLlmsTxt} /> <Switch checked={experimentalLlmsTxt} onCheckedChange={setExperimentalLlmsTxt} />

View File

@@ -29,16 +29,19 @@ export class OpenAIClient implements LLMClient {
const openaiTools = Object.entries(tools).map(([name, t]) => zodToOpenAITool(name, t)) const openaiTools = Object.entries(tools).map(([name, t]) => zodToOpenAITool(name, t))
// Build request body // Build request body
let toolChoice: unknown = 'required'
if (options?.toolChoiceName && !this.config.disableNamedToolChoice) {
toolChoice = { type: 'function', function: { name: options.toolChoiceName } }
}
const requestBody: Record<string, unknown> = { const requestBody: Record<string, unknown> = {
model: this.config.model, model: this.config.model,
temperature: this.config.temperature, temperature: this.config.temperature,
messages, messages,
tools: openaiTools, tools: openaiTools,
parallel_tool_calls: false, parallel_tool_calls: false,
// Require tool call: specific tool if provided, otherwise any tool tool_choice: toolChoice,
tool_choice: options?.toolChoiceName
? { type: 'function', function: { name: options.toolChoiceName } }
: 'required',
} }
modelPatch(requestBody) modelPatch(requestBody)

View File

@@ -21,6 +21,7 @@ export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
apiKey: config.apiKey || '', apiKey: config.apiKey || '',
temperature: config.temperature ?? DEFAULT_TEMPERATURE, temperature: config.temperature ?? DEFAULT_TEMPERATURE,
maxRetries: config.maxRetries ?? LLM_MAX_RETRIES, maxRetries: config.maxRetries ?? LLM_MAX_RETRIES,
disableNamedToolChoice: config.disableNamedToolChoice ?? false,
customFetch: (config.customFetch ?? fetch).bind(globalThis), // fetch will be illegal unless bound customFetch: (config.customFetch ?? fetch).bind(globalThis), // fetch will be illegal unless bound
} }
} }

View File

@@ -95,6 +95,12 @@ export interface LLMConfig {
temperature?: number temperature?: number
maxRetries?: number maxRetries?: number
/**
* remove the tool_choice field from the request.
* @note fix "Invalid tool_choice type: 'object'" for some LLMs.
*/
disableNamedToolChoice?: boolean
/** /**
* Custom fetch function for LLM API requests. * Custom fetch function for LLM API requests.
* Use this to customize headers, credentials, proxy, etc. * Use this to customize headers, credentials, proxy, etc.

View File

@@ -156,6 +156,14 @@ const result = await agent.execute('Fill in the form with test data')`}
defaultValue: '3', defaultValue: '3',
description: isZh ? 'API 调用失败时的最大重试次数' : 'Maximum retries on API failure', description: isZh ? 'API 调用失败时的最大重试次数' : 'Maximum retries on API failure',
}, },
{
name: 'disableNamedToolChoice',
type: 'boolean',
defaultValue: 'false',
description: isZh
? '禁用命名 tool_choice始终使用 "required" 字符串。适用于不支持 tool_choice 对象格式的 LLM 服务。'
: 'Disable named tool_choice, always use "required" string. For LLM services that don\'t support the object format of tool_choice.',
},
{ {
name: 'customFetch', name: 'customFetch',
type: 'typeof fetch', type: 'typeof fetch',