@gt-fe/eap-runtime 0.0.20 → 0.0.22
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/README.md +4 -3
- package/dist/api/handlers/invoke.handler.d.ts +7 -0
- package/dist/api/handlers/invoke.handler.js +52 -10
- package/dist/api/handlers/stream.handler.d.ts +6 -0
- package/dist/api/handlers/stream.handler.js +60 -39
- package/dist/api/routes/agent.routes.d.ts +8 -1
- package/dist/checkpoint/file-checkpoint-saver.d.ts +4 -0
- package/dist/checkpoint/file-checkpoint-saver.js +51 -22
- package/dist/checkpoint/index.d.ts +1 -1
- package/dist/checkpoint/index.js +1 -1
- package/dist/common/types.d.ts +2 -2
- package/dist/common/types.js +1 -1
- package/dist/execution/index.d.ts +2 -0
- package/dist/execution/index.js +2 -0
- package/dist/execution/interceptors.d.ts +40 -0
- package/dist/execution/interceptors.js +23 -0
- package/dist/execution/ports.d.ts +18 -0
- package/dist/execution/ports.js +13 -0
- package/dist/governance/audit-reporter.d.ts +14 -0
- package/dist/governance/audit-reporter.js +18 -0
- package/dist/governance/index.d.ts +2 -0
- package/dist/governance/index.js +1 -0
- package/dist/governance/permission-guard.d.ts +6 -2
- package/dist/governance/permission-guard.js +38 -5
- package/dist/graph/adapters/graph-nesting.d.ts +12 -0
- package/dist/graph/adapters/graph-nesting.js +29 -0
- package/dist/graph/adapters/index.d.ts +2 -0
- package/dist/graph/adapters/index.js +2 -0
- package/dist/graph/adapters/langgraph-checkpointer.d.ts +11 -0
- package/dist/graph/adapters/langgraph-checkpointer.js +48 -9
- package/dist/graph/adapters/langgraph.adapter.d.ts +33 -11
- package/dist/graph/adapters/langgraph.adapter.js +200 -98
- package/dist/graph/adapters/skill-isolate.d.ts +13 -0
- package/dist/graph/adapters/skill-isolate.js +39 -0
- package/dist/graph/adapters/subgraph-compiler.d.ts +7 -2
- package/dist/graph/adapters/subgraph-compiler.js +63 -30
- package/dist/graph/contracts/executable-graph.d.ts +33 -5
- package/dist/graph/hitl/hitl-handler.d.ts +13 -4
- package/dist/graph/hitl/hitl-handler.js +80 -7
- package/dist/graph/nodes/index.d.ts +1 -0
- package/dist/graph/nodes/index.js +1 -0
- package/dist/graph/nodes/llm-node.d.ts +3 -1
- package/dist/graph/nodes/llm-node.js +26 -2
- package/dist/graph/nodes/rag/dedup.d.ts +20 -0
- package/dist/graph/nodes/rag/dedup.js +77 -0
- package/dist/graph/nodes/rag/denoise.d.ts +58 -0
- package/dist/graph/nodes/rag/denoise.js +220 -0
- package/dist/graph/nodes/rag/fusion/fuse.d.ts +11 -0
- package/dist/graph/nodes/rag/fusion/fuse.js +25 -0
- package/dist/graph/nodes/rag/fusion/normalize.d.ts +9 -0
- package/dist/graph/nodes/rag/fusion/normalize.js +135 -0
- package/dist/graph/nodes/rag/index.d.ts +18 -0
- package/dist/graph/nodes/rag/index.js +17 -0
- package/dist/graph/nodes/rag/prompts.d.ts +12 -0
- package/dist/graph/nodes/rag/prompts.js +40 -0
- package/dist/graph/nodes/rag/rag-node.d.ts +21 -0
- package/dist/graph/nodes/rag/rag-node.js +135 -0
- package/dist/graph/nodes/rag/rag-search-tool.d.ts +48 -0
- package/dist/graph/nodes/rag/rag-search-tool.js +230 -0
- package/dist/graph/nodes/rag/react-loop.d.ts +57 -0
- package/dist/graph/nodes/rag/react-loop.js +467 -0
- package/dist/graph/nodes/rag/retrievers/http-embedder.d.ts +29 -0
- package/dist/graph/nodes/rag/retrievers/http-embedder.js +66 -0
- package/dist/graph/nodes/rag/retrievers/in-memory-retriever.d.ts +17 -0
- package/dist/graph/nodes/rag/retrievers/in-memory-retriever.js +18 -0
- package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.d.ts +46 -0
- package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.js +85 -0
- package/dist/graph/nodes/rag/tools.d.ts +90 -0
- package/dist/graph/nodes/rag/tools.js +260 -0
- package/dist/graph/nodes/rag/types.d.ts +226 -0
- package/dist/graph/nodes/rag/types.js +9 -0
- package/dist/graph/nodes/tool-node.d.ts +19 -1
- package/dist/graph/nodes/tool-node.js +158 -33
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/memory/semantic-retrieval.service.d.ts +1 -1
- package/dist/memory/semantic-retrieval.service.js +13 -23
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.js +2 -2
- package/dist/providers/local-llm-provider.d.ts +6 -0
- package/dist/providers/local-llm-provider.js +29 -5
- package/dist/providers/routing-llm-provider.d.ts +7 -2
- package/dist/providers/routing-llm-provider.js +71 -16
- package/dist/registry/deployment-snapshot.js +18 -4
- package/dist/registry/graph-loader.d.ts +2 -0
- package/dist/registry/graph-loader.js +39 -2
- package/dist/registry/preload-flags.d.ts +1 -1
- package/dist/registry/preload-flags.js +1 -1
- package/dist/runtime/agent-runtime.d.ts +5 -0
- package/dist/runtime/agent-runtime.js +107 -70
- package/dist/runtime/dev-rag-deps.d.ts +21 -0
- package/dist/runtime/dev-rag-deps.js +186 -0
- package/dist/runtime/dev-runtime.d.ts +18 -2
- package/dist/runtime/dev-runtime.js +68 -8
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/types.d.ts +10 -9
- package/dist/session/apply-session-compression.d.ts +85 -0
- package/dist/session/apply-session-compression.js +281 -0
- package/dist/session/apply-session-memory.d.ts +21 -0
- package/dist/session/apply-session-memory.js +48 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/persist-turn-messages.d.ts +57 -1
- package/dist/session/persist-turn-messages.js +160 -5
- package/dist/sse/events/index.d.ts +13 -4
- package/dist/sse/events/index.js +9 -1
- package/dist/sse/events/rag-events.d.ts +63 -0
- package/dist/sse/events/rag-events.js +9 -0
- package/dist/sse/events/session-events.d.ts +38 -0
- package/dist/sse/events/session-events.js +4 -0
- package/dist/token/index.d.ts +2 -1
- package/dist/token/index.js +2 -1
- package/dist/token/record-task-token-usage.d.ts +25 -0
- package/dist/token/record-task-token-usage.js +44 -0
- package/dist/token/token-detail-mode.d.ts +4 -4
- package/dist/token/token-detail-mode.js +4 -4
- package/dist/token/token-detail.d.ts +8 -0
- package/dist/token/token-detail.js +22 -0
- package/dist/token/token-stats.service.d.ts +2 -2
- package/dist/token/token-stats.service.js +1 -1
- package/dist/tool/create-tool-executor.d.ts +6 -0
- package/dist/tool/create-tool-executor.js +4 -3
- package/dist/tool/harness-primitives.d.ts +9 -1
- package/dist/tool/harness-primitives.js +43 -12
- package/dist/tool/index.d.ts +2 -1
- package/dist/tool/index.js +2 -1
- package/dist/tool/tool-confirmation.js +8 -5
- package/dist/tool/tool-executor.interface.d.ts +27 -0
- package/dist/tool/tool-orchestrator.d.ts +3 -1
- package/dist/tool/tool-orchestrator.js +15 -6
- package/dist/tool/tool-retry.d.ts +15 -0
- package/dist/tool/tool-retry.js +69 -0
- package/package.json +2 -2
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* denoise — 降噪管线(design-v2 §6.3,M4)
|
|
3
|
+
*
|
|
4
|
+
* 数万 token 候选压成数百 token,LLM 只做判定与选择、绝不产出正文:
|
|
5
|
+
* ① 近重去重(字符 n-gram Jaccard,纯函数,确定性优先)
|
|
6
|
+
* ② 一次结构化 LLM 判定(主力模型,temp 0)→ DenoiseVerdict[]
|
|
7
|
+
* { rawId, relevant, reason, keepSentences }——rawId 为复合键 sourceId:rawId
|
|
8
|
+
* (rawId 仅源内唯一;payload 标签、verdict 匹配、filtered 项三处必须同用复合键,
|
|
9
|
+
* 裸 id 跨源碰撞会让两份文档共享同一 verdict:错滤另一源、按别家句子编号截正文)
|
|
10
|
+
* ③ 代码按句子编号拼装原文(零改写:返回文本逐字出自原文句子)
|
|
11
|
+
*
|
|
12
|
+
* 防御策略(§6.5 误滤宁保守):
|
|
13
|
+
* verdict 调用或解析失败 / 文档缺 verdict 项 / keepSentences 无有效编号 → 整篇保留;
|
|
14
|
+
* 判定存活集为空 → react-loop 走 QUERY_REWRITE 重写兜底(本模块只报 verdictEmpty)。
|
|
15
|
+
*/
|
|
16
|
+
import type { ILLMProvider } from '../llm-node.js';
|
|
17
|
+
import type { DenoiseVerdict, RagTokenUsage, RetrievalDoc } from './types.js';
|
|
18
|
+
export declare const RAG_EMPTY_MARKER = "[rag] \u672A\u627E\u5230\u9AD8\u76F8\u5173\u7ED3\u679C";
|
|
19
|
+
/** 格式化(降噪后的)文档集为最终上下文 */
|
|
20
|
+
export declare function formatContext(query: string, docs: RetrievalDoc[]): string;
|
|
21
|
+
export interface DenoiseInput {
|
|
22
|
+
query: string;
|
|
23
|
+
/** 融合后的候选文档集(可能数万 token) */
|
|
24
|
+
docs: RetrievalDoc[];
|
|
25
|
+
llmProvider: ILLMProvider;
|
|
26
|
+
/** 主力模型(与决策同款,不分档) */
|
|
27
|
+
model: string;
|
|
28
|
+
nodeId: string;
|
|
29
|
+
/** 近重判定阈值,缺省 0.8 */
|
|
30
|
+
nearDupThreshold?: number;
|
|
31
|
+
tokenUsage: RagTokenUsage[];
|
|
32
|
+
}
|
|
33
|
+
export interface DenoiseResult {
|
|
34
|
+
/** 存活文档(正文已按 keepSentences 截取) */
|
|
35
|
+
docs: RetrievalDoc[];
|
|
36
|
+
contextText: string;
|
|
37
|
+
/** 判定后存活集是否为空(react-loop 据此触发重写兜底) */
|
|
38
|
+
verdictEmpty: boolean;
|
|
39
|
+
/** 被滤文档与原因(空结果重写定向 / rag.denoise 事件用);rawId 为复合键 sourceId:rawId(rewrite 归因整键匹配用) */
|
|
40
|
+
filtered: Array<{
|
|
41
|
+
rawId: string;
|
|
42
|
+
reason: string;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
export declare function denoise(input: DenoiseInput): Promise<DenoiseResult>;
|
|
46
|
+
export declare function rewriteQuery(input: {
|
|
47
|
+
query: string;
|
|
48
|
+
reasons: string[];
|
|
49
|
+
llmProvider: ILLMProvider;
|
|
50
|
+
model: string;
|
|
51
|
+
nodeId: string;
|
|
52
|
+
tokenUsage: RagTokenUsage[];
|
|
53
|
+
}): Promise<string | undefined>;
|
|
54
|
+
/** 确定性分句:按中英文句终符与换行切,句符随句保留(零改写:句子为原文子串) */
|
|
55
|
+
export declare function splitSentences(content: string): string[];
|
|
56
|
+
/** 解析判定输出:剥代码围栏、截取首尾方括号;解析失败返回 undefined(保守放行全部)。
|
|
57
|
+
* rawId 不校验复合键形态——LLM 抄错(裸 id/截断)时 find 不中,该文档无 verdict 即整篇保留,仍走保守路径 */
|
|
58
|
+
export declare function parseVerdicts(text: string): DenoiseVerdict[] | undefined;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* denoise — 降噪管线(design-v2 §6.3,M4)
|
|
3
|
+
*
|
|
4
|
+
* 数万 token 候选压成数百 token,LLM 只做判定与选择、绝不产出正文:
|
|
5
|
+
* ① 近重去重(字符 n-gram Jaccard,纯函数,确定性优先)
|
|
6
|
+
* ② 一次结构化 LLM 判定(主力模型,temp 0)→ DenoiseVerdict[]
|
|
7
|
+
* { rawId, relevant, reason, keepSentences }——rawId 为复合键 sourceId:rawId
|
|
8
|
+
* (rawId 仅源内唯一;payload 标签、verdict 匹配、filtered 项三处必须同用复合键,
|
|
9
|
+
* 裸 id 跨源碰撞会让两份文档共享同一 verdict:错滤另一源、按别家句子编号截正文)
|
|
10
|
+
* ③ 代码按句子编号拼装原文(零改写:返回文本逐字出自原文句子)
|
|
11
|
+
*
|
|
12
|
+
* 防御策略(§6.5 误滤宁保守):
|
|
13
|
+
* verdict 调用或解析失败 / 文档缺 verdict 项 / keepSentences 无有效编号 → 整篇保留;
|
|
14
|
+
* 判定存活集为空 → react-loop 走 QUERY_REWRITE 重写兜底(本模块只报 verdictEmpty)。
|
|
15
|
+
*/
|
|
16
|
+
import { DENOISE_VERDICT, QUERY_REWRITE } from './prompts.js';
|
|
17
|
+
import { dedupNearDup } from './dedup.js';
|
|
18
|
+
import { docTitle } from './tools.js';
|
|
19
|
+
export const RAG_EMPTY_MARKER = '[rag] 未找到高相关结果';
|
|
20
|
+
/** 格式化(降噪后的)文档集为最终上下文 */
|
|
21
|
+
export function formatContext(query, docs) {
|
|
22
|
+
if (!docs.length) {
|
|
23
|
+
return `${RAG_EMPTY_MARKER}(query: ${query})`;
|
|
24
|
+
}
|
|
25
|
+
const header = `[rag] 检索上下文(query: ${query},共 ${docs.length} 条,已判定相关并压缩)`;
|
|
26
|
+
const blocks = docs.map((d, i) => {
|
|
27
|
+
return `【${i + 1}】来源:${d.sourceId} / ${docTitle(d)}\n${d.content}`;
|
|
28
|
+
});
|
|
29
|
+
return [header, ...blocks].join('\n\n');
|
|
30
|
+
}
|
|
31
|
+
export async function denoise(input) {
|
|
32
|
+
if (input.docs.length === 0) {
|
|
33
|
+
return {
|
|
34
|
+
docs: [],
|
|
35
|
+
contextText: `${RAG_EMPTY_MARKER}(query: ${input.query})`,
|
|
36
|
+
verdictEmpty: true,
|
|
37
|
+
filtered: [],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// ① 近重去重(确定性,先砍掉不浪费 LLM token)
|
|
41
|
+
const candidates = dedupNearDup(input.docs, input.nearDupThreshold ?? 0.8);
|
|
42
|
+
if (candidates.length === 0) {
|
|
43
|
+
return {
|
|
44
|
+
docs: [],
|
|
45
|
+
contextText: `${RAG_EMPTY_MARKER}(query: ${input.query})`,
|
|
46
|
+
verdictEmpty: true,
|
|
47
|
+
filtered: [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// ② 一次结构化判定 + ③ 按编号拼装
|
|
51
|
+
const split = candidates.map((d) => splitSentencesWithSeps(d.content));
|
|
52
|
+
const sentences = split.map((s) => s.sentences);
|
|
53
|
+
const verdicts = await judgeVerdicts(input, candidates, sentences);
|
|
54
|
+
const kept = [];
|
|
55
|
+
const filtered = [];
|
|
56
|
+
candidates.forEach((doc, i) => {
|
|
57
|
+
// 匹配键必须是复合键 sourceId:rawId(与 payload 标签同构):rawId 仅源内唯一
|
|
58
|
+
const key = `${doc.sourceId}:${doc.rawId}`;
|
|
59
|
+
const v = verdicts?.find((x) => x.rawId === key);
|
|
60
|
+
if (v && !v.relevant) {
|
|
61
|
+
filtered.push({ rawId: key, reason: v.reason });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
kept.push({ ...doc, content: pickSentences(split[i], v?.keepSentences) });
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
docs: kept,
|
|
68
|
+
contextText: formatContext(input.query, kept),
|
|
69
|
+
verdictEmpty: kept.length === 0,
|
|
70
|
+
filtered,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// ─────────────────────────────────────────────
|
|
74
|
+
// 空结果兜底:query 重写(react-loop 调用)
|
|
75
|
+
// ─────────────────────────────────────────────
|
|
76
|
+
export async function rewriteQuery(input) {
|
|
77
|
+
const reasonSummary = input.reasons.slice(0, 5).join(';');
|
|
78
|
+
const user = `原查询:${input.query}\n被滤原因摘要:${reasonSummary || '(检索无结果或未提供)'}`;
|
|
79
|
+
const config = { model: input.model, systemPrompt: QUERY_REWRITE, temperature: 0 };
|
|
80
|
+
// 调用层异常不穿透:重写失败回退原 query,不阻塞检索收束
|
|
81
|
+
let response;
|
|
82
|
+
try {
|
|
83
|
+
response = await invokeTracked(input.llmProvider, input.model, input.nodeId, input.tokenUsage, user, config);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.warn('[rag-denoise] query 重写调用失败,回退原 query', err);
|
|
87
|
+
return input.query;
|
|
88
|
+
}
|
|
89
|
+
const text = response.content
|
|
90
|
+
.trim()
|
|
91
|
+
.replace(/^["'「『]+/, '')
|
|
92
|
+
.replace(/["'」』]+$/, '')
|
|
93
|
+
.trim();
|
|
94
|
+
return text.length > 0 ? text : undefined;
|
|
95
|
+
}
|
|
96
|
+
// ─────────────────────────────────────────────
|
|
97
|
+
// 内部:判定调用 / 分句 / 解析 / 选句
|
|
98
|
+
// ─────────────────────────────────────────────
|
|
99
|
+
async function judgeVerdicts(input, candidates, sentences) {
|
|
100
|
+
const payload = candidates
|
|
101
|
+
.map((d, i) => {
|
|
102
|
+
const numbered = sentences[i].map((s, j) => ` ${j + 1}. ${s}`).join('\n');
|
|
103
|
+
// 标签必须带复合键 sourceId:rawId:跨源同 rawId 时裸 id 标签无法区分
|
|
104
|
+
return `【${d.sourceId}:${d.rawId}】${docTitle(d)}\n${numbered}`;
|
|
105
|
+
})
|
|
106
|
+
.join('\n\n');
|
|
107
|
+
const user = `用户问题:${input.query}\n\n候选文档:\n${payload}`;
|
|
108
|
+
const config = {
|
|
109
|
+
model: input.model,
|
|
110
|
+
systemPrompt: DENOISE_VERDICT,
|
|
111
|
+
temperature: 0,
|
|
112
|
+
};
|
|
113
|
+
// 调用层异常不穿透:判定失败视同 verdicts 缺失 → 整篇保留(宁保守)
|
|
114
|
+
let response;
|
|
115
|
+
try {
|
|
116
|
+
response = await invokeTracked(input.llmProvider, input.model, input.nodeId, input.tokenUsage, user, config);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
console.warn('[rag-denoise] 判定调用失败,候选整篇保留', err);
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
return parseVerdicts(response.content);
|
|
123
|
+
}
|
|
124
|
+
/** 统一的带记账 LLM 调用(denoise/rewrite 与 decide 同一套记账口径) */
|
|
125
|
+
async function invokeTracked(provider, model, nodeId, tokenUsage, user, config) {
|
|
126
|
+
const startedAt = Date.now();
|
|
127
|
+
const response = await provider.invoke([{ role: 'user', content: user }], config);
|
|
128
|
+
tokenUsage.push({
|
|
129
|
+
nodeId,
|
|
130
|
+
model: response.model || model,
|
|
131
|
+
inputTokens: response.inputTokens,
|
|
132
|
+
outputTokens: response.outputTokens,
|
|
133
|
+
durationMs: Date.now() - startedAt,
|
|
134
|
+
});
|
|
135
|
+
return response;
|
|
136
|
+
}
|
|
137
|
+
/** 确定性分句:按中英文句终符与换行切,句符随句保留(零改写:句子为原文子串) */
|
|
138
|
+
export function splitSentences(content) {
|
|
139
|
+
return splitSentencesWithSeps(content).sentences;
|
|
140
|
+
}
|
|
141
|
+
/** 分句并记录句间分隔符(seps[i] = 第 i 句与第 i+1 句之间的原文空白/换行)。
|
|
142
|
+
* 拼装侧必须按 seps 还原句间空白:中文通常空串(行为不变),英文空格不还原则
|
|
143
|
+
* 多句 keep 后粘连成 "days.Ship"。
|
|
144
|
+
* 英文句点仅在后随空白时切(`3.14`、`e.g.` 等点后无空格不切);`e.g. ` 后随空格
|
|
145
|
+
* 仍会误切——不做缩写白名单,宁误切不粘连(误切只损句子粒度,不损内容) */
|
|
146
|
+
function splitSentencesWithSeps(content) {
|
|
147
|
+
const raw = content.split(/((?<=[。!?!?;;])\s*|(?<=\.)\s+|\n+)/);
|
|
148
|
+
const sentences = [];
|
|
149
|
+
const seps = [];
|
|
150
|
+
for (let i = 0; i < raw.length; i += 2) {
|
|
151
|
+
const text = (raw[i] ?? '').trim();
|
|
152
|
+
const sep = raw[i + 1] ?? '';
|
|
153
|
+
if (!text) {
|
|
154
|
+
// 空文本段只出现在首尾或相邻分隔符之间:句中空段把后随分隔符并入前句,空白不丢
|
|
155
|
+
if (seps.length > 0)
|
|
156
|
+
seps[seps.length - 1] += sep;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
sentences.push(text);
|
|
160
|
+
seps.push(sep);
|
|
161
|
+
}
|
|
162
|
+
return sentences.length > 0 ? { sentences, seps } : { sentences: [content.trim()], seps: [] };
|
|
163
|
+
}
|
|
164
|
+
/** 解析判定输出:剥代码围栏、截取首尾方括号;解析失败返回 undefined(保守放行全部)。
|
|
165
|
+
* rawId 不校验复合键形态——LLM 抄错(裸 id/截断)时 find 不中,该文档无 verdict 即整篇保留,仍走保守路径 */
|
|
166
|
+
export function parseVerdicts(text) {
|
|
167
|
+
const stripped = text.replace(/```(?:json)?/g, '');
|
|
168
|
+
const start = stripped.indexOf('[');
|
|
169
|
+
const end = stripped.lastIndexOf(']');
|
|
170
|
+
if (start < 0 || end <= start)
|
|
171
|
+
return undefined;
|
|
172
|
+
let parsed;
|
|
173
|
+
try {
|
|
174
|
+
parsed = JSON.parse(stripped.slice(start, end + 1));
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
if (!Array.isArray(parsed))
|
|
180
|
+
return undefined;
|
|
181
|
+
const verdicts = parsed
|
|
182
|
+
.filter((x) => x !== null && typeof x === 'object')
|
|
183
|
+
.map((x) => {
|
|
184
|
+
const keep = Array.isArray(x['keepSentences'])
|
|
185
|
+
? x['keepSentences'].filter((n) => typeof n === 'number')
|
|
186
|
+
: undefined;
|
|
187
|
+
return {
|
|
188
|
+
rawId: String(x['rawId'] ?? ''),
|
|
189
|
+
relevant: x['relevant'] !== false, // 缺省 true(宁保守)
|
|
190
|
+
reason: String(x['reason'] ?? ''),
|
|
191
|
+
...(keep && keep.length > 0 ? { keepSentences: keep } : {}),
|
|
192
|
+
};
|
|
193
|
+
})
|
|
194
|
+
.filter((v) => v.rawId.length > 0);
|
|
195
|
+
return verdicts.length > 0 ? verdicts : undefined;
|
|
196
|
+
}
|
|
197
|
+
/** keepSentences 有效则按编号拼装原文句子,否则整篇(缺省/编号全无效 → 整篇,宁保守)。
|
|
198
|
+
* prompt 未约束编号顺序与唯一性:编号是原文位置,不去重会复读、乱序拼装条件句主句对调,
|
|
199
|
+
* 拼装侧兜底去重 + 升序 */
|
|
200
|
+
function pickSentences(split, keep) {
|
|
201
|
+
const { sentences, seps } = split;
|
|
202
|
+
let indices;
|
|
203
|
+
if (!keep || keep.length === 0) {
|
|
204
|
+
indices = sentences.map((_, i) => i);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
const picked = [...new Set(keep)]
|
|
208
|
+
.filter((n) => Number.isInteger(n) && n >= 1 && n <= sentences.length)
|
|
209
|
+
.sort((a, b) => a - b);
|
|
210
|
+
indices = picked.length > 0 ? picked.map((n) => n - 1) : sentences.map((_, i) => i);
|
|
211
|
+
}
|
|
212
|
+
let out = '';
|
|
213
|
+
indices.forEach((idx, order) => {
|
|
214
|
+
// 句间用前一句的原文分隔符还原(中文空串不变;英文空格保留)
|
|
215
|
+
if (order > 0)
|
|
216
|
+
out += seps[indices[order - 1]] ?? '';
|
|
217
|
+
out += sentences[idx];
|
|
218
|
+
});
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fuse — 融合截断(纯函数)
|
|
3
|
+
*
|
|
4
|
+
* 各源只在源内取 topN(不跨源排序/比较分数),按源序并入、跨源去重、截断 totalK。
|
|
5
|
+
* 源间用量分配由 ReAct 决策动态决定(调哪个源/调几次/每次 topK),
|
|
6
|
+
* 静态配额已移除:异构源权重无语义,防单源灌水由调用级 topK 与 denoise 承担。
|
|
7
|
+
* @see design-v2 §6.2
|
|
8
|
+
*/
|
|
9
|
+
import type { RetrievalDoc } from '../types.js';
|
|
10
|
+
export declare const DEFAULT_TOTAL_K = 10;
|
|
11
|
+
export declare function fuse(perSource: Map<string, RetrievalDoc[]>, totalK?: number): RetrievalDoc[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fuse — 融合截断(纯函数)
|
|
3
|
+
*
|
|
4
|
+
* 各源只在源内取 topN(不跨源排序/比较分数),按源序并入、跨源去重、截断 totalK。
|
|
5
|
+
* 源间用量分配由 ReAct 决策动态决定(调哪个源/调几次/每次 topK),
|
|
6
|
+
* 静态配额已移除:异构源权重无语义,防单源灌水由调用级 topK 与 denoise 承担。
|
|
7
|
+
* @see design-v2 §6.2
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_TOTAL_K = 10;
|
|
10
|
+
export function fuse(perSource, totalK = DEFAULT_TOTAL_K) {
|
|
11
|
+
const seen = new Set(); // 跨源去重(sourceId:rawId)
|
|
12
|
+
const merged = [];
|
|
13
|
+
for (const [, docs] of perSource) {
|
|
14
|
+
for (const d of docs) {
|
|
15
|
+
if (merged.length >= totalK)
|
|
16
|
+
return merged;
|
|
17
|
+
const key = `${d.sourceId}:${d.rawId}`;
|
|
18
|
+
if (seen.has(key))
|
|
19
|
+
continue;
|
|
20
|
+
seen.add(key);
|
|
21
|
+
merged.push(d);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return merged; // 不排序:候选集不重叠,跨源排序无意义(§6 前提)
|
|
25
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 知识库工具结果归一化 — 字符串 content → RetrievalDoc[]
|
|
3
|
+
*
|
|
4
|
+
* 知识库是外部黑盒,最低契约(design-v2 §6.1):
|
|
5
|
+
* 返回有序列表,每条至少有 content 和某种唯一 id;score 可选。
|
|
6
|
+
* 本层做防御性解析:JSON 数组 / 常见包裹字段 / 单条对象 / 纯文本逐级降级。
|
|
7
|
+
*/
|
|
8
|
+
import type { RetrievalDoc } from '../types.js';
|
|
9
|
+
export declare function normalizeToolResult(content: string, toolName: string, sourceId?: string): RetrievalDoc[];
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 知识库工具结果归一化 — 字符串 content → RetrievalDoc[]
|
|
3
|
+
*
|
|
4
|
+
* 知识库是外部黑盒,最低契约(design-v2 §6.1):
|
|
5
|
+
* 返回有序列表,每条至少有 content 和某种唯一 id;score 可选。
|
|
6
|
+
* 本层做防御性解析:JSON 数组 / 常见包裹字段 / 单条对象 / 纯文本逐级降级。
|
|
7
|
+
*/
|
|
8
|
+
const LIST_WRAPPER_KEYS = ['items', 'results', 'rows', 'documents', 'data', 'list', 'references'];
|
|
9
|
+
const CONTENT_KEYS = ['content', 'text', 'chunk', 'document', 'snippet', 'answer'];
|
|
10
|
+
const ID_KEYS = ['id', 'chunkId', 'docId', 'rawId', 'documentId'];
|
|
11
|
+
const META_KEYS = ['title', 'fileName', 'source', 'url', 'updatedAt', 'filePath', 'sourceTitle'];
|
|
12
|
+
export function normalizeToolResult(content, toolName, sourceId = 'kb') {
|
|
13
|
+
if (!content.trim())
|
|
14
|
+
return [];
|
|
15
|
+
let parsed;
|
|
16
|
+
try {
|
|
17
|
+
parsed = JSON.parse(content);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return textFallback(content, toolName, sourceId);
|
|
21
|
+
}
|
|
22
|
+
const unwrapped = unwrapGatewayEnvelope(parsed);
|
|
23
|
+
const list = asDocList(unwrapped);
|
|
24
|
+
if (!list)
|
|
25
|
+
return textFallback(content, toolName, sourceId);
|
|
26
|
+
const docs = [];
|
|
27
|
+
list.forEach((item, idx) => {
|
|
28
|
+
const doc = toDoc(item, idx, toolName, sourceId);
|
|
29
|
+
if (doc)
|
|
30
|
+
docs.push(doc);
|
|
31
|
+
});
|
|
32
|
+
// 列表条目全缺正文(引用仅元数据)且一条 doc 未产出时,回退单条对象路径:
|
|
33
|
+
// 顶层 answer 等正文键还能命中,避免有效答案被误报空手;部分成功维持列表优先;
|
|
34
|
+
// 顶层数组形态无单条可回退,保持结构化空手语义
|
|
35
|
+
if (docs.length === 0 && !Array.isArray(unwrapped) && unwrapped && typeof unwrapped === 'object') {
|
|
36
|
+
const whole = toDoc(unwrapped, 0, toolName, sourceId);
|
|
37
|
+
if (whole)
|
|
38
|
+
return [whole];
|
|
39
|
+
}
|
|
40
|
+
// 空列表是合法的"检索无结果"(结构化空手),保持空返回;
|
|
41
|
+
// textFallback 只用于完全无法解析为列表的返回
|
|
42
|
+
return docs;
|
|
43
|
+
}
|
|
44
|
+
function asDocList(parsed) {
|
|
45
|
+
if (Array.isArray(parsed))
|
|
46
|
+
return parsed;
|
|
47
|
+
if (parsed && typeof parsed === 'object') {
|
|
48
|
+
for (const key of LIST_WRAPPER_KEYS) {
|
|
49
|
+
const v = parsed[key];
|
|
50
|
+
// 空列表包裹字段跳过继续找(如对话式 KB references 空但 answer 有值),
|
|
51
|
+
// 非空包裹字段优先成文档列表
|
|
52
|
+
if (Array.isArray(v) && v.length > 0)
|
|
53
|
+
return v;
|
|
54
|
+
}
|
|
55
|
+
return [parsed]; // 单条对象
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* gateway 包裹形解包:{code,success,result:{content:"[...]"}}(dev 直连 http 工具
|
|
61
|
+
* 无网关解包层,执行器把整个响应体当 content 回传)。仅当内层字符串以 '[' 开头
|
|
62
|
+
* 才尝试再解析——普通文本 content 走 textFallback 语义不受影响。
|
|
63
|
+
*/
|
|
64
|
+
function unwrapGatewayEnvelope(parsed) {
|
|
65
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
66
|
+
return parsed;
|
|
67
|
+
const obj = parsed;
|
|
68
|
+
const candidates = [
|
|
69
|
+
obj['result']?.['content'],
|
|
70
|
+
obj['content'],
|
|
71
|
+
];
|
|
72
|
+
for (const c of candidates) {
|
|
73
|
+
if (typeof c !== 'string' || !c.trim())
|
|
74
|
+
continue;
|
|
75
|
+
if (c.trim().startsWith('[')) {
|
|
76
|
+
try {
|
|
77
|
+
const inner = JSON.parse(c);
|
|
78
|
+
if (Array.isArray(inner))
|
|
79
|
+
return inner;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
/* 落到下面的文本保留 */
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// 包裹形里的纯文本 content:保真为单条文档(宁保留不丢弃)
|
|
86
|
+
return [{ content: c }];
|
|
87
|
+
}
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
function toDoc(item, idx, toolName, sourceId) {
|
|
91
|
+
if (typeof item === 'string') {
|
|
92
|
+
return item.trim()
|
|
93
|
+
? { content: item, sourceId, rawId: `${toolName}#${idx}` }
|
|
94
|
+
: undefined;
|
|
95
|
+
}
|
|
96
|
+
if (!item || typeof item !== 'object')
|
|
97
|
+
return undefined;
|
|
98
|
+
const obj = item;
|
|
99
|
+
const content = firstString(obj, CONTENT_KEYS);
|
|
100
|
+
if (!content)
|
|
101
|
+
return undefined;
|
|
102
|
+
const rawId = firstString(obj, ID_KEYS) ?? `${toolName}#${idx}`;
|
|
103
|
+
const score = typeof obj['score'] === 'number' ? obj['score'] : undefined;
|
|
104
|
+
const metadata = {};
|
|
105
|
+
for (const key of META_KEYS) {
|
|
106
|
+
if (obj[key] !== undefined)
|
|
107
|
+
metadata[key] = obj[key];
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
content,
|
|
111
|
+
sourceId,
|
|
112
|
+
rawId,
|
|
113
|
+
...(score !== undefined ? { score } : {}),
|
|
114
|
+
...(Object.keys(metadata).length > 0 ? { metadata } : {}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** 非结构化返回:整段文本降级为单条结果,不静默丢弃 */
|
|
118
|
+
function textFallback(content, toolName, sourceId) {
|
|
119
|
+
return [
|
|
120
|
+
{
|
|
121
|
+
content,
|
|
122
|
+
sourceId,
|
|
123
|
+
rawId: `${toolName}:raw`,
|
|
124
|
+
metadata: { note: '非结构化返回,整段作为单条结果' },
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
function firstString(obj, keys) {
|
|
129
|
+
for (const key of keys) {
|
|
130
|
+
const v = obj[key];
|
|
131
|
+
if (typeof v === 'string' && v.trim())
|
|
132
|
+
return v;
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RAG 检索子智能体节点 — 公共导出
|
|
3
|
+
*
|
|
4
|
+
* @see mt-docs/1-requirements/specs/agentic-rag/design-v2.md
|
|
5
|
+
*/
|
|
6
|
+
export { RagNode } from './rag-node.js';
|
|
7
|
+
export { runReactLoop, type RagLoopInput, type RagLoopOutput } from './react-loop.js';
|
|
8
|
+
export { buildRagTools, briefList, RAG_FINISH_TOOL_NAME, LOCAL_SEARCH_TOOL_NAME, KEYWORD_SEARCH_TOOL_NAME, type RagToolSet, } from './tools.js';
|
|
9
|
+
export { normalizeToolResult } from './fusion/normalize.js';
|
|
10
|
+
export { fuse, DEFAULT_TOTAL_K } from './fusion/fuse.js';
|
|
11
|
+
export { dedupExact, dedupNearDup } from './dedup.js';
|
|
12
|
+
export { denoise, rewriteQuery, splitSentences, parseVerdicts, formatContext, RAG_EMPTY_MARKER, type DenoiseInput, type DenoiseResult, } from './denoise.js';
|
|
13
|
+
export { RAG_REACT, DENOISE_VERDICT, QUERY_REWRITE } from './prompts.js';
|
|
14
|
+
export { InMemoryRetriever } from './retrievers/in-memory-retriever.js';
|
|
15
|
+
export { RagSearchToolExecutor, ragSearchLoadedTool, RAG_SEARCH_TOOL_NAME, type RagSearchToolOptions, } from './rag-search-tool.js';
|
|
16
|
+
export { HttpEmbedder, type HttpEmbedderOptions } from './retrievers/http-embedder.js';
|
|
17
|
+
export { LocalSqliteRetriever, type LocalSqliteRetrieverOptions, } from './retrievers/local-sqlite-retriever.js';
|
|
18
|
+
export type { RetrievalDoc, IRetriever, IEmbedder, VectorRow, KeywordRow, VectorEnginePort, RagNodeConfig, RagNodeDeps, RagTokenUsage, DenoiseVerdict, } from './types.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RAG 检索子智能体节点 — 公共导出
|
|
3
|
+
*
|
|
4
|
+
* @see mt-docs/1-requirements/specs/agentic-rag/design-v2.md
|
|
5
|
+
*/
|
|
6
|
+
export { RagNode } from './rag-node.js';
|
|
7
|
+
export { runReactLoop } from './react-loop.js';
|
|
8
|
+
export { buildRagTools, briefList, RAG_FINISH_TOOL_NAME, LOCAL_SEARCH_TOOL_NAME, KEYWORD_SEARCH_TOOL_NAME, } from './tools.js';
|
|
9
|
+
export { normalizeToolResult } from './fusion/normalize.js';
|
|
10
|
+
export { fuse, DEFAULT_TOTAL_K } from './fusion/fuse.js';
|
|
11
|
+
export { dedupExact, dedupNearDup } from './dedup.js';
|
|
12
|
+
export { denoise, rewriteQuery, splitSentences, parseVerdicts, formatContext, RAG_EMPTY_MARKER, } from './denoise.js';
|
|
13
|
+
export { RAG_REACT, DENOISE_VERDICT, QUERY_REWRITE } from './prompts.js';
|
|
14
|
+
export { InMemoryRetriever } from './retrievers/in-memory-retriever.js';
|
|
15
|
+
export { RagSearchToolExecutor, ragSearchLoadedTool, RAG_SEARCH_TOOL_NAME, } from './rag-search-tool.js';
|
|
16
|
+
export { HttpEmbedder } from './retrievers/http-embedder.js';
|
|
17
|
+
export { LocalSqliteRetriever, } from './retrievers/local-sqlite-retriever.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RAG 三 Prompt — 一职一 prompt(design-v2 §5.5)
|
|
3
|
+
*
|
|
4
|
+
* 三个 prompt 均用同一主力模型(2026-08-19 决策不分档);
|
|
5
|
+
* 判定/重写调用 temp 0。
|
|
6
|
+
*/
|
|
7
|
+
/** RAG_REACT — 循环决策:整定 / 检索 / 终止 */
|
|
8
|
+
export declare const RAG_REACT = "\u4F60\u662F\u4F01\u4E1A\u77E5\u8BC6\u68C0\u7D22\u667A\u80FD\u4F53\uFF0C\u4E3A\u4E0B\u6E38\u667A\u80FD\u4F53\u68C0\u7D22\u8BC1\u636E\uFF0C\u4E0D\u76F4\u63A5\u56DE\u7B54\u7528\u6237\u95EE\u9898\u3002\n\n\u884C\u4E3A\u89C4\u5219\uFF1A\n- \u9996\u8F6E\u5148\u5224\u65AD\u68C0\u7D22\u4EFB\u52A1\u662F\u5426\u81EA\u5305\u542B\uFF1B\u6709\u6307\u4EE3/\u7701\u7565\u65F6\u5148\u7ED3\u5408\u5BF9\u8BDD\u5386\u53F2\u6539\u5199\u4E3A\u72EC\u7ACB\u68C0\u7D22\u67E5\u8BE2\n- \u590D\u6742\u95EE\u9898\u53EF\u62C6\u5B50\u67E5\u8BE2\uFF0C\u5355\u8F6E\u5E76\u884C\u8C03\u7528\u591A\u4E2A\u68C0\u7D22\u5DE5\u5177\uFF08\u5982\u540C\u65F6\u67E5\u77E5\u8BC6\u5E93\u4E0E\u672C\u5730\u6587\u4EF6\uFF09\n- \u6BCF\u8F6E\u62FF\u5230\u65B0\u7ED3\u679C\u540E\u5148\u8BC4\u4F30\uFF1A\u5DF2\u8986\u76D6\u54EA\u4E9B\u5B50\u95EE\u9898\u3001\u8FD8\u7F3A\u4EC0\u4E48\uFF1B\u53EA\u68C0\u7D22\u7F3A\u5931\u7684\u4FE1\u606F\uFF0C\u4E0D\u91CD\u590D\u68C0\u7D22\u5DF2\u8986\u76D6\u90E8\u5206\n- \u5355\u4E2A\u6765\u6E90\u5931\u8D25\u65F6\u6362\u5176\u4ED6\u6765\u6E90\u91CD\u8BD5\n- \u67D0\u6765\u6E90\u8FDE\u7EED\u8FD4\u56DE\u7A7A\u3001\u6216\u7ED3\u679C\u5168\u90E8\u88AB\u5224\u5B9A\u4E0D\u76F8\u5173\u88AB\u6EE4\u9664\u65F6\uFF0C\u4E0B\u4E00\u8F6E\u5FC5\u987B\u6539\u7528\u5176\u4ED6\u6570\u636E\u6E90\u5DE5\u5177\uFF08\u6216\u5E76\u884C\u591A\u6E90\uFF09\uFF0C\u7981\u6B62\u5BF9\u540C\u4E00\u6765\u6E90\u53CD\u590D\u6539\u5199\u91CD\u67E5\n\n\u7EC8\u6B62\uFF08\u8C03\u7528 rag_finish \u5373\u6536\u624B\uFF0C\u6EE1\u8DB3\u5176\u4E00\u5373\u53EF\uFF09\uFF1A\n- \u68C0\u7D22\u4EFB\u52A1\u7684\u5404\u5B50\u95EE\u9898\u5747\u6709\u9AD8\u76F8\u5173\u7247\u6BB5\u8986\u76D6\n- \u6700\u8FD1\u4E00\u8F6E\u65B0\u589E\u7ED3\u679C\u4E0E\u5DF2\u6709\u7ED3\u679C\u9AD8\u5EA6\u91CD\u590D\n- \u5DF2\u6536\u96C6\u8DB3\u591F\u7684\u9AD8\u76F8\u5173\u7247\u6BB5";
|
|
9
|
+
/** DENOISE_VERDICT — 相关性判定 + 句子编号抽取(M4,CRAG grader 式结构化输出) */
|
|
10
|
+
export declare const DENOISE_VERDICT = "\u4F60\u662F\u8BC4\u4F30\u68C0\u7D22\u6587\u6863\u4E0E\u7528\u6237\u95EE\u9898\u76F8\u5173\u6027\u7684\u5224\u5206\u5668\u3002\n\n\u8F93\u5165\u5305\u542B\u7528\u6237\u95EE\u9898\u548C\u82E5\u5E72\u5019\u9009\u6587\u6863\uFF08\u6BCF\u7BC7\u5E26\u590D\u5408\u6587\u6863 id\uFF0C\u6B63\u6587\u5DF2\u5206\u53E5\u5E76\u7F16\u53F7\uFF09\u3002\n\u9010\u7BC7\u5224\u65AD\uFF1A\u6587\u6863\u662F\u5426\u5305\u542B\u4E0E\u95EE\u9898\u76F8\u5173\u7684\u5173\u952E\u8BCD\u6216\u8BED\u4E49\u3002\n\n\u89C4\u5219\uFF1A\n- rawId \u662F\u590D\u5408 id\uFF08\u683C\u5F0F\"\u6E90id:\u6587\u6863id\"\uFF0C\u5982 kb:17\uFF09\uFF0C\u5FC5\u987B\u539F\u6837\u7167\u6284\u6587\u6863\u6807\u7B7E\u3010\u3011\u5185\u7684\u5B8C\u6574 id\uFF0C\u4E0D\u5F97\u53EA\u6284\u6587\u6863 id \u6216\u6539\u5199\u2014\u2014\u4E0D\u540C\u6765\u6E90\u7684\u6587\u6863 id \u53EF\u80FD\u76F8\u540C\uFF0C\u6284\u9519\u4F1A\u5224\u5230\u522B\u7684\u6587\u6863\u4E0A\n- \u4EC5\u5F53\u786E\u6709\u76F8\u5173\u5185\u5BB9\u624D\u5224 relevant=true\uFF1B\u62FF\u4E0D\u51C6\u65F6\u5224 true\uFF08\u8BEF\u6EE4\u4EE3\u4EF7\u5927\u4E8E\u8BEF\u7559\uFF0C\u5B81\u4FDD\u5B88\uFF09\n- relevant=true \u65F6\u7ED9\u51FA keepSentences\uFF1A\u53EA\u4FDD\u7559\u76F4\u63A5\u652F\u6491\u95EE\u9898\u7684\u53E5\u5B50\u7F16\u53F7\uFF0C\u65E0\u5173\u53E5\u5B50\u5254\u9664\uFF1B\u6574\u7BC7\u90FD\u76F8\u5173\u5219\u7701\u7565\u8BE5\u5B57\u6BB5\n- relevant=false \u65F6\u7ED9\u4E00\u53E5\u8BDD reason\uFF0C\u8BF4\u660E\u4E0D\u76F8\u5173\u7684\u4F9D\u636E\n\n\u53EA\u8F93\u51FA\u4E00\u4E2A JSON \u6570\u7EC4\uFF0C\u4E0D\u8981\u8F93\u51FA\u4EFB\u4F55\u5176\u4ED6\u6587\u5B57\u3002\u6BCF\u7BC7\u6587\u6863\u4E00\u9879\uFF0C\u683C\u5F0F\uFF1A\n[{\"rawId\":\"...\",\"relevant\":true,\"reason\":\"...\",\"keepSentences\":[1,3]},{\"rawId\":\"...\",\"relevant\":false,\"reason\":\"...\"}]";
|
|
11
|
+
/** QUERY_REWRITE — 空结果兜底重写(M4,CRAG rewriter 式) */
|
|
12
|
+
export declare const QUERY_REWRITE = "\u4F60\u662F\u67E5\u8BE2\u6539\u5199\u5668\u3002\u539F\u68C0\u7D22\u67E5\u8BE2\u672A\u547D\u4E2D\u9AD8\u76F8\u5173\u5185\u5BB9\uFF0C\u8BF7\u6539\u5199\u4E3A\u66F4\u5229\u4E8E\u68C0\u7D22\u7684\u7248\u672C\u3002\n\n- \u63A8\u7406\u539F\u95EE\u9898\u7684\u5E95\u5C42\u8BED\u4E49\u610F\u56FE\uFF0C\u6362\u7528\u66F4\u53EF\u80FD\u547D\u4E2D\u7684\u5173\u952E\u8BCD\u4E0E\u8868\u8FF0\n- \u7ED3\u5408\u88AB\u6EE4\u539F\u56E0\u6458\u8981\u5B9A\u5411\u8C03\u6574\uFF08\u5982\u672F\u8BED\u4E0D\u51C6\u3001\u89D2\u5EA6\u504F\u5DEE\uFF09\n- \u53EA\u8F93\u51FA\u6539\u5199\u540E\u7684\u67E5\u8BE2\u672C\u8EAB\uFF1A\u4E00\u884C\u7EAF\u6587\u672C\uFF0C\u4E0D\u8981\u89E3\u91CA\u3001\u4E0D\u8981\u5F15\u53F7";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RAG 三 Prompt — 一职一 prompt(design-v2 §5.5)
|
|
3
|
+
*
|
|
4
|
+
* 三个 prompt 均用同一主力模型(2026-08-19 决策不分档);
|
|
5
|
+
* 判定/重写调用 temp 0。
|
|
6
|
+
*/
|
|
7
|
+
/** RAG_REACT — 循环决策:整定 / 检索 / 终止 */
|
|
8
|
+
export const RAG_REACT = `你是企业知识检索智能体,为下游智能体检索证据,不直接回答用户问题。
|
|
9
|
+
|
|
10
|
+
行为规则:
|
|
11
|
+
- 首轮先判断检索任务是否自包含;有指代/省略时先结合对话历史改写为独立检索查询
|
|
12
|
+
- 复杂问题可拆子查询,单轮并行调用多个检索工具(如同时查知识库与本地文件)
|
|
13
|
+
- 每轮拿到新结果后先评估:已覆盖哪些子问题、还缺什么;只检索缺失的信息,不重复检索已覆盖部分
|
|
14
|
+
- 单个来源失败时换其他来源重试
|
|
15
|
+
- 某来源连续返回空、或结果全部被判定不相关被滤除时,下一轮必须改用其他数据源工具(或并行多源),禁止对同一来源反复改写重查
|
|
16
|
+
|
|
17
|
+
终止(调用 rag_finish 即收手,满足其一即可):
|
|
18
|
+
- 检索任务的各子问题均有高相关片段覆盖
|
|
19
|
+
- 最近一轮新增结果与已有结果高度重复
|
|
20
|
+
- 已收集足够的高相关片段`;
|
|
21
|
+
/** DENOISE_VERDICT — 相关性判定 + 句子编号抽取(M4,CRAG grader 式结构化输出) */
|
|
22
|
+
export const DENOISE_VERDICT = `你是评估检索文档与用户问题相关性的判分器。
|
|
23
|
+
|
|
24
|
+
输入包含用户问题和若干候选文档(每篇带复合文档 id,正文已分句并编号)。
|
|
25
|
+
逐篇判断:文档是否包含与问题相关的关键词或语义。
|
|
26
|
+
|
|
27
|
+
规则:
|
|
28
|
+
- rawId 是复合 id(格式"源id:文档id",如 kb:17),必须原样照抄文档标签【】内的完整 id,不得只抄文档 id 或改写——不同来源的文档 id 可能相同,抄错会判到别的文档上
|
|
29
|
+
- 仅当确有相关内容才判 relevant=true;拿不准时判 true(误滤代价大于误留,宁保守)
|
|
30
|
+
- relevant=true 时给出 keepSentences:只保留直接支撑问题的句子编号,无关句子剔除;整篇都相关则省略该字段
|
|
31
|
+
- relevant=false 时给一句话 reason,说明不相关的依据
|
|
32
|
+
|
|
33
|
+
只输出一个 JSON 数组,不要输出任何其他文字。每篇文档一项,格式:
|
|
34
|
+
[{"rawId":"...","relevant":true,"reason":"...","keepSentences":[1,3]},{"rawId":"...","relevant":false,"reason":"..."}]`;
|
|
35
|
+
/** QUERY_REWRITE — 空结果兜底重写(M4,CRAG rewriter 式) */
|
|
36
|
+
export const QUERY_REWRITE = `你是查询改写器。原检索查询未命中高相关内容,请改写为更利于检索的版本。
|
|
37
|
+
|
|
38
|
+
- 推理原问题的底层语义意图,换用更可能命中的关键词与表述
|
|
39
|
+
- 结合被滤原因摘要定向调整(如术语不准、角度偏差)
|
|
40
|
+
- 只输出改写后的查询本身:一行纯文本,不要解释、不要引号`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RagNode — type:rag 节点壳(LangGraph 节点签名)
|
|
3
|
+
*
|
|
4
|
+
* 薄壳:从 state 取运行期上下文(ephemeral channels)→ 圈定工具表 →
|
|
5
|
+
* 调 react-loop 内核 → 以 runtime 节点惯用形态返回 partial state。
|
|
6
|
+
* 实例在编译期创建、跨请求复用,不放任何请求级状态。
|
|
7
|
+
*
|
|
8
|
+
* @see design-v2 §5.2 / §5.3
|
|
9
|
+
*/
|
|
10
|
+
import type { RagNodeConfig, RagNodeDeps } from './types.js';
|
|
11
|
+
type LangGraphState = Record<string, unknown>;
|
|
12
|
+
export declare class RagNode {
|
|
13
|
+
private readonly nodeId;
|
|
14
|
+
private readonly config;
|
|
15
|
+
private readonly deps;
|
|
16
|
+
constructor(nodeId: string, config: RagNodeConfig, deps: RagNodeDeps);
|
|
17
|
+
execute(state: LangGraphState): Promise<LangGraphState>;
|
|
18
|
+
/** 身份组装照 ToolNode 惯例:executionContext 优先,state 兜底 */
|
|
19
|
+
private buildAuthContext;
|
|
20
|
+
}
|
|
21
|
+
export {};
|