
1. 从零搭建AI Agent先搞清楚它到底是个什么东西很多人第一次听到“AI Agent”这个词脑子里浮现的是科幻电影里那种能自己思考、自己行动的机器人。其实没那么玄乎。我做了几个Agent项目之后最直观的理解是AI Agent就是一个能自己决定“下一步做什么”的程序。它跟普通的脚本或者工作流最大的区别在于脚本是你写死了第一步干什么、第二步干什么而Agent是给它一个目标它自己判断该调用什么工具、该查什么资料、该什么时候停下来。那它跟LLM又是什么关系这个问题我被问过不下几十次。简单说LLM大语言模型是Agent的“大脑”但光有大脑不够。你想想一个人只有大脑没有手脚能干活吗Agent就是给LLM装上了手脚——工具系统让它能查天气、能搜网页、能读文件、能执行代码记忆系统让它能记住之前聊过什么规划能力让它能把一个大任务拆成若干小步骤。所以常说的DeepSeek、GPT这类它们本身是LLM是模型不是Agent。你把LLM套上一个循环逻辑再给它配上工具它才变成Agent。这篇文章适合谁看如果你写过一点Python知道变量、函数、循环是怎么回事但没接触过Agent开发那正好。如果你连Python都没装过也没关系我会把安装步骤写得足够细。整篇内容我会用一个具体的例子贯穿做一个能查天气、能算数学、能搜索本地文件的个人助理Agent。这个例子足够简单能让你在一两个小时内跑通但又涵盖了Agent开发的全部核心环节。注意搭建Agent不需要什么高端显卡你手头的笔记本就能跑。我们调用的是云端LLM的API本地只负责逻辑编排。2. 环境准备Python和依赖库的安装2.1 Python安装别在这第一步就卡住Windows用户直接去Python官网下载安装包版本选3.10或3.11都行别选太新的有些库还没跟上。安装的时候有一个关键操作勾选“Add Python to PATH”。我见过太多人装完Python在命令行敲python提示“不是内部或外部命令”就是因为没勾这个。如果你忘了勾重新运行安装程序选“Modify”把那个选项补上就行。macOS用户稍微省心一点系统自带Python但版本可能偏旧。建议用Homebrew装一个brew install python3.11。Linux用户更不用说了包管理器一行命令的事。装完之后验证一下python --version pip --version两条命令都能输出版本号说明环境没问题。如果pip提示找不到试试python -m pip --version这俩是等价的。2.2 虚拟环境别嫌麻烦我强烈建议每个项目都建一个独立的虚拟环境。原因很简单不同项目依赖的库版本可能冲突全局安装迟早出问题。创建和激活的命令python -m venv agent-env # Windows agent-env\Scripts\activate # macOS / Linux source agent-env/bin/activate激活之后命令行前面会出现(agent-env)的标识。以后所有pip install都装在这个环境里跟系统Python隔离开。2.3 核心依赖库就装这几个我们需要的库不多但每一个都有明确用途pip install openai requests python-dotenvopenai调用LLM API的官方库兼容大多数主流模型服务。requests发HTTP请求查天气、搜网页都靠它。python-dotenv管理API密钥等敏感信息后面会详细讲。如果你打算做更复杂的Agent可以再加langchain或者llama-index但第一个Agent我建议手写核心循环别一上来就用框架。框架帮你省了事但也把细节藏起来了出了问题你都不知道从哪查。实操心得装库的时候如果遇到网络超时可以加国内镜像源比如pip install openai -i https://pypi.tuna.tsinghua.edu.cn/simple。这个操作不涉及任何敏感内容纯粹是加速下载。3. 核心设计ReAct模式为什么适合第一个Agent3.1 ReAct是什么用大白话讲ReAct是“Reasoning Acting”的缩写翻译过来就是“推理加行动”。它的核心逻辑是一个循环想一步做一步看结果再想下一步。举个例子你问Agent“北京今天适合穿什么衣服”它的思考过程是这样的思考我需要知道北京今天的天气。行动调用天气查询工具参数是“北京”。观察工具返回“晴15到25摄氏度”。思考温度适中晴天建议穿薄外套或长袖。回答北京今天晴15到25度建议穿薄外套。这个循环可以重复多次直到Agent认为任务完成输出最终答案。为什么选ReAct而不是别的模式因为它最直观最接近人类解决问题的过程而且实现起来不复杂。你不需要搞什么复杂的规划算法就是一个while循环加上LLM的调用。3.2 Agent和普通LLM调用的本质区别普通调用LLM是这样的你发一条消息它回一条消息结束。Agent调用是这样的你发一条消息它可能回一个“我要调用工具”的指令你执行工具把结果喂回去它再回一个“我还要调用另一个工具”你再执行如此往复直到它说“这是我的最终答案”。这个区别看起来小但意义重大。普通LLM只能用它训练时学到的知识Agent可以实时获取外部信息。普通LLM只能聊天Agent可以真正“做事”。3.3 工具系统的设计原则工具就是Agent能调用的函数。设计工具时有几个原则我踩过坑之后总结出来的一个工具只做一件事。别搞一个“万能工具”什么都能干LLM会懵的。参数要少而明确。最好不超过三个参数每个参数的类型和含义都要在描述里写清楚。返回值要简洁。别返回一大坨JSONLLM解析起来费劲还浪费token。错误处理要友好。工具执行失败时返回一个人类能看懂的提示而不是一堆报错堆栈。我们这次做三个工具查天气、算数学、搜本地文件。每个工具对应一个Python函数函数上面写好文档字符串LLM会根据文档字符串来判断什么时候调用哪个工具。4. 实操过程一步步把Agent搭起来4.1 项目结构先搭个架子在开始写代码之前先把目录结构定好。我习惯这样组织my-agent/ ├── .env ├── agent.py ├── tools.py └── requirements.txt.env存放API密钥不提交到代码仓库。agent.py主程序包含Agent循环逻辑。tools.py所有工具函数的定义。requirements.txt依赖列表方便复现环境。4.2 密钥管理这一步绝对不能省把API密钥硬编码在代码里然后不小心传到公开仓库这种事我见过太多次了。正确做法是用.env文件LLM_API_KEY你的密钥 LLM_BASE_URL你的API地址然后在代码里这样读取import os from dotenv import load_dotenv load_dotenv() api_key os.getenv(LLM_API_KEY) base_url os.getenv(LLM_BASE_URL).env文件要加到.gitignore里确保不会被提交。如果你用的是共享电脑还可以考虑用系统环境变量但.env对个人项目来说足够方便。注意任何时候都不要把密钥打印到日志里也不要在报错信息里暴露密钥。有些库的报错会带上请求头信息记得检查一下。4.3 工具函数的实现先写tools.py。每个工具函数都要有清晰的文档字符串这是给LLM看的“说明书”。import requests import os def get_weather(city: str) - str: 查询指定城市的当前天气。 Args: city: 城市名称例如北京、上海。 Returns: 天气描述字符串包含温度和天气状况。 # 这里用一个免费的天气API做示例 # 实际使用时替换成你申请的API try: url fhttps://api.example.com/weather?city{city} resp requests.get(url, timeout5) data resp.json() return f{city}当前天气{data[condition]}温度{data[temp]}摄氏度 except Exception as e: return f查询天气失败{str(e)} def calculate(expression: str) - str: 计算数学表达式。 Args: expression: 数学表达式字符串例如2 3 * 4。 Returns: 计算结果字符串。 try: # 只允许基本数学运算防止代码注入 allowed set(0123456789-*/.() ) if not all(c in allowed for c in expression): return 表达式包含不允许的字符 result eval(expression) return f计算结果{result} except Exception as e: return f计算失败{str(e)} def search_files(keyword: str, directory: str .) - str: 在指定目录下搜索包含关键词的文件。 Args: keyword: 搜索关键词。 directory: 搜索目录默认为当前目录。 Returns: 匹配的文件列表最多返回10个。 matches [] for root, dirs, files in os.walk(directory): for f in files: if keyword.lower() in f.lower(): matches.append(os.path.join(root, f)) if len(matches) 10: break if len(matches) 10: break if matches: return 找到以下文件\n \n.join(matches) return 没有找到匹配的文件这三个函数都很简单但覆盖了Agent工具系统的典型场景网络请求、本地计算、文件操作。4.4 Agent主循环核心中的核心agent.py是整个项目的灵魂。我先把完整代码放出来然后逐段解释。import json import os from openai import OpenAI from dotenv import load_dotenv from tools import get_weather, calculate, search_files load_dotenv() client OpenAI( api_keyos.getenv(LLM_API_KEY), base_urlos.getenv(LLM_BASE_URL) ) # 工具注册表名称 - 函数 TOOLS { get_weather: get_weather, calculate: calculate, search_files: search_files } # 工具描述告诉LLM有哪些工具可用 TOOL_DESCRIPTIONS 你可以使用以下工具 1. get_weather(city: str) - 查询指定城市的天气 2. calculate(expression: str) - 计算数学表达式 3. search_files(keyword: str, directory: str) - 搜索文件 当你需要使用工具时请按以下格式输出 THOUGHT: 你的思考过程 ACTION: 工具名称 ARGS: JSON格式的参数 当你不需要使用工具可以直接回答时请按以下格式输出 THOUGHT: 你的思考过程 ANSWER: 你的最终回答 def run_agent(user_input: str, max_steps: int 5) - str: 运行Agent主循环。 messages [ {role: system, content: TOOL_DESCRIPTIONS}, {role: user, content: user_input} ] for step in range(max_steps): # 调用LLM response client.chat.completions.create( modelyour-model-name, messagesmessages, temperature0 ) reply response.choices[0].message.content print(f--- 第{step1}步 ---) print(reply) # 解析LLM的输出 if ANSWER: in reply: answer reply.split(ANSWER:)[-1].strip() return answer if ACTION: in reply: try: action_line reply.split(ACTION:)[1].split(\n)[0].strip() args_line reply.split(ARGS:)[1].split(\n)[0].strip() args json.loads(args_line) # 执行工具 if action_line in TOOLS: result TOOLS[action_line](**args) else: result f未知工具{action_line} # 把结果喂回给LLM messages.append({role: assistant, content: reply}) messages.append({role: user, content: f工具执行结果{result}}) except Exception as e: messages.append({role: assistant, content: reply}) messages.append({role: user, content: f解析失败{str(e)}请重新输出}) else: # 没有ACTION也没有ANSWER直接返回 return reply return 达到最大步数限制任务未完成 if __name__ __main__: while True: user_input input(\n你) if user_input.lower() in [exit, quit]: break answer run_agent(user_input) print(f\nAgent{answer})这段代码的核心逻辑就是一个for循环最多跑max_steps轮。每一轮把当前的消息历史发给LLMLLM返回文本我们解析文本里有没有ACTION或ANSWER。有ACTION就执行工具把结果追加到消息历史里进入下一轮。有ANSWER就直接返回。4.5 提示词的设计细节TOOL_DESCRIPTIONS这个系统提示词非常关键。它做了三件事告诉LLM有哪些工具可用、每个工具的参数是什么、输出格式应该长什么样。格式约定用THOUGHT、ACTION、ARGS、ANSWER这几个标记是为了方便程序解析。为什么用这种自定义格式而不是OpenAI的function calling因为function calling需要特定的API支持不是所有模型都兼容。自定义文本格式虽然土一点但通用性强你换任何模型都能跑。等你熟悉了基本流程再去用function calling或者框架提供的高级功能会理解得更透彻。实操心得temperature设成0让LLM的输出尽量确定。Agent场景下不需要创造力需要的是稳定和可预测。5. 跑起来之后你会遇到的那些坑5.1 LLM不按格式输出怎么办这是最常见的问题。你明明在提示词里写了“请按THOUGHT/ACTION/ARGS格式输出”但LLM有时候就是自由发挥给你回一段散文。我的处理办法是在解析失败时把错误信息喂回去让它重试messages.append({role: user, content: 你的输出格式不正确请严格按照THOUGHT/ACTION/ARGS或THOUGHT/ANSWER格式重新输出。})通常重试一两次就能纠正。如果反复失败可能是提示词不够明确或者模型能力太弱。换个模型试试。5.2 工具参数解析失败json.loads经常因为LLM输出的JSON格式不对而报错比如多了个逗号、少了引号。可以在提示词里强调“ARGS必须是合法的JSON”同时在代码里做容错try: args json.loads(args_line) except json.JSONDecodeError: # 尝试修复常见问题 args_line args_line.replace(, ) args json.loads(args_line)5.3 Agent陷入死循环有时候Agent会反复调用同一个工具比如一直查天气查个不停。max_steps参数就是防这个的。另外可以在提示词里加一句“如果已经获得足够信息请直接给出ANSWER”。如果还是循环检查一下工具返回的结果是不是让LLM误以为需要再查一次。5.4 常见问题速查表问题现象可能原因解决方法提示“不是内部或外部命令”Python未加入PATH重新安装并勾选Add to PATHpip安装超时网络问题加国内镜像源LLM返回401错误密钥错误或过期检查.env文件中的密钥工具执行结果为空工具函数报错被吞在工具函数里加详细日志Agent不调用工具提示词不够明确强化工具描述和格式要求输出乱码编码问题确保文件保存为UTF-86. 进阶方向这个Agent还能怎么玩6.1 加入记忆系统现在的Agent每次对话都是独立的不记得之前聊过什么。加一个简单的记忆系统就能解决把历史对话存到一个列表里每次调用LLM时把最近N轮对话一起发过去。更复杂的做法是用向量数据库做长期记忆把重要信息存起来需要时检索出来。6.2 接入更多工具工具系统是Agent能力的边界。你可以接入搜索引擎、数据库、邮件发送、日历管理等等。每加一个工具就在TOOLS字典和TOOL_DESCRIPTIONS里注册一下。注意工具多了之后提示词会变长token消耗会增加需要权衡。6.3 多Agent协作一个Agent干不完的活可以拆给多个Agent。比如一个负责规划一个负责执行一个负责检查。每个Agent有自己的提示词和工具集通过消息传递来协作。这个方向就比较复杂了建议先把单Agent玩熟再说。6.4 用框架加速开发当你手写了几遍Agent循环之后可以试试LangChain、LlamaIndex这些框架。它们把工具调用、记忆管理、多Agent协作都封装好了开发效率会高很多。但前提是你理解底层原理不然出了问题只能干瞪眼。我个人在实际操作中的体会是第一个Agent一定要手写哪怕代码丑一点、功能少一点。手写一遍之后你对ReAct循环、工具调用、提示词设计的理解会深刻得多。后面再用框架就是如虎添翼而不是囫囵吞枣。最后分享一个小技巧调试Agent的时候把每一步的LLM输出都打印出来。这样你能清楚地看到Agent在想什么、为什么调用这个工具、为什么给出这个答案。这个习惯帮我省了无数排查时间。