feat(llms): add transformRequestBody hook and improve prompt assembly (#480)

* feat(llms): add transformRequestBody hook and refine prompt handling

* docs(website): document transformRequestBody usage

* refactor(extension): keep function-valued config handling consistent in useAgent

* feat: simplify `transformRequestBody`

---------

Co-authored-by: Simon <10131203+gaomeng1900@users.noreply.github.com>
This commit is contained in:
zzy-life
2026-04-27 19:46:46 +08:00
committed by GitHub
parent 349609614b
commit a7cc935453
6 changed files with 69 additions and 2 deletions

View File

@@ -45,6 +45,17 @@ export class OpenAIClient implements LLMClient {
}
modelPatch(requestBody)
let transformedBody: Record<string, unknown> | undefined
try {
transformedBody = this.config.transformRequestBody(requestBody)
} catch (error) {
throw new InvokeError(
InvokeErrorType.CONFIG_ERROR,
`transformRequestBody failed: ${(error as Error).message}`,
error
)
}
const finalRequestBody = transformedBody ?? requestBody
// 2. Call API
let response: Response
@@ -55,7 +66,7 @@ export class OpenAIClient implements LLMClient {
'Content-Type': 'application/json',
...(this.config.apiKey && { Authorization: `Bearer ${this.config.apiKey}` }),
},
body: JSON.stringify(requestBody),
body: JSON.stringify(finalRequestBody),
signal: abortSignal,
})
} catch (error: unknown) {
@@ -225,7 +236,7 @@ export class OpenAIClient implements LLMClient {
reasoningTokens: data.usage?.completion_tokens_details?.reasoning_tokens,
},
rawResponse: data,
rawRequest: requestBody,
rawRequest: finalRequestBody,
}
}
}

View File

@@ -14,6 +14,7 @@ export const InvokeErrorType = {
UNKNOWN: 'unknown',
// Non-retryable
CONFIG_ERROR: 'config_error', // Invalid local configuration or hook
AUTH_ERROR: 'auth_error', // Authentication failed
CONTEXT_LENGTH: 'context_length', // Prompt too long
CONTENT_FILTER: 'content_filter', // Content filtered

View File

@@ -21,6 +21,7 @@ export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
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
}

View File

@@ -95,6 +95,16 @@ export interface LLMConfig {
temperature?: number
maxRetries?: number
/**
* Transform the final request body before sending it to the provider.
* Use this to implement provider-specific request tweaks such as caching hints or custom flags.
*
* Return a new object, or mutate the input object and return undefined.
*/
transformRequestBody?: (
requestBody: Record<string, unknown>
) => Record<string, unknown> | undefined
/**
* remove the tool_choice field from the request.
* @note fix "Invalid tool_choice type: 'object'" for some LLMs.