Skip to content

Commit 9082b9e

Browse files
wuliang229copybara-github
authored andcommitted
feat(environment): Add EnvironmentToolset for file I/O and command execution
This change introduces a new EnvironmentToolset within the ADK, providing tools to interact with a BaseEnvironment. The toolset includes Execute, ReadFile, EditFile, WriteFile. System instructions are injected to guide the LLM on workspace usage and file manipulation. Co-authored-by: Liang Wu <wuliang@google.com> PiperOrigin-RevId: 890672569
1 parent 1486925 commit 9082b9e

File tree

4 files changed

+548
-0
lines changed

4 files changed

+548
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
"""Environment toolset for command execution and file I/O."""
16+
17+
from __future__ import annotations
18+
19+
from ._environment_toolset import EnvironmentToolset
20+
21+
__all__ = [
22+
'EnvironmentToolset',
23+
]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
"""Constants for the environment toolset."""
16+
17+
from __future__ import annotations
18+
19+
# ---------------------------------------------------------------------------
20+
# Default limits
21+
# ---------------------------------------------------------------------------
22+
23+
DEFAULT_TIMEOUT = 30
24+
"""Default execution timeout in seconds."""
25+
26+
MAX_OUTPUT_CHARS = 30_000
27+
"""Maximum characters returned to the LLM per tool call."""
28+
29+
# ---------------------------------------------------------------------------
30+
# System instruction templates
31+
# ---------------------------------------------------------------------------
32+
33+
ENVIRONMENT_INSTRUCTION = """\
34+
Your environment is at {working_dir}/
35+
36+
# Environment Rules
37+
38+
DO:
39+
- Chain sequential, dependent commands with `&&` in a single `Execute` call
40+
- To read existing files, always use the `ReadFile` tool. Use `EditFile` to modify existing files.
41+
42+
DON'T:
43+
- Use `Execute` to run cat, head, or tail when `ReadFile` tools can do the job
44+
- Combine `EditFile` or `ReadFile` with `Execute` in the same response (Instead, call the file tool first, then `Execute` in the next turn)
45+
- Use multiple `Execute` calls for dependent commands (they run in parallel)
46+
"""
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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+
"""Environment toolset that provides tools to interact with an environment."""
16+
17+
from __future__ import annotations
18+
19+
import logging
20+
from typing import Any
21+
from typing import Optional
22+
from typing import TYPE_CHECKING
23+
24+
from typing_extensions import override
25+
26+
from ...utils.feature_decorator import experimental
27+
from ..base_toolset import BaseToolset
28+
from ._constants import ENVIRONMENT_INSTRUCTION
29+
from ._tools import _EditFileTool
30+
from ._tools import _ExecuteTool
31+
from ._tools import _ReadFileTool
32+
from ._tools import _WriteFileTool
33+
34+
if TYPE_CHECKING:
35+
from ...agents.readonly_context import ReadonlyContext
36+
from ...environments._base_environment import BaseEnvironment
37+
from ...models.llm_request import LlmRequest
38+
from ..base_tool import BaseTool
39+
from ..tool_context import ToolContext
40+
41+
logger = logging.getLogger('google_adk.' + __name__)
42+
43+
44+
@experimental
45+
class EnvironmentToolset(BaseToolset):
46+
"""Toolset providing tools to interact with an environment.
47+
48+
Tools provided:
49+
- **Execute** -- run shell commands
50+
- **ReadFile** -- read file contents
51+
- **EditFile** -- surgical text replacement
52+
- **WriteFile**q -- create/overwrite files
53+
54+
The toolset injects an environment-level system instruction on each
55+
LLM call that establishes environment identity and tool selection
56+
rules.
57+
"""
58+
59+
def __init__(
60+
self,
61+
*,
62+
environment: BaseEnvironment,
63+
**kwargs: Any,
64+
):
65+
"""Create an environment toolset.
66+
67+
Args:
68+
environment: The environment used to execute commands and
69+
perform file I/O.
70+
**kwargs: Forwarded to ``BaseToolset.__init__``.
71+
"""
72+
super().__init__(**kwargs)
73+
self._environment = environment
74+
self._environment_initialized = False
75+
76+
@override
77+
async def get_tools(
78+
self,
79+
readonly_context: Optional[ReadonlyContext] = None,
80+
) -> list[BaseTool]:
81+
if not self._environment_initialized:
82+
await self._environment.initialize()
83+
self._environment_initialized = True
84+
return [
85+
_ExecuteTool(self._environment),
86+
_ReadFileTool(self._environment),
87+
_EditFileTool(self._environment),
88+
_WriteFileTool(self._environment),
89+
]
90+
91+
@override
92+
async def process_llm_request(
93+
self, *, tool_context: ToolContext, llm_request: LlmRequest
94+
) -> None:
95+
"""Inject environment-level system instruction."""
96+
if not self._environment_initialized:
97+
await self._environment.initialize()
98+
self._environment_initialized = True
99+
working_dir = self._environment.working_dir
100+
instruction = ENVIRONMENT_INSTRUCTION.format(
101+
working_dir=working_dir,
102+
)
103+
llm_request.append_instructions([instruction])
104+
105+
@override
106+
async def close(self) -> None:
107+
if self._environment_initialized:
108+
await self._environment.close()
109+
self._environment_initialized = False

0 commit comments

Comments
 (0)