0. 系列闭环(不公开源码也能跟读)
端到端链路:Vue 前端 → api/routes/chat.py → Guide 多轮 SSE → run_analysis_pipeline(解析→分析→匹配→报告)→ tools/pdf_exporter PDF。
本篇:第 1/17 篇 · 总览 · 17 篇地图
| 阶段 | 用户可见 | 代码入口 | 对应篇 |
|---|---|---|---|
| 建会话 | 欢迎语 | 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 |
| 说明 | |
|---|---|
| 读本篇前 | 无(建议从此篇开始) |
| 读完本篇 | 说出 FastAPI 接入层、LangGraph 五节点、两条执行路径(HTTP vs CLI) |
| 下一环 | 第 02 篇:五个 node 函数如何串起来(第 2 篇) |
全系列闭环索引:SERIES-LOOP.md
1. 要解决什么问题
常见职业规划产品有两类极端:
- 纯问卷:霍兰德 120 题,填完得一个代码,缺少对你履历与困惑的上下文。
- 纯聊天:一个大 Prompt 包打天下,解析、测评、匹配、写报告挤在一次调用里,输出不稳定。
iCan 的做法是:多轮对话采集上下文 → 结构化画像 → 分 Agent 做分析与匹配 → 生成可下载 PDF 报告。
后端用 FastAPI(main.py 挂载 api/routes/chat.py 等路由)提供 API 与 SSE/WebSocket,编排用 LangGraph StateGraph(workflow.py 的 create_workflow())把 5 段流水线串起来。
下文说明架构与选型;关键逻辑会贴代码片段便于对照理解。
2. 技术选型(为什么不是「一个超级 Prompt」)
| 层次 | 选型 | 在本项目里的原因 |
|---|---|---|
| Web | FastAPI | LLM 调用是 IO 密集;异步路由 + SSE 流式回复体验更好 |
| 编排 | LangGraph | Guide 要循环追问,后面四段要线性执行;图结构比 LCEL 链好表达 |
| LLM | ChatOpenAI 兼容 API | 同一套 invoke_llm;换 DeepSeek / Ollama 只改 .env 的 base_url 与 model |
| 报告 | ReportLab + matplotlib | 中文 PDF、雷达图/柱状图可控,不依赖浏览器打印 |
| 前端 | Vue 3 + Vite | 对话页 + 报告生成进度 + PDF 下载 |
没有选 CrewAI / AutoGen 的原因:当前流程是固定 DAG(Guide 条件环 + 四段顺序),LangGraph 的 add_conditional_edges 足够,且状态 TypedDict 清晰。
3. 系统架构
3.1 总览(draw.io)
3.2 数据流(Mermaid)
1 | |
顶层状态类型:iCanWorkflowState(core/state.py)。Guide 另有内层 GuideState 与子图 create_guide_graph(),第 6 篇专讲嵌套。
4. 顶层工作流代码(核心 30 行)
实现位置:workflow.py → create_workflow()。
1 | |
route_after_guide 读 Guide 返回的 needs_more_info 等字段,决定继续对话还是进入解析链路(配合循环上限,见第 07 篇)。
5. 五个 Agent 分工
| 节点 | 模块 | 输入 → 输出 | 说明 |
|---|---|---|---|
guide_node |
agents/guide.py |
对话历史 → collected_info、是否充分 |
内层 5 步:welcome → … → check_sufficiency |
resume_parser_node |
agents/resume_parser.py |
原始文本 → 结构化 JSON 画像 | 使用 get_light_model() + JSON 降级解析 |
profile_analyzer_node |
agents/profile_analyzer.py |
画像 → 能力/性格/价值观 + RIASEC | 多节点子图,第 3 篇讲霍兰德打分 |
career_matcher_node |
agents/career_matcher.py |
画像 → 三级路径推荐 | 纵向 / 横向 / 转型三档 |
reporter_node |
agents/reporter.py |
分析结果 → Markdown 报告 | 再经 tools/pdf_exporter.py 出 PDF |
注意:实现里是 async 函数 + 子 StateGraph,不是五个 Python 类。
6. LLM 怎么接(别写死「只用 DeepSeek」)
llm/providers.py 统一封装:
get_chat_model()/get_light_model()→ChatOpenAI(api_key, base_url, model=...)invoke_llm/invoke_llm_with_json→ 普通文本与 JSON 模式
配置来自环境变量(config.py + pydantic-settings):
| 场景 | 典型配置 |
|---|---|
本地开发 .env |
LLM_MODEL_CHAT=deepseek-v4-flash,LLM_BASE_URL=https://api.deepseek.com/v1 |
| Docker + Ollama | qwen3.5:9b + http://host.docker.internal:11434/v1(Qwen3 需 /no_think 注入) |
| 代码默认值 | 未配 env 时回落 gpt-4o(部署务必显式写 .env) |
第 8 篇展开双模型策略;当前 ResumeParser 走 light model,Reporter 仍走 chat model(与部分早期文档不一致,以代码为准)。
7. 部署要点(Docker)
多阶段 Dockerfile:Node 构建前端静态资源 → Python 镜像安装依赖 → uvicorn ican.main:app。
踩坑集中在第 15 篇,这里只列三条:
- 先装 CPU 版 PyTorch,避免 sentence-transformers 拖入 CUDA 大包。
- **镜像内装
fonts-noto-cjk**,否则 PDF 中文方块。 - 前端
dist单独 COPY,不要把宿主机node_modules打进镜像。
8. 目录结构(便于按篇跳转)
1 | |
9. 踩坑记录(对照源码)
代码默认模型不是 DeepSeek
config.py里LLM_MODEL_CHAT默认gpt-4o,LLM_BASE_URL默认 OpenAI 官方地址。本地常用deepseek-v4-flash是.env部署配置,不是框架写死;漏配 env 会直接连错端点。线上对话与
create_workflow()不是同一条路
SSE 多轮聊天走workflow.py的run_guide_chat()(内部调agents/guide.py的run_guide_single_turn);信息充分后由api/routes/chat.py触发run_analysis_pipeline(),直接串resume_parser→profile_analyzer→career_matcher→reporter,而不是每次ainvoke完整顶层图。CLI 或一次性跑通才用run_workflow()+create_workflow()。PlannerState已定义但未接入core/state.py里有PlannerState,顶层workflow.py没有 Planner 节点;行动规划内容目前合并在agents/reporter.py的报告章节里,文章勿写「六 Agent」。顶层状态用
workflow_messages累积,不是messagesiCanWorkflowState的 Reducer 字段是workflow_messages: Annotated[list[str], operator.add];messages只出现在内层GuideState,混用会导致调试时看错日志字段。双模型分工以代码为准
llm/providers.py模块注释曾描述 Reporter 可走 mini 档模型,但agents/reporter.py当前章节生成仍调get_chat_model();只有agents/resume_parser.py确定走get_light_model()。Ollama 不可用时的降级路径
run_analysis_pipeline()会先check_ollama_available();失败则走workflow.py内_regex_quick_profile+_generate_fallback_report,报告质量明显下降,需在运维层保证 LLM 可达。
10. 系列导读
| 篇 | 主题 |
|---|---|
| 1 | 系统全景(本篇) |
| 2 | 五 Agent 协作与 iCanWorkflowState |
| 3 | 霍兰德 RIASEC + OpenAI 兼容 API 部署示例 |
| 4–7 | LangGraph 状态、路由、嵌套、容错 |
| 8–11 | FastAPI 集成、SSE/WS、DB、PDF |
| 12–14 | Prompt 与 JSON 稳定输出 |
| 15–17 | Docker、中间件、配置管理 |
11. 小结
iCan 的核心不是「换一个更大的模型」,而是 用 LangGraph 把不确定的 LLM 步骤拆成可测试的节点,用 FastAPI 承接流式交互,用 ReportLab 交付可留存 PDF。
下一篇:LangGraph 多 Agent 编排 — 五个子图如何衔接、route_after_guide 与 Guide 内层循环如何配合。