Welcome back! In Part 1 we learned the core moves of dependable agents:
Structured Outputs (JSON contracts enforced with Pydantic) and Function Calling (LLM plans, the code fetches real data).
In Part 2, we will compose those moves into a tiny but complete product flow: a job application tracker that can create records and update their status—safely, predictably, and with real data enrichment.
If Part 1 taught the moves, Part 2 shows the dance: chaining structured outputs and tool calls into a small, auditable workflow we can actually ship.
A small agent that understands free-text like
“Create a job application for Apple” or “Update Amazon to VP interview”.
It will:
1/ Build a pandas Dataframe
2/ User text
└─► classify() → IntentResult {
intent ∈ {create, modify, other},
company?, stage, confidence, rationale
}
└─ if intent==create and confidence>0.5:
└─► create_application(IntentResult)
├─ Orchestrator HARD RULES:
│ • infer ticker from company
│ • MUST call get_company_sector(symbol) before finalizing
│ • never output sector unless from the tool
├─ Tool pass: call Alpha Vantage → {Symbol, Sector}
└─ Final typed JSON → CreateResult
(company, stage, symbol, sector, confidence, rationale)
└─► write row to DataFrame (+ timestamps)
└─ elif intent==modify and confidence>0.5:
└─► update row in DataFrame (Stage, Updated_at)
└─ else:
└─► no-op / message
Why this works well:
Environment & client
Loads STOCK_API_KEY via dotenv and creates a single OpenAI() client.
Schemas (Pydantic)
IntentResult — what the user meant: intent, company?, stage, confidence, rationale.CreateResult — what we store for creates: adds symbol, sector.Tool spec + handler
tools exposes get_company_sector(symbol) to the model.get_company_sector() calls Alpha Vantage OVERVIEW and returns {Symbol, Sector} (or {'Sector': 'Undefined'} fallback).LLM calls
classify() uses Structured Outputs via responses.parse(..., text_format=IntentResult) to return a typed intent.create_application() runs an orchestration loop with HARD RULES, lets the model call the sector tool, then finalizes as a typed CreateResult.Persistence
job_application_dataframe is the store.Stage and Updated_at.