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