Add incumbent-on-post example: agent + MCP, scorer, evals - #12
Merged
Conversation
When a new opportunity posts, brief the likely incumbent, prior award, and funding from one notice. This is the cookbook's first MCP integration — a PydanticAI agent drives the hosted Tango MCP rather than hand-wiring the SDK. The recipe is built around an honest arc: a fresh recompete gets a new solicitation number, so it never joins to the prior award on the number. The pivot is the contracting office, and the incumbent is whoever's award is expiring near the notice — an inference, so the output is a ranked shortlist with confidence and evidence, never a single asserted name, with "likely new work" as a first-class answer. - brief.py: PydanticAI agent over the hosted Tango MCP, returning a typed IncumbentBrief (the easy version). - scorer.py: deterministic candidate ranking (expiry-window x dollars x recurrence), bounded server-side — reproducible and eval-able (the honest version). - evals.py: structural invariants on the scorer; runs without an LLM key. - demo.svg: self-contained pipeline diagram built from a real run. - just incumbent / just incumbent-evals; pydantic-ai-slim added to the examples group; README tables updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Let brief.py run against any OpenAI-compatible server (LM Studio, Ollama, vLLM) in addition to the hosted Anthropic default. Set OPENAI_BASE_URL + MODEL to point at a local server; ANTHROPIC_API_KEY is then no longer required. Adds the openai extra to pydantic-ai-slim. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Note the two independent "local" axes (MCP server vs. model) and show how to point the agent at an OpenAI-compatible server via OPENAI_BASE_URL + MODEL. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A new example,
examples/incumbent-on-post/: when a new opportunity hits SAM.gov, brief the likely incumbent, the prior award value, and whether the money is still flowing — from one notice.It's the cookbook's first MCP integration. Every other example hand-wires the
tango-pythonSDK; here the tool catalog comes from the hosted Tango MCP server (https://govcon.dev/mcp) and a PydanticAI agent drives it.Why this shape
The obvious join — match the new opportunity to the prior contract on the solicitation number — doesn't work: a fresh recompete gets a brand-new number; the predecessor award carries the old one. There's no clean key. So the recipe pivots on the contracting office (the thing that persists across a recompete) and ranks candidates by whose award is expiring near the notice — not by who won most recently. Because that's an inference, the output is a ranked shortlist with confidence + evidence, never a single asserted name, with "likely new work" as a first-class answer.
The files
brief.pyIncumbentBrief(the easy version).scorer.pyexpiring_gte/lte— reproducible, eval-able (the honest version).evals.pysample_notice.jsondemo.svgPlus:
just incumbent/just incumbent-evals,pydantic-ai-slim[anthropic,mcp,openai]added to the examples dependency group, and both README tables updated.Model: hosted or local
The agent uses the hosted Anthropic model by default, but runs against any OpenAI-compatible server (LM Studio, Ollama, vLLM) by setting
OPENAI_BASE_URL+MODEL— at which pointANTHROPIC_API_KEYisn't needed. The MCP server (hosted vs. local stdio) is an independent axis. Both are documented in the example README.Testing
just incumbent-evals— all scorer invariants hold against the live API (top court-reporting incumbent at VA NCO 5 resolves to Vet Reporting LLC; far-future window correctly returns "new work").just incumbent— agent connects to the hosted MCP and returns a structured 3-candidate brief, independently agreeing with the scorer's top pick, and catching a real office-code disambiguation (36C250 vs 36C245).demo.svgvalidates as XML.Per repo convention, this example is not run in CI (it calls a paid LLM and a live API);
evals.pyis deterministic but still hits the live API.Risks / notes
demo.svghardcodes the sample brief's specifics (vendor names, 2029 recompete date, −3% funding figure). Ifsample_notice.jsonchanges, the diagram drifts from a fresh run — kept real for impact; easy to genericize if preferred.get_budget_account+ quarterly cash flow is a documented next step.🤖 Generated with Claude Code