0. 系列闭环(不公开源码也能跟读)
端到端链路:Vue 前端 → api/routes/chat.py → Guide 多轮 SSE → run_analysis_pipeline(解析→分析→匹配→报告)→ tools/pdf_exporter PDF。
本篇:第 14/17 篇 · Prompt 环 · Guide 对话
| 阶段 | 用户可见 | 代码入口 | 对应篇 |
|---|---|---|---|
| 建会话 | 欢迎语 | POST /api/sessions | 09 |
| 多轮对话 | SSE 流式 | chat/stream → run_guide_single_turn | 06, 14 |
| 信息充分 | 开始分析 | _run_analysis_background | 05, 07 |
| 履历解析 | 进度 30% | run_resume_parser | 12 |
| 画像/RIASEC | 进度 50% | run_profile_analyzer | 03, 13 |
| 职业匹配 | 进度 70% | run_career_matcher | 02 |
| 报告 | 进度 90% | run_reporter | 11 |
| 下载 PDF | 文件 | GET …/report/pdf | 11, 15 |
| 说明 | |
|---|---|
| 读本篇前 | 第 06 篇子图节点名 |
| 读完本篇 | 把 GUIDE_STAGE_TEMPLATES 五阶段与节点一一对应 |
| 下一环 | 第 09 篇:SSE 多轮对话(第 15 篇) |
全系列闭环索引:SERIES-LOOP.md
1. 要解决什么问题
职业规划对话如果一上来就列清单问学历、年限、技能,用户会有「被面试」的感觉;如果完全自由闲聊,又容易在信息不足时进入分析流水线,导致 ResumeParser 和 RIASEC 输出空洞。
iCan 的 guide_node(workflow.py)调用 agents/guide.py 的 run_guide_agent,用 内层 StateGraph 五节点 + 阶段 Prompt 模板 做渐进挖掘;外层 route_after_guide 再决定何时进入 resume_parser_node。前端 SSE 多轮对话则走 workflow.py 的 run_guide_chat() → agents/guide.py 的 run_guide_single_turn(),入口在 api/routes/chat.py。
2. 内层五节点(与代码一致)
实现里的节点名与 Prompt 阶段对应关系:
| 图节点 | Prompt 阶段 key | 作用 |
|---|---|---|
welcome |
greeting |
欢迎语、了解来意 |
assess_need |
assess_need |
判断规划/转型/技能等诉求 |
collect_basic_info |
collect_basic_info |
学历、年限、岗位、技能 |
dig_deeper |
dig_deeper |
偏好、价值观、成就来源 |
check_sufficiency |
— | LLM + 关键词判断能否 handoff |
1 | |
System Prompt(llm/prompts.py 的 GUIDE_SYSTEM_PROMPT)定义角色「小C」、对话策略(渐进挖掘、情绪识别、每次 1–2 问、200 字以内),各节点再注入 GUIDE_STAGE_TEMPLATES 里对应阶段的 user 指令。内层图由 agents/guide.py 的 create_guide_graph() 编译,run_guide_agent() 为统一入口。
3. 阶段模板示例
模板集中在 llm/prompts.py,节点函数只负责拼 messages 并 invoke_llm:
1 | |
welcome 节点典型调用(agents/guide.py):
1 | |
collect_basic_info / dig_deeper 会把用户近几条发言追加到 collected_info["collected_raw"],供后续 workflow.py 的 resume_parser_node 拼接解析。
4. 信息充分性:LLM 判断 + 关键词兜底
check_sufficiency(agents/guide.py)不是 Prompt 模板阶段,而是独立节点:
- 拼接用户历史文本,用关键词表(「年」「行业」「岗位」「技能」等)做粗筛。
- 另起一轮短对话,System 用独立助手 Prompt(非
GUIDE_SYSTEM_PROMPT),让 LLM 只回答sufficient/insufficient。
1 | |
条件路由 should_continue:
is_info_sufficient→handoff(子图 END)- 否则
loop_count >= 8→ 强制handoff - 否则回到
dig_deeper
这与外层 workflow.py 的 route_after_guide 不同:外层读 needs_more_info,且 user_msg_count >= 3 也会强制进入 resume_parser_node,防止用户永远卡在 Guide。
1 | |
信息充分后,api/routes/chat.py 调用 run_analysis_pipeline() 触发后续四 Agent,不再跑完整 Guide 子图。
5. 单轮模式 vs 子图模式
前端多轮聊天时,常走 run_guide_single_turn(agents/guide.py):每次用户发一条消息,System + history + user 调 LLM,不用 welcome→… 链。充分性用关键词计数启发式(found_keywords >= 6 或 >=4 且文本 >=50 字),与子图里 LLM 判停不同:
1 | |
首次冷启动或 CLI 仍可 ainvoke(create_guide_graph()) 跑完整五节点。
选型依据:
| 模式 | 适用 | 代价 |
|---|---|---|
| 子图 | 首条消息、需要自动阶段推进 | 多次 LLM 调用 |
| 单轮 | 已有多轮 history 的 SSE 对话 | 阶段感弱,靠 System Prompt 自律 |
6. Prompt 写作要点(复盘)
- 阶段指令放 user 消息,System 只放稳定人格与规则,方便换阶段而不改人格。
- 禁止一次问太多:System 里写死「每次最多 1–2 个问题」。
- 负面情绪先共情:写进 System,减少模型急于收集字段。
- 充分性独立于生成回复:避免模型在闲聊回复里自己说「信息够了」但 state 未更新。
7. 踩坑
- 阶段名不一致:System Prompt 文本里写
confirm,图里实际是check_sufficiency— 维护时以agents/guide.py节点为准。 - loop 双计数:子图
should_continue用len(messages)作loop_count(上限 8);外层route_after_guide用user_msg_count >= 3。测试时要分别打日志。 - light model 未用于 Guide:Guide 全程
get_chat_model()(llm/providers.py),别在文档里写成 light model。 - 单轮 vs 子图判停标准不同:SSE 路径用关键词启发式,子图用 LLM
sufficient/insufficient;同一用户可能在两种模式下「充分性」结论不一致。 collected_raw拼接方式:collect_basic_info/dig_deeper只取近 3 条 user 消息追加,超长历史可能丢早期关键信息——依赖conversation_history全量在 Parser 阶段补回。
8. 小结
Guide 的 Prompt 策略 = GUIDE_SYSTEM_PROMPT 定调 + 分阶段模板 + check_sufficiency 结构化判停 + should_continue 防死循环。
下一篇:Docker 部署踩坑 — PyTorch CPU、中文字体与前端构建。
附录:关键源码(逐行注释)
以下代码摘自 iCan 实现,每行上方均有中文注释,不公开仓库也可跟读。
生成命令:python3 bin/build-ican-annotated-snippets.py
GUIDE_SYSTEM_PROMPT 等(节选)
1 | |
welcome 节点
1 | |
check_sufficiency 节点
1 | |
系列导航
| 篇 | 主题 |
|---|---|
| 1 | 系统全景 |
| 2 | 五 Agent 协作 |
| 3 | 霍兰德 RIASEC |
| 4–7 | 状态 · 路由 · 嵌套 · 容错 |
| 8–11 | LLM 层 · SSE/WS · DB 迁移 · PDF |
| 12–14 | JSON Prompt · RIASEC Prompt · 14 Guide Prompt(本篇) |
| 15–17 | Docker · 中间件 · 配置 |