UNPKG

@unified-llm/core

Version:

Unified LLM interface (in-memory).

504 lines 22.7 kB
import { validateChatRequest } from '../../utils/validation.js'; import BaseProvider from '../base-provider.js'; const VALID_MESSAGE_ROLES = [ 'system', 'user', 'assistant', 'tool', 'function', 'developer', ]; 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, _b, _c, _d, _f, _g, _h; validateChatRequest(request); const deepseekRequest = this.convertToDeepSeekFormat(request); const modelName = deepseekRequest.model || this.model || 'deepseek-chat'; let messages = [...deepseekRequest.messages]; while (true) { const response = await fetch(`${this.baseUrl}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}`, }, body: JSON.stringify({ ...deepseekRequest, messages, 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 = ''; const rawChunks = []; const bufferedTextDeltas = []; const toolCallAccumulator = new Map(); let finishReason; let assistantRole; 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) { const trimmed = line.trim(); if (!trimmed.startsWith('data:')) continue; const data = trimmed.slice(5).trim(); if (!data || data === '[DONE]') { continue; } try { const chunk = JSON.parse(data); rawChunks.push(chunk); const choice = (_b = chunk.choices) === null || _b === void 0 ? void 0 : _b[0]; if (!choice) continue; if (choice.finish_reason) { finishReason = choice.finish_reason; } const delta = choice.delta || {}; if (typeof delta.role === 'string' && VALID_MESSAGE_ROLES.includes(delta.role)) { assistantRole = delta.role; } const deltaContent = delta.content; if (typeof deltaContent === 'string') { bufferedTextDeltas.push(deltaContent); } else if (Array.isArray(deltaContent)) { deltaContent.forEach((part) => { if (typeof part === 'string') { bufferedTextDeltas.push(part); } else if (typeof (part === null || part === void 0 ? void 0 : part.text) === 'string') { bufferedTextDeltas.push(part.text); } }); } if (Array.isArray(delta.tool_calls)) { for (const toolCall of delta.tool_calls) { const index = (_c = toolCall.index) !== null && _c !== void 0 ? _c : 0; if (!toolCallAccumulator.has(index)) { toolCallAccumulator.set(index, { id: toolCall.id || '', type: toolCall.type || 'function', function: { name: ((_d = toolCall.function) === null || _d === void 0 ? void 0 : _d.name) || '', arguments: ((_f = toolCall.function) === null || _f === void 0 ? void 0 : _f.arguments) || '', }, }); } else { const existing = toolCallAccumulator.get(index); if (toolCall.id) existing.id = toolCall.id; if (toolCall.type) existing.type = toolCall.type; if ((_g = toolCall.function) === null || _g === void 0 ? void 0 : _g.name) existing.function.name = toolCall.function.name; if ((_h = toolCall.function) === null || _h === void 0 ? void 0 : _h.arguments) { existing.function.arguments += toolCall.function.arguments; } } } } } catch (_e) { // Ignore parse errors } } } if (finishReason === 'tool_calls' && this.tools && toolCallAccumulator.size > 0) { const assistantMessage = { role: assistantRole || 'assistant', content: bufferedTextDeltas.length > 0 ? bufferedTextDeltas.join('') : null, tool_calls: Array.from(toolCallAccumulator.values()), }; const toolResults = []; for (const toolCall of toolCallAccumulator.values()) { if (toolCall.type !== 'function') continue; const customFunction = this.tools.find(func => func.function.name === toolCall.function.name); if (!customFunction) { continue; } try { const args = toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : {}; const mergedArgs = { ...(customFunction.args || {}), ...args, }; 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, }); } } if (toolResults.length > 0) { messages = [ ...messages, assistantMessage, ...toolResults, ]; continue; // request a new streamed response with tool outputs in context } } yield { id: this.generateMessageId(), model: modelName, provider: 'deepseek', message: { id: this.generateMessageId(), role: 'assistant', content: [], createdAt: new Date() }, text: '', createdAt: new Date(), rawResponse: undefined, eventType: 'start', outputIndex: 0, }; let acc = ''; for (const piece of bufferedTextDeltas) { if (!piece) continue; acc += piece; const ev = { id: this.generateMessageId(), model: modelName, provider: 'deepseek', message: { id: this.generateMessageId(), role: 'assistant', content: [{ type: 'text', text: piece }], createdAt: new Date() }, text: acc, createdAt: new Date(), rawResponse: undefined, eventType: 'text_delta', outputIndex: 0, delta: { type: 'text', text: piece }, }; yield ev; } yield { id: this.generateMessageId(), model: modelName, provider: 'deepseek', message: { id: this.generateMessageId(), role: assistantRole || 'assistant', content: acc ? [{ type: 'text', text: acc }] : [], createdAt: new Date() }, text: acc, finish_reason: finishReason, createdAt: new Date(), rawResponse: rawChunks, eventType: 'stop', outputIndex: 0, }; break; } } 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 (_a) { 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