Skip to main content
Glama

Genkit MCP

Official
by firebase
compat_oai_test.ts38.6 kB
/** * Copyright 2024 The Fire Company * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { describe, expect, it, jest } from '@jest/globals'; import type { GenerateRequest, GenerateResponseData, MessageData, Part, Role, } from 'genkit'; import type OpenAI from 'openai'; import type { ChatCompletion, ChatCompletionChunk, ChatCompletionMessageToolCall, ChatCompletionRole, } from 'openai/resources/index.mjs'; import { fromOpenAIChoice, fromOpenAIChunkChoice, fromOpenAIToolCall, ModelRequestBuilder, openAIModelRunner, toOpenAIMessages, toOpenAIRequestBody, toOpenAIRole, toOpenAITextAndMedia, } from '../src/model'; jest.mock('@genkit-ai/ai/model', () => ({ ...(jest.requireActual('@genkit-ai/ai/model') as Record<string, unknown>), defineModel: jest.fn(), })); describe('toOpenAIRole', () => { const testCases: { genkitRole: Role; expectedOpenAiRole: ChatCompletionRole; }[] = [ { genkitRole: 'user', expectedOpenAiRole: 'user', }, { genkitRole: 'model', expectedOpenAiRole: 'assistant', }, { genkitRole: 'system', expectedOpenAiRole: 'system', }, { genkitRole: 'tool', expectedOpenAiRole: 'tool', }, ]; for (const test of testCases) { it(`should map Genkit "${test.genkitRole}" role to OpenAI "${test.expectedOpenAiRole}" role`, () => { const actualOutput = toOpenAIRole(test.genkitRole); expect(actualOutput).toBe(test.expectedOpenAiRole); }); } it('should throw an error for unknown roles', () => { expect(() => toOpenAIRole('unknown' as Role)).toThrowError( "role unknown doesn't map to an OpenAI role." ); }); }); describe('toOpenAiTextAndMedia', () => { it('should transform text content correctly', () => { const part: Part = { text: 'hi' }; const actualOutput = toOpenAITextAndMedia(part, 'low'); expect(actualOutput).toStrictEqual({ type: 'text', text: 'hi' }); }); it('should transform media content correctly', () => { const part: Part = { media: { contentType: 'image/jpeg', url: 'https://example.com/image.jpg', }, }; const actualOutput = toOpenAITextAndMedia(part, 'low'); expect(actualOutput).toStrictEqual({ type: 'image_url', image_url: { url: 'https://example.com/image.jpg', detail: 'low', }, }); }); it('should throw an error for unknown parts', () => { const part: Part = { data: 'hi' }; expect(() => toOpenAITextAndMedia(part, 'low')).toThrowError( `Unsupported genkit part fields encountered for current message role: {"data":"hi"}` ); }); }); describe('toOpenAiMessages', () => { const testCases = [ { should: 'should transform tool request content correctly', inputMessages: [ { role: 'model', content: [ { toolRequest: { ref: 'call_SVDpFV2l2fW88QRFtv85FWwM', name: 'tellAFunnyJoke', input: { topic: 'bob' }, }, }, ], }, ], expectedOutput: [ { role: 'assistant', tool_calls: [ { id: 'call_SVDpFV2l2fW88QRFtv85FWwM', type: 'function', function: { name: 'tellAFunnyJoke', arguments: '{"topic":"bob"}', }, }, ], }, ], }, { should: 'should transform tool response text content correctly', inputMessages: [ { role: 'tool', content: [ { toolResponse: { ref: 'call_SVDpFV2l2fW88QRFtv85FWwM', name: 'tellAFunnyJoke', output: 'Why did the bob cross the road?', }, }, ], }, ], expectedOutput: [ { role: 'tool', tool_call_id: 'call_SVDpFV2l2fW88QRFtv85FWwM', content: 'Why did the bob cross the road?', }, ], }, { should: 'should transform tool response json content correctly', inputMessages: [ { role: 'tool', content: [ { toolResponse: { ref: 'call_SVDpFV2l2fW88QRFtv85FWwM', name: 'tellAFunnyJoke', output: { test: 'example' }, }, }, ], }, ], expectedOutput: [ { role: 'tool', tool_call_id: 'call_SVDpFV2l2fW88QRFtv85FWwM', content: JSON.stringify({ test: 'example' }), }, ], }, { should: 'should transform text content correctly', inputMessages: [ { role: 'user', content: [{ text: 'hi' }] }, { role: 'model', content: [{ text: 'how can I help you?' }] }, { role: 'user', content: [{ text: 'I am testing' }] }, ], expectedOutput: [ { role: 'user', content: 'hi' }, { role: 'assistant', content: 'how can I help you?' }, { role: 'user', content: 'I am testing' }, ], }, { should: 'should transform multi-modal (text + media) content correctly', inputMessages: [ { role: 'user', content: [ { text: 'describe the following image:' }, { media: { contentType: 'image/jpeg', url: 'https://img.freepik.com/free-photo/abstract-autumn-beauty-multi-colored-leaf-vein-pattern-generated-by-ai_188544-9871.jpg?size=626&ext=jpg&ga=GA1.1.735520172.1710720000&semt=ais', }, }, ], }, ], expectedOutput: [ { role: 'user', content: [ { type: 'text', text: 'describe the following image:' }, { type: 'image_url', image_url: { url: 'https://img.freepik.com/free-photo/abstract-autumn-beauty-multi-colored-leaf-vein-pattern-generated-by-ai_188544-9871.jpg?size=626&ext=jpg&ga=GA1.1.735520172.1710720000&semt=ais', detail: 'auto', }, }, ], }, ], }, { should: 'should transform system messages correctly', inputMessages: [ { role: 'system', content: [{ text: 'system message' }] }, ], expectedOutput: [{ role: 'system', content: 'system message' }], }, ]; for (const test of testCases) { it(test.should, () => { const actualOutput = toOpenAIMessages( test.inputMessages as MessageData[] ); expect(actualOutput).toStrictEqual(test.expectedOutput); }); } }); describe('fromOpenAiToolCall', () => { it('should transform tool call correctly', () => { const toolCall: ChatCompletionMessageToolCall = { id: 'call_SVDpFV2l2fW88QRFtv85FWwM', type: 'function', function: { name: 'tellAFunnyJoke', arguments: '{"topic":"bob"}', }, }; const actualOutput = fromOpenAIToolCall(toolCall, { message: { tool_calls: [toolCall] }, finish_reason: 'tool_calls', } as ChatCompletion.Choice); expect(actualOutput).toStrictEqual({ toolRequest: { ref: 'call_SVDpFV2l2fW88QRFtv85FWwM', name: 'tellAFunnyJoke', input: { topic: 'bob' }, }, }); }); it('should proxy null-ish arguments', () => { const toolCall: ChatCompletionMessageToolCall = { id: 'call_SVDpFV2l2fW88QRFtv85FWwM', type: 'function', function: { name: 'tellAFunnyJoke', arguments: '', }, }; const actualOutput = fromOpenAIToolCall(toolCall, { message: { tool_calls: [toolCall] }, finish_reason: 'tool_calls', } as ChatCompletion.Choice); expect(actualOutput).toStrictEqual({ toolRequest: { ref: 'call_SVDpFV2l2fW88QRFtv85FWwM', name: 'tellAFunnyJoke', input: '', }, }); }); it('should throw an error if tool call is missing required fields', () => { const toolCall: ChatCompletionMessageToolCall = { id: 'call_SVDpFV2l2fW88QRFtv85FWwM', type: 'function', function: undefined as any, }; expect(() => fromOpenAIToolCall(toolCall, { message: { tool_calls: [toolCall] }, finish_reason: 'tool_calls', } as ChatCompletion.Choice) ).toThrowError( 'Unexpected openAI chunk choice. tool_calls was provided but one or more tool_calls is missing.' ); }); }); describe('fromOpenAiChoice', () => { const testCases: { should: string; choice: ChatCompletion.Choice; jsonMode?: boolean; expectedOutput: GenerateResponseData; }[] = [ { should: 'should work with text', choice: { index: 0, message: { role: 'assistant', content: 'Tell a joke about dogs.', refusal: null, }, finish_reason: 'whatever' as any, logprobs: null, }, expectedOutput: { finishReason: 'other', message: { role: 'model', content: [{ text: 'Tell a joke about dogs.' }], }, }, }, { should: 'should work with json', choice: { index: 0, message: { role: 'assistant', content: JSON.stringify({ json: 'test' }), refusal: null, }, finish_reason: 'content_filter', logprobs: null, }, jsonMode: true, expectedOutput: { finishReason: 'blocked', message: { role: 'model', content: [{ data: { json: 'test' } }], }, }, }, { should: 'should work with tools', choice: { index: 0, message: { role: 'assistant', content: 'Tool call', refusal: null, tool_calls: [ { id: 'ref123', type: 'function', function: { name: 'exampleTool', arguments: JSON.stringify({ param: 'value' }), }, }, ], }, finish_reason: 'tool_calls', logprobs: null, }, expectedOutput: { message: { role: 'model', content: [ { toolRequest: { name: 'exampleTool', input: { param: 'value' }, ref: 'ref123', }, }, ], }, finishReason: 'stop', }, }, ]; for (const test of testCases) { it(test.should, () => { const actualOutput = fromOpenAIChoice(test.choice, test.jsonMode); expect(actualOutput).toStrictEqual(test.expectedOutput); }); } }); describe('fromOpenAiChunkChoice', () => { const testCases: { should: string; chunkChoice: ChatCompletionChunk.Choice; jsonMode?: boolean; expectedOutput: GenerateResponseData; }[] = [ { should: 'should work with text', chunkChoice: { index: 0, delta: { role: 'assistant', content: 'Tell a joke about dogs.', }, finish_reason: 'whatever' as any, }, expectedOutput: { finishReason: 'other', message: { role: 'model', content: [{ text: 'Tell a joke about dogs.' }], }, }, }, { should: 'should work with json', chunkChoice: { index: 0, delta: { role: 'assistant', content: JSON.stringify({ json: 'test' }), }, finish_reason: null, logprobs: null, }, jsonMode: true, expectedOutput: { finishReason: 'unknown', message: { role: 'model', content: [{ data: { json: 'test' } }], }, }, }, { should: 'should work with tools', chunkChoice: { index: 0, delta: { role: 'assistant', content: 'Tool call', tool_calls: [ { index: 0, id: 'ref123', function: { name: 'exampleTool', arguments: JSON.stringify({ param: 'value' }), }, }, ], }, finish_reason: 'tool_calls', }, expectedOutput: { message: { role: 'model', content: [ { toolRequest: { name: 'exampleTool', input: { param: 'value' }, ref: 'ref123', }, }, ], }, finishReason: 'stop', }, }, ]; for (const test of testCases) { it(test.should, () => { const actualOutput = fromOpenAIChunkChoice( test.chunkChoice, test.jsonMode ); expect(actualOutput).toStrictEqual(test.expectedOutput); }); } }); describe('toOpenAiRequestBody', () => { const testCases = [ { should: '(gpt-3.5-turbo) handles request with text messages', modelName: 'gpt-3.5-turbo', genkitRequest: { messages: [ { role: 'user', content: [{ text: 'Tell a joke about dogs.' }] }, ], tools: [], output: { format: 'text' }, config: { topP: 1, frequency_penalty: 0.7, logit_bias: { science: 12, technology: 8, politics: -5, sports: 3, }, logprobs: true, presence_penalty: -0.3, seed: 42, top_logprobs: 10, user: 'exampleUser123', }, }, expectedOutput: { messages: [ { role: 'user', content: 'Tell a joke about dogs.', }, ], model: 'gpt-3.5-turbo', response_format: { type: 'text' }, top_p: 1, frequency_penalty: 0.7, logit_bias: { science: 12, technology: 8, politics: -5, sports: 3, }, logprobs: true, presence_penalty: -0.3, seed: 42, top_logprobs: 10, user: 'exampleUser123', }, }, { should: '(gpt-3.5-turbo) handles request with text messages and tools', modelName: 'gpt-3.5-turbo', genkitRequest: { messages: [ { role: 'user', content: [{ text: 'Tell a joke about dogs.' }] }, { role: 'model', content: [ { toolRequest: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', input: { topic: 'dogs' }, }, }, ], }, { role: 'tool', content: [ { toolResponse: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', output: 'Why did the dogs cross the road?', }, }, ], }, ], tools: [ { name: 'tellAFunnyJoke', description: 'Tells jokes about an input topic. Use this tool whenever user asks you to tell a joke.', inputSchema: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, outputSchema: { type: 'string', $schema: 'http://json-schema.org/draft-07/schema#', }, }, ], output: { format: 'text' }, }, expectedOutput: { messages: [ { role: 'user', content: 'Tell a joke about dogs.', }, { role: 'assistant', tool_calls: [ { id: 'call_yTnDw3xY3KH3pkvDvccCizn1', type: 'function', function: { name: 'tellAFunnyJoke', arguments: '{"topic":"dogs"}', }, }, ], }, { role: 'tool', tool_call_id: 'call_yTnDw3xY3KH3pkvDvccCizn1', content: 'Why did the dogs cross the road?', }, ], tools: [ { type: 'function', function: { name: 'tellAFunnyJoke', parameters: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, }, }, ], model: 'gpt-3.5-turbo', response_format: { type: 'text', }, }, }, { should: '(gpt-3.5-turbo) sets response_format if output.format=json', modelName: 'gpt-3.5-turbo', genkitRequest: { messages: [ { role: 'user', content: [{ text: 'Tell a joke about dogs.' }] }, { role: 'model', content: [ { toolRequest: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', input: { topic: 'dogs' }, }, }, ], }, { role: 'tool', content: [ { toolResponse: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', output: 'Why did the dogs cross the road?', }, }, ], }, ], tools: [ { name: 'tellAFunnyJoke', description: 'Tells jokes about an input topic. Use this tool whenever user asks you to tell a joke.', inputSchema: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, outputSchema: { type: 'string', $schema: 'http://json-schema.org/draft-07/schema#', }, }, ], output: { format: 'json' }, }, expectedOutput: { messages: [ { role: 'user', content: 'Tell a joke about dogs.', }, { role: 'assistant', tool_calls: [ { id: 'call_yTnDw3xY3KH3pkvDvccCizn1', type: 'function', function: { name: 'tellAFunnyJoke', arguments: '{"topic":"dogs"}', }, }, ], }, { role: 'tool', tool_call_id: 'call_yTnDw3xY3KH3pkvDvccCizn1', content: 'Why did the dogs cross the road?', }, ], tools: [ { type: 'function', function: { name: 'tellAFunnyJoke', parameters: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, }, }, ], model: 'gpt-3.5-turbo', response_format: { type: 'json_object' }, }, }, { should: '(gpt-4-turbo) sets response_format if output.format=json', modelName: 'gpt-4-turbo', genkitRequest: { messages: [ { role: 'user', content: [{ text: 'Tell a joke about dogs.' }] }, { role: 'model', content: [ { toolRequest: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', input: { topic: 'dogs' }, }, }, ], }, { role: 'tool', content: [ { toolResponse: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', output: 'Why did the dogs cross the road?', }, }, ], }, ], tools: [ { name: 'tellAFunnyJoke', description: 'Tells jokes about an input topic. Use this tool whenever user asks you to tell a joke.', inputSchema: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, outputSchema: { type: 'string', $schema: 'http://json-schema.org/draft-07/schema#', }, }, ], output: { format: 'json' }, }, expectedOutput: { messages: [ { role: 'user', content: 'Tell a joke about dogs.', }, { role: 'assistant', tool_calls: [ { id: 'call_yTnDw3xY3KH3pkvDvccCizn1', type: 'function', function: { name: 'tellAFunnyJoke', arguments: '{"topic":"dogs"}', }, }, ], }, { role: 'tool', tool_call_id: 'call_yTnDw3xY3KH3pkvDvccCizn1', content: 'Why did the dogs cross the road?', }, ], tools: [ { type: 'function', function: { name: 'tellAFunnyJoke', parameters: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, }, }, ], model: 'gpt-4-turbo', response_format: { type: 'json_object' }, }, }, { should: '(gpt-4o) sets response_format if output.format=json', modelName: 'gpt-4o', genkitRequest: { messages: [ { role: 'user', content: [{ text: 'Tell a joke about dogs.' }] }, { role: 'model', content: [ { toolRequest: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', input: { topic: 'dogs' }, }, }, ], }, { role: 'tool', content: [ { toolResponse: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', output: 'Why did the dogs cross the road?', }, }, ], }, ], tools: [ { name: 'tellAFunnyJoke', description: 'Tells jokes about an input topic. Use this tool whenever user asks you to tell a joke.', inputSchema: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, outputSchema: { type: 'string', $schema: 'http://json-schema.org/draft-07/schema#', }, }, ], output: { format: 'json' }, }, expectedOutput: { messages: [ { role: 'user', content: 'Tell a joke about dogs.', }, { role: 'assistant', tool_calls: [ { id: 'call_yTnDw3xY3KH3pkvDvccCizn1', type: 'function', function: { name: 'tellAFunnyJoke', arguments: '{"topic":"dogs"}', }, }, ], }, { role: 'tool', tool_call_id: 'call_yTnDw3xY3KH3pkvDvccCizn1', content: 'Why did the dogs cross the road?', }, ], tools: [ { type: 'function', function: { name: 'tellAFunnyJoke', parameters: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, }, }, ], model: 'gpt-4o', response_format: { type: 'json_object' }, }, }, ]; for (const test of testCases) { it(test.should, () => { const actualOutput = toOpenAIRequestBody( test.modelName, test.genkitRequest as GenerateRequest ); expect(actualOutput).toStrictEqual(test.expectedOutput); }); } it('(gpt4) does NOT set response_format in openai request body', () => { // In either case - output.format='json' or output.format='text' - do NOT set response_format in the OpenAI request body explicitly. const modelName = 'gpt-4'; const genkitRequestTextFormat = { messages: [ { role: 'user', content: [{ text: 'Tell a joke about dogs.' }] }, { role: 'model', content: [ { toolRequest: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', input: { topic: 'dogs' }, }, }, ], }, { role: 'tool', content: [ { toolResponse: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', output: 'Why did the dogs cross the road?', }, }, ], }, ], tools: [ { name: 'tellAFunnyJoke', description: 'Tells jokes about an input topic. Use this tool whenever user asks you to tell a joke.', inputSchema: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, outputSchema: { type: 'string', $schema: 'http://json-schema.org/draft-07/schema#', }, }, ], output: { format: 'text' }, }; const genkitRequestJsonFormat = { messages: [ { role: 'user', content: [{ text: 'Tell a joke about dogs.' }] }, { role: 'model', content: [ { toolRequest: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', input: { topic: 'dogs' }, }, }, ], }, { role: 'tool', content: [ { toolResponse: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', output: 'Why did the dogs cross the road?', }, }, ], }, ], tools: [ { name: 'tellAFunnyJoke', description: 'Tells jokes about an input topic. Use this tool whenever user asks you to tell a joke.', inputSchema: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, outputSchema: { type: 'string', $schema: 'http://json-schema.org/draft-07/schema#', }, }, ], output: { format: 'json' }, }; const expectedOutput = { messages: [ { role: 'user', content: 'Tell a joke about dogs.', }, { role: 'assistant', tool_calls: [ { id: 'call_yTnDw3xY3KH3pkvDvccCizn1', type: 'function', function: { name: 'tellAFunnyJoke', arguments: '{"topic":"dogs"}', }, }, ], }, { role: 'tool', tool_call_id: 'call_yTnDw3xY3KH3pkvDvccCizn1', content: 'Why did the dogs cross the road?', }, ], tools: [ { type: 'function', function: { name: 'tellAFunnyJoke', parameters: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, }, }, ], model: 'gpt-4', }; const actualOutput1 = toOpenAIRequestBody( modelName, genkitRequestTextFormat as GenerateRequest ); const actualOutput2 = toOpenAIRequestBody( modelName, genkitRequestJsonFormat as GenerateRequest ); expect(actualOutput1).toStrictEqual({ ...expectedOutput, response_format: { type: 'text', }, }); expect(actualOutput2).toStrictEqual({ ...expectedOutput, response_format: { type: 'json_object', }, }); }); it('(gpt4-vision) sets response_format in openai request body', () => { // In either case - output.format='json' or output.format='text' - do NOT set response_format in the OpenAI request body explicitly. const modelName = 'gpt-4-vision'; const genkitRequestTextFormat = { messages: [ { role: 'user', content: [{ text: 'Tell a joke about dogs.' }] }, { role: 'model', content: [ { toolRequest: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', input: { topic: 'dogs' }, }, }, ], }, { role: 'tool', content: [ { toolResponse: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', output: 'Why did the dogs cross the road?', }, }, ], }, ], tools: [ { name: 'tellAFunnyJoke', description: 'Tells jokes about an input topic. Use this tool whenever user asks you to tell a joke.', inputSchema: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, outputSchema: { type: 'string', $schema: 'http://json-schema.org/draft-07/schema#', }, }, ], output: { format: 'text' }, }; const genkitRequestJsonFormat = { messages: [ { role: 'user', content: [{ text: 'Tell a joke about dogs.' }] }, { role: 'model', content: [ { toolRequest: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', input: { topic: 'dogs' }, }, }, ], }, { role: 'tool', content: [ { toolResponse: { ref: 'call_yTnDw3xY3KH3pkvDvccCizn1', name: 'tellAFunnyJoke', output: 'Why did the dogs cross the road?', }, }, ], }, ], tools: [ { name: 'tellAFunnyJoke', description: 'Tells jokes about an input topic. Use this tool whenever user asks you to tell a joke.', inputSchema: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, outputSchema: { type: 'string', $schema: 'http://json-schema.org/draft-07/schema#', }, }, ], output: { format: 'json' }, }; const expectedOutput = { messages: [ { role: 'user', content: 'Tell a joke about dogs.', }, { role: 'assistant', tool_calls: [ { id: 'call_yTnDw3xY3KH3pkvDvccCizn1', type: 'function', function: { name: 'tellAFunnyJoke', arguments: '{"topic":"dogs"}', }, }, ], }, { role: 'tool', tool_call_id: 'call_yTnDw3xY3KH3pkvDvccCizn1', content: 'Why did the dogs cross the road?', }, ], tools: [ { type: 'function', function: { name: 'tellAFunnyJoke', parameters: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'], additionalProperties: false, $schema: 'http://json-schema.org/draft-07/schema#', }, }, }, ], model: 'gpt-4-vision', }; const actualOutput1 = toOpenAIRequestBody( modelName, genkitRequestTextFormat as GenerateRequest ); const actualOutput2 = toOpenAIRequestBody( modelName, genkitRequestJsonFormat as GenerateRequest ); expect(actualOutput1).toStrictEqual({ ...expectedOutput, response_format: { type: 'text', }, }); expect(actualOutput2).toStrictEqual({ ...expectedOutput, response_format: { type: 'json_object', }, }); }); }); describe('openAIModelRunner', () => { it('should correctly run non-streaming requests', async () => { const openaiClient = { chat: { completions: { create: jest.fn(async () => ({ choices: [{ message: { content: 'response' } }], })), }, }, }; const runner = openAIModelRunner( 'gpt-4o', openaiClient as unknown as OpenAI ); await runner({ messages: [] }); expect(openaiClient.chat.completions.create).toHaveBeenCalledWith( { model: 'gpt-4o', }, { signal: undefined } ); }); it('should correctly run streaming requests', async () => { const openaiClient = { beta: { chat: { completions: { stream: jest.fn( () => // Simulate OpenAI SDK request streaming new (class { isFirstRequest = true; [Symbol.asyncIterator]() { return { next: async () => { const returnValue = this.isFirstRequest ? { value: { choices: [{ delta: { content: 'response' } }], }, done: false, } : { done: true }; this.isFirstRequest = false; return returnValue; }, }; } async finalChatCompletion() { return { choices: [{ message: { content: 'response' } }] }; } })() ), }, }, }, }; const sendChunk = jest.fn(); const abortSignal = jest.fn(); const runner = openAIModelRunner( 'gpt-4o', openaiClient as unknown as OpenAI ); await runner( { messages: [] }, { sendChunk, streamingRequested: true, abortSignal: abortSignal as unknown as AbortSignal, } ); expect(openaiClient.beta.chat.completions.stream).toHaveBeenCalledWith( { model: 'gpt-4o', stream: true, stream_options: { include_usage: true, }, }, { signal: abortSignal } ); }); it('should run with requestBuilder', async () => { const openaiClient = { chat: { completions: { create: jest.fn(async () => ({ choices: [{ message: { content: 'response' } }], })), }, }, }; const requestBuilder: ModelRequestBuilder = (req, params) => { (params as any).foo = 'bar'; }; const runner = openAIModelRunner( 'gpt-4o', openaiClient as unknown as OpenAI, requestBuilder ); await runner({ messages: [], config: { temperature: 0.1 } }); expect(openaiClient.chat.completions.create).toHaveBeenCalledWith( { model: 'gpt-4o', foo: 'bar', temperature: 0.1, }, { signal: undefined } ); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/firebase/genkit'

If you have feedback or need assistance with the MCP directory API, please join our Discord server