const createHttpError = (statusCode, message) => { const error = new Error(message); error.statusCode = statusCode; return error; }; /** * 文本预处理 */ const preprocessText = (text) => { return text .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .replace(/\n{3,}/g, '\n\n') .split('\n') .map(line => line.trim()) .filter(line => line.length > 0) .join('\n') .trim(); }; /** * 将文本分块(适合长文本) */ const splitTextIntoChunks = (text, chunkSize = 500, overlap = 50) => { const chunks = []; let start = 0; while (start < text.length) { const end = Math.min(start + chunkSize, text.length); let chunkEnd = end; // 尝试在句子边界处切断 if (end < text.length) { const lastPeriod = text.lastIndexOf('.', end); const lastQuestion = text.lastIndexOf('?', end); const lastExclamation = text.lastIndexOf('!', end); const lastNewline = text.lastIndexOf('\n', end); const breakPoint = Math.max(lastPeriod, lastQuestion, lastExclamation, lastNewline); if (breakPoint > start + chunkSize / 2) { chunkEnd = breakPoint + 1; } } chunks.push(text.slice(start, chunkEnd).trim()); start = chunkEnd - overlap; } return chunks; }; export class LLMService { constructor(env) { this.baseUrl = (env.LLM_BASE_URL ?? "").replace(/\/+$/, ""); this.apiKey = env.LLM_API_KEY ?? ""; this.model = env.LLM_MODEL_NAME ?? ""; } isEnabled() { return Boolean(this.baseUrl && this.apiKey && this.model); } async chat(messages, temperature = 0.7) { if (!this.isEnabled()) { throw createHttpError(400, "LLM 服务未配置,请提供 LLM_BASE_URL/LLM_API_KEY/LLM_MODEL_NAME"); } const response = await fetch(`${this.baseUrl}/chat/completions`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.apiKey}` }, body: JSON.stringify({ model: this.model, messages: messages, temperature: temperature, max_tokens: 4096 }) }); if (!response.ok) { const errorText = await response.text(); throw createHttpError(response.status, `LLM 请求失败:${errorText}`); } const data = await response.json(); return data; } /** * 分析文本并提取详细的实体和关系(MiroFish 风格) * @param {string} text - 用户输入的文本 * @param {object} existingEntities - 现有实体列表(用于识别是否已存在) */ async analyzeText(text, existingEntities = {}) { if (!text?.trim()) { throw createHttpError(400, "分析文本不能为空"); } const existingContext = existingEntities.persons?.length > 0 || existingEntities.organizations?.length > 0 ? ` ## 已有实体列表(极其重要!) **如果文本中提到的人/组织已存在于下方列表中,必须复用相同的 ID,不要创建新实体!** 已有的人物: ${(existingEntities.persons || []).map(p => `- ID: "${p.id}", 名字:"${p.name}", 描述:${p.summary}`).join('\n')} 已有的组织: ${(existingEntities.organizations || []).map(o => `- ID: "${o.id}", 名字:"${o.name}", 描述:${o.summary}`).join('\n')} **代词解析指南**: - "我女朋友" = 已有实体中的"女朋友"(如果有) - "她" = 根据上下文推断指代哪个女性角色 - "他" = 根据上下文推断指代哪个男性角色 - "丽丽" = 如果已有实体中有"丽丽",复用 ID` : ''; const systemPrompt = `你是一个恋爱关系知识图谱构建专家。从用户输入的文本中提取实体和关系,用于后续的恋爱决策建议。 ## 核心原则 1. **重点关注**:用户本人("我")和恋爱对象(女朋友/男朋友/心仪对象)需要详细记录 2. **其他人物**:朋友、闺蜜、同事等只需要记录基本信息(名字、与用户的关系) 3. **事件细节**:记录争吵、约会、礼物、重要对话等影响关系的事件 4. **情感线索**:提取情绪变化、态度、期望等软性信息 ## 输出格式(严格 JSON) { "persons": [ { "id": "p1", "name": "人物名称", "summary": "人物描述", "role": "用户 | 恋爱对象 | 朋友 | 家人 | 同事 | 其他" } ], "events": [ { "id": "e1", "type": "事件类型", "summary": "事件描述(包含情感细节)", "occurred_at": "ISO 时间", "participants": ["p1"], "emotional_tone": "positive | neutral | negative", "importance": 1-10 } ], "topics": [ { "id": "t1", "name": "主题名称" } ], "relations": [ { "source": "p1", "target": "p2", "type": "关系类型", "summary": "关系描述" } ] } ## 关系类型参考 - 用户与恋爱对象:LOVES, DATING, MARRIED_TO, ENGAGED_TO, BROKEN_UP_WITH, CONFLICT_WITH - 用户与他人:FRIENDS_WITH, COLLEAGUE_OF, CLASSMATE_OF, SIBLING_OF - 恋爱对象与他人:FRIENDS_WITH, COLLEAGUE_OF, FAMILY_OF, CONFLICT_WITH ## 重要规则 1. **用户识别**:"我"=用户本人,固定 ID 为"user" 2. **恋爱对象**:"女朋友/男朋友/她/他"=恋爱对象,固定 ID 为"partner" 3. **其他人物**:不需要详细描述,只记录名字和与核心人物的关系 4. **实体去重**:如果文本中提到的人已存在于"已有实体"列表中,**复用相同的 ID** 5. **时间标准化**:occurred_at 使用 ISO 格式 6. **情感标注**:emotional_tone 标注事件的情感倾向(positive/neutral/negative) 只返回 JSON,不要有其他文字。`; const messages = [ { role: "system", content: systemPrompt }, { role: "user", content: `${existingContext}\n\n## 待分析文本\n${text}` } ]; console.log("[DEBUG] LLM request messages:", JSON.stringify(messages)); const result = await this.chat(messages, 0.3); const content = result?.choices?.[0]?.message?.content; console.log("[DEBUG] LLM raw response:", content); if (!content) { throw createHttpError(500, "LLM 返回内容为空"); } let parsed; try { const jsonMatch = content.match(/\{[\s\S]*\}/); parsed = jsonMatch ? JSON.parse(jsonMatch[0]) : JSON.parse(content); } catch (e) { throw createHttpError(500, `LLM 返回格式错误:${content.substring(0, 200)}`); } return parsed; } isEmptyAnalysis(data) { return !data || (!Array.isArray(data.persons) || data.persons.length === 0) && (!Array.isArray(data.events) || data.events.length === 0) && (!Array.isArray(data.topics) || data.topics.length === 0) && (!Array.isArray(data.relations) || data.relations.length === 0); } /** * 基于图谱数据生成恋爱决策建议 */ async generateRelationshipAdvice(data) { const systemPrompt = `你是一个恋爱关系咨询师,擅长分析情侣关系模式并给出专业建议。 请根据以下数据生成恋爱建议: 1. 事件统计:各类事件的数量和情感倾向 2. 最近事件:最近发生的 10 个事件 3. 第三方影响:朋友/家人对关系的参与程度 ## 输出格式(JSON) { "relationship_health": "healthy | neutral | concerning", "summary": "关系状态总结(100 字以内)", "patterns": [ { "pattern": "识别出的模式(如'频繁争吵'、'缺乏沟通')", "evidence": "支持该模式的事件或数据", "suggestion": "针对性建议" } ], "third_party_influence": "第三方影响分析", "action_items": [ "具体可执行的建议 1", "具体可执行的建议 2", "具体可执行的建议 3" ], "positive_notes": "关系中积极的方面(鼓励)" }`; const userMessage = ` ## 事件统计 ${JSON.stringify(data.eventStats, null, 2)} ## 最近事件 ${JSON.stringify(data.recentEvents, null, 2)} ## 第三方参与 ${JSON.stringify(data.thirdParty, null, 2)} 请分析这段恋爱关系的健康状况,并给出专业建议。`; const result = await this.chat([ { role: 'system', content: systemPrompt }, { role: 'user', content: userMessage } ], 0.5); const content = result?.choices?.[0]?.message?.content; try { const jsonMatch = content.match(/\{[\s\S]*\}/); return jsonMatch ? JSON.parse(jsonMatch[0]) : JSON.parse(content); } catch (e) { return { relationship_health: 'neutral', summary: content || '无法生成详细分析', patterns: [], action_items: ['建议与伴侣坦诚沟通', '关注彼此的情感需求'], positive_notes: '每段关系都有起伏,重要的是共同努力' }; } } }