Skip to content

Commit 190caee

Browse files
committed
Improvements for set_output tool prompt/params parsing
1 parent 0cf182b commit 190caee

File tree

3 files changed

+41
-3
lines changed

3 files changed

+41
-3
lines changed

common/src/tools/params/tool/set-output.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ import type { $ToolParams } from '../../constants'
66

77
const toolName = 'set_output'
88
const endsAgentStep = false
9+
10+
// WHY `data` EXISTS IN THE INPUT SCHEMA:
11+
// Subagents inherit their parent's tool definitions, and because of prompt caching
12+
// we cannot modify or add tools mid-conversation. OpenAI models enforce the tool's
13+
// input schema strictly, so we need a permissive shape that any model can call.
14+
// An empty schema or `z.object({}).passthrough()` would be rejected by OpenAI's
15+
// strict schema enforcement. The `data: z.record(...)` field is a deliberately
16+
// vague shape that satisfies OpenAI while allowing us to inject the real
17+
// outputSchema later in the conversation (in the instructions prompt).
18+
//
19+
// At runtime, the handler (`packages/agent-runtime/src/tools/handlers/tool/set-output.ts`)
20+
// tries parsing against the real outputSchema in two ways:
21+
// 1. Parse the raw output (agent passed fields at top level)
22+
// 2. Fallback: parse `output.data` (agent wrapped fields in `data`)
23+
// This means both `{ results: [...] }` and `{ data: { results: [...] } }` are accepted.
924
const inputSchema = z
1025
.looseObject({
1126
data: z.record(z.string(), z.any()).optional(),

packages/agent-runtime/src/templates/strings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ export async function getAgentPrompt<T extends StringField>(
226226
if (outputSchema) {
227227
addendum += '\n\n## Output Schema\n\n'
228228
addendum +=
229-
'When using the set_output tool, your output must conform to this schema:\n\n'
229+
'When using the set_output tool, your output must conform to this schema. You may pass the fields either directly as top-level parameters or inside a `data` field — both are accepted.\n\n'
230230
addendum += '```json\n'
231231
try {
232232
// Convert Zod schema to JSON schema for display

packages/agent-runtime/src/tools/handlers/tool/set-output.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,24 @@ export const handleSetOutput = (async (params: {
5252
agentTemplate.outputSchema.parse(data)
5353
finalOutput = data
5454
} catch (error2) {
55-
const errorMessage = `Output validation error: Output failed to match the output schema and was ignored. You might want to try again! Issues: ${error}`
55+
// Show whichever error has fewer issues — that represents the "closer" parse
56+
// attempt and gives the agent more actionable feedback for retrying.
57+
const issues1 = getZodIssueCount(error)
58+
const issues2 = getZodIssueCount(error2)
59+
const usedData = issues2 < issues1
60+
const bestError = usedData ? error2 : error
61+
const prefix = usedData
62+
? 'Output validation error: Your output was found inside the `data` field but still failed validation. Please fix the issues and try again without wrapping in `data`. Issues: '
63+
: 'Output validation error: Output failed to match the output schema and was ignored. You might want to try again! Issues: '
64+
const errorMessage = `${prefix}${bestError}`
5665
logger.error(
5766
{
5867
output,
5968
agentType: agentState.agentType,
6069
agentId: agentState.agentId,
61-
error,
70+
topLevelError: error,
71+
dataFieldError: error2,
72+
usedDataFieldError: usedData,
6273
},
6374
'set_output validation error',
6475
)
@@ -78,3 +89,15 @@ export const handleSetOutput = (async (params: {
7889

7990
return { output: jsonToolResult({ message: 'Output set' }) }
8091
}) satisfies CodebuffToolHandlerFunction<ToolName>
92+
93+
function getZodIssueCount(error: unknown): number {
94+
if (
95+
error != null &&
96+
typeof error === 'object' &&
97+
'issues' in error &&
98+
Array.isArray((error as { issues: unknown }).issues)
99+
) {
100+
return (error as { issues: unknown[] }).issues.length
101+
}
102+
return Infinity
103+
}

0 commit comments

Comments
 (0)