Skip to content

Commit b26cff1

Browse files
committed
fix: prefer result over output in Gemini Interactions function_result parsing
1 parent b40e4e6 commit b26cff1

3 files changed

Lines changed: 75 additions & 7 deletions

File tree

docs/gemini-interactions/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ <h3>Content[] with Function Result (Tool Response)</h3>
134134
{
135135
<span class="prop">"role"</span>: <span class="str">"user"</span>,
136136
<span class="prop">"parts"</span>: [
137-
{ <span class="prop">"type"</span>: <span class="str">"function_result"</span>, <span class="prop">"name"</span>: <span class="str">"get_weather"</span>, <span class="prop">"call_id"</span>: <span class="str">"call_abc123"</span>, <span class="prop">"output"</span>: <span class="str">"72F sunny"</span> }
137+
{ <span class="prop">"type"</span>: <span class="str">"function_result"</span>, <span class="prop">"name"</span>: <span class="str">"get_weather"</span>, <span class="prop">"call_id"</span>: <span class="str">"call_abc123"</span>, <span class="prop">"result"</span>: <span class="str">"72F sunny"</span> }
138138
]
139139
}
140140
],

src/__tests__/gemini-interactions.test.ts

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ describe("geminiInteractionsToCompletionRequest", () => {
251251
{
252252
type: "function_result",
253253
call_id: "call_abc",
254-
output: { temperature: 72 },
254+
result: { temperature: 72 },
255255
},
256256
],
257257
},
@@ -393,7 +393,7 @@ describe("geminiInteractionsToCompletionRequest", () => {
393393
expect(result.messages).toEqual([{ role: "user", content: "hi" }]);
394394
});
395395

396-
it("converts function_result with string output (passes through as-is)", () => {
396+
it("converts function_result with string result (passes through as-is)", () => {
397397
const result = geminiInteractionsToCompletionRequest({
398398
model: "gemini-2.5-flash",
399399
input: [
@@ -403,7 +403,7 @@ describe("geminiInteractionsToCompletionRequest", () => {
403403
{
404404
type: "function_result",
405405
call_id: "call_str",
406-
output: "plain string result",
406+
result: "plain string result",
407407
},
408408
],
409409
},
@@ -465,7 +465,7 @@ describe("geminiInteractionsToCompletionRequest", () => {
465465
{
466466
type: "function_result",
467467
call_id: "call_legacy_result",
468-
output: { status: "ok" },
468+
result: { status: "ok" },
469469
},
470470
],
471471
},
@@ -477,6 +477,53 @@ describe("geminiInteractionsToCompletionRequest", () => {
477477
expect(result.messages[0].tool_call_id).toBe("call_legacy_result");
478478
});
479479

480+
// ─── result vs output preference tests ────────────────────────────────
481+
482+
it("falls back to output when result is not present (backwards compat)", () => {
483+
const result = geminiInteractionsToCompletionRequest({
484+
model: "gemini-2.5-flash",
485+
input: [
486+
{
487+
role: "user",
488+
content: [
489+
{
490+
type: "function_result",
491+
call_id: "call_old",
492+
output: { legacy: true },
493+
},
494+
],
495+
},
496+
],
497+
});
498+
expect(result.messages).toHaveLength(1);
499+
expect(result.messages[0].role).toBe("tool");
500+
expect(result.messages[0].content).toBe('{"legacy":true}');
501+
expect(result.messages[0].tool_call_id).toBe("call_old");
502+
});
503+
504+
it("prefers result over output when both are present on a function_result", () => {
505+
const result = geminiInteractionsToCompletionRequest({
506+
model: "gemini-2.5-flash",
507+
input: [
508+
{
509+
role: "user",
510+
content: [
511+
{
512+
type: "function_result",
513+
call_id: "call_both",
514+
result: { from: "result" },
515+
output: { from: "output" },
516+
},
517+
],
518+
},
519+
],
520+
});
521+
expect(result.messages).toHaveLength(1);
522+
expect(result.messages[0].role).toBe("tool");
523+
expect(result.messages[0].content).toBe('{"from":"result"}');
524+
expect(result.messages[0].tool_call_id).toBe("call_both");
525+
});
526+
480527
// ─── content vs parts preference test ─────────────────────────────────
481528

482529
it("prefers content over parts when both are present on a Turn", () => {
@@ -1011,7 +1058,7 @@ describe("Gemini Interactions — fixture matching", () => {
10111058
{
10121059
type: "function_result",
10131060
call_id: "call_abc",
1014-
output: "result",
1061+
result: "result",
10151062
},
10161063
],
10171064
},
@@ -1179,4 +1226,23 @@ describe("Gemini Interactions — edge cases", () => {
11791226
expect(deltas).toHaveLength(1);
11801227
expect((deltas[0].delta as Record<string, unknown>).text).toBe("");
11811228
});
1229+
1230+
it("returns 500 for unrecognized fixture response type", async () => {
1231+
instance = await createServer([
1232+
{
1233+
match: { userMessage: "bad-shape" },
1234+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1235+
response: { unknownField: true } as any,
1236+
},
1237+
]);
1238+
const res = await post(`${instance.url}/v1beta/interactions`, {
1239+
model: "gemini-2.5-flash",
1240+
input: "bad-shape",
1241+
stream: false,
1242+
});
1243+
expect(res.status).toBe(500);
1244+
const body = JSON.parse(res.body);
1245+
expect(body.error.code).toBe("INTERNAL");
1246+
expect(body.error.message).toBe("Fixture response did not match any known type");
1247+
});
11821248
});

src/gemini-interactions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ interface InteractionsContentBlock {
4646
id?: string;
4747
arguments?: Record<string, unknown>;
4848
output?: unknown;
49+
result?: unknown;
4950
}
5051

5152
interface InteractionsTurn {
@@ -132,10 +133,11 @@ export function geminiInteractionsToCompletionRequest(
132133
} else if (funcResultParts.length > 0) {
133134
// Tool response messages
134135
for (const part of funcResultParts) {
136+
const resultValue = part.result ?? part.output;
135137
messages.push({
136138
role: "tool",
137139
content:
138-
typeof part.output === "string" ? part.output : JSON.stringify(part.output ?? ""),
140+
typeof resultValue === "string" ? resultValue : JSON.stringify(resultValue ?? ""),
139141
tool_call_id: part.call_id ?? part.id ?? "",
140142
});
141143
}

0 commit comments

Comments
 (0)