使用OpenAI、LangChain、MongoDB构建一个AI agent

LangChain真是好起来了。24年中的时候用LangChain V2差点把我气死,现在V3用起来开始真香了~

像 ChatGPT、Gemini 和 Claude 这样的大模型已成为企业必不可少的工具。如今,几乎每家公司都希望根据自己的需求或客户群体,开发一款定制化的AI Agent。

这篇文章将重点介绍如何创建一个个性化的助手,这个助手不仅能进行功能调用,还能将对话记录存储在数据库中,实现多会话的连续互动,同时能够执行网页搜索并总结相关信息。

为了更好地组织结构并便于未来扩展功能,使用 Langchain、Langgraph 和 LangSmith 这三个工具,它们会简化整个过程,并提升系统的功能性。Langchain 可以简化流媒体处理、工具调用,并支持多种不同的 LLM。Langgraph 是一个组织工具,帮助我们选择使用的工具,并且让智能助手自主决策路径。LangSmith 则是一个观察工具,它能帮助我们监控向 LLM 提问到最终得到答案的整个过程。

完整代码见仓库:https://github.com/zpillsbury/ai-agent。

资源准备

  • Tavily API key
  • OpenAI API key
  • MongoDB Atlas
  • Langsmith API key

首先,在你的 .env 文件里加上几个关键的环境变量,包括 openai_keytavily_keymongo_uri

📝 .env

OPENAPI\_KEY\=OPENAI\_KEY=sk-proj-XXXXXX
TAVILY\_API\_KEY\=tvly-XXXXXXXXXXXXXXXXXXXXXXXX
MONGO\_URI\=mongodb+srvXXXXXXXXXXXXXXXXXXXXXXXXXXX

LANGCHAIN\_TRACING\_V2\=true
LANGCHAIN\_ENDPOINT\=https://api.smith.langchain.com
LANGCHAIN\_API\_KEY\=lsv2\_pt\_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\_xxxxxxxxxx
LANGCHAIN\_PROJECT\=project-name

然后,把这些Key和 URI 都配置到你的设置文件里,确保 langsmith、OpenAI key、Tavily key和 Mongo URI 一个都不少。

📝 app/utilities/settings.py

from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    openai_key: SecretStr
    tavily_api_key: str
    mongo_uri: SecretStr


    # LangSmith
    langchain_tracing_v2: bool = True
    langchain_endpoint: str = "https://api.smith.langchain.com"
    langchain_project: str = "ai-agent"
    langchain_api_key: str

    model_config = SettingsConfigDict(env_file=".env", extra="ignore")


settings = Settings()

使用设置中的API key设置 OpenAI 客户端。

📝 app/main.py

from langchain_openai import ChatOpenAI

from .utilities.settings import settings

llm = ChatOpenAI(
    openai_api_key=settings.openai_key,
    model_name="gpt-4o-mini",
    max_retries=2,
)

Langgraph

为了让聊天机器人的各个部分能够顺畅交流,需要设置一个全局"状态"变量,这样数据就能在各个节点间流通了。用 LLM 生成消息之后,通过 langgraphadd_messages 函数,把这些消息加入到聊天机器人的记忆里。

📝 app/main.py

from typing import Annotated, TypedDict

from langchain_core.messages import BaseMessage
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
from langgraph.graph.state import CompiledStateGraph


class State(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]

langgraph 中,每个节点其实就是一个函数。我们现在要做一个新的 chatbot 函数,它会通过 llm.ainvoke 去调用 OpenAI,把当前的消息状态传过去。OpenAI 会给我们返回一条新的 AI 消息,然后我们就把这个新消息更新到状态里。通过上一步配置的 add_messages 函数,新消息会自动加入到现有的消息队列中。

最后提一下,langgraph 提供了同步和异步两种方法。如果你想要异步处理,只需在方法名前加个 a。这次我们选择异步的 ainvoke,而不是同步的 invoke

📝 app/main.py

async def chatbot(state: State) -> State:
    """
    Chatbot
    """
    response_message = await llm.ainvoke(state["messages"])

    return {"messages": [response_message]}

现在,我们要用 add_node 把新写的函数加入到流程图里。在这个图中,节点之间的连线就是所谓的"边"

接下来,要用 add_edge 把图的起点 START 和聊天机器人节点连起来。最后用 graph_builder.compile 来编译这个图表。不编译的话,这个图表是没法用的。

其实这就是代码话的流程编排,很多AI WebUI都有。

📝 app/main.py

async def get_graph() -> CompiledStateGraph:    """    Get the graph    """    graph_builder = StateGraph(State)    graph_builder.add_node("chatbot", chatbot)    graph_builder.add_edge(START, "chatbot")    graph_builder.add_edge("chatbot", END)    graph = graph_builder.compile()    return graph

目前流程图长这样:

图1 流程图

然后让我们来编写一个新异步函数 run_graph,通过 graph.astream(同步的话就用 stream)来启动我们精心编译的图。这个函数会激活"chatbot"节点,也就是之前定义的 chatbot 函数。

首先,它会放入一个System Prompt,再加上用户提出的问题,这样就构成了我们的对话状态。这些信息会被发送到 OpenAI,AI会回一条消息,我们的函数则会把这个回复加入到对话状态中。

在图的循环中,我们可以随时查看事件变量,了解当前的对话状态。我们会从这些事件中挖掘出 value,再从这个 value 中提取出最后一条消息的精华,最后把这份精华传回给用户。

至于系统提示,它就像是为 AI 助手提供了一份"情报",让它的搜索更加精确。因为 LLM 的训练数据有截止时间,所以它可能会更擅长挖掘历史资料,而不是最新的知识。

📝 app/main.py

async def get_graph() -> CompiledStateGraph:
    """
    Get the graph
    """
    graph_builder = StateGraph(State)

    graph_builder.add_node("chatbot", chatbot)

    graph_builder.add_edge(START, "chatbot")
    graph_builder.add_edge("chatbot", END)

    graph = graph_builder.compile()

    return graph

接下来,写一个新异步函数 main,这个函数的核心任务就是接收用户的输入。同时,加入一个循环语句,它的作用是监听用户的输入,一旦用户键入(quit、exit 或 q),聊天就会在终端优雅地结束。

📝 app/main.py

from datetime import datetime, timezone
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage

now = datetime.now(timezone.utc)

system_prompt = f"""
You are an AI assistant helping an user.

Current Date: {now}
"""

asyncdef run_graph(question: str) -> None:
    """
    Run the graph
    """
    asyncfor event in graph.astream(
        {
            "messages": [
                SystemMessage(content=system_prompt),
                HumanMessage(content=question),
            ]
        }
    ):
        for value in event.values():
            print(value["messages"][-1].content)

    returnNone

工具调用

现在,我们要利用 .env.settings 文件中的 tavily_api_key。通过 TavilySearchResults,我们的 AI 助手就能在网上进行搜索了。这些搜索工具会被集成到 tools 中,方便未来我们随时扩充工具箱。然后,我们通过 llm_with_tools = llm.bind_tools 这行代码,将工具列表绑定到 AI 助手身上。最后,记得把代码中所有用到 llm 的地方,都替换成 llm_with_tools,这样一来,AI 助手就能使用这些工具了。

📝 app/main.py

async def main() -> None:
    """
    AI Agent
    """
    whileTrue:
        question = input("q: ")
        if question.lower() in ["quit", "exit", "q"]:
            print("Goodbye!")
            break

        await run_graph(question)

    returnNone


if __name__ == "__main__":
    anyio.run(main)

下一步,加入一个 tool_node,这个节点的作用是激活调用 AI 消息的工具。设置一个 add_conditional_edge,这个特殊的边可以让 AI 助手根据不同的需求,导向不同的节点。它会先判断是否需要进行工具调用,如果不需要,那么流程就会直接导向 END 节点,结束这次交互。

📝 app/main.py

from langchain_community.tools.tavily_search import TavilySearchResults


web_search = TavilySearchResults(max_results=2)
tools = [web_search]

llm = ChatOpenAI(
    openai_api_key=settings.openai_key,
    model_name="gpt-4o-mini",
    max_retries=2,
)

llm_with_tools = llm.bind_tools(tools)

asyncdef chatbot(state: State) -> State:
    """
    Chatbot
    """
    response_message = await llm_with_tools.ainvoke(state["messages"])

    return {"messages": [response_message]}

存储对话数据

接下来,利用 .env.settings 文件中的 mongo_uri。首先,在代码的顶部加入 async_mongodb_client。然后,我们会在 get_graph 函数中设置一个检查点,它的作用是将记忆存储到 mongoDB。注意,这个操作需要放在异步函数里面才能正常执行哦。

📝 app/main.py

async def get_graph() -> CompiledStateGraph:
    """
    Get the graph
    """
    graph_builder = StateGraph(State)

    graph_builder.add_node("chatbot", chatbot)

    tool_node = ToolNode(tools=tools)
    graph_builder.add_node("tools", tool_node)

    graph_builder.add_edge(START, "chatbot")

    graph_builder.add_conditional_edges("chatbot", tools_condition)
    graph_builder.add_edge("tools", "chatbot")

    graph = graph_builder.compile()

    return graph

现在,利用 MongoDB 来设置 thread_id,这样做可以保存不同聊天记录中的消息。这个功能很强大,它让助手能够跨多个聊天 session 记住之前的对话内容。而且,如果想要为不同的聊天创建独立的记录,只需要使用不同的 thread_id 就可以轻松实现。

📝 app/main.py

from typing import Annotated, Any, TypedDict
from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.mongodb.aio import AsyncMongoDBSaver
from motor.motor_asyncio import AsyncIOMotorClient

async_mongodb_client: AsyncIOMotorClient[Any] = AsyncIOMotorClient(
    settings.mongo_uri.get_secret_value()


asyncdef get_graph() -> CompiledStateGraph:
    """
    Get the graph
    """
    checkpointer = AsyncMongoDBSaver(
        client=async_mongodb_client,
        db_name="ai",
        checkpoint_collection_name="checkpoints",
        writes_collection_name="checkpoint_writes",
    )

    graph_builder = StateGraph(State)

    graph_builder.add_node("chatbot", chatbot)

    tool_node = ToolNode(tools=tools)
    graph_builder.add_node("tools", tool_node)

    graph_builder.add_edge(START, "chatbot")

    graph_builder.add_conditional_edges("chatbot", tools_condition)
    graph_builder.add_edge("tools", "chatbot")

    graph = graph_builder.compile(checkpointer=checkpointer)

    return graph

现在,可以执行脚本,看看运行结果。由于输入的是当前日期,所以它能展示出团队最新的动态和信息。

from langchain_core.runnables import RunnableConfig


asyncdef run_graph(config: RunnableConfig, question: str) -> None:
    """
    Run the graph
    """
    graph = await get_graph()

    asyncfor event in graph.astream(
        {
            "messages": [
                SystemMessage(content=system_prompt),
                HumanMessage(content=question),
            ]
        },
        config=config,
        stream_mode="values",
    ):
        event["messages"][-1].pretty_print()

    returnNone


asyncdef main() -> None:
    """
    AI Agent
    """
    config = RunnableConfig(configurable={"thread_id": 1})

    whileTrue:
        question = input("q: ")
        if question.lower() in ["quit", "exit", "q"]:
            print("Goodbye!")
            break

        await run_graph(config=config, question=question)

    returnNone

可以在你的 langsmith 项目中逐步查看运行过程。

图2 Langsmith 可视化Trace

这就是打造一个能够自主决定是否需要调用工具、能够在网上查找信息,并且能够为每个对话线程保存信息,从而记住之前对话的 AI 助手的基本步骤。因为我们用的是 langchain,所以以后想要加入流媒体处理、支持多种模型和其他 AI 模型(llms)的功能,也会变得相当简单。

如何学习AI大模型?

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

四、AI大模型商业化落地方案

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

相关推荐
数据分析能量站31 分钟前
RWKV 语言模型
人工智能·语言模型·自然语言处理
吃个糖糖32 分钟前
38 Opencv HOG特征检测
人工智能·opencv·计算机视觉
deephub1 小时前
深度强化学习中SAC算法:数学原理、网络架构及其PyTorch实现
人工智能·pytorch·神经网络·强化学习
阿正的梦工坊1 小时前
PyTorch中的__init__.pyi文件:作用与C++实现关系解析
c++·人工智能·pytorch
Jackilina_Stone1 小时前
【HUAWEI】HCIP-AI-MindSpore Developer V1.0 | 第一章 神经网络基础( 2 卷积神经网络 ) | 学习笔记
人工智能·笔记·深度学习·神经网络·cnn
摸鱼仙人~1 小时前
pytorch中nn.Conv2d详解及参数设置原则
人工智能·pytorch·python
AIGC大时代1 小时前
学术写作中的各种流程图如何利用Kimi进行辅助构建?
人工智能·深度学习·chatgpt·aigc·流程图·ai写作
大佬,救命!!!2 小时前
重新整理机器学习和神经网络框架
人工智能·笔记·深度学习·神经网络·机器学习·类比学习
人工智能技术咨询.2 小时前
工信部电子标准院计算机视觉证书报考指南!
人工智能·深度学习·学习·计算机视觉·语言模型
绍兴贝贝2 小时前
代码随想录算法训练营第五十天|图论基础|深度优先搜索理论基础|KM98.所有可达路径|广度优先搜索理论基础
数据结构·人工智能·python·算法·力扣