0. Series Loop (Readable 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: 4/17 · Data Loop · State TypedDict
| 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 |
| Information Sufficient | 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 This Article | Return values of each node in Article 02 |
| After This Article | Distinguish outer iCanWorkflowState from inner GuideState |
| Next Loop | Article 05: Routing with needs_more_info (Article 5) |
Full Series Loop Index: SERIES-LOOP.md
1. LangGraph’s State Passing Mechanism
LangGraph’s core concept is state‑driven. Each node receives a TypedDict defined in core/state.py, processes it, returns partial fields to update, and LangGraph automatically merges them into the global state.
1 | |
The key question of this mechanism is: How is merging done?
2. TypedDict Defines Agent State
Implementation location: core/state.py. LangGraph uses Python’s TypedDict to define state structures:
1 | |
total=False means all fields are optional (nodes can return only the fields they need to update).
3. Annotated[list, operator.add] — Reducer Explained
Default Behavior: Overwrite
Without a Reducer, LangGraph’s default behavior is new value overwrites old value:
1 | |
This is correct for fields like current_agent or needs_more_info where only the latest value matters.
Reducer Behavior: Accumulate
1 | |
Annotated[list[str], operator.add] tells LangGraph: merge this field using operator.add (list concatenation).
1 | |
Why the Guide Inner messages Must Use a Reducer
In the multi‑turn subgraph of agents/guide.py, each of welcome / assess_need / collect_basic_info / dig_deeper returns {"messages": [reply]}. Without a Reducer:
1 | |
4. How to Choose Overwrite vs Accumulate
Selection Principle
| Field Characteristic | Usage | Example |
|---|---|---|
| Only the latest value | Direct assignment (overwrite) | current_agent, needs_more_info |
| History needed | Annotated + operator.add | GuideState.messages, workflow_messages |
| Incrementally filled dict | Direct assignment (overwrites entire dict) | structured_profile, personal_profile |
| Accumulating list | Annotated + operator.add | messages (inner), workflow_messages (outer) |
Common Mistakes
Mistake 1: conversation_history with Reducer but it’s a list of dicts
1 | |
Reason: conversation_history contains messages from both user and assistant; the order (user first, then assistant) must be controlled manually. LangGraph’s automatic merge would break the conversation structure.
Mistake 2: dict type with Reducer
1 | |
5. Layered State Design
The iCan project adopts outer + inner layering in core/state.py: the top‑level iCanWorkflowState and per‑agent states like GuideState / ProfileAnalysisState / CareerMatchState / ReporterState, etc.
Note: PlannerState is also defined in core/state.py, but workflow.py has not yet connected the Planner node. Do not draw a sixth agent in the state transition diagram.
Outer State (core/state.py — iCanWorkflowState)
1 | |
Inner State (core/state.py — GuideState)
1 | |
Why Layering
- Responsibility Isolation: Guide’s internal fields (e.g.,
emotion_state,missing_fields) are defined inGuideStateand never enteriCanWorkflowState. - Independent Testing:
run_guide_agent(guide_state)/run_profile_analyzer(analyzer_state)can be unit‑tested without the top‑level graph. - Data Transformation: The
*_nodefunctions inworkflow.pymanually handle outer‑inner mapping.
Data Transformation Example (compare with workflow.py)
1 | |
Similarly, profile_analyzer_node: takes structured_profile from the outer state, constructs a ProfileAnalysisState, calls run_profile_analyzer(), and assembles the result into personal_profile.
6. Other Reducer Usages
Beyond operator.add, you can use other reducers:
1 | |
7. Pitfall Records
- Outer misuse of
messages: OnlyGuideState.messageshas the Reducer; the top‑level isworkflow_messages. Both audit scripts and log checks should refer tocore/state.py. conversation_historymust not have a Reducer: The user/assistant order is maintained byworkflow.py’sguide_nodemanually appending; automatic merging would break the dialogue structure.ProfileAnalysisState.analysis_messages: Inner analysis process messages can accumulate, but the outer state only takes structured fields (ability_model,riasec_scores, etc.). Do notreturn guide_resultwholesale.- Initial
needs_more_info:create_initial_workflow_state()defaults toFalse; it becomesTrue/Falseonly after the first entry intoguide_node. Pay attention to the initial value when writing tests.
8. Preventing State Pollution
Problem Scenario
If internal state of the Guide Agent (e.g., emotion_state) accidentally appears in the outer state, downstream nodes might incorrectly read it.
Prevention Measures
- Type Validation: TypedDict strictly defines fields for each state; undefined fields will not appear.
- Manual Mapping: Node functions return only the fields that need updating, not irrelevant ones.
- Separate State Types: Each Agent has its own TypedDict, providing compile‑time field checking.
9. Summary
Key takeaways for LangGraph state management in iCan:
- All TypedDicts are centralized in
core/state.py. - Default overwrite for single‑value fields like
current_agent,needs_more_info. - Annotated + Reducer for
GuideState.messagesandiCanWorkflowState.workflow_messages. - Layering + manual mapping: the
*_nodefunctions inworkflow.pyare the sole conversion layer between outer and inner states. PlannerStateis defined but not yet connected; when extending, do not confuse it with the existing five‑stage pipeline.
Next Article: Conditional routing in
workflow.py— howroute_after_guideandshould_continueinside Guide’s inner state work together.