For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
主导航

本地 Shell

让智能体能够在本地 Shell 中运行命令。

本地 Shell 工具已过时。对于新的使用场景,请改用 shell 工具搭配 GPT-5.1。了解 更多

本地 Shell 工具允许智能体在您或用户提供的机器上本地运行 Shell 命令。它专为配合 Codex CLIcodex-mini-latest 使用而设计。命令在您自己的运行时中执行,因此 您可以完全控制实际运行哪些命令。API 仅返回指令,不会在 OpenAI 基础设施上执行这些指令。

本地 Shell 可通过 Responses APIcodex-mini-latest 配合使用。其他模型不支持此工具,也无法通过 Chat Completions API 使用。

运行任意 Shell 命令可能存在危险。在将命令转发到系统 Shell 之前,务必确保命令在沙盒中执行,或添加严格的允许列表或拒绝列表。


参考实现请参阅 Codex CLI

工作原理

本地 Shell 工具让智能体能够访问终端并持续循环运行。

模型发送 Shell 命令,由您的代码在本地机器上执行,再将输出返回给模型。通过这一循环,模型无需用户额外干预即可完成构建、测试、运行的循环。

您的代码必须实现一个循环,监听 local_shell_call 输出项并执行其中的命令。我们强烈建议在沙盒中执行这些命令,以防止运行非预期的命令。

集成本地 Shell 工具

在您的应用中集成本地 Shell 工具,需要遵循以下主要步骤:

  1. 向模型发送请求: 将 local_shell 工具加入可用工具列表。

  2. 接收模型的响应: 检查响应是否包含 local_shell_call 项。 此工具调用包含 exec 等操作,以及要执行的命令。

  3. 执行请求的操作: 在您控制的本地环境中运行命令。

  4. 返回操作输出: 执行操作后,将命令输出返回给模型。

  5. 重复上述步骤: 发送新请求,将更新后的状态作为 local_shell_call_output 传入,并重复这一循环,直到模型不再请求操作或您决定停止。

工作流程示例

下面是一个展示请求与响应循环的最简示例。选择一种语言, 即可查看对应 SDK 的等效工作流程。为简洁起见,示例省略了生产级 沙盒隔离和安全检查。在未采取额外防护措施的情况下, 请勿在生产环境中执行不受信任的命令

import { spawn } from "node:child_process";
import process from "node:process";
import OpenAI from "openai";

const client = new OpenAI();
const MAX_TIMEOUT_MS = 10_000;

function runCommand(command, options) {
  return new Promise((resolve) => {
    let stdout = "";
    let stderr = "";
    let settled = false;
    let groupPoll;
    const child = spawn(command[0], command.slice(1), {
      ...options,
      detached: process.platform !== "win32",
      stdio: ["ignore", "pipe", "pipe"],
    });
    const finish = (suffix = "") => {
      if (settled) return;
      settled = true;
      clearTimeout(timer);
      clearTimeout(groupPoll);
      resolve(stdout + stderr + suffix);
    };
    const processGroupIsRunning = () => {
      if (process.platform === "win32" || !child.pid) return false;
      try {
        process.kill(-child.pid, 0);
        return true;
      } catch {
        return false;
      }
    };
    const finishAfterProcessGroup = (suffix) => {
      if (settled) return;
      if (processGroupIsRunning()) {
        groupPoll = setTimeout(() => finishAfterProcessGroup(suffix), 10);
      } else {
        finish(suffix);
      }
    };
    const killProcessTree = () => {
      try {
        if (process.platform !== "win32" && child.pid) {
          process.kill(-child.pid, "SIGKILL");
        } else {
          child.kill("SIGKILL");
        }
      } catch {
        child.kill("SIGKILL");
      }
      child.stdout?.destroy();
      child.stderr?.destroy();
    };
    const timer = setTimeout(() => {
      killProcessTree();
      finish("Command timed out.\n");
    }, options.timeout);

    child.stdout?.on("data", (chunk) => {
      stdout += chunk;
    });
    child.stderr?.on("data", (chunk) => {
      stderr += chunk;
    });
    child.on("error", (error) => {
      finish(`Command failed: ${error.message}.\n`);
    });
    child.on("close", (code, signal) => {
      if (signal) {
        finishAfterProcessGroup(`Command failed with signal ${signal}.\n`);
      } else if (code !== 0) {
        finishAfterProcessGroup(`Command failed with exit code ${code}.\n`);
      } else {
        finishAfterProcessGroup("");
      }
    });
  });
}

let response = await client.responses.create({
  model: "codex-mini-latest",
  tools: [{ type: "local_shell" }],
  parallel_tool_calls: false,
  input: "List files in the current directory.",
});

while (true) {
  const shellCall = response.output.find(
    (item) => item.type === "local_shell_call"
  );
  if (!shellCall) break;

  const { command, env, timeout_ms, user, working_directory } =
    shellCall.action;
  let output;
  if (user) {
    output = `Unsupported execution user: ${user}.\n`;
  } else if (command.length === 0) {
    output = "Command is empty.\n";
  } else {
    const timeout =
      timeout_ms && timeout_ms > 0
        ? Math.min(timeout_ms, MAX_TIMEOUT_MS)
        : MAX_TIMEOUT_MS;
    try {
      output = await runCommand(command, {
        cwd: working_directory ?? process.cwd(),
        env: { PATH: process.env.PATH ?? "", ...env },
        timeout,
      });
    } catch (error) {
      output = `Command failed: ${error instanceof Error ? error.message : String(error)}.\n`;
    }
  }

  response = await client.responses.create({
    model: "codex-mini-latest",
    tools: [{ type: "local_shell" }],
    parallel_tool_calls: false,
    previous_response_id: response.id,
    input: [
      {
        type: "local_shell_call_output",
        id: shellCall.call_id,
        output,
      },
    ],
  });
}

console.log(response.output_text);

最佳实践

  • 沙盒或容器中 执行命令。可以考虑使用 Docker 或 受隔离限制的用户账户。
  • 设置资源限制 (时间、内存、网络)。模型提供的 timeout_ms 仅供参考,您应强制执行自己设定的限制。
  • 过滤或仔细审查 高风险命令(例如 rmcurl、 网络工具)。
  • 记录每条命令及其输出 ,以便审计和调试。

错误处理

如果命令在您这边执行失败,例如退出码非零或超时,您仍然可以发送 local_shell_call_output;请在 output 字段中包含错误消息。

模型可以选择从错误中恢复,或尝试执行其他命令。如果您发送的数据格式不正确(例如缺少 id),API 会返回标准的 400 验证错误。