analysis-tools.js
12.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
288
289
290
291
/**
* AI 深度分析 MCP 工具集 — Phase 5
*
* 工具:
* detect_communities — 标签传播社区发现
* get_followup_reminders — 超 N 天未互动提醒
* activate_contact_profile — 全量聚合分析,更新 analysisStatus
* get_conversation_insights — 对话数据分析(话题分布、互动热度)
*/
const { getGraphStore } = require('../../../db/GraphStore');
/** 解析 name_or_id,先精确查 id 再按名称模糊查 */
function resolvePerson(nameOrId) {
const g = getGraphStore();
let node = g.getNode(nameOrId);
if (!node || node._type !== 'Person') {
const results = g.findByName(nameOrId, 'Person');
node = results[0] || null;
}
return node;
}
function createAnalysisTools() {
return [
// ── 5.1 社区发现 ─────────────────────────────────────────────
{
name: 'detect_communities',
description: '运行社区发现算法(标签传播),识别人脉中的圈子群体,返回分组结果。',
parameters: {
type: 'object',
properties: {
max_iterations: {
type: 'number',
description: '最大迭代次数,默认 10',
},
},
},
async execute(params = {}) {
try {
const g = getGraphStore();
const result = g.detectCommunities(params.max_iterations || 10);
if (result.communities.length === 0) {
return '图谱中暂无 Person 节点,无法进行社区发现。';
}
const lines = [
`🔍 社区发现完成,共识别 ${result.communities.length} 个圈子:\n`,
];
for (const c of result.communities) {
const names = c.nodes.map(n => n.name).join('、');
lines.push(`圈子 ${c.id}(${c.nodes.length} 人):${names}`);
}
return lines.join('\n');
} catch (err) {
return `❌ 社区发现失败:${err.message}`;
}
},
},
// ── 5.2 跟进提醒 ─────────────────────────────────────────────
{
name: 'get_followup_reminders',
description: '获取超过 N 天未互动的联系人列表,生成跟进提醒建议。',
parameters: {
type: 'object',
properties: {
days_since: {
type: 'number',
description: '超过几天未互动,默认 30',
},
limit: {
type: 'number',
description: '最多返回多少人,默认 20',
},
},
},
async execute(params = {}) {
try {
const g = getGraphStore();
const days = params.days_since || 30;
const candidates = g.getFollowUpCandidates(days, params.limit || 20);
if (candidates.length === 0) {
return `✅ 近 ${days} 天内所有重要联系人均已互动,暂无跟进提醒。`;
}
const lines = [
`⏰ 超过 ${days} 天未互动的联系人(${candidates.length} 人):\n`,
];
for (const c of candidates) {
const lastStr = c.lastInteraction || '从未互动';
lines.push(`• ${c.person.name}(${c.daysSince} 天前:${lastStr})`);
lines.push(` → ${c.suggestion}`);
}
return lines.join('\n');
} catch (err) {
return `❌ 跟进提醒获取失败:${err.message}`;
}
},
},
// ── 5.3 激活联系人档案 ───────────────────────────────────────
{
name: 'activate_contact_profile',
description: '对指定联系人执行全量聚合分析,更新 analysisStatus 为 complete,返回分析摘要。',
parameters: {
type: 'object',
required: ['person_name_or_id'],
properties: {
person_name_or_id: {
type: 'string',
description: '联系人姓名或节点 id',
},
},
},
async execute(params) {
try {
const g = getGraphStore();
const person = resolvePerson(params.person_name_or_id);
if (!person) {
return `❌ 未找到联系人:${params.person_name_or_id}`;
}
// 获取所有边和关联节点
const { outgoing, incoming } = g.getEdges(person.id);
const allEdges = [...outgoing, ...incoming];
// 统计互动次数(Event 节点)
let interactionCount = 0;
const relatedOrgs = new Set();
const recentEvents = [];
const now = Date.now();
let latestEventDate = null;
for (const edge of allEdges) {
const neighborId = edge.from_id === person.id ? edge.to_id : edge.from_id;
const neighbor = g.getNode(neighborId);
if (!neighbor) continue;
if (neighbor._type === 'Event') {
interactionCount++;
const dateStr = neighbor.date || neighbor.createdAt || null;
if (dateStr) {
recentEvents.push({ name: neighbor.name || neighbor.summary, date: dateStr });
if (!latestEventDate || dateStr > latestEventDate) {
latestEventDate = dateStr;
}
}
} else if (neighbor._type === 'Organization') {
relatedOrgs.add(neighbor.name || neighborId);
}
}
// 最近 3 个事件
recentEvents.sort((a, b) => (b.date > a.date ? 1 : -1));
const topEvents = recentEvents.slice(0, 3);
// 更新 analysisStatus = 'complete'
g.setAnalysisStatus(person.id, 'complete');
const daysSinceLast = latestEventDate
? Math.floor((now - new Date(latestEventDate).getTime()) / (1000 * 60 * 60 * 24))
: null;
const summary = [
`📊 联系人档案激活完成:${person.name}`,
`─────────────────────────────`,
`互动次数:${interactionCount} 次`,
`关联组织:${relatedOrgs.size > 0 ? [...relatedOrgs].join('、') : '(无)'}`,
`最近互动:${latestEventDate || '(无记录)'}${daysSinceLast !== null ? `(${daysSinceLast} 天前)` : ''}`,
];
if (topEvents.length > 0) {
summary.push('\n最近事件:');
topEvents.forEach(e => summary.push(` • ${e.date} — ${e.name || '(无标题)'}`));
}
summary.push('\nanalysisStatus 已更新为 complete。');
return summary.join('\n');
} catch (err) {
return `❌ 档案激活失败:${err.message}`;
}
},
},
// ── 5.4 对话洞察 ─────────────────────────────────────────────
{
name: 'get_conversation_insights',
description: '分析联系人的对话数据,返回话题分布、互动热度、待跟进事项。',
parameters: {
type: 'object',
required: ['person_name_or_id'],
properties: {
person_name_or_id: {
type: 'string',
description: '联系人姓名或节点 id',
},
},
},
async execute(params) {
try {
const g = getGraphStore();
const person = resolvePerson(params.person_name_or_id);
if (!person) {
return `❌ 未找到联系人:${params.person_name_or_id}`;
}
const { outgoing, incoming } = g.getEdges(person.id);
const allEdges = [...outgoing, ...incoming];
const typeDist = {}; // Event type 分布
const monthDist = {}; // 按月分布(互动热度)
const hashtags = {}; // 话题标签(来自 X 动态)
let totalEvents = 0;
for (const edge of allEdges) {
const neighborId = edge.from_id === person.id ? edge.to_id : edge.from_id;
const neighbor = g.getNode(neighborId);
if (!neighbor || neighbor._type !== 'Event') continue;
totalEvents++;
// 类型分布
const t = neighbor.type || 'other';
typeDist[t] = (typeDist[t] || 0) + 1;
// 月度热度
const dateStr = neighbor.date || neighbor.createdAt;
if (dateStr) {
const month = String(dateStr).slice(0, 7); // YYYY-MM
monthDist[month] = (monthDist[month] || 0) + 1;
}
// 话题标签
if (Array.isArray(neighbor.hashtags)) {
for (const tag of neighbor.hashtags) {
if (tag) hashtags[tag] = (hashtags[tag] || 0) + 1;
}
}
}
// 按月排序,取最近 6 个月
const sortedMonths = Object.entries(monthDist)
.sort((a, b) => b[0].localeCompare(a[0]))
.slice(0, 6);
// 按频率排序 hashtags
const topHashtags = Object.entries(hashtags)
.sort((a, b) => b[1] - a[1])
.slice(0, 10);
const insights = {
personId: person.id,
personName: person.name,
totalEvents,
typeDistribution: typeDist,
recentActivity: Object.fromEntries(sortedMonths),
topHashtags: Object.fromEntries(topHashtags),
analysisStatus: person._analysisStatus,
};
const lines = [
`📈 联系人洞察:${person.name}`,
`─────────────────────────────`,
`关联事件总数:${totalEvents}`,
`事件类型分布:${JSON.stringify(typeDist)}`,
`近期互动热度(月):${JSON.stringify(Object.fromEntries(sortedMonths))}`,
];
if (topHashtags.length > 0) {
lines.push(`常见话题标签:${topHashtags.map(([k, v]) => `${k}(${v})`).join('、')}`);
}
lines.push('\n---\n原始数据:\n```json\n' + JSON.stringify(insights, null, 2) + '\n```');
return lines.join('\n');
} catch (err) {
return `❌ 对话洞察分析失败:${err.message}`;
}
},
},
];
}
module.exports = { createAnalysisTools };