news 2026/9/9 13:16:39

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Llama 4 与 DeepSeek-R1 的 RAG 对决:基于 LlamaIndex + Opik 的多模型对比评测实战指南

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

【免费下载链接】ai-engineering-hubIn-depth tutorials on LLMs, RAGs and real-world AI agent applications.项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub

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 4meta-llama/llama-4-scout-17b-16e-instructMeta 的开源 Llama 4 系列 MoE 模型,ID 后缀 17b-16e 表明其稀疏专家结构
DeepSeek-R1deepseek-r1-distill-llama-70bDeepSeek-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 会话缓存与流式对话

应用用 Streamlitsession_state做两级缓存:id区分会话、file_cache会话ID-文件名为键缓存query_engine对象,避免同一文档反复重建索引(见 app.py 与 app.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):

步骤输入事件职责关键调用
ingestStartEvent(dirname)从目录加载文档并建索引SimpleDirectoryReader(...)VectorStoreIndex.from_documents()
retrieveStartEvent(query, index)向量检索 Top-K 文档index.as_retriever(similarity_top_k=2)aretrieve(query)
synthesizeRetrieverEvent(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 settingtask_threadsevaluation 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 应用中“换模型到底值不值”最务实的回答方式。

【免费下载链接】ai-engineering-hubIn-depth tutorials on LLMs, RAGs and real-world AI agent applications.项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 13:16:21

SpringBoot匿名系统源码拆解:从自动装配到匿名身份设计

一篇能让你从“看热闹”到“看门道”的源码拆解,刚好适合正在接触SpringBoot、又不想只写增删改查的朋友。这个匿名系统项目,单看名字你可能觉得就是个普通练手demo,但真把它拉开看,里面有SpringBoot的自动装配原理、拦截器与切面…

作者头像 李华
网站建设 2026/9/9 13:15:30

图像去雨实战指南:Derain模型设计、训练与部署全解析

简介:Derain是一份基于Python实现的图像去雨项目资源包,面向图像处理与计算机视觉方向的学习者和开发者,重点解决恶劣天气下拍摄图像因雨滴干扰而清晰度下降的问题。项目围绕雨滴去除任务,涵盖预处理、特征提取、雨滴建模与背景恢…

作者头像 李华
网站建设 2026/9/9 13:13:42

Hy4预览版50小时实测:游戏合集、性能表现与免费体验全解析

1. 这个Hy4预览版到底是什么1.1 一句话说清核心定位先说结论:Hy4是眼下讨论度挺高的一个游戏平台预览版本,官方这次不仅把内核开放给玩家提前体验,还顺手整理了一批游戏合集放进来,算是给所有观望的人一个“先尝后买”的机会。和那…

作者头像 李华
网站建设 2026/9/9 13:13:17

不用装插件打开 SWF 文件:Ruffle Flash Player 模拟器上手指南

不用装插件打开 SWF 文件:Ruffle Flash Player 模拟器上手指南 【免费下载链接】ruffle A Flash Player emulator written in Rust 项目地址: https://gitcode.com/GitHub_Trending/ru/ruffle 双击 .swf 文件,提示"未找到打开方式"。下…

作者头像 李华
网站建设 2026/9/9 13:12:41

QSPI接口NAND Flash驱动移植实战:从选型到性能调优

简介:面向STM32F446嵌入式开发者的QSPI NAND闪存驱动实例,基于四线SPI接口实现与多种SPI NAND器件的通信,实测可完成读、写、擦除、识别等基本操作。QSPI相比传统SPI具有更高数据吞吐,适用于音频缓存、图像存储、日志记录等对容量…

作者头像 李华