feat: structured output
This commit is contained in:
parent
0e8f7694ed
commit
d2beb78457
5 changed files with 47 additions and 99 deletions
|
|
@ -576,21 +576,21 @@ export namespace SessionPrompt {
|
|||
toolChoice: outputFormat.type === "json_schema" ? "required" : undefined,
|
||||
})
|
||||
|
||||
// Handle structured output logic
|
||||
// (outputFormat already set above before process call)
|
||||
// If structured output was captured, save it and exit immediately
|
||||
// This takes priority because the StructuredOutput tool was called successfully
|
||||
if (structuredOutput !== undefined) {
|
||||
processor.message.structured_output = structuredOutput
|
||||
processor.message.finish = processor.message.finish ?? "stop"
|
||||
await Session.updateMessage(processor.message)
|
||||
break
|
||||
}
|
||||
|
||||
// Check if model finished (finish reason is not "tool-calls" or "unknown")
|
||||
const modelFinished =
|
||||
processor.message.finish && !["tool-calls", "unknown"].includes(processor.message.finish)
|
||||
|
||||
if (modelFinished && !processor.message.error) {
|
||||
// Check if structured output was captured successfully
|
||||
if (structuredOutput !== undefined) {
|
||||
// Store structured output on the final assistant message
|
||||
processor.message.structured_output = structuredOutput
|
||||
await Session.updateMessage(processor.message)
|
||||
break
|
||||
} else if (outputFormat.type === "json_schema") {
|
||||
if (outputFormat.type === "json_schema") {
|
||||
// Model stopped without calling StructuredOutput tool
|
||||
processor.message.error = new MessageV2.StructuredOutputError({
|
||||
message: "Model did not produce structured output",
|
||||
|
|
@ -794,7 +794,6 @@ export namespace SessionPrompt {
|
|||
inputSchema: jsonSchema(toolSchema as any),
|
||||
async execute(args) {
|
||||
// AI SDK validates args against inputSchema before calling execute()
|
||||
// So args is guaranteed to match the schema at this point
|
||||
input.onSuccess(args)
|
||||
return {
|
||||
output: "Structured output captured successfully.",
|
||||
|
|
|
|||
|
|
@ -43,18 +43,22 @@ describe("StructuredOutput Integration", () => {
|
|||
},
|
||||
required: ["answer"],
|
||||
},
|
||||
retryCount: 0,
|
||||
},
|
||||
})
|
||||
|
||||
// Verify structured output was captured
|
||||
expect(result.info.structured_output).toBeDefined()
|
||||
expect(typeof result.info.structured_output).toBe("object")
|
||||
// Verify structured output was captured (only on assistant messages)
|
||||
expect(result.info.role).toBe("assistant")
|
||||
if (result.info.role === "assistant") {
|
||||
expect(result.info.structured_output).toBeDefined()
|
||||
expect(typeof result.info.structured_output).toBe("object")
|
||||
|
||||
const output = result.info.structured_output as any
|
||||
expect(output.answer).toBe(4)
|
||||
const output = result.info.structured_output as any
|
||||
expect(output.answer).toBe(4)
|
||||
|
||||
// Verify no error was set
|
||||
expect(result.info.error).toBeUndefined()
|
||||
// Verify no error was set
|
||||
expect(result.info.error).toBeUndefined()
|
||||
}
|
||||
|
||||
// Clean up
|
||||
// Note: Not removing session to avoid race with background SessionSummary.summarize
|
||||
|
|
@ -93,24 +97,28 @@ describe("StructuredOutput Integration", () => {
|
|||
},
|
||||
required: ["company"],
|
||||
},
|
||||
retryCount: 0,
|
||||
},
|
||||
})
|
||||
|
||||
// Verify structured output was captured
|
||||
expect(result.info.structured_output).toBeDefined()
|
||||
const output = result.info.structured_output as any
|
||||
// Verify structured output was captured (only on assistant messages)
|
||||
expect(result.info.role).toBe("assistant")
|
||||
if (result.info.role === "assistant") {
|
||||
expect(result.info.structured_output).toBeDefined()
|
||||
const output = result.info.structured_output as any
|
||||
|
||||
expect(output.company).toBeDefined()
|
||||
expect(output.company.name).toBe("Anthropic")
|
||||
expect(typeof output.company.founded).toBe("number")
|
||||
expect(output.company).toBeDefined()
|
||||
expect(output.company.name).toBe("Anthropic")
|
||||
expect(typeof output.company.founded).toBe("number")
|
||||
|
||||
if (output.products) {
|
||||
expect(Array.isArray(output.products)).toBe(true)
|
||||
if (output.products) {
|
||||
expect(Array.isArray(output.products)).toBe(true)
|
||||
}
|
||||
|
||||
// Verify no error was set
|
||||
expect(result.info.error).toBeUndefined()
|
||||
}
|
||||
|
||||
// Verify no error was set
|
||||
expect(result.info.error).toBeUndefined()
|
||||
|
||||
// Clean up
|
||||
// Note: Not removing session to avoid race with background SessionSummary.summarize
|
||||
})
|
||||
|
|
@ -133,15 +141,16 @@ describe("StructuredOutput Integration", () => {
|
|||
},
|
||||
})
|
||||
|
||||
// Verify no structured output (text mode)
|
||||
expect(result.info.structured_output).toBeUndefined()
|
||||
// Verify no structured output (text mode) and no error
|
||||
expect(result.info.role).toBe("assistant")
|
||||
if (result.info.role === "assistant") {
|
||||
expect(result.info.structured_output).toBeUndefined()
|
||||
expect(result.info.error).toBeUndefined()
|
||||
}
|
||||
|
||||
// Verify we got a response with parts
|
||||
expect(result.parts.length).toBeGreaterThan(0)
|
||||
|
||||
// Verify no error was set
|
||||
expect(result.info.error).toBeUndefined()
|
||||
|
||||
// Clean up
|
||||
// Note: Not removing session to avoid race with background SessionSummary.summarize
|
||||
})
|
||||
|
|
|
|||
|
|
@ -162,7 +162,8 @@ describe("structured-output.createStructuredOutputTool", () => {
|
|||
onSuccess: () => {},
|
||||
})
|
||||
|
||||
expect(tool.id).toBe("StructuredOutput")
|
||||
// AI SDK tool type doesn't expose id, but we set it internally
|
||||
expect((tool as any).id).toBe("StructuredOutput")
|
||||
})
|
||||
|
||||
test("creates tool with description", () => {
|
||||
|
|
@ -223,8 +224,9 @@ describe("structured-output.createStructuredOutputTool", () => {
|
|||
},
|
||||
})
|
||||
|
||||
expect(tool.execute).toBeDefined()
|
||||
const testArgs = { name: "Test Company" }
|
||||
const result = await tool.execute(testArgs, {
|
||||
const result = await tool.execute!(testArgs, {
|
||||
toolCallId: "test-call-id",
|
||||
messages: [],
|
||||
abortSignal: undefined as any,
|
||||
|
|
@ -241,7 +243,8 @@ describe("structured-output.createStructuredOutputTool", () => {
|
|||
onSuccess: () => {},
|
||||
})
|
||||
|
||||
const modelOutput = tool.toModelOutput({
|
||||
expect(tool.toModelOutput).toBeDefined()
|
||||
const modelOutput = tool.toModelOutput!({
|
||||
output: "Test output",
|
||||
title: "Test",
|
||||
metadata: { valid: true },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue