暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Langchain 入门:用结构化思维构建 LLM 应用!

AI云枢 2025-05-30
88
过去一年多,大语言模型的浪潮席卷了整个技术领域。我们可能都尝试过直接调用 OpenAI 或其他模型的 API,感受过它神奇的能力。但是,随着项目复杂度提升,你是否也遇到了这些痛点:
  • Prompt 管理噩梦? 你的 Prompt 是硬编码在代码里,还是散落在各个文件中?修改一个 Prompt 需要改动多个地方?
  • 上下文处理繁琐? 如何在多轮对话中优雅地管理历史信息,避免信息丢失或冗余?
  • 应用逻辑混乱? 想让 LLM 完成一个复杂任务(比如先检索文档再总结),代码写出来像一堆“屎山”,流程难以追踪?
  • 模型切换成本高? 如果老板突然说要换个模型提供商,你是不是要改动大量的调用代码?

这些问题,正是 Langchain 框架想要解决的。它不是一个简单的 LLM 调用库,而是一个强大的LLM 应用开发框架。本期我们将深入理解 Langchain 的核心哲学和最基础但至关重要的组件,让你学会如何用结构化的思维,像搭建乐高积木一样,高效、健壮地构建 LLM 应用。

为什么要用 Langchain?

想象一下,你正在开发一个复杂的 LLM 应用,它可能需要:
  1. 根据用户提问,先从公司内部知识库中检索相关文档。
  2. 将检索到的文档和用户提问一起作为 Prompt 发送给 LLM。
  3. LLM 生成答案后,可能需要对答案进行结构化解析(例如提取关键实体)。
  4. 如果 LLM 无法回答,还需要调用外部工具(如搜索引擎)获取信息。
  5. 在整个过程中,还要保持对用户对话历史的记忆。

如果完全手写这些逻辑,你会发现代码迅速膨胀,可读性差,更别提维护和协作了。
Langchain 提供的解决方案核心是:模块化与可组合性。
它将 LLM 应用开发中常见的模式和组件进行抽象和封装,提供了一系列“标准件”和“连接器”。
  • 模块化 (Modularity):将 LLM 调用、Prompt 构建、信息检索、内存管理、工具使用等功能,拆分为独立的、可复用的组件。就像把一整块代码拆分成一个个微服务。
  • 可组合性 (Composability):提供灵活的机制(尤其是 Chains 和 LCEL),让你能像搭积木一样,将这些独立的模块串联起来,形成复杂的业务逻辑流。
用一句话概括:Langchain 帮助我们从零散的 LLM API 调用,走向结构化、可维护、可扩展的 LLM 应用开发体系。 这对于团队协作和长期项目维护至关重要。

Langchain 核心模块深度解析与实践
环境准备:
创建并激活虚拟环境
    python -m venv venv
    # Windows
    .\venv\Scripts\activate
    # macOS/Linux
    source venv/bin/activate
    安装必要的库
      pip install langchain langchain-openai # Langchain 核心库和 OpenAI 模块
      设置 OpenAI API Key
      为了运行示例代码,你需要设置你的 OpenAI API 密钥。最安全和推荐的方式是设置为环境变量:
        # 在终端中运行,Windows 用户使用 set 而非 export
        export OPENAI_API_KEY="你的OpenAI API Key"
        或者在代码中临时设置 (不推荐用于生产环境):
          import os
          os.environ["OPENAI_API_KEY"] = "你的OpenAI API Key"

          LLMs & Chat Models:Langchain 的抽象层
          作用:LLMs (Language Models) 和 Chat Models (Chat Models) 是 Langchain 中与各种大语言模型进行交互的抽象接口。它们的目标是提供一个统一的 API 表面,无论你后端连接的是 OpenAI、Google Gemini、还是本地部署的 Llama,你的上层应用代码几乎无需改动。

          • LLM (旧式接口):主要用于那些接收一个纯文本字符串作为输入,并返回一个纯文本字符串输出的模型(通常是文本补全任务)。例如,OpenAI 早期的一些 text-davinci-003 模型。在实际开发中,由于主流模型都已转向对话模式,这个接口的使用场景逐渐减少。
          • ChatModel (推荐使用):这是目前主流且更强大的接口。它接收一个 结构化的消息列表 作为输入(由 SystemMessageHumanMessageAIMessage 组成),并返回一个 结构化的 AI 消息对象。这完美契合了现代对话型模型的特点,更利于复杂的上下文管理和角色扮演。

          核心方法:invoke()
          invoke() 是 Langchain 组件的标准同步调用方法。它接收输入,等待组件处理完成后返回结果。

          代码示例
            import os
            from langchain_openai import ChatOpenAI
            from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
            # 确保 OPENAI_API_KEY 已设置
            print("--- 2.1 LLM 与 Chat Model 接口演示 ---")
            # 初始化一个 ChatModel - 推荐使用 gpt-3.5-turbo 或 gpt-4
            # temperature 控制模型生成文本的随机性,0 表示确定性最高,1 表示创造性最高。
            chat_model = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.7)
            # 场景一:简单的用户提问
            print("\n--- 场景一:简单的用户提问 ---")
            user_query = "你好,请用一句话介绍一下 Langchain。"
            response = chat_model.invoke([
                HumanMessage(content=user_query)
            ])
            print(f"用户提问: {user_query}")
            print(f"ChatModel 响应: {response.content}"# response 是 AIMessage 对象,内容在 .content 属性中
            # 场景二:结合系统消息,为模型设定角色或行为
            print("\n--- 场景二:结合系统消息,为模型设定角色或行为 ---")
            messages_with_system = [
                SystemMessage(content="你是一个专业的编程语言导师,擅长简洁明了地解释概念。"),
                HumanMessage(content="请解释一下 Python 中的装饰器。")
            ]
            response_system_role = chat_model.invoke(messages_with_system)
            print(f"设定角色后 ChatModel 响应: {response_system_role.content}")
            # 场景三:模拟多轮对话的结构(实际应用中会结合 Memory 模块)
            print("\n--- 场景三:模拟多轮对话结构 ---")
            # 这里的 messages 列表代表了对话历史
            dialog_history = [
                HumanMessage(content="我最喜欢的编程语言是 Python。"),
                AIMessage(content="Python 是一个非常流行的语言!你喜欢它的哪些特性呢?"),
                HumanMessage(content="我喜欢它的简洁性和丰富的库生态。")
            ]
            response_multi_turn = chat_model.invoke(dialog_history)
            print(f"ChatModel 响应 (模拟多轮): {response_multi_turn.content}")
            深入理解
            • 统一接口:无论你将来切换到其他 ChatModel(如 GoogleGenerativeAI),调用 invoke 方法的格式基本不变,极大降低了模型迁移成本。
            • 结构化输入输出ChatModel 接收的是 BaseMessage 列表,这比简单的字符串更容易管理复杂的上下文,比如区分用户说的、AI 说的、系统设定的。

            Prompt Templates:Prompt Engineering 的“脚手架”
            作用:Prompt Templates (Prompt 模板) 是 Langchain 中用于结构化地构建发送给大语言模型指令(Prompt)的工具。它让你的 Prompt 不再是硬编码的字符串,而是带有变量的模板,实现了 Prompt 的参数化

            核心价值(对于团队开发)
            • Prompt 即代码:将 Prompt 文本从业务逻辑中分离,像配置一样管理。
            • 版本控制友好:Prompt 内容的变化可以像代码一样进行版本管理。
            • 协作效率:不同团队成员可以独立维护和优化不同的 Prompt 模板。
            • 测试性:更容易针对不同输入测试 Prompt 的效果。
            • 一致性:确保在不同场景下生成的 Prompt 格式和质量一致。

            两种主要类型:
            • PromptTemplate:适用于 LLM 类型的模型,它接收字符串变量,最终生成一个纯文本 Prompt 字符串。
            • ChatPromptTemplate:强烈推荐 用于 ChatModel 类型模型。它允许你以更细粒度的方式定义 Prompt,包括 SystemMessage(系统指令)、HumanMessage(用户输入)和 AIMessage(AI 回复)的模板。

            代码示例
              from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
              from langchain_core.messages import HumanMessage, SystemMessage
              print("\n--- 2.2 Prompt Templates 演示 ---")
              # 1. 使用 PromptTemplate (适用于旧式LLM,了解即可)
              # 作用:将输入变量填充到模板字符串中
              poem_template = PromptTemplate(
                  input_variables=["topic""style"],
                  template="请给我写一首关于 {topic} 的诗歌,风格是 {style}。",
              )
              # 格式化 Prompt,传入变量
              formatted_poem_prompt = poem_template.format(topic="秋天的落叶", style="伤感")
              print(f"格式化后的 Prompt (PromptTemplate):\n{formatted_poem_prompt}")
              # 2. 使用 ChatPromptTemplate (推荐,适用于 ChatModel)
              # 作用:构建结构化的消息列表,每个消息可以有自己的模板
              coding_assistant_template = ChatPromptTemplate.from_messages([
                  SystemMessage(content="你是一个经验丰富的Python程序员,擅长解答编程问题,并给出代码示例。"),
                  HumanMessage(content="请用 {language} 语言,实现一个 {functionality} 的函数。"),
              ])
              # 格式化 ChatPromptTemplate,传入变量,返回一个消息对象列表
              messages_for_coding = coding_assistant_template.format_messages(
                  language="Python",
                  functionality="计算斐波那契数列的第 n 项"
              )
              print(f"\n格式化后的 Chat Messages (ChatPromptTemplate):")
              for msg in messages_for_coding:
                  print(f"  {type(msg).__name__}{msg.content[:50]}..."# 打印部分内容
              # 将格式化后的消息发送给 ChatModel
              # chat_model 在 2.1 节已初始化
              response_code = chat_model.invoke(messages_for_coding)
              print(f"\nChatModel 生成的代码解释:\n{response_code.content[:300]}..."# 打印部分响应
              # 另一个更复杂的 ChatPromptTemplate 示例:需要JSON格式输出
              json_prompt = ChatPromptTemplate.from_messages([
                  SystemMessage(content="你是一个数据提取专家,请将用户信息提取为 JSON 格式。"),
                  HumanMessage(content="请从以下文本中提取用户的姓名和年龄:\n\n用户资料:我叫李明,今年28岁。"),
                  HumanMessage(content="请确保输出为严格的 JSON 格式,例如: `{{\"name\": \"\", \"age\": 0}}`")
              ])
              json_messages = json_prompt.format_messages() # 无需额外变量,直接格式化
              response_json = chat_model.invoke(json_messages)
              print(f"\nChatModel 生成的 JSON:\n{response_json.content}")
              深入理解
              • 模板的本质:Prompt 模板的本质是实现了 BasePromptTemplate 接口的类,它们负责将传入的变量字典转换为模型能理解的输入格式(字符串或 BaseMessage 列表)。
              • 参数化 Prompt 的威力:在实际项目中,Prompt 往往非常复杂,包含大量的指示、示例。通过模板化,你可以将这些复杂性封装起来,只暴露需要动态填充的参数,大大降低了业务逻辑代码的耦合度。

              LCEL (Langchain Expression Language) & Chains:构建模块化 LLM 流水线
              作用:Chains(链)是 Langchain 实现 可组合性 的核心。它允许你将多个 Langchain 组件(如 Prompt Template、LLM、输出解析器、内存管理模块等)像管道一样连接起来,形成一个端到端的逻辑流。

              LCEL 的重要性 (Langchain Expression Language)
              在 Langchain 的最新版本中,LCEL 是构建链的首选方式。它是一种声明式的方式,使用简单的 | 管道操作符,让你可以清晰地定义数据如何在组件之间流动。LCEL 提供了:
              • 极佳的可读性:数据流向一目了然。
              • 强大的灵活性:支持复杂的输入/输出处理、并行执行、分支逻辑等。
              • 性能优化:LCEL 组件原生支持异步(ainvoke)和流式传输(stream),提高应用响应速度。
              • 易于测试:每个组件都是独立的,方便进行单元测试。

              核心操作符:| (管道操作符)
              A | B | C 表示将组件 A 的输出作为组件 B 的输入,再将 B 的输出作为 C 的输入。

              代码示例
              用户输入一个编程概念 -> 链生成一个关于该概念的 Python 代码示例和简短解释。
                from langchain_core.prompts import ChatPromptTemplate
                from langchain_openai import ChatOpenAI
                from langchain_core.output_parsers import StrOutputParser # 用于将模型输出解析成字符串
                print("\n--- 2.3 LCEL (Chains) 演示 ---")
                # 1. 定义 Prompt Template
                code_gen_prompt = ChatPromptTemplate.from_messages([
                    SystemMessage(content="你是一个专业的 Python 编程助手,擅长清晰地解释概念并提供简洁的代码示例。"),
                    HumanMessage(content="请为我解释并提供一个关于 Python '{concept}' 的代码示例。"),
                    HumanMessage(content="请确保解释和代码都清晰易懂。")
                ])
                # 2. 初始化 Chat Model
                # chat_model 在 2.1 节已初始化
                # 3. 定义输出解析器
                # StrOutputParser 会把 ChatModel 返回的 AIMessage 对象中的 .content 部分提取出来
                output_parser = StrOutputParser()
                # 4. 构建 LCEL 链
                # 链的输入是一个字典,例如 {"concept": "装饰器"}
                # 1. `code_gen_prompt` 接收 {"concept": "..."},生成 Chat Messages 列表
                # 2. `chat_model` 接收 Chat Messages 列表,返回 AIMessage 对象
                # 3. `output_parser` 接收 AIMessage 对象,解析成纯字符串
                code_explanation_chain = code_gen_prompt | chat_model | output_parser
                # 5. 调用链并传入输入变量
                input_concept = "生成器"
                print(f"正在生成关于 '{input_concept}' 的代码示例和解释...")
                result = code_explanation_chain.invoke({"concept": input_concept})
                print(f"\n链的输出:\n{result}")
                # 另一个链的例子:先将用户输入转换为英文,再进行解释
                from langchain_core.runnables import RunnablePassthrough # 用于传递输入
                # 步骤1:翻译 Prompt
                translate_prompt = ChatPromptTemplate.from_messages([
                    SystemMessage(content="你是一个专业的翻译家,将用户输入的中文短语翻译成英文。"),
                    HumanMessage(content="请将 '{text}' 翻译成英文。")
                ])
                # 步骤2:英文解释 Prompt
                explain_prompt = ChatPromptTemplate.from_messages([
                    SystemMessage(content="你是一个专业词汇解释器,用简洁的英文解释词语。"),
                    HumanMessage(content="请解释一下 '{english_text}' 这个词汇的含义。")
                ])
                # 构建一个更复杂的链:
                # 1. 接收一个包含 "text" 的字典,例如 {"text": "并行计算"}
                # 2. 通过 RunnablePassthrough 将 "text" 传给 translate_prompt
                # 3. translate_chain 翻译中文到英文,其输出是英文文本
                # 4. assign() 将翻译结果添加到字典中,键为 "english_text"
                # 5. explain_prompt 接收 "english_text",生成解释 Prompt
                # 6. chat_model 和 output_parser 处理最终的解释
                translation_and_explanation_chain = (
                    {"text": RunnablePassthrough()} # 接收原始输入并传递
                    | translate_prompt 
                    | chat_model 
                    | output_parser
                    .assign(english_text=lambda x: x) # 将翻译结果赋值给 english_text 键
                    | explain_prompt 
                    | chat_model 
                    | output_parser
                )
                # 注意:上面的 .assign() 只是一个示意,实际复杂的链可能需要更精妙的 RunnableParallel/RunnableMap 来处理多输入
                # 这里简化为串行处理,只传递一个主要输入。
                # 假设我们需要一个更清晰的多步骤链,来演示输入传递
                # Step 1: 翻译
                translator_chain = (
                    translate_prompt | chat_model | output_parser
                )
                # Step 2: 解释 (需要上一步的翻译结果)
                # 这里的{"english_text": translator_chain} 表示将 translator_chain 的输出作为 english_text 的值
                full_pipeline = {
                    "english_text": translator_chain,
                    "original_text": RunnablePassthrough() # 传递原始的 "text" 输入,供后续步骤使用(如果需要)
                } | explain_prompt | chat_model | output_parser
                input_text_for_pipeline = "并发编程"
                print(f"\n正在处理概念:'{input_text_for_pipeline}'")
                explanation_result = full_pipeline.invoke({"text": input_text_for_pipeline})
                print(f"解释结果:\n{explanation_result}")
                深入理解
                • LCEL :LCEL 的核心是 Runnable 接口。所有能被 | 连接的组件都实现了这个接口。它们定义了如何接收输入、如何处理、以及如何生成输出。
                • 类型匹配:组件之间通过管道连接时,需要确保左侧组件的输出类型与右侧组件的预期输入类型匹配。例如,ChatPromptTemplate 输出的是 BaseMessage 列表,正好是 ChatModel 的输入。
                • RunnablePassthrough:这是一个非常有用的 LCEL 组件,它会将输入原封不动地传递下去。在构建需要保留原始输入,或将输入传递给链中多个组件的复杂链时非常有用。
                • StrOutputParser:虽然简单,但它扮演着重要的角色。LLM 返回的是一个 AIMessage 对象,而我们通常需要其中的 content 字符串进行后续处理或展示,StrOutputParser 恰好完成了这个转换。

                总结
                通过本期教程,我们深入探讨了 Langchain 的基础,但却是构建任何 LLM 应用的基石:
                • LLMs & Chat Models:作为与大模型交互的统一抽象层,让我们轻松切换模型。
                • Prompt Templates:让你的 Prompt 变得可管理、可复用、可协作,是高效 Prompt Engineering 的关键。
                • LCEL & Chains:这是 Langchain 的灵魂!它以声明式、可读性极高的方式,将各种组件串联起来,构建出复杂而健壮的 LLM 业务逻辑流水线。

                掌握了这些,你已经迈出了 Langchain 开发的第一步,并且对 Langchain 的核心设计理念有了更深的理解。


                文章转载自AI云枢,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                评论