openai-agents-python 沙箱实战:基于 SandboxAgent 构建带源码引用的 10-K 金融问答 Agent
【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python
导读
本文围绕 openai-agents-python 仓库中的dataroom_qa沙箱教程示例,完整讲解如何构建一个"检索优先"(retrieval-first)的金融问答 Agent:它在一个有界的合成 10-K 年报语料包上运行,借助沙箱内的bash与文件检索能力查找证据,并以可点击的源码级引用(文本按行号、PDF 按页码)回答财务问题。读完本文,你将掌握沙箱 manifest 的加载方式、SandboxAgent的配置要点、Unix-local 与 Docker 两种运行模式,以及如何为自己的金融语料设计可验证的引用协议。
一、示例目标:在合成 10-K 数据包上做有依据的金融问答
examples/sandbox/tutorials/dataroom_qa/README.md开宗明义:本示例的目标是"Answer grounded financial questions over a synthetic 10-K packet",即基于一个合成 10-K 数据包回答有依据(grounded)的财务问题。
这里的"数据包"使用合成公司数据,但文档形态完全模仿真实年报节选:
- MD&A(管理层讨论与分析)文本使用 10-K 的Part II, Item 7结构;
- 财务报表 PDF 与脚注文本使用Part II, Item 8结构。
合成公司名为HelioCart, Inc.,财务口径保持一致性设计(例如 MD&A 中注明"净收入"与"收入"可互换使用、分部脚注注明"订阅与交易平台收入"与"平台分部收入"是同一指标),这些细节都用于考验 Agent 在交叉引用多个文件时是否能保持口径统一。
该模式的价值在于:在一个有界的金融语料上做检索优先的 Agent 工作流,让每一个指标和解释都与源文件保持绑定。相比直接依赖模型记忆作答,这种设计天然支持审计、追责与二次核验。
二、数据准备:运行 fixture 生成器
示例的输入数据并不是预置在仓库中的静态文件,而是由生成器脚本产出。从仓库根目录执行:
uv run python examples/sandbox/tutorials/data/dataroom/setup.pyexamples/sandbox/tutorials/data/dataroom/setup.py负责生成全部 8 个 fixture 文件:
| 文件 | 内容 | 对应 10-K 章节 |
|---|---|---|
10-k-mdna-overview.txt | 收入、毛利率、营业利润的年度对比 | Part II, Item 7 |
10-k-mdna-liquidity.txt | 经营现金流、资本开支、自由现金流 | Part II, Item 7 |
10-k-note-segments.txt | 分部收入(Platform / Services) | Part II, Item 8, Note 4 |
10-k-note-geography.txt | 地区收入(Americas / EMEA / APAC) | Part II, Item 8, Note 5 |
10-k-note-balance-sheet.txt | 现金、递延收入等资产负债表指标 | Part II, Item 8, Note 7 |
10-k-statements-of-operations.pdf | 经营成果表(净收入/毛利/营业利润) | 单页 PDF |
10-k-balance-sheets.pdf | 资产负债表(现金/应收/递延收入) | 单页 PDF |
10-k-statements-of-cash-flows.pdf | 现金流量表(经营现金流/资本开支/自由现金流) | 单页 PDF |
2.1 fixture 生成器的实现要点
生成器本身就是一个值得学习的小工具,它展示了如何在不依赖任何第三方 PDF 库的情况下手工构造可检索的合成 PDF:
write_plain_pdf()直接按 PDF 1.4 规范手工拼装对象(Catalog / Pages / Page / Contents / Font),用 Helvetica Type1 字体逐行写出文本,并生成合法的xref交叉引用表与trailer;write_financial_pdf()将表格行用" | ".join(row)拼成纯文本行写入 PDF,保证每行内容可被文本抽取工具读取;pdf_escape()负责转义 PDF 字符串中的\、(、)等特殊字符。
从源码结构看,这样设计的目的是让每个 PDF 恰好是单页纯文本,方便 Agent 用pypdf等工具按"页码"定位证据,与 README 中约定的n引用格式一一对应。
三、运行方式:Unix-local 与 Docker 双模式
3.1 前置条件
运行前需要在 shell 环境中设置OPENAI_API_KEY,并先生成 fixture 数据(见上一节)。main.py启动时会校验数据文件是否存在,若缺失会直接提示先运行setup.py。
3.2 Unix-local 模式(默认)
uv run python examples/sandbox/tutorials/dataroom_qa/main.py这是默认运行方式,直接在本机以 Unix-local 沙箱执行,无需 Docker。首次回答完成后,示例会保持沙箱会话打开,通过 Rich 渲染的交互提示符接收追问(终端会提示 "Enter follow-up prompts. Press Ctrl-D or Ctrl-C to finish.")。
3.3 一次性运行(非交互)
uv run python examples/sandbox/tutorials/dataroom_qa/main.py --no-interactive传入--no-interactive后,脚本只执行预设的问答回合并退出,适合 CI、批量验证或自动化场景。
3.4 Docker 模式
要复用同一个 manifest 在 Docker 中运行,需要先构建共享教程镜像,再传入--docker:
docker build --tag sandbox-tutorials:latest examples/sandbox/tutorials uv run python examples/sandbox/tutorials/dataroom_qa/main.py --dockerexamples/sandbox/tutorials/Dockerfile揭示了镜像的依赖构成:
- 基础镜像
python:3.14-slim,内置uv; - 安装
ca-certificates、git、poppler-utils(PDF 文本抽取)、ripgrep(证据检索); - 通过
uv pip install安装pypdf,用于解析 PDF 文本。
这些工具正是沙箱 Agent 在运行时执行检索命令所需的运行时依赖。
3.5 命令行参数速查
main.py通过argparse暴露了 4 个参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
--model | gpt-5.4-mini | 使用的模型名称 |
--question | 见下文默认问题 | 发送给 Agent 的提示词 |
--docker | False | 使用 Docker 沙箱替代 Unix-local |
--image | sandbox-tutorials:latest | 与--docker搭配使用的镜像名 |
--no-interactive | False | 只跑脚本回合,跳过终端追问 |
默认问题(DEFAULT_QUESTION)本身就是一份很好的"追问设计"范例,覆盖了利润表与现金流两个维度的交叉验证:
"How did revenue, gross margin, operating income, and operating cash flow change in FY2025 versus FY2024, and which segment contributed the most revenue?"
要回答这个问题,Agent 至少需要检索 MD&A 概览(收入/毛利率/营业利润)、流动性 MD&A(经营现金流)、分部脚注(收入贡献最大的分部),并核对 PDF 中的经营成果表,体现了多文件交叉检索的设计意图。
四、核心实现拆解:main.py 的五层结构
examples/sandbox/tutorials/dataroom_qa/main.py是示例的全部实现,其结构可以拆解为五个关键环节:
4.1 第 1 层:指令加载
README 中 "How instructions are loaded" 一节说明:启动时,wrapper 将本文件夹的AGENTS.md加载进 Agent 指令。在代码中,AGENTS.md的内容以dedent字符串硬编码在main.py中(AGENTS_MD常量),内容为:
# AGENTS.md Answer the user's financial question using only the synthetic 10-K packet in `data/`. ## Evidence & citations - Cite every material claim with markdown links in these formats (no bare links): - `1` for text sources - `2` for PDF sources (each synthetic PDF is one page) - Use `rg` and `sed` to find and quote exact evidence; do not use `data/setup.py`. Keep the final answer direct and finance-oriented.这段指令有三个关键约束:
- 只用
data/目录内的合成 10-K 数据包作答,防止模型凭记忆编造; - 每一条实质性主张都必须带引用,且规定了两种精确格式(文本按行号、PDF 按页码),禁止裸链接;
- 明确工具使用方式:用
rg和sed查找并引用精确证据,且禁止调用data/setup.py(防止 Agent 重新生成或篡改语料)。
4.2 第 2 层:manifest 构建
manifest = Manifest( entries={ "AGENTS.md": File(content=AGENTS_MD.encode("utf-8")), "data": LocalDir(src=DATAROOM_DATA_DIR), } )这对应 README 中 "builds a hard-coded manifest that maps the shared SEC packet ... into the sandbox asdata/..." 的描述:将共享的 SEC 数据包从examples/sandbox/tutorials/data/dataroom/映射进沙箱的data/目录,同时在沙箱根目录注入一份AGENTS.md。
其中LocalDir来自agents.sandbox.entries,表示把宿主机目录作为整体挂载进沙箱;File用于在沙箱内直接创建文本文件。Manifest类型定义在 src/agents/sandbox/manifest.py,它支持文件、目录、挂载、快照等多种条目类型,并在校验失败时抛出InvalidManifestPathError。
4.3 第 3 层:SandboxAgent 配置
agent = SandboxAgent( name="Dataroom Analyst", model=model, instructions=AGENTS_MD, capabilities=[Shell()], )SandboxAgent定义在 src/agents/sandbox/sandbox_agent.py,是Agent的沙箱专用子类。从源码注释可以确认一个重要设计原则:沙箱的传输细节(client、client options、会话)不存储在 Agent 上,而是在运行时通过RunConfig(sandbox=...)注入。这使 Agent 定义与沙箱环境解耦,同一个 Agent 可以复用于不同后端。
capabilities=[Shell()]为该 Agent 启用了 bash shell 能力(Shell来自agents.sandbox.capabilities)。README 的 "Demo shape" 一节明确示例的运行时原语就是sandbox-local bash / file search——即模型通过沙箱内的 bash 工具执行rg/sed等命令来完成证据检索,而非依赖云端 RAG 工具。
4.4 第 4 层:沙箱客户端与会话
client, sandbox = await create_sandbox_client_and_session( manifest=manifest, use_docker=use_docker, image=image, )该辅助函数定义在 examples/sandbox/tutorials/misc.py:
- 非 Docker 模式:
UnixLocalSandboxClient()(来自agents.sandbox.sandboxes.unix_local),在宿主机本地创建沙箱; - Docker 模式:通过
docker.from_env()构造DockerSandboxClient,并传入DockerSandboxClientOptions(image=image); build_docker_environment()会探测当前 Docker 上下文(包括 Docker Desktop、Colima 等),自动设置DOCKER_HOST,避免硬编码某个具体守护进程提供商。
会话创建后进入async with sandbox:生命周期块,finally中调用await client.delete(sandbox)确保清理。
4.5 第 5 层:流式运行与交互循环
result = Runner.run_streamed( agent, conversation, max_turns=20, run_config=RunConfig( sandbox=SandboxRunConfig(session=sandbox), tracing_disabled=True, workflow_name="Dataroom Q&A example", ), ) return await print_streamed_result(result)要点:
- 使用
Runner.run_streamed进行流式运行,max_turns=20限制最大工具调用轮数,防止检索循环失控; - 通过
RunConfig(sandbox=SandboxRunConfig(session=sandbox))把已创建的沙箱会话注入本次运行,对应第 4.3 节提到的"运行时注入"设计; tracing_disabled=True关闭 tracing,workflow_name标记工作流名;print_streamed_result遍历result.stream_events(),用print_event(定义于misc.py,基于 Rich 面板渲染)逐条展示 reasoning、工具调用(含exec_command的 bash 语法高亮)、工具输出、消息输出,最后以绿色面板打印最终回答;- 首轮完成后,
run_interactive_loop进入追问循环(除非--no-interactive),每次追问都会带着完整对话历史再次走run_turn。
五、预期产物:流式回答中的源码级引用
README 的 "Expected artifacts" 一节给出了可验证的验收标准:
- 在流式 Agent 回答中得到带直接引用的答案;
- 引用遵循固定格式:
- 文本摘录:
n—— 精确到行号; - 合成 PDF(单页):
n—— 精确到页码。
- 文本摘录:
这种"每个数字背后都有源文件坐标"的输出形态,正是金融问答场景最看重的可审计性:读者可以把回答中的任何一个指标(如 FY2025 收入 1,284 百万美元、毛利率 71.4%、营业利润 186 百万美元)直接回溯到对应文件的对应行/页,快速完成人工核验。
六、深入理解:沙箱检索机制与镜像依赖
6.1 为什么用沙箱内 bash 检索而非预置 RAG
从main.py可以看到,示例没有使用file_search等云端检索工具,而是刻意把检索能力限定为sandbox-local bash/file search(capabilities=[Shell()])。结合 examples/sandbox/tutorials/Dockerfile 中预装ripgrep、poppler-utils、pypdf的事实可以推断,设计意图是:
- 让检索逻辑完全透明、可复现——
rg/sed是确定性的命令行工具; - 让 Agent 的检索行为在受控沙箱内执行,与宿主机隔离;
- 让 PDF 文本抽取能力(
pypdf)与纯文本检索(rg)在同一声明式镜像内就绪,Docker 与 Unix-local 两种模式行为一致。
6.2 相关源码入口
深入理解本示例可继续阅读以下文件:
- examples/sandbox/tutorials/dataroom_qa/main.py:示例主程序(manifest、Agent、流式运行、参数解析);
- examples/sandbox/tutorials/data/dataroom/setup.py:合成 10-K 语料生成器;
- examples/sandbox/tutorials/misc.py:沙箱客户端/会话创建、Rich 事件渲染、交互循环;
- examples/sandbox/tutorials/Dockerfile:教程共享 Docker 镜像(ripgrep / poppler-utils / pypdf);
- src/agents/sandbox/sandbox_agent.py:
SandboxAgent定义与"运行时注入沙箱"的设计说明; - src/agents/sandbox/manifest.py:
Manifest类型与校验逻辑; - src/agents/sandbox/sandboxes/unix_local.py 与 src/agents/sandbox/sandboxes/docker.py:两种沙箱后端实现。
七、小结:从示例迁移到自己的金融语料
dataroom_qa虽然针对合成数据,但其模式可直接迁移到真实场景:
- 语料准备:把任意有界的金融文档集(年报、招股书、季报)整理为文本或单页 PDF,保证可被
rg与pypdf检索; - manifest 映射:用
Manifest把宿主机语料目录映射进沙箱的data/,并注入一份约束明确的AGENTS.md; - 指令约束:在指令中强制"只用
data/作答 + 每条主张必须带n引用"; - 能力裁剪:只开启
Shell()能力,让 Agent 用确定性命令检索证据; - 运行与验收:Unix-local 快速调试、Docker 保证环境一致,用
--no-interactive接入自动化回归。
当你的业务需要"每一个数字都能被追溯到源文件"的可审计问答时,这个检索优先 + 沙箱执行 + 源码级引用的组合,就是一个开箱即用的参考实现。
【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考