fix(a2a): declare a2a-sdk[http-server] so the a2a extra can serve - #6671
fix(a2a): declare a2a-sdk[http-server] so the a2a extra can serve#6671arunpshankar wants to merge 1 commit into
Conversation
The `a2a` extra installs `a2a-sdk` with no extras, which is enough to
import the package but not to run the server. `sse-starlette` is only
declared under a2a-sdk's `http-server`, `fastapi` and `all` extras, and
every server dispatcher imports it at module level:
a2a.server.routes.jsonrpc_dispatcher
a2a.server.routes.rest_dispatcher
a2a.compat.v0_3.jsonrpc_adapter
`to_a2a()` builds an `A2AStarletteApplication`, which routes through the
first of these, so a clean install of `google-adk[a2a]` raises
ModuleNotFoundError: No module named 'sse_starlette' at startup.
This is invisible during development because `mcp` requires
sse-starlette, so any environment that has ever installed the `mcp`
extra already satisfies it by accident. It surfaces on the first clean
install - typically a container - and the runtime reports a failed
startup probe on the port rather than the missing module.
Repro, from an empty venv:
pip install "a2a-sdk>=0.3.4,<2" # what the a2a extra installs
python -c "import a2a.server.routes.jsonrpc_dispatcher"
# ModuleNotFoundError: No module named 'sse_starlette'
pip install "a2a-sdk[http-server]>=0.3.4,<2"
python -c "import a2a.server.routes.jsonrpc_dispatcher" # ok
`http-server` rather than a direct `sse-starlette` pin because it states
the intent - ADK serves the A2A HTTP application - and leaves the
transitive set to a2a-sdk. The extra exists at the pinned floor (0.3.4)
and carries sse-starlette through the current 1.1.2, so it holds across
the whole `>=0.3.4,<2` range.
This is not the lazy-import case: the import is inside a2a-sdk, and it
is needed to serve, not merely to import. It also leaves
test_constructing_agent_defers_optional_mcp_server_stack unaffected -
that asserts sse_starlette is not imported when constructing an Agent,
which installing it does not change.
|
Stronger repro than the one in the description — this is the released Clean venv, from google.adk.agents.llm_agent import LlmAgent
from google.adk.a2a.utils.agent_to_a2a import to_a2a
agent = LlmAgent(name="policy_agent", model="gemini-2.5-flash",
description="d", instruction="i")
app = to_a2a(agent) # returns a Starlette app fine
async def boot():
async with app.router.lifespan_context(app):
...
asyncio.run(boot())Two details that make this worse than a plain missing dependency:
And it does not reproduce on most development machines, because |
Link to Issue or Description of Change
No existing issue.
Problem:
The
a2aextra installsa2a-sdkwith no extras. That is enough toimport the package but not to run the server:
sse-starletteis declaredonly under a2a-sdk's
http-server,fastapiandallextras, and everyserver dispatcher imports it at module level.
to_a2a()builds anA2AStarletteApplication, which routes througha2a.server.routes.jsonrpc_dispatcher, so a clean install ofgoogle-adk[a2a]fails at startup with:This stays invisible during development because
mcprequiressse-starlette, so any environment that has ever installed the
mcpextraalready satisfies it by accident. It surfaces on the first genuinely
clean install — typically a container — and the runtime then reports a
failed startup probe on the port rather than the missing module, which
sends you looking at networking instead of packaging.
Solution:
Declare
a2a-sdk[http-server]in thea2aextra.http-serverrather than a directsse-starlettepin because it statesthe intent — ADK serves the A2A HTTP application — and leaves the
transitive set to a2a-sdk to decide. The extra exists at the pinned floor
(0.3.4) and carries sse-starlette through the current 1.1.2, so it holds
across the whole
>=0.3.4,<2range.This is not the lazy-import case that some earlier dependency reports
turned out to be: the import lives inside a2a-sdk rather than in ADK, and
it is needed to serve, not merely to import.
Testing Plan
Unit Tests:
No unit test is added, and I want to be explicit about why rather than
add a token one: this is an install-time property, and CI installs the
extras it needs, so the failure is not observable from inside a test
session that can already import the module. Happy to add one if you would
like it — the natural home looks like the packaging-adjacent checks in
tests/unittests/test_import_loading.py.That file's
test_constructing_agent_defers_optional_mcp_server_stackasserts
sse_starletteis not imported when constructing anAgent.This change does not affect it: installing a package does not import it.
Manual End-to-End (E2E) Tests:
From an empty virtualenv, installing exactly what the
a2aextrainstalls today:
With this change:
pyproject-fmt==2.24.0, the version pinned in.pre-commit-config.yaml,reports
no change for pyproject.toml.Checklist