Home

Chapter 1 · Chapter Project

Multi-model AI assistant with structured output

Estimated time: 4–6 hours · Portfolio grade

Overview

This is the portfolio project for Chapter 1. It demonstrates everything a hiring manager expects an AI engineer to know before their first interview: calling multiple LLM providers, managing conversation state, extracting structured data, handling errors and cost, and shipping a usable CLI tool.

You are building a polished command-line AI assistant that supports both OpenAI and Anthropic, has persistent memory, a document analysis mode, and a cost dashboard. When you finish, this is something you can show in an interview or point to in your portfolio.

Prerequisites

  • All of Chapter 1 complete: Lessons 1–7, 47 and Projects P001–P002
  • Both an OpenAI API key and an Anthropic API key in your .env
  • Packages installed: uv add openai anthropic pydantic python-dotenv pypdf (PyPDF2 is deprecated — use pypdf)

What you are building

A single CLI application (ai.py) with four modes:

1. Interactive chat

uv run ai.py chat --provider openai
uv run ai.py chat --provider anthropic
uv run ai.py chat --provider anthropic --model claude-haiku-4-5-20251001

Persistent conversation with history saved per session name. Dark terminal output — model replies in a different colour from the user prompt.

2. Document analysis

uv run ai.py analyse report.pdf --provider openai
uv run ai.py analyse contract.txt --provider anthropic --detail detailed

Structured summary extraction using the provider specified. Output includes all DocumentSummary fields plus cost.

3. Provider comparison

uv run ai.py compare "Explain async Python in one paragraph"

Sends the same prompt to both OpenAI and Anthropic simultaneously (using asyncio), prints both responses side-by-side with their costs, and shows which was faster.

4. Cost dashboard

uv run ai.py costs

Reads a local log file and prints: total spend today, total spend this week, cost breakdown by provider and mode, most expensive single call.

Requirements

Architecture

Chat mode

Analyse mode

Compare mode

Cost dashboard

Error handling

Project layout

ai-foundations/
├── ai.py              ← CLI entry point (argparse subcommands)
├── llm/
│   ├── __init__.py
│   ├── base.py        ← LLMClient ABC
│   ├── openai_client.py
│   └── anthropic_client.py
├── tools/
│   ├── __init__.py
│   ├── document.py    ← file reading, DocumentSummary model
│   └── costs.py       ← CostLogger, dashboard renderer
├── sessions/          ← created at runtime
├── cost_log.jsonl     ← created at runtime
├── .env
├── .env.example
├── .gitignore
└── pyproject.toml

Suggested build order

  1. Create the folder structure and empty module files.
  2. Implement llm/base.py, openai_client.py, anthropic_client.py. Test both clients with a simple call before touching the CLI.
  3. Implement tools/costs.py — CostLogger.log() writes a line; CostLogger.dashboard() reads and aggregates.
  4. Implement tools/document.py — file reader and DocumentSummary model.
  5. Build ai.py subcommand by subcommand: chat first, then analyse, then compare, then costs.
  6. Add session persistence.
  7. Add the compare mode’s asyncio concurrency.
  8. Polish error handling and help text.
asyncio for compare mode: Use asyncio.run() as the entry point and asyncio.gather() to run both provider calls in parallel. Each LLM client will need an async version of complete() — or you can run the synchronous clients in a ThreadPoolExecutor via loop.run_in_executor(). Either approach is acceptable.

Completion checklist

How to present this in an interview

When a hiring manager asks about your portfolio, lead with the architecture: “I built an LLM-provider abstraction layer so the calling code is completely provider-agnostic. Swapping between OpenAI and Anthropic is a single flag.”

Then walk them through the cost dashboard: every call is logged with latency, tokens, and cost, and the dashboard aggregates by provider and mode. This shows you think in terms of production economics, not just demos.

The compare mode is the conversation-starter: “I wanted to A/B test the same prompt across providers, so I built a concurrent comparison mode with asyncio.” This demonstrates async Python, which is a core skill for Chapter 3.

Extension challenges

Community Showcase

Finished this project? Share your GitHub repo link in the discussion below. Browse what others built to get inspiration and give feedback.