跳转到内容

在持久内核中探索数据

本指南介绍如何让智能体只加载一次昂贵的数据,然后就它提出好几个问题,而不是为每个问题都付一遍加载的代价。

eval 工具运行的是一个持久解释器。变量、导入和已加载的数据都会在多次调用之间保留。

一次性的 python -c 会为每个问题都付一遍初始化开销。加载一个数据框要花几秒和上百兆字节;而把它画成图只要几毫秒。当每个问题的代价都是那次加载时,智能体问的问题就会比它本该问的更少——而且每个答案都以文本形式返回,它还得再解析一遍。

加载 data/runs.csv,然后告诉我每个智能体的 p95 耗时。

import pandas as pd
runs = pd.read_csv("data/runs.csv")
len(runs)

最后一个表达式会像 notebook 那样被回显,因此 len(runs) 无需 print 就能报告行数。

然后,在另一次调用中:

runs.groupby("agent")["duration_ms"].quantile(0.95).sort_values()

runs 仍然处于已加载状态。pandas 数据框会以表格形式返回,而不是以 repr 形式返回。

import matplotlib.pyplot as plt
runs.groupby("agent")["duration_ms"].mean().plot.bar()

图形会在单元格执行结束后被捕获,并以图像形式返回,由转录渲染出来。你能看到图表,智能体也能看到。

图形在捕获后会被关闭。一个没有被回收的图形也会被下一个单元格重新绘制并再次发送,因此一次包含五个单元格的会话会把第一张图表发五遍。

这些写法会被改写成普通代码,而不是直接报错:

你输入的写法 会发生什么
%pip install x 运行 pip,然后清空模块缓存,让下一次 import 能看到它
%cd path os.chdir
!command 被捕获的子进程
%%bash 把单元格的剩余部分作为 shell 脚本执行
%%timeit 1000 次计时运行

其中真正要紧的是 %pip 的缓存逐出:往一个正在运行的解释器里安装东西,对已经导入过的模块毫无作用,所以少了这一步,你刚安装的版本就不是正在运行的那个版本。

paths = tool("find", {"pattern": "**/*.dart", "path": "lib"})
for path in paths.splitlines()[:100]:
contents = tool("read", {"path": path})
...

tool(name, args) 会重新进入智能体自己的工具注册表。这意味着一个单元格可以对上百个文件做扇出,或者通过 task 进行委派,而无需模型为每个条目花掉一个回合。

桥接调用在尚未返回期间会暂停单元格的超时计时。这份预算衡量的是无活动时间,而不是实际耗时——一个扇出到子智能体、等了四分钟的单元格并不是卡死了,而在扇出途中把它杀掉会丢失整个内核的状态。

eval(code: "…", reset: true, description: "Start a clean interpreter")

丢弃当前解释器并启动一个干净的解释器。所有已加载的内容都会消失,而这正是重点。

对话有隔离舱时就运行在隔离舱里,否则运行在宿主机上。

由模型驱动的、正在读取不受信任仓库的持久解释器,正是“只在隔离舱中执行”这套机制存在的理由:一次性的 bash 调用至少会结束,而内核是一个会记住东西的 shell。

它不会为了做到这一点而启动一个隔离舱——这和阻止机台标签页自动启动的是同一条规则。如果你希望内核在隔离舱中运行,请先打开该对话的 shell。参见给智能体一台用于测试的机器。

  • 每个内核同一时间只能有一个单元格。 两个单元格在同一个解释器里争抢,就是两个程序的两半在同一个命名空间里交错执行。
  • 输出有上限。 处于打印循环中的单元格会被截断,而不会任由它填满上下文窗口。
  • 每个单元格最多四张图像。 绘图循环可能在每次迭代时都产出一个图形,而每一张图像都是承载它的那次请求中一笔很大的固定开销。
  • 内核会随运行一起终止。 对话结束后仍然占着一个数据框的内核就是一处泄漏。