Skip to content

Commit e24a783

Browse files
aditik0303claude
andcommitted
fix(google-adk): cap before_tool args + review test coverage (page 6)
Follow-up to the #362 review pass — the earlier commit covered AgentTool walk, _stringify cap, cached-agent rebind, trace_id drop, and the cap warning; these are the rest of page 6: - callbacks.py:249 (Major) — before_tool no longer passes tool args uncapped. New _cap_args bounds the payload: within budget the dict passes through unchanged (per-key rules still work); once its serialized size exceeds the cap it is replaced with a single capped {"_truncated": ...}. Contrast with after_tool, which already capped its result. - test_callbacks.py (Minor) — the tree test now also asserts the container agent is NOT decorated; added a huge-args cap test. (Double-attach with a pre-existing user callback is already covered by test_install_governance_preserves_existing_callback_and_runs_first.) Acknowledged, unchanged (flagged as follow-ups): - callbacks sync-only (Major): making governance async is a protocol-wide change (the evaluator is sync) — deferred, not one-off here. - _session_state accumulation across cached-agent reuse (Minor): fixed by the rebind added in the earlier commit (rebind resets the counters). Per-task safety for ParallelAgent's concurrent callbacks is a known limitation. - LIFO cap ordering (Minor): the cap now logs; which nodes are dropped past the cap is non-deterministic but all found nodes are governed. Tests: 66 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ef4518b commit e24a783

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

packages/uipath-google-adk/src/uipath_google_adk/governance/callbacks.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def before_tool(self, tool: Any, args: Dict[str, Any], tool_context: Any) -> Non
331331
tool_name = getattr(tool, "name", None) or "unknown"
332332
self._evaluator.evaluate_tool_call(
333333
tool_name=tool_name,
334-
tool_args=args or {},
334+
tool_args=self._cap_args(args or {}),
335335
agent_name=self._agent_name,
336336
runtime_id=self._session_id,
337337
session_state=self._session_state,
@@ -458,6 +458,24 @@ def _part_text(cls, part: Any) -> str:
458458

459459
return "\n".join(p for p in pieces if p)
460460

461+
@classmethod
462+
def _cap_args(cls, args: Dict[str, Any], cap: int = _BEFORE_MODEL_TEXT_CAP) -> Any:
463+
"""Bound the tool-args payload before it reaches the evaluator.
464+
465+
``before_tool`` receives args straight from ADK; a huge blob (e.g. a
466+
tool called with a multi-megabyte string) would otherwise be scanned
467+
uncapped — contrast with ``after_tool``, which caps its result. Within
468+
budget the dict is passed through unchanged (so per-key rules still
469+
work); once its serialized size exceeds ``cap`` it is replaced with a
470+
single capped, stringified form.
471+
"""
472+
if not isinstance(args, dict) or not args:
473+
return args
474+
blob = cls._stringify(args, cap + 1)
475+
if len(blob) <= cap:
476+
return args
477+
return {"_truncated": blob[:cap]}
478+
461479
@staticmethod
462480
def _stringify(value: Any, cap: int = _BEFORE_MODEL_TEXT_CAP) -> str:
463481
"""Render a dict / object payload as compact, scannable text, capped.

packages/uipath-google-adk/tests/governance/test_callbacks.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ def test_install_governance_installs_on_all_llm_agents_in_tree():
137137
assert len(leaf.before_model_callback) == 1
138138
assert leaf.after_model_callback and leaf.before_tool_callback
139139
assert leaf.after_tool_callback
140+
# the container agent has no model-callback surface → must NOT be decorated
141+
assert not hasattr(root, "before_model_callback")
140142

141143

142144
def test_install_governance_is_idempotent():
@@ -350,6 +352,18 @@ def test_before_tool_passes_args_and_session_state():
350352
assert kwargs["session_state"]["tool_calls"] == 1
351353

352354

355+
def test_before_tool_caps_huge_args():
356+
"""A huge arg blob must not reach the evaluator uncapped (contrast with the
357+
small-args case, which passes through unchanged)."""
358+
ev = FakeEvaluator()
359+
cb = _make_callbacks(ev)
360+
huge = "x" * (_BEFORE_MODEL_TEXT_CAP + 5000)
361+
cb.before_tool(FakeTool("t"), {"blob": huge}, tool_context=None)
362+
tool_args = ev.calls[-1][1]["tool_args"]
363+
assert set(tool_args) == {"_truncated"}
364+
assert len(tool_args["_truncated"]) <= _BEFORE_MODEL_TEXT_CAP
365+
366+
353367
def test_after_tool_stringifies_dict_response():
354368
ev = FakeEvaluator()
355369
cb = _make_callbacks(ev)

0 commit comments

Comments
 (0)