Google ADK¶
Provena integrates with the Google Agent Development Kit (ADK) through a tool callback that records tool invocations and responses to your audit trail.
Installation¶
This installs google-adk>=1.0. ProvenaADKCallback is designed to be lightweight with no heavy internal dependencies, adhering directly to the Google ADK callback interface.
Quick start¶
from google.adk.agents import Agent
from provena import ContextTrail
from provena.integrations.google_adk import ProvenaADKCallback
# Initialize the trail and callback
trail = ContextTrail(storage_path="adk_audit.db")
callback = ProvenaADKCallback(trail=trail)
# Attach callback to the agent
agent = Agent(
name="research-agent",
after_tool_callback=callback.after_tool_call,
)
How it works¶
ProvenaADKCallback implements the after_tool_call method, designed to be passed to an ADK Agent's after_tool_callback parameter:
def after_tool_call(
self,
tool: Any,
args: dict[str, Any],
tool_context: Any,
tool_response: Any,
) -> Any | None:
tool_name = (
getattr(tool, "name", None) or getattr(tool, "__name__", "unknown") # (1)!
)
content = str(tool_response) if tool_response is not None else "" # (2)!
if content: # (3)!
self._trail.log(
content=content,
source=ContextSource.TOOL,
source_name=f"adk:{tool_name}", # (4)!
metadata={"args": args} if args else {}, # (5)!
)
return None # (6)!
- Tool name resolution: Extracts
tool.nameif present (for class/object-based tools), falls back totool.__name__(for function-based tools), or"unknown". - Response serialization: Converts
tool_responseto string. - Empty response handling: If
tool_responseisNoneor yields an empty string, nothing is logged to the trail. - Source naming: Logged with source
ContextSource.TOOL("tool") and source nameadk:<tool_name>. - Argument metadata: If tool invocation arguments
argsare present, they are stored in the record's metadata dictionary as{"args": args}. - Pass-through return behavior: Returns
None. Under Google ADK's callback protocol, returningNonepreserves the originaltool_responseand passes it through to the agent without modification.
Logged attributes¶
| Attribute | Logged Value | Description |
|---|---|---|
content |
str(tool_response) |
Stringified tool response |
source |
ContextSource.TOOL ("tool") |
Identifies input as tool execution |
source_name |
adk:<tool_name> |
Identifies the tool invoked |
metadata |
{"args": args} |
Call arguments supplied to the tool (or {} if empty) |
Provenance tracking & limitations¶
- No provenance auto-extraction:
ProvenaADKCallbackdoes not automatically populateProvenanceMetadatafields (source_url,author,created_at). - Default status: Tool output records receive provenance status
MISSING. - Pass-through behavior: The callback only observes responses; it does not filter or sanitize tool output.
Auditing tool inputs and outputs
Because args is saved in the record metadata, Provena allows you to audit both what parameters were passed to the tool and the resulting output hash.
Full working example¶
from google.adk.agents import Agent
from provena import ContextTrail
from provena.integrations.google_adk import ProvenaADKCallback
# Define a tool function
def weather_lookup(location: str) -> str:
"""Fetch current weather for a given city."""
return f"Weather in {location}: 22°C, Sunny."
# Set up the Provena audit trail and callback
trail = ContextTrail(storage_path="adk_audit.db")
callback = ProvenaADKCallback(trail=trail)
# Create an agent with the callback attached
agent = Agent(
name="weather-assistant",
tools=[weather_lookup],
after_tool_callback=callback.after_tool_call,
)
# Agent execution (tool calls trigger after_tool_call automatically)
# response = agent.run("What's the weather in Seattle?")
Verifying the trail¶
After execution, verify the cryptographic integrity of recorded tool responses:
# Summary of audit trail
summary = trail.summary()
print(f"Total records: {summary['total']}")
print(f"Sources: {summary['sources']}")
print(f"Provenance: {summary['provenance']}")
# Verify hash chain
verdict = trail.verify_chain()
assert verdict.intact, f"Chain broken: {verdict.details}"
print(f"Chain intact: {verdict.total_records} records verified")
# Inspect records with argument metadata
records = trail.query(source="tool", limit=10)
for r in records:
print(f" [{r['source_name']}] args={r.get('metadata', {}).get('args')}")
print(f" Hash: {r['content_hash'][:12]}")
trail.close()
Expected output:
Total records: 1
Sources: {'tool': 1}
Provenance: {'MISSING': 1}
Chain intact: 1 records verified
[adk:weather_lookup] args={'location': 'Seattle'}
Hash: 3c8e4a1b0f5d
Context manager pattern¶
with ContextTrail(storage_path="adk_audit.db") as trail:
callback = ProvenaADKCallback(trail=trail)
agent = Agent(
name="assistant",
tools=[weather_lookup],
after_tool_callback=callback.after_tool_call,
)
# Run agent...
verdict = trail.verify_chain()
print(f"Chain intact: {verdict.intact}")
# trail.close() is called automatically