Agent 人机协作回路:审阅-修正-重试的工程化闭环(续篇)
Agent 人机协作回路审阅-修正-重试的工程化闭环续篇场景痛点Agent执行任务出错。用户纠正了输出。下次执行同类任务Agent又犯同样的错。这不是记忆系统能解决的问题。记忆系统存储偏好和事实。人机协作回路要解决的是实时反馈如何立即影响当前执行流——用户说这不对Agent应该停下来、理解纠正意图、修正执行策略、重新尝试而不是等下次对话才生效。当前多数Agent框架的处理方式用户纠正→记录到对话历史→LLM在下次调用时可能参考历史。这是被动的、延迟的、不可靠的。工程化闭环要求纠正信号→意图解析→策略调整→重试→校验→确认全流程可追踪、可度量、可回滚。底层机制与原理剖析人机协作回路的本质是一个有限状态机Agent执行流在每个关键节点都暴露审阅锚点给用户。关键机制拆解审阅锚点Review Anchor。不是每一步都停下来审阅——那会让用户疲惫。锚点只在关键决策点触发方案规划完成后、执行完高风险操作后、最终结果输出前。锚点由review_policy配置默认三个锚点位置。纠正意图分类。用户说不对时意图可能是(a) 方案方向错误→需要重新规划(b) 某步骤参数错误→需要调整参数重试(c) 最终输出格式错误→需要重新格式化。不分类就盲目重试浪费调用次数和用户耐心。重试约束。重试不是无限循环。每次重试消耗token和时间。设置max_retries3超过后转为人工接管模式——Agent停止自主执行变成纯问答模式由用户逐步指导。生产级代码实现HumanCollaborationLoop协作状态机核心// agent/human-collab-loop.ts import { AgentExecutor, TaskPlan, StepResult } from ./executor; type ReviewAnchor after_plan | after_risky_step | before_result; type CorrectionIntent replan | retry_step | refine_output; type LoopState planning | review_plan | executing | review_result | retry | replan | done | human_takeover; interface CorrectionSignal { intent: CorrectionIntent; targetStep?: number; // retry_step时指向具体步骤索引 feedback: string; // 用户原始纠正文本 suggestedFix?: string; // 用户建议的修正方向 timestamp: string; } interface LoopConfig { reviewAnchors: ReviewAnchor[]; maxRetries: number; riskyStepPatterns: string[]; // 匹配高风险步骤的模式如delete,drop,send timeoutPerRetryMs: number; } class HumanCollaborationLoop { private state: LoopState planning; private retryCount 0; private plan: TaskPlan | null null; private stepResults: StepResult[] []; private currentStepIndex 0; private config: LoopConfig; constructor( private executor: AgentExecutor, private userId: string, config?: PartialLoopConfig ) { // 为什么有默认配置而非全部自定义降低接入成本多数团队不需要5个审阅锚点 this.config { reviewAnchors: [after_plan, after_risky_step, before_result], maxRetries: 3, riskyStepPatterns: [delete, drop, send, deploy, publish], timeoutPerRetryMs: 30000, ...config }; } // 主循环驱动Agent从planning到done async run(taskDescription: string): PromiseLoopResult { while (this.state ! done this.state ! human_takeover) { switch (this.state) { case planning: await this.doPlanning(taskDescription); break; case review_plan: await this.doReviewPlan(); break; case executing: await this.doExecuting(); break; case review_result: await this.doReviewResult(); break; case retry: await this.doRetry(); break; case replan: await this.doReplan(); break; } } return { state: this.state, plan: this.plan, stepResults: this.stepResults, totalRetries: this.retryCount }; } private async doPlanning(task: string): void { this.plan await this.executor.plan(task); // 规划完成后检查是否需要审阅锚点 if (this.config.reviewAnchors.includes(after_plan)) { this.state review_plan; } else { this.state executing; } } private async doReviewPlan(): void { // 向用户展示方案并等待确认/驳回 const review await this.presentForReview({ type: plan_review, content: this.plan!, message: 方案已生成共${this.plan!.steps.length}步。请审阅 }); if (review.approved) { this.state executing; } else { this.state replan; this.lastCorrection review.correction; } } private async doExecuting(): void { if (!this.plan || this.currentStepIndex this.plan.steps.length) { this.state review_result; return; } const step this.plan.steps[this.currentStepIndex]; const result await this.executor.executeStep(step); this.stepResults.push(result); this.currentStepIndex; // 检查是否为高风险步骤触发审阅锚点 const isRisky this.config.riskyStepPatterns.some( pattern step.tool.toLowerCase().includes(pattern) ); if (isRisky this.config.reviewAnchors.includes(after_risky_step)) { this.state review_result; } else if (this.currentStepIndex this.plan.steps.length) { // 最后一步完成触发结果审阅锚点 if (this.config.reviewAnchors.includes(before_result)) { this.state review_result; } else { this.state done; } } // 否则继续执行下一步 } private async doReviewResult(): void { const review await this.presentForReview({ type: result_review, content: this.stepResults[this.stepResults.length - 1], message: 步骤${this.currentStepIndex}执行完成。请审阅结果 }); if (review.approved) { if (this.currentStepIndex this.plan!.steps.length) { this.state done; } else { this.state executing; } } else { this.lastCorrection review.correction; this.state retry; } } private lastCorrection: CorrectionSignal | null null; private async doRetry(): void { if (!this.lastCorrection) { this.state executing; return; } this.retryCount; // 超过最大重试次数转为人工接管 // 为什么不直接报错退出Agent停止自主执行不代表任务失败用户可以手动继续 if (this.retryCount this.config.maxRetries) { this.state human_takeover; return; } const intent this.lastCorrection.intent; switch (intent) { case retry_step: // 调整当前步骤参数后重试 // 为什么只改参数不改方案用户明确指出是参数问题整体方案没问题 const step this.plan!.steps[this.lastCorrection.targetStep ?? this.currentStepIndex - 1]; const adjustedStep this.adjustStepParams(step, this.lastCorrection); const result await this.executor.executeStep(adjustedStep); this.stepResults[this.stepResults.length - 1] result; this.state review_result; break; case replan: // 问题需要重新规划 this.state replan; break; case refine_output: // 输出格式/内容需要优化不重新执行只重新格式化 const refined await this.executor.refineOutput( this.stepResults[this.stepResults.length - 1], this.lastCorrection.feedback ); this.stepResults[this.stepResults.length - 1] refined; this.state review_result; break; } } private async doReplan(): void { // 将纠正反馈融入新的规划 // 为什么保留原始任务描述修正的是策略不是任务本身 const correctionContext this.lastCorrection ? \n\n注意上一版方案被驳回原因是${this.lastCorrection.feedback}。请调整策略。 : ; this.plan await this.executor.plan( this.plan!.originalTask correctionContext ); this.currentStepIndex 0; this.stepResults []; this.state review_plan; } // 根据纠正信号调整步骤参数 private adjustStepParams(step: any, correction: CorrectionSignal): any { const adjusted { ...step }; if (correction.suggestedFix) { // 将用户建议注入步骤的额外参数 adjusted.params { ...adjusted.params, _correction_hint: correction.suggestedFix }; } return adjusted; } // 向用户展示审阅内容实际实现对接前端交互组件 private async presentForReview(reviewPayload: any): PromiseReviewResponse { // 此处对接具体的前端审阅组件或CLI交互 // 返回用户确认或驳回纠正信息 throw new Error(需要接入具体的用户交互组件); } } interface LoopResult { state: LoopState; plan: TaskPlan | null; stepResults: StepResult[]; totalRetries: number; } interface ReviewResponse { approved: boolean; correction?: CorrectionSignal; }CorrectionIntentClassifier纠正意图分类器// agent/correction-classifier.ts import { LLMClient } from ./llm-client; const INTENT_PROMPT 分析用户的纠正反馈判断意图类别。只返回JSON不要解释。 类别定义 - replan方案方向/整体策略错误需要重新规划 - retry_step某步骤的参数或执行细节错误调整后重试即可 - refine_output输出格式/呈现方式需要优化不重新执行 输入 任务{task} 当前步骤{current_step} Agent输出{agent_output} 用户反馈{user_feedback} 输出格式 {intent:replan|retry_step|refine_output,target_step:null|数字,reason:简短解释} ; class CorrectionIntentClassifier { constructor(private llm: LLMClient) {} async classify( task: string, currentStep: string, agentOutput: string, userFeedback: string ): PromiseCorrectionSignal { const prompt INTENT_PROMPT .replace({task}, task) .replace({current_step}, currentStep) .replace({agent_output}, agentOutput) .replace({user_feedback}, userFeedback); const response await this.llm.complete(prompt, { temperature: 0, // 为什么temperature0意图分类是确定性任务不需要创造性 maxTokens: 200 }); try { const parsed JSON.parse(response); return { intent: parsed.intent, targetStep: parsed.target_step, feedback: userFeedback, suggestedFix: parsed.reason, timestamp: new Date().toISOString() }; } catch { // 解析失败时默认为retry_step——最保守的策略不会浪费重新规划的资源 return { intent: retry_step, feedback: userFeedback, timestamp: new Date().toISOString() }; } } }协作指标收集// agent/collab-metrics.ts class CollabMetricsCollector { private metrics { totalLoops: 0, loopsWithCorrection: 0, correctionsByIntent: { replan: 0, retry_step: 0, refine_output: 0 } as Recordstring, number, averageRetriesPerLoop: 0, humanTakeovers: 0, avgTimeFromCorrectionToRetry: 0 }; recordLoopResult(result: LoopResult): void { this.metrics.totalLoops; if (result.totalRetries 0) { this.metrics.loopsWithCorrection; } this.metrics.averageRetriesPerLoop (this.metrics.averageRetriesPerLoop * (this.metrics.totalLoops - 1) result.totalRetries) / this.metrics.totalLoops; if (result.state human_takeover) { this.metrics.humanTakeovers; } } recordCorrection(signal: CorrectionSignal): void { this.metrics.correctionsByIntent[signal.intent]; } // 生成周报数据 // 为什么自动生成报告而非手动统计团队没有时间手动分析协作数据 weeklyReport(): string { const correctionRate this.metrics.loopsWithCorrection / this.metrics.totalLoops; const takeoverRate this.metrics.humanTakeovers / this.metrics.totalLoops; return [ 协作回路周报, 总执行次数: ${this.metrics.totalLoops}, 涉及纠正的执行: ${this.metrics.loopsWithCorrection} (${(correctionRate * 100).toFixed(1)}%), 平均重试次数: ${this.metrics.averageRetriesPerLoop.toFixed(2)}, 人工接管次数: ${this.metrics.humanTakeovers} (${(takeoverRate * 100).toFixed(1)}%), 纠正意图分布:, 重新规划: ${this.metrics.correctionsByIntent.replan}, 步骤重试: ${this.metrics.correctionsByIntent.retry_step}, 输出优化: ${this.metrics.correctionsByIntent.refine_output}, takeoverRate 0.1 ? ⚠️ 人工接管率超过10%建议检查Agent规划质量 : , correctionRate 0.3 ? ⚠️ 纠正率超过30%建议优化Agent初始规划策略 : ].filter(Boolean).join(\n); } }边界分析与架构权衡审阅锚点数量与用户体验的矛盾三个锚点方案审阅高风险步骤审阅结果审阅对复杂任务合理。但对简单任务查一下天气三次停顿令人烦躁。解决方案动态锚点策略。根据方案复杂度自动调整function decideAnchors(plan: TaskPlan): ReviewAnchor[] { const riskySteps plan.steps.filter(s RISKY_PATTERNS.some(p s.tool.includes(p)) ); if (plan.steps.length 2 riskySteps.length 0) { // 简单无风险任务只审阅最终结果 return [before_result]; } if (riskySteps.length 0) { // 有风险操作保留全部锚点 return [after_plan, after_risky_step, before_result]; } // 中等复杂度方案结果 return [after_plan, before_result]; }纠正意图分类的准确率LLM分类意图的准确率约85%。15%的误分类会导致retry_step误分为replan浪费重新规划的资源或replan误分为retry_step方向错误的情况下调整参数必然再失败。缓解方案连续两次retry_step失败后自动升级为replan。不依赖分类器100%准确靠失败计数兜底。异步场景的审阅机制用户可能不在屏幕前。Agent停在审阅锚点等待用户响应如果等了30分钟没人回复怎么办两种策略超时自动继续审阅锚点超时后Agent按当前方案继续执行。风险是用户回来后发现结果不满意。超时暂停Agent进入idle状态等用户回来后恢复。适合高风险操作。策略选择取决于步骤风险等级。高风险步骤必须暂停等待低风险步骤可以超时继续。与反馈闭环系统的关系人机协作回路处理实时纠正当前执行流内生效。反馈闭环系统处理长期学习跨会话生效。两者互补但不重叠协作回路产生的纠正信号应同步写入反馈闭环系统——当前任务的纠正经验成为未来任务的预防知识。反馈闭环系统的知识应注入协作回路的规划阶段——Agent在规划时参考历史纠正减少需要人工干预的概率。总结人机协作回路把Agent从执行完等用户骂变成关键节点主动请用户审阅。核心设计审阅锚点在关键决策点暂停不是每步都停。锚点数量根据任务复杂度和风险等级动态调整。纠正意图分类决定重试策略replan重新规划retry_step调整参数refine_output优化呈现。分类错误时靠连续失败计数兜底。重试有上限。超过3次转为人工接管Agent停止自主执行变成问答模式。协作指标量化回路健康度纠正率30%说明Agent规划质量不够人工接管率10%说明Agent不适合当前任务域。协作回路与反馈闭环互补实时纠正→长期学习→预防未来同类错误。这套机制让Agent不再是要么全信要么全否的黑箱变成一个可与用户协商、可被纠正、可追踪决策过程的协作伙伴。资料说明本文中的协议、版本、性能、成本和行业趋势应以可核验的一手资料为准。未标注统计口径的比例、时间表和预测仅作工程讨论不应视为行业事实。可参考 0730 资料来源索引并在发布前将具体来源贴到对应断言之后。