SITUATION

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.

What we will build

How the pieces fit

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:

STEPS

  1. Environment & client

    Loads STOCK_API_KEY via dotenv and creates a single OpenAI() client.

  2. Schemas (Pydantic)

  3. Tool spec + handler

  4. LLM calls

  5. Persistence