0. Series Loop (Follow Along Without Public Source Code)

End-to-End Pipeline: Vue Frontend → api/routes/chat.py → Guide Multi-turn SSE → run_analysis_pipeline (Parse → Analyze → Match → Report) → tools/pdf_exporter PDF.
This Article: 13/17 · Prompt Loop · RIASEC

Stage User Visible Code Entry Corresponding Article
Create Session Welcome Message POST /api/sessions 09
Multi-turn Dialogue SSE Streaming chat/stream → run_guide_single_turn 06, 14
Sufficient Info Start Analysis _run_analysis_background 05, 07
Resume Parsing Progress 30% run_resume_parser 12
Profile/RIASEC Progress 50% run_profile_analyzer 03, 13
Career Matching Progress 70% run_career_matcher 02
Report Progress 90% run_reporter 11
Download PDF File GET …/report/pdf 11, 15
Description
Before reading this Article 03 analyze_riasec
After reading this Compare Prompt vs analyzer output fields
Next loop Article 14: Guide Five-Stage Prompt (Article 14)

Full series closed-loop index: SERIES-LOOP.md

1. Pain Point: Scale Logic vs Dialogue Inference

Traditional SDS questionnaires have 120 fixed questions, users rate each, system calculates scores for six dimensions.
iCan takes a different path: users naturally describe their background and preferences in Guide conversations, and the analyze_riasec node of ProfileAnalyzer then infers R/I/A/S/E/C from the structured profile.

The difficulty is that LLMs tend to “assign scores arbitrarily,” outputting precise numbers without solid evidence, and downstream CareerMatcher will treat those erroneous codes as fact.

This article explains how the Prompt writes the psychological dimension definitions clearly and constrains “scoring + rationale + stating a range when information is insufficient.”


2. Position in the Pipeline

Implementation entry: create_profile_analyzer_graph() in agents/profile_analyzer.py. The subgraph enters analyze_riasec after analyzing abilities, style, personality, and values; the result is written to the outer personal_profile.riasec_scores via profile_analyzer_node in workflow.py:

1
2
3
4
5
6
7
8
9
10
11
12
flowchart LR
P[structured_profile] --> L[load_profile]
L --> AB[analyze_abilities]
AB --> WS[infer_work_style]
WS --> PT[infer_personality]
PT --> AV[analyze_values]
AV --> AR[analyze_riasec]
AR --> SW[identify_strengths_weaknesses]
SW --> SY[synthesize_profile]
AR --> J[invoke_llm_with_json]
J --> R[riasec_scores]
R --> M[career_matcher_node]

Each node uses get_chat_model() (llm/providers.py), and the System Prompt shares PROFILE_ANALYZER_SYSTEM_PROMPT in llm/prompts.py.

RIASEC Prompt Structure


3. Prompt Structure: System Template + Dynamic User Content

RIASEC rules are written in section 6 of PROFILE_ANALYZER_SYSTEM_PROMPT in llm/prompts.py, sharing a single System Prompt with the ability model, Big Five personality, etc., to avoid scattered node Prompts that are hard to maintain.

Core excerpt (abridged):

1
2
3
4
5
6
7
8
9
10
11
12
### 6. Holland RIASEC Analysis
Based on John Holland's career interest theory, analyze the user's tendencies across six dimensions:
- R (Realistic): Enjoys operation, practice, hands-on work
- I (Investigative): Enjoys analysis, exploration, research
# ... A/S/E/C definitions ...

Score each dimension from 0-10, and mark the top 2-3 dimensions as the user's "Holland Code."

## Important Rules
- Scores must have supporting evidence; do not assign arbitrary scores. Each score must include a rationale in the analysis.
- Holland analysis should infer from the user's work experience and skills, not guess blindly.
- If information is insufficient to make an accurate judgment, state this in the analysis and provide a possible range.

The analyze_riasec node truncates structured_profile to approximately 2000 characters, appends it to the user message, and calls invoke_llm_with_json (llm/providers.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# agents/profile_analyzer.py — analyze_riasec
async def analyze_riasec(state: ProfileAnalysisState) -> dict:
structured_profile = state.get("structured_profile", {})
profile_summary = json.dumps(structured_profile, ensure_ascii=False)[:2000]
messages = [
{"role": "system", "content": PROFILE_ANALYZER_SYSTEM_PROMPT},
{"role": "user", "content": (
f"Please analyze the user's Holland RIASEC career interests based on the following structured resume information.\n"
f"Score the six dimensions (0-10)...\n\nResume Information:\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"}
return {"riasec_scores": riasec_scores}

If response_format in invoke_llm_with_json is ignored by Ollama, it falls back to parse_riasec_scores() in llm/parsers.py to extract six-dimensional scores from plain text.

Note: RIASEC and Big Five analysis are defined in the same JSON Schema, but at runtime they are called by independent nodes, allowing separate retry or model replacement.


4. Expected JSON Shape and Holland Code

The model should return a riasec block nested within the full profile JSON:

1
2
3
4
5
6
7
{
"riasec": {
"R": 6, "I": 9, "A": 3, "S": 5, "E": 7, "C": 4,
"holland_code": "IEA",
"analysis": "I dimension: User mentioned architecture design and technical selection multiple times……"
}
}

Holland Code takes the top 2–3 letters by score. In implementation, the holland_code returned by the LLM is in the JSON, but analyze_riasec persists only the R–C six floats into riasec_scores; if JSON parsing fails, the node returns a zero-score dictionary to avoid crashing the entire subgraph (see Article 07 for fault tolerance). tools/pdf_exporter.py reads the six keys of riasec_scores to draw a bar chart and does not depend on the holland_code field.


5. Connection with CareerMatcher

generate_candidate_paths in agents/career_matcher.py reads personal_profile (including riasec_scores), combined with CAREER_MATCHER_SYSTEM_PROMPT in llm/prompts.py, to generate three-level paths. The Prompt requires combining the Holland Code to explain recommendations, rather than maintaining a static mapping table:

1
2
3
Based on the user's Holland Code IRS, explain in the recommendation:
- Which directions align with I/R (e.g., technical architecture, data engineering)
- Which directions are stretches (e.g., pure sales-type E-oriented positions)

career_matcher_node in workflow.py writes recommended_paths to the outer career_matches, for agents/reporter.py to include in the report.


6. Boundary with Traditional Questionnaires (Written into Product Copy)

Dimension SDS Questionnaire iCan LLM Evaluation
Standardization High Medium (depends on dialogue quality)
Validity Long-term validated No formal scale validity
Experience 120 questions Multi-turn dialogue
Applicability Formal assessment Exploration & report aid

External messaging should be clear: Dialogue-based inference cannot replace standardized career assessments; it is suitable as a starting point for career planning discussions.


7. Pitfalls Recorded

  1. Thin profile still yields high scores: If agents/resume_parser.py only extracts job titles, RIASEC may fabricate scores. Solution: Guide stage tries to collect industry/skills/preferences (Article 14); Parser output confidence_scores (not parsing_confidence) can indicate parsing quality perception.
  2. Holland Code letter order: Some models output EIA instead of sorted by score; current MVP relies on Prompt constraints and does not reorder in post-processing.
  3. Inconsistent with PDF chart fields: tools/pdf_exporter.py bar chart reads riasec_scores flat dict, keys must be uppercase single letters RC, matching analyze_riasec output format.
  4. json_object not supported: Some Ollama models ignore response_format, must use the four-layer parse_json_from_text / parse_riasec_scores in llm/parsers.py from Article 12.
  5. System Prompt Section 6 duplicates node user Prompt: PROFILE_ANALYZER_SYSTEM_PROMPT already contains RIASEC definitions, but analyze_riasec user message reiterates dimensions—intentional reinforcement; modifications require syncing both places.

8. Summary

The key to the Holland Prompt is not memorizing the six dimension definitions, but: binding to structured_profile, forcing rationale, acknowledging insufficient information, outputting holland_code for downstream Matcher.

Next article: Guide Five-Stage Dialogue Prompt — Dual-track judgment with GUIDE_SYSTEM_PROMPT and check_sufficiency.


Appendix: Key Source Code (Line-by-Line Comments)

The following code is excerpted from the iCan implementation, each line has a Chinese comment above it, so you can follow along without the public repository.
Generation command: python3 bin/build-ican-annotated-snippets.py

Prompt Constants (Excerpt)

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
# ========== Prompt Constants (Excerpt) ==========
# Source file: llm/prompts.py Lines 1-100

# L2: [Document] File description: Centralized management module for prompt templates
# L3: [Document] Business description: Centralizes management of all Agent prompt templates in the iCan project, including:
# L4: [Document] - GuideAgent (Dialogue Guidance): Multi-stage dialogue strategy, progressively extracts user information
# L5: [Document] - ResumeParserAgent (Resume Parsing): Extracts structured information from user text
# L6: [Document] - ProfileAnalyzerAgent (Personal Analysis): Deep five-dimensional analysis based on profile
# L7: [Document] - CareerMatcherAgent (Career Matching): Three-level career matching based on personal profile
# L8: [Document] - ReporterAgent (Report Output): Integrates analysis results into structured report text
# L9: [Document] All prompts follow PRD Chapter 9 Prompt Design Points: Role setting, task description,
# L10: [Document] output format, constraints, example guidance.
# L11: [Document] Data flow: This module is imported by various Agents -> fill variables -> send to LLM
# (Lines L1-12 are function/module docstrings, converted to comments for readability)

# L14: ==========================================================================
# L15: GuideAgent Dialogue Guidance Prompt
# L16: ==========================================================================

# L18: Assignment: Update local variable or state field
GUIDE_SYSTEM_PROMPT = """You are a senior career counselor with 20 years of experience, named "Xiao C." You have dual expertise in psychological counseling and career consulting, skilled at deeply understanding a person's professional development needs through natural conversation.

# L20: Your Core Abilities
# L21: Execute this statement (details in business description above)
1. **Professional Career Planning Ability**: Proficient in career development paths, market trends, and job requirements across industries
# L22: Execute this statement (details in business description above)
2. **Psychological Counseling Ability**: Good at listening and empathizing, can identify users' emotional states and deep needs
# L23: Execute this statement (details in business description above)
3. **Information Mining Ability**: Systematically collect user key information through progressive questioning
# L24: Execute this statement (details in business description above)
4. **Analytical Judgment Ability**: Form a preliminary user profile quickly based on collected information

# L26: Dialogue Strategy
# L27: Execute this statement (details in business description above)
- **Progressive Mining**: Start with open-ended questions, gradually focus on specific details; avoid asking too many structured questions at once
# L28: Execute this statement (details in business description above)
- **Emotion Recognition**: Pay attention to user emotional changes, provide encouragement and recognition timely to build trust
# L29: Execute this statement (details in business description above)
- **Flexible Response**: Adjust conversation direction flexibly based on user responses; do not mechanically follow a fixed process
# L30: Execute this statement (details in business description above)
- **Information Completion**: Subtly guide users to provide missing key information during natural conversation
# L31: Execute this statement (details in business description above)
- **Summary Confirmation**: Actively summarize collected information at key points to ensure accurate understanding

# L33: Dialogue Stages
# L34: Execute this statement (details in business description above)
You will automatically determine the current stage based on conversation progress:
# L35: Execute this statement (details in business description above)
1. **greeting (Opening Greeting)**: Warmly welcome the user, briefly introduce services, understand basic intention
# L36: Execute this statement (details in business description above)
2. **assess_need (Needs Assessment)**: Determine if core need is career planning, transition consulting, resume optimization, etc.
# L37: Execute this statement (details in business description above)
3. **collect_basic_info (Basic Information Collection)**: Understand education background, work experience, current position, etc.
# L38: Execute this statement (details in business description above)
4. **dig_deeper (Deep Mining)**: Deeply understand core skills, career achievements, work preferences, values, etc.
# L39: Execute this statement (details in business description above)
5. **confirm (Confirmation Summary)**: Summarize all collected information, confirm with user, prepare to generate analysis report

# L41: Important Rules
# L42: Execute this statement (details in business description above)
- Always reply in Chinese
# L43: Execute this statement (details in business description above)
- Tone should be friendly and professional, like an experienced friend
# L44: Execute this statement (details in business description above)
- Each reply should be within 200 characters to maintain conversation rhythm
# L45: Execute this statement (details in business description above)
- Do not ask too many questions at once; at most 1-2 questions per response
# L46: Execute this statement (details in business description above)
- If user answers vaguely, use specific scenarios to guide
# L47: Execute this statement (details in business description above)
- If user expresses negative emotions, offer emotional support before continuing guidance
# L48: Execute this statement (details in business description above)
- When sufficient information is collected, proactively propose entering analysis stage
# L51: [Document] GUIDE_STAGE_TEMPLATES = {
# L52: [Document] "greeting": (
# L53: [Document] "User just started the conversation. Warmly welcome the user, briefly introduce the career planning services you can provide, "
# L54: [Document] "and use a relaxed open-ended question to understand the user's intention.\n\n"
# L55: [Document] "Example opening: Hello! I'm Xiao C, your personal career planning consultant. I have 20 years of career planning experience, "
# L56: [Document] "and have helped thousands of professionals find their direction. What would you like to talk about today? Are you facing a career bottleneck, "
# L57: [Document] "or considering a new direction?"
# L58: [Document] ),
# L59: [Document] "assess_need": (
# L60: [Document] "User has started the conversation. Use 1-2 precise questions to determine the user's core need category:\n"
# L61: [Document] "- Career planning: needs clear career direction or development plan\n"
# L62: [Document] "- Career transition: wants to change industry or role\n"
# L63: [Document] "- Resume optimization: needs to improve application materials\n"
# L64: [Document] "- Skills development: wants to know which capabilities to supplement\n"
# L65: [Document] "- Workplace confusion: facing specific work difficulties\n\n"
# L66: [Document] "Based on the user's response, naturally guide the conversation deeper. Do not ask 'What service do you need?' directly, "
# L67: [Document] "but infer from the conversation content."
# L68: [Document] ),
# L69: [Document] "collect_basic_info": (
# L70: [Document] "Understand the user's core needs. Now collect the following basic information naturally in the conversation (do not ask all at once):\n"
# L71: [Document] "1. Education background (degree, major, school)\n"
# L72: [Document] "2. Years of work experience and industry experience\n"
# L73: [Document] "3. Current/recent position and responsibilities\n"
# L74: [Document] "4. Core skills and tech stack\n"
# L75: [Document] "5. Important career achievements or project experience\n\n"
# L76: [Document] "Note: Acquire this information naturally like a chat, not an interview-like list of questions."
# L77: [Document] "Start from the topic the user is most willing to discuss."
# L78: [Document] ),
# L79: [Document] "dig_deeper": (
# L80: [Document] "Basic information is mostly collected. Now dig deeper into the following dimensions:\n"
# L81: [Document] "1. **Work preferences**: Prefer working independently or collaboratively? Prefer stability or challenge?\n"
# L82: [Document] "2. **Sources of fulfillment**: What kind of things give the user the most sense of accomplishment?\n"
# L83: [Document] "3. **Career values**: What does the user value most? (Salary, growth, work-life balance, impact, etc.)\n"
# L84: [Document] "4. **Personality traits**: More extroverted or introverted? Good at analysis or intuitive judgment?\n"
# L85: [Document] "5. **Root cause of career confusion**: What is the real issue bothering the user?\n"
# L86: [Document] "6. **Expectations and goals**: What are the expectations for future career development?\n\n"
# L87: [Document] "Combine previously collected information to ask targeted deep questions."
# L88: [Document] "For example: 'You mentioned leading a team to complete project XX earlier; what part gave you the most sense of accomplishment at that time?'"
# L89: [Document] ),
# L90: [Document] "confirm": (
# L91: [Document] "Sufficient information has been collected from the conversation. Do the following:\n"
# L92: [Document] "1. Summarize the information you've learned about the user concisely (education, experience, skills, preferences, goals, etc.)\n"
# L93: [Document] "2. Ask if the user has anything to add or correct\n"
# L94: [Document] "3. If the user confirms information is correct, inform that the analysis stage is about to begin, which will generate:\n"
# L95: [Document] " - Personal career profile analysis\n"
# L96: [Document] " - Career direction recommendations\n"
# L97: [Document] " - Action suggestions\n\n"
# L98: [Document] "Example summary format:\n"
# L99: [Document] 'Let me summarize what we\'ve discussed: You are a professional with X years of experience in the XX industry, '
# L100: [Document] 'currently working as XX, skilled in XX and XX. You hope for better development in the XX direction,'

analyze_riasec Invoke LLM

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
# ========== analyze_riasec Invoke LLM ==========
# Source file: agents/profile_analyzer.py Lines 328-395

# L328: Async function analyze_riasec: can be awaited, suitable for IO-bound LLM/DB calls
async def analyze_riasec(state: ProfileAnalysisState) -> dict:
# L330: [Document] Analyze Holland RIASEC career interests.
# L332: [Document] Function description:
# L333: [Document] Based on work experience, skill characteristics, and career development path in the structured profile,
# L334: [Document] analyze the user's tendencies on the six Holland dimensions (R/I/A/S/E/C),
# L335: [Document] calculate scores for each dimension and determine the user's Holland Code (top 2-3 dimensions).
# L337: [Document] Input parameters:
# L338: [Document] state (ProfileAnalysisState): Personal analysis state object, must include structured_profile.
# L340: [Document] Output parameters:
# L341: [Document] dict: State update dictionary, containing riasec_scores (RIASEC six-dimensional scores).
# (Lines L329-342 are function/module docstrings, converted to comments for readability)
# L343: Start try block, except handles fallback
try:
# L344: Logging, for online troubleshooting of node input/output
logger.info("[analyze_riasec] Starting execution, input: state=%s", {k: str(v)[:100] for k, v in state.items()})
# L345: Assignment: Update local variable or state field
structured_profile = state.get("structured_profile", {})

# L347: Assignment: Update local variable or state field
profile_summary = json.dumps(structured_profile, ensure_ascii=False)[:2000]
# L348: Assignment: Update local variable or state field
riasec_prompt = (
# L349: Execute this statement (details in business description above)
f"Please analyze the user's Holland RIASEC career interests based on the following structured resume information.\n"
# L350: Execute this statement (details in business description above)
f"Score the six dimensions (0-10): R(Realistic), I(Investigative), A(Artistic), "
# L351: Execute this statement (details in business description above)
f"S(Social), E(Enterprising), C(Conventional).\n\n"
# L352: Execute this statement (details in business description above)
f"Resume Information:\n{profile_summary}\n\n"
# L353: Execute this statement (details in business description above)
f"Please output in the following JSON format:\n"
# L354: Execute this statement (details in business description above)
f'{{"R": score 0-10, "I": score 0-10, "A": score 0-10, '
# L355: Execute this statement (details in business description above)
f'"S": score 0-10, "E": score 0-10, "C": score 0-10, '
# L356: Execute this statement (details in business description above)
f'"holland_code": "e.g., IAS", "analysis": "RIASEC analysis text"}}'
# L357: Execute this statement (details in business description above)
)

# L359: Assignment: Update local variable or state field
messages = [
# L360: Execute this statement (details in business description above)
{"role": "system", "content": PROFILE_ANALYZER_SYSTEM_PROMPT},
# L361: Execute this statement (details in business description above)
{"role": "user", "content": riasec_prompt},
# L362: Execute this statement (details in business description above)
]

# L364: Logging, for online troubleshooting of node input/output
logger.info("[analyze_riasec] Calling LLM to analyze RIASEC")
# L365: Get chat model instance (configured from settings.LLM_MODEL_CHAT)
model = get_chat_model()
# L366: Call LLM and parse JSON; internally uses JSON mode → text fallback chain
riasec_data = await invoke_llm_with_json(model, messages)

# L368: Extract RIASEC scores to standard format
# L369: Assignment: Update local variable or state field
riasec_scores = {
# L370: Execute this statement (details in business description above)
"R": float(riasec_data.get("R", 0)),
# L371: Execute this statement (details in business description above)
"I": float(riasec_data.get("I", 0)),
# L372: Execute this statement (details in business description above)
"A": float(riasec_data.get("A", 0)),
# L373: Execute this statement (details in business description above)
"S": float(riasec_data.get("S", 0)),
# L374: Execute this statement (details in business description above)
"E": float(riasec_data.get("E", 0)),
# L375: Execute this statement (details in business description above)
"C": float(riasec_data.get("C", 0)),
# L376: Execute this statement (details in business description above)
}

# L378: Logging, for online troubleshooting of node input/output
logger.info(
# L379: Assignment: Update local variable or state field
"[analyze_riasec] RIASEC analysis completed, holland_code=%s, scores=%s",
# L380: Execute this statement (details in business description above)
riasec_data.get("holland_code", "unknown"),
# L381: Execute this statement (details in business description above)
riasec_scores,
# L382: Execute this statement (details in business description above)
)

# L384: Assignment: Update local variable or state field
result = {
# L385: Execute this statement (details in business description above)
"riasec_scores": riasec_scores,
# L386: Execute this statement (details in business description above)
}
# L387: Logging, for online troubleshooting of node input/output
logger.info("[analyze_riasec] Execution completed, output: riasec_scores=%s", riasec_scores)
# L388: Return fields to merge into state (LangGraph will merge)
return result

# L390: Catch exception to avoid crashing entire graph/request
except Exception as e:
# L391: Logging, for online troubleshooting of node input/output
logger.error("[analyze_riasec] Exception analyzing RIASEC: %s", e, exc_info=True)
# L392: Return fields to merge into state (LangGraph will merge)
return {
# L393: Execute this statement (details in business description above)
"riasec_scores": {"R": 0.0, "I": 0.0, "A": 0.0, "S": 0.0, "E": 0.0, "C": 0.0},
# L394: Execute this statement (details in business description above)
}

parse_riasec_scores

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
# ========== parse_riasec_scores ==========
# Source file: llm/parsers.py Lines 95-160

# L95: Synchronous function parse_riasec_scores: routing decision or factory method
def parse_riasec_scores(text: str) -> dict:
# L97: [Document] Parse RIASEC six-dimensional scores from LLM response.
# L99: [Document] Function description:
# L100: [Document] Parse scores for the six Holland dimensions (R/I/A/S/E/C) from text.
# L101: [Document] Supports multiple formats:
# L102: [Document] 1. JSON format: {"R": 7, "I": 8, ...}
# L103: [Document] 2. List format: R: 7, I: 8, A: 5, ...
# L104: [Document] 3. Description format: Realistic(R): 7 points, Investigative(I): 8 points, ...
# L105: [Document] If a dimension is not found, defaults to 0.0.
# L107: [Document] Input parameters:
# L108: [Document] text (str): LLM response text containing RIASEC score information
# L110: [Document] Output parameters:
# L111: [Document] dict: {"R": float, "I": float, "A": float, "S": float, "E": float, "C": float}
# L112: [Document] Each value is a float between 0.0 and 10.0
# (Lines L96-113 are function/module docstrings, converted to comments for readability)
# L114: Start try block, except handles fallback
try:
# L115: Logging, for online troubleshooting of node input/output
logger.info(f"[parse_riasec_scores] Starting execution, input: text length={len(text)}")
# L116: Logging, for online troubleshooting of node input/output
logger.debug(f"[parse_riasec_scores] Text preview: {text[:300]}")

# L118: Conditional branch
if not text or not text.strip():
# L119: Logging, for online troubleshooting of node input/output
logger.warning("[parse_riasec_scores] Input text is empty, returning all-zero scores")
# L120: Return fields to merge into state (LangGraph will merge)
return {"R": 0.0, "I": 0.0, "A": 0.0, "S": 0.0, "E": 0.0, "C": 0.0}

# L122: Assignment: Update local variable or state field
default_scores = {"R": 0.0, "I": 0.0, "A": 0.0, "S": 0.0, "E": 0.0, "C": 0.0}

# L124: Strategy 1: Attempt to extract from JSON
# L125: Extract JSON from LLM text (four-layer regex/parsing strategy)
json_data = parse_json_from_text(text)
# L126: Conditional branch
if json_data:
# L127: Mapping from English full names to RIASEC abbreviations
# L128: Assignment: Update local variable or state field
name_mapping = {
# L129: Execute this statement (details in business description above)
"realistic": "R", "investigative": "I", "artistic": "A",
# L130: Execute this statement (details in business description above)
"social": "S", "enterprising": "E", "conventional": "C",
# L131: Execute this statement (details in business description above)
"现实型": "R", "研究型": "I", "艺术型": "A",
# L132: Execute this statement (details in business description above)
"社会型": "S", "企业型": "E", "常规型": "C",
# L133: Execute this statement (details in business description above)
}
# L134: Build normalized dictionary
# L135: Assignment: Update local variable or state field
normalized = {}
# L136: Loop
for k, v in json_data.items():
# L137: Assignment: Update local variable or state field
key_lower = k.lower().strip()
# L138: Conditional branch
if key_lower in name_mapping:
# L139: Assignment: Update local variable or state field
normalized[name_mapping[key_lower]] = v
# L140: Conditional branch
elif key_lower.upper() in ["R", "I", "A", "S", "E", "C"]:
# L141: Assignment: Update local variable or state field
normalized[key_lower.upper()] = v
# L142: Conditional branch else
else:
# L143: Assignment: Update local variable or state field
normalized[k] = v

# L145: Assignment: Update local variable or state field
scores = {}
# L146: Loop
for key in ["R", "I", "A", "S", "E", "C"]:
# L147: Conditional branch
if key in normalized:
# L148: Start try block, except handles fallback
try:
# L149: Assignment: Update local variable or state field
val = float(normalized[key])
# L150: Assignment: Update local variable or state field
scores[key] = min(10.0, max(0.0, val))
# L151: Catch exception, avoid crashing entire graph/request
except (ValueError, TypeError):
# L152: Assignment: Update local variable or state field
scores[key] = 0.0
# L153: Conditional branch else
else:
# L154: Assignment: Update local variable or state field
scores[key] = 0.0

# L156: Check if at least one non-zero value exists
# L157: Conditional branch
if any(v > 0 for v in scores.values()):
# L158: Logging, for online troubleshooting of node input/output
logger.info(f"[parse_riasec_scores] Execution completed (Strategy 1: JSON extraction), returning: {scores}")
# L159: Return fields to merge into state (LangGraph will merge)
return scores

Series Navigation

Article Topic
1 System Overview
2 Five-Agent Collaboration
3 Holland RIASEC
4–7 State · Routing · Nesting · Fault Tolerance
8–11 LLM Layer · SSE/WS · DB Migration · PDF
12–14 JSON Prompt · 13 RIASEC Prompt (This Article) · Guide Prompt
15–17 Docker · Middleware · Configuration

← Back to iCan Topic