Skip to content

Commit 0404173

Browse files
authored
fix: #316 developer-friendly message for output type errors (#672)
1 parent 5d2fc83 commit 0404173

File tree

3 files changed

+113
-2
lines changed

3 files changed

+113
-2
lines changed

.changeset/ninety-clubs-sink.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openai/agents-core': patch
3+
---
4+
5+
fix: #316 developer-friendly message for output type errors

packages/agents-core/src/runImplementation.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,44 @@ function getApprovalIdentity(approval: ApprovalItemLike): string | undefined {
173173
}
174174
}
175175

176+
function formatFinalOutputTypeError(error: unknown): string {
177+
// Surface structured output validation hints without echoing potentially large or sensitive payloads.
178+
try {
179+
if (error instanceof z.ZodError) {
180+
const issue = error.issues[0];
181+
if (issue) {
182+
const issuePathParts = Array.isArray(issue.path) ? issue.path : [];
183+
const issuePath =
184+
issuePathParts.length > 0
185+
? issuePathParts.map((part) => String(part)).join('.')
186+
: '(root)';
187+
const message = truncateForDeveloper(issue.message ?? '');
188+
return `Invalid output type: final assistant output failed schema validation at "${issuePath}" (${message}).`;
189+
}
190+
return 'Invalid output type: final assistant output failed schema validation.';
191+
}
192+
193+
if (error instanceof Error && error.message) {
194+
return `Invalid output type: ${truncateForDeveloper(error.message)}`;
195+
}
196+
} catch {
197+
// Swallow formatting errors so we can return a generic message below.
198+
}
199+
200+
return 'Invalid output type: final assistant output did not match the expected schema.';
201+
}
202+
203+
function truncateForDeveloper(message: string, maxLength = 160): string {
204+
const trimmed = message.trim();
205+
if (!trimmed) {
206+
return 'Schema validation failed.';
207+
}
208+
if (trimmed.length <= maxLength) {
209+
return trimmed;
210+
}
211+
return `${trimmed.slice(0, maxLength - 3)}...`;
212+
}
213+
176214
/**
177215
* @internal
178216
* Walks a raw model response and classifies each item so the runner can schedule follow-up work.
@@ -898,13 +936,14 @@ export async function resolveTurnAfterModelResponse<TContext>(
898936
);
899937
const [error] = await safeExecute(() => parser(potentialFinalOutput));
900938
if (error) {
939+
const outputErrorMessage = formatFinalOutputTypeError(error);
901940
addErrorToCurrentSpan({
902-
message: 'Invalid output type',
941+
message: outputErrorMessage,
903942
data: {
904943
error: String(error),
905944
},
906945
});
907-
throw new ModelBehaviorError('Invalid output type');
946+
throw new ModelBehaviorError(outputErrorMessage);
908947
}
909948

910949
return new SingleStepResult(

packages/agents-core/test/runImplementation.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2317,6 +2317,73 @@ describe('resolveTurnAfterModelResponse', () => {
23172317
}
23182318
});
23192319

2320+
it('throws descriptive error when structured output validation fails', async () => {
2321+
const structuredAgent = new Agent({
2322+
name: 'StructuredAgent',
2323+
outputType: z.object({
2324+
summary: z.string(),
2325+
sections: z.array(
2326+
z.object({
2327+
title: z.string(),
2328+
points: z.array(
2329+
z.object({
2330+
label: z.string(),
2331+
score: z.number().min(0).max(1),
2332+
}),
2333+
),
2334+
}),
2335+
),
2336+
}),
2337+
});
2338+
2339+
const response: ModelResponse = {
2340+
output: [
2341+
fakeModelMessage(
2342+
JSON.stringify({
2343+
summary: 'Example',
2344+
sections: [
2345+
{
2346+
title: 'One',
2347+
points: [{ label: 42, score: 0.5 }],
2348+
},
2349+
],
2350+
}),
2351+
),
2352+
],
2353+
usage: new Usage(),
2354+
} as any;
2355+
2356+
const processedResponse = processModelResponse(
2357+
response,
2358+
structuredAgent,
2359+
[],
2360+
[],
2361+
);
2362+
2363+
const structuredState = new RunState(
2364+
new RunContext(),
2365+
'test input',
2366+
structuredAgent,
2367+
1,
2368+
);
2369+
2370+
await expect(
2371+
withTrace('test', () =>
2372+
resolveTurnAfterModelResponse(
2373+
structuredAgent,
2374+
'test input',
2375+
[],
2376+
response,
2377+
processedResponse,
2378+
runner,
2379+
structuredState,
2380+
),
2381+
),
2382+
).rejects.toThrowError(
2383+
/Invalid output type: final assistant output failed schema validation at "sections\.0\.points\.0\.label" \(Expected string, received number\)./,
2384+
);
2385+
});
2386+
23202387
it('does not finalize after computer actions in the same turn; runs again', async () => {
23212388
const computerAgent = new Agent({
23222389
name: 'ComputerAgent',

0 commit comments

Comments
 (0)