跳到主要内容

数据截至 (上游 commit ae57a2357745)

AgenticSeek — 架构与原理

30 秒导读: AgenticSeek 是一个全本地运行的多专家 AI 助手(自我定位为 Manus 的本地替代品)。你说一句话,它先用两个小型文本分类器判断「该谁接、多难」,再交给会写代码 / 找文件 / 爬网 / 做规划的专家 agent。工具调用不走 function calling,而是认模型回复里的 markdown 代码块


1. 这是什么(零基础也能懂)

一句话定义: AgenticSeek 是一个跑在你自己机器上的 AI 助手,能自主上网、写并运行代码、翻你指定目录里的文件,还能把一个大任务拆成多步依次做完。

解决什么问题、给谁用

想象你想要这样一件事:

「上网查一下 2025 年最好的几个免费天气 API,挑一个,然后在我的工作目录里写一个 Python 小程序把今天的天气打印出来。」

这件事跨了三种能力:上网判断写并运行代码。云端助手能做,但你的文件、你的搜索记录、你的对话都要出门。AgenticSeek 的取舍就一句话:宁可用小一点的模型,也要全部留在本机。

它面向的人群很具体:

  • 手上有一块能跑 14B~70B 本地模型的显卡;
  • 不愿意把代码库、文件、搜索词交给云端 API;
  • 能接受「本地小模型比 GPT 笨一些」这个代价。

它能做什么

能力具体是什么
自主上网起一个真实 Chrome,搜索、逐页阅读、记笔记、甚至填表登录
写并运行代码Python / Bash / C / Go / Java,写完直接执行,看到报错自己改
操作文件在你指定的工作目录里递归找文件、读内容(含 PDF)
任务规划复杂请求拆成多步 JSON 计划,逐步调度不同 agent
语音交互本地 TTS(kokoro)+ 本地 STT(Vosk),带回声过滤
两种界面终端 CLI(cli.py)或 React 网页 + FastAPI 后端(api.py

用起来什么样

最直观的是 CLI 模式。下面是一次真实交互的形状(示意,非源码输出):

$ uv run cli.py
Initializing...
Loading zero-shot pipeline...
Loading LLM router model...
AgenticSeek is ready.

➤➤➤ 帮我在工作目录里写个 Python 脚本,打印当前目录下所有 .py 文件

Selected agent: coder (roles: code)
Thinking...
────────────────────────────────────────
import os
for f in os.listdir('.'):
if f.endswith('.py'):
print(f)
────────────────────────────────────────
Executing 1 python blocks...
[success] Execution success, code output:
api.py
cli.py

注意两件事,它们是理解整个项目的钥匙:

  1. Selected agent: coder —— 路由发生在调用 LLM 之前,不是模型自己选的。
  2. 模型只是写了一段普通的 markdown 代码块,系统自己把它抠出来跑掉了

一句话直觉

把 AgenticSeek 想成一个小型电话总机 + 一队专家

  • 总机(路由器)是两个廉价的文本分类器,几十毫秒就判断出「这通电话该转给谁」;
  • 专家(各 agent)拿到活之后,用同一套办法干活——说话时顺手写代码块,系统替他按下回车

总机不用 LLM,是因为本地小模型做元决策(「我该派谁」)非常不稳;把这件事交给判别式小模型,确定、便宜、可控。


2. 顶层全景(它大概怎么转)

2.1 主链路

怎么读这张图:从上往下就是一次请求的生命周期,右侧是每一层用到的关键文件。

用户输入(键盘 / 麦克风 / 网页 POST /query)
|
v
┌──────────────┐
│ Interaction │ 统一入口:拿输入、调路由、说结果 sources/interaction.py
└──────┬───────┘
v
┌──────────────┐
│ AgentRouter │ ① 检语种→翻英文 ② 估难度 ③ 投票选人 sources/router.py
└──────┬───────┘
| 难度 HIGH ──────────────┐
v v
┌──────────────┐ ┌──────────────┐
│ 单个专家 Agent│ │ PlannerAgent │ 拆成 JSON 计划,逐步调度
└──────┬───────┘ └──────┬───────┘
| |(内部再调上面那些 Agent)
v v
┌──────────────────────────────────────┐
│ LLM 循环:问模型 → 抠出代码块 → 执行 │ sources/agents/agent.py
│ → 反馈回灌 memory → 再问 │ sources/tools/*.py
└──────────────────────────────────────┘
|
v
最终答复(文字 + 每个代码块的执行结果)

2.2 部件一句话职责

部件干什么在哪个文件
Interaction会话总控:读输入、调路由、保存/恢复会话、朗读答案sources/interaction.py
AgentRouter不用 LLM,用两个分类器决定「谁接活、多难」sources/router.py
Agent(基类)统一的「问 LLM → 抠代码块 → 执行 → 回灌」骨架sources/agents/agent.py
CasualAgent闲聊,不带任何工具sources/agents/casual_agent.py
CoderAgent写并执行 Python/Bash/C/Go/Javasources/agents/code_agent.py
FileAgent找文件、读文件、跑 shellsources/agents/file_agent.py
BrowserAgent搜索 + 逐页导航 + 记笔记 + 填表sources/agents/browser_agent.py
PlannerAgent拆任务、调度上面四个、每步之后重规划sources/agents/planner_agent.py
Tools(基类)markdown 代码块的解析器 + 工作区路径守卫sources/tools/tools.py
BrowserSelenium 驱动:隐身、转 Markdown、填表、截图sources/browser.py
Provider14 个 LLM 后端的统一 dispatch 表sources/llm_provider.py
Memory对话历史 + 落盘 + 实验性摘要压缩sources/memory.py
workspace 工具工作区 / 运行时目录解析与越权拦截sources/workspace.py

2.3 主线走一遍(高层,不进代码)

以「查天气 API 并写个脚本」为例:

  1. Interaction.get_user() 拿到这句话。
  2. AgentRouter.select_agent() 先用 langid 检出语种,必要时用 MarianMT 翻成英文;再估复杂度——这句话跨了「查网 + 写码」,被判 HIGH直接交给 PlannerAgent,投票环节都不走。
  3. PlannerAgent.make_plan() 让 LLM 输出一段 ```json 计划:任务 1 交 Web、任务 2 交 Coder,并声明任务 2 需要任务 1 的产出。
  4. 逐步执行BrowserAgent 起真 Chrome 搜索、读页、记笔记;CoderAgent 拿到笔记,写 Python 代码块。
  5. 执行与自愈:代码块被 Tools.load_exec_block() 抠出来,丢进子进程跑;报错就把 stderr 当成「用户消息」推回 memory,让模型重写,最多 5 次。
  6. 每步之后重规划PlannerAgent.update_plan() 把这一步的结果和成败喂回 LLM,问它「计划要不要改」,模型答 NO_UPDATE 就照原计划走。
  7. 最后由 Interaction 打印/朗读答案。

3. 阅读地图(建议顺序)

按「由浅入深」排的。只想抓精华的话,读 01 和 02 就够了——那是这个项目最有辨识度的两处设计。

顺序章节讲什么为什么值得读
101-routing.md分类器投票路由 + 复杂度分级「不用 LLM 做路由」的完整可运行样本
202-blocks-and-execution.md代码块协议、执行循环、五种解释器没有 function calling 时,工具调用还能怎么做
303-planner.mdJSON 计划、依赖注入、逐步重规划弱模型上怎么撑起 divide-and-conquer
404-browser.md隐身浏览器、页面转 Markdown、笔记式导航纯文本驱动(无视觉)的浏览器 agent 范本
505-llm-and-memory.md多后端 dispatch、记忆压缩、reasoning 抽取本地/云端后端抽象的最小实现
606-sandbox-and-safety.md工作区隔离、命令黑名单、API token这套系统真正的信任边界在哪

4. 巧妙之处(可以带走的技术)

4.1 把「选谁干活」从 LLM 手里拿走

绝大多数 agent 框架让 LLM 自己决定调哪个工具。AgenticSeek 反过来:路由是判别任务,就用判别模型。

AgentRouter 里跑的是 facebook/bart-large-mnli(零样本分类)加一个 AdaptiveClassifier(现场 few-shot 学习),两者各出一个标签+置信度,归一化后比高低(sources/router.py:370-390router_vote)。代价是几百毫秒的 CPU 推理,换来的是不受模型胡言乱语影响的确定性分发

4.2 用 markdown 代码块当工具协议

本地小模型输出严格 JSON 的成功率不高,但写代码块是它们被训练得最熟的动作。于是每个工具认领一个围栏标签(python / bash / json / web_search / file_finder),Tools.load_exec_block() 直接扫文本(sources/tools/tools.py:150-200)。

附赠两个小设计:围栏行写成 python:main.py 就顺便存盘;块前有缩进时会按缩进量整体反缩进,免得模型把代码嵌在列表里就跑不了了。

4.3 把系统反馈伪装成用户消息

执行失败后,Agent.execute_modules() 把报错以 role='user' 推回 memory(sources/agents/agent.py:280)。这样对模型来说,「编译器骂你了」和「用户骂你了」是同一件事——不需要额外的 tool-result 角色,任何 chat 接口都吃得下。

4.4 网页 → Markdown,而不是网页 → 截图

Browser.get_text() 先剥掉 script/style,用 markdownify 转成 Markdown,再逐行做「这是不是一句人话」的判定is_sentence:含数字,或词数≥5 且有标点/够长),把导航栏碎词滤掉,最后硬截到 32768 字符(sources/browser.py:390-418)。整条链路不需要视觉模型。

4.5 语音的回声过滤

开着音箱做语音助手,麦克风会听见自己刚说的话。sources/echo_filter.py 的办法很朴素但有效:双向找 3 个连续词的公共子串,命中就判为回声丢弃(is_echo)。


5. 边界与局限(诚实)

5.1 沙箱是「目录级」的,不是「进程级」的

  • 文件路径有真守卫:resolve_workspace_path()realpath + commonpath 拦截 ../ 越权(sources/workspace.py:71-93),FileFinder 的每次读文件都过这道关。
  • 但代码执行没有。Python 走 subprocess.run([sys.executable, "-c", code]),Bash 走 subprocess.Popen(..., shell=True)——只是 cwd 设成工作区,进程本身能读写整台机器sources/tools/PyInterpreter.py:60-66sources/tools/BashInterpreter.py:57-64)。
  • 危险命令黑名单(sources/tools/safety.py只在 safe_mode=True 时生效,而 config.ini 里默认 False

真正的隔离靠 Docker:docker-compose.yml 只把 ${WORK_DIR} 挂进容器,后端端口只发布在 127.0.0.1在宿主机直接 uv run cli.py 就没有这层保护。 详见 06-sandbox-and-safety.md

5.2 路由标签和实际 agent 对不齐

投票的两侧用的是两套不同的标签

  • BART 那侧的候选标签是运行时 agent 的 roletalk / code / files / web / planificationsources/router.py:455)。
  • AdaptiveClassifier 那侧的标签来自手写 few-shot,含 mcp(5 条)和一条拼错的 codingsources/router.py:209)。

如果自适应分类器返回 mcpcoding 并赢下投票,select_agentfor 循环找不到匹配的 agent,返回 None,整轮请求失败(sources/router.py:464-471)。MCP agent 在 cli.py:52-54 是注释掉的,api.py 里根本没注册。

5.3 MCP agent 尚未可用

源码开头直书 “MCP agent is an active work in progress, not functional yet”(sources/agents/mcp_agent.py:9),且没有 MCP_FINDER_API_KEY 时自己关闭。

5.4 coder agent 的循环有个反直觉的分支

CoderAgent.process() 里,bash 块执行成功也不会跳出循环——判断条件是 exec_success and self.get_last_tool_type() != "bash"sources/agents/code_agent.py:76)。设计意图看起来是让模型能连续下多条 shell 命令,但副作用是:一串顺利的 bash 操作跑满 5 轮后,函数会返回 “I'm sorry, I couldn't find a solution to your problem.”(sources/agents/code_agent.py:83-84),并且中途每轮都会打印 “Execution failure”。

5.5 coder 的 prompt 教了一种解析不出来的 file_finder 写法

prompts/base/coder_agent.txt 教模型这样找文件:围栏里直接写文件名(或用 ```file_finder:read)。但 FileFinder.execute() 取参数靠 get_parameter_value(block, "name"),即必须有 name=xxx 这一行sources/tools/fileFinder.py:132sources/tools/tools.py:127-140)。裸文件名会得到 “Error: No filename provided”。只有 prompts/base/file_agent.txt 教的 name=toto.py 形式能正常工作。

5.6 记忆压缩是实验性的,而且默认关

所有 agent 构造 Memory 时都传 memory_compression=False。即使打开,上下文长度是从模型名里正则抠一个「数字+b」再按幂律估算sources/memory.py:47-68get_ideal_ctx),模型名不含数字(如 gpt-4o)时直接返回 None,压缩与截断全部跳过。

5.7 一次只能处理一个请求

api.py 用一个模块级 is_generating 布尔量挡并发,第二个请求直接 429(api.py:248-250)。全局共享同一个 Interaction 单例,没有会话隔离。


6. 横向对比(同 shelf 兄弟)

维度AgenticSeek常见做法
谁决定调哪个 agent专用文本分类器投票(BART + AdaptiveClassifier)由 LLM 自己在 prompt 里决定
工具调用协议markdown 代码围栏 + 标签OpenAI function calling / JSON schema
任务分解一次生成完整 JSON 计划,每步之后重问一次要不要改一次性计划,或纯 ReAct 逐步决策
浏览器感知纯文本:HTML → Markdown → 句子过滤截图 + 视觉模型,或 DOM 可访问性树
部署取向本地优先,云端 API 是可选降级云端 API 优先
多语言路由前用 Helsinki-NLP MarianMT 翻成英文少见

延伸阅读(同 shelf):


7. 代码地图(导航索引)

主题文件关键符号
CLI 入口 + 主循环cli.pymain
HTTP 后端 + 系统装配api.pyinitialize_systemprocess_queryget_latest_answer
会话总控sources/interaction.pyInteraction.thinkInteraction.get_usertranscription_job
路由sources/router.pyAgentRouter.select_agentrouter_voteestimate_complexitylearn_few_shots_tasks
agent 基类与执行循环sources/agents/agent.pyAgent.execute_modulesAgent.remove_blocksAgent.sync_llm_request
工具基类与块解析sources/tools/tools.pyTools.load_exec_blockTools.save_blockTools.resolve_path
代码执行sources/tools/PyInterpreter.pyBashInterpreter.pyPyInterpreter.executerefuse_interactive_codeBashInterpreter.execute
规划sources/agents/planner_agent.pyPlannerAgent.make_planupdate_planstart_agent_process
浏览器 agentsources/agents/browser_agent.pyBrowserAgent.processmake_navigation_promptparse_answer
浏览器驱动sources/browser.pyBrowser.get_textgo_tofill_formapply_web_safety
LLM 后端sources/llm_provider.pyProvider.respondProvider.available_providers
记忆sources/memory.pyMemory.pushMemory.compressget_ideal_ctx
工作区与运行时目录sources/workspace.pyresolve_workspace_pathget_work_dirruntime_subdir
命令黑名单sources/tools/safety.pyis_unsafeunsafe_commands_unix
API 鉴权sources/api_auth.pyrequire_api_token
系统提示词prompts/base/*.txtcoder_agent.txtplanner_agent.txtfile_agent.txt