diff --git a/pyproject.toml b/pyproject.toml index ca33e11..6153613 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ # deployed runtime stays inside it as well. # The mcp extra is what makes mcp: tools work; without it the toolset # module fails to import where the agent is deployed. - "google-adk[mcp]>=2.6,<2.10", + "google-adk[mcp]>=2.6,<2.15", # Signs a GitHub App's JWT for the app connections. ADK brings it in # already; it is named because gete imports it itself. "cryptography>=43", diff --git a/tests/test_runtime_build.py b/tests/test_runtime_build.py index 58ed998..12aa9d3 100644 --- a/tests/test_runtime_build.py +++ b/tests/test_runtime_build.py @@ -514,6 +514,15 @@ async def dispatched(agent: Any, called: str) -> Any: tools it resolved for the turn, the callback it awaits - so one test drives the real thing and fails if any of that moves. """ + return (await dispatched_together(agent, [called]))[0] + + +async def dispatched_together(agent: Any, called: list[str]) -> list[Any]: + """What ADK answers function calls the model made in one turn. + + ADK runs calls from one turn concurrently, so this is where calls can + interleave. They get ids call-1, call-2, ... in the order given. + """ from google.adk.agents.invocation_context import InvocationContext from google.adk.agents.run_config import RunConfig from google.adk.flows.llm_flows.functions import handle_function_call_list_async @@ -539,11 +548,14 @@ async def dispatched(agent: Any, called: str) -> Any: assert invocation.canonical_tools_cache is not None event = await handle_function_call_list_async( invocation, - [types.FunctionCall(id="call-1", name=called, args={})], + [ + types.FunctionCall(id=f"call-{number}", name=name, args={}) + for number, name in enumerate(called, start=1) + ], request.tools_dict, ) assert event is not None - return event.content.parts[0].function_response.response + return [part.function_response.response for part in event.content.parts] async def test_adk_answers_a_made_up_name_with_the_tools_it_resolved( @@ -562,6 +574,43 @@ async def test_adk_answers_a_made_up_name_with_the_tools_it_resolved( } +def write_call_reading_module(directory: Path) -> None: + package = directory / "src" / "call_reader" + package.mkdir(parents=True) + (package / "__init__.py").write_text("") + (package / "tools.py").write_text( + "import asyncio\n" + "\n" + "from gete.request_context import current_tool_call\n" + "\n" + "async def whose_call() -> dict:\n" + ' """Name the call this tool runs in."""\n' + " await asyncio.sleep(0.01)\n" + " call = current_tool_call()\n" + ' return {"call": call.tool_context.function_call_id if call else None}\n' + "\n" + "TOOLS = [whose_call]\n" + ) + + +async def test_each_call_in_a_turn_sees_only_the_call_adk_bound_it_to( + project: ProjectBuilder, +) -> None: + """Through ADK: what the callback binds reaches the tool body, and two + calls running together do not see each other's - the user's tokens + travel this way to tools that never get a context.""" + path = resolved_path( + project, + "mail-triage", + {"source": "./src", "tools": [{"python": "call_reader.tools:TOOLS"}]}, + ) + write_call_reading_module(path.parent) + assert await dispatched_together(build(path), ["whose_call", "whose_call"]) == [ + {"call": "call-1"}, + {"call": "call-2"}, + ] + + async def test_an_arbitrary_gete_error_is_generic_like_any_other( project: ProjectBuilder, ) -> None: diff --git a/uv.lock b/uv.lock index d605517..d6b141a 100644 --- a/uv.lock +++ b/uv.lock @@ -735,7 +735,7 @@ dev = [ requires-dist = [ { name = "click", marker = "extra == 'cli'", specifier = ">=8.1" }, { name = "cryptography", specifier = ">=43" }, - { name = "google-adk", extras = ["mcp"], specifier = ">=2.6,<2.10" }, + { name = "google-adk", extras = ["mcp"], specifier = ">=2.6,<2.15" }, { name = "google-auth", marker = "extra == 'cli'", specifier = ">=2.38" }, { name = "httpx", specifier = ">=0.28" }, { name = "jsonschema", specifier = ">=4.23" }, @@ -755,12 +755,13 @@ dev = [ [[package]] name = "google-adk" -version = "2.9.2" +version = "2.14.0" source = { registry = "https://pypi.flatt.tech/simple/" } dependencies = [ { name = "aiosqlite" }, { name = "authlib" }, { name = "click" }, + { name = "cryptography" }, { name = "fastapi" }, { name = "google-auth", extra = ["pyopenssl"] }, { name = "google-genai" }, @@ -782,9 +783,9 @@ dependencies = [ { name = "watchdog" }, { name = "websockets" }, ] -sdist = { url = "https://pypi.flatt.tech/files/packages/db/08/7ac5e31305b9c0e1714af8ea8c1a5df16e980090eed5e5e456dc8fe5b583/google_adk-2.9.2.tar.gz", hash = "sha256:9d4b91a3cc3a52a471ca5831127e4c46ac174da0db519b4d157a09011737da10", size = 4002237, upload-time = "2026-09-18T18:10:46.112Z" } +sdist = { url = "https://pypi.flatt.tech/files/packages/ca/b4/79b0b2140d45274a7d0e502cf0aa8d01d671e9c2f35d2eb96df2165d8c96/google_adk-2.14.0.tar.gz", hash = "sha256:c6762050a8d1edac10f03730f99500733e862b729422dbd63187154dc470b7f0", size = 4574051, upload-time = "2026-11-07T01:03:34.755Z" } wheels = [ - { url = "https://pypi.flatt.tech/files/packages/23/a6/e375f8a124e28f31c0cd0f9ff8607d7bdc652f8ce4bc67c39863353b4c6d/google_adk-2.9.2-py3-none-any.whl", hash = "sha256:ad887aadff34ebfe06586ded2b04848413c8bed6d6c7dd14dc7485ec7e54b2d2", size = 4631024, upload-time = "2026-09-18T18:10:43.492Z" }, + { url = "https://pypi.flatt.tech/files/packages/9f/06/4329fd6fda8c85ad3f70256d502d45d636a125e85fb4a85848a98d061938/google_adk-2.14.0-py3-none-any.whl", hash = "sha256:f7d8c79bafc4f9215402219c9542db0bde702952448a09ffd51c5bd22b135b0e", size = 5302590, upload-time = "2026-11-07T01:03:32.405Z" }, ] [package.optional-dependencies] @@ -817,7 +818,7 @@ requests = [ [[package]] name = "google-genai" -version = "2.22.0" +version = "2.34.0" source = { registry = "https://pypi.flatt.tech/simple/" } dependencies = [ { name = "anyio" }, @@ -831,9 +832,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://pypi.flatt.tech/files/packages/37/f1/f2f31b2a6bd826bc2cb73068df880954a026474c03a4315637d20cc13965/google_genai-2.22.0.tar.gz", hash = "sha256:9fa3b5d9ddb635005d8ab2d6206fb2b3d7204b66965bbce7de13ecd1a866ebcd", size = 684719, upload-time = "2026-09-02T18:06:02.906Z" } +sdist = { url = "https://pypi.flatt.tech/files/packages/22/89/4621aee9cb183efa5b8a187f0e315c49fddcfb8cd7f238b4a3bf4946dcd0/google_genai-2.34.0.tar.gz", hash = "sha256:8e182cf3f73384167b96861cf0aef5102b5070f2db1ea11002879e913806ef37", size = 791853, upload-time = "2026-10-29T06:43:23.706Z" } wheels = [ - { url = "https://pypi.flatt.tech/files/packages/de/96/0120d214958cb54f2b9c48da9f564a0e6eae269e9dd702415fb1d0551cc7/google_genai-2.22.0-py3-none-any.whl", hash = "sha256:c514001c45470cc0a942440ae1b8215445d12bfb6c373aac94637127e1f74ec6", size = 1088792, upload-time = "2026-09-02T18:06:00.629Z" }, + { url = "https://pypi.flatt.tech/files/packages/2e/42/c24db865d9231b1c672ea26242a4dcf340e200bb1a9823dce4aa9c4c07a4/google_genai-2.34.0-py3-none-any.whl", hash = "sha256:a4cb02aebc4c357f863d2938132f0e3f7967fc5e00b8a4078673f68aad340665", size = 1258155, upload-time = "2026-10-29T06:43:21.121Z" }, ] [[package]] @@ -1277,41 +1278,41 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.42.1" +version = "1.45.2" source = { registry = "https://pypi.flatt.tech/simple/" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://pypi.flatt.tech/files/packages/b4/1c/125e1c936c0873796771b7f04f6c93b9f1bf5d424cea90fda94a99f61da8/opentelemetry_api-1.42.1.tar.gz", hash = "sha256:56c63bea9f77b62856be8c47600474acad853b2924b99b1687c4cb6297166716", size = 72296, upload-time = "2026-05-21T16:32:49.335Z" } +sdist = { url = "https://pypi.flatt.tech/files/packages/19/14/cf3a6b6448849656e1e5f4e2101f3988dc6789724050096aba1bc5ccf592/opentelemetry_api-1.45.2.tar.gz", hash = "sha256:d7b0008e21e5fd61cbe7a7f34ca3b6905a78ab677d9bc395e299359c75cda6b4", size = 73277, upload-time = "2026-10-29T17:29:37.888Z" } wheels = [ - { url = "https://pypi.flatt.tech/files/packages/a3/ca/9520cc1f3dfbbd03ac5903bbf55833e257bc64b1cf30fa8b0d6df374d821/opentelemetry_api-1.42.1-py3-none-any.whl", hash = "sha256:51a69edacadbc03a8950ace1c4c21099cacc538820ac2c9e36277e78cebba714", size = 61311, upload-time = "2026-05-21T16:32:28.822Z" }, + { url = "https://pypi.flatt.tech/files/packages/a9/2d/610db864d7513158cebb8f36cc88e67e73bd6c7aa1905ef18d89e1281c81/opentelemetry_api-1.45.2-py3-none-any.whl", hash = "sha256:2cbd737377e710ee740d261c94e93fa0f08e6d037c18f17513cc0a2d94479a34", size = 60335, upload-time = "2026-10-29T17:29:06.955Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.42.1" +version = "1.45.2" source = { registry = "https://pypi.flatt.tech/simple/" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://pypi.flatt.tech/files/packages/40/f7/b390bd9bfd703bf98a68fea1f27786c6872331fd617164a54b8a59bdc008/opentelemetry_sdk-1.42.1.tar.gz", hash = "sha256:8c834e8f8c9ba4171d4ec843d0cb8a67e4c7394d3f9e9297e582cbd9456ddbf7", size = 239262, upload-time = "2026-05-21T16:33:04.641Z" } +sdist = { url = "https://pypi.flatt.tech/files/packages/7b/1a/74a21d4d6969fb8b432ac397e66112a8167a6deb267ea4559dbaf2fe91ef/opentelemetry_sdk-1.45.2.tar.gz", hash = "sha256:7eec4de13dd21ff3a1670dcb0e02079e692736f94d733dfdce5af461d884248d", size = 221705, upload-time = "2026-10-29T17:29:54.685Z" } wheels = [ - { url = "https://pypi.flatt.tech/files/packages/8f/6b/4287766cfbde577ae2272e8884abac325aeaac0d64f41c61d5b8cc595105/opentelemetry_sdk-1.42.1-py3-none-any.whl", hash = "sha256:083cd4bbfaa5aa7b5a9e552430d9951219967cfb27aa61feb13a77aba1fc839d", size = 170907, upload-time = "2026-05-21T16:32:45.894Z" }, + { url = "https://pypi.flatt.tech/files/packages/9f/a5/9a3b793ef48245f2336a16beb2324c2397dbb3f987cd68942f77cca06156/opentelemetry_sdk-1.45.2-py3-none-any.whl", hash = "sha256:ff974ea70ee4f1d51e2ec152db37c2b626c7bf82bb3f7d86114fd8704e7199c6", size = 141756, upload-time = "2026-10-29T17:29:33.722Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.63b1" +version = "0.66b2" source = { registry = "https://pypi.flatt.tech/simple/" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://pypi.flatt.tech/files/packages/93/99/4d7dd6df64795951413ce6e815f8cf1eb191daf7196ae86574589643d5f3/opentelemetry_semantic_conventions-0.63b1.tar.gz", hash = "sha256:3daf963611334b365e98a57438183eb012d3bfb40b2d931a9af613476b8701a9", size = 148340, upload-time = "2026-05-21T16:33:05.455Z" } +sdist = { url = "https://pypi.flatt.tech/files/packages/37/3f/50f95972b50e8e353f5b9342034c9cd17730f1956f009ce6a3fbbd89f9ce/opentelemetry_semantic_conventions-0.66b2.tar.gz", hash = "sha256:7da5f1a5dc4383eb66711d4d3d3e71824d9403ac1559fea021cc103c1a4c4976", size = 150246, upload-time = "2026-10-29T17:29:55.682Z" } wheels = [ - { url = "https://pypi.flatt.tech/files/packages/cb/7a/7fe66f5f3682b1dd47d88cc4e11f1c6c0966b737de2d16671146e23c39a5/opentelemetry_semantic_conventions-0.63b1-py3-none-any.whl", hash = "sha256:dfe5ef4dee82586b746f522b818ceb298d00b3d59f660042bd79404bff8d0682", size = 203713, upload-time = "2026-05-21T16:32:47.016Z" }, + { url = "https://pypi.flatt.tech/files/packages/67/bd/6bf33ed31521c565931c037e9e81bac47c1acb000e850c6e24220e2a0c00/opentelemetry_semantic_conventions-0.66b2-py3-none-any.whl", hash = "sha256:4cbe61e5f9275d57ab31708446e7938ef4291ebbe6a7fca6f7a629c5acae87d8", size = 206280, upload-time = "2026-10-29T17:29:35.062Z" }, ] [[package]]