Repository navigation
test(harness): a Google ADK reference agent for the hosted path (TH-8809) - #305
khushalsonawat wants to merge 5 commits into
Conversation
…809) A small support agent on Google ADK 2.13.0, the same desk as the OpenAI Agents SDK and Pydantic AI references, written the ADK way: an app package with root_agent, served by adk web. Its model goes through the model gateway as LiteLlm openai/<model> with the gateway URL and key, so LiteLLM's OpenAI-compatible provider sends the routed name unchanged on chat completions. Its three tools are plain functions that ADK calls inline. Offline tests pin what the hosted path does with it: it is detected as google_adk; the build recognises the ADK server, app and port and binds the tool tracer to the three tools under support_agent.agent; the real ADK Runner, loaded as adk web loads it and driven by a stub model, makes tool calls that the real tracer records exactly; and the model wiring. Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
A clean install of the reference's requirements failed on the first model call with No module named 'mcp': LiteLLM imports it when a call carries tools, and neither google-adk nor litellm brings it in outside their extras. Pin mcp==2.3.0, which both allow. Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
| == ["root_agent"] | ||
| for node in module.body | ||
| ) | ||
| assert 'MODEL = os.getenv("OPENAI_MODEL", "vertex_ai/gemini-3.7-flash")' in source |
There was a problem hiding this comment.
P3. This matches source text, so a reformat of agent.py breaks it, and test_import_needs_no_environment... already checks agent.MODEL against the default. Delete the line.
| assert interface["response_path"] == "content.parts.0.text" | ||
|
|
||
| assert agent.environment["HARNESS_TOOL_TRACE"] == "{{WORLD_DIR}}/agent-tool-calls.jsonl" | ||
| bindings = json.loads(agent.environment["ALK_TOOL_TRACE_BINDINGS"]) |
There was a problem hiding this comment.
P3. The bindings this asserts are the ones the test writes into the contract itself, and _source_tool_trace_environment copies them through unchanged, so the assert compares the test's input with itself. Renaming return_policy in the example leaves this test green (three other tests catch it).
Fix: assert the bindings against the example's real functions, or drop the bindings claim from the test's name.
| DETECTED = {"name": "google_adk", "version": None} | ||
|
|
||
|
|
||
| def _copy_example(destination: Path) -> Path: |
There was a problem hiding this comment.
P3. Same copied setup as the other reference-agent PRs (_copy_example, the stand-in writer, the fresh-import fixture, the detection tests, the build test skeleton, the tracer driver, the pin tests). See the shared-helper comment on #301: this file would import from that one module instead.
…dk-reference Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…lpers, and check the tracer bindings against the real agent (TH-8809) The test file uses tests/harness/reference_agents.py from #301. The build test checks each tracer binding against support_agent.agent's real tools, so renaming a tool fails it, and the assert matching the model in the agent's source text is gone. Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
| name="support_agent", | ||
| model=LiteLlm( | ||
| model="openai/" + MODEL, | ||
| api_base=os.getenv("OPENAI_BASE_URL"), |
There was a problem hiding this comment.
P2. With OPENAI_API_KEY set and OPENAI_BASE_URL missing or empty, api_base is None and LiteLLM's openai/ provider falls back to https://api.openai.com/v1, so the gateway key is sent to OpenAI. #301 and #304 now refuse this case; this example does not, and README line 25 still documents the fallback.
Repro on the pinned google-adk 2.13.0 and litellm 1.106.2: import support_agent.agent with only OPENAI_API_KEY=gateway-canary-key, block httpx, and call root_agent.model.generate_content_async(...). Three requests go to https://api.openai.com/v1/chat/completions with Authorization: Bearer gateway-canary-key (LiteLLM retries).
Fix: read both variables once at the top of agent.py, raise RuntimeError naming OPENAI_BASE_URL when the key is set and the URL is not, and pass them to LiteLlm. An import with nothing set still works, so the existing tests stay green. Add a test that load_example(OPENAI_API_KEY="k"), and the same with OPENAI_BASE_URL="", raises without the key in the message, and change README line 25 to #301's wording.
| assert sorted(item["name"] for item in bindings) == sorted( | ||
| tool.__name__ for tool in example.root_agent.tools | ||
| ) | ||
| for item in bindings: |
There was a problem hiding this comment.
Summary
TH-8809 is part of T43: qualify the hosted harness across frameworks. This is Google ADK's offline half, on the same desk as the OpenAI Agents SDK (#301) and Pydantic AI (#304) references. A local end-to-end run with a real reading and a real model is summarised under Verification.
examples/harness/google_adk_support/is written the way ADK agents ship: an app packagesupport_agent/withroot_agent, served by ADK's own server (adk web --port 8080 .). The kit already runs that natively: it finds the app, moves the port per world, creates a session per conversation and posts each new message to/run_sse.LiteLlm(model="openai/" + MODEL, api_base=OPENAI_BASE_URL, api_key=OPENAI_API_KEY). LiteLLM's OpenAI-compatible provider sends the routed name (vertex_ai/gemini-3.7-flash) unchanged to the model gateway on chat completions. A barevertex_ai/...LiteLlm name would call Vertex directly, outside the gateway.support_agent.agent.google-adk==2.13.0,litellm==1.106.2andmcp==2.3.0. The pinned LiteLLM importsmcpon the first model call and neither other pin brings it in, so without it a clean install fails that call.adk webkeeps sessions undersupport_agent/.adk/, which the example's.gitignoreexcludes.tests/harness/test_google_adk_reference.pychecks:google_adkwith no blocker, as the run folder, in place and as a subfolder, and a shadowing module changes the result./run_sseprotocol, and binds the tracer to exactly the three tools, each checked againstsupport_agent.agent's real tools.Runner, loaded the wayadk webloads it and driven by a stub model, calls the tools, and the real tracer bootstrap records exactly those calls over two turns.No kit code changes. The tests use the shared helpers in
tests/harness/reference_agents.py.Stacked on #301 (
feat/th-8800-openai-agents-reference): it uses #301's shared test helpers, so #301 merges first, with a merge commit.Verification
ruff checkon the new filestest_bundle_author_v2.py. 326 passed.uv run pytest -q: not run, because no shared runtime is touched.sdk-smokeruns a fixed list of test files, which didn't include this one. It now does (tests/harness/test_google_adk_reference.py); it runs the kit's own ADK 2.7.1, a kit dependency.test_example_in_place_is_not_shadowed_by_the_repository) had a fix-only check: CLEAR, nothing above Low. Themcppin and the audit fixes (this CI-list line, and this description) had a fix-only check as well: CLEAR, nothing above Low.vertex_ai/gemini-3.7-flashthrough the model gateway): an earlier local step on a stub model had shown a clean install failing the first model call withNo module named 'mcp', hence themcppin. With it, ADK 2.13.0 gave 3 right replies, with Gemini tool turns through the gateway, and the trace file has the 3 calls. The reading chose a generic http route rather than the kit's native one, and the transcript recorded 5 calls for 3 (see the known limits).openai/prefix orapi_basedropped;__init__.py;litellmorroot_agentremoved;Review round 1 (Dileep)
support_agent.agenttools, so renaming or dropping a tool fails it.agent.py's source text is gone; the import test checksagent.MODEL.test_bundle_author_v2.py, 317 passed; the planted bugs (a renamed tool, the tracer without its hook) were caught.Known limits
adk api_serverisn't recognised as ADK's server until fix(harness): recognise adk api_server and its agents directory as ADK's HTTP server #259 lands; this example usesadk web.POST /runon fixed port 8080, not the kit's native route (port per world,/run_sse). Outside this PR; a register row.{}arguments (5 for 3). The runner uses the transcript's calls when there are any, so grading would see the duplicates. This is the kit's tool-evidence parsing, outside this PR; a register row.Checklist
agent_learning.*,agent-learn, or@future-agi/agent-learning-kit