首页
/ Llama 4 与 DeepSeek-R1 的 RAG 对决:基于 LlamaIndex + Opik 的多模型对比评测实战指南

Llama 4 与 DeepSeek-R1 的 RAG 对决:基于 LlamaIndex + Opik 的多模型对比评测实战指南

2026-09-08 10:00:56作者:苗圣禹Peter

llama-4_vs_deepseek-r1 是 ai-engineering-hub 中一个“以检索增强生成(RAG)为核心赛道的模型对比评测”实战项目:用同一套 LlamaIndex RAG 管道、同一份知识库与同一条评估链路,让 Groq 托管的 Llama 4 与 DeepSeek-R1 在同题竞答,再用开源可观测平台 Opik 的四个 RAG 指标完成量化对比。读完本文,你将掌握如何搭建支持模型热切换的 Streamlit 问答应用、如何用 LlamaIndex Workflow 编排 ingest→retrieve→synthesize 流水线,以及如何复用同一个评估数据集对两个模型做公平的离线评测。


一、项目概览:一条流水线,两套模型,同一把尺子

项目的核心思路非常清晰:让所有非模型因素保持一致,只让“大模型”成为唯一的变量

  • 检索与索引层统一使用 LlamaIndex(VectorStoreIndex + FastEmbed 本地嵌入模型);
  • 推理层统一通过 Groq 高速 API 调用两个参赛模型;
  • 评测层统一使用 Opik 的 LLM-as-a-Judge 指标(幻觉、答案相关性、上下文精度、上下文召回);
  • 评测语料固定为仓库内置的 Paul Graham 文章与 5 道问答对。

llama-4_vs_deepseek-r1/README.md 的说明看,这套工程由三个可独立运行的部分组成,对应目录结构如下:

llama-4_vs_deepseek-r1/
├── app.py                  # Streamlit 交互式 RAG 问答应用(在线对比)
├── workflow.py             # LlamaIndex Workflow 事件驱动流水线
├── evaluation.ipynb        # Opik 驱动的离线模型评测(评分核心)
├── pyproject.toml / uv.lock # uv 依赖锁定
├── assets/                 # 界面所需的品牌 Logo
├── data/
│   └── DeepSeek.pdf        # workflow.py 示例用的待索引文档
└── eval-data/
    ├── test.csv            # 5 道带标准答案与上下文的问题集
    └── paul_graham/
        └── paul_graham_essay.txt  # 评测知识库语料

一个直观的区分是:app.py 面向交互式人工对比(上传自己的 PDF 即可轮流提问两个模型),evaluation.ipynb 面向批量式自动评测(在固定语料上打分比较)。


二、环境准备与依赖同步

项目基于 uv 管理 Python 依赖,pyproject.toml 声明了完整的技术栈:

  • llama-index >= 0.12.28(核心框架)以及 llama-index-llms-groqllama-index-embeddings-fastembedllama-index-utils-workflowllama-index-embeddings-huggingfacellama-index-embeddings-instructorllama-index-llms-ollama 等扩展包(依赖清单见 pyproject.toml);
  • opik >= 1.6.13(评测与可观测);
  • streamlit >= 1.44.1(Web 界面);
  • python-dotenv(环境变量加载,已被锁定在 uv.lock 中);
  • 要求 requires-python >= 3.12

在项目目录下同步依赖:

uv sync

uv sync 会依据 pyproject.tomluv.lock 创建虚拟环境并锁定安装全部依赖。若需要在 Notebook 内核中使用该环境,请选择 .venv 对应的 Python 内核(evaluation.ipynb 的元数据中即记录了 .venv 内核)。


三、环境变量配置:哪些 Key 是必需的

README 要求配置以下环境变量:

GROQ_API_KEY=...
OPENAI_API_KEY=...

两个变量职责不同:

变量 用途 缺失后果
GROQ_API_KEY 调用 Llama 4 / DeepSeek-R1 两个参赛模型(RAG 生成阶段) app.py 直接报错并 st.stop()
OPENAI_API_KEY 评估阶段充当“裁判/评判模型”以及 o1 相关场景 评估指标无法打分

README 明确提示:OpenAI API Key 是评估阶段需要的(“needed for using o1 and a judge during evaluation”)。在 evaluation.ipynb 中可以看到,opik.evaluation.evaluate()experiment_config 里以 {"model": "gpt-3.5-turbo"} 指定了裁判模型——也就是由 OpenAI 模型去评判 Groq 两个候选模型的答案质量,因此两者缺一不可。

README 建议参照 .env.example 创建自己的 .env 文件。应用启动时,app.py 顶部会调用 load_dotenv()app.py)自动读取项目根目录的 .envevaluation.ipynb 同样在执行首段调用 load_dotenv()。另外在 app.py 的侧边栏中你也可以直接输入 Groq API Key,代码会优先读取会话中输入的值,其次才是环境变量(见 app.py):

api_key = st.session_state.get("groq_api_key", os.getenv("GROQ_API_KEY"))

四、双模型映射机制:一个“开关”切换参赛选手

项目在两个文件中用几乎相同的方式维护了“界面选项 → Groq 模型 ID”的映射关系。

在交互应用 app.py 中,通过下拉框选项选择模型:

@st.cache_resource
def load_llm(model_option):
    if model_option == "Llama 4":
        llm = Groq(model="meta-llama/llama-4-scout-17b-16e-instruct")
    elif model_option == "DeepSeek-R1":
        llm = Groq(model="deepseek-r1-distill-llama-70b")
        return llm
    return llm

workflow.py 中则收敛为一张字典,并通过 Settings.llm 注入全局配置:

GROQ_MODELS = {
    "Llama 4": "meta-llama/llama-4-scout-17b-16e-instruct",
    "DeepSeek-R1": "deepseek-r1-distill-llama-70b"
}

两个参赛模型的具体 Groq 模型标识如下:

参赛模型 Groq 模型 ID 说明
Llama 4 meta-llama/llama-4-scout-17b-16e-instruct Meta 的开源 Llama 4 系列 MoE 模型,ID 后缀 17b-16e 表明其稀疏专家结构
DeepSeek-R1 deepseek-r1-distill-llama-70b DeepSeek-R1 蒸馏到 Llama-70B 的推理模型,擅长逐步推理

注意两个文件中的下拉选项命名略有差异:app.py 使用 "Llama 4"evaluation.ipynb 使用 "Llama-4"(连字符),使用时需保持字符串与映射键一致,否则 workflow.py 会抛出 ValueError(见 workflow.py)。


五、在线对比:运行 Streamlit 交互式 RAG 应用

README 给出的启动命令非常简短:

streamlit run app.py

启动后浏览器会打开一个标题为 “Llama 4 vs DeepSeek-R1 RAG Battle” 的界面。整体交互链路如下。

5.1 侧边栏:填 Key、选模型、传 PDF

左侧边栏集中了三个核心操作(见 app.py):

  1. 输入 Groq API Key(密码框,若留空则回退到环境变量);
  2. 在下拉框中选择 Llama 4DeepSeek-R1
  3. 通过文件上传控件选择 .pdf 文档(type="pdf" 限定文件类型)。

PDF 上传后,应用将其写入 tempfile.TemporaryDirectory() 临时目录,再交给 LlamaIndex 的 SimpleDirectoryReader 读取(见 app.py):

loader = SimpleDirectoryReader(
    input_dir=temp_dir,
    required_exts=[".pdf"],
    recursive=True
)
docs = loader.load_data()

5.2 索引构建与 QA 提示词定制

索引用的是本地嵌入模型 + 内存向量索引,无需额外部署向量数据库(见 app.py):

embed_model = FastEmbedEmbedding(model_name="BAAI/bge-large-en-v1.5")
Settings.embed_model = embed_model
index = VectorStoreIndex.from_documents(docs, show_progress=True)
Settings.llm = llm
query_engine = index.as_query_engine(streaming=True)

qa_prompt_tmpl_str = (
    "Context information is below.\n"
    "---------------------\n"
    "{context_str}\n"
    "---------------------\n"
    "Given the context information above I want you to think step by step to answer the query in a crisp manner, in case you don't know the answer say 'I don't know!'.\n"
    "Query: {query_str}\n"
    "Answer: "
)
qa_prompt_tmpl = PromptTemplate(qa_prompt_tmpl_str)
query_engine.update_prompts(
    {"response_synthesizer:text_qa_template": qa_prompt_tmpl}
)

值得注意的两处实现细节:

  • 提示词内置“逐步思考 + 拒绝作答”约束:要求模型基于上下文 step-by-step 给出简洁回答,不知道就说 I don't know!,可有效抑制 RAG 场景下的幻觉。
  • 提示词注入点:通过 update_prompts({"response_synthesizer:text_qa_template": ...}) 覆盖响应合成器的默认文本问答模板,这也是后续 Opik 评估中 ContextRecall 等指标得以成立的基础——模型必须先忠实引用上下文。

另外,嵌入模型用的是 BAAI/bge-large-en-v1.5,该模型会在首次索引时由 FastEmbed 自动下载。

5.3 会话缓存与流式对话

应用用 Streamlit session_state 做两级缓存:id 区分会话、file_cache会话ID-文件名 为键缓存 query_engine 对象,避免同一文档反复重建索引(见 app.pyapp.py)。

问答部分走流式输出:query_engine.query(prompt) 返回流式响应,主循环逐个读取 streaming_response.response_gen 的分块并实时渲染(见 app.py):

streaming_response = query_engine.query(prompt)
for chunk in streaming_response.response_gen:
    full_response += chunk
    message_placeholder.markdown(full_response + "▌")

界面右上角的 “Clear ↺” 按钮对应 reset_chat():清空消息记录与上下文缓存,并调用 gc.collect() 及时释放内存中的索引(见 app.py)。

在线对比的标准操作是:先选 Llama 4 上传文档提问并记录答案 → 点击 Clear → 切换到 DeepSeek-R1(同一份 PDF 会因缓存命中而跳过重建索引)→ 提出相同问题,从而直观感受两者在回答风格、推理深度上的差异。


六、流水线重构:用 LlamaIndex Workflow 编排 RAG

workflow.py 演示了用 LlamaIndex 新一代 Workflow API 将同一套 RAG 逻辑写成事件驱动流水线的做法。它继承 Workflow 基类,定义了一个携带检索结果的 RetrieverEvent

class RetrieverEvent(Event):
    """Result of running retrieval"""
    nodes: list[NodeWithScore]

三个 @step 装饰的步骤构成了完整的处理链(见 workflow.py):

步骤 输入事件 职责 关键调用
ingest StartEvent(dirname) 从目录加载文档并建索引 SimpleDirectoryReader(...)VectorStoreIndex.from_documents()
retrieve StartEvent(query, index) 向量检索 Top-K 文档 index.as_retriever(similarity_top_k=2)aretrieve(query)
synthesize RetrieverEvent(nodes) 基于检索节点生成流式答案 CompactAndRefine(streaming=True)asynthesize()

关键点在于检索与索引对两个模型完全共享:无论最终选择 Llama 4 还是 DeepSeek-R1,RAGWorkflow 只在初始化时通过 GROQ_MODELS 决定 Groq 实例,retrievesynthesize 两步骤的逻辑完全一致,从源码结构可以推断这正是为了保证“对比只看模型差异”。

其中 retrieve 步骤还通过 await ctx.set("query", query) 把查询词写入 Workflow 上下文,供后续 synthesizectx.get("query") 取回——这是 LlamaIndex Workflow 在步骤间传递数据的标准用法。

文件末尾的 main() 给出了端到端示例:初始化 Llama 4 工作流 → ingest_documents("data") 索引 data/DeepSeek.pdf → 提问 "How was DeepSeekR1 trained?" 并逐块流式打印答案(见 workflow.py):

python workflow.py

七、离线评测:用 Opik 给两个模型打 RAG 分数

在线问答只能给人看“体感”,真正可复现的结论要靠 evaluation.ipynb 这套离线评测。评测流程分为五步。

7.1 配置 Opik 与接入 LlamaIndex 追踪

首先初始化 Opik 并接入 LlamaIndex 的回调机制,让每次 RAG 查询自动上报成可观测 Trace

import opik
opik.configure(use_local=False)   # 关闭本地模式,上报至 Opik 云端项目

from llama_index.core import Settings
from llama_index.core.callbacks import CallbackManager
from opik.integrations.llama_index import LlamaIndexCallbackHandler

opik_callback_handler = LlamaIndexCallbackHandler()
Settings.callback_manager = CallbackManager([opik_callback_handler])

LlamaIndexCallbackHandler 会自动把 LlamaIndex 的检索、合成等操作全部记录到 Opik,方便事后在 Trace 视图里排查“是检索没召回,还是模型答错”。

7.2 建立评测数据集

评测数据来自 eval-data/test.csv,它包含三列:Question(问题)、Answer(标准答案)、Context(支持答案的原文片段)。Notebook 把 CSV 转成 Opik 数据集所需的 input / expected_output / context 结构:

client = Opik()
dataset = client.get_or_create_dataset(name="Test dataset")

df = pd.read_csv("./eval-data/test.csv")
qa_pairs = [
    {"input": row["Question"], "expected_output": row["Answer"], "context": row["Context"]}
    for _, row in df.iterrows()
]
# dataset.insert(qa_pairs)   # 首次创建数据集时取消注释

其中一条样例数据(内容为 Paul Graham 自传的阅读理解):

{
 'input': 'What was the very first programming language Paul Graham used when he began learning to program on the IBM 1401?',
 'expected_output': 'He used an early version of Fortran on the IBM 1401.',
 'context': 'The language we used was an early version of Fortran. ...'
}

知识库语料则是 eval-data/paul_graham/paul_graham_essay.txt(Paul Graham 长文 “What I Worked On”)。注意:评测阶段的嵌入模型换成了 nomic-ai/nomic-embed-text-v1,与 app.py 在线应用的 BAAI/bge-large-en-v1.5 不同——这是为了说明“评测环境与线上环境可以各自独立调优”,但在对比两模型时,嵌入与语料必须全程一致才能保证公平。

7.3 封装被测任务并切换参赛模型

被测对象被包装成 Opik 可调度的 evaluation_task,其中用 @track 装饰以生成独立 Trace:

from opik import track

@track
def my_llm_application(input: str) -> str:
    response = query_engine.query(input)
    return str(response)

def evaluation_task(x):
    return {"output": my_llm_application(x['input'])}

而“谁上场”由 model_name 决定:

model_name = 'Llama-4'
# model_name = 'DeepSeek-R1'   # 换人时切换这行
llm = load_llm(model_name)     # Groq(...) 封装
Settings.llm = llm

这意味着跑完全部 5 道题后,把 model_name 改成另一个模型再执行一次 evaluate(),就会得到两份使用完全相同数据集、相同指标、不同候选模型的实验结果,二者可直接对齐比较。

7.4 定义评分指标并执行

评测使用 Opik 提供的四个经典 RAG 指标(见 opik.evaluation.metrics):

from opik.evaluation.metrics import (
    Hallucination,        # 幻觉:答案是否偏离给定上下文
    AnswerRelevance,      # 答案相关性:是否切题
    ContextPrecision,     # 上下文精度:检索片段中相关部分占比
    ContextRecall         # 上下文召回:标准答案所需信息是否被检索到
)

执行评测时,把四个指标一次性传入,并用 experiment_config 指定裁判模型为 GPT-3.5-turbo:

from opik.evaluation import evaluate

evaluation = evaluate(
    dataset=dataset,
    task=evaluation_task,
    experiment_name=model_name,            # 用模型名区分两场实验
    scoring_metrics=[hallucination_metric, answer_relevance_metric,
                     context_precision_metric, context_recall_metric],
    experiment_config={"model": "gpt-3.5-turbo"}
)

实验结束后可在 Opik 平台对比两场 experiment 的指标均值,形成类似“Llama-4 幻觉率 vs DeepSeek-R1 幻觉率”的可量化结论。

7.5 限流与重试:一个来自 Notebook 的真实提醒

evaluation.ipynb 中完整保留了实际运行遇到的 Groq 429 Rate Limit 错误记录:评测 Llama 4 时提示 tokens per minute (TPM): Limit 6000, Used 11451,多次重试后仍失败。这提供了一个非常真实的生产经验:

  • Opik 的评测默认用线程池并发执行任务(代码路径为 ThreadPoolExecutor + task_threads 参数),多个请求同时涌向 Groq 很容易撞上 TPM 限额;
  • Opik 检测到限流后会提示:“We recommend reducing the amount of parallel requests by setting task_threads evaluation parameter to a smaller number”;
  • 因此,执行本评测时应控制并发度(例如在 evaluate() 中显式调小 task_threads),并为上游 API 预留足够的 token 额度,必要时对两个模型分批串行评测。

八、评测公平性与注意事项总结

把这套“模型对比”工程跑出可信结论,需要注意几点:

  1. 变量隔离:切换模型时,索引、嵌入模型、语料、测试集与评分指标一律保持不变,只改 load_llm / GROQ_MODELS 中映射的模型 ID;
  2. 双 Key 就绪GROQ_API_KEY 负责生成候选回答,OPENAI_API_KEY 供 Opik 的裁判模型打分,缺一不可;
  3. .env 正确加载:确保在项目根目录运行(load_dotenv() 默认查找当前目录的 .env),或直接在 Streamlit 侧边栏输入 Key;
  4. 规避限流:降低 task_threads、错峰运行,避免 429 中断整场评测;
  5. 入口区分:想要人工体验上传文档问答用 streamlit run app.py;想要批量量化打分则在 Jupyter 中顺序跑通 evaluation.ipynb 并为两个模型各执行一次 evaluate();想要复用事件驱动架构则在 workflow.py 基础上扩展自己的步骤。
# 一键复现三件套
uv sync                        # 1. 同步依赖
streamlit run app.py           # 2. 在线双模型问答(需 GROQ_API_KEY)
python workflow.py             # 3. 流水线示例(索引 data/DeepSeek.pdf 后提问)

通过这套工程,你既可以把任意“文档问答 + 模型选型”的需求落地为可运行应用,也能把它沉淀成可重复执行的评测基线——这正是 RAG 应用中“换模型到底值不值”最务实的回答方式。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
899
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
532
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
521
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
392