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-groq、llama-index-embeddings-fastembed、llama-index-utils-workflow、llama-index-embeddings-huggingface、llama-index-embeddings-instructor、llama-index-llms-ollama等扩展包(依赖清单见 pyproject.toml);opik >= 1.6.13(评测与可观测);streamlit >= 1.44.1(Web 界面);python-dotenv(环境变量加载,已被锁定在 uv.lock 中);- 要求
requires-python >= 3.12。
在项目目录下同步依赖:
uv syncuv sync会依据pyproject.toml与uv.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)自动读取项目根目录的.env;evaluation.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):
- 输入 Groq API Key(密码框,若留空则回退到环境变量);
- 在下拉框中选择
Llama 4或DeepSeek-R1; - 通过文件上传控件选择
.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):
| 步骤 | 输入事件 | 职责 | 关键调用 |
|---|---|---|---|
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实例,retrieve与synthesize两步骤的逻辑完全一致,从源码结构可以推断这正是为了保证“对比只看模型差异”。
其中retrieve步骤还通过await ctx.set("query", query)把查询词写入 Workflow 上下文,供后续synthesize用ctx.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_threadsevaluation parameter to a smaller number”; - 因此,执行本评测时应控制并发度(例如在
evaluate()中显式调小task_threads),并为上游 API 预留足够的 token 额度,必要时对两个模型分批串行评测。
八、评测公平性与注意事项总结
把这套“模型对比”工程跑出可信结论,需要注意几点:
- 变量隔离:切换模型时,索引、嵌入模型、语料、测试集与评分指标一律保持不变,只改
load_llm/GROQ_MODELS中映射的模型 ID; - 双 Key 就绪:
GROQ_API_KEY负责生成候选回答,OPENAI_API_KEY供 Opik 的裁判模型打分,缺一不可; .env正确加载:确保在项目根目录运行(load_dotenv()默认查找当前目录的.env),或直接在 Streamlit 侧边栏输入 Key;- 规避限流:降低
task_threads、错峰运行,避免 429 中断整场评测; - 入口区分:想要人工体验上传文档问答用
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),仅供参考