diff --git a/genkit-tools/common/src/types/parts.ts b/genkit-tools/common/src/types/parts.ts index eef420cc7b..ee460ab071 100644 --- a/genkit-tools/common/src/types/parts.ts +++ b/genkit-tools/common/src/types/parts.ts @@ -196,6 +196,7 @@ export type Part = z.infer; export const MultipartToolResponseSchema = z.object({ output: z.unknown().optional(), content: z.array(PartSchema).optional(), + metadata: z.record(z.unknown()).optional(), }); export type MultipartToolResponse = z.infer; diff --git a/genkit-tools/genkit-schema.json b/genkit-tools/genkit-schema.json index 26cc4fbf4f..7080fa8fcf 100644 --- a/genkit-tools/genkit-schema.json +++ b/genkit-tools/genkit-schema.json @@ -906,6 +906,10 @@ "items": { "$ref": "#/$defs/Part" } + }, + "metadata": { + "type": "object", + "additionalProperties": {} } }, "additionalProperties": false diff --git a/go/ai/gen.go b/go/ai/gen.go index e391ef2215..eec7cdfd80 100644 --- a/go/ai/gen.go +++ b/go/ai/gen.go @@ -326,7 +326,8 @@ type ModelResponseChunk struct { // MultipartToolResponse represents a tool response with both structured output and content parts. type MultipartToolResponse struct { // Content holds additional message parts providing context or details. - Content []*Part `json:"content,omitempty"` + Content []*Part `json:"content,omitempty"` + Metadata map[string]any `json:"metadata,omitempty"` // Output contains the structured output data from the tool. Output any `json:"output,omitempty"` } diff --git a/js/ai/src/generate/resolve-tool-requests.ts b/js/ai/src/generate/resolve-tool-requests.ts index c72fb096d4..5105ce5d4f 100644 --- a/js/ai/src/generate/resolve-tool-requests.ts +++ b/js/ai/src/generate/resolve-tool-requests.ts @@ -132,6 +132,7 @@ export async function resolveToolRequest( output: multipartResponse.output, content: multipartResponse.content, } as ToolResponse, + metadata: multipartResponse.metadata, }); return { response }; diff --git a/js/ai/src/parts.ts b/js/ai/src/parts.ts index 53fe2f086f..8985bd2ae9 100644 --- a/js/ai/src/parts.ts +++ b/js/ai/src/parts.ts @@ -187,6 +187,7 @@ export type Part = z.infer; export const MultipartToolResponseSchema = z.object({ output: z.unknown().optional(), content: z.array(PartSchema).optional(), + metadata: z.record(z.unknown()).optional(), }); export type MultipartToolResponse = z.infer; diff --git a/js/ai/src/tool.ts b/js/ai/src/tool.ts index 585669ecf0..ee09030fd2 100644 --- a/js/ai/src/tool.ts +++ b/js/ai/src/tool.ts @@ -301,6 +301,7 @@ export type MultipartToolFn = ( ) => Promise<{ output?: z.infer; content?: Part[]; + metadata?: Record; }>; export function defineTool( diff --git a/js/ai/tests/generate/generate_test.ts b/js/ai/tests/generate/generate_test.ts index a45c3ab0e8..42d00419a3 100644 --- a/js/ai/tests/generate/generate_test.ts +++ b/js/ai/tests/generate/generate_test.ts @@ -632,6 +632,7 @@ describe('generate', () => { return { output: 'main output', content: [{ text: 'part 1' }], + metadata: { custom: 'data' }, }; } ); @@ -699,6 +700,7 @@ describe('generate', () => { }, ], }, + metadata: { custom: 'data' }, }, ], }, diff --git a/js/ai/tests/tool_test.ts b/js/ai/tests/tool_test.ts index e9fa56262e..1bca6e48b1 100644 --- a/js/ai/tests/tool_test.ts +++ b/js/ai/tests/tool_test.ts @@ -135,6 +135,28 @@ describe('defineInterrupt', () => { }); }); + it('should define a multipart tool returning metadata', async () => { + const t = defineTool( + registry, + { name: 'test_meta', description: 'test', multipart: true }, + async () => { + return { + output: 'main output', + content: [{ text: 'part 1' }], + metadata: { customField: 123 }, + }; + } + ); + assert.equal(t.__action.metadata.type, 'tool.v2'); + assert.equal(t.__action.actionType, 'tool.v2'); + const result = await t({}); + assert.deepStrictEqual(result, { + output: 'main output', + content: [{ text: 'part 1' }], + metadata: { customField: 123 }, + }); + }); + it('should handle fallback output', async () => { const t = defineTool( registry, diff --git a/py/packages/genkit/src/genkit/core/typing.py b/py/packages/genkit/src/genkit/core/typing.py index 02f5927450..96abb6c3d4 100644 --- a/py/packages/genkit/src/genkit/core/typing.py +++ b/py/packages/genkit/src/genkit/core/typing.py @@ -936,6 +936,7 @@ class MultipartToolResponse(BaseModel): model_config: ClassVar[ConfigDict] = ConfigDict(alias_generator=to_camel, extra='forbid', populate_by_name=True) output: Any | None = None content: list[Part] | None = None + metadata: dict[str, Any] | None = None class RerankerRequest(BaseModel):