资讯详情

从零搭建你的第一个AI智能体:不依赖框架的Python实现

📅 2026/10/20 21:24:05 | 华诺云谱 👁 阅读
从零搭建你的第一个AI智能体:不依赖框架的Python实现
现在有很多人来找我聊AI开口就是“我想做一个AI智能体Agent”但真到动手的时候又不知道从哪开始。这个东西听起来玄乎说白了它就是一个能自己“看情况、做决定、调工具”的程序。我见过太多教程一上来就甩一堆框架结果小白装环境就装了一整天最后连一个能跑的demo都没见着。这篇我就用最直白的方式带你从零搭一个自己的Agent不依赖任何神秘框架只要会一点Python跟着敲就能跑通。1. 先弄明白Agent到底是什么1.1 别被名词唬住Agent就是“会调用工具的机器人”早些年我们聊天机器人你问一句它答一句答不上来就死机。Agent不一样它手里有一堆工具比如计算器、搜索引擎、日历、数据库查询接口。它接到你的问题后不是直接回答而是先想“这个问题我需要用哪个工具解决”然后自己把工具调起来拿到结果再组织语言回答你。我给你打个比方。你让一个实习生去整理月度报表他不会魔法但他知道用Excel的函数、公司的数据库、邮件模板。他拿到任务后自己判断用什么工具然后一步步操作最后给你一份成品。Agent就是那个实习生大模型是他的大脑工具是他的手脚而“判断、操作、反馈”这个过程就是Agent的核心循环。1.2 核心逻辑模型决定下一步工具完成动作一个最基本的Agent内部其实就是一个循环把用户的问题和已有的对话历史发给大模型。大模型返回两种可能要么直接给最终回答要么说“我需要调用某个工具参数是什么”。如果大模型说要调用工具程序就去执行那个工具函数拿到结果。把工具结果发回给大模型让模型结合结果生成下一步动作或最终回答。这个循环一直重复直到模型认为可以给出最终答案。这就是所谓“ReAct”的精髓思考Reasoning→ 行动Acting→ 观察Observation。很多人把Agent想得很神秘实际上你只要控制好这个循环就已经拥有一个最小可用的Agent了。后面所有的花活记忆、规划、多智能体协作都是在循环上做增强。2. 环境准备把地基打牢2.1 轻量起手不需要重型框架小白最容易犯的错就是一上来安装一大堆框架还没写代码就被依赖冲突劝退。我的建议是先只用Python的requests库直接打大模型接口。等你能跑通一个最简单的Agent再去研究那些花哨的框架不迟。你只需要准备Python 3.9哪个发行版都行官方装的就可以。一个大模型服务的API地址和密钥。可以是任何提供“对话补全”接口的服务商甚至本地跑一个开源模型也行接口格式按服务商文档来。requests库用pip install requests安装。我不建议在这个阶段去碰那些几千行代码的Agent框架因为一旦出问题你分不清是模型的问题还是框架的问题。自己写二十行核心循环所有逻辑都明明白白。2.2 获取密钥并安全配置假设你用的是某家大模型服务的API一般流程是注册开发者账号 → 创建一个应用 → 获取一个形如sk-xxxx的密钥。注意密钥就是你的钱包千万别到处贴。写代码时不要把这个密钥硬编码在脚本里万一你哪天把代码传到公开平台密钥就泄露了。我习惯把它放在环境变量里例如在终端执行export API_KEY你的密钥 export API_BASEhttps://api.example.com/v1然后在Python代码里读取import os api_key os.environ.get(API_KEY) api_base os.environ.get(API_BASE) if not api_key or not api_base: raise RuntimeError(请先设置API_KEY和API_BASE环境变量)如果你用的是Windows终端命令换成set或者直接在代码里用os.environ设置也行但千万别写死真实密钥。3. 第一个最小Agent让模型学会调用工具3.1 先写一个不带工具的机器人我们一步一步来。第一步写一个能对话的裸模型调用函数。这里以HTTP方式调用大模型接口不绑定任何特定服务商import requests def chat_with_model(messages, api_key, api_base): headers { Content-Type: application/json, Authorization: fBearer {api_key} } payload { model: your-model-name, messages: messages, temperature: 0.7 } resp requests.post( f{api_base}/chat/completions, headersheaders, jsonpayload, timeout30 ) resp.raise_for_status() data resp.json() return data[choices][0][message][content]调用时传一个消息列表messages [{role: user, content: 11等于几}] print(chat_with_model(messages, api_key, api_base))模型会直接回答“2”。但这不是Agent因为它只会动嘴不会动手。想象一下如果用户问“帮我计算一下2345*6789/12”模型虽然能算但可能算错因为它本质上是预测下一个词不是真的在做算术。3.2 给Agent加一个“计算器”工具要让模型学会用工具我们需要做三件事第一把工具的描述告诉模型让模型知道有这么一个计算器可以用以及怎么传参数。第二解析模型返回的“工具调用请求”。第三执行真正的计算把结果再喂给模型。先定义一个计算器函数def calculator(expression: str) - str: try: # 安全起见这里只允许简单四则运算 result eval(expression, {__builtins__: {}}, {}) return str(result) except Exception as e: return f计算错误: {e}注意直接用eval有风险生产环境需要解析表达式这里只是为了演示。然后定义工具描述让大模型知道这个工具的存在tools [ { type: function, function: { name: calculator, description: 计算一个数学表达式例如 2345*6789 或 11。, parameters: { type: object, properties: { expression: { type: string, description: 需要计算的数学表达式 } }, required: [expression] } } } ]这段描述很重要。模型全靠这段描述来理解什么时候该调用工具参数名和格式必须和你的函数完全一致。接下来修改我们的调用把tools传给模型payload { model: your-model-name, messages: messages, tools: tools, tool_choice: auto, }此时模型返回的消息里可能多出一个字段类似tool_calls里面带着函数名和参数。我们把它解析出来def process_tool_call(message, messages): tool_calls message.get(tool_calls, []) for tc in tool_calls: fn_name tc[function][name] fn_args json.loads(tc[function][arguments]) if fn_name calculator: result calculator(fn_args[expression]) messages.append({ role: tool, tool_call_id: tc[id], content: result }) return messages注意要把tool角色的消息加进对话里这样模型才能看到工具返回的结果从而组织最终回答。3.3 把“工具调用循环”封装成一个Agent类上面的代码太散了。为了清晰和复用我把它封装成一个极简的Agent类。你只要复制这段代码替换了自己的模型名和API地址就能跑起来。import json import requests class SimpleAgent: def __init__(self, api_key, api_base, model_name, toolsNone): self.api_key api_key self.api_base api_base self.model_name model_name self.tools tools or [] self.messages [] def call_model(self): headers { Content-Type: application/json, Authorization: fBearer {self.api_key} } payload { model: self.model_name, messages: self.messages, tools: self.tools, tool_choice: auto, temperature: 0.7 } resp requests.post( f{self.api_base}/chat/completions, headersheaders, jsonpayload, timeout60 ) resp.raise_for_status() return resp.json()[choices][0][message] def execute_tools(self, message): for tc in message.get(tool_calls, []): fn_name tc[function][name] fn_args json.loads(tc[function][arguments]) if fn_name calculator: result calculator(fn_args[expression]) self.messages.append({ role: tool, tool_call_id: tc[id], content: result }) # 以后有更多工具在这里继续加分支 def run(self, user_input): self.messages.append({role: user, content: user_input}) max_steps 5 for _ in range(max_steps): message self.call_model() if message.get(tool_calls): self.messages.append(message) self.execute_tools(message) # 模型需要看到工具结果后重新生成所以继续循环 else: self.messages.append(message) return message[content] return 步骤太多我停下来了。使用起来非常简单agent SimpleAgent(api_key, api_base, your-model-name, toolstools) print(agent.run(帮我算一下 (2345*6789)/12)) print(agent.run(计算器可以算吗算一下100/7))我自己的代码里还有一个关键点每次模型返回tool_calls时要把模型那条消息也追加到self.messages里。如果漏掉这一步模型后面就不知道它刚才已经调用过工具甚至会重复调用或者认为工具结果不存在。3.4 跑起来测试你的第一个Agent运行上面代码你可能会看到两种输出如果模型判断不需要工具直接输出例如“结果是1316.1667”。如果模型判断需要计算器后台会先调用calculator再根据返回结果生成回答。这里有一个很常见的现象模型第一次返回的不是最终答案而是“我想我需要用计算器来计算这个表达式”。那么你的循环接下来会把tool消息喂回去模型第二次就会给出“计算结果是约1316.17”。这就是Agent一次完整的“思考-行动-观察-回答”过程。你还可以试着问一些不需要计算的问题比如“你好呀”模型就会跳过工具直接回答。所以tool_choice设置为auto时模型自己决定用不用工具。4. 让Agent更像样加入记忆和规划4.1 记忆功能让Agent记住上下文上面那个Agent是无状态的你每次run之间它记不住之前的对话。真实场景里用户经常说“那再加2呢”就必须让它记住之前的计算。最简单的方法就是把历史消息保留在self.messages里别清空。就像我们类里做的那样每次run追加用户消息和模型回答下一轮就能看到历史。但历史无限扩大会撑爆上下文窗口。一个实用的做法是限制历史条数。比如只保留最近10条消息def trim_history(self, max_messages10): if len(self.messages) max_messages: self.messages self.messages[-max_messages:]注意如果历史里有tool消息裁剪时最好把成组的“模型工具调用请求”和对应的“工具结果”一起保留否则模型会看到孤立的tool消息可能报错。所以我一般会把max_messages设成偶数并且在裁剪时检查一下首尾角色。进阶一点的记忆是摘要。当历史太多让模型把之前的内容总结成一段话然后把那段话放在系统提示词里代替所有旧历史。这个做法省token但会丢失细节适合需要长期记忆但信息不敏感的场合。4.2 规划能力用提示词引导模型分步思考工具调用是Agent的“手”但很多任务需要它先做个计划比如“帮我安排一场团建要考虑时间和天气”。这时候只靠工具还不够我们需要让模型像人一样先列步骤。最经典的做法是在系统提示词里加入ReAct格式你是一个智能助手当你需要完成一个复杂任务时请按以下步骤进行 1. 思考分析用户需求列出需要获取的信息。 2. 行动调用相应工具获取信息。 3. 观察查看工具返回结果。 4. 重复上述步骤直到获得足够信息给出最终回答。把这段提示词放在self.messages的第一个system角色里模型就会倾向于做分步思考。不要小看提示词的力量很多时候不需要改代码只需要改提示词Agent的规划能力就能明显提升。我试过一个例子让Agent“帮我比较两款手机的性价比预算3000元”。它先调用搜索引擎工具查参数再调用计算器算分数最后输出推荐结论。整个链条是模型自己规划的而不是我代码里写死的。这种灵活性是Agent最大的魅力。5. 实操中常见的坑与排查思路5.1 模型死活不调用工具怎么办这是小白遇到最多的问题。排查顺序是这样第一看你传的tools格式是否符合服务商要求。有些平台要求每个工具必须有唯一的name和严格的parameters结构如果你之前见过其他框架的写法别照搬直接对照文档检查。第二看工具描述是否足够清晰。比如你写“计算器”模型可能不确定它能不能算带括号的表达式。描述里写清楚“支持加减乘除和括号”模型就更容易决策。第三看模型本身是否支持function calling。不是所有模型都支持。如果遇到不支持的只能换一种思路比如在提示词里告诉模型“你如果要计算请输出格式CALC:你的表达式”然后代码里去解析这个指令。这种做法很土但有效。第四把tool_choice设为required或指定具体函数名强制模型调用工具。但这会剥夺模型判断的自由一般只用于调试。5.2 工具调用成功但模型说“报错”或者结果不对如果你已经看到模型输出了一串tool_calls但执行时出错先打印日志看是不是参数解析问题。最常见的是json.loads解析失败因为模型返回的arguments偶尔会带多余字符。稳妥一点的做法是尝试解析失败时把原始字符串返回给模型让它自我纠正或者干脆捕获异常再请求一次模型。还有一次我发现代码明明调用了calculator但模型回答时的数字还是错的。原因是我在execute_tools里把结果算出来了却忘了把工具结果消息追加到self.messages里导致模型压根没看到计算结果只能自己瞎猜。这个问题很隐蔽但也最容易修你只要在每次工具执行后打印一下messages列表就能发现问题。5.3 上下文越聊越乱Agent“失忆”了症状是多轮对话后模型开始忘记用户最早的需求。原因是历史太长信息被淹没。解决办法是前面提到的裁剪和摘要。有一个细节很多人忽略系统提示词应该放在messages最前面而且每次请求最好带上你最新的指令。如果你中途改了提示词要把它加在历史消息前面。如果你裁剪历史别把系统提示词裁没了。另外temperature不要调得太高。我之前为了追求“创意”把温度设成1.5结果Agent在调用工具时开始自由发挥生成了不存在的函数名。工具调用类任务建议温度设为0.2到0.5之间准确率更高。6. 进阶方向与个人体会6.1 从玩具到生产力可以扩展的方向你跑通上面这个Agent之后已经掌握了核心循环。接下来可以做这些事给Agent接上更多工具比如搜索、数据库查询、邮件发送、文件读写。工具越多Agent越强大但同时你需要做工具的可控性和权限管理防止它乱调。把工具列表从静态变成动态比如运行时按需加载这样可以让Agent拥有几十个工具而不会浪费token。引入“多Agent协作”一个规划Agent拆解任务分派给专门的工作Agent最后汇总。复杂度指数级上升但确实更接近理想中的智能体。结合检索增强生成RAG让Agent能读取你本地的文档库回答时先检索相关片段再组织答案。这是目前落地最广的方向之一。6.2 给小白的三条走心建议第一先跑通再优化。别一开始就想着做一个“一统天下的超级智能体”先让那个计算器Agent跑起来你才会真正理解其中每个循环步骤的意义。第二调试时一定要打日志尤其是messages和tool_calls你会看到模型每一步的想法远远比猜测“为什么回答不对”要高效。第三看文档比看视频重要。服务商的API文档会明确告诉你参数格式和限制很多时候出问题只是因为你没看那几行字。这个内容后续还可以这样扩展当你的Agent开始调用外部API时一定要注意错误重试和超时控制当你给它接上文件工具时要设计好文件路径白名单当你希望它处理隐私数据时记得在系统提示词里强调脱敏原则。这些都不是空洞的建议都是我踩过坑之后总结出来的。最后再分享一个小技巧每次跑Agent前先问它一句“你打算分几步完成这个任务每步需要什么信息”你会惊讶地发现很多错误在还没执行时就被模型自己纠正了。这就是聪明的Agent和死板的脚本之间的区别。好了动手去写你的第一个Agent吧。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑