01 项目总览与架构概览
结论先说:一个能长期运行的 Agent 框架,至少要把五层分开 #
如果你第一次看 SecurityClaw,很容易被它的技能目录、RAG 引擎、LangGraph 图和一堆 provider 抽象绕晕。但把所有文件先压成架构层,你会发现这个项目真正重要的不是“用了多少 AI 组件”,而是它把一个可运行 Agent 系统拆成了五层:
- 入口层:用户如何进入系统
- 编排层:任务如何被组织、分发和推进
- 能力层:系统到底会哪些技能
- 知识层:系统如何持有和检索证据
- 基础设施层:外部依赖如何被隔离
这篇的任务就是把总图建立起来。后面 02-09 篇,都是从这张总图里拆出的局部。
一、先看总图:不要先陷进某个 4000 行文件 #
flowchart TD
User[用户 / 定时任务 / Web 请求] --> Entry[入口层 main.py / service]
Entry --> Runner[编排层 runner.py / chat_router]
Runner --> Skills[能力层 skills/*]
Runner --> RAG[知识层 rag_engine.py / memory]
Runner --> Graph[状态机 LangGraph / loop]
Skills --> DB[基础设施层 db_connector.py]
Skills --> LLM[基础设施层 llm_provider.py]
RAG --> DB
RAG --> LLM
Graph --> LLM
如果你记住这张图,再去看代码,就不会有“所有东西混在一起”的感觉。
二、入口层:系统怎么被唤起 #
入口层解决的问题非常简单:谁发起了一次 Agent 运行?
在 SecurityClaw 里,入口主要是 CLI 和服务接口。
# 伪代码:入口层只负责收请求,不负责理解业务
@click.group()
def cli():
pass
cli.add_command(chat) # 人工交互
cli.add_command(run) # 后台循环
cli.add_command(dispatch) # 手动触发某个技能
cli.add_command(service) # Web 服务
cli.add_command(list_skills) # 可观测入口
cli.add_command(status) # 运行状态入口
入口层不该做什么 #
入口层不应该:
- 直接执行业务逻辑
- 直接调数据库
- 直接决定用哪个技能
- 直接持有权限策略
它只负责把用户意图交给编排层。
这是一个非常容易被写坏的边界。很多小项目会在 CLI 入口里直接 if question contains X: call skill Y,这样一旦入口变多(CLI / API / cron),逻辑会立刻复制粘贴。
三、编排层:Agent 为什么不是“一次 LLM 调用” #
编排层是整个系统的核心。它回答的是:
这次任务应该怎么推进?当前状态是什么?下一步由谁负责?什么时候该停?
编排层通常由两个部分组成:
runner.py这类系统调度器chat_router/logic.py这类具体执行循环
# 伪代码:编排层的三件套
class Runner:
def setup(self):
skills = loader.discover()
scheduler.register(skills)
def build_context(self):
return {"db": db, "llm": llm, "memory": memory, "config": cfg}
def dispatch(self, question: str):
state = create_initial_state(question)
return graph.invoke(state)
编排层的职责边界 #
- 管状态
- 管循环
- 管调度
- 管恢复
- 管停止
但它不应该亲自变成技能实现。编排层要“安排能力”,不能自己变成能力。
四、能力层:为什么 skills/ 不只是插件目录 #
很多人看 skills/ 会把它理解成“插件系统”。这个理解只对了一半。
真正准确的理解是:
skills/ 是系统的能力表面,它把可调用能力拆成一组带契约的最小单元。
一个技能目录至少会带来三种信息:
- 这项能力做什么
- 它什么时候跑
- 它允许怎么跑
skills/geoip_lookup/
├── manifest.yaml
├── instruction.md
├── logic.py
└── hooks.py
为什么这层必须独立出来 #
因为 Agent 的可扩展性,本质上不是“prompt 还能写多长”,而是:
- 能不能低成本加新能力
- 加了新能力后会不会污染旧逻辑
- 一个坏技能会不会拖垮系统
把能力层做成目录化、声明化、可跳过的结构,就是在解决这几个问题。
五、知识层:RAG 和记忆为什么不是同一回事 #
Agent 系统里最容易混淆的两样东西是:
- 运行中的状态
- 可长期检索的知识
SecurityClaw 把它们拆开,是一个非常正确的决定。
5.1 运行状态 #
属于短期、结构化、为下一步服务
5.2 工作记忆 / checkpoint #
属于会话级,可恢复
5.3 RAG #
属于长期知识层,用来提供证据,不是直接拿来当状态机字段
State -> 当前这轮在干什么
Checkpoint -> 下次从哪里恢复
RAG -> 我长期知道什么事实
如果把三者混在一起:
- prompt 会膨胀
- 恢复会变慢
- 失败会变得不可解释
六、基础设施层:为什么要抽象 DB 和 LLM #
基础设施层存在的根本原因不是“设计优雅”,而是:
业务逻辑不该知道自己到底连的是 OpenSearch、MockDB 还是 SQLite;也不该知道自己到底调的是 Ollama、OpenAI 还是 Fake LLM。
class BaseDBConnector(ABC):
def search(self, query: str): ...
def index_document(self, doc: dict): ...
def knn_search(self, embedding, k: int): ...
class BaseLLMProvider(ABC):
def chat(self, messages): ...
def embed(self, text: str): ...
这一层解决了什么 #
- 测试可跑:Mock provider 替掉真实依赖
- 部署可切换:本地、线上、离线模式都能共存
- 架构可扩展:不需要把每个技能重写一遍
如果这层不抽象,Agent 项目会非常快地从“框架”退化成“绑定某一个模型和某一个数据库的脚本集合”。
七、为什么这五层必须同时成立 #
只要缺一层,系统就会迅速变脆。
缺入口层边界 #
新增 API / cron 时逻辑复制
缺编排层 #
系统退化成“prompt + tool” 的拼装
缺能力层 #
扩展能力必须改核心逻辑
缺知识层 #
每次任务都得从零开始理解
缺基础设施抽象 #
测试和部署都会被真实依赖绑死
也就是说,一个可运行的 Agent 框架,不能只看“模型能不能规划”,要看这五层是否各自独立。
八、这一篇真正要你带走的框架 #
如果你以后再看任何 Agent 项目,先问它这五个问题:
- 入口在哪里?
- 编排层在哪里?
- 能力是怎么注册和执行的?
- 知识层怎么分 state / memory / RAG?
- 外部依赖有没有被抽象?
这五个问题能比“它用了哪个模型”更快判断项目是否靠谱。
九、验证命令 #
python main.py list-skills
python main.py status
pytest tests/ -x --tb=short