@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
716 lines (667 loc) • 30.3 kB
text/typescript
import { OpenAI } from 'openai';
import { describe, expect, it } from 'vitest';
import { imageUrlToBase64 } from '@/utils/imageToBase64';
import { OpenAIChatMessage, UserMessageContentPart } from '../types/chat';
import {
buildAnthropicBlock,
buildAnthropicMessage,
buildAnthropicMessages,
buildAnthropicTools,
} from './anthropicHelpers';
import { parseDataUri } from './uriParser';
// Mock the parseDataUri function since it's an implementation detail
vi.mock('./uriParser', () => ({
parseDataUri: vi.fn().mockReturnValue({
mimeType: 'image/jpeg',
base64: 'base64EncodedString',
type: 'base64',
}),
}));
vi.mock('@/utils/imageToBase64');
describe('anthropicHelpers', () => {
describe('buildAnthropicBlock', () => {
it('should return the content as is for text type', async () => {
const content: UserMessageContentPart = { type: 'text', text: 'Hello!' };
const result = await buildAnthropicBlock(content);
expect(result).toEqual(content);
});
it('should transform an image URL into an Anthropic.ImageBlockParam', async () => {
const content: UserMessageContentPart = {
type: 'image_url',
image_url: { url: 'data:image/jpeg;base64,base64EncodedString' },
};
const result = await buildAnthropicBlock(content);
expect(parseDataUri).toHaveBeenCalledWith(content.image_url.url);
expect(result).toEqual({
source: {
data: 'base64EncodedString',
media_type: 'image/jpeg',
type: 'base64',
},
type: 'image',
});
});
it('should transform a regular image URL into an Anthropic.ImageBlockParam', async () => {
vi.mocked(parseDataUri).mockReturnValueOnce({
mimeType: 'image/png',
base64: null,
type: 'url',
});
vi.mocked(imageUrlToBase64).mockResolvedValue({
base64: 'convertedBase64String',
mimeType: 'image/jpg',
});
const content = {
type: 'image_url',
image_url: { url: 'https://example.com/image.png' },
} as const;
const result = await buildAnthropicBlock(content);
expect(parseDataUri).toHaveBeenCalledWith(content.image_url.url);
expect(imageUrlToBase64).toHaveBeenCalledWith(content.image_url.url);
expect(result).toEqual({
source: {
data: 'convertedBase64String',
media_type: 'image/jpg',
type: 'base64',
},
type: 'image',
});
});
it('should use default media_type for URL images when mimeType is not provided', async () => {
vi.mocked(parseDataUri).mockReturnValueOnce({
mimeType: null,
base64: null,
type: 'url',
});
vi.mocked(imageUrlToBase64).mockResolvedValue({
base64: 'convertedBase64String',
mimeType: 'image/png',
});
const content = {
type: 'image_url',
image_url: { url: 'https://example.com/image' },
} as const;
const result = await buildAnthropicBlock(content);
expect(result).toEqual({
source: {
data: 'convertedBase64String',
media_type: 'image/png',
type: 'base64',
},
type: 'image',
});
});
it('should throw an error for invalid image URLs', async () => {
vi.mocked(parseDataUri).mockReturnValueOnce({
mimeType: null,
base64: null,
// @ts-ignore
type: 'invalid',
});
const content = {
type: 'image_url',
image_url: { url: 'invalid-url' },
} as const;
await expect(buildAnthropicBlock(content)).rejects.toThrow('Invalid image URL: invalid-url');
});
});
describe('buildAnthropicMessage', () => {
it('should correctly convert system message to assistant message', async () => {
const message: OpenAIChatMessage = {
content: [{ type: 'text', text: 'Hello!' }],
role: 'system',
};
const result = await buildAnthropicMessage(message);
expect(result).toEqual({ content: [{ type: 'text', text: 'Hello!' }], role: 'user' });
});
it('should correctly convert user message with string content', async () => {
const message: OpenAIChatMessage = {
content: 'Hello!',
role: 'user',
};
const result = await buildAnthropicMessage(message);
expect(result).toEqual({ content: 'Hello!', role: 'user' });
});
it('should correctly convert user message with content parts', async () => {
const message: OpenAIChatMessage = {
content: [
{ type: 'text', text: 'Check out this image:' },
{ type: 'image_url', image_url: { url: 'data:image/png;base64,abc123' } },
],
role: 'user',
};
const result = await buildAnthropicMessage(message);
expect(result.role).toBe('user');
expect(result.content).toHaveLength(2);
expect((result.content[1] as any).type).toBe('image');
});
it('should correctly convert tool message', async () => {
const message: OpenAIChatMessage = {
content: 'Tool result content',
role: 'tool',
tool_call_id: 'tool123',
};
const result = await buildAnthropicMessage(message);
expect(result.role).toBe('user');
expect(result.content).toEqual([
{
content: 'Tool result content',
tool_use_id: 'tool123',
type: 'tool_result',
},
]);
});
it('should correctly convert assistant message with tool calls', async () => {
const message: OpenAIChatMessage = {
content: 'Here is the result:',
role: 'assistant',
tool_calls: [
{
id: 'call1',
type: 'function',
function: {
name: 'search',
arguments: '{"query":"anthropic"}',
},
},
],
};
const result = await buildAnthropicMessage(message);
expect(result.role).toBe('assistant');
expect(result.content).toEqual([
{ text: 'Here is the result:', type: 'text' },
{
id: 'call1',
input: { query: 'anthropic' },
name: 'search',
type: 'tool_use',
},
]);
});
it('should correctly convert function message', async () => {
const message: OpenAIChatMessage = {
content: 'def hello(name):\n return f"Hello {name}"',
role: 'function',
};
const result = await buildAnthropicMessage(message);
expect(result).toEqual({
content: 'def hello(name):\n return f"Hello {name}"',
role: 'assistant',
});
});
});
describe('buildAnthropicMessages', () => {
it('should correctly convert OpenAI Messages to Anthropic Messages', async () => {
const messages: OpenAIChatMessage[] = [
{ content: 'Hello', role: 'user' },
{ content: 'Hi', role: 'assistant' },
];
const result = await buildAnthropicMessages(messages);
expect(result).toHaveLength(2);
expect(result).toEqual([
{ content: 'Hello', role: 'user' },
{ content: 'Hi', role: 'assistant' },
]);
});
it('messages should dont need end with user', async () => {
const messages: OpenAIChatMessage[] = [
{ content: 'Hello', role: 'user' },
{ content: 'Hello', role: 'user' },
{ content: 'Hi', role: 'assistant' },
];
const contents = await buildAnthropicMessages(messages);
expect(contents).toHaveLength(3);
expect(contents).toEqual([
{ content: 'Hello', role: 'user' },
{ content: 'Hello', role: 'user' },
{ content: 'Hi', role: 'assistant' },
]);
});
describe('Tool messages', () => {
it('should handle empty tools', async () => {
const messages: OpenAIChatMessage[] = [
{
content: '## Tools\n\nYou can use these tools',
role: 'user',
},
{
content: '',
role: 'assistant',
tool_calls: [],
},
];
const contents = await buildAnthropicMessages(messages);
expect(contents).toEqual([
{
content: '## Tools\n\nYou can use these tools',
role: 'user',
},
{
content: '',
role: 'assistant',
},
]);
});
it('should correctly convert OpenAI tool message to Anthropic format', async () => {
const messages: OpenAIChatMessage[] = [
{
content: '告诉我杭州和北京的天气,先回答我好的',
role: 'user',
},
{
content:
'好的,我会为您查询杭州和北京的天气信息。我现在就开始查询这两个城市的当前天气情况。',
role: 'assistant',
tool_calls: [
{
function: {
arguments: '{"city": "\\u676d\\u5dde"}',
name: 'realtime-weather____fetchCurrentWeather',
},
id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
type: 'function',
},
{
function: {
arguments: '{"city": "\\u5317\\u4eac"}',
name: 'realtime-weather____fetchCurrentWeather',
},
id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
type: 'function',
},
],
},
{
content:
'[{"city":"杭州市","adcode":"330100","province":"浙江","reporttime":"2024-06-24 17:02:14","casts":[{"date":"2024-06-24","week":"1","dayweather":"小雨","nightweather":"中雨","daytemp":"26","nighttemp":"20","daywind":"西","nightwind":"西","daypower":"1-3","nightpower":"1-3","daytemp_float":"26.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"大雨","nightweather":"中雨","daytemp":"23","nighttemp":"19","daywind":"东","nightwind":"东","daypower":"1-3","nightpower":"1-3","daytemp_float":"23.0","nighttemp_float":"19.0"},{"date":"2024-06-26","week":"3","dayweather":"中雨","nightweather":"中雨","daytemp":"24","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"21.0"},{"date":"2024-06-27","week":"4","dayweather":"中雨-大雨","nightweather":"中雨","daytemp":"24","nighttemp":"22","daywind":"南","nightwind":"南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"22.0"}]}]',
name: 'realtime-weather____fetchCurrentWeather',
role: 'tool',
tool_call_id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
},
{
content:
'[{"city":"北京市","adcode":"110000","province":"北京","reporttime":"2024-06-24 17:03:11","casts":[{"date":"2024-06-24","week":"1","dayweather":"晴","nightweather":"晴","daytemp":"33","nighttemp":"20","daywind":"北","nightwind":"北","daypower":"1-3","nightpower":"1-3","daytemp_float":"33.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"21.0"},{"date":"2024-06-26","week":"3","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"},{"date":"2024-06-27","week":"4","dayweather":"多云","nightweather":"多云","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"}]}]',
name: 'realtime-weather____fetchCurrentWeather',
role: 'tool',
tool_call_id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
},
{
content: '继续',
role: 'user',
},
];
const contents = await buildAnthropicMessages(messages);
expect(contents).toEqual([
{ content: '告诉我杭州和北京的天气,先回答我好的', role: 'user' },
{
content: [
{
text: '好的,我会为您查询杭州和北京的天气信息。我现在就开始查询这两个城市的当前天气情况。',
type: 'text',
},
{
id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
input: { city: '杭州' },
name: 'realtime-weather____fetchCurrentWeather',
type: 'tool_use',
},
{
id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
input: { city: '北京' },
name: 'realtime-weather____fetchCurrentWeather',
type: 'tool_use',
},
],
role: 'assistant',
},
{
content: [
{
content: [
{
text: '[{"city":"杭州市","adcode":"330100","province":"浙江","reporttime":"2024-06-24 17:02:14","casts":[{"date":"2024-06-24","week":"1","dayweather":"小雨","nightweather":"中雨","daytemp":"26","nighttemp":"20","daywind":"西","nightwind":"西","daypower":"1-3","nightpower":"1-3","daytemp_float":"26.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"大雨","nightweather":"中雨","daytemp":"23","nighttemp":"19","daywind":"东","nightwind":"东","daypower":"1-3","nightpower":"1-3","daytemp_float":"23.0","nighttemp_float":"19.0"},{"date":"2024-06-26","week":"3","dayweather":"中雨","nightweather":"中雨","daytemp":"24","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"21.0"},{"date":"2024-06-27","week":"4","dayweather":"中雨-大雨","nightweather":"中雨","daytemp":"24","nighttemp":"22","daywind":"南","nightwind":"南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"22.0"}]}]',
type: 'text',
},
],
tool_use_id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
type: 'tool_result',
},
{
content: [
{
text: '[{"city":"北京市","adcode":"110000","province":"北京","reporttime":"2024-06-24 17:03:11","casts":[{"date":"2024-06-24","week":"1","dayweather":"晴","nightweather":"晴","daytemp":"33","nighttemp":"20","daywind":"北","nightwind":"北","daypower":"1-3","nightpower":"1-3","daytemp_float":"33.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"21.0"},{"date":"2024-06-26","week":"3","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"},{"date":"2024-06-27","week":"4","dayweather":"多云","nightweather":"多云","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"}]}]',
type: 'text',
},
],
tool_use_id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
type: 'tool_result',
},
],
role: 'user',
},
{ content: '继续', role: 'user' },
]);
});
it('should handle user messages with tool correctly', async () => {
const messages: OpenAIChatMessage[] = [
{
content: '搜索下 482的所有质因数?\n\n',
role: 'user',
},
{
content: '',
role: 'assistant',
tool_calls: [
{
function: {
arguments: '{"query": "482的质因数分解"}',
name: 'searchWithSearXNG',
},
id: 'toolu_01AgNoyb9FKuY8TGePPjEfrE',
type: 'function',
},
],
},
{
content:
'[{"content":"因式分解, 2 * 241 ; 因数, 1, 2, 241, 482 ; 因数个数, 4 ; 因数和, 726 ; 前一个整数, 481.","title":"该数性质482","url":"https://zh.numberempire.com/482"}]',
name: 'searchWithSearXNG',
role: 'tool',
tool_call_id: 'toolu_01AgNoyb9FKuY8TGePPjEfrE',
},
];
const contents = await buildAnthropicMessages(messages);
expect(contents).toEqual([
{ content: '搜索下 482的所有质因数?\n\n', role: 'user' },
{
content: [
{
id: 'toolu_01AgNoyb9FKuY8TGePPjEfrE',
input: { query: '482的质因数分解' },
name: 'searchWithSearXNG',
type: 'tool_use',
},
],
role: 'assistant',
},
{
content: [
{
content: [
{
text: '[{"content":"因式分解, 2 * 241 ; 因数, 1, 2, 241, 482 ; 因数个数, 4 ; 因数和, 726 ; 前一个整数, 481.","title":"该数性质482","url":"https://zh.numberempire.com/482"}]',
type: 'text',
},
],
tool_use_id: 'toolu_01AgNoyb9FKuY8TGePPjEfrE',
type: 'tool_result',
},
],
role: 'user',
},
]);
});
it('should work well starting with tool message', async () => {
const messages: OpenAIChatMessage[] = [
{
content:
'[{"content":"因式分解, 2 * 241 ; 因数, 1, 2, 241, 482 ; 因数个数, 4 ; 因数和, 726 ; 前一个整数, 481.","title":"该数性质482","url":"https://zh.numberempire.com/482"}]',
name: 'searchWithSearXNG',
role: 'tool',
tool_call_id: 'toolu_01AgNoyb9FKuY8TGePPjEfrE',
},
{
content: '',
role: 'assistant',
tool_calls: [
{
function: {
arguments: '{"query": "杭州有啥好吃的"}',
name: 'searchWithSearXNG',
},
id: 'toolu_02AgNoyb9FKuY8TGePPjEfrE',
type: 'function',
},
],
},
{
content: '[{"content":"没啥好吃的","title":"该数性质482","url":"e.com/482"}]',
name: 'searchWithSearXNG',
role: 'tool',
tool_call_id: 'toolu_02AgNoyb9FKuY8TGePPjEfrE',
},
];
const contents = await buildAnthropicMessages(messages);
expect(contents).toEqual([
{
content:
'[{"content":"因式分解, 2 * 241 ; 因数, 1, 2, 241, 482 ; 因数个数, 4 ; 因数和, 726 ; 前一个整数, 481.","title":"该数性质482","url":"https://zh.numberempire.com/482"}]',
role: 'user',
},
{
content: [
{
id: 'toolu_02AgNoyb9FKuY8TGePPjEfrE',
input: {
query: '杭州有啥好吃的',
},
name: 'searchWithSearXNG',
type: 'tool_use',
},
],
role: 'assistant',
},
{
content: [
{
content: [
{
text: '[{"content":"没啥好吃的","title":"该数性质482","url":"e.com/482"}]',
type: 'text',
},
],
tool_use_id: 'toolu_02AgNoyb9FKuY8TGePPjEfrE',
type: 'tool_result',
},
],
role: 'user',
},
]);
});
});
it('should correctly handle thinking content part', async () => {
const messages: OpenAIChatMessage[] = [
{
content: '告诉我杭州和北京的天气,先回答我好的',
role: 'user',
},
{
content: [
{ thinking: '经过一番思考', type: 'thinking', signature: '123' },
{
type: 'text',
text: '好的,我会为您查询杭州和北京的天气信息。我现在就开始查询这两个城市的当前天气情况。',
},
],
role: 'assistant',
tool_calls: [
{
function: {
arguments: '{"city": "\\u676d\\u5dde"}',
name: 'realtime-weather____fetchCurrentWeather',
},
id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
type: 'function',
},
{
function: {
arguments: '{"city": "\\u5317\\u4eac"}',
name: 'realtime-weather____fetchCurrentWeather',
},
id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
type: 'function',
},
],
},
{
content:
'[{"city":"杭州市","adcode":"330100","province":"浙江","reporttime":"2024-06-24 17:02:14","casts":[{"date":"2024-06-24","week":"1","dayweather":"小雨","nightweather":"中雨","daytemp":"26","nighttemp":"20","daywind":"西","nightwind":"西","daypower":"1-3","nightpower":"1-3","daytemp_float":"26.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"大雨","nightweather":"中雨","daytemp":"23","nighttemp":"19","daywind":"东","nightwind":"东","daypower":"1-3","nightpower":"1-3","daytemp_float":"23.0","nighttemp_float":"19.0"},{"date":"2024-06-26","week":"3","dayweather":"中雨","nightweather":"中雨","daytemp":"24","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"21.0"},{"date":"2024-06-27","week":"4","dayweather":"中雨-大雨","nightweather":"中雨","daytemp":"24","nighttemp":"22","daywind":"南","nightwind":"南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"22.0"}]}]',
name: 'realtime-weather____fetchCurrentWeather',
role: 'tool',
tool_call_id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
},
{
content:
'[{"city":"北京市","adcode":"110000","province":"北京","reporttime":"2024-06-24 17:03:11","casts":[{"date":"2024-06-24","week":"1","dayweather":"晴","nightweather":"晴","daytemp":"33","nighttemp":"20","daywind":"北","nightwind":"北","daypower":"1-3","nightpower":"1-3","daytemp_float":"33.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"21.0"},{"date":"2024-06-26","week":"3","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"},{"date":"2024-06-27","week":"4","dayweather":"多云","nightweather":"多云","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"}]}]',
name: 'realtime-weather____fetchCurrentWeather',
role: 'tool',
tool_call_id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
},
{
content: '继续',
role: 'user',
},
];
const contents = await buildAnthropicMessages(messages);
expect(contents).toEqual([
{ content: '告诉我杭州和北京的天气,先回答我好的', role: 'user' },
{
content: [
{
signature: '123',
thinking: '经过一番思考',
type: 'thinking',
},
{
text: '好的,我会为您查询杭州和北京的天气信息。我现在就开始查询这两个城市的当前天气情况。',
type: 'text',
},
{
id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
input: { city: '杭州' },
name: 'realtime-weather____fetchCurrentWeather',
type: 'tool_use',
},
{
id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
input: { city: '北京' },
name: 'realtime-weather____fetchCurrentWeather',
type: 'tool_use',
},
],
role: 'assistant',
},
{
content: [
{
content: [
{
text: '[{"city":"杭州市","adcode":"330100","province":"浙江","reporttime":"2024-06-24 17:02:14","casts":[{"date":"2024-06-24","week":"1","dayweather":"小雨","nightweather":"中雨","daytemp":"26","nighttemp":"20","daywind":"西","nightwind":"西","daypower":"1-3","nightpower":"1-3","daytemp_float":"26.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"大雨","nightweather":"中雨","daytemp":"23","nighttemp":"19","daywind":"东","nightwind":"东","daypower":"1-3","nightpower":"1-3","daytemp_float":"23.0","nighttemp_float":"19.0"},{"date":"2024-06-26","week":"3","dayweather":"中雨","nightweather":"中雨","daytemp":"24","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"21.0"},{"date":"2024-06-27","week":"4","dayweather":"中雨-大雨","nightweather":"中雨","daytemp":"24","nighttemp":"22","daywind":"南","nightwind":"南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"22.0"}]}]',
type: 'text',
},
],
tool_use_id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
type: 'tool_result',
},
{
content: [
{
text: '[{"city":"北京市","adcode":"110000","province":"北京","reporttime":"2024-06-24 17:03:11","casts":[{"date":"2024-06-24","week":"1","dayweather":"晴","nightweather":"晴","daytemp":"33","nighttemp":"20","daywind":"北","nightwind":"北","daypower":"1-3","nightpower":"1-3","daytemp_float":"33.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"21.0"},{"date":"2024-06-26","week":"3","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"},{"date":"2024-06-27","week":"4","dayweather":"多云","nightweather":"多云","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"}]}]',
type: 'text',
},
],
tool_use_id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
type: 'tool_result',
},
],
role: 'user',
},
{ content: '继续', role: 'user' },
]);
});
it('should enable cache control', async () => {
const messages: OpenAIChatMessage[] = [
{ content: 'Hello', role: 'user' },
{ content: 'Hello', role: 'user' },
{ content: 'Hi', role: 'assistant' },
];
const contents = await buildAnthropicMessages(messages, { enabledContextCaching: true });
expect(contents).toHaveLength(3);
expect(contents).toEqual([
{ content: 'Hello', role: 'user' },
{ content: 'Hello', role: 'user' },
{
content: [{ cache_control: { type: 'ephemeral' }, text: 'Hi', type: 'text' }],
role: 'assistant',
},
]);
});
});
describe('buildAnthropicTools', () => {
it('should correctly convert OpenAI tools to Anthropic format', () => {
const tools: OpenAI.ChatCompletionTool[] = [
{
type: 'function',
function: {
name: 'search',
description: 'Searches the web',
parameters: {
type: 'object',
properties: {
query: { type: 'string' },
},
required: ['query'],
},
},
},
];
const result = buildAnthropicTools(tools);
expect(result).toEqual([
{
name: 'search',
description: 'Searches the web',
input_schema: {
type: 'object',
properties: {
query: { type: 'string' },
},
required: ['query'],
},
},
]);
});
it('should enable cache control', () => {
const tools: OpenAI.ChatCompletionTool[] = [
{
type: 'function',
function: {
name: 'search',
description: 'Searches the web',
parameters: {
type: 'object',
properties: {
query: { type: 'string' },
},
required: ['query'],
},
},
},
];
const result = buildAnthropicTools(tools, { enabledContextCaching: true });
expect(result).toEqual([
{
name: 'search',
description: 'Searches the web',
input_schema: {
type: 'object',
properties: {
query: { type: 'string' },
},
required: ['query'],
},
cache_control: { type: 'ephemeral' },
},
]);
});
});
});