在带宽销售客服中,真正危险的往往不是回答不够流畅,而是流程在信息不足或用户尚未确认时提前进入推荐。要稳定控制这类多轮对话,不能只依赖提示词判断,还需要把业务约束写进状态和路由。下面从 LangGraph 主图出发,拆解分类分流、条件边与确认闸门的具体配合方式。
做带宽销售客服最容易翻车的,不是模型不会说话,而是意图混在一起还急着推产品:用户刚问,系统已经甩一堆卡片;金额期限用途还没齐,推荐理由却写得像成交了。缺字段就推、未确认就推,信任会先垮掉。
公开仓库 langgraph_fly_base 的解法不是「再写一个更大的 Prompt」,而是把主链路收成一张 LangGraph 主图:FlowState 显式记账,分类分流后用条件边把闸门卡住——信息未齐、未确认时,路由强制停在收集与确认,进不了推荐。
本文只讲主图编排:问题分类、条件边、确认闸门。双库 TopN 推荐细节留给后续文章,这里只点到「确认通过后进推荐子图」。

依据仅限公开仓库 README 与源码;下文会把「已实现 / 半成品 / 未来」分开写,并给出文件证据。
入口不是分类,而是 问题修复 → 问题分类。build_flow_graph 里 set_entry_point(FIX),再 add_edge(FIX, CLASSIFY),保证每轮先过修复节点,再进分类总闸。

对应节点(sale_app/core/mutil/node_names.py):
| 常量 | 中文节点名 | 角色 |
|---|---|---|
| FIX | 问题修复 | 改写 / 规范用户问题 |
| CLASSIFY | 问题分类 | 五类分流总闸 |
| CHAT | 闲聊经理 | 非业务闲聊 |
| INTENT | 意图确认 | 是否有带宽意向 |
| GATHER | 信息收集 | 四项信息多轮补齐 |
| CONFIRM | 信息确认 | 摘要 + 确认闸门 |
| RECOMMEND | 产品推荐 | 推荐子图挂载点 |
| QA | 产品解答专家 | 知识库 RAG |
| CONVERSION | 客户转化 | 推荐后留资 / 预约 |
中文标签流程图如下(与 flow_graph.py 边一致):

flowchart TD
START([用户提问]) --> FIX[问题修复]
FIX --> CLASSIFY[问题分类]
CLASSIFY -->|decide_router| CHAT[闲聊经理]
CLASSIFY -->|decide_router| QA[产品解答专家]
CLASSIFY -->|decide_router| INTENT[意图确认]
CLASSIFY -->|decide_router| GATHER[信息收集]
CLASSIFY -->|decide_router| CONFIRM[信息确认]
CLASSIFY -->|decide_router| RECOMMEND[产品推荐]
CLASSIFY -->|decide_router| CONVERSION[客户转化]
INTENT -->|有意向| GATHER
INTENT -->|无意向| CHAT
GATHER -->|information_router 未齐或本轮结束| END1([本轮结束])
GATHER -->|四项齐且需推荐| CONFIRM
CONFIRM -->|confirm_router 未确认| END2([本轮结束等确认])
CONFIRM -->|已确认 isInfoConfirmed| RECOMMEND
CHAT --> END3([结束])
QA --> END4([结束])
RECOMMEND --> END5([结束])
CONVERSION --> END6([结束])
要点有三:
next,而是过 decide_router 再改写目标节点。information_router 先送进确认。confirm_router 只有 isInfoConfirmed 为真才进推荐,否则本轮 END,等用户下一句。主图状态在 sale_app/core/mutil/flow_graph.py 的 FlowState:
class FlowState(TypedDict):
messages: Annotated[List[BaseMessage], operator.add]
next: str
pre_node: str
question: str
fixed_question: str
history: list
information_sequences: Annotated[list, operator.add]
product_list: list
isRecommend: bool
isInfoConfirmed: bool
awaiting_info_confirm: bool
info_summary: str
awaiting_conversion: bool
conversion_completed: bool
和「闸门」直接相关的字段:
| 字段 | 作用 |
|---|---|
information_sequences | 已收集序号;满 4 项才算齐(collection_utils.is_collection_complete) |
awaiting_info_confirm | 已出摘要,正在等用户确认或修改 |
isInfoConfirmed | 确认通过;confirm_router 看它放行推荐 |
info_summary | 给用户看的摘要文本 |
product_list / isRecommend | 推荐结果与推荐意图标记 |
awaiting_conversion / conversion_completed | 推荐后转化阶段 |
多轮续跑靠 SQLite Checkpoint:startup_chain 用 AsyncSqliteSaver.from_conn_string(...),路径落在 storage/memory_file/ch@t_history.db,图 compile(checkpointer=_checkpointer)。同一 session_id 进来时,闸门位不会丢——这是条件边能「卡住」的前提。
分类成员在构图时写死(flow_graph.py):
信息收集 / 闲聊经理 / 意图确认 / 产品推荐 / 产品解答专家
链路是:
suggest_category_from_state(classify_utils.py):能规则定就直接返回,跳过分类 LLM。例如已推过产品且像转化 → 客户转化;正在等确认 → 信息确认;等转化未完成 → 客户转化。question_class_func(question_class_node.py):拼历史(QUESTION_CLASS_HISTORY_TURNS)+ 当前问题 + 类别列表,调 LLM。parse_category_name:先剥 markdown 代码块,再 json.loads 取 category_name;解析失败则在原文里扫合法类名;再不行用 fallback_category。fallback_category 同样看闸门:等确认、收集未齐、推荐后转化 / QA,避免分类抽风把流程打乱。README 里也提示可用 QUESTION_CLASS_FEW_SHOT、LLM_ENABLE_REASONING=false 给分类提速——分类是简单结构化输出,没必要开长思考。
三个 router 都在 sale_app/core/mutil/flow_routers.py。这是主图的「硬逻辑」,比 Prompt 自觉可靠。
decide_router:分类后总闸分类节点只写出 state["next"],真正去哪由 decide_router 决定。典型规则:
has_recommended):转化意图 → 客户转化;产品问答关键词 → 产品解答专家;若分类仍想回收集 / 确认 / 推荐 → 也倾向转化,避免推完又绕回推销。awaiting_info_confirm 或上一节点是确认 → 强制回 信息确认。信息确认;上一节点还在收集 → 继续 信息收集;否则才允许 产品推荐。一句话:LLM 可以猜错下一跳,router 会把人拦回正确阶段。
information_router:收集齐了先确认,否则本轮结束已确认或已推荐 → FINISH(END)
isRecommend 且四项齐 → 信息确认
否则 → FINISH(等用户下一轮继续填)
收集节点可以多轮说话,但齐了也不直连推荐,必须过确认门。
confirm_router:确认位为真才进推荐isInfoConfirmed → 产品推荐
否则 → FINISH
这就是「确认闸门」的最后一扇门:摘要发出去、用户还没说确认时,图直接结束本轮;下一轮分类 / decide 会因 awaiting_info_confirm 再次落到确认节点。
推荐发生在确认之后——双库并行、TopN 合并去重是推荐子图的事,本文不展开,排期另文。
实现见 sale_app/core/agent/information_confirm.py。
从收集刚进入(pre_node == 信息收集 且尚未 awaiting):调摘要链,写出汇总,并设:
awaiting_info_confirm=TrueisInfoConfirmed=Falseinfo_summary=...(末尾提示回复「确认」或说明修改)用户回复阶段:
| 判定 | 关键词示例 | 状态回写 |
|---|---|---|
| 确认 | 确认、没问题、正确、对的、可以、是的、好的、ok、yes | isInfoConfirmed=True,awaiting_info_confirm=False |
| 修改 | 改、修改、不对、错了、更正、补充、换成、应该是 | 重生成摘要,继续 awaiting |
| 其它 | — | 提示再确认,闸门仍关 |
这里要说清楚权衡:确认是半规则关键词,不是完整 NLU。优点是稳、快、可测;缺点是「嗯可以吧再看看」这类软同意可能判不准,复杂纠错也依赖摘要 LLM。对带宽场景,「明确确认才推」比「模型觉得可以就推」更安全,所以仓库选择了关键词闸门。
确认通过后,confirm_router 放行到 产品推荐 节点;该节点挂的是 build_recommend_graph(llm).compile() 子图——主图只负责「何时能进」,子图负责「怎么推」。
公开代码里流式能力是齐的:
POST /api/ch@t/stream(app/routers/[email protected])→ StreamingResponse,事件 meta / 业务 item / done / error。astream_flow(flow_graph.py)基于 chain.astream_events(..., version="v2")。可见性策略:
| 集合 | 节点 | 行为 |
|---|---|---|
STREAM_TOKEN_NODES | 闲聊经理、产品解答专家、产品推荐 | on_ch@t_model_stream 逐 token |
STRUCTURED_REPLY_NODES | 信息收集、信息确认、客户转化 | 节点结束时整段推 token |
TRACKED_WORKFLOW_NODES | 含修复、分类及业务节点 | step_start / step_end / progress |
_is_graph_node_chain_event 还会过滤:事件名必须等于 langgraph_node、非 hidden、非子图内部任务——聊天页「执行过程面板」才不会被内部 Runnable 刷屏。
| 状态 | 内容 | 公开证据 |
|---|---|---|
| 已实现 | 主图节点与边;FIX→CLASSIFY;五类分类;三 router;确认闸门;意图→收集;SQLite Checkpoint;SSE step / token | flow_graph.py、flow_routers.py、information_confirm.py、app/routers/[email protected];README 更新日志 2026-09-04 |
| 半成品 / 权衡 | 确认靠关键词非 NLU;分类依赖 LLM JSON + 启发式兜底;推荐后路由大量短语规则(转化 / QA) | information_confirm.py 关键词表;classify_utils.py 的 PRODUCT_QA_KEYWORDS / CONVERSION_PHRASES |
| 未来 / 另文 | 双库 TopN 并行检索、合并去重深挖(排期约 9/26);Milvus 稠密 + SPLADE 混合检索细节 | README「推荐系统」节;本文只点到确认后进推荐子图 |
不要把「仓库里已有推荐子图」理解成「本文已经讲透双库」——主图闸门和推荐检索是两层问题。
环境变量至少配好 LLM_API_KEY(或 ZHIPU_API_KEY),可参考 README。
Docker(推荐):
cp .env.example .env
docker compose up -d --build
# 聊天:http://127.0.0.1:8182/api/ch@t
# 健康:http://127.0.0.1:8182/health
本地 uvicorn:
pip install -r requirements.txt
uvicorn app.main:app --reload --host 127.0.0.1 --port 8000
建议在聊天页走通一条闭环(不必盯双库细节):
若升级 LangGraph 1.x 后状态怪异,README 建议清理旧 checkpoint:storage/memory_file/ch@t_history.db。
销售客服主图的核心不是「分类模型有多聪明」,而是:
FlowState 把闸门做成布尔位与摘要,多轮靠 SQLite Checkpoint 续上。decide_router / information_router / confirm_router。下一篇会专门拆 双库并行 TopN 推荐:recommend_product 与默认 KB 如何并行召回、合并去重——那是确认闸门打开之后的故事。
项目地址(欢迎 Star / Issue):
github.com/liuyanqun08…
相关专栏:大模型学习记录