function contentText(payload) { return payload?.content?.map((part) => part.text || '').join('') || '' } function parseJson(text) { const cleaned = text.replace(/^```(?:json)?/i, '').replace(/```$/, '').trim() return JSON.parse(cleaned) } export async function deepseek(system, user, jsonOrOptions = false) { const options = typeof jsonOrOptions === 'boolean' ? { json: jsonOrOptions } : { ...(jsonOrOptions || {}) } const json = Boolean(options.json) const apiKey = process.env.DEEPSEEK_API_KEY if (!apiKey) throw Object.assign(new Error('DEEPSEEK_API_KEY 尚未配置'), { status: 503 }) const endpoint = `${(process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com/anthropic').replace(/\/$/, '')}/v1/messages` const attempts = json ? 2 : 1 let prompt = user let lastError for (let attempt = 0; attempt < attempts; attempt += 1) { const controller = new AbortController() const timeout = setTimeout( () => controller.abort(), Number(options.timeoutMs || process.env.AI_TIMEOUT_MS || 30000), ) try { const response = await fetch(endpoint, { method: 'POST', signal: controller.signal, headers: { 'content-type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01', }, body: JSON.stringify({ model: process.env.DEEPSEEK_MODEL || 'deepseek-v4-flash', max_tokens: Number(options.maxTokens || process.env.AI_MAX_TOKENS || 4096), temperature: options.temperature ?? (json ? 0 : 0.3), thinking: options.thinking === true ? { type: 'enabled' } : { type: 'disabled' }, system, messages: [{ role: 'user', content: [{ type: 'text', text: prompt }] }], }), }) const body = await response.json() if (!response.ok) { throw Object.assign(new Error(body?.error?.message || `AI 请求失败 (${response.status})`), { status: 502 }) } const text = contentText(body) if (!json) return text try { return parseJson(text) } catch (error) { lastError = error prompt = `${user}\n\n上一次响应不是合法 JSON。请重新生成完整结果:只允许输出一个可被 JSON.parse 直接解析的 JSON 值;不得使用 Markdown;字符串中的双引号、换行和反斜杠必须正确转义。` } } catch (error) { if (error?.name === 'AbortError') { throw Object.assign(new Error('AI 响应超时,请稍后重试'), { status: 504 }) } throw error } finally { clearTimeout(timeout) } } throw Object.assign(new Error('AI 返回格式异常,自动重试后仍无法解析'), { status: 502, cause: lastError }) } export function conversationContext(messages, limit = 30) { return messages.slice(-limit).map((m) => { const role = m.sender === 'agent' ? '我方' : m.sender === 'system' ? '系统' : '对方' const content = m.originalText || m.attachmentName || `[${m.type || '消息'}]` const translation = m.translatedText && m.translatedText !== content ? `(译文:${m.translatedText})` : '' return `${m.createdAt || ''} ${role}: ${content}${translation}` }).join('\n') }