Agent 反馈闭环:用户纠错如何反向优化 Prompt 和工具链

一、用户反复纠正同一个错误,Agent 却永远学不会

Agent 上线两周后,客服团队反馈了 50 个类似问题。
"Agent 把退货地址搞错了"出现了 15 次。
"搜索结果的格式不是用户想要的"出现了 20 次。
每次用户纠正,Agent 当下改对了。
下次换个用户问类似问题,它又犯同样的错。

问题在于这些纠正信息是一次性的。
修改完当前对话就丢弃了,没有沉淀为系统记忆。
反馈闭环就是把这些纠正信息结构化存储。
下一次类似场景自动应用修正。

这是 Agent 产品中最常见的"记忆断层"问题。
传统的 Chatbot 每次对话是独立的——上一轮的纠正不会影响下一轮的行为。
Agent 比 Chatbot 多了工具调用和推理能力,但在学习能力上并没有本质提升。
它仍然是一个无状态的服务——每次 Prompt + 工具调用 → 输出,不会因为"上次用户纠正了退货地址"而在下次自动修正。

这种"记忆力缺失"背后是架构问题,不是模型问题。
模型有能力理解反馈并在当前对话中修正,但修正结果被丢弃在了对话历史中——下一轮全新的 System Prompt 里没有这些修正信息。
所以反馈闭环要解决的不是"如何让模型理解反馈",而是"如何把一次修正确认为系统级的规则"。

二、三层反馈闭环架构

flowchart TB
    A[用户纠错反馈] --> B{反馈类型}
    B -->|短期| C[即时修正层]
    B -->|中期| D[Few-shot 示例库]
    B -->|长期| E[Prompt 模板优化]

    C --> F[当前对话中纠正输出]
    D --> G[存入示例库,相似场景自动引用]
    E --> H[定期批量更新 System Prompt]

    G --> I[相似度匹配选最优示例]
    I --> F

    H --> J[统计高频纠错 → 更新规则]
    J --> G

三层的时间尺度不同:即时修正是秒级(当前对话),Few-shot 注入是分钟级(相似场景触发),Prompt 优化是天/周级(定期统计)。
这三层不应该互相替代,而应该同时运行——即时修正解决当下问题,Few-shot 防止短期内同类错误,Prompt 优化从根本上减少某类错误的产生。

Few-shot 示例库的核心挑战不在存储,而在相似度匹配。
用简单的关键词匹配(如代码中的 find_similar 方法)只能覆盖显式关联——比如同样提到"地址"的场景。
但用户可能用不同的措辞表达同一个问题:"地址不对"和"你发到哪里去了"在语义上指向同一个错误类型。
生产环境应该用 Embedding 做语义相似度匹配,而不是关键词匹配。

另外,Few-shot 示例不是越多越好。
注入 3 个高度相关的示例,效果远好于注入 10 个泛泛相关的示例。
示例库的去重和质量筛选是反馈闭环中最容易被忽略的工程工作。

三、反馈闭环的 Python 实现

下面的代码实现了一个三层反馈闭环:即时纠正记录、Few-shot 示例匹配和 Prompt 自动优化建议。

"""
feedback_loop.py - Agent 反馈闭环
"""

import hashlib
import json
import logging
from datetime import datetime
from typing import Dict, List, Optional, Tuple
from collections import defaultdict

logger = logging.getLogger(__name__)


@dataclass
class Correction:
    """单条纠正记录"""
    id: str
    original_output: str      # Agent 原始输出
    corrected_output: str     # 用户纠正后的输出
    context: str              # 对话上下文
    feedback_type: str        # 错误类型
    timestamp: float


class FeedbackCollector:
    """反馈收集器"""

    def __init__(self, max_examples: int = 100):
        self.max_examples = max_examples
        self.corrections: List[Correction] = []
        self.error_stats: Dict[str, int] = defaultdict(int)

    def record(
        self,
        original: str,
        corrected: str,
        context: str,
        feedback_type: str = "general",
    ):
        """记录一次纠正"""
        correction = Correction(
            id=hashlib.md5(
                f"{original}{corrected}".encode()
            ).hexdigest()[:8],
            original_output=original,
            corrected_output=corrected,
            context=context,
            feedback_type=feedback_type,
            timestamp=datetime.now().timestamp(),
        )

        # 去重
        for c in self.corrections:
            if c.original_output == original and c.corrected_output == corrected:
                return  # 已有相同纠正

        self.corrections.append(correction)
        self.error_stats[feedback_type] += 1

        # 限制最大示例数
        if len(self.corrections) > self.max_examples:
            # 保留最新的
            self.corrections = self.corrections[-self.max_examples:]

    def find_similar(
        self, current_context: str, top_k: int = 3
    ) -> List[Correction]:
        """
        根据当前上下文找相似的纠正示例
        用于 Few-shot Prompt 构建
        """
        # 简化:关键词匹配
        scores = []
        context_lower = current_context.lower()

        for c in self.corrections:
            score = 0
            for word in context_lower.split():
                if word in c.context.lower():
                    score += 1
            if score > 0:
                scores.append((score, c))

        scores.sort(key=lambda x: x[0], reverse=True)
        return [c for _, c in scores[:top_k]]

    def build_few_shot_prompt(self, current_context: str) -> str:
        """构建 Few-shot 示例"""
        examples = self.find_similar(current_context)
        if not examples:
            return ""

        lines = ["## 历史纠正示例(请参考这些修正):"]
        for i, ex in enumerate(examples):
            lines.append(f"\n示例 {i+1}:")
            lines.append(f"原始输出: {ex.original_output}")
            lines.append(f"纠正后: {ex.corrected_output}")

        return "\n".join(lines)

    def analyze_patterns(self) -> Dict:
        """分析高频错误模式,生成 Prompt 优化建议"""
        patterns = {}

        # 统计错误类型
        patterns["top_errors"] = sorted(
            self.error_stats.items(),
            key=lambda x: x[1],
            reverse=True,
        )[:5]

        # 检查是否有重复修正模式
        if self.corrections:
            corrections_by_type = defaultdict(list)
            for c in self.corrections:
                corrections_by_type[c.feedback_type].append(c)

            for ftype, corrections in corrections_by_type.items():
                if len(corrections) >= 3:
                    patterns[f"pattern_{ftype}"] = {
                        "count": len(corrections),
                        "suggestion": self._suggest_fix(ftype, corrections),
                    }

        return patterns

    def _suggest_fix(
        self, ftype: str, corrections: List[Correction]
    ) -> str:
        """针对错误类型建议 Prompt 修复"""
        suggestions = {
            "format_error": (
                "在 System Prompt 中添加:始终使用 Markdown 格式输出,"
                "列表使用 - 开头"
            ),
            "wrong_address": (
                "在工具调用前添加地址校验步骤,"
                "确认地址存在于地址库中"
            ),
            "missing_info": (
                "在回复前检查是否包含必要信息:名称、时间、地点"
            ),
        }
        return suggestions.get(
            ftype,
            f"累计 {len(corrections)} 次类似错误,建议人工分析",
        )


class PromptOptimizer:
    """Prompt 自动优化器"""

    def __init__(self, feedback_collector: FeedbackCollector):
        self.feedback = feedback_collector

    def optimize(
        self, current_prompt: str
    ) -> Tuple[str, str]:
        """
        根据反馈数据优化 System Prompt
        返回: (优化后的 Prompt, 优化说明)
        """
        patterns = self.feedback.analyze_patterns()
        suggestions = []
        optimized = current_prompt

        for key, info in patterns.items():
            if key.startswith("pattern_") and "suggestion" in info:
                suggestion = info["suggestion"]
                if suggestion not in optimized:
                    optimized += f"\n\n## 优化规则: {suggestion}"
                    suggestions.append(suggestion)

        return optimized, "\n".join(suggestions) if suggestions else "无需优化"


# ---- Agent 中集成反馈 ----

class AgentWithFeedback:
    """带反馈闭环的 Agent"""

    def __init__(self, system_prompt: str):
        self.system_prompt = system_prompt
        self.feedback = FeedbackCollector()
        self.optimizer = PromptOptimizer(self.feedback)

    def respond(self, user_input: str, context: str = "") -> str:
        """生成回复(注入 Few-shot 示例)"""
        # 构建增强 Prompt
        enhanced_prompt = self.system_prompt

        # 注入历史纠正示例
        few_shot = self.feedback.build_few_shot_prompt(
            context + " " + user_input
        )
        if few_shot:
            enhanced_prompt += "\n\n" + few_shot

        # 调用 LLM(实际项目)
        response = call_llm(enhanced_prompt, user_input)
        return response

    def collect_feedback(
        self,
        original_output: str,
        corrected_output: str,
        context: str,
    ):
        """收集用户纠正反馈"""
        # 自动识别错误类型
        ftype = self._classify_error(original_output, corrected_output)
        self.feedback.record(
            original_output, corrected_output, context, ftype
        )

        # 检查是否需要更新 Prompt
        stats = self.feedback.error_stats
        if max(stats.values(), default=0) >= 5:
            logger.warning("累计纠正已达阈值,建议执行 Prompt 优化")

    def optimize_periodically(self):
        """定期优化(如每周触发)"""
        new_prompt, changes = self.optimizer.optimize(self.system_prompt)
        if changes != "无需优化":
            logger.info(f"Prompt 优化: {changes}")
            self.system_prompt = new_prompt

    def _classify_error(
        self, original: str, corrected: str
    ) -> str:
        """自动分类错误类型"""
        if len(original) != len(corrected):
            return "format_error"
        if "地址" in original or "地址" in corrected:
            return "wrong_address"
        if len(corrected) > len(original) * 1.5:
            return "missing_info"
        return "general"

FeedbackCollector 的去重逻辑不容忽视。
如果同一个纠正被重复记录(不同用户的相似反馈),示例库会膨胀,Few-shot Prompt 也会越来越长。
MD5 去重可以保证相同内容的纠正只存一份,但对于"相似但不完全相同"的纠正(比如两次"地址错了"的具体地址不同),需要额外的语义去重策略。

四、反馈闭环的成本考量

Few-shot 示例会增加 Prompt Token 消耗。
每个示例约 100-300 Token。
建议限制最多引用 3 个示例。
Token 增加约 10-20%,但准确率提升 5-15%。

自动错误分类可能不准。
_classify_error 的简单规则覆盖不了复杂场景。
建议先人工标注一段时间,积累数据后再做自动分类。

还有一个容易被忽略的成本:Prompt 自动优化的风险。
每次自动修改 System Prompt 都有可能引入新的问题——比如优化了一条规则但和已有的规则冲突。
建议 Prompt 优化生成的是"建议"而非自动应用——由人工 Review 后手动执行,而不是让系统自动替换 System Prompt。
在你有了足够的 A/B 测试数据证明自动优化的效果后,再考虑自动化。

另外,反馈数据本身的价值随时间衰减。
三个月前的纠正可能对应一个已经被修复的问题——但如果问题修复了,对应的 Few-shot 示例还留在库里,反而会误导模型。
建议反馈库设置 TTL(如 30 天),过期示例自动清理,确保示例库始终反映的是"最近的有效反馈"。

五、总结

Agent 反馈闭环让用户纠错沉淀为系统能力。
三层机制:即时修正、Few-shot 示例库、Prompt 定期优化。
相似度匹配保证示例的相关性。
Token 成本增加约 10-20%,但可显著提升准确率。
周期性 Prompt 优化防止同类错误反复出现。

落地优先级:先做反馈收集(记录每次纠正),再做 Few-shot 注入(相似场景复用),最后做自动 Prompt 优化(人工审核)。
不要一上来就追求全自动——反馈闭环的难点不在技术实现,而在如何保证反馈数据的质量和时效性。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐