Packages
Dataset management and caching for AI research benchmarks
Current section
Files
Jump to
Current section
Files
lib/dataset_manager/evaluator/f1.ex
defmodule CrucibleDatasets.Evaluator.F1 do
@moduledoc """
Token-level F1 score (precision and recall).
Used for text generation where partial matches are meaningful.
Computes F1 based on overlap of tokens between predicted and expected.
"""
@doc """
Compute F1 score between predicted and expected text.
Returns value between 0.0 and 1.0.
"""
def compute(predicted, expected) when is_binary(predicted) and is_binary(expected) do
predicted_tokens = tokenize(predicted)
expected_tokens = tokenize(expected)
common = MapSet.intersection(predicted_tokens, expected_tokens)
common_count = MapSet.size(common)
if common_count == 0 do
0.0
else
pred_size = MapSet.size(predicted_tokens)
exp_size = MapSet.size(expected_tokens)
if pred_size == 0 or exp_size == 0 do
0.0
else
precision = common_count / pred_size
recall = common_count / exp_size
if precision + recall == 0 do
0.0
else
2 * (precision * recall) / (precision + recall)
end
end
end
end
# Handle map inputs (extract answer field)
def compute(predicted, expected) when is_map(predicted) and is_map(expected) do
pred_text = extract_text(predicted)
exp_text = extract_text(expected)
compute(pred_text, exp_text)
end
def compute(predicted, expected) when is_map(predicted) and is_binary(expected) do
pred_text = extract_text(predicted)
compute(pred_text, expected)
end
def compute(predicted, expected) when is_binary(predicted) and is_map(expected) do
exp_text = extract_text(expected)
compute(predicted, exp_text)
end
# For non-string types, convert to string first
def compute(predicted, expected) when is_map(predicted) or is_map(expected) do
pred_str = if is_map(predicted), do: inspect(predicted), else: to_string(predicted)
exp_str = if is_map(expected), do: inspect(expected), else: to_string(expected)
compute(pred_str, exp_str)
end
def compute(predicted, expected) do
pred_str = to_string(predicted)
exp_str = to_string(expected)
compute(pred_str, exp_str)
end
# Tokenize text into set of normalized tokens
defp tokenize(text) when is_binary(text) do
text
|> String.downcase()
|> String.replace(~r/[^\w\s]/, " ")
|> String.split(~r/\s+/, trim: true)
|> MapSet.new()
end
# Extract text from map (check common answer fields)
defp extract_text(map) when is_map(map) do
Map.get(map, :answer) || Map.get(map, "answer") ||
Map.get(map, :text) || Map.get(map, "text") ||
Map.get(map, :response) || Map.get(map, "response") ||
to_string(map)
end
end