M3 · LangGraph 事前信心 + interrupt 实现

从"硬编答案"到"我不知道":Agent 决策信心机制

借鉴论文 3 的事前信心引导:每个 Agent 决策节点加 confidence 评分(0-1),低信心时输出"我不知道"并触发 Frank 拍板。配套 LangGraph interrupt 机制,让 Agent 在关键决策点暂停等人。

01 / 问题

当前 Agent 决策的两个极端

❌ 硬编答案(幻觉)

Agent 信心不足时,强行输出一个看起来合理但错误的决策。例如:

"为账号 X 推荐剧 Y"(实际 X 的粉丝画像不支持 Y 的题材)

后果:发出去的视频 0 播放,浪费素材 + 账号权重

❌ 完全放弃

Agent 信心不足时,报错"无法决策"然后停机。例如:

"对不起,我没有足够信息选剧,请提供更多上下文..."

后果:Loop 卡死,Frank 不知道发生了什么

v3 解决思路:引入第三种状态——"我不知道 + 具体原因"。Agent 诚实输出信心不足的事实和原因,触发 Frank 拍板,而不是硬编或放弃。论文 3 验证:这种方法比硬编/放弃都更靠谱。

02 / 机制

三层信心机制

≥ 0.8
高信心 · 自动执行

Agent 信心 ≥ 80%,按决策结果自动执行。不发飞书告警。

→ 继续
0.6 — 0.8
中信心 · 执行 + 飞书告警

Agent 信心 60-80%,按决策结果执行,但同步飞书告警,让 Frank 看到但不需要立即拍板。

→ 继续 + 告警
< 0.6
低信心 · "我不知道" + Frank 拍板

Agent 信心 < 60%,不输出决策结果,而是返回 {status: "uncertain", reason: "..."},触发 LangGraph interrupt,Frank 在飞书卡片拍板后继续

→ 暂停 + 拍板
03 / Schema

状态 Schema 设计(v3 关键代码)

在 LangGraph 状态里增加 confidence + uncertain_reason + human_decision 三个字段。

from typing import TypedDict, Literal, Optional
from langgraph.graph import StateGraph, END

# ===== v3 Loop 状态:加 3 个新字段 =====
class LoopState(TypedDict):
    # ===== v1-v2 原有字段 =====
    run_id: str
    feedback: dict
    candidates: list[dict]
    selected: list[dict]
    owned_tasks: list[dict]
    cut_outputs: list[dict]
    publish_results: list[dict]
    errors: list[dict]

    # ===== v3 新增:决策信心相关 =====
    last_decision: Optional[dict]            # 最近一次决策结果
    confidence: float                        # 0-1,决策信心
    uncertain_reason: Optional[str]          "我不知道"的原因
    decision_history: list[dict]             # 所有决策历史(含信心)
    human_decision: Optional[dict]           # Frank 拍板结果
    human_decision_required: bool            # 是否需要 Frank 拍板
    daily_interrupt_count: int               # 当日已触发拍板次数(上限 10)

关键设计:

confidence < 0.6human_decision_required = True → 触发 interrupt

human_decision 记录 Frank 拍板内容("选 A" / "选 B" / "跳过" / "重新选"),供后续 run 借鉴

daily_interrupt_count 防止 Frank 拍板疲劳(D2 决策点:日上限 10 次)

04 / 实现

决策节点代码(含 interrupt)

from langgraph.graph import StateGraph, END
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.prebuilt import ToolNode
import random

# ===== 决策节点:每次决策都带 confidence =====
async def decision_node(state: LoopState) -> dict:
    """
    调用 Qwen-Max 做选剧/剪辑/发布决策。
    返回结果必须包含 confidence 字段。
    """
    user_input = state.get("last_user_input", "")
    feedback = state."feedback"

    # Qwen-Max 返回 JSON:{action, params, confidence, reason}
    response = await qwen_max.decide(
        prompt=build_decision_prompt(user_input, feedback),
        tools=state."available_tools",  # 来自 M1 工具箱
        schema={
            "action": "string",
            "params": "object",
            "confidence": "number",  # 0-1
            "reason": "string",
        }
    )

    confidence = response["confidence"]
    daily_count = state.get("daily_interrupt_count", 0)

    # ===== 论文 3 核心:信心分层处理 =====
    if confidence >= 0.8:
        # 高信心:直接执行
        return {
            "last_decision": response,
            "confidence": confidence,
            "human_decision_required": False,
            "decision_history": state.get("decision_history", []) + [{
                "decision": response, "layer": "auto_high", "ts": now()
            }],
        }

    elif confidence >= 0.6:
        # 中信心:执行 + 飞书告警
        await feishu.send_alert(
            title="⚠️ AI Loop 中信心决策",
            body=f"决策: {response['action']} 信心: {confidence} 原因: {response['reason']}",
            level="warn",
        )
        return {
            "last_decision": response,
            "confidence": confidence,
            "human_decision_required": False,
            "decision_history": state.get("decision_history", []) + [{
                "decision": response, "layer": "auto_mid_alert", "ts": now()
            }],
        }

    # 低信心:检查日拍板上限
    if daily_count >= 10:
        # 超过上限:降级到中信心执行
        await feishu.send_alert(
            title="🚨 AI Loop 拍板超限,降级自动执行",
            body=f"日拍板已 {daily_count} 次,降级自动。决策: {response['action']}",
            level="critical",
        )
        return {
            "last_decision": response,
            "confidence": confidence,
            "human_decision_required": False,
            "decision_history": state.get("decision_history", []) + [{
                "decision": response, "layer": "auto_degraded", "ts": now()
            }],
        }

    # 正常低信心:触发拍板
    await feishu.send_decision_card(
        title="🤔 AI Loop 需要你拍板",
        decision=response,
        run_id=state["run_id"],
    )
    return {
        "last_decision": response,
        "confidence": confidence,
        "uncertain_reason": response["reason"],
        "human_decision_required": True,
        "daily_interrupt_count": daily_count + 1,
    }


# ===== 路由:检查是否需要等 Frank 拍板 =====
def route_after_decision(state: LoopState) -> Literal["human_review", "execute"]:
    if state.get("human_decision_required", False):
        return "human_review"
    return "execute"


# ===== Human Review 节点:等 Frank 拍板 =====
def human_review_node(state: LoopState) -> dict:
    """
    LangGraph interrupt 模式:暂停,等 Frank 在飞书卡片上点按钮。
    """
    # interrupt() 会暂停整个 graph,等外部输入
    human_input = interrupt({
        "question": state["uncertain_reason"],
        "ai_suggestion": state["last_decision"],
        "options": ["采用 AI 建议", "选 A 方案", "选 B 方案", "跳过", "重新选"],
    })

    # Frank 拍板后继续
    return {
        "human_decision": human_input,
        "human_decision_required": False,
        "decision_history": state.get("decision_history", []) + [{
            "decision": human_input, "layer": "human_review", "ts": now()
        }],
    }
05 / 接口

飞书拍板卡片设计

Frank 在飞书群里直接看到卡片,点按钮拍板,Agent 自动继续。

AI
AI Loop Agent

🤔 需要你拍板 · run_20260606_001

不确定原因:
目标账号「FB_北美_情感_001」的粉丝画像中"30-45 岁女性"占 65%,但 Qwen 推荐了"霸总短剧"(该剧历史 80% 用户为 18-25 岁女性)。两者匹配度 0.42信心 0.45

AI 建议:改选"婚姻家庭"类短剧

Top 3 备选:

① 《豪门婆婆的逆袭》— 婚姻家庭 / 完播率 78%

② 《全职妈妈重返职场》— 婚姻家庭 / 完播率 72%

③ 《50 岁离婚记》— 婚姻家庭 / 完播率 68%

采用 AI 建议
选 ①
选 ②
选 ③
跳过

点击"采用 AI 建议"或选 ①/②/③ 触发 LangGraph interrupt 恢复; 点击"跳过"则该 run 放弃,记录到 history。

06 / 联动

M3 × M1 联动:信心机制 + 工具箱

flowchart LR P[感知] --> D{决策节点
Qwen-Max} D -->|confidence ≥ 0.8| EX[自动执行] D -->|0.6-0.8| EX2[执行 + 飞书告警] D -->|< 0.6| H[Frank 拍板] H -->|恢复| EX3[继续执行] subgraph TOOL[工具箱 v1 · M1 产出] T1[select_drama_by_audience] T2[ai_cut_v1] T3[publish_video_to_platform] T4[...30 个工具] end EX --> TOOL EX2 --> TOOL EX3 --> TOOL TOOL -.调用历史.-> M[历史记忆
M4 沉淀期更新] M -.-> D style D fill:#f4e5d4,stroke:#c97b3f style H fill:#f0dfe1,stroke:#b06367 style M fill:#d9eae3,stroke:#2f6f5e

M1 工具调用历史 → M4 沉淀期更新工具库 → 提升 M3 决策信心

关键闭环:信心会随工具箱成熟而提升

良性循环:M1 工具箱规模越大 → LLM 决策时"可参考的案例"越多 → 信心自动提升 → 触发 Frank 拍板的频率自动降低。D1 可能 30% 触发拍板,D30 降到 10%,D60 降到 5%。

07 / 实施

D1 — D7 实施步骤

D1 Schema 落地:3 个新字段加进 LoopState FOCUS · 状态先行
  • 子明:confidence / uncertain_reason / human_decision 加进 LangGraph State
  • 子明:加 daily_interrupt_count 计数器(Postgres 存)
  • 博恒:decision_history 表(包含 ts / layer / decision 字段)
D2 决策节点改造:所有 LLM 调用强制返回 confidence FOCUS · prompt 工程
  • 子明:改 Qwen-Max 调用 prompt schema,必填 confidence 字段
  • 子明:写 3 个 test case 验证:高/中/低信心分别走 3 个分支
  • 博恒:建飞书告警通道(中信心触发)
D3 飞书拍板卡片 v1 上线 FOCUS · Frank 体验闭环
  • 子明 + 飞书开发:飞书卡片模板(5 个按钮:采用/选 1/选 2/选 3/跳过)
  • 子明:卡片 → LangGraph interrupt 恢复链路跑通
  • Frank:亲自测试 5 个场景,验证卡片可读性 + 点击响应时间
D4 路由改造:三层信心自动分流 FOCUS · 决策路径
  • 子明:实现 route_after_decision 函数(高/中/低三路)
  • 子明:实现 daily_interrupt_count 上限检查(10 次/天)
  • 子明:实现降级逻辑(超限自动转中信心执行)
D5 集成测试:5 真实场景端到端 FOCUS · 真实数据
  • 4 个组:各出 1-2 个测试场景
  • Frank:实际拍板 5-10 次
  • 验证:① 卡片可达 ② 点击响应 < 30s ③ Agent 继续正确 ④ 历史记录完整
D6 拍板疲劳保护:上限/告警/降级全跑通 FOCUS · 保护 Frank
  • 子明:压测:故意构造 15 个低信心决策,验证前 10 触发拍板、后 5 降级
  • 子明:飞书告警:"日拍板上限已用 X/10"
  • Frank:确认日拍板上限 10 次合理(可调)
D7 M3 上线 + 第一份拍板日志 FOCUS · 全部交付
  • 子明:merge M3 到 main branch
  • 博恒:出"AI Loop M3 上线 · 第一周拍板日志"
  • Frank:在 AI Loop 群公告 M3 上线
08 / 支持

需要 Frank 提供的支持(P0/P1/P2)

P0
飞书机器人"决策拍板"消息权限

需要 Frank 联系飞书 admin:① 申请 AI Loop 机器人"interactive card"权限 ② 消息推到"AI Loop 拍板群"(新群?现有 AI Loop 群加 Frank/玉 即可)③ 测试 Frank 点按钮能否触发 callback URL

截止:D2 18:00 · 负责人:Frank · 协调对象:飞书 admin / 玉

P0
日拍板上限的最终决定

文档默认 10 次/天。Frank 需要根据 D3-D6 实际体验决定:① 上限是 10 还是 20?② 是否分"选剧/剪辑/发布"独立计数?③ 周末是否提高上限(Frank 不在时)?

截止:D6 12:00 · 负责人:Frank

P0
飞书卡片 UI 风格定调

§5 给出的卡片是 MVP,Frank 看完可能想改:① 按钮文案("采用 AI 建议" 还是"执行")② 是否加"我不确定"二次确认按钮 ③ 卡片在群里怎么 @ 谁(@Frank 全部 vs @specific)

截止:D3 14:00 · 负责人:Frank

P1
LangGraph Postgres 部署

M3 依赖 checkpointer 持久化状态(LangGraph interrupt 必需)。需要 Frank 协调:① 复用现有 Postgres 实例?② 单独建一个新库?③ iCenter 后端运维负责人配合

截止:D1 22:00 · 负责人:Frank · 协调对象:iCenter 后端运维

P1
confidence 阈值的拍板决定

文档默认 0.8 / 0.6。Frank 需根据 4 个组业务复杂度决定:① 选剧阈值 ② 剪辑阈值 ③ 发布阈值(可不一样)。阈值越低 → 越少拍板 → 越多自动执行

截止:D4 18:00 · 负责人:Frank · 与 4 个组讨论

P2
拍板"跳过"后的兜底策略

Frank 偶尔点"跳过"(不想处理),需要兜底:① 跳过 → 用 AI 建议自动执行 ② 跳过 → 推回人工池 ③ 跳过 → 等明天?建议 Frank 在 D5 测试后给结论

截止:D5 · 负责人:Frank

P2
decision_history 隐私边界

Frank 的拍板记录是否要给 4 个组看到?CEO 是否要看到?建议 ① 4 个组可见自己拍的板 ② Frank 全部可见 ③ CEO 可见汇总(不含具体理由)。Frank 拍板

截止:D5 · 负责人:Frank