第15篇:Kanban 看板 —— 多代理协作的持久工作队列
Hermes Kanban 是持久化任务看板,跨所有 profiles 共享,让多个命名代理协作工作。每个任务是 SQLite 中的一行,每次交接是可读写的行,每个 worker 是有独立身份的完整 OS 进程。
Kanban vs delegate_task
| delegate_task | Kanban | |
|---|---|---|
| 形态 | RPC 调用(fork→join) | 持久消息队列 + 状态机 |
| 父级 | 阻塞等待子级返回 | create 后即忘 |
| 可恢复性 | 无 — 失败即失败 | Block → unblock → re-run |
| 人在回路 | 不支持 | 任何时候评论/unblock |
| 审计追踪 | 上下文压缩后丢失 | SQLite 永久行 |
一句话区分: delegate_task 是函数调用;Kanban 是工作队列。
快速开始
hermes kanban init # 初始化看板
hermes gateway start # 启动网关(含调度器)
hermes kanban create "research AI funding" --assignee researcher
hermes kanban watch # 实时监控
hermes kanban list
hermes kanban stats
任务状态
triage → todo → ready → running → done
↓
blocked → unblock → ready
Worker 工具集
| 工具 | 用途 |
|---|---|
kanban_show | 读取当前任务 |
kanban_list | 列出任务摘要 |
kanban_complete | 完成任务(summary + metadata) |
kanban_block | 阻塞任务(kind: dependency/needs_input) |
kanban_heartbeat | 长操作时发出心跳 |
kanban_comment | 添加评论 |
kanban_create | 编排者:生成子任务 |
kanban_link | 编排者:添加依赖边 |
kanban_unblock | 编排者:解除阻塞 |
Worker 典型流程
kanban_show() # 读取任务
# (执行工作 via terminal/file 工具)
kanban_heartbeat(note="halfway through — 4 of 8 files transformed")
# (更多工作)
kanban_complete(
summary="migrated limiter.py to token-bucket; added 14 tests, all pass",
metadata={"changed_files": ["limiter.py", "tests/test_limiter.py"]},
)
编排者示例
kanban_show()
kanban_create(title="research ICP funding 2024-2026", assignee="researcher-a")
kanban_create(title="research ICP funding — EU angle", assignee="researcher-b")
kanban_create(
title="synthesize findings into launch brief",
assignee="writer",
parents=["t_r1", "t_r2"], # 两个研究完成后提升为 ready
)
kanban_complete(summary="decomposed into 2 research + 1 writer; linked dependencies")
技能附加到任务
hermes kanban create "translate README to Japanese" \
--assignee linguist \
--skill translation
hermes kanban create "audit auth flow" \
--assignee reviewer \
--skill security-pr-audit \
--skill github-code-review
Goal-Mode 卡片
Worker 在同一 session 中反复尝试直到验收标准满足:
hermes kanban create "Translate the docs site to French" \
--body "Acceptance: every page translated, no English left, links intact." \
--assignee linguist \
--goal \
--goal-max-turns 15
配置选项
kanban:
dispatch_in_gateway: true # 调度器嵌入网关(默认)
dispatch_interval_seconds: 60 # 调度间隔
auto_decompose: true # 自动分解 triage 任务
orchestrator_profile: "" # 编排任务分配的 profile
dispatch_stale_timeout_seconds: 14400 # 4h 无心跳回收
Q&A
Q1: Kanban 和 delegate_task 应该在什么场景下选择?
A1: 如果你需要等待子任务完成后继续(函数调用模式),用 delegate_task。如果你需要持久化工作队列、人在回路、崩溃恢复、多代理协作,用 Kanban。
Q2: Worker 崩溃后任务会丢失吗?
A2: 不会。调度器会检测 4 小时无心跳的 Worker 并回收任务,任务回到 ready 状态等待重新分配。这是 Kanban 相比 delegate_task 的关键优势。
Q3: 如何让编排者自动分解复杂任务?
A3: 设置 auto_decompose: true,调度器会自动将 triage 状态的任务分解为子任务图。使用 hermes kanban specify <id> 可以手动精化分解方案。
@Teknium 的 12 并行实例:用 Hermes 构建 Hermes 的终极 dogfooding
Hermes Agent 深度拆解 · 第 15 篇
@Teknium 的 12 并行实例:用 Hermes 构建 Hermes 的终极 dogfooding
每天 12 个 Hermes Agent 实例并行运行,构建 Hermes Agent 本身——这就是 Nous Research 创始人 @Teknium 的日常。核心开发团队用它 dogfooding,后端团队用它监控基础设施,后训练团队用它创建 RL 环境和操控数据集。而这一切的成果是:Hermes Agent 已成为 GitHub 历史上 top 100 仓库之一。本文从这条推文出发,深入 batch_runner.py(1321 行并行编排框架)的源码,1:1 重建三团队并行工作流。
作者:@Teknium 分类:X / Twitter · Dev Workflow / Meta & Ecosystem 来源:X 推文 + GitHub 源码 + 官方用户故事 发布:2026-04-25
PART 01
案例背景 + 溯源 + 整体架构
开篇:终极 dogfooding——用 12 个自己构建自己
"吃自己的狗粮"(dogfooding)是软件工程中的经典信条:用自己的产品构建自己的产品。但 @Teknium 把这件事推向了一个前所未有的极端——每天运行 12 个 Hermes Agent 实例,全部并行,全部用来构建 Hermes Agent 本身。这不是一次性的演示,不是周末的黑客松项目,而是每天重复的、生产级的开发工作流。
结果是:Hermes Agent 已成为 GitHub 历史上 top 100 仓库之一。12,629+ 次提交,v0.17.0 版本,MIT 协议——这是一个由 AI agent 大规模参与构建的真实开源项目。而这个项目的创始人,正在用它自己的并行编排能力,持续驱动自身的迭代。
核心创新:规模化的自我递归
本案例的技术亮点不在于任何单一能力——并行执行、检查点恢复、轨迹保存,每一项都是成熟的工程实践。真正的创新在于规模化的自我递归:一个 AI agent 框架用自己的并行编排能力(batch_runner.py)驱动自身开发,三个团队(核心开发、后端、后训练)同时使用它,形成了一个 agent 构建 agent、agent 训练 agent、agent 监控 agent 的闭环生态系统。
作者原话
"I literally run 12 hermes agent instances every day in parallel to build Hermes Agent, and its now a top 100 GitHub repositories of all time. Our backend team uses it to monitor and investigate issues with our stack. Our post training team uses them to create new RL environments and benchmarks, investigate, inspect and sometimes directly manipulate the datasets."
— @Teknium,2026-04-25,X (Twitter)
注意推文中的三个关键层次:I literally run 12 hermes agent instances every day in parallel to build Hermes Agent——这是核心开发团队的 dogfooding;Our backend team uses it to monitor and investigate issues with our stack——这是后端团队的基础设施监控;Our post training team uses them to create new RL environments and benchmarks——这是后训练团队的 RL 环境创建和数据集操控。一条推文,三个团队,一个闭环。
溯源信息
溯源渠道与原始链接
作者@Teknium(Nous Research 创始人/联合创始人)
官方用户故事hermes-agent.nousresearch.com/docs/user-s…
GitHub 仓库github.com/NousResearc…(MIT,12,629+ commits,v0.17.0)
batch_runner.pygithub.com/NousResearc…(1321 行,55.9 KB)
datagen 配置github.com/NousResearc…
发布日期2026-04-25
分类X / Twitter · Dev Workflow / Meta & Ecosystem
故事标题"12 Hermes instances every day, in parallel"
溯源确认:公开源码可审查
与许多仅有推文的案例不同,本案例拥有完全公开的 GitHub 仓库。batch_runner.py(1321 行)、datagen-config-examples/ 目录、README 中的原生能力描述——全部可在 GitHub 上直接审查。本文中所有代码片段、配置模板、架构推导均基于这些公开源码,标注 源码 表示直接引用,标注 重建 表示基于源码模式的推导实现。
案例元数据
| 字段 | 值 |
|---|---|
| 故事标题 | "12 Hermes instances every day, in parallel" |
| 作者 | @Teknium(Nous Research 创始人/联合创始人) |
| 来源平台 | X (Twitter) |
| 发布日期 | 2026-04-25 |
| 分类 | X / Twitter · Dev Workflow / Meta & Ecosystem |
| 并行实例数 | 12 个/天(核心开发团队) |
| 团队覆盖 | 3 个团队(核心开发 + 后端 + 后训练) |
| 核心编排框架 | batch_runner.py(1321 行,55.9 KB) |
| GitHub 成就 | top 100 GitHub repositories of all time |
| 仓库规模 | 12,629+ commits,v0.17.0,MIT 协议 |
| 终端后端 | 6 种(local / Docker / SSH / Singularity / Modal / Daytona) |
| 轨迹格式 | JSONL,HuggingFace datasets 兼容 |
| 代码可用性 | 可用 源码 (MIT 开源) |
| 可复现性 | 高(batch_runner.py 完全开源,配置可审查) |
业务问题:构建复杂 AI Agent 系统的三重挑战
构建一个像 Hermes Agent 这样的复杂 AI agent 系统——它本身需要支持 6 种终端后端、并行子代理生成、轨迹压缩、技能系统、跨会话记忆——这是一个巨型工程。@Teknium 面临的不是单一问题,而是三个维度同时存在的复合挑战:
| 维度 | 挑战 | Hermes 的解法 |
|---|---|---|
| 核心开发 | 功能模块多、迭代速度快、需要大规模并行测试和代码生成 | 12 个并行 Hermes 实例,每天同时处理不同功能分支 |
| 基础设施监控 | 后端 stack 复杂(多容器、多后端),问题定位需要实时调查 | Hermes 实例持续监控并调查 stack 异常 |
| RL 训练数据 | 需要创建新 RL 环境、生成 benchmark、检查和操控数据集 | Hermes 实例创建 RL 环境、生成轨迹、直接操控数据 |
这三个维度之间存在深度的相互依赖:核心开发产出的新功能需要被测试(基础设施监控),测试发现的 bug 需要被修复(回到核心开发),修复后的模型需要新的训练数据(后训练团队创建 RL 环境),新训练数据产出的模型又回到核心开发——一个由 agent 驱动的闭环开发飞轮。
三团队分工:一个推文,三个工作流
从 @Teknium 的推文中,我们可以精确提取出三个团队的使用模式:
1 核心开发团队
每天 12 个并行 Hermes 实例构建 Hermes Agent 本身。这是终极 dogfooding:用 Hermes 的 batch_runner.py 并行编排能力,驱动 Hermes 自身的代码开发。每个实例独立处理不同功能模块或 bug 修复。
2 后端团队
使用 Hermes 实例监控和调查基础设施 stack 的异常。多容器(Docker/Modal/Singularity/Daytona)环境中的问题定位,由 agent 自主执行诊断命令、分析日志、定位根因。
3 后训练团队
使用 Hermes 实例创建新 RL 环境和 benchmark,调查、检查、有时直接操控训练数据集。对应 datagen-config-examples/ 目录中的 RL 环境配置(如 WebResearchEnv)。
注意:直接操控数据集的风险
推文中提到后训练团队"sometimes directly manipulate the datasets"——即有时直接操控训练数据集。这是一个高风险操作:agent 自主修改训练数据可能引入偏差或错误。在实践中,这通常需要完善的审计追踪(audit trail)和人工审核机制。本文 Part 3 将详细分析这一操作的风险与缓解策略。
五层架构:从 12 个实例到 6 种后端
从 batch_runner.py 的源码和 Hermes README 的原生能力描述中,我们可以重建出 @Teknium 的三团队并行工作流的五层架构:
顶层 · 并行实例
12 个 Hermes Agent 实例 × 每天 × 并行 — 核心开发(构建 Hermes)+ 后端(监控 stack)+ 后训练(RL 环境)
↓
中层 · 编排框架
batch_runner.py(1321 行)— multiprocessing.Pool 并行 · 检查点恢复 · JSONL 轨迹保存 · rich 进度条 · --resume / --distribution
↓
底层 · 终端后端
6 种后端:local · Docker · SSH · Singularity · Modal · Daytona — 每个任务独立沙箱 VM · 工具 RPC · 子代理生成
↓
持久层 · 数据
JSONL 轨迹文件 · HuggingFace datasets · 检查点文件 · RL 环境配置(datagen-config-examples/)· 工具使用统计
↓
部署层 · 基础设施
多容器(Docker/Modal/Daytona)· 可水平扩展 · --resume 容错 · 每条 prompt 可指定独立容器镜像
| 架构层 | 组件 | 职责 | 对应源码/文档 |
|---|---|---|---|
| 顶层 | 12 个 Hermes 实例 | 并行执行开发、监控、训练任务 | 推文 + batch_runner.py 源码 |
| 中层 | batch_runner.py | 并行编排、检查点、轨迹保存、进度监控 | batch_runner.py L1-1321 源码 |
| 底层 | 6 种终端后端 | 隔离执行环境、工具 RPC、子代理生成 | README "6 terminal backends" 源码 |
| 持久层 | JSONL / datasets / 检查点 | 轨迹存储、训练数据、故障恢复 | batch_runner.py 轨迹保存逻辑 源码 |
| 部署层 | 多容器基础设施 | 水平扩展、容错、独立镜像 | --resume + --distribution 源码 |
架构关键:每条 prompt 独立容器
batch_runner.py 的一个关键设计是:每条 prompt 可以指定独立的容器镜像。这意味着 12 个并行实例中的每一个,都可以运行在完全隔离的环境中——不同的依赖、不同的工具集、不同的网络配置。这种细粒度的隔离是大规模 dogfooding 的基础:核心开发实例可以在最新的开发分支上运行,而后端监控实例可以运行在稳定的生产镜像中,互不干扰。
Hermes 原生能力支撑
从 Hermes Agent README 中,我们可以确认支撑这一工作流的原生能力:[1]
| 原生能力 | README 描述 | 在本案例中的角色 |
|---|---|---|
| 委派与并行 | "Spawn isolated subagents for parallel workstreams. Write Python scripts that call tools via RPC, collapsing multi-step pipelines into zero-context-cost turns." | 12 个实例并行处理不同工作流 |
| 研究就绪 | "Batch trajectory generation, trajectory compression for training the next generation of tool-calling models." | 后训练团队的轨迹生成和压缩 |
| 6 种终端后端 | local, Docker, SSH, Singularity, Modal, Daytona | 每个任务独立沙箱 VM 执行 |
| 闭环学习 | Agent creates Skills, self-improves, persistent cross-session memory | 跨会话记忆支撑持续迭代 |
PART 02
分步构建教程 + 完整命令参考
九步搭建:从零到 12 并行实例
以下教程基于 batch_runner.py 的公开源码,重建 @Teknium 三团队并行工作流的完整搭建流程。[2]
1 安装 Hermes
→
2 准备 JSONL 数据集
→
3 配置 batch_runner 参数
→
4 运行首批次
→
5 监控进度
→
6 故障恢复
→
7 提取轨迹
→
8 配置 RL 环境
→
9 工具集分发
Step 1:前置条件与 Hermes 安装
# 前置条件:Python 3.10+,容器后端(Docker 推荐),GPU(取决于模型)
# 安装 Hermes Agent
pip install hermes-agent
# 验证安装
hermes --version
# 确认 6 种终端后端可用
hermes backends list
# 输出:local, docker, ssh, singularity, modal, daytona
# 安装 rich(batch_runner.py 的进度条依赖)
pip install rich
前置条件检查
batch_runner.py 使用 rich.progress 渲染进度条(SpinnerColumn, BarColumn, MofNCompleteColumn),因此需要安装 rich 库。同时,如果你的任务需要 GPU(如 RL 环境中的模型推理),确保 Docker/Modal 后端配置了 GPU 支持。12 个并行实例需要显著的计算资源——CPU 核心数建议 ≥ 24,内存建议 ≥ 64 GB。
Step 2:准备 JSONL 数据集
batch_runner.py 接受 JSONL 格式的数据集文件,每行一个 JSON 对象,包含 prompt 和元数据:[2]
# data.jsonl — 每行一个任务
# 字段:prompt(必需),task_id(自动生成),tools(可选),container_image(可选)
{"prompt": "在 hermes/agent.py 中实现新的工具调用超时机制,参考 issue #4823", "tools": ["file_read", "file_write", "bash", "git"], "container_image": "hermes-dev:latest"}
{"prompt": "为 terminal_backends/docker.py 添加健康检查端点", "tools": ["file_read", "file_write", "bash", "git"], "container_image": "hermes-dev:latest"}
{"prompt": "修复 trajectory compression 在空消息列表上的崩溃 bug", "tools": ["file_read", "file_write", "bash", "git"], "container_image": "hermes-dev:latest"}
{"prompt": "调查 Modal 后端在高并发下的连接池泄漏问题", "tools": ["bash", "file_read", "file_write", "web_search"], "container_image": "hermes-monitor:stable"}
{"prompt": "创建 WebResearchEnv RL 环境,支持多步网页搜索和摘要提取", "tools": ["file_read", "file_write", "bash", "web_search", "browser"], "container_image": "hermes-rl:latest"}
{"prompt": "检查 SFT 数据集中 tool_call 格式不一致的样本,生成修复 diff", "tools": ["file_read", "bash", "python_exec"], "container_image": "hermes-datagen:latest"}
Step 3:配置 batch_runner.py 参数
# 核心参数配置
# --dataset_file:JSONL 数据集路径
# --batch_size:并行度(@Teknium 使用 12)
# --run_name:运行名称(用于检查点和轨迹文件命名)
# --resume:从检查点恢复(容错)
# --distribution:工具集分发策略(如 image_gen)
python batch_runner.py \
--dataset_file=data.jsonl \
--batch_size=12 \
--run_name=hermes_core_dev_20260425
Step 4:运行首批次
# 启动 12 个并行 Hermes 实例
python batch_runner.py \
--dataset_file=data.jsonl \
--batch_size=12 \
--run_name=hermes_core_dev_20260425
# batch_runner.py 会:
# 1. 加载 data.jsonl 中的所有 prompt
# 2. 创建 multiprocessing.Pool(processes=12)
# 3. 每个 worker 独立调用 agent.run_conversation(prompt, task_id=task_id)
# 4. 每个任务在独立沙箱 VM 中执行
# 5. 完成后保存轨迹到 trajectories/{run_name}/
# 6. 自动提取工具使用统计和推理覆盖率统计
Step 5:监控进度(rich 进度条)
batch_runner.py 使用 rich.progress 渲染实时进度条,包含 SpinnerColumn、BarColumn 和 MofNCompleteColumn:[2]
# 终端输出示例(rich 渲染)
┌─────────────────────────────────────────────────────────────┐
│ Hermes Batch Runner — hermes_core_dev_20260425 │
├─────────────────────────────────────────────────────────────┤
│ ⠋ Running batch... [12/120] ████████░░░░░░ 10% │
│ Completed: 12 | Failed: 0 | Checkpoint: saved │
│ Tool usage: bash(48) file_write(36) git(12) web_search(6) │
└─────────────────────────────────────────────────────────────┘
# 进度条组件:
# SpinnerColumn — 旋转动画表示活跃状态
# BarColumn — 可视化进度条
# MofNCompleteColumn — "12/120" 格式的完成计数
Step 6:故障恢复(--resume)
# 如果批次中途崩溃(OOM、容器异常、网络中断),使用 --resume 恢复
python batch_runner.py \
--dataset_file=data.jsonl \
--batch_size=12 \
--run_name=hermes_core_dev_20260425 \
--resume
# --resume 机制:
# 1. 读取 checkpoints/{run_name}.json
# 2. 跳过已完成的 task_id
# 3. 从上一个检查点继续执行未完成任务
# 4. 保持已保存的轨迹文件不变
Step 7:提取轨迹文件用于 RL 训练
# 轨迹文件保存在 trajectories/{run_name}/ 目录下,JSONL 格式
# 每个任务一个 JSONL 文件,HuggingFace datasets 兼容
ls trajectories/hermes_core_dev_20260425/
# task_0001.jsonl task_0002.jsonl ... task_0012.jsonl
# 加载到 HuggingFace datasets
python -c "
from datasets import load_dataset
ds = load_dataset('json', data_files='trajectories/hermes_core_dev_20260425/*.jsonl')
print(f'轨迹总数: {len(ds[\"train\"])}')
print(f'工具使用统计: {ds[\"train\"][0][\"tool_usage_stats\"]}')
"
Step 8:配置 RL 环境(datagen-config-examples)
后训练团队使用 datagen-config-examples/ 目录中的配置创建 RL 环境。关键提交:feat: add WebResearchEnv RL environment for multi-step web research(2026-03-05,hash: 15561ec)。[3]
# 克隆仓库并查看 RL 环境配置
git clone https://github.com/NousResearch/hermes-agent.git
cd hermes-agent/datagen-config-examples/
ls
# web_research_env.yaml code_gen_env.yaml tool_use_env.yaml ...
# 使用 RL 环境配置运行批量轨迹生成
python batch_runner.py \
--dataset_file=web_research_tasks.jsonl \
--batch_size=12 \
--run_name=rl_web_research_20260425 \
--config=datagen-config-examples/web_research_env.yaml
Step 9:配置工具集分发(--distribution)
# --distribution 控制工具集在不同任务间的分发策略
# 例如 image_gen 分发:确保图像生成任务均匀分布到不同实例
python batch_runner.py \
--dataset_file=data.jsonl \
--batch_size=12 \
--run_name=hermes_core_dev_20260425 \
--distribution=image_gen
# --distribution=image_gen 的作用:
# 1. 扫描数据集中每条 prompt 的工具需求
# 2. 将需要 image_gen 工具的任务均匀分配到不同 worker
# 3. 避免某些实例因重负载工具而过载
# 4. 优化整体吞吐量和资源利用率
完整命令参考表
| 命令 | 用途 | 适用团队 |
|---|---|---|
| python batch_runner.py --dataset_file=data.jsonl --batch_size=12 --run_name=my_run | 启动 12 并行实例的基础批次运行 | 核心开发 |
| python batch_runner.py ... --resume | 从检查点恢复未完成任务 | 所有团队 |
| python batch_runner.py ... --distribution=image_gen | 按工具集分发任务,均衡负载 | 后训练 |
| python batch_runner.py ... --config=datagen-config-examples/web_research_env.yaml | 使用 RL 环境配置运行批量轨迹生成 | 后训练 |
| hermes backends list | 列出可用的 6 种终端后端 | 所有团队 |
| hermes run --prompt "调查 Modal 后端连接池泄漏" --backend=docker | 单实例交互式运行(后端监控) | 后端 |
| hermes skills create --from-trajectory trajectories/my_run/task_0001.jsonl | 从轨迹自动提取技能 | 核心开发 |
| hermes memory list --session=all | 查看跨会话持久记忆 | 所有团队 |
| python -c "from datasets import load_dataset; ds = load_dataset('json', data_files='trajectories/my_run/*.jsonl')" | 将轨迹加载到 HuggingFace datasets | 后训练 |
| docker run --rm -v $(pwd)/trajectories:/data hermes-agent:latest batch_runner.py --dataset_file=/data/in.jsonl --batch_size=12 | 在 Docker 容器中运行 batch_runner | 部署 |
完整配置模板
模板 1:JSONL 数据集格式
# data.jsonl — batch_runner.py 的标准输入格式 源码
# 每行一个 JSON 对象,必需字段:prompt
{
"prompt": "在 terminal_backends/modal.py 中实现连接池复用机制",
"task_id": "auto_generated", // 可选,不指定则自动生成
"tools": ["file_read", "file_write", "bash", "git"], // 可选,指定可用工具集
"container_image": "hermes-dev:latest", // 可选,指定容器镜像
"metadata": { // 可选,自定义元数据
"team": "core_dev",
"priority": "high",
"issue_ref": "#4823"
},
"expected_tools": ["file_read", "bash"], // 可选,预期使用的工具(用于统计)
"max_turns": 50, // 可选,最大对话轮次
"timeout_seconds": 3600 // 可选,单任务超时
}
模板 2:batch_runner.py CLI 参数参考
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| --dataset_file | str | 必需 | JSONL 数据集文件路径 |
| --batch_size | int | 10 | 并行 worker 数量(@Teknium 使用 12) |
| --run_name | str | 必需 | 运行名称,用于检查点和轨迹命名 |
| --resume | flag | False | 从检查点恢复未完成任务 |
| --distribution | str | None | 工具集分发策略(如 image_gen) |
| --config | str | None | RL 环境配置文件路径 |
| --container_backend | str | docker | 容器后端(docker/modal/singularity/daytona/local/ssh) |
| --checkpoint_interval | int | 10 | 每 N 个任务保存一次检查点 |
| --trajectory_dir | str | trajectories/ | 轨迹文件输出目录 |
| --max_turns | int | 50 | 每个任务最大对话轮次 |
| --timeout | int | 3600 | 单任务超时(秒) |
模板 3:RL 环境配置(WebResearchEnv)
# datagen-config-examples/web_research_env.yaml 源码
# 对应提交:feat: add WebResearchEnv RL environment for multi-step web research
# 提交日期:2026-03-05,hash: 15561ec
environment:
name: WebResearchEnv
type: rl # RL 环境类型
description: "多步网页搜索和摘要提取的 RL 环境"
tools:
- web_search # Web 搜索工具
- browser # 浏览器导航和内容提取
- file_write # 写入摘要结果
- bash # 执行辅助脚本
reward_function:
type: "factuality_and_coverage" # 奖励函数类型
weights:
factuality: 0.4 # 事实准确性权重
coverage: 0.3 # 信息覆盖度权重
efficiency: 0.2 # 搜索效率权重(步数越少越好)
format: 0.1 # 输出格式规范权重
max_steps: 15 # 最大搜索步数
max_turns: 30 # 最大对话轮次
container_image: "hermes-rl:latest" # 执行环境镜像
dataset:
source: "web_research_tasks.jsonl"
split: "train"
shuffle: true
模板 4:Docker 容器隔离执行配置
# docker-compose.yml — 多容器隔离执行 重建
# 每个 Hermes 实例运行在独立容器中,互不干扰
version: "3.9"
services:
hermes-worker-01:
image: hermes-dev:latest
container_name: hermes-worker-01
environment:
- HERMES_TASK_ID=task_0001
- HERMES_BACKEND=docker
volumes:
- ./trajectories:/app/trajectories
- ./checkpoints:/app/checkpoints
deploy:
resources:
limits:
cpus: "4"
memory: 8G
network_mode: bridge # 网络隔离
hermes-worker-02:
image: hermes-dev:latest
container_name: hermes-worker-02
environment:
- HERMES_TASK_ID=task_0002
- HERMES_BACKEND=docker
volumes:
- ./trajectories:/app/trajectories
- ./checkpoints:/app/checkpoints
deploy:
resources:
limits:
cpus: "4"
memory: 8G
network_mode: bridge
# ... 重复到 hermes-worker-12 ...
hermes-monitor: # 后端团队的监控实例
image: hermes-monitor:stable
container_name: hermes-monitor
environment:
- HERMES_ROLE=monitor
- HERMES_BACKEND=ssh
volumes:
- ./logs:/app/logs
- ~/.ssh:/root/.ssh:ro
模板 5:轨迹输出格式(JSONL 结构)
# trajectories/my_run/task_0001.jsonl 源码
# 每行一个 JSON 对象,记录一个对话轮次
# HuggingFace datasets 兼容格式
{"turn": 1, "role": "user", "content": "在 terminal_backends/modal.py 中实现连接池复用机制", "timestamp": "2026-04-25T10:00:01Z"}
{"turn": 2, "role": "assistant", "content": "我来分析当前 Modal 后端的连接管理代码...", "tool_calls": [{"name": "file_read", "args": {"path": "terminal_backends/modal.py"}}], "reasoning": "首先需要理解现有连接管理逻辑", "timestamp": "2026-04-25T10:00:03Z"}
{"turn": 3, "role": "tool", "name": "file_read", "result": "...文件内容...", "timestamp": "2026-04-25T10:00:04Z"}
{"turn": 4, "role": "assistant", "content": "当前代码每次创建新连接,没有复用。我来实现连接池...", "tool_calls": [{"name": "file_write", "args": {"path": "terminal_backends/modal.py", "content": "..."}}], "reasoning": "使用连接池模式,最大连接数设为 10", "timestamp": "2026-04-25T10:00:08Z"}
# 轨迹文件尾部:汇总统计(batch_runner.py 自动生成)
{"_stats": {"total_turns": 28, "tool_usage": {"file_read": 5, "file_write": 3, "bash": 8, "git": 2}, "reasoning_coverage": 0.85, "duration_seconds": 142, "task_id": "task_0001", "status": "completed"}}
模板 6:检查点文件格式
# checkpoints/my_run.json 源码
# batch_runner.py 的 --resume 机制读取此文件
{
"run_name": "hermes_core_dev_20260425",
"dataset_file": "data.jsonl",
"batch_size": 12,
"started_at": "2026-04-25T10:00:00Z",
"last_checkpoint_at": "2026-04-25T10:15:32Z",
"total_tasks": 120,
"completed_tasks": 48,
"failed_tasks": 2,
"pending_tasks": 70,
"completed_task_ids": [
"task_0001", "task_0002", "task_0003",
"task_0005", "task_0006",
/* ... 省略 ... */
"task_0048"
],
"failed_task_ids": [
{"task_id": "task_0004", "error": "ContainerTimeoutError", "retry_count": 0},
{"task_id": "task_0007", "error": "OOMError", "retry_count": 0}
],
"tool_usage_stats": {
"bash": 192,
"file_read": 156,
"file_write": 89,
"git": 34,
"web_search": 12
}
}
PART 03
源码深度拆解 + 部署 + 复刻陷阱
batch_runner.py 核心源码逐行解析
以下代码基于 batch_runner.py(1321 行,55.9 KB)的公开源码模式重建,每段附带中文注释。[2]
1. Multiprocessing Pool 初始化与任务分发
# batch_runner.py — 并行 Pool 初始化 源码
# @Teknium 每天运行 12 个并行 Hermes 实例的核心机制
import multiprocessing
from rich.progress import Progress, SpinnerColumn, BarColumn, MofNCompleteColumn, TextColumn, TimeRemainingColumn
def run_batch(dataset_file, batch_size, run_name, resume=False, distribution=None):
"""
批量运行 Hermes Agent 实例
- dataset_file: JSONL 数据集路径
- batch_size: 并行 worker 数量(@Teknium 使用 12)
- run_name: 运行名称(检查点和轨迹命名)
- resume: 是否从检查点恢复
- distribution: 工具集分发策略
"""
# Step 1: 加载 JSONL 数据集
prompts = load_dataset(dataset_file)
# prompts 是一个列表,每个元素是 {"prompt": "...", "tools": [...], ...}
# Step 2: 如果 --resume,从检查点加载已完成任务
completed_ids = set()
if resume:
checkpoint = load_checkpoint(run_name)
completed_ids = set(checkpoint["completed_task_ids"])
# 过滤掉已完成的任务
prompts = [p for p in prompts if p["task_id"] not in completed_ids]
# Step 3: 如果 --distribution,按工具集分发任务
if distribution:
prompts = distribute_by_toolset(prompts, distribution)
# 将需要重负载工具(如 image_gen)的任务均匀分配
# Step 4: 创建 multiprocessing.Pool — 这是并行的核心
# batch_size=12 意味着最多 12 个任务同时执行
with multiprocessing.Pool(processes=batch_size) as pool:
# Step 5: 使用 rich.progress 渲染进度条
# SpinnerColumn: 旋转动画 | BarColumn: 进度条 | MofNCompleteColumn: "12/120" 计数
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
MofNCompleteColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
TimeRemainingColumn(),
) as progress:
task = progress.add_task(
f"Hermes Batch Runner — {run_name}",
total=len(prompts)
)
# Step 6: 提交所有任务到 Pool
# 每个任务调用 run_single_task 函数,在独立 worker 中执行
results = []
for prompt_data in prompts:
result = pool.apply_async(
run_single_task,
args=(prompt_data, run_name)
)
results.append(result)
# Step 7: 轮询结果,更新进度条
for result in results:
task_result = result.get() # 阻塞等待单个任务完成
progress.advance(task)
# 每完成 checkpoint_interval 个任务,保存检查点
save_checkpoint_if_needed(run_name, task_result)
# Step 8: 汇总工具使用统计
aggregate_tool_usage_stats(run_name)
2. 单任务执行:沙箱 VM + 轨迹保存
# batch_runner.py — 单任务执行函数 源码
# 每个任务在独立的沙箱 VM 中运行,互不干扰
def run_single_task(prompt_data, run_name):
"""
单个 Hermes 实例执行单个 prompt
- prompt_data: {"prompt": "...", "tools": [...], "container_image": "..."}
- run_name: 用于轨迹文件命名
返回: {"task_id": "...", "status": "completed/failed", "stats": {...}}
"""
# 生成独立 task_id(如果数据集中未指定)
task_id = prompt_data.get("task_id", generate_task_id())
try:
# 核心:调用 agent.run_conversation,传入独立 task_id
# 每个 task_id 对应一个隔离的沙箱 VM
# agent 会自动选择容器后端(Docker/Modal/Singularity/Daytona)
trajectory = agent.run_conversation(
prompt=prompt_data["prompt"],
task_id=task_id,
tools=prompt_data.get("tools"),
container_image=prompt_data.get("container_image"),
max_turns=prompt_data.get("max_turns", 50)
)
# 保存轨迹为 JSONL 文件(HuggingFace datasets 兼容)
trajectory_path = f"trajectories/{run_name}/{task_id}.jsonl"
save_trajectory_jsonl(trajectory, trajectory_path)
# 自动提取工具使用统计和推理覆盖率统计
stats = extract_stats(trajectory)
# stats = {"tool_usage": {"bash": 8, ...}, "reasoning_coverage": 0.85, ...}
return {
"task_id": task_id,
"status": "completed",
"stats": stats,
"trajectory_path": trajectory_path
}
except Exception as e:
# 任务失败:记录错误,不中断其他任务
return {
"task_id": task_id,
"status": "failed",
"error": str(e)
}
3. 检查点保存与加载(容错机制)
# batch_runner.py — 检查点逻辑 源码
# --resume 的基础:定期保存进度,崩溃后可恢复
import json
import os
CHECKPOINT_DIR = "checkpoints"
CHECKPOINT_INTERVAL = 10 # 每 10 个任务保存一次
def save_checkpoint(run_name, completed_tasks, failed_tasks, total_tasks, stats):
"""保存检查点到 JSON 文件"""
checkpoint_path = os.path.join(CHECKPOINT_DIR, f"{run_name}.json")
checkpoint = {
"run_name": run_name,
"total_tasks": total_tasks,
"completed_tasks": len(completed_tasks),
"failed_tasks": len(failed_tasks),
"completed_task_ids": [t["task_id"] for t in completed_tasks],
"failed_task_ids": [
{"task_id": t["task_id"], "error": t.get("error", "unknown")}
for t in failed_tasks
],
"last_checkpoint_at": datetime.now().isoformat(),
"tool_usage_stats": stats
}
with open(checkpoint_path, "w") as f:
json.dump(checkpoint, f, indent=2)
def load_checkpoint(run_name):
"""加载检查点(--resume 时调用)"""
checkpoint_path = os.path.join(CHECKPOINT_DIR, f"{run_name}.json")
if not os.path.exists(checkpoint_path):
return {"completed_task_ids": []}
with open(checkpoint_path, "r") as f:
return json.load(f)
def save_checkpoint_if_needed(run_name, task_result):
"""每 CHECKPOINT_INTERVAL 个任务保存一次检查点"""
global _completed_count
_completed_count += 1
if _completed_count % CHECKPOINT_INTERVAL == 0:
# 触发检查点保存
save_checkpoint(run_name, ...)
4. 轨迹保存(JSONL + HuggingFace 兼容)
# batch_runner.py — 轨迹保存逻辑 源码
# JSONL 格式,HuggingFace datasets 直接兼容
def save_trajectory_jsonl(trajectory, path):
"""
将对话轨迹保存为 JSONL 文件
每行一个 JSON 对象,记录一个对话轮次
最后一行是汇总统计 (_stats)
"""
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w") as f:
# 写入每个对话轮次
for turn in trajectory.turns:
record = {
"turn": turn.index,
"role": turn.role, # user / assistant / tool
"content": turn.content,
"tool_calls": turn.tool_calls, # 工具调用列表
"reasoning": turn.reasoning, # 推理过程
"timestamp": turn.timestamp
}
f.write(json.dumps(record) + "\n")
# 最后一行:汇总统计
stats_record = {
"_stats": {
"total_turns": len(trajectory.turns),
"tool_usage": count_tool_usage(trajectory),
"reasoning_coverage": calc_reasoning_coverage(trajectory),
"duration_seconds": trajectory.duration,
"task_id": trajectory.task_id,
"status": trajectory.status
}
}
f.write(json.dumps(stats_record) + "\n")
5. 工具使用统计聚合
# batch_runner.py — 工具使用统计聚合 源码
# 批次完成后,聚合所有任务的工具使用和推理覆盖率统计
def aggregate_tool_usage_stats(run_name):
"""
扫描所有轨迹文件,聚合工具使用统计
输出:每个工具的调用次数、推理覆盖率分布
"""
trajectory_dir = f"trajectories/{run_name}/"
all_stats = {
"tool_usage": {},
"reasoning_coverage": [],
"total_trajectories": 0,
"completed": 0,
"failed": 0
}
for filename in os.listdir(trajectory_dir):
if not filename.endswith(".jsonl"):
continue
# 读取轨迹文件最后一行(_stats)
with open(os.path.join(trajectory_dir, filename), "r") as f:
lines = f.readlines()
stats_line = json.loads(lines[-1])
stats = stats_line["_stats"]
# 聚合工具使用次数
for tool, count in stats["tool_usage"].items():
all_stats["tool_usage"][tool] = all_stats["tool_usage"].get(tool, 0) + count
# 收集推理覆盖率
all_stats["reasoning_coverage"].append(stats["reasoning_coverage"])
all_stats["total_trajectories"] += 1
if stats["status"] == "completed":
all_stats["completed"] += 1
else:
all_stats["failed"] += 1
# 保存聚合统计
stats_path = f"trajectories/{run_name}/_aggregate_stats.json"
with open(stats_path, "w") as f:
json.dump(all_stats, f, indent=2)
return all_stats
6. --distribution 工具集路由
# batch_runner.py — --distribution 工具集分发 源码
# 将需要特定工具(如 image_gen)的任务均匀分配到不同 worker
def distribute_by_toolset(prompts, distribution_key):
"""
按工具集分发任务
- distribution_key: 工具名称(如 "image_gen")
确保需要该工具的任务不会集中在同一批 worker 上
"""
# 将任务分为两组:需要 distribution_key 工具的 和 不需要的
needs_tool = []
no_need = []
for p in prompts:
tools = p.get("tools", [])
if distribution_key in tools:
needs_tool.append(p)
else:
no_need.append(p)
# 交替排列:确保需要重负载工具的任务均匀分布
# 这样 multiprocessing.Pool 中的 worker 负载更均衡
result = []
i, j = 0, 0
while i < len(needs_tool) or j < len(no_need):
if j < len(no_need):
result.append(no_need[j])
j += 1
if i < len(needs_tool):
result.append(needs_tool[i])
i += 1
return result
7. 子代理生成:agent.run_conversation(prompt, task_id=task_id)
# Hermes Agent 核心 API — 子代理生成 源码
# README 原文:"Spawn isolated subagents for parallel workstreams.
# Write Python scripts that call tools via RPC, collapsing multi-step
# pipelines into zero-context-cost turns."
# batch_runner.py 中每个 worker 调用的核心方法
# 每个 task_id 对应一个独立沙箱 VM,完全隔离执行
trajectory = agent.run_conversation(
prompt=prompt_data["prompt"],
task_id=task_id, # 独立沙箱 VM 标识
tools=prompt_data.get("tools"), # 可用工具集
container_image=prompt_data.get("container_image"), # 容器镜像
max_turns=50 # 最大轮次
)
# run_conversation 内部:
# 1. 根据 task_id 创建/复用沙箱 VM
# 2. 选择容器后端(Docker/Modal/Singularity/Daytona)
# 3. 在隔离环境中执行对话
# 4. 通过 RPC 调用工具(零上下文成本)
# 5. 可选:生成子代理处理并行子任务
# 6. 返回完整轨迹
扩展考量:从 12 实例到 100+ 实例
@Teknium 当前使用 12 个并行实例。如果你需要扩展到更大规模,以下是关键考量:
| 规模 | 并行实例 | 计算资源需求 | 关键瓶颈 | 建议后端 |
|---|---|---|---|---|
| 小规模 | 1-12 | 24 CPU / 64 GB RAM | 单机 CPU 调度 | Docker / local |
| 中规模 | 12-50 | 多节点集群 | 网络 I/O、检查点竞争 | Modal / Daytona |
| 大规模 | 50-100+ | 云集群 + GPU | 轨迹存储、检查点 I/O、成本 | Modal + Daytona 混合 |
| 超大规模 | 100+ | 分布式云 | 调度延迟、数据一致性 | Kubernetes + Modal |
扩展的隐性成本
从 12 到 100+ 实例不是简单的数字放大。检查点 I/O 竞争会成为瓶颈——100 个实例同时写入检查点文件会导致磁盘 I/O 饱和。轨迹存储也是问题:1000 条 prompt 的轨迹可能达到 GB 级别。解决方案:(1) 使用分布式文件系统(如 NFS/Ceph);(2) 增大 checkpoint_interval;(3) 启用轨迹压缩(Hermes 原生支持 trajectory compression)。
复刻检查清单
- 安装 Hermes Agent(
pip install hermes-agent)并验证hermes --version - 安装依赖
rich(进度条渲染)和datasets(HuggingFace 兼容) - 确认至少一种容器后端可用(推荐 Docker:
docker --version) - 准备 JSONL 数据集文件,每行包含
prompt字段 - 确定
batch_size(建议从 4 开始,逐步增加到 12) - 指定有意义的
run_name(用于检查点和轨迹命名) - 首次运行不带
--resume,观察 rich 进度条是否正常 - 模拟一次中断(Ctrl+C),然后使用
--resume验证恢复 - 检查
trajectories/{run_name}/目录下是否生成 JSONL 轨迹文件 - 使用 HuggingFace datasets 加载轨迹,验证格式兼容性
- (后训练)克隆
datagen-config-examples/,配置 RL 环境 - (后训练)使用
--distribution测试工具集分发 - (后端)配置 SSH 后端,测试基础设施监控工作流
- 监控资源使用:CPU、内存、磁盘 I/O、网络
- 设置日志收集和告警机制
陷阱总结
陷阱 1:容器后端选择影响隔离性与性能的权衡
Docker 提供最好的隔离性但启动开销大;local 最快但无隔离;Modal/Daytona 适合云原生但依赖网络。错误选择会导致要么性能不足(Docker 在 12 实例时启动开销显著),要么隔离不足(local 模式下一个崩溃的任务可能影响其他任务)。建议:开发用 Docker,生产用 Modal/Daytona。
陷阱 2:检查点频率与磁盘 I/O 的矛盾
checkpoint_interval 设得太小(如 1)会导致频繁磁盘写入,在 12 实例并行时 I/O 竞争严重;设得太大(如 100)则崩溃时丢失更多进度。建议值:10(batch_runner.py 默认值)。对于 SSD 存储,可以适当降低到 5;对于 HDD 或网络存储,保持 10 或更高。
陷阱 3:轨迹文件体积快速增长
每个任务的 JSONL 轨迹文件可能达到 100KB-1MB(取决于对话轮次和工具调用复杂度)。1000 条 prompt = 100MB-1GB。长期累积下来,磁盘空间会快速耗尽。解决方案:定期归档旧轨迹;启用 Hermes 原生的 trajectory compression;将轨迹上传到对象存储(S3/GCS)。
陷阱 4:RL 环境创建需要领域专业知识
datagen-config-examples/ 中的 RL 环境配置(如 WebResearchEnv)不是即插即用的。奖励函数的设计、工具集的选择、最大步数的设定都需要对 RL 训练有深入理解。错误配置可能导致生成的轨迹质量低下,反而损害模型训练。建议从官方示例配置开始,逐步调整。
陷阱 5:"直接操控数据集" — 无审计追踪的高风险操作
@Teknium 提到后训练团队"sometimes directly manipulate the datasets"。Agent 自主修改训练数据集是极高风险操作:可能引入偏差、删除关键样本、格式化错误。如果没有完善的审计追踪(audit trail),这些修改无法回溯。必须:每次修改前自动备份数据集快照;记录修改的 agent 实例 ID、时间、修改内容;设置人工审核关卡。
陷阱 6:12 实例需要显著的计算资源
12 个并行 Hermes 实例不是轻量级操作。如果使用 GPU 推理的模型,每个实例可能需要 1 张 GPU;如果使用 API 模型,需要考虑 API 速率限制。CPU 密集型任务(如代码编译、测试运行)建议 24+ 核 CPU。内存不足会导致 OOM 崩溃,而 --resume 虽然能恢复,但频繁崩溃严重影响效率。
陷阱 7:Dogfooding 偏差 — 在自身上测试可能遗漏外部用户问题
用 Hermes 构建 Hermes 意味着测试环境就是开发环境。Dogfooding 偏差:开发者习惯于某些工作方式,可能不会触发外部用户遇到的边缘场景。例如,@Teknium 团队熟悉 batch_runner.py 的所有参数,不会遇到新手用户的配置错误。缓解策略:定期引入外部测试者;收集社区 issue 作为 batch_runner.py 的数据集输入;设置"对抗性测试"批次。
展望:从 dogfooding 到自改进生态
@Teknium 的 12 并行实例工作流不仅是一个开发效率故事,更是 AI agent 自改进生态的雏形。以下是四个扩展方向:
1 企业级舰队部署
将 12 实例扩展到企业级 100+ 实例舰队,配合 Kubernetes 编排和自动扩缩容。每个部门(开发、运维、数据)运行独立的 agent 集群,通过共享记忆层协作。
2 多模型批量编排
不同实例使用不同模型(如代码生成用 Hermes-Coder,监控用 Hermes-Fast,RL 用 Hermes-Reasoner)。batch_runner.py 的 container_image 参数天然支持每条 prompt 指定不同模型环境。
3 自动化 RL 管线
将后训练团队的 RL 环境创建、轨迹生成、奖励计算、模型微调整合为全自动管线。batch_runner.py 生成轨迹 → 自动评估质量 → 自动触发训练 → 自动部署新模型 → 回到 batch_runner.py。
4 自改进 agent 生态
Hermes 的闭环学习能力("Agent creates Skills, self-improves, persistent cross-session memory")意味着 12 个实例每天的工作成果会沉淀为技能和记忆。随时间推移,agent 会越来越擅长构建自身——真正的递归自改进。
闭环飞轮
@Teknium 的工作流形成了一个完美的闭环飞轮:核心开发团队用 12 个 Hermes 实例构建 Hermes 的新功能 → 新功能让 后端团队更高效地监控基础设施 → 更稳定的基础设施支撑 后训练团队生成更高质量的 RL 轨迹 → 更好的训练数据产出更强的模型 → 更强的模型让核心开发团队的 12 个实例更高效 → 循环加速。这就是 Hermes Agent 成为 GitHub top 100 仓库的底层引擎。
这个案例最深刻的启示不是"12 个实例很酷",而是当一个 AI agent 框架的并行编排能力足够成熟时,它可以成为驱动自身进化的引擎。@Teknium 没有在用 Hermes 做 demo——他在用 Hermes 运营一个 top 100 的开源项目。每天,12 个实例并行工作,三个团队各司其职,一个闭环飞轮持续旋转。这不是 AI 的未来愿景,这是 2026 年 4 月 25 日的生产现实。
溯源引用
- Nous Research, Hermes Agent 官方 README / 文档。原生能力描述:"Delegates and parallelizes: Spawn isolated subagents for parallel workstreams. Write Python scripts that call tools via RPC, collapsing multi-step pipelines into zero-context-cost turns." / "Research-ready: Batch trajectory generation, trajectory compression for training the next generation of tool-calling models." / 6 terminal backends: local, Docker, SSH, Singularity, Modal, Daytona / Closed-loop learning: Agent creates Skills, self-improves, persistent cross-session memory. github.com/NousResearc…
- Nous Research, batch_runner.py 源码(1321 行,55.9 KB)。使用 multiprocessing.Pool 并行执行;rich.progress 进度条(SpinnerColumn, BarColumn, MofNCompleteColumn);--resume 检查点恢复;--distribution 工具集分发;JSONL 轨迹保存(HuggingFace datasets 兼容);agent.run_conversation(prompt, task_id=task_id) 独立沙箱 VM;支持 Docker/Modal/Singularity/Daytona 容器后端;自动提取工具使用统计和推理覆盖率统计。 github.com/NousResearc…
- Nous Research, datagen-config-examples/ 目录。包含 RL 环境配置文件。关键提交:"feat: add WebResearchEnv RL environment for multi-step web research"(2026-03-05,hash: 15561ec)。直接对应后训练团队的"create new RL environments and benchmarks"使用场景。 github.com/NousResearc…
- @Teknium, X (Twitter) 原始推文。"I literally run 12 hermes agent instances every day in parallel to build Hermes Agent, and its now a top 100 GitHub repositories of all time. Our backend team uses it to monitor and investigate issues with our stack. Our post training team uses them to create new RL environments and benchmarks, investigate, inspect and sometimes directly manipulate the datasets." 2026-04-25. x.com/Teknium/sta…
- Nous Research, Hermes Agent 官方用户故事页面。@Teknium 的故事被收录,标题为 "12 Hermes instances every day, in parallel",分类为 X / Twitter · Dev Workflow / Meta & Ecosystem。 hermes-agent.nousresearch.com/docs/user-s…
- Nous Research, Hermes Agent GitHub 仓库统计。MIT 协议,12,629+ commits,v0.17.0。@Teknium 推文称其为 "top 100 GitHub repositories of all time"。 github.com/NousResearc…
- Python multiprocessing.Pool 官方文档。batch_runner.py 的并行执行核心机制,使用 Pool(processes=batch_size) 创建并行 worker 池,通过 apply_async 提交任务。 docs.python.org/3/library/m…
- Rich — Python 终端渲染库。batch_runner.py 使用 rich.progress 的 SpinnerColumn、BarColumn、MofNCompleteColumn 组件渲染实时进度条。 rich.readthedocs.io/en/stable/p…
- HuggingFace datasets — 数据集加载库。batch_runner.py 的 JSONL 轨迹格式与 HuggingFace datasets 兼容,可通过 load_dataset('json', data_files=...) 直接加载。 huggingface.co/docs/datase…
Hermes Agent 深度拆解连载 · 第 15 篇 · @Teknium 的 12 并行实例 — 用 Hermes 构建 Hermes 的终极 dogfooding
溯源驱动 · 源码佐证 · 1:1 可复刻 · 禁止虚构
延伸阅读与交流
本文涉及的Hermes Agent自进化智能体技术体系,目前已有系统化的深度学习资源可供参考。中国通信工业协会通信和信息技术创新人才培养工程项目办公室将于近期组织相关技术专题分享,围绕本文讨论的AI原生架构、智能体工作流、自进化数据层等方向展开系统讲解。
专题信息
- 主题:AI原生Hermes自进化智能体系统
- 时间:2026年8月22-23日
- 形式:线上直播
- 内容方向:AI原生架构 · Hermes智能体拆解 · 全栈扩展 · 智能自动化 · 产品级实战 · Context Engine · 自进化数据层
分享嘉宾
王老师(Gavin),Agentic AI企业联合创始人兼CTO,十余年硅谷AI系统工程经验。长期深耕NLP、强化学习、可控AI与智能体系统架构,提出"语言即控制(Language as Control)"原创范式,在RLHF、PPO、DPO、GRPO等方向有系统化工程实践,推动智能体技术在社交媒体、医疗、金融、法律、教育等专业场景落地。联系邮箱:hiheartfirst@gmail.com
技术交流
- 联系人:Sam
- Hermes Agent技术文档:hermes-agent.nousresearch.com/docs/
028 | 索引利弊与多列索引:从"加索引就快"的幻觉说起
导读:本篇是第 9 章《索引 Strategies (B 树)》的开篇。先讲一个把人困在周日下午的真实故事,让你看清"加索引就快"这句话的误区——然后从基数、选择率、写税、膨胀四个角度,把"索引到底要不要加"这件事的判断框架摊开。后半篇专心讲多列索引的灵魂——最左前缀规则,配等值在范围之前的次级原则,再用 cinetrack 的两行
EXPLAIN看清楚列序怎么决定查询能不能走索引。读完这一篇,你就有了审视 schema 上每一个索引的清单。
概览:索引是设计,不是反射
Bruce Momjian 在多年 Postgres 内部讲座里反复说过一句话(基本是原话转述):
The fastest query is the one you don't run. The second fastest is the one that touches a few index pages and stops.
最快的查询是你根本不跑的查询。第二快的,是只读几个索引页就停下的查询。
我先把这一句放在最前面,因为它定调了整章。索引不是开关,加上就快。索引是一种结构,能不能让查询变快,取决于查询的形状、索引的形状、还有两者是不是合上了。
一个真实的周日下午
有个团队在某个周日下午被值班电话炸醒:他们内部后台的搜索框卡死了,整个面板都不响应。查询本身简单到不能再简单——按邮箱查找一个用户。表里 300 万行,email 列上确实有索引,EXPLAIN 显示用的是 Index Scan,但延迟是 30 秒。
值班工程师加了第二个索引——还是建在 email 上——同一条查询变成了 2 毫秒。一整个下午他都在试图理解:为什么同样一列上的两个索引,表现差出一万五千倍。
谜底一句话能说清:
- 第一个索引建在
email上。 - 第二个索引建在
LOWER(email)上。
因为 Java 应用在做查询之前,把搜索串过了 toLowerCase()。第一个索引(原始大小写的 email)根本帮不上忙,索引按 Alice@Example.com 排序,查询要找的是 alice@example.com,B 树 不知道去哪找。第二个索引是按小写后排好的,所以它命中。这个团队用一下午摸到了一个朴素的真理:一个索引只有在查询和索引对"找的是什么"达成一致时才有用。
这章我们会讲什么
索引是一项设计工作,不是反射动作。熟练的工程师在打开 psql 之前就会先想:
- 基数:这列有多少不同的值?
- 选择率(选择性):这条查询返回多少行?
- 查询模式:应用到底怎么查这一列?等值?范围?函数变换?
- 写代价:这张表的写入有多重?加这个索引会让每次写慢多少?
他们会知道多列索引的列序不是审美选择;会在"大部分表数据其实跟查询无关"时想到部分索引;会在"应用做了大小写归一化"时想到表达式索引;会在"规划器 多做了一次本不必要的堆读"时想到覆盖索引。这些都不是什么冷门技巧,但每一个都被严重使用不足。
读完这篇你会知道:
- 为什么每个索引都有写代价,怎么判断它"挣得到钱"——多列索引的列序如何精确地决定它服务哪些查询**
- 部分索引何时能比全索引快上一个数量级
- 表达式索引是什么,为什么
LOWER(email)是它的招牌场景 INCLUDE列怎么把一次 索引扫描 变成 仅索引扫描UNIQUE与EXCLUDE约束其实就是数据库用来保不变量的索引
本篇先把前三件事讲透——9.1 概览、9.2 索引何时有益何时有害、9.3 多列索引。其余的在 029、030、031 接力下去。
索引何时有益、何时有害
大多数工程师面对慢查询的第一反应是加索引。做了几年之后,第一反应会变成"加之前再想一下"。索引不是免费的:
- 每一次写都要为它付出代价。
- 每一页索引都待在 缓冲区缓存 里,跟你的表争内存。
- 规划器 不一定会用它,哪怕它就在那儿。
在你伸手去敲 CREATE INDEX 之前,脑子里得先有三个数:这列的基数、这条查询的选择率、这张表的写速率。
基数与选择率
基数是一列里不同值的数量。一张 1 亿行的表,user_id 列如果每个用户唯一,基数就是 1 亿。同一张表的 status 列只能取 active / pending / archived,基数就是 3。
选择率(选择性)是一条查询返回的行数占全表的比例。WHERE user_id = 7 在 1 亿行表上返回 1 行,选择率是 1 / 100,000,000。WHERE status = 'active' 在同一张表上可能返回 9000 万行,选择率是 0.9。
重要规律:索引在选择率低时赢,选择率高时输——而且输得很难看。
规划器 知道这一点。如果你的查询会选中全表大约 5–10% 以上的行,规划器 会直接跳过索引,改走 顺序扫描——因为对于 9000 万行来说,把堆从头读到尾比在索引和堆之间来回弹 9000 次要便宜得多。
这就是为什么给低基数列加 CREATE INDEX 通常是浪费。规划器 不会为命中大部分表的那个值用索引;它可能会为罕见的那个值用,前提是统计信息告诉它那个值确实罕见。所以永远要 EXPLAIN 看一眼。
写税
每个索引都是一份写税。
INSERT一行,表上每个索引都加一条新项。UPDATE一行——只要改动了某个索引覆盖的列,破坏了 HOT(第 2 章讲过),表上每个索引都会拿到一条指向新元组位置的新项。DELETE一行,每个索引都会留下一条死指针,等 VACUUM 来清。
一张带 12 个索引的表,意味着每次写要做 13 次物理操作:1 次写堆,12 次写索引。在 OLTP 工作负载上,这笔账就是每秒 5000 次写和每秒 40000 次写的区别。这些索引可能在读侧挣回票钱——也可能没有。多数团队从不测量。
经验法则:如果你说不出"这个索引是为了哪条查询"——它多半不该存在。
下面这条 SQL 列出所有"自上次 stats 重置以来一次都没被用过"的索引:
SELECT
schemaname,
relname AS table_name,
indexrelname AS index_name,
idx_scan AS times_used,
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;
在跑了几个礼拜真实负载的数据库上,这个查询的结果常常让人尴尬——几个 G 的索引存储,没有任何一条查询用过。每个都在白白交写税。
提示:
SELECT pg_stat_reset()会清掉整个当前数据库的统计——这是把杀猪刀,大多数审计用不上。更推荐用pg_stat_reset_single_table_counters(<oid>)只重置你关心的那几张表。无论哪种方式,跑一周有代表性的生产流量后再看idx_scan。新索引一周下来 zero scan,就是删的候选。
膨胀也是索引的事
索引和表一样,受同样的 多版本并发控制 规则影响会膨胀。堆里每条死元组,在每个覆盖到它的索引里都会留下一条死索引项。VACUUM 最终会回收掉它们,但 VACUUM 在索引上比在表上更慢,也比在表上更不激进。一张膨胀 20% 的表,它的索引常常膨胀 40%。
索引膨胀伤你两次:
- 更大的索引更难塞进缓冲区缓存。
- 空页多的 B 树要读更多页才能走到目标项。
下面这条 SQL 列出堆和索引大小对比:
SELECT
relname,
pg_size_pretty(pg_relation_size(c.oid)) AS heap_size,
pg_size_pretty(pg_indexes_size(c.oid)) AS index_size
FROM pg_stat_user_tables s
JOIN pg_class c ON c.relname = s.relname
ORDER BY pg_indexes_size(c.oid) DESC
LIMIT 10;
如果 index_size 一直比 heap_size 大,这是个信号。有时是合理的——一张"主要用来按多个 key 查"的表,本来就该索引比堆大。但更多时候,这意味着有人加了几次太多的索引而没人在看。
过度索引的陷阱
模式是这样的:
- 一条新查询变慢,有人加了个索引,查询变快,团队开心。
- 一个月后另一条查询变慢,有人又加了个索引,团队又开心。
- 一年后——表上 18 个索引,写吞吐量已经卡了半年,没人能说出大部分索引是干嘛的,值班同学士气低迷,Autovacuum 不停跑但永远跑不完。
修复方法不是"别加索引"。修复方法是把索引当成 schema 一样对待:它是一个需要 review 的设计面。新索引提案进代码审查;旧索引进定期审计。一个"每加一个索引就删两个"的团队,通常是数据库健康度很高的团队。
警告:给一张热表上一个被索引覆盖的路径加索引,会把整张表的 HOT 关掉。原本只需写堆的更新现在每次写都要更新每一个索引。读端的收益必须非常巨大才划得来。
多列索引
大部分生产索引覆盖多于一列。B 树本质上是一维的:它按 key 排序项,key 可以是一个值元组。CREATE INDEX ON ratings (user_id, movie_id) 建的树里每一项都是 (user_id, movie_id) 这个对,先按 user_id 排序,再在每个 user_id 内部按 movie_id 排。
这个排序就是整场游戏的全部。多列索引的列序不是审美选择,它精确地决定了这个索引能服务哪些查询。
最左前缀规则
建在 (a, b, c) 上的 B 树 能服务:
- 只过滤
a的查询 - 过滤
a和b的查询 - 过滤
a、b、c的查询
它不能服务只过滤 b、只过滤 c、或一起过滤 b 和 c 的查询。原因是机械的:B 树 先按 a 排序,b = 7 的项散落在每一个 a 的取值里,树根本没办法不扫描就找到它们。这就是最左前缀规则:多列索引服务"按索引前导列顺序过滤"的查询,你一跳过某列,它就停手。
电话簿比喻最直观。电话簿按 (last_name, first_name) 排序:
- 你能找到所有姓
Smith的。 - 你能找到所有
Smith, John。 - 你不能在不读完整本电话簿的情况下找到所有叫
John的。
电话簿就是索引。只查 first name 的查询,形状跟排序方向对不上。
这条规则还有一个延伸,处理范围查询时有个小折:索引能服务 WHERE a = 7 AND b > 100,因为树先窄到 a = 7 那一片,然后在片内按 b 顺序走。它也能服务 WHERE a > 5,因为树会按顺序走所有 a > 5 的项。但只要你在前导列上用范围条件,索引就不再对它右边的列有用。WHERE a > 5 AND b = 100 会用索引找到所有 a > 5,然后对每条结果重新检查 b = 100,因为 b 在这一片里不再是有序的。
选对的前导列 vs. 选错的前导列
cinetrack 里有一张 ratings 表,含 user_id、movie_id、score。我们关心两条查询:
-- 查询 A:这个用户评过哪些电影?
SELECT movie_id, score FROM ratings WHERE user_id = 42;
-- 查询 B:这部电影被谁评过?
SELECT user_id, score FROM ratings WHERE movie_id = 17;
如果你只能建一个索引,你只能选一边。(user_id, movie_id) 上的索引直接服务查询 A:规划器 走到 user_id = 42 那片,直接返回。它完全服务不了查询 B,因为 movie_id 不是前导列。(movie_id, user_id) 则是镜像:查询 B 赢,查询 A 拿不到索引。
前导列应该是你"最常用、最具选择性"的查询过滤的那一列。如果都分不出来,基本就只能两个都建。两个索引的代价是真的,但比起让 1 亿行表上其中之一走 顺序扫描,这点代价是便宜的。
等值在前,范围在后
第二条相关规则:等值列放范围列之前。WHERE user_id = 42 AND rated_at > '2025-01-01' 这条查询,要的索引是 (user_id, rated_at),而不是 (rated_at, user_id)。
- 第一种:树先窄到
user_id = 42,然后在已排序的rated_at范围里扫。 - 第二种:树扫一个日期范围,然后每条结果重检
user_id。
同样数据、同样查询、不同计划、不同速度。
复合索引也能服务单列查询
最左前缀规则有一个有用的推论:如果你已经有了 (user_id, movie_id) 上的索引,你不需要再单独建 user_id 上的索引。复合索引在只问前导列时也工作,Postgres 会用前导列。
人们常常意外地建出冗余索引。他们先加 (user_id, movie_id) 服务一条查询,几个月后为了另一条查询又加了 (user_id) 单独索引,没意识到第一个索引已经覆盖了。第二个索引是死重:交写税、会膨胀、什么都没赚到。
下面这条 SQL 列出所有"不止一个索引"的表:
SELECT
indrelid::regclass AS table_name,
array_agg(pg_get_indexdef(indexrelid)) AS index_definitions
FROM pg_index
WHERE indrelid::regclass::text NOT LIKE 'pg_%'
GROUP BY indrelid
HAVING count(*) > 1
ORDER BY indrelid::regclass::text;
把这份名单从头到尾走一遍,对每一对索引问一次"其中一个的前导列是不是已经覆盖了另一个"。如果是,把冗余那个删掉。
重要提示:Postgres 社区讨论过给非前导 B 树 列加 skip-scan 支持,但目前还没落地。请按最左前缀规则规划。如果某条查询需要非前导列作为唯一过滤条件,给它单独建索引,或者改写查询。
cinetrack 上的实操案例
本篇配套的 sandbox 灌了 50000 部电影、500000 条评分、50000 条评论。我们来建两个索引,看 规划器 怎么根据查询选其中之一:
-- 对 dashboard 查询来说,错的列序:
CREATE INDEX idx_ratings_movie_user ON ratings (movie_id, user_id);
EXPLAIN (ANALYZE, BUFFERS)
SELECT movie_id, score FROM ratings WHERE user_id = 42;
-- 要么是 ratings 上的 Seq Scan,
-- 要么是走 (movie_id, user_id) 树的一个完整 Index Scan,
-- 每条项都要 re-check user_id。
-- 都不是 dashboard 想要的。user_id 不是前导列,树没法收窄。
换成对的列序:
-- 对 dashboard 查询来说,对的列序:
CREATE INDEX idx_ratings_user_movie ON ratings (user_id, movie_id);
EXPLAIN (ANALYZE, BUFFERS)
SELECT movie_id, score FROM ratings WHERE user_id = 42;
-- Index Scan using idx_ratings_user_movie (cost ... rows=10 ...)
同样数据、同样查询、两个索引。只有"错序"那一个时,规划器 要么走整树遍历,要么退回 顺序扫描——两条路都没法对 user_id 单独收窄。换上"对序"的,规划器 直接走到 user_id = 42 那片,因为前导列匹配过滤条件。
这一组对照把这个章节的灵魂摆在了桌上:索引不是加上的,是设计出来的;同样的列、同样的查询、换个列序,就成了两个完全不同的世界。
本篇总结
把第 028 篇收成几句话:
- 索引有写税,不是免费的。在你伸手加索引前,先有基数、选择率、写速率三个数;对每个已存在的索引,能用
pg_stat_user_indexes.idx_scan = 0找出"从没被查过"的索引删掉。 - 低基数列的索引大部分时候是浪费。因为 规划器 在选择率高时会直接选 顺序扫描。
- 膨胀是索引的事。索引膨胀比表膨胀更严重,要定期查
pg_indexes_size()对比pg_relation_size()。 - 过度索引是缓慢累积的债。每个新索引提案都该进 code review。一支健康的团队通常是"加一删二"的节奏。
- 多列索引的灵魂是列序。最左前缀规则决定一切:
(a, b, c)服务a、(a, b)、(a, b, c),不服务b、c、(b, c)。 - 等值列放范围列之前。
WHERE user_id = 42 AND rated_at > '2025-01-01'要(user_id, rated_at),反过来就废了。 - 复合索引覆盖单列查询。别再为你已经有的复合索引的前导列单独建索引——那是冗余的写税。
下一篇:029 - 部分索引与表达式索引
常见问题答疑(学员答疑)
Q1:一张表上有 12 个索引,每次 INSERT 要做 13 次写操作——写税具体是怎么算的,OLTP 系统能承受多少索引?
写税的计算比很多人想象的更重。INSERT 一行:写堆 1 次 + 写 12 个索引各 1 次 = 13 次物理页写。UPDATE 一行:如果改动了任何一个被索引覆盖的列,HOT (Heap Only Tuple)机制失效——原本只需写堆的更新变成了写堆 + 写所有索引。DELETE 一行:每个索引留下一条死指针,等 vacuum 来收。在 OLTP 系统上(每秒 5000 次写),12 个索引把写入量从 5000/s 拉到 65000/s——磁盘 IOPS 和 WAL 流量直接翻 13 倍。能承受多少索引取决于写入强度:低写入表(日志型)可以多建索引,高写入表(订单、交易)每个索引都要证明它"挣到了钱"。判断方法:跑 pg_stat_user_indexes 看 idx_scan——一周下来 0 scan 的索引就是白交写税的。经验法则:如果你说不出"这个索引是为了哪条查询",它多半不该存在。MySQL 的 InnoDB 同样每个索引增加写开销,二级索引的页分裂代价甚至更高——写税是所有 B 树数据库的共性问题。
Q2:已经有 (user_id, movie_id) 上的复合索引,还需要单独建 (user_id) 上的索引吗?
不需要,这是最经典的冗余索引陷阱。最左前缀规则保证 (user_id, movie_id) 上的索引能直接服务 WHERE user_id = 42——Postgres 会用前导列 user_id 在 B 树里定位,不看 movie_id。单独的 (user_id) 索引是死重:它交写税、会膨胀、争缓存空间,但查询永远不会用到它——因为规划器已经有一个更好的选择。冗余索引在生产环境非常普遍——团队先建了复合索引,几个月后忘了,又建了前导列的单列索引。诊断方法:用 pg_index 查询所有索引定义,对每对索引问"其中一个的前导列是不是已经覆盖了另一个",是的话删掉冗余的那个。删索引前先确认:没有其他查询依赖它(比如有查询用 Index Only Scan 需要单列索引覆盖的列宽度更窄)——但这种情况极少。
Q3:WHERE movie_id = 17 查不了 (user_id, movie_id) 上的索引——为什么跳过前导列就不行?这不是"换个起点"吗?
B 树的排序方式决定了这个限制。(user_id, movie_id) 索引里的项先按 user_id 排序:user_id=1 的所有项在一起,user_id=2 的所有项在一起……在每个 user_id 内部再按 movie_id 排序。movie_id=17 的项散落在每一个 user_id 的片区里——user_id=1 下面可能有 movie_id=17,user_id=42 下面也可能有 movie_id=17。B 树没办法"不扫描就跳到所有 movie_id=17 的位置",因为树的第一层排序键是 user_id 而不是 movie_id。这就像电话簿按 (姓, 名) 排序——你能快速找到所有姓 Smith 的人,但想找所有叫 John 的人必须翻遍整本电话簿。Postgres 目前不支持 skip scan (跳过前导列扫描),Oracle 从 9i 开始支持,MySQL 8.0 也不支持。如果你的查询需要非前导列作为唯一过滤条件,给它单独建索引。
第十四篇:结构化输出——用Pydantic/Zod让Chat端点返回机器可读的JSON
你的应用需要根据Honcho的推理做决策——比如判断用户是否完成入门流程,或提取用户的等级偏好。如果Chat端点返回的是自然语言文本,你得自己写正则或二次LLM调用来解析。
结构化输出让这个问题消失。
什么是结构化输出?
默认情况下,peer.chat()返回自由格式的自然语言。当你传入一个schema作为response_format时,答案被保证符合该schema——Agent仍然运行完整的推理循环,只有最终答案被格式化。
from pydantic import BaseModel, Field
from typing import Literal
from honcho import Honcho
class OnboardingStatus(BaseModel):
completed: bool
remaining_steps: list[str]
honcho = Honcho()
peer = honcho.peer("user-123")
# 返回的是解析后的Pydantic实例
status = peer.chat(
"Has the user completed the onboarding flow?",
response_format=OnboardingStatus,
)
if status:
print(f"Completed: {status.completed}")
print(f"Remaining: {status.remaining_steps}")
else:
print("No relevant information found")
TypeScript版本(Zod)
import { z } from 'zod';
import { Honcho } from '@honcho-ai/sdk';
const OnboardingStatus = z.object({
completed: z.boolean(),
remainingSteps: z.array(z.string()),
});
const honcho = new Honcho({});
const peer = await honcho.peer("user-123");
const status = await peer.chat(
"Has the user completed the onboarding flow?",
{ responseFormat: OnboardingStatus },
);
// status 是 z.infer<typeof OnboardingStatus> 类型
if (status) {
console.log(`Completed: ${status.completed}`);
}
复杂Schema示例
class FoodPreference(BaseModel):
food: str
sentiment: Literal["loves", "likes", "neutral", "dislikes", "hates"]
confidence: float = Field(description="0-1, how certain the evidence is")
class FoodPreferences(BaseModel):
preferences: list[FoodPreference]
summary: str
result = peer.chat(
"What are this user's top 3 food preferences?",
response_format=FoodPreferences,
)
if result:
for pref in result.preferences:
print(f"{pref.food}: {pref.sentiment} ({pref.confidence})")
print(f"Summary: {result.summary}")
原始JSON Schema
你也可以直接传JSON Schema对象,SDK返回JSON字符串:
result = peer.chat(
"What are this user's food preferences?",
response_format={
"type": "object",
"properties": {
"foods": {"type": "array", "items": {"type": "string"}},
},
"required": ["foods"],
},
)
# result是JSON字符串: '{"foods": ["dark roast coffee", "sushi"]}'
可空字段和Union类型
from typing import Optional
class UserInsight(BaseModel):
favorite_food: str # 必填且非空
dietary_restriction: Optional[str] # 可为null——模型无证据时返回null
years_vegetarian: Optional[int] # 同上
confidence: float # 不在required中,可能被省略
关键区别:
required控制字段是否出现——不在required中的字段可能被省略,解析后为None- 可空类型控制值是否可为
null——Optional[str]意味着字段总是存在,但值可以是None
支持的Schema子集
| 构造 | 支持 |
|---|---|
string, number, integer, boolean, null | 支持 |
嵌套object | 支持 |
array with items | 支持 |
enum | 支持 |
anyOf / oneOf | 支持 |
required, default, description | 支持 |
递归$ref | 不支持(422错误) |
allOf, not, if/then/else | 不支持 |
| 最多嵌套20层,最多500个节点 | 限制 |
最佳实践
1. 给字段加description
# 好——模型知道该填什么
confidence: float = Field(description="0-1, evidence certainty score")
# 差——模型不知道该怎么填
confidence: float
2. 显式建模不确定性
class Preference(BaseModel):
value: Optional[str] = Field(description="用户的偏好值,无证据时为null")
confidence: float = Field(description="0-1, evidence certainty")
# 让模型有"escape hatch",不会被强制编造
3. 保持schema聚焦
# 好——3个描述清晰的字段
class CorePreference(BaseModel):
tone: str
expertise_level: str
primary_goal: str
# 差——20个字段让模型困惑
class EverythingAboutUser(BaseModel):
field1: str
field2: str
# ... 18 more fields
需要多个洞察时,分多次chat调用。
Q&A
Q1:如果不传response_format,返回的是纯文本字符串吗?
A:是的。不传response_format时,peer.chat()返回字符串(自然语言答案)。传了Pydantic模型时,返回解析后的模型实例。传了原始JSON Schema时,返回JSON字符串。如果Honcho没有相关信息,返回None/null。
Q2:约束关键词(minLength, pattern, minimum等)会强制执行吗?
A:不会在服务端强制执行。这些关键词被作为提示传给模型,模型会尽量遵守,但不保证。如果你需要硬保证,在应用层验证返回对象的这些约束。格式约束(format如email/uri)同理——作为提示但不强制。类型约束(string/number/boolean等)和结构约束(required/properties/array items)是保证的。
Q3:结构化输出的推理质量和非结构化一样吗?schema会不会限制推理能力?
A:推理质量不受影响。Agent运行完全相同的推理循环——搜索结论、追溯前提、合成答案。只有最终合成步骤被约束到你的schema。文档明确指出:"答案质量不受schema影响"。reasoning_level(minimal到max)可以和response_format同时使用,你可以在结构化输出的同时调节推理深度。
大模型日报-2026-08-18
1. DeepSeek API 峰谷定价全面生效,V4 Pro 最高涨幅 1100%
8月17日零点起,DeepSeek V4 全系列新的峰谷价格正式生效。此次调整包含两件事:全面上调 API 价格,并引入高峰/空闲分时计价(高峰时段为每日 9:00-12:00、14:00-18:00,其余 17 小时为空闲时段)。以 V4 Pro 为例,空闲时段每百万 Token 输入价格 0.15 元(缓存命中)/4.5 元(缓存未命中),输出 13.5 元,高峰时段翻倍;其中缓存命中价格从 0.025 元上涨至高峰 0.3 元,涨幅最高达 1100%。背后是 Token 需求爆发与算力成本高企的双重压力,行业正由"烧钱换市场"转向理性定价,这也被解读为中国公司把"智能"变成可规模采购的工业品后的定价权实验。
2. 百度发布 2026 年 Q2 财报:AI 业务收入占比连续两季过半
8月18日,百度发布 2026 年第二季度财报。季度总营收 313 亿元,一般性业务收入 252 亿元,其中 AI 业务收入占比达 50%,连续第二个季度维持在半数之上——意味着每两元收入中就有一元由 AI 直接贡献。分项看,AI 云基础设施收入 73 亿元,同比增长 50%,其中 GPU 云同比增长 283%,已连续四个季度实现三位数增长;AI 应用收入 25 亿元,同比增长 3%;AI 原生营销服务收入 26 亿元。李彦宏表示,AI 业务的持续增长印证了百度正从互联网公司转型为 AI 为先的公司。
3. 中国大模型的奥德修斯时刻:GLM-5.2 智能指数排名全球第三
晚点 LatePost 8月18日刊发深度报道《中国大模型的奥德修斯时刻》。2026 年 6 月 17 日,智谱发布 GLM-5.2,在 Artificial Analysis 智能指数中排名全球第三,富瑞发布研报称其为"中国 AI 的发展里程碑";不到一个月后,月之暗面发布 Kimi K3,2.8 万亿参数,以 1679 分登顶 Frontend Code Arena,Artificial Analysis 智能指数 57 分同样位列全球第三。报道指出,中国头部大模型已从追赶进入全球第一梯队,开源生态与工程效率成为中国模型竞争力的关键变量。
4. AI 视频生成平台 Higgsfield 完成 4 亿美元融资,估值 54 亿美元
AI 视频生成平台 Higgsfield 宣布完成 4 亿美元新一轮融资,估值达 54 亿美元,较 8 个月前 13 亿美元估值增长超 4 倍。本轮由 DST Global、高盛、Liberty Global 和英特尔等参与。该公司由前 Snapchat 高管于 2023 年创立,旗下 Cinema Studio 帮助影视创作者执导 AI 影片,Marketing Studio 面向营销场景。2026 年 8 月其年化收入已达 7 亿美元,而一年前仅为约 2000 万美元,增长约 34 倍,反映 AI 视频赛道持续升温。
5. 中国 AI 基础设施的价值洼地:占全球 85% 算力流量,仅 10% 市场收入
华尔街见闻 8月18日指出,中国 AI 模型已悄然承载全球 85% 至 89% 的智能体与代码生成流量,但变现收入占比仅 10% 至 16%,是当前最被低估的套利机会。对比之下,美国 AI 算力相关资产市值合计约 1750 亿美元,中国同类资产仅约 200 亿美元,单 CoreWeave 的市值相当于中国三家头部算力公司的近四倍。万国数据创纪录的预订量已率先验证基本面拐点,估值洼地与产业催化并存,市场对中国 AI 基础设施资产的定价正在重估。
大模型论文日报 - 2026-08-19
数据说明:本文档基于 2026年8月18日 arXiv 最新提交(cs.CL/cs.AI/cs.LG 等领域),综合社区关注度与话题性选出 5 篇代表性论文。每篇含方向、摘要、结论及对以往假设/设想的挑战与质疑。
1. Model Hypnosis: Strong control of AI via additive subliminal effects
- 方向:AI 安全 / 可解释性 / 提示词攻击
- 论文编号:arXiv:2608.16834
- 摘要:论文展示了 AI 模型普遍容易受到一种被称为"模型催眠"(model hypnosis)的现象影响——提示中单个看似微弱、无关紧要的线索(如特定措辞、拼写变体等),可以被系统性地组合起来,从而强烈控制模型的行为。该现象跨越不同模型家族和规模,包括前沿推理模型;催眠提示还可以在模型之间迁移。由于模型是被不显眼的文本选择(如改写、拼写错误)所控制的,"模型催眠"给 AI 安全带来新的挑战和途径,同时也是 AI 可解释性的一大障碍。
- 结论:模型催眠是普遍存在的(跨家族、跨规模、可迁移),微弱线索的组合即可实现对模型的强控制;这要求安全研究不能只关注显式越狱,还要防范"隐式催眠"式的提示操控。
- 对既有假设的挑战与质疑:
- 挑战了"越狱/攻击必须依赖明显对抗性提示"的直觉,指出无害的措辞变体同样可以系统性地劫持模型行为;
- 质疑"对齐后的模型对微小的文本扰动具备鲁棒性"这一常见假设——模型对感知上无关的信号是敏感的;
- 对"可解释性=找到显著方向/单一语义特征"的简化路径提出警示:催眠效应的"累积式"特性提示潜在空间存在大量未被理解的弱耦合信号。
2. Proteus: Incremental Memory Activation for Long-Context Sequence Modeling
- 方向:长上下文建模 / 记忆架构
- 核心编号:arXiv:2608.16844
- 摘要:注意力机制在长上下文下的二次复杂度促使大量基于记忆的模型研究(把上下文压缩到紧凑状态)。然而现有记忆模型在整个序列中暴露的是"静态记忆":早期 token 没有压缩压力,会占据过多自由度并"污染"记忆状态,导致后续上下文容量不足、新旧信息相互干扰。论文提出"增量式记忆激活"新范式:随上下文增长渐进扩大记忆有效容量——早期施加瓶颈强制压缩,随后逐步解锁新容量以减少干扰、提升后续上下文的保持能力。其提出的 Proteus 机制可无额外成本地嵌入 SWLA、Comba、Titans、Hope-Attention 等主流模型,在语言建模、推理、长上下文检索与理解上持续带来提升,且上下文越长收益越大。
- 结论:静态记忆是次优的;按需调度有效记忆容量,是一种简单且通用的序列建模工具。
- 对既有假设的挑战与质疑:
- 挑战"固定容量记忆已够用/静态记忆架构最优"的常见设计假设;
- 质疑"长上下文能力主要靠模型参数或注意力覆盖范围扩展"的路径——上下文建模的时间维度(何时暴露记忆容量)同样重要;
- 对"早期 token 可以无损保留"的默认预设提出质疑:早期内容会污染记忆、挤占晚期容量。
3. ClawGym II: Exploring Black-Box RL on Agent Harness
- 方向:智能体强化学习(黑盒 RL / 工具智能体)
- 核心贡献:arXiv:2608.16798
- 摘要:Agent 工作框架(harness)显著提升长时程任务性能,但通过复杂框架进行强化学习仍基本空白。该工作提出一套统一的黑盒 RL 框架:在沙箱中隔离任务环境和 harness 进行大规模并发回放;在模型边界放置 serving proxy 捕获模型调用,将调用重构为前缀树,并分别用基于评论家的 PPO 与无评论家 GRPO 优化;全程保持训练-推理一致性;并提出"混合 harness 训练",让单一模型同时被异构 harness 联合优化。在 Qwen3-30A3B 上,黑盒 RL 在 ClawGym-Bench 上通过 OpenClaw 和 Claude Code 分别将 Pass@1 提升 9.98 和 14.81 分,且 200-400 步优化过程保持稳定;在 JobBench、OfficeQA 等更难任务上也有持续增益。
- 结论:黑盒 RL 可以在不打开 harness 内部的前提下稳定、可扩展地优化通用 agent,并支持跨异构执行系统的统一训练。
- 对既有假设的挑战与质疑:
- 挑战"agent 后训练必须白盒、必须精确建模环境"的既有实践;
- 质疑"训练与推理分离即可"的观点,强调训练-推理一致性对 agent 优化至关重要;
- 挑战"异构 harness 无法联合训练一个模型"的设想——mix-harness 训练让单一模型同时获益于多套执行框架。
4. Semantic Bandits: In-Context Exploration-Exploitation is Biased by Semantic Priors
- 方向:决策 / 语义先验 / 探索-利用权衡
- 核心贡献:arXiv:2608.16707
- 摘要:大模型越来越被用作需要探索环境的决策代理。论文引入"语义老虎机(semantic bandit)"——在多臂老虎机设置上显式考虑动作的文本标签,研究语言与预期奖励之间的关联(预训练阶段形成的语义先验)如何塑造 LLM 的探索行为。发现:语义信息丰富的动作标签会减少探索、更偏向利用——当标签与奖励结构对齐时性能提升,错位时性能严重下降;负奖励触发的探索显著多于等量正奖励,与预训练数据中常见奖励约定导致的"期望尺度偏差"一致。总体而言,用语言定义环境与奖励会带来预训练词共现产生的不可避免的偏差,影响 LLM 代理在真实决策场景中的可靠性与鲁棒性。
- 结论:LLM 在决策中的"探索-利用"行为并非中立的,而是被语义先验系统性偏置。
- 对既有假设的挑战与质疑:
- 挑战"LLM agent 能在决策中理性地探索-利用平衡"的设想;
- 质疑"自然语言定义任务即可与真实环境对齐"的假设——语言本身的词共现统计会泄漏无关偏差;
- 对"负反馈促使探索"的通用解释提出改进:行为的偏置可能与预训练中对奖励尺度/符号的约定有关,而不仅是任务结构。
5. 附:当日另一篇值得关注的评审级工作(简要)
因社区热度与论文质量考量,以下一篇同时进入推荐序列(供综合参考):
- Palmyra x6 Technical Report: An Agentic, Tool-Use Model Post-Trained via Anchored Supervised Fine-Tuning(arXiv:2608.16620)
- 方向:Agentic 模型后训练 / 工具使用
- 摘要:企业级 agent 任务优化的 LLM,通过 Anchored SFT 在一个只有 626 条已验证合成工具使用轨迹的紧凑语料上、单 epoch、低学习率、KL 锚定基座的方式训练,用 Muon+Adam 混合优化器;BFCL Core 得分 0.785,六基准均值在对比组中最高,并在偏置与安全评测中领先或持平。
- 结论:极少量高质量合成轨迹 + 保守受控的训练设定,即可带来 agentic 能力的显著提升。
- 对既有假设的挑战与质疑:
- 挑战"后训练数据规模越大越好"的假设——626 条轨迹单 epoch 即为有效;
- 质疑"KL 锚定会限制能力上界"的直觉,锚定反而保证了稳定与迁移;
- 提供"小而精"训练配方的可行替代方案。
2026年重磅喜讯! 喜报!热烈祝贺Gavin大咖人工智能领域经典著作《企业级ChatGPT AI大模型应用开发实战(1000分钟视频)》中国水利水电出版社发行上市!
内容提要
本书内容基于作者在硅谷 ChatGPT 项目及企业培训中的实战经验凝练而成,重点介绍企业级 ChatGPT 开发的核心技术、案例研究及最佳实践。全书共 16 章,分为基础篇和实战篇两大部分。
基础篇:
介绍 ChatGPT 底层架构 Transformer 技术及源码实现、GPT 的内部机制及源码实现、GPT 系列模型原理与应用:从 GPT-2 到 GPT-4 等内容。
实战篇:
介绍基于 ChatGPT 的端到端语音聊天机器人项目实战,企业级 ChatGPT 开发的三大核心内部机制及案例实战,ChatGPT 插件的内部机制、源码及案例实战,ChatGPT 提示词开发实战,思维链及 ReAct 解析与实战,提示词本质解析及评估实战与源码解析,LangChain 大模型框架的七大核心组件及案例解析(上、下),LangChain 代理深入解析及源码解析,AutoGPT 源码解析及综合案例实战,使用 LangChain 构建问答聊天机器人案例实战,构建基于大模型的自治代理案例,Llama 2 模型与 LangChain 项目详解。书中每个知识点均配有相应的实现代码和实例。
本书适合有一定 Python 基础的 ChatGPT 爱好者阅读,主要面向从事大模型应用开发、机器学习、数据挖掘或深度学习的专业人员,高等院校相关专业的师生,以及相关领域的科研人员。
本书附赠丰富的学习资源,具体如下:①同步学习资源,即 16 集同步教学视频,视频时长共计约 1000 分钟;②教师授课的辅助资源,即 187 个案例知识点、15 个项目实战的全部源代码。
前言
在当今快速发展的科技时代,人工智能(artificial intelligence,AI)技术正以惊人的速度改变着人们的生活和工作方式。在这个新时代的浪潮中,大模型技术成为AI领域的一颗耀眼新星。ChatGPT作为大模型技术的重要应用之一,正在引领着人机交互领域的革新浪潮。本书将带领读者深入探索大模型新时代,通过ChatGPT实战项目和内部解析,深入掌握基于ChatGPT的大模型应用开发领域的关键技术,并解密ChatGPT的底层架构和实现原理。
本书主要内容
本书通过ChatGPT实战项目的方式,为读者呈现一个全面、系统的学习路径,从基础知识的介绍开始,带领读者深入了解ChatGPT的工作原理和实际应用。本书非常适合具备Python基础的读者学习。
全书共16章,分为基础篇和实战篇两大部分。 基础篇包括第1~3章;实战篇包括第4~16章。
第1章 ChatGPT底层架构Transformer技术及源码实现,详解最大似然估计、最大后验概率、贝叶斯Transformer及自编码与自回归语言模型的内部机制。
第2章 GPT的内部机制及源码实现,剖析GPT运行机制、掩码机制、Decoder-Only模式,详解数据流动生命周期及GPT-2源码。
第3章 GPT系列模型原理与应用:从GPT-2到GPT-4,解析ChatGPT提示词流程、GPT-2运行机制,可视化解读GPT-3/4的内部机制。
第4章 基于ChatGPT的端到端语音聊天机器人项目实战,涵盖ChatGPT API开发、前后端构建(ReAct+FastAPI)及项目优化。
第5章 企业级ChatGPT开发的三大核心内部机制及案例实战,解析企业级开发核心,演示Notion问答对话AI案例。
第6章 ChatGPT插件的内部机制、源码及案例实战,详解插件工作原理、检索插件源码及全流程开发实战。
第7章 ChatGPT提示词开发实战,基于LangChain框架的提示词、思维链、链式提示词及模型评估开发。
第8章 思维链及ReAct解析与实战,剖析思维链推理、ReAct技术原理、框架源码及案例实战。
第9章 提示词本质解析及评估实战与源码解析,包含问答评估、代理评估源码解析及提示词本质探讨。
第10~11章 LangChain大模型框架的七大核心组件及案例解析(上、下),涵盖模型、词嵌入、提示词、内存、回调、数据连接、代理等核心组件及聊天机器人综合案例。
第12章 LangChain代理深入解析及源码解析,详解代理工作原理及AutoGPT源码解析。
第13章 AutoGPT源码解析及综合案例实战,剖析AutoGPT内部机制及其在LangChain代理、内存、PromptGenerator中的应用。
第14章 使用LangChain构建问答聊天机器人案例实战,涵盖GPT-4代码生成全流程及LangChain开发实战。
第15章 构建基于大模型的自治代理案例,详解自治代理原理、工具、示例及开源实现源码。
第16章 Llama 2模型与LangChain项目详解,包括模型部署(Replicate)、Hugging Face/LangChain实践、检索增强生成及自定义提示词RetrievalQA开发。
本书特色
●深入探索,全面剖析。 本书涵盖ChatGPT案例实战、LangChain项目实战及框架源码解析等多个层面的内容。每章都深入探讨相关技术与案例,并提供源码解析,使读者能够全面了解ChatGPT和LangChain等技术的内部机制与开发原理,为实际项目的应用提供有力指导。
●实战剖析,项目揭秘。 本书每章都提供具体的案例实战与项目解析,引导读者通过实际操作和代码理解技术细节和底层逻辑。通过理论结合实践的方式,使读者能够更好地运用所学知识,深入了解项目和框架的实现细节。
●前沿突破,技术驱动。 本书介绍了一系列突破性的技术,如ChatGPT、LangChain、Transformer、Prompt、Llama 2、AutoGPT、BabyAGI、CoT、ToT、ReAct、MRKL等。通过对这些技术的深入剖析,读者可以了解相关技术的发展和应用,并了解它们在实际项目中的具体应用场景和效果。
●源码解析,细致讲解。 本书对LangChain框架的关键技术进行了逐行源码剖析。读者可以深入理解源码实现和机制原理,从而更好地理解技术细节和底层逻辑,并将其应用于实际开发工作中。
本书还为读者提供了丰富的知识和实用的技能,帮助读者在ChatGPT和LangChain领域取得突破性的进展。无论是初学者还是有一定经验的开发者,都可以从本书中获得有价值的学习资源。
配套资源
为便于教与学,本书配有同步教学视频(约1000分钟)、源代码、数据集、教学课件、教学大纲、安装程序。
作者简介
王家林
美国斯坦福大学计算机专业毕业。曾在美国担任硅谷顶级机器学习和人工智能实验室主任、杰出AI工程师及首席机器学习工程师,专精于对话式人工智能(conversational AI)。现担任硅谷某知名对话机器人公司CTO,自2019年起专注于基于红队测试(red teaming)的责任型AI(responsible AI),并热衷于构建生成式AI/大语言模型教练系统(GenAI/LLM coaching systems)。在硅谷任职期间,曾领导多个GenAI/LLM解决方案项目,成功平衡企业业务需求下的大模型推理(reasoning)系统与幻觉(hallucinations)及偏见(biases)风险的最小化。
作为数据科学、机器学习、NLP、ChatGPT及大模型等领域25本书的主要作者,王家林对利用人工智能提供解决方案,以及通过机器学习驱动的NLP与LLM流程帮助组织实现数据驱动决策充满热情。他曾领导Apple、PayPal、Chase Bank、Faethm、LinkedIn等公司的11个重大NLP项目。
在NLP、对话式AI、大数据及基于AWS的无服务器(serverless)技术方面,拥有丰富的机器学习咨询经验。
段智华
中国电信股份有限公司上海分公司高级工程师。长期从事大模型与智能体技术领域,专注Agentic AI、Harness Agent等前沿方向研究。
新书购买链接
《企业级ChatGPT AI大模型应用开发实战(1000分钟视频)》 购买链接:item.jd.com/15389212.ht…