Skip to content

Commit 8eeff35

Browse files
enishercopybara-github
authored andcommitted
feat: Demonstrate CodeExecutor customization for environment setup
This change introduces a powerful pattern for customizing code execution environments by extending a base `CodeExecutor`. It showcases how to inject setup code to prepare the environment before a user's code is run, enabling advanced use cases that require specific configurations. As a practical example, this change implements `CustomCodeExecutor`, a subclass of `VertexAiCodeExecutor`, to solve the problem of rendering non-standard characters in `matplotlib` plots (Issue #2993). The custom executor programmatically adds a Japanese font to the `matplotlib` font manager at runtime. This is achieved by overriding the `execute_code` method to add font files to execution input and prepend the necessary font-loading logic. This approach is not limited to fonts and can be adapted for other setup tasks. Fixes: #2993 PiperOrigin-RevId: 825240143
1 parent edfe553 commit 8eeff35

3 files changed

Lines changed: 252 additions & 0 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Custom Code Executor Agent Sample
2+
3+
This directory contains a sample agent that demonstrates how to customize a
4+
`CodeExecutor` to perform environment setup before executing code. The specific
5+
example shows how to add support for Japanese fonts in `matplotlib` plots by
6+
subclassing `VertexAiCodeExecutor`.
7+
8+
## Overview
9+
10+
This sample showcases a powerful pattern for customizing code execution
11+
environments. By extending a base `CodeExecutor`, you can inject setup code to
12+
prepare the environment before a user's code is run. This enables advanced use
13+
cases that require specific configurations, in this case, adding custom fonts.
14+
15+
## Key Concept: `CodeExecutor` Customization
16+
17+
The Agent Development Kit (ADK) allows for powerful customization of code
18+
execution by extending existing `CodeExecutor` classes. By subclassing an
19+
executor (e.g., `VertexAiCodeExecutor`) and overriding its `execute_code`
20+
method, you can inject custom logic to:
21+
22+
- Modify the code before it's executed.
23+
- Add files to the execution environment.
24+
- Process the results after execution.
25+
26+
## Example: Adding Japanese Font Support
27+
28+
The `CustomCodeExecutor` in this sample solves a common issue where non-Latin
29+
characters do not render correctly in plots generated by `matplotlib` due to
30+
missing fonts in the execution environment.
31+
32+
It achieves this by: 1. **Subclassing `VertexAiCodeExecutor`**: It inherits all
33+
the functionality of the standard Vertex AI code executor. 2. **Overriding
34+
`execute_code`**: Before calling the parent's `execute_code` method, it performs
35+
the following steps: a. Downloads a Japanese font file (`NotoSerifJP`). b. Adds
36+
the font file to the list of files to be uploaded to the execution environment.
37+
c. Prepends a Python code snippet to the user's code. This snippet uses
38+
`matplotlib.font_manager` to register the newly available font file, making it
39+
available for plotting. 3. **Executing the modified code**: The combined code
40+
(setup snippet + original code) is then executed in the Vertex AI environment,
41+
which now has the Japanese font available for `matplotlib`.
42+
43+
This ensures that any plots generated during the agent's session can correctly
44+
display Japanese characters.
45+
46+
## How to use
47+
48+
### Prerequisites
49+
50+
Ensure you have configured your environment for using
51+
[Google Cloud Vertex AI](https://google.github.io/adk-docs/get-started/quickstart/#gemini---google-cloud-vertex-ai).
52+
You will need to have a Google Cloud Project with the Vertex AI API enabled.
53+
54+
### Running the agent
55+
56+
You can run this agent using the ADK CLI.
57+
58+
To interact with the agent through the command line:
59+
60+
```bash
61+
adk run contributing/samples/custom_code_execution "Plot a bar chart with these categories and values: {'リンゴ': 10, 'バナナ': 15, 'オレンジ': 8}. Title the chart '果物の在庫' (Fruit Stock)."
62+
```
63+
64+
To use the web interface:
65+
66+
```bash
67+
adk web contributing/samples/
68+
```
69+
70+
Then select `custom_code_execution` from the list of agents and interact with
71+
it.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2025 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: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# Copyright 2025 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+
"""Data science agent, with a custom code executor that enables Japanese fonts."""
16+
17+
from __future__ import annotations
18+
19+
import base64
20+
from typing import Optional
21+
import urllib.request
22+
23+
from google.adk.agents.invocation_context import InvocationContext
24+
from google.adk.agents.llm_agent import Agent
25+
from google.adk.code_executors.code_execution_utils import CodeExecutionInput
26+
from google.adk.code_executors.code_execution_utils import CodeExecutionResult
27+
from google.adk.code_executors.code_execution_utils import File
28+
from google.adk.code_executors.vertex_ai_code_executor import VertexAiCodeExecutor
29+
from typing_extensions import override
30+
31+
# The Python code snippet to be prepended to the user's code.
32+
# This will register the Japanese font with matplotlib.
33+
_FONT_SETUP_CODE = """
34+
import matplotlib.font_manager as fm
35+
36+
font_path = "NotoSerifJP[wght].ttf"
37+
try:
38+
fm.fontManager.addfont(font_path)
39+
prop = fm.FontProperties(fname=font_path)
40+
plt.rcParams['font.family'] = prop.get_name()
41+
print("Japanese font enabled for matplotlib.")
42+
except Exception as e:
43+
print(f"Failed to set Japanese font: {e}")
44+
"""
45+
46+
47+
def _load_font_file(font_url: str, font_filename: str) -> Optional[File]:
48+
"""Downloads a font file and returns it as a File object."""
49+
try:
50+
with urllib.request.urlopen(font_url) as response:
51+
font_bytes = response.read()
52+
except Exception as e:
53+
print(f"Failed to download font: {e}")
54+
return None
55+
56+
# Base64-encode the font content.
57+
font_content = base64.b64encode(font_bytes).decode("utf-8")
58+
return File(name=font_filename, content=font_content)
59+
60+
61+
class CustomCodeExecutor(VertexAiCodeExecutor):
62+
"""A Vertex AI code executor that automatically enables Japanese fonts."""
63+
64+
@override
65+
def execute_code(
66+
self,
67+
invocation_context: InvocationContext,
68+
code_execution_input: CodeExecutionInput,
69+
) -> CodeExecutionResult:
70+
font_url = "https://github.com/notofonts/noto-cjk/raw/refs/heads/main/google-fonts/NotoSerifJP%5Bwght%5D.ttf"
71+
font_filename = "NotoSerifJP[wght].ttf"
72+
font_file = _load_font_file(font_url, font_filename)
73+
# If the font download fails, execute the original code without it.
74+
if font_file is not None:
75+
# Add the font file to the input files.
76+
code_execution_input.input_files.append(font_file)
77+
78+
# Prepend the font setup code to the user's code.
79+
code_execution_input.code = (
80+
f"{_FONT_SETUP_CODE}\n\n{code_execution_input.code}"
81+
)
82+
83+
# Execute the modified code.
84+
return super().execute_code(invocation_context, code_execution_input)
85+
86+
87+
def base_system_instruction():
88+
"""Returns: data science agent system instruction."""
89+
90+
return """
91+
# Guidelines
92+
93+
**Objective:** Assist the user in achieving their data analysis goals within the context of a Python Colab notebook, **with emphasis on avoiding assumptions and ensuring accuracy.** Reaching that goal can involve multiple steps. When you need to generate code, you **don't** need to solve the goal in one go. Only generate the next step at a time.
94+
95+
**Code Execution:** All code snippets provided will be executed within the Colab environment.
96+
97+
**Statefulness:** All code snippets are executed and the variables stays in the environment. You NEVER need to re-initialize variables. You NEVER need to reload files. You NEVER need to re-import libraries.
98+
99+
**Imported Libraries:** The following libraries are ALREADY imported and should NEVER be imported again:
100+
101+
```tool_code
102+
import io
103+
import math
104+
import re
105+
import matplotlib.pyplot as plt
106+
import numpy as np
107+
import pandas as pd
108+
import scipy
109+
```
110+
111+
**Output Visibility:** Always print the output of code execution to visualize results, especially for data exploration and analysis. For example:
112+
- To look a the shape of a pandas.DataFrame do:
113+
```tool_code
114+
print(df.shape)
115+
```
116+
The output will be presented to you as:
117+
```tool_outputs
118+
(49, 7)
119+
120+
```
121+
- To display the result of a numerical computation:
122+
```tool_code
123+
x = 10 ** 9 - 12 ** 5
124+
print(f'{{x=}}')
125+
```
126+
The output will be presented to you as:
127+
```tool_outputs
128+
x=999751168
129+
130+
```
131+
- You **never** generate ```tool_outputs yourself.
132+
- You can then use this output to decide on next steps.
133+
- Print just variables (e.g., `print(f'{{variable=}}')`.
134+
135+
**No Assumptions:** **Crucially, avoid making assumptions about the nature of the data or column names.** Base findings solely on the data itself. Always use the information obtained from `explore_df` to guide your analysis.
136+
137+
**Available files:** Only use the files that are available as specified in the list of available files.
138+
139+
**Data in prompt:** Some queries contain the input data directly in the prompt. You have to parse that data into a pandas DataFrame. ALWAYS parse all the data. NEVER edit the data that are given to you.
140+
141+
**Answerability:** Some queries may not be answerable with the available data. In those cases, inform the user why you cannot process their query and suggest what type of data would be needed to fulfill their request.
142+
143+
"""
144+
145+
146+
root_agent = Agent(
147+
model="gemini-2.5-flash",
148+
name="data_science_agent",
149+
instruction=base_system_instruction() + """
150+
151+
152+
You need to assist the user with their queries by looking at the data and the context in the conversation.
153+
You final answer should summarize the code and code execution relavant to the user query.
154+
155+
You should include all pieces of data to answer the user query, such as the table from code execution results.
156+
If you cannot answer the question directly, you should follow the guidelines above to generate the next step.
157+
If the question can be answered directly with writing any code, you should do that.
158+
If you doesn't have enough data to answer the question, you should ask for clarification from the user.
159+
160+
You should NEVER install any package on your own like `pip install ...`.
161+
When plotting trends, you should make sure to sort and order the data by the x-axis.
162+
163+
164+
""",
165+
code_executor=CustomCodeExecutor(),
166+
)

0 commit comments

Comments
 (0)