代码仓库链接: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 包含以下组件:
- PromptTemplate(提示模板):定义输入数据如何转换为模型的提示。
- LLM(大型语言模型):如 OpenAI 的 GPT-4,用于生成响应。
- OutputParser(输出解析器):将模型的输出解析为所需的格式。
这些组件通过 LangChain Expression Language(LCEL)进行组合,形成一个完整的处理链。
3.2 LCEL Chain
LangChain Expression Language(LCEL)是一种声明式的语言,用于简化和优化 LangChain 中链的构建与组合。它允许开发者以模块化的方式,将提示模板、语言模型、输出解析器等组件通过管道符(|)连接,形成清晰、可维护的链式结构:
shell
prompt | llm | parser
LCEL 的核心优势:
- 声明式链式组合 :使用
|操作符,将不同组件连接成链,提升代码的可读性和可维护性。 - 流式处理支持:LCEL 支持将语言模型的输出以流的形式传递给下游组件,实现低延迟的响应。
- 异步与并行执行:支持异步 API 调用和自动并行处理,提高系统的吞吐量和响应速度。
- 中间结果访问:能够访问链中各个步骤的中间结果,便于调试和监控。
- 输入输出模式验证:自动推断链的输入输出模式,生成 Pydantic 和 JSON Schema,用于数据验证和文档生成。
- 与 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核心思路:"在生成答案之前,先去找资料。"
比如:想象你是个学生,老师问你:"地球为什么有四季?"
你可能不会立刻回答,而是会:
- 去图书馆查书 → 这就是 "检索"(Retrieval)
- 把找到的书摘出相关部分 → 这叫 "增强上下文"(Augmented)
- 根据整理的资料写成一段话交给老师 → 这就是 "生成回答"(Generation)
4.1.1 为什么需要RAG
-
大模型知识是固定的,不能实时更新
LLM(如 GPT-4)只能回答训练数据中出现过的内容,知识截止时间早(例如 GPT-4 截止于 2023年末)
- 引入"外部知识源"(如你自己的文档、网页、数据库),模型就能回答实时、专业、私有领域的问题。
- 无需重新训练或微调大模型。
-
大模型可能"胡编乱造"
语言模型容易"幻觉"(hallucination),生成看似正确但实际错误的内容。
- 通过"检索真实文档"作为上下文增强,显著降低幻觉率;
- 生成内容可追溯、有出处,提高可信度。
-
微调成本高,更新不灵活
微调(fine-tuning)需要 GPU、数据清洗、训练技巧,而且一旦模型更新,微调就失效。
- 只需更新文档或向量库,不动大模型;
- 比微调更灵活、快捷、成本更低。
-
支持私有知识问答、企业场景定制
企业/团队通常有自己的私有知识(如内部SOP、产品文档、客户问答),这些不会出现在开源大模型中。
- 构建属于自己的知识问答系统(知识库QA);
- 可以对接公司内网文档、数据库、工单系统等。
4.1.2 RAG实现基本流程
索引(Indexing)------ 构建知识库
目的是将原始文档转换为向量索引,供后续检索使用。
过程详解:
- 文档加载:读取各种类型的原始内容,如 PDF、TXT、Word、网页等。
- 文本切分:将长文本按段拆分成小块(例如每 500 个字),以便后续处理。
- 向量化(Embedding):使用嵌入模型(如 OpenAI Embedding、BGE)将每个文本块转换为数字向量,表示其语义特征。
- 构建索引:将所有向量存入向量数据库(如 FAISS、Chroma、Weaviate),方便后续高效相似度检索。
检索(Retrieve)
当用户提出一个问题时,先从向量数据库中检索相关内容。
过程详解:
- 将用户问题转换为向量;
- 在向量库中检索"最相似的文本块"(基于余弦相似度、欧式距离等);
- 返回 Top-K 个相关段落作为"知识上下文"。
增强(Augment)
将检索到的内容"增强"到 Prompt 中,提供给语言模型作为参考材料。
过程详解:
- 将用户问题转换为向量(同样使用 Embedding 模型);
- 在向量数据库中查找与问题向量最接近的文本片段;
- 取出 Top-K 个相似文本块,作为上下文参考材料。
生成(Generate)
语言模型根据增强后的 Prompt,生成结构清晰、内容准确的回答。
过程详解:
- Prompt 输入给大语言模型(如 GPT、Claude、LLaMA);
- 模型结合上下文知识进行推理、组织语言并输出答案;
高级系统可能还会:
- 对多个回答候选进行评分、重排序(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 模型就是一个翻译器,它能把中文"翻译成"一串数字,让计算机能理解这句话的意思。
假设我们有两个句子:
- "我喜欢苹果"
- "我爱吃苹果"
我们人一眼就知道这两句话是一个意思。但计算机不知道。这时候,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.5、BAAI/bge-m3、shibing624/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函数说明:
- 检索器(如 Chroma)返回多个文档段落;
- 它们需要拼接成一个整体上下文传给 LLM;
- 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:
- 调用 get_session_history(session_id);
- 获取该 session_id 对应的历史对话(如不存在,则创建);
- 注入到 prompt 中 MessagesPlaceholder(variable_name="chat_history") 这个位置;
- 将新的消息记录(本轮问题和回复)自动追加到历史中。
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.
运行后模型会:
- 分析你的问题
- 判断需要调用 get_weather 工具
- 构造调用语句 get_weather(city="北京")
- 拿到结果后继续处理
- 返回最终答案
大模型如何知道调用工具?
- 因为你注册了 @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