diff --git a/js/plugins/compat-oai/src/model.ts b/js/plugins/compat-oai/src/model.ts index 5590ac14ad..a265566edb 100644 --- a/js/plugins/compat-oai/src/model.ts +++ b/js/plugins/compat-oai/src/model.ts @@ -378,7 +378,7 @@ export function fromOpenAIChoice( // Build content array based on what's present in the message let content: Part[] = []; - if (toolRequestParts) { + if (toolRequestParts && toolRequestParts.length > 0) { content = toolRequestParts as ToolRequestPart[]; } else { // Handle reasoning_content if present @@ -426,7 +426,7 @@ export function fromOpenAIChunkChoice( // Build content array based on what's present in the delta let content: Part[] = []; - if (toolRequestParts) { + if (toolRequestParts && toolRequestParts.length > 0) { content = toolRequestParts as ToolRequestPart[]; } else { // Handle reasoning_content if present @@ -644,7 +644,7 @@ export function openAIModelRunner( /** * Method to define a new Genkit Model that is compatible with Open AI - * Chat Completions API. + * Chat Completions API. * * These models are to be used to chat with a large language model. * diff --git a/js/plugins/compat-oai/tests/compat_oai_test.ts b/js/plugins/compat-oai/tests/compat_oai_test.ts index 295d76a9d1..91678261d5 100644 --- a/js/plugins/compat-oai/tests/compat_oai_test.ts +++ b/js/plugins/compat-oai/tests/compat_oai_test.ts @@ -209,7 +209,7 @@ describe('toOpenAiTextAndMedia', () => { describe('toOpenAiMessages', () => { const testCases = [ { - should: 'should transform tool request content correctly', + should: 'transform tool request content correctly', inputMessages: [ { role: 'model', @@ -241,7 +241,7 @@ describe('toOpenAiMessages', () => { ], }, { - should: 'should transform tool response text content correctly', + should: 'transform tool response text content correctly', inputMessages: [ { role: 'tool', @@ -265,7 +265,7 @@ describe('toOpenAiMessages', () => { ], }, { - should: 'should transform tool response json content correctly', + should: 'transform tool response json content correctly', inputMessages: [ { role: 'tool', @@ -289,7 +289,7 @@ describe('toOpenAiMessages', () => { ], }, { - should: 'should transform text content correctly', + should: 'transform text content correctly', inputMessages: [ { role: 'user', content: [{ text: 'hi' }] }, { role: 'model', content: [{ text: 'how can I help you?' }] }, @@ -302,7 +302,7 @@ describe('toOpenAiMessages', () => { ], }, { - should: 'should transform multi-modal (text + media) content correctly', + should: 'transform multi-modal (text + media) content correctly', inputMessages: [ { role: 'user', @@ -334,7 +334,7 @@ describe('toOpenAiMessages', () => { ], }, { - should: 'should transform system messages correctly', + should: 'transform system messages correctly', inputMessages: [ { role: 'system', content: [{ text: 'system message' }] }, ], @@ -423,7 +423,7 @@ describe('fromOpenAiChoice', () => { expectedOutput: GenerateResponseData; }[] = [ { - should: 'should work with text', + should: 'work with text', choice: { index: 0, message: { @@ -443,7 +443,7 @@ describe('fromOpenAiChoice', () => { }, }, { - should: 'should work with json', + should: 'work with json', choice: { index: 0, message: { @@ -464,7 +464,7 @@ describe('fromOpenAiChoice', () => { }, }, { - should: 'should work with tools', + should: 'work with tools', choice: { index: 0, message: { @@ -502,7 +502,7 @@ describe('fromOpenAiChoice', () => { }, }, { - should: 'should work with reasoning_content', + should: 'work with reasoning_content', choice: { index: 0, message: { @@ -523,7 +523,7 @@ describe('fromOpenAiChoice', () => { }, }, { - should: 'should work with both reasoning_content and content', + should: 'work with both reasoning_content and content', choice: { index: 0, message: { @@ -543,6 +543,27 @@ describe('fromOpenAiChoice', () => { }, }, }, + { + should: 'not ignore content when tool_calls is an empty array', + choice: { + index: 0, + message: { + role: 'assistant', + content: 'I have the answer.', + tool_calls: [], + refusal: null, + }, + finish_reason: 'stop', + logprobs: null, + }, + expectedOutput: { + finishReason: 'stop', + message: { + role: 'model', + content: [{ text: 'I have the answer.' }], + }, + }, + }, ]; for (const test of testCases) { @@ -561,7 +582,7 @@ describe('fromOpenAiChunkChoice', () => { expectedOutput: GenerateResponseData; }[] = [ { - should: 'should work with text', + should: 'work with text', chunkChoice: { index: 0, delta: { @@ -579,7 +600,7 @@ describe('fromOpenAiChunkChoice', () => { }, }, { - should: 'should work with json', + should: 'work with json', chunkChoice: { index: 0, delta: { @@ -599,7 +620,7 @@ describe('fromOpenAiChunkChoice', () => { }, }, { - should: 'should work with tools', + should: 'work with tools', chunkChoice: { index: 0, delta: { @@ -635,7 +656,7 @@ describe('fromOpenAiChunkChoice', () => { }, }, { - should: 'should work with reasoning_content', + should: 'work with reasoning_content', chunkChoice: { index: 0, delta: { @@ -653,7 +674,7 @@ describe('fromOpenAiChunkChoice', () => { }, }, { - should: 'should work with both reasoning_content and content', + should: 'work with both reasoning_content and content', chunkChoice: { index: 0, delta: {