LangChain模型调用与多轮对话完整实战

摘要:本文记录使用 LangChain 调用阿里云百炼 Qwen 模型的完整过程,包括创建 API Key、在 Windows 中配置环境变量、连接 OpenAI 兼容接口、理解三种消息类型,以及手动维护多轮对话历史。本文使用 Python 3.10.18 和 uv 项目环境。

文章目录


一、从"调用模型"开始学习 LangChain

1、这一篇要解决什么问题

上一篇已经完成了 uv、Python 3.10.18、项目虚拟环境和 PyCharm 解释器的配置。这一篇正式进入大模型开发,完成下面几件事:

  1. 获取阿里云百炼 API Key;
  2. 将密钥安全地保存到 Windows 环境变量;
  3. 使用 ChatOpenAI 连接 Qwen 模型;
  4. 理解系统、用户和 AI 三种消息;
  5. 实现能够保留上下文的多轮对话;
  6. 理解模型调用、LangChain 和对话历史之间的关系。

初学时最容易混淆的是:Qwen 是模型,阿里云百炼提供模型服务,LangChain 则负责组织消息、调用模型以及连接后续的提示词、知识库和工具。

它们之间的关系可以简单理解为:

text 复制代码
Python 程序
    ↓
LangChain
    ↓
OpenAI 兼容接口
    ↓
阿里云百炼 Qwen 模型
    ↓
模型回答

2、本文运行环境

本文使用:

text 复制代码
操作系统:Windows 11
Python:3.10.18
环境管理:uv
开发工具:PyCharm + Jupyter Notebook
模型:qwen-plus
环境变量:DASHSCOPE_API_KEY

项目已经存在 pyproject.tomluv.lock,因此先在项目根目录同步环境:

powershell 复制代码
uv sync --locked

验证核心依赖:

powershell 复制代码
uv run python -c "import langchain, langchain_openai; print('LangChain 环境正常')"

二、准备阿里云百炼 API Key

1、API Key 是什么

API Key 可以理解为程序访问模型服务时使用的身份凭证。每次调用模型,服务端都会根据它判断:

  • 请求属于哪个账号或业务空间;
  • 当前账号是否有模型调用权限;
  • 调用量和费用应该记录到哪里。

因此,API Key 的作用类似密码,不能直接发布在文章、GitHub 仓库、Notebook 输出或截图中。

2、创建自己的 API Key

进入阿里云百炼控制台,在 API Key 管理页面创建密钥。创建时要确认密钥所属的地域和业务空间,因为后面使用的 API 地址必须与它匹配。

创建成功后,应立即把密钥保存到安全位置。

模型调用流程可以概括为:

text 复制代码
读取环境变量中的 API Key
        ↓
向百炼兼容接口发送 messages
        ↓
百炼验证身份并调用指定模型
        ↓
返回 AIMessage
        ↓
读取 response.content

三、在 Windows 中安全保存密钥

1、为什么不能把密钥写进代码

下面这种写法虽然能运行,但不推荐:

python 复制代码
api_key = "sk-这里是完整密钥"

一旦把文件发送给别人、上传到代码仓库,或者在截图中露出代码,密钥就可能泄露。更安全的方式是把密钥保存在操作系统环境变量中,程序运行时再读取。

本文使用的变量名是:

text 复制代码
DASHSCOPE_API_KEY

2、通过系统界面配置

Win + R 打开运行窗口,输入:

text 复制代码
sysdm.cpl

进入"高级"→"环境变量",在用户变量区域新建:

text 复制代码
变量名:DASHSCOPE_API_KEY
变量值:自己的完整 API Key

配置完成后,要关闭已经打开的 PowerShell、PyCharm 和 Jupyter 内核,再重新打开。已经运行的程序不会自动读取新环境变量。

3、安全验证环境变量

不要直接在终端输出完整密钥。可以只检查变量是否存在:

powershell 复制代码
if ($env:DASHSCOPE_API_KEY) {
    Write-Host "DASHSCOPE_API_KEY 已配置"
} else {
    Write-Host "DASHSCOPE_API_KEY 未读取到"
}

也可以在 Python 中验证:

python 复制代码
import os

api_key = os.getenv("DASHSCOPE_API_KEY")
print("API Key 已读取" if api_key else "API Key 未读取")

如果显示"未读取",先重启终端和 IDE,而不是反复创建同名变量。

四、使用 ChatOpenAI 连接 Qwen

1、为什么调用 Qwen 却使用 ChatOpenAI

ChatOpenAI 并不表示只能调用 OpenAI 官方模型。它封装的是 OpenAI 风格的聊天接口。

阿里云百炼提供 OpenAI 兼容接口,所以只要修改三项内容,就可以通过 ChatOpenAI 调用 Qwen:

  • model:要调用的模型名称;
  • api_key:百炼 API Key;
  • base_url:百炼的 OpenAI 兼容地址。

这就是"兼容接口"的价值:上层代码结构基本不变,只替换模型服务的连接信息。

2、初始化模型

为了兼容课程项目中锁定的 LangChain 版本,本文使用 openai_api_keyopenai_api_base 参数:

python 复制代码
import os
from langchain_openai import ChatOpenAI

api_key = os.getenv("DASHSCOPE_API_KEY")

if not api_key:
    raise RuntimeError("未读取到 DASHSCOPE_API_KEY,请检查环境变量并重启 IDE")

llm = ChatOpenAI(
    model="qwen-plus",
    openai_api_key=api_key,
    openai_api_base=(
        "https://dashscope.aliyuncs.com/"
        "compatible-mode/v1"
    ),
    temperature=0.7,
)

新版本 langchain-openai 通常也支持更简洁的参数名:

python 复制代码
llm = ChatOpenAI(
    model="qwen-plus",
    api_key=api_key,
    base_url=(
        "https://dashscope.aliyuncs.com/"
        "compatible-mode/v1"
    ),
)

课程项目有 uv.lock 时,应以当前锁定版本支持的写法为准,不要为了模仿最新示例而随意升级整套依赖。

3、Base URL 应该使用哪个

base_url 是模型服务的访问地址,它不是 API Key,也不能被环境变量中的密钥自动替代。

常见的北京地域兼容地址是:

text 复制代码
https://dashscope.aliyuncs.com/compatible-mode/v1

百炼目前也为部分地域和业务空间提供专属域名,形式类似:

text 复制代码
https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1

应优先使用自己业务空间页面提供的地址,并确保 API Key、地域和业务空间相匹配。老师示例中的旧地址能够运行,是因为该兼容入口仍然可用,并不代表所有专属模型和业务空间都能混用。

五、完成第一次模型调用

1、最简单的 invoke 调用

模型初始化后,可以直接传入一个字符串:

python 复制代码
response = llm.invoke("请用一句话介绍你自己")
print(response.content)

这里发生了两件事:

  1. llm.invoke() 把输入发送给远程模型;
  2. 模型返回一个 AIMessage 对象。

response 不是普通字符串,真正的文本内容保存在:

python 复制代码
response.content

还可以查看返回对象的类型:

python 复制代码
print(type(response))

结果通常类似:

text 复制代码
<class 'langchain_core.messages.ai.AIMessage'>

2、模型什么时候产生费用

创建 ChatOpenAI 对象时只是保存连接配置,不会立即向模型提问。

真正发起网络请求的是:

python 复制代码
llm.invoke(...)

也就是说,只有运行到调用语句时,程序才会把输入发送到阿里云百炼,并按照实际输入、输出和模型计费规则产生用量。

Notebook 已经显示的旧输出不会重复调用模型;重新执行对应单元格才会再次请求。

六、理解 LangChain 的三种消息

1、SystemMessage 负责设定规则

SystemMessage 用来告诉模型应该扮演什么角色、遵守什么要求:

python 复制代码
from langchain_core.messages import SystemMessage

system_message = SystemMessage(
    content="你是一名耐心的 Python 老师,请用通俗中文回答。"
)

它通常不是用户真正提出的问题,而是整个对话的背景和行为规则。

2、HumanMessage 表示用户输入

HumanMessage 表示用户发送的内容:

python 复制代码
from langchain_core.messages import HumanMessage

human_message = HumanMessage(
    content="什么是 Python 虚拟环境?"
)

3、AIMessage 表示模型回答

调用聊天模型后返回的是 AIMessage

python 复制代码
response = llm.invoke([
    system_message,
    human_message,
])

print(response.content)

三种消息和 OpenAI 兼容接口中的角色一一对应:

LangChain 消息 接口角色 含义
SystemMessage system 系统要求和角色设定
HumanMessage user 用户输入
AIMessage assistant 模型回答

使用消息对象比直接拼接字符串更清晰,也为后面的多轮对话、提示词模板和工具调用打下基础。

七、实现真正的多轮对话

1、模型不会自动记住上一次调用

下面是两个彼此独立的请求:

python 复制代码
answer1 = llm.invoke("我叫小明。")
answer2 = llm.invoke("我叫什么名字?")

第二次调用时只发送了"我叫什么名字",没有把第一句话一起发送。因此模型大概率不知道答案。

这是大模型 API 与聊天网页最容易让初学者误解的地方:模型接口默认是无状态的。网页看起来有记忆,是因为应用程序在每次请求时重新附带了历史消息。

2、手动维护消息列表

先建立对话历史:

python 复制代码
from langchain_core.messages import (
    SystemMessage,
    HumanMessage,
)

messages = [
    SystemMessage(
        content="你是一名友好的中文助手。"
    )
]

第一轮对话:

python 复制代码
messages.append(
    HumanMessage(content="我叫小明,正在学习 LangChain。")
)

response = llm.invoke(messages)
messages.append(response)

print(response.content)

第二轮对话:

python 复制代码
messages.append(
    HumanMessage(content="我叫什么名字?正在学习什么?")
)

response = llm.invoke(messages)
messages.append(response)

print(response.content)

此时第二次调用收到的消息结构是:

text 复制代码
SystemMessage:你是一名友好的中文助手
HumanMessage:我叫小明,正在学习 LangChain
AIMessage:第一轮回答
HumanMessage:我叫什么名字?正在学习什么?

因为历史消息被完整传入,模型才能结合上下文回答。

3、为什么还要保存 AI 的回答

只保存用户问题是不完整的:

python 复制代码
messages.append(HumanMessage(content=user_input))

每轮调用后还应该把模型返回的 AIMessage 放入历史:

python 复制代码
response = llm.invoke(messages)
messages.append(response)

否则下一轮模型只看见用户连续说了什么,却不知道自己之前回答了什么,容易产生上下文断裂。

4、用循环实现连续聊天

把上面的逻辑放进循环,就能得到一个简单的命令行聊天程序:

python 复制代码
from langchain_core.messages import (
    SystemMessage,
    HumanMessage,
)

messages = [
    SystemMessage(
        content="你是一名耐心、简洁的中文学习助手。"
    )
]

while True:
    user_input = input("我:").strip()

    if user_input.lower() in {"exit", "quit", "退出"}:
        print("对话结束")
        break

    messages.append(
        HumanMessage(content=user_input)
    )

    response = llm.invoke(messages)
    messages.append(response)

    print("AI:", response.content)

这段代码已经具备聊天应用最核心的结构:

text 复制代码
接收输入
→ 写入历史
→ 调用模型
→ 保存回答
→ 显示结果

八、用角色指令完成简单翻译

1、通过 SystemMessage 固定任务

除了聊天,还可以利用系统消息规定模型的工作:

python 复制代码
messages = [
    SystemMessage(
        content="你是一名翻译助手,把用户输入翻译成英文。"
    ),
    HumanMessage(
        content="人工智能正在改变我们的学习方式。"
    ),
]

response = llm.invoke(messages)
print(response.content)

这种写法已经能够完成固定任务,但目标语言被写死在系统消息中。下一篇将使用 ChatPromptTemplate 把目标语言设计成变量,从而用同一份模板完成中文、英文、意大利语和日语等不同任务。

九、常见问题

1、环境变量明明配置了,Python 却读取不到

先检查变量是否存在:

python 复制代码
import os
print(bool(os.getenv("DASHSCOPE_API_KEY")))

如果结果是 False

  • 关闭并重新打开 PyCharm;
  • 重启 Notebook 内核;
  • 检查变量名是否准确;
  • 确认变量建立在当前用户下;
  • 不要在变量值外面额外加引号。

2、出现 401 或 AuthenticationError

这类错误通常与身份验证有关:

  • API Key 不完整或复制错误;
  • Key 已被删除或禁用;
  • Key 与地域、业务空间不匹配;
  • 环境变量中保存的仍是旧 Key。

不要通过反复安装 Python 包来解决认证错误,因为依赖安装和服务端身份认证是两件不同的事。

3、出现 404 或模型不存在

重点检查:

  • base_url 是否正确;
  • 地址末尾是否包含 /compatible-mode/v1
  • model="qwen-plus" 是否拼写正确;
  • 当前业务空间是否有对应模型的调用权限。

4、代码运行成功但 PyCharm 仍然标红

先确认 Notebook 或脚本使用的是:

text 复制代码
项目目录\.venv\Scripts\python.exe

再执行:

powershell 复制代码
uv run python -c "import langchain_openai; print(langchain_openai.__file__)"

如果能够输出 .venv 中的包路径,说明运行环境正常,标红更可能是 IDE 索引尚未刷新。

5、对话越长为什么越慢

手动维护多轮历史时,每一次调用都会重新发送消息列表。历史越长:

  • 输入 Token 越多;
  • 调用费用可能越高;
  • 网络传输和模型处理时间越长;
  • 早期信息可能超过模型上下文长度。

实际应用通常会限制历史轮数、压缩旧对话或总结历史,而不是无限追加消息。

十、完整示例

1、可直接运行的多轮对话代码

python 复制代码
import os

from langchain_openai import ChatOpenAI
from langchain_core.messages import (
    SystemMessage,
    HumanMessage,
)


api_key = os.getenv("DASHSCOPE_API_KEY")

if not api_key:
    raise RuntimeError(
        "未读取到 DASHSCOPE_API_KEY,请检查环境变量并重启 IDE"
    )

llm = ChatOpenAI(
    model="qwen-plus",
    openai_api_key=api_key,
    openai_api_base=(
        "https://dashscope.aliyuncs.com/"
        "compatible-mode/v1"
    ),
    temperature=0.7,
)

messages = [
    SystemMessage(
        content="你是一名耐心、简洁的中文学习助手。"
    )
]

while True:
    user_input = input("我:").strip()

    if user_input.lower() in {"exit", "quit", "退出"}:
        print("对话结束")
        break

    if not user_input:
        continue

    messages.append(
        HumanMessage(content=user_input)
    )

    response = llm.invoke(messages)
    messages.append(response)

    print("AI:", response.content)

在项目根目录运行:

powershell 复制代码
uv run python chat.py

十一、总结

1、本篇掌握的核心知识

通过这次练习,可以得出几个非常重要的结论:

  1. API Key 是敏感凭证,应保存在环境变量中;
  2. base_url 决定请求发送到哪个模型服务;
  3. ChatOpenAI 可以连接兼容 OpenAI 协议的模型服务;
  4. invoke() 才是真正发起模型请求的步骤;
  5. 返回结果是 AIMessage,正文通过 .content 获取;
  6. SystemMessage 负责规则,HumanMessage 负责用户输入;
  7. 模型接口默认没有长期记忆;
  8. 多轮对话的本质是应用程序重新发送历史消息;
  9. 用户消息和 AI 回答都应该加入消息列表;
  10. 历史越长,Token 消耗和调用时间通常越高。

完成这一篇后,我们已经不只是"调用一次模型",而是掌握了聊天应用最基本的数据结构。下一篇将继续学习 ChatPromptTemplate,把写死在代码中的角色、语言和任务变成可复用的提示词模板。

相关推荐
mapengfei2 小时前
LangChain学习背景
langchain·llm
程序员三明治2 小时前
【AI】RAG 生成阶段的最后一公里:Prompt 设计、幻觉抑制与引用对齐
java·人工智能·ai·大模型·llm·prompt·rag
shawxlee3 小时前
vue3+axios挑战最简洁实用的配置封装+接口调用:请求拦截器、响应拦截器、通用请求方法(单个请求/并发请求/下载文件)、统一接口管理
前端·经验分享·vue·接口·axios·api
kishu_iOS&AI3 小时前
【02】Context Engineering:Prompt不再是核心
ai·大模型·loop·rag·harness·agent 架构
chaors14 小时前
DeepResearchSystem 0x02:Graph 构建
langchain·openai·ai编程
thesky12345615 小时前
27届大模型岗面试准备(三):位置编码全景——从绝对编码到 RoPE/ALiBi 的演进与手推
人工智能·ai·大模型
扯蛋43818 小时前
从脱敏到审批:我用 LangChain 11 个中间件给羽毛球 AI 助手装了一整套"安全阀"
langchain·llm·agent
BraveWang19 小时前
【LangChain 1.x】13、安全护栏|PII 脱敏、自定义护栏与多层叠加防护
langchain