Skip to content

Commit d5d34bf

Browse files
authored
Merge branch 'main' into update-frontend-assets
2 parents a2463b9 + a721c1e commit d5d34bf

7 files changed

Lines changed: 241 additions & 3 deletions

File tree

contributing/samples/integrations/gepa/rater_lib.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def __call__(self, messages: list[dict[str, Any]]) -> dict[str, Any]:
167167
Returns:
168168
A dictionary containing rating information including score.
169169
"""
170-
env = jinja2.Environment()
170+
env = jinja2.Environment(autoescape=True)
171171
env.globals['user_input'] = (
172172
messages[0].get('parts', [{}])[0].get('text', '') if messages else ''
173173
)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright 2026 Google LLC
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+
import sys
15+
from unittest import mock
16+
17+
# Mock the retry module before importing rater_lib
18+
mock_retry_module = mock.MagicMock()
19+
20+
21+
def mock_retry_decorator(*args, **kwargs):
22+
def decorator(func):
23+
return func
24+
25+
return decorator
26+
27+
28+
mock_retry_module.retry = mock_retry_decorator
29+
sys.modules["retry"] = mock_retry_module
30+
31+
from gepa import rater_lib
32+
33+
34+
def test_rater_escapes_html_inputs_to_prevent_xss():
35+
"""Rater escapes HTML tags in user and model inputs to prevent XSS.
36+
37+
Setup: Mock genai.Client to return a dummy rating response.
38+
Act: Call Rater with messages containing HTML tags.
39+
Assert: Verify that the rendered prompt template contains escaped HTML.
40+
"""
41+
# Arrange
42+
with mock.patch("google.genai.Client") as mock_client_cls:
43+
mock_client = mock_client_cls.return_value
44+
mock_generate = mock_client.models.generate_content
45+
mock_generate.return_value.text = (
46+
"Property: The agent fulfilled the user's primary request.\n"
47+
"Evidence: mock evidence\n"
48+
"Rationale: mock rationale\n"
49+
"Verdict: yes"
50+
)
51+
52+
template_path = (
53+
"contributing/samples/integrations/gepa/rubric_validation_template.txt"
54+
)
55+
rater = rater_lib.Rater(
56+
tool_declarations="[]", validation_template_path=template_path
57+
)
58+
59+
messages = [
60+
{
61+
"role": "user",
62+
"parts": [{"text": "<script>alert('XSS')</script>"}],
63+
},
64+
{
65+
"role": "model",
66+
"parts": [{"text": "Hello <img src=x onerror=alert(1)>"}],
67+
},
68+
]
69+
70+
# Act
71+
rater(messages)
72+
73+
# Assert
74+
assert mock_generate.called
75+
call_args = mock_generate.call_args
76+
contents = call_args.kwargs["contents"]
77+
78+
assert "&lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;" in contents
79+
assert "Hello &lt;img src=x onerror=alert(1)&gt;" in contents

contributing/samples/integrations/gepa/rubric_validation_template.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,12 @@ Verdict: no
155155
</available_tools>
156156

157157
<main_prompt>
158-
{{user_input}}
158+
{{user_input|e}}
159159
</main_prompt>
160160
</user_prompt>
161161

162162
<responses>
163-
{{model_response}}
163+
{{model_response|e}}
164164
</responses>
165165

166166
<properties>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Managed Agent - Code Execution
2+
3+
## Overview
4+
5+
This sample runs a `ManagedAgent` configured with the built-in **code execution**
6+
tool so it can write and run code server-side to compute answers.
7+
8+
Unlike a regular `LlmAgent` (which enables code execution via
9+
`code_executor=BuiltInCodeExecutor()`), `ManagedAgent` has no `code_executor`
10+
field. Instead you pass the raw built-in tool config
11+
`types.Tool(code_execution=types.ToolCodeExecution())` in `tools` -- the same
12+
config `BuiltInCodeExecutor` produces under the hood. This makes the sample a
13+
demonstration of the raw `types.Tool` server-side tool path.
14+
15+
## Sample Inputs
16+
17+
- `What is the sum of the first 50 prime numbers? Use code to compute it.`
18+
19+
The model writes and runs code server-side; the answer (5117) comes from the
20+
executed code rather than the model guessing.
21+
22+
- `Now do the same for the first 100 primes.`
23+
24+
A follow-up turn that reuses the recovered remote sandbox and the previous
25+
interaction (answer: 24133), demonstrating multi-turn chaining.
26+
27+
## Graph
28+
29+
```mermaid
30+
graph LR
31+
User -->|message| ManagedAgent
32+
ManagedAgent -->|interactions.create| ManagedAgentsAPI
33+
ManagedAgentsAPI -->|server-side code execution| ManagedAgentsAPI
34+
ManagedAgentsAPI -->|streamed events| ManagedAgent
35+
ManagedAgent -->|answer| User
36+
```
37+
38+
## How To
39+
40+
- **Create the agent**: instantiate `ManagedAgent` with an `agent_id`, an
41+
`environment` spec, and
42+
`tools=[types.Tool(code_execution=types.ToolCodeExecution())]`. No `model` is
43+
set -- the model is part of the managed agent on the server.
44+
- **Enable code execution**: `ManagedAgent` has no `code_executor` field, so the
45+
raw `types.Tool(code_execution=...)` config is passed in `tools`. The
46+
interactions converter turns it into the server-side `code_execution` tool.
47+
- **Provision a sandbox**: `environment={'type': 'remote'}` requests a fresh
48+
remote sandbox. The resulting environment id is stored on emitted events, so
49+
subsequent turns automatically recover and reuse it.
50+
- **Multi-turn chaining**: the agent recovers the `previous_interaction_id` from
51+
the session events, so follow-up turns continue the same interaction without
52+
any extra wiring.
53+
- **Drive it**: a `ManagedAgent` is a `BaseAgent`, so a standard `Runner` runs
54+
it just like any other agent.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2026 Google LLC
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 . import agent
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright 2026 Google LLC
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+
"""A ManagedAgent that runs server-side code execution.
16+
17+
``ManagedAgent`` calls the Managed Agents API directly from its run loop instead
18+
of running a local model loop. It currently supports server-side tools only.
19+
20+
Unlike ``LlmAgent``, ``ManagedAgent`` has no ``code_executor`` field, so code
21+
execution is enabled by passing the raw built-in tool config
22+
``types.Tool(code_execution=types.ToolCodeExecution())`` in ``tools`` -- the
23+
same config ``BuiltInCodeExecutor`` produces under the hood. The model writes
24+
and runs code on the server to compute answers.
25+
26+
A fresh remote sandbox is provisioned via ``environment={'type': 'remote'}``;
27+
the environment id is recovered from prior events so multi-turn conversations
28+
reuse the same sandbox.
29+
30+
Run with ``adk web`` /
31+
``adk run contributing/samples/managed_agent/code_execution``. See the README
32+
for the required environment / auth setup.
33+
"""
34+
35+
import os
36+
37+
from google.adk.agents import ManagedAgent
38+
from google.genai import types
39+
40+
# The Managed Agent id served by the Managed Agents API. Override with the
41+
# MANAGED_AGENT_ID environment variable if your project has access to a
42+
# different agent.
43+
_DEFAULT_AGENT_ID = 'antigravity-preview-05-2026'
44+
45+
root_agent = ManagedAgent(
46+
name='managed_code_execution_agent',
47+
agent_id=os.environ.get('MANAGED_AGENT_ID', _DEFAULT_AGENT_ID),
48+
# Provision a remote sandbox for the agent. The environment id is recovered
49+
# from prior events, so follow-up turns reuse the same sandbox.
50+
environment={'type': 'remote'},
51+
# ManagedAgent has no `code_executor` field; enable server-side code
52+
# execution by passing the raw built-in tool config. This is the same config
53+
# BuiltInCodeExecutor appends for a regular LlmAgent.
54+
tools=[types.Tool(code_execution=types.ToolCodeExecution())],
55+
)

tests/integration/test_managed_agent.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,38 @@ async def test_google_search_project_hail_mary():
8686
assert (
8787
'james ortiz' in answer.lower()
8888
), f'expected the grounded answer to contain "James Ortiz"; got: {answer!r}'
89+
90+
91+
@pytest.mark.asyncio
92+
async def test_code_execution_prime_sum():
93+
agent = ManagedAgent(
94+
name='managed_code_execution_agent',
95+
agent_id=_AGENT_ID,
96+
environment={'type': 'remote'},
97+
tools=[types.Tool(code_execution=types.ToolCodeExecution())],
98+
)
99+
session_service = InMemorySessionService()
100+
runner = Runner(
101+
app_name='managed_agent_it',
102+
agent=agent,
103+
session_service=session_service,
104+
)
105+
session = await session_service.create_session(
106+
app_name='managed_agent_it', user_id='test_user'
107+
)
108+
109+
events = await _run_turn(
110+
runner,
111+
session,
112+
'What is the sum of the first 50 prime numbers? Use code to compute it.',
113+
)
114+
115+
answer = _joined_text(events)
116+
print('\n=== ManagedAgent code execution answer ===\n', answer)
117+
# The model may stream the number with thousands separators and/or stray
118+
# whitespace (e.g. "5,117" or "5, 117"), so remove all whitespace and commas
119+
# before matching the code-executed sum.
120+
normalized = ''.join(answer.split()).replace(',', '')
121+
assert (
122+
'5117' in normalized
123+
), f'expected the code-executed sum 5117; got: {answer!r}'

0 commit comments

Comments
 (0)