AI / ML / DL / 神经网络 —— 这些词到底什么关系?
这四个词被混着用了几十年,但它们是同心圆关系:外圈最大,内圈最具体。
1.1 一张图记住四者关系
从外到内,每一层都是上一层的子集:
- AI(人工智能) —— 最大的一圈。所有让机器表现出智能行为的方法都算,包括 1970 年代的专家系统、围棋穷举搜索、规则引擎。"会下棋"和"会聊天"都属于 AI。
- ML(机器学习 / Machine Learning) —— AI 的子集。不靠人写规则,而是让机器从数据里"自己学规律"。比如垃圾邮件分类、信用卡欺诈检测,都属于 ML。
- DL(深度学习 / Deep Learning) —— ML 的子集。用多层神经网络做学习。"深度"指的是层数多(几十到几百层),不是"理解得深"。
- 神经网络(Neural Network) —— DL 的具体实现工具。一组用矩阵乘法堆出来的数学结构,灵感来自生物神经元,但其实跟生物脑差得远。
类比: AI 是"医学",ML 是"内科",DL 是"心血管科",神经网络是"心导管手术这门具体技术"。聊天的时候随便用,但讨论实现时要分清。
1.2 LLM 在哪一层?
LLM(Large Language Model)属于深度学习这一层,用的是一种叫 Transformer 的神经网络结构(2017 年 Google 那篇 Attention Is All You Need)。"大"指的是参数量大 —— 从几十亿到上万亿不等。
1.3 一个常见误解:AI ≠ 神经网络
很多人以为 AI 就是神经网络,但 AlphaGo 的核心其实是蒙特卡洛树搜索(一种搜索算法)+ 神经网络辅助评估;自动驾驶是 感知(神经网络)+ 规划(搜索/优化)+ 控制(控制论) 的拼盘;很多金融风控模型至今还在用梯度提升树(GBDT)而不是神经网络。神经网络很强,但不是 AI 的全部。
LLM 是怎么工作的 —— Token、上下文、概率采样
如果你只看一段,看这段:LLM 的本质是「给定前面的文字,猜下一个 token 是什么」的概率机器。仅此而已。它的所有"聪明"都从这一个简单动作里涌现出来。
2.1 Token 是什么 —— 文字的最小单位
LLM 不是按"字"或"词"思考的,而是按 token。一个 token 大概是:
- 英文里 ≈ 0.75 个单词。比如
understanding可能被切成under+stand+ing三个 token。 - 中文里 ≈ 1.5 个汉字。"机器学习" 可能是 1 ~ 2 个 token。
- 标点符号、空格、换行也都是 token。
为什么要切成 token?因为词汇表必须有限(一般是 5 万 ~ 20 万),不可能为世界上每个单词都分配一个编号,否则模型存不下。Token 是"够用又不太多"的折中。
2.2 上下文窗口 —— LLM 能"看到"多少
每次对话,你输入的内容 + 系统提示 + 历史聊天记录全部加起来不能超过上下文窗口(context window)。这个长度按模型而定:
- GPT-3.5 早期:4K tokens(约 3000 汉字)
- GPT-4 / Claude 3:128K ~ 200K tokens(一本短篇小说)
- Gemini 1.5 / Claude 4:1M ~ 2M tokens(《红楼梦》全本还有富余)
超出窗口怎么办?模型看不到。聊天软件会自动截断或摘要历史消息塞回去 —— 这就是为什么长聊天到后面,AI 会"忘"了你十几条之前说过的话。
2.3 概率采样 —— 它不是在"想",是在"掷骰子"
当 LLM 看完输入后,它内部会对词汇表里所有 token 算一个概率分布:下一个 token 是 苹果 的概率 18%、香蕉 12%、桃子 7%……然后从这个分布里采样一个出来。所谓"温度(temperature)"参数就是控制采样的随机性 —— 温度 0 时永远选概率最高的(确定输出),温度高时偶尔选低概率的(创意输出)。
所以:同一个 prompt 多次提问,答案可能不一样,不是"它今天心情不好",而是采样真的有随机性。如果你想要稳定输出,把 temperature 调到 0。
2.4 自回归 —— 它一次只吐一个 token
LLM 不是"一口气写完",而是一个 token 一个 token 地往外吐,每吐一个就把它接到输入末尾,再去预测下一个。这就是你看到 ChatGPT "一个字一个字往外蹦"的视觉效果由来 —— 这不是动画特效,是模型真的在那么干。
模型是怎么炼成的 —— 三阶段训练
现代 LLM 几乎都遵循"三阶段"配方:先吞下半个互联网,再被人类老师"调教",最后被人类反复"打分"调味。
3.1 阶段 1:Pretraining(预训练) —— 让它"读完世界"
把整个互联网(书、网页、代码、Wikipedia、Reddit、论文…)切成 token 喂给模型,目标只有一个:给定前 N 个 token,预测第 N+1 个 token。这个阶段:
- 耗时几个月、烧几千块到几万块 H100 GPU、电费几千万到上亿美元。
- 产物是一个「续写器」:你给一段话,它能很自然地续下去,但不会"对话",也不会听指令。
- 训练完后的模型叫 Base Model(基座模型)。比如 LLaMA-3-Base、Qwen-Base。
3.2 阶段 2:SFT(Supervised Fine-Tuning,监督微调)—— 教它"听话"
人工标注一批(指令, 理想回答)的样本(一般几万到几十万条),让模型在这些数据上继续训练。目标是让它学会"接到指令就给答案",而不是傻乎乎地续写。比如:
- 输入:用三句话解释什么是 RAG。
- 理想输出:RAG 是检索增强生成……(人工写好)
SFT 之后,模型变成 Instruct Model(指令模型),开始有"对话感"。
3.3 阶段 3:RLHF / DPO —— 教它"喜欢人类喜欢的"
RLHF(Reinforcement Learning from Human Feedback,基于人类反馈的强化学习):让模型对同一个 prompt 生成几个回答,让人类标注员选出"哪个更好",再用这些偏好数据去微调模型。这个阶段做的事情是:
- 让模型回答更有帮助(helpful)
- 让模型更无害(harmless)—— 拒绝危险请求
- 让模型更诚实(honest)—— 不知道就说不知道
DPO(Direct Preference Optimization)是 2023 年后流行的简化方案,效果接近 RLHF 但不需要训练单独的奖励模型,工程上更省事。
关键认知:"GPT-4 好用"不只是因为它大,更因为 RLHF/DPO 阶段被人类反复调教过。一个没经过这一步的纯基座模型即使更大,使用体验也会很差 —— 它会答非所问、会拒绝得莫名其妙、会胡乱发挥。
Prompt 工程 —— 跟 AI 说话的技术
同一个模型,会不会用差距巨大。Prompt 工程就是研究"怎么说能让模型给出最好的答案"。
4.1 三种基本角色:System / User / Assistant
大部分对话型 LLM 的输入由三种角色构成:
- System Prompt(系统提示):在对话最开头,定义模型的身份、风格、规则。比如"你是一个只用中文回答的法律顾问,绝不给具体诉讼建议。"这是产品经理塑造 AI 人格的主战场。
- User:用户输入。
- Assistant:模型自己输出,下一轮会作为历史回填。
4.2 Few-shot Prompting —— 给它几个例子
不要只说"请把英文翻成中文",而是给它几个例子:
例1:Hello → 你好
例2:Good morning → 早上好
例3:See you later → ?
有了例子,模型对你想要的格式、风格、详略都心里有数了。这是最便宜、最不需要技巧的提升方法。
4.3 Chain of Thought(CoT,思维链)—— 让它「想出声来」
对于复杂推理任务(数学、逻辑、多步规划),加一句 "Let's think step by step" 或 "请一步步推理",模型会先把推理过程写出来再给答案。准确率往往能提升 20%+。
2024 年之后的推理模型(o1、DeepSeek R1、Claude Thinking)把这个机制内置了 —— 它在内部就先「想」很久才输出答案,你不再需要手动加 CoT。
4.4 几个反直觉的小技巧
- 明确说"不要做什么",往往不如说"应该做什么"。大模型对"否定"的处理常常翻车。
- 用 Markdown 结构化你的 prompt。用标题、列表、分隔符(
---)把"任务""上下文""输出格式"分开,比塞成一大段强得多。 - 把"输出格式"写成 JSON schema,模型遵循度比中文描述好。
- 对模型「说好话」有时确实管用。"这个任务对我很重要,请认真完成" 这种 prompt 在某些榜单上确实显示出小幅提升 —— 不是它真有感情,是训练数据里被认真对待的指令也常常带这种语气。
幻觉是什么 —— 为什么它一本正经地胡说八道
幻觉(Hallucination):LLM 自信满满地编造出一个不存在的事实、不存在的论文、不存在的 API、不存在的人物。这不是 bug,这是它的工作机制决定的。
5.1 为什么会幻觉?
回到第 2 章那句话:LLM 是「预测下一个 token」的概率机器。它没有"真假"概念,只有"下一个最自然的 token 是什么"。当你问它一个它没见过的问题(比如某个冷门论文的作者),它不会说"我不知道",它会按"作者名字看起来应该长这样"去合成一个答案。结果就是:引用一篇根本不存在的论文,作者名字是真人组合,DOI 是编的。
5.2 哪些情况最容易幻觉?
- 具体数字、日期、引用 —— 越精确的事实,幻觉概率越高
- 近期的事 —— 训练数据有截止日期,模型对截止日期之后的事会瞎编
- 小众领域 —— 训练数据里出现频次少的,记得就模糊
- 追问到非常深的细节 —— 浅层概念它知道,但你一直追问 N 层之后,它开始 freestyle
5.3 怎么办?
不能消除,但可以缓解:
- RAG(下一节讲)—— 让它先查资料再回答
- 工具调用(搜索、计算器、数据库)—— 让它"做"而不是"猜"
- 引用要求 —— prompt 明确要求"每个事实都要给出来源 URL"
- 降低温度 —— 减少随机性
- 关键事实人类复核 —— 这是底线
认知校准:不要把 LLM 当成搜索引擎用,它给的具体引用 80% 都需要核实。它擅长的是"理解 / 改写 / 推理 / 创作",不是"记住事实"。
RAG vs Fine-tune —— 让模型「懂」你的两条路
想让 LLM 知道你公司的内部资料,有两条路:把资料"喂给它看"(RAG)或"教它记住"(Fine-tune)。9 成场景该选第一条。
6.1 RAG —— Retrieval-Augmented Generation(检索增强生成)
工作流程:
- 把你的资料切成小块,每块算一个向量(embedding),存进向量数据库。
- 用户提问时,把问题也变成向量,去库里搜语义最近的几块。
- 把搜到的几块原文 + 用户的问题一起塞给 LLM,让它基于这些资料回答。
优点:实时(资料更新立即生效)、便宜(不用训练)、可控(看得见用了哪些资料)。
缺点:检索到无关内容会污染答案;切块策略不好会丢失上下文;多跳推理(要综合多个文档)效果一般。
6.2 Fine-tune —— 微调
把你的资料组织成(问, 答)对,在基座模型上继续训练。优点是模型"内化"了知识,不需要每次都检索;缺点是:
- 贵 —— 即便用 LoRA 等高效方案也得几千到几万人民币起步
- 慢 —— 资料更新就得重新训练
- 容易"灾难性遗忘"原本的能力
- 调出来的模型只能是你的专属版本,不能用最新的 GPT-4o
6.3 怎么选?
| 需求 | 推荐 |
|---|---|
| 让模型回答时引用公司知识库 | RAG |
| 资料更新频繁(每天/每周) | RAG |
| 要让模型学会一种特殊"风格"或"领域语言" | Fine-tune |
| 极致延迟敏感(每次省下检索一跳) | Fine-tune |
| 不知道选哪个 | RAG(先用着,需要再升级) |
2026 年的实践趋势:长上下文 + RAG + Karpathy 风格的 LLM Wiki 越来越主流;纯微调正在退场,只有少数特别明确的场景还在用。
什么是 Agent —— 它和 Chatbot 不是一回事
如果一个东西只会聊天,它是 Chatbot;如果它能看任务 → 自己分步 → 调工具 → 反思 → 拿结果,它才是 Agent。
7.1 一句话定义
Agent = LLM + 工具 + 记忆 + 循环 + 目标。
分别意味着:
- LLM:大脑,做判断、写代码、解析意图。
- 工具:手脚,能读文件、调 API、跑命令、搜网页。
- 记忆:上下文 + 长期存储,记得任务进展和过去发生了什么。
- 循环:不是"问一次答一次",而是反复调用 LLM直到任务完成或失败。
- 目标:一个明确的成功标准("修好这个 bug"、"订到符合条件的机票")。
7.2 ReAct 循环 —— Agent 的核心运行模式
2022 年提出的 ReAct(Reason + Act)模式被几乎所有 Agent 框架沿用:
循环 {
Thought(思考):现在该做什么?
Action(行动):调用某个工具
Observation(观察):拿到工具结果
}
直到觉得 done 为止 → Final Answer
Cursor、Claude Code、Devin、AutoGPT —— 内核都是这个循环,区别只在工具集、记忆策略、控制流的复杂度。
7.3 Chatbot vs Agent —— 一张表说清
| Chatbot | Agent | |
|---|---|---|
| 输出 | 一段文字 | 一系列动作 + 最终结果 |
| 调用次数 | 每轮 1 次 LLM | 一个任务可能调用 LLM 几十到几百次 |
| 有无副作用 | 无(只生成文字) | 有(写文件、发邮件、改数据库) |
| 错了怎么办 | 用户重新问 | 自己反思、重试、求助 |
| 典型代表 | ChatGPT 网页版 | Claude Code、Cursor、Devin、Manus |
Tool Use / Function Calling —— Agent 的「手」
LLM 自己只会输出文字。要让它"做事",必须给它一套工具,并教它什么时候该调哪个工具。这就是 Tool Use。
8.1 一个工具长什么样
工具就是一个函数,需要告诉 LLM 三件事:
- 名字:
search_web - 描述:"用关键词搜索互联网,返回最相关的 10 条标题和摘要"
- 参数 schema:
{ query: string, top_k?: number }
当用户提问"今天上海天气",LLM 看到自己有 search_web 这个工具,会输出一个特殊格式的"调用请求":
{
"tool": "search_web",
"args": { "query": "上海今天天气" }
}
Agent 框架接到这个请求后,真的去执行这个函数,把结果("23-28℃,多云")回填给 LLM,LLM 再用人话总结给用户。
8.2 Function Calling 是什么
OpenAI 在 2023 年 6 月把这套机制官方化,叫 Function Calling。意思是:模型在训练时就被教会"看到工具描述,按 JSON 格式输出调用请求",准确率比让你在 prompt 里手动教高得多。后来所有主流 LLM(Claude、Gemini、Qwen、DeepSeek)都内置了这个能力。
8.3 几种常见工具类型
- 检索类:搜网页、查文档、问数据库
- 执行类:跑 shell 命令、执行代码、操作浏览器
- 读写类:读文件、写文件、改代码、提交 git
- 外部服务类:发邮件、订机票、调企业内部 API
- 计算类:调用真正的计算器(LLM 自己算 17 位数加法会算错)
8.4 Tool Use 让幻觉问题减轻一半
很多原本会"幻觉"的问题,给它工具就解决了:
- 问最新新闻 → 给它
search_web - 问精确算账 → 给它
execute_python - 问内部资料 → 给它
query_knowledge_base
这是 2023 年之后 Agent 真正能"干活"的关键转折 —— 工具让模型从"凭记忆瞎说"变成"动手验证"。
Memory —— Agent 的短期与长期记忆
LLM 本身没有记忆 —— 它每次推理都是"从零开始"。Agent 想做长任务,就必须自己造一套记忆系统。
9.1 短期记忆 —— Context 里的「便签」
当前对话/任务的所有信息(system prompt、历史消息、最近调用的工具结果)都塞在 LLM 的上下文窗口里,这就是短期记忆。它的特点:
- 速度极快(就是模型输入)
- 容量有限(窗口大小约束)
- 任务一结束就清空
当对话变长、token 用爆窗口时,Agent 会做压缩 / 摘要 / 滑窗:把老消息摘要成一段,丢掉细节,只保留要点。这就是为什么长聊天里 AI 会"忘"前面的细节。
9.2 长期记忆 —— 跨会话的「档案柜」
真正想"记得用户、记得过往任务",需要把信息持久化到外部存储。常见做法:
- 键值对存储:用户偏好、个人档案。"用户喜欢简洁回答" → 下次对话时塞进 system prompt。
- 向量数据库:把过往对话/任务结果向量化存起来,需要时按语义检索。
- 结构化数据库:日程、任务清单、订单记录这种有 schema 的数据,老老实实进 SQL/NoSQL。
- 文件系统 / Markdown 知识库:Karpathy 提倡的 LLM Wiki 路线 —— 把记忆做成结构化 Markdown 文件夹,Agent 像程序员读代码一样读它。
9.3 记忆策略 —— 工程上最难的部分之一
"什么该记"、"什么该忘"、"什么时候该回忆"是 Agent 工程里最有讲究的设计点。错误的记忆策略会导致:
- 记得太少 → Agent 像"金鱼",每次都从头开始
- 记得太多 → 上下文爆炸 + 召回噪音 + 成本飙升
- 记得错的 → 把过去的错误事实当"真实"反复使用
Multi-Agent —— 一群 Agent 协作
单个 Agent 很强,但有个上限:上下文不够长、思路容易跑偏、专长不够。Multi-Agent 就是让多个 Agent 分工协作。
10.1 一个最经典的 Multi-Agent 模式
用户 → 总管 Agent(拆解任务)
├── 调研 Agent(搜资料)
├── 编码 Agent(写代码)
├── 审核 Agent(找 bug)
└── 汇总 Agent(生成报告)
每个 Agent 有自己的独立 system prompt、独立工具、独立上下文。它们之间通过消息传递协作,像一个有分工的团队。
10.2 几种主流模式
- Manager-Worker:一个总管派活给多个 worker(最常见,也叫 supervisor 模式)
- Pipeline:流水线,一个做完交下一个(适合明确分阶段的任务)
- Debate:两个 Agent 互相挑刺,在辩论中找到更好答案
- Society:一群 Agent 自由通信,模拟"小镇"或"公司"(如斯坦福小镇实验)
2025 年开源生态里 三省六部 Edict(基于 OpenClaw)就是中文圈一个有意思的尝试:把"太子分拣 / 三省规划审核派发 / 六部执行"这套帝国制度做成 12 个 Agent 协作框架。
10.3 Multi-Agent 不是银弹
很多场景单 Agent + 长上下文反而更稳:
- 多 Agent 之间消息传递会丢失上下文细节
- 每个 Agent 都要钱,整体成本几倍翻
- 调试比单 Agent 难得多 —— 出错时不知道是哪一环
Anthropic 在 2024 年的研究显示:很多被宣称需要 Multi-Agent 的任务,单 Agent + Tool Use 就能做得更好。所以非必要不上 Multi-Agent。
Agent Harness —— 让模型真能干活的「外壳」
这是这个博客的核心主题。一句话:Harness 就是 Agent = LLM + 工具 + 记忆 + 循环 + 目标 等式右边除了 LLM 之外的所有东西。
11.1 为什么需要 Harness 这个词
2024 年中之后,业界发现一件事:同一个 LLM,套上不同 harness,能力差距巨大。
同样是 Claude 3.5 Sonnet:
- 裸 API 调用 → 一个聊天工具
- 装进 Cursor → 一个 IDE 助手
- 装进 Claude Code → 一个能写完整项目的工程师
- 装进 Computer Use → 一个能操作电脑的"虚拟员工"
差别不在模型,在外壳。Anthropic 工程师有一句话:"If you're not the model, you're the harness." —— 你做不了 LLM,那就好好做 harness。
11.2 Harness 的"八根支柱"
本博客的 Agent Harness 章节会逐根展开:
- 身份与权限(Identity) —— 这个 Agent 是"谁",能用谁的身份做事
- 沙箱与运行时(Sandbox) —— 它在哪里干活,怎么不污染主机
- 工具系统(Tools) —— 它能做什么动作,调用接口长什么样
- 技能与策略(Skills) —— 复合能力的封装与复用
- 记忆系统(Memory) —— 它"记得"什么,怎么记
- 编排与控制流(Orchestration) —— 任务怎么拆、怎么调度、怎么重试
- 观测与评测(Observability & Eval) —— 怎么知道它干得好不好
- 安全与对齐(Safety) —— 怎么防它失控、防它被骗、防它被注入恶意指令
11.3 一个直观比喻
LLM 是赛车手 —— 反应快、判断准、爱踩油门。
但你不能让他光着膀子站在马路上跑。
Harness 就是赛车 + 赛道 + 计时器 + 维修组:
赛车(沙箱)让他在受控环境里加速;
赛道(编排)告诉他往哪开;
计时器(观测)记录每一圈的成绩;
维修组(工具)在他需要的时候递扳手。
没有这套 harness,再好的车手也只是个会跑步的人。
MCP 协议 —— Agent 时代的「USB-C」
2024 年底 Anthropic 抛出 Model Context Protocol(MCP),2025 年成为行业默认。一句话:它是一份"工具与 LLM 之间怎么对话"的统一规范,让 Agent 接工具像电脑接外设一样即插即用。
12.1 MCP 之前的乱象
每家 LLM 厂商有自己的 function calling 格式:OpenAI 用一套,Anthropic 用一套,Google 又一套。每接入一个新工具就要写胶水代码、调试格式、踩兼容坑。生态分裂。
12.2 MCP 的核心抽象
MCP 把世界分成两边:
- MCP Server:包装一类资源 —— 你的文件系统、你的数据库、你的 GitHub、你的 Notion、你的浏览器…… 每个 Server 用一份 JSON schema 暴露自己的能力。
- MCP Client / Host:跑 LLM 的那一端 —— Claude Desktop、Cursor、Cline、各种 Agent 框架。它们连上 Server 后,自动就能让 LLM 调用 Server 的工具。
用户的视角变成:装一个新 MCP Server,AI 就立刻多了一项能力,不用改代码、不用换框架。
12.3 为什么这件事很重要
MCP 之于 Agent,相当于USB-C 之于硬件:
- 统一接口 → 工具开发者写一次到处能用
- 用户能装能卸 → 工具变成"可热插拔"的能力
- 催生了一个 MCP Server 生态 —— 现在 GitHub 上几千个 MCP Server,覆盖几乎所有常用工具
2026 年的 Agent 框架(OpenClaw、各种 IDE 助手)几乎都原生支持 MCP,"接什么工具"这件事,从工程师写代码变成了用户点几下安装。
动手路径 —— 从读到做的 6 个起点
看完这 12 章,你已经有了完整的"地图"。现在该选一个口子真正动手了。下面是一条可操作的入门路径,按"动手成本从低到高"排序。
13.1 起点 ① —— 把 ChatGPT/Claude 用得真的好(0 代码)
大部分人甚至连这一步都没做扎实。具体功课:
- 系统学一遍 prompt 工程的 7 种基本套路(角色、Few-shot、CoT、结构化输出、自我审查、约束输出、上下文裁剪)
- 买一个会员(GPT Plus / Claude Pro 二选一),坚持用 1 个月 → 形成肌肉记忆
- 把生活中至少 5 件事固定用 AI 处理:邮件、会议纪要、阅读总结、代码 review、复杂决策的"反方陈述"
13.2 起点 ② —— 试一个 CLI Agent(小代码)
装一个 Claude Code 或 Cursor 或 Cline,给它一个真实的小项目(不是 hello world,而是真要解决的事)。比如:"帮我把这堆图片按拍摄日期分类"。亲手观察它怎么调用工具、怎么自我修正 —— 这是理解 Agent 最快的方法。
13.3 起点 ③ —— 装几个 MCP Server(中等代码)
给 Claude Desktop 装上 filesystem、github、web-search 三个 MCP Server。然后让它做一些"以前要打开 5 个软件才能做的事"。亲手感受 Tool Use 把 LLM 从"聊天"变成"干活"的那个分水岭。
13.4 起点 ④ —— 在 OpenAI / Anthropic SDK 里裸写一个 Agent(200 行代码)
不要用任何框架。用最朴素的方式实现 ReAct 循环:
- 定义两个工具:
search_web、read_url - 写一个 while 循环:调 LLM → 解析 tool call → 执行 → 把结果回填 → 再调 LLM
- 给它一个任务:"帮我整理 2025 年 AI 重要事件"
这 200 行代码会让你彻底理解"Agent 不是魔法"。看懂这个,你看任何开源 Agent 框架都不怵。
下面给一份可以直接复制到 IDE 跑通的完整示例。三步走:
- 装依赖(三个,都是 Python 生态最常见的库):
pip install openai httpx beautifulsoup4 - 把你的 OpenAI API Key 设进环境变量(去 platform.openai.com/api-keys 申请):
export OPENAI_API_KEY="sk-..." # macOS / Linux set OPENAI_API_KEY=sk-... # Windows CMD $env:OPENAI_API_KEY="sk-..." # Windows PowerShell - 把下面整段保存为
tiny_agent.py,然后python tiny_agent.py:
# =============================================================
# tiny_agent.py —— 200 行裸写一个 Agent,不依赖任何 Agent 框架
#
# 核心思路:所谓 Agent,就是一个 while 循环:
# 1. 把"用户任务 + 历史对话"喂给 LLM
# 2. LLM 要么直接回答(循环结束),要么说"我想调某个工具"
# 3. 我们本地执行那个工具,把结果塞回历史
# 4. 回到第 1 步,直到 LLM 不再要求调工具
#
# 就这么多,没了。看懂这 200 行,你就理解所有 Agent 框架了。
# =============================================================
import os
import json
import httpx # 用来发 HTTP 请求(比 requests 更现代)
from bs4 import BeautifulSoup # 用来从 HTML 里抽正文
from openai import OpenAI # OpenAI 官方 SDK
from urllib.parse import quote_plus
# 创建 OpenAI 客户端,会自动读 OPENAI_API_KEY 环境变量
client = OpenAI()
# 选用的模型。gpt-4o-mini 便宜够用;要更强可换 "gpt-4o" 或 "gpt-4.1"
MODEL = "gpt-4o-mini"
# =============================================================
# 第一部分:定义两个"工具"(其实就是两个普通 Python 函数)
# =============================================================
def search_web(query: str) -> str:
"""
用 DuckDuckGo 的 HTML 端点做搜索(不需要 API key,免费)。
返回前 5 条结果的"标题 + 链接 + 摘要",每条之间用空行分隔。
"""
# DuckDuckGo 的"轻量版"搜索页,专门给爬虫用,HTML 很干净
url = f"https://html.duckduckgo.com/html/?q={quote_plus(query)}"
headers = {"User-Agent": "Mozilla/5.0 (tiny-agent demo)"}
html = httpx.get(url, headers=headers, timeout=15).text
soup = BeautifulSoup(html, "html.parser")
results = []
# DuckDuckGo HTML 版的结果块都是 .result 这个 class
for item in soup.select(".result")[:5]:
title_el = item.select_one(".result__a")
snippet_el = item.select_one(".result__snippet")
if not title_el:
continue
title = title_el.get_text(strip=True)
link = title_el.get("href", "")
snippet = snippet_el.get_text(strip=True) if snippet_el else ""
results.append(f"- {title}\n {link}\n {snippet}")
if not results:
return "没有搜到相关结果,请换个关键词试试。"
return "\n\n".join(results)
def read_url(url: str) -> str:
"""
抓取一个网页,去掉 HTML 标签,返回纯文本。
为了不把 LLM 撑爆,只返回前 4000 个字符(够它判断要不要继续读)。
"""
headers = {"User-Agent": "Mozilla/5.0 (tiny-agent demo)"}
try:
html = httpx.get(url, headers=headers, timeout=15, follow_redirects=True).text
except Exception as e:
return f"抓取失败:{e}"
soup = BeautifulSoup(html, "html.parser")
# 把 script / style 标签整个删掉,避免噪音
for tag in soup(["script", "style", "noscript"]):
tag.decompose()
text = soup.get_text("\n", strip=True)
# 折叠过多的空行
lines = [ln for ln in text.split("\n") if ln.strip()]
text = "\n".join(lines)
return text[:4000]
# =============================================================
# 第二部分:把工具"描述"给 LLM
#
# LLM 不会自动知道我们写了什么函数,必须用一段 JSON Schema 告诉它:
# - 这个工具叫什么名字
# - 它干什么用
# - 它要哪些参数、每个参数是什么类型
# LLM 会根据这段描述,自己决定何时、用什么参数调它。
# =============================================================
TOOLS = [
{
"type": "function",
"function": {
"name": "search_web",
"description": "在互联网上搜索信息,返回相关网页的标题、链接和摘要。当你需要查找新闻、事件、资料时使用。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词,用自然语言或英文都可以"
}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "read_url",
"description": "打开一个网页 URL,读取它的正文内容。当 search_web 给了你链接,需要看具体内容时使用。",
"parameters": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "要打开的网页地址,必须是完整的 http:// 或 https:// 开头的 URL"
}
},
"required": ["url"]
}
}
}
]
# 工具名 -> 真实函数 的映射,方便循环里按名字找
TOOL_IMPL = {
"search_web": search_web,
"read_url": read_url,
}
# =============================================================
# 第三部分:ReAct 循环(整个 Agent 的核心,就这十几行)
# =============================================================
def run_agent(user_task: str, max_steps: int = 10) -> str:
"""
跑一个 Agent,直到它回答完任务或达到最大步数。
"""
# messages 是对话历史,第一条是 system prompt(给 Agent 立人设)
messages = [
{
"role": "system",
"content": (
"你是一个善于搜索和总结的研究助手。"
"拿到任务后,先用 search_web 找资料,必要时用 read_url 读具体网页,"
"最后给出有条理的中文总结。"
"不要编造来源,所有结论都要基于搜到的内容。"
)
},
{"role": "user", "content": user_task},
]
for step in range(1, max_steps + 1):
print(f"\n========== 第 {step} 轮 ==========")
# ===== 调 LLM =====
resp = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=TOOLS, # 把工具列表传进去
tool_choice="auto", # 让模型自己决定要不要调工具
)
msg = resp.choices[0].message
# ===== 情况 A:LLM 没要求调工具,说明它给出最终答案了 =====
if not msg.tool_calls:
print("【LLM 给出最终答案】")
return msg.content or ""
# ===== 情况 B:LLM 要求调一个或多个工具 =====
# 先把 LLM 这条"我要调工具"的消息加到历史里(必须)
messages.append(msg)
# 依次执行每个工具调用
for tool_call in msg.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f"【LLM 想调用工具】{name}({args})")
# 在本地真的把函数跑起来
try:
result = TOOL_IMPL[name](**args)
except Exception as e:
result = f"工具执行出错:{e}"
# 打印一下结果的前 200 字,方便你观察 Agent 在干什么
print(f"【工具返回】{result[:200]}{'...' if len(result) > 200 else ''}")
# 把工具结果回填到历史里,role 必须是 "tool",且要带上 tool_call_id
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
# 进入下一轮:把"工具结果"再喂给 LLM,让它决定下一步
return "达到最大步数,仍未给出最终答案。"
# =============================================================
# 入口:跑一个示例任务
# =============================================================
if __name__ == "__main__":
task = "帮我整理 2025 年 AI 领域的 3 个重要事件,每个写一句话即可。"
answer = run_agent(task)
print("\n\n========== 最终答案 ==========")
print(answer)
↑ 把它保存为 tiny_agent.py,跑起来你会在控制台看到 Agent 一轮一轮的思考过程:先搜了什么、点开了哪个链接、最后怎么总结。整个文件去掉注释只有不到 100 行真实代码。
看完这段代码,至少有 3 个"原来如此"的瞬间:
- 工具不是魔法 —— 它就是两个普通 Python 函数,加一段 JSON 描述。LLM 看到描述后,自己决定何时调用、传什么参数。
- "Agent 在思考"是个错觉 —— 实际上每一轮都是一次独立的 LLM 调用。所谓"记忆",是因为我们每次都把整个对话历史重新喂回去。
- 循环退出条件极其朴素 —— 模型这次没要求调工具,就说明它觉得"够了,可以回答了"。没有什么复杂的"决策状态机"。
所有开源 Agent 框架(LangChain、AutoGen、CrewAI、OpenHands…)本质上都是在这个循环外面包了更多东西:错误重试、并行 tool call、记忆压缩、子 Agent、流式输出、可观测性…… 但核心永远是这十几行 while 循环。
13.5 起点 ⑤ —— 试一个真正的 Harness 框架
装 OpenClaw 或类似的本地 Agent 框架,跑通它的 Skills 系统。看一个真正的 production-grade harness 是怎么把"沙箱、记忆、工具、编排、观测"组织起来的。这一步会让你看到第 11 章那"八根支柱"在代码里长什么样。
13.6 起点 ⑥ —— 读完本博客 Agent Harness 章节
这是给愿意走深的人准备的。Agent Harness 章节有 6 篇长文,把上述八根支柱全部展开,重点放在企业级视角 —— 怎么让 Agent 跨系统、长链路、安全可控地工作。这是"业余玩具"和"生产可用 Agent"之间的那道坎。
13.7 模型怎么选 —— 2026 年中现状
| 场景 | 推荐 |
|---|---|
| 日常聊天 / 写作 / 阅读总结 | ChatGPT、Claude(任选)、Kimi |
| 写代码 / 做 Agent 大脑 | Claude(编码最强)、GPT、DeepSeek |
| 复杂推理 / 数学 / 科研 | OpenAI o-系列、DeepSeek R1、Claude Thinking |
| 本地部署(隐私 / 离线) | LLaMA、Qwen、DeepSeek(蒸馏版) |
| 中文场景 / 国内合规 | Qwen、DeepSeek、Kimi、豆包 |
| 不确定选啥 | Claude(综合体验最稳) |
13.8 一句送给小白的话
不要等"完全学会"再动手。这个领域 6 个月就换一代,知识半衰期极短。边用边读边写,能用起来比能讲清楚重要 10 倍。这篇文章的目标不是让你"懂 AI",是让你知道哪些词不需要查、哪些事不再神秘、下一步该看什么。剩下的,去用。