
1. 项目概述从“执行”到“思考”的跨越最近在AI圈子里“智能体”这个词的热度是越来越高。从各种AI应用平台到开发者社区大家似乎都在讨论如何让大模型不止是“一问一答”而是能像人一样自主规划、执行任务。我手头这个项目就是想抛开那些复杂的框架和平台用最直接的Python代码从零开始“手撸”一个具备基础“思考”能力的AI智能体原型。所谓“思考”在这里并不是指科幻电影里的强人工智能而是指让程序具备一种自主决策和任务拆解的能力。比如你给它一个模糊的指令“帮我分析一下最近的销售数据”一个简单的聊天机器人可能只会回复“我无法处理文件”。但一个会“思考”的智能体应该能理解这个指令背后的意图然后自主规划出几个步骤1. 请求用户上传数据文件2. 读取并解析数据3. 调用合适的分析工具比如计算环比、找出Top产品4. 生成一份结构化的报告。这个过程就是智能体区别于普通聊天程序的核心。这个项目的核心价值在于理解其运作机理。市面上已经有Dify、Coze等优秀的智能体平台它们封装得很好拖拖拽拽就能搭建。但如果你想知道智能体到底是怎么“想”的它的“大脑”里发生了什么那么亲手用代码实现一遍是无可替代的学习路径。通过这个项目你将彻底搞懂提示词工程如何驱动决策、任务链是如何被拆解和执行的、以及如何与外部工具API进行交互。无论你是想深入AI应用开发还是仅仅对背后的原理感到好奇这个实践都能让你获益匪浅。2. 核心架构设计构建智能体的“大脑”与“四肢”要构建一个能“思考”的智能体我们需要为其设计一个清晰的架构。这个架构主要包含两个核心部分决策中心大脑和工具集四肢。大脑负责理解、规划和决策四肢负责执行具体的动作。2.1 决策中心基于大语言模型的“指挥官”智能体的“思考”能力本质上来源于大语言模型。我们不会去训练一个模型而是通过API例如DeepSeek、GPT等来调用现成的强大模型。决策中心的核心工作是提示词工程。我们需要设计一套“系统提示词”来塑造智能体的角色和行为模式。这套提示词需要明确告诉模型身份与目标你是一个AI助手目标是帮助用户完成任务。能力与限制你可以通过使用工具来获取信息或执行操作你不能直接知道实时信息或操作外部系统。思考范式你必须遵循“思考-行动-观察”的循环。收到用户请求后先思考需要做什么、分几步、用什么工具然后选择并调用一个工具获得工具返回的结果后观察结果并决定下一步是继续调用工具还是直接给用户最终答案。输出格式你必须用严格的JSON格式来回应以便我的程序能解析你的决策。一个基础的提示词设计如下你是一个任务执行AI助手。你的决策必须遵循严格的JSON格式。 格式说明 { “thought”: “你的思考过程分析当前状况和下一步计划。”, “action”: { “name”: “要执行的动作名称必须是以下工具之一[‘search_web’ ‘calculate’ ‘final_answer’]”, “args”: {“key”: “value”} // 动作所需的参数 } } 工具列表 - search_web(query): 执行网络搜索。参数query搜索关键词。 - calculate(expression): 执行数学计算。参数expression数学表达式如’(53)*2’。 - final_answer(answer): 向用户给出最终答案。参数answer你的回答文本。 工作流程 1. 用户提出请求。 2. 你根据请求在“thought”中分析。 3. 你决定一个“action”。如果需要多步就一步一步来。 4. 执行action后你会收到“observation”工具执行结果。 5. 基于observation你再次进入“thought-action”循环直到任务完成使用final_answer。 现在开始处理用户请求{user_input}这个提示词将大模型变成了一个遵守固定规则的决策引擎其输出是结构化的数据而非自由文本这是我们程序能与之对话的基础。2.2 工具集赋予智能体“动手”能力智能体不能只“想”不“做”。工具集就是它可调用的函数集合。每个工具都是一个Python函数执行特定的、模型自身无法完成的任务比如计算、查询数据库、调用第三方API等。在我们的原型里先实现两个简单的工具import requests import json import math def search_web(query: str) - str: 模拟网络搜索实际应用中需接入真实搜索API # 此处为演示返回模拟结果。真实场景可调用Serper、Google Search等API。 mock_results { “Python安装”: “访问python.org官网下载对应操作系统的安装包按照向导安装即可。”, “今天天气”: “北京晴15-25摄氏度上海多云18-28摄氏度。”, “圆周率”: “圆周率π是一个数学常数约等于3.14159。” } return mock_results.get(query, f”未找到关于‘{query}’的模拟信息。“) def calculate(expression: str) - str: 执行安全的数学表达式计算 # 警告实际生产中直接eval极其危险这里仅作演示。 # 应采用ast.literal_eval或专用数学表达式解析库如numexpr。 try: # 极其简化的安全过滤切勿用于生产环境 if any(c for c in expression if c in “__” or c.isalpha() and c not in ‘pi e’): return “错误表达式包含不安全字符。” result eval(expression, {“__builtins__”: None}, {“pi”: math.pi, “e”: math.e}) return str(result) except Exception as e: return f”计算错误{e}” def final_answer(answer: str) - str: 这是一个特殊工具用于终止循环并返回最终答案给用户 return answer注意calculate函数中使用了eval()这在实际项目中是高危操作极易导致代码注入漏洞。此处仅用于原理演示。真实项目必须使用ast.literal_eval进行严格限制或使用numexpr这类安全的库来评估数学表达式。工具函数的设计原则是功能单一、接口明确、返回字符串。智能体的“大脑”通过工具名称和参数来调用它们。3. 核心循环实现让智能体“动”起来有了“大脑”提示词大模型和“四肢”工具集我们需要一个核心驱动循环将它们串联起来这就是经典的ReAct (Reasoning Acting)循环的简化版。3.1 主循环逻辑与状态管理这个循环是智能体的“心跳”它不断重复“思考-行动-观察”的过程直到任务完成。我们需要管理一个“状态”记录当前的对话历史、工具调用结果等。class SimpleAgent: def __init__(self, api_key: str, model: str “deepseek-chat”): self.api_key api_key self.model model self.base_url “https://api.deepseek.com/v1/chat/completions” # 示例URL self.conversation_history [] # 记录对话和观察用于上下文 def call_llm(self, messages: list) - dict: 调用大模型API headers { “Authorization”: f”Bearer {self.api_key}”, “Content-Type”: “application/json” } data { “model”: self.model, “messages”: messages, “temperature”: 0.1, # 低温度使输出更确定、更遵循格式 “max_tokens”: 1000 } try: response requests.post(self.base_url, headersheaders, jsondata, timeout30) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: # 处理网络或API错误 print(f”API调用失败{e}”) if hasattr(e, ‘response’) and e.response is not None: print(f”错误详情{e.response.text}”) # 返回一个模拟响应防止程序崩溃便于演示 return {“choices”: [{“message”: {“content”: ‘{“thought”: “API连接失败” “action”: {“name”: “final_answer” “args”: {“answer”: “网络服务暂时不可用请稍后再试。”}}}}’}]} def parse_llm_response(self, response_text: str) - dict: 解析模型返回的JSON字符串 try: # 模型返回的内容可能包含markdown代码块标记需要清理 cleaned_text response_text.strip() if cleaned_text.startswith(‘json’): cleaned_text cleaned_text[7:] if cleaned_text.endswith(‘’): cleaned_text cleaned_text[:-3] cleaned_text cleaned_text.strip() return json.loads(cleaned_text) except json.JSONDecodeError as e: print(f”JSON解析失败原始响应{response_text}”) # 解析失败时返回一个让智能体结束的指令 return { “thought”: “我的响应格式出错了。”, “action”: { “name”: “final_answer”, “args”: {“answer”: “抱歉我处理您的请求时出现了内部错误。”} } } def run(self, user_input: str, max_steps: int 10) - str: 运行智能体主循环 print(f”用户{user_input}”) # 初始化系统提示词包含工具定义和流程规则 system_prompt “””你是一个任务执行AI助手...此处填入2.1节完整的提示词“””.format(user_inputuser_input) messages [{“role”: “system” “content”: system_prompt}] self.conversation_history messages.copy() step 0 while step max_steps: step 1 print(f”\n—- 第{step}步 —-“) # 1. 思考调用LLM获取决策 llm_response self.call_llm(self.conversation_history) llm_message llm_response[“choices”][0][“message”] llm_content llm_message[“content”] print(f”AI思考{llm_content}”) # 将AI的回复加入历史维持上下文 self.conversation_history.append({“role”: “assistant” “content”: llm_content}) # 2. 解析决策 decision self.parse_llm_response(llm_content) thought decision.get(“thought” “无思考内容”) action decision.get(“action” {}) action_name action.get(“name”) action_args action.get(“args” {}) print(f”解析结果思考 - {thought} 行动 - {action_name} 参数 - {action_args}”) # 3. 执行行动 if action_name “final_answer”: final_msg action_args.get(“answer” “任务完成。”) print(f”最终答案{final_msg}”) return final_msg # 映射并调用工具 tool_map { “search_web”: search_web, “calculate”: calculate, } if action_name in tool_map: tool_func tool_map[action_name] try: # 将参数字典展开为关键字参数传入函数 observation tool_func(**action_args) except TypeError as e: observation f”工具调用参数错误{e}” except Exception as e: observation f”工具执行异常{e}” else: observation f”错误未知的工具名称‘{action_name}’。可用工具有{list(tool_map.keys())}” print(f”工具执行结果观察{observation}”) # 4. 观察将结果作为新一轮的“用户消息”加入历史驱动下一轮思考 observation_msg f”上一次你决定执行 {action_name} 结果是{observation}。请根据这个结果继续思考下一步。” self.conversation_history.append({“role”: “user” “content”: observation_msg}) # 循环超过最大步数强制结束 return “任务过于复杂或陷入循环已终止。”这个SimpleAgent类封装了整个智能体的生命周期。run方法中的while循环就是核心。它每次迭代都请求模型思考 - 解析JSON决策 - 执行对应工具 - 将结果反馈给模型。直到模型决定调用final_answer工具循环终止。3.2 关键参数解析与配置心得在实现过程中有几个关键参数和配置点直接影响智能体的表现系统提示词的温度temperature在call_llm函数中我们设置了“temperature”: 0.1。这个参数控制模型输出的随机性。范围通常在0到2之间。值越低如0.1输出越确定、可预测更严格地遵循指令格式适合需要稳定JSON输出的场景。值越高输出越有创造性但也更可能不按格式来。对于任务型智能体低温度是首选。最大步数max_steps在run方法中我们设置了max_steps10。这是一个重要的安全阀防止智能体陷入无限循环或执行过于冗长的任务链。如果10步之后还没得到final_answer就强制终止。在实际应用中你可以根据任务复杂度调整这个值并设计更优雅的超时处理比如提示用户任务可能太复杂。错误处理与鲁棒性代码中包含了多处try...except块。这是智能体能否稳定运行的关键。API可能超时、返回非JSON内容、工具可能出错。良好的错误处理能将异常转化为智能体能理解的“观察”observation让它有机会调整策略而不是让整个程序崩溃。例如在parse_llm_response中如果JSON解析失败我们会返回一个导向final_answer的决策优雅地结束会话并告知用户。实操心得调试智能体时一定要把每一步的thought、action和observation打印出来。这是理解你智能体“思维过程”的唯一窗口。很多时候效果不好不是逻辑问题而是提示词没写清楚或者模型的输出格式有轻微偏差比如多了个换行符导致JSON解析失败。逐步打印日志能帮你快速定位问题层。4. 运行示例与效果分析让我们用写好的智能体跑几个例子看看它是如何“思考”的。首先初始化智能体你需要替换成自己的API Key# 注意以下为示例你需要使用有效的API Key # 为安全起见建议从环境变量读取API Key import os # api_key os.getenv(“DEEPSEEK_API_KEY”) api_key “your_api_key_here” # 临时测试用切勿提交到代码仓库 agent SimpleAgent(api_keyapi_key, model“deepseek-chat”)示例一处理复合问题result agent.run(“先帮我计算一下(1527)除以6等于多少然后再搜索一下‘Python安装’的步骤。”) print(“\n 最终回复 \n”, result)可能的执行日志输出用户先帮我计算一下(1527)除以6等于多少然后再搜索一下‘Python安装’的步骤。 —- 第1步 —- AI思考{“thought”: “用户提出了一个包含两个连续任务的请求。首先需要计算数学表达式(1527)/6然后需要搜索‘Python安装’。我应该按顺序执行。第一步使用calculate工具。”, “action”: {“name”: “calculate”, “args”: {“expression”: “(1527)/6”}}} 解析结果思考 - 用户提出了一个包含两个连续任务的请求… 行动 - calculate 参数 - {‘expression’: ‘(1527)/6’} 工具执行结果观察7.0 —- 第2步 —- AI思考{“thought”: “计算结果是7.0。第一步任务完成。现在开始第二步任务搜索‘Python安装’。使用search_web工具。”, “action”: {“name”: “search_web”, “args”: {“query”: “Python安装”}}} 解析结果思考 - 计算结果是7.0。第一步任务完成… 行动 - search_web 参数 - {‘query’: ‘Python安装’} 工具执行结果观察访问python.org官网下载对应操作系统的安装包按照向导安装即可。 —- 第3步 —- AI思考{“thought”: “两个任务都已完成。计算结果是7.0搜索得到了Python安装的步骤。现在可以整合信息给用户最终答案了。”, “action”: {“name”: “final_answer”, “args”: {“answer”: “计算结果是7.0。\\n\\n关于Python安装访问python.org官网下载对应操作系统的安装包按照向导安装即可。”}}} 解析结果思考 - 两个任务都已完成… 行动 - final_answer 参数 - {‘answer’: ‘计算结果是7.0…’} 最终答案计算结果是7.0。 关于Python安装访问python.org官网下载对应操作系统的安装包按照向导安装即可。效果分析智能体成功识别了这是一个序列任务。它没有试图一次性完成所有事而是先思考决定第一步做什么计算执行后获得观察结果再基于此思考第二步搜索最后整合信息给出答案。这完美体现了“思考-行动-观察”的循环价值。示例二处理无法直接回答的问题result agent.run(“今天的天气怎么样”) print(“\n 最终回复 \n”, result)可能的执行日志输出用户今天的天气怎么样 —- 第1步 —- AI思考{“thought”: “用户询问实时天气。我无法直接获取实时信息但我有search_web工具可以模拟搜索天气信息。我应该使用这个工具。”, “action”: {“name”: “search_web”, “args”: {“query”: “今天天气”}}} 解析结果思考 - 用户询问实时天气… 行动 - search_web 参数 - {‘query’: ‘今天天气’} 工具执行结果观察北京晴15-25摄氏度上海多云18-28摄氏度。 —- 第2步 —- AI思考{“thought”: “搜索工具返回了一些模拟的天气信息。由于用户没有指定城市我提供了示例信息。现在可以将这个信息作为答案返回给用户。”, “action”: {“name”: “final_answer”, “args”: {“answer”: “根据模拟搜索的结果示例数据北京晴15-25摄氏度上海多云18-28摄氏度。请注意要获取精确的实时天气请使用专业的天气应用或网站。”}}} 解析结果思考 - 搜索工具返回了一些模拟的天气信息… 行动 - final_answer 参数 - {‘answer’: ‘根据模拟搜索的结果…’} 最终答案根据模拟搜索的结果示例数据北京晴15-25摄氏度上海多云18-28摄氏度。请注意要获取精确的实时天气请使用专业的天气应用或网站。效果分析智能体知道自己“不能直接知道天气”但它知道自己“能通过工具搜索”。于是它规划了使用search_web工具的步骤并在最终答案中诚实地说明了信息的来源和局限性。这展示了智能体对自身能力边界的认知和利用工具扩展能力的过程。5. 进阶优化与扩展方向我们实现了一个基础但完整的智能体原型。要让其更实用、更强大可以从以下几个方向进行扩展5.1 增强工具能力与安全性接入真实API将search_web替换为真实的搜索引擎API如Serper Dev、Google Custom Search JSON API。为calculate函数实现一个安全的表达式求值器使用numexpr库。增加更多工具文件操作读取用户上传的CSV/Excel文件并进行分析。代码执行在一个安全的沙箱环境中执行简单的Python代码片段需极度谨慎。数据库查询连接数据库执行SQL查询使用参数化查询防止注入。网络请求调用其他RESTful API获取股票价格、新闻摘要等。工具描述自动化目前工具列表是手写在提示词里的。可以写一个函数自动收集所有工具函数的名称、描述和参数schema动态生成提示词部分这样新增工具时就不必手动修改提示词了。5.2 改进决策与记忆机制短期记忆上下文管理我们当前的conversation_history会不断增长可能很快超过模型的最大上下文长度如DeepSeek V4-Pro的1048565 tokens。需要实现一个上下文窗口管理策略比如只保留最近N轮对话或者对历史对话进行智能摘要Summarization。长期记忆向量数据库为智能体配备一个“笔记本”。可以将重要的对话内容、工具执行结果转换成向量存入像Chroma、Pinecone这样的向量数据库。当处理新任务时先进行向量相似度搜索找到相关的历史记忆从而做出更连贯、个性化的决策。复杂任务规划对于多步骤的复杂任务可以引入更高级的规划器。例如让模型先输出一个完整的任务计划Task List然后再逐步执行和勾选。这比一步一想的ReAct循环更适合宏观规划。5.3 提升稳定性与用户体验输出格式加固模型有时会不按JSON格式输出。除了代码中的清理逻辑还可以在提示词中更加强调格式甚至采用支持JSON Mode的API如果所用模型支持。另一种方案是使用“输出解析器”Output Parser例如LangChain提供的Pydantic解析器它能以更强的约束引导模型输出。错误处理与重试当工具调用失败或模型输出格式错误时不应直接结束。可以设计一个重试机制比如将错误信息反馈给模型让它“反思”并尝试另一种方案最多重试3次。人机交互与确认对于高风险操作如删除文件、发送邮件可以让智能体在执行前先调用一个ask_for_confirmation工具将计划的操作呈现给用户等待用户明确确认后再执行。6. 常见问题与排查实录在开发和调试这个智能体的过程中我踩过不少坑。这里把一些典型问题和解决方法记录下来希望能帮你节省时间。问题现象可能原因排查与解决思路API调用返回400错误提示‘type’ must be in [“enabled”, “disabled”, “auto”]请求体JSON数据的某个字段值不符合API要求。可能是stream,safe_mode等字段。1. 仔细检查API文档确认每个字段的可选值。2. 在代码中打印出发送的data字典与文档示例逐字段对比。3. 暂时简化请求体只保留必填字段model, messages逐步添加可选字段测试。API返回400 ‘this model‘s maximum context length is ... tokens发送的对话历史conversation_history总token数超过了模型限制。1. 实现上下文管理限制历史消息条数或计算token数并截断最早的消息。2. 对长历史进行摘要压缩只保留核心信息。3. 使用模型时注意其上下文窗口大小选择适合的模型。JSON解析失败报JSONDecodeError1. 模型输出不符合JSON格式如多了额外文本。2. 输出包含Markdown代码块符号\json。1. 在parse_llm_response函数中加强清洗逻辑如我们代码所示。2. 在提示词中强烈要求“只输出纯JSON不要有任何其他解释或标记”。3. 降低API的temperature参数减少输出随机性。智能体陷入死循环不断重复同一个工具1. 工具返回的观察结果未能给模型提供新的、有效的决策信息。2. 任务本身无法由现有工具完成模型陷入困惑。1. 检查工具返回的观察结果是否清晰、有意义。确保失败时有明确的错误信息。2. 在提示词中增加约束“如果一个工具连续调用两次且结果相同应尝试其他方法或直接给出最终答案。”3. 设置max_steps强制终止循环。工具调用时报TypeError: func() got an unexpected keyword argument ‘xxx’模型输出的action参数args的键名与工具函数定义的参数名不匹配。1. 在提示词中明确列出每个工具所需的精确参数名。2. 在工具调用代码中加入参数映射或默认值处理增强鲁棒性。3. 打印出action_args和工具函数的参数列表进行对比调试。智能体“忘记”了最初的用户请求在长循环中最初的用户请求被压到了上下文很靠后的位置模型可能注意力分散。1. 在每一轮给模型的提示中都重新附加或强调最初的用户请求。2. 实现短期记忆摘要将长对话压缩成“用户想做什么”的简短描述放在系统提示中。一个典型的调试过程当我第一次运行智能体时它经常在第三步之后输出一些奇怪的文本而不是JSON。我打开了每一步的详细日志发现模型在第二轮思考后输出的内容开头有“好的根据上一步的结果...”这样的自然语言然后才是JSON。这污染了JSON解析。解决方法就是在parse_llm_response函数中加入更强大的文本清洗并在系统提示词的开头用非常醒目的方式强调“你必须且只能输出一个合法的JSON对象不要有任何其他前缀、后缀或解释性文字。” 通常加粗或全大写能引起模型更多注意。手撸一个智能体的过程就像在教一个天赋异禀但缺乏常识的孩子如何一步步解决问题。你需要用最精确的语言提示词告诉它规则为它准备好工具函数并设计好一个不会让它跑偏的流程循环。这个过程充满挑战但当你看到它按照你的设计有条不紊地拆解并完成一个复杂任务时那种成就感是直接用现成平台无法比拟的。这个原型只是一个起点你可以沿着上面提到的扩展方向把它打造成一个真正能处理你日常工作流的得力助手。