ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用Python从零搭建AI Agent平台:核心代码与避坑指南

用Python从零搭建AI Agent平台:核心代码与避坑指南 那阵子团队里讨论最多的一个词就是“AI Agent”。代码仓库堆了一堆实验脚本有人拿它写日报总结有人让它帮忙发邮件还有人到处问能不能直接买一套现成的平台。问了一圈之后发现市面上的平台要么太重、要么太贵更关键的是别人封装好的Agent本质上是“别人的同事”怎么指挥都用不顺手。于是花了大概两个周末我从完全零基础开始用最普通的Python代码搭了一个属于自己的AI Agent平台雏形。跑通的那一刻我意识到一件事所谓AI Agent开发并没有大多数人想象得那么高不可攀真正卡住新手的往往不是模型能力而是缺一套清晰的搭建思路。这篇文章就是把那一段从0到1的全过程整理出来包括我踩过的坑、绕过的弯、以及最后沉淀下来的可复用方法。如果你也想亲手“造一个同事”而不是当一个只会调API的工具人这篇应该能帮你省下不少查资料的力气。1. Agent和LLM、AI模型的区别先别再概念混战打开任何一个技术社区关于Agent的讨论几乎都会滑向同一个误区把AI Agent、LLM、AI模型当成同义词混着用。实际上三者完全是不同层级的东西。不把概念分清楚后面搭平台的设计思路很容易一开始就歪了。1.1 用工资表上的角色来理解这三层结构我习惯用一家公司的“同事”来做类比。AI模型相当于刚毕业的实习生脑子聪明、知识面广但你让他干什么他就只干什么不主动、不追问、也不会自己安排工作顺序。LLM大语言模型是这批实习生里最出类拔萃的那一类阅读理解能力极强你给他一段提示词他能给出语感相当好的回复但本质上他依然只是一个“能说会道的实习生”。Agent则完全不同。Agent是那个实习生加上完整的“岗位职责说明书”、一套“工作工具”、以及“遇到问题找谁帮忙”的决策机制。一个Agent不再是被动等待指令的而是有一个目标然后自己去拆解步骤、调用工具、检查结果、调整策略直到最终把任务完成。关键差异就藏在这句话里LLM提供的是“生成能力”Agent提供的是“完成任务的能力”。放到工资表上看更直观角色类比对象核心能力典型代表AI模型应届实习生处理输入、生成输出各种深度学习模型LLM能力强的应届生理解语言、生成文本各类大语言模型AI Agent老练的正式员工自主拆解目标、调用工具、完成任务我自己搭的那个“新同事”1.2 LLM是Agent的“大脑”但Agent不止有大脑很多初次接触Agent开发的人会误以为“把OpenAI的API封装一下然后写个循环调用”就叫Agent。这个理解不算错但远远不够。一个正经的Agent平台至少需要大脑、感知、行动、记忆四个模块协同工作。大脑就是LLM负责推理和决策。感知模块负责接收外部信息比如用户的自然语言指令、系统日志、甚至是传感器数据。行动模块则是Agent的“手脚”表现为一个个具体的工具——查数据库、调接口、发消息、写文件。记忆模块解决的是“上下文一致性”问题让Agent记得几分钟前自己说过什么、做过什么。所以当你说“我要搭一个AI Agent平台”的时候本质上是把这四个模块组合起来而不是单纯地去调一个模型接口。明白了这一点后面设计架构的时候思路就会清晰很多因为你面对的不再是一个黑盒而是一个有明确组成部分的系统。这也解释了为什么热搜里总有“agent和llm和ai模型有什么区别”这种问题——因为大多数教程根本没说清楚这层关系就直接教写代码了。2. 选型定调我不建议一上来就上重型框架先让最小闭环先跑起来市面上的Agent开发框架多到让人眼花缭乱LangChain、AutoGen、CrewAI、Dify……每一个都有大量粉丝每一个都在说自己是最佳方案。但我个人的建议是第一次搭平台不要碰这些重型框架至少别一上来就碰。2.1 为什么先从裸代码开始而不是直接用LangChain这类框架原因很简单框架会掩盖太多底层逻辑。你用LangChain搭建Agent写出AgentExecutor一个函数调用就能让模型“自动”调用工具。但模型是怎么调用工具的工具注册表长什么样循环终止条件怎么定的这些核心机制你完全没有感知。一旦运行报错排查过程就是灾难。你不知道是框架内部bug还是自己配置有误还是模型本身判断出了问题。所谓“从0到1搭建”重点恰恰在于建立对Agent底层机制的直觉而不是学会调用某个框架的封装函数。我能理解有些读者时间紧、只想先看到效果那也至少要手写一遍最简单的零依赖循环再决定要不要引入框架。这个手写的过程花不了多少时间但对理解Agent的运作机制帮助是巨大的。2.2 选型逻辑Python 轻量提示词循环 工具注册表这次搭建的核心思路是设计一个最小可行的闭环系统。所谓闭环系统指的是Agent的运行遵循一个循环接收任务、调用LLM进行推理决策、如果决策结果是调用工具就执行工具并返回结果、把结果喂回给LLM继续推理、直到LLM判断任务已完成并输出最终答案。技术选型上全部用Python。理由不用多说生态最丰富资料最多。LLM调用部分直接使用OpenAI兼容的API格式好处是DeepSeek、百度千帆、甚至是本地部署的Ollama都提供了兼容接口一套代码走遍所有模型换模型只需要改base_url和key。工具部分则自己设计一个简单的注册机制每个工具就是一个Python函数加一个JSON Schema描述。这种设计的核心优势在于可控所有逻辑都在自己手上出了问题可以单步调试。性能上虽然不如那些经过高度优化的框架但对个人造“同事”来说完全够用。2.3 平台化思维从第一天就要有目录结构虽然最小闭环很轻但目录结构不能乱。我见过太多人把Agent代码写成一个大脚本所有工具函数堆在同一个文件里几百行之后自己都找不着北。平台化的核心是“可扩展”第一天就按模块划分好后面加新同事的时候会省心很多。我当时的目录结构大概长这样agent-platform/ ├── main.py # 入口启动Agent平台 ├── core/ │ ├── agent.py # Agent核心循环逻辑 │ ├── llm.py # LLM调用封装 │ ├── tools.py # 工具注册与执行器 │ └── memory.py # 记忆管理 ├── tools/ │ ├── weather.py # 天气查询工具 │ ├── email_sender.py # 邮件发送工具 │ ├── file_ops.py # 文件操作工具 │ └── web_search.py # 网页搜索工具 ├── config.py # 配置文件 ├── prompts/ │ └── system_prompt.py # 系统提示词模板 └── requirements.txt这个结构设计背后的逻辑是core层负责Agent运行的通用机制tools层放具体的工具实现以后想让Agent多一个技能就在tools目录里新增一个文件然后在core的注册表里登记一下。这才是“平台”应该有的样子而不是一个什么都堆在一起的玩具脚本。3. 亲手跑通第一个“同事”核心代码逐步拆解接下来是全文最核心的部分。我会把搭建过程中最重要的几个代码模块逐个拆开讲不是那种粘贴完整源码然后说“你们自己跑一下”的教程而是每一段都讲清楚为什么这么写、背后的设计意图是什么。3.1 第一步配置环境与统一模型接口平台运行的第一步是解决“大脑连接”的问题。我用一个llm.py模块统一封装所有模型调用后续无论换哪个模型只需要改配置文件代码主体一行都不用动。# config.py MODEL_CONFIG { base_url: https://api.deepseek.com/v1, # 可换成其他OpenAI兼容接口 api_key: your-api-key-here, model: deepseek-chat, temperature: 0.3, max_tokens: 4096 }# core/llm.py import json from openai import OpenAI import config class LLMClient: def __init__(self): self.client OpenAI( base_urlconfig.MODEL_CONFIG[base_url], api_keyconfig.MODEL_CONFIG[api_key] ) self.model config.MODEL_CONFIG[model] self.temperature config.MODEL_CONFIG[temperature] self.max_tokens config.MODEL_CONFIG[max_tokens] def chat(self, messages): 统一对话入口所有LLM调用都走这里 response self.client.chat.completions.create( modelself.model, messagesmessages, temperatureself.temperature, max_tokensself.max_tokens ) return response.choices[0].message.content这里有几个细节值得说明。temperature设成0.3因为在Agent场景里我们追求的是稳定和可预期而不是创意发散如果temperature太高Agent的决策会变得随机同样的任务跑两次得到完全不同的路径这在实际应用中是灾难。base_url写成https://api.deepseek.com/v1是因为DeepSeek提供OpenAI兼容接口同理如果后续想换Ollama本地模型只需要把base_url换成http://localhost:11434/v1即可这就是统一接口封装的最大价值。3.2 第二步工具注册表机制——让Agent学会使用“手脚”工具注册是整个平台设计的灵魂。Agent调用工具的方式不能是硬编码的而是要有一套动态注册机制让LLM可以根据需求“发现”有哪些工具可用以及每个工具需要什么参数。# core/tools.py import inspect import json class ToolRegistry: def __init__(self): self.tools {} def register(self, name, description, parameters, func): 注册一个工具 self.tools[name] { type: function, function: { name: name, description: description, parameters: parameters, func: func # 实际调用函数不进API请求仅平台内部用 } } def get_openai_tools(self): 返回符合OpenAI工具调用格式的列表 result [] for name, tool_info in self.tools.items(): tool_copy json.loads(json.dumps(tool_info)) tool_copy[function].pop(func) result.append(tool_copy) return result def execute(self, name, arguments): 执行工具调用 if name not in self.tools: raise ValueError(f未知工具: {name}) func self.tools[name][function][func] args json.loads(arguments) if isinstance(arguments, str) else arguments return func(**args)然后写一个简单的天气查询工具让它成为Agent的第一个“技能”# tools/weather.py import datetime import random def get_weather(city: str, date: str None): 查询指定城市的天气情况。 注意这里用一个模拟实现代替真实API调用实际项目中替换为真实的天气API即可。 date date or datetime.date.today().isoformat() weather_data { city: city, date: date, temperature: round(random.uniform(15, 30), 1), condition: random.choice([晴, 多云, 小雨]), wind_level: random.randint(1, 5) } return json.dumps(weather_data, ensure_asciiFalse)工具注册的方式也很简单registry ToolRegistry() registry.register( nameget_weather, description查询指定城市在指定日期的天气情况返回温度、天气状况、风力等级, parameters{ type: object, properties: { city: {type: string, description: 城市名称如北京、上海}, date: {type: string, description: 日期格式YYYY-MM-DD可选} }, required: [city] }, funcget_weather )注册机制的精髓在于“双份信息”。一份是给模型看的JSON Schema描述参数格式一份是给平台实际执行的函数对象两者通过相同的名字绑定在一起。当LLM决定调用“get_weather”并给出参数时平台从注册表找到对应函数执行再把结果返回给LLM。这套机制实际上就是RAG、Function Calling这些技术的基础也是现在各大Agent平台工具调用机制的底层原理。3.3 第三步Agent主循环——从“一次对话”变成“自主完成任务”Agent的核心循环是整个平台的心脏。简单说它要完成这样几件事把用户任务发给LLMLLM返回它接下来的决策回答用户或者调用某个工具如果决策是调用工具就执行并把结果喂回去循环往复直到LLM认为任务完成输出最终答案。# core/agent.py import json from core.llm import LLMClient from core.tools import ToolRegistry from prompts.system_prompt import SYSTEM_PROMPT class Agent: def __init__(self, name: str, llm_client: LLMClient, registry: ToolRegistry): self.name name self.llm llm_client self.registry registry self.messages [ {role: system, content: SYSTEM_PROMPT.format(agent_namename)} ] def run(self, task: str, max_iterations: int 5): 执行任务max_iterations限制最大循环次数防止死循环 self.messages.append({role: user, content: task}) iteration 0 while iteration max_iterations: response self.llm.chat(self.messages) # 判断LLM的响应是否为结构化格式即工具调用指令 parsed None try: start response.find({) if start ! -1: json_str response[start:response.rfind(}) 1] parsed json.loads(json_str) except json.JSONDecodeError: parsed None if parsed and tool_call in parsed: # 决策是要调用工具 tool_name parsed[tool_call][name] tool_args json.dumps(parsed[tool_call][arguments]) result self.registry.execute(tool_name, tool_args) self.messages.append({role: assistant, content: response}) self.messages.append({ role: user, content: f工具执行结果{result}请根据结果继续处理任务 }) iteration 1 continue else: # 决策是直接输出最终答案 self.messages.append({role: assistant, content: response}) return response return f[已达到最大迭代次数 {max_iterations}任务未能完成]这段代码里有一个我在实际踩坑之后总结出来的设计要点不要直接依赖模型的function calling响应格式而是自己解析结构化输出。部分模型在特定场景下返回的function calling格式可能不标准或者经过一些网关转发后会变形。我在循环里加入了JSON解析兜底逻辑从文本中提取最有可能的结构化片段。这让Agent的稳定性提升了一个档次。3.4 第四步系统提示词——决定“新同事”性格和能力边界的文档提示词这一步经常被新手忽略但实际上它决定了Agent到底是“靠谱同事”还是“疯疯癫癫的实习生”。系统提示词的本质是给Agent设定岗位说明书和规章制度。以下是我实际在用的一个系统提示词模板你可以直接复制改造你是{agent_name}一个自主完成任务的AI助手。 工作原则 1. 你会收到一个任务描述需要独立拆解并完成任务。 2. 如果需要查询实时信息或执行外部动作必须调用工具。 3. 调用工具时严格按以下JSON格式输出不要包含任何其他文字 {{tool_call: {{name: 工具名, arguments: {{参数1: 值1, 参数2: 值2}}}}}} 4. 工具执行结果会作为用户消息返回给你你需要根据结果继续推进任务。 5. 只有当任务已经完成时才可以直接输出最终答案不要输出额外解释。 6. 不要编造工具结果如果工具返回错误如实记录错误并尝试其他方案。这个提示词里最关键的是第3条——定义了工具调用的统一输出格式。我见过不少Agent代码LLM正常回复没问题但一旦让它调用工具就各种格式错误问题就出在提示词没限定清楚输出格式。模型不是不明白要干什么而是不知道“应该用哪种方式表达自己的意图”。3.5 第五步串起来跑一个完整任务所有模块就绪后在main.py里把它们组装起来跑一个真实任务来验证整个闭环。# main.py from core.agent import Agent from core.llm import LLMClient from core.tools import ToolRegistry from tools.weather import get_weather def main(): llm_client LLMClient() registry ToolRegistry() # 注册第一个工具 registry.register( nameget_weather, description查询指定城市在指定日期的天气情况, parameters{ type: object, properties: { city: {type: string, description: 城市名称}, date: {type: string, description: 日期格式YYYY-MM-DD可选} }, required: [city] }, funcget_weather ) agent Agent(name小天, llm_clientllm_client, registryregistry) result agent.run(北京今天天气怎么样适合出门跑步吗) print(最终回答:, result) if __name__ __main__: main()跑起来的真实效果让我印象很深。Agent先调用天气工具查询“北京”和“今天”的天气拿到结果之后基于温度、天气状况和风力给出了“适合散步但不适合长跑”之类的判断。全程没有一个“今天天气很好适合出门”这种万金油回答因为它拿到了真实的工具返回数据。这就是Agent和普通LLM的差距——它会主动获取信息而不是凭训练数据里的感觉瞎猜。4. 让“新同事”真正可用记忆机制、多工具协作与长任务处理最小闭环跑通之后距离“人人都能造同事”这个目标还差临门一脚。一个只会在单轮对话里调用工具的Agent还远远算不上“同事”。真正能干活的新同事需要具备三个关键能力记得住做过的事、使唤得动更多工具、能扛得住复杂的长期任务。4.1 记忆机制从“金鱼脑”到“有点记性”初始代码里所有历史消息都放在self.messages列表里通过把每次助手回复和工具结果追加进列表实现了“上下文记忆”。问题在于大语言模型的上下文窗口是有限的消息越积越多最终一定会撑爆窗口。这就是为什么Agent平台必须引入记忆管理模块。我在实际项目中采用的方法很简单分两层短期记忆和长期记忆。短期记忆就是当前对话的上下文直接通过messages传递但它有一个上限例如超过30轮就触发压缩机制——把早期的对话内容用LLM生成一个摘要替换掉原始消息保留关键信息但大幅压缩token占用。长期记忆则解决“跨会话记住事实”的问题——把重要信息用户偏好、项目背景、历史决策抽取出来持久化到本地向量数据库里。下次对话时根据用户当前问题做相似度检索找到之前记录的相关信息注入到系统提示词里。当时我用了sqlite-vec这个轻量级本地向量数据库模块不需要额外部署服务一个文件搞定。核心实现逻辑是这样# core/memory.py import sqlite_vec import sqlite3 from core.llm import LLMClient class MemoryStore: def __init__(self, db_pathmemory.db): self.db sqlite3.connect(db_path) self.db.enable_load_extension(True) sqlite_vec.load(self.db) self.db.execute(CREATE VIRTUAL TABLE IF NOT EXISTS vec_memory USING vec0(embedding float[1024])) self.db.execute(CREATE TABLE IF NOT EXISTS memory_text (id INTEGER PRIMARY KEY, content TEXT, topic TEXT)) self.llm LLMClient() def save(self, content: str, topic: str): embedding self._get_embedding(content) cur self.db.execute(INSERT INTO memory_text (content, topic) VALUES (?, ?), (content, topic)) row_id cur.lastrowid self.db.execute(INSERT INTO vec_memory (rowid, embedding) VALUES (?, ?), (row_id, embedding)) self.db.commit() def search(self, query: str, limit: int 3): query_embedding self._get_embedding(query) rows self.db.execute( SELECT rowid, distance FROM vec_memory WHERE embedding MATCH ? AND k ?, (query_embedding, limit) ).fetchall() results [] for rowid, distance in rows: row self.db.execute(SELECT content, topic FROM memory_text WHERE id ?, (rowid,)).fetchone() results.append(row[0]) return results def _get_embedding(self, text: str): # 使用同一个LLM接口获取向量表示 response self.llm.embeddings(text) return response记忆模块的实际体验是质的飞跃。没有记忆的Agent每次对话都是“熟悉的陌生人”有了记忆之后它会在一段时间后记得“用户是产品经理平时喜欢简洁明确的回答风格”会在决策时把这些偏好考虑进去。这看起来只是一小步但对于“造同事”这个目标而言记忆就是同事之间信任感的基础。4.2 工具从一到多注册表与技能树的扩展只有一个天气工具的Agent本质上是个“单技能实习生”。但工具注册表的机制决定了扩展极其简单。我在tools目录里不断增加新工具从文件操作到邮件发送再到网页搜索每加一个技能只需要三步写一个函数、写一个描述Schema、调用register注册。这里有一个特别值得分享的实操技巧工具描述要写得像给不懂技术的人看的工作说明而不是给程序员看的接口文档。LLM是通过描述来决定什么时候调用工具的描述写得含糊它就会犹豫不决或者干脆不调用。描述写得详细、有触发场景它就能精准地对号入座。比如邮件发送工具我一开始写的描述是“发送邮件”结果模型经常在需要邮件时不知道应该用它。改成“当用户需要向他人发送邮件、回复邮件、或者通知某个人某个事项时使用此工具参数包括收件人邮箱、主题、正文注意正文必须是纯文本格式不能包含HTML”行为就准确多了。这个经验后来被我总结成了工具描述写作的“三要素”触发场景、参数说明、约束条件。4.3 长任务处理让Agent“扛得住”复杂需求现实工作任务很少有一步能完成的。这也是为什么Agent平台必须支持任务的拆解与规划。我在Agent里增加了一个规划模块——当收到一个复杂任务时Agent先不直接动手而是先调用一个“任务规划”工具把大目标拆成若干子任务然后逐个执行。这个用自然语言触发的规划流程通过一个名为create_plan的工具实现。该工具本身也是注册表里的一个普通工具但它内部逻辑要求LLM输出一系列步骤。每执行完一个步骤Agent就把结果记录到计划执行状态里然后判断是继续下一个步骤还是任务已全部完成。这个设计花了我不少时间调优其中最大的坑是模型在任务执行到一半时会“忘记”自己正在执行原计划自动发散到其他无关的事情上。解决办法是在系统提示词里加入一条铁律“你正在执行一个多步骤任务所有操作必须严格围绕当前步骤展开不要主动引入与当前步骤无关的新任务。每完成一步回顾一下计划中剩余的步骤然后继续。”这句话的效果比我想象中好很多模型的发散行为显著减少。5. 调优实战把“新同事”从疯疯癫癫调到能交付工作技术圈有一句话写代码只占20%的时间剩下80%都在调bug。搭建Agent更是如此。下面这些坑是我在实际调试过程中踩过的每一个都花了不止半小时排查整理出来希望能帮你避开同样的弯路。5.1 排查链路实录当模型“自以为是”地编造工具结果第一次跑平台的时候我让Agent调用天气工具结果它直接返回了“北京今天25度晴适合运动”。数据看起来像模像样但我自己的工具函数压根没有输出这个格式的数据。检查之后发现问题出在两点一是提示词里关于工具结果返回的机制不够明确模型以为只要“心里有数”就能直接回答二是max_iterations设得太小模型还没等工具结果返回就已经开始“脑补”答案。排查的完整链路是这样的。第一步检查Agent的完整消息记录看它有没有调用工具的决策。结果发现压根没有触发工具调用。第二步检查工具描述是否符合模型的理解习惯发现描述里没有强调“必须调用工具才能获取天气数据”。第三步修改提示词明确告知“你没有内置天气知识所有天气信息必须通过get_weather工具获取禁止自己编造天气数据”。修改后再跑行为立刻转变。这个排查过程让我意识到一个关键点LLM的“诚实性”高度依赖提示词的显式约束。你不告诉它不能编造它就会自信地编造。这也是为什么Agent平台里系统提示词的质量直接决定了Agent的可靠性下限。5.2 循环跑飞问题Agent陷入反复调用同一个工具的死亡螺旋另一个高频bug是Agent陷入循环比如让它查天气它查完一次不满足再查一次不同日期的再然后又开始重复查询同一个日期的直到达到max_iterations上限输出一个失败提示。当时排查发现问题出在工具返回的结果数据量太大。我那个天气工具直接返回了整串JSON数组LLM处理这么多信息时注意力被分散反复确认“我是不是漏看了什么”。解决方案有两步一是精简工具返回内容只返回最关键的字段把详细数据放附件或日志里二是在循环条件上增加一个针对“重复调用相同参数工具”的检测如果连续两次以上调用同一工具且参数相同直接中断循环并提示用户。后来我还加了一个“冷静期”机制——当Agent连续调用工具3次并没有产生新信息时强制让它输出当前结论并停止工具调用。这个机制在实战中效果立竿见影。5.3 提示词调试的黄金法则一次只改一个变量关于提示词调试我悟出过一个特别重要的道理提示词输出的不确定性决定了你根本没法像测代码一样做回归测试。每次修改提示词跑一遍可能只是因为运气好碰对了结果。因此最科学的调试方式是一次只改一个变量并且同一场景至少要跑3次以多数结果来评价改动效果。还有一个不太起眼但很实用的习惯把每次实验的提示词版本和测试用例结果都记录下来。我甚至为此建了一个简单的表格每一次实验做了什么改动、测试结果稳定不稳定、花了多少token、有没有异常行为全部记录在案。看似麻烦但当你迭代到第10版提示词的时候这个表格就是你的最强大脑。实验版本改动内容测试结果备注v1初始提示词能调用工具但会编造结果需加禁止编造约束v2增加“禁止编造工具结果”编造明显减少但偶尔循环调用新增重复检测机制v3精简工具返回字段循环问题显著改善稳定性提升明显v4增加“冷静期”机制行为稳定偶有发散需加入任务回顾约束这个表到今天还挂在我的项目文档里它见证了一个Agent从“疯疯癫癫的实习生”到“基本靠谱的新同事”的全过程。6. 从代码到平台封装成别人也能用起来的Agent基础设施当你的Agent在本地稳定跑起来之后下一步自然就是思考平台化。把我自己的代码封装成一套可以给别人用的基础服务这意味着要从单文件脚本升级成带有标准接口、可视化管理方式和稳定运行能力的系统。6.1 API化封装让Agent能力可以被外部系统调用平台化的第一步是把Agent能力封装成HTTP API服务。我用的是FastAPI框架原因很简单异步原生、自带Swagger文档、类型校验开箱即用。# api/server.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel from core.agent import Agent from core.llm import LLMClient from core.tools import ToolRegistry app FastAPI(titleMy Agent Platform) llm_client LLMClient() registry ToolRegistry() # ... 注册所有工具 ... agent_instance Agent(name小天, llm_clientllm_client, registryregistry) class TaskRequest(BaseModel): task: str session_id: str default app.post(/agent/run_sync) async def run_sync(request: TaskRequest): try: result agent_instance.run(request.task) return {status: ok, result: result} except Exception as e: raise HTTPException(status_code500, detailstr(e)) if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)封装成API之后这套Agent平台就从一个“本地脚本”变成了“基础设施”。任何内部系统不管是IM机器人、自动化运维平台还是办公自动化工具都可以通过HTTP调用Agent的能力。这一步是“从0到1”和“从1到N”的分水岭。6.2 可视化配置让“造同事”变成选填表单代码层面的封装对开发者友好但如果目标是“人人都能造同事”还需要一个可视化配置前端。不过这个阶段我并不建议自己写前端直接用现成的开源工具做一层UI封装就够了。比如用Streamlit搭一个简单的管理面板列出当前所有已注册的工具、运行日志、会话历史再放一个对话窗口可以快速测试Agent行为。Streamlit的实现成本极低大约几百行Python代码就能搞定基础界面但它解决了平台化的核心痛点让非技术团队成员也能参与Agent的配置与测试。我当时的做法是让产品同事用Streamlit面板测试不同工具描述的效果收集他们反馈的“工具该在什么时候被调用”再以此迭代系统提示词。这个节奏比我自己闷头调试快得多。6.3 多Agent协作让不同角色开始“共事”当平台里不止一个“同事”之后多Agent协作就是绕不开的话题。我的实现方案是采用“编排者-执行者”架构一个主管Agent负责接收复杂任务分析任务内容判断该分派给哪个专业Agent然后把子任务分发出去并汇总结果。主管Agent的系统提示词里定义了一个派发协议它需要通过调用dispatch_task工具把子任务发出去。各个专业Agent执行完后返回结果主管再判断是否需要二次派发或直接汇总。这套架构的好处是每个Agent只需要负责一个狭窄的领域提示词可以写得非常聚焦准确率自然更高。举个例子我让一个Agent专职做资料检索一个Agent专职做文本润色另一个Agent专职做数据分析。当用户提出“帮我写一份北京互联网行业薪酬分析报告”时主管Agent先派检索Agent收集数据再派分析Agent处理数据最后让润色Agent出稿。整个流程虽然走完需要几分钟但最终交付质量远超单个Agent硬扛。这个场景也让我对“agent编程”有了新的认知Agent开发的本质是组织管理学你管理的是一个“虚拟团队”不是在写普通的业务代码。7. 平台部署与维护从能跑到稳定跑的进阶实践搭建完成只是第一步真正让Agent平台被团队常态化使用还需要考虑部署和运维的问题。这部分的经验我是通过连续两周的线上试运行才慢慢攒下来的。7.1 部署方案对比Docker化还是裸机上跑开发阶段直接在本地跑没有问题但一旦平台需要常驻服务Docker化部署几乎是必然选择。原因很朴素依赖隔离、环境一致、一键回滚。我当时写了一个极其精简的Dockerfile核心就几步FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD [uvicorn, api.server:app, --host, 0.0.0.0, --port, 8000]这里有个小坑值得提醒python:3.11-slim镜像默认缺不少系统库如果Agent工具涉及图片处理或Pandas这类需要C扩展的包需要额外安装构建工具。我当时在容器里跑Pandas就直接撞上了缺底层库的报错最后在Dockerfile里加了一行RUN apt-get update apt-get install -y gcc g libffi-dev才解决问题。7.2 成本控制与安全边界平台化的防火墙意识Agent平台一旦对多人开放成本失控是迟早的事。没有限制的Agent会疯狂消耗token特别是在多Agent协作场景里一次复杂任务动辄烧掉几万token。我的处理方式是在API网关层做三层限制频率限制、预算限制、上下文长度限制。频率限制防止单个用户短时间大量调用预算限制给每个用户分配每日token配额上下文长度限制防止长对话导致的单次调用费用爆炸。另外Agent能接触的外部工具权限必须单独管理。比如我让文件操作工具只能操作指定目录禁止跨目录访问系统文件邮件发送工具的收件人必须匹配团队内部域名的白名单。安全意识是平台化过程中最不能妥协的部分。7.3 可观测性Agent的“黑盒”如何变透明调试Agent最大的痛苦在于不确定性一个任务跑得成功不代表下个任务也成功。为了建立可观测性我在Agent核心循环里增加了结构化日志记录——每轮循环都记录下当时的模型输入、模型输出、是否调用了工具、调用了哪个工具、工具返回了什么。每一条日志都带一个 request_id这样一次任务的所有行为都能串成一条完整的链路。有了这份日志之后排查问题就不再是“猜模型在想什么”而是直接看日志。有一次线上问题反馈是“Agent经常答非所问”查日志后发现原来是模型在多轮工具调用后上下文被截断最早的用户指令被挤出了窗口。这个发现直接推动了记忆压缩机制的实现。提示如果你想搭一个稍微正式一点的AI Agent平台可观测性模块一定不要省。Agent的调试方式和普通程序完全不同普通程序有报错栈Agent没有——它只会给你一个错误或者不那么正确的答案。这时候只有完整的链路日志才能帮你定位问题。8. 进阶路线图从今天动手到成为一个能干的“同事”写到这里距离最初那个“从0到1”的承诺已经非常远了——你手里的不再是一个脚本而是一套具备记忆、多工具、多Agent协作能力且可部署、可观测、可扩展的mini平台。最后分享几条进阶路线送给打算在这条路上继续走下去的读者。8.1 路线一把“同事”接进真实的协作工具我目前正在做的事情是把Agent接进团队日常使用的协作软件里。这样Agent就能直接接收群里的指令、读取群里的文档、把执行结果回复到群里。这个方向的技术难度不大主要工作集中在授权管理、消息分发、权限隔离上。但一旦打通Agent才真正从一个“测试环境里的玩具”变成“办公环境里的同事”。个人经验是从IM机器人切入的接受度和使用频率最高因为自然语言交互的学习成本最低不需要专门培训就能让所有人都用起来。8.2 路线二拥抱MCP生态让Agent技能大爆发如果你关注AI Agent开发生态一定会经常看到MCP这个词。MCP的全称是Model Context Protocol一个开放协议可以把它想象成Agent工具界的USB接口——任何符合MCP规范的工具服务都可以即插即用地接入任意MCP兼容的Agent平台。我研究过这个协议之后最大的感受是工具生态的建立才是Agent平台真正实现“自助式扩展”的关键。你不用再为每一个新工具自己写注册代码只需要准备一个MCP Server的配置。目前社区里已经有了大量现成的MCP Server文件系统、数据库、浏览器操作、各类SaaS服务应有尽有。把这些Server接进自己的平台Agent的能力半径瞬间扩大一个数量级。8.3 路线三让Agent具备“技能学习”能力不再依赖手动写工具再往前走一步就是让Agent自己学会使用新工具。这也是热搜里“ai agent skill”这个方向的核心——Agent不再需要开发者手动编写工具函数而是可以阅读工具的使用文档自动生成调用方式。我的实验思路是把工具文档喂给Agent作为参考上下文然后观察它是否能在没有显式注册的情况下正确调用。这个方向还很不成熟但它指出了一个明确的趋势未来的Agent平台不再是一个固定的工具集合而是一个能让Agent自主发现、学习和使用工具的“能力孵化器”。到那个时候“人人都能造同事”就真的不只是标题里的一句口号了。我在这个项目里除了收获了代码和架构经验还有一层更深的体会搭建Agent的过程其实是在逼自己把模糊的需求拆解成明确的结构。那些被封装在现成平台里的复杂逻辑只有亲手重写一遍才能真正变成自己的东西。如果这篇文章能让你少踩几个坑或者让某个一直想动手又没有信心的人把第一行代码写下去那它就没有白写。
返回列表