import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { request } from 'undici'; import { ProviderStreamResult, StreamChatRequest } from '../types/chat.types'; import { AiProvider } from './provider.interface'; @Injectable() export class VolcProvider implements AiProvider { readonly code = 'volc'; supports(model?: string): boolean { if (!model) return true; return ( model.toLowerCase().includes('volc') || model.toLowerCase().includes('ark') ); } async streamChat(req: StreamChatRequest): Promise { const apiKey = process.env.VOLC_API_KEY; const baseUrl = process.env.VOLC_BASE_URL || 'https://ark.cn-beijing.volces.com/api/v3'; if (!apiKey) { throw new InternalServerErrorException('VOLC_API_KEY 未配置'); } const model = req.model || 'ep-default'; const upstreamBody = { model, stream: false, messages: (req.messages || []).map((m) => ({ role: m.role, content: m.content, })), }; const { statusCode, body } = await request(`${baseUrl}/chat/completions`, { method: 'POST', body: JSON.stringify(upstreamBody), headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}`, }, }); if (statusCode < 200 || statusCode >= 300) { const text = await body.text(); throw new InternalServerErrorException( `火山引擎调用失败: ${statusCode} - ${text}`, ); } const json = (await body.json()) as any; const content: string = json.choices?.[0]?.message?.content ?? '[Volc] 未返回内容,请检查请求参数或模型配置。'; const promptTokens: number = json.usage?.prompt_tokens ?? 0; const completionTokens: number = json.usage?.completion_tokens ?? 0; const totalTokens: number = json.usage?.total_tokens ?? Math.max(1, promptTokens + completionTokens); const chunks = this.splitText(content, 24).map((c) => ({ content: c })); return { requestId: json.id || `volc_${Date.now()}`, providerCode: this.code, model, chunks, usage: { promptTokens, completionTokens, totalTokens }, }; } private splitText(text: string, size: number) { const result: string[] = []; for (let i = 0; i < text.length; i += size) { result.push(text.slice(i, i + size)); } return result; } }