200 行 Python 代码,从零手搓极简 Agent,吃透智能体核心原理!
<p><img src="https://img2024.cnblogs.com/blog/2525618/202604/2525618-20260403192328005-151597377.png"></p><p>现在市面上关于 <strong><em>AI Agent(智能体</em>)</strong> 的讨论越来越多,但大多停留在概念层面:自主思考、任务拆解、多智能体协同…… 听起来很高大上,却很少有人告诉你:<strong>AI Agent 的本质,到底是什么?</strong></p>
<p>对于开发者来说,与其听一堆虚无缥缈的名词,不如亲手写一个。</p>
<p><strong>今天我们不聊复杂理论,不套 LangChain、LlamaIndex 等框架,直接扒开 AI Agent 最底层的运行逻辑。</strong></p>
<p>仅用不到 200 行 Python 代码,从零手搓一个完整可运行的极简 Agent,逐行拆解核心模块,新手复制粘贴就能直接跑通。</p>
<p>最终你到手的这个 Agent,具备四大核心能力:</p>
<ol>
<li><strong>短期记忆对话</strong>:维护上下文,让无状态的 LLM 记住当前对话内容。</li>
<li><strong>Function Calling 工具调用</strong>:给 LLM 装上 『手脚』,可读写文件、执行操作。</li>
<li><strong>智能上下文管理</strong>:手动清空 / 压缩 + 自动压缩上下文,彻底解决 token 溢出问题。</li>
<li><strong>长期记忆 + 极简 RAG</strong>:跨会话记住用户偏好,程序重启也不丢失,语义检索只给模型最相关的记忆。</li>
</ol>
<p><img src="https://img2024.cnblogs.com/blog/2525618/202604/2525618-20260403192341345-130913102.png"></p>
<p>吃透这套极简实现,你就能轻松举一反三:扩展技能(Skill)、多智能体(Multi-Agent)、自主规划等复杂能力,本质都是在这套底层逻辑上扩展。</p>
<p><em><strong>Talk is cheap, show me the code!</strong></em> 文末附完整可运行 Python 源码!</p>
<h1 id="1-核心-llm">1. 核心 LLM</h1>
<p>很多人对大模型有个核心误区:觉得对话时它能记住上一句话,是模型本身有记忆能力。</p>
<p>但事实是,<strong>所有大模型天生都是无状态的</strong>—— 你上一轮和它说过什么,它本身根本不会留存。</p>
<p>那我们平时用的对话机器人,为什么能连贯对话?</p>
<p>答案非常简单:<strong>所谓的「短期记忆」,本质就是我们在代码里维护一个 messages 列表,把每一轮的对话历史,完整地传给大模型。</strong></p>
<p>再套一个<code>while True</code>循环让程序持续运行,就是一个最简单的 Agent 雏形了。话不多说,直接上代码:</p>
<pre><code class="language-python">from openai import OpenAI
client = OpenAI(base_url='http://localhost:11434/v1/', api_key='ollama-local')# 大家按需修改为自己的大模型
MODEL ="qwen3.5:4b"
# 对话主循环
def chat_loop():
# 维护对话上下文,实现短期记忆
messages = []
print("=== 极简Agent启动成功,输入/quit退出 ===")
while True:
user_input = input("\n你:").strip()
# 退出指令
if user_input == "/quit":
print("Agent:再见!")
break
# 加入用户输入到上下文
messages.append({"role": "user", "content": user_input})
# 调用大模型
response = client.chat.completions.create(model=MODEL, messages=messages)
reply = response.choices.message.content
# 加入模型回复到上下文,完成记忆闭环
messages.append({"role": "assistant", "content": reply})
print(f"Agent:{reply}")
if __name__ == "__main__":
chat_loop()
</code></pre>
<h1 id="2-function-calling--工具调用">2. Function Calling工具调用</h1>
<p><strong>LLM 只会动口(具有极强的意图识别能力),但不会动手(没法读取本地文件、没法获取实时数据、没法执行代码...)。</strong></p>
<p>Function Calling 就是给 LLM 装上手脚,思路非常简单:</p>
<ol>
<li>我们提前定义好能实现具体功能的工具函数(读文件、写文件等);</li>
<li>把工具的「使用说明书」(名称、功能、参数要求),整理成大模型能理解的格式,和用户指令一起传给 LLM;</li>
<li>LLM 收到需求后,自主决策:要不要调用工具?调用哪个?传什么参数?</li>
<li>代码收到 LLM 的调用指令后,执行对应的工具函数,拿到执行结果;</li>
<li>把执行结果再返回给 LLM,让它基于真实结果生成最终回答。</li>
</ol>
<p><img src="https://img2024.cnblogs.com/blog/2525618/202603/2525618-20260315224733327-1327608060.png"></p>
<p>这里有个关键细节:很多复杂需求,大模型很可能需要连续调用多次工具。</p>
<p>比如你让它「把本地 a.txt 的内容整理成 markdown 格式,保存成 b.md」,它就需要先调用「读文件」拿内容,再调用「写文件」保存整理后的结果。</p>
<p>所以,我们要在主循环里,再加一层<strong>内层的 ReAct 循环</strong>,让它能反复调用工具,直到不需要再执行操作,再给你最终回复。</p>
<h2 id="1定义工具">(1)定义工具</h2>
<p>先给 Agent 写两个最基础的工具:读文件、写文件,这就是它能直接用的「手脚」。</p>
<pre><code class="language-python">import json
def read_file(file_path: str) -> str:
"""读取本地文件"""
try:
with open(file_path, "r", encoding="utf-8") as f:
return f.read()
except Exception as e:
return f"读取失败:{str(e)}"
def write_file(file_path: str, content: str) -> str:
"""写入本地文件"""
try:
with open(file_path, "w", encoding="utf-8") as f:
f.write(content)
return f"写入成功:{file_path}"
except Exception as e:
return f"写入失败:{str(e)}"
</code></pre>
<h2 id="2给-llm-的工具描述">(2)给 LLM 的工具描述</h2>
<p>大模型不会凭空知道怎么调用工具,我们需要把工具的信息,整理成它能识别的标准格式,同时做一个函数名到实际函数的映射,方便后续执行。</p>
<pre><code class="language-python">TOOLS = [
{
"type": "function",
"function": {
"name": "read_file",
"description": "读取本地文件内容",
"parameters": {"type": "object", "properties": {"file_path": {"type": "string", "description": "要读取的文件路径"}}, "required": ["file_path"]}
}
},
{
"type": "function",
"function": {
"name": "write_file",
"description": "写入内容到本地文件",
"parameters": {"type": "object", "properties": {"file_path": {"type": "string", "description": "要写入的文件路径"}, "content": {"type": "string", "description": "要写入的内容"}}, "required": ["file_path", "content"]}
}
}
]
# 工具执行映射
TOOL_MAP = {
"read_file": read_file,
"write_file": write_file
}
</code></pre>
<h2 id="3加入内层工具循环">(3)加入内层工具循环</h2>
<p>我们修改之前的<code>chat_loop</code>函数,加入内层循环,支持多轮连续工具调用:</p>
<pre><code class="language-python">def chat_loop():
print("=== 极简Agent启动成功,输入/quit退出 ===")
# 维护对话上下文,实现短期记忆
messages = []
while True:
user_input = input("\n你:").strip()
# 退出指令
if user_input == "/quit":
print("Agent:再见!")
break
# 加入用户输入到上下文
messages.append({"role": "user", "content": user_input})
# 🔥 内层循环:支持多轮工具调用
while True:
# 调用大模型,判断是否需要调用工具
response = client.chat.completions.create(model=MODEL, messages=messages, tools=TOOLS, tool_choice="auto")
message = response.choices.message
# 如果有工具调用,执行工具
if hasattr(message, 'tool_calls') and message.tool_calls:
messages.append(message)
# 遍历执行所有工具调用
for tool_call in message.tool_calls:
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
# 执行工具
tool_result = TOOL_MAP(**tool_args)
# 把工具结果返回给LLM,开启下一次循环
messages.append({"role": "tool", "tool_call_id": tool_call.id, "name": tool_name, "content": tool_result})
print(f"Agent:调用工具{tool_name}({tool_args}) -> {tool_result}")
else:
# 无工具调用,直接取回复
reply = message.content
break
# 加入模型回复到上下文,完成记忆闭环
messages.append({"role": "assistant", "content": reply})
print(f"Agent:{reply}")
</code></pre>
<h1 id="3-上下文管理">3. 上下文管理</h1>
<p>随着对话轮次越来越多,<code>messages</code>列表会越来越长,很快就会撑爆大模型的上下文窗口,导致 token 溢出、程序报错。</p>
<p>为了彻底解决这个问题,我们给 Agent 加上 3 个核心的上下文管理能力:</p>
<ol>
<li><code>/clear</code>:手动清空所有短期记忆,从头开始对话</li>
<li><code>/compact</code>:手动压缩当前上下文,只保留核心信息,大幅减少 token 占用。</li>
<li>自动压缩 :对话长度超过阈值时,自动触发压缩,从根源避免溢出。</li>
</ol>
<h2 id="1实现上下文压缩函数">(1)实现上下文压缩函数</h2>
<p>压缩的核心逻辑不是直接删掉历史,而是让大模型把长对话历史,压缩成一段精简摘要,保留核心事实,去掉冗余内容(比如工具调用的过程细节),既保留记忆,又大幅减少 token 量。</p>
<pre><code class="language-python"># 压缩上下文
def compact_context(messages : list):
message_json = json.dumps(messages, ensure_ascii=False)
if len(message_json) <= 10000:
return messages
compact_prompt = f"将以下对话历史压缩为精简摘要,保留核心信息和关键事实,去除冗余(比如调用工具过程),压缩后不能超过100个字符:\n{message_json}"
compact_response = client.chat.completions.create(model=MODEL, messages=[{"role": "user", "content": compact_prompt}])
return [{"role": "assistant", "content": f"【历史对话摘要】{compact_response.choices.message.content}"}]
</code></pre>
<h2 id="2把上下文管理能力集成到主循环">(2)把上下文管理能力集成到主循环</h2>
<p>我们在主循环里加入手动指令的判断,同时加上自动压缩的触发逻辑:</p>
<pre><code class="language-python"># 对话主循环
def chat_loop():
print("=== 极简Agent启动成功,输入/quit退出, 输入/compact压缩上下文, 输入/clear清空上下文 ===")
# 维护对话上下文,实现短期记忆
messages = []
while True:
user_input = input("\n你:").strip()
if user_input == "/compact":
messages = compact_context(messages)
print("Agent:上下文压缩完成!")
continue
if user_input == "/clear":
messages = []
print("Agent:上下文已清空!")
continue
# 调用大模型,判断是否需要自动压缩上下文
messages = compact_context(messages)
</code></pre>
<h1 id="4-长期记忆">4. 长期记忆</h1>
<p>到这里,我们的 Agent 已经有了短期记忆、能干活、还能避免 token 溢出,但它的记忆还是「一次性」的 ,一旦关掉程序,重启之后所有对话记忆就全清零了。</p>
<p>我们想要的,是一个能真正「记住你」的 Agent:你的名字、习惯、偏好、定的规则,哪怕重启程序,它也依然记得。这就是<strong>长期记忆</strong>。</p>
<p>最直接的想法,是把所有记忆都存到本地文件,每次启动都加载进来塞给大模型。但这个方法有个致命问题:记忆会越来越多,很快就撑爆上下文窗口,而且大部分记忆和当前问题无关,塞给模型反而会干扰判断。</p>
<p>所以,我们需要一个更聪明的方案:<strong>用向量检索(RAG)实现长期记忆,只把和当前用户问题最相关的记忆,召回给大模型,既不浪费 token,又能精准匹配需求。</strong></p>
<p>我们的实现方案也很简单:封装两个工具函数 —— 保存记忆、检索记忆,让大模型可以自主调用。</p>
<h2 id="1实现长期记忆的工具">(1)实现长期记忆的工具</h2>
<p>我们用轻量级向量化模型实现语义检索,用 json 文件做持久化存储。</p>
<pre><code class="language-python">from sentence_transformers import SentenceTransformer, util
import numpy as np
# 长期记忆向量库
LONG_MEMORY_DB = "long_memory_db.json"
# 向量化模型(轻量级,CPU可跑)
embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
# 加载记忆库
def load_memory_db():
try:
with open(LONG_MEMORY_DB, "r", encoding="utf-8") as f:
return json.load(f)
except:
return {"docs": [], "embeddings": []}
# 保存记忆库
def save_memory_db(data):
with open(LONG_MEMORY_DB, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# 工具1:保存长期记忆(LLM自主调用)
def save_long_memory(content: str) -> str:
data = load_memory_db()
data["docs"].append(content)
data["embeddings"].append(embedding_model.encode(content).astype(np.float32).tolist())
save_memory_db(data)
return f"✅ 记忆已保存:{content[:30]}..."
# 工具2:检索相关记忆(LLM自主调用)
def query_long_memory(query: str, top_k=2) -> str:
data = load_memory_db()
docs = data["docs"]
embs = data["embeddings"]
if not docs:
return "暂无长期记忆"
q_emb = embedding_model.encode(query).astype(np.float32)
db_embs = np.array(embs, dtype=np.float32)
scores = np.dot(q_emb, db_embs.T) / (np.linalg.norm(q_emb) * np.linalg.norm(db_embs, axis=1) + 1e-8)
print(scores)
top_idx = np.argsort(scores)[::-1][:top_k]
memories = for i in top_idx if scores > 0.5]
if not memories:
return "未找到相关记忆"
return "📌 相关记忆:\n" + "\n".join(memories)
</code></pre>
<h2 id="2将记忆工具注册到-agent-中">(2)将记忆工具注册到 Agent 中</h2>
<p>我们把这两个记忆工具,加入到之前的<code>TOOLS</code>和<code>TOOL_MAP</code>里,让大模型可以自主调用:</p>
<pre><code class="language-python"># 工具注册:LLM需要的函数描述格式
TOOLS = [
...
{
"type": "function",
"function": {
"name": "save_long_memory",
"description": "当用户提到重要偏好、习惯、规则、秘密等需要长期记住的内容时,自动保存",
"parameters": {
"type": "object",
"properties": {"content": {"type": "string", "description": "要记住的内容"}},
"required": ["content"]
}
}
},
{
"type": "function",
"function": {
"name": "query_long_memory",
"description": "当回答用户问题需要历史偏好、习惯、规则时,先检索记忆",
"parameters": {
"type": "object",
"properties": {"query": {"type": "string", "description": "检索关键词"}},
"required": ["query"]
}
}
}
]
# 工具执行映射
TOOL_MAP = {
...
"save_long_memory": save_long_memory,
"query_long_memory": query_long_memory
}
</code></pre>
<p>到这里,你的 Agent 就真正拥有了「长期记忆」能力。你可以和它说「我叫一枫,以后你都叫我枫哥」,它会自动保存记忆;哪怕你重启程序,再问它「我叫什么」,它也会自动检索记忆,准确回应你。</p>
<h1 id="5-能力扩展">5. 能力扩展</h1>
<p>当你理解这套底层逻辑后,会发现<strong>几乎所有高级 Agent 能力,都可以基于「工具模式」 扩展</strong>:</p>
<p><u>Q:如何实现 Skill(技能)系统?</u></p>
<p>A:读取项目下所有 skill 文件的名称和元信息,封装成工具描述传给大模型,让大模型自主判断是否需要加载对应技能。如果需要,直接通过文件路径,用我们已经实现的读文件工具,就能加载技能的完整内容。</p>
<p><u>Q:如何实现 Multi-Agent(多智能体)?</u></p>
<p>A:把我们当前这个极简 Agent 直接封装成一个工具,父 Agent 需要时调用子 Agent,执行完成后回收结果即可。把代码里 “顺序调用工具” 的逻辑改成 “并行工具调用”,就能实现多智能体并行执行。</p>
<h1 id="6-总结">6. 总结</h1>
<p>到这里,我们没有用任何复杂的重型框架,只用了不到 200 行的核心代码,就实现了一个完整可用、能落地干活的 AI Agent,它具备以下核心要素:短期记忆、工具调用、上下文管理、长期记忆 RAG。</p>
<p>而所谓的 AI Agent,从来都不是什么玄之又玄的黑科技,它的本质,其实就是三句话:</p>
<p><strong>用大模型做大脑决策,用 messages 列表做记忆存储,用工具函数做执行手脚,用循环做持续思考。</strong></p>
<p>所有你能看到的复杂 Agent 方案,不管是 LangChain、AutoGPT,还是各种各样的多智能体框架,本质都是在这套最核心的逻辑上,做了封装、扩展和优化。</p>
<blockquote>
<p>我已经把完整的、可直接运行的源码整理好了,关注我的公众号【一枫说码】,后台回复【Agent 源码】,就能直接拿到完整代码,复制粘贴就能跑通。</p>
</blockquote>
<p>你还想给 Agent 加什么能力?欢迎在评论区留言讨论。</p><br><br>
来源:https://www.cnblogs.com/yifeng-coding/p/19818672
頁:
[1]