/** * 图谱 MCP 工具集 — Phase 1 * * 6 个工具供 Master Agent 通过 [TOOL_CALL:] 调用: * create_person / get_person / link_persons / * create_event / search_graph / get_relationship_path */ const { getGraphStore } = require('../../../db/GraphStore'); // ─── 辅助函数 ──────────────────────────────────────────────────── function store() { return getGraphStore(); } /** 将节点对象格式化为 AI 可读的文本摘要 */ function nodeSummary(node) { if (!node) return '(未找到)'; const { _type, _analysisStatus, _createdAt, _updatedAt, id, ...fields } = node; return JSON.stringify({ type: _type, status: _analysisStatus, ...fields }, null, 2); } /** 解析 name_or_id:先尝试按 id 精确查,再按名称模糊查 */ function resolvePerson(nameOrId) { const g = store(); let node = g.getNode(nameOrId); if (!node) { const results = g.findByName(nameOrId, 'Person'); node = results[0] || null; } return node; } // ─── 工具定义 ──────────────────────────────────────────────────── function createPersonTool() { return { name: 'create_person', description: '在知识图谱中创建或更新一个 Person 节点(联系人)。如果同名联系人已存在,会用新数据合并更新。', parameters: { type: 'object', required: ['name'], properties: { name: { type: 'string', description: '联系人姓名' }, alias: { type: 'string', description: '别名或英文名(可选)' }, company: { type: 'string', description: '所在公司' }, role: { type: 'string', description: '职位/职务' }, wechatId: { type: 'string', description: '微信 ID' }, phone: { type: 'string', description: '手机号' }, email: { type: 'string', description: '邮箱' }, linkedin: { type: 'string', description: 'LinkedIn URL 或 username' }, twitter: { type: 'string', description: 'X/Twitter handle' }, notes: { type: 'string', description: '备注信息' }, }, }, async execute(params) { const g = store(); // 检查是否已存在同名节点,若有则 upsert const existing = g.findByName(params.name, 'Person'); const id = existing[0]?.id; const node = g.createNode('Person', { ...(id ? { id } : {}), ...params }); return `✅ 联系人已保存:\n${nodeSummary(node)}`; }, }; } function getPersonTool() { return { name: 'get_person', description: '查询一个联系人的完整档案,包括所有属性和关联关系。支持按 id 或姓名查询。', parameters: { type: 'object', required: ['name_or_id'], properties: { name_or_id: { type: 'string', description: '联系人姓名或节点 id' }, }, }, async execute(params) { const g = store(); const node = resolvePerson(params.name_or_id); if (!node) return `❌ 未找到联系人:${params.name_or_id}`; const { outgoing, incoming } = g.getEdges(node.id); const fmtEdge = (e, dir) => { const neighbor = dir === 'out' ? e.to_id : e.from_id; const neighborNode = g.getNode(neighbor); const neighborName = neighborNode?.name || neighbor; const meta = Object.keys(e.data).length ? ` (${JSON.stringify(e.data)})` : ''; return dir === 'out' ? ` → [${e.relation}] ${neighborName} (${neighborNode?._type || '?'})${meta}` : ` ← [${e.relation}] ${neighborName} (${neighborNode?._type || '?'})${meta}`; }; const lines = [ `📋 联系人档案:${node.name}`, `─────────────────────────`, nodeSummary(node), ]; if (outgoing.length) { lines.push('\n🔗 关联关系(出边):'); outgoing.forEach(e => lines.push(fmtEdge(e, 'out'))); } if (incoming.length) { lines.push('\n🔗 关联关系(入边):'); incoming.forEach(e => lines.push(fmtEdge(e, 'in'))); } if (!outgoing.length && !incoming.length) { lines.push('\n(暂无关联关系)'); } return lines.join('\n'); }, }; } function linkPersonsTool() { return { name: 'link_persons', description: '在图谱中建立两个节点之间的关系边。节点可以是 Person / Organization / Project 等任意类型。', parameters: { type: 'object', required: ['from_name_or_id', 'to_name_or_id', 'relation'], properties: { from_name_or_id: { type: 'string', description: '起始节点(姓名或 id)' }, to_name_or_id: { type: 'string', description: '目标节点(姓名或 id)' }, relation: { type: 'string', description: '关系类型:KNOWS / BELONGS_TO / PARTICIPATED / MENTIONED_IN / ASSOCIATED / 自定义', }, channel: { type: 'string', description: '认识渠道(如 wechat / linkedin)' }, since: { type: 'string', description: '关系建立时间(如 2023-06)' }, notes: { type: 'string', description: '关系备注' }, }, }, async execute(params) { const g = store(); const { from_name_or_id, to_name_or_id, relation, ...meta } = params; // 解析 from let fromNode = resolvePerson(from_name_or_id); if (!fromNode) fromNode = g.getNode(from_name_or_id); if (!fromNode) return `❌ 未找到节点:${from_name_or_id}`; // 解析 to(支持 Person 或 Organization/Project) let toNode = g.getNode(to_name_or_id); if (!toNode) { const results = g.search(to_name_or_id); toNode = results[0] || null; } if (!toNode) return `❌ 未找到节点:${to_name_or_id}`; const edge = g.createEdge(fromNode.id, toNode.id, relation, meta); return `✅ 关系已建立:${fromNode.name} --[${relation}]--> ${toNode.name}\n边 id: ${edge.id}`; }, }; } function createEventTool() { return { name: 'create_event', description: '在图谱中创建一个事件节点(对话/会议/社媒动态等),并关联参与者。', parameters: { type: 'object', required: ['type', 'summary'], properties: { type: { type: 'string', description: '事件类型:chat / meeting / post / other' }, summary: { type: 'string', description: '事件摘要' }, date: { type: 'string', description: '事件日期(YYYY-MM-DD)' }, rawText: { type: 'string', description: '原始文本内容(可选)' }, participants: { type: 'array', items: { type: 'string' }, description: '参与者姓名或 id 列表', }, }, }, async execute(params) { const g = store(); const { participants = [], ...eventData } = params; const event = g.createNode('Event', eventData); const linked = []; for (const p of participants) { const person = resolvePerson(p); if (person) { g.createEdge(person.id, event.id, 'PARTICIPATED'); linked.push(person.name); } } return `✅ 事件已创建:${event.id}\n摘要:${event.summary}\n关联参与者:${linked.join('、') || '(无)'}`; }, }; } function searchGraphTool() { return { name: 'search_graph', description: '在整个知识图谱中全文搜索,返回匹配的节点列表。支持按姓名、公司、备注等字段模糊查询。', parameters: { type: 'object', required: ['query'], properties: { query: { type: 'string', description: '搜索关键词' }, type: { type: 'string', description: '限定节点类型:Person / Organization / Project / Event / Asset(可选)' }, }, }, async execute(params) { const g = store(); const results = params.type ? g.search(params.query).filter(n => n._type === params.type) : g.search(params.query); if (results.length === 0) return `🔍 未找到与「${params.query}」相关的节点。`; const lines = [`🔍 搜索「${params.query}」,找到 ${results.length} 条结果:\n`]; results.slice(0, 10).forEach((n, i) => { lines.push(`${i + 1}. [${n._type}] ${n.name || n.id} (id: ${n.id}, 状态: ${n._analysisStatus})`); }); if (results.length > 10) lines.push(`\n…共 ${results.length} 条,仅显示前 10 条`); return lines.join('\n'); }, }; } function getRelationshipPathTool() { return { name: 'get_relationship_path', description: '查找两个联系人之间的关系路径(最短路径 BFS),揭示间接关系链。', parameters: { type: 'object', required: ['from_name_or_id', 'to_name_or_id'], properties: { from_name_or_id: { type: 'string', description: '起点联系人(姓名或 id)' }, to_name_or_id: { type: 'string', description: '终点联系人(姓名或 id)' }, max_depth: { type: 'number', description: '最大搜索深度,默认 6' }, }, }, async execute(params) { const g = store(); const from = resolvePerson(params.from_name_or_id); const to = resolvePerson(params.to_name_or_id); if (!from) return `❌ 未找到:${params.from_name_or_id}`; if (!to) return `❌ 未找到:${params.to_name_or_id}`; const path = g.getRelationshipPath(from.id, to.id, params.max_depth || 6); if (path === null) return `❌ ${from.name} 和 ${to.name} 之间暂无可达的关系路径。`; if (path.length === 0) return `✅ ${from.name} 和 ${to.name} 是同一个人。`; const lines = [`🔗 关系路径(${path.length} 步):\n`, ` ${from.name}`]; for (const step of path) { const arrow = step.direction === 'outgoing' ? '→' : '←'; const meta = step.data?.channel ? ` [via ${step.data.channel}]` : ''; lines.push(` ${arrow} [${step.relation}]${meta}`); lines.push(` ${step.neighborName} (${step.neighborType})`); } return lines.join('\n'); }, }; } function createGraphTools() { return [ createPersonTool(), getPersonTool(), linkPersonsTool(), createEventTool(), searchGraphTool(), getRelationshipPathTool(), ]; } module.exports = { createGraphTools };