Skip to content

Commit 217a90a

Browse files
google-genai-botcopybara-github
authored andcommitted
feat(eventarc): add Eventarc Advanced toolset for ADK
This adds a new integration for Google Cloud Eventarc Advanced. Provides `eventarc_toolset` which allows LLM agents to publish structured CloudEvents. It strictly validates and sanitizes attributes according to the CloudEvents 1.0 specification and supports dynamically resolving fields like `time` and `id` at runtime using lambdas. Includes comprehensive testing and documentation. PiperOrigin-RevId: 952384562
1 parent bf4143a commit 217a90a

16 files changed

Lines changed: 682 additions & 300 deletions

File tree

contributing/samples/integrations/eventarc/README.md

Lines changed: 0 additions & 115 deletions
This file was deleted.

contributing/samples/integrations/eventarc/__init__.py

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Eventarc Domain-Specific Agent Sample
2+
3+
## Overview
4+
5+
This sample agent demonstrates the `create_publish_tool` factory from the Eventarc first-party tool suite in ADK (`google.adk.integrations.eventarc`). It shows how to create domain-specific, strict-schema publishing tools. This allows you to lock down event routing parameters (e.g. `bus`, `type`, `source`) using `CloudEventAttributesBinding` to static values, runtime lambdas, or selectively-exposed agent fields (`AgentProvided`), while binding the event payload to a strictly validated Pydantic model. This prevents hallucinated routing destinations and guarantees structured JSON event data matching your business domain.
6+
7+
## Sample Inputs
8+
9+
- `We just successfully completed a vendor outreach call with customer CUST-883. Resolution notes: All issues resolved.`
10+
11+
- `Log a dynamic outreach event for customer CUST-992. It should go to the message bus 'projects/your_project/locations/us-central1/messageBuses/outreach-bus' and the subject is 'urgent-outreach'.`
12+
13+
- `We just successfully completed a vendor outreach call with customer CUST-123. Resolution notes: All issues resolved. This is a high priority outreach.`
14+
15+
- `Please ping the system with high priority, do not retry.`
16+
17+
## Graph
18+
19+
```mermaid
20+
graph TD
21+
DomainAgent[adk_sample_domain_eventarc_agent] -->|calls| StaticTool(complete_outreach_static)
22+
DomainAgent -->|calls| DynamicTool(complete_outreach_dynamic)
23+
DomainAgent -->|calls| LambdaTool(complete_outreach_lambda)
24+
DomainAgent -->|calls| PingTool(ping_system)
25+
```
26+
27+
## How To
28+
29+
### Prerequisites: Set up Eventarc
30+
31+
Before running the agent, you must enable the Eventarc APIs and create a target Message Bus in your Google Cloud Project.
32+
33+
1. Enable the Eventarc APIs:
34+
35+
```bash
36+
gcloud services enable eventarc.googleapis.com eventarcpublishing.googleapis.com
37+
```
38+
39+
2. Create a Message Bus:
40+
41+
```bash
42+
gcloud eventarc message-buses create my-bus \
43+
--location=us-central1 \
44+
--logging-config=DEBUG
45+
```
46+
47+
*(Make sure to update the `BUS_NAME` variable in `agent.py` to match your actual bus URI).*
48+
49+
`create_publish_tool` is highly flexible. It uses `pydantic.create_model` to construct the LLM's function signature, encapsulating the `payload_schema` inside an `event_data` parameter and appending any parameter marked with `AgentProvided`.
50+
51+
### Example A: Fully Statically Bound (Safest)
52+
53+
The developer locks down all routing. The agent only provides the business data.
54+
55+
```python
56+
complete_outreach_static_tool = toolset.create_publish_tool(
57+
name="complete_outreach_static",
58+
description="Logs a completed outreach attempt (statically bound routing).",
59+
payload_schema=OutreachContext,
60+
bus=f"projects/{PROJECT_ID}/locations/us-central1/messageBuses/{BUS_NAME}",
61+
ce_attributes_binding=CloudEventAttributesBinding(
62+
type="vendor_outreach.completed",
63+
source="//my-agent/outreach",
64+
)
65+
)
66+
```
67+
68+
**What the Agent Sees:** `complete_outreach_static(event_data: OutreachContext)`
69+
70+
### Example B: Agent-Provided Attributes (Dynamic)
71+
72+
The developer forces the agent to decide the routing bus and the event subject based on the conversation context.
73+
74+
```python
75+
complete_outreach_dynamic_tool = toolset.create_publish_tool(
76+
name="complete_outreach_dynamic",
77+
description="Logs a completed outreach attempt (dynamic routing).",
78+
payload_schema=OutreachContext,
79+
bus=AgentProvided("The full regional bus name"),
80+
ce_attributes_binding=CloudEventAttributesBinding(
81+
type="vendor_outreach.completed",
82+
source="//my-agent/outreach",
83+
subject=AgentProvided("The unique Customer ID being reached out to.")
84+
)
85+
)
86+
```
87+
88+
**What the Agent Sees:** `complete_outreach_dynamic(event_data: OutreachContext, bus: str, subject: str)`
89+
90+
### Example C: Lambda Execution & Mixed Custom Attributes
91+
92+
The developer uses Python callables to generate IDs dynamically at runtime.
93+
94+
```python
95+
def get_custom_trace_id(payload: OutreachContext) -> str:
96+
return f"trace-{payload.customer_id}-{uuid.uuid4().hex[:8]}"
97+
98+
complete_outreach_lambda_tool = toolset.create_publish_tool(
99+
name="complete_outreach_lambda",
100+
description="Logs a completed outreach attempt.",
101+
payload_schema=OutreachContext,
102+
bus=f"projects/{PROJECT_ID}/locations/us-central1/messageBuses/{BUS_NAME}",
103+
ce_attributes_binding=CloudEventAttributesBinding(
104+
type="vendor_outreach.completed",
105+
source="//my-agent/outreach",
106+
id=get_custom_trace_id, # Executed at runtime
107+
custom_attributes={
108+
"environment": "production", # Statically bound
109+
"priority": AgentProvided("The priority of the outreach: 'high' or 'low'")
110+
}
111+
)
112+
)
113+
```
114+
115+
**What the Agent Sees:** `complete_outreach_lambda(event_data: OutreachContext, priority: str)`
116+
117+
### Example D: Empty Payloads & Dynamic Defaults
118+
119+
The developer wants to emit a simple signal (no business payload). If the agent omits the priority, it is dynamically calculated.
120+
121+
```python
122+
def default_priority(_: None) -> str:
123+
return "low"
124+
125+
ping_system_tool = toolset.create_publish_tool(
126+
name="ping_system",
127+
description="Pings the system. No data required.",
128+
payload_schema=None, # No payload!
129+
bus=f"projects/{PROJECT_ID}/locations/us-central1/messageBuses/{BUS_NAME}",
130+
ce_attributes_binding=CloudEventAttributesBinding(
131+
type="system.ping",
132+
source="//my-agent/ping",
133+
custom_attributes={
134+
"retry": AgentProvided("Whether to retry on failure", default="false"),
135+
"priority": AgentProvided("The priority of the ping", default=default_priority)
136+
}
137+
)
138+
)
139+
```
140+
141+
**What the Agent Sees:** `ping_system(retry: str = "false", priority: str | None = None)`
142+
143+
### Example E: Handling Reserved Keywords and Strict Validation
144+
145+
`create_publish_tool` strictly validates custom attributes to ensure they comply with the CloudEvent specification. If your custom attribute name collides with a Python reserved keyword, an implicit pointer, or starts with a digit, the tool factory automatically handles this. It securely modifies the exposed parameter name for the LLM (e.g., `self_` or `_123foo`) to avoid collisions, and translates it back during runtime execution.
146+
147+
## Next Steps: Building Event-Driven AI Workflows
148+
149+
Publishing an event to a Message Bus is only the first half of the journey. To route these events to other agents or microservices, you will need to set up Eventarc Pipelines and Enrollments.
150+
151+
To learn how to connect multiple AI agents together using Eventarc, check out the official codelab: **[Build Event-Driven AI Agents with Eventarc, Cloud Run and ADK](https://codelabs.devsite.corp.google.com/eventarc-ai-agents#0)**.

0 commit comments

Comments
 (0)