程云来 / 杭州
Back to Blog·LangGraph
·About 10 min

LangGraph 过程监控(三):反馈按钮怎样变成调优闭环

把前端反馈、后端事实、Phoenix Trace、离线评估和灰度发布串起来,让一次‘不好用’能推动下一版系统被验证地改进。


一个反馈按钮不能自动告诉你答案错在哪里

设想用户等了 18 秒,看到一段没有引用的回答,最后点击 👎。这个动作至少可能表达四件不同的事:答案事实错误、引用不可信、等待太久、页面没有解释过程。若后端只保存 liked=false,这些问题会被压成一个无法行动的数字。

反馈闭环的目标不是收集更多按钮,而是让每条反馈都能回到当时的运行。例如用户评价“引用不足”,开发者应能从 run_id 找到:检索节点返回了几个来源、最终回答引用了哪几个、使用了哪个模型和提示词版本,以及用户评价的是哪一条消息。本文使用“资料调研助手”作为贯穿案例,反馈只评价一次已经完成的回答,不把它伪装成训练标签。

先设计问题,再设计按钮

第一版可以使用二元反馈加可选原因:

text
有帮助 / 没帮助
没帮助原因:事实不准确|引用不足|过程太慢|格式不符合预期|其他
补充说明:可选,限制长度

按钮旁边应明确反馈对象是“这条回答”还是“整次运行”。如果一个运行产生多条消息,反馈必须带 message_id;如果用户评价的是过程,则需要另一个 target=run,不能靠前端当前选中项猜测。

前端只提交事实和用户意图,不把当前页面的完整 State 上传:

typescript
type FeedbackRequest = {
  client_event_id: string;
  run_id: string;
  message_id: string;
  value: "positive" | "negative";
  reason?: "incorrect" | "citation" | "slow" | "format" | "other";
  comment?: string;
  ui_version: string;
};

client_event_id 由客户端为一次点击生成,用于重复提交幂等。run_idmessage_id 必须由服务端校验归属;不能因为用户提交了一个字符串,就允许他评价或读取别人的运行。

后端先保存事实,再异步处理

反馈接口的同步职责很小:鉴权、校验运行归属、做幂等写入并返回结果。分类、脱敏、关联 Trace 和进入评估集可以异步执行,避免页面因为下游分析不可用而失败:

python
def submit_feedback(user, request: FeedbackRequest) -> FeedbackResponse:
    run = run_repository.get(request.run_id)
    if run is None or run.owner_id != user.id:
        raise PermissionError("run is not accessible")

    if not run.has_message(request.message_id):
        raise ValueError("message does not belong to run")

    row = feedback_repository.insert_idempotent(
        client_event_id=request.client_event_id,
        run_id=request.run_id,
        message_id=request.message_id,
        value=request.value,
        reason=request.reason,
        comment=redact(request.comment),
        app_version=run.app_version,
        model_version=run.model_version,
    )
    feedback_queue.publish({"feedback_id": row.id})
    return FeedbackResponse(id=row.id, accepted=True)

这段代码刻意没有把反馈直接写进模型提示词,也没有在请求里同步调用 Phoenix。数据库记录的是用户实际做过的事;异步任务再把 feedback_id 与 Trace、版本和任务类型关联起来。这样 Phoenix 暂时不可用时,反馈事实仍然不会丢,后续仍可补做关联。

反馈和 Phoenix Trace 如何关联

Phoenix1 提供 Trace 的查询和关联入口;反馈记录需要保存能定位到对应运行的标识。

反馈表至少要能关联:

字段作用
run_id / message_id找到用户评价的具体运行和消息
trace_id在 Phoenix 打开对应 Trace
thread_id理解恢复和多轮上下文
app_version / prompt_version / model_version比较版本变化
task_type / tenant_id按业务场景和权限分桶
created_at计算反馈延迟和灰度窗口

如果 Trace 中没有 run_id,可以在应用入口补上;如果反馈表没有 trace_id,也可以通过受控的运行映射表查询。不要让前端直接提交一个 Phoenix 查询 URL,更不要把完整 Trace 内容复制进反馈表。

关联成功后,开发者可以从一条负反馈看到完整的调查路径:检索是否为空、哪个节点重试、模型版本和 token、最终事件是否超时。Trace 解释“系统做了什么”,反馈说明“用户认为结果是否有用”;只有前者,团队可能优化一个用户根本不在意的问题,只有后者,又不知道应该改哪一步。

监控的不只是反馈比例

全局负反馈率很容易误导。一个版本可能减少了点击,却同时提高了等待时长;另一个版本可能让用户更满意,但成本翻倍。建议按任务类型、版本和原因同时看:

text
质量:正 / 负反馈率、引用覆盖、人工复核通过率
运行:p50 / p95 延迟、失败率、重试率、暂停时长
成本:每次运行 token、模型费用、工具调用次数
体验:从 run.started 到首个可见事件的时间、断线率

指标标签使用有限枚举,例如 task_typereason;不要把 run_id、用户评论或 prompt 作为高基数指标标签。原始反馈和 Trace 仍保存在受控存储中,指标只用于聚合趋势。

把样本变成可处理的问题桶

负反馈不是标准答案,它只是一个需要解释的信号。比如用户标记“事实错误”,仍要检查引用、检索时间和任务上下文,不能直接把用户评论当作正确答案。异步处理可以先按原因分桶,再由人工复核高影响样本:

正在绘制图表…
查看 Mermaid 源码
flowchart LR
    A[用户反馈] --> B[鉴权与幂等写入]
    B --> C[脱敏与关联 Trace]
    C --> D{问题分桶}
    D -->|事实 / 引用| E[检查检索与证据]
    D -->|过程慢| F[检查节点与模型耗时]
    D -->|格式问题| G[检查输出协议与 UI]
    D -->|其他| H[人工复核]

Mermaid 大图

可滚动查看图表,点击缩放比例可恢复 100%。按 Esc 关闭。

分桶规则可以迭代,但原始用户选择和评论应保持不可变,避免改了分类后无法复盘历史。

从反馈沉淀评估集,而不是直接训练

经过脱敏和人工确认的样本才适合进入评估集。一个样本至少包含输入摘要、期望条件、实际输出、引用、运行版本和失败标签:

json
{
  "case_id": "case-2026-001",
  "input": {"topic": "LangGraph 持久化"},
  "expectations": ["说明 thread_id 的作用", "引用官方资料"],
  "actual": {"answer": "...", "citations": ["..."]},
  "labels": ["missing_citation"],
  "source_run_id": "run-123",
  "source_version": "prompt-7/model-4"
}

负反馈样本不等于正确答案,正反馈也不等于事实正确。每次调优应在同一批评估样本上比较答案质量、引用覆盖、工具次数、延迟和费用;线上反馈负责发现问题,离线评估负责判断候选改动是否值得发布。

用一次小改动验证闭环

调优不要同时改模型、提示词、检索器和 UI。先写一条可证伪的假设,例如“检索为空时先请求澄清,会减少引用不足的负反馈,但会增加一次交互延迟”。然后按照下面的顺序执行;如果结果没有改善,应该能指出是哪一个假设被否定,而不是只能说“新版本感觉不太好”:

正在绘制图表…
查看 Mermaid 源码
flowchart TD
    A[生产反馈] --> B[脱敏分桶]
    B --> C[评估样本]
    C --> D[一个修复假设]
    D --> E[离线评估]
    E -->|不通过| D
    E -->|通过| F[小流量灰度]
    F --> G[比较质量 / 延迟 / 成本]
    G -->|改善| H[扩大并记录版本]
    G -->|回归| I[停止或回滚]
    H --> A
    I --> D

Mermaid 大图

可滚动查看图表,点击缩放比例可恢复 100%。按 Esc 关闭。

图下的判断不能只看点赞比例。灰度期间同时比较负反馈率、关键 Trace 的延迟和失败率、token / 费用以及人工复核结果;改动只有在目标指标改善且没有突破成本或安全边界时才扩大流量。

两条反馈闭环失败路径

用户重复点击,反馈数量被放大

先查 client_event_id、数据库唯一约束和网络重试日志。若同一次点击写入多行,说明幂等只停留在前端;处理方式是后端以 client_event_id 建唯一键,并让重复请求返回第一次结果。验证时连续提交同一请求,反馈表只能保留一条事实。

负反馈很多,却无法判断应该改什么

先看是否缺少 message_id、版本和 Trace 关联,以及自由文本是否被直接丢弃。若所有问题都落在 other,说明按钮采集了情绪却没有诊断入口;处理方式是补有限原因、保留脱敏评论,并人工抽样建立评估样本。验证时从一个样本可以打开 Trace、复现输入并说出下一步修复假设。

反馈系统的边界

用户反馈可能包含个人信息、恶意输入或偶然偏好,不能自动当作训练标签。高风险决策需要人工复核和权限控制;反馈接口也要限制频率,防止通过 run_id 枚举其他用户的答案。

闭环的终点不是“收到了反馈”,而是下一版系统能够用同一批样本和线上证据证明自己变好了。比如针对“引用不足”改了检索过滤器,至少要同时比较引用覆盖、负反馈率、延迟和成本;如果无法说清改动假设、评估结果和回滚条件,继续收集更多按钮只会增加噪声。

最小落地顺序

  1. 前端增加二元反馈和有限原因,携带 run_idmessage_id、应用版本。
  2. 后端以 client_event_id 幂等写入反馈表,并校验运行归属。
  3. Phoenix Trace 统一记录运行、节点、模型和工具的关联属性。
  4. 建立按原因、版本和任务类型切分的基础看板。
  5. 每周人工复盘高影响样本,形成第一版评估集。
  6. 每次只验证一个调优假设,先离线、再灰度,最后扩大或回滚。

参考资料

Footnotes

  1. Phoenix 官方文档:用于查询和关联 LLM、Agent 与工具调用的 Trace。