-
Notifications
You must be signed in to change notification settings - Fork 59
Description
Just upgraded to 0.2.1 and found that OpenAI is having a bug with using the web search tool.
The error I'm getting is:
8/20/2025, 11:13:03 AM [CONVEX A(agents:executePromptRun)] Uncaught Error: ArgumentValidationError: Value does not match validator.
Path: .messages[0].message
Value: {content: [{args: "<prompt removed>", toolCallId: "ws_68a53d3a37848193a9b1f4b664d3e97603d3ba2133a3a382", toolName: "web_search_preview", type: "tool-call"}, {result: {type: "json", value: {query: "<prompt removed>", status: "completed"}}, toolCallId: "ws_68a53d3a37848193a9b1f4b664d3e97603d3ba2133a3a382", toolName: "web_search_preview", type: "tool-result"}, {providerOptions: {openai: {itemId: "msg_68a53d3c85708193a32374be3fccc95f03d3ba2133a3a382"}}, text: "<Text response>", type: "text"}], role: "assistant"}
Validator: v.union(v.object({content: v.union(v.string(), v.array(v.union(v.object({providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), text: v.string(), type: v.literal("text")}), v.object({image: v.union(v.string(), v.bytes()), mimeType: v.optional(v.string()), providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), type: v.literal("image")}), v.object({data: v.union(v.string(), v.bytes()), filename: v.optional(v.string()), mimeType: v.string(), providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), type: v.literal("file")})))), providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), role: v.literal("user")}), v.object({content: v.union(v.string(), v.array(v.union(v.object({providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), text: v.string(), type: v.literal("text")}), v.object({data: v.union(v.string(), v.bytes()), filename: v.optional(v.string()), mimeType: v.string(), providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), type: v.literal("file")}), v.object({providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), signature: v.optional(v.string()), state: v.optional(v.union(v.literal("streaming"), v.literal("done"))), text: v.string(), type: v.literal("reasoning")}), v.object({data: v.string(), providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), type: v.literal("redacted-reasoning")}), v.object({args: v.any(), providerExecuted: v.optional(v.boolean()), providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), toolCallId: v.string(), toolName: v.string(), type: v.literal("tool-call")})))), providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), role: v.literal("assistant")}), v.object({content: v.array(v.object({args: v.optional(v.any()), experimental_content: v.optional(v.array(v.union(v.object({text: v.string(), type: v.literal("text")}), v.object({data: v.string(), mimeType: v.optional(v.string()), type: v.literal("image")})))), isError: v.optional(v.boolean()), providerExecuted: v.optional(v.boolean()), providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), result: v.any(), toolCallId: v.string(), toolName: v.string(), type: v.literal("tool-result")})), providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), role: v.literal("tool")}), v.object({content: v.string(), providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), role: v.literal("system")}))
Here is the structure a bit better laid out:
{
"content": [
{
"args": "<Prompt>",
"toolCallId": "ws*68a53749ec888194aac7fa1386ad722a099480cbbec2232f",
"toolName": "web_search_preview",
"type": "tool-call"
},
{
"result": {
"type": "json",
"value": {
"query": "<Prompt>",
"status": "completed"
}
},
"toolCallId": "ws_68a53749ec888194aac7fa1386ad722a099480cbbec2232f",
"toolName": "web_search_preview",
"type": "tool-result"
},
{
"providerOptions": {
"openai": {
"itemId": "msg_68a5374cec30819496c5bbf1cdc48c61099480cbbec2232f"
}
},
"text": "Response",
"type": "text"
}
],
"role": "assistant"
}
I've dug into the code and found that that assistant only expects to have the tool-call, not the tool-result.
https://github.com/get-convex/agent/blob/main/src/validators.ts#L96C1-L107C3
I checked out the code and tried to add that vToolResultPart but the addMessages needs updating to handle that case and I wasn't sure if I was on the right track.
This is using gpt-4.1 with the following config:
await thread.generateText({
prompt: 'Who is the latest company by market value?',
tools: {
web_search_preview: openai.tools.webSearchPreview({
searchContextSize: 'high',
userLocation: {
type: 'approximate',
city: 'Perth',
region: 'Western Australia',
country: 'AU',
timezone: 'Australia/Perth',
},
}),
},
toolChoice: {
type: 'tool',
toolName: 'web_search_preview',
},
})
Also tried GPT-5 (on a whim) and after removing the toolChoice I get this error:
8/20/2025, 11:40:55 AM [CONVEX A(agents:executePromptRun)] Uncaught Error: ArgumentValidationError: Value does not match validator.
Path: .messages[0].reasoningDetails[0]
Value: {providerMetadata: {openai: {itemId: "rs_68a543c1b20c8190b487ed7cdd309d760c2d24ffe2fb3ff3", reasoningEncryptedContent: null}}, text: "", type: "reasoning"}
Validator: v.union(v.object({providerOptions: v.optional(v.record(v.string(), v.record(v.string(), v.any()))), signature: v.optional(v.string()), state: v.optional(v.union(v.literal("streaming"), v.literal("done"))), text: v.string(), type: v.literal("reasoning")}), v.object({signature: v.optional(v.string()), text: v.string(), type: v.literal("text")}), v.object({data: v.string(), type: v.literal("redacted")}))
Happy to investigate / test further, just need a steer if adding more pathways to the addMessage is the correct solution.