diff --git a/package.json b/package.json index f5d4a4c..4f9efb2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "langtail", - "version": "0.13.1", + "version": "0.13.2", "description": "", "main": "./Langtail.js", "packageManager": "pnpm@8.15.6", @@ -103,8 +103,8 @@ "react": "=>18.2.0" }, "dependencies": { - "@ai-sdk/provider": "^0.0.5", - "@ai-sdk/provider-utils": "^0.0.8", + "@ai-sdk/provider": "^0.0.24", + "@ai-sdk/provider-utils": "^1.0.20", "@langtail/handlebars-evalless": "^0.1.2", "commander": "^12.1.0", "date-fns": "^3.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2783fc3..6ae1468 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,11 +6,11 @@ settings: dependencies: '@ai-sdk/provider': - specifier: ^0.0.5 - version: 0.0.5 + specifier: ^0.0.24 + version: 0.0.24 '@ai-sdk/provider-utils': - specifier: ^0.0.8 - version: 0.0.8(zod@3.23.8) + specifier: ^1.0.20 + version: 1.0.20(zod@3.23.8) '@langtail/handlebars-evalless': specifier: ^0.1.2 version: 0.1.2 @@ -82,8 +82,8 @@ devDependencies: packages: - /@ai-sdk/provider-utils@0.0.8(zod@3.23.8): - resolution: {integrity: sha512-J/ZNvFhORd3gCeK3jFvNrxp1Dnvy6PvPq21RJ+OsIEjsoHeKQaHALCWG0aJunXDuzd+Mck/lCg7LqA0qmIbHIg==} + /@ai-sdk/provider-utils@1.0.20(zod@3.23.8): + resolution: {integrity: sha512-ngg/RGpnA00eNOWEtXHenpX1MsM2QshQh4QJFjUfwcqHpM5kTfG7je7Rc3HcEDP+OkRVv2GF+X4fC1Vfcnl8Ow==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 @@ -91,15 +91,15 @@ packages: zod: optional: true dependencies: - '@ai-sdk/provider': 0.0.5 + '@ai-sdk/provider': 0.0.24 eventsource-parser: 1.1.2 nanoid: 3.3.6 secure-json-parse: 2.7.0 zod: 3.23.8 dev: false - /@ai-sdk/provider@0.0.5: - resolution: {integrity: sha512-TZDldBZ5clAsNwJ2PSeo/b1uILj9a2lvi0rnOj2RCNZDgaVqFRVIAnKyeHusCRv2lzhPIw03B3fiGI6VoLzOAA==} + /@ai-sdk/provider@0.0.24: + resolution: {integrity: sha512-XMsNGJdGO+L0cxhhegtqZ8+T6nn4EoShS819OvCgI2kLbYTIvk0GWFGD0AXJmxkxs3DrpsJxKAFukFR7bvTkgQ==} engines: {node: '>=18'} dependencies: json-schema: 0.4.0 @@ -1262,7 +1262,7 @@ packages: source-map: 0.6.1 wordwrap: 1.0.0 optionalDependencies: - uglify-js: 3.17.4 + uglify-js: 3.19.3 dev: false /has-flag@3.0.0: @@ -2315,8 +2315,8 @@ packages: resolution: {integrity: sha512-eiutMaL0J2MKdhcOM1tUy13pIrYnyR87fEd8STJQFrrAwImwvlXkxlZEjaKah8r2viPohld08lt73QfLG1NxMg==} dev: true - /uglify-js@3.17.4: - resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} + /uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} hasBin: true requiresBuild: true diff --git a/src/bin/generateTools.ts b/src/bin/generateTools.ts index 82b31ec..05cd9bb 100644 --- a/src/bin/generateTools.ts +++ b/src/bin/generateTools.ts @@ -33,7 +33,8 @@ const fetchTools = async

= undef return Object.fromEntries(prompt.state.tools.map(tool => [ tool.function.name, { description: tool.function.description, - parameters: jsonSchemaToZod(tool.function.parameters) + parameters: jsonSchemaToZod(tool.function.parameters), + ...(tool.function.handler?.enabled === true && { hosted: tool.function.handler.enabled }) } ])); } @@ -139,7 +140,7 @@ const generateTools = async ({ out }: GenerateToolsOptions) => { .replace('// @ts-ignore\n', '') .replace( REPLACE_LINE, - `const toolsObject = ${stringifyToolsObject(toolsObject)};` + `const toolsObject = ${stringifyToolsObject(toolsObject)} as const;` ); fs.writeFileSync(outputFile, fileString, 'utf8'); diff --git a/src/bin/langtailTools.ts.template b/src/bin/langtailTools.ts.template index 89a7c6c..402156d 100644 --- a/src/bin/langtailTools.ts.template +++ b/src/bin/langtailTools.ts.template @@ -28,7 +28,7 @@ type ChatModelInterface

, V extend } type ToolOverrides

, V extends Version> = { - [K in keyof ToolsType[P][E][V]]?: ToolsType[P][E][V][K] extends { parameters: z.ZodTypeAny } ? Partial> : never + [K in keyof ToolsType[P][E][V]]?: ToolsType[P][E][V][K] extends { parameters: z.ZodTypeAny } ? (ToolsType[P][E][V][K] extends { hosted: true } ? never : Partial>) : never }; function tools

, V extends Version, OVERRIDES extends ToolOverrides>( diff --git a/src/schemas.ts b/src/schemas.ts index 3129438..711489e 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -53,6 +53,9 @@ export interface Functions { description: string parameters: Record id?: string + handler?: { + enabled: boolean + } } export interface Tools { diff --git a/src/vercel-ai/convert-to-openai-chat-messages.ts b/src/vercel-ai/convert-to-openai-chat-messages.ts index c7c2f1b..e6439d9 100644 --- a/src/vercel-ai/convert-to-openai-chat-messages.ts +++ b/src/vercel-ai/convert-to-openai-chat-messages.ts @@ -1,10 +1,15 @@ -import { LanguageModelV1Prompt } from '@ai-sdk/provider'; +import { + LanguageModelV1Prompt, + UnsupportedFunctionalityError, +} from '@ai-sdk/provider'; import { convertUint8ArrayToBase64 } from '@ai-sdk/provider-utils'; import { OpenAIChatPrompt } from './openai-chat-prompt'; -export function convertToOpenAIChatMessages( - prompt: LanguageModelV1Prompt, -): OpenAIChatPrompt { +export function convertToOpenAIChatMessages({ + prompt +}: { + prompt: LanguageModelV1Prompt; +}): OpenAIChatPrompt { const messages: OpenAIChatPrompt = []; for (const { role, content } of prompt) { @@ -15,6 +20,11 @@ export function convertToOpenAIChatMessages( } case 'user': { + if (content.length === 1 && content[0].type === 'text') { + messages.push({ role: 'user', content: content[0].text }); + break; + } + messages.push({ role: 'user', content: content.map(part => { @@ -31,12 +41,21 @@ export function convertToOpenAIChatMessages( ? part.image.toString() : `data:${part.mimeType ?? 'image/jpeg' };base64,${convertUint8ArrayToBase64(part.image)}`, + + // OpenAI specific extension: image detail + detail: part.providerMetadata?.openai?.imageDetail, }, }; } + case 'file': { + throw new UnsupportedFunctionalityError({ + functionality: 'File content parts in user messages', + }); + } } }), }); + break; } @@ -78,6 +97,7 @@ export function convertToOpenAIChatMessages( tool_calls: toolCalls.length > 0 ? toolCalls : undefined, }); + break; } @@ -88,6 +108,7 @@ export function convertToOpenAIChatMessages( tool_call_id: toolResponse.toolCallId, content: JSON.stringify(toolResponse.result), }); + } break; } diff --git a/src/vercel-ai/langtail-language-model.test.ts b/src/vercel-ai/langtail-language-model.test.ts deleted file mode 100644 index 5919e40..0000000 --- a/src/vercel-ai/langtail-language-model.test.ts +++ /dev/null @@ -1,611 +0,0 @@ -import { LanguageModelV1Prompt } from '@ai-sdk/provider'; -import { - JsonTestServer, - StreamingTestServer, - convertStreamToArray, -} from '@ai-sdk/provider-utils/test'; -import { createLangtail } from './langtail-provider'; -import { mapOpenAIChatLogProbsOutput } from './map-openai-chat-logprobs'; - -const TEST_PROMPT: LanguageModelV1Prompt = [ - { role: 'user', content: [{ type: 'text', text: 'Hello' }] }, -]; - -const TEST_LOGPROBS = { - content: [ - { - token: 'Hello', - logprob: -0.0009994634, - top_logprobs: [ - { - token: 'Hello', - logprob: -0.0009994634, - }, - ], - }, - { - token: '!', - logprob: -0.13410144, - top_logprobs: [ - { - token: '!', - logprob: -0.13410144, - }, - ], - }, - { - token: ' How', - logprob: -0.0009250381, - top_logprobs: [ - { - token: ' How', - logprob: -0.0009250381, - }, - ], - }, - { - token: ' can', - logprob: -0.047709424, - top_logprobs: [ - { - token: ' can', - logprob: -0.047709424, - }, - ], - }, - { - token: ' I', - logprob: -0.000009014684, - top_logprobs: [ - { - token: ' I', - logprob: -0.000009014684, - }, - ], - }, - { - token: ' assist', - logprob: -0.009125131, - top_logprobs: [ - { - token: ' assist', - logprob: -0.009125131, - }, - ], - }, - { - token: ' you', - logprob: -0.0000066306106, - top_logprobs: [ - { - token: ' you', - logprob: -0.0000066306106, - }, - ], - }, - { - token: ' today', - logprob: -0.00011093382, - top_logprobs: [ - { - token: ' today', - logprob: -0.00011093382, - }, - ], - }, - { - token: '?', - logprob: -0.00004596782, - top_logprobs: [ - { - token: '?', - logprob: -0.00004596782, - }, - ], - }, - ], -}; - -const provider = createLangtail({ - apiKey: 'test-api-key', -}); - -const model = provider.chat('test-prompt', { - model: 'gpt-3.5-turbo' -}); - -describe('doGenerate', () => { - const server = new JsonTestServer( - 'https://api.langtail.com/project-prompt/test-prompt/production', - ); - - server.setupTestEnvironment(); - - function prepareJsonResponse({ - content = '', - usage = { - prompt_tokens: 4, - total_tokens: 34, - completion_tokens: 30, - }, - logprobs = null, - finish_reason = 'stop', - }: { - content?: string; - usage?: { - prompt_tokens: number; - total_tokens: number; - completion_tokens: number; - }; - logprobs?: { - content: - | { - token: string; - logprob: number; - top_logprobs: { token: string; logprob: number }[]; - }[] - | null; - } | null; - finish_reason?: string; - }) { - server.responseBodyJson = { - id: 'chatcmpl-95ZTZkhr0mHNKqerQfiwkuox3PHAd', - object: 'chat.completion', - created: 1711115037, - model: 'gpt-3.5-turbo-0125', - choices: [ - { - index: 0, - message: { - role: 'assistant', - content, - }, - logprobs, - finish_reason, - }, - ], - usage, - system_fingerprint: 'fp_3bc1b5746c', - }; - } - - it('should extract text response', async () => { - prepareJsonResponse({ content: 'Hello, World!' }); - - const { text } = await model.doGenerate({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - }); - - expect(text).toStrictEqual('Hello, World!'); - }); - - it('should extract usage', async () => { - prepareJsonResponse({ - content: '', - usage: { prompt_tokens: 20, total_tokens: 25, completion_tokens: 5 }, - }); - - const { usage } = await model.doGenerate({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - }); - - expect(usage).toStrictEqual({ - promptTokens: 20, - completionTokens: 5, - }); - }); - - /* - it('should extract logprobs', async () => { - prepareJsonResponse({ - logprobs: TEST_LOGPROBS, - }); - - const response = await provider - .chat('gpt-3.5-turbo', { logprobs: 1 }) - .doGenerate({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - }); - expect(response.logprobs).toStrictEqual( - mapOpenAIChatLogProbsOutput(TEST_LOGPROBS), - ); - }); - */ - - it('should extract finish reason', async () => { - prepareJsonResponse({ - content: '', - finish_reason: 'stop', - }); - - const response = await model.doGenerate({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - }); - - expect(response.finishReason).toStrictEqual('stop'); - }); - - it('should expose the raw response headers', async () => { - prepareJsonResponse({ content: '' }); - - server.responseHeaders = { - 'test-header': 'test-value', - }; - - const { rawResponse } = await model.doGenerate({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - }); - - expect(rawResponse?.headers).toStrictEqual({ - // default headers: - 'content-type': 'application/json', - - // custom header - 'test-header': 'test-value', - }); - }); - - it('should pass the model and the messages', async () => { - prepareJsonResponse({ content: '' }); - - await model.doGenerate({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - }); - - expect(await server.getRequestBodyJson()).toStrictEqual({ - model: 'gpt-3.5-turbo', - tools: [], - messages: [{ role: 'user', content: [{ type: 'text', text: 'Hello' }] }], - stream: false, - }); - }); - - it('should pass custom headers', async () => { - prepareJsonResponse({ content: '' }); - - const provider = createLangtail({ - apiKey: 'test-api-key', - openaiOrganization: 'test-organization', - openaiProject: 'test-project', - headers: { - 'Custom-Header': 'test-header', - }, - }); - - await provider.chat('test-prompt').doGenerate({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - }); - - const requestHeaders = await server.getRequestHeaders(); - - expect(requestHeaders.get('OpenAI-Organization')).toStrictEqual( - 'test-organization', - ); - expect(requestHeaders.get('OpenAI-Project')).toStrictEqual('test-project'); - expect(requestHeaders.get('Custom-Header')).toStrictEqual('test-header'); - }); - - it('should pass the api key as X-API-Key header', async () => { - prepareJsonResponse({ content: '' }); - - const provider = createLangtail({ apiKey: 'test-api-key' }); - - await provider.chat('test-prompt').doGenerate({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - }); - - expect( - (await server.getRequestHeaders()).get('X-API-Key'), - ).toStrictEqual('test-api-key'); - }); -}); - -describe('doStream', () => { - const server = new StreamingTestServer( - 'https://api.langtail.com/project-prompt/test-prompt/production', - ); - - server.setupTestEnvironment(); - - function prepareStreamResponse({ - content, - usage = { - prompt_tokens: 17, - total_tokens: 244, - completion_tokens: 227, - }, - logprobs = null, - finish_reason = 'stop', - }: { - content: string[]; - usage?: { - prompt_tokens: number; - total_tokens: number; - completion_tokens: number; - }; - logprobs?: { - content: - | { - token: string; - logprob: number; - top_logprobs: { token: string; logprob: number }[]; - }[] - | null; - } | null; - finish_reason?: string; - }) { - server.responseChunks = [ - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1702657020,"model":"gpt-3.5-turbo-0613",` + - `"system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}\n\n`, - ...content.map(text => { - return ( - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1702657020,"model":"gpt-3.5-turbo-0613",` + - `"system_fingerprint":null,"choices":[{"index":1,"delta":{"content":"${text}"},"finish_reason":null}]}\n\n` - ); - }), - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1702657020,"model":"gpt-3.5-turbo-0613",` + - `"system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"${finish_reason}","logprobs":${JSON.stringify( - logprobs, - )}}]}\n\n`, - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1702657020,"model":"gpt-3.5-turbo-0613",` + - `"system_fingerprint":"fp_3bc1b5746c","choices":[],"usage":${JSON.stringify( - usage, - )}}\n\n`, - 'data: [DONE]\n\n', - ]; - } - - it('should stream text deltas', async () => { - prepareStreamResponse({ - content: ['Hello', ', ', 'World!'], - finish_reason: 'stop', - usage: { - prompt_tokens: 17, - total_tokens: 244, - completion_tokens: 227, - }, - logprobs: TEST_LOGPROBS, - }); - - const { stream } = await model.doStream({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - }); - - // note: space moved to last chunk bc of trimming - expect(await convertStreamToArray(stream)).toStrictEqual([ - { type: 'text-delta', textDelta: '' }, - { type: 'text-delta', textDelta: 'Hello' }, - { type: 'text-delta', textDelta: ', ' }, - { type: 'text-delta', textDelta: 'World!' }, - { - type: 'finish', - finishReason: 'stop', - logprobs: mapOpenAIChatLogProbsOutput(TEST_LOGPROBS), - usage: { promptTokens: 17, completionTokens: 227 }, - }, - ]); - }); - - it('should stream tool deltas', async () => { - server.responseChunks = [ - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` + - `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"role":"assistant","content":null,` + - `"tool_calls":[{"index":0,"id":"call_O17Uplv4lJvD6DVdIvFFeRMw","type":"function","function":{"name":"test-tool","arguments":""}}]},` + - `"logprobs":null,"finish_reason":null}]}\n\n`, - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` + - `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\\""}}]},` + - `"logprobs":null,"finish_reason":null}]}\n\n`, - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` + - `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"value"}}]},` + - `"logprobs":null,"finish_reason":null}]}\n\n`, - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` + - `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\":\\""}}]},` + - `"logprobs":null,"finish_reason":null}]}\n\n`, - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` + - `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Spark"}}]},` + - `"logprobs":null,"finish_reason":null}]}\n\n`, - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` + - `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"le"}}]},` + - `"logprobs":null,"finish_reason":null}]}\n\n`, - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` + - `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Day"}}]},` + - `"logprobs":null,"finish_reason":null}]}\n\n`, - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` + - `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\"}"}}]},` + - `"logprobs":null,"finish_reason":null}]}\n\n`, - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` + - `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]}\n\n`, - `data: {"id":"chatcmpl-96aZqmeDpA9IPD6tACY8djkMsJCMP","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` + - `"system_fingerprint":"fp_3bc1b5746c","choices":[],"usage":{"prompt_tokens":53,"completion_tokens":17,"total_tokens":70}}\n\n`, - 'data: [DONE]\n\n', - ]; - - const { stream } = await model.doStream({ - inputFormat: 'prompt', - mode: { - type: 'regular', - tools: [ - { - type: 'function', - name: 'test-tool', - parameters: { - type: 'object', - properties: { value: { type: 'string' } }, - required: ['value'], - additionalProperties: false, - $schema: 'http://json-schema.org/draft-07/schema#', - }, - }, - ], - }, - prompt: TEST_PROMPT, - }); - - expect(await convertStreamToArray(stream)).toStrictEqual([ - { - type: 'tool-call-delta', - toolCallId: 'call_O17Uplv4lJvD6DVdIvFFeRMw', - toolCallType: 'function', - toolName: 'test-tool', - argsTextDelta: '{"', - }, - { - type: 'tool-call-delta', - toolCallId: 'call_O17Uplv4lJvD6DVdIvFFeRMw', - toolCallType: 'function', - toolName: 'test-tool', - argsTextDelta: 'value', - }, - { - type: 'tool-call-delta', - toolCallId: 'call_O17Uplv4lJvD6DVdIvFFeRMw', - toolCallType: 'function', - toolName: 'test-tool', - argsTextDelta: '":"', - }, - { - type: 'tool-call-delta', - toolCallId: 'call_O17Uplv4lJvD6DVdIvFFeRMw', - toolCallType: 'function', - toolName: 'test-tool', - argsTextDelta: 'Spark', - }, - { - type: 'tool-call-delta', - toolCallId: 'call_O17Uplv4lJvD6DVdIvFFeRMw', - toolCallType: 'function', - toolName: 'test-tool', - argsTextDelta: 'le', - }, - { - type: 'tool-call-delta', - toolCallId: 'call_O17Uplv4lJvD6DVdIvFFeRMw', - toolCallType: 'function', - toolName: 'test-tool', - argsTextDelta: ' Day', - }, - { - type: 'tool-call-delta', - toolCallId: 'call_O17Uplv4lJvD6DVdIvFFeRMw', - toolCallType: 'function', - toolName: 'test-tool', - argsTextDelta: '"}', - }, - { - type: 'tool-call', - toolCallId: 'call_O17Uplv4lJvD6DVdIvFFeRMw', - toolCallType: 'function', - toolName: 'test-tool', - args: '{"value":"Sparkle Day"}', - }, - { - type: 'finish', - finishReason: 'tool-calls', - logprobs: undefined, - usage: { promptTokens: 53, completionTokens: 17 }, - }, - ]); - }); - - it('should expose the raw response headers', async () => { - prepareStreamResponse({ content: [] }); - - server.responseHeaders = { - 'test-header': 'test-value', - }; - - const { rawResponse } = await model.doStream({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - }); - - expect(rawResponse?.headers).toStrictEqual({ - // default headers: - 'content-type': 'text/event-stream', - 'cache-control': 'no-cache', - connection: 'keep-alive', - - // custom header - 'test-header': 'test-value', - }); - }); - - it('should pass the messages and the model', async () => { - prepareStreamResponse({ content: [] }); - - await model.doStream({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - }); - - expect(await server.getRequestBodyJson()).toStrictEqual({ - stream: true, - tools: [], - model: 'gpt-3.5-turbo', - messages: [{ role: 'user', content: [{ type: 'text', text: 'Hello' }] }], - }); - }); - - it('should pass custom headers', async () => { - prepareStreamResponse({ content: [] }); - - const provider = createLangtail({ - apiKey: 'test-api-key', - openaiOrganization: 'test-organization', - openaiProject: 'test-project', - headers: { - 'Custom-Header': 'test-header', - }, - }); - - await provider.chat('test-prompt').doStream({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - }); - - const requestHeaders = await server.getRequestHeaders(); - - expect(requestHeaders.get('OpenAI-Organization')).toStrictEqual( - 'test-organization', - ); - expect(requestHeaders.get('OpenAI-Project')).toStrictEqual('test-project'); - expect(requestHeaders.get('Custom-Header')).toStrictEqual('test-header'); - }); - - it('should pass the api key as X-API-Key header', async () => { - prepareStreamResponse({ content: [] }); - - const provider = createLangtail({ apiKey: 'test-api-key' }); - - await provider.chat('test-prompt').doStream({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - }); - - expect( - (await server.getRequestHeaders()).get('X-API-Key'), - ).toStrictEqual('test-api-key'); - }); -}); diff --git a/src/vercel-ai/langtail-language-model.ts b/src/vercel-ai/langtail-language-model.ts index 4e09b3c..22072ab 100644 --- a/src/vercel-ai/langtail-language-model.ts +++ b/src/vercel-ai/langtail-language-model.ts @@ -4,7 +4,6 @@ import { LanguageModelV1FinishReason, LanguageModelV1LogProbs, LanguageModelV1StreamPart, - UnsupportedFunctionalityError, } from '@ai-sdk/provider'; import { ParseResult, @@ -112,7 +111,7 @@ export class LangtailChatLanguageModel