用 Opik 公平评测代码生成大模型:Code Model Comparison 实战指南
本指南围绕
ai-engineering-hub仓库中的 code-model-comparison 应用展开。该应用面向"代码生成大模型选型"这一真实需求:把 GitHub 仓库代码作为上下文,让两款前沿模型并行、流式生成同一段代码,再用 Opik 的 G-Eval 自动化指标对"正确性 / 可读性 / 最佳实践"三维度打分并可视化对比。读完本文,你将掌握从仓库摄入、双模型并行对比到基于 LLM-as-a-Judge 的代码质量评估全流程的实现思路,并能直接在本地运行这套工具。
技术栈与模块全景
code-model-comparison 是一个基于 Streamlit 的对比评测应用,其技术分工十分清晰,README 与 pyproject.toml 中声明的依赖一一对应:
| 技术 | 在项目中的职责 | 对应源码 |
|---|---|---|
| Streamlit | Web UI、会话状态管理、双列展示 | app.py |
| LiteLLM | 统一调用多家模型(OpenRouter 路由),流式异步补全 | model_service.py |
| Gitingest | 将 GitHub 仓库整体摄取为文本上下文 | code_ingestion.py |
| Opik | 可观测性平台 + G-Eval(LLM-as-a-Judge)评估指标 | code_evaluation_opik.py |
| Plotly / Pandas | 评测结果的条形图与明细表渲染 | app.py |
整个数据流为:用户在侧边栏填入 GitHub 仓库 URL → Gitingest 摄取仓库 → 同一 prompt 分发给两个模型流式生成 → 两份代码并行送入 Opik G-Eval → 得分可视化对比。四个模块各司其职,模块边界清晰,便于替换模型供应商或评估策略。
环境准备与安装
项目要求 Python 3.12 或更高版本(见 pyproject.toml 中的 requires-python = ">=3.12"),依赖通过 uv 统一管理:
uv sync
pyproject.toml 声明了核心依赖:litellm>=1.71.1(模型编排)、opik>=1.8.13(评估)、gitingest>=0.1.4(代码摄取)、streamlit>=1.45.1(UI),以及 anthropic、plotly、pandas、python-dotenv 等支撑库。
密钥与 Opik 配置
复制 .env.example 为 .env 并填写密钥。仓库中的 .env.example 内容如下:
OPENROUTER_API_KEY=
OPENAI_API_KEY=
OPENROUTER_API_KEY:模型统一走 OpenRouter 网关调用,这是运行所必需的。若缺失或无效,model_service.py 会给出 "Invalid or missing API key" 之类的明确报错。OPENAI_API_KEY:为评测阶段的评估大模型预留(评估打分由 LLM 作为评判者完成)。
Opik 的凭据不是写在 .env,而是放在仓库根目录的 .opik.config 中(README 中"Look for the .opik.config file in the root directory"所指即此文件):
[opik]
url_override = https://www.comet.com/opik/api/
workspace =
project_name = Code Evaluation
api_key =
其中 url_override 默认指向 Comet Opik 云端 API 端点,project_name 默认为 Code Evaluation(评测运行会归档到该 Opik 项目),请将 api_key 和 workspace 填为你在 Opik/Comet 控制台的实际值。若使用自托管 Opik,将 url_override 改为自建服务地址即可。
启动与使用流程
配置完成后启动:
streamlit run app.py
七个核心操作步骤
README 给出的使用流程可直接对应到 app.py 的界面实现:
- 选择待对比的模型:页面上方两列下拉框各选一个模型(Model 1 / Model 2)。可选项来自 model_service.py 的
AVAILABLE_MODELS字典。 - 输入 GitHub 仓库 URL:位于左侧边栏的 "GitHub Repository URL" 文本框。
- 点击 "Ingest Repository" 摄取仓库:成功后把返回的上下文写入
st.session_state.context。 - 在聊天框输入代码生成 prompt。
- 并排查看两模型生成的代码:两个模型各自以流式输出渲染在左右两栏。
- 点击 "Evaluate Code" 触发评估:两个模型的生成代码会分别进入 Opik 评测管线。
- 查看对比指标:页面底部展示双模型的分维度条形图与明细表。
有参考答案(Ground Truth)时的对比
侧边栏还提供了一个可选输入 "Reference Code":当你手里有期望实现的"标准答案"代码时填入其中,评估时会将其作为 EXPECTED_CODE 一并送入评判器,使正确性打分更有依据。这一能力在 code_evaluation_opik.py 中实现——上下文由 ACTUAL_CODE(模型生成代码)和可选的 EXPECTED_CODE(参考代码)拼接而成。
支持哪些模型:OpenRouter 统一网关
model_service.py 中的 AVAILABLE_MODELS 定义了默认候选集,展示名与 OpenRouter 上的真实模型 ID 通过映射关系解耦:
AVAILABLE_MODELS = {
"Claude Sonnet 4": "openrouter/anthropic/claude-sonnet-4",
"Qwen3-Coder": "openrouter/qwen/qwen3-coder",
"Gemini 2.5 Flash": "openrouter/google/gemini-2.5-flash",
"GPT-4.1": "openrouter/openai/gpt-4.1",
}
四个名字即 UI 下拉框中的选项,值即传给 LiteLLM 的 model 参数。要调整或扩充候选模型,只需修改此字典即可(注意:需保证对应模型确实在 OpenRouter 上开放)。get_model_mapping() 会校验传入的展示名,非法名称会抛出 ValueError 并给出友好提示。
底层调用参数
get_model_response_async 是核心生成函数(见 model_service.py),它使用 LiteLLM 的异步接口:
response = await acompletion(
model=model_mapping,
messages=messages,
api_key=os.getenv("OPENROUTER_API_KEY"),
max_tokens=2000,
stream=True,
)
几个关键设计值得注意:
max_tokens=2000:限定单次生成的 token 上限,防止长代码输出失控;stream=True+ 异步遍历 chunk:逐块产出内容,UI 得以实现打字机式实时渲染;- 错误分级处理:根据异常文本特征区分"密钥缺失/鉴权失败""配额或限流""模型不可用"三种常见故障,并向用户展示针对性提示。
并行双模型流式生成的实现原理
为保证对比公平,两个模型接收完全相同的系统指令与用户 query。系统提示在 model_service.py 中固定拼装,要求模型:严格沿用仓库既有模式与命名约定、保持风格一致、写清 docstring、确保与现有组件无缝集成、只输出代码不加解释。
生成阶段的关键在于 get_parallel_responses 返回两个异步生成器,随后由 app.py 通过 await get_parallel_responses(...) 取得,再用 asyncio.gather 并发消费两个流(见 app.py 的 process_model1_stream / process_model2_stream 与 asyncio.gather 调用),实现"同一 prompt、同一时刻、左右并排"的公平对比。
流式渲染时对输出做了一层清理:逐段去除模型可能包裹的 ```python / ``` 代码围栏,再实时刷新到 st.code 容器中。历史会话、最近一次生成结果、评估结果等均持久化在 st.session_state 中;一旦用户中途更换了任一模型,程序会自动清空旧的生成与评估缓存,避免"新模型 + 旧数据"造成误导(见 app.py)。
代码摄取:用 Gitingest 把仓库变成上下文
code_ingestion.py 是摄入模块,逻辑极简但边界清晰:
from gitingest import ingest
def ingest_github_repo(repo_url: str) -> dict[str, str]:
# 校验 URL 必须是 https://github.com/ 或 http://github.com/ 开头
...
summary, structure, content = ingest(repo_url)
context = {
"summary": summary, # 仓库整体摘要
"structure": structure, # 目录结构树
"content": content # 源码正文(拼接后作为模型上下文)
}
return context
在进入摄取前,它先用前缀校验过滤掉非 GitHub 的 URL;ingest() 产出的三个字段中,content 会被插入到模型系统提示的 "Repository Context" 占位处,让模型"读"到真实的仓库代码风格后再生成本仓库语境下的新代码。此外 app.py 会对摄入结果的类型与 content 字段做防御性校验,结构非法时提示用户重新摄取。
评估机制深度解析:Opik G-Eval 三维度打分
评估模块 code_evaluation_opik.py 是整个项目的灵魂,它基于 Opik 的 GEval 指标(一种 LLM-as-a-Judge / prompt-based 评估方式)实现了三个独立指标。
三个评估维度及判分标准
| 维度 | 评估关注点 | 评估步骤摘要(源码) |
|---|---|---|
| Code Correctness | 功能正确性、边界处理、完整性 | 全部功能是否实现;边界是否覆盖;潜在运行时错误;输出是否符合预期 |
| Code Readability | 命名、格式、文档、结构 | 命名是否清晰一致;格式缩进;注释与 docstring;代码逻辑组织 |
| Best Practices | 异常处理、安全性、效率、模块化 | 异常是否正确捕获;安全实践;性能;功能是否拆分为可复用模块 |
每个指标都是独立的 GEval 实例,通过 task_introduction 设定"专家裁判"角色、通过 evaluation_criteria 给出分步评估指令与打分 rubric,name 用于区分指标(见 code_evaluation_opik.py)。
README 中给出的通用分数解读(0-10 分制)如下,且与源码中各 rubric 文本完全一致:
- 0-2:存在严重问题或代码不可用
- 3-5:基础实现但有明显缺陷
- 6-8:良好实现,存在少量小问题
- 9-10:优秀实现,满足所有标准
以正确性指标为例,其 rubric 原文精确对应这段区间(code_evaluation_opik.py):
Score 0-2: Code is non-functional or has critical errors
Score 3-5: Code works but misses key functionality
Score 6-8: Code is mostly correct with minor issues
Score 9-10: Code is completely correct
打分流程与分数换算
evaluate_code 的执行链路如下:
- 拼接上下文:构造
ACTUAL_CODE(生成代码),若传入参考代码则追加EXPECTED_CODE块; - 依次打分:三个 GEval 指标分别对同一份 context 调用
metric.score(output=context); - 分数换算:Opik 返回 0-1 区间分,源码将其
* 10换算为 0-10 便于人读(code_evaluation_opik.py); - 计算总分:
overall_score = (correctness + readability + best_practices) / 3,即三者的算术平均(README 中"The overall score is calculated as an average of these three metrics"即此实现); - 判定通过阈值:
passed = overall_score >= 7.0,即 70% 通过线(见 code_evaluation_opik.py)。
返回结构是带 schema 的字典:overall_score、detailed_metrics(各维度 score 与 reason)、passed 布尔值;任何异常都会被兜底捕获并返回 error 字段与零分结果,保证上游 UI 不至于崩溃。
评判者模型说明
app.py 的评估结果区域标题注明 "Evaluation results generated with GPT-4o using Opik"(见 app.py),GEval 实例在仓库源码中未显式指定底层 LLM 参数,而是交由 Opik 客户端的环境配置决定评估所用模型——这与 README/.env 要求同时配置 OPENAI_API_KEY、以及 .opik.config 中设置 Opik 凭据的约束相呼应。换言之:OPENROUTER_API_KEY 服务"代码生成",Opik 侧配置的 LLM 服务"代码评判"。
结果可视化:一眼看出差距
评估完成后,app.py 负责渲染结果,主要分三层:
- 数据组装:把两个模型在 Correctness / Readability / Best Practices / Overall 四项上的得分整理成 Pandas DataFrame;
- 分组条形图:用 Plotly Express 的
px.bar(barmode="group")绘制深色主题对比图,双模型不同配色,直观呈现各维度分差; - 明细表:分别列出每个模型的三个维度得分 + 得分理由(reason),其中 reason 是 G-Eval 裁判给出的"判分依据"文本,可追溯为什么得这个分。
渲染前还会对评估结果的嵌套结构做严格校验(是否含 overall_score、三个指标各自的 score 字段),结构不合法时明确报错而不渲染空图,避免静默失败。
工程细节与踩坑提示
综合 README 与源码,运行与扩展时还有几点经验:
- 模型换选即重置:
selected_models变化时会清空last_generated_code与evaluation_results,保证对比结果与当前选中的模型始终一致(app.py); - 必须先摄取再提问:聊天入口会校验
st.session_state.context,未摄入仓库时给出 "Please ingest a GitHub repository first!" 拦截(app.py); - 长代码防溢出:页面级 CSS 对
<pre>/<code>设置了white-space: pre-wrap与自动换行,避免超宽代码块破坏双栏布局(app.py); - 双栏流式刷新:使用
st.empty()占位容器 + 边收边刷,是 Streamlit 下实现"类打字机流式输出"的常用套路; - 扩展新评估维度:参照 code_evaluation_opik.py 的写法新增一个
GEval实例,再在detailed_metrics与 UI 组装处同步补充即可。
小结
code-model-comparison 展示了"代码生成模型评测闭环"的完整工程化样板:用 Gitingest 让模型读懂目标仓库、用 LiteLLM + OpenRouter 统一网关实现多模型对拍、用 Opik G-Eval 以 LLM-as-a-Judge 的方式对正确性/可读性/最佳实践三维度 0-10 打分,并用 Streamlit + Plotly 落地为可交互的双栏对比界面。无论你是要做"Claude vs Qwen vs Gemini"式的模型选型实验,还是想为团队沉淀一套可复用的代码质量评估管线,这个应用的结构都值得直接参考。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00