Skip to content

Commit 08209ee

Browse files
tianttclaudeyaozheng-fang
authored
feat: ADK 1.19/2.0 cross-version regression suite + lazy-import compat fixes (#559)
* update new feature and enhancement for log * feat: add ADK 1.19/2.0 cross-version regression suite and lazy-import compat fixes Adds a 44-test regression suite targeting every veadk↔ADK seam where 1.19 and 2.0 differ, and fixes two compat bugs the suite caught when run against ADK 2.0. New tests (tests/test_adk_compat_regression.py) ----------------------------------------------- - A. Direct coverage of every public helper in veadk.utils.adk_compat that test_adk_compat.py didn't already exercise (get_adk_version, is_adk_gte, should_use_async_db_drivers, llm_request_has_field, plus the getter path for get_event_function_responses). - B. Edge cases for the event extractors: parts=None / empty / mixed, no content, broken getters falling back to part traversal. - C. Integration against real ADK Event / Content / Part objects so we notice if ADK silently changes its public surface. - D. Agent.run override gated by is_adk_gte("2.0.0") (skipped on v2 by design; verifies NotImplementedError still surfaces on v1). - E. ArkLlm version-branching: ImportError when LlmRequest lacks previous_interaction_id; get_previous_interaction_id on real LlmRequest. - F. tool_attributes_extractors fallback variants (model_dump path, empty sentinel, attribute object, missing attrs). - G. Runner.intercept_new_message integration with a synthetic LLM: session_service is InMemorySessionService for local STM, create_session contract, end-to-end yield, None-event filtering, part.text=None tolerance. - H. ADK public-surface assumptions (version module, LlmRequest.model_fields, Event.get_function_calls/responses). Compat fixes caught by running the suite under ADK 2.0 ------------------------------------------------------ - veadk/agent.py: gate the legacy Agent.run NotImplementedError override on `not is_adk_gte("2.0.0")`. ADK 2.0 promotes BaseAgent.run to a @Final async generator that the Workflow/NodeRunner engine invokes internally; overriding it breaks workflow execution. - veadk/utils/patches.py: walk `mod.__dict__` instead of `dir(mod)` + `getattr` when patching tracers across google.adk.* modules. ADK 2.0's `google.adk.tools` package defines `__getattr__` for lazy submodule loading; the old dir+getattr pattern triggered every lazy module, including optional ones like discovery_engine_search_tool that need google-cloud-* deps veadk doesn't ship. - veadk/evaluation/eval_set_recorder.py: defer the `from google.adk.cli.utils import evals` import to the call site. ADK 2.0 has that module top-import gcs_eval_set_results_manager unconditionally, which requires google-cloud-storage — needlessly tainting any veadk caller who only imports veadk.Runner. Regression results ------------------ Both versions show identical counts; each skips exactly the one test that doesn't apply to its API surface (the suite is symmetric across versions): * ADK 1.19.0: 195 passed + 1 skipped (skip: test_get_previous_interaction_id_with_real_llm_request — v1 LlmRequest lacks previous_interaction_id) * ADK 2.0.0: 195 passed + 1 skipped (skip: test_agent_run_raises_notimplemented_on_legacy_adk — override is removed on v2 by design) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: add Apache-2.0 header to adk_compat.py and apply ruff format - Add the missing license header to veadk/utils/adk_compat.py so the license-header-check CI passes. - Reformat the new compat test files and eval_set_recorder.py with ruff-format (v0.11.12) so the pre-commit CI passes. --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: fangyaozheng@bytedance.com <fangyaozheng@bytedance.com>
1 parent 4bfdcf5 commit 08209ee

14 files changed

Lines changed: 1051 additions & 52 deletions

File tree

tests/test_adk_compat.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from types import SimpleNamespace
16+
17+
from veadk.memory.short_term_memory_backends.mysql_backend import MysqlSTMBackend
18+
from veadk.memory.short_term_memory_backends.postgresql_backend import (
19+
PostgreSqlSTMBackend,
20+
)
21+
from veadk.memory.short_term_memory_backends.sqlite_backend import SQLiteSTMBackend
22+
from veadk.tracing.telemetry.attributes.extractors.tool_attributes_extractors import (
23+
tool_gen_ai_tool_output,
24+
)
25+
from veadk.tracing.telemetry.attributes.extractors.types import ToolAttributesParams
26+
import veadk.utils.adk_compat as adk_compat
27+
28+
29+
def test_get_previous_interaction_id_missing_field():
30+
llm_request = SimpleNamespace()
31+
assert adk_compat.get_previous_interaction_id(llm_request) is None
32+
33+
34+
def test_get_previous_interaction_id_with_field():
35+
llm_request = SimpleNamespace(previous_interaction_id="interaction_123")
36+
assert adk_compat.get_previous_interaction_id(llm_request) == "interaction_123"
37+
38+
39+
def test_get_event_function_calls_from_getter():
40+
expected_calls = [SimpleNamespace(name="tool_a")]
41+
42+
class Event:
43+
def get_function_calls(self):
44+
return expected_calls
45+
46+
calls = adk_compat.get_event_function_calls(Event())
47+
assert calls == expected_calls
48+
49+
50+
def test_get_event_function_calls_fallback_to_parts():
51+
part1 = SimpleNamespace(function_call=SimpleNamespace(name="tool_1"))
52+
part2 = SimpleNamespace(function_call=None)
53+
event = SimpleNamespace(content=SimpleNamespace(parts=[part1, part2]))
54+
55+
calls = adk_compat.get_event_function_calls(event)
56+
assert len(calls) == 1
57+
assert calls[0].name == "tool_1"
58+
59+
60+
def test_get_event_function_calls_getter_error_fallback_to_parts():
61+
class Event:
62+
content = SimpleNamespace(
63+
parts=[SimpleNamespace(function_call="fallback_call")]
64+
)
65+
66+
def get_function_calls(self):
67+
raise RuntimeError("broken getter")
68+
69+
calls = adk_compat.get_event_function_calls(Event())
70+
assert calls == ["fallback_call"]
71+
72+
73+
def test_get_event_function_responses_fallback_to_parts():
74+
part = SimpleNamespace(function_response=SimpleNamespace(name="tool_resp"))
75+
event = SimpleNamespace(content=SimpleNamespace(parts=[part]))
76+
77+
responses = adk_compat.get_event_function_responses(event)
78+
assert len(responses) == 1
79+
assert responses[0].name == "tool_resp"
80+
81+
82+
def test_mysql_backend_url_respects_async_driver_flag(monkeypatch):
83+
monkeypatch.setattr(
84+
"veadk.memory.short_term_memory_backends.mysql_backend.should_use_async_db_drivers",
85+
lambda: True,
86+
)
87+
backend = MysqlSTMBackend()
88+
assert backend._db_url.startswith("mysql+aiomysql://")
89+
90+
monkeypatch.setattr(
91+
"veadk.memory.short_term_memory_backends.mysql_backend.should_use_async_db_drivers",
92+
lambda: False,
93+
)
94+
backend = MysqlSTMBackend()
95+
assert backend._db_url.startswith("mysql+pymysql://")
96+
97+
98+
def test_postgresql_backend_url_respects_async_driver_flag(monkeypatch):
99+
monkeypatch.setattr(
100+
"veadk.memory.short_term_memory_backends.postgresql_backend.should_use_async_db_drivers",
101+
lambda: True,
102+
)
103+
backend = PostgreSqlSTMBackend()
104+
assert backend._db_url.startswith("postgresql+asyncpg://")
105+
106+
monkeypatch.setattr(
107+
"veadk.memory.short_term_memory_backends.postgresql_backend.should_use_async_db_drivers",
108+
lambda: False,
109+
)
110+
backend = PostgreSqlSTMBackend()
111+
assert backend._db_url.startswith("postgresql://")
112+
113+
114+
def test_sqlite_backend_url_respects_async_driver_flag(monkeypatch, tmp_path):
115+
db_file = tmp_path / "compat-test.db"
116+
117+
monkeypatch.setattr(
118+
"veadk.memory.short_term_memory_backends.sqlite_backend.should_use_async_db_drivers",
119+
lambda: True,
120+
)
121+
backend = SQLiteSTMBackend(local_path=str(db_file))
122+
assert backend._db_url.startswith("sqlite+aiosqlite:///")
123+
124+
monkeypatch.setattr(
125+
"veadk.memory.short_term_memory_backends.sqlite_backend.should_use_async_db_drivers",
126+
lambda: False,
127+
)
128+
backend = SQLiteSTMBackend(local_path=str(db_file))
129+
assert backend._db_url.startswith("sqlite:///")
130+
131+
132+
def test_tool_output_extractor_accepts_dict_response():
133+
function_response_event = SimpleNamespace(
134+
content=SimpleNamespace(
135+
parts=[
136+
SimpleNamespace(
137+
function_response={
138+
"id": "id_1",
139+
"name": "tool_name",
140+
"response": {"ok": True},
141+
}
142+
)
143+
]
144+
)
145+
)
146+
params = ToolAttributesParams(
147+
tool=SimpleNamespace(name="tool_name"),
148+
args={},
149+
function_response_event=function_response_event,
150+
)
151+
152+
response = tool_gen_ai_tool_output(params)
153+
assert '"name": "tool_name"' in response.content
154+
155+
156+
def test_tool_output_extractor_accepts_object_response():
157+
function_response_event = SimpleNamespace(
158+
content=SimpleNamespace(
159+
parts=[
160+
SimpleNamespace(
161+
function_response=SimpleNamespace(
162+
id="id_2",
163+
name="tool_obj",
164+
response={"status": "done"},
165+
)
166+
)
167+
]
168+
)
169+
)
170+
params = ToolAttributesParams(
171+
tool=SimpleNamespace(name="tool_obj"),
172+
args={},
173+
function_response_event=function_response_event,
174+
)
175+
176+
response = tool_gen_ai_tool_output(params)
177+
assert '"name": "tool_obj"' in response.content

0 commit comments

Comments
 (0)