AI_Coding_Langchain学习与实践(1)

网上有很多大模型入门的课程大致的都是大模型LLM+Agent+Transformer+Langchain+RAG,不出意外的很多都在讲Langchain,而且从24年到当前26年都有不同的讲法,作为AI框架学习中非常重要的一环,是需要花点时间进行学习的。

什么是LangChain

LangChain的定义

LangChain 是开源大模型应用开发框架 (支持 Python / TypeScript),2022 年推出,定位是大模型的胶水层,专门用来把 LLM、私有数据、外部工具、业务流程组装成可用 AI 程序。 它本身不提供大模型,只负责搭建调用、调度、串联整套工作流,一套代码可无缝切换 GPT、Gemini、Llama、通义千问等任意模型,无厂商锁定。

LangChain 就是 Lang + Chain

  • Lang = Language(语言)--- 指 LLM 本身
  • Chain = 链 --- 指把多个步骤串起来的流水线

合起来就是 语言模型链:把 LLM、Prompt、Parser、检索、记忆等组件像工厂流水线一样串联。

完成生态三件套

  • LangChain(核心开发库) 基础组件、链式编排、RAG、简单 Agent,快速搭建原型与常规 AI 应用。
  • LangGraph(复杂智能体运行时) 图结构状态管理,做多轮循环、多智能体、人机介入、长任务持久执行,复杂 Agent 必备底层。
  • LangSmith(生产运维平台) 调试、链路追踪、提示词管理、评测、监控、日志,上线后排查优化用(商业付费)。

LangChain新版本

变的是上层 Agent 入口、老式类 Chain、包结构、消息增强;不变的是 LCEL 底层、模型抽象、记忆 / 工具 / RAG 整套业务流程、核心组件定义思想。

核心改动:

  1. 包结构彻底拆分、精简主包:v1.x:langchain只保留Agent 高阶能力 ,旧版大量传统 Chain、老组件全部移出到 langchain-classic 兼容包LangChain

  2. 三层固定生态固化 :

  • langchain-core:底层标准抽象、LCEL、消息、Runnable 基类(所有版本通用底座)
  • langchain:1.0 专属 Agent 上层 API
  • langgraph:底层图运行时,不再独立作为首选入口
  1. Python 版本门槛抬高:不再支持 3.9,最低 Python3.10+LangChain

  2. Agent 体系完全重构(最大变化)-1.x:统一唯一入口 create_agent(),全部内置基于 LangGraph 运行时

  3. 新增Middleware 中间件:模型调用、工具调用前后可全局拦截、修改消息、加日志、鉴权,0.x 只能靠手动包装回调

  4. 1.x 主包不再内置这些老式 Chain,默认强制 LCEL 管道 | 写法作为标准编排

  5. 消息 Message 格式标准化升级: 新增 .content_blocks 多块结构化消息,统一承载文本、引用、推理过程、多模态图片、服务端工具调用,0.x 只有单一字符串 content,跨模型厂商格式混乱;消息修剪、上下文压缩工具标准化移入 langchain.messages 模块;多模态输入输出接口统一对齐 OpenAI、Claude、通义千问等所有 ChatModel

  6. 导入路径、模块命名大洗牌 :

  • 工具:@tool、BaseTool、DynamicTool 统一收敛到 langchain.tools
  • Prompt 不再是独立一级大模块,模板全部归到 core,动态提示逻辑靠中间件实现
  • 禁止深度导入内部私有文件,构建打包严格只开放官方导出入口,大幅减少升级报错风险
  1. 检索 RAG 定位调整 : Retrieval 降为进阶应用能力,不再列为顶层基础组件;基础链路只保留 Message、Model、Tool、Agent、LCEL 编排

在安装LangChain之前需要注意一下python的版本,比如说我在公司的电脑上是3.9,pip install就会中途报错,无法安装1.x以上版本,只能安装 0.x的版本。

LangChain与Chain

回顾算法与数据结构的课程,有一个链表结构,它是存储组织方式,是底层内存结构,当然它跟我们现在说的LangChain完全不是一个东西,不过在哲学的概念上却有着相似的理论。

chain 本意就是 "一环扣一环串起来的链条",类似于乐高玩具的一个一个小块,可以通过几个环或块先组合成最小的件,再把小的件组合成稍大一点的组件,然后用这些组件去搭建出汽车、轮船等。

LangChain七大核心基础组件(搭积木式组合)

  1. Models 模型统一接口 封装三类模型统一调用方式:对话大模型 (ChatModel)、基础文本 LLM、Embedding 嵌入模型;换模型只改一行参数,不用重写请求逻辑。
  2. Prompt Templates 提示词模板 标准化提示工程,用变量填充动态内容,统一系统人设、格式约束,稳定输出质量。
  3. Memory 对话记忆 保存多轮聊天上下文,支持内存、向量库、数据库持久存储,解决长对话遗忘问题。
  4. Chains 链式工作流(LCEL 语言编排) 把多个步骤串成流水线,经典场景:
    • RAG 检索链:读文档→向量化检索→拼上下文→丢给 LLM 回答
    • 摘要链、翻译链、数据解析链
  5. Document Loaders & Splitters 文档处理 一键加载 PDF/Word/Markdown/ 网页,自动切块、清洗文本,为私有知识库做准备。
  6. Retrievers 检索器 对接向量库(Chroma、FAISS、Milvus、PGVector 等),语义匹配取出相关资料,实现检索增强生成 RAG(解决大模型幻觉、私有知识问答)。
  7. Tools + Agents 智能代理
    • Tools:封装计算器、联网搜索、API 调用、数据库查询等外部能力
    • Agent:让 LLM 自主判断 "要不要调用工具、调用哪个、执行几步",自动拆解复杂任务(查天气、查订单、数据分析)

从这七大核心基础组件来看,4,5,6的组合应用一般就是RAG了,我觉得这也就是为什么AI框架讲了LangChain然后必讲RAG的原因吧。

1,2,3基本上与前面的文章用python做一个智能编程助手(1)实现的功能相对应,而7与用python做一个智能编程助手(2)有很多相关性。所以LangChain的学习实践也对应之前的文章进行吧。

LangChain入门实践

01_申请API_Key和初始化客户端

申请API_Key是API调用的前置条件,目前国内已经有很多大模型服务的云平台(例如某炼,某山),还有一些模型厂商提供自家模型的API服务(例如ds,glm等),这些可以自行搜索、注册和创建API_KEY,然后把它保存到.env的文件中。

bash 复制代码
# 大模型1号
LLM_API_KEY1 = sk-****

# 大模型2号
LLM_API_KEY2 = sk-****

然后在python文件中先实现环境变量的加载

bash 复制代码
pip install python-dotenv
python 复制代码
# 加载环境变量
import os
import dotenv

dotenv.load_dotenv()

base_url = "https://XXXX/compatible-mode/v1"
api_key = os.os.getenv("LLM_API_KEY1")

在前面使用openai兼容的API,可以直接初始化客户端了,但并不涉及大模型,至于大模型的调用需要在client.chat.completions.create()中和message, stream等一起。

python 复制代码
client = OpenAI(
    api_key=api_key,
    base_url=base_url
)

这里就算是Langchain与openai兼容直接调用的第一种区别吧,Langchain 使用**Models 模型统一接口,**封装三类模型统一调用方式。所以先来安装Langchain的依赖:

bash 复制代码
pip install langchain
pip install langchain-openai
pip install langchain-deepseek

在安装langchain的主包的时候,会自动拉进来很多的依赖,比如说:

|--------------------|--------|------------------------------------|
| 包名 | 版本 | 属于哪个主库的依赖 |
| langchain-core | 1.4.0 | langchain 的核心库(消息、LCEL、Runnable 等) |
| langchain-protocol | 0.0.15 | langchain 协议层 |
| langsmith | 0.8.5 | langchain-core 的追踪/可观测性 SDK |
| langgraph | 1.2.1 | langchain 的图执行引擎(后续 Step 20+ 会用到) |
| langgraph-sdk | 0.3.15 | langgraph 的 SDK |

ChatOpenAI初始化

然后,可以使用不同的方式来创建带模型的客户端初始化,比如说openai兼容的:

python 复制代码
# 环境加载略

from langchain_openai import ChatOpenAI

# 初始化 LLM
model = ChatOpenAI(
    model="glm-5",
    api_key=api_key,
    base_url=base_url
)

response = model.invoke("你好")
print(response.content)

从上面的代码可以看到,Langchain直接把模型和api_key,url放到一起了,而且如果只是用户消息则可以直接简写成 .invoke("你好")这种形式,我们来看一下print(response)与在openai兼容的API调用中的异同点

javascript 复制代码
{
  "content": "你好!有什么我可以帮你的吗?",
  "additional_kwargs": {
    "refusal": null
  },
  "response_metadata": {
    "token_usage": {
      "completion_tokens": 239,
      "prompt_tokens": 11,
      "total_tokens": 250,
      "completion_tokens_details": {
        "accepted_prediction_tokens": null,
        "audio_tokens": null,
        "reasoning_tokens": 226,
        "rejected_prediction_tokens": null,
        "text_tokens": 239
      },
      "prompt_tokens_details": {
        "audio_tokens": null,
        "cached_tokens": null,
        "text_tokens": 11
      }
    },
    "model_provider": "openai",
    "model_name": "qwen3.6-plus-2026-04-02",
    "system_fingerprint": null,
    "id": "chatcmpl-9b9a03b8-a166-9203-b3cc-0a33644dab2b",
    "finish_reason": "stop",
    "logprobs": null
  },
}

由上面和下面的内容看,非流式调用的情况下,两者输出的信息差不多,但结构是不一样的,也就是说一旦用了Langchain,就不能像openai那样获取相关的比如Token信息了。

javascript 复制代码
{
  "id": "chatcmpl-9cea652f-490d-9405-8867-7322796ae322",
  "choices": [
    {
      "finish_reason": "stop",
      "index": 0,
      "logprobs": null,
      "message": {
        "content": "这句话翻译成英语是",
        "refusal": null,
        "role": "assistant",
        "annotations": null,
        "audio": null,
        "function_call": null,
        "tool_calls": null
      }
    }
  ],
  "created": 1779616443,
  "model": "glm-5",
  "object": "chat.completion",
  "service_tier": null,
  "system_fingerprint": null,
  "usage": {
    "completion_tokens": 34,
    "prompt_tokens": 23,
    "total_tokens": 57,
    "completion_tokens_details": null,
    "prompt_tokens_details": {
      "audio_tokens": null,
      "cached_tokens": 0
    }
  }

init_chat_model初始化

我们还可以使用另外一种方式来进行API和LLM的初始化操作,这种方式就好像在外面又进行了一层封装,把一些当前主流的协议和大模型都预置了对应的url,这样只要从.env中获取对应的api_key就可以进行后续的操作了。当然,仅限于已经被Langchain记录在案的,如果本身还是openai兼容(不是openai的url)那还是需要指定供应商provider,以及url和api_key,这种情况下反而感觉有些累赘了。在python中可以CTRL+点击进入文档查看支持的供应商

python 复制代码
from langchain.chat_models import init_chat_model

# 被Langchain记录在案的平台和模型
model = init_chat_model(model = 'deepseek-v4-flash')

response = model.invoke("你好")
print(response.content)


# Openai兼容,还需要url
base_url = "https://XXX/compatible-mode/v1"
api_key = os.getenv("LLM_API_KEY1")
model2 = init_chat_model(
    model = 'qwen3.6-plus',
    model_provider = 'openai', # 需要指定为openai,否则会报错
    api_key = api_key,
    base_url = base_url
)

response2 = model2.invoke("你好")
print(response2.content)

02_第一次会话,提示词模板与数据解析器

从这里,开始展现Langchain的优势了。

首先是提示词模板,很有意思的是我前面做的智能编程助手中就没有用系统提示词,而用户提示词也完全是使用的用户的文本输入,因此刚开始的时候我就很疑惑为什么要多此一举来整一个提示词模板出来?但随着继续的学习才发现,原来并不是我之前想的那样简单,提示词工程本就是一个比较复杂的东西,因此提示词模板还可以优化客户输入的提示词,帮忙小白用户更好的描述和解决问题,另外在模板中还可以把某些参数以变量的形式放在里面,实现用户在界面上可以选择的功能,比如说一个翻译智能体,有下拉列表可以选择翻译成英文,法文,日文,德文等,而提示词模板就可以简单的用提示词模板把需要固定的东西固定下来~

python 复制代码
from langchain_core.prompts import ChatPromptTemplate

prompt_template = ChatPromptTemplate.from_messages([
    ("system", "你是一名专业的翻译助手,把以下文本翻译成{target_language}:\n"),
    ("human", "用户:{input_text}")
])

而对于调用LLM后response,上面也看到了里面有很多与回答没有太多关系的内容,我们也可以用相应的输出解析器来解析其文本内容

python 复制代码
from langchain_core.output_parsers import StrOutputParser

parser = StrOutputParser()  
# result = parser.invoke(response.content)
# print(result)

从上面的代码看到,如果只是这样分开的一步一步的调用,那么它反而比直接print (response.content) 还要复杂,但在Langchain里不是这样用的,Langchain的一大特色就是把上面这些步骤都像链条一样串起来,最后直接进行invoke就搞定了

python 复制代码
import os
import json
from dotenv import load_dotenv
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

# 加载 .env 文件
load_dotenv()

# 从环境变量获取 API Key
api_key = os.getenv("LLM_API_KEY2")

# 初始化 LLM
llm = ChatOpenAI(
    model="qwen3.6-plus",
    api_key=api_key,
    base_url="https://xxx/compatible-mode/v1"
)

### 提示词模板
prompt_template = ChatPromptTemplate.from_messages([
    ("system", "请将下面的内容翻译成{target_language}"),
    ("user", "{input}")
])

### parser解析器
parser = StrOutputParser()

### 得到链
chain = prompt_template | llm | parser

### 通过链来调用
result = chain.invoke({"target_language": "English", "input": "我下午有一节课,不能去打球。"})
print(result)

03_通过langserve部署本地服务

上面已经制作了一个翻译的demo,这里只是执行了一次,但langchain的强大这处在于能把这个制作成一个本地部署,然后你可以在其他程序里调用它,然后它就变成了一个可以循环调用的智能体(好像还没有记忆功能,不过在翻译里也不需要)

要实现它,首先要安装相关的库,注意里面的双引号和all,如果只输入langserve是不行的。

bash 复制代码
pip install "langserve[all]"

安装库之后,就可以直接在上面的代码下面用FastAPI部署本地服务

python 复制代码
from fastapi import FastAPI
from langserve import add_routes

app = FastAPI(title="my langchain api", version="1.0.1",desccription="这是一个基于langchain的api")
add_routes(
    app,
    chain,
    path="/chainDemo",
)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="localhost", port=8000)
-----------------
LANGSERVE: Playground for chain "/chainDemo/" is live at:
LANGSERVE:  │
LANGSERVE:  └──> /chainDemo/playground/
LANGSERVE:
LANGSERVE: See all available routes at /docs/
INFO:     Application startup complete.
INFO:     Uvicorn running on http://localhost:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:49170 - "POST /chainDemo/invoke HTTP/1.1" 200 OK

接下来,可以使用ApiPost等软件进行HTTP请求,当然也可以自己写一个py脚本来请求

python 复制代码
from langserve import RemoteRunnable

if __name__ == '__main__':
    client = RemoteRunnable('http://127.0.0.1:8000/chainDemo/')
    ret = client.invoke({'target_language':'english','input':'在你最美的时刻'})
    print(ret)
    
------------------
At your most beautiful moment

*(Note: Depending on the context, it can also be phrased as "In your most beautiful moment" or "When you are at your most beautiful." If you share the full sentence or context, I can fine-tune it for you.)*

这时,我们即使把代码中的 response = chain.invoke... 删除了也没有问题,应该只要是chain = prompt_template | model | parser 存在就行。

由此我们看到,相比于Openai兼容的API调用而言,其实并不是客户端初始化,模型选择更换,方便输入,提示词模板更简单的问题,而是Langchain基于这些可以完成包括本地部署在内的其他直接应用。

04_流式输出多轮会话与记忆机制

在Langchain中改为流式非常简单,只需要把invoke改成stream就可以了,但流式输出是一个Token一个Token出来的,而且还有很多空白的内容,因此我们需要判断它们是否为空字符,然后把它们连起来。

python 复制代码
from langserve import RemoteRunnable

if __name__ == '__main__':
    client = RemoteRunnable('http://127.0.0.1:8000/chainDemo/')
    ret = client.stream({'target_language':'english','input':'在你最美的时刻'})
    for chunk in ret:
        if chunk !="":
            print(chunk,end='')

消息列表记录对话历史

接下来是循环和多轮会话,这个在Openai兼容里也讲过了,首先是用while True实现一个循环,当用户输入 /exit时退出循环,否则就可以一直跟AI进行对话。其次是AI并不知道你前面说过什么,你需要把之前的对话再发给它。我们还是先用一个朴素的列表来记录历史对话消息:

python 复制代码
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage

# 加载 .env 文件
load_dotenv()

# 三个关键
API_KEY = os.getenv("DASHSCOPE_API_KEY")
BASE_URL = 'https://xxx/compatible-mode/v1'
BASE_MODEL = 'qwen3.6-plus-2026-04-02'
language = "中文"

# 创建模型
model = ChatOpenAI(
    api_key=API_KEY,
    base_url=BASE_URL,
    model=BASE_MODEL,
)

# 初始化消息列表,包含系统提示
messages = [SystemMessage(content=f"你是一个专业的助手,用{language}回答用户的问题。")]

# 交互式对话
while True:
    user_input = input("你: ")
    if user_input.strip().lower() == "/exit":
        print("再见!")
        break

    # 添加用户消息
    messages.append(HumanMessage(content=user_input))

    # 调用模型(发送完整消息列表)
    response = model.invoke(messages)

    # 添加 AI 回复
    messages.append(AIMessage(content=response.content))

    print(f"AI: {response.content}")

RunnableWithMessageHistory实现多轮会话

由上面的代码,与我们之前不用LangChain的API调用几乎差不多,但这样并不是LangChain的样子,于是我们在上面的代码的基础上,改成LangChain 0.x版本的 RunnableWithMessageHistory的方式,注意一下当前会提示 RunnableWithMessageHistory已经弃用,但不妨碍正常运行。

python 复制代码
# 加载 .env 文件
# API_KEY...
# 创建模型... (略)
language = "中文"

# 聊天机器人案例
prompt = ChatPromptTemplate.from_messages([
    ("system", f"你是一个专业的助手,用{language}回答用户的问题。"),
    ("placeholder", "{chat_history}"),
    ("human", "{msg_1}"),
])
chain = prompt | model

from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables.history import RunnableWithMessageHistory
# 保存历史记录
store = {} # 用于保存记录,key= sessionId, value= history对象

def get_session_history(sessionId):
    if sessionId not in store:
        store[sessionId] = InMemoryChatMessageHistory()
    return store[sessionId]

do_message = RunnableWithMessageHistory(
    runnable = chain,
    get_session_history = get_session_history,
    input_messages_key = "msg_1",
)
config = {'configurable': {'session_id': '123456', 'language': language}}

# 交互式对话
while True:
    user_input = input("你: ")
    if user_input.strip().lower() == "/exit":
        print("再见!")
        break
    response = do_message.invoke(
        input={
            "msg_1": [HumanMessage(content=user_input)],
            "language": language
        },
        config=config,
    )
    print(f"AI: {response.content}")

RunnableWithMessageHistory的方式有点绕,这也是为什么 LangChain 官方弃用它,转向 LangGraph 的原因。它要求你在三个地方对齐变量名,任何一个对不上就报错。

  1. prompt 模板: ("human", "{msg_1}")

  2. Runnable 配置: input_messages_key="msg_1"

  3. invoke 调用: {"msg_1": ...}

它的工作机制为:

invoke(input={"msg_1": ...})

RunnableWithMessageHistory 根据 input_messages_key="msg_1"

找到这个字段,把消息内容提取出来

同时从 store 中取出 chat_history

一起传给 prompt 模板:

{chat_history} ← 历史消息

{msg_1} ← 新消息

它的确是使用了链,但很明显这些里面有的链有重复,这是由于chain里套用了提示词模板,而提示词模板需要调用用户新消息以及历史消息,而历史消息以及chain却又在RunnableWithMessageHistory里面。

用LangGraph实现多轮会话记忆

python 复制代码
# 加载 .env文件,API_KEY,创建模型 略

from langchain_core.messages import HumanMessage, SystemMessage
from langgraph.graph import StateGraph, MessagesState
from langgraph.checkpoint.memory import MemorySaver

# 定义系统提示
system_prompt = SystemMessage(content=f"你是一个专业的助手,用{language}回答用户的问题。")

# 定义节点函数
def chatbot_node(state: MessagesState) -> dict:
    messages = [system_prompt] + state["messages"]
    response = model.invoke(messages)
    return {"messages": [response]}

# 构建图
graph = StateGraph(MessagesState)
graph.add_node("chatbot", chatbot_node)
graph.set_entry_point("chatbot")

# 编译图,使用 MemorySaver 作为 checkpointer
memory = MemorySaver()
app = graph.compile(checkpointer=memory)

# 配置 thread_id
config = {"configurable": {"thread_id": "123456"}}

# 交互式对话
while True:
    user_input = input("你: ")
    if user_input.strip().lower() == "/exit":
        print("再见!")
        break
    response = app.invoke(
        {"messages": [HumanMessage(content=user_input)]},
        config=config,
    )
    print(f"AI: {response['messages'][-1].content}")

我们看到,相较于RunnableWithMessageHistory的绕, LangGraph直接传 messages,没有变量名对齐的问题

python 复制代码
app.invoke({"messages": [HumanMessage("你好")]}, config=config)

用 LangGraph 的图 + checkpointer 替代 RunnableWithMessageHistory ,功能更强(支持中断、回滚、分支),但学习成本也更高。

小结

Checkpoint = 游戏存档(可以读档重来、多存档位、从任意点继续)

如果你的应用只是"问-答-问-答",手动 messages 列表方式够用。如果需要 Agent 执行复杂任务(调用工具、分步推理、人类审批),Checkpoint 是必需的。

本节我们学习实践了LangChain的model初始化,提示词模板,解析器以及chain,但到目前为止与Openai兼容的API调用的对话机器人确实没有明显的区别。但这些知识并不是平移,而是后续学习的基础。到下一节,我们从Agent继续~