200 行 Python 代码,从零手搓极简 Agent,吃透智能体核心原理!
现在市面上关于AI Agent智能体的讨论越来越多但大多停留在概念层面自主思考、任务拆解、多智能体协同…… 听起来很高大上却很少有人告诉你AI Agent 的本质到底是什么对于开发者来说与其听一堆虚无缥缈的名词不如亲手写一个。今天我们不聊复杂理论不套 LangChain、LlamaIndex 等框架直接扒开 AI Agent 最底层的运行逻辑。仅用不到 200 行 Python 代码从零手搓一个完整可运行的极简 Agent逐行拆解核心模块新手复制粘贴就能直接跑通。最终你到手的这个 Agent具备四大核心能力短期记忆对话维护上下文让无状态的 LLM 记住当前对话内容。Function Calling 工具调用给 LLM 装上 『手脚』可读写文件、执行操作。智能上下文管理手动清空 / 压缩 自动压缩上下文彻底解决 token 溢出问题。长期记忆 极简 RAG跨会话记住用户偏好程序重启也不丢失语义检索只给模型最相关的记忆。吃透这套极简实现你就能轻松举一反三扩展技能Skill、多智能体Multi-Agent、自主规划等复杂能力本质都是在这套底层逻辑上扩展。Talk is cheap, show me the code文末附完整可运行 Python 源码1. 核心 LLM很多人对大模型有个核心误区觉得对话时它能记住上一句话是模型本身有记忆能力。但事实是所有大模型天生都是无状态的—— 你上一轮和它说过什么它本身根本不会留存。那我们平时用的对话机器人为什么能连贯对话答案非常简单所谓的「短期记忆」本质就是我们在代码里维护一个 messages 列表把每一轮的对话历史完整地传给大模型。再套一个while True循环让程序持续运行就是一个最简单的 Agent 雏形了。话不多说直接上代码fromopenaiimportOpenAI clientOpenAI(base_urlhttp://localhost:11434/v1/,api_keyollama-local)# 大家按需修改为自己的大模型MODELqwen3.5:4b# 对话主循环defchat_loop():# 维护对话上下文实现短期记忆messages[]print( 极简Agent启动成功输入/quit退出 )whileTrue:user_inputinput(\n你).strip()# 退出指令ifuser_input/quit:print(Agent再见)break# 加入用户输入到上下文messages.append({role:user,content:user_input})# 调用大模型responseclient.chat.completions.create(modelMODEL,messagesmessages)replyresponse.choices[0].message.content# 加入模型回复到上下文完成记忆闭环messages.append({role:assistant,content:reply})print(fAgent{reply})if__name____main__:chat_loop()2. Function Calling 工具调用LLM 只会动口具有极强的意图识别能力但不会动手没法读取本地文件、没法获取实时数据、没法执行代码…。Function Calling 就是给 LLM 装上手脚思路非常简单我们提前定义好能实现具体功能的工具函数读文件、写文件等把工具的「使用说明书」名称、功能、参数要求整理成大模型能理解的格式和用户指令一起传给 LLMLLM 收到需求后自主决策要不要调用工具调用哪个传什么参数代码收到 LLM 的调用指令后执行对应的工具函数拿到执行结果把执行结果再返回给 LLM让它基于真实结果生成最终回答。这里有个关键细节很多复杂需求大模型很可能需要连续调用多次工具。比如你让它「把本地 a.txt 的内容整理成 markdown 格式保存成 b.md」它就需要先调用「读文件」拿内容再调用「写文件」保存整理后的结果。所以我们要在主循环里再加一层内层的 ReAct 循环让它能反复调用工具直到不需要再执行操作再给你最终回复。1定义工具先给 Agent 写两个最基础的工具读文件、写文件这就是它能直接用的「手脚」。importjsondefread_file(file_path:str)-str:读取本地文件try:withopen(file_path,r,encodingutf-8)asf:returnf.read()exceptExceptionase:returnf读取失败{str(e)}defwrite_file(file_path:str,content:str)-str:写入本地文件try:withopen(file_path,w,encodingutf-8)asf:f.write(content)returnf写入成功{file_path}exceptExceptionase:returnf写入失败{str(e)}2给 LLM 的工具描述大模型不会凭空知道怎么调用工具我们需要把工具的信息整理成它能识别的标准格式同时做一个函数名到实际函数的映射方便后续执行。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}3加入内层工具循环我们修改之前的chat_loop函数加入内层循环支持多轮连续工具调用defchat_loop():print( 极简Agent启动成功输入/quit退出 )# 维护对话上下文实现短期记忆messages[]whileTrue:user_inputinput(\n你).strip()# 退出指令ifuser_input/quit:print(Agent再见)break# 加入用户输入到上下文messages.append({role:user,content:user_input})# 内层循环支持多轮工具调用whileTrue:# 调用大模型判断是否需要调用工具responseclient.chat.completions.create(modelMODEL,messagesmessages,toolsTOOLS,tool_choiceauto)messageresponse.choices[0].message# 如果有工具调用执行工具ifhasattr(message,tool_calls)andmessage.tool_calls:messages.append(message)# 遍历执行所有工具调用fortool_callinmessage.tool_calls:tool_nametool_call.function.name tool_argsjson.loads(tool_call.function.arguments)# 执行工具tool_resultTOOL_MAP[tool_name](**tool_args)# 把工具结果返回给LLM开启下一次循环messages.append({role:tool,tool_call_id:tool_call.id,name:tool_name,content:tool_result})print(fAgent调用工具{tool_name}({tool_args}) -{tool_result})else:# 无工具调用直接取回复replymessage.contentbreak# 加入模型回复到上下文完成记忆闭环messages.append({role:assistant,content:reply})print(fAgent{reply})3. 上下文管理随着对话轮次越来越多messages列表会越来越长很快就会撑爆大模型的上下文窗口导致 token 溢出、程序报错。为了彻底解决这个问题我们给 Agent 加上 3 个核心的上下文管理能力/clear手动清空所有短期记忆从头开始对话/compact手动压缩当前上下文只保留核心信息大幅减少 token 占用。自动压缩 对话长度超过阈值时自动触发压缩从根源避免溢出。1实现上下文压缩函数压缩的核心逻辑不是直接删掉历史而是让大模型把长对话历史压缩成一段精简摘要保留核心事实去掉冗余内容比如工具调用的过程细节既保留记忆又大幅减少 token 量。# 压缩上下文defcompact_context(messages:list):message_jsonjson.dumps(messages,ensure_asciiFalse)iflen(message_json)10000:returnmessages compact_promptf将以下对话历史压缩为精简摘要保留核心信息和关键事实去除冗余比如调用工具过程压缩后不能超过100个字符\n{message_json}compact_responseclient.chat.completions.create(modelMODEL,messages[{role:user,content:compact_prompt}])return[{role:assistant,content:f【历史对话摘要】{compact_response.choices[0].message.content}}]2把上下文管理能力集成到主循环我们在主循环里加入手动指令的判断同时加上自动压缩的触发逻辑# 对话主循环defchat_loop():print( 极简Agent启动成功输入/quit退出, 输入/compact压缩上下文, 输入/clear清空上下文 )# 维护对话上下文实现短期记忆messages[]whileTrue:user_inputinput(\n你).strip()ifuser_input/compact:messagescompact_context(messages)print(Agent上下文压缩完成)continueifuser_input/clear:messages[]print(Agent上下文已清空)continue# 调用大模型判断是否需要自动压缩上下文messagescompact_context(messages)4. 长期记忆到这里我们的 Agent 已经有了短期记忆、能干活、还能避免 token 溢出但它的记忆还是「一次性」的 一旦关掉程序重启之后所有对话记忆就全清零了。我们想要的是一个能真正「记住你」的 Agent你的名字、习惯、偏好、定的规则哪怕重启程序它也依然记得。这就是长期记忆。最直接的想法是把所有记忆都存到本地文件每次启动都加载进来塞给大模型。但这个方法有个致命问题记忆会越来越多很快就撑爆上下文窗口而且大部分记忆和当前问题无关塞给模型反而会干扰判断。所以我们需要一个更聪明的方案用向量检索RAG实现长期记忆只把和当前用户问题最相关的记忆召回给大模型既不浪费 token又能精准匹配需求。我们的实现方案也很简单封装两个工具函数 —— 保存记忆、检索记忆让大模型可以自主调用。1实现长期记忆的工具我们用轻量级向量化模型实现语义检索用 json 文件做持久化存储。fromsentence_transformersimportSentenceTransformer,utilimportnumpyasnp# 长期记忆向量库LONG_MEMORY_DBlong_memory_db.json# 向量化模型轻量级CPU可跑embedding_modelSentenceTransformer(all-MiniLM-L6-v2)# 加载记忆库defload_memory_db():try:withopen(LONG_MEMORY_DB,r,encodingutf-8)asf:returnjson.load(f)except:return{docs:[],embeddings:[]}# 保存记忆库defsave_memory_db(data):withopen(LONG_MEMORY_DB,w,encodingutf-8)asf:json.dump(data,f,ensure_asciiFalse,indent2)# 工具1保存长期记忆LLM自主调用defsave_long_memory(content:str)-str:dataload_memory_db()data[docs].append(content)data[embeddings].append(embedding_model.encode(content).astype(np.float32).tolist())save_memory_db(data)returnf✅ 记忆已保存{content[:30]}...# 工具2检索相关记忆LLM自主调用defquery_long_memory(query:str,top_k2)-str:dataload_memory_db()docsdata[docs]embsdata[embeddings]ifnotdocs:return暂无长期记忆q_embembedding_model.encode(query).astype(np.float32)db_embsnp.array(embs,dtypenp.float32)scoresnp.dot(q_emb,db_embs.T)/(np.linalg.norm(q_emb)*np.linalg.norm(db_embs,axis1)1e-8)print(scores)top_idxnp.argsort(scores)[::-1][:top_k]memories[docs[i]foriintop_idxifscores[i]0.5]ifnotmemories:return未找到相关记忆return 相关记忆\n\n.join(memories)2将记忆工具注册到 Agent 中我们把这两个记忆工具加入到之前的TOOLS和TOOL_MAP里让大模型可以自主调用# 工具注册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}到这里你的 Agent 就真正拥有了「长期记忆」能力。你可以和它说「我叫一枫以后你都叫我枫哥」它会自动保存记忆哪怕你重启程序再问它「我叫什么」它也会自动检索记忆准确回应你。5. 能力扩展当你理解这套底层逻辑后会发现几乎所有高级 Agent 能力都可以基于「工具模式」 扩展Q如何实现 Skill技能系统A读取项目下所有 skill 文件的名称和元信息封装成工具描述传给大模型让大模型自主判断是否需要加载对应技能。如果需要直接通过文件路径用我们已经实现的读文件工具就能加载技能的完整内容。Q如何实现 Multi-Agent多智能体A把我们当前这个极简 Agent 直接封装成一个工具父 Agent 需要时调用子 Agent执行完成后回收结果即可。把代码里 “顺序调用工具” 的逻辑改成 “并行工具调用”就能实现多智能体并行执行。6. 总结到这里我们没有用任何复杂的重型框架只用了不到 200 行的核心代码就实现了一个完整可用、能落地干活的 AI Agent它具备以下核心要素短期记忆、工具调用、上下文管理、长期记忆 RAG。而所谓的 AI Agent从来都不是什么玄之又玄的黑科技它的本质其实就是三句话用大模型做大脑决策用 messages 列表做记忆存储用工具函数做执行手脚用循环做持续思考。所有你能看到的复杂 Agent 方案不管是 LangChain、AutoGPT还是各种各样的多智能体框架本质都是在这套最核心的逻辑上做了封装、扩展和优化。我已经把完整的、可直接运行的源码整理好了关注我的公众号【一枫说码】后台回复【Agent 源码】就能直接拿到完整代码复制粘贴就能跑通。你还想给 Agent 加什么能力欢迎在评论区留言讨论。