From 7d6be512b82353d8ef75f4dcfd2ac9511cb0a440 Mon Sep 17 00:00:00 2001 From: Arvin Xu Date: Thu, 11 Jun 2026 20:40:12 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(model-runtime):=20align=20to?= =?UTF-8?q?ol-calling=20fallback=20tests=20&=20surface=20missing=20tool=20?= =?UTF-8?q?call=20as=20error=20(#15691)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✅ test(model-runtime): align tool-calling fallback tests with new return shape #15680 changed generateObject's tool-calling fallback to return the parsed schema object (same shape as the json_schema path) instead of an array of tool calls, and reworked its error handling, but left the pre-existing "tool calling fallback" block in index.test.ts asserting the old behavior, breaking CI on canary: - result is now the parsed object, not [{ name, arguments }] - the no-tool-call path returns undefined via debug log without console.error - the parse-failure path logs the single matched tool call, not the array Co-Authored-By: Claude Fable 5 * 🐛 fix(model-runtime): surface missing tool call in generateObject fallback as error tool_choice forces the structured-output function, so a response without a tool call means the provider misbehaved. #15680 routed this branch to a debug-namespace log that is invisible in production, leaving callers with an unexplained undefined. Log it via console.error with the response message as context, matching the parse-failure branch. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- .../core/openaiCompatibleFactory/index.test.ts | 15 +++++++++------ .../src/core/openaiCompatibleFactory/index.ts | 4 +++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts b/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts index 71a1ead6b4..fc2d724342 100644 --- a/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +++ b/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts @@ -3173,9 +3173,9 @@ describe('LobeOpenAICompatibleFactory', () => { { headers: undefined, signal: undefined }, ); - expect(result).toEqual([ - { arguments: { age: 28, name: 'Alice' }, name: 'person_extractor' }, - ]); + // The fallback returns the parsed schema object, same shape as the + // json_schema path + expect(result).toEqual({ age: 28, name: 'Alice' }); }); it('should not forward internal thinking to generic OpenAI-compatible generateObject requests', async () => { @@ -3255,7 +3255,10 @@ describe('LobeOpenAICompatibleFactory', () => { const result = await instanceWithToolCalling.generateObject(payload); - expect(consoleSpy).toHaveBeenCalledWith('parse tool call arguments error:', undefined); + expect(consoleSpy).toHaveBeenCalledWith( + 'no tool call found in structured output response:', + mockResponse.choices[0].message, + ); expect(result).toBeUndefined(); consoleSpy.mockRestore(); @@ -3298,7 +3301,7 @@ describe('LobeOpenAICompatibleFactory', () => { expect(consoleSpy).toHaveBeenCalledWith( 'parse tool call arguments error:', - mockResponse.choices[0].message.tool_calls, + mockResponse.choices[0].message.tool_calls[0], ); expect(result).toBeUndefined(); @@ -3350,7 +3353,7 @@ describe('LobeOpenAICompatibleFactory', () => { { headers: options.headers, signal: options.signal }, ); - expect(result).toEqual([{ arguments: { data: 'test' }, name: 'data_extractor' }]); + expect(result).toEqual({ data: 'test' }); }); }); }); diff --git a/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts b/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts index 31d768bef2..5e9aadaf49 100644 --- a/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +++ b/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts @@ -898,7 +898,9 @@ export const createOpenAICompatibleRuntime = = an toolCalls?.find((item) => item.function?.name === tool.function.name) ?? toolCalls?.[0]; if (!toolCall?.function) { - log('no tool call found in structured output response'); + // tool_choice forces this function, so a missing tool call means the + // provider misbehaved — surface it instead of silently returning undefined + console.error('no tool call found in structured output response:', res.choices[0]?.message); return undefined; }