首页
/ agno CodeMode 实战:用持久化 Python 内核构建编程型 Agent(附 01_basics 测试日志全解析)

agno CodeMode 实战:用持久化 Python 内核构建编程型 Agent(附 01_basics 测试日志全解析)

2026-09-09 21:12:50作者:蔡丛锟

导读

本篇技术指南围绕 agno 开源仓库中 cookbook/code/01_basics 目录下的 basic.pywith_shell.py 两个最小可运行示例,及其对应的 TEST_LOG.md 测试记录展开。你将掌握 agno CodeMode 工具集的完整用法:如何让 Agent 在一个随会话存活的 IPython 内核中编写并执行 Python 与 %%bash 单元格,如何理解内核变量跨轮次持久化、快照恢复与安全边界,以及如何对照真实测试结果验证 Agent 的行为。读完本文,你可以在自己的 agno 项目中用 20 行代码复现一个"零工具面"的编程型 Agent。

一、背景:为什么需要一个"可编程环境"而不是一堆工具

传统的工具型 Agent 思路是给模型一张宽泛的工具 schema:web_searchread_fileexecute_python……工具越多,模型的选择开销越大,输出上下文也越容易被中间结果撑爆。agno CodeMode 提供了另一种思路——给模型一个"活着的解释器"作为唯一环境。

cookbook/code/01_basics/basic.py 的模块注释可以看到它的设计目标:

"Give the agent one programmable environment instead of a wide tool schema. The model writes Python; the code runs in an IPython kernel that lives as long as the session, so variables, imports, and helper functions survive across turns."

核心收益体现在两个层面:

  • 状态持久化:中间计算结果以变量形式保存在内核中,不进入对话 transcript,只在最后输出结论。例如 basic.py 中前 200 个斐波那契数完全以内核变量的形式存在,对话里只出现"67 even; 42 digits"这样的结论。
  • 零额外工具面:模型只需要 execute(以及可选的 restart)两个函数即可完成任意计算任务。

二、关联文档解读:01_basics 的测试记录

TEST_LOG.md01_basics 目录下的官方测试日志,记录于 2026-08-21,验证了该目录两个示例在真实环境中的运行结果。测试基线如下:

  • 模型:gpt-5.5(通过 agno 的 OpenAIResponses 模型类调用)
  • 内核依赖:ipykernel 7.3.0、jupyter_client 8.9.1、dill 0.4.1
  • 运行环境:仓库 worktree 自带的 Python(agno 来自当前分支,from agno.tools.code import CodeMode

日志还特别说明了一个无害的告警:每次运行 stderr 都会打印一行 [IPKernelApp] WARNING | Kernel is running over TCP without encryption...。这是 ipykernel 7 在 kernel 启动时产生的标准提示,对 CodeMode 运行的仅回环(loopback-only)内核不构成实际风险,也不属于该分支引入的变更。了解这一点,可以在调试时避免误判。

三、示例一:basic.py——用内核变量完成斐波那契计算

3.1 代码解析

cookbook/code/01_basics/basic.py 是"最小的可工作 CodeMode Agent":

from agno.agent import Agent
from agno.models.openai import OpenAIResponses
from agno.tools.code import CodeMode

code = CodeMode()

agent = Agent(
    model=OpenAIResponses(id="gpt-5.5"),
    tools=[code],
    instructions="Use the code environment to compute answers. Print summaries, not raw data.",
    markdown=True,
)

if __name__ == "__main__":
    try:
        agent.print_response(
            "Build a list of the first 200 Fibonacci numbers in the code environment, "
            "keep it in a variable, and tell me only how many of them are even and "
            "how many digits the largest one has.",
            session_id="code-mode-basic",
        )
    finally:
        code.shutdown()

几个关键点:

  • CodeMode() 无参数构造:默认开启 executerestart 两个工具、允许 %%bash 单元格、启用快照(见下文参数表)。
  • instructions 要求"只打印摘要,不打印原始数据":这是让中间数据留在内核、只让结论进入对话的关键 prompt 设计。
  • session_id 显式指定:CodeMode 按 session_id 一对一维护内核与快照,固定 session_id 可以让同一内核跨多次运行复用。
  • finally: code.shutdown():显式释放内核。源码中 code_mode.pyshutdown 会先快照再杀掉内核;即便开发者忘记调用,对象销毁时 weakref.finalize 注册的 _cleanup_kernels 也会兜底清理,但显式调用仍是推荐做法。

3.2 测试结果与验证

TEST_LOG 记录的运行结果为:一次 execute 调用即完成任务,单元格打印 even_count=67, largest_digits=42,Agent 回答 "67 even; 42 digits"。日志确认了正确性:该单元格构建的是从零开始的斐波那契序列(zero-first sequence),其最大项 173402521172797813159685037284371942044301 恰好是 42 位数字。进程退出码为 0,无 traceback。

四、示例二:with_shell.py——%%bash 单元格的规则

4.1 代码解析

cookbook/code/01_basics/with_shell.py 演示 IPython 单元格魔法(cell magic)随内核免费可用,模型可以直接使用 %%bash 进行 shell 编排:

code = CodeMode()

agent = Agent(
    model=OpenAIResponses(id="gpt-5.5"),
    tools=[code],
    instructions=[
        "Use the code environment for shell work.",
        "Report only what you learned, not raw command output.",
    ],
    markdown=True,
)

if __name__ == "__main__":
    try:
        agent.print_response(
            "Using a %%bash cell, count how many Python files are in the current "
            "directory tree, then report the count and the Python version the "
            "environment is running.",
            session_id="code-mode-shell",
        )
    finally:
        code.shutdown()

4.2 关于 %%bash 的三个关键规则

结合源码与 README,%%bash 的使用必须遵守以下规则:

  1. %%bash 必须是单元格的第一行。CodeMode 生成的内核指令明确要求 "%%bash must be the first line of its cell - no comment, import, or statement before it"。源码在 code_mode.py 中甚至对"位置错误的 %%bash"会追加一条 hint:%%bash must be the first line of its cell - move any comment, import, or statement into a separate cell
  2. 每个 %%bash 单元格是一次性子 shell(throw-away subshell)cdexport 和 shell 变量不会延续到下一个单元格。依赖 shell 状态的步骤必须放在同一个单元格里,或者改用内核级操作。
  3. %cdos.environ[...] 是内核级的,会持久生效:它们作用于之后的每一个 %%bash 单元格。这一设计见 README.md 的说明。

4.3 测试结果与验证

TEST_LOG 记录了该示例最有说服力的一次行为:模型在第一个单元格直接调用了裸 python,而测试机器上不存在 python 命令;Agent 自主重新发起,改用 python3 后成功。最终回答为:4,577 个 Python 文件、Python 3.14.6。日志验证方式如下,可以视为复现该示例的判据:

find . -name '*.py' -type f | wc -l   # 应输出 4577
python3 -V                             # 应输出 Python 3.14.6

进程同样退出码为 0,无 traceback。

4.4 allow_shell=False 的降级策略

README 与脚本注释都强调:传入 allow_shell=False 会移除 %%bash 魔法,任何 %%bash 单元格都会被拒绝。源码中 _rejects_shellcode_mode.py 实现判断,被拒绝时返回 Error: %%bash cells are disabled (allow_shell=False)。对应单元测试 test_code.py 验证了这一点。但务必注意:这只是"减少误触发的保险丝"(a footgun reducer),不是安全边界——CodeMode 从来不是沙箱。

五、源码纵深:CodeMode 的架构与核心参数

5.1 架构总览

CodeMode 是 agno 的一个 Toolkit 子类,实现在 code_mode.py。模块 agno/tools/code 由以下部分组成:

  • code_mode.py——CodeMode 本体:模型面工具、生命周期、快照调度
  • kernel.py——KernelSession:一个 session 对应一个 IPython 内核子进程,负责单元格执行、超时、忙碌策略
  • snapshot.py——SnapshotManager:基于 dill 的会话状态持久化
  • bridge.py——ToolBridge:把注入的工具桥接进内核,作为可 await 的句柄
  • errors.pytypes.py——错误类型与 CellResult 数据结构

kernel.py 的模块注释可以看到其底层设计:每个 KernelSession 拥有一个 IPython 内核子进程,通过显式的 KernelSpec 启动,从不查询系统安装的 kernelspec,解释器精确等于所请求的 python;所有 ZMQ 通道、锁与定时器运行在单一后台事件循环(LoopRunner)上,使同步与异步工具面共享同一客户端和同一把按会话的 asyncio.Lock

5.2 完整参数表(来自构造函数)

CodeMode.__init__code_mode.py 中定义了以下可配置项,均为可选:

参数 默认值 作用说明
tools None 注入到内核中的工具序列(Toolkit / Callable / Function),以内核内可 await 句柄形式提供
fs None FileSystem 实例,用于存放快照;不传则关闭快照持久化
snapshot True 是否启用会话快照(dill 序列化,恢复即执行代码)
snapshot_debounce 1.5 快照防抖间隔(秒)
max_variable_bytes 2_000_000 单个变量超过该字节数则不入快照,需重建
max_snapshot_bytes 64_000_000 会话总状态超过该字节数则不入快照
max_output_chars 65_536 单单元格输出字符上限(流式截断)
max_result_bytes 1_000_000 工具结果负载上限,超出抛 ResultTooLarge
allow_restart True 是否向模型暴露 restart 工具(内核损坏/卡死时重建)
allow_shell True 是否允许 %%bash 单元格;False 时直接拒绝
on_busy_kernel "wait" 内核忙时策略:"wait" 等待(busy_wait 秒)或 "restart" 强制重启后重跑
busy_wait 5.0 忙碌轮询等待秒数
idle_ttl 1800 空闲淘汰时间(秒),超时内核被回收
timeout 300 每个单元格的执行超时;None 表示不设限(有挂死风险,生产环境务必保留超时)
python None 内核解释器路径,显式指定则完全可控
cwd None 内核工作目录
env None 内核环境变量字典
startup_code None 内核启动时执行的代码
max_images_per_cell 8 单单元格最多返回的图片数
max_image_bytes 5_000_000 单张图片大小上限
max_kernels None 进程内同时保活的内核上限,超出按 LRU 淘汰(先快照再淘汰)

依赖项方面,模块在 init.py 中强制检查 dillipykerneljupyter_client,缺失时会提示安装命令:pip install 'agno[code]'

5.3 模型面工具与开发者面工具

CodeMode 对外暴露两类接口(均有同步/异步成对实现,且被 test_code.py 的测试强制保证"每个公开方法都有 async 孪生"):

  • 模型面execute(run_context, code)——执行一个单元格并返回 stdout、stderr、末表达式 repr 与 traceback;restart(run_context)——重建内核,清空全部状态。
  • 开发者面run(session_id, code) 返回原始 CellResultvariables(session_id) 返回"变量名 → 类型名"映射(跳过下划线前缀与 IPython 内部名);value(session_id, name) 通过 dill 往返取出单个变量;shutdown(session_id=None) 快照后杀内核(None 表示全部)。

值得注意的安全细节:_session_keycode_mode.py 中强制 session_id 只来自框架注入的 RunContext,"模型永远无法指定参数去触达另一个会话的内核"。

5.4 快照与多用户隔离

  • 快照:启用 fssnapshot=True 时,SnapshotManager 会周期性把内核命名空间 dill 序列化保存。恢复(restore)也是执行代码,因此快照存储继承持有它的数据库的信任级别——这也是源码在模块 docstring 中强调的信任模型。恢复发生在 bootstrap 之前(code_mode.py),保证新鲜的动态句柄覆盖过期的 pickle 句柄。
  • 用户隔离:会话在内存与快照清单中记录归属用户(owner),后续其他用户的 run 访问同一 session_id 时会被拒绝并返回 OWNER_REFUSALcode_mode.py)。这是多租户场景下防止 A 用户读到 B 用户内核变量的关键机制。
  • 会话共享语义:内核与快照仅按 session_id 键控。团队模式下 leader 与成员共享同一 session_id 时,会共享同一个内核命名空间;两个 CodeMode 实例快照进同一 FileSystem 且使用相同 session_id 时,会互相恢复对方的变量。若不希望共享,应给每个 CodeMode 独立的 FileSystem 命名空间。

六、安全边界:明确这不是沙箱

这是使用 CodeMode 前必须读三遍的结论。源码在 code_mode.py 的模块 docstring 中开宗明义:

"CodeMode executes arbitrary Python and shell with the permissions of the process running the agent. It is not a sandbox and does not pretend to be one: use it with a trusted operator or inside an isolated container."

同时在运行时,每次调用 execute/restart 都会经 _warn() 输出一次警告:CodeMode 以当前进程权限执行任意 Python 与 shell,不是沙箱,需要人工监督或在隔离容器中运行(code_mode.py)。allow_shell=False 只能降低误用概率,不能提供隔离。

七、运行方式与复现建议

README.md 的说明,从仓库根目录直接运行即可:

python cookbook/code/01_basics/basic.py
python cookbook/code/01_basics/with_shell.py

复现 TEST_LOG 记录的判定标准:

  • basic.py 应输出 "67 even; 42 digits",且只发生一次 execute 调用;
  • with_shell.py 应输出 4,577 个 Python 文件与当前解释器版本号;
  • 两条脚本均以退出码 0 结束,无 traceback;
  • stderr 上出现 [IPKernelApp] WARNING | Kernel is running over TCP without encryption... 属正常现象,不影响功能。

八、总结

TEST_LOG.md 的两次真实运行可以看到,CodeMode 把一个"可编程环境"完整地交给了模型:basic.py 用内核变量承载 200 个斐波那契数,对话中只出现结论;with_shell.py 让模型自主完成 pythonpython3 的纠错后统计出 4,577 个 Python 文件。二者都没有引入任何除 execute 之外的模型面工具。结合 code_mode.py 源码可知,其能力边界由内核会话、dill 快照、忙碌/超时策略与明确的非沙箱警告共同定义。对于需要"计算密集型 + 中间状态不污染上下文 + 跨轮次复用"的 Agent 场景,这是 agno 中一个开箱即用的高性价比选择。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395