UNPKG

@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.

462 lines (415 loc) • 13.2 kB
// @vitest-environment node import { describe, expect, it, vi } from 'vitest'; import { createAnthropicGenerateObject } from './generateObject'; describe('Anthropic generateObject', () => { it('should throw error when neither tools nor schema is provided', async () => { const mockClient = { messages: { create: vi.fn(), }, }; const payload = { messages: [{ content: 'Generate data', role: 'user' as const }], model: 'claude-3-5-sonnet-20241022', }; await expect(createAnthropicGenerateObject(mockClient as any, payload as any)).rejects.toThrow( 'tools or schema is required', ); }); describe('use struct output schema', () => { it('should return structured data on successful API call', async () => { const mockClient = { messages: { create: vi.fn().mockResolvedValue({ content: [ { input: { age: 30, name: 'John' }, name: 'person_extractor', type: 'tool_use', }, ], }), }, }; const payload = { messages: [{ content: 'Generate a person object', role: 'user' as const }], model: 'claude-3-5-sonnet-20241022', schema: { description: 'Extract person information', name: 'person_extractor', schema: { properties: { age: { type: 'number' }, name: { type: 'string' } }, required: ['name', 'age'], type: 'object' as const, }, }, }; const result = await createAnthropicGenerateObject(mockClient as any, payload); expect(mockClient.messages.create).toHaveBeenCalledWith( expect.objectContaining({ max_tokens: 8192, messages: [{ content: 'Generate a person object', role: 'user' }], model: 'claude-3-5-sonnet-20241022', tool_choice: { name: 'person_extractor', type: 'tool', }, tools: [ { description: 'Extract person information', input_schema: { properties: { age: { type: 'number' }, name: { type: 'string' }, }, required: ['name', 'age'], type: 'object', }, name: 'person_extractor', }, ], }), expect.objectContaining({}), ); expect(result).toEqual({ age: 30, name: 'John' }); }); it('should handle system messages correctly', async () => { const mockClient = { messages: { create: vi.fn().mockResolvedValue({ content: [ { input: { status: 'success' }, name: 'status_extractor', type: 'tool_use', }, ], }), }, }; const payload = { messages: [ { content: 'You are a helpful assistant', role: 'system' as const }, { content: 'Generate status', role: 'user' as const }, ], model: 'claude-3-5-sonnet-20241022', schema: { name: 'status_extractor', schema: { properties: { status: { type: 'string' } }, type: 'object' as const }, }, }; const result = await createAnthropicGenerateObject(mockClient as any, payload); expect(mockClient.messages.create).toHaveBeenCalledWith( expect.objectContaining({ messages: expect.any(Array), system: [{ text: 'You are a helpful assistant', type: 'text' }], }), expect.any(Object), ); expect(result).toEqual({ status: 'success' }); }); it('should handle options correctly', async () => { const mockClient = { messages: { create: vi.fn().mockResolvedValue({ content: [ { input: { data: 'test' }, name: 'data_extractor', type: 'tool_use', }, ], }), }, }; const payload = { messages: [{ content: 'Generate data', role: 'user' as const }], model: 'claude-3-5-sonnet-20241022', schema: { name: 'data_extractor', schema: { properties: { data: { type: 'string' } }, type: 'object' as const }, }, }; const options = { signal: new AbortController().signal, }; const result = await createAnthropicGenerateObject(mockClient as any, payload, options); expect(mockClient.messages.create).toHaveBeenCalledWith( expect.any(Object), expect.objectContaining({ signal: options.signal, }), ); expect(result).toEqual({ data: 'test' }); }); it('should return undefined when no tool use found in response', async () => { const mockClient = { messages: { create: vi.fn().mockResolvedValue({ content: [ { text: 'Some text response without tool use', type: 'text', }, ], }), }, }; const payload = { messages: [{ content: 'Generate data', role: 'user' as const }], model: 'claude-3-5-sonnet-20241022', schema: { name: 'test_tool', schema: { type: 'object' }, }, }; const result = await createAnthropicGenerateObject(mockClient as any, payload as any); expect(result).toBeUndefined(); }); it('should handle complex nested schemas', async () => { const mockClient = { messages: { create: vi.fn().mockResolvedValue({ content: [ { input: { metadata: { created: '2024-01-01', }, user: { name: 'Alice', profile: { age: 25, preferences: ['music', 'sports'], }, }, }, name: 'user_extractor', type: 'tool_use', }, ], }), }, }; const payload = { messages: [{ content: 'Generate complex user data', role: 'user' as const }], model: 'claude-3-5-sonnet-20241022', schema: { description: 'Extract complex user information', name: 'user_extractor', schema: { properties: { metadata: { type: 'object' }, user: { properties: { name: { type: 'string' }, profile: { properties: { age: { type: 'number' }, preferences: { items: { type: 'string' }, type: 'array' }, }, type: 'object', }, }, type: 'object', }, }, type: 'object' as const, }, }, }; const result = await createAnthropicGenerateObject(mockClient as any, payload); expect(result).toEqual({ metadata: { created: '2024-01-01', }, user: { name: 'Alice', profile: { age: 25, preferences: ['music', 'sports'], }, }, }); }); }); describe('tools calling', () => { it('should handle tools calling mode with multiple tools', async () => { const mockClient = { messages: { create: vi.fn().mockResolvedValue({ content: [ { input: { city: 'New York', unit: 'celsius' }, name: 'get_weather', type: 'tool_use', }, { input: { timezone: 'America/New_York' }, name: 'get_time', type: 'tool_use', }, ], }), }, }; const payload = { messages: [{ content: 'What is the weather and time in New York?', role: 'user' as const }], model: 'claude-3-5-sonnet-20241022', tools: [ { function: { description: 'Get weather information', name: 'get_weather', parameters: { properties: { city: { type: 'string' }, unit: { type: 'string' }, }, required: ['city'], type: 'object' as const, }, }, type: 'function' as const, }, { function: { description: 'Get current time', name: 'get_time', parameters: { properties: { timezone: { type: 'string' }, }, required: ['timezone'], type: 'object' as const, }, }, type: 'function' as const, }, ], }; const result = await createAnthropicGenerateObject(mockClient as any, payload as any); expect(mockClient.messages.create).toHaveBeenCalledWith( expect.objectContaining({ max_tokens: 8192, messages: [{ content: 'What is the weather and time in New York?', role: 'user' }], model: 'claude-3-5-sonnet-20241022', tool_choice: { type: 'any', }, tools: [ { description: 'Get weather information', input_schema: { properties: { city: { type: 'string' }, unit: { type: 'string' }, }, required: ['city'], type: 'object', }, name: 'get_weather', }, { description: 'Get current time', input_schema: { properties: { timezone: { type: 'string' }, }, required: ['timezone'], type: 'object', }, name: 'get_time', }, ], }), expect.objectContaining({}), ); expect(result).toEqual([ { arguments: { city: 'New York', unit: 'celsius' }, name: 'get_weather' }, { arguments: { timezone: 'America/New_York' }, name: 'get_time' }, ]); }); it('should handle tools calling mode with single tool', async () => { const mockClient = { messages: { create: vi.fn().mockResolvedValue({ content: [ { input: { a: 5, b: 3, operation: 'add' }, name: 'calculate', type: 'tool_use', }, ], }), }, }; const payload = { messages: [{ content: 'Add 5 and 3', role: 'user' as const }], model: 'claude-3-5-sonnet-20241022', tools: [ { function: { description: 'Perform mathematical calculation', name: 'calculate', parameters: { properties: { a: { type: 'number' }, b: { type: 'number' }, operation: { type: 'string' }, }, required: ['operation', 'a', 'b'], type: 'object' as const, }, }, type: 'function' as const, }, ], }; const result = await createAnthropicGenerateObject(mockClient as any, payload as any); expect(result).toEqual([{ arguments: { a: 5, b: 3, operation: 'add' }, name: 'calculate' }]); }); }); it('should propagate API errors correctly', async () => { const apiError = new Error('API Error: Model not found'); const mockClient = { messages: { create: vi.fn().mockRejectedValue(apiError), }, }; const payload = { messages: [{ content: 'Generate data', role: 'user' as const }], model: 'claude-3-5-sonnet-20241022', schema: { name: 'test_tool', schema: { type: 'object' }, }, }; await expect(createAnthropicGenerateObject(mockClient as any, payload as any)).rejects.toThrow( 'API Error: Model not found', ); }); it('should handle abort signals correctly', async () => { const apiError = new Error('Request was cancelled'); apiError.name = 'AbortError'; const mockClient = { messages: { create: vi.fn().mockRejectedValue(apiError), }, }; const payload = { messages: [{ content: 'Generate data', role: 'user' as const }], model: 'claude-3-5-sonnet-20241022', schema: { name: 'test_tool', schema: { type: 'object' }, }, }; const options = { signal: new AbortController().signal, }; await expect( createAnthropicGenerateObject(mockClient as any, payload as any, options), ).rejects.toThrow(); }); });