Skip to content
BlueX888Public

About

A deep-research multi-agent team in 1000 lines —— 一天读完的可运行教科书

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

nanoteam

A deep-research team in 1000 lines.

核心代码行数 依赖 协议

快速开始 · 工作原理 · 配置 · 入门文档 · 机制精读 · 示例


你见过 deep research 工作:提一个问题,它拆解、分头调查、汇总成报告。 但它内部到底发生了什么?

打开任何一个多智能体框架找答案,迎接你的是几万行代码、层层抽象和回调地狱。 nanoteam 反着来:一个 Leader、几个 Worker、174 行核心代码,一天读完, 从此你对"多智能体系统"这五个字不再心虚。

它不是又一个框架,而是一份可以运行的教材。

  • 小到能读完 — 核心代码 ≤ 1000 行是写进 CI 的承诺,当前只用了 174 行
  • 薄到能看穿 — 唯一依赖是 openai SDK,没有基类、没有装饰器、没有魔法,数据流向一眼可见
  • 真到能跑通 — 不是伪代码,配好三个环境变量就能产出真实的研究报告

配套两级阅读:overview.md(入门:跑通并看懂大概)→ how-it-works.md(精读:拆、做、合、工具与失效边界)。

快速开始

三条命令,从零到第一份研究报告:

pip install -e .
export NANOTEAM_API_KEY=<你的Key> NANOTEAM_BASE_URL=<服务地址> NANOTEAM_MODEL=<模型名>
nanoteam "调研新手学习多智能体系统的三条路线,对比后给出建议"

然后换成你自己的问题,或者直接把资料喂给它:

nanoteam "综合这两份笔记,给出结论" a.md b.md

附上文件,它就变成"基于给定材料的 deep research":Leader 把资料与角度分给各 Worker,再综合成报告。

"174 行"是怎么数的? 只统计 nanoteam/ 下除 prompts.py、__init__.py 外 .py 文件的非空、非注释行,与 nanoGPT 惯例一致。不信可以自己数: sh scripts/count_lines.sh——超过 1000 行,CI 直接挂掉。

它是怎么工作的

没有黑箱。一次完整运行 = N + 2 次模型调用:1 次分解 + N 次执行 + 1 次汇总, 成本可以掰着手指头算(开启 web 搜索后每次"执行"变为至多 MAX_TOOL_ROUNDS 轮工具循环,见下文"配置"):

nanoteam 工作流:用户目标经过 Leader 分解、N 个 Worker 执行、Leader 汇总,形成最终报告

运行时,整个过程摊开在你眼前——过程透明是本项目的生命线:

[leader] 收到目标:调研新手学习多智能体系统的三条路线,对比后给出建议
[leader] 分解出 4 个任务:
  1. 调研路线一:从主流框架入手(CrewAI/AutoGen)
  2. 调研路线二:从极简实现入手(读源码型项目)
  ...
[worker-1] 开始执行:调研路线一 ...
[worker-1] 完成 ✓(产出 812 字)
...
[leader] 全部任务完成,开始汇总
[leader] 最终产出 ↓

配置

三个环境变量,没有配置文件,没有 CLI 选项。不设默认值、不偏向任何供应商—— 任选一家 OpenAI 兼容服务即可(嫌麻烦可 cp .env.example .env 填好后 source .env):

环境变量 含义 必填
NANOTEAM_API_KEY 模型服务的 API Key ✅
NANOTEAM_BASE_URL OpenAI 兼容服务地址 ✅
NANOTEAM_MODEL 模型名 ✅
NANOTEAM_SEARCH_URL / NANOTEAM_SEARCH_KEY 开启 web 搜索(v0.2) 可选
NANOTEAM_CONFIRM 执行前人工确认(v0.2) 可选
两种供应商的配置示例(DeepSeek / Qwen)
# DeepSeek 写法
export NANOTEAM_API_KEY=<你的 API Key>
export NANOTEAM_BASE_URL=https://api.deepseek.com
export NANOTEAM_MODEL=deepseek-chat

# Qwen(百炼 / DashScope)写法
export NANOTEAM_API_KEY=<你的 API Key>
export NANOTEAM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
export NANOTEAM_MODEL=<模型名,如 qwen-plus>

想让它真的上网搜? 再加两个环境变量,Worker 就会通过 function calling 自主搜索互联网——从"基于已有知识/材料的研究"升级为真正的 deep research:

export NANOTEAM_SEARCH_URL=https://api.tavily.com/search   # Tavily:零改动直用
export NANOTEAM_SEARCH_KEY=<你的搜索 Key>

接口形状是常见的"POST JSON {"query": ...} + Bearer 鉴权,响应含 results 列表(title/url/content)",Tavily 原生即此格式。想换国内的博查等服务? 按 search.py 顶部注释改 run() 一个函数即可——那是全项目唯一发 HTTP 请求的地方,约 20 行。

想在执行前把把关? 设置 NANOTEAM_CONFIRM=1,分解结果展示后会停下来问你 (回车继续,输入 n 取消)。这是 human-in-the-loop 的最小形态——只有 "继续/取消"两个选择,编辑任务留作读者练习:

export NANOTEAM_CONFIRM=1   # 不设置即保持全自动

仓库结构与行数分布

六个文件讲完整个系统,每个文件一句话说清职责:

文件 行数(实测) 职责
nanoteam/task.py 14 核心数据结构:Task 与 Plan,整个系统唯一的状态容器
nanoteam/llm.py 37 模型访问薄封装,唯一接触第三方 SDK 的文件
nanoteam/search.py 23 搜索薄封装,唯一发起 HTTP 请求的文件(标准库 urllib,零新依赖)
nanoteam/worker.py 28 Worker:一次 run() 即一个全新实例,上下文隔离 + 最小工具循环
nanoteam/leader.py 40 Leader:decompose / dispatch / aggregate,教学核心
nanoteam/cli.py 32 入口:读材料、可选的分解确认、跑流程、渲染过程
合计 174 / 1000
nanoteam/prompts.py 不计入 全部提示词与工具声明(一等公民,场景锚定只发生在这里)
tests/ examples/ 不计入 冒烟测试与三个示例

值得注意的设计约定

这几条不是实现细节,而是可以带走复用的 agent 工程心法。

nanoteam 五条设计约定:判断与搬运分开、接缝可数、fail fast、代码中立、边界公开

模型做判断,代码做搬运。 decompose 时模型只看"目标 + 文件清单(路径与行数)",给出任务与文件分组; 把文件内容搬进 Task.materials 的是代码。让模型碰它不必碰的大块数据, 是很多 agent 系统又贵又脆的根源。

接缝是可数的。 v0.1 系统与外界的接缝只有 llm.chat 一处;开启搜索后变成两处(模型在 llm.py,搜索在 search.py)——"一个工具让接缝从一处变成两处"正是 v0.2 的第一课。引用形式统一 from nanoteam import llm + llm.chat(...) (search 同理),禁止 from nanoteam.llm import chat:后者会让冒烟测试 的模块级替换静默失效。

fail fast,不兜底。 API 失败不重试;分解 JSON 无效时抛出并打印模型原始输出,让你亲眼观察模型行为; 材料超过 MAX_MATERIALS_CHARS(10 万字符,cli.py 中可改的常量)直接报错退出; 工具循环超过 MAX_TOOL_ROUNDS(5 轮,在 worker.py)同样抛错而非静默截断。 每一次失败都摊开给你看,这正是教材该有的样子。

定位锚定,代码中立。 deep research 只出现在提示词与示例中,核心代码是通用编排引擎—— 换一套 prompts.py,nanoteam 就是另一个领域的团队。

失效边界写在明面上。 Leader 会累积所有 Worker 的结论,Worker 一多(经验值 4 个以上)就会逼近上下文 窗口上限。MVP 的 3–6 个任务不会触及,但你应该知道它会在哪里坏掉。另外,开启 搜索后模型调用次数从确定的 N + 2 变为一个区间,成本不再是精确可预估的常数。

冒烟测试与 CI

零网络、零 key、零测试框架,毫秒级跑完:

python tests/test_smoke.py    # 离线跑完整闭环
sh scripts/count_lines.sh     # 行数检查,> 1000 即失败

测试的手法只有一种:用赋值逐一替换系统与外界的接缝(llm.chat、 llm.chat_tools、search.run)。看懂这个测试,你就理解了"接缝是可数的"—— 接缝每多一处,测试就多替换一处。

示例(examples/)

示例 讲什么
research_sources.md 纵切:多份资料分头研读后综合(材料切分的典型形态)
research_angles.md 横切:同一材料多角度分析
analyze_repo/ 进阶·自举:让 nanoteam 研究自己的代码库(文件遍历脚本不进核心代码)
可讨论的临时决议

以下两点为不阻塞实现而采取的临时决议,欢迎在 issue 中讨论:

  1. 不设任何默认模型与默认 base_url:三个环境变量缺一即报错退出(报错信息附两种 供应商的配置示例)。这避免了项目隐含推荐某家供应商,代价是首次配置多一步。
  2. 最终报告仅终端打印,不落盘。"把报告写入 Markdown 文件"留作下面的读者练习。

读者练习(从易到难)

读懂之后,亲手改一改才算真的懂:

  1. 把最终报告写入 Markdown 文件(提示:改 cli.py 的最后三行);
  2. 修改 MAX_MATERIALS_CHARS,体会材料超限时"报错 / 截断 / 摘要"三种方案的取舍;
  3. 换掉 prompts.py 的三段提示词,把研究团队改造成翻译团队或评审团队—— 验证"核心代码零场景专用逻辑"是真的;
  4. 把确认模式(NANOTEAM_CONFIRM)升级为"可编辑":确认时允许输入任务序号删掉 某个任务再执行——这是向"多轮人机协作"(社区扩展方向)迈出的第一步。

License

MIT

About

A deep-research multi-agent team in 1000 lines —— 一天读完的可运行教科书

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages