Remove provider-level tool validation, add validateToolCall helper

This commit is contained in:
Mario Zechner 2025-12-08 18:04:33 +01:00
parent 0196308266
commit 8bec289dc6
14 changed files with 59 additions and 68 deletions

View file

@ -194,8 +194,8 @@ const response = await complete(model, context);
// Check for tool calls in the response
for (const block of response.content) {
if (block.type === 'toolCall') {
// Arguments are automatically validated against the TypeBox schema using AJV
// If validation fails, an error event is emitted
// Execute your tool with the arguments
// See "Validating Tool Arguments" section for validation
const result = await executeWeatherApi(block.arguments);
// Add tool result with text content
@ -253,7 +253,7 @@ for await (const event of s) {
}
if (event.type === 'toolcall_end') {
// Here toolCall.arguments is complete and validated
// Here toolCall.arguments is complete (but not yet validated)
const toolCall = event.toolCall;
console.log(`Tool completed: ${toolCall.name}`, toolCall.arguments);
}
@ -267,22 +267,43 @@ for await (const event of s) {
- Arrays may be incomplete
- Nested objects may be partially populated
- At minimum, `arguments` will be an empty object `{}`, never `undefined`
- Full validation only occurs at `toolcall_end` when arguments are complete
- The Google provider does not support function call streaming. Instead, you will receive a single `toolcall_delta` event with the full arguments.
### Provider tool-call validation
### Validating Tool Arguments
By default, providers validate streamed tool calls against your tool schema and abort the stream on validation errors. Set `validateToolCallsAtProvider: false` on `stream`, `streamSimple`, `complete`, `completeSimple`, or `AgentLoopConfig` to skip provider-level validation and let downstream code (for example, `agentLoop` via `executeToolCalls``validateToolArguments`) surface schema errors as `toolResult` messages. This enables the model to retry after receiving a validation error.
When using `agentLoop`, tool arguments are automatically validated against your TypeBox schemas before execution. If validation fails, the error is returned to the model as a tool result, allowing it to retry.
When implementing your own tool execution loop with `stream()` or `complete()`, use `validateToolCall` to validate arguments before passing them to your tools:
```typescript
await streamSimple(model, context, {
apiKey: 'your-key',
validateToolCallsAtProvider: false
});
```
import { stream, validateToolCall, Tool } from '@mariozechner/pi-ai';
- `true` (default): Provider validates tool calls and emits an error if arguments do not match the schema
- `false`: Provider emits tool calls even when arguments are invalid; callers must validate and handle errors themselves
const tools: Tool[] = [weatherTool, calculatorTool];
const s = stream(model, { messages, tools });
for await (const event of s) {
if (event.type === 'toolcall_end') {
const toolCall = event.toolCall;
try {
// Validate arguments against the tool's schema (throws on invalid args)
const validatedArgs = validateToolCall(tools, toolCall);
const result = await executeMyTool(toolCall.name, validatedArgs);
// ... add tool result to context
} catch (error) {
// Validation failed - return error as tool result so model can retry
context.messages.push({
role: 'toolResult',
toolCallId: toolCall.id,
toolName: toolCall.name,
content: [{ type: 'text', text: error.message }],
isError: true,
timestamp: Date.now()
});
}
}
}
```
### Complete Event Reference