@zooique/memora 0.3.0 → 2.0.0
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 +8 -8
- package/dist/agent/agent.d.ts +69 -92
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +267 -197
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/assembler.d.ts +3 -0
- package/dist/agent/assembler.d.ts.map +1 -1
- package/dist/agent/assembler.js +14 -4
- package/dist/agent/assembler.js.map +1 -1
- package/dist/agent/builtinToolHandlers.d.ts +1 -1
- package/dist/agent/builtinToolHandlers.d.ts.map +1 -1
- package/dist/agent/builtinToolHandlers.js +5 -4
- package/dist/agent/builtinToolHandlers.js.map +1 -1
- package/dist/agent/guardrail.d.ts +47 -0
- package/dist/agent/guardrail.d.ts.map +1 -0
- package/dist/agent/guardrail.js +51 -0
- package/dist/agent/guardrail.js.map +1 -0
- package/dist/agent/loop.d.ts +79 -18
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +317 -231
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/managers/chatLockManager.d.ts +116 -0
- package/dist/agent/managers/chatLockManager.d.ts.map +1 -0
- package/dist/agent/managers/chatLockManager.js +191 -0
- package/dist/agent/managers/chatLockManager.js.map +1 -0
- package/dist/agent/managers/insightExtractor.d.ts +34 -69
- package/dist/agent/managers/insightExtractor.d.ts.map +1 -1
- package/dist/agent/managers/insightExtractor.js +133 -189
- package/dist/agent/managers/insightExtractor.js.map +1 -1
- package/dist/agent/managers/memoryAdvisor.d.ts +102 -2
- package/dist/agent/managers/memoryAdvisor.d.ts.map +1 -1
- package/dist/agent/managers/memoryAdvisor.js +262 -4
- package/dist/agent/managers/memoryAdvisor.js.map +1 -1
- package/dist/agent/managers/memoryDecayScheduler.d.ts +85 -0
- package/dist/agent/managers/memoryDecayScheduler.d.ts.map +1 -1
- package/dist/agent/managers/memoryDecayScheduler.js +215 -0
- package/dist/agent/managers/memoryDecayScheduler.js.map +1 -1
- package/dist/agent/managers/memoryInspector.d.ts +230 -64
- package/dist/agent/managers/memoryInspector.d.ts.map +1 -1
- package/dist/agent/managers/memoryInspector.js +439 -81
- package/dist/agent/managers/memoryInspector.js.map +1 -1
- package/dist/agent/managers/relationBuilder.d.ts +131 -0
- package/dist/agent/managers/relationBuilder.d.ts.map +1 -0
- package/dist/agent/managers/relationBuilder.js +192 -0
- package/dist/agent/managers/relationBuilder.js.map +1 -0
- package/dist/agent/managers/sessionArchiver.d.ts +6 -0
- package/dist/agent/managers/sessionArchiver.d.ts.map +1 -1
- package/dist/agent/managers/sessionArchiver.js +68 -8
- package/dist/agent/managers/sessionArchiver.js.map +1 -1
- package/dist/agent/managers/sessionManager.d.ts.map +1 -1
- package/dist/agent/managers/sessionManager.js +9 -17
- package/dist/agent/managers/sessionManager.js.map +1 -1
- package/dist/agent/managers/textPolishManager.d.ts +62 -0
- package/dist/agent/managers/textPolishManager.d.ts.map +1 -0
- package/dist/agent/managers/textPolishManager.js +99 -0
- package/dist/agent/managers/textPolishManager.js.map +1 -0
- package/dist/agent/personaMatcher.d.ts +29 -0
- package/dist/agent/personaMatcher.d.ts.map +1 -0
- package/dist/agent/personaMatcher.js +60 -0
- package/dist/agent/personaMatcher.js.map +1 -0
- package/dist/agent/toolExecutor.d.ts +2 -2
- package/dist/agent/toolExecutor.d.ts.map +1 -1
- package/dist/agent/toolExecutor.js +2 -1
- package/dist/agent/toolExecutor.js.map +1 -1
- package/dist/agent/tracer.d.ts +31 -0
- package/dist/agent/tracer.d.ts.map +1 -1
- package/dist/agent/tracer.js +31 -0
- package/dist/agent/tracer.js.map +1 -1
- package/dist/agent/types.d.ts +132 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js +38 -0
- package/dist/agent/types.js.map +1 -1
- package/dist/config/loader.d.ts +52 -40
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +52 -32
- package/dist/config/loader.js.map +1 -1
- package/dist/eval/evalRunner.d.ts +94 -0
- package/dist/eval/evalRunner.d.ts.map +1 -0
- package/dist/eval/evalRunner.js +141 -0
- package/dist/eval/evalRunner.js.map +1 -0
- package/dist/eval/evalTypes.d.ts +33 -3
- package/dist/eval/evalTypes.d.ts.map +1 -1
- package/dist/eval/evalTypes.js +18 -2
- package/dist/eval/evalTypes.js.map +1 -1
- package/dist/eval/scenarios.d.ts +33 -0
- package/dist/eval/scenarios.d.ts.map +1 -0
- package/dist/eval/scenarios.js +109 -0
- package/dist/eval/scenarios.js.map +1 -0
- package/dist/index.d.ts +51 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -6
- package/dist/index.js.map +1 -1
- package/dist/llm/abortSignal.d.ts +27 -0
- package/dist/llm/abortSignal.d.ts.map +1 -0
- package/dist/llm/abortSignal.js +44 -0
- package/dist/llm/abortSignal.js.map +1 -0
- package/dist/llm/embedding.d.ts +7 -2
- package/dist/llm/embedding.d.ts.map +1 -1
- package/dist/llm/embedding.js +66 -46
- package/dist/llm/embedding.js.map +1 -1
- package/dist/llm/factory.js +1 -1
- package/dist/llm/factory.js.map +1 -1
- package/dist/llm/openaiCompatible.d.ts.map +1 -1
- package/dist/llm/openaiCompatible.js +26 -27
- package/dist/llm/openaiCompatible.js.map +1 -1
- package/dist/llm/provider.d.ts +0 -14
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/llm/provider.js.map +1 -1
- package/dist/logging/logger.d.ts +1 -0
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +43 -37
- package/dist/logging/logger.js.map +1 -1
- package/dist/memory/inMemoryStorage.d.ts.map +1 -1
- package/dist/memory/inMemoryStorage.js +8 -7
- package/dist/memory/inMemoryStorage.js.map +1 -1
- package/dist/memory/lockManager.d.ts +48 -0
- package/dist/memory/lockManager.d.ts.map +1 -0
- package/dist/memory/lockManager.js +159 -0
- package/dist/memory/lockManager.js.map +1 -0
- package/dist/memory/projectManager.d.ts +23 -53
- package/dist/memory/projectManager.d.ts.map +1 -1
- package/dist/memory/projectManager.js +46 -227
- package/dist/memory/projectManager.js.map +1 -1
- package/dist/memory/projectRegistry.d.ts +62 -0
- package/dist/memory/projectRegistry.d.ts.map +1 -0
- package/dist/memory/projectRegistry.js +158 -0
- package/dist/memory/projectRegistry.js.map +1 -0
- package/dist/memory/recall.d.ts +3 -3
- package/dist/memory/recall.d.ts.map +1 -1
- package/dist/memory/recall.js +3 -3
- package/dist/memory/recall.js.map +1 -1
- package/dist/memory/sourceValidation.d.ts +49 -0
- package/dist/memory/sourceValidation.d.ts.map +1 -0
- package/dist/memory/sourceValidation.js +141 -0
- package/dist/memory/sourceValidation.js.map +1 -0
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +2 -1
- package/dist/memory/store.js.map +1 -1
- package/dist/memory/types.d.ts +0 -47
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/memory/types.js +0 -138
- package/dist/memory/types.js.map +1 -1
- package/dist/memory/vectorStore.d.ts +82 -7
- package/dist/memory/vectorStore.d.ts.map +1 -1
- package/dist/memory/vectorStore.js +12 -8
- package/dist/memory/vectorStore.js.map +1 -1
- package/dist/persona/personaManager.d.ts +30 -4
- package/dist/persona/personaManager.d.ts.map +1 -1
- package/dist/persona/personaManager.js +47 -14
- package/dist/persona/personaManager.js.map +1 -1
- package/dist/security/pathGuard.d.ts.map +1 -1
- package/dist/security/pathGuard.js +3 -1
- package/dist/security/pathGuard.js.map +1 -1
- package/dist/skill/skillManager.d.ts.map +1 -1
- package/dist/skill/skillManager.js +2 -1
- package/dist/skill/skillManager.js.map +1 -1
- package/dist/utils/array.d.ts +24 -0
- package/dist/utils/array.d.ts.map +1 -0
- package/dist/utils/array.js +24 -0
- package/dist/utils/array.js.map +1 -0
- package/dist/utils/errors.d.ts +10 -8
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +12 -9
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/eventEmitter.d.ts +7 -0
- package/dist/utils/eventEmitter.d.ts.map +1 -1
- package/dist/utils/eventEmitter.js.map +1 -1
- package/dist/utils/objects.d.ts +17 -0
- package/dist/utils/objects.d.ts.map +1 -0
- package/dist/utils/objects.js +19 -0
- package/dist/utils/objects.js.map +1 -0
- package/dist/utils/segmenter.d.ts +17 -0
- package/dist/utils/segmenter.d.ts.map +1 -1
- package/dist/utils/segmenter.js +26 -0
- package/dist/utils/segmenter.js.map +1 -1
- package/dist/utils/strings.d.ts +22 -1
- package/dist/utils/strings.d.ts.map +1 -1
- package/dist/utils/strings.js +29 -1
- package/dist/utils/strings.js.map +1 -1
- package/dist/utils/time.d.ts +18 -3
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/time.js +24 -8
- package/dist/utils/time.js.map +1 -1
- package/dist/utils/toError.d.ts +8 -4
- package/dist/utils/toError.d.ts.map +1 -1
- package/dist/utils/toError.js +25 -5
- package/dist/utils/toError.js.map +1 -1
- package/package.json +11 -1
package/dist/agent/loop.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LOOP_CONSTANTS } from '../agent/constants.js';
|
|
2
2
|
import { ContextManager } from '../agent/contextManager.js';
|
|
3
|
+
import { runGuardrails } from '../agent/guardrail.js';
|
|
3
4
|
import { NOOP_TRACER, TRACE_SPANS } from '../agent/tracer.js';
|
|
4
5
|
import { MemoraError, isRetryableErrorCode, toError } from '../utils/errors.js';
|
|
5
6
|
import { safeSetTimeout } from '../utils/safeTimer.js';
|
|
@@ -80,7 +81,13 @@ export class AgentLoop {
|
|
|
80
81
|
});
|
|
81
82
|
}
|
|
82
83
|
/**
|
|
83
|
-
*
|
|
84
|
+
* 处理一轮用户输入(编排方法)
|
|
85
|
+
*
|
|
86
|
+
* 拆分为 4 个子方法:
|
|
87
|
+
* - handleRecallAndInputGuard:召回注入 + 输入护栏
|
|
88
|
+
* - handleIteration:单次迭代编排(abort 检查 + LLM 调用 + 分支路由)
|
|
89
|
+
* - handleToolCalls:工具调用分支 + Reflection
|
|
90
|
+
* - handleTextResponse:纯文本结束 + 输出护栏
|
|
84
91
|
*
|
|
85
92
|
* @param userInput - 用户原始输入
|
|
86
93
|
* @param recalledMemories - 记忆召回结果(Agent.memory.search() 产出),
|
|
@@ -94,158 +101,20 @@ export class AgentLoop {
|
|
|
94
101
|
inputLength: userInput.length,
|
|
95
102
|
});
|
|
96
103
|
try {
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
recallCount: recalledMemories?.length ?? 0,
|
|
100
|
-
});
|
|
101
|
-
// 将召回记忆以 system 消息注入(优先级高、不污染 user 输入)
|
|
102
|
-
// 替代旧方案:嵌入 user 消息+反指令→模型易混淆
|
|
103
|
-
if (recalledMemories?.length) {
|
|
104
|
-
this.injectRecallAsSystem(recalledMemories);
|
|
105
|
-
}
|
|
106
|
-
// 补充 span 属性:让宿主监控面板能按命中/未命中过滤
|
|
107
|
-
recallSpan.setAttribute('hit', recalledMemories !== undefined && recalledMemories.length > 0);
|
|
108
|
-
recallSpan.end();
|
|
109
|
-
// 召回命中率统计:每轮对话算一次召回,结果非空算命中
|
|
110
|
-
this.metricRecallTotalCount++;
|
|
111
|
-
if (recalledMemories && recalledMemories.length > 0) {
|
|
112
|
-
this.metricRecallHitCount++;
|
|
113
|
-
}
|
|
114
|
-
// 有记忆召回时,通知上层(用于 UI 展示"召回透明度"——记忆名称 + 相似度)
|
|
115
|
-
// 暴露 id/name/score/source 摘要,不泄露完整 content
|
|
116
|
-
if (recalledMemories?.length) {
|
|
117
|
-
yield {
|
|
118
|
-
type: 'recall',
|
|
119
|
-
memories: recalledMemories.map((m) => ({
|
|
120
|
-
id: m.id,
|
|
121
|
-
name: m.name,
|
|
122
|
-
score: m.score,
|
|
123
|
-
source: m.source,
|
|
124
|
-
})),
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
// 输入护栏检查:在用户输入注入上下文之前,检查是否命中护栏规则
|
|
128
|
-
// 护栏自身异常时降级为"放行 + 记日志",不阻断用户对话
|
|
129
|
-
const inputGuardResult = this.runInputGuardrails(userInput);
|
|
130
|
-
if (inputGuardResult.blocked) {
|
|
131
|
-
// P3: try/finally 确保 done 一定送达,即使 text yield 异常
|
|
132
|
-
try {
|
|
133
|
-
yield { type: 'text', content: inputGuardResult.message ?? 'Input blocked by guardrail' };
|
|
134
|
-
}
|
|
135
|
-
finally {
|
|
136
|
-
yield { type: 'done' };
|
|
137
|
-
}
|
|
104
|
+
// 1. 召回注入 + 输入护栏(返回 true 表示已 block 并 yield done,应 return)
|
|
105
|
+
if (yield* this.handleRecallAndInputGuard(userInput, recalledMemories))
|
|
138
106
|
return;
|
|
139
|
-
|
|
140
|
-
if (inputGuardResult.warning) {
|
|
141
|
-
// warn 级别只通知,不阻断
|
|
142
|
-
yield {
|
|
143
|
-
type: 'text',
|
|
144
|
-
content: `${this.ui.guardrailWarningPrefix} ${inputGuardResult.warning}`,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
// 安全规范 §6:用户输入用 <user_input> 标签包裹,增强 LLM 对注入攻击的免疫力
|
|
107
|
+
// 2. 用户消息 push(安全规范 §6:用 <user_input> 标签包裹,增强 LLM 对注入攻击的免疫力)
|
|
148
108
|
this.messages.push({ role: 'user', content: `<user_input>${userInput}</user_input>` });
|
|
109
|
+
// 3. 迭代循环
|
|
149
110
|
let iteration = 0;
|
|
150
111
|
while (iteration < this.maxIterations) {
|
|
151
112
|
iteration++;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (signal?.aborted) {
|
|
155
|
-
yield { type: 'aborted', reason: this.ui.abortedByUser };
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
// 调用 LLM(带重试 + 截断保护)
|
|
159
|
-
const chatOpts = this.buildChatOptions();
|
|
160
|
-
// 上下文摘要:如果启用且首次截断,生成摘要
|
|
161
|
-
let contextSummary;
|
|
162
|
-
if (this.enableContextSummary &&
|
|
163
|
-
this.contextManager.shouldTruncate(this.messages)) {
|
|
164
|
-
// 摘要缓存管理已移至 ContextManager.getOrCreateSummary
|
|
165
|
-
// 传入 signal,让摘要生成可被用户取消中断(避免 generator 挂起)
|
|
166
|
-
contextSummary = await this.contextManager.getOrCreateSummary(this.messages, signal);
|
|
167
|
-
}
|
|
168
|
-
const safeMessages = this.contextManager.truncateMessages(this.messages, contextSummary);
|
|
169
|
-
// 截断后同步替换工作记忆,防止 messages 数组无限增长
|
|
170
|
-
// 持久化由 MessageHistory 负责,工作记忆只需保留当前上下文窗口内的消息
|
|
171
|
-
if (safeMessages !== this.messages) {
|
|
172
|
-
this.messages = [...safeMessages];
|
|
173
|
-
}
|
|
174
|
-
const llmResult = yield* this.callLlmWithRetry(safeMessages, chatOpts, signal, iteration);
|
|
175
|
-
if (llmResult.aborted) {
|
|
176
|
-
// LLM 调用中断时仍保留已生成的部分文本到上下文消息列表
|
|
177
|
-
// 让下一轮 LLM 能看到中断响应(追加 interrupted 标记让 LLM 识别非完整回复)
|
|
178
|
-
// 注意:工具调用中断(execResult.aborted)不在此处理,因 executeToolCalls
|
|
179
|
-
// 已 push assistant(含 toolCalls),追加文本标记会破坏工具调用结构
|
|
180
|
-
if (llmResult.fullContent.trim()) {
|
|
181
|
-
this.messages.push({
|
|
182
|
-
role: 'assistant',
|
|
183
|
-
content: llmResult.fullContent + this.ui.interrupted,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
yield { type: 'aborted', reason: this.ui.abortedByUser };
|
|
113
|
+
const result = yield* this.handleIteration(iteration, signal);
|
|
114
|
+
if (result === 'aborted' || result === 'done')
|
|
187
115
|
return;
|
|
188
|
-
}
|
|
189
|
-
// 工具调用分支
|
|
190
|
-
if (llmResult.toolCalls && llmResult.toolCalls.length > 0) {
|
|
191
|
-
const execResult = yield* this.executeToolCalls(llmResult.toolCalls, llmResult.fullContent, signal);
|
|
192
|
-
if (execResult.aborted) {
|
|
193
|
-
yield { type: 'aborted', reason: this.ui.abortedByUser };
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
// Reflection(反思/自修正):检查是否有可重试的错误
|
|
197
|
-
// 如果工具结果中有 retryable 错误,在 LLM 上下文中追加反思提示
|
|
198
|
-
// 帮助 LLM 聚焦于修正而非放弃
|
|
199
|
-
const hasRetryableError = this.messages
|
|
200
|
-
.slice(-llmResult.toolCalls.length) // 只看本轮工具结果
|
|
201
|
-
.some((m) => m.role === 'tool' && this.isRetryableToolError(m.content));
|
|
202
|
-
if (hasRetryableError) {
|
|
203
|
-
// 反思次数限制:通过前缀匹配统计已推送的 REFLECTION_HINT 消息
|
|
204
|
-
// (实际推送的 content 带有后缀说明,需用 startsWith 而非严格相等)
|
|
205
|
-
const reflectionHint = this.messages.filter((m) => m.role === 'system' && m.content.startsWith('[REFLECTION_HINT]')).length;
|
|
206
|
-
if (reflectionHint < this.maxReflectionRetries) {
|
|
207
|
-
this.messages.push({
|
|
208
|
-
role: 'system',
|
|
209
|
-
content: `[REFLECTION_HINT] 上次工具调用失败,错误可重试。请分析错误原因,修正参数后重新调用工具。剩余反思次数:${this.maxReflectionRetries - reflectionHint}`,
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
// 继续循环:把工具结果回填给 LLM
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
// 纯文本结束
|
|
217
|
-
if (llmResult.fullContent) {
|
|
218
|
-
this.messages.push({ role: 'assistant', content: llmResult.fullContent });
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
// LLM 返回空响应(既无文本也无工具调用)的兜底处理
|
|
222
|
-
// 正常 LLM 不会返回空响应,但某些 provider 异常/边界情况下可能发生
|
|
223
|
-
logger.warn({ iteration }, 'LLM 返回空响应(无文本、无工具调用),使用兜底提示');
|
|
224
|
-
const fallbackText = '(模型未返回有效内容,请重试或换一种方式提问)';
|
|
225
|
-
this.messages.push({ role: 'assistant', content: fallbackText });
|
|
226
|
-
yield { type: 'text', content: fallbackText };
|
|
227
|
-
}
|
|
228
|
-
// 输出护栏检查:在响应返回给用户之前,检查是否命中护栏规则
|
|
229
|
-
const outputGuardResult = this.runOutputGuardrails(llmResult.fullContent);
|
|
230
|
-
if (outputGuardResult.blocked) {
|
|
231
|
-
// P3: try/finally 确保 done 一定送达,即使 text yield 异常
|
|
232
|
-
try {
|
|
233
|
-
yield { type: 'text', content: outputGuardResult.message ?? 'Output blocked by guardrail' };
|
|
234
|
-
}
|
|
235
|
-
finally {
|
|
236
|
-
yield { type: 'done' };
|
|
237
|
-
}
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
if (outputGuardResult.warning) {
|
|
241
|
-
yield {
|
|
242
|
-
type: 'text',
|
|
243
|
-
content: `${this.ui.guardrailWarningPrefix} ${outputGuardResult.warning}`,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
yield { type: 'done' };
|
|
247
|
-
return;
|
|
248
116
|
}
|
|
117
|
+
// 4. 最大迭代兜底
|
|
249
118
|
logger.warn({ iterations: iteration }, '达到最大迭代次数');
|
|
250
119
|
yield { type: 'text', content: this.ui.maxIterationsReached };
|
|
251
120
|
yield { type: 'done' };
|
|
@@ -254,6 +123,232 @@ export class AgentLoop {
|
|
|
254
123
|
responseSpan.end();
|
|
255
124
|
}
|
|
256
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* 召回注入 + 输入护栏(processUserInput 子方法 1/4)
|
|
128
|
+
*
|
|
129
|
+
* 职责:
|
|
130
|
+
* - 注入记忆召回结果(system 消息,优先级高、不污染 user 输入)
|
|
131
|
+
* - 召回命中率统计(metricRecallTotalCount / metricRecallHitCount)
|
|
132
|
+
* - 通知上层 UI 召回透明度(yield recall chunk)
|
|
133
|
+
* - 输入护栏检查(block 时 yield text + done,warn 时 yield text)
|
|
134
|
+
*
|
|
135
|
+
* @yields recall / text(guardrail block/warn)/ done(block 时)
|
|
136
|
+
* @returns true 表示输入被 block 已 yield done,调用方应 return;false 表示继续
|
|
137
|
+
*/
|
|
138
|
+
async *handleRecallAndInputGuard(userInput, recalledMemories) {
|
|
139
|
+
// 注入记忆召回结果(agent上下文组装协议 §1:Agent 记忆召回结果层)
|
|
140
|
+
const recallSpan = this.tracer.startSpan(TRACE_SPANS.RECALL, {
|
|
141
|
+
recallCount: recalledMemories?.length ?? 0,
|
|
142
|
+
});
|
|
143
|
+
// 将召回记忆以 system 消息注入(优先级高、不污染 user 输入)
|
|
144
|
+
if (recalledMemories?.length) {
|
|
145
|
+
this.injectRecallAsSystem(recalledMemories);
|
|
146
|
+
}
|
|
147
|
+
// 补充 span 属性:让宿主监控面板能按命中/未命中过滤
|
|
148
|
+
recallSpan.setAttribute('hit', recalledMemories !== undefined && recalledMemories.length > 0);
|
|
149
|
+
recallSpan.end();
|
|
150
|
+
// 召回命中率统计:每轮对话算一次召回,结果非空算命中
|
|
151
|
+
this.metricRecallTotalCount++;
|
|
152
|
+
if (recalledMemories && recalledMemories.length > 0) {
|
|
153
|
+
this.metricRecallHitCount++;
|
|
154
|
+
}
|
|
155
|
+
// 有记忆召回时,通知上层(用于 UI 展示"召回透明度"——记忆名称 + 相似度)
|
|
156
|
+
// 暴露 id/name/score/source 摘要,不泄露完整 content
|
|
157
|
+
if (recalledMemories?.length) {
|
|
158
|
+
yield {
|
|
159
|
+
type: 'recall',
|
|
160
|
+
memories: recalledMemories.map((m) => ({
|
|
161
|
+
id: m.id,
|
|
162
|
+
name: m.name,
|
|
163
|
+
score: m.score,
|
|
164
|
+
source: m.source,
|
|
165
|
+
})),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
// 输入护栏检查:在用户输入注入上下文之前,检查是否命中护栏规则
|
|
169
|
+
// 护栏自身异常时降级为"放行 + 记日志",不阻断用户对话
|
|
170
|
+
const inputGuardSpan = this.tracer.startSpan(TRACE_SPANS.GUARDRAIL_INPUT, {
|
|
171
|
+
ruleCount: this.guardrailRules.length,
|
|
172
|
+
});
|
|
173
|
+
const inputGuardResult = runGuardrails(this.guardrailRules, userInput, this.ui);
|
|
174
|
+
inputGuardSpan.setAttribute('blocked', inputGuardResult.blocked);
|
|
175
|
+
inputGuardSpan.setAttribute('warned', !!inputGuardResult.warning);
|
|
176
|
+
inputGuardSpan.end();
|
|
177
|
+
if (inputGuardResult.blocked) {
|
|
178
|
+
// P3: try/finally 确保 done 一定送达,即使 text yield 异常
|
|
179
|
+
// guardrailBlocked: true 让 eval 框架和宿主 UI 通过结构化字段判断护栏触发
|
|
180
|
+
try {
|
|
181
|
+
yield {
|
|
182
|
+
type: 'text',
|
|
183
|
+
content: inputGuardResult.message ?? 'Input blocked by guardrail',
|
|
184
|
+
guardrailBlocked: true,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
yield { type: 'done' };
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
if (inputGuardResult.warning) {
|
|
193
|
+
// warn 级别只通知,不阻断
|
|
194
|
+
yield {
|
|
195
|
+
type: 'text',
|
|
196
|
+
content: `${this.ui.guardrailWarningPrefix} ${inputGuardResult.warning}`,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 单次迭代编排(processUserInput 子方法 2/4)
|
|
203
|
+
*
|
|
204
|
+
* 职责:
|
|
205
|
+
* - abort 检查
|
|
206
|
+
* - 上下文摘要 + 截断
|
|
207
|
+
* - LLM 调用(callLlmWithRetry)
|
|
208
|
+
* - LLM 中断处理
|
|
209
|
+
* - 分支路由:工具调用 → handleToolCalls;纯文本 → handleTextResponse
|
|
210
|
+
*
|
|
211
|
+
* @yields text / aborted / done(由子方法委托)
|
|
212
|
+
* @returns 'aborted' | 'done' | 'continue'(continue 表示继续下一轮迭代)
|
|
213
|
+
*/
|
|
214
|
+
async *handleIteration(iteration, signal) {
|
|
215
|
+
logger.debug({ iteration, messageCount: this.messages.length }, 'Agent Loop 迭代');
|
|
216
|
+
// 每次迭代前检查是否已被取消
|
|
217
|
+
if (signal?.aborted) {
|
|
218
|
+
yield { type: 'aborted', reason: this.ui.abortedByUser };
|
|
219
|
+
return 'aborted';
|
|
220
|
+
}
|
|
221
|
+
// 调用 LLM(带重试 + 截断保护)
|
|
222
|
+
const chatOpts = this.buildChatOptions();
|
|
223
|
+
// 上下文摘要:如果启用且首次截断,生成摘要
|
|
224
|
+
let contextSummary;
|
|
225
|
+
if (this.enableContextSummary &&
|
|
226
|
+
this.contextManager.shouldTruncate(this.messages)) {
|
|
227
|
+
// 摘要缓存管理已移至 ContextManager.getOrCreateSummary
|
|
228
|
+
// 传入 signal,让摘要生成可被用户取消中断(避免 generator 挂起)
|
|
229
|
+
contextSummary = await this.contextManager.getOrCreateSummary(this.messages, signal);
|
|
230
|
+
}
|
|
231
|
+
const safeMessages = this.contextManager.truncateMessages(this.messages, contextSummary);
|
|
232
|
+
// 截断后同步替换工作记忆,防止 messages 数组无限增长
|
|
233
|
+
// 持久化由 MessageHistory 负责,工作记忆只需保留当前上下文窗口内的消息
|
|
234
|
+
if (safeMessages !== this.messages) {
|
|
235
|
+
this.messages = [...safeMessages];
|
|
236
|
+
}
|
|
237
|
+
const llmResult = yield* this.callLlmWithRetry(safeMessages, chatOpts, signal, iteration);
|
|
238
|
+
if (llmResult.aborted) {
|
|
239
|
+
// LLM 调用中断时仍保留已生成的部分文本到上下文消息列表
|
|
240
|
+
// 让下一轮 LLM 能看到中断响应(追加 interrupted 标记让 LLM 识别非完整回复)
|
|
241
|
+
// 注意:工具调用中断(execResult.aborted)不在此处理,因 executeToolCalls
|
|
242
|
+
// 已 push assistant(含 toolCalls),追加文本标记会破坏工具调用结构
|
|
243
|
+
if (llmResult.fullContent.trim()) {
|
|
244
|
+
this.messages.push({
|
|
245
|
+
role: 'assistant',
|
|
246
|
+
content: llmResult.fullContent + this.ui.interrupted,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
yield { type: 'aborted', reason: this.ui.abortedByUser };
|
|
250
|
+
return 'aborted';
|
|
251
|
+
}
|
|
252
|
+
// 工具调用分支
|
|
253
|
+
if (llmResult.toolCalls && llmResult.toolCalls.length > 0) {
|
|
254
|
+
return yield* this.handleToolCalls(llmResult, signal);
|
|
255
|
+
}
|
|
256
|
+
// 纯文本结束分支
|
|
257
|
+
return yield* this.handleTextResponse(llmResult);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* 工具调用分支 + Reflection(processUserInput 子方法 3/4)
|
|
261
|
+
*
|
|
262
|
+
* 职责:
|
|
263
|
+
* - executeToolCalls 执行工具调用
|
|
264
|
+
* - abort 检查(工具执行中断)
|
|
265
|
+
* - Reflection:检查可重试错误,追加反思提示
|
|
266
|
+
*
|
|
267
|
+
* @yields aborted(工具执行中断时)
|
|
268
|
+
* @returns 'aborted' | 'continue'(continue 表示工具结果已回填,继续下一轮 LLM 调用)
|
|
269
|
+
*/
|
|
270
|
+
async *handleToolCalls(llmResult, signal) {
|
|
271
|
+
const execResult = yield* this.executeToolCalls(llmResult.toolCalls, llmResult.fullContent, signal);
|
|
272
|
+
if (execResult.aborted) {
|
|
273
|
+
yield { type: 'aborted', reason: this.ui.abortedByUser };
|
|
274
|
+
return 'aborted';
|
|
275
|
+
}
|
|
276
|
+
// Reflection(反思/自修正):检查是否有可重试的错误
|
|
277
|
+
// 如果工具结果中有 retryable 错误,在 LLM 上下文中追加反思提示
|
|
278
|
+
// 帮助 LLM 聚焦于修正而非放弃
|
|
279
|
+
const hasRetryableError = this.messages
|
|
280
|
+
.slice(-llmResult.toolCalls.length) // 只看本轮工具结果
|
|
281
|
+
.some((m) => m.role === 'tool' && this.isRetryableToolError(m.content));
|
|
282
|
+
if (hasRetryableError) {
|
|
283
|
+
// 反思次数限制:通过前缀匹配统计已推送的 REFLECTION_HINT 消息
|
|
284
|
+
// (实际推送的 content 带有后缀说明,需用 startsWith 而非严格相等)
|
|
285
|
+
const reflectionHint = this.messages.filter((m) => m.role === 'system' && m.content.startsWith('[REFLECTION_HINT]')).length;
|
|
286
|
+
if (reflectionHint < this.maxReflectionRetries) {
|
|
287
|
+
this.messages.push({
|
|
288
|
+
role: 'system',
|
|
289
|
+
content: `[REFLECTION_HINT] 上次工具调用失败,错误可重试。请分析错误原因,修正参数后重新调用工具。剩余反思次数:${this.maxReflectionRetries - reflectionHint}`,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// 继续循环:把工具结果回填给 LLM
|
|
294
|
+
return 'continue';
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* 纯文本结束 + 输出护栏(processUserInput 子方法 4/4)
|
|
298
|
+
*
|
|
299
|
+
* 职责:
|
|
300
|
+
* - push assistant 消息(含空响应兜底)
|
|
301
|
+
* - 输出护栏检查(block 时 yield text + done,warn 时 yield text)
|
|
302
|
+
* - yield done 结束本轮对话
|
|
303
|
+
*
|
|
304
|
+
* @yields text(空响应兜底 / guardrail block/warn)/ done
|
|
305
|
+
* @returns 'done'(调用方收到后 return)
|
|
306
|
+
*/
|
|
307
|
+
async *handleTextResponse(llmResult) {
|
|
308
|
+
// 纯文本结束
|
|
309
|
+
if (llmResult.fullContent) {
|
|
310
|
+
this.messages.push({ role: 'assistant', content: llmResult.fullContent });
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
// LLM 返回空响应(既无文本也无工具调用)的兜底处理
|
|
314
|
+
// 正常 LLM 不会返回空响应,但某些 provider 异常/边界情况下可能发生
|
|
315
|
+
logger.warn('LLM 返回空响应(无文本、无工具调用),使用兜底提示');
|
|
316
|
+
const fallbackText = '(模型未返回有效内容,请重试或换一种方式提问)';
|
|
317
|
+
this.messages.push({ role: 'assistant', content: fallbackText });
|
|
318
|
+
yield { type: 'text', content: fallbackText };
|
|
319
|
+
}
|
|
320
|
+
// 输出护栏检查:在响应返回给用户之前,检查是否命中护栏规则
|
|
321
|
+
const outputGuardSpan = this.tracer.startSpan(TRACE_SPANS.GUARDRAIL_OUTPUT, {
|
|
322
|
+
ruleCount: this.guardrailRules.length,
|
|
323
|
+
});
|
|
324
|
+
const outputGuardResult = runGuardrails(this.guardrailRules, llmResult.fullContent, this.ui);
|
|
325
|
+
outputGuardSpan.setAttribute('blocked', outputGuardResult.blocked);
|
|
326
|
+
outputGuardSpan.setAttribute('warned', !!outputGuardResult.warning);
|
|
327
|
+
outputGuardSpan.end();
|
|
328
|
+
if (outputGuardResult.blocked) {
|
|
329
|
+
// P3: try/finally 确保 done 一定送达,即使 text yield 异常
|
|
330
|
+
// guardrailBlocked: true 让 eval 框架和宿主 UI 通过结构化字段判断护栏触发
|
|
331
|
+
try {
|
|
332
|
+
yield {
|
|
333
|
+
type: 'text',
|
|
334
|
+
content: outputGuardResult.message ?? 'Output blocked by guardrail',
|
|
335
|
+
guardrailBlocked: true,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
finally {
|
|
339
|
+
yield { type: 'done' };
|
|
340
|
+
}
|
|
341
|
+
return 'done';
|
|
342
|
+
}
|
|
343
|
+
if (outputGuardResult.warning) {
|
|
344
|
+
yield {
|
|
345
|
+
type: 'text',
|
|
346
|
+
content: `${this.ui.guardrailWarningPrefix} ${outputGuardResult.warning}`,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
yield { type: 'done' };
|
|
350
|
+
return 'done';
|
|
351
|
+
}
|
|
257
352
|
/**
|
|
258
353
|
* 调用 LLM(带指数退避重试)
|
|
259
354
|
*
|
|
@@ -407,43 +502,33 @@ export class AgentLoop {
|
|
|
407
502
|
content: fullContent,
|
|
408
503
|
toolCalls,
|
|
409
504
|
});
|
|
410
|
-
//
|
|
505
|
+
// 执行工具(E-803 并行优化:独立 tool_call 并发执行,事件按原始顺序 yield)
|
|
506
|
+
// 设计:方案 B(保持顺序的并发)
|
|
507
|
+
// - 并发发起所有工具执行(Promise.all,真并发,总耗时 ≈ 最慢的工具)
|
|
508
|
+
// - 批量 yield tool_start(UI 按 toolCallId 创建所有工具卡片)
|
|
509
|
+
// - 按原始顺序 push messages + yield tool_result(保证 Reflection slice(-N) 正确)
|
|
510
|
+
// - messages 顺序确定 → Reflection 的 slice(-toolCalls.length) 仍取到本轮完整结果
|
|
511
|
+
// - 宿主 UI 按 toolCallId 配对 tool_start/tool_result,不依赖严格交替顺序
|
|
512
|
+
// 执行前检查取消(批量,避免 abort 后还发起工具)
|
|
513
|
+
if (signal?.aborted) {
|
|
514
|
+
return { aborted: true };
|
|
515
|
+
}
|
|
516
|
+
// 1. 批量 yield tool_start + 并发发起所有工具执行
|
|
517
|
+
const toolPromises = [];
|
|
411
518
|
for (const tc of toolCalls) {
|
|
412
|
-
// 工具执行前检查取消
|
|
413
|
-
if (signal?.aborted) {
|
|
414
|
-
return { aborted: true };
|
|
415
|
-
}
|
|
416
519
|
// 工具调用统计:每次工具执行 +1
|
|
417
520
|
this.metricToolCallCount++;
|
|
418
521
|
yield { type: 'tool_start', toolCallId: tc.id, name: tc.function.name, args: tc.function.arguments };
|
|
419
|
-
//
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
// 工具执行包裹 signal 中断,避免 abort 无法中断卡住的 generator
|
|
430
|
-
// 原 await toolExecutor 不响应 signal,工具卡住时 generator 挂起、_chatBusy 泄漏
|
|
431
|
-
result = await this.raceToolWithSignal(tc.function.name, tc.function.arguments, signal);
|
|
432
|
-
}
|
|
433
|
-
catch (err) {
|
|
434
|
-
const e = toError(err);
|
|
435
|
-
toolSpan.recordException(e);
|
|
436
|
-
if (err instanceof MemoraError) {
|
|
437
|
-
const code = err.errorCode ?? 'UNKNOWN';
|
|
438
|
-
result = `[ERR:TOOL:${code}] 错误:${err.title}${err.detail ? ` — ${err.detail}` : ''}`;
|
|
439
|
-
logger.warn({ tool: tc.function.name, errorCode: code, title: err.title }, '工具执行失败,错误已回传给 LLM');
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
result = `[ERR:TOOL:UNKNOWN] 错误:工具执行异常 — ${e.message}`;
|
|
443
|
-
logger.error({ tool: tc.function.name, err }, '工具执行异常');
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
toolSpan.end();
|
|
522
|
+
// 并发发起工具执行(不 await,收集 Promise 由 Promise.all 统一等待)
|
|
523
|
+
toolPromises.push(this.executeOneTool(tc, signal));
|
|
524
|
+
}
|
|
525
|
+
// 2. 等待全部工具完成(真并发,总耗时 ≈ 最慢的工具而非所有工具之和)
|
|
526
|
+
const results = await Promise.all(toolPromises);
|
|
527
|
+
// 3. 按原始顺序 push messages + yield tool_result
|
|
528
|
+
// 顺序确定保证:Reflection 的 slice(-toolCalls.length) 能取到本轮完整结果
|
|
529
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
530
|
+
const tc = toolCalls[i];
|
|
531
|
+
const result = results[i];
|
|
447
532
|
this.messages.push({
|
|
448
533
|
role: 'tool',
|
|
449
534
|
content: result,
|
|
@@ -514,6 +599,58 @@ export class AgentLoop {
|
|
|
514
599
|
abortPromise,
|
|
515
600
|
]);
|
|
516
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* 执行单个工具(E-803 抽取:为并行化提供独立执行单元)
|
|
604
|
+
*
|
|
605
|
+
* 职责:
|
|
606
|
+
* - startSpan / endSpan(工具执行 Span,并发时 span 时间重叠,可观测性改进)
|
|
607
|
+
* - raceToolWithSignal 竞争包裹(兼容 signal 中断,每个工具独立 race)
|
|
608
|
+
* - 异常捕获并转为结构化错误字符串([ERR:TOOL:code] 前缀,供 Reflection 解析)
|
|
609
|
+
*
|
|
610
|
+
* 不含职责(由 executeToolCalls 主循环控制,保证顺序确定):
|
|
611
|
+
* - yield tool_start / tool_result(事件顺序由主循环批量 yield 保证)
|
|
612
|
+
* - messages.push(消息顺序由主循环按原始顺序 push,确保 Reflection slice 正确)
|
|
613
|
+
* - metricToolCallCount / metricToolFailureCount(统计由主循环控制)
|
|
614
|
+
*
|
|
615
|
+
* 并发安全:本方法无共享状态,多个 executeOneTool 可同时执行。
|
|
616
|
+
* toolExecutor 内部无状态(纯分发 + 参数校验),天然支持并发调用。
|
|
617
|
+
*
|
|
618
|
+
* @param tc 单个工具调用描述(id + function.name + function.arguments)
|
|
619
|
+
* @param signal 可选的 AbortSignal
|
|
620
|
+
* @returns 工具结果字符串(成功)或 [ERR:TOOL:code] 错误字符串(失败)
|
|
621
|
+
*/
|
|
622
|
+
async executeOneTool(tc, signal) {
|
|
623
|
+
// 工具执行 Span(并发时多个 span 时间重叠,tracer 可观测并发度)
|
|
624
|
+
const toolSpan = this.tracer.startSpan(TRACE_SPANS.TOOL_EXEC, {
|
|
625
|
+
toolName: tc.function.name,
|
|
626
|
+
});
|
|
627
|
+
try {
|
|
628
|
+
// 工具执行包裹 signal 中断,避免 abort 无法中断卡住的 generator
|
|
629
|
+
// raceToolWithSignal 天然兼容并发:每个调用独立 race,{ once: true } 监听器无副作用
|
|
630
|
+
return await this.raceToolWithSignal(tc.function.name, tc.function.arguments, signal);
|
|
631
|
+
}
|
|
632
|
+
catch (err) {
|
|
633
|
+
// 工具执行可能因文件不存在、路径越界等原因失败
|
|
634
|
+
// 捕获异常并转为结构化错误结果字符串,回传给 LLM 让其自行调整策略
|
|
635
|
+
// 避免错误直接传播到 agent.chat() 导致整个对话中断
|
|
636
|
+
const e = toError(err);
|
|
637
|
+
toolSpan.recordException(e);
|
|
638
|
+
if (err instanceof MemoraError) {
|
|
639
|
+
const code = err.errorCode ?? 'UNKNOWN';
|
|
640
|
+
const result = `[ERR:TOOL:${code}] 错误:${err.title}${err.detail ? ` — ${err.detail}` : ''}`;
|
|
641
|
+
logger.warn({ tool: tc.function.name, errorCode: code, title: err.title }, '工具执行失败,错误已回传给 LLM');
|
|
642
|
+
return result;
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
const result = `[ERR:TOOL:UNKNOWN] 错误:工具执行异常 — ${e.message}`;
|
|
646
|
+
logger.error({ tool: tc.function.name, err }, '工具执行异常');
|
|
647
|
+
return result;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
finally {
|
|
651
|
+
toolSpan.end();
|
|
652
|
+
}
|
|
653
|
+
}
|
|
517
654
|
/**
|
|
518
655
|
* 构建 system prompt(注入人格 + 规则 + 领域 + 工具描述)
|
|
519
656
|
*/
|
|
@@ -765,58 +902,7 @@ export class AgentLoop {
|
|
|
765
902
|
logger.debug({ removedCount, remainingMessages: this.messages.length }, '临时 system 消息已清理');
|
|
766
903
|
}
|
|
767
904
|
}
|
|
768
|
-
// ───
|
|
769
|
-
/**
|
|
770
|
-
* 输入护栏检查
|
|
771
|
-
*
|
|
772
|
-
* 在用户输入注入上下文之前运行,遍历所有 guardrail 规则,
|
|
773
|
-
* 用正则匹配用户输入。命中 block action 时返回 blocked=true。
|
|
774
|
-
*
|
|
775
|
-
* 护栏自身异常(正则编译失败等)降级为"放行 + 记日志",
|
|
776
|
-
* 永远不阻断用户对话(降级优先原则)。
|
|
777
|
-
*/
|
|
778
|
-
runInputGuardrails(input) {
|
|
779
|
-
if (this.guardrailRules.length === 0)
|
|
780
|
-
return { blocked: false };
|
|
781
|
-
for (const rule of this.guardrailRules) {
|
|
782
|
-
try {
|
|
783
|
-
// 从记忆内容中提取 pattern(格式:pattern: /regex/ action: block|warn)
|
|
784
|
-
const patternMatch = rule.content.match(/pattern:\s*(.+)/);
|
|
785
|
-
const actionMatch = rule.content.match(/action:\s*(block|warn)/);
|
|
786
|
-
if (!patternMatch || !actionMatch)
|
|
787
|
-
continue;
|
|
788
|
-
const pattern = patternMatch[1]?.trim();
|
|
789
|
-
const action = actionMatch[1]?.trim();
|
|
790
|
-
if (!pattern || !action)
|
|
791
|
-
continue;
|
|
792
|
-
// 去掉正则定界符 //
|
|
793
|
-
const regexStr = pattern.startsWith('/') && pattern.endsWith('/') ? pattern.slice(1, -1) : pattern;
|
|
794
|
-
const regex = new RegExp(regexStr, 'i');
|
|
795
|
-
if (regex.test(input)) {
|
|
796
|
-
if (action === 'block') {
|
|
797
|
-
logger.warn({ rule: rule.name, pattern: regexStr }, '输入护栏阻断');
|
|
798
|
-
return { blocked: true, message: this.ui.inputBlockedByGuard(rule.name) };
|
|
799
|
-
}
|
|
800
|
-
logger.warn({ rule: rule.name, pattern: regexStr }, '输入护栏警告');
|
|
801
|
-
return { blocked: false, warning: this.ui.inputBlockedByGuard(rule.name) };
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
catch (err) {
|
|
805
|
-
// 护栏自身异常降级:放行 + 记日志
|
|
806
|
-
logger.error({ rule: rule.name, err }, '护栏规则执行异常,已降级放行');
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
return { blocked: false };
|
|
810
|
-
}
|
|
811
|
-
/**
|
|
812
|
-
* 输出护栏检查
|
|
813
|
-
*
|
|
814
|
-
* 在 LLM 响应返回给用户之前运行,防止敏感信息泄露。
|
|
815
|
-
* 输入/输出共享同一护栏规则集。
|
|
816
|
-
*/
|
|
817
|
-
runOutputGuardrails(output) {
|
|
818
|
-
return this.runInputGuardrails(output);
|
|
819
|
-
}
|
|
905
|
+
// ─── Reflection 辅助方法 ────────────────────────────────
|
|
820
906
|
/**
|
|
821
907
|
* 判断工具错误结果是否可重试(Reflection 用)
|
|
822
908
|
*
|