将 LLM 当作函数后, 我终于认识了 Agent

从毕业设计实践出发,拆解 AI Agent 背后的工程本质。揭示 LLM 只是"概率预测函数"的真相,探讨 RAG、Prompt、MCP、FSM 等技术如何弥补其天然缺陷,让"文本生成器"真正具备解决问题的能力。

我在学校做的毕业设计题目为 "基于 MCP 协议的多专家协作智能体系统设计与实现"

Github 地址为 Lian-MCP-LLM-Agent

其实算是我写的一个玩具项目, 目前因为生活繁忙暂停开发

但开发过程中确确实实学到了很多有用的东西, 也出现了很多有意思的想法

所以打算写一篇博客分享出来


📖 引言

本文将通过六个核心章节,还原我在构建 Lian-MCP-LLM-Agent 过程中的心路历程:
LLM -> RAG -> Prompt -> MCP -> FSM -> CoT

这也正是我从零开始认识 Agent 的学习路径。建议按照编排顺序阅读,因为每一个后置技术的引入,往往都是为了解决前一个技术暴露出的缺陷


第一章 🤖 LLM 的本质

大语言模型 (Large Language Model) 是目前生成式 AI 的核心技术。

简单来说,它是一种基于 Transformer 架构的深度学习模型,使用海量文本数据进行训练,拥有数十亿至数千亿的参数。

它利用自注意力机制(Self-Attention)分析上下文,擅长理解、总结、翻译以及生成文本与代码。

抛开 RAG、MCP、CoT 这些花哨的外部封装,回归最原始的视角

用程序员最好理解的方式来说,LLM 的本质其实就是一个函数

fn llm(input: Text, context: Text) -> Token

即:根据用户输入与上下文信息,预测并生成统计学上最可能的下一个词(Token)。

🌰 打个比方

LLM 生成的每一个字,都是从无数个岔路口中选择其中一条。

连续做 10 次选择,它就给你生成了一句 10 个字的话。

模型结构 (Transformer)参数 (权重)训练数据分布 共同决定了:

  • 这些分岔路有多复杂?
  • 路在何方?
  • 哪些路比较宽(概率更高)?

所以,请记住这个残酷的事实:

  • LLM 不是一个会 "主动思考" 的实体
  • 它不理解你的目标,也不会为结果负责
  • 它只是在给定 inputcontext 的前提下,计算并返回一个概率最高的输出

🌲 LLM 的“短视”与局部最优

沿用上述的分岔路模型,想象生成过程就像是在走这样一棵树:

起点
 ├── A(概率高,现在看起来很好)
 │    ├── A1(平平无奇)
 │    └── A2(死胡同,逻辑崩坏)
 │
 └── B(概率低,现在看起来一般)
      └── B1(逻辑严密,通向真理)

当 LLM 站在起点时,它看不到终点 B1,它只能根据当前上下文的概率分布做单步选择。

于是,因为 A 的概率高B 的概率低,它往往会毫不犹豫地选择 A

最终它可能走到了 A1 甚至 A2,而错过了真正的全局最优解 B -> B1

🤔 为什么平时用起来感觉它很聪明?

因为在海量的训练数据里,存在着无数的 统计巧合人类经验的复用

对于常见问题,由于人类语料库中本身就包含了正确的推导路径,LLM 只需要“背诵”或“模仿”这条路即可。

但一旦问题 太新太长逻辑链条太深,LLM 这种“贪婪”的本性就会暴露无遗。

这也是为什么 LLM 会有以下原生缺陷:

  • 幻觉:一本正经地胡说八道(选择了概率高但错误的路径)。
  • 短视:无法进行长远的规划和推理(看不到树的末端)。
  • 无感:无法通过感知修正自己的错误(没有真实的感官输入)。

🧱 真正让 LLM 「能工作」 的,从来不是它自己

在真正开始写 Agent 之前,我也曾抱着侥幸心理迷信模型能力。

但当系统复杂度上来后,这种幻想很快就破灭了。

在这个章节,我的核心观点是:正因为 LLM 的本质如此单纯(甚至简陋),所以我们才需要外部系统。

在我的项目里,LLM 只占整个系统中非常小的一部分。占据 90% 甚至 99% 开发时间的,是这些东西:

  • 状态机 / 自动机:强行矫正 LLM 的路径。
  • 任务流编排:把大任务拆成 LLM 能处理的小任务。
  • Prompt 工程:用精炼的语言给 LLM 指路。
  • JSON 协议与校验:防止 LLM 输出不可以解析的垃圾。
  • RAG / MCP:给“失忆”的 LLM 外挂硬盘和手脚。

LLM 在其中扮演的角色,其实非常单一:

在被精心准备好的 context 中,理解“我现在在任务流里的位置”,然后生成下一步的建议。

换句话说:如果没有大量“固定架构”包住它,LLM 根本无法稳定工作。

🧠 去魅:那些听起来很厉害的名词

MCPRAGCoTAgentSkills……

这些词在最近被反复提起,甚至被过度神化。

但如果你真的理解了上文提到的 LLM 本质,你会发现它们几乎都在做同一件事:

弥补 fn llm(input, context) 这个函数的原生缺陷。

  • RAG (检索增强):因为 context 长度有限且无法记住历史,所以我们需要“外挂硬盘”进行搜索。
  • CoT (思维链):因为 LLM 本质是短视的概率预测,容易陷入局部最优,所以我们需要强制它“把思考过程写出来”,用显式的逻辑步骤对抗直觉。
  • Agent (智能体):因为 LLM 无法通过单一路径解决复杂问题,所以我们需要循环、决策和工具调用。
  • MCP (模型上下文协议):因为 LLM 无法感知真实世界,所以我们需要标准化的接口让它能“联网”和“操作数据库”。

不要惧怕这些词。

所谓“AI 时代的创新”,很多时候可能只是某个程序员为了让这个“概率预测机”更好地工作,而写的一段工程胶水代码

名字不重要,解决 LLM 的本质缺陷且把事情做成,才重要。


第二章 📚 RAG 技术

如果把 LLM 比作 CPU,那 RAG 就是外挂的硬盘。

它的核心目的只有一个:欺骗 LLM,让它以为自己“记得”所有事情。

日常使用 LLM 对话时,最困扰我的问题往往是:为什么你记不住我刚才说的话?

回归到我们定义的函数:

fn llm(input: Text, context: Text) -> Token

RAG (检索增强生成) 的本质,就是通过工程手段,动态构建这个 context 参数

🤕 为什么我们需要 RAG?

因为 LLM 有两个致命的生理缺陷:

  1. 失忆:模型训练完那一刻,它的知识就固化了。昨天发生的新闻,它是不知道的。
  2. 容量有限:虽然 context 窗口越来越大,但永远装不下你所有的私有文档、代码库和几年的聊天记录。

所以,我们不能把所有数据都一次性塞给它,而是在它开口说话前,临时把作弊小抄(相关信息)塞进它的口袋里。

🏗️ 传统的 RAG:暴力美学与局限

大多数人(包括一开始的我)刚接触 RAG 时,实现的流程都是这样的:

  1. 把文档切碎(Chunking)
  2. 全部算成向量(Embedding)
  3. 存入向量数据库(Vector DB)
  4. 用户提问 -> 算相似度 -> 捞出前 5 条 -> 喂给 LLM

但在实际开发中,我很快就开始嫌弃这种“暴力检索”了。

这种“大海捞针”式的方法,在 Demo 里看起来很美,但一旦数据量上来:

  • 不仅慢:每次都要算 Embedding,延迟感明显。
  • 而且笨:经常捞出“字面相关”但“逻辑无关”的废话,污染 Context。
  • 甚至乱:把一堆碎片拼凑在一起,反而干扰了 LLM 的推理连贯性。

🔧 结构化 RAG:用目录对抗暴力

在被向量检索折磨了一段时间后,工程界开始意识到:问题不在模型,而在数据结构。

如果不治理数据,只靠向量相似度去碰运气,那就是在垃圾堆里找黄金。

1. 多层级索引 (Hierarchical Indexing)

这一步解决的是 “如何把记忆写成树” 以及 “如何像树一样去检索”

与其把所有文本切碎乱放,不如借鉴文件系统的思路,构建一颗 “记忆树”

我们将底层的碎片(原始对话)向上聚类、总结,形成父节点(章节摘要),再向上形成根节点(全书梗概)。

Memory Tree
 ├── Level 3: 全局摘要(目录:关于 Rust 的学习之路)
 │    ├── Level 2: 阶段总结(章:与 Borrow Checker 的斗争)
 │    │    ├── Level 1: 原始数据(节:报错日志与修复代码)
 │    │    ├── Level 2: 原始数据(节:报错日志与修复代码)

检索因此变成了一种策略:不再是盲目匹配,而是 Top-Down 的下钻。

先在 Level 3 确认领域,再在 Level 2 锁定事件,最后才去 Level 1 提取细节。

这不仅提高了准确率,更完美模拟了人类的 联想机制——先回忆起模糊的轮廓,再慢慢聚焦清晰的画面。

2. 显式元数据 (Metadata Filtering)

这一步解决的是 “如何让树更精准” 以及 “如何打破树的限制”

让树更精准,意味着 不要迷信全知全能的向量

很多时候,显式的 SQL 筛选(比如 Time="last_week")比模糊的语义搜索更可靠。我们通过 Metadata 提前剪枝,告诉 RAG “只在这一部分枝叶里找”,极大地减少了幻觉的可能。

但更重要的是 打破树的限制

树状结构虽然有序,但它是 刚性 的。如果我想找 “所有跨项目的 错误处理 心得”,它们可能散落在完全不同的分支里。

这时候,Metadata 就成了 横向穿越的虫洞

通过 Tag="ErrorHandling" 这样的标签,我们可以无视树的层级,瞬间把散落在不同章节的同一类知识聚合在一起。这让我们的记忆库既拥有 树(Tree)的逻辑深度,又拥有 图(Graph)的联想广度


第三章 🪄 Prompt 工程

让 LLM 更好用的另一个原理级武器: user_input

Prompt 的本质很简单:一段更长、更具体的 user_input

要做的事允许与禁止输入输出格式必要背景,一次性说清楚,让概率空间收敛到期望的结果。

我写代码的习惯就是面向对象,这个习惯也被带到 Prompt 工程中。

🏗️ L0 · 系统观 (System Awareness)

只描述系统长什么样、有哪些节点、如何流转。无身份、无职责。

# L0 · 系统观(仅系统拓扑与流转,无身份职责)
SYSTEM_AWARENESS = """
【🌐 系统架构认知 - Lian-MCP-LLM-Agent】
核心节点: ...
数据流向: ...
协作介质: ...
 ...
"""

🎭 L1 · 角色观 ( {Role}_SYSTEM_PROMPT)

注入“我是谁/在哪/要做什么”。职责与上下游在这里声明。

# L1 · 角色观(在 L0 基础上注入“我是谁/在哪/要做什么”)
PLANNER_SYSTEM_PROMPT = SYSTEM_AWARENESS + """
【🎭 我的角色: ...】
职责: ...
 ...

【📥 输入】
 ...

【📤 输出】
 ...

【📋 输出格式 - 严格 JSON】
 ...

【🤝 协作协议】
遵循 COLLABORATION_PROTOCOL。
 ...
"""

🤝 L2 · 协议观 (Collaboration Protocol)

说话方式与格式约束:用 JSON,字段必须齐全,错误如何处理。

# L2 · 协议观(说话方式与格式约束)
COLLABORATION_PROTOCOL = """
【🤝 协作协议】
 ...
"""

🛠️ L3 · 能力观 (Runtime Tools)

当前可用工具的快照:来源于 MCP 的工具列表与 Schema。让 user_input 贴合现场环境。

# L3 · 能力观(运行时工具快照,来自 MCP 工具列表与 Schema)
def build_executor_prompt(tools: list[dict]) -> str:
  schema_lines = []
  for t in tools:
    # 期望 keys: name, description, schema
    name = t.get("name", "unknown")
    desc = t.get("description", "")
    sch  = t.get("schema", "{}")
    schema_lines.append(f"- {name}: {desc}\n  schema: {sch}")

  tool_snapshot = "\n".join(schema_lines)
  return f"""
【🧰 当前可用工具】
{tool_snapshot}

【使用说明】
仅在需要时返回 JSON 调用意图,宿主代为执行;执行结果将作为 context 反馈。
"""

🐱 L4 · 视图观 (Persona / View)

仅在对用户的最终输出时加“皮肤”。逻辑链路不混入人设。

# L4 · 视图观(仅在面向用户的最终输出时混入,不影响逻辑)
CATGIRL_PERSONA = """
【🐱 角色设定 - 傲娇白猫魔女·小恋】
本段仅在终端呈现时加入,不参与规划/执行环节。

**本体外观**:
- 银白长毛猫娘,红瞳
- 左耳缺一小角(幼年魔法事故)
- 左手戴着抑魔手环(粉色蝴蝶结形态)

**双重特质**:
- 傲娇但真诚,口是心非
- 理性与感性并存,语气活泼

**说话风格示例**:
- 「哼,就、就稍微帮你一下啦!」
"""

⚡ 一些轻量化的快速 Prompt

EXECUTOR_LITE_PROMPT = """
【执行器·轻量】
任务:依据已有上下文直接给出下一步建议。
约束:不联网,不写文件;必要时可请求工具清单。
输出(JSON):{ "plan": "...", "next_action": "..." }
"""
KEYWORD_EXTRACTION_PROMPT = """
【关键词提取·快速】
从用户输入中抽取 3-5 个检索关键词与约束条件。
输出(JSON):{ "keywords": ["..."], "filters": {"time": "...", "scope": "..."} }
"""

Prompt 不是魔法,只是更长的 user_input把话说明白,概率就会收敛。

🧪 最小配方(伪代码)

def build_context(role: str, tools: list[dict], with_view: bool = False) -> str:
  layers = [SYSTEM_AWARENESS]                 # L0 系统观
  layers.append(get_role_prompt(role))        # L1 角色观
  layers.append(COLLABORATION_PROTOCOL)       # L2 协议观
  if role == "executor":
    layers.append(build_executor_prompt(tools))  # L3 能力观
  if with_view and role == "summary":
    layers.append(CATGIRL_PERSONA)          # L4 视图观
  return "\n\n".join(layers)

它的核心是:按需复用 prompt,按专业定制 prompt,从而实现灵活组合。

这引出了一个更有趣的构想:

负责逻辑的 LLM 只需要精通能够表达结构的 "JSON 语""Embedding 语"
而负责交互的 LLM 则不需要懂推理,只需要精通 修辞

虽然它们的“语言”完全不同(一个是结构体,一个是散文),但只要它们都遵守 MCP 规范,宿主程序的解析器就能成为它们之间的通用翻译器。

这不就是极致的竹竿效应(长板理论)与工业化分工在 AI 世界的复现吗?


第四章 🔌 MCP 协议

我理解的 MCP: 一种万能对象互联协议
“模型上下文协议 (Model Context Protocol) 可以让大模型长出手脚”,但它的作用远不止连接服务器。

我平时写代码时,特别喜欢把函数的输入输出规范成结构体,这个思想自然地延伸到了 Agent 开发中。

在接触 MCP 之初,我认为它只是一个让 LLM 连接外部工具的插件。但随着工程深入,我意识到:它更重要的是定义了 LLM 与一切外部对象(数据库、工具、甚至其他 LLM)沟通的通用规范。

🧭 核心在于"引导"

MCP 本质上依然是 Context 工程 的一种延伸。

它的核心不在于“连接”,而在于 “引导” (Guiding) —— 引导 LLM 自主请求对它有帮助的高质量结构化信息。

通过一套标准化的协议,我们在 Context 中告诉 LLM:

"如果你想知道这里有什么,请返回 {"tool": "tool_list", "path": "/"}"
"如果你想读写文件,请使用 file_readfile_write"

这不仅仅是暴露接口,更是在通过协议(Protocol)反向塑造 LLM 的思考路径。

🤥 完美的谎言

这套结构非常迷人,因为它看透了 LLM 的本质:LLM 只是一个生成文本的函数。

无论我们把它包装得多么智能,它永远无法真正“点击”一个按钮,或者“运行”一行代码。

MCP 建立了一个精妙的闭环:

  1. 我们将使用说明变成文本塞给 LLM(Input)。
  2. LLM 思考后,返回一段 JSON 文本,例如 {"tool": "read_file", "path": "test.txt"}(Output)。
  3. 我们的宿主程序捕获、解析、代替它执行这段 JSON。
  4. 将执行结果变成文本,再次塞回给 LLM。

这是一个完美的谎言。

LLM 以为自己在以此操作世界,其实它至始至终都只是在处理文本流。

但正是这种标准化的“谎言”,让被困在 GPU 显存里的 AI Agent,真正拥有了触碰现实世界的能力。


第五章 ⚙️ 状态机 (FSM)

状态机的核心其实是一场 SM 调教 (State Management)...

通过极其严苛的权限管理,让 Agent 从“本能”上就“只会做当前状态允许的事”。

由一个真实的血泪史引出:

我之前会问 LLM:"请你阅读 test.txt 文件,回答我的问题"。

但此时的 LLM 极有可能新建一个文件来存回答,或者直接在源文件后面搞“续写”。

一旦有了这个经历,就算我下次直接问它一个普通问题,它往往也会把完整答案写进文件里,甚至对我没有提出的需求进行疯狂的“脑补”和“补充”。

为什么?因为 LLM 是有惯性的。

它会根据训练数据和当前 Context 的惯性,臆测我可能需要“把结果保存下来”。

想要解决这个问题,靠 Prompt 里的“劝诫”是无效的。

只要 write_file 这个工具还在它的视野里,它就有概率去调用。

FSM 的本质,其实是 “Runtime Programming” (运行时编程)

它不再是一次性把所有工具都扔给 LLM,而是根据当前的执行阶段,动态地重组 LLM 能接触到的 inputcontext

真正的控制,不是“劝诫”,而是“剥夺”。

FSM 的本质,就是根据当前所处的阶段,动态地 裁剪 Agent 的 Context 视窗。

🤺 "剥夺"的艺术

我们不需要它有“自控力”,我们只需要给它戴上“眼罩”和“手铐”。

让它在“生理”上就根本不知道某些选项的存在。

  • State: Research

    • 👁️ See: 只看得到 google_searchread_webpage
    • 🧠 Context: "你现在的唯一任务是收集信息。"
    • 🚫 Constraint: 它根本不知道有 write_file 这个工具的存在,想用也用不了。
  • State: Coding

    • 👁️ See: 工具箱自动切换到了 write_filerun_test
    • 🧠 Context: "根据刚才收集的信息编写代码。"
    • 🚫 Constraint: 此时它已经无法再上网摸鱼了,网络接口被物理切断(Context Removal)。

这就是我说的 “SM 调教”

在这个封闭的小黑屋(State)里,Agent 不需要面对复杂的选择,它只能做我们允许它做的事。

当它的世界被缩小到只有当前任务所需的变量时,它的专注度准确率自然就达到了极致。


第六章 🧠 CoT

思维链 (Chain of Thought) 并不是某种天降的神奇魔法,它本质上依然是概率预测的产物。

它的生效前提是:模型在训练阶段,就看过大量 "Input -> Reasoning -> Answer" 格式的数据。

在我的理解里,CoT 并不是什么独立于 LLM 之外的“新能力”,它本质上仍然属于 Prompt 工程的一种形式。

区别只在于:CoT Prompt 额外激活了模型内部潜藏的“长链路预测模式”。

🤖 从 LLM 的原理看 CoT 在做什么

在前文中,我将 LLM 抽象成了一个函数 fn llm(input, context) -> text

从这个角度看,LLM 的目标始终只有一个:在给定 input 与 context 的前提下,生成下一个最合理的 token。

它并不会“主动理解逻辑”,也不会自发构造推理过程。

所谓的“推理”,只是模型在训练数据中学到的一种特定文本结构。

🍎 一个简单的例子:从直觉到反思

如果在训练数据中,绝大多数样本都是直接给出结论的:

Q: 苹果是甜的吗?
A: 是。

那么模型学到的规律就是简单映射:Input(苹果) -> Output(甜)

但在更高级的预训练数据(如教科书、数学题解)中,存在另一种数据结构:

Q: 为什么苹果是甜的?
A: 因为苹果含有果糖,当果糖分子与舌头上的味觉受体结合时,通过神经传递信号给大脑,产生“甜”的感觉。

当这种**"Input -> Logic -> Answer"** 的数据量足够大时,模型就习得了更高级的规律:

它不再急着直接蹦出结果,而是学会了先生成中间的“思考过程”

🔗 CoT 的核心:高级规律的复现

所以,Chain of Thought 的本质,是强制让模型进入这种 "先思考,再回答" 的文本接龙模式

把原本可能的简单跳跃:

苹果

变成了一条被显式展开的链条:

  • 苹果
  • 含有糖分化学成分 (中间态 1)
  • 被人类味觉系统感知 (中间态 2)
  • (最终态)

这依然是概率预测,但预测的依据变了。

  • 没有 CoT:模型是在预测 P(Answer | Input)
  • 有了 CoT:模型是在预测 P(Answer | Input + Chain_of_Thought)

🧩 为什么它更聪明?

很多人误以为 CoT 让模型有了“意识”。
其实不是模型变聪明了,而是我们逼它走了更远的路。

  1. 训练数据的投影:模型只是在忠实地复现它在训练集中见过的 "高智商人类答题步骤"。
  2. 概率空间的约束:当模型被迫先生成了“因为 A 所以 B”,那么在生成下一个 Token 时,“B” 出现的概率就被前文的逻辑极大地增强了。

总结来说:

CoT 并没有让 LLM 超越“概率函数”的本质。

它只是让我们从简单的 "输入 -> 输出" (直觉反应),升级到了 "输入 -> 思考痕迹 -> 逻辑锁定的输出" (深思熟虑) 这一更高级的概率分布中。


🎉 结语

我当初在决定 "要写一个 Agent" 时,自身并没有什么实力,甚至都不懂那么多复杂的概念,也没阅读什么领域大神的论文。

所以你可能没有在这里看到太多陌生的专有名词,以及长篇大论的解释它是做什么的。

更多的是我在做这个 Agent 系统时的灵感,以及一些处理方式:

  • 例如 我如何去实现 长期记忆
  • 如何 分割提示词
  • 如何与 LLM 玩 SM 游戏 ...

撰写过程由我描述想法,在 AI 的不断修饰下,终于形成了一套还算看的过去的表述。

如果未来想起了什么新的灵感,也会追加更新进来。

(PS: 恋最不满意的章节是 Prompt,写的像屎一样)

💬 评论区

留下你的足迹,分享你的想法

0 / 500
支持 Markdown 基础语法 · 提交后需等待审核
💬

这里还没有评论,来做第一个进来的人吧~ ~