使用 LangSmith 运行 SWE-bench
SWE-bench 是开发者测试其编码代理最流行(且极具挑战性!)的基准之一。在本教程中,我们将展示如何将 SWE-bench 数据集加载到 LangSmith 中,并轻松在其上运行评估,从而让您比使用现成的 SWE-bench 评估套件获得对代理行为更清晰的洞察。这使得您能够更快地定位特定问题,并快速迭代您的代理以提升性能!
加载数据
为了加载数据,我们将从 Hugging Face 获取 dev 分割,但根据您的使用场景,您可能希望获取 test 或 train 分割之一,如果您想合并多个分割,可以使用 pd.concat。
import pandas as pd
splits = {'dev': 'data/dev-00000-of-00001.parquet', 'test': 'data/test-00000-of-00001.parquet', 'train': 'data/train-00000-of-00001.parquet'}
df = pd.read_parquet("hf://datasets/princeton-nlp/SWE-bench/" + splits["dev"])
编辑“version"
这是一个非常关键的步骤!如果跳过,其余代码将无法运行!
version列包含所有字符串值,但它们均以浮点格式存储,因此在上传CSV以创建LangSmith数据集时会被转换为浮点数。虽然您可以在实验期间将值转换回字符串,但像"0.10"这样的值会出现问题:当被转换为浮点数后得到0.1,而如果将其转换为字符串则会得到"0.1"——这会导致在执行您提出的补丁时出现键错误。
为了解决这个问题,我们需要让 LangSmith 停止尝试将 version 列转换为浮点数。为此,我们可以给每个值添加一个与浮点数不兼容的字符串前缀。然后在评估时根据该前缀进行分割,以获取实际的 version 值。这里我们选择的字符串前缀是 "version:"。
未来将在向 LangSmith 上传 CSV 时添加选择列类型的功能,以避免需要使用此变通方法。
df['version'] = df['version'].apply(lambda x: f"version:{x}")
将数据上传至 LangSmith
另存为 CSV
要将数据上传到 LangSmith,我们首先需要将其保存为 CSV 文件,这可以使用 pandas 提供的to_csv函数来实现。请确保将此文件保存在您容易访问的位置。
df.to_csv("./../SWE-bench.csv",index=False)
手动上传 CSV 到 LangSmith
我们现在准备将 CSV 文件上传到 LangSmith。一旦您进入 LangSmith 网站(smith.langchain.com),请前往左侧导航栏的 Datasets & Testing 选项卡,然后点击右上角的 + New Dataset 按钮。
然后点击顶部的 Upload CSV 按钮,并选择您在上一保存的 CSV 文件。然后,您可以为您的数据集命名和添加描述。
接下来,选择 Key-Value 作为数据集类型。最后进入 Create Schema 部分,并将所有密钥添加为 Input fields。此示例中没有 Output fields,因为我们的评估器不是与参考结果进行比较,而是将在 Docker 容器中运行我们实验的输出,以确保代码确实解决了 PR 问题。
一旦您填写了 Input fields(并让 Output fields 留空!),您就可以点击右上角的蓝色 Create 按钮,您的数据集就会被创建!
编程式上传 CSV 到 LangSmith
或者,您可以使用如下代码块中所示的 SDK 将您的 CSV 文件上传到 LangSmith:
dataset = client.upload_csv(
csv_file="./../SWE-bench-dev.csv",
input_keys=list(df.columns),
output_keys=[],
name="swe-bench-programatic-upload",
description="SWE-bench dataset",
data_type="kv"
)
为更快测试创建数据集拆分
由于在全部示例上运行 SWE-bench 评估器耗时较长,您可以创建一个“测试”拆分集以快速测试评估器和您的代码。阅读 本指南 了解更多关于管理数据集拆分的信息,或观看此简短视频了解操作方法(要到达视频的起始页面,只需点击上述创建的数据集并进入 Examples 标签页):
运行我们的预测函数
在 SWE-bench 上运行评估的方式与您在 LangSmith 上通常运行的大多数评估略有不同,因为我们没有参考输出。因此,我们首先在不运行评估器的情况下生成所有输出(注意 evaluate 调用未设置 evaluators 参数)。在这种情况下,我们返回了一个虚拟的 predict 函数,但您可以在 predict 函数中插入您的智能体逻辑,使其按预期工作。
from langsmith import evaluate
from langsmith import Client
client = Client()
def predict(inputs: dict):
return {"instance_id":inputs['instance_id'],"model_patch":"None","model_name_or_path":"test-model"}
result = evaluate(
predict,
data=client.list_examples(dataset_id="a9bffcdf-1dfe-4aef-8805-8806f0110067",splits=["test"]),
)
查看实验 'perfect-lip-22' 的评估结果: https://smith.langchain.com/o/ebbaf2eb-769b-4505-aca2-d11de10372a4/datasets/a9bffcdf-1dfe-4aef-8805-8806f0110067/compare?selectedSessions=182de5dc-fc9d-4065-a3e1-34527f952fd8
3it [00:00, 24.48it/s]
使用 SWE-bench 评估我们的预测
现在我们可以运行以下代码,在 Docker 中执行我们上面生成的预测补丁。此代码对 SWE-bench run_evaluation.py 文件进行了轻微修改。
基本上,该代码用于设置 Docker 镜像以并行运行预测任务,从而大幅缩短评估所需的时间。此截图解释了 SWE-bench 在底层进行评估的基本原理。若要全面理解其机制,请务必阅读 GitHub 仓库 中的相关代码。

函数 convert_runs_to_langsmith_feedback 将 docker 文件生成的日志转换为格式优美的 .json 文件,其中包含以 LangSmith 典型 key/score 方法呈现的反馈。
from swebench.harness.run_evaluation import run_instances
import resource
import docker
from swebench.harness.docker_utils import list_images, clean_images
from swebench.harness.docker_build import build_env_images
from pathlib import Path
import json
import os
RUN_EVALUATION_LOG_DIR = Path("logs/run_evaluation")
LANGSMITH_EVALUATION_DIR = './langsmith_feedback/feedback.json'
def convert_runs_to_langsmith_feedback(
predictions: dict,
full_dataset: list,
run_id: str
) -> float:
"""
Convert logs from docker containers into LangSmith feedback.
Args:
predictions (dict): Predictions dict generated by the model
full_dataset (list): List of all instances
run_id (str): Run ID
"""
feedback_for_all_instances = {}
for instance in full_dataset:
feedback_for_instance = []
instance_id = instance['instance_id']
prediction = predictions[instance_id]
if prediction.get("model_patch", None) in ["", None]:
# Prediction returned an empty patch
feedback_for_all_instances[prediction['run_id']] = [{"key":"non-empty-patch","score":0},
{"key":"completed-patch","score":0},
{"key":"resolved-patch","score":0}]
continue
feedback_for_instance.append({"key":"non-empty-patch","score":1})
report_file = (
RUN_EVALUATION_LOG_DIR
/ run_id
/ prediction["model_name_or_path"].replace("/", "__")
/ prediction['instance_id']
/ "report.json"
)
if report_file.exists():
# If report file exists, then the instance has been run
feedback_for_instance.append({"key":"completed-patch","score":1})
report = json.loads(report_file.read_text())
# Check if instance actually resolved the PR
if report[instance_id]["resolved"]:
feedback_for_instance.append({"key":"resolved-patch","score":1})
else:
feedback_for_instance.append({"key":"resolved-patch","score":0})
else:
# The instance did not run successfully
feedback_for_instance += [{"key":"completed-patch","score":0},{"key":"resolved-patch","score":0}]
feedback_for_all_instances[prediction['run_id']] = feedback_for_instance
os.makedirs(os.path.dirname(LANGSMITH_EVALUATION_DIR), exist_ok=True)
with open(LANGSMITH_EVALUATION_DIR, 'w') as json_file:
json.dump(feedback_for_all_instances, json_file)
def evaluate_predictions(
dataset: list,
predictions: list,
max_workers: int,
force_rebuild: bool,
cache_level: str,
clean: bool,
open_file_limit: int,
run_id: str,
timeout: int,
):
"""
Run evaluation harness for the given dataset and predictions.
"""
# set open file limit
assert len(run_id) > 0, "Run ID must be provided"
resource.setrlimit(resource.RLIMIT_NOFILE, (open_file_limit, open_file_limit))
client = docker.from_env()
existing_images = list_images(client)
print(f"Running {len(dataset)} unevaluated instances...")
# build environment images + run instances
build_env_images(client, dataset, force_rebuild, max_workers)
run_instances(predictions, dataset, cache_level, clean, force_rebuild, max_workers, run_id, timeout)
# clean images + make final report
clean_images(client, existing_images, cache_level, clean)
convert_runs_to_langsmith_feedback(predictions,dataset,run_id)
dataset = []
predictions = {}
for res in result:
predictions[res['run'].outputs['instance_id']] = {**res['run'].outputs,**{"run_id":str(res['run'].id)}}
dataset.append(res['run'].inputs['inputs'])
for d in dataset:
d['version'] = d['version'].split(":")[1]
evaluate_predictions(dataset,predictions,max_workers=8,force_rebuild=False,cache_level="env",clean=False \
,open_file_limit=4096,run_id="test",timeout=1_800)
Running 3 unevaluated instances...
Base image sweb.base.arm64:latest already exists, skipping build.
Base images built successfully.
Total environment images to build: 2
Building environment images: 100%|██████████| 2/2 [00:47<00:00, 23.94s/it]
All environment images built successfully.
Running 3 instances...
0%| | 0/3 [00:00<?, ?it/s]
Evaluation error for sqlfluff__sqlfluff-884: >>>>> Patch Apply Failed:
patch unexpectedly ends in middle of line
patch: **** Only garbage was found in the patch input.
Check (logs/run_evaluation/test/test-model/sqlfluff__sqlfluff-884/run_instance.log) for more information.
Evaluation error for sqlfluff__sqlfluff-4151: >>>>> Patch Apply Failed:
patch unexpectedly ends in middle of line
patch: **** Only garbage was found in the patch input.
Check (logs/run_evaluation/test/test-model/sqlfluff__sqlfluff-4151/run_instance.log) for more information.
Evaluation error for sqlfluff__sqlfluff-2849: >>>>> Patch Apply Failed:
patch: **** Only garbage was found in the patch input.
patch unexpectedly ends in middle of line
Check (logs/run_evaluation/test/test-model/sqlfluff__sqlfluff-2849/run_instance.log) for more information.
100%|██████████| 3/3 [00:30<00:00, 10.04s/it]
All instances run.
Cleaning cached images...
Removed 0 images.
向 LangSmith 发送评估
现在,我们可以使用 evaluate_existing 函数将我们的评估反馈发送到 LangSmith。在这种情况下,我们的 evaluate 函数非常简单,因为上面的 convert_runs_to_langsmith_feedback 函数通过将所有反馈保存到一个文件中极大地简化了我们的工作。
from langsmith import evaluate_existing
from langsmith.schemas import Example, Run
def swe_bench_evaluator(run: Run, example: Example):
with open(LANGSMITH_EVALUATION_DIR, 'r') as json_file:
langsmith_eval = json.load(json_file)
return {"results": langsmith_eval[str(run.id)]}
experiment_name = result.experiment_name
evaluate_existing(experiment_name, evaluators=[swe_bench_evaluator])
View the evaluation results for experiment: 'perfect-lip-22' at:
https://smith.langchain.com/o/ebbaf2eb-769b-4505-aca2-d11de10372a4/datasets/a9bffcdf-1dfe-4aef-8805-8806f0110067/compare?selectedSessions=182de5dc-fc9d-4065-a3e1-34527f952fd8
3it [00:01, 1.52it/s]
<ExperimentResults perfect-lip-22>
运行后,我们可以进入数据集的实验选项卡,并检查我们的反馈键是否已正确分配。如果已正确分配,您应该会看到类似以下内容的图像:
