Compare commits
5
Commits
905973d739
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
738ef1697c | ||
|
|
45cf00bf14 | ||
|
|
b41779c051 | ||
|
|
e08a17ffa0 | ||
|
|
cd41676ee3 |
+1
-1
@@ -12,7 +12,7 @@ services:
|
||||
- ADMINS=${ADMINS}
|
||||
- DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}
|
||||
- PORT=3000
|
||||
- VIRTUAL_HOST=ai.foresh.com
|
||||
- VIRTUAL_HOST=xiaocai.ai.foresh.com
|
||||
- VIRTUAL_PORT=3000
|
||||
- CERT_NAME=default
|
||||
networks:
|
||||
|
||||
+5
-3
@@ -19,10 +19,11 @@ function getErrorMsg(status, data) {
|
||||
}
|
||||
}
|
||||
|
||||
async function callDeepSeek(messages, userId, temperature = 0.1, jsonMode = true) {
|
||||
async function callDeepSeek(messages, userId, temperature = 0.1, jsonMode = true, tools) {
|
||||
console.log('调用 DeepSeek...');
|
||||
var body = { model: 'deepseek-v4-flash', messages: messages, temperature: temperature, stream: false, thinking: { type: 'disabled' } };
|
||||
if (jsonMode) body.response_format = { type: 'json_object' };
|
||||
if (tools && tools.length) body.tools = tools;
|
||||
if (jsonMode && !(tools && tools.length)) body.response_format = { type: 'json_object' };
|
||||
if (userId) body.user_id = sanitizeUserId(userId);
|
||||
var res = await fetch('https://api.deepseek.com/chat/completions', {
|
||||
method: 'POST',
|
||||
@@ -50,7 +51,8 @@ async function callDeepSeek(messages, userId, temperature = 0.1, jsonMode = true
|
||||
// 输入消息大小日志
|
||||
//var inputChars = JSON.stringify(messages).length;
|
||||
//console.log('输入大小: ' + inputChars + ' 字符, ' + messages.length + ' 条消息');
|
||||
return data.choices[0].message.content;
|
||||
// 返回整个 message 对象,可能包含 content 或 tool_calls(供上层决定走工具还是 JSON)
|
||||
return data.choices[0].message;
|
||||
}
|
||||
|
||||
module.exports = { callDeepSeek };
|
||||
|
||||
+33
-90
@@ -1,97 +1,40 @@
|
||||
// AI Prompt 模板 — 模块化拼接
|
||||
// AI Prompt 模板 — 工具模式(阶段一)
|
||||
// 新建采购 / 查询 / 聊天 / 忽略 走 Function Tool 调用;
|
||||
// 删除 / 清空 保留 JSON 输出路径(旧逻辑兜底,见 messages.js)。
|
||||
// 图片关联由后端决定,不在 prompt 中引导模型输出 use_recent_image。
|
||||
|
||||
function buildSystemPrompt(username, isAdmin) {
|
||||
const role = '你是智能财务助手"小财"。结合对话历史理解用户意图,只返回JSON。';
|
||||
const role = '你是智能财务助手"小财"。当前用户:' + username + ',管理员状态:' + isAdmin + '(true=管理员,false=普通用户)。';
|
||||
|
||||
const intent = [
|
||||
'意图分类:',
|
||||
'- 采购/付款/发票相关(新建):action="purchase",提取字段:purchase_item, quantity(默认1), unit_price(默认0), freight(默认0), payment_method(淘宝默认支付宝), invoice_type(默认无票), status, applicant, remarks, created_time',
|
||||
'- 查询汇总:action="query"',
|
||||
' 触发条件:当用户询问“买过什么/有没有买过/查一下/查查看/看看/搜索/找一下/列出/我买过/查查/搜搜”等查询历史采购记录的意图时,必须返回 action="query"。',
|
||||
' 示例:',
|
||||
' "查查看我买过记账本么?" → {"action":"query"}',
|
||||
' "我买过扩展坞吗?" → {"action":"query"}',
|
||||
' "绿联拓展坞有没有买过?" → {"action":"query"}',
|
||||
' 注意:即使物品名听起来奇怪或可能不存在,也绝对不能返回 ignore,必须返回 query 让系统去匹配。',
|
||||
'- 聊天:action="chat",reply简短回复',
|
||||
'- 删除指定物品:action="delete",delete_item为物品名',
|
||||
'- 清空所有采购数据:action="clear_all"',
|
||||
'- 忽略(仅当完全无关):action="ignore"',
|
||||
' 适用场景:纯闲聊(如“你好”“今天天气如何”)或与财务、采购完全无关的话题。',
|
||||
' 严禁将任何包含“买过”“查查看”“有没有”“找一下”等查询意图的句子判为 ignore,必须归为 query。'
|
||||
const guidance = [
|
||||
'请根据用户消息选择合适的方式回应:',
|
||||
'',
|
||||
'一、调用工具(适用于以下场景):',
|
||||
'1. 新建采购记录 → 调用工具 create_purchase。',
|
||||
' 触发条件:用户明确表达了购买/采购/下单/付款/买了/花了等消费意图,且不是在查询历史。',
|
||||
' 物品名、数量、单价、运费、支付方式、发票类型、状态、备注等字段含义见工具参数定义。',
|
||||
' 关键规则1:如果用户没有明确提供单价(价格),不要调用 create_purchase,而应调用 reply_chat 追问价格。',
|
||||
' 关键规则2:用户如提及"交了""付了""已付款""已收货""已完成"等,必须把 status 填为对应状态,不要漏填。',
|
||||
' 关键规则3:一次消息中包含多笔采购时,必须为每一笔分别调用一次 create_purchase,不要合并或遗漏。',
|
||||
'2. 查询历史采购记录 → 调用工具 query_purchases,参数 keywords 为物品名或关键词(查全部则用空字符串)。',
|
||||
' 触发条件:用户询问"买过什么/有没有买过/查一下/找找看/搜索/列表/我买过/看看"等查询意图。',
|
||||
' 即使物品名听起来奇怪或可能不存在,也必须用 query_purchases,绝不能调 create_purchase。',
|
||||
'3. 纯闲聊、引导手动修改、追问价格、权限不足拒绝 → 调用工具 reply_chat,参数 reply 为要显示给用户的回复。',
|
||||
'4. 完全无关的消息(纯符号、纯表情、明显不是对助手说的话)→ 调用工具 ignore。',
|
||||
'',
|
||||
'二、直接输出 JSON(不要调用任何工具):',
|
||||
'5. 删除采购记录 → 输出 {"action":"delete","delete_item":"物品名","quantity":数量,"amount":金额},其中 quantity/amount 可选。',
|
||||
'6. 清空所有采购数据 → 输出 {"action":"clear_all"}。仅管理员(isAdmin=true)可执行;',
|
||||
' 非管理员必须输出 {"action":"chat","reply":"仅管理员可执行此操作"}。',
|
||||
'',
|
||||
'三、通用规则:',
|
||||
'- 用户要求修改、更正、调整、更新已有采购记录的任何意图(改数量、改价格、改状态、补发票、加备注等),一律用 reply_chat 引导用户手动操作,不要尝试更新记录。',
|
||||
'- 修改意图的统一回复模板:"如需修改记录,请前往采购清单页面,找到对应条目后手动编辑。"',
|
||||
'- 如果用户同时表达了购买和查询,优先判断为购买(create_purchase),除非提问明显是查询历史。',
|
||||
'- 若无法确定用户意图,默认调用 reply_chat 并给出友好回复,绝不忽略。'
|
||||
].join('\n');
|
||||
|
||||
const queryPriority = [
|
||||
'🔍 查询优先原则:',
|
||||
'- 用户任何以“查”“找”“看”“搜索”“有没有”“买过”开头的消息,无论是否完整或物品名是否已知,都必须返回 action="query",不得返回 ignore、chat 或 ask。',
|
||||
'- 系统会自行处理模糊匹配,你无需判断物品是否存在。'
|
||||
].join('\n');
|
||||
|
||||
const permission = [
|
||||
'⚠️ 权限规则:',
|
||||
'- 当前用户' + username + ',管理员状态:' + isAdmin + '。仅管理员可执行删除/清空操作。',
|
||||
'- 如果用户要求删除或清空,且 isAdmin 为 false,你必须返回 action="chat" 并说明"仅管理员可操作"。',
|
||||
'- 如果 isAdmin 为 true,正常返回 delete 或 clear_all。'
|
||||
].join('\n');
|
||||
|
||||
const deleteRules = [
|
||||
'⚠️ 删除规则:',
|
||||
'- 特殊批量删除:delete_item="__AMOUNT_ZERO__"(金额为0)、"__NULL_NAME__"(空名称)',
|
||||
'- 精确删除:用户指定数量/金额时同时返回 quantity/amount 字段,只匹配一条',
|
||||
'- 不要返回 action="ask" 处理删除请求'
|
||||
].join('\n');
|
||||
|
||||
const attachmentRules = [
|
||||
'⚠️ 附件规则:',
|
||||
'- 图片+物品名("这是XX的图片")→ 只返回 purchase_item,绝对不要 is_new/quantity/unit_price',
|
||||
'- 引用历史图片("上面/前面/刚刚那张""存入附件/添加到附件/加到XX的附件/关联到")→ use_recent_image: true,**同时必须提供** purchase_item',
|
||||
'- 纯图片+物品名不是新建采购,系统自动关联附件'
|
||||
].join('\n');
|
||||
|
||||
const prohibitionRules = [
|
||||
'⚠️ 绝对禁止:',
|
||||
'- 图片+物品名请求 → 只能返回 purchase_item,禁止返回 is_new、quantity、unit_price、amount'
|
||||
].join('\n');
|
||||
|
||||
const modifyGuide = [
|
||||
'⚠️ 修改引导规则(极其重要!):',
|
||||
'- 聊天窗口仅用于**新建**采购记录和查询汇总。',
|
||||
'- 如果用户要求**修改/更正/调整/改一下/更新/纠正**已有采购记录,或者说某商品已付款、已收到等变更已有采购记录的意图 → 你必须返回 action="chat",reply引导用户去采购清单页面手动修改。',
|
||||
' 示例回复:"请在采购清单中找到对应记录,点击进入详情页面直接修改。修改后点击保存即可。"',
|
||||
'- 如果用户要求修改,你绝不要尝试去匹配或更新已有记录,只能引导用户手动操作。'
|
||||
].join('\n');
|
||||
|
||||
const purchaseRules = [
|
||||
'⚠️ 采购规则:',
|
||||
'- 聊天默认为**新建**采购记录。只要用户提到购买/采购/下单/付款/买了/花了,就创建新记录。',
|
||||
'- 采购状态(status)只能从以下 4 个值中选择,绝不允许自创:',
|
||||
' * "待付款" — 采购已记录,等待付款',
|
||||
' * "已付款" — 已完成支付,等待收货',
|
||||
' * "已收货" — 已收到货物',
|
||||
' * "已完成" — 发票已收,全部结束',
|
||||
'- 状态映射:',
|
||||
' * "付了/已付/付款了/买了/花了"等明确已付款完成意图的 → "已付款"',
|
||||
' * "到了/收到货了"等明确已收到货意图的 → "已收货"',
|
||||
' * "发票到/全搞定/完结"等明确有发票或结束意图的 → "已完成"',
|
||||
' * 新建不指定 → "待付款"',
|
||||
'- 只有明确消费意图(购买/采购/下单/付款/买了/花了)才返回 action="purchase"',
|
||||
'- 纯陈述且无查询意图 → action="ignore"',
|
||||
'- 若陈述中包含“查/看/找/搜索/有没有/买过”等查询关键词 → 必须 action="query"',
|
||||
'- 信息不完整 → action="ask" 追问',
|
||||
'- amount 由系统自动计算(quantity × unit_price + freight),AI 无需提供',
|
||||
'- 无法确定物品名 → action="ask" 追问'
|
||||
].join('\n');
|
||||
|
||||
return [
|
||||
role,
|
||||
intent,
|
||||
queryPriority,
|
||||
permission,
|
||||
deleteRules,
|
||||
attachmentRules,
|
||||
prohibitionRules,
|
||||
modifyGuide,
|
||||
purchaseRules
|
||||
].join('\n');
|
||||
return [role, guidance].join('\n\n');
|
||||
}
|
||||
|
||||
module.exports = { buildSystemPrompt };
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// DeepSeek Function Tools 定义
|
||||
// 阶段一:迁移 purchase / chat / ignore 为工具调用;query 也提供工具入口(避免查询意图误判成新建),
|
||||
// 但其汇总逻辑仍复用 handlers.js 的 query 分支(不做二次调用回填)。
|
||||
// delete / clear_all 保留旧 JSON 输出路径(在 prompt 中引导走 JSON,见 prompt.js)。
|
||||
// 图片关联不在工具参数中暴露,由后端根据消息文本/附件自行决定(见 handlers.js 的 create_purchase 分支)。
|
||||
|
||||
const STATUS_ENUM = ['待付款', '已付款', '已收货', '已完成'];
|
||||
const INVOICE_ENUM = ['无票', '电子票'];
|
||||
|
||||
const createPurchaseTool = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'create_purchase',
|
||||
description:
|
||||
'新建一条采购记录。仅在用户明确表达购买/采购/下单/付款/买了/花了等消费意图且不是查询历史时调用。' +
|
||||
'注意:如果用户没有明确提供单价(价格),不要调用本工具,应改为调用 reply_chat 追问价格。',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
purchase_item: { type: 'string', description: '物品名称,必填' },
|
||||
quantity: { type: 'number', description: '数量,默认 1' },
|
||||
unit_price: { type: 'number', description: '单价。必须大于 0,若用户未明确给出价格请不要调用本工具,改为 reply_chat 追问。' },
|
||||
freight: { type: 'number', description: '运费,默认 0' },
|
||||
payment_method: { type: 'string', description: '支付方式,如 支付宝/微信,未提及则默认未指定' },
|
||||
invoice_type: { type: 'string', enum: INVOICE_ENUM, description: '发票类型,默认无票' },
|
||||
status: { type: 'string', enum: STATUS_ENUM, description: '付款/收货状态。若用户明确说了已付款、交了钱、付了款、已收、已到货、已完成等,必须主动填对应值(如已付款),不要省略。仅当完全没提状态时才用默认待付款。' },
|
||||
remarks: { type: 'string', description: '备注,如购买平台等' }
|
||||
},
|
||||
required: ['purchase_item']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const replyChatTool = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'reply_chat',
|
||||
description:
|
||||
'直接回复一段文本给用户。用于以下情况:' +
|
||||
'1) 纯闲聊(如打招呼、问天气等);' +
|
||||
'2) 用户要求修改/更正/调整/更新已有采购记录时,引导其去采购清单页面手动编辑;' +
|
||||
'3) 用户有购买意图但缺少价格信息时,追问价格;' +
|
||||
'4) 权限不足需要拒绝(如非管理员要求清空记录);' +
|
||||
'5) 其他需要回复但不属于新建采购或查询/删除/清空的情况。',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
reply: { type: 'string', description: '显示给用户的回复内容' }
|
||||
},
|
||||
required: ['reply']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const queryPurchasesTool = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'query_purchases',
|
||||
description:
|
||||
'查询历史采购记录。当用户询问"买过什么/有没有买过/查一下/找找看/搜索/列表/我买过/看看"等查询历史采购的意图时调用。' +
|
||||
'即使物品名听起来奇怪或可能不存在,也必须调用本工具,绝不能调用 create_purchase 或 ignore。' +
|
||||
'调用后系统会搜索本地数据库并生成回答,本工具不直接返回查询结果。',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
keywords: { type: 'string', description: '用户想查找的物品名或关键词;若查询全部记录则设为空字符串 ""' }
|
||||
},
|
||||
required: ['keywords']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const ignoreTool = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'ignore',
|
||||
description:
|
||||
'忽略当前消息,不产生任何回复。用于纯符号、纯表情、明显不是对助手说的话等无关消息。',
|
||||
parameters: { type: 'object', properties: {} }
|
||||
}
|
||||
};
|
||||
|
||||
const TOOLS = [createPurchaseTool, replyChatTool, queryPurchasesTool, ignoreTool];
|
||||
|
||||
module.exports = { TOOLS };
|
||||
+8
-15
@@ -7,7 +7,7 @@ const { broadcastToRoom } = require('./ws');
|
||||
// 对话历史(内存)
|
||||
const conversationHistory = new Map();
|
||||
function getHistory(roomId) { if (!conversationHistory.has(roomId)) conversationHistory.set(roomId, []); return conversationHistory.get(roomId); }
|
||||
function addToHistory(roomId, role, content) { var h = getHistory(roomId); h.push({ role: role, content: content }); if (h.length > 60) conversationHistory.set(roomId, h.slice(-30)); }
|
||||
function addToHistory(roomId, role, content) { var h = getHistory(roomId); h.push({ role: role, content: content }); if (h.length > 16) conversationHistory.set(roomId, h.slice(-8)); }
|
||||
|
||||
// 待处理操作
|
||||
const pendingActions = new Map();
|
||||
@@ -96,10 +96,6 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) {
|
||||
}
|
||||
|
||||
if (aiResult.action === 'purchase') {
|
||||
if (aiResult.use_recent_image && (!aiResult.purchase_item || aiResult.purchase_item === 'null')) {
|
||||
const lastPurchase = db.prepare("SELECT item FROM purchases WHERE room_id = ? AND item IS NOT NULL AND item != '' ORDER BY created_at DESC LIMIT 1").get(roomId);
|
||||
if (lastPurchase) { aiResult.purchase_item = lastPurchase.item; }
|
||||
}
|
||||
const item = aiResult.purchase_item;
|
||||
if (!item || item === 'null' || item === 'undefined' || item.trim().length < 2) {
|
||||
storeAndBroadcastText(roomId, '小财', '请提供具体的物品名称。');
|
||||
@@ -108,13 +104,9 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) {
|
||||
}
|
||||
|
||||
let usedRecentImage = false;
|
||||
if (aiResult.use_recent_image) {
|
||||
const recentMsg = db.prepare("SELECT attachments FROM messages WHERE room_id = ? AND attachments IS NOT NULL AND attachments != '' AND attachments != '[]' ORDER BY id DESC LIMIT 1").get(roomId);
|
||||
if (recentMsg) {
|
||||
try { const files = JSON.parse(recentMsg.attachments); if (files.length) { attachments = files; usedRecentImage = true; } } catch(e) {}
|
||||
}
|
||||
}
|
||||
if (!usedRecentImage && !attachments?.length && /(图片|附件|加到|存入|关联|作为附件)/.test(originalText)) {
|
||||
// 图片关联:由后端根据消息文本判断,不再依赖模型返回 use_recent_image。
|
||||
// 用户消息提及图片/附件等词且本次未带附件时,自动关联最近的图片消息。
|
||||
if (!attachments?.length && /(图片|附件|加到|存入|关联|作为附件)/.test(originalText)) {
|
||||
const recentMsg = db.prepare("SELECT attachments FROM messages WHERE room_id = ? AND attachments IS NOT NULL AND attachments != '' AND attachments != '[]' ORDER BY id DESC LIMIT 1").get(roomId);
|
||||
if (recentMsg) {
|
||||
try { const files = JSON.parse(recentMsg.attachments); if (files.length) { attachments = files; usedRecentImage = true; } } catch(e) {}
|
||||
@@ -138,7 +130,7 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) {
|
||||
}
|
||||
|
||||
const id = uuidv4();
|
||||
const paymentMethod = aiResult.payment_method || (aiResult.method === '淘宝' ? '支付宝' : (aiResult.method || '未指定'));
|
||||
const paymentMethod = aiResult.payment_method || '未指定';
|
||||
const invoiceType = aiResult.invoice_type || '无票';
|
||||
const status = aiResult.status || '待付款';
|
||||
const applicant = aiResult.applicant || username;
|
||||
@@ -147,7 +139,7 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) {
|
||||
id, roomId, item, quantity, unitPrice, amount, freight, paymentMethod, invoiceType, status, applicant, remarks, createdTime
|
||||
);
|
||||
db.prepare('INSERT INTO purchase_history (purchase_id, action, user, timestamp) VALUES (?,?,?,?)').run(id, '创建采购:' + item + ',数量' + quantity + ',金额¥' + amount + ',状态' + status, '小财', now);
|
||||
replyText = '✅ 已记录采购:' + item + ',数量 ' + quantity + ',金额 ¥' + amount + ',状态 ' + status;
|
||||
replyText = '✅ 已记录采购:' + item + ',数量 ' + quantity + ',金额 ¥' + amount + ',状态 ' + status + ' <a href="#" onclick="openDetail(\'' + id + '\')">查看详情</a>';
|
||||
purchase = { id };
|
||||
|
||||
if (attachments?.length) {
|
||||
@@ -190,7 +182,8 @@ async function callDeepSeekForSummary(prompt, userId) {
|
||||
{ role: 'system', content: '你是一个财务助手,请根据采购记录生成简洁回复。' },
|
||||
{ role: 'user', content: prompt }
|
||||
];
|
||||
return callDeepSeek(messages, userId, 0.3, false);
|
||||
const msg = await callDeepSeek(messages, userId, 0.3, false);
|
||||
return msg && typeof msg === 'object' ? (msg.content || '') : (msg || '');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
<script>
|
||||
// 版本号
|
||||
(function() {
|
||||
document.getElementById('version-text').textContent = 'v2.8.2-260729';
|
||||
document.getElementById('version-text').textContent = 'v3.0.1-260829';
|
||||
})();
|
||||
document.addEventListener('input', function(e) {
|
||||
if (e.target.id === 'msg-input' || e.target.id === 'purchase-msg-input') {
|
||||
|
||||
@@ -21,6 +21,8 @@ function initWebSocket() {
|
||||
}
|
||||
} else if (data.type === 'query_pending') {
|
||||
showTempBubble(data.text);
|
||||
} else if (data.type === 'clear_temp') {
|
||||
clearTempBubble();
|
||||
} else if (data.type === 'purchase_updated') {
|
||||
if (Store.currentRoom) loadPurchases();
|
||||
updateSummaryPreview();
|
||||
|
||||
@@ -5,6 +5,7 @@ const { broadcastToRoomExcludeSelf, broadcastToRoom } = require('../ws');
|
||||
const { buildSystemPrompt } = require('../ai/prompt');
|
||||
const { validate, normalize } = require('../ai/validator');
|
||||
const { callDeepSeek } = require('../ai/client');
|
||||
const { TOOLS } = require('../ai/tools');
|
||||
const { getHistory, addToHistory, hasPendingAction, handleAIResult, executePendingAction } = require('../handlers');
|
||||
const db = require('../db');
|
||||
const { timestamp } = require('../utils');
|
||||
@@ -47,9 +48,11 @@ router.post('/:roomId/messages', authMiddleware, async (req, res) => {
|
||||
const aiResponse = await analyzeWithDeepSeek(text || '', historyMessages, username, req.user.isAdmin, roomId);
|
||||
if (aiResponse) {
|
||||
var responses = Array.isArray(aiResponse) ? aiResponse : [aiResponse];
|
||||
var allIgnore = responses.every(function(r) { return r.action === 'ignore'; });
|
||||
responses.forEach(function(r) {
|
||||
if (r.action !== 'ignore') handleAIResult(r, roomId, username, text || '', attachments || []);
|
||||
});
|
||||
if (allIgnore) broadcastToRoom(roomId, { type: 'clear_temp' });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('AI 分析失败:', e.message);
|
||||
@@ -67,7 +70,39 @@ async function analyzeWithDeepSeek(text, historyMessages, username, isAdmin, roo
|
||||
];
|
||||
historyMessages.forEach(function(m) { messages.push(m); });
|
||||
messages.push({ role: 'system', content: '当前服务器时间:' + new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }) });
|
||||
const content = await callDeepSeek(messages, username);
|
||||
const message = await callDeepSeek(messages, username, 0.1, true, TOOLS);
|
||||
|
||||
// 1) 工具调用路径:模型调用了 create_purchase / query_purchases / reply_chat / ignore
|
||||
if (message && Array.isArray(message.tool_calls) && message.tool_calls.length) {
|
||||
message.tool_calls.forEach(function(tc, i) {
|
||||
const fn = tc.function || {};
|
||||
console.log('🤖 AI 工具调用[' + i + ']: ' + (fn.name || '?') + ' args=' + (fn.arguments || '').substring(0, 300));
|
||||
});
|
||||
const results = [];
|
||||
message.tool_calls.forEach(function(tc) {
|
||||
const fn = tc.function || {};
|
||||
const name = fn.name || '';
|
||||
let args = {};
|
||||
try { args = JSON.parse(fn.arguments || '{}'); } catch(e) { console.warn('⚠️ 工具参数解析失败:', name, (fn.arguments || '').substring(0, 100)); args = {}; }
|
||||
if (name === 'create_purchase') {
|
||||
// 工具参数已是结构化对象,补上 action 字段以复用现有 handleAIResult 的 purchase 分支
|
||||
results.push(Object.assign({ action: 'purchase' }, args));
|
||||
} else if (name === 'query_purchases') {
|
||||
// 复用现有 query 分支(本地查库 + 二次汇总),不依赖模型返回结果
|
||||
results.push({ action: 'query', keywords: args.keywords || '' });
|
||||
} else if (name === 'reply_chat') {
|
||||
results.push({ action: 'chat', reply: args.reply || '好的。' });
|
||||
} else {
|
||||
// ignore 或未知工具 → 忽略
|
||||
console.warn('⚠️ 未识别的工具名:', name);
|
||||
results.push({ action: 'ignore' });
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
// 2) 纯文本/JSON 路径:query / delete / clear_all 等按旧逻辑解析
|
||||
const content = (message && typeof message === 'object') ? (message.content || '') : (message || '');
|
||||
console.log('🤖 AI 返回:', content.substring(0, 200));
|
||||
try {
|
||||
var cleaned = content.replace(/```json|```/g, '').trim();
|
||||
|
||||
Reference in New Issue
Block a user