浅学 LangChain,AI 赋能软件测试

代码仓库链接:langchain-scratch

主要内容是通过实际动手来理解 LangChain 的各种功能,包括模型调用、提示模板、链式组合、RAG 检索增强生成、Agent 工具调用等等。

第一阶段:大模型与 LangChain 基础

🎯 阶段目标:

  • 熟悉 LangChain 框架及其核心组成模块
  • 熟悉 LangChain 环境安装和配置
  • 掌握调用 OpenAI/DeepSeek/本地Ollama模型方法
  • 掌握 LangChain 调用模型封装和配置

1.1 LangChain 简介与架构

LangChain 是一个用于构建基于大语言模型(LLM)应用的开源框架。

它的核心目标是:

👉 帮助开发者更方便地将 LLM 与外部数据、工具和业务流程结合起来,构建更实用、更强大的 AI 应用。

例如:RAG 问答系统、智能助理、多轮对话、Agent 自动决策工具人等。

官方文档:https://python.langchain.com/docs/introduction/

LangChain 的核心组成可以分为以下几个模块:

  • 1、模型接口(Model I/O)

    • 模型(Model):支持多种 LLM 提供商,如 OpenAI、Anthropic、Cohere、Hugging Face、Ollama 等。
    • 提示模板(Prompt Templates):用于构建和管理提示词,支持动态插值和格式化。
    • 输出解析器(Output Parsers):将 LLM 的输出解析为结构化数据,便于后续处理。
  • 2、链(Chains)

    • 将多个组件组合成一个处理流程,实现复杂的任务,如问答系统、摘要生成等。
    • 支持自定义链的构建,满足特定应用需求。
  • 3、记忆(Memory)

    • 用于存储和检索对话历史,实现上下文感知的交互。
    • 支持多种存储后端,如本地内存、数据库等。
  • 4、代理(Agents)

    • 具备决策能力的组件,能够根据用户输入动态选择工具和操作路径。
    • 适用于需要多步骤推理和工具调用的复杂任务。
  • 5、工具(Tools)

    • 外部功能接口,如搜索引擎、数据库查询、API 调用等。
    • 可被代理调用,扩展 LLM 的能力范围。
  • 6、检索器(Retrievers)

    • 用于从外部知识库中检索相关文档,支持 RAG(Retrieval-Augmented Generation)模式。
    • 支持多种向量数据库和检索策略。
  • 7、文档加载器(Document Loaders)

    • 用于加载和预处理各种格式的文档,如 PDF、HTML、Markdown 等。
    • 支持自定义加载器,满足特定数据源需求。

1.2 LangChain在软件测试中应用场景

场景一: 测试用例生成

根据需求文档、接口文档或历史缺陷,自动生成测试用例。

  • 使用 LangChain + RetrievalQA,将需求文档上传至向量库,通过大模型生成结构化的测试用例(例如:测试点、步骤、预期结果等)。
  • 使用 LangChain Expression Language (LCEL) 组成链式流程:文档读取 ➝ 信息提取 ➝ 用例生成。

场景二: 测试脚本生成

根据自然语言的测试步骤生成自动化脚本(如 Selenium、Playwright、Pytest 脚本)。

  • 使用 LangChain Agent 工具调用 Code Interpreter 工具,支持 Python 脚本生成。
  • 可输入自然语言需求如:"帮我写一个登录功能的 Selenium 脚本"。

场景三、缺陷智能分析与聚类

分析大量历史缺陷数据,提取共性、进行分类、聚类分析。

  • 将缺陷描述、日志等向量化存储,使用 LangChain + FAISS/Chroma 构建缺陷知识库。
  • 使用 LLM 进行缺陷聚类、趋势总结,识别高频缺陷。

场景四、日志异常分析

测试过程中获取的服务日志量庞大,手动排查异常低效。

  • 将日志拆分后存入向量库。
  • 构建 LangChain 问答链,实现"我这个请求超时是因为啥?"这类查询。
  • 配合 LLM 实现智能摘要与重点异常提示。

场景五、接口文档解析与测试自动化

通过接口文档自动生成接口测试用例和测试数据。

  • LangChain 加载 Swagger 或 Markdown 文档,使用 LLM 提取字段、参数、请求类型。
  • 自动生成接口请求模板 + 边界值组合。

场景六、测试知识助手 / Copilot 助手

为测试团队提供一个智能助手,回答关于测试工具、用例设计、脚本开发、项目环境等问题。

  • 使用 LangChain + RAG 构建内部知识库(上传测试相关文档如测试策略、测试标准、平台操作手册等)。
  • 支持对话式问答,如"pytest 参数化怎么用?"、"我们项目的接口测试标准是啥?"。

1.3 LangChain 安装与环境配置

Miniconda 是轻量级、灵活性强的 Python 虚拟环境管理工具,可以用于在同一个机器上安装不同 Python 版本的软件包及其依赖,并能够在不同的 Python 环境之间切换。

下载地址:https://docs.conda.io/en/latest/miniconda.html

验证conda是否安装成功:

shell 复制代码
conda --version

miniconda使用:

复制代码
conda create -n langchain python=3.10 -y		# 创建一个 Python 3.10 的环境,名为 langchain
conda activate langchain						# 激活虚拟环境
conda env list									# 列出当前所有的虚拟环境
conda deactivate								# 退出当前环境

安装 LangChain 相关依赖:

复制代码
pip install langchain==0.3.25				# langchain库
pip install langchain-openai==0.3.16  		# OpenAI 集成 
pip install langchain-deepseek==0.1.3     	# DeepSeek 云端集成 
pip install langchain-ollama==0.3.2       	# Ollama 本地模型支持 
1.3.1 LangChain调用OpenAI GPT

注册 OpenAI 账户,并且创建获取 API Key https://platform.openai.com/account/api-keys

python 复制代码
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="gpt-4",
    temperature=0.5,
    api_key="sk-......"
)
res = llm.invoke("写一个加密算法的接口测试用例")
print(res)

temperature 是大语言模型(LLM)中的一个生成控制参数 ,它用于控制模型回答的 随机性和创造力 ,数值范围通常为 0 ~ 1

temperature 值 特性 示例表现
0 完全确定性(最可预测答案) 更稳定、适合逻辑严密的任务
0.3 ~ 0.5 稍微有创意但仍相对稳定 适合常规写作、编码、测试脚本生成
0.7 ~ 1.0 更具创造力、更发散 适合头脑风暴、文案创意等场景
1.3.2 LangChain调用在线DeepSeek

注册 DeepSeek 账户,并且创建获取 API Key https://platform.deepseek.com/api_keys

python 复制代码
from langchain_deepseek import ChatDeepSeek

llm = ChatDeepSeek(
    model="deepseek-chat",
    temperature=0.5,
    api_key="sk-acb9f39cbefa42e79d7d574fb23eda91"
)
res = llm.invoke("请使用python和requests库编写一个登录的接口自动化脚本")
print(res)
1.3.3 调用本地 Ollama 部署的 DeepSeek

Ollama 是一个开源工具,旨在让你本地运行大语言模型(LLM)变得非常简单。只需一条命令,就可以下载并运行像 LLaMA、DeepSeek、Qwen 等强大的模型,而不需要自己配置复杂的依赖或 GPU 驱动。

下载地址:https://ollama.com/download

验证ollama是否安装成功:

shell 复制代码
ollama --version

ollama操作命令:

shell 复制代码
ollama run deepseek-r1:1.5b					# 运行一个模型,第一次会自动下载模型
ollama list									# 列出所有已安装的模型
ollama remove deepseek-r1:1.5b				# 删除模型
python 复制代码
from langchain_ollama import OllamaLLM

llm = OllamaLLM(
    model="deepseek-r1:1.5b",		#也可以是其他的ollama部署的本地模型,比如qwen2.5:7b
    temperature=0.5,
    base_url="http://localhost:11434"             
)
res = llm.invoke("请简单介绍性能测试中相关的指标")
print(res)
1.3.4 调用模型封装

上述示例代码存在一个问题:api_key、model 等配置信息是直接写死的

为了更安全、可维护,可以使用 .env 文件保存模型相关配置,并借助 python-dotenv 加载 .env 文件中的配置到环境变量,让配置与代码解耦。

目录结构如下:

复制代码
project/
├── .env              		 # 本地配置
├── llm_factory.py           # 模型工厂,提供所有模型的封装

.env配置文件内容:

ini 复制代码
# 可选值:ollama | openai | deepseek
LLM_PROVIDER=openai

# OpenAI
OPENAI_API_KEY=sk-xxx
OPENAI_MODEL=gpt-4

# DeepSeek
DEEPSEEK_API_KEY=ds-xxx
DEEPSEEK_MODEL=deepseek-chat

# Ollama
OLLAMA_MODEL=qwen:7b

llm_factory.py文件内容如下:

python 复制代码
from langchain_ollama import OllamaLLM
from langchain_openai import ChatOpenAI
from langchain_deepseek import ChatDeepSeek
from dotenv import load_dotenv
import os

load_dotenv()

LLM_PROVIDER = os.getenv("LLM_PROVIDER") 

def get_llm(temperature=0.5):
    if LLM_PROVIDER== "ollama":
        return OllamaLLM(
          model=os.getenv("OLLAMA_MODEL"), 
          temperature=temperature
        )
    elif LLM_PROVIDER == "openai":
        return ChatOpenAI(
          openai_api_key=os.getenv("OPENAI_API_KEY"), 
          model_name=os.getenv("OPENAI_MODEL"), 
          temperature=temperature
        )
    elif LLM_PROVIDER == "deepseek":
        return ChatDeepSeek(
           model_name=os.getenv("DEEPSEEK_MODEL"), 
           api_key=os.getenv("DEEPSEEK_API_KEY"),
           temperature=temperature,
        )
    else:
        raise ValueError(f"不支持的 LLM_PROVIDER: {LLM_PROVIDER}")

封装之后优势:

  • 解耦配置:模型参数和密钥都写在 .env,方便多人协作
  • 支持多模型切换:通过修改 .env 即可切换模型,无需改代码
  • 代码结构清晰:统一用 get_llm() 创建模型,主程序更简洁
  • 适配本地和云大模型服务:支持 Ollama(本地) 和 OpenAI/DeepSeek(云)

第二阶段:LangChain模型IO

🎯 阶段目标:

  • 熟悉 Model I/O 模块和组成部分
  • 熟悉输入格式化 PromptTemplate 和 ChatPromptTemplate
  • 熟悉输出解析器作用和常见的输出解析器
  • 掌握 StrOutputParser、JsonOutputParser、PydanticOutputParser

在 LangChain 框架中,模型输入输出(Model I/O)模块是与语言模型(如 GPT-4、Claude、ChatGLM 等)交互的核心组件。它负责处理输入数据的格式化、调用模型生成输出,并对输出结果进行解析。通过标准化的接口和流程,Model I/O 模块简化了与不同模型的集成,提升了开发效率和代码的可维护性。

Model I/O 模块主要由以下三个部分组成:

输入格式化(Prompt Templates):定义输入模板,结合用户输入生成模型提示词。即图中Format部分

模型调用(Models):使用封装的模型接口发送请求,获取模型的原始输出。即图中Predict部分

输出解析(Output Parsers):通过 Output Parser 将模型输出解析为结构化数据,供后续处理或展示。即图中Parse部分

2.1 输入格式化(Prompt Templates)

Prompt Templates 是用于构建模型输入的模板化工具。它们允许开发者将用户输入与预定义的模板结合,生成结构化的提示词,从而引导模型生成期望的输出。LangChain 提供了多种类型的 Prompt Templates,包括 Prompt Templates、ChatPromptTemplates 等,支持动态示例选择、上下文注入等高级功能。

Prompt 是 LLM 输入的提示,决定模型的行为与输出内容,设计好坏直接决定输出质量

2.1.1 PromptTemplate

PromptTemplate(字符串模板) 是最简单的 Prompt 模板,接收一段带变量占位符的字符串,并在调用时填入实际内容,负责将用户提供的变量填入模板并输出最终 Prompt。

默认使用 Python 的 f-string 语法 {变量名}

python 复制代码
from langchain.prompts import PromptTemplate

template = """
你是一位经验丰富的软件测试工程师。
请根据以下功能描述生成 3 条测试用例:
功能:{feature}
"""
prompt_template = PromptTemplate.from_template(template)
prompt = prompt_template.format(feature="用户注册功能")
print(prompt)
  • from_template 快速从字符串生成模板
  • .format() 用变量填充模板
2.1.2 ChatPromptTemplate

ChatPromptTemplate(对话模板) 用于构建多轮对话场景,将"系统/System"、"用户/Human"、"助手/Assistant"等角色消息组织成列表,通过角色和占位符组合,可以明确设定模型身份和对话内容,引导生成更符合预期的回复

角色类型:

角色名称 简介 说明
system 系统角色 定义 AI 的行为、角色、语气等,通常在对话开始时设定
human 用户输入 模拟用户的提问、请求、输入等
ai(或 assistant) AI 回复 表示大模型以 AI 的身份回复内容

基础使用:一个系统 + 一个用户消息

python 复制代码
from langchain.prompts import ChatPromptTemplate

prompt_template = ChatPromptTemplate.from_messages([
    ("system", "你是一个有帮助的AI助手。"),
    ("human", "请用一句话解释什么是LangChain。")
])
prompt = prompt_template.format_messages()
print(prompt)

变量参数:

python 复制代码
from llm_factory import get_llm
from langchain.prompts import ChatPromptTemplate
import os

llm = get_llm()

prompt_template = ChatPromptTemplate.from_messages([
    ("system", "你是一个专业的{domain}。"),
    ("human", "请编写一份关于{feature}的产品需求文档。")
])
prompt = prompt_template.format_messages(domain="产品专家", feature="登录")
print(prompt)
response = llm.invoke(prompt)
print(response)

多轮上下文构建:

python 复制代码
from llm_factory import get_llm
from langchain.prompts import ChatPromptTemplate
import os

llm = get_llm()

prompt_template = ChatPromptTemplate.from_messages([
    ("system", "你是一个测试工程师。请用专业术语回答。"),
    ("human", "什么是接口自动化?"),
    ("ai", "接口自动化是指通过代码方式对系统提供的 API 进行功能验证。"),
    ("human", "那什么是 Mock?")
])
prompt = prompt_template.format_messages()
print(prompt)
res = llm.invoke(prompt)
print(res)
特性 PromptTemplate ChatPromptTemplate
构建方式 单字符串模板 多轮消息结构(system/human/ai)
更适合用途 单轮问答、任务指令 多轮对话、角色扮演、上下文构建

2.2 输出解析器(Output Parsers)

模型的原始输出通常是非结构化的文本,Output Parsers 负责将这些输出解析为结构化的数据格式,如字典、列表或自定义对象。LangChain 提供了多种解析器,如 StrOutputParser、JsonOutputParser、PydanticOutputParser等,支持从简单的字符串提取到复杂的 JSON 解析,满足不同的解析需求。

在 RAG、数据抽取、API 集成等场景中,结构化输出可显著提升下游系统的稳定性和开发效率,保证输出格式一致,减少后续解析错误。

2.2.1 StrOutputParser

将 LLM 的输出保留为纯字符串(str)返回,不做任何结构化处理。

适用场景:当只需要模型的原始文本输出时。

python 复制代码
from llm_factory import get_llm
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
import os

# 初始化模型
llm = get_llm()

# 初始化解析器
parser = StrOutputParser() 

# 创建提示模板
prompt_template = PromptTemplate.from_template("请将下面的中文翻译成英文:{text}")

# 构造提示
prompt = prompt_template.format(text="今天天气很好")

# 调用 LLM 并使用解析器
res = llm.invoke(prompt)
result = parser.invoke(res)

print("原始输出:", res)
print("解析后:", result)
2.2.2 JsonOutputParser

比如:你让大模型提取一段话中的关键信息:"苹果手机售价5999元,现在还有200台库存。"

你希望模型能输出成这样:

json 复制代码
{
  "产品名称": "苹果手机",
  "价格": 5999,
  "库存": 200
}

那接下来你可以把这个数据:存进数据库、显示在网页上、传给 API 做下一步处理

JsonOutputParser 会把模型返回的 JSON 字符串,直接转换为 Python 的字典(dict)或者列表(list)对象。

适用场景:你期望模型返回的数据为 标准 JSON 格式 的场景,比如:信息抽取、问答结构化、表单填写、JSON 结果解析。

案例:

  • 场景设定:你正在开发一个电商后台,想通过模型自动生成一条商品数据。

  • 你问模型:

    "请提供一个产品的名称、价格和库存数量。"

    我们希望它输出结构如下:

    json 复制代码
    {
      "name": "蓝牙耳机",
      "price": 299,
      "stock": 150
    }

代码实现:

python 复制代码
from llm_factory import get_llm
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
import os

# 初始化模型
llm = get_llm()

# 初始化解析器
parser = JsonOutputParser()

# 创建提示模板
prompt_template = ChatPromptTemplate.from_messages([
    ("system", "你是一个返回 JSON 数据的机器人。"),
    ("human", "请提供一个产品的名称、价格和库存数量。")
])

# 构造提示
prompt = prompt_template.format_messages()

# 调用 LLM 并使用解析器
res = llm.invoke(prompt)
result = parser.invoke(res)

print("原始输出:", res)
print("解析后:", result)
2.2.2.1 输出"纯 JSON"

注意:LangChain 的 JsonOutputParser 是使用 json.loads() 直接反序列化的。如果输出中包含任何非 JSON 内容(如前缀、解释、markdown 等),就会抛出错误。

比如:

json 复制代码
这是你要的 JSON:
{
  "name": "蓝牙耳机",
  "price": 299,
  "stock": 150
}

LangChain 非常贴心地给我们提供了一个辅助方法:

python 复制代码
parser.get_format_instructions()

这个方法返回一段提示内容,用于告诉大模型它必须输出标准 JSON 格式。例如,它返回类似这样:

shell 复制代码
Respond with a valid JSON object.

可以将它加入你的 Prompt,让 LLM 明确知道该如何输出,配合解析器使用更稳定。

如果想让大模型输出结构化数据,而你只关心字段值,不关心字段类型校验 ------ JsonOutputParser 就是最佳选择

2.2.3 PydanticOutputParser

PydanticOutputParser 是 LangChain 提供的一个 结构化输出解析器,用于将 LLM 返回的 JSON 字符串 直接解析为 Pydantic 模型对象(而非原始字典)。

它结合了 JSON 解析 + 数据校验 + 类型转换 三重能力。

适合需要强类型、安全解析的结构化应用场景,如:

  • 表单数据抽取
  • 结构化知识抽取
  • 与数据库/后端接口对接
2.2.3.1 Pydantic

Pydantic 是一个基于 Python 类型提示(type hints)的数据验证与解析库,旨在通过声明式的方式创建"模型类"来保证数据的结构与类型安全。

通过类和类型注解,把数据结构写死,并且自动验证输入值是否符合要求。

比如:Pydantic 定义了一个"产品"模型:

python 复制代码
class Product(BaseModel):
    name: str
    price: float
    stock: int

这就规定了:

  • name 必须是字符串
  • price 必须是小数
  • stock 必须是整数

如果你丢进去这样的数据:

json 复制代码
{
  "name": "耳机",
  "price": "便宜",
  "stock": "三百"
}

Pydantic 会立刻抛出一个 ValidationError,告诉你"数据格式不对!"

案例:

  • 场景设定:我们还是希望模型返回一个产品的信息,包括名称、价格和库存。
  • 但是,这一次我们想要"强校验"------不容许模型输出乱七八糟的数据。

PydanticOutputParser示例:

python 复制代码
from llm_factory import get_llm
from langchain.prompts import ChatPromptTemplate
from langchain.output_parsers import PydanticOutputParser
import os
from pydantic import BaseModel, Field

# 定义 Pydantic 类
class Product(BaseModel):
    name: str = Field(description="产品名称")
    price: float = Field(description="产品价格")
    stock: int = Field(description="库存数量")

# 创建解析器
parser = PydanticOutputParser(pydantic_object=Product)
# 生成格式说明(帮助模型输出规范)
format_instructions = parser.get_format_instructions()

# 初始化模型
llm = get_llm()

# 创建提示模板
prompt_template = ChatPromptTemplate.from_messages([
    ("system", "你是一个返回 JSON 数据的机器人。"),
    ("human", "请严格按照以下格式,输出一个真实的产品信息实例(不要输出格式说明,不要输出字段描述,只输出真实数据):\n{format_instructions}")
])

# 构造提示
prompt = prompt_template.format_messages(format_instructions=format_instructions)

# 调用 LLM 并使用解析器
response = llm.invoke(prompt)
result = parser.invoke(response)

print("原始输出:", response)
print("解析后:", result)

第三阶段:LCEL语法和Chain

3.1 什么是 Chain?

在 LangChain 中,chain(链) 是指将多个组件(如提示模板、LLM、输出解析器等)按顺序组合,形成一个处理流程的结构。这种结构使得数据从一个组件流向下一个组件,最终产生期望的输出。这使得开发者可以将多个处理步骤组合成一个统一的流程,从而构建复杂的应用程序。

Chain 的基本结构,一个典型的 Chain 包含以下组件:

  1. PromptTemplate(提示模板):定义输入数据如何转换为模型的提示。
  2. LLM(大型语言模型):如 OpenAI 的 GPT-4,用于生成响应。
  3. OutputParser(输出解析器):将模型的输出解析为所需的格式。

这些组件通过 LangChain Expression Language(LCEL)进行组合,形成一个完整的处理链。

3.2 LCEL Chain

LangChain Expression Language(LCEL)是一种声明式的语言,用于简化和优化 LangChain 中链的构建与组合。它允许开发者以模块化的方式,将提示模板、语言模型、输出解析器等组件通过管道符(|)连接,形成清晰、可维护的链式结构:

shell 复制代码
prompt | llm | parser

LCEL 的核心优势:

  1. 声明式链式组合 :使用 | 操作符,将不同组件连接成链,提升代码的可读性和可维护性。
  2. 流式处理支持:LCEL 支持将语言模型的输出以流的形式传递给下游组件,实现低延迟的响应。
  3. 异步与并行执行:支持异步 API 调用和自动并行处理,提高系统的吞吐量和响应速度。
  4. 中间结果访问:能够访问链中各个步骤的中间结果,便于调试和监控。
  5. 输入输出模式验证:自动推断链的输入输出模式,生成 Pydantic 和 JSON Schema,用于数据验证和文档生成。
  6. 与 LangServe 和 LangSmith 的无缝集成:LCEL 构建的链可以直接部署到 LangServe,并在 LangSmith 中进行详细的追踪和分析。

LCEL语法Chain的应用:

python 复制代码
from llm_factory import get_llm
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
import os

# 初始化模型
llm = get_llm()

# 创建提示模板
prompt = ChatPromptTemplate.from_template("请讲一个关于{topic}的笑话。")

# 定义输出解析器
parser = StrOutputParser()

# 使用 LCEL 构建链
chain = prompt | llm | parser

# 执行链
result = chain.invoke({"topic": "冰淇淋"})
print(result)

3.3 流式输出Stream

通过 chain.stream() 方法,可以实时获取语言模型的输出,适用于需要即时反馈的应用场景。

python 复制代码
from llm_factory import get_llm
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
import os

# 初始化模型
llm = get_llm()

# 创建提示模板
prompt = ChatPromptTemplate.from_template("请讲一个关于{topic}的笑话。")

# 定义输出解析器
parser = StrOutputParser()

# 使用 LCEL 构建链
chain = prompt | llm | parser

# 执行链并以流式方式处理输出
for chunk in chain.stream({"topic": "猫"}):
    print(chunk, end="", flush=True)

stream() 返回的是一个生成器(Python Generator),每次迭代返回 LLM 生成的一小段(如 token)。

第四阶段:LangChain与RAG

4.1 RAG介绍

RAG(Retrieval Augmented Generation,检索增强生成)是一种结合信息检索与生成模型的人工智能技术,旨在通过检索外部知识库中的信息来增强语言模型的生成能力。

RAG核心思路:"在生成答案之前,先去找资料。"

比如:想象你是个学生,老师问你:"地球为什么有四季?"

你可能不会立刻回答,而是会:

  1. 去图书馆查书 → 这就是 "检索"(Retrieval)
  2. 把找到的书摘出相关部分 → 这叫 "增强上下文"(Augmented)
  3. 根据整理的资料写成一段话交给老师 → 这就是 "生成回答"(Generation)
4.1.1 为什么需要RAG
  • 大模型知识是固定的,不能实时更新

    LLM(如 GPT-4)只能回答训练数据中出现过的内容,知识截止时间早(例如 GPT-4 截止于 2023年末)

    • 引入"外部知识源"(如你自己的文档、网页、数据库),模型就能回答实时、专业、私有领域的问题。
    • 无需重新训练或微调大模型。
  • 大模型可能"胡编乱造"

    语言模型容易"幻觉"(hallucination),生成看似正确但实际错误的内容。

    • 通过"检索真实文档"作为上下文增强,显著降低幻觉率;
    • 生成内容可追溯、有出处,提高可信度。
  • 微调成本高,更新不灵活

    微调(fine-tuning)需要 GPU、数据清洗、训练技巧,而且一旦模型更新,微调就失效。

    • 只需更新文档或向量库,不动大模型;
    • 比微调更灵活、快捷、成本更低。
  • 支持私有知识问答、企业场景定制

    企业/团队通常有自己的私有知识(如内部SOP、产品文档、客户问答),这些不会出现在开源大模型中。

    • 构建属于自己的知识问答系统(知识库QA);
    • 可以对接公司内网文档、数据库、工单系统等。
4.1.2 RAG实现基本流程

索引(Indexing)------ 构建知识库

目的是将原始文档转换为向量索引,供后续检索使用。

过程详解:

  1. 文档加载:读取各种类型的原始内容,如 PDF、TXT、Word、网页等。
  2. 文本切分:将长文本按段拆分成小块(例如每 500 个字),以便后续处理。
  3. 向量化(Embedding):使用嵌入模型(如 OpenAI Embedding、BGE)将每个文本块转换为数字向量,表示其语义特征。
  4. 构建索引:将所有向量存入向量数据库(如 FAISS、Chroma、Weaviate),方便后续高效相似度检索。

检索(Retrieve)

当用户提出一个问题时,先从向量数据库中检索相关内容。

过程详解:

  1. 将用户问题转换为向量;
  2. 在向量库中检索"最相似的文本块"(基于余弦相似度、欧式距离等);
  3. 返回 Top-K 个相关段落作为"知识上下文"。

增强(Augment)

将检索到的内容"增强"到 Prompt 中,提供给语言模型作为参考材料。

过程详解:

  1. 将用户问题转换为向量(同样使用 Embedding 模型);
  2. 在向量数据库中查找与问题向量最接近的文本片段;
  3. 取出 Top-K 个相似文本块,作为上下文参考材料。

生成(Generate)

语言模型根据增强后的 Prompt,生成结构清晰、内容准确的回答。

过程详解:

  1. Prompt 输入给大语言模型(如 GPT、Claude、LLaMA);
  2. 模型结合上下文知识进行推理、组织语言并输出答案;

高级系统可能还会:

  • 对多个回答候选进行评分、重排序(Rerank);
  • 加入 Chain-of-Thought 多步推理;
  • 判断是否需要多轮问答补充信息。

4.2 文档处理

在 LangChain 中,文档处理是构建基于大语言模型(LLM)的应用(如问答系统、文档摘要、RAG 等)的核心环节。LangChain 提供了一套完整的文档处理流程,包括文档加载、切分、向量化、检索和分析等步骤

4.2.1 文档加载(Document Loader)

LangChain 提供了多种加载器,用于从不同格式的文件中读取内容,并将其转换为统一的 Document 对象。每个 Document 通常包括:

  • .page_content:主要文本内容;
  • .metadata:元信息(文件名、页码、来源等)。

LangChain 内置的几种常见文档加载器:

  • TextLoader:加载纯文本文件。
  • PyMuPDFLoader:加载 PDF 文件。
  • UnstructuredWordDocumentLoader:加载 Word 文档。
  • UnstructuredMarkdownLoader:加载 Markdown 文件。

依赖安装:

shell 复制代码
pip install langchain langchain-community unstructured pymupdf python-docx markdown

加载纯文本 TXT 文件

python 复制代码
from langchain_community.document_loaders import TextLoader

loader = TextLoader("example.txt", encoding="utf-8")
# 返回一个包含 Document 对象的列表
docs = loader.load()
print(docs[0].page_content)
print(docs[0].page_content[:200])

加载 PDF 文件

python 复制代码
from langchain_community.document_loaders import PyMuPDFLoader

loader = PyMuPDFLoader("example.pdf")
docs = loader.load()

加载 WORD 文件

python 复制代码
from langchain_community.document_loaders import UnstructuredWordDocumentLoader

loader = UnstructuredWordDocumentLoader("example.docx")
docs = loader.load()

加载 Markdown 文件

python 复制代码
from langchain_community.document_loaders import UnstructuredMarkdownLoader

loader = UnstructuredMarkdownLoader("example.md")
docs = loader.load()
4.2.2 文档切分(Text Splitters)

大语言模型(LLM)都有一个上下文窗口的限制,也就是说,它每次最多只能处理有限长度的文本,比如:

  • GPT-3.5 大约只能处理 4K 个 token;
  • GPT-4 Turbo 可以处理到 128K tokens。

但一篇完整的文档,动辄上万字,远远超出了模型的处理能力,所以我们就需要把它进行切分。

LangChain 提供了多种分割器:

  • CharacterTextSplitter:按照固定字符数来切,适用于简单快速处理。
  • RecursiveCharacterTextSplitter:递归地按段落、句子、字符等层次分割,保持语义完整性。
  • SemanticChunker:基于语义的分割,确保每个片段的语义连贯性。

选择合适的分割器可以提高模型处理的效率和准确性。

python 复制代码
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyMuPDFLoader

# 1. 加载 PDF 文档
loader = PyMuPDFLoader("example.pdf")
docs = loader.load()

# 2. 使用 RecursiveCharacterTextSplitter 进行分块 
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=300,			# 每个块的最大字符数
    chunk_overlap=100 		# 相邻块之间有 100 字符重叠
)

chunks = text_splitter.split_documents(docs)
for chunk in chunks:
    print(chunk.page_content)
    print("-------")

4.3 向量化(Embeddings)

4.3.1 向量

在物理中,"向量"是指有 大小方向 的量。

比如:风速可以表示为一个向量,它不仅有风速的大小,还告诉你风吹的方向。

那在计算机中,向量是怎么表示的呢?

它是一个一组数字组成的数组,比如:

复制代码
[12, 13, 19, 8, 9]

你可以想象成 Excel 表格中的一行,或者是一个坐标点,在一个五维空间中的位置。

如果二维是 (x, y),那三维就是 (x, y, z),那五维呢?就是五个数字了。

这些数字,组合在一起,就表示了一个"点",而这个点就是一个向量,数字的个数叫向量的维度。

为什么做向量化?

打个比方:我们现在有几百段文档,我们想从里面找出"和我提的问题最相关的那几段"。

  • 用传统的关键词匹配方式,很容易漏掉语义相似但词不一样的内容。
  • 向量化能用数学方法比较它们的"方向"和"距离",就能判断出哪些语义相近。
4.3.2 Embedding模型

Embedding 模型是一种通过深度学习算法,把"人类能理解的信息"------像文字、图片、音频等,转换成 机器可以理解的向量的模型。

Embedding 模型就是一个翻译器,它能把中文"翻译成"一串数字,让计算机能理解这句话的意思。

假设我们有两个句子:

  1. "我喜欢苹果"
  2. "我爱吃苹果"

我们人一眼就知道这两句话是一个意思。但计算机不知道。这时候,Embedding 模型就上场了,它会把两句话变成两组向量,比如:

  • "我喜欢苹果" → [0.12, 0.55, -0.32, ...]
  • "我爱吃苹果" → [0.11, 0.58, -0.30, ...]

然后你就会发现这两个向量"很接近",代表它们在语义上是"相似的"。Embedding 模型的作用就是: 把人类的语言变成机器能比对的数字向量

4.3.3 向量相似度

为了比较两个文本是否相似,我们需要在向量空间中计算它们的"距离"或"相似度"。

  • 余弦相似度(Cosine Similarity):衡量两个向量夹角的余弦值,值越接近 1 表示向量方向越一致,也就是语义越相似

  • 欧氏距离(Euclidean Distance) :计算向量之间的直线距离,距离越小表示越相似,公式为
    ∑ i ( v i − w i ) 2 ∑ i ( v i − w i ) 2 ∑ i ( v i − w i ) 2 ∑i(vi−wi)2\sqrt{\sum_i (v_i-w_i)^2}∑i(vi−wi)2 ∑i(vi−wi)2i∑(vi−wi)2 ∑i(vi−wi)2

实践中,余弦相似度更常用于文本向量,因为它只关注方向不受长度影响,而欧氏距离适用于数值向量差异度量。

4.3.4 常见Embedding 模型

以下是几种常用的文本 Embedding 模型:

对比 OpenAIEmbeddings HuggingFaceEmbeddings
模型位置 在线(OpenAI API) 本地 / HuggingFace Hub
语言支持 英文为主,多语言支持较弱 有强大的中文模型(如 BGE)
优点 高质量、开箱即用 免费、支持离线部署
缺点 需付费、需联网 本地模型需下载,推理速度取决于硬件
  • OpenAI:OpenAIEmbeddings,精度高,适用于英语及多语言
  • HuggingFace提供的模型,模型开源并且选择灵活 https://huggingface.co/models

在 LangChain 中,我们可以直接使用这些模型,只需要配置一下接口和参数就行了。

shell 复制代码
pip install -U langchain-huggingface

推荐模型:BAAI/bge-small-zh-v1.5BAAI/bge-m3shibing624/text2vec-base-chinese

python 复制代码
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyMuPDFLoader
from langchain_huggingface import HuggingFaceEmbeddings

# 1. 加载 PDF 文档
loader = PyMuPDFLoader("example.pdf")
docs = loader.load()

# 2. 使用 RecursiveCharacterTextSplitter 进行分块
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=300,  # 每个块的最大字符数
    chunk_overlap=100  # 相邻块之间有 100 字符重叠
)

chunks = text_splitter.split_documents(docs)

# 使用OpenAI提供的嵌入模型 需要设置OPENAI_API_KEY
# embedding = OpenAIEmbeddings(
#     model="text-embedding-ada-002",
# )

# 3. 使用HuggingFace提供的开源嵌入模型
embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-small-zh-v1.5",
    model_kwargs={"device": "cpu"},  # 若有 GPU,改为 "cuda"
    encode_kwargs={"normalize_embeddings": True}  # 推荐开启归一化以便余弦相似度计算
)

4.4 向量数据库(Vector Stores)

当用大模型(比如 OpenAI 的模型)对一段文本、图片或音频进行"向量化(embedding)"时,会得到一个包含几百维或几千维的向量。这个向量可以代表原始内容的"语义"。

比如:

python 复制代码
[0.12, -0.33, 0.91, ...]  # 表示"我想吃披萨"这个句子的语义向量

然后你就可以把它存入向量数据库中,之后你可以通过另一个向量(比如"附近餐馆推荐")去查找语义上最相近的内容

为什么需要向量数据库?

为什么不能每次都算一遍相似度,而非要建数据库?

  • 速度:每次都要算几十万个向量,非常慢;
  • 规模:一旦数据量大了,比如几万个文档,不建索引查找太低效。

向量数据库就像提前做好的索引,查找语义相似的内容只要几毫秒,极大提升了性能。

4.4.1 VS普通数据库

和普通数据库的区别是什么?

特性 传统数据库 向量数据库
查询方式 精准匹配(SQL) 相似度匹配(Top-K)
存储内容 文本、数字、结构化数据 高维向量(语义)
适用场景 电商、业务数据管理 AI语义搜索、推荐系统
4.4.2 LangChain 支持哪些向量数据库?

LangChain 里支持很多种向量数据库,下面是常用的几种:

名称 特点说明
FAISS Facebook 开源,轻量级,本地跑,速度快
Chroma 社区流行,易上手,默认数据库就是 Chroma
Pinecone 商业向量数据库,性能强,支持大规模云服务
Weaviate 支持结构化+向量混合搜索,非常强大
Milvus 国人开发的工业级向量数据库,适合企业部署

安装依赖:

shell 复制代码
pip install chromadb

使用示例:

python 复制代码
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyMuPDFLoader
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma

# 1. 加载 PDF 文档
loader = PyMuPDFLoader("example.pdf")
docs = loader.load()

# 2. 使用 RecursiveCharacterTextSplitter 进行分块
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=300,  # 每个块的最大字符数
    chunk_overlap=100  # 相邻块之间有 100 字符重叠
)

chunks = text_splitter.split_documents(docs)

# 使用OpenAI提供的嵌入模型 需要设置OPENAI_API_KEY
# embedding = OpenAIEmbeddings(
#     model="text-embedding-ada-002",
# )

# 3. 使用HuggingFace提供的开源嵌入模型
embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-small-zh-v1.5",
    model_kwargs={"device": "cpu"},  # 若有 GPU,改为 "cuda"
    encode_kwargs={"normalize_embeddings": True}  # 推荐开启归一化以便余弦相似度计算
)

# 4. 相似度检索
vectorstore = Chroma.from_documents(chunks, embeddings, persist_directory="chroma_store")
query = "怎么判断 Redis 某个节点是否正常工作?"
results = vectorstore.similarity_search(query, k=3)
for doc in results:
    print(doc.page_content)
    print("------------------------------------------------")

4.5 RAG系统完整代码实现

python 复制代码
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyMuPDFLoader
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from llm_factory import get_llm

# 1. 加载 PDF 文档
loader = PyMuPDFLoader("example.pdf")
docs = loader.load()

# 2. 文本分块
text_splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=100)
chunks = text_splitter.split_documents(docs)

# 3. 嵌入模型
embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-small-zh-v1.5",
    model_kwargs={"device": "cpu"},  # 若有 GPU,改为 "cuda"
    encode_kwargs={"normalize_embeddings": True}  # 推荐开启归一化以便余弦相似度计算
)

# 4. 构建向量数据库
vectorstore = Chroma.from_documents(chunks, embeddings, persist_directory="chroma_store")
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

# 5. Prompt 模板
template = """
你是一个专业的中文技术问答助手,善于结合知识库内容回答用户问题。

# 用户问题
{question}

# 相关知识库内容
{context}

请基于上面的知识库内容准确、简洁地用中文回答用户的问题。
- 如果内容涉及操作,请分点说明;
- 如果找不到相关信息,请直接回复:"未找到知识库内容";
- 不要编造知识,不要凭空猜测。
"""
prompt = ChatPromptTemplate.from_template(template)

# 6. LLM
llm = get_llm()

# 7.拼接文档
def format_docs(docs):	# 定义一个函数,参数 docs 是一个 Document 对象列表
    return "\n".join([doc.page_content for doc in docs])
	# 对每个文档对象提取其 page_content(即正文内容)
    # 用换行符 \n 拼接成一个长字符串作为输出

# 8. RAG Chain
rag_chain = (
    {
        "question": RunnablePassthrough(),	# 把用户输入的原始问题原样传递到后续链条中
        "context": retriever | RunnableLambda(format_docs)	# 根据用户问题检索出相关内容,并拼接成 {context} 填入 Prompt
    }
    | prompt
    | llm
    | StrOutputParser()
)

# 9. 运行
res = rag_chain.invoke("怎么判断 Redis 某个节点是否正常工作?")
print(res)

format_docs函数说明:

  1. 检索器(如 Chroma)返回多个文档段落;
  2. 它们需要拼接成一个整体上下文传给 LLM;
  3. format_docs 就是这个"拼接器"。

第五阶段:LangChain 进阶模块讲解

5.1 Message History 模块

在 LangChain 中,**消息历史(Message History)**是用于存储和管理对话历史的组件,特别适合在多轮对话中让模型"记住"之前发生的交流内容。

为什么需要 Memory?当你使用 LLM 构建聊天助手、客服机器人等多轮对话场景时,模型需要知道上下文(用户和 AI 之前说了什么),这时候就需要 Message History 来:

  • 自动记录每轮对话
  • 把历史消息格式化注入到 Prompt 中
  • 保持会话一致性

应用场景举例:

场景 没有 Message History 使用 Message History
聊天机器人 每次都从零开始 可以接续上次对话
客服助手 无法记住用户问题 可以记住上下文语境
多轮问答 不理解"它是谁" 正确理解指代语义
5.1.1 保存历史记忆

使用 RunnableWithMessageHistory 实现"记住对话上下文"的能力:

python 复制代码
from llm_factory import get_llm
from langchain.prompts import ChatPromptTemplate,MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

# 初始化 LLM
llm = get_llm()

# 定义 Prompt 模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一位有记忆的 AI 助手。"),
    MessagesPlaceholder(variable_name="chat_history"),
    ("human", "{question}")
])

# 组合 Chain
chain = prompt | llm | StrOutputParser()

# 定义会话存储容器
SESSION_STORE = {}

# 返回每个 session_id 的历史记录实例
def get_session_history(session_id):
    if session_id not in SESSION_STORE:
        print(f"[新会话创建] session_id = {session_id}")
        SESSION_STORE[session_id] = ChatMessageHistory()
    return SESSION_STORE[session_id]

# 封装成带历史记忆的 chain
chain_memory = RunnableWithMessageHistory(
    chain,
    get_session_history=get_session_history,
    input_messages_key="question",
    history_messages_key="chat_history"
)

# 使用模拟 session_id 调用
session_id = "user-session-123"

response_1 = chain_memory.invoke(
    {"question": "我叫小明"},
    config={"configurable": {"session_id": session_id}}
)
print("AI:", response_1)

response_2 = chain_memory.invoke(
    {"question": "你记得我是谁吗?"},
    config={"configurable": {"session_id": session_id}}
)
print("AI:", response_2)

response_3 = chain_memory.invoke(
    {"question": "我之前跟你说了什么?"},
    config={"configurable": {"session_id": session_id}}
)
print("AI:", response_3)

代码解释:

  • MessagesPlaceholder:占位符,用于插入历史对话;
  • SESSION_STORE:简单字典,用于保存每个用户的对话历史(仅在内存中,程序重启会丢失);
  • get_session_history函数:使用 RunnableWithMessageHistory 时由 LangChain 框架自动调用,用于为每个 session_id 获取或创建一段会话历史记录;
  • chain_memory.invoke :LangChain 会自动根据 config["configurable"]["session_id"] 中传入的 session_id:
    1. 调用 get_session_history(session_id);
    2. 获取该 session_id 对应的历史对话(如不存在,则创建);
    3. 注入到 prompt 中 MessagesPlaceholder(variable_name="chat_history") 这个位置;
    4. 将新的消息记录(本轮问题和回复)自动追加到历史中。
5.1.2 持久化历史记忆

由于上述SESSION_STORE中的历史记忆保存在内存中,当程序重启之后会丢失,所以我们需要一种数据持久化保存的方式(比如我们可以将数据保存在数据库中)

这里我们只需要将 get_session_history 返回的对象替换成支持持久化的消息历史类,比如:使用 SQLite 持久化(推荐方式):

python 复制代码
from llm_factory import get_llm
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import SQLChatMessageHistory

# 初始化 LLM
llm = get_llm()

# Prompt 模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一位有记忆的 AI 助手。"),
    MessagesPlaceholder(variable_name="chat_history"),
    ("human", "{question}")
])

# 构建 Chain
chain = prompt | llm | StrOutputParser()

# 会话历史记录工厂函数
def get_session_history(session_id):
    return SQLChatMessageHistory(session_id=session_id, connection="sqlite:///memory.db")

# 包装成带记忆的 Chain
chain_memory = RunnableWithMessageHistory(
    chain,
    get_session_history=get_session_history,
    input_messages_key="question",
    history_messages_key="chat_history"
)

# 测试调用
session_id = "user-session-123"

response_1 = chain_memory.invoke(
    {"question": "我叫小明"},
    config={"configurable": {"session_id": session_id}}
)
print("AI:", response_1)

response_2 = chain_memory.invoke(
    {"question": "你记得我是谁吗?"},
    config={"configurable": {"session_id": session_id}}
)
print("AI:", response_2)

response_3 = chain_memory.invoke(
    {"question": "我之前跟你说了什么?"},
    config={"configurable": {"session_id": session_id}}
)
print("AI:", response_3)

5.2 Agent & Tool 模块详解

5.2.2 Agent 核心概念

在 LangChain 中,Agent 是一个非常核心的概念,它允许语言模型(LLM)在不知道最终任务结构的情况下动态决策使用哪些工具(tools)、以什么顺序调用它们,并最终完成复杂的任务。

LangChain Agent = LLM + 工具调用能力 + 推理决策能力

传统的链(Chain)是固定的调用流程:

rust 复制代码
输入 -> 处理 -> 输出(结构固定)

而 Agent 更像一个思考者,可以根据任务灵活选择工具:

rust 复制代码
用户提问 -> LLM 分析 -> 选择工具1 -> 读取数据 -> 工具2 处理 -> 汇总 -> 输出

Agent工作流程示例:

复制代码
Step 1: 用户提问:
"请帮我查找今天北京的天气并转为华氏温度。"

Step 2: LLM 分析任务,计划如下:
- 使用搜索工具获取天气(Tool1)
- 使用计算器转换摄氏到华氏(Tool2)

Step 3: Agent 调用 Tool1,得到 20℃
Step 4: Agent 调用 Tool2,得到 68°F
Step 5: 输出结果:"今天北京的天气是 20℃,换算为华氏温度是 68°F。"
5.2.3 Tools 工具详解

Tool 是 Agent 可以使用的功能,简单理解为一个封装了某个行为的函数。

比如你可以定义一个天气查询的 Tool:

python 复制代码
from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """返回指定城市的天气"""
    return f"{city} 今天晴,20℃"

使用Agent:

python 复制代码
from llm_factory import get_llm
from langchain.agents import initialize_agent, AgentType

# 初始化模型
llm = get_llm()

# 初始化 Agent
agent = initialize_agent(
    tools=[get_weather],
    llm=llm,
    agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION,	# ZERO_SHOT:表示模型在没有示例的情况下也能推理,		REACT_DESCRIPTION:是一种提示设计技巧,让模型先思考再行动
    verbose=True										# 开启详细的日志记录
)

# 执行
response = agent.invoke("北京的天气怎么样?")
print("最终回答:", response)

输出:

复制代码
> Entering new AgentExecutor chain...
Thought: I need to find out the current weather in Beijing.
Action:
​```
{
  "action": "get_weather",
  "action_input": "Beijing"
}
​```
Observation: Beijing 今天晴,20℃
Thought:I now know the final answer
Final Answer: 北京今天晴,气温20℃。

> Finished chain.

运行后模型会:

  1. 分析你的问题
  2. 判断需要调用 get_weather 工具
  3. 构造调用语句 get_weather(city="北京")
  4. 拿到结果后继续处理
  5. 返回最终答案

大模型如何知道调用工具?

  • 因为你注册了 @tool 装饰器,它会把这个函数的说明文档(docstring)、参数名、参数类型都提供给大模型,大模型会"判断"哪个工具符合你的问题,并自动发起调用。
5.2.4 调用LangChain内置工具

LangChain 内置了一些工具(例如搜索、运行shell命令、运行python代码、web浏览器访问、数据库操作等),我们可以直接拿来使用

https://docs.langchain.com/oss/python/integrations/tools

python 复制代码
import os
from langchain.agents import initialize_agent, AgentType
from llm_factory import get_llm
from langchain_community.tools.tavily_search import TavilySearchResults

# 初始化模型
llm = get_llm()

# 设置Tavily API Key
os.environ["TAVILY_API_KEY"] = "tvly-dev-..."

# 内置工具
tools = [
    TavilySearchResults(max_results=1)  # 搜索工具,可查天气
]

# 初始化 Agent
agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

# 执行
response = agent.invoke("北京的天气怎么样?")
print("最终结果:", response)

第六阶段:项目实战1-AutoGen自动化脚本生成器

6.1 项目目标

基于 LangChain + 大模型 来开发"接口自动化测试脚本自动生成"功能,可以大大提高接口测试效率

  • 支持swagger接口文档上传解析
  • 自动生成接口测试脚本( Pytest 格式)

6.2 项目目录结构

复制代码
swagger_testgen/
├── app.py                 # Streamlit 前端
├── llm_factory.py         # 模型封装 & 加载配置
├── parser.py              # 解析 Swagger 文件
├── generator.py           # 使用 LLM 生成测试代码
├── requirements.txt       # 所需依赖
└── .env				   # APIkey & 模型配置

6.3 模块功能详解

6.3.1 解析Swagger文档数据:

parser.py

python 复制代码
import json
import yaml

def parse_swagger(swagger_content: str):
    try:
        data = yaml.safe_load(swagger_content)
    except yaml.YAMLError:
        data = json.loads(swagger_content)

    paths = data.get("paths", {})
    parsed_endpoints = []
    for path, methods in paths.items():
        for method, details in methods.items():
            endpoint_info = {
                "path": path,
                "method": method.upper(),
                "summary": details.get("summary", ""),
                "parameters": details.get("parameters", []),
                "requestBody": details.get("requestBody", {}),
            }
            parsed_endpoints.append(endpoint_info)
    return parsed_endpoints
6.3.2 用LLM生成Pytest脚本:

generator.py

python 复制代码
from langchain_core.runnables import RunnableMap
from llm_factory import get_llm
from langchain.prompts import ChatPromptTemplate

llm = get_llm()

def generate_test_code(endpoint_info: dict):
    prompt = ChatPromptTemplate.from_template("""
你是一个资深接口测试工程师。请根据以下 API 接口信息,使用 Pytest 格式生成一个自动化测试脚本:

路径: {path}
方法: {method}
功能描述: {summary}
参数: {parameters}
请求体: {request_body}

要求:
- 使用 requests 发送请求
- 包含基本断言
- 使用函数方式组织测试

请直接输出完整的 Python 测试代码。
""")

    # 构建 chain 流程
    chain = (
        RunnableMap({
            "path": lambda x: x["path"],
            "method": lambda x: x["method"],
            "summary": lambda x: x["summary"],
            "parameters": lambda x: x["parameters"],
            "request_body": lambda x: x.get("requestBody", {})
        })
        | prompt
        | llm
    )

    response = chain.invoke(endpoint_info)
    return response.content
6.3.3 Streamlit前端页面:

实现功能:

  • 上传swagger文档
  • 自动解析接口列表
  • 用户从下拉框中选择接口
  • 点击按钮,一键生成pytest代码
  • 代码展示在网页上

app.py

python 复制代码
import streamlit as st
from parser import parse_swagger
from generator import generate_test_code

st.set_page_config(page_title="AutoTestGen", layout="wide")
st.title("🧪 AutoTestGen接口测试自动化生成平台")

uploaded_file = st.file_uploader("上传 Swagger JSON 或 YAML 文件", type=["json", "yaml", "yml"])
if uploaded_file:
    content = uploaded_file.read().decode("utf-8")
    st.success("✅ Swagger 文件上传成功,正在解析...")
    try:
        endpoints = parse_swagger(content)
        if not endpoints:
            st.error("❌ 未能解析任何接口,请检查 Swagger 文件内容。")
        else:
            st.success(f"🎉 成功解析 {len(endpoints)} 个接口,请选择一个进行测试代码生成。")  # ✅ 显示接口数量

            selected = st.selectbox("选择一个接口", options=[f"{e['method']} {e['path']}" for e in endpoints])
            if selected:
                endpoint = endpoints[[f"{e['method']} {e['path']}" for e in endpoints].index(selected)]

                if st.button("🎯 生成 Pytest 测试代码"):
                    with st.spinner("正在生成测试脚本..."):
                        code = generate_test_code(endpoint)
                        st.code(code, language="python")
    except Exception as e:
        st.error(f"⚠️ 解析失败:{e}")

6.4 运行步骤

启动 Web 应用:

python 复制代码
streamlit run app.py

第七阶段:项目实战2-企业知识库问答系统

7.1 项目目标

结合 LangChain 构建一个可以接入测试平台内部文档(如测试手册、缺陷库、接口文档、技术文档)的知识库问答系统,让测试人员可以通过自然语言查询获取精确答案,支持 ChatGPT 风格对话、精准定位内容来源。

7.2 项目目录结构

复制代码
rag_project/
├── app.py                     # Streamlit 应用入口
├── llm_factory.py             # 模型封装 & 加载配置
├── load_docs.py           	   # 文档读取 + 分割 + 向量构建
├── query_engine.py			   # 向量检索 + LLM 问答链
├── docs/                      # 企业内部文档文件夹
├── vectorstore/               # Chroma 本地向量存储
├── requirements.txt		   # 项目依赖配置
└── .env					   # APIkey & 模型配置

7.3 模块功能详解

7.3.1 文档加载模块:

load_docs.py

支持 PDF / Markdown / Word / TXT 等多格式:

python 复制代码
# load_docs.py
import os
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from llm_factory import DOCS_PATH, VECTORSTORE_PATH, EMBEDDING_MODEL
from langchain_community.document_loaders import PyPDFLoader, UnstructuredMarkdownLoader, UnstructuredWordDocumentLoader

SUPPORTED_LOADERS = {
    ".pdf": PyPDFLoader,
    ".md": UnstructuredMarkdownLoader,
    ".docx": UnstructuredWordDocumentLoader,
}

def add_new_doc_to_vectorstore(doc_path):
    ext = os.path.splitext(doc_path)[1].lower()
    loader_cls = SUPPORTED_LOADERS.get(ext)
    if not loader_cls:
        raise ValueError("不支持的文档类型")
    loader = loader_cls(doc_path)
    docs = loader.load()
    splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=100)
    split_docs = splitter.split_documents(docs)
    embeddings = HuggingFaceEmbeddings(
        model_name=EMBEDDING_MODEL,
        model_kwargs={"device": "cpu"},  # 若有 GPU,改为 "cuda"
        encode_kwargs={"normalize_embeddings": True}  # 推荐开启归一化以便余弦相似度计算
    )
    # 加载已有向量库
    if os.path.exists(VECTORSTORE_PATH):
        vectorstore = Chroma(persist_directory=VECTORSTORE_PATH,embedding_function=embeddings)
        vectorstore.add_documents(split_docs)
    else:
        vectorstore = Chroma.from_documents(documents=split_docs,embedding=embeddings,persist_directory=VECTORSTORE_PATH)
    # Chroma 添加文档后,需要手动 .persist() 才会写入磁盘
    vectorstore.persist()
7.3.2 向量检索 + LLM 问答链:

query_engine.py

python 复制代码
# query_engine.py
from langchain_community.vectorstores import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
from langchain.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableMap, RunnablePassthrough, RunnableLambda
from langchain_core.output_parsers import StrOutputParser
from llm_factory import VECTORSTORE_PATH, EMBEDDING_MODEL, get_llm

def load_vectorstore():
    embeddings = HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL, model_kwargs={"device": "cpu"})
    # 加载 Chroma 向量库
    return Chroma(
        persist_directory=VECTORSTORE_PATH,
        embedding_function=embeddings
    )

# 在模块级别加载向量库,只需加载一次
vectorstore = load_vectorstore()

def get_sources_from_docs(docs):
    sources = []
    for doc in docs:
        meta = doc.metadata
        src = f"{meta.get('source', '未知文档')}"
        if "page" in meta:
            src += f" (页码: {meta['page']})"
        sources.append(src)
    return list(set(sources))

def query_knowledge_base(query, k=3, stream=False):
    # 使用模块级别的vectorstore实例,避免重复加载
    retriever = vectorstore.as_retriever(search_kwargs={"k": k})

    template = """
    你是一个专业的中文技术问答助手,善于结合知识库内容回答用户问题。

    # 用户问题
    {question}

    # 相关知识库内容
    {context}

    请基于上面的知识库内容准确、简洁地用中文回答用户的问题。
    - 如果内容涉及操作,请分点说明;
    - 如果找不到相关信息,请直接回复:"未找到知识库内容";
    - 不要编造知识,不要凭空猜测。
    """
    prompt = ChatPromptTemplate.from_template(template)

    llm = get_llm()

    # 拼接文档
    def format_docs(docs):  # 定义一个函数,参数 docs 是一个 Document 对象列表
        return "\n".join([doc.page_content for doc in docs])

    # RAG Chain
    rag_chain = (
            {
                "question": RunnablePassthrough(),  # 把用户输入的原始问题原样传递到后续链条中
                "context": retriever | RunnableLambda(format_docs)  # 根据用户问题检索出相关内容,并拼接成 {context} 填入 Prompt
            }
            | prompt
            | llm
            | StrOutputParser()
    )
    
    # 获取检索结果以提取来源
    docs = retriever.invoke(query)
    sources = get_sources_from_docs(docs)
    
    # 根据流式需求返回结果
    if stream:
        return rag_chain.stream(query), sources
    else:
        answer = rag_chain.invoke(query)
        return answer, sources
7.3.3 Streamlit 界面:

app.py

python 复制代码
# app.py
import streamlit as st
from query_engine import query_knowledge_base
import os
from load_docs import add_new_doc_to_vectorstore


st.set_page_config(page_title="测试平台知识库问答助手", page_icon="📚")
st.title("‍💻 Lemon测试专属知识库系统")

if "history" not in st.session_state:
    st.session_state["history"] = []

if "uploaded_files" not in st.session_state:
    st.session_state["uploaded_files"] = set()

st.sidebar.header("📂 上传文档更新知识库")
uploaded_files = st.sidebar.file_uploader("上传新文档", type=["pdf", "md", "docx"], accept_multiple_files=True)
if uploaded_files:
    os.makedirs("docs", exist_ok=True)
    for uploaded_file in uploaded_files:
        if uploaded_file.name not in st.session_state["uploaded_files"]:
            save_path = os.path.join("docs", uploaded_file.name)
            with open(save_path, "wb") as f:
                f.write(uploaded_file.getbuffer())
            st.sidebar.success(f"已上传:{uploaded_file.name}")
            with st.spinner(f"正在加载并向量化 {uploaded_file.name},请稍候..."):
                try:
                    add_new_doc_to_vectorstore(save_path)
                    st.sidebar.success(f"{uploaded_file.name} 已增量入库!")
                    st.session_state["uploaded_files"].add(uploaded_file.name)
                except Exception as e:
                    st.sidebar.error(f"{uploaded_file.name} 处理失败: {e}")

# 聊天历史展示
for chat in st.session_state["history"]:
    with st.chat_message("user"):
        st.markdown(chat["query"])
    with st.chat_message("assistant"):
        st.markdown(chat["answer"])
        st.markdown("**内容来源:**")
        for src in chat["sources"]:
            st.markdown(f"- {src}")

# 底部输入框
query = st.chat_input("请输入你的问题...")


if query:
    st.chat_message("user").markdown(query)
    answer_placeholder = st.empty()
    sources_placeholder = st.empty()
    answer = ""
    
    answer_stream, sources = query_knowledge_base(query, stream=True)
    for chunk in answer_stream:
        answer += chunk
        answer_placeholder.markdown(answer)
    
    sources_placeholder.markdown("**内容来源:**")
    for src in sources:
        sources_placeholder.markdown(f"- {src}")
    
    # 仍然可以保存到历史记录,但仅用于展示
    st.session_state["history"].append({"query": query, "answer": answer, "sources": sources})
    st.rerun()

7.4 运行步骤

启动 Web 应用:

python 复制代码
streamlit run app.py
相关推荐
玄同7654 小时前
LangChain v1.0 中间件深度解析:从 Callback 到 Middleware 的演进
人工智能·语言模型·自然语言处理·中间件·langchain·agent·智能体
沐雪架构师5 小时前
LangChain 1.0 记忆管理:短期与长期记忆详解
服务器·数据库·langchain
TGITCIC8 小时前
LangChain入门(十五)- LangGraph为什么这么香,看它是如何逆天DIFY的
langchain·工作流·rag·ai agent·ai智能体·langgraph·agentic
玄同76520 小时前
告别 AgentExecutor:LangChain v1.0+ Agent 模块深度迁移指南与实战全解析
人工智能·语言模型·自然语言处理·langchain·nlp·agent·智能体
TGITCIC21 小时前
LangChain入门(十四)- Agentic RAG 的正确打开方式:用 LangChain 实现“有思考、可解释、不遗漏”的检索增强问答
langchain·rag·ai agent·agentic·智能体开发·rag增强检索·agentic flow
TGITCIC1 天前
LangChain入门(十三)- 6步实操Agent落地大法
langchain·agent·rag·ai agent·ai开发·agent开发·ai智能体开发
一只大侠的侠1 天前
零基础入门:使用LangChain + GPT-4
langchain
董厂长1 天前
langchain上下文管理的方式
langchain·上下文压缩·上下文管理
程序员三藏2 天前
selenium测试框架快速搭建
自动化测试·软件测试·python·selenium·测试工具·职场和发展·测试用例