feat: rewrite llm config and model patch
This commit is contained in:
@@ -4,17 +4,24 @@
|
||||
import * as z from 'zod/v4'
|
||||
|
||||
import { InvokeError, InvokeErrorTypes } from './errors'
|
||||
import type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types'
|
||||
import type {
|
||||
InvokeOptions,
|
||||
InvokeResult,
|
||||
LLMClient,
|
||||
Message,
|
||||
ResolvedLLMConfig,
|
||||
Tool,
|
||||
} from './types'
|
||||
import { modelPatch, zodToOpenAITool } from './utils'
|
||||
|
||||
/**
|
||||
* Client for OpenAI compatible APIs
|
||||
*/
|
||||
export class OpenAIClient implements LLMClient {
|
||||
config: Required<LLMConfig>
|
||||
config: ResolvedLLMConfig
|
||||
private fetch: typeof globalThis.fetch
|
||||
|
||||
constructor(config: Required<LLMConfig>) {
|
||||
constructor(config: ResolvedLLMConfig) {
|
||||
this.config = config
|
||||
this.fetch = config.customFetch
|
||||
}
|
||||
@@ -39,12 +46,15 @@ export class OpenAIClient implements LLMClient {
|
||||
|
||||
const requestBody: Record<string, unknown> = {
|
||||
model: this.config.model,
|
||||
temperature: this.config.temperature,
|
||||
messages,
|
||||
tools: openaiTools,
|
||||
parallel_tool_calls: false,
|
||||
tool_choice: toolChoice,
|
||||
}
|
||||
// Only sent if the caller explicitly set it. Most new models throw if this is set.
|
||||
if (this.config.temperature !== undefined) {
|
||||
requestBody.temperature = this.config.temperature
|
||||
}
|
||||
|
||||
modelPatch(requestBody)
|
||||
let transformedBody: Record<string, unknown> | undefined
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// Internal constants
|
||||
export const LLM_MAX_RETRIES = 2
|
||||
export const DEFAULT_TEMPERATURE = 0.7 // higher randomness helps auto-recovery
|
||||
+41
-24
@@ -1,34 +1,23 @@
|
||||
import { OpenAIClient } from './OpenAIClient'
|
||||
import { DEFAULT_TEMPERATURE, LLM_MAX_RETRIES } from './constants'
|
||||
import { InvokeError, InvokeErrorTypes } from './errors'
|
||||
import type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types'
|
||||
import type {
|
||||
InvokeOptions,
|
||||
InvokeResult,
|
||||
LLMClient,
|
||||
LLMConfig,
|
||||
Message,
|
||||
ResolvedLLMConfig,
|
||||
Tool,
|
||||
} from './types'
|
||||
|
||||
export { InvokeError, InvokeErrorTypes }
|
||||
export type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool }
|
||||
|
||||
export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
|
||||
// Runtime validation as defensive programming (types already guarantee these)
|
||||
if (!config.baseURL || !config.model) {
|
||||
throw new Error(
|
||||
'[PageAgent] LLM configuration required. Please provide: baseURL, model. ' +
|
||||
'See: https://alibaba.github.io/page-agent/docs/features/models'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
baseURL: config.baseURL,
|
||||
model: config.model,
|
||||
apiKey: config.apiKey || '',
|
||||
temperature: config.temperature ?? DEFAULT_TEMPERATURE,
|
||||
maxRetries: config.maxRetries ?? LLM_MAX_RETRIES,
|
||||
transformRequestBody: config.transformRequestBody ?? ((requestBody) => requestBody),
|
||||
disableNamedToolChoice: config.disableNamedToolChoice ?? false,
|
||||
customFetch: (config.customFetch ?? fetch).bind(globalThis), // fetch will be illegal unless bound
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LLM module
|
||||
*/
|
||||
export class LLM extends EventTarget {
|
||||
config: Required<LLMConfig>
|
||||
config: ResolvedLLMConfig
|
||||
client: LLMClient
|
||||
|
||||
constructor(config: LLMConfig) {
|
||||
@@ -90,3 +79,31 @@ async function withRetry<T>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function parseLLMConfig(config: LLMConfig): ResolvedLLMConfig {
|
||||
// Runtime validation as defensive programming (types already guarantee these)
|
||||
if (!config.baseURL || !config.model) {
|
||||
throw new Error(
|
||||
'[PageAgent] LLM configuration required. Please provide: baseURL, model. ' +
|
||||
'See: https://alibaba.github.io/page-agent/docs/features/models'
|
||||
)
|
||||
}
|
||||
|
||||
if (config.temperature !== undefined) {
|
||||
console.warn(
|
||||
'[PageAgent] LLMConfig.temperature is deprecated and will be removed in a future version. ' +
|
||||
'Use transformRequestBody to set it only for models you have verified accept it.'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
baseURL: config.baseURL,
|
||||
model: config.model,
|
||||
apiKey: config.apiKey || '',
|
||||
temperature: config.temperature,
|
||||
maxRetries: config.maxRetries ?? 2,
|
||||
transformRequestBody: config.transformRequestBody ?? ((requestBody) => requestBody),
|
||||
disableNamedToolChoice: config.disableNamedToolChoice ?? false,
|
||||
customFetch: (config.customFetch ?? fetch).bind(globalThis), // fetch will be illegal unless bound
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,12 @@ export interface LLMConfig {
|
||||
model: string
|
||||
apiKey?: string
|
||||
|
||||
/**
|
||||
* @deprecated No longer a standard parameter; many models reject it outright.
|
||||
* Use `transformRequestBody` to set it only for models you've verified.
|
||||
*/
|
||||
temperature?: number
|
||||
|
||||
maxRetries?: number
|
||||
|
||||
/**
|
||||
@@ -118,3 +123,5 @@ export interface LLMConfig {
|
||||
*/
|
||||
customFetch?: typeof globalThis.fetch
|
||||
}
|
||||
|
||||
export type ResolvedLLMConfig = Required<Omit<LLMConfig, 'temperature'>> & { temperature?: number }
|
||||
|
||||
+10
-157
@@ -1,162 +1,15 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { modelPatch } from './utils'
|
||||
import { modelPatch, normalizeModelName } from './utils'
|
||||
|
||||
/**
|
||||
* Baseline request body used as starting point for each provider test.
|
||||
* Mirrors what OpenAIClient builds before calling modelPatch.
|
||||
*/
|
||||
function baseBody(model: string) {
|
||||
return {
|
||||
model,
|
||||
temperature: 0.7,
|
||||
messages: [],
|
||||
tools: [],
|
||||
parallel_tool_calls: false,
|
||||
tool_choice: 'required' as unknown,
|
||||
}
|
||||
}
|
||||
|
||||
describe('modelPatch', () => {
|
||||
it('returns body unchanged when model is missing', () => {
|
||||
const body = { temperature: 0.7 }
|
||||
expect(modelPatch(body)).toBe(body)
|
||||
expect(body).toEqual({ temperature: 0.7 })
|
||||
})
|
||||
|
||||
it('qwen: bumps temperature and disables thinking', () => {
|
||||
const body = baseBody('qwen-max')
|
||||
modelPatch(body)
|
||||
expect(body.temperature).toBe(1.0)
|
||||
expect(body).toMatchObject({ enable_thinking: false })
|
||||
})
|
||||
|
||||
it('qwen: keeps higher caller-provided temperature', () => {
|
||||
const body = baseBody('qwen-max')
|
||||
body.temperature = 1.5
|
||||
modelPatch(body)
|
||||
expect(body.temperature).toBe(1.5)
|
||||
})
|
||||
|
||||
it('claude: disables thinking and converts tool_choice "required" -> { type: "any" }', () => {
|
||||
const body = baseBody('claude-3-5-sonnet')
|
||||
modelPatch(body)
|
||||
expect(body).toMatchObject({
|
||||
thinking: { type: 'disabled' },
|
||||
tool_choice: { type: 'any' },
|
||||
})
|
||||
})
|
||||
|
||||
it('claude: converts named tool_choice to { type: "tool", name }', () => {
|
||||
const body = baseBody('claude-3-5-sonnet')
|
||||
body.tool_choice = { type: 'function', function: { name: 'doStuff' } }
|
||||
modelPatch(body)
|
||||
expect(body.tool_choice).toEqual({ type: 'tool', name: 'doStuff' })
|
||||
})
|
||||
|
||||
it('claude-opus-4-7: drops temperature', () => {
|
||||
const body = baseBody('claude-opus-4-7')
|
||||
modelPatch(body)
|
||||
expect(body).not.toHaveProperty('temperature')
|
||||
})
|
||||
|
||||
it('claude-opus-47 (alt id form): drops temperature', () => {
|
||||
// Provider sometimes ships ids with the dot stripped; modelPatch normalizes.
|
||||
const body = baseBody('claude-opus-47-20251029')
|
||||
modelPatch(body)
|
||||
expect(body).not.toHaveProperty('temperature')
|
||||
})
|
||||
|
||||
it('claude-opus-4-8: drops temperature', () => {
|
||||
const body = baseBody('claude-opus-4-8')
|
||||
modelPatch(body)
|
||||
expect(body).not.toHaveProperty('temperature')
|
||||
})
|
||||
|
||||
it('claude-opus-48 (alt id form): drops temperature', () => {
|
||||
const body = baseBody('claude-opus-48-20251210')
|
||||
modelPatch(body)
|
||||
expect(body).not.toHaveProperty('temperature')
|
||||
})
|
||||
|
||||
it('grok: removes tool_choice and disables reasoning/thinking', () => {
|
||||
const body = baseBody('grok-4')
|
||||
modelPatch(body)
|
||||
expect(body).not.toHaveProperty('tool_choice')
|
||||
expect(body).toMatchObject({
|
||||
thinking: { type: 'disabled', effort: 'minimal' },
|
||||
reasoning: { enabled: false, effort: 'low' },
|
||||
})
|
||||
})
|
||||
|
||||
it('gpt-5: sets verbosity=low and reasoning_effort=low', () => {
|
||||
const body = baseBody('gpt-5')
|
||||
modelPatch(body)
|
||||
expect(body).toMatchObject({ verbosity: 'low', reasoning_effort: 'low' })
|
||||
})
|
||||
|
||||
it('gpt-5-mini: low effort, temperature=1', () => {
|
||||
const body = baseBody('gpt-5-mini')
|
||||
modelPatch(body)
|
||||
expect(body).toMatchObject({
|
||||
verbosity: 'low',
|
||||
reasoning_effort: 'low',
|
||||
temperature: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it('gpt-5.1 (gpt-51): disables reasoning', () => {
|
||||
const body = baseBody('gpt-5.1')
|
||||
modelPatch(body)
|
||||
expect(body).toMatchObject({ verbosity: 'low', reasoning_effort: 'none' })
|
||||
})
|
||||
|
||||
it('gpt-5.4 (gpt-54): drops reasoning_effort', () => {
|
||||
const body = baseBody('gpt-5.4')
|
||||
modelPatch(body)
|
||||
expect(body).toMatchObject({ verbosity: 'low' })
|
||||
expect(body).not.toHaveProperty('reasoning_effort')
|
||||
})
|
||||
|
||||
it('gpt-5.5 (gpt-55): drops reasoning_effort and temperature', () => {
|
||||
const body = baseBody('gpt-5.5')
|
||||
modelPatch(body)
|
||||
expect(body).toMatchObject({ verbosity: 'low' })
|
||||
expect(body).not.toHaveProperty('reasoning_effort')
|
||||
expect(body).not.toHaveProperty('temperature')
|
||||
})
|
||||
|
||||
it('gemini: sets reasoning_effort=minimal', () => {
|
||||
const body = baseBody('gemini-2.5-pro')
|
||||
modelPatch(body)
|
||||
expect(body).toMatchObject({ reasoning_effort: 'minimal' })
|
||||
})
|
||||
|
||||
it('deepseek: removes tool_choice', () => {
|
||||
const body = baseBody('deepseek-chat')
|
||||
modelPatch(body)
|
||||
expect(body).not.toHaveProperty('tool_choice')
|
||||
})
|
||||
|
||||
it('minimax: clamps temperature into (0, 1] and removes parallel_tool_calls', () => {
|
||||
const body = baseBody('minimax-m2')
|
||||
body.temperature = 0
|
||||
modelPatch(body)
|
||||
expect(body.temperature).toBeGreaterThan(0)
|
||||
expect(body.temperature).toBeLessThanOrEqual(1)
|
||||
expect(body).not.toHaveProperty('parallel_tool_calls')
|
||||
})
|
||||
|
||||
it('minimax: caps temperature at 1', () => {
|
||||
const body = baseBody('minimax-m2')
|
||||
body.temperature = 2
|
||||
modelPatch(body)
|
||||
expect(body.temperature).toBe(1)
|
||||
})
|
||||
|
||||
it('normalizes provider-prefixed model id (openai/gpt-5)', () => {
|
||||
const body = baseBody('openai/gpt-5')
|
||||
modelPatch(body)
|
||||
expect(body).toMatchObject({ verbosity: 'low', reasoning_effort: 'low' })
|
||||
describe('normalizeModelName', () => {
|
||||
it.each([
|
||||
['gpt-5.2', 'gpt-52'],
|
||||
['gpt_5_2', 'gpt52'],
|
||||
['GPT-52-2026-01-01', 'gpt-52-2026-01-01'],
|
||||
['openai/gpt-5.2-chat', 'gpt-52-chat'],
|
||||
['claude_sonnet4_5', 'claudesonnet45'],
|
||||
])('%s -> %s', (input, expected) => {
|
||||
expect(normalizeModelName(input)).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
+69
-69
@@ -24,8 +24,17 @@ export function zodToOpenAITool(name: string, tool: Tool) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch model specific parameters
|
||||
* @note in-place modification
|
||||
* Patch model specific parameters. Only patches known models.
|
||||
*
|
||||
* @purpose
|
||||
* - Reconcile the differences in the parameter schema each model accepts.
|
||||
* - Disable thinking/reasoning, or lower it to the minimum where a full disable is impossible.
|
||||
* - Minimize returned tokens.
|
||||
* - Raise temperature for known smaller models to improve auto-recovery odds.
|
||||
* @note Honor temperature if explicitly set by the user
|
||||
*
|
||||
* @todo Need vendor-specific patches.
|
||||
* Local and 3rd-party hosted models may have different schema.
|
||||
*/
|
||||
export function modelPatch(body: Record<string, any>) {
|
||||
const model: string = body.model || ''
|
||||
@@ -34,13 +43,30 @@ export function modelPatch(body: Record<string, any>) {
|
||||
const modelName = normalizeModelName(model)
|
||||
|
||||
if (modelName.startsWith('qwen')) {
|
||||
debug('Applying Qwen patch: use higher temperature for auto fixing')
|
||||
body.temperature = Math.max(body.temperature || 0, 1.0)
|
||||
debug('Patch Qwen: disable thinking')
|
||||
body.enable_thinking = false
|
||||
if (body.temperature === undefined && !/max|plus/.test(modelName)) {
|
||||
debug('Patch Qwen: raise temperature to 1.0')
|
||||
body.temperature = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
if (modelName.startsWith('claude')) {
|
||||
debug('Applying Claude patch: disable thinking')
|
||||
if (modelName.startsWith('deepseek')) {
|
||||
debug('Patch DeepSeek: disable thinking, remove tool_choice')
|
||||
body.thinking = { type: 'disabled' }
|
||||
delete body.tool_choice
|
||||
}
|
||||
|
||||
if (modelName.startsWith('gpt-5')) {
|
||||
// verbosity trims output tokens across the whole GPT-5 family.
|
||||
body.verbosity = 'low'
|
||||
// The GPT-5.0 generation only supports "minimal"; 5.1+ supports "none".
|
||||
body.reasoning_effort = /^gpt-5(-|$)/.test(modelName) ? 'minimal' : 'none'
|
||||
debug(`Patch GPT-5: verbosity=low, reasoning_effort=${body.reasoning_effort}`)
|
||||
}
|
||||
|
||||
if (modelName.startsWith('claude') && /opus|sonnet|haiku/.test(modelName)) {
|
||||
debug('Patch Claude: disable thinking')
|
||||
body.thinking = { type: 'disabled' }
|
||||
|
||||
// Convert tool_choice to Claude format
|
||||
@@ -53,76 +79,50 @@ export function modelPatch(body: Record<string, any>) {
|
||||
debug('Applying Claude patch: convert tool_choice format')
|
||||
body.tool_choice = { type: 'tool', name: body.tool_choice.function.name }
|
||||
}
|
||||
|
||||
// TODO: Claude naming pattern has changed
|
||||
// needs proper handling
|
||||
if (
|
||||
modelName.startsWith('claude-opus-4-7') ||
|
||||
modelName.startsWith('claude-opus-47') ||
|
||||
modelName.startsWith('claude-opus-4-8') ||
|
||||
modelName.startsWith('claude-opus-48')
|
||||
) {
|
||||
debug('Applying Claude-4.7/4.8 patch: remove temperature')
|
||||
delete body.temperature
|
||||
}
|
||||
}
|
||||
|
||||
if (modelName.startsWith('grok')) {
|
||||
debug('Applying Grok patch: removing tool_choice')
|
||||
delete body.tool_choice
|
||||
debug('Applying Grok patch: disable reasoning and thinking')
|
||||
body.thinking = { type: 'disabled', effort: 'minimal' }
|
||||
body.reasoning = { enabled: false, effort: 'low' }
|
||||
}
|
||||
|
||||
if (modelName.startsWith('gpt')) {
|
||||
debug('Applying GPT patch: set verbosity to low')
|
||||
body.verbosity = 'low'
|
||||
|
||||
// *-chat-latest models don't support reasoning_effort — skip patches that set it
|
||||
if (modelName.includes('chat-latest')) {
|
||||
debug('Omitting reasoning_effort and temperature for chat-latest')
|
||||
delete body.reasoning_effort
|
||||
delete body.temperature
|
||||
} else if (modelName.startsWith('gpt-52')) {
|
||||
debug('Applying GPT-52 patch: disable reasoning')
|
||||
body.reasoning_effort = 'none'
|
||||
} else if (modelName.startsWith('gpt-51')) {
|
||||
debug('Applying GPT-51 patch: disable reasoning')
|
||||
body.reasoning_effort = 'none'
|
||||
} else if (modelName.startsWith('gpt-54')) {
|
||||
debug('Applying GPT-5.4 patch: remove reasoning_effort')
|
||||
delete body.reasoning_effort
|
||||
} else if (modelName.startsWith('gpt-55')) {
|
||||
debug('Applying GPT-5.4 patch: remove reasoning_effort and temperature')
|
||||
delete body.reasoning_effort
|
||||
delete body.temperature
|
||||
} else if (modelName.startsWith('gpt-5-mini')) {
|
||||
debug('Applying GPT-5-mini patch: set reasoning effort to low, temperature to 1')
|
||||
body.reasoning_effort = 'low'
|
||||
body.temperature = 1
|
||||
} else if (modelName.startsWith('gpt-5')) {
|
||||
debug('Applying GPT-5 patch: set reasoning effort to low')
|
||||
body.reasoning_effort = 'low'
|
||||
}
|
||||
}
|
||||
|
||||
if (modelName.startsWith('gemini')) {
|
||||
debug('Applying Gemini patch: set reasoning effort to minimal')
|
||||
body.reasoning_effort = 'minimal'
|
||||
debug('Patch Gemini: reasoning_effort=low')
|
||||
body.reasoning_effort = 'low'
|
||||
if (/^gemini-25(?!.*pro)/.test(modelName)) {
|
||||
debug('Patch Gemini 2.5 non-Pro: reasoning_effort=none')
|
||||
body.reasoning_effort = 'none'
|
||||
} else if (
|
||||
modelName.startsWith('gemini-35-flash') ||
|
||||
modelName.startsWith('gemini-31-flash-lite') ||
|
||||
modelName.startsWith('gemini-3-flash')
|
||||
) {
|
||||
debug('Patch Gemini 3.x Flash/Lite: reasoning_effort=minimal')
|
||||
body.reasoning_effort = 'minimal'
|
||||
}
|
||||
}
|
||||
|
||||
if (modelName.startsWith('deepseek')) {
|
||||
debug('Applying DeepSeek patch: remove tool_choice')
|
||||
delete body.tool_choice
|
||||
if (modelName.startsWith('glm')) {
|
||||
debug('Patch GLM: disable thinking')
|
||||
body.thinking = { type: 'disabled' }
|
||||
}
|
||||
|
||||
if (modelName.startsWith('grok')) {
|
||||
if (/^grok-4-?3/.test(modelName)) {
|
||||
debug('Patch Grok 4.3: reasoning_effort=none')
|
||||
body.reasoning_effort = 'none'
|
||||
} else if (modelName.startsWith('grok-3-mini') || modelName.startsWith('grok-code-fast')) {
|
||||
debug('Patch Grok mini/code: reasoning_effort=low')
|
||||
body.reasoning_effort = 'low'
|
||||
}
|
||||
}
|
||||
|
||||
if (modelName.startsWith('kimi') && !modelName.includes('code')) {
|
||||
// kimi-k2.7-code cannot disable thinking (errors), hence the code exclusion.
|
||||
debug('Patch Kimi: disable thinking')
|
||||
body.thinking = { type: 'disabled' }
|
||||
}
|
||||
|
||||
if (modelName.startsWith('minimax')) {
|
||||
debug('Applying MiniMax patch: clamp temperature to (0, 1]')
|
||||
// MiniMax API rejects temperature = 0; clamp to a small positive value
|
||||
body.temperature = Math.max(body.temperature || 0, 0.01)
|
||||
if (body.temperature > 1) body.temperature = 1
|
||||
// MiniMax does not support parallel_tool_calls
|
||||
// Only M3 can disable thinking; M2.x accepts the field as a silent no-op.
|
||||
// parallel_tool_calls is unsupported.
|
||||
debug('Patch MiniMax: disable thinking, remove parallel_tool_calls')
|
||||
body.thinking = { type: 'disabled' }
|
||||
delete body.parallel_tool_calls
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ export function modelPatch(body: Record<string, any>) {
|
||||
* They should be treated as the same model.
|
||||
* Normalize them to `gpt-52`
|
||||
*/
|
||||
function normalizeModelName(modelName: string): string {
|
||||
export function normalizeModelName(modelName: string): string {
|
||||
let normalizedName = modelName.toLowerCase()
|
||||
|
||||
// remove prefix before '/'
|
||||
|
||||
@@ -208,9 +208,7 @@ export async function inputTextElement(element: HTMLElement, text: string) {
|
||||
selection?.removeAllRanges()
|
||||
selection?.addRange(range)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
||||
doc.execCommand('delete', false)
|
||||
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
||||
doc.execCommand('insertText', false, text)
|
||||
}
|
||||
|
||||
|
||||
@@ -143,13 +143,6 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
required: false,
|
||||
description: 'LLM AK',
|
||||
},
|
||||
{
|
||||
name: 'temperature',
|
||||
type: 'number',
|
||||
description: isZh
|
||||
? '模型温度参数,控制输出随机性'
|
||||
: 'Model temperature, controls output randomness',
|
||||
},
|
||||
{
|
||||
name: 'maxRetries',
|
||||
type: 'number',
|
||||
|
||||
@@ -124,8 +124,8 @@ export default function Models() {
|
||||
</li>
|
||||
<li>
|
||||
{isZh
|
||||
? 'ToolCall 能力较弱的模型可能返回错误的格式,常见错误能够自动恢复,建议设置较高的 temperature'
|
||||
: 'Models with weaker ToolCall capabilities may return incorrect formats. Common errors usually auto-recover. Higher temperature recommended'}
|
||||
? 'ToolCall 能力较弱的模型可能返回错误的格式,常见错误能够自动恢复'
|
||||
: 'Models with weaker ToolCall capabilities may return incorrect formats. Common errors usually auto-recover'}
|
||||
</li>
|
||||
<li>
|
||||
{isZh
|
||||
|
||||
Reference in New Issue
Block a user