A small project that exercises the Claude API in three increasingly complete
stages: plain Messages calls, hand-written tool use, and streaming. Built to
map directly onto docs.claude.com/en/api/overview (Messages, tool use,
streaming).
pip install -r requirements.txt
export ANTHROPIC_API_KEY=sk-ant-... # from platform.claude.com/settings/keys| File | What it teaches |
|---|---|
mock_data.py |
Fake tickets, customer records, system status — no external deps needed |
stage1_classify.py |
Basic Messages.create() call, system prompts, getting reliable structured JSON out of the model |
stage2_tools.py |
Hand-written tool-use loop: model requests a tool → you execute it → you send tool_result back → repeat until stop_reason != "tool_use" |
stage3_streaming.py |
Same loop, but streamed — including accumulating input_json_delta fragments into a complete tool call before executing it |
Run any stage directly, e.g.:
python stage1_classify.py
python stage2_tools.py
python stage3_streaming.py- Stage 1: models occasionally ignore "no markdown fences" — the defensive
JSON parsing in
classify_ticket()is there on purpose, not decoration. - Stage 2: tool results are sent back as a
userrole message, notassistantortool— easy to get backwards coming from other APIs. Also notice the loop has amax_turnsguard — always bound agent loops. - Stage 3: the tricky part is that
input_json_deltaevents give you fragments of a JSON string, not parsed objects — you only get a valid tool call once you've accumulated and parsed the full string atcontent_block_stop.
- Swap
mock_data.pyfor a real API (e.g. a public REST API) to see how error handling changes with real network failures. - Add a third tool that can fail (e.g. a lookup that sometimes 404s) and handle that failure gracefully inside the loop — this is the kind of resilience question that comes up in architecture discussions.
- Try the same flow against Claude on Bedrock or Vertex to see what changes in auth and request shape versus the direct API.