index.js
11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/**
* 图谱 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 };