最近在学 LangChain,踩了不少坑,也有一些"原来这么简单"的时刻。把学到的东西整理一下,从最基础的模型调用,到提示词模板、输出解析器、链式调用,最后把应用部署成 Web 服务。

1. 模型包装器:统一调用各家大模型

LangChain 做的第一件事就是把各家大模型的 API 包了一层。不管你用通义千问、DeepSeek 还是腾讯混元,调用方式都一样。

最简方式:直接用社区模型

from langchain_community.chat_models import ChatTongyi

model = ChatTongyi()
response = model.invoke("你是谁?")
print(response.content)

前提是你配好了环境变量 DASHSCOPE_API_KEY。跑起来没问题,但灵活度不够。

通用方式:ChatOpenAI 兼容各平台

大部分国内模型都兼容 OpenAI 的接口规范,所以用 ChatOpenAI 一把梭:

import os
from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    model="qwen-max-latest",
    temperature=0.7
)

result = model.invoke("用一句话解释什么是LangChain")
print(result.content)

换 DeepSeek?改三个参数就行:

model = ChatOpenAI(
    api_key=os.getenv("Deepseek_Key"),
    base_url="https://api.deepseek.com/v1",
    model="deepseek-chat",
    temperature=0.7
)

我自己封装了一个工具函数,省得每次写一堆:

def get_lc_model_client(api_key, base_url, model, temperature=0.7):
    return ChatOpenAI(
        api_key=api_key,
        base_url=base_url,
        model=model,
        temperature=temperature
    )

2. 提示词模板:别再手动拼字符串了

硬编码提示词写死在代码里,改起来痛苦。LangChain 提供了模板机制,用占位符动态填充内容。

字符串模板

最简单的形式,一个模板字符串加几个变量:

from langchain_core.prompts import PromptTemplate

prompt = PromptTemplate(
    template="你是一个翻译助手,请将以下内容翻译成{language}:{text}"
)

# 填入实际内容
actual_prompt = prompt.format(language="中文", text="I am a programmer")
print(actual_prompt)
# 输出:你是一个翻译助手,请将以下内容翻译成中文:I am a programmer

对话模板(多角色)

真实场景里,大模型对话有角色区分——system 设定身份,human 是用户输入。用 ChatPromptTemplate

from langchain_core.prompts import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate
)

prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(
        "你是一个翻译助手,请将以下内容翻译成{language}"
    ),
    HumanMessagePromptTemplate.from_template("{text}")
])

actual_prompt = prompt.format(language="中文", text="I am a programmer")

也可以用简写形式,效果一样:

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个翻译助手,请将以下内容翻译成{language}"),
    ("human", "{text}")
])

少样本提示(Few-shot)

有时候你需要给模型"举几个例子",让它学会你想要的输出格式:

from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate

# 准备示例
examples = [
    {"sinput": "2+2", "soutput": "4", "sdescription": "加法运算"},
    {"sinput": "5-2", "soutput": "3", "sdescription": "减法运算"},
]

# 单个示例的格式模板
example_template = PromptTemplate(
    template="算式:{sinput} 值:{soutput} 类型:{sdescription}"
)

# 组装少样本模板
prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_template,
    prefix="你是一个数学专家,能够准确说出算式的类型,",
    suffix="现在给你算式:{input},值:{output},告诉我类型:",
    input_variables=["input", "output"]
)

result = model.invoke(prompt.format(input="2*5", output="10"))
print(result.content)  # 乘法运算

模型看了加法和减法的例子,就能推断出 2*5 是乘法。

Partial 变量:分步填充模板

有些参数你想提前固定,剩下的后面再填。比如做一个"学习助手",先锁定学科,再接收用户的具体问题。用 prompt.partial() 就行:

from langchain_core.prompts import PromptTemplate

prompt = PromptTemplate(
    template="讲一个关于{date}的小故事:{text}",
    input_variables=["date", "text"]
)

# 先固定 date 参数
half_prompt = prompt.partial(date="2008-08-08")

# 后续只需要填 text
result = model.invoke(half_prompt.format(text="一个幸福的爱情故事"))
print(result.content)

3. 链(Chain):用管道符串联整个流程

LangChain 里最优雅的设计是链式调用。用 | 把提示词模板、模型、解析器串起来,数据自动流转:

from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

# 三个组件
prompt = PromptTemplate(
    template="你是一个翻译助手,请将以下内容翻译成{language}:{text}"
)
model = ChatOpenAI(
    api_key="your-api-key",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    model="qwen-max-latest"
)
parser = StrOutputParser()

# 一行搞定
chain = prompt | model | parser

# 调用
result = chain.invoke({"language": "中文", "text": "I am a programmer"})
print(result)  # 我是一个程序员

数据流向:dict → prompt模板填充 → model推理 → parser提取文本。不用手动传中间结果,代码干净很多。

4. 输出解析器:把模型的回答变成结构化数据

大模型返回的是自然语言文本。如果你需要 JSON、列表这类结构化数据,就要用解析器。

StrOutputParser:提取纯文本

模型返回的是 AIMessage 对象,解析器帮你拿到 .content 字符串:

from langchain_core.output_parsers import StrOutputParser

parser = StrOutputParser()
chain = prompt | model | parser
# 返回的直接是字符串,不是 AIMessage 对象

JsonOutputParser:拿到 dict

告诉模型"用 JSON 格式返回",然后用 JSON 解析器接:

from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个专业的程序员"),
    ("user", "{input}")
])

parser = JsonOutputParser()
chain = prompt | model | parser

result = chain.invoke({
    "input": "langchain是什么?用question表示问题,用ans表示回答,返回JSON格式"
})
print(type(result))  # <class 'dict'>
print(result)        # {'question': 'langchain是什么?', 'ans': '...'}

注意:你得在提示词里明确说"返回 JSON 格式",不然模型可能返回普通文本,解析器会报错。

CommaSeparatedListOutputParser:拿到列表

让模型用逗号分隔回答,解析器帮你拆成 Python 列表:

from langchain_core.output_parsers import CommaSeparatedListOutputParser

parser = CommaSeparatedListOutputParser()
chain = prompt | model | parser

result = chain.invoke({
    "input": "列出Python的三个主要版本,用逗号分隔"
})
print(result)  # ['Python 2', 'Python 3.x', 'Python 3.12']

DatetimeOutputParser:拿到日期对象

问模型时间相关的问题,直接拿到 Python 的 datetime 对象:

from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import DatetimeOutputParser

output_parser = DatetimeOutputParser()

template = """
回答用户的问题:{question}

{format_instructions}
"""

prompt = PromptTemplate.from_template(
    template,
    partial_variables={
        "format_instructions": output_parser.get_format_instructions()
    },
)

chain = prompt | model | output_parser
result = chain.invoke({"question": "新中国是什么时候成立的?"})
print(result)  # 1949-10-01 00:00:00

这里 get_format_instructions() 会生成一段提示文本,告诉模型用什么格式回答日期。解析器再把字符串转成 datetime。

自定义解析器:内置的不够用就自己写

LangChain 内置的解析器覆盖了常见场景,但总有它们搞不定的时候。比如你想让模型返回特定格式的日期字符串,还要做校验,内置的 DatetimeOutputParser 不支持自定义格式。这时候继承 BaseOutputParser 自己写一个:

import datetime
from langchain_core.output_parsers import BaseOutputParser
from langchain_core.exceptions import OutputParserException


class DateStringParser(BaseOutputParser[str]):
    """自定义日期解析器,支持任意日期格式"""

    # 期望的日期格式,可以在实例化时指定
    target_format: str = "%Y-%m-%d"

    def parse(self, text: str) -> str:
        """把模型返回的文本解析成指定格式的日期字符串"""
        stripped_text = text.strip()
        try:
            dt_object = datetime.datetime.strptime(stripped_text, self.target_format)
            return dt_object.strftime(self.target_format)
        except ValueError as e:
            raise OutputParserException(
                f"日期格式不对。期望 '{self.target_format}',收到: '{stripped_text}'. 错误: {e}"
            )

    def get_format_instructions(self) -> str:
        """告诉模型应该怎么输出"""
        example_date = datetime.datetime.now().strftime(self.target_format)
        return (
            f"请严格按照以下格式输出日期:'{self.target_format}'.\n"
            f"例如: {example_date}\n"
            f"只返回日期字符串,不要包含任何其他文本!"
        )

两个方法各司其职:get_format_instructions() 生成提示词片段,约束模型的输出格式;parse() 拿到模型返回的文本后做校验和转换。格式不对直接抛异常,不会让脏数据溜过去。

用起来和内置解析器一样,塞进链里就行:

from langchain_core.prompts import PromptTemplate

# 想要 月/日/年 时:分:秒 这种格式?改 target_format 就行
date_parser = DateStringParser(target_format="%m/%d/%Y %H:%M:%S")

template = """
回答用户的问题:{question}

{format_instructions}
"""

prompt = PromptTemplate.from_template(
    template,
    partial_variables={"format_instructions": date_parser.get_format_instructions()},
)

chain = prompt | model | date_parser

result = chain.invoke({"question": "北京夏季奥运会的开幕时间是?"})
print(result)  # 08/08/2008 20:00:00

写自定义解析器的套路就这些:继承 BaseOutputParser,实现 parse()get_format_instructions()。前者负责"怎么解析",后者负责"怎么告诉模型按格式输出"。想解析什么格式都行,XML、YAML、自定义 DSL,逻辑你自己定。

5. 部署:把 Chain 变成 Web 服务

写好的 chain 想给别人用?LangServe 能把它直接部署成 HTTP 接口。

服务端

from fastapi import FastAPI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate
from langchain_openai import ChatOpenAI
from langserve import add_routes

# 组装 chain
model = ChatOpenAI(
    api_key="your-api-key",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    model="qwen-max-latest"
)

prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template("请将以下的内容翻译成{language}"),
    ("human", "{text}")
])

chain = prompt | model | StrOutputParser()

# 部署
app = FastAPI(title="翻译服务", version="1.0")
add_routes(app, chain, path="/translate")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="localhost", port=8000)

启动后访问 http://localhost:8000/docs 能看到自动生成的 API 文档。

客户端调用

Python 客户端用 RemoteRunnable

from langserve import RemoteRunnable

client = RemoteRunnable("http://localhost:8000/translate")
result = client.invoke({"language": "意大利文", "text": "我喜欢编程"})
print(result)

其他语言直接发 HTTP 请求也行:

curl -X POST http://localhost:8000/translate/invoke \
  -H "Content-Type: application/json" \
  -d '{"input": {"language": "意大利文", "text": "我喜欢编程"}}'

整体架构回顾

用户输入
  ↓
PromptTemplate(模板填充)
  ↓
ChatOpenAI(模型推理)
  ↓
OutputParser(结构化输出)
  ↓
LangServe(对外服务)

LangChain 的思路就是把 LLM 应用拆成可组合的模块。每个模块干一件事,用 | 串起来。改模型?换一行。改输出格式?换个解析器。加个前处理?插到链里。

调试:看看链里到底发生了什么

链式调用写起来简洁,但出了问题不好排查——中间结果看不见。LangChain 提供了全局调试开关,打开后会把每一步的输入输出都打印出来:

from langchain_core.globals import set_debug

set_debug(True)

加这两行就行,放在代码最前面。之后跑任何 chain,控制台会输出每个组件收到什么、返回什么。prompt 拼出来长什么样、模型原始返回是什么、解析器处理前后的变化,全都能看到。

调试完记得关掉,不然日志刷屏:

set_debug(False)

开发阶段建议常开,部署时关闭。

环境配置备忘

pip install langchain langchain-openai langchain-community langserve fastapi uvicorn

API Key 建议放环境变量里,别写代码里:

export DASHSCOPE_API_KEY="sk-xxx"
export Deepseek_Key="sk-xxx"

写到这里差不多了。LangChain 的 Model I/O 模块就是干这些事——包装模型、格式化提示词、解析输出、组成链、部署服务。后面还有 RAG、Agent、Memory 这些更有意思的东西,等学到了再写。