简介:面向软件开发者和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.9+,requests库调大模型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_steps=10, memory_size=12): 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": f"Bearer {self.api_key}"} resp = requests.post(f"{self.base_url}/chat/completions", json=payload, headers=headers, timeout=60) 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_ascii=False) except Exception as e: content = json.dumps({"error": str(e)}, ensure_ascii=False) 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_key=os.getenv("API_KEY"), base_url=os.getenv("BASE_URL"), model_name=os.getenv("MODEL_NAME"), system_prompt=SYSTEM_PROMPT, ) if __name__ == "__main__": print("Agent已启动,输入exit退出") while True: user_input = input("你: ") if user_input.lower() == "exit": break reply = agent.chat(user_input) print(f"Agent: {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_steps=10就是兜底方案,到了就强制终止。还有一个我在实践中发现的技巧:系统提示词里加一条“如果你发现工具返回结果已经满足用户需求,立即停止并生成最终回复”,循环率能下降一半以上。
5. 一些我踩过的值得说的坑
最后分享几个我在这个项目里最想吐槽的坑。第一个就是JSON解析的容错。我用real-world模型测试时,发现部分模型生成的arguments有尾逗号或者单引号,直接用json.loads会抛异常,我后来加了replace尾逗号的预处理。第二个是工具结果格式不统一,有的工具返回纯字符串,有的返回dict,模型处理起来很容易飘,我在Executor里统一转成了字符串才缓解。
还有一点,做Agent功能测试时,别只测“正确答案”路径,一定要故意给工具传错参数,看Agent能不能优雅地告诉用户“我调用工具失败了”。这决定了你的Agent在真实场景里的稳定性。我现在把这个当成了一个默认测试项,每次加新工具都会先故意触发一次异常,确认系统不会崩、模型也会如实反馈,才敢放出去用。
这套源码麻雀虽小五脏俱全,核心的推理循环、工具注册、记忆管理都有了,想继续深入的话,可以试着再加一个“多工具连用”的场景,比如先查询数据库再根据结果做计算,感受一下Agent真正“自动编排”的威力。搭建Agent系统的关键从来不在框架选得多花哨,而是把这条决策循环吃透、跑通,你往后看任何Agent项目都会轻松很多。
本文还有配套的精品资源,点击获取