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_nodeworkflow.py)调用 agents/guide.pyrun_guide_agent,用 内层 StateGraph 五节点 + 阶段 Prompt 模板 做渐进挖掘;外层 route_after_guide 再决定何时进入 resume_parser_node。前端 SSE 多轮对话则走 workflow.pyrun_guide_chat()agents/guide.pyrun_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
2
3
4
5
6
7
flowchart LR
W[welcome] --> A[assess_need]
A --> C[collect_basic_info]
C --> D[dig_deeper]
D --> S[check_sufficiency]
S -->|不足| D
S -->|充分或 loop>=8| H[handoff END]

Guide 内层五节点

System Prompt(llm/prompts.pyGUIDE_SYSTEM_PROMPT)定义角色「小C」、对话策略(渐进挖掘、情绪识别、每次 1–2 问、200 字以内),各节点再注入 GUIDE_STAGE_TEMPLATES 里对应阶段的 user 指令。内层图由 agents/guide.pycreate_guide_graph() 编译,run_guide_agent() 为统一入口。


3. 阶段模板示例

模板集中在 llm/prompts.py,节点函数只负责拼 messages 并 invoke_llm

1
2
3
4
5
6
7
8
9
10
11
12
GUIDE_STAGE_TEMPLATES = {
"greeting": (
"用户刚刚开始对话。请热情地欢迎用户,简要介绍你能提供的职业规划服务,"
"并用一个轻松的开放性问题了解用户的来意。"
),
"dig_deeper": (
"基础信息已经收集得差不多了。现在请深入挖掘以下维度:\n"
"1. 工作偏好:独立 vs 团队?稳定 vs 挑战?\n"
"2. 成就感来源、职业价值观、性格特点、困惑根源、期望目标\n"
"可以结合之前收集的信息,提出有针对性的深度问题。"
),
}

welcome 节点典型调用(agents/guide.py):

1
2
3
4
5
6
messages = [
{"role": "system", "content": GUIDE_SYSTEM_PROMPT},
{"role": "user", "content": GUIDE_STAGE_TEMPLATES["greeting"]},
]
reply = await invoke_llm(get_chat_model(), messages) # llm/providers.py
return {"messages": [reply], "current_stage": "assess_need"}

collect_basic_info / dig_deeper 会把用户近几条发言追加到 collected_info["collected_raw"],供后续 workflow.pyresume_parser_node 拼接解析。


4. 信息充分性:LLM 判断 + 关键词兜底

check_sufficiencyagents/guide.py)不是 Prompt 模板阶段,而是独立节点:

  1. 拼接用户历史文本,用关键词表(「年」「行业」「岗位」「技能」等)做粗筛。
  2. 另起一轮短对话,System 用独立助手 Prompt(非 GUIDE_SYSTEM_PROMPT),让 LLM 只回答 sufficient / insufficient
1
2
3
4
5
6
7
8
9
optional_keywords = ["年", "行业", "岗位", "职位", "技能", "经验", "公司", "专业", "学历", "方向", "期望", "困惑"]
all_user_text = " ".join(msg["content"] for msg in conversation_history if msg.get("role") == "user")
found_keywords = [kw for kw in optional_keywords if kw in all_user_text]

sufficiency_prompt = (
f"已收集信息:{collected_info}\n用户对话:{all_user_text[:1000]}\n"
f"发现的关键词:{found_keywords}\n请只回答 'sufficient' 或 'insufficient'。"
)
is_sufficient = "sufficient" in (await invoke_llm(model, messages)).lower()

条件路由 should_continue

  • is_info_sufficienthandoff(子图 END)
  • 否则 loop_count >= 8 → 强制 handoff
  • 否则回到 dig_deeper

这与外层 workflow.pyroute_after_guide 不同:外层读 needs_more_info,且 user_msg_count >= 3 也会强制进入 resume_parser_node,防止用户永远卡在 Guide。

1
2
3
4
5
6
# workflow.py — route_after_guide 节选
if not state.get("needs_more_info", True):
return "resume_parser_node"
if user_msg_count >= 3:
return "resume_parser_node"
return "guide_node"

信息充分后,api/routes/chat.py 调用 run_analysis_pipeline() 触发后续四 Agent,不再跑完整 Guide 子图。


5. 单轮模式 vs 子图模式

前端多轮聊天时,常走 run_guide_single_turnagents/guide.py):每次用户发一条消息,System + history + user 调 LLM,不用 welcome→… 链。充分性用关键词计数启发式(found_keywords >= 6>=4 且文本 >=50 字),与子图里 LLM 判停不同:

1
2
3
4
5
6
# agents/guide.py — run_guide_single_turn 充分性判断
is_sufficient = (
(len(found_keywords) >= 6) or
(len(found_keywords) >= 4 and len(all_user_text) >= 50)
)
collected_info = {"collected_raw": all_user_text}

首次冷启动或 CLI 仍可 ainvoke(create_guide_graph()) 跑完整五节点。

选型依据:

模式 适用 代价
子图 首条消息、需要自动阶段推进 多次 LLM 调用
单轮 已有多轮 history 的 SSE 对话 阶段感弱,靠 System Prompt 自律

6. Prompt 写作要点(复盘)

  1. 阶段指令放 user 消息,System 只放稳定人格与规则,方便换阶段而不改人格。
  2. 禁止一次问太多:System 里写死「每次最多 1–2 个问题」。
  3. 负面情绪先共情:写进 System,减少模型急于收集字段。
  4. 充分性独立于生成回复:避免模型在闲聊回复里自己说「信息够了」但 state 未更新。

7. 踩坑

  • 阶段名不一致:System Prompt 文本里写 confirm,图里实际是 check_sufficiency — 维护时以 agents/guide.py 节点为准。
  • loop 双计数:子图 should_continuelen(messages)loop_count(上限 8);外层 route_after_guideuser_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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# ========== GUIDE_SYSTEM_PROMPT 等(节选) ==========
# 源文件: llm/prompts.py 行 1-80

# L2: 【文档】文件说明:Prompt 模板集中管理模块
# L3: 【文档】业务说明:集中管理 iCan 项目所有 Agent 的 Prompt 模板,包括:
# L4: 【文档】- GuideAgent(对话引导):多阶段对话策略,渐进式挖掘用户信息
# L5: 【文档】- ResumeParserAgent(履历解析):从用户文本中提取结构化信息
# L6: 【文档】- ProfileAnalyzerAgent(个人分析):基于履历进行五维度深度分析
# L7: 【文档】- CareerMatcherAgent(职业匹配):基于个人画像进行三级职业匹配
# L8: 【文档】- ReporterAgent(报告输出):将分析结果整合为结构化报告文本
# L9: 【文档】所有 Prompt 遵循 PRD 第九章 Prompt 设计要点:角色设定、任务描述、
# L10: 【文档】输出格式、约束条件、示例引导。
# L11: 【文档】数据流向:本模块被各 Agent 导入使用 -> 填充变量 -> 发送给 LLM
# (L1-12 为函数/模块文档字符串,已转为注释便于阅读)

# L14: ==========================================================================
# L15: GuideAgent 对话引导 Prompt
# L16: ==========================================================================

# L18: 赋值:更新局部变量或 state 字段
GUIDE_SYSTEM_PROMPT = """你是一位拥有20年经验的高级职业规划师,名叫"小C"。你同时具备心理咨询师和职业顾问的双重素养,擅长通过自然对话深入了解一个人的职业发展需求。

# L20: 你的核心能力
# L21: 执行该语句(细节见上文业务描述)
1. **职业规划专业能力**:精通各行业职业发展路径、市场趋势、岗位要求
# L22: 执行该语句(细节见上文业务描述)
2. **心理咨询能力**:善于倾听、共情,能识别用户的情绪状态和深层需求
# L23: 执行该语句(细节见上文业务描述)
3. **信息挖掘能力**:通过渐进式提问,系统性地收集用户的关键信息
# L24: 执行该语句(细节见上文业务描述)
4. **分析判断能力**:基于收集的信息,快速形成对用户的初步画像

# L26: 对话策略
# L27: 执行该语句(细节见上文业务描述)
- **渐进式挖掘**:从开放式问题开始,逐步聚焦到具体细节,避免一上来就问太多结构化问题
# L28: 执行该语句(细节见上文业务描述)
- **情绪识别**:关注用户的情绪变化,适时给予鼓励和认可,建立信任感
# L29: 执行该语句(细节见上文业务描述)
- **灵活应对**:根据用户的回答灵活调整对话方向,不要机械地按照固定流程走
# L30: 执行该语句(细节见上文业务描述)
- **信息补全**:在自然对话中巧妙地引导用户提供缺失的关键信息
# L31: 执行该语句(细节见上文业务描述)
- **总结确认**:在关键节点主动总结已收集的信息,确保理解准确

# L33: 对话阶段
# L34: 执行该语句(细节见上文业务描述)
你会根据对话进展自动判断当前所处的阶段:
# L35: 执行该语句(细节见上文业务描述)
1. **greeting(开场问候)**:热情欢迎用户,简要介绍服务,了解用户的基本来意
# L36: 执行该语句(细节见上文业务描述)
2. **assess_need(需求评估)**:判断用户的核心需求是职业规划、转型咨询还是简历优化等
# L37: 执行该语句(细节见上文业务描述)
3. **collect_basic_info(基础信息收集)**:了解用户的教育背景、工作年限、当前岗位等基础信息
# L38: 执行该语句(细节见上文业务描述)
4. **dig_deeper(深度挖掘)**:深入了解用户的核心技能、职业成就、工作偏好、价值观等
# L39: 执行该语句(细节见上文业务描述)
5. **confirm(确认总结)**:总结所有收集到的信息,与用户确认后准备生成分析报告

# L41: 重要规则
# L42: 执行该语句(细节见上文业务描述)
- 始终使用中文回复
# L43: 执行该语句(细节见上文业务描述)
- 语气亲切专业,像一位经验丰富的朋友
# L44: 执行该语句(细节见上文业务描述)
- 每次回复控制在200字以内,保持对话节奏
# L45: 执行该语句(细节见上文业务描述)
- 不要一次性问太多问题,每次最多提1-2个问题
# L46: 执行该语句(细节见上文业务描述)
- 如果用户回答模糊,可以用具体场景来引导
# L47: 执行该语句(细节见上文业务描述)
- 如果用户表达了负面情绪,先给予情感支持再继续引导
# L48: 执行该语句(细节见上文业务描述)
- 当收集到足够信息后,主动提议进入分析阶段
# L51: 【文档】GUIDE_STAGE_TEMPLATES = {
# L52: 【文档】"greeting": (
# L53: 【文档】"用户刚刚开始对话。请热情地欢迎用户,简要介绍你能提供的职业规划服务,"
# L54: 【文档】"并用一个轻松的开放性问题了解用户的来意。\n\n"
# L55: 【文档】"示例开场白:你好!我是小C,你的专属职业规划顾问。我拥有20年的职业规划经验,"
# L56: 【文档】"帮助过上千位职场人找到自己的方向。今天想聊聊什么?是遇到了职业瓶颈,"
# L57: 【文档】"还是在考虑新的发展方向?"
# L58: 【文档】),
# L59: 【文档】"assess_need": (
# L60: 【文档】"用户已经开始了对话。请通过1-2个精准的问题,判断用户的核心需求类别:\n"
# L61: 【文档】"- 职业规划:需要明确职业方向或制定发展计划\n"
# L62: 【文档】"- 职业转型:想转换行业或岗位\n"
# L63: 【文档】"- 简历优化:需要优化求职材料\n"
# L64: 【文档】"- 技能提升:想了解需要补充哪些能力\n"
# L65: 【文档】"- 职场困惑:遇到具体的工作困境\n\n"
# L66: 【文档】"根据用户的回答,自然地引导对话深入。不要直接问'你需要什么服务',"
# L67: 【文档】"而是通过对话内容来推断。"
# L68: 【文档】),
# L69: 【文档】"collect_basic_info": (
# L70: 【文档】"已经了解了用户的基本需求。现在请在自然对话中收集以下基础信息(不要一次性全问):\n"
# L71: 【文档】"1. 教育背景(学历、专业、毕业院校)\n"
# L72: 【文档】"2. 工作年限和行业经验\n"
# L73: 【文档】"3. 当前/最近的岗位和职责\n"
# L74: 【文档】"4. 核心技能和技术栈\n"
# L75: 【文档】"5. 重要的职业成就或项目经历\n\n"
# L76: 【文档】"注意:要像聊天一样自然地获取这些信息,而不是像面试一样逐条询问。"
# L77: 【文档】"可以先从用户最愿意谈论的话题切入。"
# L78: 【文档】),
# L79: 【文档】"dig_deeper": (
# L80: 【文档】"基础信息已经收集得差不多了。现在请深入挖掘以下维度:\n"

welcome 节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# ========== welcome 节点 ==========
# 源文件: agents/guide.py 行 21-66

# L21: 异步函数 welcome:可被 await,适合 IO 型 LLM/DB 调用
async def welcome(state: GuideState) -> dict:
# L23: 【文档】欢迎节点:生成欢迎语和初始引导。
# L25: 【文档】功能描述:
# L26: 【文档】根据系统 Prompt 中的 greeting 阶段模板,调用 LLM 生成
# L27: 【文档】热情的欢迎语和开放性引导问题,开启与用户的对话。
# L29: 【文档】入参说明:
# L30: 【文档】state (GuideState): 对话引导状态对象,包含对话历史、当前阶段等信息。
# L32: 【文档】出参说明:
# L33: 【文档】dict: 状态更新字典,包含 messages 更新和 current_stage 更新。
# (L22-34 为函数/模块文档字符串,已转为注释便于阅读)
# L35: 开始 try 块,后续 except 负责兜底
try:
# L36: 记录日志,便于线上排查节点入参/出参
logger.info("[welcome] 开始执行,入参: state=%s", {k: str(v)[:100] for k, v in state.items()})
# L37: 多轮对话列表,元素为 {role, content}
conversation_history = state.get("conversation_history", [])
# L38: 赋值:更新局部变量或 state 字段
stage_template = GUIDE_STAGE_TEMPLATES.get("greeting", "")

# L40: 赋值:更新局部变量或 state 字段
messages = [
# L41: 执行该语句(细节见上文业务描述)
{"role": "system", "content": GUIDE_SYSTEM_PROMPT},
# L42: 执行该语句(细节见上文业务描述)
{"role": "user", "content": stage_template},
# L43: 执行该语句(细节见上文业务描述)
]
# L44: 如果有历史对话,附加到消息列表
# L45: 多轮对话列表,元素为 {role, content}
for msg in conversation_history:
# L46: 执行该语句(细节见上文业务描述)
messages.append(msg)

# L48: 记录日志,便于线上排查节点入参/出参
logger.info("[welcome] 调用 LLM 生成欢迎语,消息数量: %d", len(messages))
# L49: 获取对话大模型实例(配置来自 settings.LLM_MODEL_CHAT)
model = get_chat_model()
# L50: 调用 LLM 返回纯文本,带 60s 超时与 Qwen3 /no_think 注入
reply = await invoke_llm(model, messages)

# L52: 记录日志,便于线上排查节点入参/出参
logger.info("[welcome] LLM 回复长度: %d", len(reply) if reply else 0)
# L53: 赋值:更新局部变量或 state 字段
result = {
# L54: 执行该语句(细节见上文业务描述)
"messages": [reply],
# L55: 执行该语句(细节见上文业务描述)
"current_stage": "assess_need",
# L56: 执行该语句(细节见上文业务描述)
}
# L57: 记录日志,便于线上排查节点入参/出参
logger.info("[welcome] 执行完成,出参: current_stage=assess_need, 回复长度=%d", len(reply) if reply else 0)
# L58: 返回本节点要合并进 state 的字段(LangGraph 会 merge)
return result

# L60: 捕获异常,避免整图/整请求崩溃
except Exception as e:
# L61: 记录日志,便于线上排查节点入参/出参
logger.error("[welcome] 欢迎节点执行异常: %s", e, exc_info=True)
# L62: 返回本节点要合并进 state 的字段(LangGraph 会 merge)
return {
# L63: 执行该语句(细节见上文业务描述)
"messages": ["你好!我是小C,你的专属职业规划顾问。今天想聊聊什么?"],
# L64: 执行该语句(细节见上文业务描述)
"current_stage": "assess_need",
# L65: 执行该语句(细节见上文业务描述)
}

check_sufficiency 节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# ========== check_sufficiency 节点 ==========
# 源文件: agents/guide.py 行 251-334

# L251: 异步函数 check_sufficiency:可被 await,适合 IO 型 LLM/DB 调用
async def check_sufficiency(state: GuideState) -> dict:
# L253: 【文档】信息充分性检查节点:判断是否收集到足够信息。
# L255: 【文档】功能描述:
# L256: 【文档】综合评估已收集的用户信息,判断是否足够进行下一步的
# L257: 【文档】履历解析和个人画像分析。检查关键字段是否齐全,
# L258: 【文档】并设置 is_info_sufficient 标志。
# L260: 【文档】入参说明:
# L261: 【文档】state (GuideState): 对话引导状态对象,包含已收集信息和缺失字段。
# L263: 【文档】出参说明:
# L264: 【文档】dict: 状态更新字典,设置 is_info_sufficient 和 missing_fields。
# (L252-265 为函数/模块文档字符串,已转为注释便于阅读)
# L266: 开始 try 块,后续 except 负责兜底
try:
# L267: 记录日志,便于线上排查节点入参/出参
logger.info("[check_sufficiency] 开始执行,入参: state=%s", {k: str(v)[:100] for k, v in state.items()})
# L268: 赋值:更新局部变量或 state 字段
collected_info = state.get("collected_info", {})
# L269: 多轮对话列表,元素为 {role, content}
conversation_history = state.get("conversation_history", [])

# L271: 定义关键信息字段
# L272: 赋值:更新局部变量或 state 字段
required_keys = ["collected_raw"]
# L273: 赋值:更新局部变量或 state 字段
optional_keywords = [
# L274: 执行该语句(细节见上文业务描述)
"年", "行业", "岗位", "职位", "技能", "经验",
# L275: 执行该语句(细节见上文业务描述)
"公司", "专业", "学历", "方向", "期望", "困惑",
# L276: 执行该语句(细节见上文业务描述)
]

# L278: 检查对话历史中是否包含关键信息
# L279: 赋值:更新局部变量或 state 字段
all_user_text = " ".join(
# L280: 多轮对话列表,元素为 {role, content}
msg.get("content", "") for msg in conversation_history if msg.get("role") == "user"
# L281: 执行该语句(细节见上文业务描述)
)
# L282: 赋值:更新局部变量或 state 字段
found_keywords = [kw for kw in optional_keywords if kw in all_user_text]

# L284: 使用 LLM 做更精确的充分性判断
# L285: 赋值:更新局部变量或 state 字段
sufficiency_prompt = (
# L286: 执行该语句(细节见上文业务描述)
f"基于以下已收集的用户信息和对话内容,判断是否已经收集到足够的信息来进行职业分析。\n"
# L287: 执行该语句(细节见上文业务描述)
f"已收集信息:{collected_info}\n"
# L288: 执行该语句(细节见上文业务描述)
f"用户对话文本:{all_user_text[:1000]}\n"
# L289: 执行该语句(细节见上文业务描述)
f"发现的关键词:{found_keywords}\n\n"
# L290: 执行该语句(细节见上文业务描述)
f"请只回答 'sufficient' 或 'insufficient',并简要说明理由。"
# L291: 执行该语句(细节见上文业务描述)
)

# L293: 赋值:更新局部变量或 state 字段
messages = [
# L294: 执行该语句(细节见上文业务描述)
{"role": "system", "content": "你是一个信息完整性评估助手,只根据给定的信息判断是否足够。"},
# L295: 执行该语句(细节见上文业务描述)
{"role": "user", "content": sufficiency_prompt},
# L296: 执行该语句(细节见上文业务描述)
]

# L298: 记录日志,便于线上排查节点入参/出参
logger.info("[check_sufficiency] 调用 LLM 进行信息充分性检查")
# L299: 获取对话大模型实例(配置来自 settings.LLM_MODEL_CHAT)
model = get_chat_model()
# L300: 调用 LLM 返回纯文本,带 60s 超时与 Qwen3 /no_think 注入
reply = await invoke_llm(model, messages)

# L302: 赋值:更新局部变量或 state 字段
is_sufficient = "sufficient" in reply.lower()
# L303: 赋值:更新局部变量或 state 字段
missing = []
# L304: 条件分支
if not is_sufficient:
# L305: 计算缺失的信息维度
# L306: 条件分支
if not any(kw in all_user_text for kw in ["年", "经验"]):
# L307: 执行该语句(细节见上文业务描述)
missing.append("工作年限/经验")
# L308: 条件分支
if not any(kw in all_user_text for kw in ["行业", "公司"]):
# L309: 执行该语句(细节见上文业务描述)
missing.append("行业信息")
# L310: 条件分支
if not any(kw in all_user_text for kw in ["岗位", "职位"]):
# L311: 执行该语句(细节见上文业务描述)
missing.append("当前岗位")
# L312: 条件分支
if not any(kw in all_user_text for kw in ["技能", "技术"]):
# L313: 执行该语句(细节见上文业务描述)
missing.append("核心技能")

# L315: 记录日志,便于线上排查节点入参/出参
logger.info(
# L316: 赋值:更新局部变量或 state 字段
"[check_sufficiency] 充分性检查结果: is_sufficient=%s, 缺失字段=%s, LLM回复长度=%d",
# L317: 执行该语句(细节见上文业务描述)
is_sufficient, missing, len(reply) if reply else 0,
# L318: 执行该语句(细节见上文业务描述)
)

# L320: 赋值:更新局部变量或 state 字段
result = {
# L321: Guide 判定用户信息是否足够进入分析阶段
"is_info_sufficient": is_sufficient,
# L322: 执行该语句(细节见上文业务描述)
"missing_fields": missing,
# L323: 执行该语句(细节见上文业务描述)
}
# L324: Guide 判定用户信息是否足够进入分析阶段
logger.info("[check_sufficiency] 执行完成,出参: is_info_sufficient=%s, missing_fields=%s", is_sufficient, missing)
# L325: 返回本节点要合并进 state 的字段(LangGraph 会 merge)
return result

# L327: 捕获异常,避免整图/整请求崩溃
except Exception as e:
# L328: 记录日志,便于线上排查节点入参/出参
logger.error("[check_sufficiency] 信息充分性检查节点执行异常: %s", e, exc_info=True)
# L329: 异常时默认信息不足,继续对话
# L330: 返回本节点要合并进 state 的字段(LangGraph 会 merge)
return {
# L331: Guide 判定用户信息是否足够进入分析阶段
"is_info_sufficient": False,
# L332: 执行该语句(细节见上文业务描述)
"missing_fields": ["检查过程异常,需要重新评估"],
# L333: 执行该语句(细节见上文业务描述)
}

系列导航

主题
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 · 中间件 · 配置

← 返回 iCan 专题