首页
/ 用 RAG 对比评测 OpenAI o3 与 Claude 3.7 Sonnet:基于 GitHub 代码检索的端到端评测与可观测性实战

用 RAG 对比评测 OpenAI o3 与 Claude 3.7 Sonnet:基于 GitHub 代码检索的端到端评测与可观测性实战

2026-09-08 21:29:36作者:柯茵沙

导读

本项目以"对 GitHub 开源仓库做 RAG 检索增强问答"为统一基准,让 OpenAI o3-mini 与 Claude 3.7 Sonnet 两款模型回答同一批代码生成类问题,并借助 CometML Opik 构建从链路追踪(Tracing)到自动化评测(Evaluation)的端到端可观测流水线。读完本文,你将掌握:如何用 LlamaIndex 将 GitHub 仓库切分成可检索的代码节点、如何搭建可切换模型的 Streamlit 问答界面,以及如何用 Opik 的 LLM-as-a-Judge 指标对两个模型的生成结果做量化对比。

一、项目定位:为什么要在"代码 RAG"上做模型对比

o3-vs-claude-code 目录围绕一个核心问题展开:针对真实开源仓库的代码问答场景,OpenAI o3-mini 与 Claude 3.7 Sonnet 谁的生成质量更高? 官方 README 给出了明确定位——"Compare Claude 3.7 Sonnet and OpenAI o3 using RAG over code (GitHub)",同时强调该项目"leveraged CometML Opik to build an e2e evaluation and observability pipeline for a RAG application"(借助 Opik 为 RAG 应用构建端到端评测与可观测性流水线)。

换句话说,它把两件事结合在了一起:

  1. 一个可控的对比实验环境:同一个检索管线、同一份上下文、同一组问题,仅替换底层 LLM,从而把"模型能力差异"从"应用实现差异"中剥离出来;
  2. 一套可复现的评测机制:用带人工标注参考答案(ground truth)的测试集,配合 LLM 裁判打分,量化比较两个模型的输出质量,而不是靠肉眼观感下结论。

二、整体架构与工作流

app.py 与 Opik for LLM evaluation.ipynb 的源码结构看,整个链路可分为四个阶段:

GitHub URL → 克隆仓库 → 按文件类型解析与切分(CodeSplitter / MarkdownNodeParser)
          → 向量化(FastEmbed bge-base-en-v1.5) → 建索引(Qdrant / 内存)
          → 检索(similarity_top_k=4) → 流式问答(o3-mini 或 Claude 3.7 Sonnet)
          → Opik 追踪(LlamaIndexCallbackHandler) → LLM Judge 评测打分
  • 交互阶段由 Streamlit 应用承载(streamlit run app.py);
  • 评测阶段由 Notebook 承载(Opik for LLM evaluation.ipynb),评测数据在 data/test.csv 中。

两个阶段复用同一套解析、切分、索引与查询逻辑,保证了"评测的差异只来自模型本身"这一实验前提。

三、环境准备与安装

1. 获取 API Key

按 README 的说明,运行本项目需要三个密钥:

密钥 用途
Opik API Key 将追踪与评测结果上报到 Opik 平台(使用云端版时必需)
OpenAI API Key 调用 o3-mini 模型,以及评测阶段作为裁判模型(gpt-4o)
Anthropic API Key 调用 Claude 3.7 Sonnet

将这些密钥写入项目根目录的 .env 文件。README 中提示参考 .env.example(当前仓库副本中未包含该文件,但结合源码中的 load_dotenv() 调用可推断,典型的键名形如 OPIK_API_KEYOPENAI_API_KEYANTHROPIC_API_KEYapp.py 第 29 行与 Notebook 均通过 python-dotenv 加载环境变量)。

2. 安装依赖

要求 Python 3.11 或更高版本(Notebook 的元数据中也标注了 Python 3.11.11),README 给出的安装命令如下:

pip install opik llama-index llama-index-agent-openai llama-index-llms-openai llama-index-llms-anthropic --upgrade --quiet

除上述包外,从 app.py 的导入语句还可以看到实际运行还依赖以下组件:

  • streamlit(交互界面)、python-dotenv(环境变量)、nest_asyncio(Notebook 中异步兼容)、qdrant-clientllama-index-vector-stores-qdrant(Qdrant 向量库)、llama-index-embeddings-fastembed(本地嵌入模型)、llama-index-embeddings-huggingface(Notebook 中的备选嵌入方案)。

建议使用虚拟环境安装,避免与系统 Python 环境冲突。

四、运行交互式对比应用

安装完成后,在 o3-vs-claude-code 目录下启动:

streamlit run app.py

启动后在侧边栏可以完成三个关键操作(对应 app.py 第 102-192 行的侧边栏逻辑):

  1. 选择模型:在 OpenAI o3-miniClaude 3.7 Sonnet 之间切换(第 105-108 行的 model_options 字典);
  2. 输入 GitHub 仓库 URL:例如 https://github.com/Lightning-AI/LitServe
  3. 点击 Load:触发克隆、解析、建索引、构建查询引擎的完整流程。

页面顶部标题即为 "Claude 3.7 Sonnet vs OpenAI o3!",并在右侧提供 "Clear ↺" 按钮一键清空会话(对应 reset_chat() 函数)。

五、核心实现剖析:app.py 逐模块解读

1. 模型抽象:一个函数支持双 Provider

@st.cache_resource
def load_llm(model_name, provider="openai"):
    if provider == "anthropic":
        return Anthropic(model=model_name)
    elif provider == "openai":
        return OpenAI(model=model_name)
    else:
        raise ValueError(f"Unsupported provider: {provider}")

app.py 第 32-39 行)

@st.cache_resource 保证同一模型实例在多次交互中复用,避免重复初始化造成的资源浪费。这是实现"同一个 RAG 管线、仅换模型"的关键抽象点——后续只需在 Settings.llm 上覆盖赋值,即可无缝切换推理后端。

2. GitHub URL 解析与仓库克隆

def parse_github_url(url):
    pattern = r"https://github\.com/([^/]+)/([^/]+)"
    match = re.match(pattern, url)
    return match.groups() if match else (None, None)

def validate_owner_repo(owner, repo):
    return bool(owner) and bool(repo)

app.py 第 43-53 行)

URL 解析通过正则提取 ownerrepo,随后调用 git clone 将仓库克隆到当前工作目录(第 130-131 行:若本地目录 ./{repo} 不存在则克隆,已存在则直接复用)。克隆失败或 URL 不合法时,界面会分别提示 "Error occurred while cloning the repository, carefully check the url" 与 "Invalid owner or repository"。

3. 按文件类型解析与切分(代码 RAG 的关键)

def parse_docs_by_file_types(ext, language, input_dir_path):
    files = glob.glob(f"{input_dir_path}/**/*{ext}", recursive=True)
    if len(files) > 0:
        loader = SimpleDirectoryReader(
            input_dir=input_dir_path, required_exts=[ext], recursive=True
        )
        docs = loader.load_data()
        parser = (
            MarkdownNodeParser()
            if ext == ".md"
            else CodeSplitter.from_defaults(language=language)
        )
        nodes = parser.get_nodes_from_documents(docs)
        return nodes
    return []

app.py 第 55-73 行)

应用默认处理五类文件(第 134-140 行):

扩展名 解析器 语言参数
.md MarkdownNodeParser —(按 Markdown 结构切分)
.py CodeSplitter.from_defaults(language="python") python
.ipynb CodeSplitter python
.js CodeSplitter javascript
.ts CodeSplitter typescript

值得说明的是,代码文件与自然语言文档的切分策略完全不同CodeSplitter 会尽量在类、函数等逻辑边界处切分,保留代码的语义完整性;MarkdownNodeParser 则按标题层级组织节点。这正是"对代码仓库做 RAG"区别于普通文档 RAG 的核心技术点。每个扩展名处理完成后会打印 Found N files ... / Processed N nodes ... 日志,便于排查仓库是否有可检索内容。

4. 向量化与索引构建

Settings.embed_model = FastEmbedEmbedding(model_name="BAAI/bge-base-en-v1.5")
try:
    index = create_index(nodes)      # Qdrant 向量库路径
except:
    index = VectorStoreIndex(nodes=nodes)  # 内存索引兜底

app.py 第 150-154 行)

嵌入模型采用 BAAI/bge-base-en-v1.5(通过 FastEmbedEmbedding 本地运行,无需单独部署 Embedding 服务)。索引构建有两条路径:

  • 首选 Qdrantcreate_index()(第 77-86 行)会为每个会话生成一个 uuid4() 唯一集合名(chat_with_docs_{uuid}),通过 QdrantVectorStoreStorageContext 写入向量库,天然支持会话隔离与持久化;
  • 兜底内存索引:从源码结构看,app.py 第 152 行调用 create_index(nodes) 时未传入 client 参数(而函数签名要求该参数),因此该调用会抛出 TypeError 并被 except 捕获,实际大多走 VectorStoreIndex(nodes=nodes) 的内存索引路径;Notebook 中的评测流程则直接使用内存索引。这一点体现了"可插拔存储"的设计:向量库不可用时应用仍可运行。

5. 检索与生成参数

query_engine = index.as_query_engine(streaming=True, similarity_top_k=4)

app.py 第 159 行)

  • streaming=True:开启流式输出,前端逐 token 渲染;
  • similarity_top_k=4:检索阶段取与问题最相似的 4 个节点作为上下文注入 Prompt。

6. 自定义 Prompt 模板

qa_prompt_tmpl_str = (
    "Context information is below.\n"
    "---------------------\n"
    "{context_str}\n"
    "---------------------\n"
    "Given the context information and your knowledge, I want you to think step by step "
    "to answer the query in a crisp manner, incase 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}
)

app.py 第 162-175 行)

该模板要求模型基于上下文"step by step"思考并简洁作答,无法回答时明确输出 I don't know!——这一约束对评测尤其重要,可以抑制模型的幻觉式作答。而 Notebook 中的评测版模板更进一步(见下文第八节),强制要求回答必须包含代码片段,与"代码生成评测"的定位完全对齐。

7. 流式聊天界面

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

app.py 第 231-239 行)

用户输入通过 st.chat_input 捕获,追加到 st.session_state.messages 维护会话历史;回答期间用 光标模拟打字效果,完成后将完整回答写回历史记录。reset_chat() 则负责清空 messagescontext 并触发 gc.collect() 释放内存。

六、评测数据集:data/test.csv

评测样本存放在 data/test.csv,采用 question,answer 两列结构:question 是待评测问题,answer 是人工编写/认可的参考答案(ground truth)。从数据内容看,测试集围绕 LitServe 框架(Lightning AI 的推理服务库)设计,覆盖了五种典型场景:

  1. SimpleLitAPI:输入一个数,返回平方与立方的和(server.py 完整示例);
  2. 文本嵌入 API:基于 SentenceTransformer(BAAI/bge-large-en-v1.5)+ LitServe 构建;
  3. RAG API:LlamaIndex + Qdrant 向量库 + Ollama 本地 llama3.2 的组合;
  4. Whisper 私有 API:将 OpenAI Whisper(large 模型、GPU 加速)封装为推理服务;
  5. 随机森林部署:加载 model.pkl 并封装为分类 API。

设计精巧之处在于:所有问题都是"描述性需求 + 具体约束"(如指定模型、端口、批大小、加速器等),答案要求产出完整可运行的代码。这种设定对模型的能力区分度很高——不仅能检验"是否理解需求",还能检验"是否产出符合约束的语法正确代码",天然适配 LLM-as-a-Judge 的自动打分。

七、用 Opik 构建端到端可观测性

1. 初始化与配置

import opik
opik.configure(use_local=False)

(Notebook 第一个代码单元)

use_local=False 表示使用 Opik 云端(需在 .env 中配置 Opik API Key);若要本地部署 Opik 服务,可改为 use_local=True 并指向本地地址。

2. 追踪 RAG 全链路调用

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 内部所有操作(文档加载、节点切分、embedding、检索、LLM 调用、响应合成)以 Trace/Span 的形式上报到 Opik。开发者无需在业务代码中埋点,就能在 Opik 平台看到一次问答从"检索到哪 4 个节点"到"模型生成了什么"的完整调用链,极大降低了 RAG 应用的调试成本。

3. 在评测中复用同一套 RAG 管线

Notebook 中的 setup_chat_engine(github_url, model_provider=...) 与 app.py 高度同构:解析 URL → 克隆 → 按文件类型切分 → 建索引 → 配置 LLM → 构建 streaming=True, similarity_top_k=4 的查询引擎。值得注意的两点差异:

  • 支持 Claude 3.5 Sonnetclaude-3-5-sonnet-20240620)作为第三个可选项;
  • 评测版 Prompt 强制要求代码输出:
Given the context information above, you must always include a code snippet in your response.
Think step by step to answer the query, and then provide a relevant code example that demonstrates the concept.
...
If you don't know the answer, say 'I don't know!' but still provide a minimal code example of what you think might work.

Notebook 示例中以 https://github.com/Lightning-AI/LitServe 作为评测仓库(与 test.csv 的题目主题一致),调用方式为:

model_name = 'Claude 3.7 Sonnet'
query_engine = setup_chat_engine(github_url, model_provider=model_name)
response = query_engine.query("What is this repo about?")

八、LLM-as-a-Judge:自动化评测实现

1. 创建评测数据集

from opik import Opik
client = Opik()
dataset = client.get_or_create_dataset(name="Eval Code Generation")

评测前需要把 test.csv 中的问答对导入该数据集(Notebook 已创建名为 Eval Code Generation 的数据集)。

2. 包装评测任务

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'])}

@track 装饰器让每次评测调用也被记录到 Opik,实现"评测本身可观测"。

3. 自定义 LLM Judge 指标

Notebook 定义了一个完整的 LLMJudgeMetric 类(继承 opik.evaluation.metrics.base_metric.BaseMetric),核心逻辑如下:

  • 裁判模型:默认 gpt-4o(通过 openai 客户端调用);
  • 打分维度:正确性(是否实现相同功能)、完整性(是否包含必要组件)、效率(实现方式是否同样高效);并明确"只要功能等价就给高分"、"只关注代码与功能,忽略文本";
  • 输出格式:强制要求裁判返回严格的 JSON:{"score": <0~1之间的数>},其中 0 表示完全不相关/错误,1 表示与参考答案功能等价;
  • 落库:通过 score_result.ScoreResult(name=..., value=...) 将分数封装为 Opik 指标。
code_quality_metric = LLMJudgeMetric()

4. 运行评测实验

from opik.evaluation import evaluate

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

这就是整个对比实验的"收敛点":为每个模型各跑一次 evaluateexperiment_name 分别设为 "OpenAI o3-mini" 与 "Claude 3.7 Sonnet",即可在 Opik 平台同一数据集上并排对比两个模型的平均分、逐样本得分分布与失败案例。experiment_config 记录实验元信息(如推理模型),保证实验可复现、可追溯。

九、对比实验的设计要点与落地建议

综合整个项目,这套"代码 RAG + 双模型对比 + Opik 评测"的方案在工程上有几个值得借鉴的设计决策:

  1. 控制变量:解析、切分、嵌入、检索、Prompt 全部固定,唯一变量是底层 LLM,使得分差异能归因到模型本身;
  2. 面向代码的评测口径:测试题要求产出完整可运行代码,裁判按"功能等价性"而非"文本相似度"打分,避免传统 ROUGE/BLEU 在代码场景的失效问题;
  3. 评测与交互解耦:交互看效果用 Streamlit(app.py),批量量化用 Notebook(Opik for LLM evaluation.ipynb),两者共享核心管线代码,可独立演进;
  4. 可观测性贯穿始终:追踪(Callback Handler)与评测(Judge 指标、实验管理)都收敛到 Opik 单一平台,链路视图 + 得分视图 + 数据集管理一站式完成。

注意事项与前提:本方案依赖 OpenAI、Anthropic 与 Opik 三方服务的 API Key,且需要能访问外网以克隆仓库与下载模型/依赖;嵌入模型与切分参数(similarity_top_k=4)对结果有影响,若要横向对比其他评测集,建议保持这些参数一致并记录在 experiment_config 中。

十、在仓库中继续深入

如果你想进一步研究或复用本方案,可在仓库内继续查看以下文件:

  • o3-vs-claude-code/app.py:Streamlit 交互应用完整实现(模型抽象、仓库解析、代码切分、向量检索、流式问答);
  • o3-vs-claude-code/Opik for LLM evaluation.ipynb:Opik 追踪与 LLM Judge 评测的完整 Notebook(含自定义指标类与 evaluate 调用);
  • o3-vs-claude-code/data/test.csv:评测数据集(问题 + 参考答案),可自行扩充更多代码生成场景;
  • o3-vs-claude-code/data_prep.ipynb:数据准备 Notebook(当前为空,可结合 data/test.csv 自行补充生成逻辑);
  • 仓库中还有多个可交叉参考的项目:deploy-agentic-rag(LitServe 部署 RAG API)、github-rag(本地代码仓库问答)、eval-and-observability(Opik 评测与可观测性专项)等,可与本项目的评测管线相互印证。

通过本项目的实践,你将同时掌握两条硬技能:基于 LlamaIndex 对代码仓库构建 RAG,以及用 Opik 搭建"追踪 + 评测 + 实验对比"的端到端可观测体系——这两者正是当前 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
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
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
391