【LangChain开发笔记01】入门Demo与接口调用源码分析

本文介绍了如何使用 LangChain 调用大模型 API,并分析了其接口调用的底层实现原理。

快速上手

大模型 API 获取

截至本笔记写作日期(2025年6月22日),阿里云百炼 还有服务开通送 token 的活动,并且是不同模型的不同版本(少数除外)各送 100w token,足够学习使用了。

在之后的学习过程中,我们默认使用阿里云百炼提供的大模型 DeepSeek-V3。

首先需要一个 API-Key 来访问大模型:

在阿里云百炼的控制台 → API-Key 界面,点击右上角【创建我的 API-KEY】,把 sk 开头的这个字符串记录下来即可。

image-20250622023256461

然后需要知道访问的接口 URL,这个可以在阿里云百炼的 API 参考文档里找到:

https://dashscope.aliyuncs.com/compatible-mode/v1

API Key 和接口 URL 都准备就绪,Token 额度也白嫖好了,接下来就开始调用大模型吧!

另外需要注意查看 DeepSeek 系列模型的 Token 使用情况:DeepSeek - 详情

LangChain 调用

先安装 LangChain 相关的库:

pip install langchain
pip install langchain_openai

通常,我们会把 API Key 和接口 URL 写到环境变量中,然后在 Python 程序里通过 os.getenv 导入对应值。

但是写环境变量还是太麻烦了,而且不同项目可能用到不同的 API Key 和 URL,数量一多就难以管理。

所以方便起见,我们使用库 dotenv 来简化环境变量的管理:pip install dotenv

它的用法是:

  1. 在项目目录下新建一个 .env 文件,里面写入环境变量,每行一个。例如:
DASHSCOPE_API_KEY=sk-xxxx
BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
  1. 在项目中调用 load_dotenv(),即可自动将 .env 文件里的配置写入环境变量,并且只在程序运行时生效,不会干扰到 shell 或其他 Python 程序使用的环境变量。

环境变量准备就绪,接下来就是第一个 LangChain 示例,开始与大模型的首次对话:

from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
from langchain.schema import HumanMessage
import os

load_dotenv()

dsv3_chat = ChatOpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("BASE_URL"),
    model="deepseek-v3" 
    # 通过模型名即可指定对应模型,准确的模型名称需要在阿里云百炼的模型详情里查看
)

msg = HumanMessage(content="你好")

messages = [msg]

dsv3_chat_response = dsv3_chat.invoke(messages)

print(dsv3_chat_response)

当然,考虑到用户体验,我们最好使用流式输出:

from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
from langchain.schema import HumanMessage
import os

load_dotenv()

dsv3_chat = ChatOpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("BASE_URL"),
    model="deepseek-v3",
    streaming=True # 开启流式输出
)

msg = HumanMessage(
    content="你好"
)

messages = [msg]

# 处理响应,逐块打印在控制台
for chunk in dsv3_chat.stream(messages):
    print(chunk.content, end="", flush=True)

接口调用原理

上述例子里的请求和响应是经过高度封装的,本质上其实只是请求一个 HTTP URL 、接收它的响应内容并打印。

在一些大模型 API 文档(如 DeepSeek 的官方 API),你会看到这样的调用示例:

curl https://api.deepseek.com/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <DeepSeek API Key>" \
  -d '{
        "model": "deepseek-chat",
        "messages": [
          {"role": "system", "content": "You are a helpful assistant."},
          {"role": "user", "content": "Hello!"}
        ],
        "stream": false
      }'

显然,就是一个非常朴素的 HTTP 请求。在 Python 里,也可以用最朴素的 requests 库来请求它,完成大模型调用:

import requests

DeepSeek_API_Key=...

response = requests.post(
    headers={
        "Content-Type": "application/json",
        "Authorization": f"Bearer {DeepSeek_API_Key}"
    }
    url="https://api.deepseek.com/chat/completions",
    data={
        "model": "deepseek-chat",
        "messages": [{
            "role": "user",
            "content": "你好"
        }],
        "stream": False
    }
)

print(response.text)

显然,为了和大模型说一句“你好”,我们写了一大堆代码。这太丑了!我们当然不想这样。我们可以自己封装样式统一调用接口,但不同的大模型又可能采用不同的请求格式,每碰到一个新的就做兼容,这也很麻烦。

好在 OpenAI 提供了统一的调用接口格式1,各家大模型(例如 DeepSeek 官方 API 在文档里提到的)会自发地适配它:

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ.get("OPENAI_API_KEY"),
)

response = client.responses.create(
    model="gpt-4o",
    instructions="You are a coding assistant that talks like a pirate.",
    input="How do I check if a Python object is an instance of a class?",
)

print(response.output_text)

只要我们选择的大模型支持 OpenAI 格式,就可以通过以上方式调用,不用再手写 HTTP 请求了。

但是,LangChain 将模型本身的属性(如 URL 和模型名称)和模型的调用过程做了区分,模型信息写入到 ChatOpenAI 对象中,并且将实际调用过程写在 invoke 方法中。

查看 invoke 的源码,可以追踪到 langchain_core/language_models/chat_models.py 中的方法 _generate_with_cache。它的作用是对单组消息进行生成,并自动处理缓存、速率限制、流式输出、回调、元数据等

如果是非流式输出,_generate_with_cache会调用 _generate 方法。而 _generate 的具体实现则取决于执行 invokeChatOpenAI 对象。

为了找到 _generate 具体是如何调用大模型的,我们去看langchain_openai/chat_models/base.pyChatOpenAI 类的定义。然后发现 ChatOpenAI 并没有实现 _generate 方法,而是它的父类 BaseChatModel 实现了它。

BaseChatModel._generate() 中,若非流式输出,则会按属性配置情况进入四个分支之一进行处理。对于上文的简单 Case,我们进入的是最后一个分支:

else:
    response = self.client.create(**payload)

熟悉吗?这就是前面我们看到的 OpenAI 的调用代码。

所以,LangChain 的 invoke 方法是基于 OpenAI API 的再次封装,在简单请求大模型的基础上添加了缓存机制、速率限制、元数据补充(如响应信息、ID)和上报调用信息的回调方法等。

HumanMessage 与 AIMessage

除了 ChatOpenAIinvoke,示例代码里还有两个新的类:HumanMessage 和 (没有显式出现但是响应的类型是它)AIMessage

HumanMessage 没什么特别的,只是允许在原始输入的基础上添加其他参数:

def __init__(
    self, content: Union[str, list[Union[str, dict]]], **kwargs: Any
) -> None:
    """Pass in content as positional arg.

    Args:
        content: The string contents of the message.
        kwargs: Additional fields to pass to the message.
    """
    super().__init__(content=content, **kwargs)

AIMessage 则定义了更多的成员变量,主要是附加了工具信息 tool_callsinvalid_tool_calls,即在回答之前,大模型进行了什么工具调用;以及 usage_metadata,附带了 token 用量等统计信息。

关于它们的实际使用,我们会在学习到工具调用时了解。

Footnotes

  1. https://github.com/openai/openai-python