Agent 开发前置基础:从 LLM 调用到可控 Agent 的完整知识框架
如果你要从零开始理解 Agent 开发,这篇文章是索引 #
过去三周,我通过 SecurityClaw 这个开源 SOC Agent 框架,把 Agent 开发的 18 个核心主题走了一遍。这篇文章不是教程——它是一个知识地图,告诉你每个概念在哪个文件里,以及它们之间的调用关系。
如果你想按章节系统读完整个专题,直接看这里:SecurityClaw 学习笔记总纲。

第一层:LLM 是引擎,不是大脑 #
很多人入门 Agent 的第一个误区是把 LLM 当成决策中枢。实际上,LLM 只负责一件事:给定状态,输出下一步动作建议。所有"能不能执行"、“执行到一半怎么办”、“状态怎么恢复”——这些是代码的责任。
# Agent 的核心不是 LLM,是这个循环体
class AgentLoop:
def run(self, state: AgentState) -> FinalOutput:
while not self.should_stop(state):
action = self.llm.plan(state) # LLM 建议下一步
result = self.execute(action) # 代码验证 + 执行
state = self.reflect(state, result) # 评估结果,更新状态
return state.final_output
这个模式在 SecurityClaw 的多个文件中反复出现:core/runner.py(编排层)、core/chat_router/logic.py(LangGraph 循环)、skills/ 下的各个技能执行器。
第二层:六个必须理解的概念 #
1. 状态管理(State) #
Agent 的"记忆"是一个显式的数据结构,不是隐式的对话历史:
class AgentState(TypedDict):
messages: list[Message] # LLM 对话记录
task_plan: Optional[Plan] # 当前执行计划
execution_history: list[Step] # 已完成步骤(含成功/失败)
context: dict[str, Any] # 注入的环境(DB、LLM、配置)
error_count: int # 连续失败计数(防止死循环)
final_output: Optional[str] # 最终输出
为什么用 TypedDict 而不是 Pydantic? 在 LangGraph 的节点间传递状态时,不可变字典操作({**state, key: val})比 Pydantic 模型的 .copy(update=...) 更高效,且与 LangGraph 的 checkpoint 机制兼容。
2. 工具调用(Tool Calling) #
def dispatch(tool_name: str, args: dict, runtime: Runtime) -> Observation:
# 模型只建议 tool_name + args,代码负责五步验证
if tool_name not in runtime.whitelist: return error("not allowed")
if not runtime.validator.check(args): return error("invalid args")
if not runtime.authorizer.check(tool_name): return error("permission denied")
if runtime.budget.remaining < cost: return error("budget exceeded")
return runtime.execute(tool_name, args) # 终于执行
关键设计:工具调用的每一步失败都返回一个结构化的 Observation 对象(含 status 和 error),而不是抛异常。这让 LLM 能在下一轮规划时看到"上次为什么失败",决定是否重试。
3. 规划与循环(Planning & Loop) #
flowchart TD
Input[用户输入] --> Plan[LLM 规划<br/>生成 Action 列表]
Plan --> Execute[执行 Action]
Execute --> Eval{评估结果}
Eval -->|成功| Next{还有 Action?}
Eval -->|失败| Retry{重试次数 < 3?}
Retry -->|是| Plan
Retry -->|否| Final[强制输出]
Next -->|是| Execute
Next -->|否| Final
error_count >= 3 强制停止是这个循环里最重要的安全阀。没有它,LLM 可能在"执行失败→重新规划→再次失败"的循环中消耗所有 token budget。
4. RAG 与上下文工程 #
# 一个好的 build_context 不只是拼接文本
def build_context(query: str, state: AgentState) -> str:
# 1. 语义检索
docs = rag.retrieve(query, k=5)
# 2. 按 relevance score 过滤(> 0.7 才保留)
relevant = [d for d in docs if d.score > 0.7]
# 3. 格式化:源 + 时间 + 内容
return "\n".join(f"[{d.source}] {d.timestamp}: {d.text}" for d in relevant)
SecurityClaw 的 RAG 引擎(core/rag_engine.py)有三个核心方法:store()(嵌入→存向量库)、retrieve()(kNN 搜索+keyword fallback)、build_context()(格式化→注入 prompt)。
5. 安全护栏(Guardrails) #
Agent 的权限检查必须发生在执行前,不是在规划阶段:
# 这是 Agent 的安全边界——每条权限规则都是一个显式的 deny
class PermissionRule:
tool: str # 哪个工具
require_role: str # 需要什么角色
max_args_len: int # 参数长度限制
rate_limit: int # 每分钟最大调用次数
SecurityClaw 把权限收敛到 AgentRunner.execute_step() 这一个函数里,而不是分散在各个 skill 中。这样不会出现"某个 skill 忘了校验权限"的 bug。
6. 可观测性(Observability) #
没有可观测性的 Agent 就是一个黑盒。SecurityClaw 在 core/agent_state.py 的 execution_history 字段中记录了每一步的完整信息:
class StepResult:
status: Literal["SUCCESS", "ERROR", "TIMEOUT", "DENIED"]
action: Action # 哪个动作
tool_name: str # 调了哪个工具
duration_ms: int # 执行耗时
error: Optional[str] # 失败原因
data: Optional[dict] # 返回数据
有了 execution_history,你可以回溯任何一个 Agent 决策:“为什么它选了那个工具?执行了多久?为什么失败了?”
知识框架总览 #
| 层级 | 概念 | SecurityClaw 对应文件 | 核心问题 |
|---|---|---|---|
| 1 | LLM 调用 | core/llm_provider.py | 怎么让 LLM 输出结构化方案? |
| 2 | 状态管理 | core/agent_state.py | 多步之间如何不丢上下文? |
| 3 | 工具调用 | core/runner.py:dispatch() | 谁负责验证和执行? |
| 4 | 规划循环 | core/chat_router/logic.py | 执行失败后如何重试? |
| 5 | RAG 检索 | core/rag_engine.py | 如何注入外部知识? |
| 6 | 安全护栏 | core/runner.py:execute_step() | 权限在哪里检查? |
| 7 | 技能系统 | skill_loader.py + skills/ | 如何动态加载新能力? |
| 8 | 可观测性 | execution_history | 出错了怎么回溯? |
读完这套系列,你应该能回答这三个问题 #
Agent 和"能调工具的 LLM"有什么区别? — Agent 有显式的状态管理、执行前验证、失败恢复机制。LLM 只管规划,代码负责所有执行边界。
为什么用 LangGraph 而不是手写 while 循环? — LangGraph 提供了检查点(中断+恢复)、条件路由、状态隔离,手写循环要实现这些得自己搞序列化。
最容易被忽略的设计是什么? — 安全护栏和可观测性。大多数 Agent demo 只关心"能不能跑通",但生产环境里,“跑错了怎么办"和"为什么跑错了"比"能不能跑"重要十倍。
基于 SecurityClaw 源码分析的 18 篇学习笔记系列总结