代码执行

假设 agent 要分析一份销售数据:先把 CSV 写入沙箱,在有状态 kernel 中加载并计算,再生成图表,保存后下载。

数据集和图表通过文件 API 传递,分析过程在 kernel 会话中执行。

如果不需要保留状态,只调用一次 POST /v2/code/executePOST /v1/code/execute 即可。

要求

GET /v2/sandboxcapabilities 中应包含 code_interpreter

GET /v1/capabilities 的返回结果中应包含 code_interpreter

kernel 会话要求主机 Python 安装 ipykernel,并安装 pandasmatplotlib。可以通过 code_interpreter.python_kernels 检查 kernel 是否可用;AIO 镜像已内置这三个包。

在 kernel 会话中分析数据集

kernel 在多次调用之间保留 df,并返回富输出:DataFrame 为 text/html,图表为 image/png。它写下的文件留在沙箱里,由文件 API 提供。示例用 Agg 后端绘图并以 savefig 保存,因此在两种 Python 后端上都能运行,图表通过文件 API 取回。

Python
TypeScript
BASE_URL = "http://127.0.0.1:18091"
SESSION = "analysis"
sb = Aio(BASE_URL)

# 1. The dataset, written through the file plane.
sb.post("/v2/fs/write", path="/tmp/analysis/sales.csv", content=(
    "month,revenue,cost\n2026-01,120,80\n2026-02,135,82\n2026-03,150,90\n"
    "2026-04,142,95\n2026-05,168,99\n2026-06,180,104\n"
))

# 2. Name a session, then load and compute in it.
first = sb.post("/v2/code/execute", language="python", session_id=SESSION, code="""
import pandas as pd
df = pd.read_csv("/tmp/analysis/sales.csv")
df["margin"] = df.revenue - df.cost
print(df.margin.describe())
""")
print(first["outputs"][0]["text"])
# -> "count     6.000000\nmean     57.500000\nstd      13.546217\n…"

# 3. Plot in the same session, and save the chart into the sandbox.
sb.post("/v2/code/execute", language="python", session_id=SESSION, code="""
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
df.plot(x="month", y=["revenue", "cost"], kind="bar", figsize=(6, 3))
plt.tight_layout()
plt.savefig("/tmp/analysis/plot.png", dpi=120)
""")

# 4. The chart is a sandbox file now; the agent sees it on the file plane.
files = sb.get("/v2/fs/list", path="/tmp/analysis")["files"]
print([(f["name"], f["size"]) for f in files])

# 5. Download the bytes: attachment; filename="plot.png".
chart = sb.http.get("/v2/fs/download", params={"path": "/tmp/analysis/plot.png"})
print(len(chart.content))
open("margin.png", "wb").write(chart.content)

sb.delete(f"/v2/code/sessions/{SESSION}")
Python
TypeScript
from agent_sandbox import Sandbox

BASE_URL = "http://127.0.0.1:18091"
# The SDK's default timeout is 60 s; kernel start can be slower, so raise it.
client = Sandbox(base_url=BASE_URL, timeout=120)

# 1. The dataset, written through the file plane.
client.file.write_file(file="/tmp/analysis/sales.csv", content=(
    "month,revenue,cost\n2026-01,120,80\n2026-02,135,82\n2026-03,150,90\n"
    "2026-04,142,95\n2026-05,168,99\n2026-06,180,104\n"
))

# 2. Create a session, then load and compute in it.
session_id = client.jupyter.create_session().data.session_id

first = client.jupyter.execute_code(session_id=session_id, code="""
import pandas as pd
df = pd.read_csv("/tmp/analysis/sales.csv")
df["margin"] = df.revenue - df.cost
print(df.margin.describe())
""").data
print(first.outputs[0].text)
# -> "count     6.000000\nmean     57.500000\nstd      13.546217\n…"

# 3. Plot in the same session, and save the chart into the sandbox.
client.jupyter.execute_code(session_id=session_id, code="""
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
df.plot(x="month", y=["revenue", "cost"], kind="bar", figsize=(6, 3))
plt.tight_layout()
plt.savefig("/tmp/analysis/plot.png", dpi=120)
""")

# 4. The chart is a sandbox file now; the agent sees it on the file plane.
files = client.file.list_path(path="/tmp/analysis").data.files
print([(f.name, f.size) for f in files])

# 5. Download the bytes: attachment; filename="plot.png".
chart = b"".join(client.file.download_file(path="/tmp/analysis/plot.png"))
print(len(chart))
open("margin.png", "wb").write(chart)

client.jupyter.delete_session(session_id=session_id)

outputs 是 notebook 输出列表:

output_type内容
streamstdout 或 stderr 的 text
execute_resultdatatext/plain,DataFrame 另有 text/html
display_datadata 含图表的 image/png
errorenameevaluetraceback

Agent 循环可以把 stream 文本和 error 的 traceback 回传给模型,并复用同一个会话。

这样模型就能在同一个 df 上继续迭代。kernel 会话空闲 300 秒后回收;前一个代码块的最后一行可以提前结束会话。

模型看到的输出

下面两种结构都来自 kernel 后端。在该会话中执行 df.head(3) 会返回一个 execute_result,其中同时包含表格的两种表示。

下面的 text/html 已裁剪,只保留结构;完整内容有 819 个字符。

配合 %matplotlib inline 绘制的图表属于 display_data,其中的 image/png 是不带 data: 前缀的裸 base64。

只出现该输出类型用得到的字段:

{
  "data": {
    "text/html": "<div>\n<style scoped>\n…\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>month</th>\n…\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>2026-01</td>\n      <td>120</td>\n      <td>80</td>\n      <td>40</td>\n    </tr>\n…\n  </tbody>\n</table>\n</div>",
    "text/plain": "     month  revenue  cost  margin\n0  2026-01      120    80      40\n1  2026-02      135    82      53\n2  2026-03      150    90      60"
  },
  "execution_count": 2,
  "metadata": {},
  "output_type": "execute_result"
}
{
  "data": {
    "image/png": "iVBORw0KGgoAAAANSUhEUgAAAk4AAAEiCAYAAAAPh11JAAAAOnRFWHRTb2Z0…",
    "text/plain": "<Figure size 600x300 with 1 Axes>"
  },
  "metadata": {},
  "output_type": "display_data"
}

每个输出对象都带同样九个字段,该类型用不到的填 null

{
  "data": {
    "text/html": "<div>\n<style scoped>\n…\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>month</th>\n…\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>2026-01</td>\n      <td>120</td>\n      <td>80</td>\n      <td>40</td>\n    </tr>\n…\n  </tbody>\n</table>\n</div>",
    "text/plain": "     month  revenue  cost  margin\n0  2026-01      120    80      40\n1  2026-02      135    82      53\n2  2026-03      150    90      60"
  },
  "ename": null,
  "evalue": null,
  "execution_count": 2,
  "metadata": {},
  "name": null,
  "output_type": "execute_result",
  "text": null,
  "traceback": null
}
{
  "data": {
    "image/png": "iVBORw0KGgoAAAANSUhEUgAAAk4AAAEiCAYAAAAPh11JAAAAOnRFWHRTb2Z0…",
    "text/plain": "<Figure size 600x300 with 1 Axes>"
  },
  "ename": null,
  "evalue": null,
  "execution_count": null,
  "metadata": {},
  "name": null,
  "output_type": "display_data",
  "text": null,
  "traceback": null
}

一次性执行

不使用会话时,每次执行都会启动一个临时进程:

Python
TypeScript
BASE_URL = "http://127.0.0.1:18091"
sb = Aio(BASE_URL)

py = sb.post("/v2/code/execute", language="python", code="print(sum([1,2,3]))")
js = sb.post("/v2/code/execute", language="javascript",
             code="console.log([1,2,3].reduce((a,b)=>a+b))")
print(py["stdout"].strip(), js["stdout"].strip())
# -> 6 6
Python
TypeScript
from agent_sandbox import Sandbox

BASE_URL = "http://127.0.0.1:18091"
client = Sandbox(base_url=BASE_URL)

py = client.code.execute_code(language="python", code="print(sum([1,2,3]))").data
js = client.code.execute_code(language="javascript",
                              code="console.log([1,2,3].reduce((a,b)=>a+b))").data
print(py.stdout.strip(), js.stdout.strip())
# -> 6 6

两种写法都从返回结构中取出 data。Python 那次调用的完整应答是:

{
  "success": true,
  "message": "ok",
  "data": {
    "code": "print(sum([1,2,3]))",
    "language": "python",
    "status": "ok",
    "execution_count": 1,
    "exit_code": 0,
    "outputs": [
      {
        "output_type": "stream",
        "name": "stdout",
        "text": "6\n"
      }
    ],
    "stdout": "6\n",
    "stderr": "",
    "session_id": null
  }
}
{
  "success": true,
  "message": "ok",
  "data": {
    "code": "print(sum([1,2,3]))",
    "language": "python",
    "status": "ok",
    "execution_count": 1,
    "exit_code": 0,
    "outputs": [
      {
        "output_type": "stream",
        "name": "stdout",
        "text": "6\n"
      }
    ],
    "stdout": "6\n",
    "stderr": null,
    "session_id": null
  }
}

判断执行结果时,不要只看 HTTP 状态码。

代码抛出异常或执行超时,接口仍返回 HTTP 200,但 successfalse

情况successdata.status
代码抛出异常falseerror
执行超时falsetimeout

错误输出中的关键字段如下:

{
  "output_type": "error",
  "ename": "<exception type>",
  "evalue": "<error message>",
  "traceback": [
    "<stack trace>"
  ]
}

不同调用方式的失败处理:

调用方式失败时的行为
Aio helper检查 success;失败时抛出 RuntimeError
SDK原样返回响应,由调用方检查 success

富输出和后端

只有 Python 后端为 kernel 时,代码执行才支持富输出;即使不创建会话,也适用。

Python 后端DataFrame 输出图表输出
kernelexecute_result 中包含 text/htmldisplay_data 中包含 image/png
原生 REPL只有 text/plain不返回图表

通过 GET /v2/sandbox 中的 capabilities.code_interpreter.backend 查看当前后端:

{
  "capabilities": {
    "code_interpreter": {
      "backend": "kernel"
    }
  }
}

通过 GET /v1/capabilities 中的 code_interpreter.backend 查看当前后端:

{
  "data": {
    "code_interpreter": {
      "backend": "kernel"
    }
  }
}

设置 AIO_CODE_BACKEND=kernel 后,code 路由固定使用 kernel。无论该变量如何设置,/v1/jupyter/execute 始终使用 kernel。

会话复用

连续调用使用同一个 session_id,变量得以保留。会话在第一次使用时创建,不需要先开一个:

Python
TypeScript
BASE_URL = "http://127.0.0.1:18091"
sb = Aio(BASE_URL)

sb.post("/v2/code/execute", language="python", code="x = 21", session_id="s1")
r = sb.post("/v2/code/execute", language="python", code="print(x * 2)",
            session_id="s1")
print(r["stdout"].strip())
# -> 42
Python
TypeScript
from agent_sandbox import Sandbox

BASE_URL = "http://127.0.0.1:18091"
client = Sandbox(base_url=BASE_URL)

client.code.execute_code(language="python", code="x = 21", session_id="s1")
r = client.code.execute_code(language="python", code="print(x * 2)",
                             session_id="s1").data
print(r.stdout.strip())
# -> 42

会话空闲后会被回收:kernel 层 300 秒,原生 REPL 1800 秒。

单次执行的时长由 timeout 决定,默认 30 秒、最多 900 秒。code plane 的 info 路由会同时报告后端、解释器版本和这些上限。

有状态 JavaScript

命名会话同样能保留 JavaScript 全局变量;不带会话的调用是无状态的:

Python
TypeScript
BASE_URL = "http://127.0.0.1:18091"
sb = Aio(BASE_URL)

sb.post("/v2/code/execute", language="javascript", code="globalThis.n = 41",
        session_id="s1")
r = sb.post("/v2/code/execute", language="javascript", code="console.log(n + 1)",
            session_id="s1")
print(r["stdout"].strip())
# -> 42
Python
TypeScript
from agent_sandbox import Sandbox

BASE_URL = "http://127.0.0.1:18091"
client = Sandbox(base_url=BASE_URL)

client.nodejs.execute_code(code="globalThis.n = 41", session_id="s1")
r = client.nodejs.execute_code(code="console.log(n + 1)", session_id="s1").data
print(r.stdout.strip())
# -> 42

选择后端

code 路由上的 Python 使用第一个可用的后端:

  1. AIO_JUPYTER_ENDPOINT,可连通时
  2. 内嵌 kernel,安装了 ipykernel
  3. 原生 Python REPL

AIO_CODE_BACKEND=auto|native|kernel 固定选择。JavaScript 始终在原生 REPL 上执行。AIO_CODE_PREWARMAIO_KERNEL_PREWARM 启用预热池,默认都是 0

错误

情况结果
缺少字段、字段类型错误、不支持的 language422
会话不存在404
该语言没有解释器503,并说明缺少什么
代码异常或超时200success: falsedata.statuserrortimeout

相关页面