资讯动态

手写Agent核心:从零搭建可运行的AI Agent系统

发布时间:2026/9/20 17:59:53 来源:尧图企业网站定制
简介面向软件开发者和AI学习者的一套可运行Agent系统源码包提供从零搭建Agent系统的完整实践涵盖系统从离线版向联机版升级、AI搜索、报告生成与笔记自动记录等典型场景适合希望结合RAG与Agent做自动化工作流的Python开发者。压缩包共9个文件核心为4个Python功能模块对应研究者、编辑者和笔记记录者角色另含依赖清单、示例配置及Markdown说明文档可快速复现环境并了解项目结构整体仅15KB非常轻量。已有148人学习内容围绕Researcher、Editor、Note Taker三个角色分工Researcher负责调用搜索工具搜集信息Editor据此生成报告Note Taker则自动整理归档知识演示了一套完整的人机协作流程其中搜索工具与笔记工具的集成方式尤为值得参考。通过阅读源码和配套说明可以理解Agent系统从离线版升级到联机版的关键路径掌握RAG检索增强生成在具体项目中的落地方法同时获得一套可直接运行的Python脚本作为二次开发的基础。 我做了近两年的Agent项目从最早用LangChain拼积木到后来自己从零写核心调度再到梳理出一套能直接上生产的轻量框架中间踩过的坑比写过的代码还多。最近经常有朋友问想自己搭一个Agent系统到底该从哪下手网上的教程要么是概念满天飞、要么是框架封装太黑盒照着抄完还是一脸懵。这篇就把我实际跑通的一套方案完整拆开包含可运行的源码思路和核心代码不讲虚的直接照着搭就能动。如果你正准备入坑Agent开发或者已经被各种Agent框架绕晕了这篇内容应该能帮你把“Agent到底是个什么东西、它内部是怎么转起来的、我自己怎么快速搞一个能跑的”这三件事一次理清楚。无论你是后端工程师、算法工程师还是刚接触大模型应用开发的学生只要会一点Python基础就能跟着把系统跑起来。1. 整体设计思路先搞明白Agent系统在解决什么问题1.1 为什么我不建议你直接套框架市面上Agent框架一大堆LangChain、AutoGen、CrewAI、MetaGPT……随便一个都能在两小时内拼出个Demo。但我的真实感受是框架用多了反而更容易迷失。框架帮你把调度、记忆、工具调用都封装好了你写的只是业务逻辑一旦出问题根本不知道是哪一层在作怪。我建议的路线是先用最少的依赖自己手写一个能跑的最小Agent核心吃透里面的循环机制、工具注册、上下文管理这三个关键点然后再去用框架你会有一种“原来框架底层就是这样”的通透感。我实际采用的技术栈非常朴素Python 3.9requests库调大模型API没有装任何Agent框架。整个系统核心就是一个推理循环模型根据当前对话状态决定“该调用哪个工具→工具返回结果→模型继续推理→直到给出最终答案”。这个循环你在任何框架里都能找到影子但自己写一遍理解深度完全不一样。1.2 一个Agent系统的核心构成我在设计这套系统时只保留了四个最核心的模块Agent Core调度中枢维护整个推理循环负责把用户请求、工具返回结果、历史记忆拼装成提示词再交给大模型拿到输出后做决策。Tool Registry工具注册中心以字典形式维护所有工具的定义和实现模型通过名称索引调用新增工具只需注册不改核心代码。Memory记忆模块管理多轮对话的上下文窗口既要保留关键信息又不能超出模型上下文长度限制。Executor执行器真正去运行工具代码的模块处理工具抛出的异常防止单次工具失败导致整个Agent挂掉。这四块我理解为Agent的“四肢和大脑”Core是大脑Registry是工具箱Memory是工作台Executor是手。1.3 为什么这套方案能“可运行”网上不少Agent教程贴出来的代码其实是伪代码或者依赖一大堆需要翻文档才能配好的外部服务照着抄根本跑不起来。我在设计这套源码时制定了三条硬性标准第一依赖极简。除了调用大模型API用到requests其余全部用Python标准库。这样你只需要把API Key配上装一个requests就能跑。第二抽象完整、实现精简。我不搞几十个类和层层继承每个模块就一个核心类方法数控制在几个以内。你读代码的时间成本和理解成本都低。第三内置可替换的模拟工具。如果你暂时没有大模型API我也在源码里留了一个Mock模式用规则匹配返回预设结果先把Agent的流程跑通后续再接入真实模型。2. 核心概念拆解Agent和普通程序的区别到底在哪2.1 Agent的本质从“写死逻辑”到“模型自主决策”普通程序是“你告诉我做什么我就按代码逻辑执行”。Agent则不同它不再依赖开发者把每个分支都写死而是把“该做什么”的决策权交给了大模型。模型根据用户目标自己决定调用哪个工具、按什么顺序调用、如何组合结果。我常用一个外卖骑手的类比来说明这个区别。传统程序像骑手只认固定路线导航怎么规划他就怎么走Agent像骑手有一个聪明的调度大脑路况变了会临时改道、多个订单会自己排序优化路线。这个“调度大脑”就是大模型而“执行动作的能力”就来自工具调用。这套系统的核心价值就体现在你把二十个工具交给Agent它能根据用户的一句话自主组合出一条解决问题的路径。这种“自主规划-分步执行-动态调整”的能力就是Agent区别于普通脚本的灵魂所在。2.2 Tool机制Agent的“手”和“脚”没有工具调用的Agent只是个聊天机器人一旦接上工具它才真正拥有改变世界的能力。我在Torch Registry里每个工具都包含两部分工具定义告诉模型这个工具叫什么、是干嘛的、参数格式是什么和工具实现模型决定调用后真正执行的代码。工具定义这步极其关键。模型是靠你的描述来决定何时调用工具的描述不清晰模型就会胡乱调用或者该调不调。比如你设计一个天气查询工具定义就写清楚城市参数需要是中文全称返回的是实时温度加天气现象。目的就是让模型没有任何歧义地区分工具边界。我踩过一个典型的坑早期设计工具时有个工具叫“date_query”描述写的是“查询当前日期和时间”但没说明返回格式。结果模型调用后把返回值直接当成答案给了用户压根不知道其实返回的是JSON结构。后来我把工具返回结果统一转成字符串并在系统提示词里强调“工具返回的是JSON字符串你需要解析后组织语言回复用户”这个幺蛾子就再没出过。2.3 系统提示词Agent的“人设和规则”系统提示词System Prompt在Agent系统里比在普通聊天应用里重要得多。因为它不仅要定义人设还要告诉模型你有哪些工具可用、什么情况下必须调用工具、工具返回了结果该怎么处理、什么情况下该结束并给出最终回答。我写的系统提示词里有一段固定的“行动准则”当你需要实时信息时调用查询工具当你需要执行计算时调用计算工具当你认为仅凭已有知识就能回答时直接回复用户。这段规则持续有效地降低了模型的“幻觉式工具调用”——也就是用户问个常识性问题模型也非要去调一下工具的空转行为。3. 实操搭建过程完整源码核心模块逐行拆解3.1 环境准备和基础配置先把最基础的环境准备好。我用的是Python 3.10安装好requests库就够了。然后把API密钥配置到环境变量里我习惯放在项目根目录下的.env文件中用python-dotenv读取避免密钥硬编码进源码。pip install requests python-dotenv项目目录结构我设计得非常清晰每个文件职责单一agent_project/ ├── main.py # 程序入口交互式对话 ├── agent.py # Agent核心类推理循环 ├── tools.py # 工具定义和实现 ├── prompts.py # 系统提示词配置 ├── memory.py # 简单上下文管理 └── .env # API密钥配置3.2 Agent核心类推理循环的实现这是整套系统的心脏我会完整写出来代码量不大但每行都关键。import json import requests from tools import TOOL_MAP, TOOL_SCHEMAS class Agent: def __init__(self, api_key, base_url, model_name, system_prompt, max_steps10, memory_size12): self.api_key api_key self.base_url base_url self.model_name model_name self.system_prompt system_prompt self.max_steps max_steps self.memory [] self.memory_size memory_size def chat(self, user_input): self.memory.append({role: user, content: user_input}) for step in range(self.max_steps): response, tool_calls self._call_model() if tool_calls: for call in tool_calls: self._execute_tool(call) continue self.memory.append({role: assistant, content: response}) return response return 已达最大执行步数任务终止。 def _call_model(self): messages [{role: system, content: self.system_prompt}] self.memory[-self.memory_size:] payload { model: self.model_name, messages: messages, tools: TOOL_SCHEMAS, tool_choice: auto, } headers {Authorization: fBearer {self.api_key}} resp requests.post(f{self.base_url}/chat/completions, jsonpayload, headersheaders, timeout60) resp.raise_for_status() msg resp.json()[choices][0][message] return msg.get(content, ), msg.get(tool_calls, []) def _execute_tool(self, call): name call[function][name] args json.loads(call[function][arguments] or {}) try: result TOOL_MAP[name](**args) content json.dumps(result, ensure_asciiFalse) except Exception as e: content json.dumps({error: str(e)}, ensure_asciiFalse) self.memory.append({ role: assistant, content: None, tool_calls: [{id: call[id], type: function, function: {name: name, arguments: call[function][arguments]}}], }) self.memory.append({role: tool, tool_call_id: call[id], content: content})这里面的关键动作是模型返回tool_calls时我不直接给用户回复而是把工具调用信息和结果都追加到记忆里然后进入下一轮循环。直到模型输出纯文本content时才作为最终答案返回。3.3 工具注册让Agent“能干活”在工具的tools.py里我定义了两个简单但实用的工具一个查时间一个做四则运算。这两个工具虽小但足以完整演示工具定义、参数校验、结果返回的全流程。from datetime import datetime import json TOOL_SCHEMAS [ { type: function, function: { name: get_current_time, description: 获取当前日期和时间精确到秒, parameters: {type: object, properties: {}, required: []}, }, }, { type: function, function: { name: calculate, description: 执行四则运算表达式例如 (12 34) * 5, parameters: { type: object, properties: { expression: {type: string, description: 数学表达式} }, required: [expression], }, }, }, ] def get_current_time(): return {time: datetime.now().strftime(%Y-%m-%d %H:%M:%S)} def calculate(expression: str ): # 仅允许数字、运算符、括号和空格避免注入攻击 allowed set(0123456789-*/(). ) if not set(expression).issubset(allowed): return {error: 表达式包含非法字符} try: result eval(expression) # 已验证字符白名单风险可控 return {result: result} except Exception as e: return {error: f计算失败: {e}} TOOL_MAP { get_current_time: get_current_time, calculate: calculate, }新增一个工具只需要三步写实现函数、把schema加进TOOL_SCHEMAS、映射进TOOL_MAP。后续想接入天气API、数据库查询按这个模式照葫芦画瓢就行。3.4 主程序跑起来看效果main.py只负责交互把用户输入传给Agent并打印回复import os from dotenv import load_dotenv from agent import Agent from prompts import SYSTEM_PROMPT load_dotenv() agent Agent( api_keyos.getenv(API_KEY), base_urlos.getenv(BASE_URL), model_nameos.getenv(MODEL_NAME), system_promptSYSTEM_PROMPT, ) if __name__ __main__: print(Agent已启动输入exit退出) while True: user_input input(你: ) if user_input.lower() exit: break reply agent.chat(user_input) print(fAgent: {reply})启动后你可以依次测试这类对话先问“现在几点了”模型会直接调用get_current_time再问“(123 456) * 2 等于多少”模型调到calculate然后把计算结果组织成一句自然语言回复。整个链路完整走一遍Agent的核心机制就通透了大半。4. 常见问题与排查技巧4.1 API连通性问题报错信息五花八门但九成问题出在三个地方base_url拼错、API Key没传对、模型名不支持。我的建议是先别急着跑完整Agent先用一个最朴素的requests代码直接调一下chat接口确认单轮对话通得再说。注意看base_url是否以/v1结尾很多服务商的URL格式有差异如果你配的是官方兼容地址记得核对completions路径。4.2 工具调用一直失败或返回空结果如果你发现模型的tool_calls永远为空、或者调用参数老是缺字段先把TOOL_SCHEMAS打印出来仔细比对格式。不同模型对tools参数的兼容程度不一样有些能力弱的模型你给它传tools它压根不理会只能退化成普通对话这时就要考虑换个能力更强的新版模型。还有一个隐蔽问题工具执行报错时如果直接把异常信息塞给模型模型可能会陷入循环反复调用同一个出错工具。我在_execute_tool里统一把error信息包成JSON结构同时在系统提示词里写了一句“工具返回error时如实告知用户工具执行失败”这样模型就不会死磕了。4.3 上下文爆炸和记忆丢失我现在这套实现用了memory_size限制只保留最近12条消息。这样做的代价是如果中间的某次工具调用结果被挤掉了模型后面的推理可能会失去上下文。更完善的做法是做一个摘要记忆每轮结束后把关键信息压缩成一段话存进长期记忆我实现的是一个简化版但思路就是短期保留细节长期保留摘要。4.4 死循环问题模型偶尔会陷入“调用工具→看结果→再调用同一个工具”的死循环。我的max_steps10就是兜底方案到了就强制终止。还有一个我在实践中发现的技巧系统提示词里加一条“如果你发现工具返回结果已经满足用户需求立即停止并生成最终回复”循环率能下降一半以上。5. 一些我踩过的值得说的坑最后分享几个我在这个项目里最想吐槽的坑。第一个就是JSON解析的容错。我用real-world模型测试时发现部分模型生成的arguments有尾逗号或者单引号直接用json.loads会抛异常我后来加了replace尾逗号的预处理。第二个是工具结果格式不统一有的工具返回纯字符串有的返回dict模型处理起来很容易飘我在Executor里统一转成了字符串才缓解。还有一点做Agent功能测试时别只测“正确答案”路径一定要故意给工具传错参数看Agent能不能优雅地告诉用户“我调用工具失败了”。这决定了你的Agent在真实场景里的稳定性。我现在把这个当成了一个默认测试项每次加新工具都会先故意触发一次异常确认系统不会崩、模型也会如实反馈才敢放出去用。这套源码麻雀虽小五脏俱全核心的推理循环、工具注册、记忆管理都有了想继续深入的话可以试着再加一个“多工具连用”的场景比如先查询数据库再根据结果做计算感受一下Agent真正“自动编排”的威力。搭建Agent系统的关键从来不在框架选得多花哨而是把这条决策循环吃透、跑通你往后看任何Agent项目都会轻松很多。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价