Trae Agent Docker 逻辑分析
概述
Trae Agent 支持在 Docker 容器中执行工具调用,提供隔离的执行环境。本文档详细分析项目中 Docker 相关的逻辑和实现。
一、整体架构
┌─────────────────────────────────────────────────────────────────────────────┐
│ BaseAgent (基类) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ docker_config: dict | None │ │
│ │ docker_manager: DockerManager | None │ │
│ │ docker_keep: bool │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼ (如果配置了 docker_config)
┌─────────────────────────────────────────────────────────────────────────────┐
│ DockerManager (容器管理) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ - 启动/停止容器 │ │
│ │ - 挂载工作目录 │ │
│ │ - 复制工具到容器 │ │
│ │ - 管理交互式 Shell │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ DockerToolExecutor (工具执行代理) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ - 决定工具在 Docker 还是宿主机执行 │ │
│ │ - 路径翻译 (宿主机路径 ↔ 容器路径) │ │
│ │ - 构建执行命令 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
二、核心组件
2.1 DockerManager 类
文件: trae_agent/agent/docker_manager.py
class DockerManager:
"""
管理 Docker 容器生命周期和命令执行
支持有状态(交互式)和无状态(非交互式)模式
"""
CONTAINER_TOOLS_PATH = "/agent_tools" # 容器内工具路径
def __init__(
self,
image: str | None, # Docker 镜像名
container_id: str | None, # 现有容器 ID(复用)
dockerfile_path: str | None, # Dockerfile 路径(构建镜像)
docker_image_file: str | None, # 镜像 tar 文件(加载)
workspace_dir: str | None, # 宿主机工作目录
tools_dir: str | None, # 工具目录
interactive: bool = False, # 是否交互式
):
self.client = docker.from_env() # Docker 客户端
self.image = image
self.container_id = container_id
self.container = None
self.shell = None # pexpect Shell 会话
self._is_managed = True # 是否由本实例管理容器
初始化方式
| 方式 | 参数 | 说明 |
|---|---|---|
| 镜像名 | image | 使用已有镜像启动容器 |
| 容器 ID | container_id | 附加到现有容器 |
| Dockerfile | dockerfile_path | 构建新镜像并启动 |
| 镜像文件 | docker_image_file | 从 tar 加载镜像 |
核心方法
def start(self):
"""启动/附加容器,挂载工作目录,复制工具,启动 Shell"""
# 1. 构建镜像(如果提供了 Dockerfile)
if self.dockerfile_path:
self.image = self._build_image()
# 2. 加载镜像(如果提供了 tar 文件)
elif self.docker_image_file:
self.image = self._load_image()
# 3. 附加到现有容器
if self.container_id:
self.container = self.client.containers.get(self.container_id)
self._is_managed = False # 不管理生命周期
# 4. 启动新容器
elif self.image:
volumes = {
os.path.abspath(self.workspace_dir): {
"bind": self.container_workspace,
"mode": "rw"
}
}
self.container = self.client.containers.run(
self.image,
command="sleep infinity", # 保持容器运行
detach=True,
volumes=volumes,
working_dir=self.container_workspace,
)
# 5. 复制工具到容器
self._copy_tools_to_container()
# 6. 启动持久化 Shell
self._start_persistent_shell()
def execute(self, command: str, timeout: int = 300) -> tuple[int, str]:
"""执行命令(使用交互式 Shell)"""
return self._execute_interactive(command, timeout)
def stop(self):
"""停止 Shell,清理容器(如果是管理的)"""
if self.shell:
self.shell.close(force=True)
if self.container and self._is_managed:
self.container.stop()
self.container.remove()
交互式 Shell 执行
def _execute_interactive(self, command: str, timeout: int) -> tuple[int, str]:
"""在持久化 Shell 中执行命令"""
marker = "---CMD_DONE---"
marker_command = f"echo {marker}$?"
# 发送命令和标记
self.shell.sendline(command)
self.shell.sendline(marker_command)
# 等待标记和退出码
self.shell.expect(marker + r"(\d+)", timeout=timeout)
exit_code = int(self.shell.match.group(1))
# 清理输出(移除命令回显)
output = self._clean_output(self.shell.before, command)
return exit_code, output
2.2 DockerToolExecutor 类
文件: trae_agent/tools/docker_tool_executor.py
class DockerToolExecutor:
"""
工具执行代理
根据工具名称决定是在 Docker 还是宿主机执行
"""
def __init__(
self,
original_executor: ToolExecutor, # 原始执行器(宿主机)
docker_manager: DockerManager, # Docker 管理器
docker_tools: list[str], # 在 Docker 中执行的工具列表
host_workspace_dir: str | None, # 宿主机工作目录
container_workspace_dir: str, # 容器工作目录
):
self._original_executor = original_executor
self._docker_manager = docker_manager
self._docker_tools_set = set(docker_tools)
self._host_workspace_dir = host_workspace_dir
self._container_workspace_dir = container_workspace_dir
路径翻译
def _translate_path(self, host_path: str) -> str:
"""
将宿主机路径翻译为容器路径
例如:
- 宿主机: /home/user/project/src/main.py
- 容器: /workspace/src/main.py
"""
if not self._host_workspace_dir:
return host_path
abs_host_path = os.path.abspath(host_path)
# 检查路径是否在工作目录下
if os.path.commonpath([abs_host_path, self._host_workspace_dir]) == self._host_workspace_dir:
relative_path = os.path.relpath(abs_host_path, self._host_workspace_dir)
container_path = os.path.join(self._container_workspace_dir, relative_path)
return os.path.normpath(container_path)
return host_path
工具调用路由
async def sequential_tool_call(
self,
tool_calls: list[ToolCall]
) -> list[ToolResult]:
"""顺序执行工具调用,根据需要路由到 Docker"""
results = []
for tool_call in tool_calls:
if tool_call.name in self._docker_tools_set:
# 在 Docker 中执行
result = self._execute_in_docker(tool_call)
else:
# 在宿主机执行
result_list = await self._original_executor.sequential_tool_call([tool_call])
result = result_list[0]
results.append(result)
return results
Docker 内执行
def _execute_in_docker(self, tool_call: ToolCall) -> ToolResult:
"""在 Docker 容器中执行工具"""
# 1. 参数预处理(路径翻译)
processed_args = {}
for key, value in tool_call.arguments.items():
if key == "path" and isinstance(value, str):
processed_args[key] = self._translate_path(value)
else:
processed_args[key] = value
# 2. 构建命令
if tool_call.name == "bash":
command_to_run = processed_args.get("command")
elif tool_call.name == "str_replace_based_edit_tool":
executable_path = f"{self._docker_manager.CONTAINER_TOOLS_PATH}/edit_tool"
cmd_parts = [executable_path, processed_args.get("command")]
for key, value in processed_args.items():
if key != "command" and value is not None:
cmd_parts.append(f"--{key} '{str(value)}'")
command_to_run = " ".join(cmd_parts)
elif tool_call.name == "json_edit_tool":
executable_path = f"{self._docker_manager.CONTAINER_TOOLS_PATH}/json_edit_tool"
# ... 类似构建 ...
# 3. 执行命令
exit_code, output = self._docker_manager.execute(command_to_run)
return ToolResult(
call_id=tool_call.call_id,
name=tool_call.name,
result=output,
success=exit_code == 0,
)
三、与 Agent 的集成
3.1 BaseAgent 初始化
文件: trae_agent/agent/base_agent.py
class BaseAgent:
def __init__(self, agent_config: AgentConfig, docker_config: dict | None = None, ...):
# 创建原始工具执行器
original_tool_executor = ToolExecutor(self._tools)
if docker_config:
# 创建 Docker 管理器
self.docker_manager = DockerManager(
image=docker_config.get("image"),
container_id=docker_config.get("container_id"),
dockerfile_path=docker_config.get("dockerfile_path"),
docker_image_file=docker_config.get("docker_image_file"),
workspace_dir=docker_config["workspace_dir"],
tools_dir=tools_dir,
interactive=False,
)
# 创建 Docker 工具执行器
self._tool_caller = DockerToolExecutor(
original_executor=original_tool_executor,
docker_manager=self.docker_manager,
docker_tools=["bash", "str_replace_based_edit_tool", "json_edit_tool"],
host_workspace_dir=docker_config.get("workspace_dir"),
container_workspace_dir=self.docker_manager.container_workspace,
)
else:
# 不使用 Docker
self._tool_caller = original_tool_executor
self.docker_manager = None
3.2 执行流程
async def execute_task(self) -> AgentExecution:
"""执行任务"""
# 1. 启动 Docker 容器(如果配置了)
if self.docker_manager:
self.docker_manager.start()
try:
# 2. 执行主循环
while step_number <= self._max_steps:
# ... 执行步骤 ...
pass
finally:
# 3. 清理资源
if self.docker_manager and not self.docker_keep:
self.docker_manager.stop()
四、使用方式
4.1 命令行使用
# 使用 Docker 执行
trae-cli run \
--file problem.txt \
--docker-image "python:3.11" \
--docker-workspace-dir ./workspace
# 使用 Dockerfile 构建
trae-cli run \
--file problem.txt \
--docker-dockerfile ./Dockerfile \
--docker-workspace-dir ./workspace
# 保持容器(调试用)
trae-cli run \
--file problem.txt \
--docker-image "python:3.11" \
--docker-keep
4.2 编程方式使用
from trae_agent.agent import TraeAgent
# Docker 配置
docker_config = {
"image": "python:3.11-slim",
"workspace_dir": "/home/user/project",
}
# 创建 Agent
agent = TraeAgent(
config=agent_config,
docker_config=docker_config,
docker_keep=False, # 执行完后删除容器
)
# 执行任务
execution = await agent.execute_task()
4.3 复用现有容器
# 附加到现有容器
docker_config = {
"container_id": "abc123def456",
"workspace_dir": "/home/user/project",
}
agent = TraeAgent(config, docker_config=docker_config)
五、设计特点
| 特点 | 说明 |
|---|---|
| 透明代理 | 对 Agent 透明,无需修改工具调用代码 |
| 路径自动翻译 | 自动处理宿主机和容器之间的路径映射 |
| 混合执行 | 支持部分工具在 Docker,部分在宿主机 |
| 生命周期管理 | 自动启动/停止容器,支持复用现有容器 |
| 交互式 Shell | 使用 pexpect 维护持久化 Shell 会话 |
| 工具隔离 | 在容器中执行危险操作(如 bash) |
六、关键文件
| 文件 | 功能 |
|---|---|
trae_agent/agent/docker_manager.py | Docker 容器管理 |
trae_agent/tools/docker_tool_executor.py | 工具执行代理 |
trae_agent/agent/base_agent.py | Agent 基类集成 |
七、注意事项
- 路径映射:确保宿主机工作目录正确映射到容器
- 工具复制:工具需要复制到容器中才能使用
- 权限问题:容器内可能需要调整文件权限
- 性能开销:Docker 执行有一定性能开销
- 调试模式:使用
docker_keep=True保留容器用于调试
最后更新: 2026-03-16