koishi-plugin-isthattrue 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/agents/searchAgent.d.ts +8 -1
- package/lib/config.d.ts +4 -2
- package/lib/index.js +281 -104
- package/lib/services/anspire.d.ts +30 -0
- package/lib/services/tavily.d.ts +30 -0
- package/lib/utils/prompts.d.ts +9 -9
- package/package.json +2 -2
|
@@ -30,6 +30,9 @@ export declare class SearchAgentManager {
|
|
|
30
30
|
private ctx;
|
|
31
31
|
private config;
|
|
32
32
|
private agents;
|
|
33
|
+
private tavilyAgent;
|
|
34
|
+
private anspireAgent;
|
|
35
|
+
private logger;
|
|
33
36
|
constructor(ctx: Context, config: Config);
|
|
34
37
|
private initAgents;
|
|
35
38
|
/**
|
|
@@ -37,7 +40,11 @@ export declare class SearchAgentManager {
|
|
|
37
40
|
*/
|
|
38
41
|
searchAll(content: string): Promise<SearchResult[]>;
|
|
39
42
|
/**
|
|
40
|
-
* 带超时的Promise包装
|
|
43
|
+
* 带超时的Promise包装 (LLM Agent)
|
|
41
44
|
*/
|
|
42
45
|
private withTimeout;
|
|
46
|
+
/**
|
|
47
|
+
* 带超时的Promise包装 (通用)
|
|
48
|
+
*/
|
|
49
|
+
private withTimeoutGeneric;
|
|
43
50
|
}
|
package/lib/config.d.ts
CHANGED
|
@@ -7,14 +7,16 @@ export interface Config {
|
|
|
7
7
|
searchModels: string[];
|
|
8
8
|
/** 验证用模型(低幻觉率) */
|
|
9
9
|
verifyModel: string;
|
|
10
|
+
/** Tavily API Key */
|
|
11
|
+
tavilyApiKey: string;
|
|
12
|
+
/** Anspire API Key */
|
|
13
|
+
anspireApiKey: string;
|
|
10
14
|
/** 超时时间(毫秒) */
|
|
11
15
|
timeout: number;
|
|
12
16
|
/** 最大重试次数 */
|
|
13
17
|
maxRetries: number;
|
|
14
18
|
/** 是否显示详细过程 */
|
|
15
19
|
verbose: boolean;
|
|
16
|
-
/** 是否启用图片OCR */
|
|
17
|
-
enableOCR: boolean;
|
|
18
20
|
/** 输出格式 */
|
|
19
21
|
outputFormat: 'auto' | 'markdown' | 'plain';
|
|
20
22
|
/** 是否使用合并转发消息 */
|
package/lib/index.js
CHANGED
|
@@ -177,105 +177,223 @@ Content: ${content}`);
|
|
|
177
177
|
}
|
|
178
178
|
};
|
|
179
179
|
|
|
180
|
+
// src/services/tavily.ts
|
|
181
|
+
var TavilySearchAgent = class {
|
|
182
|
+
constructor(ctx, config) {
|
|
183
|
+
this.ctx = ctx;
|
|
184
|
+
this.config = config;
|
|
185
|
+
this.apiKey = config.tavilyApiKey;
|
|
186
|
+
this.logger = ctx.logger("isthattrue");
|
|
187
|
+
}
|
|
188
|
+
static {
|
|
189
|
+
__name(this, "TavilySearchAgent");
|
|
190
|
+
}
|
|
191
|
+
apiKey;
|
|
192
|
+
logger;
|
|
193
|
+
/**
|
|
194
|
+
* 检查服务是否可用
|
|
195
|
+
*/
|
|
196
|
+
isAvailable() {
|
|
197
|
+
return !!this.apiKey;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* 执行搜索
|
|
201
|
+
*/
|
|
202
|
+
async search(query) {
|
|
203
|
+
const startTime = Date.now();
|
|
204
|
+
this.logger.info("[Tavily] 开始搜索:", query.substring(0, 50));
|
|
205
|
+
try {
|
|
206
|
+
const response = await this.ctx.http.post(
|
|
207
|
+
"https://api.tavily.com/search",
|
|
208
|
+
{
|
|
209
|
+
api_key: this.apiKey,
|
|
210
|
+
query,
|
|
211
|
+
search_depth: "advanced",
|
|
212
|
+
include_answer: true,
|
|
213
|
+
max_results: 5
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
timeout: this.config.timeout
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
const findings = this.formatFindings(response);
|
|
220
|
+
const sources = response.results.map((r) => r.url);
|
|
221
|
+
const elapsed = Date.now() - startTime;
|
|
222
|
+
this.logger.info(`[Tavily] 搜索完成,耗时 ${elapsed}ms,找到 ${response.results.length} 条结果`);
|
|
223
|
+
return {
|
|
224
|
+
agentId: "tavily",
|
|
225
|
+
perspective: "Tavily 网络搜索",
|
|
226
|
+
findings,
|
|
227
|
+
sources,
|
|
228
|
+
confidence: this.calculateConfidence(response)
|
|
229
|
+
};
|
|
230
|
+
} catch (error) {
|
|
231
|
+
this.logger.error("[Tavily] 搜索失败:", error);
|
|
232
|
+
return {
|
|
233
|
+
agentId: "tavily",
|
|
234
|
+
perspective: "Tavily 网络搜索",
|
|
235
|
+
findings: `搜索失败: ${error.message}`,
|
|
236
|
+
sources: [],
|
|
237
|
+
confidence: 0
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* 格式化搜索结果
|
|
243
|
+
*/
|
|
244
|
+
formatFindings(response) {
|
|
245
|
+
const parts = [];
|
|
246
|
+
if (response.answer) {
|
|
247
|
+
parts.push(`摘要: ${response.answer}`);
|
|
248
|
+
}
|
|
249
|
+
if (response.results.length > 0) {
|
|
250
|
+
parts.push("\n相关结果:");
|
|
251
|
+
for (const result of response.results.slice(0, 3)) {
|
|
252
|
+
parts.push(`- ${result.title}: ${result.content.substring(0, 150)}...`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return parts.join("\n") || "未找到相关信息";
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* 计算置信度
|
|
259
|
+
*/
|
|
260
|
+
calculateConfidence(response) {
|
|
261
|
+
if (response.results.length === 0) return 0.1;
|
|
262
|
+
const avgScore = response.results.reduce((sum, r) => sum + r.score, 0) / response.results.length;
|
|
263
|
+
return Math.min(avgScore, 0.9);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// src/services/anspire.ts
|
|
268
|
+
var AnspireSearchAgent = class {
|
|
269
|
+
constructor(ctx, config) {
|
|
270
|
+
this.ctx = ctx;
|
|
271
|
+
this.config = config;
|
|
272
|
+
this.apiKey = config.anspireApiKey;
|
|
273
|
+
this.logger = ctx.logger("isthattrue");
|
|
274
|
+
}
|
|
275
|
+
static {
|
|
276
|
+
__name(this, "AnspireSearchAgent");
|
|
277
|
+
}
|
|
278
|
+
apiKey;
|
|
279
|
+
logger;
|
|
280
|
+
/**
|
|
281
|
+
* 检查服务是否可用
|
|
282
|
+
*/
|
|
283
|
+
isAvailable() {
|
|
284
|
+
return !!this.apiKey;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* 执行搜索
|
|
288
|
+
*/
|
|
289
|
+
async search(query) {
|
|
290
|
+
const startTime = Date.now();
|
|
291
|
+
this.logger.info("[Anspire] 开始搜索:", query.substring(0, 50));
|
|
292
|
+
try {
|
|
293
|
+
const truncatedQuery = query.substring(0, 64);
|
|
294
|
+
const response = await this.ctx.http.get(
|
|
295
|
+
"https://plugin.anspire.cn/api/ntsearch/search",
|
|
296
|
+
{
|
|
297
|
+
params: {
|
|
298
|
+
query: truncatedQuery,
|
|
299
|
+
top_k: "10"
|
|
300
|
+
},
|
|
301
|
+
headers: {
|
|
302
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
303
|
+
"Content-Type": "application/json",
|
|
304
|
+
"Accept": "*/*"
|
|
305
|
+
},
|
|
306
|
+
timeout: this.config.timeout
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
const findings = this.formatFindings(response);
|
|
310
|
+
const sources = response.results.map((r) => r.url);
|
|
311
|
+
const elapsed = Date.now() - startTime;
|
|
312
|
+
this.logger.info(`[Anspire] 搜索完成,耗时 ${elapsed}ms,找到 ${response.results.length} 条结果`);
|
|
313
|
+
return {
|
|
314
|
+
agentId: "anspire",
|
|
315
|
+
perspective: "Anspire 网络搜索",
|
|
316
|
+
findings,
|
|
317
|
+
sources,
|
|
318
|
+
confidence: this.calculateConfidence(response)
|
|
319
|
+
};
|
|
320
|
+
} catch (error) {
|
|
321
|
+
this.logger.error("[Anspire] 搜索失败:", error);
|
|
322
|
+
return {
|
|
323
|
+
agentId: "anspire",
|
|
324
|
+
perspective: "Anspire 网络搜索",
|
|
325
|
+
findings: `搜索失败: ${error.message}`,
|
|
326
|
+
sources: [],
|
|
327
|
+
confidence: 0
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* 格式化搜索结果
|
|
333
|
+
*/
|
|
334
|
+
formatFindings(response) {
|
|
335
|
+
if (!response.results || response.results.length === 0) {
|
|
336
|
+
return "未找到相关信息";
|
|
337
|
+
}
|
|
338
|
+
const parts = ["相关结果:"];
|
|
339
|
+
for (const result of response.results.slice(0, 5)) {
|
|
340
|
+
const content = result.content.substring(0, 150);
|
|
341
|
+
parts.push(`- ${result.title}: ${content}...`);
|
|
342
|
+
}
|
|
343
|
+
return parts.join("\n");
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* 计算置信度
|
|
347
|
+
*/
|
|
348
|
+
calculateConfidence(response) {
|
|
349
|
+
if (!response.results || response.results.length === 0) return 0.1;
|
|
350
|
+
const avgScore = response.results.reduce((sum, r) => sum + r.score, 0) / response.results.length;
|
|
351
|
+
return Math.min(Math.max(avgScore, 0.1), 0.9);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
180
355
|
// src/utils/prompts.ts
|
|
181
|
-
var SEARCH_AGENT_SYSTEM_PROMPT =
|
|
356
|
+
var SEARCH_AGENT_SYSTEM_PROMPT = `你是事实核查搜索员。搜索验证声明的相关信息。
|
|
182
357
|
|
|
183
|
-
|
|
184
|
-
1. 仔细分析待验证的声明
|
|
185
|
-
2. 从你被分配的角度进行深度搜索
|
|
186
|
-
3. 收集相关的证据和来源
|
|
187
|
-
4. 客观评估找到的信息
|
|
358
|
+
搜索角度:官方来源、新闻报道、学术研究、社交讨论、历史背景。
|
|
188
359
|
|
|
189
|
-
|
|
190
|
-
请以JSON格式返回你的搜索结果:
|
|
360
|
+
输出JSON:
|
|
191
361
|
\`\`\`json
|
|
192
|
-
{
|
|
193
|
-
"perspective": "你的搜索角度",
|
|
194
|
-
"findings": "详细的搜索发现",
|
|
195
|
-
"sources": ["来源1", "来源2"],
|
|
196
|
-
"supports_claim": true/false/null,
|
|
197
|
-
"confidence": 0.0-1.0,
|
|
198
|
-
"key_evidence": "最关键的证据摘要"
|
|
199
|
-
}
|
|
362
|
+
{"findings":"发现摘要","sources":["来源URL"],"supports":true/false/null,"confidence":0.0-1.0}
|
|
200
363
|
\`\`\`
|
|
201
364
|
|
|
202
|
-
|
|
203
|
-
- 保持客观中立,不要预设立场
|
|
204
|
-
- 如果找不到相关信息,诚实说明
|
|
205
|
-
- 注明信息来源的可信度
|
|
206
|
-
- 区分事实陈述和观点表达`;
|
|
365
|
+
要求:客观中立,注明来源可信度,找不到就说明。`;
|
|
207
366
|
function getSearchPerspectives(count) {
|
|
208
|
-
|
|
209
|
-
"官方来源和权威机构",
|
|
210
|
-
"新闻媒体报道",
|
|
211
|
-
"学术研究和专家观点",
|
|
212
|
-
"社交媒体和公众讨论",
|
|
213
|
-
"历史背景和上下文"
|
|
214
|
-
];
|
|
215
|
-
return perspectives.slice(0, count);
|
|
367
|
+
return Array.from({ length: count }, (_, i) => `搜索Agent ${i + 1}`);
|
|
216
368
|
}
|
|
217
369
|
__name(getSearchPerspectives, "getSearchPerspectives");
|
|
218
|
-
function buildSearchPrompt(content,
|
|
219
|
-
return
|
|
220
|
-
"${content}"
|
|
221
|
-
|
|
222
|
-
## 你的搜索角度: ${perspective}
|
|
370
|
+
function buildSearchPrompt(content, _perspective) {
|
|
371
|
+
return `验证声明:"${content}"
|
|
223
372
|
|
|
224
|
-
|
|
373
|
+
从多角度搜索相关证据。`;
|
|
225
374
|
}
|
|
226
375
|
__name(buildSearchPrompt, "buildSearchPrompt");
|
|
227
|
-
var VERIFY_AGENT_SYSTEM_PROMPT =
|
|
376
|
+
var VERIFY_AGENT_SYSTEM_PROMPT = `你是事实核查裁判。基于搜索证据做出判决。
|
|
228
377
|
|
|
229
|
-
|
|
230
|
-
1. 综合分析所有搜索结果
|
|
231
|
-
2. 评估各来源的可信度
|
|
232
|
-
3. 识别证据之间的一致性和矛盾
|
|
233
|
-
4. 做出谨慎的最终判决
|
|
378
|
+
判决类别:TRUE(真实)、FALSE(虚假)、PARTIALLY_TRUE(部分真实)、UNCERTAIN(无法确定)
|
|
234
379
|
|
|
235
|
-
|
|
236
|
-
- TRUE (真实): 有充分可靠证据支持
|
|
237
|
-
- FALSE (虚假): 有充分可靠证据反驳
|
|
238
|
-
- PARTIALLY_TRUE (部分真实): 声明中部分内容属实
|
|
239
|
-
- UNCERTAIN (无法确定): 证据不足或相互矛盾
|
|
240
|
-
|
|
241
|
-
## 输出格式:
|
|
242
|
-
请以JSON格式返回你的判决:
|
|
380
|
+
输出JSON:
|
|
243
381
|
\`\`\`json
|
|
244
|
-
{
|
|
245
|
-
"verdict": "TRUE/FALSE/PARTIALLY_TRUE/UNCERTAIN",
|
|
246
|
-
"confidence": 0.0-1.0,
|
|
247
|
-
"reasoning": "详细的判决理由",
|
|
248
|
-
"key_evidence": "支持判决的关键证据",
|
|
249
|
-
"caveats": "需要注意的限制或例外",
|
|
250
|
-
"sources": ["最可靠的来源列表"]
|
|
251
|
-
}
|
|
382
|
+
{"verdict":"TRUE/FALSE/PARTIALLY_TRUE/UNCERTAIN","confidence":0.0-1.0,"reasoning":"判决理由","sources":["来源"]}
|
|
252
383
|
\`\`\`
|
|
253
384
|
|
|
254
|
-
|
|
255
|
-
- 遵循"无罪推定"原则,证据不足时倾向于"无法确定"
|
|
256
|
-
- 重视权威来源,但也关注多方交叉验证
|
|
257
|
-
- 考虑信息的时效性
|
|
258
|
-
- 区分事实错误和表述不精确
|
|
259
|
-
- 保持谦逊,承认认知局限`;
|
|
385
|
+
原则:证据不足时判UNCERTAIN,重视权威来源,考虑时效性。`;
|
|
260
386
|
function buildVerifyPrompt(originalContent, searchResults) {
|
|
261
|
-
const resultsText = searchResults.map((r, i) =>
|
|
262
|
-
${r.
|
|
263
|
-
|
|
264
|
-
return `## 待验证的原始声明:
|
|
265
|
-
"${originalContent}"
|
|
266
|
-
|
|
267
|
-
## 各搜索Agent的调查结果:
|
|
387
|
+
const resultsText = searchResults.map((r, i) => `[${i + 1}] ${r.findings}
|
|
388
|
+
来源: ${r.sources.slice(0, 3).join(", ") || "无"}`).join("\n\n");
|
|
389
|
+
return `声明:"${originalContent}"
|
|
268
390
|
|
|
391
|
+
搜索结果:
|
|
269
392
|
${resultsText}
|
|
270
393
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
请综合以上所有搜索结果,对原始声明做出最终判决。`;
|
|
394
|
+
请判决。`;
|
|
274
395
|
}
|
|
275
396
|
__name(buildVerifyPrompt, "buildVerifyPrompt");
|
|
276
|
-
var OCR_PROMPT = `请识别并提取这张图片中的所有文字内容。
|
|
277
|
-
如果图片中没有文字,请描述图片的主要内容。
|
|
278
|
-
直接输出识别结果,不需要额外说明。`;
|
|
279
397
|
function formatVerificationOutput(content, searchResults, verdict, reasoning, sources, confidence, processingTime, format = "markdown") {
|
|
280
398
|
const verdictEmoji = {
|
|
281
399
|
true: "✅ 真实",
|
|
@@ -442,15 +560,12 @@ var SearchAgent = class {
|
|
|
442
560
|
parseResponse(content) {
|
|
443
561
|
try {
|
|
444
562
|
const jsonMatch = content.match(/```json\s*([\s\S]*?)\s*```/);
|
|
563
|
+
let parsed;
|
|
445
564
|
if (jsonMatch) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
sources: parsed2.sources || [],
|
|
450
|
-
confidence: parsed2.confidence
|
|
451
|
-
};
|
|
565
|
+
parsed = JSON.parse(jsonMatch[1]);
|
|
566
|
+
} else {
|
|
567
|
+
parsed = JSON.parse(content);
|
|
452
568
|
}
|
|
453
|
-
const parsed = JSON.parse(content);
|
|
454
569
|
return {
|
|
455
570
|
findings: parsed.findings || parsed.key_evidence,
|
|
456
571
|
sources: parsed.sources || [],
|
|
@@ -465,41 +580,76 @@ var SearchAgentManager = class {
|
|
|
465
580
|
constructor(ctx, config) {
|
|
466
581
|
this.ctx = ctx;
|
|
467
582
|
this.config = config;
|
|
583
|
+
this.logger = ctx.logger("isthattrue");
|
|
468
584
|
this.initAgents();
|
|
469
585
|
}
|
|
470
586
|
static {
|
|
471
587
|
__name(this, "SearchAgentManager");
|
|
472
588
|
}
|
|
473
589
|
agents = [];
|
|
590
|
+
tavilyAgent = null;
|
|
591
|
+
anspireAgent = null;
|
|
592
|
+
logger;
|
|
474
593
|
initAgents() {
|
|
475
594
|
const models = this.config.searchModels;
|
|
476
595
|
for (let i = 0; i < models.length; i++) {
|
|
477
596
|
this.agents.push(new SearchAgent(this.ctx, this.config, i, models[i]));
|
|
478
597
|
}
|
|
598
|
+
const tavilyAgent = new TavilySearchAgent(this.ctx, this.config);
|
|
599
|
+
if (tavilyAgent.isAvailable()) {
|
|
600
|
+
this.tavilyAgent = tavilyAgent;
|
|
601
|
+
this.logger.info("Tavily 搜索已启用");
|
|
602
|
+
}
|
|
603
|
+
const anspireAgent = new AnspireSearchAgent(this.ctx, this.config);
|
|
604
|
+
if (anspireAgent.isAvailable()) {
|
|
605
|
+
this.anspireAgent = anspireAgent;
|
|
606
|
+
this.logger.info("Anspire 搜索已启用");
|
|
607
|
+
}
|
|
479
608
|
}
|
|
480
609
|
/**
|
|
481
610
|
* 并行执行所有Agent的搜索
|
|
482
611
|
*/
|
|
483
612
|
async searchAll(content) {
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
489
|
-
logger.info(`启动 ${this.agents.length} 个搜索Agent`);
|
|
490
|
-
const results = await Promise.all(
|
|
491
|
-
this.agents.map(
|
|
492
|
-
(agent) => this.withTimeout(
|
|
613
|
+
const allPromises = [];
|
|
614
|
+
for (const agent of this.agents) {
|
|
615
|
+
allPromises.push(
|
|
616
|
+
this.withTimeout(
|
|
493
617
|
agent.search(content),
|
|
494
618
|
this.config.timeout,
|
|
495
619
|
agent
|
|
496
620
|
)
|
|
497
|
-
)
|
|
498
|
-
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
if (this.tavilyAgent) {
|
|
624
|
+
allPromises.push(
|
|
625
|
+
this.withTimeoutGeneric(
|
|
626
|
+
this.tavilyAgent.search(content),
|
|
627
|
+
this.config.timeout,
|
|
628
|
+
"tavily",
|
|
629
|
+
"Tavily 网络搜索"
|
|
630
|
+
)
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
if (this.anspireAgent) {
|
|
634
|
+
allPromises.push(
|
|
635
|
+
this.withTimeoutGeneric(
|
|
636
|
+
this.anspireAgent.search(content),
|
|
637
|
+
this.config.timeout,
|
|
638
|
+
"anspire",
|
|
639
|
+
"Anspire 网络搜索"
|
|
640
|
+
)
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
if (allPromises.length === 0) {
|
|
644
|
+
this.logger.warn("未配置任何搜索源");
|
|
645
|
+
return [];
|
|
646
|
+
}
|
|
647
|
+
this.logger.info(`启动 ${allPromises.length} 个搜索Agent (LLM: ${this.agents.length}, API: ${allPromises.length - this.agents.length})`);
|
|
648
|
+
const results = await Promise.all(allPromises);
|
|
499
649
|
return results;
|
|
500
650
|
}
|
|
501
651
|
/**
|
|
502
|
-
* 带超时的Promise包装
|
|
652
|
+
* 带超时的Promise包装 (LLM Agent)
|
|
503
653
|
*/
|
|
504
654
|
async withTimeout(promise, timeout, agent) {
|
|
505
655
|
return Promise.race([
|
|
@@ -515,6 +665,23 @@ var SearchAgentManager = class {
|
|
|
515
665
|
confidence: 0
|
|
516
666
|
}));
|
|
517
667
|
}
|
|
668
|
+
/**
|
|
669
|
+
* 带超时的Promise包装 (通用)
|
|
670
|
+
*/
|
|
671
|
+
async withTimeoutGeneric(promise, timeout, agentId, perspective) {
|
|
672
|
+
return Promise.race([
|
|
673
|
+
promise,
|
|
674
|
+
new Promise((_, reject) => {
|
|
675
|
+
setTimeout(() => reject(new Error("搜索超时")), timeout);
|
|
676
|
+
})
|
|
677
|
+
]).catch((error) => ({
|
|
678
|
+
agentId,
|
|
679
|
+
perspective,
|
|
680
|
+
findings: `错误: ${error.message}`,
|
|
681
|
+
sources: [],
|
|
682
|
+
confidence: 0
|
|
683
|
+
}));
|
|
684
|
+
}
|
|
518
685
|
};
|
|
519
686
|
|
|
520
687
|
// src/agents/verifyAgent.ts
|
|
@@ -808,13 +975,16 @@ var Config = import_koishi2.Schema.intersect([
|
|
|
808
975
|
searchModels: import_koishi2.Schema.array(import_koishi2.Schema.dynamic("model")).default([]).description("搜索用模型列表 (每个模型对应一个搜索Agent,需要支持联网搜索)"),
|
|
809
976
|
verifyModel: import_koishi2.Schema.dynamic("model").default("无").description("验证用模型 (推荐使用低幻觉率模型)")
|
|
810
977
|
}).description("模型配置"),
|
|
978
|
+
import_koishi2.Schema.object({
|
|
979
|
+
tavilyApiKey: import_koishi2.Schema.string().default("").role("secret").description("Tavily API Key (可选,用于补充搜索)"),
|
|
980
|
+
anspireApiKey: import_koishi2.Schema.string().default("").role("secret").description("Anspire API Key (可选,用于补充搜索)")
|
|
981
|
+
}).description("搜索API配置"),
|
|
811
982
|
import_koishi2.Schema.object({
|
|
812
983
|
timeout: import_koishi2.Schema.number().min(1e4).max(3e5).default(6e4).description("单次请求超时时间(毫秒)"),
|
|
813
984
|
maxRetries: import_koishi2.Schema.number().min(0).max(5).default(2).description("失败重试次数")
|
|
814
985
|
}).description("Agent配置"),
|
|
815
986
|
import_koishi2.Schema.object({
|
|
816
987
|
verbose: import_koishi2.Schema.boolean().default(false).description("显示详细验证过程 (进度提示)"),
|
|
817
|
-
enableOCR: import_koishi2.Schema.boolean().default(true).description("启用图片文字识别"),
|
|
818
988
|
outputFormat: import_koishi2.Schema.union([
|
|
819
989
|
import_koishi2.Schema.const("auto").description("自动 (QQ使用纯文本)"),
|
|
820
990
|
import_koishi2.Schema.const("markdown").description("Markdown"),
|
|
@@ -880,24 +1050,24 @@ function apply(ctx, config) {
|
|
|
880
1050
|
try {
|
|
881
1051
|
const startTime = Date.now();
|
|
882
1052
|
let textToVerify = content.text;
|
|
883
|
-
if (content.images.length > 0
|
|
1053
|
+
if (content.images.length > 0) {
|
|
884
1054
|
if (verbose) {
|
|
885
|
-
await session.send("📷
|
|
1055
|
+
await session.send("📷 正在分析图片内容...");
|
|
886
1056
|
}
|
|
887
1057
|
try {
|
|
888
1058
|
const prepared = await messageParser.prepareForLLM(content);
|
|
889
1059
|
if (prepared.imageBase64List.length > 0) {
|
|
890
|
-
const
|
|
1060
|
+
const imageResponse = await chatluna.chat({
|
|
891
1061
|
model: config.verifyModel,
|
|
892
|
-
message:
|
|
1062
|
+
message: "请描述这张图片中的主要内容,特别是其中可能需要事实核查的声明或信息。",
|
|
893
1063
|
images: prepared.imageBase64List
|
|
894
1064
|
});
|
|
895
|
-
textToVerify = `${content.text}
|
|
1065
|
+
textToVerify = content.text ? `${content.text}
|
|
896
1066
|
|
|
897
|
-
[图片内容]: ${
|
|
1067
|
+
[图片内容]: ${imageResponse.content}` : imageResponse.content;
|
|
898
1068
|
}
|
|
899
1069
|
} catch (error) {
|
|
900
|
-
logger.warn("
|
|
1070
|
+
logger.warn("图片分析失败:", error);
|
|
901
1071
|
}
|
|
902
1072
|
}
|
|
903
1073
|
if (!textToVerify.trim()) {
|
|
@@ -954,7 +1124,14 @@ function apply(ctx, config) {
|
|
|
954
1124
|
(detail) => (0, import_koishi3.h)("message", { nickname: "事实核查", userId: session.selfId }, detail)
|
|
955
1125
|
);
|
|
956
1126
|
await session.send(summary);
|
|
957
|
-
|
|
1127
|
+
try {
|
|
1128
|
+
await session.send((0, import_koishi3.h)("message", { forward: true }, forwardNodes));
|
|
1129
|
+
} catch (forwardError) {
|
|
1130
|
+
logger.warn("合并转发发送失败,回退到普通消息:", forwardError);
|
|
1131
|
+
for (const detail of details) {
|
|
1132
|
+
await session.send(detail);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
958
1135
|
return;
|
|
959
1136
|
}
|
|
960
1137
|
const output = formatVerificationOutput(
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { Config } from '../config';
|
|
3
|
+
import { SearchResult } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* Anspire 搜索服务
|
|
6
|
+
* https://plugin.anspire.cn
|
|
7
|
+
*/
|
|
8
|
+
export declare class AnspireSearchAgent {
|
|
9
|
+
private ctx;
|
|
10
|
+
private config;
|
|
11
|
+
private apiKey;
|
|
12
|
+
private logger;
|
|
13
|
+
constructor(ctx: Context, config: Config);
|
|
14
|
+
/**
|
|
15
|
+
* 检查服务是否可用
|
|
16
|
+
*/
|
|
17
|
+
isAvailable(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* 执行搜索
|
|
20
|
+
*/
|
|
21
|
+
search(query: string): Promise<SearchResult>;
|
|
22
|
+
/**
|
|
23
|
+
* 格式化搜索结果
|
|
24
|
+
*/
|
|
25
|
+
private formatFindings;
|
|
26
|
+
/**
|
|
27
|
+
* 计算置信度
|
|
28
|
+
*/
|
|
29
|
+
private calculateConfidence;
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { Config } from '../config';
|
|
3
|
+
import { SearchResult } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* Tavily 搜索服务
|
|
6
|
+
* https://tavily.com/
|
|
7
|
+
*/
|
|
8
|
+
export declare class TavilySearchAgent {
|
|
9
|
+
private ctx;
|
|
10
|
+
private config;
|
|
11
|
+
private apiKey;
|
|
12
|
+
private logger;
|
|
13
|
+
constructor(ctx: Context, config: Config);
|
|
14
|
+
/**
|
|
15
|
+
* 检查服务是否可用
|
|
16
|
+
*/
|
|
17
|
+
isAvailable(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* 执行搜索
|
|
20
|
+
*/
|
|
21
|
+
search(query: string): Promise<SearchResult>;
|
|
22
|
+
/**
|
|
23
|
+
* 格式化搜索结果
|
|
24
|
+
*/
|
|
25
|
+
private formatFindings;
|
|
26
|
+
/**
|
|
27
|
+
* 计算置信度
|
|
28
|
+
*/
|
|
29
|
+
private calculateConfidence;
|
|
30
|
+
}
|
package/lib/utils/prompts.d.ts
CHANGED
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
* Prompt 模板集合
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
* 搜索Agent系统提示词
|
|
5
|
+
* 搜索Agent系统提示词 (精简版)
|
|
6
6
|
*/
|
|
7
|
-
export declare const SEARCH_AGENT_SYSTEM_PROMPT = "\u4F60\u662F\
|
|
7
|
+
export declare const SEARCH_AGENT_SYSTEM_PROMPT = "\u4F60\u662F\u4E8B\u5B9E\u6838\u67E5\u641C\u7D22\u5458\u3002\u641C\u7D22\u9A8C\u8BC1\u58F0\u660E\u7684\u76F8\u5173\u4FE1\u606F\u3002\n\n\u641C\u7D22\u89D2\u5EA6\uFF1A\u5B98\u65B9\u6765\u6E90\u3001\u65B0\u95FB\u62A5\u9053\u3001\u5B66\u672F\u7814\u7A76\u3001\u793E\u4EA4\u8BA8\u8BBA\u3001\u5386\u53F2\u80CC\u666F\u3002\n\n\u8F93\u51FAJSON\uFF1A\n```json\n{\"findings\":\"\u53D1\u73B0\u6458\u8981\",\"sources\":[\"\u6765\u6E90URL\"],\"supports\":true/false/null,\"confidence\":0.0-1.0}\n```\n\n\u8981\u6C42\uFF1A\u5BA2\u89C2\u4E2D\u7ACB\uFF0C\u6CE8\u660E\u6765\u6E90\u53EF\u4FE1\u5EA6\uFF0C\u627E\u4E0D\u5230\u5C31\u8BF4\u660E\u3002";
|
|
8
8
|
/**
|
|
9
|
-
* 生成搜索Agent的角度提示
|
|
9
|
+
* 生成搜索Agent的角度提示 (已废弃,现在每个Agent搜索所有角度)
|
|
10
10
|
*/
|
|
11
11
|
export declare function getSearchPerspectives(count: number): string[];
|
|
12
12
|
/**
|
|
13
|
-
* 搜索请求模板
|
|
13
|
+
* 搜索请求模板 (精简版)
|
|
14
14
|
*/
|
|
15
|
-
export declare function buildSearchPrompt(content: string,
|
|
15
|
+
export declare function buildSearchPrompt(content: string, _perspective: string): string;
|
|
16
16
|
/**
|
|
17
|
-
* 验证Agent系统提示词
|
|
17
|
+
* 验证Agent系统提示词 (精简版)
|
|
18
18
|
*/
|
|
19
|
-
export declare const VERIFY_AGENT_SYSTEM_PROMPT = "\u4F60\u662F\
|
|
19
|
+
export declare const VERIFY_AGENT_SYSTEM_PROMPT = "\u4F60\u662F\u4E8B\u5B9E\u6838\u67E5\u88C1\u5224\u3002\u57FA\u4E8E\u641C\u7D22\u8BC1\u636E\u505A\u51FA\u5224\u51B3\u3002\n\n\u5224\u51B3\u7C7B\u522B\uFF1ATRUE(\u771F\u5B9E)\u3001FALSE(\u865A\u5047)\u3001PARTIALLY_TRUE(\u90E8\u5206\u771F\u5B9E)\u3001UNCERTAIN(\u65E0\u6CD5\u786E\u5B9A)\n\n\u8F93\u51FAJSON\uFF1A\n```json\n{\"verdict\":\"TRUE/FALSE/PARTIALLY_TRUE/UNCERTAIN\",\"confidence\":0.0-1.0,\"reasoning\":\"\u5224\u51B3\u7406\u7531\",\"sources\":[\"\u6765\u6E90\"]}\n```\n\n\u539F\u5219\uFF1A\u8BC1\u636E\u4E0D\u8DB3\u65F6\u5224UNCERTAIN\uFF0C\u91CD\u89C6\u6743\u5A01\u6765\u6E90\uFF0C\u8003\u8651\u65F6\u6548\u6027\u3002";
|
|
20
20
|
/**
|
|
21
|
-
* 构建验证请求
|
|
21
|
+
* 构建验证请求 (精简版)
|
|
22
22
|
*/
|
|
23
23
|
export declare function buildVerifyPrompt(originalContent: string, searchResults: Array<{
|
|
24
24
|
perspective: string;
|
|
@@ -28,7 +28,7 @@ export declare function buildVerifyPrompt(originalContent: string, searchResults
|
|
|
28
28
|
/**
|
|
29
29
|
* 图片OCR提示词
|
|
30
30
|
*/
|
|
31
|
-
export declare const OCR_PROMPT = "\
|
|
31
|
+
export declare const OCR_PROMPT = "\u63D0\u53D6\u56FE\u7247\u4E2D\u7684\u6587\u5B57\u3002\u65E0\u6587\u5B57\u5219\u7B80\u8FF0\u56FE\u7247\u5185\u5BB9\u3002";
|
|
32
32
|
/**
|
|
33
33
|
* 输出格式化模板
|
|
34
34
|
*/
|
package/package.json
CHANGED