Packages
Dataset management and caching for AI research benchmarks
Retired package: Deprecated - Use 0.5.0+
Current section
Files
Jump to
Current section
Files
lib/dataset_manager/loader/human_eval.ex
defmodule CrucibleDatasets.Loader.HumanEval do
@moduledoc """
HumanEval code generation benchmark loader.
HumanEval contains 164 programming problems with function signatures and test cases.
Used to evaluate code generation capabilities.
## HuggingFace Dataset
The official HumanEval dataset is hosted at `openai/openai_humaneval` on HuggingFace.
## Example
{:ok, dataset} = CrucibleDatasets.Loader.HumanEval.load()
{:ok, dataset} = CrucibleDatasets.Loader.HumanEval.load(sample_size: 50)
"""
alias CrucibleDatasets.{Dataset, Source, Format}
@repo_id "openai/openai_humaneval"
@doc """
Load HumanEval dataset from HuggingFace.
## Options
* `:sample_size` - Limit number of items. Default: all (164)
* `:offline` - If true, use synthetic data for testing. Default: false
## Examples
{:ok, dataset} = HumanEval.load()
{:ok, dataset} = HumanEval.load(sample_size: 50)
"""
@spec load(keyword()) :: {:ok, Dataset.t()} | {:error, term()}
def load(opts \\ []) do
# Support both :synthetic and legacy :offline option
synthetic = Keyword.get(opts, :synthetic, Keyword.get(opts, :offline, false))
if synthetic do
load_synthetic(opts)
else
load_from_huggingface(opts)
end
end
# Load from HuggingFace
defp load_from_huggingface(opts) do
sample_size = Keyword.get(opts, :sample_size)
# HumanEval on HuggingFace is stored as parquet
file_path = "openai_humaneval/test-00000-of-00001.parquet"
case Source.HuggingFace.download(@repo_id, file_path, []) do
{:ok, local_path} ->
case parse_humaneval_parquet(local_path, sample_size) do
{:ok, _} = success -> success
{:error, _} -> load_synthetic(opts)
end
{:error, _reason} ->
# Try alternative path
case Source.HuggingFace.download(@repo_id, "data/test-00000-of-00001.parquet", []) do
{:ok, local_path} ->
case parse_humaneval_parquet(local_path, sample_size) do
{:ok, _} = success ->
success
{:error, reason} ->
if Application.get_env(:crucible_datasets, :fallback_to_synthetic, false) do
load_synthetic(opts)
else
{:error, {:parse_failed, reason}}
end
end
{:error, reason} ->
if Application.get_env(:crucible_datasets, :fallback_to_synthetic, false) do
load_synthetic(opts)
else
{:error, {:huggingface_download_failed, reason}}
end
end
end
end
defp parse_humaneval_parquet(path, sample_size) do
case Format.Parquet.parse(path) do
{:ok, rows} ->
items =
rows
|> Enum.with_index()
|> Enum.map(fn {row, idx} ->
task_id = row["task_id"] || row[:task_id] || "HumanEval/#{idx}"
prompt = row["prompt"] || row[:prompt]
canonical = row["canonical_solution"] || row[:canonical_solution]
test_code = row["test"] || row[:test]
entry_point = row["entry_point"] || row[:entry_point]
%{
id: "humaneval_#{idx}",
input: %{
signature: prompt,
tests: test_code,
entry_point: entry_point,
description: extract_description(prompt)
},
expected: canonical,
metadata: %{
task_id: task_id,
difficulty: estimate_difficulty(canonical)
}
}
end)
final_items = if sample_size, do: Enum.take(items, sample_size), else: items
dataset =
Dataset.new(
"humaneval",
"1.0",
final_items,
%{
source: "huggingface:#{@repo_id}",
license: "MIT",
domain: "code_generation",
language: "python"
}
)
{:ok, dataset}
{:error, reason} ->
{:error, {:parse_error, reason}}
end
end
# Load synthetic data for offline testing
defp load_synthetic(opts) do
items = generate_sample_items(opts)
dataset =
Dataset.new(
"humaneval",
"1.0",
items,
%{
source: "synthetic",
license: "MIT",
domain: "code_generation",
language: "python"
}
)
{:ok, dataset}
end
# Generate sample HumanEval items for testing
defp generate_sample_items(opts) do
count = Keyword.get(opts, :sample_size, 10)
problems = [
{"has_close_elements", "list of numbers",
"Check if any two numbers are closer than threshold"},
{"separate_paren_groups", "string", "Separate nested parentheses groups"},
{"truncate_number", "float", "Return decimal part of number"},
{"below_zero", "list of operations", "Check if balance goes below zero"},
{"mean_absolute_deviation", "list of numbers", "Calculate mean absolute deviation"},
{"intersperse", "list and delimiter", "Insert delimiter between elements"},
{"parse_nested_parens", "string", "Parse nested parentheses depth"},
{"filter_by_substring", "list of strings", "Filter strings containing substring"},
{"sum_product", "list of integers", "Return sum and product"},
{"rolling_max", "list of numbers", "Generate rolling maximum"}
]
problems
|> Enum.take(count)
|> Enum.with_index()
|> Enum.map(fn {{name, inputs, description}, idx} ->
%{
id: "humaneval_#{idx}",
input: %{
signature: generate_signature(name, inputs),
tests: generate_tests(name),
entry_point: name,
description: description
},
expected: generate_solution(name),
metadata: %{
task_id: "HumanEval/#{idx}",
difficulty: Enum.random(["easy", "medium", "hard"])
}
}
end)
end
defp generate_signature(name, _inputs) do
"""
def #{name}(numbers: List[float], threshold: float) -> bool:
\"\"\" Check if in given list of numbers, are any two numbers closer to each other than
given threshold.
>>> #{name}([1.0, 2.0, 3.0], 0.5)
False
>>> #{name}([1.0, 2.8, 3.0, 4.0, 5.0, 2.0], 0.3)
True
\"\"\"
"""
end
defp generate_tests(_name) do
"""
def check(candidate):
assert candidate([1.0, 2.0, 3.9, 4.0, 5.0, 2.2], 0.3) == True
assert candidate([1.0, 2.0, 3.9, 4.0, 5.0, 2.2], 0.05) == False
assert candidate([1.0, 2.0, 5.9, 4.0, 5.0], 0.95) == True
assert candidate([1.0, 2.0, 5.9, 4.0, 5.0], 0.8) == False
"""
end
defp generate_solution(_name) do
"""
for idx, elem in enumerate(numbers):
for idx2, elem2 in enumerate(numbers):
if idx != idx2:
distance = abs(elem - elem2)
if distance < threshold:
return True
return False
"""
end
@doc """
Parse HumanEval JSONL format.
"""
def parse_jsonl(content) do
content
|> String.split("\n", trim: true)
|> Enum.with_index()
|> Enum.map(fn {line, idx} ->
case Jason.decode(line) do
{:ok, item} ->
%{
id: "humaneval_#{idx}",
input: %{
signature: item["prompt"],
tests: item["test"],
entry_point: item["entry_point"],
description: item["prompt"] |> extract_description()
},
expected: item["canonical_solution"],
metadata: %{
task_id: item["task_id"],
difficulty: estimate_difficulty(item["canonical_solution"])
}
}
_ ->
nil
end
end)
|> Enum.reject(&is_nil/1)
end
defp extract_description(nil), do: ""
defp extract_description(prompt) do
# Extract docstring from prompt
prompt
|> String.split("\n")
|> Enum.find("", &String.contains?(&1, "\"\"\""))
|> String.trim()
end
defp estimate_difficulty(nil), do: "medium"
defp estimate_difficulty(solution) do
# Simple heuristic: longer solutions are harder
solution_length = String.length(solution)
cond do
solution_length < 100 -> "easy"
solution_length < 300 -> "medium"
true -> "hard"
end
end
end