0. 系列闭环(不公开源码也能跟读)

端到端链路:Vue 前端 → api/routes/chat.py → Guide 多轮 SSE → run_analysis_pipeline(解析→分析→匹配→报告)→ tools/pdf_exporter PDF。
本篇:第 3/17 篇 · 分析环 · 霍兰德 RIASEC

阶段 用户可见 代码入口 对应篇
建会话 欢迎语 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
说明
读本篇前 第 02 篇 profile_analyzer_node
读完本篇 读懂 analyze_riasec 如何产出六维分
下一环 第 13 篇:RIASEC 专用 Prompt(第 4 篇)

全系列闭环索引:SERIES-LOOP.md

一、霍兰德 RIASEC 模型介绍

霍兰德职业兴趣理论(Holland’s Theory of Vocational Personalities)是由美国心理学家约翰·霍兰德在 1959 年提出的。它将人的职业兴趣分为六个维度:

维度 英文 类型 典型职业
R Realistic 实用型 工程师、技术员、建筑师
I Investigative 研究型 科学家、数据分析师、医生
A Artistic 艺术型 设计师、作家、音乐家
S Social 社会型 教师、咨询师、社工
E Enterprising 企业型 企业家、销售经理、律师
C Conventional 常规型 会计、行政、审计

霍兰德代码:取评分最高的 2-3 个维度组成代码(如 IAS、RCE),每个代码对应一组匹配的职业。

为什么选这个模型

  • 心理学界公认,有 60+ 年实证研究支持
  • O*NET(美国职业信息网)的官方分类基础
  • 六维度结构清晰,适合 LLM 量化评估
  • 不需要用户做 120 道题,通过对话就能推断

霍兰德 RIASEC 评分流程

二、LLM 接入:OpenAI 兼容接口(DeepSeek 为常见部署配置)

实现上没有 DeepSeek 专用 SDK。统一通过 langchain_openai.ChatOpenAIllm/providers.py)接入,用 LLM_BASE_URL + LLM_MODEL_CHAT / LLM_MODEL_LIGHT 切换厂商——DeepSeek、OpenAI 官方、Ollama 本地均走同一套 invoke_llm / invoke_llm_with_json

与其他部署方案对比

方案 JSON 模式 国内访问 说明
DeepSeek API(如 deepseek-v4-flash) ✅ 直连 项目 .env 常用配置
GPT-4o 等 OpenAI 官方 视网络 config.py 未配 env 时的代码默认值
Ollama + Qwen3 本地 Docker 默认可指向 11434,需 /no_think

.env 示例(部署配置,非框架写死):

1
2
LLM_MODEL_CHAT=deepseek-v4-flash
LLM_BASE_URL=https://api.deepseek.com/v1

双模型策略(以当前代码为准)

llm/providers.py 提供 get_chat_model()get_light_model();默认值来自 config.py(未配 env 时为 gpt-4o):

调用方 当前实现
Guide / ProfileAnalyzer / CareerMatcher get_chat_model()
ResumeParser(agents/resume_parser.py get_light_model()
Reporter(agents/reporter.py get_chat_model()(文档若写 light,以代码为准)

通过 .env 切换 LLM_MODEL_CHAT / LLM_MODEL_LIGHT,无需改业务代码。.env 里写 deepseek-v4-flash 只是部署示例,不代表代码默认。

三、RIASEC 在 agents/profile_analyzer.py 中的位置

霍兰德打分不是独立服务,而是 ProfileAnalyzer 子图的一个节点。create_profile_analyzer_graph() 定义顺序链:

1
2
load_profile → analyze_abilities → infer_work_style → infer_personality
→ analyze_values → analyze_riasec → identify_strengths_weaknesses → synthesize_profile → END

analyze_riasecProfileAnalysisState.structured_profile 读取上游 agents/resume_parser.py 的结构化画像,截断 JSON 到约 2000 字符后调 LLM:

1
2
3
4
5
6
7
8
# agents/profile_analyzer.py — analyze_riasec 核心
profile_summary = json.dumps(structured_profile, ensure_ascii=False)[:2000]
messages = [
{"role": "system", "content": PROFILE_ANALYZER_SYSTEM_PROMPT}, # llm/prompts.py
{"role": "user", "content": f"请基于以下结构化履历信息,分析用户的霍兰德 RIASEC...\n{profile_summary}"},
]
riasec_data = await invoke_llm_with_json(get_chat_model(), messages)
riasec_scores = {k: float(riasec_data.get(k, 0)) for k in "RIASEC"}

顶层 workflow.pyprofile_analyzer_noderiasec_scores 写入 personal_profile,供 career_matcher_nodetools/pdf_exporter.py 制图使用。

四、LLM 如何做量化评估

传统问卷 vs LLM 评估

传统霍兰德测评需要用户回答 120+ 道题,耗时 20-30 分钟。而 LLM 评估的思路是:

  1. 用户在对话中描述自己的经历、偏好、困惑
  2. LLM 从这些自然语言中推断六个维度的倾向
  3. 给出 0-10 分的评分,并说明理由

Prompt 设计

规则集中在 llm/prompts.pyPROFILE_ANALYZER_SYSTEM_PROMPT 第六节;analyze_riasec 节点在 user 消息里再约束 JSON 形状:

1
2
3
4
5
6
7
# llm/prompts.py — 节选(第六节)
### 6. 霍兰德 RIASEC 分析
基于约翰·霍兰德的职业兴趣理论,分析用户在六个维度的倾向:
- R(Realistic)现实型:喜欢操作、实践、动手
- I(Investigative)研究型:喜欢分析、探索、研究
# ... A/S/E/C 定义 ...
每个维度给出 0-10 分的评分,并标注最高的 2-3 个维度作为用户的"霍兰德代码"

输出格式:

1
2
3
4
5
6
7
8
9
10
11
12
{
"riasec": {
"R": 6,
"I": 9,
"A": 3,
"S": 5,
"E": 7,
"C": 4,
"holland_code": "IEA",
"analysis": "用户具有强烈的研究型倾向,在5年Java开发中表现出对技术深度的追求..."
}
}

评分依据的设计

关键在于 Prompt 中要求 LLM 说明每个评分的理由

1
2
3
评分要有依据,不要随意打分,每个评分都要在 analysis 中说明理由。
分析要客观公正,既不过分夸大也不过分贬低。
如果信息不足以做出准确判断,在分析中说明并给出可能的范围。

这比单纯输出分数更有价值——用户能看到”为什么 I 得了 9 分”。

五、RIASEC 评分与职业匹配的衔接

Holland Code 如何进入 CareerMatcher

项目没有硬编码 HOLLAND_CAREER_MAP 字典。agents/career_matcher.pygenerate_candidate_paths 把完整 personal_profile(含 riasec_scores)序列化后,连同 llm/prompts.pyCAREER_MATCHER_SYSTEM_PROMPT 发给 LLM,由 Prompt 要求结合霍兰德代码解释三级路径:

1
2
3
4
5
6
7
8
### 第一级:纵向深耕(Deepen)
在用户当前行业/岗位上继续深入发展。匹配度 80-95%。

### 第二级:横向拓展(Expand)
在相关领域或相邻行业拓展。匹配度 60-80%。

### 第三级:转型探索(Transform)
完全跨行业或跨领域的转型探索。匹配度 40-60%。

Prompt 中要求 LLM 在推荐文案里引用 holland_code(如 IEA),说明哪些方向与高分维度一致、哪些是 stretch 方向——比维护静态映射表更灵活,但也更依赖 Prompt 约束与 JSON 解析稳定性(见第 12 篇)。

六、可视化:tools/pdf_exporter.py 柱状图

PDF 报告嵌入图表时,从 personal_profile.riasec_scores 读取 R–C 六键;key 必须与 analyze_riasec 输出一致(单字母大写)。

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
# tools/pdf_exporter.py — _generate_bar_chart 节选
def _generate_bar_chart(holland_data: dict = None) -> str:
plt.rcParams["axes.unicode_minus"] = False

categories = list(holland_data.keys())
values = list(holland_data.values())

colors = ["#0d9488", "#14b8a6", "#2dd4bf", "#5eead4", "#99f6e4", "#ccfbf1"]

fig, ax = plt.subplots(figsize=(5.5, 4.5))
bars = ax.bar(categories, values, color=colors[:len(categories)],
edgecolor="white", linewidth=1.8, width=0.62)

for bar, val in zip(bars, values):
ax.text(bar.get_x() + bar.get_width() / 2., bar.get_height() + 0.25,
f"{val}", ha="center", va="bottom", fontsize=12, fontweight="bold")

ax.set_title("霍兰德兴趣分布", fontsize=15, fontweight="bold")
plt.tight_layout()

buf = io.BytesIO()
plt.savefig(buf, format="png", dpi=160, bbox_inches="tight")
plt.close()
buf.seek(0)
return base64.b64encode(buf.read()).decode()

中文字体处理

在 Docker 部署中,matplotlib 默认不支持中文。解决方案:

  1. 安装系统字体包:apt-get install fonts-noto-cjk
  2. 在代码中指定字体优先级:
1
2
3
4
5
6
plt.rcParams["font.sans-serif"] = [
"Noto Sans CJK SC", # Linux Docker 环境
"WenQuanYi Zen Hei", # 备选 Linux 字体
"PingFang SC", # macOS
"SimHei", # Windows
]

七、测评效果评估

与传统问卷的对比

维度 传统问卷 LLM 评估
用户耗时 20-30 分钟 5 分钟对话
题目数量 120+ 道 自然对话
评分精度 标准化量表 基于语义推断
适应性 固定题目 动态追问
用户体验 枯燥 像和朋友聊天

局限性

  • 精度依赖输入质量:用户描述越详细,评估越准确
  • 无标准化量表:不像 SDS 量表有大量效度验证
  • 文化差异:霍兰德模型基于美国职场,中国场景需要调整权重

八、踩坑记录

  1. 勿把 DeepSeek 写成代码默认config.py 默认 gpt-4o;DeepSeek 是 .envLLM_BASE_URL=https://api.deepseek.com/v1 的部署选择。
  2. **holland_code 未写入 riasec_scores**:analyze_riasec 只把 R–C 六维 float 写入 state,holland_code 在 LLM JSON 里但不持久化到 riasec_scores;PDF 若需展示代码要从原始 JSON 或后处理补全。
  3. JSON 解析失败全零分analyze_riasec 异常时返回六维 0.0,下游 Matcher 仍会继续——需结合 Guide 对话质量与 Parser 置信度(confidence_scores)判断。
  4. **Ollama Qwen3 需 /no_think**:llm/providers.py_inject_no_think 在检测到 Ollama + qwen3 时自动注入,否则 RIASEC JSON 可能被 thinking 块污染。

九、小结

用 LLM 实现霍兰德测评的核心思路是:把心理学量表写进 llm/prompts.py,由 agents/profile_analyzer.pyanalyze_riasec 从结构化画像推断各维度评分

关键设计:

  • OpenAI 兼容接口 + 环境变量切换模型(DeepSeek / GPT / Ollama 均可)
  • Prompt 要求评分 + 理由,确保可解释性
  • tools/pdf_exporter.py 的 matplotlib 柱状图嵌入 PDF
  • Docker 环境安装 fonts-noto-cjk 解决中文显示

下一篇:core/state.py 中 TypedDict + Annotated Reducer 的分层用法。


← 返回 iCan 专题