🐛 fix(model-runtime): align tool-calling fallback tests & surface missing tool call as error (#15691)

*  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 <noreply@anthropic.com>

* 🐛 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 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Arvin Xu
2026-06-11 20:40:12 +08:00
committed by GitHub
parent 1130f7df32
commit 7d6be512b8
2 changed files with 12 additions and 7 deletions
@@ -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' });
});
});
});
@@ -898,7 +898,9 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = 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;
}