三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

基于LangChain与DeepSeek从零构建AI编程智能体:原理、工具与实战

基于LangChain与DeepSeek从零构建AI编程智能体:原理、工具与实战

1. 项目概述:为什么现在要自己动手做AI编程智能体?

最近几个月,AI编程领域的热度几乎被“智能体”这个词给承包了。无论是技术社区还是产品发布会,你都能看到LangChain、Claude Code、DeepSeek这些名字频繁出现。作为一个在软件开发一线摸爬滚打了十多年的老码农,我最初看到这些新概念时,第一反应也是有点懵:这不就是给大语言模型(LLM)套了层壳吗?有什么新鲜的?

但当我真正沉下心来,尝试用传统的API调用方式去让模型帮我完成一个稍微复杂点的编程任务时,问题就暴露出来了。比如,我想让模型帮我写一个包含用户认证、数据增删改查的完整后端模块。直接提问的结果往往是:模型能给出漂亮的代码片段,但文件结构是乱的,依赖没说明,甚至不同代码块之间逻辑都对不上。它缺乏一个“思考-执行-验证”的循环能力,更像是一个有问必答、但不管落地的“顾问”。

而这,恰恰就是“AI编程智能体”要解决的核心问题。它不是一个简单的聊天机器人,而是一个具备一定自主性的数字助手。你可以把它理解为一个初级程序员,你给它一个模糊的需求(比如“搭建一个博客系统的评论模块”),它能自己拆解任务:先设计数据库表结构,然后写模型层代码,接着是API接口,最后可能还会写点简单的单元测试。它会调用代码解释器来验证语法,会检索文档来确认API用法,如果出错了,它还能根据错误信息调整策略,重新尝试。

所以,这个系列文章的目的很明确:我们不谈空泛的概念,就实实在在地从零开始,手把手搭建一个能真正干活的AI编程智能体。我会用最主流的开源框架LangChain作为骨架,结合当前性价比和性能都备受瞩目的DeepSeek模型作为“大脑”,一步步把它构建起来。你会看到智能体是如何理解任务、使用工具(比如搜索、读写文件、执行命令)、并持续迭代的。无论你是想提升自己的开发效率,还是对AI应用开发感兴趣,这个实践过程都会让你对“智能体”有一个透彻的、接地气的理解。

2. 核心组件选型:LangChain + DeepSeek,为什么是它们?

搭建一个智能体,就像组装一台电脑,你需要选对主板(框架)和CPU(模型)。市面上选择很多,但经过反复对比和实测,我最终锁定了LangChain作为框架,DeepSeek系列模型作为核心LLM。这个组合不是拍脑袋决定的,下面我详细拆解一下背后的考量。

2.1 框架之争:为什么是LangChain,而不是LangGraph或Dify?

首先明确一点,LangChain和LangGraph不是互斥关系,而更像是“基础库”和“高级框架”的关系。LangGraph是建立在LangChain之上的,专门用于构建有状态、多步骤的智能体工作流。

  • LangChain:它的定位是“构建LLM应用的工具包”。它提供了极其丰富的模块化组件,比如与各种模型对接的LLM接口、管理对话历史的Memory、定义工具的Tool类、以及组织调用链的Chain。它的优势在于灵活和透明。你可以清晰地控制智能体的每一个环节,从提示词(Prompt)模板的编写,到工具调用的逻辑,再到输出的解析,全部尽在掌握。这对于学习和理解智能体的底层原理至关重要。官方文档详尽,社区庞大,遇到问题基本都能找到解决方案。
  • LangGraph:它引入了“图”的概念,特别适合描述那些有循环、分支、并行等复杂逻辑的智能体。比如,一个智能体可能需要先判断任务类型,再决定调用A工具还是B工具,然后根据工具返回的结果决定是继续还是结束。用LangGraph来描绘这种流程非常直观。但对于我们初识智能体的目标来说,它引入了一定的抽象复杂度。
  • Dify/Coze等平台:这类属于低代码/无代码的AI应用平台。它们优点是快,拖拖拽拽就能做出一个能用的智能体。但缺点是黑盒化,你很难深入定制底层逻辑,也无法将智能体无缝集成到你自己的代码工程里。学习它们,你更多是在学习某个平台的使用方法,而非智能体本身的技术。

我的选择理由:从零学习,核心目标是“理解原理”和“获得完全的控制权”。因此,从最基础、最模块化的LangChain开始,是打下坚实根基的最佳路径。理解了LangChain,再去看LangGraph会觉得水到渠成。而平台类工具,更适合在明确需求后快速构建原型或轻量级应用。

2.2 模型选择:DeepSeek何以成为开源新贵?

模型是智能体的“大脑”,它的成本、能力和稳定性直接决定智能体的表现。OpenAI的GPT系列固然强大,但API费用和网络稳定性是长期绕不开的痛点。而DeepSeek(特别是DeepSeek-V3和最新的V4 Flash)在近期以其惊人的性能价格比,成为了开源社区和许多企业的首选。

  1. 极高的性价比:DeepSeek API的定价策略极具侵略性,相同性能下成本远低于主流商用API。这对于需要频繁调用、进行长上下文推理的编程智能体来说,意味着你可以用更低的成本进行大量的实验和迭代。
  2. 出色的代码能力:DeepSeek系列模型在多项代码生成基准测试(如HumanEval, MBPP)中名列前茅,其对编程语言语法、逻辑的理解和生成能力,已经达到了顶尖水平,完全足以胜任编程助手的工作。
  3. 友好的上下文长度:支持128K甚至更长的上下文,这意味着智能体可以记住更长的对话历史和多轮工具调用结果,对于处理复杂的、多步骤的编程任务至关重要。
  4. 灵活的部署方式:除了使用官方API,你还可以通过开源库(如ollama,vllm,lmstudio)在本地或自己的服务器上部署DeepSeek模型。这为数据敏感或需要离线使用的场景提供了可能。

实操心得:模型版本选择:对于编程智能体,我强烈推荐使用deepseek-chatdeepseek-coder系列的最新版本。deepseek-chat通用对话能力强,对指令的理解更精准;deepseek-coder则在代码专项上更精炼。起步阶段,使用官方API的deepseek-chat是平衡成本与效果的最佳选择。后续我们会演示如何配置。

2.3 环境与工具准备:你的编程战场

工欲善其事,必先利其器。在写第一行代码之前,我们需要把环境搭建好。这里我会给出一个清晰、可复现的清单。

1. 基础环境:

  • Python:版本 >= 3.10。这是LangChain和大多数AI库的主要语言环境。
  • 包管理工具:推荐使用pip,但为了环境隔离,我强烈建议使用condavenv创建虚拟环境。
    # 使用 venv 创建虚拟环境 python -m venv ai_agent_env # 激活环境 (Linux/macOS) source ai_agent_env/bin/activate # 激活环境 (Windows) ai_agent_env\Scripts\activate

2. 核心依赖安装:在激活的虚拟环境中,运行以下命令安装核心库。我们不会一次性安装所有,而是按需引入,保持环境干净。

pip install langchain langchain-community langchain-core
  • langchain: 核心框架。
  • langchain-community: 包含大量第三方集成(工具、模型等)。
  • langchain-core: 基础抽象和运行时。

3. 模型接入依赖:我们要通过API调用DeepSeek,所以需要安装OpenAI SDK(因为DeepSeek API兼容OpenAI格式)。

pip install openai

4. 代码编辑器/IDE:

  • VSCode:无疑是当前最流行的选择,拥有海量的AI和Python插件。我们后续会提到的“Claude Code”本质上是VSCode的一个扩展,它集成了Claude模型的能力。但我们的目标是自己构建智能体,因此更推荐使用纯净的VSCode,搭配Python、Jupyter等基础插件即可。
  • PyCharm:专业Python IDE,对代码导航、重构支持更好,社区版免费。

注意事项:尽量避免在全局Python环境中直接安装。使用虚拟环境可以避免不同项目间的依赖冲突,这是Python开发的一个好习惯。另外,请确保你的网络环境能够稳定访问DeepSeek的API服务(api.deepseek.com)。

3. 智能体基石:与大模型(DeepSeek)建立连接

智能体的一切思考都源于大模型。因此,我们的第一步就是教会LangChain如何与DeepSeek对话。这里的关键是理解LangChain的ChatModel抽象层。

3.1 获取并安全存储API Key

首先,你需要去DeepSeek的官网注册账号并获取API Key。这个过程很简单,和大多数云服务类似。

安全第一:永远不要将API Key硬编码在代码中!常见的做法是将其存储在环境变量里。

# 在终端中设置环境变量 (临时,重启后失效) export DEEPSEEK_API_KEY="your_api_key_here" # 或者,更推荐的做法是写入shell配置文件(~/.bashrc, ~/.zshrc)或使用.env文件

在Python中,我们可以使用os模块或python-dotenv库来读取。

pip install python-dotenv

创建一个名为.env的文件在项目根目录,内容如下:

DEEPSEEK_API_KEY=your_actual_deepseek_api_key

然后在你的Python代码开头加载它:

from dotenv import load_dotenv import os load_dotenv() # 加载 .env 文件中的所有变量 api_key = os.getenv("DEEPSEEK_API_KEY") if not api_key: raise ValueError("请在 .env 文件中设置 DEEPSEEK_API_KEY")

3.2 初始化DeepSeek聊天模型

LangChain为兼容OpenAI API格式的模型提供了统一的接口ChatOpenAI。DeepSeek的API端点(base_url)与OpenAI不同,我们需要在初始化时指定。

from langchain_openai import ChatOpenAI # 初始化DeepSeek模型 llm = ChatOpenAI( model="deepseek-chat", # 指定模型名称 openai_api_key=api_key, # 传入你的API Key openai_api_base="https://api.deepseek.com", # 指定DeepSeek的API基础地址 temperature=0.1, # 控制创造性,编程任务需要较低的值以保证确定性 max_tokens=2048, # 单次回复的最大token数 ) # 进行一次简单的测试对话 from langchain_core.messages import HumanMessage response = llm.invoke([HumanMessage(content="你好,请用Python写一个函数计算斐波那契数列的第n项。")]) print(response.content)

参数详解:

  • model: 这里填写deepseek-chat。如果你想使用代码专用模型,可以尝试deepseek-coder(如果API支持)。
  • openai_api_base:这是关键!必须指向DeepSeek的端点https://api.deepseek.com。如果指向默认的OpenAI端点,调用会失败。
  • temperature: 取值范围0~2。值越低,输出越确定、保守;值越高,输出越随机、有创造性。对于代码生成,我通常设置在0.1到0.3之间,以平衡准确性和一点点探索性。
  • max_tokens: 限制模型单次响应的长度。根据任务复杂度调整,对于代码生成,2048或4096通常足够。

3.3 与原生API调用的区别:为什么需要LangChain?

你可能会有疑问:我直接用requests库发HTTP请求不也一样吗?为什么要多一层LangChain?让我们看一个直接调用和通过LangChain调用的简单对比。

原生API调用(示例):

import requests import json url = "https://api.deepseek.com/chat/completions" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } data = { "model": "deepseek-chat", "messages": [{"role": "user", "content": "写一个Python的hello world。"}], "temperature": 0.1 } response = requests.post(url, headers=headers, json=data) result = response.json() print(result['choices'][0]['message']['content'])

LangChain调用(如前所示):看起来LangChain的代码似乎更复杂?但它的优势在于抽象和组合

  1. 统一的接口:无论后端是DeepSeek、GPT还是本地部署的Ollama模型,你与llm对象交互的方式(invoke,stream)都是一样的。更换模型时,你只需要修改初始化参数,业务逻辑代码几乎不用动。
  2. 消息历史管理:LangChain提供了ChatMessageHistory等组件,能轻松管理多轮对话的上下文,这是构建对话式智能体的基础。
  3. 链式调用(Chain)的基础llm对象可以轻松地与其他组件(如提示词模板、输出解析器、工具)连接起来,形成复杂的处理流水线。这是构建智能体的核心模式。

实操心得:流式输出:在处理长文本生成(如生成一篇文档或长代码)时,使用流式输出可以极大改善用户体验,无需等待全部生成完毕。LangChain对此有很好的支持。

for chunk in llm.stream([HumanMessage(content="写一个长故事")]): print(chunk.content, end="", flush=True) # 逐块打印

在构建智能体时,将关键步骤(如“思考中”、“正在调用工具”、“生成代码”)以流式或日志形式输出,能让整个过程更加透明和可调试。

4. 赋予智能体“手脚”:工具(Tools)的定义与使用

一个只会思考的模型,只是一个知识库。智能体之所以“智能”,是因为它能利用“工具”来影响外部世界。对于编程智能体来说,工具就是它的手脚,可以是执行Shell命令、读写文件、搜索网页、查询数据库等等。

4.1 理解LangChain中的Tool

在LangChain中,一个Tool本质上是一个可被模型调用的函数。它需要三个核心部分:

  1. 名称(name):模型识别工具的唯一标识。
  2. 描述(description):用自然语言描述这个工具的功能。这部分至关重要!模型完全依靠描述来决定在什么情况下调用哪个工具。描述必须清晰、准确,说明输入是什么、输出是什么、用来解决什么问题。
  3. 执行函数(func):实际的Python函数,包含工具要执行的逻辑。

4.2 构建编程智能体的核心工具集

下面我们来定义几个对编程智能体至关重要的工具。

工具一:执行Shell命令这是智能体与本地开发环境交互的最直接方式,可以运行脚本、安装包、启动服务等。

from langchain.tools import Tool import subprocess import sys def execute_shell_command(command: str) -> str: """ 在安全环境下执行Shell命令并返回结果。 参数: command (str): 要执行的Shell命令字符串。 返回: str: 命令的标准输出和标准错误。 """ try: # 使用subprocess.run来安全地执行命令,并设置超时 result = subprocess.run( command, shell=True, capture_output=True, text=True, timeout=30, # 设置超时,防止长时间运行 cwd="./workspace" # 指定工作目录,隔离智能体操作范围 ) output = f"STDOUT:\n{result.stdout}\n" if result.stderr: output += f"STDERR:\n{result.stderr}\n" output += f"返回码: {result.returncode}" return output except subprocess.TimeoutExpired: return "错误:命令执行超时(30秒)。" except Exception as e: return f"执行命令时发生异常:{str(e)}" # 将函数包装成Tool shell_tool = Tool( name="execute_shell", func=execute_shell_command, description="""在本地工作区的Shell环境中执行命令。用于运行脚本、安装Python包(pip install)、查看目录(ls)、启动服务等。 输入应该是一个完整的、可执行的命令字符串。例如:'pip install requests' 或 'python -m http.server 8000'。 """ )

工具二:读写文件智能体需要能查看现有代码、创建新文件或修改文件。

import os def read_file(file_path: str) -> str: """读取指定路径文件的内容。""" try: # 限制文件读取范围,防止访问系统文件 safe_path = os.path.join("./workspace", file_path.lstrip("/")) if not os.path.exists(safe_path): return f"错误:文件 '{safe_path}' 不存在。" with open(safe_path, 'r', encoding='utf-8') as f: return f.read() except Exception as e: return f"读取文件时出错:{str(e)}" def write_file(file_path: str, content: str) -> str: """将内容写入指定路径的文件。如果文件存在则覆盖。""" try: safe_path = os.path.join("./workspace", file_path.lstrip("/")) # 确保目录存在 os.makedirs(os.path.dirname(safe_path), exist_ok=True) with open(safe_path, 'w', encoding='utf-8') as f: f.write(content) return f"成功写入文件:{safe_path}" except Exception as e: return f"写入文件时出错:{str(e)}" read_tool = Tool( name="read_file", func=read_file, description="读取工作区内指定文件的内容。输入是文件的相对路径(例如:'src/main.py')。" ) write_tool = Tool( name="write_file", func=write_file, description="将内容写入工作区内的指定文件。输入应该是一个JSON字符串,包含'file_path'和'content'两个键。例如:'{{\"file_path\": \"test.py\", \"content\": \"print(\\\"hello\\\")\"}}'。" )

工具三:搜索网络(可选)当智能体需要查找最新的文档、库用法或解决特定错误时,网络搜索工具非常有用。这里以模拟搜索为例,实际可以接入Serper、Google Search等API。

# 假设我们有一个模拟搜索函数,实际应用中需替换为真正的搜索API def mock_web_search(query: str) -> str: """模拟网络搜索,返回相关摘要。实际应接入Serper/Google Search API。""" # 这里只是一个占位符 return f"模拟搜索关键词 '{query}' 的结果:\n- 相关文档1: ...\n- Stack Overflow解答: ...\n- 官方指南: ..." search_tool = Tool( name="web_search", func=mock_web_search, description="在互联网上搜索信息。当需要查找未知的API文档、解决特定错误代码或获取最新技术信息时使用。输入是一个搜索查询字符串。" )

4.3 工具集成的安全与边界

安全是重中之重!赋予AI执行命令和读写文件的能力是强大的,也是危险的。我们必须设立边界:

  1. 工作目录隔离:所有文件操作都限制在./workspace目录下。使用os.path.join和路径检查,防止智能体通过../../../这样的路径逃逸到系统目录。
  2. 命令白名单/黑名单:在生产环境中,应对execute_shell工具执行的命令进行过滤。禁止执行rm -rf /format等危险命令。可以维护一个允许的命令前缀列表(如['pip install', 'python', 'ls', 'cat'])。
  3. 超时控制:对执行时间长的命令(如复杂编译)设置超时,防止阻塞。
  4. 权限最小化:运行智能体的系统用户应具有最小必要权限,不要使用root或管理员账户。

注意事项:在开发调试阶段,你可以先放宽限制,但心中必须要有这根弦。一个有效的测试方法是,故意让智能体去执行“请列出系统根目录文件”这样的指令,观察你的安全机制是否生效。

5. 组装智能体:让模型学会思考与行动

现在,我们有了“大脑”(LLM)和“手脚”(Tools)。接下来,我们需要一个“决策机制”来让大脑指挥手脚。在LangChain中,这通常通过“代理”(Agent)来实现。

5.1 理解ReAct模式与AgentExecutor

目前最主流、效果最好的智能体范式是ReAct(Reason + Act)。模型在行动前会先进行“思考”(Reasoning),解释它为什么要调用某个工具以及期望得到什么,然后执行行动(Act),最后根据工具返回的结果进行下一步的思考或给出最终答案。

LangChain的AgentExecutor就是这个范式的实现者。它负责:

  1. 将用户的输入、对话历史、可用工具列表整合成一个提示词(Prompt)给LLM。
  2. 解析LLM的输出,判断是应该调用工具,还是直接给出最终答案。
  3. 如果调用工具,则执行对应的工具函数,并将结果作为新的上下文喂给LLM,继续循环。
  4. 直到LLM输出最终答案,循环结束。

5.2 创建提示词模板

提示词是引导模型行为的关键。我们需要一个专门为智能体设计的提示词模板,告诉它:你是谁,你有什么能力,你应该如何思考。

from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder # 构建系统提示词 system_prompt = """你是一个专业的AI编程助手,可以调用工具来帮助用户完成编程任务。 你拥有以下工具: {tools} 你必须严格遵守以下规则: 1. 在决定使用哪个工具时,请先简要说明你的思考过程(Reasoning)。 2. 每次只能调用一个工具。 3. 工具调用必须严格按照其描述所要求的输入格式。 4. 根据工具返回的结果,决定下一步是继续调用工具还是给出最终答案。 5. 如果你认为任务已经完成,或者无法通过现有工具完成,请直接给出清晰、友好的最终答案。 用户的问题可能是中文或英文,请用相应的语言回复。 对话历史: {chat_history} 现在,开始处理用户的最新请求: {input} """ prompt = ChatPromptTemplate.from_messages([ ("system", system_prompt), MessagesPlaceholder(variable_name="chat_history"), # 预留位置存放历史消息 ("human", "{input}"), MessagesPlaceholder(variable_name="agent_scratchpad"), # 预留位置存放智能体的思考-行动记录 ])

提示词要点解析:

  • {tools}: 运行时会被替换为可用工具的名称和描述列表。
  • {chat_history}: 存储用户与智能体的多轮对话,让智能体有上下文记忆。
  • {agent_scratchpad}: 这是LangChain Agent专用的占位符,用于在运行过程中自动插入模型之前的“思考”和“行动”记录,保持思维的连贯性。

5.3 配置Agent并创建执行器

我们将使用LangChain提供的create_react_agent函数,它封装了ReAct模式的标准逻辑。

from langchain.agents import create_react_agent, AgentExecutor from langchain.memory import ConversationBufferMemory # 1. 准备工具列表 tools = [shell_tool, read_tool, write_tool, search_tool] # 将之前定义的工具放入列表 # 2. 创建记忆组件,用于保存对话历史 memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) # 3. 创建ReAct智能体 agent = create_react_agent(llm, tools, prompt) # 4. 创建智能体执行器,这是驱动整个循环的核心 agent_executor = AgentExecutor( agent=agent, tools=tools, memory=memory, verbose=True, # 开启详细日志,方便调试,能看到“思考”过程 handle_parsing_errors=True, # 处理模型输出解析错误,避免程序崩溃 max_iterations=10, # 限制最大迭代次数,防止陷入死循环 early_stopping_method="generate", # 当模型连续两次输出最终答案时停止 )

关键参数说明:

  • verbose=True强烈建议在开发时开启。它会在控制台打印出模型的完整思考链(Chain of Thought),包括它决定调用哪个工具、调用的参数、工具返回的结果等。这是调试智能体逻辑的最重要依据。
  • handle_parsing_errors=True: 模型有时可能不会输出完全符合LangChain期望的格式(比如一个包含工具调用和参数的JSON块)。设置这个参数可以让执行器尝试从错误中恢复,或让模型重试,而不是直接抛出异常。
  • max_iterations=10: 安全阀。防止智能体在一个简单问题上无限循环调用工具。
  • early_stopping_method="generate": 停止条件之一。当模型连续两次输出(即不调用工具)时,认为它已准备好给出最终答案。

5.4 运行你的第一个智能体

现在,让我们用一个简单的任务来测试这个智能体。

# 任务:让智能体创建一个Python文件并运行它 result = agent_executor.invoke({ "input": "请你在工作区创建一个名为 'hello_agent.py' 的Python文件,内容是一个简单的HTTP服务器,然后运行它,并告诉我它是否成功启动。" }) print("\n=== 智能体最终回答 ===") print(result["output"])

当你运行这段代码时,如果verbose=True,你会在控制台看到类似下面的输出:

> 进入新的AgentExecutor链... 思考:用户想让我创建一个Python HTTP服务器文件并运行它。我需要先创建文件,然后执行它。 首先,我应该使用 write_file 工具来创建这个文件。 动作:{"action": "write_file", "action_input": "{\"file_path\": \"hello_agent.py\", \"content\": \"from http.server import HTTPServer, SimpleHTTPRequestHandler\\n\\nserver = HTTPServer((\\\"localhost\\\", 8000), SimpleHTTPRequestHandler)\\nprint(\\\"Server started at http://localhost:8000\\\")\\nserver.serve_forever()\"}"} 观察:成功写入文件:./workspace/hello_agent.py 思考:文件已经创建好了。现在我需要运行它。由于这是一个会长期运行的服务,我应该用后台方式运行,或者只运行一下看看有没有报错。先尝试运行一下。 动作:{"action": "execute_shell", "action_input": "cd workspace && python hello_agent.py &"} 观察:STDOUT: Server started at http://localhost:8000 返回码: 0 思考:命令返回码是0,并且输出了启动成功的消息。看来服务器已经成功在后台启动了。我可以检查一下进程是否在运行。 动作:{"action": "execute_shell", "action_input": "ps aux | grep python hello_agent.py"} 观察:...(进程列表) 思考:进程存在。任务完成。现在可以给用户最终答案了。 最终答案:已成功在工作区创建了 hello_agent.py 文件,并启动了一个简单的HTTP服务器,运行在 http://localhost:8000。进程正在后台运行中。 > 链结束。

通过这个详细的日志,你可以清晰地看到智能体“思考-行动-观察-再思考”的完整过程。这就是ReAct智能体的核心魅力。

6. 实战演练:构建一个需求分析与代码生成智能体

让我们用一个更贴近真实开发的场景来深化理解。假设我们接到一个需求:“帮我用FastAPI创建一个用户管理API,包含用户注册和登录功能,并使用SQLite数据库。”

我们将引导智能体一步步完成这个任务。为了更高效,我们需要对工具和提示词做一些增强。

6.1 增强工具:代码静态检查

在让智能体直接运行代码前,最好先进行语法检查。我们添加一个code_lint工具。

import ast def check_python_syntax(code: str) -> str: """检查Python代码的语法是否正确。""" try: ast.parse(code) return "代码语法正确。" except SyntaxError as e: return f"语法错误:第{e.lineno}行,{e.msg}\n错误文本:{e.text}" lint_tool = Tool( name="code_lint", func=check_python_syntax, description="检查给定的Python代码字符串是否存在语法错误。输入是一段完整的Python代码。" ) # 记得将 lint_tool 加入到 tools 列表中 tools.append(lint_tool)

6.2 设计分步任务提示词

对于复杂任务,我们可以通过系统提示词引导智能体进行分步规划。修改之前的系统提示词,加入更明确的指导:

system_prompt_enhanced = """你是一个资深的AI全栈开发助手。请以结构化的方式解决复杂的编程任务。 你的工作流程应该是: 1. **需求分析**:理解用户需求,明确技术栈(如框架、数据库)。 2. **系统设计**:规划文件结构、数据库表、API端点。 3. **迭代实现**:按照依赖顺序(如先创建模型,再写API)逐个实现模块。每个模块完成后,进行语法检查。 4. **集成测试**:在全部实现后,尝试运行主程序,检查是否有运行时错误。 你拥有以下工具: {tools} 规则: - 每次调用工具前,用【思考】开头说明意图。 - 优先使用 `write_file` 创建或修改代码文件。 - 创建新文件后,可立即用 `code_lint` 检查语法。 - 所有文件操作必须在 `./workspace` 目录下。 - 最终,请提供如何启动服务的说明。 当前对话历史: {chat_history} 现在,请开始处理任务: {input} """ # 使用新的提示词模板更新 agent prompt_enhanced = ChatPromptTemplate.from_messages([...]) # 类似之前,替换system部分 agent = create_react_agent(llm, tools, prompt_enhanced) agent_executor = AgentExecutor(agent=agent, tools=tools, memory=memory, verbose=True, ...)

6.3 观察智能体执行复杂任务

现在,运行这个增强版的智能体。

result = agent_executor.invoke({ "input": "使用FastAPI和SQLite,创建一个用户管理系统。需要用户注册(用户名、邮箱、密码哈希存储)和登录(返回JWT令牌)的API。请分步完成。" })

verbose日志中,你会看到智能体开始进行系统性的工作:

  1. 第一步:创建项目结构。它可能会先调用execute_shell创建虚拟环境或目录,然后用write_file创建requirements.txtmain.pymodels.pydatabase.py等文件。
  2. 第二步:实现数据模型。在models.py中定义SQLAlchemy的User模型,包含字段和密码哈希方法。写入文件后,调用code_lint检查。
  3. 第三步:实现数据库连接和工具函数。创建database.py,编写数据库引擎、会话管理、密码哈希验证函数。
  4. 第四步:实现API路由。在main.py中编写FastAPI应用,创建/register/login端点。每写完一个端点,可能都会进行语法检查。
  5. 第五步:集成与测试。最后,它可能会尝试运行python main.py来启动FastAPI开发服务器,或者至少给出启动命令。

整个过程中,智能体就像一个有条不紊的初级开发者,不断地“思考-行动-观察”,逐步将模糊的需求转化为具体的、可运行的代码文件。

实操心得:控制迭代与纠偏:智能体有时会“跑偏”,比如在实现JWT时陷入细节,反复修改同一个文件。这时,max_iterations参数就起到了作用。你也可以在观察日志时,通过抛出异常或在提示词中强调“请先完成核心功能,细节后续优化”来引导它。智能体的表现很大程度上取决于提示词的质量和工具的粒度。

7. 调试与优化:让你的智能体更可靠

构建智能体的过程不是一蹴而就的,你会遇到各种问题。下面是一些常见问题的排查思路和优化技巧。

7.1 常见问题速查表

问题现象可能原因解决方案
智能体不调用工具,直接回答“我无法完成”1. 工具描述不清晰。
2. 提示词未明确要求使用工具。
3. 模型温度(temperature)过高,导致输出随机。
1. 重写工具描述,明确使用场景和输入格式。
2. 在系统提示词中强调“你必须使用工具”。
3. 降低temperature(如设为0.1)。
智能体陷入无限循环,反复调用同一工具1. 工具返回的结果未能让模型理解任务已进展。
2. 模型对当前状态判断错误。
1. 检查工具返回的信息是否清晰。例如,执行成功应返回“成功”,而非空字符串。
2. 在提示词中加入“如果你认为上一步已经成功,请进行下一步”。
3. 设置较小的max_iterations
模型输出格式解析错误(Parsing error)模型没有严格按照LangChain要求的JSON格式输出工具调用指令。1. 设置handle_parsing_errors=True
2. 在提示词中提供更清晰的格式示例。
3. 使用更强大的模型(如DeepSeek最新版本)。
工具执行出错(如文件不存在、命令失败)1. 智能体对工作环境状态理解有误。
2. 路径或命令拼写错误。
1. 增强工具的健壮性,在工具函数内做好错误捕获,并返回详细的错误信息给模型。
2. 让智能体在操作前先使用read_fileexecute_shell(如ls)查看状态。
智能体生成的代码有逻辑错误模型本身的知识局限或上下文不足。1. 要求智能体在生成代码后,用code_lint或编写简单测试来验证。
2. 将复杂任务分解为更小的子任务,逐个验证。

7.2 高级优化技巧

  1. 定制输出解析器(Output Parser):LangChain默认的ReAct代理使用特定的格式解析模型输出。如果模型经常不遵守,你可以自定义一个更鲁棒的解析器,使用正则表达式或尝试多种格式来提取工具调用信息。
  2. 使用更智能的Agent类型:除了create_react_agent,LangChain还提供了create_self_ask_with_search_agentcreate_openai_tools_agent等。对于编程任务,OpenAI的function calling格式被很多模型兼容,可以尝试create_openai_tools_agent,它能产生更结构化的工具调用请求。
  3. 引入“验证”步骤:在关键操作后(如写入重要文件、安装关键依赖),可以设计一个验证工具,或者让智能体在提示词中养成“验证”的习惯。例如:“在安装requests包后,请验证安装是否成功。”
  4. 实现长时记忆ConversationBufferMemory会保存所有历史,可能导致上下文过长。对于超长对话,可以使用ConversationSummaryMemoryConversationBufferWindowMemory来摘要或只保留最近几轮对话。
  5. 流式输出最终结果:在agent_executor.invoke()时使用stream模式,可以实时看到智能体的思考过程和最终答案的生成,体验更好。

7.3 一个调试案例:智能体不创建文件

假设你让智能体“创建一个app.py文件”,但它总是回答“我已经在思考中创建了”,却没有实际调用write_file工具。

  • 排查步骤
    1. 检查verbose日志:首先看模型输出的原始内容。是不是模型输出了类似{"action": "write_file", ...}的文本但被错误解析了?
    2. 检查工具描述write_file的描述是否足够清晰?输入格式要求是JSON字符串,描述里写明白了吗?
    3. 修改提示词:在系统提示词中明确写出:“当你需要创建或修改文件时,你必须调用write_file工具,不要只在思考中描述。
    4. 提供示例:在提示词中直接给一个工具调用的例子。
    5. 简化任务测试:先给一个极其简单的任务测试工具调用是否正常,如“请调用write_file工具,创建一个名为test.txt的文件,内容为hello”。

通过这种层层递进的调试,你能逐渐摸清智能体的“脾气”,并找到最优的配置方式。构建一个稳定可靠的智能体,是一个需要耐心和反复迭代的过程。

← 返回列表