- 1.什么是llama-factory?
- 2.怎么下载模型文件?
- 3.怎么准备训练数据集?
- 4.怎么使用llama-factory进行LoRA和QLoRA微调?
- 5.怎么使用训练后的模型进行推理?
- 6.模型评测中常见的报错处理手段?
- 7.怎么合并LoRA模型并导出?
- 8.怎么量化模型并导出?
- 9.怎么把safetensors格式转化为GGUF格式?
- 10.怎么安装Ollama框架?
- 11.怎么启动ollama框架?
- 12.常见的Ollama命令有哪些?
- 13.怎么大模型的UI项目?
- 14.怎么调用Llama-factory的api服务?
- 15.怎么调用ollama本身的api服务?
- 16.怎么让ollama生成兼容openai的api?
- 1.怎么部署RAGFlow项目?
- 2.怎么用RAGFlow建立知识库与聊天?
- 3.怎么用langchain构建简单RAG?
- 4.怎么通过预处理数据库提高RAG的精度?
- 5.怎么通过知识图谱提高RAG的精度?
- 6.怎么使用总分层级索引提高RAG的精度?
- 7.怎么使用父子层级索引提高RAG的精度?
- 8.怎么使用多种切分方式和并行查询?
- 9.怎么使用子查询(预检索)优化RAG?
- 10.怎么使用假设答案(预检索)优化RAG?
- 11.还有哪些其他预检索技术优化RAG?
- 12.怎么让检索过程更加准确?
- 13.怎么使用Remark技术(后检索)优化RAG?
- 14.什么是Modular RAG及其框架?
- 15.经典的RAG Flow范式(Tuning阶段)?
- 16.经典的RAG Flow范式(推理阶段-顺序模式)?
- 17.经典的RAG Flow范式(推理阶段-条件模式)?
- 18.如何通过llamaindex实现条件模式?
- 19.经典的RAG Flow范式(推理阶段-分支模式)?
(1) 基础定义
LLaMA Factory 是一个专为大型语言模型(LLM)设计的高效、易用的训练与微调平台。其核心目标是通过简化的流程,让用户无需编写代码即可在本地完成多种模型的微调与训练任务,支持丰富的模型类型、训练方法和优化技术。
(2)核心功能
-
模型种类:LLaMA、LLaVA、Mistral、Mixtral-MoE、Qwen、Yi、Gemma、Baichuan、ChatGLM、Phi 等等。
-
训练算法:(增量)预训练、(多模态)指令监督微调、奖励模型训练、PPO 训练、DPO 训练、KTO 训练、ORPO 训练等等。
-
运算精度:16 比特全参数微调、冻结微调、LoRA 微调和基于 AQLM/AWQ/GPTQ/LLM.int8/HQQ/EETQ 的 2/3/4/5/6/8 比特 QLoRA 微调。
-
优化算法:GaLore、BAdam、DoRA、LongLoRA、LLaMA Pro、Mixture-of-Depths、LoRA+、LoftQ 和 PiSSA。
-
加速算子:FlashAttention-2 和 Unsloth。
-
推理引擎:Transformers 和 vLLM。
-
实验监控:LlamaBoard、TensorBoard、Wandb、MLflow、SwanLab 等等。
(3)LLaMA Factory安装
- 硬件环境校验:
安装显卡驱动和CUDA,并使用nvidia-smi命令检验。
- 软件环境准备:
# 创建名为 llama_factory 的 Python 3.10 虚拟环境
conda create -n llama_factory python=3.10
# 激活虚拟环境
conda activate llama_factory
# 安装 PyTorch 2.3.1 + CUDA 12.1 版本(确保显卡驱动支持 CUDA 12.1)
conda install pytorch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 pytorch cuda=12.1 -c pytorch -c nvidia拉取LLaMA Factory代码并安装。
git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git
cd LLaMA-Factorypip install -e ".[torch,metrics]"安装模型量化的所需资源。
# QLoRA
pip install https://github.com/jllllll/bitsandbytes-windows-webui/releases/download/wheels/bitsandbytes-0.41.2.post2-py3-none-win_amd64.whl
# awq量化
pip install autoawq启动LLaMA Factory:
# 命令行目录查看
llamafactory-cli train -h
# Web唤醒 or CUDA_VISIBLE_DEVICES=0 llamafactory-cli web
llamafactory-cli webui通过Hugging Face下载或者魔搭社区ModelScope下载。以Meta-Llama3-8B-Instruct为例。
# Hugging Face
git clone https://huggingface.co/meta-llama/Meta-Llama-3-8B-Instruct
# Model Scope
git clone https://www.modelscope.cn/LLM-Research/Meta-Llama-3-8B-Instruct.git(1)Hugging Face更多的下载方式
# Hugging Face下载
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
model_id = "meta-llama/Meta-Llama-3-8B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
model_id,
torch_dtype=torch.bfloat16,
device_map="auto",
)(2)魔搭社区更多的下载方式
# 魔搭社区下载
from modelscope import snapshot_download
local_dir = ""
model_dir = snapshot_download('LLM-Research/Meta-Llama-3-8B-Instruct',local_dir=local_dir)(1)更多的模型推理方式:魔搭社区,Hugging Face
import transformers
import torch
model_id = "LLM-Research/Meta-Llama-3-8B-Instruct"
# 通过模型是否能正常推理验证模型是否下载成功。
pipeline = transformers.pipeline(
"text-generation",
model=model_id,
model_kwargs={"torch_dtype": torch.bfloat16},
device_map="auto",
)
messages = [
{"role": "system", "content": "You are a pirate chatbot who always responds in pirate speak!"},
{"role": "user", "content": "Who are you?"},
]
terminators = [
pipeline.tokenizer.eos_token_id,
pipeline.tokenizer.convert_tokens_to_ids("<|eot_id|>")
]
outputs = pipeline(
messages,
max_new_tokens=256,
eos_token_id=terminators,
do_sample=True,
temperature=0.6,
top_p=0.9,
)
print(outputs[0]["generated_text"][-1])(1)(增量)预训练数据集
# Alpaca格式
[
{"text": "预训练文本"},
{"text": "预训练文本"}
](2)监督微调数据集
- Alpaca格式:在模型微调时,instruction对应的内容会与input对应的内容拼接后作为人类指令,而output对应的内容作为模型回答。如有,system对应的内容为系统提示词,而history分别代表历史消息中每轮对话的指令和回答。
# Alpaca格式
[
{"instruction": "人类指令(必填)",
"input": "人类输入(必填)",
"output": "模型回答(必填)",
"system": "系统提示词(选填)",
"history": [
["第一轮指令(选填)", "第一轮回答(选填)"],
["第二轮指令(选填)", "第二轮回答(选填)"]]
}
]- sharegpt格式:sharegpt格式支持更多的角色种类,比如human,gpt,observation,function等。其中human和observation必须出现在奇数位置,gpt和function必须出现在偶数位置。
[
{
"conversations":[
{
"from": "human",
"value": "人类指令",
},
{
"from": "function_call",
"value": "工具参数",
},
{
"from": "observation",
"value": "工具结果",
},
{
"from": "gpt",
"value": "模型回答",
},
],
"system": "系统提示词(选填)",
"tools": "工具描述(选填)"
}
](3)偏好数据
- Alpaca格式:
[
{
"instruction": "人类指令(必填)",
"input": "人类输入(选填)",
"chosen": "优质回答(必填)",
"rejected": "劣质回答(必填)"
}
]- sharegpt格式
{
"conversations": [
{
"from": "human",
"value": "人类指令"
}
],
"chosen": {
"from": "gpt",
"value": "模型回答!"
},
"rejected": {
"from": "gpt",
"value": "模型回答"
}
}LLaMA Factory中的文件中包含了所有可用的数据集。如果使用自定义数据集,需要在dataset_info.json文件中添加数据集的描述。 dataset_info.json文件位于LLaMA Factory根目录的data文件下,即LLaMA-Factory\data。
(1)(增量)预训练数据集
"数据集名称": {
"file_name": "data.json",
"columns": {
"prompt": "text"
}
}(2)监督微调数据集
- Alpaca格式:
"数据集名称": {
"file_name": "data.json",
"columns": {
"prompt": "instruction",
"query": "input",
"response": "output",
"system": "system",
"history": "history"
}
}- sharegpt格式
"数据集名称": {
"file_name": "data.json",
"formatting": "sharegpt",
"columns": {
"messages": "conversations",
"system": "system",
"tools": "tools"
}
}(3)偏好微调
- Alpaca格式:
"数据集名称": {
"file_name": "data.json",
"ranking": true,
"columns": {
"prompt": "instruction",
"query": "input",
"chosen": "chosen",
"rejected": "rejected"
}
}- sharegpt格式
"数据集名称": {
"file_name": "data.json",
"formatting": "sharegpt",
"ranking": true,
"columns": {
"messages": "conversations",
"chosen": "chosen",
"rejected": "rejected"
}
}一般只需要修改数据集名称和file_name,其他参数为默认,可以写明。更多的训练数据格式和配置文件
LoRA:训练参数大约占完全参数的1%,非破坏性微调,可以任意切换或者组合。
QLoRA:相较于LoRA节省30%的训练内存,但多约30%的训练时长。
llamafactory-cli train \
--stage sft \ # 训练阶段/策略,可选:rm,pt,sft,PPO,DPO,KTO...
--do_train True \ # True用于训练,False用于评测
--model_name_or_path baichuan-inc/<model_name> \ # 模型名称/路径
--preprocessing_num_workers 16 \ # 用于数据预处理的工作线程数
--finetuning_type lora \ # 微调方法,可选:freeze,LoRA,full
--template <model_name> \ # 数据集模板,与模型一一对应
--flash_attn auto \
--dataset_dir llama-factory/data/<dataset_folder> \ # 数据集所在目录
--dataset <dataset_name> \ # 数据集名称,多个数据集用","隔开
--cutoff_len 1024 \ # 截断长度,表示输入序列的最大长度
--learning_rate 5e-05 \ # 学习率
--num_train_epochs 3.0 \ # 训练周期数
--max_samples 100000 \ # 用于训练的最大样本数
--per_device_train_batch_size 2 \ # 每个设备上训练的批次大小
--gradient_accumulation_steps 8 \ # 梯度累计步数
--lr_scheduler_type cosine \ # 学习率调度曲线,可选“linear”,“consince”...
--max_grad_norm 1.0 \
--logging_steps 5 \ # 日志保存步数
--save_steps 100 \
--warmup_steps 0 \
--optim adamw_torch \ # 优化器
--packing False \ # 是否启动数据打包
--report_to none \
--output_dir saves/<model_name>/lora/train \ # 保存路径
--fp16 True \ # True使用fp16
--plot_loss True \ # 是否绘制损失图
--ddp_timeout 180000000 \
--include_num_input_tokens_seen True \
--lora_rank 8 \ # LoRA的秩
--lora_alpha 16 \ # LoRA的alpha值,值越大,新数据对权重的影响越大。
--lora_dropout 0 \ # LoRA的dropout率,防止模型过拟合。
--lora_target all \ # 采用LoRA的目标模块,一般为all
--deepspeed cache/ds_z3_config.json # 使用deepspeed加速训练只有命令行格式
# 如果通过命令行控制,则模型为继续训练,训练集为从头训练(一般适配新数据叠加训练)
--resume_from_checkpoint /workspace/checkpoint/<model_name>/checkpoint-4000 # 不设置,脚本会自动到workspace里面寻找最新的checkpoint
--output_dir new_dir # 不设置,默认原来的output_dir
# 其他参数同训练训练结束后,在output_dir下,
(1)adaptor开头的就是LoRA保存的结果(主要结果)。
(2)training_loss和training_log等记录了训练的过程。
(3)其他是训练的其他备份。
(1)0-shot评测是指在没有任何针对特定任务的训练或者实例的情况下,直接评估模型在任务上的表现。
(2)5-shot评测是指在prompt中为模型提供几个示例,以增强模型的泛化能力。
(1)主流benchmark测评
llama-factory可以通过yaml文件和命令流两种方式进行测评
### model
model_name_or_path: meta-llama/Meta-Llama-3-8B-Instruct
adapter_name_or_path: saves/llama3-8b/lora/sft
trust_remote_code: true
### method
finetuning_type: lora
### dataset
task: mmlu_test # choices: [mmlu_test, ceval_validation, cmmlu_test]
template: fewshot
lang: en
n_shot: 5
### output
save_dir: saves/llama3-8b/lora/eval
### eval
batch_size: 4# YAML命令流
llamafactory-cli evalexamples/train_lora/llama3_lora_eval.yaml- 命令流
# chat模型
llamafactory-cli eval \ # eval表示评测 CUDA_VISIBLE_DEVICES=0
--model_name_or_path /llama3/Meta-Llama-3-8B-Instruct \ # 基础模型路径
--template llama3 \ # 提示词模版
--task mmlu_test \ # 评测任务集
--lang en \ # 语言
--n_shot 5 \ # 0shot,5shot等
--batch_size 1 # 评测是的batch size
# base模型
llamafactory-cli eval \ # eval表示评测 CUDA_VISIBLE_DEVICES=0
--model_name_or_path /llama3/Meta-Llama-3-8B-Instruct \ # 基础模型路径
--template fewshot \ # 提示词模版
--task mmlu_test \ # 评测任务集
--split validation \
--lang en \ # 语言
--n_shot 5 \ # 0shot,5shot等
--batch_size 1 # 评测是的batch size- 其他更多的开源评测项目:opencompass,EleutherAI
(2)垂直数据集测评
- 环境安装
pip install jieba #中文文本分词库
pip install rouge-chinesepip install nltk #自然语言处理工具包(Natural Language Toolkit)- 批量推理
llamafactory-cli train \ # CUDA_VISIBLE_DEVICES=0
--stage sft \ # 监督微调
--do_predict \ # 现在是预测模式
--model_name_or_path /llama3/Meta-Llama-3-8B-Instruct \ # 底模路径
--adapter_name_or_path ./saves/LLaMA3-8B/lora/sft \ # lora路径
--eval_dataset alpaca_gpt4_zh,identity,adgen_local \ # 评测数据集
--dataset_dir ./data \ # 数据集路径
--template llama3 \ # 提示词模版,比如llama3 ,qwen 和训练微调一样
--finetuning_type lora \ # 微调方式 lora
--output_dir ./saves/LLaMA3-8B/lora/predict \ # 评估预测输出文件夹
--overwrite_cache \
--overwrite_output_dir \
--cutoff_len 1024 \ # 提示词截断长度
--preprocessing_num_workers 16 \ # 预处理数据的线程数量
--per_device_eval_batch_size 1 \ # 每个设备评估时的batch size
--max_samples 20 \ # 每个数据集采样多少用于预测对比
--predict_with_generate True # 现在用于生成文本- 批量评测
# 采用blue和rouge分数进行评测
llamafactory-cli train \
--stage sft \
--model_name_or_path Qwen/Qwen2-7B-Instruct-AWQ \
--preprocessing_num_workers 16 \
--finetuning_type lora \
--quantization_method bitsandbytes \ # 量化
--template qwen \
--flash_attn auto \
--dataset_dir data \
--eval_dataset <evaluation dataset> \
--cutoff_len 1024 \ # 提示词截断长度
--max_samples 100000 \
--per_device_eval_batch_size 2 \
--predict_with_generate True \
--max_new_tokens 512 \
--top_p 0.7 \
--temperature 0.95 \
--output_dir saves\Qwen2-7B-int4-Chat\lora\eval_2024-08-24-10-42-52 \
--do_predict True \
--adapter_name_or_path saves\Qwen2-7B-int4-Chat\lora\train_2024-08-18-14-43-59 \
--quantization_bit 4- 测评后结果
在output_dir下,存在:
all_results.json # 评测结果
generated_predictions.jsonl # 输入-输出
llamaboard_config.yaml # 评测的参数配置
trainer_log.jsonl # 训练日志
training_args.yaml # 训练的参数配置设置--eval_accumulation_steps=1(累计梯度)以及--per_device_eval_batch_size=1(批量大小)
比如F1score,recall,precision。需要在llama-factory根目录下,修改src\llamafactory\train\sft\metric.py和src\llamafactory\train\sft\workflow.py文件。
每个样本分开评测,一起评测得到的是平均分数。
检查提示词模板是否是对应的模板,如果仍然有问题选择default模板再次尝试。
如果想把训练的LoRA和原始大模型进行融合并输出一个完整的模型文件,可以使用以下命令。
llamafactory-cli export \ # CUDA_VISIBLE_DEVICES=0是一个环境变量,设定使用第一张GPU卡
--model_name_or_path /llama3/Meta-Llama-3-8B-Instruct \ # 底模路径
--adapter_name_or_path ./saves/LLaMA3-8B/lora/sft \ # lora路径
--template llama3 \ # 提示词模版
--finetuning_type lora \ # 微调方式lora
--export_dir megred-model-path \ # 导出路径
--export_size 2 \ # 导出每个分文件大小,2表示2G,比如一个4B权重的float16的模型,模型权重8G,就会分成4个文件保存
--export_device cpu \ # 使用cpu导出模型,而非模型后面是用cpu还是gpu运行
--export_legacy_format False # 是否使用旧格式导出,新格式默认是safetensors,旧格式就是pt,bin等格式- 其他可选参数
--export_quantization_bit: 量化位数,全精度导出时,不用填写
--export_quantization_dataset: 量化校准数据集(参见Q7)“模型路径”,“检查点路径”,“最大分块大小”,“导出目录”设置成功后开始导出。注意:**”导出量化等级“**设置为None,否则无法导出。
”模型路径“:改成合并后的模型路径;
”检查点路径“:取消选择;
”导出量化等级“:设置成需要的量化等级;
”导出路径“:新的保存路径。
Ollama框架可以帮助用户快速地使用本地的大语言模型,但只支持GGUF文件格式。其中,GGUF是llama cpp设计的大模型储存格式,可以对模型进行高效的压缩。
直接 pip 安装GGUF不是最新的版本,可能导致和最新的转换脚本会不兼容。建议直接从源码安装llama.cpp
# conda创建python=3.10版本的虚拟环境
conda create -n llama_cpp python=3.10
# 激活llama_cpp虚拟环境
conda activate llama_cpp
# torch和cuda安装等等(注意版本问题)
conda install pytorch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 pytorch-cuda=12.1 -c pytorch -c nvidia
# 拉取代码
git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpppip install --editable .返回llama.cpp项目根目录,会有一个官方提供的convert-hf-to-gguf.py脚本,用于完成huggingface格式(safetensors)到gguf格式的转换。
python convert_hf_to_gguf.py <需要转换的模型所在的路径> # 需要转换的模型路径
# 转化后的模型保存在同一路径下。我们以ollama为例,下载地址:https://ollama.com/
ollama是go语言开发的开源项目,github地址:https://github.com/ollama/ollama
ollama文档参考:https://github.com/ollama/ollama/tree/main/docs
ollama支持的是GGUF文件格式,如果是其他文件格式需要转换成GGUF文件格式
# 注意驱动程序已经安装完成!
# 方法1:使用官方安装脚本(推荐)
curl -fsSL https://ollama.com/install.sh | sh
# curl -fsSL:安全下载脚本(-f 失败时静默,-s 静默模式,-S 显示错误,-L 跟随重定向);管道 | sh:直接执行下载的安装脚本
# 方法2:手动安装
sudo curl -L https://ollama.com/download/ollama-linux-amd64 -o /usr/bin/ollama
sudo chmod +x /usr/bin/ollama
#下载二进制文件到系统路径 /usr/bin/ollama
# 添加可执行权限
# 创建专用系统用户
sudo useradd -r -s /bin/false -m -d /usr/share/ollama ollama
# 创建 systemd 服务文件
cat > /etc/systemd/system/ollama.service <<EOF
[Unit]
Description=Ollama Service
After=network-online.target # 确保网络就绪后启动
[Service]
ExecStart=/usr/bin/ollama serve # 启动命令
User=ollama # 指定运行用户
Group=ollama # 指定用户组
Restart=always # 崩溃时自动重启
RestartSec=3 # 重启等待时间
[Install]
WantedBy=default.target # 关联到默认启动组
EOF
# 启动服务
sudo systemctl daemon-reload # 重新加载服务配置
sudo systemctl enable ollama # 设置开机自启
sudo systemctl start ollama # 立即启动服务
# 移除 ollama 服务
sudo systemctl stop ollama # 停止服务
sudo systemctl disable ollama # 禁用开机启动
sudo rm /etc/systemd/system/ollama.service # 删除服务文件不再需要Windows Subsystem for Linux!Ollama可以直接通过下载地址进行安装。
推荐系统的配置应包括windows 10及其以上版本,NVIDIA/AMD Radeon驱动程序。
# 下载好的ollama文件目录
# 程序文件目录
C:\Users\Administrator\AppData\Local\Programs\Ollama
#日志文件夹
C:\Users\Administrator\AppData\Local\Ollama
# 模型和数据文件夹,可以通过设置OLLAMA_MODELS环境变量更改
C:\Users\Administrator\.ollama安装 Ollama Windows 预览版后,Ollama 将在后台运行, ollama 命令行可以在 cmd/powershell 等终端中使用。
# 可以设置的环境变量:模型目录和ollama api服务的url
OLLAMA_MODELS <model folder> # 可以自定义模型目录
OLLAMA_BASE_URL http://127.0.0.1:11434 # 可以自定义端口,方便api调用# 启动ollama服务
./ollama serve
# 指定模型的版本。如果模型没有下载,会自动下OLLAMA_MODELS目录中。
./ollama run gemma2:2bOllama支持的模型列表:https://ollama.com/library
llama pull <model name> # 下载模型
ollama rm <model name> # 删除模型
ollama show <model name> # 显示模型信息
ollama list # 列出下载好的模型(1)llama支持的是GGUF文件格式,如果是其他文件格式需要转换成GGUF文件格式
(2)构建ModelFile文件(不同的模型建议搜索对应的ModelFile文件参照)
FROM llama3.1 # 官方支持的模型
# FROM <模型路径> # 必要参数:自定义支持的模型,路径加模型文件名
#非必要参数:温度参数,参数越大,分布曲线压的越平
PARAMETER temperature 1
# 非必要参数:设置系统提示词
SYSTEM """You are Mario from Super Mario Bros. Answer as Mario, the assistant, only."""(3)注册模型
ollama create <模型名称> -f <ModelFile文件的路径> # -f 后面接ModelFile文件的路径(4) 命令聊天示例
ollama run <模型名称> # 运行模型就可以输入命令
>>> <聊天命令>- Open-webui项目:方便本地部署大模型并且带UI界面,可以管理文档资料,也可以作为聊天AI使用。项目下载地址:https://github.com/open-webui/open-webui
- 访问连接后,可以通过设置更改模型和下载模型
训练好的模型想通过api调用,接入到langchain或者其他下游业务中,可以采用llama-factory的api服务。该服务采用OpenAI的标准,基于uvicorn服务框架开发。
CUDA_VISIBLE_DEVICES=0 API_PORT=8000 llamafactory-cli api \
--model_name_or_path /llama3/Meta-Llama-3-8B-Instruct \
--adapter_name_or_path ./saves/LLaMA3-8B/lora/sft \
--template llama3 \
--finetuning_type lora如果要加速推理可以采用vllm推理后端,不过vllm只支持linux系统,不支持windows系统。如果使用vllm推理框架,需要提前将LoRA模型进行merge,使用融合后的完整模型或者训练前的基座模型。
CUDA_VISIBLE_DEVICES=0 API_PORT=8000 llamafactory-cli api \
--model_name_or_path <模型目录> \ # 修改为融合后模型或者基座模型的目录
--template llama3 \
--infer_backend vllm \ # 新添加命令
--vllm_enforce_eager # 新添加命令服务启动后,按照openai 的API 进行远程访问(替换其中的base_url,指向所部署的机器url和端口号)。
# 基本调用代码
import os
from openai import OpenAI
from transformers.utils.versions import require_version
if __name__ == '__main__':
# 更换端口号
port = 8000
client = OpenAI(
api_key="0",
base_url="http://localhost:{}/v1".format(os.environ.get("API_PORT",8000)),
)
messages = []
messages.append({"role": "user", "content": "hello!"})
result = client.chat.completions.create(
messages=messages,
model="test"
)
print(result.choices[0].message)启动ollama服务后,可以通过api调用来进行推理。 api url可以通过环境变量OLLAMA_BASE_URL来指定,也可以直接使用默认 http://127.0.0.1:11434
该api端口支持在终端流式生成和非流式生成,详细api文档参见官网
(1)Generation complication:POST /api/generate
# 流式生成
curl http://localhost:11434/api/generate -d '{
"model": "llama3.2",
"prompt": "Why is the sky blue?"
}'
# 输出
{
"model": "llama3.2",
"created_at": "2023-08-04T08:52:19.385406455-07:00",
"response": "The",
"done": false
}# 非流式生成
curl http://localhost:11434/api/generate -d '{
"model": "llama3.2",
"prompt": "Why is the sky blue?",
"stream": false
}'
# 输出
{
"model": "llama3.2",
"created_at": "2023-08-04T19:22:45.499127Z",
"response": "The sky is blue because it is the color of the sky.",
"done": true,
"context": [1, 2, 3],
"total_duration": 5043500667,
"load_duration": 5025959,
"prompt_eval_count": 26,
"prompt_eval_duration": 325953000,
"eval_count": 290,
"eval_duration": 4709213000
}(2) Chat:POST /api/chat
# 流式生成
curl http://localhost:11434/api/chat -d '{
"model": "llama3.2",
"messages": [
{
"role": "user",
"content": "why is the sky blue?"
}
]
}'# 非流式生成
curl http://localhost:11434/api/chat -d '{
"model": "llama3.2",
"messages": [
{
"role": "user",
"content": "why is the sky blue?"
}
],
"stream": false
}'大多项目需要采用openai兼容的api进行搭建,因此ollama框架也开发了对应的api服务。只需要在原有python代码上更换模型和api的url地址即可。详情参加官网,以下是一个简单的案例
from openai import OpenAI
client = OpenAI(
base_url='http://localhost:11434/v1/',
# 必要参数,但是不会有用
api_key='ollama',
)
chat_completion = client.chat.completions.create(
messages=[
{
'role': 'user',
'content': 'Say this is a test',
}
],
model='llama3.2',
)
response = client.chat.completions.create(
model="llava",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{
"type": "image_url",
"image_url": "data:image/png;base64,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",
},
],
}
],
max_tokens=300,
)
completion = client.completions.create(
model="llama3.2",
prompt="Say this is a test",
)
list_completion = client.models.list()
model = client.models.retrieve("llama3.2")
embeddings = client.embeddings.create(
model="all-minilm",
input=["why is the sky blue?", "why is the grass green?"],
)安装WSL文档(windows系统需要):https://learn.microsoft.com/zh-cn/windows/wsl/install
常见的WSL命令:https://learn.microsoft.com/zh-cn/windows/wsl/basic-commands
(1)以管理权限打开powershell
# Windows10及以上版本
# 启动wsl子系统
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
# 启用虚拟机平台支持
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
# 设置wsl2wsl
--set-default-version 2- 手动选择:一般在控制面板->程序->程序和功能->启动或关闭Windows功能->适用于Linux的Windows子系统
(2)安装docker环境
-
设置镜像本地路径:默认会将大模型拉取到本地后会保存在c:盘,我们设置到其它路径(一般在setting->Resources->Advanced->Disk image location)
-
设置镜像源:国外镜像源拉取比较慢,更新成国内镜像源。(一般在setting->Docker engine->registry-mirrors)
-
测试docker是否正常
# cmd 运行命令
docker run hello-world # 会拉取下来一个测试的hello-world镜像RAGFlow项目地址:https://github.com/infiniflow/ragflow
(1) 部署命令
# 拉取项目代码
git clone https://github.com/infiniflow/ragflow.git
# cmd命令行切换到ragflow\docker下
cd ragflow/docker
# 运行下面命令,一键自动下载项目的依赖镜像和环境
docker compose -f docker-compose-CN.yml up -d # 运行该命令会自动下载 RAGFlow 的开发版本 docker 镜像。如果你想下载并运行特定版本的 docker 镜像,请在 docker/.env 文件中找到 RAGFLOW_VERSION 变量,将其改为对应版本。(2) 测试命令
# 测试是否部署完成
docker logs -f ragflow-server如果成果会出现以下界面
使用以上地址即可登录注册!
(1)模型配置
- 启动Ollama模型,选择本地Ollama部署的大语言模型;并得到对应的模型api端口
ollama run <模型名称> # 运行模型就可以输入命令# 默认的模型端口
http://127.0.0.1:11434 # 可以自定义端口,方便api调用
# 如果是docker启动
http://host.docker.internal:11434(2)建立知识库
- 上传知识库文件,直至解析成功。
(3)新建聊天助手
- 回到主页面,点击新建助手,进行新的聊天配置
更多的详细讲解请参考指南
本次简要尝试选用Langchain和Chroma构建一个简单的RAG。Langchain负责整体流程编排,chroma提供向量数据库。
# 安装chroma,轻量并且原生支持windows,不需要wsl和docker
pip install chromadb # 安装
chroma run # 运行
# 虚拟环境
conda create -n llmrag python=3.10
# 激活虚拟环境
conda activate llmrag
# torch安装
conda install pytorch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 pytorch-cuda==12.1 -c pytorch -c nvidia
# 安装大模型的依赖
# 使用openai的模型
pip install openai
# 本地ollama
pip install -u langchain_ollama
# 支持 OpenAI 系列模型及兼容 OpenAI API 的第三方模型:通义千问,deepseek之类
pip install -u langchain_openai
# 支持chroma
pip install langchain_chroma
pip install -u langchain-community有关大模型的api获取地址:
-
通义千问:https://help.aliyun.com/zh/model-studio/obtain-api-key-app-id-and-workspace-id
-
ollama:直接运行ollama$$ollama run <model_name>$$
# 提取数据,以网页数据为例
import request
from langchain.document_loaders import webBaseLoader
loader = webBaseLoader("web url")
document = loader.load() # Document是一个包含文本和元数据的字典{"page_content": "完整文本内容", "metadata": {"source": "URL", "title": "页面标题"}}
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50) # chunk_size:每个文本块的最大字符数。 chunk_overlap:相邻文本块之间的重叠字符数。 额外的参数:length_function 长度计算方式,默认为len,可以改为token计数器; separators:分割符列表,["\n\n", "\n", "。", " ", ""]
chunks = text_splitter.split_documents(documents)
print(chunks[0].page_content) # 打印第一块# 嵌入并且存储这些文本块
from langchain.embeddings import OpenAIembeddings
from langchain_chroma import chroma
from langchain_community.embeddings import HuggingFaceBgeEmbeddings
# openai
rag_embeddings=OpenAIEmbeddings(model="text-embedding-3-large")
# hugging face上的嵌入模型
rag_embeddings=HuggingFaceBgeEmbeddings("<model_name>")
# 保存到向量数据库
vector_store = chroma.from_documents(documents=chunks, embeddings=rag_embeddings, persist_directory="<db path>")# 构建检索器
retriever = vector_store.as_retriever() # search_type="similarity", search_kwargs={"k": 5}
from langchain.prompts import ChatPromptTemplate
template = "<prompts> question: <question>, context: {context}"
prompt = ChatPromptTemplate.from_template(template)from langchain.chat_models import ChatOpenAI
from langgchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParser
from langchain_ollama.llms import ollamaLLM
# 使用ollama服务
llm = OllamaLLM(model="<model_name>")
# 使用api_key
llm = ChatOpenAI(api_key="<api_key>", base_url="<model_url>", model="<model_name>")
# 定义rag链
rag_chain = (
| {"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser())
qury = "<questions>"
resp = rag_chain.invoke(query)
print(resp)-
使用llamindex reader进行提取数据
-
使用llamaindex metadata添加元数据
-
例子:https://docs.llamaindex.ai/en/stable/examples/vector_stores/ChromaIndexDemo/
from llama_index.core.extractors import (
TitleExtractor,
QuestionsAnsweredExtractor,
)
from llama_index.core.node_parser import TokenTextSplitter
# 定义文本分割器
text_splitter = TokenTextSplitter(
separator=" ", chunk_size=512, chunk_overlap=128
)
# 定义标题提取器
title_extractor = TitleExtractor(nodes=5)
# 定义问题提取器
qa_extractor = QuestionsAnsweredExtractor(questions=3)
from llama_index.core.ingestion import IngestionPipeline
pipeline = IngestionPipeline(
transformations=[text_splitter, title_extractor, qa_extractor]
)
# 执行分割
nodes = pipeline.run(
documents=documents,
in_place=True,
show_progress=True,
)#使用Llama-Index来连接到Neo4j,以构建和查询知识图谱,将文档中的信息转化为知识图谱
from llama_index.graph_stores.neo4j import Neo4jGraphStorefrom llama_index.core import KnowledgeGraphIndex
# Neo4j数据库连接配置
username = "neo4j-xxx" # 数据库用户名(需替换为实际值)
password = "neo4j-password-xxx" # 数据库密码(需替换为实际值)
url = "neo4j-url-xxxx:7687" # 数据库连接URL(格式通常为 bolt://host:port)
database = "neo4j" # 数据库名称(默认是neo4j)
# 初始化Neo4j图存储连接
graph_store = Neo4jGraphStore(
username=username, # 传入用户名
password=password, # 传入密码
url=url, # 传入连接URL
database=database, # 传入数据库名
)
# 创建存储上下文(封装图存储连接)
storage_context = StorageContext.from_defaults(graph_store=graph_store)
# 从文档构建知识图谱索引
index = KnowledgeGraphIndex.from_documents(
documents, # 输入的文档列表(需提前定义)
storage_context=storage_context, # 关联Neo4j存储
max_triplets_per_chunk=2, # 限制每个文本块提取的三元组数量
)
# 创建空知识图谱索引(清空之前构建的内容)
index = KnowledgeGraphIndex.from_documents(
[], # 传入空文档列表
storage_context=storage_context,
)
# 手动添加三元组到索引(假设nodes[0]已存在)
node_0_tups = [
("author", "worked on", "writing"), # 三元组:主体-关系-客体
("author", "worked on", "programming"),
]
# 遍历并插入三元组
for tup in node_0_tups:
# 将三元组关联到特定节点
index.upsert_triplet_and_node(tup, nodes[0]) # nodes[0]需提前定义- 总->细,提高搜索的效率
from llama_index.core import SummaryIndex
from llama_index.core.async_utils import run_jobs
from llama_index.llms.openai import OpenAI
from llama_index.core.schema import IndexNode
from llama_index.core.vector_stores import (FilterOperator, MetadataFilter,
MetadataFilters)
async def aprocess_doc(doc, include_summary: bool = True):
"""处理文档并创建索引节点:doc: 要处理的文档对象,include_summary: 是否包含文档摘要(默认为True)"""
# 从文档元数据中提取信息
metadata = doc.metadata
# 解析创建日期
date_tokens = metadata["created_at"].split("T")[0].split("-")
year = int(date_tokens[0]) # 提取年份
month = int(date_tokens[1]) # 提取月份
day = int(date_tokens[2]) # 提取日
# 提取分配人信息(如果存在)
assignee = ("" if "assignee" not in doc.metadata else doc.metadata["assignee"])
# 从标签中提取大小信息(如果有相关标签)
size = ""
if len(doc.metadata["labels"]) > 0:
# 筛选包含"size:"的标签
size_arr = [label for label in doc.metadata["labels"] if "size:" in l]
# 提取大小值(如标签为"size:large",则提取"large")
size = size_arr[0].split(":")[1] if len(size_arr) > 0 else ""
# 构建新的元数据字典
new_metadata = {
"state": metadata["state"], # 文档状态
"year": year, # 创建年份
"month": month, # 创建月份
"day": day, # 创建日
"assignee": assignee, # 分配人
"size": size, # 大小标签
}
# 提取文档摘要(如果启用)
if include_summary:
# 创建摘要索引(针对单个文档)
summary_index = SummaryIndex.from_documents([doc])
# 创建查询引擎(使用OpenAI的GPT-3.5模型)
query_engine = summary_index.as_query_engine(llm=OpenAI(model="gpt-3.5-turbo"))
# 异步查询摘要(要求一句话总结)
query_str = "Give a one-sentence concise summary of this issue."
summary_txt = await query_engine.aquery(query_str)
summary_txt = str(summary_txt) # 转换为字符串
else:
summary_txt = "" # 如果不包含摘要,使用空字符串
# 获取文档索引ID
index_id = doc.metadata["index_id"]
# 创建元数据过滤器(用于后续检索)
filters = MetadataFilters(
filters=[
MetadataFilter(
key="index_id", # 过滤字段
operator=FilterOperator.EQ, # 等于操作符
value=int(index_id) # 目标值(转换为整数)
),
]
)
# 创建索引节点(核心数据结构)
index_node = IndexNode(
text=summary_txt, # 摘要文本
metadata=new_metadata, # 处理后的元数据
obj=doc_index.as_retriever(filters=filters), # 关联的检索器(带过滤)
index_id=doc.id_, # 文档唯一ID
)
return index_node # 返回创建的索引节点
async def aprocess_docs(docs):
"""批量异步处理文档集合,提取元数据并创建索引节点,docs: 文档对象列表,index_nodes: 处理完成后生成的索引节点列表"""
# 初始化存储容器
index_nodes = [] # 用于存储处理结果
tasks = [] # 用于存储异步任务
# 为每个文档创建异步处理任务
for doc in docs:
# 为当前文档创建处理任务(不立即执行)
task = aprocess_doc(doc)
# 将任务添加到任务队列
tasks.append(task)
# 并行执行所有异步任务
# show_progress=True 显示进度条
# workers=3 限制同时运行的任务数(避免资源过载)
index_nodes = await run_jobs(
tasks,
show_progress=True,
workers=3
)
return index_nodes # 返回所有文档处理结果- 细->总,提高搜索精确问题的准确性
'''
目的:对于每个1024大小的基础文本块,生成不同粒度的子文本块,用于构建分层索引结构
处理逻辑:
1. 为每个1024大小的基础块生成:
- 8个128大小的子块
- 4个256大小的子块
- 2个512大小的子块
2. 保留原始1024大小的块
3. 将所有块组织为索引节点
'''
# 定义子块的大小(128, 256, 512)
sub_chunk_sizes = [128, 256, 512]
# 为每种块大小创建对应的节点解析器
sub_node_parsers = [
SimpleNodeParser.from_defaults(chunk_size=c) # 创建指定块大小的解析器
for c in sub_chunk_sizes
]
# 存储所有生成的节点
all_nodes = []
# 遍历每个基础节点(假设base_nodes是大小为1024的文本块)
for base_node in base_nodes:
# 为当前基础节点生成不同粒度的子块
for node_parser in sub_node_parsers:
# 将基础节点分割为更小的子节点
sub_nodes = node_parser.get_nodes_from_documents([base_node])
# 将子节点转换为索引节点,并关联到原始基础节点
sub_inodes = [
IndexNode.from_text_node(
sn, # 子节点
base_node.node_id # 关联到基础节点的ID(建立层级关系)
)
for sn in sub_nodes
]
# 将生成的索引节点添加到总列表
all_nodes.extend(sub_inodes)
# 添加原始基础节点本身(保持完整块)
original_node = IndexNode.from_text_node(
base_node, # 原始基础节点
base_node.node_id # 使用自身ID
)
all_nodes.append(original_node)
# 创建节点ID到节点的映射字典(便于快速查找)
all_nodes_dict = {n.node_id: n for n in all_nodes}# 使用默认设置创建句子窗口节点解析器
# 该解析器会将文档拆分为重叠的句子窗口,每个窗口以中心句子为核心并包含相邻句子作为上下文
node_parser = SentenceWindowNodeParser.from_defaults(
window_size=3, # 窗口大小:每个节点包含中心句子及其前后各3个句子(共7句)
window_metadata_key="window", # 元数据键名:存储完整窗口文本
original_text_metadata_key="original_text", # 元数据键名:存储原始中心句子文本
)
# 将文档分割成句子窗口节点
# 每个节点包含:
# - text: 中心句子的原始文本
# - metadata:
# "window": 完整的上下文窗口文本(包含相邻句子)
# "original_text": 中心句子的原始文本(与text相同)
# 将原始文档拆分为句子,创建滑动窗口,为每个窗口创建节点并封装
sentence_nodes = node_parser.get_nodes_from_documents(docs)
# 基于句子窗口创建向量索引
sentence_index = VectorStoreIndex(
sentence_nodes,
service_context=service_context # 包含AI模型配置的上下文对象
)- 向量检索中chunk的大小对相似度结果产生较大影响。当chunk过小时,没有上下文信息,导致匹配不精确;当chunk过大时,chunk信息多,导致检索存在噪音。
# 初始化大型语言模型(LLM)
llm = OpenAI(model="gpt-4")
# 定义要尝试的不同分块大小
# 这些值代表每个文本块包含的token数量
chunk_sizes = [128, 256, 512, 1024]
# 准备存储不同分块结果的容器
nodes_list = [] # 存储不同分块大小的节点列表
vector_indices = [] # 存储不同分块大小构建的向量索引
# 遍历所有分块大小进行实验
for chunk_size in chunk_sizes:
print(f"Chunk Size: {chunk_size}") # 打印当前处理的分块大小
# 创建句子分割器(SentenceSplitter)
# 使用当前循环的分块大小作为参数
splitter = SentenceSplitter(chunk_size=chunk_size)
# 将文档分割成指定大小的文本块节点
nodes = splitter.get_nodes_from_documents(docs)
# 在节点元数据中添加当前分块大小信息
# 用于后续分析不同分块大小的效果
for node in nodes:
# 添加分块大小到元数据
node.metadata["chunk_size"] = chunk_size
# 配置元数据排除规则:
# 在嵌入过程中排除"chunk_size"元数据(不参与向量计算)
node.excluded_embed_metadata_keys = ["chunk_size"]
# 在LLM处理过程中排除"chunk_size"元数据(不影响生成结果)
node.excluded_llm_metadata_keys = ["chunk_size"]
# 将当前分块大小的节点列表保存到总列表中
nodes_list.append(nodes)
# 使用当前分块大小的节点构建向量索引
vector_index = VectorStoreIndex(nodes)
# 将构建的索引添加到索引列表
vector_indices.append(vector_index)2.并行优化
- 并行嵌入
# 并行embedding
from llama_index.core import Document
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.extractors import TitleExtractor
from llama_index.core.ingestion import IngestionPipeline
# 创建文档处理流水线
pipeline = IngestionPipeline(
transformations=[
# 1. 文本分割转换:将文档分割成1024字符的块,块间重叠20字符
SentenceSplitter(chunk_size=1024, chunk_overlap=20),
# 2. 标题提取转换:为每个文本块生成描述性标题
TitleExtractor(),
# 3. 嵌入生成转换:使用OpenAI模型生成文本嵌入向量
OpenAIEmbedding(),
]
)
# 禁用缓存(用于性能测试场景)
# 原因:缓存会干扰性能测量的准确性
pipeline.disable_cache = True
# 执行流水线处理
# 参数:
# documents: 待处理的文档集合
# num_workers=4: 使用4个线程并行处理
nodes = pipeline.run(documents=documents, num_workers=4)- 并行查询
import asyncio
from llama_index.core.query_engine import BaseQueryEngine
async def parallel_queries(query_engine: BaseQueryEngine, queries: list):
"""执行并行查询"""
tasks = []
for query in queries:
# 创建异步查询任务
task = query_engine.aquery(query)
tasks.append(task)
# 并行执行所有查询
results = await asyncio.gather(*tasks)
return results
# 使用示例
queries = [
"量子计算的基本原理是什么?",
"解释Transformer架构",
"气候变化对农业的影响"
]
# 获取查询引擎(index是已经构建好的向量索引)
query_engine = index.as_query_engine()
# 执行并行查询
results = asyncio.run(parallel_queries(query_engine, queries))
# 处理结果
for i, response in enumerate(results):
print(f"查询 {i+1}: {queries[i]}")
print(f"答案: {response.response}\n")- 对于复杂的查询,大模型将其拆分为多个子查询。这些子查询会同时进行,检索到的信息被汇总到一起,再有大模型整合为最终输出。Llamaindex(子查询)的例子:https://docs.llamaindex.ai/en/stable/examples/query_engine/multi_doc_auto_retrieval/multi_doc_auto_retrieval/ 。Ollama也叫多查询。
from llama_index.query_engine import SubQuestionQueryEngine
from llama_index.question_generation import BaseQuestionGenerator
from llama_index.response_synthesizers import ResponseSynthesizer
from llama_index.retrievers import BaseRetriever
from typing import Sequence
# 假设我们已经有了以下核心组件(实际实现见下文)
# 1. 问题生成器:负责将复杂问题分解为子问题
question_gen: BaseQuestionGenerator = ... # 实际实现见下文
"""
from llama_index.question_generation import LLMQuestionGenerator
from llama_index.llms import OpenAI
question_gen = LLMQuestionGenerator.from_defaults(
llm=OpenAI(model="gpt-4"), # 使用GPT-4生成子问题
prompt_template="""
将以下复杂问题分解为2-4个更具体的子问题:
原始问题: {query_str}
子问题:
""",
max_questions=4 # 最多生成4个子问题
)
"""
# 2. 响应合成器:负责将多个子问题的答案组合成最终响应
response_synthesizer: ResponseSynthesizer = ... # 实际实现见下文
"""
from llama_index.response_synthesizers import get_response_synthesizer
# 创建响应合成器
response_synthesizer = get_response_synthesizer(
llm=OpenAI(model="gpt-4-turbo"), # 使用更强的模型合成答案
response_mode="tree_summarize", # 树状总结模式
summary_template="""
基于以下子问题的答案,综合回答原始问题:
原始问题: {query_str}
子问题及答案:
{context_str}
综合回答:
"""
)
"""
# 3. 查询引擎工具集:包含多个专门处理特定类型问题的查询引擎
query_engine_tools: Sequence[BaseRetriever] = ... # 实际实现见下文
"""
from llama_index.tools import QueryEngineTool
from llama_index import VectorStoreIndex
# 创建多个专业查询引擎
nutrition_index = VectorStoreIndex(...) # 营养学知识索引
agriculture_index = VectorStoreIndex(...) # 农业知识索引
economics_index = VectorStoreIndex(...) # 经济学知识索引
culinary_index = VectorStoreIndex(...) # 烹饪知识索引
# 封装为工具集
query_engine_tools = [
QueryEngineTool(
query_engine=nutrition_index.as_query_engine(),
metadata=ToolMetadata(
name="nutrition_qa",
description="回答关于食物营养成分的问题"
)
),
QueryEngineTool(
query_engine=agriculture_index.as_query_engine(),
metadata=ToolMetadata(
name="agriculture_qa",
description="回答关于作物种植和农业的问题"
)
),
QueryEngineTool(
query_engine=economics_index.as_query_engine(),
metadata=ToolMetadata(
name="economics_qa",
description="回答关于市场价格和经济分析的问题"
)
),
QueryEngineTool(
query_engine=culinary_index.as_query_engine(),
metadata=ToolMetadata(
name="culinary_qa",
description="回答关于食物烹饪和用途的问题"
)
)
]
"""
# 创建子问题查询引擎
# 该引擎专门处理需要分解为多个子问题的复杂查询
sub_question_query_engine = SubQuestionQueryEngine(
question_gen=question_gen, # 问题分解组件
response_synthesizer=response_synthesizer, # 答案合成组件
query_engine_tools=query_engine_tools # 子问题处理工具集
)
# 使用子问题查询引擎处理复杂问题
# 示例问题要求比较两种事物(需要多角度分析)
response = sub_question_query_engine.query("比较和对比苹果和橙子。")
# 输出最终的综合回答
print(response)- 在查询时,先生成一个假设答案,而不是直接进行检索。可以解决问题和文档内容不匹配的问题。
from llama_index.core.indices.query.query_transform import HyDEQueryTransform
from llama_index.core.query_engine import TransformQueryEngine
from IPython.display import Markdown, display
# 1. 创建基础向量索引
# 从文档集合构建标准向量索引
index = VectorStoreIndex.from_documents(documents)
# 2. 创建基础查询引擎
# 使用标准检索方法
query_engine = index.as_query_engine()
# 3. 创建HyDE查询转换器
# HyDE = Hypothetical Document Embeddings (假设性文档嵌入)
hyde = HyDEQueryTransform(include_original=True)
# 参数说明:
# include_original=True: 在最终查询中包含原始查询和生成的假设文档
# 4. 创建HyDE增强的查询引擎
# 将基础查询引擎与HyDE转换器结合
hyde_query_engine = TransformQueryEngine(
query_engine, # 基础查询引擎
hyde # HyDE转换器
)
# 5. 执行HyDE增强查询
query_str = "量子纠缠的实际应用有哪些?"
response = hyde_query_engine.query(query_str)
# 6. 在Jupyter中以Markdown格式显示结果
display(Markdown(f"<b>{response}</b>"))
# 7. 单独分析HyDE转换过程
# 使用LLM根据原始查询生成一个假设性的文档(即一个假设的答案),将原始查询和生成的假设文档封装成一个QueryBundle对象。
query_bundle = hyde(query_str)
# 8. 提取生成的假设文档
# embedding_strs[0] 包含LLM生成的假设性答案
hyde_doc = query_bundle.embedding_strs[0]- include_original=False
- include_original=True
- 在查询时,先生成一个假设问题,而不是直接进行检索。可以解决已知答案和文档内容不匹配的问题。
def inverse_hyde(document):
# 使用LLM生成可能查询该文档的问题
hypothetical_query = llm.predict(f"""
给定以下文档,生成用户可能提出的3个问题:
文档:{document}
可能的问题:
1.
""")
# 返回生成的查询
return hypothetical_query-
检索阶段的目标是确定最相关的上下文。通常检索基于向量搜索,通过计算查询与索引数据之间的语义相似性。大多数检索优化技术都围绕嵌入模型展开。
-
微调Embedding
某些术语或词语在通用领域可能相关性较低,但在该行业中却可能具有显著相关性,因此可以对 Embedding 模型在特定行业或领域进行微调。Llamaindex微调:https://github.com/run-llama/finetune-embedding/tree/main ;
- 动态Embedding
动态嵌入会根据单词的上下文进行调整,同一个单词可依据周围词语生成不同的嵌入向量;而静态嵌入则为每个单词分配单一向量。例如,OpenAI 的 embeddings:ada-02 就是一款复杂的动态嵌入模型,具备强大的上下文理解能力。
除了向量搜索,还有其他检索技术,比如混合搜索 —— 通常指将向量搜索与关键词搜索相结合的技术。如果你的检索需求中包含精确的关键词匹配,这种混合搜索技术就尤为适用。
- 在 RAG 系统中,首先通过向量检索或关键词检索快速筛选出一批候选文档。Rerank(重排序) 的作用就是对这批候选文档进行精细化的二次排序,目的是将真正最相关、质量最高、信息量最大的文档提升到排名最前列。
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
class Reranker:
def __init__(self, model_name="cross-encoder/ms-marco-MiniLM-L-6-v2"):
"""
初始化重排序模型
:param model_name: 预训练模型名称(默认使用MS MARCO微调的MiniLM)
"""
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModelForSequenceClassification.from_pretrained(model_name).to(self.device)
self.model.eval()
def rerank(self, query: str, documents: list, top_k: int = 5):
"""
对文档列表进行重排序
:param query: 查询文本
:param documents: 待排序文档列表
:param top_k: 返回前K个结果
:return: 排序后的(top_k个文档, 得分)
"""
# 生成查询-文档对
pairs = [(query, doc) for doc in documents]
# 批量编码文本
features = self.tokenizer(
pairs,
padding=True,
truncation=True,
max_length=512,
return_tensors="pt"
).to(self.device)
# 计算得分
with torch.no_grad():
scores = self.model(**features).logits.squeeze(dim=1)
# 转换为概率分数
scores = torch.sigmoid(scores).cpu().numpy()
# 返回数组元素降序排列时对应的原始索引
sorted_indices = scores.argsort()[::-1]
sorted_docs = [documents[i] for i in sorted_indices]
sorted_scores = [scores[i] for i in sorted_indices]
# 返回top-k结果
return sorted_docs[:top_k], sorted_scores[:top_k]
# 使用示例
if __name__ == "__main__":
# 初始化重排序器
reranker = Reranker()
# 示例数据
query = "什么是人工智能?"
documents = [
"人工智能是计算机科学的一个分支",
"机器学习是AI的核心技术之一",
"深度学习推动人工智能发展",
"自然语言处理是AI的重要应用",
"AI伦理问题引发广泛讨论",
"计算机视觉在AI中的应用",
"人工智能的历史发展概述"
]
# 执行重排序
reranked_docs, scores = reranker.rerank(query, documents, top_k=3)
# 打印结果
print("原始文档数量:", len(documents))
print("重排序后Top 3结果:")
for i, (doc, score) in enumerate(zip(reranked_docs, scores)):
print(f"\nRank {i+1} (Score: {score:.4f}):")
print(doc)Modular RAG整合了各种方法来增强功能模块:
1.增强数掘采集: RAG已经超越了传统的非结构化数掘,现在可以处理半结构化和结构化数据,以改进检索并减少模型对外部知识源的作赖。
2.结合技术:RAG 正在与其他技术相结合,包括使用微调、适配模块和强化学习来加强检索能力,
3.可适应的检索过程:检索过程已发展到支持多轮检索增强,使用检索内容来指导生成,反之亦然。
模块化 RAG提供了一种高可扩展的范式,将 RAG 系统划分为Module Type、Module 和 Operator三层结构,每个 Module Type 代表 RAG 系统中的一个核心流程,包含多个Module,而每个功能模块又包含多个具体的 Operator,整个RAG 系统就变成了多个模块和对应 Operator 的排列组合,形成了我们所说的 RAGFlow。在Fow 中,每个 Module Type 中可以选择不同的功能模块,而每个功能模块中又可以选择一个或多个 Operator。
在此范式下,当前 RAG体系中的核心技术,涵盖6大ModuleType、14个Module和40+Operator,旨在提供对 RAG 的全面理解Moduar RAG(模块化检秦增现生成)的三层架构是其核心特点之一,提供了一个高度可重构的框架,以适应不同的应用场景和需求,
这三层架构具体包括:
1、顶层(Module Type):顶层关注RAG的关键阶段,每个阶段都被视为一个独立的模块。这一层继承了高级RAG范式的主要流程,并引入了一个编排模块来控制RAG流程的协调,顶层的模块类型包括索引、预检索、检索、后检索、生成和编排,它们代表了RAG系统中的核心流程。
2、中间层(Modue):中间层由每个横块类型内的子模块组成,进一步细化和优化功能,这些子横块负责处理特定的功能或任务,例如索引模块可能包含块优化和结构组织等子模块,而检索模块可能包含检索器选择和检索器微调等子模块。
3、底层(Operator):底层由基本操作单元-操作特构成。操作特指模块内的具体功能实现,是RAG系统中的构建块。例如,索引模块内的操作符可能负责将文档分割成块、优化块的大小和重叠大小、或者为块附加元数据等。
主要包括三个模型的微调:Retriever微调,Rerank微调和Generator微调
- Retriever微调:在RAG Flow中,对Retriever进行微调,也就是微调embedding模型(代码见后续embedding微调),常用方法:直接微调检索器,构建专门的retriever数据集来微调;LLM监督微调,根据LLM生成的结果对检索器进行微调;LLM Reward RL,利用强化学习将检索器和生成器对齐。
-
Reranker微调:使用行业数据库来微调Reranker模型,见后续微调代码。
-
Generator微调:也就是微调LLM,见前述代码。
RAG Flow的顺序结构将RAG的模块和运算符组织成线性流水线,如果它同时包含Pre-Retrieval和Post-Retrieval模块类型,就是典型的Advanced RAG范式;去掉则为典型的Naive RAG范式
目前使用最广泛的RAG Pipeline是Sequential,常见的包括检索前的Query Rewrite和HyDE以及检索后Rerank。
开源项目: QAnything ,带自研的embedding模型bce-embedding-base_v1和重排序模型bce-reranker-base_v1。
性能:随着数据上升,二阶段Rerank性能越突出。
重写-检索-阅读也是典型的顺序结构。查询重写模块是一个较小的可训练语言模型,在强化学习的背景下,重写器的优化被形式化为马尔可夫决策,以LLM的最终输出作为奖励。
采用条件模式的 RAG Flow,通过基于查询关键词或语义确定路线的路由模块,实现根据不同的条件选择不同的 RAG 路径。
-
元数据路由器/过滤器:据块中的关键字和元数据进行筛选,以缩小搜索范围。
-
语义路由器:利用查询的语义信息。
-
也可以采用混合路由方法,将基于语义和元数据的方法相结合,以增查询路由根据问题类型选择不同的路由,针对不同场景引导不同的流程。
例如,当用户查询严肃问题、学习问题或娱乐活题时,对大横型答案的容忍度是不同的。不同的路由分支通常在检索源、检索流程、配置、横型和提示方面有所不同。经典项目:Semantic-router
# 定义查询引擎和工具
from llama_index.core.tools import QueryEngineTool
# 创建基于摘要索引的查询引擎,使用树状总结响应模式和异步处理
list_query_engine = summary_index.as_query_engine(
response_mode="tree_summarize", # 使用树状结构汇总多个节点的响应
use_async=True # 启用异步处理提高效率
)
# 创建基于向量索引的查询引擎,同样使用树状总结和异步处理
vector_query_engine = vector_index.as_query_engine(
response_mode="tree_summarize",
use_async=True
)
# 创建第一个查询引擎工具:列表查询工具
list_tool = QueryEngineTool.from_defaults(
query_engine=list_query_engine, # 绑定上面创建的列表查询引擎
description="Useful for questions asking for a biography of the author.", # 工具描述:用于获取作者传记类问题
)
# 创建第二个查询引擎工具:向量查询工具
vector_tool = QueryEngineTool.from_defaults(
query_engine=vector_query_engine, # 绑定上面创建的向量查询引擎
description=(
"Useful for retrieving specific snippets from the author's life, like"
" his time in college, his time in YC, or more." # 工具描述:用于检索作者生活片段的具体信息
),
)
# 定义路由查询引擎
from llama_index.core import VectorStoreIndex
from llama_index.core.objects import ObjectIndex
# 创建工具对象的向量索引,用于后续的工具检索
obj_index = ObjectIndex.from_objects(
[list_tool, vector_tool], # 将两个工具放入索引
index_cls=VectorStoreIndex, # 指定使用向量存储索引类型
)
# 创建路由查询引擎,自动选择最合适的工具
from llama_index.core.query_engine import ToolRetrieverRouterQueryEngine
query_engine = ToolRetrieverRouterQueryEngine(
obj_index.as_retriever() # 将工具索引转换为检索器,用于根据查询匹配最佳工具
)
# 执行查询示例
response = query_engine.query(
"What is a biography of the author's life?" # 询问作者传记的问题
)
print(str(response)) # 打印响应结果采用分支模式的 RAG Flow 与条件模式的不同之处在于,它涉及多个并行分支,而不是像条件模式那样从多个选项中选择一个分支。从结构上来说,可分为两类:
-
检索前分支(多查询、并行检索)。将原查询扩展为多个子查询,然后对每个子查询进行单独检索。检索完成后,根据子查询和对应的检索内容立即生成答案;也可以只使用扩展后的检索内容,将其合成统一的文本再生成答案。
-
检索后分支(单个查询,并行生成)。此方法保留原始查询并检索多个文档块。随后,它同时使用原始查询和每个文档块进行生成,最后将生成的结果合并在一起。
























