LangChain学习笔记

参考文档:https://docs.langchain.com/oss/python/langchain/quickstart

1、LangChain介绍

1.1、核心概念

  • LLM(大语言模型):LangChain 的核心是与 LLM 的交互。LLM 接收文本输入,返回文本输出
  • Prompt(提示词):发送给 LLM 的输入文本,决定了 LLM 的输出质量
  • Chain(链):将多个组件串联起来,形成一个完整的工作流
  • Memory(记忆):让 LLM 能够记住之前的对话内容
  • Document(文档):文本数据的基本单位,包含内容和元数据
  • Vector Store(向量存储):存储文档的向量表示,用于语义检索
  • Agent(智能体):能够使用工具、进行推理和决策的 LLM 应用

1.2、LangChain

LangChain 是一个用于开发由大语言模型(LLM)驱动的应用程序的框架。它提供了一系列工具和抽象,让你能够:

  • 🔗 将 LLM 与其他数据源连接
  • 🧠 构建具有记忆能力的对话系统
  • 🤖 创建能够使用工具的智能体(Agent)
  • 📚 实现检索增强生成(RAG)系统
  • ⚡ 快速原型开发和生产部署

1.3、应用场景

  • RAG(检索增强生成):将企业内部知识库与 LLM 结合,提供基于上下文的精准回答

  • Agent(智能体)开发:构建能使用工具的自主 AI 系统,连接外部数据源和 API,自动化复杂的多步骤任务

  • 聊天机器人:带记忆的多轮对话系统,如客服机器人、虚拟助手

  • 数据连接与处理:自然语言查询数据库,生成数据报表和可视化

  • 多模态应用:处理文本、图片、音频、视频,多模态检索和生成

  • 自动化工作流:测试报告自动生成、文档处理自动化、业务流程智能化

2、环境搭建

这里选用的uv作为包管理工具

2.1、安装uv

macOS/Linux

# 1、官方安装脚本
curl -LsSf https://astral.sh/uv/install.sh | sh
uv --version
# 2、Homebrew
brew install uv
uv --version

windows

# 1、官方安装脚本
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
uv --version

2.2、版本管理

注意uv 只能卸载它自己安装的 Python**,**不能卸载之前手动安装的 Python

# 列出所有可用的 Python 版本(简介版)
uv python list

# 安装指定版本 Python
uv python install 3.14
# 安装多个版本
uv python install 3.11 3.12 3.13

# 卸载 Python 版本
uv python uninstall 3.11

# 列出本地已安装的所有 Python 版本
uv python list --only-installed

# 设置全局python版本
uv python pin --global 3.13

2.3、项目管理

uv init agent-study

3、快速开始

3.1、依赖安装

uv add "langchain>=1.2.13"
uv add langchain-community
uv add langchain-openai
uv add langchain-anthropic
uv add langchain-deepseek
uv add dotenv

3.2、API KEY配置

openai和claude用的是中转站:https://foxcode.rjj.cc/auth/register?aff=M5APLZE

.env

DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxx
DEEPSEEK_BASE_URL=https://api.deepseek.com

OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxx
OPENAI_BASE_URL=https://code.newcli.com/codex/v1

ANTHROPIC_API_KEY=sk-xxxxxxxxxxxxxxxxx
ANTHROPIC_BASE_URL=https://code.newcli.com/claude

env_util.py

import os

from dotenv import load_dotenv

load_dotenv(verbose=True)

deepseek_api_key = os.getenv("DEEPSEEK_API_KEY")
deepseek_base_url = os.getenv("DEEPSEEK_BASE_URL")

openai_api_key = os.getenv("OPENAI_API_KEY")
openai_base_url = os.getenv("OPENAI_BASE_URL")

anthropic_api_key = os.getenv("ANTHROPIC_API_KEY")
anthropic_base_url = os.getenv("ANTHROPIC_BASE_URL")

3.3、模型初始化

Model Class

最常见的方式,直接导入并实例化特定提供商的模型类

  • 类型提示完整,IDE 自动补全友好,可以访问提供商特定的参数和功能
  • 切换模型提供商需要修改导入和代码,不适合需要动态选择模型的场景
from langchain_core.messages import HumanMessage
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    res = deepseek_llm.invoke([
        HumanMessage(content="hi!", )
    ])
    print(res)


if __name__ == "__main__":
    main()
init_chat_model

LangChain 提供的统一初始化函数,通过字符串指定模型

  • 统一接口,易于切换模型,适合从配置文件读取模型名称
  • 类型提示较弱,无法使用提供商特定的高级参数
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    llm = init_chat_model(model="deepseek-chat",
                          model_provider="deepseek",
                          api_key=deepseek_api_key,
                          base_url=deepseek_base_url)
    res = llm.invoke([
        HumanMessage(content="hi!")
    ])
    print(res.content)


if __name__ == "__main__":
    main()

4、基本使用

  • SystemMessage:设定AI 的行为、角色和规则,通常放在对话开头
  • HumanMessage:用户(人类)发送的消息
  • AIMessage:AI 助手的回复

4.1、简单对话

from langchain_core.messages import HumanMessage
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    res = deepseek_llm.invoke([
        HumanMessage(content="hi!", )
    ])
    print(res)


if __name__ == "__main__":
    main()

4.2、多轮对话

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    res = deepseek_llm.invoke([
        SystemMessage(content="你是一个 Python 编程助手"),
        HumanMessage(content="如何读取文件?")
    ])
    print(res.content)


if __name__ == "__main__":
    main()

4.3、流式输出

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    messages = [
        SystemMessage(content="你是一个 Python 编程助手"),
        HumanMessage(content="使用python写快排算法")
    ]
    for chunk in deepseek_llm.stream(messages):
        print(chunk.content, end="", flush=True)


if __name__ == "__main__":
    main()

4.4、链式调用

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个{role}"),
        ("human", "{input}")
    ])

    chain = prompt | deepseek_llm

    res = chain.invoke({"role": "代码审查专家", "input": """ 检查下面这段代码有什么问题?
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个{role}"),
        ("human", "{input}")
    ])

    chain = prompt | deepseek_llm

    res = chain.invoke({
        "role": "代码审查专家",
        "input": "检查下面这段代码有什么问题?"
    })
    print(res.content)


if __name__ == "__main__":
    main()
        """})
    print(res.content)


if __name__ == "__main__":
    main()

4.5、批量处理

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    responses = deepseek_llm.batch([
        [HumanMessage("hi!")],
        [HumanMessage("介绍一下你自己")],
        [HumanMessage("测试开发工程师终面一般会问些什么?")],
    ])

    for response in responses:
        print(response.content)


if __name__ == "__main__":
    main()

4.6、异步调用

import asyncio

from langchain_core.messages import HumanMessage
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)


async def ainvoke(messages):
    return await deepseek_llm.ainvoke(messages)


def main():
    response = asyncio.run(ainvoke([HumanMessage("hi!")]))
    print(response.content)


if __name__ == "__main__":
    main()

5、Template

5.1、PromptTemplate

from_template
from langchain_core.prompts import PromptTemplate
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )

    prompt = PromptTemplate.from_template("你是一个{role}, 请用{language}回答:{question}")

    # 格式化生成最终提示词
    # print(prompt.format(role="Python专家", language="中文", question="二分法示例代码"))

    # 格式化生成最终提示词
    # print(prompt.format_prompt(role="Python专家", language="中文", question="二分法示例代码"))

    chain = prompt | deepseek_llm

    res = chain.invoke({"role": "Python专家", "language": "中文", "question": "二分法示例代码"})
    print(res.content)


if __name__ == "__main__":
    main()
partial
import datetime

from langchain_core.prompts import PromptTemplate
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def dt():
    return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )

    template = PromptTemplate.from_template("你是一个{role}, 现在时间是{dt}, 请用{language}回答:{question}")
    # 动态填充&静态填充
    .partial(dt=dt, role="Python专家")

    prompt = template_partial.format(language="中文", question="二分法示例代码")

    res = deepseek_llm.invoke(prompt)
    print(res.content)


if __name__ == "__main__":
    main()

5.2、ChatPromptTemplate

from_template

简单模板,默认创建 human 消息(可以使用role指定角色)

from langchain_core.messages import HumanMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    prompt = ChatPromptTemplate.from_template("将{text}翻译为英文")

    # message = prompt.invoke({"text": "你好,世界!"})
    # 返回消息列表
    # print(message.to_messages())
    # 返回字符串
    # print(message.to_string())

    # 返回字符串
    # print(prompt.format(text="你好,世界!"))

    # 返回消息列表
    # print(prompt.format_messages(text="你好,世界!"))
    chain = prompt | deepseek_llm | StrOutputParser()
    content = chain.invoke({"text": "你好,世界!"})
    print(content)


if __name__ == "__main__":
    main()
from_messages

多轮对话模板,用于构建完整的对话结构(system是系统提示,human是用户消息,ai是LLM的回复消息,placeholder占位符用于插入消息队列)

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个{role}"),
        ("human", "{input}")
    ])

    # message = prompt.invoke({"role": "代码专家", "input": "如何读取文件?"})
    # 返回消息列表
    # print(message.to_messages())
    # 返回字符串
    # print(message.to_string())

    # 返回字符串
    # print(prompt.format(role="你好,世界!", input="如何读取文件?"))

    # 返回消息列表
    # print(prompt.format_messages(role="你好,世界!", input="如何读取文件?"))

    chain = prompt | deepseek_llm

    res = chain.invoke({"role": "Python专家", "input": "如何读取文件?"})
    print(res.content)


if __name__ == "__main__":
    main()
partial

预先填充某些变量

import datetime

from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def dt():
    strftime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    print(strftime)
    return strftime


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    prompt = (ChatPromptTemplate.from_messages([
        ("system", "你是一个{role}, 今天是{dt}"),
        ("human", "{input}")
    ])
    # 动态填充
    .partial(dt=dt))

    # 静态填充
    prompt_partial = prompt.partial(role="Python专家")

    chain = prompt_partial | deepseek_llm

    res = chain.invoke({"input": "如何读取文件?"})
    print(res.content)


if __name__ == "__main__":
    main()
placeholder

插入消息列表

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个{role}"),
        ("placeholder", "{chat_history}"),
        ("human", "{input}")
    ])

    chat_history = [
        HumanMessage(content="我是眼眸,职位是高级测试开发工程师"),
        AIMessage(content="好的"),
    ]

    prompt_partial = prompt.partial(role="AI助手")

    chain = prompt_partial | deepseek_llm

    res = chain.invoke({"chat_history": chat_history, "input": "我的岗位职责是什么"})
    print(res.content)


if __name__ == "__main__":
    main()

6、结构化输出

6.1、pydantic

  • 自动数据验证(类型、范围、格式等)
  • 丰富的字段配置(默认值、别名、验证器)
  • 返回类型安全的对象,有代码提示
  • 可以定义自定义验证逻辑
from typing import Optional

from pydantic import BaseModel, Field


class Person(BaseModel):
    name: str = Field(description="姓名")
    age: int = Field(description="年龄")
    city: str = Field(description="城市")
    email: Optional[str] = Field(description="邮件(可选)")
    positions: str = Field(description="职位")
import datetime

from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek

from model.person import Person
from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个{role},当前时间{dt}"),
        ("placeholder", "{chat_history}"),
        ("human", "{input}")
    ])

    chat_history = [
        HumanMessage(
            content="我是眼眸,2000年出生,目前就职于上海哔哩哔哩科技有限公司,职位是高级测试开发工程师,邮箱是xumeng03@bilibili.com"),
        AIMessage(content="好的"),
    ]

    prompt_partial = prompt.partial(dt=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), role="AI助手")

    structured_output = deepseek_llm.with_structured_output(Person)

    chain = prompt_partial | structured_output

    person = chain.invoke({"chat_history": chat_history, "input": "提取Person信息"})
    print(person.model_dump())


if __name__ == "__main__":
    main()

6.2、JSON Schema

pydantic模型可以直接转为json schema解构:schema = Person.model_json_schema()

  • 跨语言通用标准
  • 不依赖 Python 特定库
  • 支持复杂的验证规则
import datetime
import json

from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek

from model.person import Person
from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个{role},当前时间{dt}"),
        ("placeholder", "{chat_history}"),
        ("human", "{input}")
    ])

    chat_history = [
        HumanMessage(
            content="我是眼眸,2000年出生,目前就职于上海哔哩哔哩科技有限公司,职位是高级测试开发工程师,邮箱是xumeng03@bilibili.com"),
        AIMessage(content="好的"),
    ]

    prompt_partial = prompt.partial(dt=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), role="AI助手")

    schema = {
        "properties": {
            "name": {
                "description": "姓名",
                "title": "Name",
                "type": "string"
            },
            "age": {
                "description": "年龄",
                "title": "Age",
                "type": "integer"
            },
            "city": {
                "description": "城市",
                "title": "City",
                "type": "string"
            },
            "email": {
                "anyOf": [
                    {
                        "type": "string"
                    },
                    {
                        "type": "null"
                    }
                ],
                "description": "邮件(可选)",
                "title": "Email"
            },
            "positions": {
                "description": "职位",
                "title": "Positions",
                "type": "string"
            }
        },
        "required": [
            "name",
            "age",
            "city",
            "email",
            "positions"
        ],
        "title": "Person",
        "type": "object"
    }

    structured_output = deepseek_llm.with_structured_output(schema)

    chain = prompt_partial | structured_output

    person = chain.invoke({"chat_history": chat_history, "input": "提取Person信息"})
    print(json.dumps(person, indent=4, ensure_ascii=False))


if __name__ == "__main__":
    main()

6.3、StrOutputParser

LLM 的原始输出是 AIMessage 对象,如果只想要文本内容,需要手动提取res.content,StrOutputParser 自动完成这个转换

from langchain_core.messages import HumanMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )

    chain = deepseek_llm | StrOutputParser()
    content = chain.invoke([
        HumanMessage(content="hi!", )
    ])
    print(content)


if __name__ == "__main__":
    main()

6.4、输出解析器

import json

from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_deepseek import ChatDeepSeek

from model.person import Person
from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )

    parser = PydanticOutputParser(pydantic_object=Person)

    # 格式说明,根据模型生成的一段固定的文本
    format_instructions = parser.get_format_instructions()

    prompt = PromptTemplate(
        template="提取以下信息:{query}\n\n{format_instructions}",
        # 使用时才提供的变量
        input_variables=["query"],
        # 创建时就固定的变量
        partial_variables={"format_instructions": format_instructions},
    )

    chain = prompt | deepseek_llm | parser

    person = chain.invoke({
                              "query": "眼眸,2000年出生,目前就职于上海哔哩哔哩科技有限公司,职位是高级测试开发工程师,邮箱是xumeng03@bilibili.com"})
    print(person.model_dump())


if __name__ == "__main__":
    main()

7、Chains

LCEL(LangChain Expression Language)使用 | 管道操作符将组件串联,每个组件都是Runnable 对象,数据从左到右流动,最常见的组合就是 prompt | llm | parser

7.1、常见的 Runnable 类型

  • PromptTemplate/ChatPromptTemplateprompt = PromptTemplate.from_template("...")
  • LLM/ChatModelllm = ChatDeepSeek(...)
  • OutputParserparser = StrOutputParser()
  • Retrieverretriever = vectorstore.as_retriever()
  • RunnableLambdaRunnableLambda(lambda x: x.upper())

7.2、基础链

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    prompt = ChatPromptTemplate.from_template("将{text}翻译为英文")
    chain = prompt | deepseek_llm | StrOutputParser()
    # 1. invoke - 单次调用
    content = chain.invoke({"text": "你好,世界!"})
    print(content)

    # 2. batch - 批量调用
    contents = chain.batch([
        {"text": "你好"},
        {"text": "再见"}
    ])
    for content in contents:
        print(content)

    # 3. stream - 流式输出
    for chunk in chain.stream({"text": "这是一很久很久以前的故事"}):
        print(chunk, end="", flush=True)


if __name__ == "__main__":
    main()

7.3、顺序链

基本使用
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url,
        timeout=60,
        max_retries=2
    )
    outline_prompt = ChatPromptTemplate.from_template("为主题{topic}写一个故事大纲")
    story_prompt = ChatPromptTemplate.from_template("基于大纲写一个完整的故事:{outline}")
    appraise_prompt = ChatPromptTemplate.from_template("评价这个故事\n{story}")
    chain = outline_prompt | deepseek_llm | StrOutputParser() | (
        lambda outline: {"outline": outline}) | story_prompt | deepseek_llm | StrOutputParser() | (
                lambda story: {"story": story}) | appraise_prompt | deepseek_llm | StrOutputParser()
    content = chain.invoke({"topic": "时空穿越"})
    print(content)


if __name__ == "__main__":
    main()
保留中间结果
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url,
        timeout=60,
        max_retries=2
    )
    outline_prompt = ChatPromptTemplate.from_template("为主题{topic}写一个故事大纲")
    story_prompt = ChatPromptTemplate.from_template("基于大纲写一个完整的故事:{outline}")
    appraise_prompt = ChatPromptTemplate.from_template("评价这个故事\n{story}")
    chain = (
            # 输入: {"topic": "..."}
            RunnablePassthrough.assign(
                outline=outline_prompt | deepseek_llm | StrOutputParser()
            ) |
            # 现在有: {"topic": "...", "outline": "..."}
            RunnablePassthrough.assign(
                story=story_prompt | deepseek_llm | StrOutputParser()
            ) |
            # 现在有: {"topic": "...", "outline": "...", "story": "..."}
            RunnablePassthrough.assign(
                appraise=appraise_prompt | deepseek_llm | StrOutputParser()
            )
            # 最终: {"topic": "...", "outline": "...", "story": "...", "appraise": "..."}
    )
    result = chain.invoke({"topic": "时空穿越"})
    print(result["topic"])
    print(result["outline"])
    print(result["story"])
    print(result["appraise"])


if __name__ == "__main__":
    main()
多输入顺序链
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url,
        timeout=60,
        max_retries=2
    )
    outline_prompt = ChatPromptTemplate.from_template("为主题{topic}写一个故事大纲,字数{topic_limit}字以内")
    story_prompt = ChatPromptTemplate.from_template("基于大纲[{outline}]写一个{story_limit}字以内故事")
    appraise_prompt = ChatPromptTemplate.from_template("评价这个故事\n{story}")
    chain = (
        # 输入: {"topic": "..."}
            RunnablePassthrough.assign(
                outline=outline_prompt | deepseek_llm | StrOutputParser()
            ) |
            # 现在有: {"topic": "...", "outline": "..."}
            RunnablePassthrough.assign(
                story=story_prompt | deepseek_llm | StrOutputParser()
            ) |
            # 现在有: {"topic": "...", "outline": "...", "story": "..."}
            RunnablePassthrough.assign(
                appraise=appraise_prompt | deepseek_llm | StrOutputParser()
            )
        # 最终: {"topic": "...", "outline": "...", "story": "...", "appraise": "..."}
    )
    result = chain.invoke({"topic": "时空穿越", "topic_limit": 100, "story_limit": 1000})
    print(result["topic"])
    print(result["outline"])
    print(result["story"])
    print(result["appraise"])


if __name__ == "__main__":
    main()

7.4、数据转换链

RunnableLambda

RunnableLambda 可以将任何Python 函数转换为 Runnable 对象,融入链中

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def uppercase(s: str) -> str:
    return s.upper()


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    transform = RunnableLambda(uppercase)
    prompt = ChatPromptTemplate.from_template("将{text}翻译为英文")
    chain = prompt | deepseek_llm | StrOutputParser() | transform
    content = chain.invoke({"text": "你好,世界!"})
    print(content)


if __name__ == "__main__":
    main()
lambda

可以直接使用 lambda,会自动包装为 RunnableLambda

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def uppercase(s: str) -> str:
    return s.upper()


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    transform = RunnableLambda(uppercase)
    prompt = ChatPromptTemplate.from_template("将{text}翻译为英文")
    chain = prompt | deepseek_llm | StrOutputParser() | transform | (lambda s: s.lower())
    content = chain.invoke({"text": "你好,世界!"})
    print(content)


if __name__ == "__main__":
    main()
RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def uppercase(s: str) -> str:
    return s.upper()


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    transform = RunnableLambda(uppercase)
    prompt = ChatPromptTemplate.from_template("将{text}翻译为英文")
    chain = (
            RunnablePassthrough.assign(
                normal=prompt | deepseek_llm | StrOutputParser() | transform
            ) |
            RunnablePassthrough.assign(
                lower=lambda x: x["text"].lower(),
            )
    )
    x = chain.invoke({"text": "你好,世界!"})
    print(x)


if __name__ == "__main__":
    main()
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def uppercase(s: str) -> str:
    return s.upper()


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    transform = RunnableLambda(uppercase)
    prompt = ChatPromptTemplate.from_template("将{text}翻译为英文")
    chain = (
            RunnablePassthrough.assign(
                normal=prompt | deepseek_llm | StrOutputParser() | transform
            ) |
            RunnablePassthrough.assign(
                lower=lambda x: x["text"].lower(),
            ) |
            RunnablePassthrough().pick(keys=["normal", "lower"])
    )
    x = chain.invoke({"text": "你好,世界!"})
    print(x)


if __name__ == "__main__":
    main()

7.5、条件顺序链

RunnableBranch
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableBranch
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    chain = (
        RunnableBranch(
            (
                lambda x: x["type"] == "code",
                ChatPromptTemplate.from_template("解释这段代码: \n{content}") | deepseek_llm | StrOutputParser()
            ),
            (
                lambda x: x["type"] == "text",
                ChatPromptTemplate.from_template("总结这段内容: \n{content}") | deepseek_llm | StrOutputParser()
            ),
            ChatPromptTemplate.from_template("原样输出用户输入的{content}") | deepseek_llm | StrOutputParser()
        )
    )
    res = chain.invoke({"type": "13213", "content": "ChatPromptTemplate.from_template(\"解释这段代码:{content}\")"})
    print(res)


if __name__ == "__main__":
    main()
多步骤条件链
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableBranch, RunnablePassthrough
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    chain = (
            RunnablePassthrough.assign(
                fruit=ChatPromptTemplate.from_template(
                    "根据用户描述分析用户更喜欢哪种水果(回复:apple/grape): \n{content}") | deepseek_llm | StrOutputParser()
            ) |
            RunnableBranch(
                (
                    lambda x: x["fruit"] == "apple",
                    ChatPromptTemplate.from_template("介绍一下水果苹果") | deepseek_llm | StrOutputParser()
                ),
                (
                    lambda x: x["fruit"] == "grape",
                    ChatPromptTemplate.from_template("介绍一下水果葡萄") | deepseek_llm | StrOutputParser()
                ),
                ChatPromptTemplate.from_template("原样输出用户输入的{content}") | deepseek_llm | StrOutputParser()
            )
    )
    res = chain.invoke({"content": "我喜欢吃葡萄,但是如果跟苹果相比,我更喜欢后者"})
    print(res)


if __name__ == "__main__":
    main()

8、Memory

LLM 本身是无状态的,每次调用都是独立的,Memory 让对话具有连续性

8.1、完整记忆

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url

store = {}


def get_session_history(session_id: str):
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个AI助手"),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{input}")
    ])
    chain1 = prompt | deepseek_llm | StrOutputParser()
    chain2 = RunnableWithMessageHistory(
        chain1,
        get_session_history,
        input_messages_key="input",
        history_messages_key="history"
    )

    res = chain2.invoke({"input": "我是眼眸,职位是高级测试开发工程师"}, config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我的岗位职责是什么"}, config={"configurable": {"session_id": "user1"}})
    print(res)


if __name__ == "__main__":
    main()

8.2、窗口记忆

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url

store = {}


def get_session_history(session_id: str, k: int = 2):
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    history = store[session_id]
    messages = history.messages
    if len(messages) > k:
        trimmed = messages[-k:]
        history.clear()
        for trim in trimmed:
            history.add_message(trim)
    return history


def main():
    deepseek_llm = ChatDeepSeek(
        model="deepseek-chat",
        api_key=deepseek_api_key,
        base_url=deepseek_base_url
    )
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个AI助手"),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{input}")
    ])
    chain1 = prompt | deepseek_llm | StrOutputParser()
    chain2 = RunnableWithMessageHistory(
        chain1,
        get_session_history,
        input_messages_key="input",
        history_messages_key="history"
    )

    res = chain2.invoke({"input": "我是眼眸,职位是高级测试开发工程师"},
                        config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我最喜欢的水果是苹果"}, config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我最喜欢的颜色是青色"}, config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我最喜欢的颜色是青色"}, config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我的岗位是什么?"}, config={"configurable": {"session_id": "user1"}})
    print(res)


if __name__ == "__main__":
    main()

8.3、Token限制记忆

trim_messages 的 token_counter 参数需要模型实现 get_num_tokens_from_messages() 方法,但 DeepSeek 没有实现

from langchain_core.messages import trim_messages
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_deepseek import ChatDeepSeek

from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)

store = {}


def get_session_history(session_id: str, k: int = 1000):
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    history = store[session_id]
    messages = history.messages
    trimmed = trim_messages(messages, max_tokens=k, strategy="last", token_counter=deepseek_llm)
    if len(trimmed) > len(history.messages):
        history.clear()
        for trim in trimmed:
            history.add_message(trim)
    print("===============start==================")
    print(history)
    print("================end===================")
    return history


def main():
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个AI助手"),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{input}")
    ])
    chain1 = prompt | deepseek_llm | StrOutputParser()
    chain2 = RunnableWithMessageHistory(
        chain1,
        get_session_history,
        input_messages_key="input",
        history_messages_key="history"
    )

    res = chain2.invoke({"input": "我是眼眸,职位是高级测试开发工程师"},
                        config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我最喜欢的水果是苹果"}, config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我最喜欢的颜色是青色"}, config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我最喜欢的颜色是青色"}, config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我的岗位是什么?"}, config={"configurable": {"session_id": "user1"}})
    print(res)


if __name__ == "__main__":
    main()

8.4、摘要记忆

from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
from langchain_deepseek import ChatDeepSeek


class SummaryMemory(BaseChatMessageHistory):
    def __init__(self, llm: ChatDeepSeek, max_messages: int = 10):
        super().__init__()
        self.llm = llm
        self.max_messages = max_messages
        self.summary = ""
        self.recent_messages = []

    @property
    def messages(self) -> list[BaseMessage]:
        """返回所有消息(摘要 + 最近消息)"""
        result = []
        if self.summary:
            result.append(SystemMessage(content=f"对话摘要:{self.summary}"))
        result.extend(self.recent_messages)
        return result

    def add_user_message(self, message: str) -> None:
        """添加用户消息"""
        self.recent_messages.append(HumanMessage(content=message))
        self._check_and_summarize()

    def add_ai_message(self, message: str) -> None:
        """添加 AI 消息"""
        self.recent_messages.append(AIMessage(content=message))
        self._check_and_summarize()

    def add_message(self, message: BaseMessage) -> None:
        """添加消息"""
        self.recent_messages.append(message)
        self._check_and_summarize()

    def clear(self) -> None:
        """清空历史"""
        self.summary = ""
        self.recent_messages = []

    def _check_and_summarize(self):
        """检查是否需要生成摘要"""
        if len(self.recent_messages) > self.max_messages:
            self._create_summary()

    def _create_summary(self):
        """生成摘要并保留最近的消息"""
        old_messages = self.recent_messages[:-4]

        formatted_msgs = "\n".join([
            f"{'用户' if isinstance(msg, HumanMessage) else 'AI'}: {msg.content}"
            for msg in old_messages
        ])

        summary_prompt = f"""当前摘要:{self.summary if self.summary else '无'}

新的对话:
{formatted_msgs}

请更新摘要,简洁地总结关键信息:"""

        res = self.llm.invoke(summary_prompt)
        self.summary = res.content
        self.recent_messages = self.recent_messages[-4:]
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_deepseek import ChatDeepSeek

from model.summary_memory import SummaryMemory
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)

store = {}


def get_session_history(session_id: str):
    if session_id not in store:
        store[session_id] = SummaryMemory(
            llm=deepseek_llm,
            max_messages=2
        )
    print("=====================start=======================")
    print(store[session_id])
    print("======================end========================")
    return store[session_id]


def main():
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个AI助手"),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{input}")
    ])
    chain1 = prompt | deepseek_llm | StrOutputParser()
    chain2 = RunnableWithMessageHistory(
        chain1,
        get_session_history,
        input_messages_key="input",
        history_messages_key="history"
    )

    res = chain2.invoke({"input": "我是眼眸,职位是高级测试开发工程师"},
                        config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我最喜欢的水果是苹果"}, config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我最喜欢的颜色是青色"}, config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我最喜欢的颜色是青色"}, config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我的岗位是什么?"}, config={"configurable": {"session_id": "user1"}})
    print(res)


if __name__ == "__main__":
    main()

8.5、持久化存储

from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_deepseek import ChatDeepSeek

from model.summary_memory import SummaryMemory
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)

store = {}


def get_session_history(session_id: str):
    return RedisChatMessageHistory(
        session_id=session_id,
        url=f"redis://:xxxxxxxx@124.222.145.150:6379",
    )


def main():
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个AI助手"),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{input}")
    ])
    chain1 = prompt | deepseek_llm | StrOutputParser()
    chain2 = RunnableWithMessageHistory(
        chain1,
        get_session_history,
        input_messages_key="input",
        history_messages_key="history"
    )

    res = chain2.invoke({"input": "我是眼眸,职位是高级测试开发工程师"},
                        config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我最喜欢的水果是苹果"}, config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我最喜欢的颜色是青色"}, config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我最喜欢的颜色是青色"}, config={"configurable": {"session_id": "user1"}})
    print(res)
    res = chain2.invoke({"input": "我的岗位是什么?"}, config={"configurable": {"session_id": "user1"}})
    print(res)


if __name__ == "__main__":
    main()

9、工具

工具(Tool) 是连接 LLM 与外部世界的桥梁,LLM 本身只能进行推理,无法直接执行数据库查询、API 调用或文件操作。工具将普通的 Python 函数转化为 LLM 可以理解、选择和调用的格式

用户问题 → HumanMessage
   ↓
LLM 决定调用工具 → AIMessage (包含 tool_calls)
   ↓
手动执行工具 → ToolMessage (工具返回结果)
   ↓
将所有消息传回 LLM → AIMessage (最终答案)

9.1、基本使用

from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.tools import tool
from langchain_deepseek import ChatDeepSeek

from model.summary_memory import SummaryMemory
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)

store = {}


@tool
def get_weather(city: str):
    """根据城市名查询天气。如果用户询问特定城市的天气,请使用此工具。"""
    return f"{city} 的天气晴朗,气温 25°C。"


def get_session_history(session_id: str):
    return RedisChatMessageHistory(
        session_id=session_id,
        url=f"redis://:xxxxxxxx@124.222.145.150:6379",
    )


def main():
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个AI助手"),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{input}")
    ])
    tools = [get_weather]
    llm_bind_tools = deepseek_llm.bind_tools(tools)
    chain1 = prompt | llm_bind_tools
    chain2 = RunnableWithMessageHistory(
        chain1,
        get_session_history,
        input_messages_key="input",
        history_messages_key="history"
    )
    res = chain2.invoke({"input": "今天上海的天气怎么样?"}, config={"configurable": {"session_id": "user1"}})
    if res.tool_calls:
        print("LLM 想调用工具:")
        print(res.tool_calls)
    else:
        print(res.content)


if __name__ == "__main__":
    main()

9.2、处理工具调用

单个工具调用
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_core.messages import ToolMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.tools import tool
from langchain_deepseek import ChatDeepSeek

from model.summary_memory import SummaryMemory
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)

store = {}


@tool
def get_weather(city: str):
    """根据城市名查询天气。如果用户询问特定城市的天气,请使用此工具。"""
    return f"{city} 的天气晴朗,气温 25°C。"


def get_session_history(session_id: str):
    return RedisChatMessageHistory(
        session_id=session_id,
        url=f"redis://:xxxxxxxx@124.222.145.150:6379",
    )


def main():
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个AI助手"),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{input}")
    ])
    tools = [get_weather]
    llm_bind_tools = deepseek_llm.bind_tools(tools)
    chain1 = prompt | llm_bind_tools
    chain2 = RunnableWithMessageHistory(
        chain1,
        get_session_history,
        input_messages_key="input",
        history_messages_key="history"
    )
    res = chain2.invoke({"input": "今天上海的天气怎么样?"}, config={"configurable": {"session_id": "user1"}})
    print("LLM思考结果: ", res)
    if res.tool_calls:
        print("LLM 想调用工具", res.tool_calls)
        history = get_session_history("user1")
        for tool_call in res.tool_calls:
            if tool_call["name"] == "get_weather":
                result = get_weather.invoke(tool_call["args"])
                print("工具调用结果: ", result)
                tool_message = ToolMessage(
                    content=str(result),
                    tool_call_id=tool_call["id"],
                )
                history.add_message(tool_message)
                res = chain2.invoke({"input": ""}, config={"configurable": {"session_id": "user1"}})
                print("LLM思考结果: ", res)
    else:
        print(res.content)


if __name__ == "__main__":
    main()
多个工具调用

一定要将所有的工具调用完才能调用模型

from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_core.messages import ToolMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.tools import tool
from langchain_deepseek import ChatDeepSeek

from model.summary_memory import SummaryMemory
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)

store = {}


@tool
def get_weather(city: str):
    """根据城市名查询天气。如果用户询问特定城市的天气,请使用此工具。"""
    return f"{city} 的天气晴朗,气温 25°C。"


@tool
def get_activity(city: str):
    """根据城市名查询活动。如果用户询问特定城市的活动,请使用此工具。"""
    return f"{city} 的活动有玉兰花拍摄、樱花拍摄"


def get_session_history(session_id: str):
    return RedisChatMessageHistory(
        session_id=session_id,
        url=f"redis://:xxxxxxxx@124.222.145.150:6379",
    )


def main():
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个AI助手"),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{input}")
    ])
    tools = [get_weather, get_activity]
    llm_bind_tools = deepseek_llm.bind_tools(tools)
    chain1 = prompt | llm_bind_tools
    chain2 = RunnableWithMessageHistory(
        chain1,
        get_session_history,
        input_messages_key="input",
        history_messages_key="history"
    )
    res = chain2.invoke({"input": "今天上海的天气怎么样?顺便帮我查询一下最近的活动"},
                        config={"configurable": {"session_id": "user1"}})
    print("LLM思考结果: ", res)
    if res.tool_calls:
        print("LLM 想调用工具", res.tool_calls)
        history = get_session_history("user1")
        for tool_call in res.tool_calls:
            if tool_call["name"] == "get_weather":
                result = get_weather.invoke(tool_call["args"])
                print("工具调用结果: ", result)
                tool_message = ToolMessage(
                    content=str(result),
                    tool_call_id=tool_call["id"],
                )
                history.add_message(tool_message)
            if tool_call["name"] == "get_activity":
                result = get_activity.invoke(tool_call["args"])
                print("工具调用结果: ", result)
                tool_message = ToolMessage(
                    content=str(result),
                    tool_call_id=tool_call["id"],
                )
                history.add_message(tool_message)
        res = chain2.invoke({"input": ""}, config={"configurable": {"session_id": "user1"}})
        print("LLM思考结果: ", res)
    else:
        print(res.content)


if __name__ == "__main__":
    main()

10、Agent

Agent 是一个使用大语言模型(LLM)作为推理引擎的系统,它能够自主决定采取哪些行动、执行顺序以及如何解释结果来达成目标,并且具有自动重试和纠错能力,当工具调用失败时,Agent 会自动分析错误原因并重试

1. 输入(Input):用户提供查询或指令
2. 思考(Thought):LLM 分析输入,查看可用工具,决定行动计划或选择特定工具
3. 行动(Action):Agent 执行选定的工具并传入参数
4. 观察(Observation):系统获取工具输出并反馈给 LLM
5. 迭代(Iteration):LLM 使用观察结果更新上下文,然后决定:
    - 执行另一个行动(返回步骤 2)
    - 提供最终答案(目标已达成)

10.1、基本使用

单论对话
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
from langchain_deepseek import ChatDeepSeek

from tool.activity_tool import get_activity
from tool.weather_tool import get_weather
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)


def main():
    # 定义工具
    tools = [get_weather, get_activity]

    # 创建 agent(最简单的方式)
    agent = create_agent(model=deepseek_llm, tools=tools)

    # 执行 agent - 使用正确的消息格式
    result = agent.invoke({"messages": [HumanMessage(content="北京的天气怎么样?")]})

    # 打印结果
    for message in result["messages"]:
        if isinstance(message, SystemMessage):
            print(f"😊 系统: {message.content}")
        elif isinstance(message, HumanMessage):
            print(f"👤 用户: {message.content}")
        elif isinstance(message, AIMessage):
            print(f"🤖 AI: {message.content}")
        elif isinstance(message, ToolMessage):
            print(f"🔧 工具: {message.content}")


if __name__ == "__main__":
    main()
多轮对话
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
from langchain_deepseek import ChatDeepSeek
from langgraph.checkpoint.memory import MemorySaver

from tool.activity_tool import get_activity
from tool.weather_tool import get_weather
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)


def main():
    # 定义工具
    tools = [get_weather, get_activity]

    # 创建内存保存器
    memory = MemorySaver()

    # 创建 agent,添加 checkpointer 以保存对话历史
    agent = create_agent(model=deepseek_llm, tools=tools, checkpointer=memory)

    # 执行 agent - 使用正确的消息格式
    result = agent.invoke({"messages": [HumanMessage(content="北京的天气怎么样?")]},
                          config={"configurable": {"thread_id": "user1"}})

    for message in result["messages"]:
        print(message)
    
    print("=================================")
    
    result = agent.invoke({"messages": [HumanMessage(content="我刚才问了什么?")]},
                          config={"configurable": {"thread_id": "user1"}})
    for message in result["messages"]:
        print(message)


if __name__ == "__main__":
    main()
结构化输出
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
from langchain_deepseek import ChatDeepSeek
from langgraph.checkpoint.memory import MemorySaver

from model.person import Person
from tool.activity_tool import get_activity
from tool.weather_tool import get_weather
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)


def main():
    # 定义工具
    tools = [get_weather, get_activity]

    # 创建内存保存器
    memory = MemorySaver()

    # 创建 agent,添加 checkpointer 以保存对话历史
    agent = create_agent(model=deepseek_llm, tools=tools, checkpointer=memory, response_format=Person)

    # 执行 agent - 使用正确的消息格式
    result = agent.invoke({"messages": [HumanMessage(
        content="我是眼眸,2000年出生,目前就职于上海哔哩哔哩科技有限公司,职位是高级测试开发工程师,邮箱是xumeng03@bilibili.com")]},
                          config={"configurable": {"thread_id": "user1"}})

    # 打印结果
    person = result["structured_response"]
    print(person.model_dump_json())


if __name__ == "__main__":
    main()

10.2、Tools

所有的工具都由三部分组成:工具名称、工具描述、工具函数

使用 @tool 装饰器
from langchain_core.tools import tool


@tool
def get_weather(city: str):
    """根据城市名查询天气。如果用户询问特定城市的天气,请使用此工具。"""
    return f"{city} 的天气晴朗,气温 25°C。"
使用 Tool 类
from langchain_core.tools import tool, Tool


def get_activity(city: str):
    return f"{city} 的活动有玉兰花拍摄、樱花拍摄"


activity = Tool(
    name="get_activity",
    description="根据城市名查询活动。如果用户询问特定城市的活动,请使用此工具。",
    func=get_activity
)
继承 BaseTool
from langchain_core.tools import BaseTool


class ActivityTool(BaseTool):
    name = "get_activity",
    description = "根据城市名查询活动。如果用户询问特定城市的活动,请使用此工具。",

    def _run(self, city: str) -> str:
        return f"{city} 的活动有玉兰花拍摄、樱花拍摄"

10.3、Agent Prompts

from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
from langchain_deepseek import ChatDeepSeek
from langgraph.checkpoint.memory import MemorySaver

from tool.activity_tool import get_activity
from tool.weather_tool import get_weather
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)


def main():
    # 定义工具
    tools = [get_weather, get_activity]

    # 创建内存保存器
    memory = MemorySaver()

    # 创建 agent,添加 checkpointer 以保存对话历史
    agent = create_agent(
        tools=tools,
        model=deepseek_llm,
        system_prompt="""你是一个专业的助手。
        
          工具使用规则:
          - 涉及天气查询,必须调用get_weather工具,不要猜测
          - 涉及活动推荐,必须调用get_activity工具
          - 如果工具返回错误,明确告知用户查询失败
          - 不要编造数据
        
          回答要求:
          - 基于工具返回的真实数据回答
          - 如果没有相关工具,明确说明无法查询
          """,
        checkpointer=memory,
    )

    # 执行 agent - 使用正确的消息格式
    result = agent.invoke({"messages": [HumanMessage("今天上海的天气怎么样?")]},
                          config={"configurable": {"thread_id": "user1"}, "recursion_limit": 5})

    # 打印结果
    for message in result["messages"]:
        if isinstance(message, SystemMessage):
            print(f"😊 系统: {message.content}")
        elif isinstance(message, HumanMessage):
            print(f"👤 用户: {message.content}")
        elif isinstance(message, AIMessage):
            print(f"🤖 AI: {message.content}")
        elif isinstance(message, ToolMessage):
            print(f"🔧 工具: {message.content}")


if __name__ == "__main__":
    main()

10.4、Output Parsers

Output Parser 是 Agent 系统的"翻译官",负责将 LLM 的文本输出转换为程序可以理解和执行的结构化数据

import datetime

from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
from langchain_core.output_parsers import PydanticOutputParser
from langchain_deepseek import ChatDeepSeek
from langgraph.checkpoint.memory import MemorySaver

from model.person import Person
from tool.activity_tool import get_activity
from tool.weather_tool import get_weather
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)


def main():
    # 定义工具
    tools = [get_weather, get_activity]

    # 创建内存保存器
    memory = MemorySaver()

    # OutputParser
    parser = PydanticOutputParser(pydantic_object=Person)

    # 创建 agent,添加 checkpointer 以保存对话历史
    agent = create_agent(
        tools=tools,
        model=deepseek_llm,
        system_prompt=f"""当前时间是{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}, 你是一个专业的助手。
        
          工具使用规则:
          - 涉及天气查询,必须调用get_weather工具,不要猜测
          - 涉及活动推荐,必须调用get_activity工具
          - 如果工具返回错误,明确告知用户查询失败
          - 不要编造数据
        
          回答要求:
          - 基于工具返回的真实数据回答
          - 如果没有相关工具,明确说明无法查询
          
          输出要求:
          - {parser.get_format_instructions()}
          """,
        checkpointer=memory,
    )

    # 执行 agent - 使用正确的消息格式
    result = agent.invoke({"messages": [HumanMessage(
        "我是眼眸,2000年出生,目前就职于上海哔哩哔哩科技有限公司,职位是高级测试开发工程师,邮箱是xumeng03@bilibili.com")]},
        config={"configurable": {"thread_id": "user1"}, "recursion_limit": 5})

    # 打印结果
    for message in result["messages"]:
        if isinstance(message, SystemMessage):
            print(f"😊 系统: {message.content}")
        elif isinstance(message, HumanMessage):
            print(f"👤 用户: {message.content}")
        elif isinstance(message, AIMessage):
            print(f"🤖 AI: {message.content}")
        elif isinstance(message, ToolMessage):
            print(f"🔧 工具: {message.content}")


if __name__ == "__main__":
    main()

10.5、Agent 模式

新版不需要选择Agent类,但需要根据场景设计prompt

ReAct模式

默认就是ReAct模式,特点是推理(Reasoning)→ 行动(Acting)→ 观察(Observation)循环

import datetime

from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
from langchain_core.output_parsers import PydanticOutputParser
from langchain_deepseek import ChatDeepSeek
from langgraph.checkpoint.memory import MemorySaver

from model.person import Person
from tool.activity_tool import get_activity
from tool.weather_tool import get_weather
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)


def main():
    # 定义工具
    tools = [get_weather, get_activity]

    # 创建内存保存器
    memory = MemorySaver()

    # OutputParser
    parser = PydanticOutputParser(pydantic_object=Person)

    # 创建 agent,添加 checkpointer 以保存对话历史
    agent = create_agent(
        tools=tools,
        model=deepseek_llm,
        system_prompt=f"""当前时间是{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}, 你是一个专业的助手。
        
          工具使用规则:
          - 涉及天气查询,必须调用get_weather工具,不要猜测
          - 涉及活动推荐,必须调用get_activity工具
          - 如果工具返回错误,明确告知用户查询失败
          - 不要编造数据
        
          回答要求:
          - 基于工具返回的真实数据回答
          - 如果没有相关工具,明确说明无法查询
          
          输出要求:
          - {parser.get_format_instructions()}
          """,
        checkpointer=memory,
    )

    # 执行 agent - 使用正确的消息格式
    result = agent.invoke({"messages": [HumanMessage(
        "我是眼眸,2000年出生,目前就职于上海哔哩哔哩科技有限公司,职位是高级测试开发工程师,邮箱是xumeng03@bilibili.com")]},
        config={"configurable": {"thread_id": "user1"}, "recursion_limit": 5})

    # 打印结果
    for message in result["messages"]:
        if isinstance(message, SystemMessage):
            print(f"😊 系统: {message.content}")
        elif isinstance(message, HumanMessage):
            print(f"👤 用户: {message.content}")
        elif isinstance(message, AIMessage):
            print(f"🤖 AI: {message.content}")
        elif isinstance(message, ToolMessage):
            print(f"🔧 工具: {message.content}")


if __name__ == "__main__":
    main()
Plan-and-Execute模式

Plan-and-Execute模式特点是先制定完整计划,再逐步执行

import datetime

from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
from langchain_core.output_parsers import PydanticOutputParser
from langchain_deepseek import ChatDeepSeek
from langgraph.checkpoint.memory import MemorySaver

from model.person import Person
from tool.activity_tool import get_activity
from tool.weather_tool import get_weather
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)


def main():
    # 定义工具
    tools = [get_weather, get_activity]

    # 创建内存保存器
    memory = MemorySaver()

    # 创建 agent,添加 checkpointer 以保存对话历史
    agent = create_agent(
        tools=tools,
        model=deepseek_llm,
        system_prompt=f"""当前时间是{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}, 你是一个规划型助手。
        
          工作流程:
          1. 首先制定完整的执行计划
          2. 列出需要调用的工具和顺序
          3. 按计划逐步执行
          4. 汇总结果
        
          示例:
          用户问:"上海天气如何,适合什么活动?"
          计划:
          - 步骤1:调用get_weather获取上海天气
          - 步骤2:根据天气调用get_activity获取活动建议
          - 步骤3:整合信息回答用户
          """,
        checkpointer=memory,
        debug=True,
    )

    # 执行 agent - 使用正确的消息格式
    result = agent.invoke({"messages": [HumanMessage("上海天气如何,适合什么活动?")]},
                          config={"configurable": {"thread_id": "user1"}, "recursion_limit": 25})

    # 打印结果
    for message in result["messages"]:
        if isinstance(message, SystemMessage):
            print(f"😊 系统: {message.content}")
        elif isinstance(message, HumanMessage):
            print(f"👤 用户: {message.content}")
        elif isinstance(message, AIMessage):
            print(f"🤖 AI: {message.content}")
        elif isinstance(message, ToolMessage):
            print(f"🔧 工具: {message.content}")


if __name__ == "__main__":
    main()
对话模式

对话模式特点是使用memory保持上下文,多轮对话

import datetime

from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
from langchain_core.output_parsers import PydanticOutputParser
from langchain_deepseek import ChatDeepSeek
from langgraph.checkpoint.memory import MemorySaver

from model.person import Person
from tool.activity_tool import get_activity
from tool.weather_tool import get_weather
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)


def main():
    # 定义工具
    tools = [get_weather, get_activity]

    # 创建内存保存器
    memory = MemorySaver()

    # 创建 agent,添加 checkpointer 以保存对话历史
    agent = create_agent(
        tools=tools,
        model=deepseek_llm,
        system_prompt=f"""当前时间是{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}, 你是一个AI助手。
        
          工具使用规则:
          - 涉及天气查询,必须调用get_weather工具,不要猜测
          - 涉及活动推荐,必须调用get_activity工具
          - 如果工具返回错误,明确告知用户查询失败
          - 不要编造数据
        
          回答要求:
          - 基于工具返回的真实数据回答
          - 如果没有相关工具,明确说明无法查询
          """,
        checkpointer=memory,
        debug=True,
    )

    # 执行 agent - 使用正确的消息格式
    result = agent.invoke({"messages": [HumanMessage("上海天气如何,适合什么活动?")]},
                          config={"configurable": {"thread_id": "user1"}, "recursion_limit": 25})

    result = agent.invoke({"messages": [HumanMessage("我的时间有限,只能参加一个活动,帮我推荐一个")]},
                          config={"configurable": {"thread_id": "user1"}, "recursion_limit": 25})

    # 打印结果
    for message in result["messages"]:
        if isinstance(message, SystemMessage):
            print(f"😊 系统: {message.content}")
        elif isinstance(message, HumanMessage):
            print(f"👤 用户: {message.content}")
        elif isinstance(message, AIMessage):
            print(f"🤖 AI: {message.content}")
        elif isinstance(message, ToolMessage):
            print(f"🔧 工具: {message.content}")


if __name__ == "__main__":
    main()
工具优先模式

工具优先模式的特点是强制使用工具,避免幻觉

import datetime

from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
from langchain_core.output_parsers import PydanticOutputParser
from langchain_deepseek import ChatDeepSeek
from langgraph.checkpoint.memory import MemorySaver

from model.person import Person
from tool.activity_tool import get_activity
from tool.weather_tool import get_weather
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)


def main():
    # 定义工具
    tools = [get_weather, get_activity]

    # 创建内存保存器
    memory = MemorySaver()

    # 创建 agent,添加 checkpointer 以保存对话历史
    agent = create_agent(
        tools=tools,
        model=deepseek_llm,
        system_prompt=f"""当前时间是{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}, 你是一个严格的助手。

            铁律:
            - 任何事实性问题必须使用工具查询
            - 禁止猜测或编造信息
            - 如果没有合适的工具,明确说"无法查询"
            - 不要基于常识回答,必须基于工具返回的数据
            
            示例:
            ❌ 错误:"上海今天应该是晴天"(猜测)
            ✅ 正确:调用get_weather("上海"),然后基于返回结果回答
          """,
        checkpointer=memory,
        debug=True,
    )

    # 执行 agent - 使用正确的消息格式
    result = agent.invoke({"messages": [HumanMessage("上海天气如何,适合什么活动?")]},
                          config={"configurable": {"thread_id": "user1"}, "recursion_limit": 25})

    result = agent.invoke({"messages": [HumanMessage("我的时间有限,只能参加一个活动,帮我推荐一个")]},
                          config={"configurable": {"thread_id": "user1"}, "recursion_limit": 25})

    # 打印结果
    for message in result["messages"]:
        if isinstance(message, SystemMessage):
            print(f"😊 系统: {message.content}")
        elif isinstance(message, HumanMessage):
            print(f"👤 用户: {message.content}")
        elif isinstance(message, AIMessage):
            print(f"🤖 AI: {message.content}")
        elif isinstance(message, ToolMessage):
            print(f"🔧 工具: {message.content}")


if __name__ == "__main__":
    main()

10.6、Agent Memory

旧版Memory对象存储对话历史,新版Checkpointer保存整个Agent状态(包括消息、中间推理步骤、工具执行历史、自定义变量等)

MemorySaver
import datetime

from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
from langchain_core.output_parsers import PydanticOutputParser
from langchain_deepseek import ChatDeepSeek
from langgraph.checkpoint.memory import MemorySaver

from model.person import Person
from tool.activity_tool import get_activity
from tool.weather_tool import get_weather
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url
)


def main():
    # 定义工具
    tools = [get_weather, get_activity]

    # 创建内存保存器
    memory = MemorySaver()

    # 创建 agent,添加 checkpointer 以保存对话历史
    agent = create_agent(
        tools=tools,
        model=deepseek_llm,
        system_prompt=f"""当前时间是{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}, 你是一个严格的助手。

            铁律:
            - 任何事实性问题必须使用工具查询
            - 禁止猜测或编造信息
            - 如果没有合适的工具,明确说"无法查询"
            - 不要基于常识回答,必须基于工具返回的数据
            
            示例:
            ❌ 错误:"上海今天应该是晴天"(猜测)
            ✅ 正确:调用get_weather("上海"),然后基于返回结果回答
          """,
        checkpointer=memory,
        debug=True,
    )

    # 执行 agent - 使用正确的消息格式
    result = agent.invoke({"messages": [HumanMessage("上海天气如何,适合什么活动?")]},
                          config={"configurable": {"thread_id": "user1"}, "recursion_limit": 25})

    result = agent.invoke({"messages": [HumanMessage("我的时间有限,只能参加一个活动,帮我推荐一个")]},
                          config={"configurable": {"thread_id": "user1"}, "recursion_limit": 25})

    # 打印结果
    for message in result["messages"]:
        if isinstance(message, SystemMessage):
            print(f"😊 系统: {message.content}")
        elif isinstance(message, HumanMessage):
            print(f"👤 用户: {message.content}")
        elif isinstance(message, AIMessage):
            print(f"🤖 AI: {message.content}")
        elif isinstance(message, ToolMessage):
            print(f"🔧 工具: {message.content}")


if __name__ == "__main__":
    main()
PostgresSaver

docker部署postgres

docker run -d -p 35432:5432 --name postgres \
	-e POSTGRES_DB=agent \
	-e POSTGRES_USER=root \
	-e POSTGRES_PASSWORD=xxxxxxxx \
	-v postgres-data:/var/lib/postgresql/data \
	postgres:16

安装依赖

uv add "psycopg[binary]"
uv add "langgraph-checkpoint-postgres[binary]"

PostgresSaver使用

import datetime

from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.checkpoint.postgres import PostgresSaver

from tool.activity_tool import get_activity
from tool.weather_tool import get_weather
from util.env_util import deepseek_api_key, deepseek_base_url

deepseek_llm = ChatOpenAI(
    model="deepseek-chat",
    api_key=deepseek_api_key,
    base_url=deepseek_base_url,
    timeout=30,
    max_retries=2
)


def main():
    # 定义工具
    tools = [get_weather, get_activity]

    # 创建内存保存器
    conn_string = "postgresql://root:xxxxxxxx@124.222.145.150:5432/agent"
    # 使用 with 的好处是自动处理资源清理,不需要手动调用 close() 或 aclose()
    with PostgresSaver.from_conn_string(conn_string) as checkpointer:
        checkpointer.setup()
        # 创建 agent,添加 checkpointer 以保存对话历史
        agent = create_agent(
            tools=tools,
            model=deepseek_llm,
            system_prompt=f"""当前时间是{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}, 你是一个AI助手。
    
              工具使用规则:
              - 涉及天气查询,必须调用get_weather工具,不要猜测
              - 涉及活动推荐,必须调用get_activity工具
              - 如果工具返回错误,明确告知用户查询失败
              - 不要编造数据
    
              回答要求:
              - 基于工具返回的真实数据回答
              - 如果没有相关工具,明确说明无法查询
              """,
            checkpointer=checkpointer,
            debug=True,
        )

        # 执行 agent - 使用正确的消息格式
        result = agent.invoke({"messages": [HumanMessage("上海天气如何,适合什么活动?")]},
                              config={"configurable": {"thread_id": "user1"}})
        print(result)

        result = agent.invoke({"messages": [HumanMessage("我的时间有限,只能参加一个活动,帮我推荐一个")]},
                              config={"configurable": {"thread_id": "user1"}, "recursion_limit": 25})
        print(result)

        # 打印结果
        for message in result["messages"]:
            if isinstance(message, SystemMessage):
                print(f"😊 系统: {message.content}")
            elif isinstance(message, HumanMessage):
                print(f"👤 用户: {message.content}")
            elif isinstance(message, AIMessage):
                print(f"🤖 AI: {message.content}")
            elif isinstance(message, ToolMessage):
                print(f"🔧 工具: {message.content}")


if __name__ == "__main__":
    main()
PostgresSaver存储详解

基本概念了解

Channel:通道,存储不同类型数据的"容器"
- messages 通道:存放对话消息
- agent_state 通道:存放 agent 的内部状态
- __start__ 通道:存放起始输入

Node:节点,执行流程中的各个步骤
- model 节点:调用 LLM 生成回复
- tools 节点:执行工具调用
- __start__ 节点:开始节点

执行流程分析

select * from checkpoints where thread_id = 'user1' order by checkpoint_id asc;

第一次调用:"上海天气如何,适合什么活动?"

这个问题需要调用两个工具:get_weather 和 get_activity
Checkpoint 1: 初始状态
├─ 触发时机: agent.invoke 开始
├─ 状态内容: {"messages": [HumanMessage("上海天气如何,适合什么活动?")]}
├─ next: "agent"
└─ parent: null

↓ [agent 节点执行:LLM 分析问题]

Checkpoint 2: Agent 决策完成
├─ 触发时机: LLM 决定调用 get_weather 工具
├─ 状态内容:
│   - messages: [用户消息, AIMessage(tool_calls=[get_weather])]
├─ next: "tools"
└─ parent: checkpoint_1

↓ [tools 节点执行:调用 get_weather("上海")]

Checkpoint 3: 第一个工具执行完成
├─ 触发时机: get_weather 返回结果
├─ 状态内容:
│   - messages: [..., ToolMessage(name="get_weather", content="上海晴天...")]
├─ next: "agent"
└─ parent: checkpoint_2

↓ [agent 节点执行:LLM 看到天气结果]

Checkpoint 4: Agent 再次决策
├─ 触发时机: LLM 决定调用 get_activity 工具
├─ 状态内容:
│   - messages: [..., AIMessage(tool_calls=[get_activity])]
├─ next: "tools"
└─ parent: checkpoint_3

↓ [tools 节点执行:调用 get_activity("上海", "晴天")]

Checkpoint 5: 第二个工具执行完成
├─ 触发时机: get_activity 返回结果
├─ 状态内容:
│   - messages: [..., ToolMessage(name="get_activity", content="推荐活动...")]
├─ next: "agent"
└─ parent: checkpoint_4

↓ [agent 节点执行:LLM 生成最终回复]

Checkpoint 6: 第一轮对话完成
├─ 触发时机: LLM 生成最终回复,不再调用工具
├─ 状态内容:
│   - messages: [..., AIMessage(content="上海今天晴天,适合户外活动...")]
├─ next: null (END)
└─ parent: checkpoint_5

第二次调用:"我的时间有限,只能参加一个活动,帮我推荐一个"

这个问题基于之前的上下文,可能不需要再调用工具

Checkpoint 7: 新一轮对话开始
├─ 触发时机: 第二次 agent.invoke 开始
├─ 状态内容:
│   - messages: [checkpoint_6 的所有历史消息 + 新的 HumanMessage]
├─ next: "agent"
└─ parent: checkpoint_6

↓ [agent 节点执行:LLM 基于历史上下文分析]

Checkpoint 8: 第二轮对话完成
├─ 触发时机: LLM 直接生成回复(无需调用工具)
├─ 状态内容:
│   - messages: [..., AIMessage(content="基于之前的活动推荐,我建议...")]
├─ next: null (END)
└─ parent: checkpoint_7

checkpoints表结构介绍

-- checkpoints 表
-- 每条记录是一个"状态快照",不是执行图中的节点
CREATE TABLE IF NOT EXISTS checkpoints
(
    thread_id            TEXT  NOT NULL COMMENT '会话线程ID,用于区分不同的对话上下文',
    checkpoint_ns        TEXT  NOT NULL DEFAULT '' COMMENT '检查点命名空间,用于在复杂图结构中隔离不同子图的状态',
    checkpoint_id        TEXT  NOT NULL COMMENT '检查点唯一标识符,通常为UUID',
    parent_checkpoint_id TEXT COMMENT '父检查点ID,指向上一个检查点形成状态演进链,支持回溯和分支',
    type                 TEXT COMMENT '检查点类型标识',
    checkpoint           JSONB NOT NULL COMMENT '检查点完整状态数据(JSONB格式),包含消息历史、中间状态等',
    metadata             JSONB NOT NULL DEFAULT '{}' COMMENT '检查点元数据(JSONB格式),存储时间戳、版本号、用户信息等附加信息',
    PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id)
) COMMENT='对话状态检查点主表,存储会话的完整状态快照';

checkpoints.checkpoint字段结构介绍

{
    // LangGraph 的 schema 版本
    "v": 4,
    // checkpoint 的唯一标识符(UUID),对应表中的 checkpoint_id
    "id": "1f12f75d-3ee6-6cad-800e-673d15950963",
    // checkpoint 创建的时间戳(ISO 8601 格式,带时区)
    "ts": "2026-04-03T15:57:33.849293+00:00",
    // 节点数据版本(每个节点处理数据时,需要知道自己看到的是哪个版本的数据)
    "versions_seen": {
        "model": {
            "branch:to:model": "00000000000000000000000000000015.0.32419480173508886"
        },
        "tools": {},
        "__input__": {},
        "__start__": {
            "__start__": "00000000000000000000000000000014.0.8675745171161198"
        }
    },
    // 通道数据,存储各个通道的实际数据值,数据在 checkpoint_blobs 表中
    "channel_values": {},
    // 通道版本号,
    "channel_versions": {
        // 消息通道版本(或者说容器被修改的次数)
        "messages": "00000000000000000000000000000016.0.25846931507483395",
        // 起始通道版本(或者说容器被修改的次数)
        "__start__": "00000000000000000000000000000015.0.32419480173508886",
        // LangGraph 内部任务队列版本(或者说容器被修改的次数)
        "__pregel_tasks": "00000000000000000000000000000012.0.34774614538873905",
        // 分支到模型版本(或者说容器被修改的次数)
        "branch:to:model": "00000000000000000000000000000016.0.25846931507483395"
    },
    // 本次更新的通道
    "updated_channels": [
        "messages"
    ]
}

checkpoints.metadata字段结构介绍

{
    // 执行步骤编号
    "step": 8,
    // 产生阶段
    "source": "loop",
    // 父节点信息
    "parents": {},
    // 创建方式标识
    "ls_integration": "langchain_create_agent"
}

执行步骤分析

select * from checkpoint_writes where thread_id = 'user1' order by checkpoint_id asc, idx asc;

第一轮对话:"上海天气如何,适合什么活动?"

记录1 → 用户输入                                                                                               
checkpoint_id: 1f12f7ca-e37f-6369-bfff-610650e70739                                                         
task_id: e0f2243c-3d0a-e98d-1b93-65c50d265057
channel: messages                                                                                           
内容: "上海天气如何,适合什么活动?"                                                                               

记录2 → 路由到 model 节点
channel: branch:to:model
作用: 将消息路由给 AI 模型处理

记录3 → AI 决定调用 get_weather 工具
checkpoint_id: 1f12f7ca-e383-647e-8000-6b6432dd7fd4
channel: messages
内容: AI 消息 + tool_call (get_weather, city="上海")

记录4 → 任务调度
channel: __pregel_tasks
作用: LangGraph 内部任务调度,准备执行工具

记录5 → get_weather 工具返回结果
checkpoint_id: 1f12f7ca-fe17-65b4-8001-c0e1a430e44f
channel: messages
内容: "上海 的天气晴朗,气温 25°C。"

记录6 → 再次路由到 model
channel: branch:to:model
作用: 将工具结果返回给 AI 继续处理

记录7 → AI 决定调用 get_activity 工具
checkpoint_id: 1f12f7ca-fe1d-6b66-8002-4d40112fc355
channel: messages
内容: AI 消息 + tool_call (get_activity, city="上海")

记录8 → 任务调度
channel: __pregel_tasks
作用: 准备执行 get_activity 工具

记录9 → get_activity 工具返回结果
checkpoint_id: 1f12f7cb-1501-6331-8003-5fa698212b4e
channel: messages
内容: "上海 的活动有玫兰芳拉扯、樱芳拉扯"

记录10 → 路由到 model
channel: branch:to:model

记录11 → AI 生成最终回复
checkpoint_id: 1f12f7cb-1505-6bdc-8004-38ff86c9bedb
channel: messages
内容: 综合天气和活动信息的完整回答

第二轮对话:"我的时间有限,只能参加一个活动,帮我推荐一个"

记录12 → 用户第二轮输入
checkpoint_id: 1f12f7cb-3b67-67cc-8006-d8daa405a148
channel: messages
内容: "我的时间有限,只能参加一个活动,帮我推荐一个"

记录13 → 路由到 model
channel: branch:to:model

记录14 → AI 基于上下文直接回复
checkpoint_id: 1f12f7cb-3b6a-6c08-8007-68be15499541
channel: messages
内容: 推荐樱芳拉扯(基于之前的天气和活动信息)

checkpoint_writes 表解构

-- checkpoint_writes 表
-- 每条记录是一个步骤
CREATE TABLE IF NOT EXISTS checkpoint_writes
(
    thread_id     TEXT    NOT NULL COMMENT '关联的会话线程ID,外键关联到checkpoints表',
    checkpoint_ns TEXT    NOT NULL DEFAULT '' COMMENT '关联的命名空间,外键关联到checkpoints表',
    checkpoint_id TEXT    NOT NULL COMMENT '关联的检查点ID,外键关联到checkpoints表',
    task_id       TEXT    NOT NULL COMMENT '写入任务唯一标识,用于标识一个具体的写入操作',
    idx           INTEGER NOT NULL COMMENT '同一任务中多个写入操作的顺序索引',
    channel       TEXT    NOT NULL COMMENT '状态通道名称,如messages、context等,标识写入的目标状态键',
    type          TEXT COMMENT '写入操作类型标识',
    value         JSONB COMMENT '写入的具体数据(JSONB格式)',
    PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)
) COMMENT='检查点写入操作记录表,追踪状态变更的细粒度操作';

checkpoint_writes.channel枚举

1. messages
用途: 存储对话消息流
内容: HumanMessage、AIMessage、ToolMessage 等
示例: 用户输入、AI回复、工具调用结果

2. branch:to:model
用途: 路由控制信号
内容: 通常为 null 或空
作用: 指示消息流应该路由到 model 节点继续处理

3. __pregel_tasks
用途: LangGraph 内部任务调度
内容: 序列化的任务对象(包含工具调用信息、状态等)
作用: 协调多个节点的执行顺序和数据传递

4. start
用途: 节点启动标记
内容: 通常为空对象 {}
作用: 标记某个节点开始执行

5. end
用途: 节点结束标记
内容: 通常为空对象 {}
作用: 标记某个节点执行完成或整个流程结束

6. state
用途: 自定义状态存储
内容: 用户定义的状态数据
示例: {"current_step": "processing", "retry_count": 0}

7. context
用途: 上下文信息存储
内容: 会话级别的上下文数据
示例: 用户偏好、会话配置等

8. metadata
用途: 元数据存储
内容: 时间戳、token 使用量、执行耗时等
示例: {"timestamp": "...", "tokens": 150}

9. tools
用途: 工具相关信息
内容: 工具定义、工具状态等

10. 自定义 channel
用途: 用户自定义的状态通道
命名: 任意字符串(如 "user_profile", "session_data")

消息存储

checkpoint_blobs 表

-- checkpoint_blobs 表
-- 
CREATE TABLE IF NOT EXISTS checkpoint_blobs
(
    thread_id     TEXT NOT NULL COMMENT '关联的会话线程ID',
    checkpoint_ns TEXT NOT NULL DEFAULT '' COMMENT '关联的命名空间',
    channel       TEXT NOT NULL COMMENT '数据通道名称,标识大对象的类型或用途',
    version       TEXT NOT NULL COMMENT '版本标识,通常对应checkpoint_id,用于关联到具体检查点',
    type          TEXT NOT NULL COMMENT '大对象数据类型标识,如image/png、application/pdf等',
    blob          BYTEA COMMENT '二进制大对象数据(BYTEA格式),存储文件、图片、文档等大型数据',
    PRIMARY KEY (thread_id, checkpoint_ns, channel, version)
) COMMENT='大对象存储表,用于存储不适合JSONB格式的大型二进制数据';

checkpoint_blobs.type枚举

1. bytes: 原始字节对象

2. bytearray: 字节数组对象

3. msgpack: MessagePack 序列化(大多数对象的默认方式)

4. pickle: pickle 序列化(仅当启用 pickle_fallback 时)

5. empty: 标记未设置的 channel 值

6. null: None/空值

checkpoint_migrations 表

-- checkpoint_migrations 表
CREATE TABLE IF NOT EXISTS checkpoint_migrations
(
    version    INTEGER NOT NULL PRIMARY KEY COMMENT '迁移版本号,递增的整数标识',
    applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '迁移应用时间戳,记录schema更新的时间'
) COMMENT='数据库迁移版本管理表,跟踪schema变更历史';
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐