从零到一:用Python与LangChain构建你的第一个AI智能体

最近和几个做后端开发的朋友聊天,发现大家不约而同地开始研究AI智能体。一个朋友想做个自动处理客服工单的小助手,另一个则打算开发个能自动分析周报并生成摘要的工具。但他们都卡在了第一步:面对琳琅满目的框架和概念,不知道从哪里下手,更担心写出来的代码只是个“玩具”,无法真正运行起来。

如果你也有类似的困惑,那么这篇文章就是为你准备的。我不打算讲太多高深的理论,而是直接带你动手,用Python和目前最流行的LangChain框架,一步步搭建一个真正能工作的基础AI智能体。我们会从一个最简单的“查询天气”的智能体开始,逐步为其添加记忆、工具调用等能力,最终形成一个可以交互的雏形。整个过程就像搭积木,你会发现,构建智能体并没有想象中那么复杂。

本文面向的是有一定Python基础的开发者,你可能熟悉API调用,但对大语言模型和智能体开发还比较陌生。我们的目标很明确:在30分钟内,让你拥有一个可运行、可扩展的智能体代码库,并理解其核心运作机制。

1. 环境搭建与核心概念扫盲

在写第一行代码之前,我们需要把“战场”准备好。智能体开发不同于传统的Web或数据分析项目,它依赖特定的库和模型服务。别担心,配置过程并不繁琐。

首先,确保你的Python版本在3.8以上。我强烈建议使用虚拟环境来管理依赖,避免污染全局环境。你可以使用venvconda,这里以venv为例:

# 创建并激活虚拟环境
python -m venv langchain_env
source langchain_env/bin/activate  # Linux/macOS
# 或 langchain_env\Scripts\activate  # Windows

接下来,安装核心依赖。LangChain是我们的主框架,另外还需要一个与大语言模型通信的库。这里我们使用OpenAI的API作为示例,因为它稳定且文档丰富。当然,你也可以使用其他兼容OpenAI接口的模型服务。

pip install langchain langchain-openai python-dotenv

注意:使用OpenAI API会产生费用。对于学习和实验,你可以注册账号并获取免费额度。请务必保管好你的API密钥,不要上传到公开代码库。

安装完成后,在项目根目录创建一个.env文件来存储你的敏感信息:

OPENAI_API_KEY=你的_api_key_在这里

现在,我们来快速理解几个核心术语,这能帮助你在后续编码时明白自己在做什么:

  • 大语言模型:你可以把它想象成一个拥有海量知识、能理解和生成文本的“大脑”。它本身不会主动做事情,需要你告诉它该做什么。
  • 智能体:这是我们今天要构建的主角。它是一个系统,核心是让LLM这个“大脑”学会使用“工具”(比如查询数据库、调用API)来完成特定任务。智能体负责协调LLM的思考、决策和行动。
  • LangChain:一个开发框架,它把构建智能体过程中那些繁琐的步骤(如连接模型、管理对话历史、定义工具)封装成了简洁的模块。我们的工作就是用这些“乐高积木”快速搭建出想要的功能。

简单来说,我们的智能体工作流是这样的:用户提出问题 -> LangChain将问题和历史对话整理好发给LLM -> LLM思考后决定是否需要调用工具以及调用哪个 -> 智能体执行工具调用 -> 将工具结果和问题再次发给LLM生成最终回答 -> 返回给用户。

2. 创建你的第一个“工具”与基础智能体

智能体的核心能力是使用工具。没有工具,LLM就只是一个聊天机器人。所以,我们的第一步是教会智能体使用一个简单的工具。

让我们实现一个最经典的示例:一个获取城市天气的模拟工具。在真实场景中,你会连接像OpenWeatherMap这样的天气API。为了简化,我们先创建一个返回固定信息的模拟函数。

在你的Python文件中(例如first_agent.py),开始编写代码:

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
from langchain.tools import tool

# 1. 加载环境变量
load_dotenv()

# 2. 定义一个工具:获取天气
@tool
def get_weather(city: str) -> str:
    """根据城市名称获取该城市的当前天气情况。"""
    # 这里是模拟数据,真实应用中应调用天气API
    weather_data = {
        "北京": "晴,15°C,微风",
        "上海": "多云,18°C,东南风2级",
        "深圳": "阵雨,22°C,南风3级",
        "纽约": "阴,10°C,北风4级",
        "伦敦": "小雨,8°C,西风3级",
    }
    return weather_data.get(city, f"抱歉,未找到{city}的天气信息。")

# 3. 初始化LLM
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

# 4. 准备工具列表
tools = [get_weather]

# 5. 构建提示词模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个乐于助人的助手,可以查询天气。请根据工具提供的信息回答用户。"),
    ("placeholder", "{chat_history}"),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

# 6. 创建智能体
agent = create_tool_calling_agent(llm=llm, tools=tools, prompt=prompt)

# 7. 创建智能体执行器
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# 8. 运行智能体
if __name__ == "__main__":
    response = agent_executor.invoke({"input": "上海今天天气怎么样?"})
    print("智能体回复:", response["output"])

运行这个脚本,你会看到类似以下的输出,它展示了智能体内部的思考过程:

> 进入新的AgentExecutor链...
我是否需要使用工具?是的,用户询问上海的天气,我需要调用get_weather工具。
动作:get_weather
动作输入:{"city": "上海"}
观察:多云,18°C,东南风2级
思考:我得到了上海的天气信息,现在可以回答用户了。
最终答案:上海今天的天气是多云,气温18摄氏度,东南风2级。

> 链结束。

智能体回复:上海今天的天气是多云,气温18摄氏度,东南风2级。

代码解读

  1. @tool装饰器将普通Python函数转换成了LangChain能识别的工具。文档字符串"""..."""非常重要,LLM会阅读它来决定是否以及如何调用这个工具。
  2. ChatOpenAI是我们与GPT-3.5 Turbo模型通信的客户端。temperature参数控制输出的随机性(0表示更确定)。
  3. create_tool_calling_agent是LangChain提供的一个高级API,它帮我们组装了一个具备工具调用能力的智能体。
  4. AgentExecutor是智能体的“发动机”,负责运行整个“思考-行动”循环。
  5. verbose=True让我们能看到智能体内部的推理链,这对调试和理解其行为至关重要。

至此,你已经成功创建了一个能理解自然语言、并调用工具完成任务的AI智能体!虽然它现在只会查天气,但框架已经搭好了。

3. 为智能体注入“记忆”能力

上面的智能体有一个明显缺陷:它记不住之前的对话。如果你接着问“那北京呢?”,它会因为缺乏上下文而感到困惑。一个有用的助手应该能记住短暂的对话历史。

LangChain提供了多种记忆模块。这里我们使用最简单的ConversationBufferMemory,它会将之前的对话内容全部保存在内存中。

让我们升级之前的代码:

# ... 前面的导入和工具定义保持不变 ...

from langchain.memory import ConversationBufferMemory

# 初始化记忆模块
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

# 更新提示词模板,包含chat_history占位符
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个乐于助人的助手,可以查询天气。请根据工具提供的信息和对话历史回答用户。"),
    ("placeholder", "{chat_history}"), # 这里会自动填充记忆中的对话
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

# 创建智能体(同上)
agent = create_tool_calling_agent(llm=llm, tools=tools, prompt=prompt)

# 创建执行器时传入memory
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    memory=memory,
    verbose=True
)

# 进行多轮对话
if __name__ == "__main__":
    queries = [
        "上海今天天气怎么样?",
        "北京呢?", # 这里智能体会利用历史,知道“北京”指的是天气
        "我之前问过哪几个城市?" # 测试记忆召回
    ]
    
    for query in queries:
        print(f"\n用户:{query}")
        response = agent_executor.invoke({"input": query})
        print(f"助手:{response['output']}")

这次运行,你会发现智能体在回答“北京呢?”时,已经不需要你再次明确说出“天气”二字,它能从上下文中理解你的意图。这就是记忆的作用。

记忆模块的选择ConversationBufferMemory简单但可能消耗大量token(影响成本和模型上下文长度限制)。对于生产环境,你可能需要考虑:

  • ConversationSummaryMemory:只保存对话的摘要。
  • ConversationBufferWindowMemory:只保留最近K轮对话。
  • VectorStoreRetrieverMemory:将记忆存入向量数据库,按相关性检索。这属于更高级的RAG技术范畴。

4. 构建多功能复合型智能体

一个只会查天气的智能体显然不够酷。现实中的任务往往是复杂的、多步骤的。例如,用户可能说:“帮我规划一下周末去杭州的行程,并查一下那里的天气。” 这需要智能体协调多个工具。

让我们为智能体再添加两个工具:一个模拟的行程规划工具,和一个计算器工具。

# ... 保持之前的导入、LLM和记忆初始化 ...

# 定义更多工具
@tool
def plan_trip(destination: str, days: int) -> str:
    """为指定目的地和天数生成一个简单的旅行计划大纲。"""
    plans = {
        "杭州": [f"第{i+1}天:游览西湖、灵隐寺等经典景点。" for i in range(days)],
        "成都": [f"第{i+1}天:参观大熊猫基地、品尝火锅。" for i in range(days)],
    }
    default_plan = [f"第{i+1}天:探索{destination}的当地文化与美食。" for i in range(days)]
    itinerary = plans.get(destination, default_plan)
    return f"为您规划的{destination}{days}日游行程:\n" + "\n".join(itinerary)

@tool
def calculate(expression: str) -> str:
    """计算一个简单的数学表达式(支持加减乘除)。"""
    try:
        # 警告:使用eval存在安全风险,此处仅用于演示。
        # 生产环境中应使用更安全的表达式解析器(如ast.literal_eval限制更严)。
        result = eval(expression)
        return f"{expression} = {result}"
    except Exception as e:
        return f"计算错误:{e}"

# 更新工具列表
tools = [get_weather, plan_trip, calculate]

# 更新系统提示词,说明智能体的所有能力
prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个多功能助手,可以帮助用户:
    1. 查询城市天气(使用get_weather工具)。
    2. 规划旅行行程(使用plan_trip工具)。
    3. 进行数学计算(使用calculate工具)。
    请根据用户需求,灵活选择使用一个或多个工具来解决问题。"""),
    ("placeholder", "{chat_history}"),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

# 重新创建智能体和执行器
agent = create_tool_calling_agent(llm=llm, tools=tools, prompt=prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, memory=memory, verbose=True)

# 测试复杂任务
if __name__ == "__main__":
    complex_query = "我想周末去杭州玩两天,先帮我查一下天气,再简单规划一下行程。"
    print(f"用户:{complex_query}")
    response = agent_executor.invoke({"input": complex_query})
    print(f"\n助手:{response['output']}")

观察verbose输出,你会看到智能体先调用了get_weather工具查询杭州天气,然后调用了plan_trip工具生成行程,最后将两个工具的结果整合,生成了一段连贯、友好的回复。这体现了智能体的任务分解多工具协调能力。

工具设计的要点

  1. 清晰的文档:工具的文档字符串是LLM理解其功能的唯一依据,务必准确描述输入参数和功能。
  2. 错误处理:工具内部应该有完善的异常处理,并返回对用户或LLM友好的错误信息。
  3. 安全性:避免在工具中使用eval等危险函数。上面的计算器工具仅作演示,实际应用应替换为安全的数学库。

5. 调试、优化与下一步方向

你的智能体已经能跑起来了,但在实际开发中,你可能会遇到各种问题。以下是一些常见场景和解决思路:

问题1:智能体不调用工具,总是直接回答。

  • 检查提示词:系统提示词是否明确要求它使用工具?是否描述了工具的功能?
  • 检查工具文档:文档字符串是否清晰?LLM可能无法理解模糊的描述。
  • 调整LLM参数:尝试稍微提高temperature(如0.1-0.3),让模型更有“创造力”去选择工具。

问题2:智能体调用了错误的工具或参数。

  • 观察思考链verbose=True的输出是你的最佳调试工具。看LLM在“思考”什么,为什么做出了错误的选择。
  • 优化工具描述:让工具的名称和文档更具区分度。例如,将calculate改为calculate_math_expression
  • 提供示例:在提示词中加入少量示例(Few-shot Learning),展示如何正确调用工具。

问题3:处理复杂、多步骤的模糊指令。 这是我们当前简单智能体的局限。例如,“帮我分析一下上个月的销售数据,然后预测下个月趋势,最后用邮件发给经理”。这需要更强大的任务规划能力。此时,你可以探索:

  • LangGraph:这是LangChain官方推出的用于构建复杂、有状态多步骤智能体应用的库。它允许你以图的形式定义工作流,明确节点(步骤)和边(流转条件),非常适合处理需要严格步骤或循环的任务。
  • 自定义Agent类型:超越create_tool_calling_agent,使用initialize_agent并指定AgentType,如ZERO_SHOT_REACT_DESCRIPTION,它会强制LLM按照“思考-行动-观察”的ReAct模式一步步推理。

下一步,你可以尝试:

  1. 连接真实API:将get_weather工具替换为真正的天气API调用(如使用requests库)。
  2. 集成知识库:使用LangChain的RAG模块,为智能体接入公司文档、产品手册等私有知识,让它能回答专业问题。
  3. 增加输出解析:使用PydanticOutputParser让LLM的输出结构化,方便你后续处理。
  4. 构建Web接口:使用FastAPI或Gradio为你的智能体做一个简单的聊天界面。

我最初搭建智能体时,总想一步到位做出复杂功能,结果在细节里卡了很久。后来发现,最好的办法就是像今天这样,从一个能跑通的简单核心开始,每成功一步,就添加一个小功能。当你看到智能体第一次正确调用工具并给出答案时,那种成就感会驱动你继续探索下去。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐