import { Anthropic } from "@anthropic-ai/sdk"; import { Message, MessageParam, Tool } from "@anthropic-ai/sdk/resources/messages/messages.mjs"; import { ComponentService } from '@/lib/services/component-service'; import { ConversationService } from '@/lib/services/conversation-service'; const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; const ANTHROPIC_BASE_URL = process.env.ANTHROPIC_BASE_URL; if (!ANTHROPIC_API_KEY) { throw new Error("ANTHROPIC_API_KEY is not set"); } export class AIClient { private anthropic: Anthropic; private tools: Tool[] = [ { name: "query-components", description: "查询PC配件信息,支持按类型、品牌、价格范围、关键词等条件搜索。单次最多返回5个配件。", input_schema: { type: "object", properties: { type: { type: "string", enum: ["CPU", "内存", "硬盘", "主板", "显卡", "机箱"], description: "配件类型" }, brand: { type: "string", description: "品牌名称" }, minPrice: { type: "number", description: "最低价格" }, maxPrice: { type: "number", description: "最高价格" }, search: { type: "string", description: "搜索关键词,会在名称、品牌、型号、描述中搜索" }, page: { type: "integer", description: "页码,默认为1" }, } } }, { name: "show-components", description: "根据提供的配件ID列表,向用户以卡片形式展示一个或多个具体型号的配件。如果你提到了某些特定配件,请用此工具更好地向用户展示。", input_schema: { type: "object", properties: { component_ids: { type: "array", description: "一个包含一个或多个要展示的配件ID的数组。这些ID来自`query-components`工具的查询结果。", items: { type: "string", description: "单个配件的唯一ID" } } }, required: ["component_ids"] } } ]; constructor() { this.anthropic = new Anthropic({ apiKey: ANTHROPIC_API_KEY, baseURL: ANTHROPIC_BASE_URL, }); } // 查询配件 private async queryComponents(args: any): Promise { try { const result = await ComponentService.queryComponents({ type: args.type, brand: args.brand, minPrice: args.minPrice, maxPrice: args.maxPrice, search: args.search, page: args.page || 1, limit: 5 }) if (result.components.length === 0) { return "未找到符合条件的配件" } // 返回格式化的结果 return JSON.stringify(result.components.map(c => ({ id: c.id, name: c.name, brand: c.brand, model: c.model, price: c.price, description: c.description, stock: c.stock, specifications: c.specifications, }))) } catch (error) { console.error('查询配件失败:', error) return `查询配件时发生错误: ${error instanceof Error ? error.message : '未知错误'}` } } async *processQuery( query: string, userId: string, conversationId?: string ): AsyncGenerator { let messages: MessageParam[]; let currentConversationId = conversationId; // 如果提供了对话ID,加载现有对话 if (conversationId) { const conversation = await ConversationService.getConversation(conversationId, userId); if (conversation) { messages = [...conversation.messages]; // 添加新的用户消息 messages.push({ role: "user", content: query }); } else { throw new Error('对话不存在或无权限访问'); } } else { // 创建新对话 currentConversationId = await ConversationService.createConversation(userId, query); // 发送新对话ID给前端 yield `conversation_id:${currentConversationId}`; // 获取新创建的对话消息 const conversation = await ConversationService.getConversation(currentConversationId, userId); if (!conversation) { throw new Error('创建对话失败'); } messages = conversation.messages; } try { let maxIterations = 20; let currentIteration = 0; while (currentIteration < maxIterations) { currentIteration++; console.log("Send Req", messages); const response = await this.anthropic.messages.create({ model: ["claude-sonnet-4-20250514", "claude-3-5-haiku-20241022"][1], max_tokens: 2048, messages, tools: this.tools, stream: true, }); let tempMsg: Message | null = null // 处理流式响应 for await (const chunk of response) { console.log(chunk); switch (chunk.type) { case "message_start": tempMsg = { id: chunk.message.id, type: chunk.message.type, role: chunk.message.role, content: [], model: chunk.message.model, usage: chunk.message.usage, stop_reason: chunk.message.stop_reason, stop_sequence: chunk.message.stop_sequence, } break case "message_delta": tempMsg = { ...tempMsg || {}, stop_reason: chunk.delta.stop_reason, stop_sequence: chunk.delta.stop_sequence, // @ts-ignore usage: chunk.usage, } break case "message_stop": break case "content_block_start": const contentBlock = chunk.content_block; switch (contentBlock.type) { case "text": tempMsg?.content.push({ type: contentBlock.type, text: contentBlock.text, citations: contentBlock.citations || [], }) yield contentBlock.text break case "tool_use": tempMsg?.content.push({ type: contentBlock.type, id: contentBlock.id, name: contentBlock.name, input: "", }); yield `\n\n**使用工具**: ${contentBlock.name}\n\n\`\`\`\n` break } break case "content_block_delta": const contentBlockDelta = chunk.delta; const targetContent = tempMsg?.content[chunk.index]; switch (contentBlockDelta.type) { case "text_delta": if (targetContent && targetContent.type === "text") { targetContent.text += contentBlockDelta.text; yield contentBlockDelta.text; } else console.error("文本块不匹配,无法追加内容", targetContent); break case "input_json_delta": if (targetContent && targetContent.type === "tool_use") { targetContent.input += contentBlockDelta.partial_json || ""; yield contentBlockDelta.partial_json || ""; } else console.error("工具调用块不匹配,无法追加输入", targetContent); break } break case "content_block_stop": let type = tempMsg?.content[chunk.index].type; if (type === "tool_use") { yield `\n\`\`\``; } break } } if (!tempMsg) { console.error("没有接收到有效的消息内容"); yield "\n\n❌ 抱歉,处理您的请求时没有返回有效的消息内容。"; break; } messages.push({ role: tempMsg.role, content: tempMsg.content.map(c => { if (c.type === "text") { return { type: c.type, text: c.text, }; } else if (c.type === "tool_use") { return { id: c.id, type: c.type, name: c.name, input: JSON.parse(c.input as string) }; } else return c; }) }); if (!tempMsg.content.find(c => c.type === "tool_use")) { break } let tempUserMsg: MessageParam = { role: "user", content: [] } let needRerun = false; for (const toolUse of tempMsg.content.filter(c => c.type === "tool_use")) { let toolArgs = JSON.parse(toolUse.input as string || "{}"); console.log(toolArgs); switch (toolUse.name) { case "query-components": // @ts-ignore tempUserMsg.content.push({ type: "tool_result", tool_use_id: toolUse.id, content: await this.queryComponents(toolArgs) }); needRerun = true; yield 'next_block' break case "show-components": let components = await ComponentService.getComponentsByIds(toolArgs.component_ids) // @ts-ignore tempUserMsg.content.push({ type: "tool_result", tool_use_id: toolUse.id, content: `成功向用户展示了 ${JSON.stringify( (await ComponentService.getComponentsByIds(toolArgs.component_ids)).map((c, index) => (c && { id: c.id, name: c.name, brand: c.brand, model: c.model, price: c.price, description: c.description, stock: c.stock, specifications: c.specifications, } || { id: toolArgs.component_ids[index].id, name: "未知配件" }) ) )}` }); needRerun = true; yield `show_card: ${JSON.stringify(components)}`; yield 'next_block' break; default: console.error(`未知工具调用: ${toolUse.name}`); } // 继续处理下一个工具调用 } // 将工具调用结果添加到消息列表中 messages.push(tempUserMsg); if (!needRerun) break } if (currentIteration >= maxIterations) { yield "\n\n⚠️ 达到最大处理轮次,对话结束。"; } // 保存更新后的对话到数据库 if (currentConversationId) { await ConversationService.updateConversationMessages(currentConversationId, userId, messages); } } catch (error) { console.error('处理查询时发生错误:', error); yield `\n\n❌ 抱歉,处理您的请求时遇到了问题: ${error instanceof Error ? error.message : '未知错误'}`; // 即使出错也要保存对话 if (currentConversationId) { try { await ConversationService.updateConversationMessages(currentConversationId, userId, messages); } catch (saveError) { console.error('保存对话失败:', saveError); } } } } /** * 获取用户的对话历史 */ async getUserConversations(userId: string, page = 1, limit = 20) { return await ConversationService.getUserConversations(userId, page, limit); } /** * 获取特定对话的详情 */ async getConversation(conversationId: string, userId: string) { return await ConversationService.getConversation(conversationId, userId); } /** * 删除对话 */ async deleteConversation(conversationId: string, userId: string) { return await ConversationService.deleteConversation(conversationId, userId); } /** * 更新对话标题 */ async updateConversationTitle(conversationId: string, userId: string, title: string) { return await ConversationService.updateConversationTitle(conversationId, userId, title); } }