Tutorials · Claude Agent SDK · examples / python / p3_workflow_patterns.pyExample code

Example code · python

p3_workflow_patterns.py

The classic workflow patterns as code: routing, prompt chaining with a gate, and voting.

Raw fileDownload all examples (zip)See its output →

Used in Part 4 §3 · The classic workflow patterns, with code and output

"""Pattern 3: the classic workflow patterns as code: routing, prompt chaining with a gate, and voting."""

import asyncio
import json

from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query
from demo_util import PROJECT


async def run(prompt, schema=None, **opts):
    """One node: a fresh-context agent. Returns typed JSON when a schema is given, else text."""
    options = ClaudeAgentOptions(cwd=PROJECT, setting_sources=[], max_turns=opts.pop("max_turns", 5),
                                 output_format={"type": "json_schema", "schema": schema} if schema else None, **opts)
    async for m in query(prompt=prompt, options=options):
        if isinstance(m, ResultMessage):
            return (m.structured_output if schema else m.result), (m.total_cost_usd or 0)


# ---------- 1. Routing: a cheap classifier picks the specialist -------------------------------
ROUTE_SCHEMA = {"type": "object", "properties": {"kind": {"type": "string", "enum": ["bug", "docs", "question"]},
                                                 "reason": {"type": "string"}}, "required": ["kind", "reason"]}
SPECIALISTS = {
    "bug":      dict(allowed_tools=["Read", "Grep", "Glob"], max_turns=8, system_prompt="You are a debugger. Locate the defect and describe the fix in two sentences."),
    "docs":     dict(allowed_tools=["Read", "Glob"],         system_prompt="You are a technical writer. Answer in two sentences."),
    "question": dict(tools=[],                               system_prompt="Answer from general knowledge in one sentence."),
}


async def routed(request):
    route, c1 = await run(f"Classify this request: {request!r}", ROUTE_SCHEMA, tools=[], model="claude-haiku-5-5", effort="low")
    answer, c2 = await run(request, **SPECIALISTS[route["kind"]])
    print(f"[route] {request!r}\n   -> kind={route['kind']} ({route['reason']})\n   -> {answer}\n   cost=${c1 + c2:.4f}")


# ---------- 2. Prompt chaining with a programmatic gate -----------------------------------------
OUTLINE_SCHEMA = {"type": "object", "properties": {"sections": {"type": "array", "items": {"type": "string"}, "minItems": 3}},
                  "required": ["sections"]}


async def chained():
    outline, c1 = await run("Outline a README for this project in 3-5 section titles.", OUTLINE_SCHEMA, allowed_tools=["Read", "Glob"])
    assert 3 <= len(outline["sections"]) <= 5, "gate failed"                       # deterministic gate between steps
    doc, c2 = await run("Write one sentence for each of these README sections, as a markdown list:\n"
                        + json.dumps(outline["sections"]), tools=[])
    print(f"[chain] outline={outline['sections']}\n{doc}\n   cost=${c1 + c2:.4f}")


# ---------- 3. Parallelization: voting -----------------------------------------------------------
VOTE_SCHEMA = {"type": "object", "properties": {"safe": {"type": "boolean"}, "reason": {"type": "string"}},
               "required": ["safe", "reason"]}


async def voted():
    prompt = "Is src/app.py safe to deploy to production as-is? Answer safe=true/false with a one-sentence reason."
    results = await asyncio.gather(*(run(prompt, VOTE_SCHEMA, allowed_tools=["Read"], effort="low") for _ in range(3)))
    votes = [r[0]["safe"] for r in results]
    print(f"[vote] votes={votes} -> majority safe={sum(votes) > 1}")
    for (v, c) in results:
        print(f"   - safe={v['safe']}: {v['reason']}")
    print(f"   cost=${sum(c for _, c in results):.4f}")


async def main():
    await routed("calculate_average crashes on an empty list, where is the bug?")
    await routed("What is the difference between a list and a tuple in Python?")
    await chained()
    await voted()


asyncio.run(main())

71 lines · Back to the folder