基于 LocalEnvironment 与 EnvironmentToolset 构建本地文件与命令执行 Agent(adk-python 实战指南)
导读
本文以 adk-python 仓库中的本地环境示例(contributing/samples/environment_and_skills/local_environment/README.md)为线索,深入讲解如何在 Agent 中接入 LocalEnvironment 与 EnvironmentToolset,使 Agent 具备读写本地文件、执行 shell 命令的能力。读完本文,你将掌握环境工具集的四个核心工具(Execute / ReadFile / EditFile / WriteFile)的调用契约、参数与错误语义、底层执行与安全机制,并能复现"写文件—读文件—执行脚本"的完整闭环示例。
一、示例概览:让 Agent 拥有"手脚"
绝大多数 Agent 只能与文本交互,无法触碰真实文件系统。本示例演示的正是 ADK 中"环境(Environment)"这一抽象:它把命令执行与文件 I/O 封装为可供 Agent 调用的工具,从而让模型能够完成"创建文件、修改文件、运行本地脚本或命令"这一类任务。
示例由两个文件组成:
| 文件 | 作用 |
|---|---|
| agent.py | 定义根 Agent,配置模型与 EnvironmentToolset |
| README.md | 说明示例用途、示例提示词与预期行为 |
示例 Agent 配置了 EnvironmentToolset,其中携带的 LocalEnvironment 为 Agent 提供了文件 I/O(读、写)和命令执行两类工具。用户只需用自然语言下达涉及文件操作与命令执行的指令,Agent 即可自主编排工具完成整条任务链路。
二、核心概念:BaseEnvironment 抽象与 LocalEnvironment 实现
在展开示例前,先理解环境抽象的分层设计。
2.1 抽象基类 BaseEnvironment
BaseEnvironment 是所有代码执行环境的抽象基类,定义了统一的生命周期与能力契约:
- 构造(
__init__):创建环境对象; - 初始化(
initialize()):首次使用前调用,例如创建工作目录; - 使用:
execute()执行 shell 命令、read_file()读取文件、write_file()写文件; - 关闭(
close()):释放资源。
其抽象能力包括:
working_dir(只读属性):环境工作目录的绝对路径;execute(command, timeout=None):在工作目录中执行 shell 命令,返回ExecutionResult;read_file(path):读取文件,返回原始字节;write_file(path, content):写入文件,父目录不存在时自动创建。
ExecutionResult 是一个 dataclass,包含四个字段:exit_code(进程退出码)、stdout(标准输出)、stderr(标准错误)、timed_out(是否超时)。
从该基类出发,ADK 还支持沙箱执行、容器环境、云端环境等多种具体实现;本文聚焦于本地子进程实现 LocalEnvironment。
2.2 LocalEnvironment:本地 asyncio 子进程执行
LocalEnvironment 通过本地 asyncio 子进程执行命令,其构造参数为:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
working_dir |
Path | None |
None |
工作区目录的绝对路径;为 None 时在 initialize() 时通过 tempfile.mkdtemp(prefix='adk_workspace_') 自动创建临时目录,并在 close() 时删除 |
env_vars |
dict[str, str] | None |
None |
额外注入子进程的环境变量,会合并到当前进程环境的副本之上 |
需要注意两个使用要点:
- 必须初始化:
working_dir属性与execute/read_file/write_file在未调用initialize()前调用会抛出RuntimeError; - 路径隔离:
_resolve_path()会把相对路径拼接进工作目录,并在resolve()后校验结果是否仍位于工作目录内,一旦发现Path escapes working directory立即抛出ValueError——这是防止 Agent 越权读写工作区外文件的关键防线。
三、EnvironmentToolset:四个工具的调用契约
EnvironmentToolset 是环境工具的聚合器,构造时接收一个环境实例:
EnvironmentToolset(
environment=LocalEnvironment(),
max_output_chars=None, # 可选:stdout/stderr/文件输出的截断字符数上限
)
当 Agent 请求工具时,它首先确保环境已初始化,然后按序暴露四个工具:
| 工具名 | 用途 | 关键参数 |
|---|---|---|
Execute |
在工作目录执行 shell 命令 | command(必填) |
ReadFile |
读取文件内容(带行号) | path(必填)、start_line、end_line |
EditFile |
对已有文件做精确文本替换 | path、old_string、new_string(均必填) |
WriteFile |
创建或整体覆写文件 | path、content(均必填) |
3.1 Execute:命令执行
ExecuteTool 接收 command 字符串,调用环境的 execute() 方法执行,默认超时 30 秒(DEFAULT_TIMEOUT,见 _constants.py),默认输出截断上限 30000 字符(MAX_OUTPUT_CHARS)。
其返回结构为 JSON 字典:
- 成功:
{"status": "ok", "stdout": ..., "stderr": ...}; - 退出码非 0:
status变为"error"并附带exit_code; - 超时:
status变为"error"并提示Command timed out after 30s.; - 命令为空或执行抛异常:返回
{"status": "error", "error": ...}。
工具描述中对模型有明确约束:仅用于运行程序、测试和构建命令,禁止用于读文件(如 cat、head、tail 应改用 ReadFile)。
3.2 ReadFile:带行号的文件读取
ReadFileTool 除 path 外还支持两个可选参数:
start_line:起始行号(从 1 开始,含),默认 1;end_line:结束行号(含),默认文件末尾。
返回内容带行号前缀(形如 1\t...),便于模型定位;当只读取了文件的一部分时,还会附带 total_lines 字段告知文件总行数。边界情况下返回错误:start_line 超过文件行数、start_line 大于 end_line、文件不存在等。
3.3 EditFile:外科手术式精确替换
EditFileTool 适用于对已有文件的小改动:将 old_string 精确匹配的文本替换为 new_string。其实现细节值得注意:
- 对
old_string做正则转义,并将\r\n归一化为可匹配\r?\n的模式,容忍行尾差异; old_string必须在文件中恰好出现一次:出现 0 次返回"未找到,请先 ReadFile 校验内容",出现多次则要求提供更多上下文使其唯一;- 创建新文件请使用
WriteFile,而非EditFile。
3.4 WriteFile:创建或覆写
WriteFileTool 接收 path 与 content 两个必填参数,用于创建新文件或整体重写已有文件。底层 LocalEnvironment.write_file() 会自动创建缺失的父目录,并支持 str(UTF-8 文本)与 bytes(二进制)两种内容类型。
3.5 环境级系统指令注入
除提供工具外,EnvironmentToolset 还会在每次 LLM 调用时通过 process_llm_request() 注入环境级系统指令(模板见 _constants.py),内容大致为:
Your environment is at {working_dir}/
# Environment Rules
DO:
- 用 && 把有依赖关系的连续命令链在单次 Execute 调用中执行
- 读已有文件始终用 ReadFile,改已有文件用 EditFile
DON'T:
- 用 Execute 执行 cat/head/tail(ReadFile 能完成的事)
- 在同一轮响应中把 EditFile/ReadFile 与 Execute 混用(应先调文件工具,下一轮再 Execute)
- 用多个 Execute 并行执行有依赖关系的命令
这套规则把工作目录位置和工具选择策略直接"喂"给模型,是保证 Agent 行为规范、减少工具误用的关键设计。
四、完整示例:从写文件到执行脚本
示例 Agent 定义于 agent.py:
from google.adk import Agent
from google.adk.environment import LocalEnvironment
from google.adk.tools.environment import EnvironmentToolset
root_agent = Agent(
model="gemini-2.5-pro",
name="local_environment_agent",
description="A simple agent that demonstrates local environment usage.",
instruction="""
You are a helpful AI assistant that can use the local environment to
execute commands and file I/O. Follow the rules of the environment and the
user's instructions.
""",
tools=[
EnvironmentToolset(
environment=LocalEnvironment(),
),
],
)
可以看到,接入本地环境能力只需要三步:
- 构造
LocalEnvironment()(不传working_dir时使用自动创建的临时目录,Agent 会话结束即清理); - 将其传入
EnvironmentToolset(environment=...); - 把 toolset 加入
Agent的tools列表。
4.1 示例提示词
用户只需下达一条自然语言指令:
"Write a Python file named
hello.pyto the working directory that prints 'Hello from ADK!'. Then read the file to verify its contents, and finally execute it using a command."
("在工作目录写一个名为 hello.py 的 Python 文件,内容打印 'Hello from ADK!'。然后读取该文件校验内容,最后用命令执行它。")
4.2 预期行为
Agent 会自主编排出如下三步工具调用链:
- Write File:调用
WriteFile写入hello.py,内容为print("Hello from ADK!"); - Read File:调用
ReadFile读取hello.py并校验内容是否正确; - Execute Command:调用
Execute运行python3 hello.py,返回其执行输出(即Hello from ADK!)。
这一链路完整覆盖了"创建文件 → 校验 → 运行"三种典型操作,是 Agent 编程(Agentic Coding)场景中最基础的闭环范式。
五、底层执行机制与安全设计
结合 LocalEnvironment 源码,可以进一步理解命令执行的底层原理:
- 子进程模型:使用
asyncio.create_subprocess_shell()以 shell 方式执行命令,并传入cwd=working_dir、合并后的env以及start_new_session=True——命令拥有独立的进程组/会话,超时清理时不会误伤宿主进程; - 超时与清理:超时后先对整个进程组发
SIGTERM,等待 5 秒宽限期(_TERMINATE_GRACE_SECONDS)后升级为SIGKILL;若仍有脱离进程组的后代进程占用输出管道,则放弃读取其输出并记录警告,避免清理流程自身阻塞; - 输出解码:stdout/stderr 以 UTF-8 解码(容错
errors='replace'),随后在工具层按max_output_chars截断后返回给 LLM; - 文件读写:
read_file/write_file通过asyncio.to_thread将同步 I/O 放入线程池,避免阻塞事件循环。
从源码结构可以推断,EnvironmentToolset 的 close() 与 get_tools() 保证了环境初始化/关闭的幂等性(_environment_initialized 标志),确保 Agent 生命周期内环境只初始化一次、结束时资源被正确回收。
六、如何运行与验证
- 环境要求:示例使用
gemini-2.5-pro模型,运行前需完成 ADK 的模型凭据配置;命令执行能力依赖本地python3与 shell 环境。 - 运行方式:可直接以脚本方式运行 agent.py 与 Agent 交互,或通过 ADK 自带的 CLI 工具加载该 Agent(仓库中的 CLI 模块 提供了运行与 Web 调试能力)。
- 样例校验:仓库的 tests/unittests/test_samples.py 会统一校验 contributing 下样例的合法性与可加载性,可作为示例代码正确性的回归保障。
七、总结与延伸
通过本示例,你可以看到 ADK 将"环境能力"抽象成一条清晰的链路:BaseEnvironment(能力契约)→ LocalEnvironment(本地实现)→ EnvironmentToolset(工具聚合 + 指令注入)→ Agent(编排入口)。四个工具各司其职——Execute 跑命令、ReadFile 读文件、WriteFile 建文件、EditFile 改文件——配合注入的环境规则,让模型能够安全、规范地操作真实文件系统。
若你的场景需要更强隔离或云端执行,可以在 src/google/adk/environment 之外探索 ADK 提供的沙箱、容器与云端环境实现,其接口与本文所述完全一致,替换 LocalEnvironment() 即可无缝迁移。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.22 K638- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python400
cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端TypeScript2 K146
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python48467
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go20843
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java34451