Trae-Agent中的docker逻辑

4 阅读5分钟

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使用已有镜像启动容器
容器 IDcontainer_id附加到现有容器
Dockerfiledockerfile_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.pyDocker 容器管理
trae_agent/tools/docker_tool_executor.py工具执行代理
trae_agent/agent/base_agent.pyAgent 基类集成

七、注意事项

  1. 路径映射:确保宿主机工作目录正确映射到容器
  2. 工具复制:工具需要复制到容器中才能使用
  3. 权限问题:容器内可能需要调整文件权限
  4. 性能开销:Docker 执行有一定性能开销
  5. 调试模式:使用 docker_keep=True 保留容器用于调试

最后更新: 2026-03-16