LangChain 框架相关概念

392 阅读17分钟

LangChain 框架相关概念

1. 架构

LangChain 作为一个框架由多个相互协作的 Python 包组成,旨在支持从原型到生产的整个应用程序生命周期。

  • langchain-core:
    • 包含 LangChain 组件(如 LLM、文档加载器、向量存储等)的基本抽象和接口定义。
    • 定义了组合这些组件的标准方式(例如 LangChain 表达式语言 LCEL)。
    • 不包含任何第三方集成,依赖保持最小化。
  • langchain:
    • 包含应用程序的“认知架构”部分,如链 (Chains)、代理 (Agents) 和检索策略 (Retrieval Strategies) 的通用实现。
    • 这些实现不依赖于特定的第三方集成,是通用的。
  • langchain-community:
    • 包含由社区维护的广泛的第三方集成(模型、数据库、API 等)。
    • 所有依赖项都是可选的,以保持包的轻量。
  • 合作伙伴包 (例如 langchain-openai, langchain-anthropic):
    • 将流行的、重要的集成拆分到独立包中,以提供更好的支持和更快的更新。
  • langgraph:
    • 用于构建有状态、多参与者应用程序的库,将步骤建模为图中的节点和边。
    • 提供用于创建常见代理类型的高级接口和用于组合自定义流程的低级 API。
  • langserve:
    • 用于将 LangChain 链部署为 REST API 的库,简化生产级 API 的部署。
  • LangSmith:
    • 一个开发者平台,用于调试、测试、评估和监控 LLM 应用程序,提供端到端的可观察性。

2. LangChain 表达式语言 (LCEL)

LCEL 是一种以声明方式组合 LangChain 组件(如模型、提示、检索器、工具等)的方法。它通过管道符 | 将组件链接在一起。

  • 核心优势:
    • 一流的流式支持: 优化首次令牌时间,可以流式传输解析后的增量输出块。
    • 异步支持: 同一链可以用同步 (invoke, stream) 或异步 (ainvoke, astream) API 调用,无需更改代码。
    • 优化的并行执行: 自动并行执行可并行的步骤。
    • 重试和回退: 可配置链中任何部分的重试和回退机制。
    • 访问中间结果: 可以流式传输复杂链的中间步骤结果。
    • 输入和输出模式: 自动推断 Pydantic 和 JSONSchema 模式,用于验证和 API 定义。
    • 无缝的 LangSmith 跟踪: 所有步骤自动记录到 LangSmith,便于观察和调试。
  • 与遗留链的关系: LCEL 旨在提供比旧式链(如 LLMChain, ConversationalRetrievalChain)更透明、更可定制的方式来构建应用。鼓励从遗留链迁移到 LCEL。

2.1 可运行接口 (Runnable Interface)

LCEL 的核心是 "Runnable" 协议,大多数 LangChain 组件都实现了这个协议。它提供了一套标准的调用方法:

  • 同步方法:
    • stream(): 流式返回响应块。
    • invoke(): 对单个输入调用链。
    • batch(): 对输入列表调用链。
  • 异步方法:
    • astream(): 异步流式返回响应块。
    • ainvoke(): 异步对单个输入调用链。
    • abatch(): 异步对输入列表调用链。
    • astream_log(): 异步流式返回中间步骤和最终响应的日志。
    • astream_events() (测试版): 异步流式传输链中发生的事件。

每个 Runnable 组件都有 input_schemaoutput_schema 属性,用于检查其预期的输入和输出 Pydantic 模型。

  • 组件的输入/输出类型示例:
    • 提示 (Prompt): 输入 dict, 输出 PromptValue
    • 聊天模型 (Chat Model): 输入 str / List[Message] / PromptValue, 输出 ChatMessage
    • LLM: 输入 str / List[Message] / PromptValue, 输出 str
    • 输出解析器 (Output Parser): 输入 模型输出, 输出 取决于解析器
    • 检索器 (Retriever): 输入 str, 输出 List[Document]
    • 工具 (Tool): 输入 str / dict, 输出 取决于工具

3. 核心组件

3.1 模型 (Models)

  • 聊天模型 (Chat Models):

    • 以消息列表作为输入,返回聊天消息。通常是较新的模型。
    • 支持不同的角色(AI, 用户, 系统等)。
    • LangChain 包装器允许传入字符串(自动转为 HumanMessage)。
    • 依赖第三方集成。
    • 标准化参数 (部分模型支持): model, temperature, timeout, max_tokens, stop, max_retries, api_key, base_url
    • 也接受特定于集成的参数。
    • 工具调用: 许多聊天模型已针对工具调用进行了微调,推荐用于需要此功能的场景。
    • 多模态: 部分模型支持图像等输入。LangChain 提供了轻量级抽象,通常接受 OpenAI 内容块格式(目前主要用于图像)。
  • 大型语言模型 (LLMs):

    • 以字符串作为输入,返回字符串。通常是较旧的模型。
    • 注意: 许多新应用即使非聊天场景也推荐使用聊天模型。
    • LangChain 包装器允许传入消息列表(自动格式化为字符串)。
    • 依赖第三方集成。

3.2 消息 (Messages)

语言模型交互的基本单位,通常包含 role (角色) 和 content (内容) 属性。

  • HumanMessage: 代表用户说的话 (role='user')。
  • AIMessage: 代表 AI 的回复 (role='assistant')。
    • response_metadata: 可能包含模型供应商特定的元数据(如 token 使用量)。
    • tool_calls: 一个 ToolCall 对象列表,表示模型请求调用工具。
      • ToolCall 包含 name (工具名), args (参数字典), id (调用 ID)。
  • SystemMessage: 指导模型行为的消息 (role='system')。并非所有模型都支持。
  • ToolMessage: 代表调用工具的结果 (role='tool')。
    • tool_call_id: 关联的 AIMessage 中工具调用的 id
    • content: 工具返回的结果(通常是字符串)。
    • artifact (可选): 存储原始工具输出或其他信息,不发送给模型但用于追踪。
  • FunctionMessage (遗留): 对应旧的 OpenAI 函数调用 API,现在应使用 ToolMessage

3.3 提示 (Prompts)

用于将用户输入和参数转换为模型指令的模板。

  • 提示模板 (Prompt Templates): 输入是字典,输出是 PromptValue (可转换为字符串或消息列表)。

    • 字符串提示模板 (PromptTemplate): 用于格式化单个字符串。
      from langchain_core.prompts import PromptTemplate
      prompt_template = PromptTemplate.from_template("给我讲个关于{topic}的笑话")
      prompt_value = prompt_template.invoke({"topic": "猫"})
      # prompt_value 可以传递给模型
      
    • 聊天提示模板 (ChatPromptTemplate): 用于格式化消息列表。
      from langchain_core.prompts import ChatPromptTemplate
      prompt_template = ChatPromptTemplate.from_messages([
          ("system", "你是一个乐于助人的助手"),
          ("user", "给我讲个关于{topic}的笑话")
      ])
      prompt_value = prompt_template.invoke({"topic": "猫"})
      # prompt_value 可以传递给聊天模型
      
    • 消息占位符 (MessagesPlaceholder): 用于在模板中插入一个消息列表。
      from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
      from langchain_core.messages import HumanMessage
      prompt_template = ChatPromptTemplate.from_messages([
          ("system", "你是一个乐于助人的助手"),
          MessagesPlaceholder("msgs") # 或者 ("placeholder", "{msgs}")
      ])
      prompt_value = prompt_template.invoke({"msgs": [HumanMessage(content="你好!")]})
      # prompt_value 会包含系统消息和传入的人类消息
      
  • 示例选择器 (Example Selectors): 用于动态选择少量示例 (Few-Shot Examples) 并将其格式化到提示中,以提高模型性能。

3.4 输出解析器 (Output Parsers)

将模型的原始输出(字符串或消息)转换为更结构化的格式(如 JSON, Pydantic 对象, 列表等)。

  • 注意: 对于支持函数/工具调用的模型,通常推荐使用该功能(或 .with_structured_output())来获取结构化数据,而不是手动解析。

  • 常见解析器:

    • JSON: 解析 JSON 输出,可与 Pydantic 模型结合。
    • XML: 解析 XML。
    • CSV: 解析逗号分隔值。
    • Pydantic: 将输出解析为指定的 Pydantic 模型。
    • Enum: 将输出解析为枚举值。
    • DateTime: 解析日期时间。
    • Structured: 简单的结构化解析(字段为字符串)。
    • OutputFixing/RetryWithError: 尝试使用 LLM 修复格式错误的输出。
    名称支持流式有格式说明调用 LLM输入类型输出类型描述
    JSONstr | 消息JSON 对象获取 JSON 输出,可指定 Pydantic 模型。可能是非工具调用下最可靠的方式。
    XMLstr | 消息dict需要 XML 输出时使用,适合擅长 XML 的模型(如 Anthropic)。
    CSVstr | 消息List[str]返回逗号分隔的值列表。
    OutputFixingstr | 消息(包装解析器类型)包装另一个解析器,在其出错时调用 LLM 尝试修复。
    RetryWithErrorstr | 消息(包装解析器类型)类似 OutputFixing,但会将原始指令也传给 LLM 以便修复。
    Pydanticstr | 消息pydantic.BaseModel接受 Pydantic 模型,返回该格式的数据。
    YAMLstr | 消息pydantic.BaseModel类似 Pydantic,但使用 YAML 编码。
    PandasDataFramestr | 消息dict用于处理 Pandas DataFrame。
    Enumstr | 消息Enum将响应解析为提供的枚举值之一。
    Datetimestr | 消息datetime.datetime将响应解析为日期时间字符串。
    Structuredstr | 消息Dict[str, str]返回结构化信息,但字段仅限字符串,适用于较简单的模型。

3.5 聊天历史 (Chat History)

用于在对话中维护上下文的机制。ChatHistory 类可以包装链,自动跟踪输入输出并将其添加到消息存储中,以便在未来的交互中加载和使用。

3.6 文档处理 (Document Handling)

  • 文档 (Documents): 包含文本内容和元数据的对象。

    • page_content (str): 文档的文本内容。
    • metadata (dict): 与文档相关的任意元数据(如来源、ID 等)。
  • 文档加载器 (Document Loaders): 用于从各种来源(文件、数据库、API 等)加载数据并创建 Document 对象。提供 .load() 方法。

  • 文本分割器 (Text Splitters): 将长文档分割成更小的、适合模型处理的块。

    • 工作原理: 通常先按小单位(如句子)分割,然后组合成达到目标大小的块,并可能保留块之间的重叠。
    • 常见类型:
    名称类别分割依据添加元数据描述
    递归 (Recursive)RecursiveCharacterTextSplitter, RecursiveJsonSplitter用户定义的字符列表推荐的起始方法,尝试保持语义相关的片段在一起。
    HTMLHTMLHeaderTextSplitter, HTMLSectionSplitterHTML 特定字符基于 HTML 结构分割,并添加来源元数据。
    MarkdownMarkdownHeaderTextSplitterMarkdown 特定字符基于 Markdown 标题等分割,并添加来源元数据。
    代码 (Code)多种语言代码特定字符支持多种编程语言的基于语法的分割。
    令牌 (Token)多种类令牌基于模型的分词器计算的令牌数量进行分割。
    字符 (Character)CharacterTextSplitter用户定义的字符简单的基于固定字符分割的方法。
    语义分块器 (Experimental)SemanticChunker句子 (语义相似度)按句子分割,然后合并语义上相邻的句子。 (来自 Greg Kamradt)
    集成:AI21 语义AI21SemanticTextSplitter主题识别文本中的连贯主题并沿主题边界分割。

3.7 嵌入 (Embeddings)

  • 嵌入模型 (Embedding Models): 创建文本的向量表示(数字数组),捕捉其语义含义。用于相似性搜索等任务。
    • LangChain 提供 Embeddings 类作为标准接口,支持多种模型供应商和本地模型。
    • 通常有两种方法:embed_documents (用于多个待索引的文本) 和 embed_query (用于单个查询文本)。

3.8 向量存储 (Vector Stores)

用于存储文本的嵌入向量及其元数据,并执行向量相似性搜索。

  • 支持根据元数据进行过滤。
  • 可以通过 .as_retriever() 方法转换为检索器接口。

3.9 检索器 (Retrievers)

一个更通用的接口,用于根据查询返回相关文档。

  • 输入是字符串查询,输出是 Document 列表。
  • 可以基于向量存储创建,也可以包括其他来源(如 Web 搜索、数据库查询等)。

3.10 键值存储 (Key-Value Stores)

用于存储任意键值对,常见于需要缓存嵌入或处理复杂索引的场景。

  • LangChain 提供 BaseStore[str, bytes] 接口 (称为 ByteStore) 用于存储二进制数据。
  • 标准接口: mget, mset, mdelete, yield_keys

3.11 工具 (Tools)

设计用于被模型调用的实用程序或功能。

  • 组成:
    • name (str): 工具的名称。
    • description (str): 工具功能的描述。
    • args_schema (Pydantic BaseModel / JSON Schema): 定义工具输入的模式。
    • func (Callable): 执行工具逻辑的函数(可选同步/异步)。
  • 工作流程:
    1. 使用 .bind_tools() 将工具列表绑定到聊天模型。
    2. 模型根据提示决定调用哪个工具,并生成 args,输出包含 tool_callsAIMessage
    3. 应用程序解析 tool_calls,获取 name, args, id
    4. 调用相应的工具函数,传入 args
    5. 将工具的输出(通常是字符串)包装在 ToolMessage 中,包含 contenttool_call_id
    6. ToolMessage 添加到消息历史中,传回给模型以继续处理或生成最终响应。
  • 调用方式:
    • 使用 args 调用: tool.invoke(tool_call["args"]) 返回原始输出,需要手动创建 ToolMessage
    • 使用 ToolCall 对象调用: tool.invoke(tool_call) 直接返回 ToolMessage
  • 最佳实践:
    • 使用支持工具调用的模型。
    • 精心设计名称、描述和模式(提示工程的一部分)。
    • 倾向于简单、范围明确的工具。
  • 工具包 (Toolkits): 为特定任务预先打包好的工具集合,提供方便的加载方法 (如 toolkit.get_tools())。

3.12 代理 (Agents)

使用 LLM 作为推理引擎来决定采取哪些行动(通常是调用工具)以及如何行动的系统。

  • LangGraph: 推荐用于构建代理的库,提供灵活性和可控性,将流程建模为图。
  • AgentExecutor (遗留): 旧的代理运行时,建议逐步迁移到 LangGraph。
  • ReAct 架构: 一种流行的代理模式,结合推理 (Reasoning) 和行动 (Acting) 的迭代过程。模型思考 -> 选择工具/响应 -> 生成参数 -> (运行时执行工具) -> 返回观察 -> 模型再次思考...

4. 开发与运维

4.1 回调 (Callbacks)

允许在 LLM 应用的不同阶段(如模型调用、链执行、工具使用等)触发自定义逻辑,用于日志记录、监控、流式处理等。

  • 事件: on_chat_model_start, on_llm_new_token, on_chain_end, on_tool_start, 等。
  • 处理器: 实现 BaseCallbackHandler (同步) 或 AsyncCallbackHandler (异步) 接口。
  • 传递方式:
    • 请求时回调: 在调用 .invoke(), .stream() 等方法时通过 callbacks 参数传入,会被所有子组件继承。
    • 构造函数回调: 在创建模型、链等对象时通过 callbacks 参数传入,仅对该对象本身生效,不继承。

4.2 流式处理 (Streaming)

处理 LLM 较长响应时间的关键技术,允许逐步消费输出。

  • .stream() / .astream(): 用户友好的 LCEL 方法,返回输出块的迭代器。自动以流式模式调用底层组件。输出块类型取决于组件(如 AIMessageChunk)。
    from langchain_anthropic import ChatAnthropic
    model = ChatAnthropic(model="claude-3-sonnet-20240229")
    for chunk in model.stream("天空是什么颜色的?"):
        print(chunk.content, end="|", flush=True)
    
  • .astream_events() (测试版): 返回包含各种事件(包括中间步骤和最终输出)的迭代器,提供比 .stream() 更详细的信息,适用于需要访问中间结果的复杂链。
  • 回调: 底层流式处理机制,通过处理 on_llm_new_token 等事件实现。功能强大但可能更复杂。

4.3 令牌 (Tokens)

LLM 处理和生成文本的基本单位。可以是单词或子词。模型输入输出的成本和限制通常基于令牌数量。

4.4 函数/工具调用 (Function/Tool Calling)

允许聊天模型生成结构化输出来调用外部函数或工具。

  • 模型不直接执行,仅生成调用所需的 nameargs
  • 是获取结构化输出的可靠方法。
  • 通过 .bind_tools() 将工具模式(函数、Pydantic 类等)绑定到模型。
  • 模型在 AIMessagetool_calls 属性中返回调用请求。
  • 应用程序负责执行工具并将结果以 ToolMessage 形式传回模型。

4.5 结构化输出 (Structured Output)

让 LLM 返回特定格式(如 JSON, YAML)的数据。

  • .with_structured_output(): 推荐的起始方法(如果模型支持)。简化了流程,只需提供 Pydantic 模型或 JSON Schema。
    from typing import Optional
    from pydantic import BaseModel, Field
    # 假设 llm 已初始化并支持此方法
    class Joke(BaseModel):
        """要讲给用户的笑话。"""
        setup: str = Field(description="笑话的铺垫")
        punchline: str = Field(description="笑话的笑点")
        rating: Optional[int] = Field(description="笑话有多好笑,从 1 到 10")
    structured_llm = llm.with_structured_output(Joke)
    joke_obj = structured_llm.invoke("给我讲个关于猫的笑话")
    print(joke_obj)
    
  • 原始提示 (Raw Prompting): 直接在提示中要求模型按特定格式输出。灵活性高,但可靠性可能因模型和提示而异,需要输出解析器。
  • JSON 模式 (JSON Mode): 特定模型支持的功能,强制模型输出有效的 JSON。通常需要结合简单的提示。
  • 工具调用 (Tool Calling): 如上所述,利用模型内置功能生成符合模式的参数,非常可靠。

4.6 少量示例提示 (Few-Shot Prompting)

在提示中包含输入输出示例以提高模型性能。

  • 生成示例: 手动创建、使用更好模型生成、利用用户/LLM 反馈。可以是单轮或多轮对话示例。
  • 示例数量: 需要实验确定最佳数量,平衡性能、成本和延迟。
  • 选择示例: 随机选择、基于相似性(语义或关键词)、基于约束(如令牌大小)。ExampleSelector 类提供支持。
  • 格式化示例:
    • 作为字符串插入系统提示。
    • 作为独立的消息序列(HumanMessage, AIMessage),可能使用 name 属性区分示例参与者。
    • 工具调用示例格式化: 需要注意不同模型对 AIMessage (带 tool_calls) 和 ToolMessage 序列的约束。可能需要添加虚拟消息或尝试字符串格式。

4.7 检索 (Retrieval) / RAG (Retrieval-Augmented Generation)

向 LLM 提供相关信息(通常来自外部数据源)以增强其响应能力,尤其针对私有或最新信息。

  • 核心流程: 用户查询 -> (可选: 查询转换) -> 检索相关文档 -> 将文档和查询提供给 LLM -> LLM 生成响应。
  • 关键技术领域:
    • 查询翻译 (Query Translation): 使用 LLM 修改或重写用户输入以优化检索。
      • 多查询: 从不同角度重写问题。
      • 分解: 将复杂问题拆分为子问题。
      • 回退: 生成更通用的问题以检索背景知识。
      • HyDE: 生成假设性文档用于检索。
    • 路由 (Routing): 使用 LLM 将查询引导至最合适的数据源(数据库、向量存储、Web搜索等)。
      • 逻辑路由: 基于规则。
      • 语义路由: 基于嵌入相似性。
    • 查询构建 (Query Construction): 使用 LLM 将自然语言查询转换为特定数据源的查询语言(如 SQL, Cypher)或元数据过滤器。
      • 文本到 SQL/Cypher
      • 自查询: 生成语义查询字符串和元数据过滤器。
    • 索引 (Indexing): 设计存储和组织文档及其嵌入的方式。
      • 向量存储: 基本方法,嵌入文本块。
      • 父文档检索器: 索引块,但返回整个文档。
      • 多向量检索器: 为文档创建多种向量表示(如摘要、假设问题)。
      • 时间加权: 考虑文档的新近度。
    • 相似性搜索改进:
      • ColBERT: 更细粒度的逐令牌嵌入和评分。
      • 混合搜索: 结合关键字和语义搜索。
      • 最大边际相关性 (MMR): 多样化搜索结果。
    • 后处理 (Post-processing): 过滤、重新排序或压缩检索到的文档。
      • 上下文压缩: 仅提取文档中的最相关部分。
      • 集成/融合 (如 RRF): 合并来自多个检索器的结果。
      • 重新排序: 使用 LLM 或其他模型根据相关性对文档排序。
    • 生成与自我纠正 (Generation & Self-Correction): 在生成阶段加入检查和修正机制,处理低质量检索或幻觉。通常使用 LangGraph 实现。
      • 纠正性 RAG: 检测到不相关文档时回退(如 Web 搜索)。
      • 自我 RAG: 检查文档相关性、幻觉、答案质量,并迭代修正。

4.8 评估 (Evaluation)

评估 LLM 应用性能和有效性的过程,通常涉及将模型响应与预定义标准或基准进行比较。LangSmith 提供了创建数据集、定义指标、运行评估和跟踪结果的功能。

4.9 追踪 (Tracing)

记录应用程序从输入到输出所经过的一系列步骤(称为 Runs)。对于观察链和代理内部行为、诊断问题至关重要。LangSmith 是主要的追踪工具。

参考文献