///|
pub fn parse_eval_jsonl(input : String) -> Result[Array[EvalCase], String] {
let lines = split_jsonl_lines(input)
let out : Array[EvalCase] = []
for i in 0.. v
Err(_) => return Err("invalid_jsonl_line:\{line_no}")
}
let row = match validate_eval_case(value, line_no) {
Ok(v) => v
Err(message) => return Err(message)
}
out.push(row)
}
Ok(out)
}
///|
fn validate_eval_case(input : Json, line_no : Int) -> Result[EvalCase, String] {
let row = match input {
Json::Object(v) => v
_ => return Err("line_\{line_no}:object_required")
}
let id = match json_required_string(row, "id", line_no) {
Ok(v) => v
Err(message) => return Err(message)
}
let prompt = match json_required_string(row, "prompt", line_no) {
Ok(v) => v
Err(message) => return Err(message)
}
let query = match json_required_string(row, "query", line_no) {
Ok(v) => v
Err(message) => return Err(message)
}
let expected = match json_required_string(row, "expected", line_no) {
Ok(v) => v
Err(message) => return Err(message)
}
let metric = match row.get("metric") {
None => Exact
Some(Json::String(v)) =>
if v == "exact" {
Exact
} else if v == "contains" {
Contains
} else {
return Err("line_\{line_no}:metric_invalid")
}
Some(_) => return Err("line_\{line_no}:metric_invalid")
}
let tags = match row.get("tags") {
None => []
Some(Json::Array(values)) => {
let out : Array[String] = []
for value in values {
match value {
Json::String(v) => out.push(v)
_ => return Err("line_\{line_no}:tags_invalid")
}
}
out
}
Some(_) => return Err("line_\{line_no}:tags_invalid")
}
let budget = match row.get("budget") {
None => None
Some(Json::Object(v)) =>
match parse_budget_patch(v, line_no) {
Ok(parsed) => Some(parsed)
Err(message) => return Err(message)
}
Some(_) => return Err("line_\{line_no}:budget_invalid")
}
Ok({ id, prompt, query, expected, metric, tags, budget })
}
///|
fn parse_budget_patch(
row : Map[String, Json],
line_no : Int,
) -> Result[EvalBudgetPatch, String] {
let max_steps = json_optional_int(row, "maxSteps")
let max_sub_calls = json_optional_int(row, "maxSubCalls")
let max_depth = json_optional_int(row, "maxDepth")
let max_prompt_read_chars = json_optional_int(row, "maxPromptReadChars")
if (row.contains("maxSteps") && max_steps == None) ||
(row.contains("maxSubCalls") && max_sub_calls == None) ||
(row.contains("maxDepth") && max_depth == None) ||
(row.contains("maxPromptReadChars") && max_prompt_read_chars == None) {
return Err("line_\{line_no}:budget_invalid")
}
Ok({ max_steps, max_sub_calls, max_depth, max_prompt_read_chars })
}
///|
fn split_jsonl_lines(input : String) -> Array[String] {
let out : Array[String] = []
for row in input.split("\n") {
let line = row.to_string()
if line.has_suffix("\r") {
out.push(line.unsafe_substring(start=0, end=line.length() - 1))
} else {
out.push(line)
}
}
out
}
///|
fn json_required_string(
row : Map[String, Json],
key : String,
line_no : Int,
) -> Result[String, String] {
match row.get(key) {
Some(Json::String(v)) => Ok(v)
_ => Err("line_\{line_no}:\{key}_must_be_string")
}
}
///|
fn json_optional_int(row : Map[String, Json], key : String) -> Int? {
match row.get(key) {
Some(Json::Number(v, ..)) => Some(v.to_int())
Some(Json::String(v)) =>
match parse_jsonl_number(v) {
Some(n) => Some(n.to_int())
None => None
}
_ => None
}
}
///|
fn parse_jsonl_number(input : String) -> Double? {
let trimmed = input.trim().to_string()
if trimmed == "" {
return None
}
let parsed = try? @json.parse(trimmed)
match parsed {
Ok(Json::Number(v, ..)) => Some(v)
_ => None
}
}