{"slug": "case-study-pydantic-ai-tool-context", "title": "Case Study: Pydantic AI Tool Context", "summary": "A case study comparing Codex GPT-5.5 runs with and without GitHits found that using GitHits reduced token usage by 508,192 and saved 90 seconds when fixing Pydantic AI tools to use context-aware patterns. The fix involved changing tool decorators from @agent.tool_plain to @agent.tool with RunContext[SupportDeps] to enable per-run dependency access for tenant routing.", "body_md": "[Back to blog](/blog/)\n\nJune 14, 2026 · 4 min read\n\n# Case Study: Pydantic AI Tool Context\n\nA measured Codex run fixing Pydantic AI tools that ignored per-run dependencies.\n\nThe fixture is a Pydantic AI support-routing agent.\n\nEach run passes tenant-local data through Pydantic AI dependencies: tenant,\nregion, ticket priorities, and escalation policies. The generated code created\nthe agent with `deps_type=SupportDeps`\n\n, but registered both tools with\n`@agent.tool_plain`\n\n. Those tools cannot receive `RunContext`\n\n, so they returned\nglobal fallback values.\n\nBoth runs used Codex GPT-5.5 against the same fixture. The prompt was:\n\n```\nFix this Python fixture so `pytest` succeeds, preserving tenant-aware Pydantic AI routing tools.\n```\n\nThe target package was `pydantic-ai 2.0.0b7`\n\n.\n\nCase study replay\n\n## Pydantic AI tenant routing tools\n\nmodel Codex GPT-5.5Fix this Python fixture so `pytest` succeeds, preserving tenant-aware Pydantic AI routing tools.\n\nWithout GitHits\n\n- tokens\n- 0\n- time\n- 0s / 189s\n\n- Ready. Click \"Watch Replay\" to start.\n- The Pydantic AI tools now use @agent.tool with RunContext[SupportDeps], so tenant, region, priorities, and escalation policies come from the active per-run dependencies.\n\nWith GitHits\n\n- tokens\n- 0\n- time\n- 0s / 99s\n\n- Ready. Click \"Watch Replay\" to start.\n- Used GitHits to confirm Pydantic AI 2.0.0b7's context-aware tool pattern, then changed the two tools from tool_plain to tool with RunContext[SupportDeps].\n\n## Result\n\n| Run | Time | Tokens | Tools |\n|---|---|---|---|\n| With GitHits | 99s | 393,469 | 21 |\n| Without GitHits | 189s | 901,661 | 28 |\n\nBoth runs produced a passing patch. The GitHits run used 508,192 fewer processed tokens and finished 90 seconds sooner.\n\n## Failure\n\nThe tools were registered with `tool_plain`\n\n:\n\n``` php\n@agent.tool_plain\ndef lookup_ticket(ticket_id: int) -> str:\n    return f\"{DEFAULT_TENANT}:{ticket_id}:{DEFAULT_PRIORITY}\"\n\n@agent.tool_plain\ndef escalation_policy(ticket_id: int) -> str:\n    return (\n        f\"{DEFAULT_TENANT}:{DEFAULT_PRIORITY}:\"\n        f\"{DEFAULT_POLICY}:{DEFAULT_REGION}\"\n    )\n```\n\n`tool_plain`\n\nis correct for tools that do not need the run context. These tools\ndepend on `SupportDeps`\n\n.\n\nThe tests checked data flow:\n\n`acme`\n\nand`globex`\n\nmust route differently.`lookup_ticket`\n\nand`escalation_policy`\n\nmust read the same per-run dependencies.- Unknown tickets should still fall back to\n`normal`\n\nand`standard`\n\n, but the fallback must keep the active tenant and region.\n\n## Fix\n\nUse context-aware tools and read `ctx.deps`\n\n:\n\n``` python\nfrom pydantic_ai import Agent, RunContext\n\n@agent.tool\ndef lookup_ticket(ctx: RunContext[SupportDeps], ticket_id: int) -> str:\n    priority = ctx.deps.priorities.get(ticket_id, DEFAULT_PRIORITY)\n    return f\"{ctx.deps.tenant}:{ticket_id}:{priority}\"\n\n@agent.tool\ndef escalation_policy(ctx: RunContext[SupportDeps], ticket_id: int) -> str:\n    priority = ctx.deps.priorities.get(ticket_id, DEFAULT_PRIORITY)\n    policy = ctx.deps.escalation_policies.get(priority, DEFAULT_POLICY)\n    return f\"{ctx.deps.tenant}:{priority}:{policy}:{ctx.deps.region}\"\n```\n\nThe patch depends on three package facts:\n\n`@agent.tool`\n\nis the decorator for tools that receive`RunContext`\n\n.`RunContext[SupportDeps]`\n\ngives the tool access to the active dependency object.- Both tools need to read\n`ctx.deps`\n\n; fixing only one still leaves inconsistent routing.\n\n## Trace\n\nThe replay shows seven GitHits tool calls:\n\n- Two\n`search`\n\ncalls for Pydantic AI docs around`agent.tool`\n\n,`RunContext`\n\n,`deps`\n\n, and`tool_plain`\n\n. - Three\n`docs_read`\n\ncalls on the current tools documentation. - One\n`code_grep`\n\ncall for the`def tool(`\n\nimplementation. - One\n`code_read`\n\ncall on the package source where`Agent.tool`\n\nis defined.\n\nThose calls gave the agent the package contract before editing: use\n`@agent.tool`\n\nwhen a tool needs `RunContext`\n\n, then access per-run dependencies\nthrough `ctx.deps`\n\n.\n\nThe no-GitHits run had to reconstruct the same information from the local environment. It searched installed `pydantic_ai`\n\ninternals, found the package path, read exports, read agent source, read run-context source, patched, tested, cleaned local artifacts, reread the file, and tested again.\n\nThat local probing accounts for most of the 508k-token gap.\n\n## Evidence\n\nA passing fixture test proves the local behavior for the tested cases. The docs and source establish that the patch uses the intended Pydantic AI mechanism.\n\nThe GitHits trace had a short evidence chain:\n\n- Docs showed the context-aware tool pattern for the current package.\n- Source search found the\n`Agent.tool`\n\nimplementation surface. - Source reads confirmed that this was the right API boundary.\n`pytest`\n\nverified tenant-specific behavior in the fixture.\n\nThe package has multiple valid tool decorators. The evidence points to the one that matches the data-flow requirement.\n\nThe final patch did not rewrite the routing model, change the tests, or move tenant data into prompts. It changed the decorator and dependency access in the two tools.\n\n## Accuracy Risk\n\nThe incorrect alternatives are close to the correct patch:\n\n- Keep\n`tool_plain`\n\nand add globals. - Put tenant data into the prompt.\n- Capture dependencies in a closure instead of using Pydantic AI’s run context.\n- Fix\n`lookup_ticket`\n\nbut leave`escalation_policy`\n\non defaults. - Change the tests to match global fallback behavior.\n\nAll of those preserve the original bug or create a more brittle fixture.\n\nThe GitHits run found the package mechanism in docs and source before editing. The no-GitHits run found the same mechanism by probing the installed package.", "url": "https://wpnews.pro/news/case-study-pydantic-ai-tool-context", "canonical_source": "https://githits.com/blog/pydantic-ai-tenant-routing-case-study/", "published_at": "2026-06-14 00:00:00+00:00", "updated_at": "2026-06-16 07:54:38.751228+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "large-language-models"], "entities": ["Pydantic AI", "Codex GPT-5.5", "GitHits", "SupportDeps", "RunContext"], "alternates": {"html": "https://wpnews.pro/news/case-study-pydantic-ai-tool-context", "markdown": "https://wpnews.pro/news/case-study-pydantic-ai-tool-context.md", "text": "https://wpnews.pro/news/case-study-pydantic-ai-tool-context.txt", "jsonld": "https://wpnews.pro/news/case-study-pydantic-ai-tool-context.jsonld"}}