UNPKG

@unified-llm/core

Version:

Unified LLM interface (in-memory).

346 lines 14.7 kB
import { validateChatRequest } from '../../utils/validation'; import BaseProvider from '../base-provider'; export class DeepSeekProvider extends BaseProvider { constructor({ apiKey, model, tools }) { super({ model: model || 'deepseek-chat', tools }); this.baseUrl = 'https://api.deepseek.com/v1'; this.apiKey = apiKey; } async chat(request) { validateChatRequest(request); try { const deepseekRequest = this.convertToDeepSeekFormat(request); let response = await this.makeAPICall('/chat/completions', deepseekRequest); let messages = [...deepseekRequest.messages]; // Handle tool calls if present while (response.choices[0].finish_reason === 'tool_calls' && this.tools) { const toolCalls = response.choices[0].message.tool_calls; const toolResults = []; if (toolCalls) { for (const toolCall of toolCalls) { if (toolCall.type === 'function') { const customFunction = this.tools.find(func => func.function.name === toolCall.function.name); if (customFunction) { try { const mergedArgs = { ...(customFunction.args || {}), ...JSON.parse(toolCall.function.arguments) }; const result = await customFunction.handler(mergedArgs); toolResults.push({ role: 'tool', content: typeof result === 'string' ? result : JSON.stringify(result), tool_call_id: toolCall.id, }); } catch (error) { toolResults.push({ role: 'tool', content: error instanceof Error ? error.message : 'Unknown error', tool_call_id: toolCall.id, }); } } } } } // Make follow-up request with tool results if (toolResults.length > 0) { messages = [ ...messages, response.choices[0].message, ...toolResults, ]; const followUpRequest = { ...deepseekRequest, messages, }; response = await this.makeAPICall('/chat/completions', followUpRequest); } else { break; } } return this.convertFromDeepSeekFormat(response); } catch (error) { throw this.handleError(error); } } async *stream(request) { var _a; validateChatRequest(request); const deepseekRequest = this.convertToDeepSeekFormat(request); const response = await fetch(`${this.baseUrl}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}`, }, body: JSON.stringify({ ...deepseekRequest, stream: true, }), }); if (!response.ok) { const error = await response.json(); throw this.handleError(error); } const reader = (_a = response.body) === null || _a === void 0 ? void 0 : _a.getReader(); if (!reader) throw new Error('No response body'); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') continue; try { const chunk = JSON.parse(data); yield this.convertStreamChunk(chunk); } catch (_e) { // Ignore parse errors } } } } } async makeAPICall(endpoint, payload) { const response = await fetch(`${this.baseUrl}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}`, }, body: JSON.stringify(payload), }); if (!response.ok) { const errorText = await response.text(); let error; try { error = JSON.parse(errorText); } catch (parseError) { error = { message: errorText }; } throw this.handleError(error); } return response.json(); } convertToDeepSeekFormat(request) { var _a, _b, _c, _d, _f, _g, _h, _j, _k, _l, _m; const model = request.model || this.model; if (!model) { throw new Error('Model is required for DeepSeek chat completions'); } const messages = request.messages.map(msg => { const content = this.normalizeContent(msg.content); // Handle tool result messages if (msg.role === 'tool' || content.some(c => c.type === 'tool_result')) { const toolResults = content.filter(c => c.type === 'tool_result'); if (toolResults.length > 0) { return toolResults.map(tr => ({ role: 'tool', content: Array.isArray(tr.content) ? tr.content.map(item => item.type === 'text' ? item.text : '[Non-text content]').join('\n') : '[Tool result]', tool_call_id: tr.toolUseId, })); } } // Handle system messages if (msg.role === 'system') { return { role: 'system', content: content.length === 1 && content[0].type === 'text' ? content[0].text : content.filter(c => c.type === 'text').map(c => c.text).join('\n') || '[System message]', }; } // Handle simple text messages if (content.length === 1 && content[0].type === 'text') { return { role: msg.role, content: content[0].text, name: msg.name, }; } // Handle tool use content const toolUseContents = content.filter(c => c.type === 'tool_use'); if (toolUseContents.length > 0) { const textContent = content.filter(c => c.type === 'text').map(c => c.text).join('\n'); return { role: msg.role, content: textContent || null, tool_calls: toolUseContents.map(toolUse => ({ id: toolUse.id, type: 'function', function: { name: toolUse.name, arguments: JSON.stringify(toolUse.input) } })), name: msg.name, }; } // Handle multimodal content const deepseekContent = content.map(c => { switch (c.type) { case 'text': return { type: 'text', text: c.text }; case 'image': return { type: 'image_url', image_url: { url: c.source.url || `data:${c.source.mediaType};base64,${c.source.data}`, }, }; default: return { type: 'text', text: '[Unsupported content type]' }; } }); return { role: msg.role, content: deepseekContent, name: msg.name, }; }).flat(); return { model: model, messages, temperature: (_a = request.generationConfig) === null || _a === void 0 ? void 0 : _a.temperature, max_tokens: (_b = request.generationConfig) === null || _b === void 0 ? void 0 : _b.max_tokens, top_p: (_c = request.generationConfig) === null || _c === void 0 ? void 0 : _c.top_p, frequencyPenalty: (_d = request.generationConfig) === null || _d === void 0 ? void 0 : _d.frequencyPenalty, presencePenalty: (_f = request.generationConfig) === null || _f === void 0 ? void 0 : _f.presencePenalty, stop: (_g = request.generationConfig) === null || _g === void 0 ? void 0 : _g.stopSequences, tools: [ ...(((_h = request.tools) === null || _h === void 0 ? void 0 : _h.map(tool => ({ type: 'function', function: tool.function, }))) || []), ...(((_j = this.tools) === null || _j === void 0 ? void 0 : _j.map(func => ({ type: 'function', function: func.function, }))) || []), ].length > 0 ? [ ...(((_k = request.tools) === null || _k === void 0 ? void 0 : _k.map(tool => ({ type: 'function', function: tool.function, }))) || []), ...(((_l = this.tools) === null || _l === void 0 ? void 0 : _l.map(func => ({ type: 'function', function: func.function, }))) || []), ] : undefined, tool_choice: request.tool_choice, responseFormat: (_m = request.generationConfig) === null || _m === void 0 ? void 0 : _m.responseFormat, }; } convertFromDeepSeekFormat(response) { const choice = response.choices[0]; const message = choice.message; const content = []; if (message.content) { content.push({ type: 'text', text: message.content }); } if (message.tool_calls) { message.tool_calls.forEach((toolCall) => { if (toolCall.type === 'function') { content.push({ type: 'tool_use', id: toolCall.id, name: toolCall.function.name, input: JSON.parse(toolCall.function.arguments), }); } }); } const unifiedMessage = { id: this.generateMessageId(), role: message.role, content, createdAt: new Date(), }; const usage = response.usage ? { inputTokens: response.usage.prompt_tokens, outputTokens: response.usage.completion_tokens, totalTokens: response.usage.total_tokens, } : undefined; // Extract text for convenience field const contentArray = Array.isArray(unifiedMessage.content) ? unifiedMessage.content : [{ type: 'text', text: unifiedMessage.content }]; const textContent = contentArray.find((c) => c.type === 'text'); return { id: response.id, model: response.model, provider: 'deepseek', message: unifiedMessage, text: (textContent === null || textContent === void 0 ? void 0 : textContent.text) || '', usage, finish_reason: choice.finish_reason, createdAt: new Date(response.created * 1000), rawResponse: response, }; } convertStreamChunk(chunk) { const choice = chunk.choices[0]; const delta = choice.delta; const content = []; if (delta.content) { content.push({ type: 'text', text: delta.content }); } const unifiedMessage = { id: this.generateMessageId(), role: delta.role || 'assistant', content, createdAt: new Date(), }; // Extract text for convenience field const contentArray = Array.isArray(unifiedMessage.content) ? unifiedMessage.content : [{ type: 'text', text: unifiedMessage.content }]; const textContent = contentArray.find((c) => c.type === 'text'); return { id: chunk.id, model: chunk.model, provider: 'deepseek', message: unifiedMessage, text: (textContent === null || textContent === void 0 ? void 0 : textContent.text) || '', finish_reason: choice.finish_reason, createdAt: new Date(chunk.created * 1000), rawResponse: chunk, }; } handleError(error) { var _a, _b; return { code: ((_a = error.error) === null || _a === void 0 ? void 0 : _a.code) || 'deepseek_error', message: ((_b = error.error) === null || _b === void 0 ? void 0 : _b.message) || error.message || 'Unknown error occurred', type: this.mapErrorType(error.statusCode || error.status), statusCode: error.statusCode || error.status, provider: 'deepseek', details: error, }; } mapErrorType(status) { if (!status) return 'api_error'; if (status === 429) return 'rate_limit'; if (status === 401) return 'authentication'; if (status >= 400 && status < 500) return 'invalid_request'; if (status >= 500) return 'server_error'; return 'api_error'; } } //# sourceMappingURL=provider.js.map