* feat(topology): typed event time and session for topology inputs Add `eventTime` (epoch seconds, UTC) and `sessionId` to `TopologyDocumentInput`. The extractor fills them from two documented metadata keys: `event_time` (integer epoch seconds, or ISO-8601 `YYYY-MM-DD` / `YYYY-MM-DD[T ]HH:MM[:SS[.frac]][Z|±HH:MM]`, naive datetimes are UTC) takes precedence over the document createdTime; an unparsable value is skipped with a debug log, never an error, and a zero createdTime leaves the field unset. `session_id` is copied verbatim and empty when absent. The keys are only consulted when the extraction loads metadata; otherwise createdTime is the only source. `parseTopologyEventTime` is public for tests and future callers; the calendar math is Hinnant's days_from_civil with range-checked fields. Extraction stats count the event time source and invalid values. Nothing downstream reads the fields yet. Artifacts, codecs and both routing fingerprints are unchanged, which the new test pins. Tests (tests/unit/topology/topology_input_event_time_catch2_test.cpp): parser accept/reject tables, metadata precedence over createdTime, invalid ignored, session_id, includeMetadata=false fallback, and byte-identical snapshot payload plus equal construction and road fingerprints with and without the typed fields. Signed-off-by: trvon <me@trevon.dev> * bench(longmemeval): event_time and session_id metadata, round docs, haystack sidecar Every exported corpus document now carries `metadata.event_time` (ISO-8601, normalized from the LongMemEval `YYYY/MM/DD (Day) HH:MM` haystack date, first-seen date per session) and `metadata.session_id`. Document ids, titles, texts, queries and qrels are unchanged in the default mode. `--round-docs` adds one document per session round (`<sid>__r<n>`): a user turn opens a round and the replies that follow join it; a leading assistant turn is its own round. Round docs share the session's `event_time`/`session_id` and add `round_index`; qrels stay at the session level. `--full-haystack` also writes `haystacks.jsonl`, one line per question with `haystack_session_ids` and the normalized `haystack_dates`, so per-question membership survives the global corpus deduplication (the 2026-10-10 preflight lost it). The converter is split into `convert` / `write_outputs`; `--self-test` runs both on a three-session synthetic fixture without a download and is the regression check for the default output. Signed-off-by: trvon <me@trevon.dev> * bench(retrieval): carry BEIR corpus metadata onto ingested documents `retrieval_quality_bench` ingests the BEIR corpus as a directory, which cannot carry per-file metadata, so `event_time`, `session_id` and `round_index` from corpus.jsonl never reached the topology extractor. `BEIRDocument::metadata` now holds the corpus line's `metadata` object (strings as is, numbers and booleans stringified; both the generic and the SciFact loader read it), and `applyBenchmarkDocumentMetadata` copies it onto the stored documents matched by exact file path through the metadata repository (`ON CONFLICT DO UPDATE`, so warm caches can repeat it). The bench runs it after the ingest wait, for cold and reused corpora alike, and logs how many documents were updated or not found. Tests: loader parses and coerces metadata; apply helper writes the keys, skips documents without metadata, counts missing ones and is idempotent against a real MetadataRepository. The portability allowlist entries for the two touched bench files are re-pointed at their shifted lines; no raw environment read was added. Signed-off-by: trvon <me@trevon.dev> --------- Signed-off-by: trvon <me@trevon.dev>
489 lines
18 KiB
Python
Executable file
489 lines
18 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Convert LongMemEval_S from HuggingFace to BEIR format for yams benchmarking.
|
|
|
|
Downloads the cleaned LongMemEval_S dataset and converts it to BEIR format
|
|
(corpus.jsonl, queries.jsonl, qrels/test.tsv) so the existing
|
|
retrieval_quality_bench can consume it directly.
|
|
|
|
Every corpus document carries a `metadata` object with `event_time` (ISO-8601,
|
|
from the session's haystack date) and `session_id`; the bench copies it onto the
|
|
ingested documents and the topology extractor reads both keys. `--round-docs`
|
|
adds one document per session round (a user turn plus the replies that follow)
|
|
with the same `event_time`/`session_id` plus `round_index`. `--full-haystack`
|
|
also writes `haystacks.jsonl`, one line per question with its haystack session
|
|
ids and dates, so per-question evaluation can scope the shared corpus.
|
|
|
|
Usage:
|
|
uv run --with datasets scripts/prepare_longmemeval_s.py
|
|
uv run --with datasets scripts/prepare_longmemeval_s.py --max-questions 50
|
|
uv run --with datasets scripts/prepare_longmemeval_s.py --full-haystack --round-docs
|
|
python3 scripts/prepare_longmemeval_s.py --self-test
|
|
|
|
Output: ~/.cache/yams/benchmarks/longmemeval_s/
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
import tempfile
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
|
|
# LongMemEval haystack dates look like "2023/05/20 (Sat) 02:21".
|
|
_LME_DATE = re.compile(
|
|
r"^(\d{4})/(\d{2})/(\d{2})(?:\s*\([A-Za-z]+\))?(?:\s+(\d{2}):(\d{2})(?::(\d{2}))?)?$"
|
|
)
|
|
_ISO_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?(?:Z|[+-]\d{2}:?\d{2})?)?$")
|
|
|
|
|
|
def normalize_event_time(raw) -> str:
|
|
"""Return an ISO-8601 `event_time` for a haystack date.
|
|
|
|
`YYYY/MM/DD (Day) HH:MM` becomes `YYYY-MM-DDTHH:MM:00`; an ISO value passes
|
|
through; anything else is returned unchanged (the extractor ignores values it
|
|
cannot parse).
|
|
"""
|
|
text = str(raw).strip()
|
|
match = _LME_DATE.match(text)
|
|
if match:
|
|
year, month, day, hour, minute, second = match.groups()
|
|
if hour is None:
|
|
return f"{year}-{month}-{day}"
|
|
return f"{year}-{month}-{day}T{hour}:{minute}:{second or '00'}"
|
|
if _ISO_DATE.match(text):
|
|
return text
|
|
return text
|
|
|
|
|
|
def _as_turn(turn) -> dict:
|
|
if isinstance(turn, str):
|
|
turn = json.loads(turn)
|
|
return turn
|
|
|
|
|
|
def _turn_lines(turn: dict) -> str:
|
|
role = turn.get("role", "unknown")
|
|
content = turn.get("content", "")
|
|
label = "User" if role == "user" else "Assistant"
|
|
return f"{label}: {content}"
|
|
|
|
|
|
def serialize_session(turns: list, date: str) -> str:
|
|
"""Serialize a chat session into searchable text.
|
|
|
|
Each turn may be a dict or a JSON string encoding {role, content}.
|
|
"""
|
|
lines = [f"[{date}]"]
|
|
for turn in turns:
|
|
lines.append(_turn_lines(_as_turn(turn)))
|
|
return "\n".join(lines)
|
|
|
|
|
|
def session_rounds(turns: list) -> list:
|
|
"""Group turns into rounds: a user turn opens a round, replies join it."""
|
|
rounds = []
|
|
for turn in turns:
|
|
turn = _as_turn(turn)
|
|
if turn.get("role") == "user" or not rounds:
|
|
rounds.append([turn])
|
|
else:
|
|
rounds[-1].append(turn)
|
|
return rounds
|
|
|
|
|
|
def serialize_round(round_turns: list, date: str) -> str:
|
|
lines = [f"[{date}]"]
|
|
lines.extend(_turn_lines(turn) for turn in round_turns)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def round_doc_id(session_id: str, index: int) -> str:
|
|
return f"{session_id}__r{index}"
|
|
|
|
|
|
@dataclass
|
|
class Conversion:
|
|
corpus: dict = field(default_factory=dict) # doc id -> {_id, title, text, metadata}
|
|
queries: list = field(default_factory=list) # [{_id, text, metadata}]
|
|
qrels: list = field(default_factory=list) # [(query_id, session_id, score)]
|
|
haystacks: list = field(default_factory=list) # [{question_id, haystack_session_ids, ...}]
|
|
abstention_count: int = 0
|
|
question_type_counts: dict = field(default_factory=dict)
|
|
session_count: int = 0
|
|
round_doc_count: int = 0
|
|
|
|
|
|
def convert(records, *, full_haystack=False, include_abstention=False, round_docs=False):
|
|
"""Build the BEIR corpus/queries/qrels (and the haystack sidecar) in memory."""
|
|
needed_sids = set() # session IDs referenced by answer_session_ids
|
|
if not full_haystack:
|
|
for item in records:
|
|
for sid in item.get("answer_session_ids", []):
|
|
needed_sids.add(sid)
|
|
|
|
conv = Conversion()
|
|
# One document per unique session, deduplicated across questions; the first
|
|
# question that mentions a session supplies its date.
|
|
for item in records:
|
|
qid = item["question_id"]
|
|
qtype = item.get("question_type", "unknown")
|
|
conv.question_type_counts[qtype] = conv.question_type_counts.get(qtype, 0) + 1
|
|
|
|
is_abstention = qid.endswith("_abs") or qtype == "false_premise"
|
|
|
|
session_ids = item["haystack_session_ids"]
|
|
dates = item["haystack_dates"]
|
|
sessions = item["haystack_sessions"]
|
|
|
|
for sid, date, turns in zip(session_ids, dates, sessions):
|
|
if sid in conv.corpus or not (full_haystack or sid in needed_sids):
|
|
continue
|
|
event_time = normalize_event_time(date)
|
|
conv.corpus[sid] = {
|
|
"_id": sid,
|
|
"title": f"Chat Session - {date}",
|
|
"text": serialize_session(turns, date),
|
|
"metadata": {"event_time": event_time, "session_id": sid},
|
|
}
|
|
conv.session_count += 1
|
|
if round_docs:
|
|
for index, round_turns in enumerate(session_rounds(turns)):
|
|
rid = round_doc_id(sid, index)
|
|
conv.corpus[rid] = {
|
|
"_id": rid,
|
|
"title": f"Chat Session - {date} - round {index}",
|
|
"text": serialize_round(round_turns, date),
|
|
"metadata": {
|
|
"event_time": event_time,
|
|
"session_id": sid,
|
|
"round_index": index,
|
|
},
|
|
}
|
|
conv.round_doc_count += 1
|
|
|
|
if full_haystack:
|
|
conv.haystacks.append(
|
|
{
|
|
"question_id": qid,
|
|
"haystack_session_ids": list(session_ids),
|
|
"haystack_dates": [normalize_event_time(d) for d in dates],
|
|
}
|
|
)
|
|
|
|
conv.queries.append(
|
|
{"_id": qid, "text": item["question"], "metadata": {"question_type": qtype}}
|
|
)
|
|
|
|
answer_sids = item.get("answer_session_ids", [])
|
|
if is_abstention:
|
|
conv.abstention_count += 1
|
|
if include_abstention:
|
|
for sid in answer_sids:
|
|
conv.qrels.append((qid, sid, 0))
|
|
else:
|
|
for sid in answer_sids:
|
|
conv.qrels.append((qid, sid, 1))
|
|
|
|
return conv
|
|
|
|
|
|
def write_outputs(out: Path, conv: Conversion, *, include_abstention, full_haystack, round_docs):
|
|
out.mkdir(parents=True, exist_ok=True)
|
|
(out / "qrels").mkdir(exist_ok=True)
|
|
|
|
with open(out / "corpus.jsonl", "w") as f:
|
|
f.writelines(json.dumps(doc, ensure_ascii=False) + "\n" for doc in conv.corpus.values())
|
|
|
|
with open(out / "queries.jsonl", "w") as f:
|
|
f.writelines(json.dumps(q, ensure_ascii=False) + "\n" for q in conv.queries)
|
|
|
|
with open(out / "qrels" / "test.tsv", "w") as f:
|
|
f.write("query-id\tcorpus-id\tscore\n")
|
|
f.writelines(f"{qid}\t{did}\t{score}\n" for qid, did, score in conv.qrels)
|
|
|
|
if full_haystack:
|
|
with open(out / "haystacks.jsonl", "w") as f:
|
|
f.writelines(json.dumps(entry, ensure_ascii=False) + "\n" for entry in conv.haystacks)
|
|
|
|
meta = {
|
|
"dataset": "LongMemEval_S",
|
|
"source": "xiaowu0162/longmemeval-cleaned",
|
|
"split": "longmemeval_s_cleaned",
|
|
"total_questions": len(conv.queries),
|
|
"total_sessions": conv.session_count,
|
|
"total_documents": len(conv.corpus),
|
|
"total_round_documents": conv.round_doc_count,
|
|
"total_qrels": len(conv.qrels),
|
|
"abstention_questions": conv.abstention_count,
|
|
"include_abstention": include_abstention,
|
|
"full_haystack": full_haystack,
|
|
"round_docs": round_docs,
|
|
"question_type_counts": conv.question_type_counts,
|
|
}
|
|
with open(out / "metadata.json", "w") as f:
|
|
json.dump(meta, f, indent=2)
|
|
return meta
|
|
|
|
|
|
def _synthetic_records():
|
|
"""Three sessions shared across two questions, plus an abstention question."""
|
|
s1 = [
|
|
{"role": "user", "content": "I adopted a dog named Pepper."},
|
|
{"role": "assistant", "content": "Congratulations on Pepper!"},
|
|
{"role": "user", "content": "She likes carrots."},
|
|
{"role": "assistant", "content": "Many dogs do."},
|
|
]
|
|
s2 = [
|
|
json.dumps({"role": "user", "content": "Planning a trip to Lisbon in June."}),
|
|
json.dumps({"role": "assistant", "content": "Lisbon is warm in June."}),
|
|
]
|
|
s3 = [
|
|
{"role": "assistant", "content": "Reminder: dentist on Friday."},
|
|
{"role": "user", "content": "Thanks."},
|
|
]
|
|
return [
|
|
{
|
|
"question_id": "q1",
|
|
"question_type": "single-session-user",
|
|
"question": "What is the name of my dog?",
|
|
"haystack_session_ids": ["s1", "s2"],
|
|
"haystack_dates": ["2023/05/20 (Sat) 02:21", "2023/05/21 (Sun) 10:00"],
|
|
"haystack_sessions": [s1, s2],
|
|
"answer_session_ids": ["s1"],
|
|
},
|
|
{
|
|
"question_id": "q2",
|
|
"question_type": "temporal-reasoning",
|
|
"question": "Where am I travelling in June?",
|
|
"haystack_session_ids": ["s2", "s3"],
|
|
"haystack_dates": ["2023/05/21 (Sun) 10:00", "2023/06/01 (Thu) 08:30"],
|
|
"haystack_sessions": [s2, s3],
|
|
"answer_session_ids": ["s2"],
|
|
},
|
|
{
|
|
"question_id": "q3_abs",
|
|
"question_type": "false_premise",
|
|
"question": "When did I sell my boat?",
|
|
"haystack_session_ids": ["s3"],
|
|
"haystack_dates": ["2023/06/01 (Thu) 08:30"],
|
|
"haystack_sessions": [s3],
|
|
"answer_session_ids": ["s3"],
|
|
},
|
|
]
|
|
|
|
|
|
def self_test() -> int:
|
|
"""Exercise the converter on a synthetic fixture; no dataset download."""
|
|
assert normalize_event_time("2023/05/20 (Sat) 02:21") == "2023-05-20T02:21:00"
|
|
assert normalize_event_time("2023/05/20") == "2023-05-20"
|
|
assert normalize_event_time("2023-05-20T02:21:00Z") == "2023-05-20T02:21:00Z"
|
|
assert normalize_event_time("someday") == "someday"
|
|
|
|
records = _synthetic_records()
|
|
|
|
# Default: answer sessions only (abstention answers included, as before), session-level
|
|
# docs, metadata on every doc, no sidecar.
|
|
default = convert(records)
|
|
assert set(default.corpus) == {"s1", "s2", "s3"}, default.corpus.keys()
|
|
assert default.session_count == 3
|
|
assert default.corpus["s1"]["title"] == "Chat Session - 2023/05/20 (Sat) 02:21"
|
|
assert default.corpus["s1"]["text"].startswith("[2023/05/20 (Sat) 02:21]\nUser: I adopted")
|
|
assert default.corpus["s1"]["metadata"] == {
|
|
"event_time": "2023-05-20T02:21:00",
|
|
"session_id": "s1",
|
|
}
|
|
assert default.corpus["s2"]["metadata"]["event_time"] == "2023-05-21T10:00:00"
|
|
assert [q["_id"] for q in default.queries] == ["q1", "q2", "q3_abs"]
|
|
assert default.qrels == [("q1", "s1", 1), ("q2", "s2", 1)]
|
|
assert default.abstention_count == 1
|
|
assert default.haystacks == []
|
|
assert default.round_doc_count == 0
|
|
|
|
with_abs = convert(records, include_abstention=True)
|
|
assert ("q3_abs", "s3", 0) in with_abs.qrels
|
|
assert with_abs.corpus == default.corpus
|
|
|
|
# Full haystack: every session, per-question membership and dates kept.
|
|
full = convert(records, full_haystack=True)
|
|
assert set(full.corpus) == {"s1", "s2", "s3"}
|
|
assert full.corpus == default.corpus # the fixture's haystacks are all answers
|
|
assert full.haystacks == [
|
|
{
|
|
"question_id": "q1",
|
|
"haystack_session_ids": ["s1", "s2"],
|
|
"haystack_dates": ["2023-05-20T02:21:00", "2023-05-21T10:00:00"],
|
|
},
|
|
{
|
|
"question_id": "q2",
|
|
"haystack_session_ids": ["s2", "s3"],
|
|
"haystack_dates": ["2023-05-21T10:00:00", "2023-06-01T08:30:00"],
|
|
},
|
|
{
|
|
"question_id": "q3_abs",
|
|
"haystack_session_ids": ["s3"],
|
|
"haystack_dates": ["2023-06-01T08:30:00"],
|
|
},
|
|
]
|
|
|
|
# Round docs: one per user-opened round, session docs unchanged.
|
|
rounds = convert(records, full_haystack=True, round_docs=True)
|
|
assert rounds.session_count == 3
|
|
# s1: 2 user rounds; s2: 1; s3: a leading assistant turn is its own round, then 1 user round.
|
|
assert rounds.round_doc_count == 5, rounds.round_doc_count
|
|
assert rounds.corpus["s1"] == full.corpus["s1"]
|
|
r0 = rounds.corpus["s1__r0"]
|
|
assert r0["text"] == (
|
|
"[2023/05/20 (Sat) 02:21]\nUser: I adopted a dog named Pepper.\n"
|
|
"Assistant: Congratulations on Pepper!"
|
|
)
|
|
assert r0["metadata"] == {
|
|
"event_time": "2023-05-20T02:21:00",
|
|
"session_id": "s1",
|
|
"round_index": 0,
|
|
}
|
|
assert rounds.corpus["s1__r1"]["metadata"]["round_index"] == 1
|
|
assert rounds.corpus["s3__r0"]["text"].endswith("]\nAssistant: Reminder: dentist on Friday.")
|
|
assert rounds.corpus["s3__r1"]["text"].endswith("]\nUser: Thanks.")
|
|
assert "s3__r2" not in rounds.corpus
|
|
assert rounds.qrels == default.qrels # qrels stay at session level
|
|
|
|
# Files: default layout has no sidecar; full haystack writes it.
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
out = Path(tmp) / "default"
|
|
meta = write_outputs(
|
|
out, default, include_abstention=False, full_haystack=False, round_docs=False
|
|
)
|
|
assert sorted(p.name for p in out.iterdir()) == [
|
|
"corpus.jsonl",
|
|
"metadata.json",
|
|
"qrels",
|
|
"queries.jsonl",
|
|
]
|
|
lines = (out / "corpus.jsonl").read_text().splitlines()
|
|
assert [json.loads(line)["_id"] for line in lines] == ["s1", "s2", "s3"]
|
|
assert json.loads(lines[0])["metadata"]["session_id"] == "s1"
|
|
assert (out / "qrels" / "test.tsv").read_text() == (
|
|
"query-id\tcorpus-id\tscore\nq1\ts1\t1\nq2\ts2\t1\n"
|
|
)
|
|
assert meta["total_sessions"] == 3 and meta["total_round_documents"] == 0
|
|
|
|
out_full = Path(tmp) / "full"
|
|
meta_full = write_outputs(
|
|
out_full, rounds, include_abstention=False, full_haystack=True, round_docs=True
|
|
)
|
|
side = [
|
|
json.loads(line) for line in (out_full / "haystacks.jsonl").read_text().splitlines()
|
|
]
|
|
assert [e["question_id"] for e in side] == ["q1", "q2", "q3_abs"]
|
|
assert side[1]["haystack_session_ids"] == ["s2", "s3"]
|
|
assert meta_full["total_documents"] == 8 and meta_full["round_docs"] is True
|
|
|
|
print("self-test passed")
|
|
return 0
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"--output-dir",
|
|
type=Path,
|
|
default=Path.home() / ".cache" / "yams" / "benchmarks" / "longmemeval_s",
|
|
help="Output directory for BEIR-format files",
|
|
)
|
|
parser.add_argument(
|
|
"--max-questions",
|
|
type=int,
|
|
default=None,
|
|
help="Limit number of questions (for quick iteration)",
|
|
)
|
|
parser.add_argument(
|
|
"--include-abstention",
|
|
action="store_true",
|
|
help="Include false-premise (abstention) questions in qrels with score=0",
|
|
)
|
|
parser.add_argument(
|
|
"--full-haystack",
|
|
action="store_true",
|
|
help="Include all haystack sessions in corpus (default: only qrel-referenced) and "
|
|
"write haystacks.jsonl with each question's haystack session ids and dates",
|
|
)
|
|
parser.add_argument(
|
|
"--round-docs",
|
|
action="store_true",
|
|
help="Also emit one document per session round (<session>__r<n>) with round_index metadata",
|
|
)
|
|
parser.add_argument(
|
|
"--self-test",
|
|
action="store_true",
|
|
help="Run the converter on a synthetic fixture and exit (no download)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
if args.self_test:
|
|
sys.exit(self_test())
|
|
|
|
try:
|
|
from datasets import load_dataset
|
|
except ImportError:
|
|
print(
|
|
"Error: 'datasets' package required.\n"
|
|
" uv pip install datasets\n"
|
|
"Or run with:\n"
|
|
" uv run --with datasets scripts/prepare_longmemeval_s.py",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
|
|
print("Loading LongMemEval_S from HuggingFace...")
|
|
# Stream to avoid downloading all splits (the _m split is >2GB and
|
|
# overflows pyarrow's int32 block size).
|
|
ds = load_dataset(
|
|
"xiaowu0162/longmemeval-cleaned",
|
|
split="longmemeval_s_cleaned",
|
|
streaming=True,
|
|
)
|
|
# Materialize the streamed dataset into a list
|
|
ds = list(ds)
|
|
|
|
if args.max_questions and args.max_questions < len(ds):
|
|
ds = ds[: args.max_questions]
|
|
print(f"Subsampled to {len(ds)} questions")
|
|
|
|
conv = convert(
|
|
ds,
|
|
full_haystack=args.full_haystack,
|
|
include_abstention=args.include_abstention,
|
|
round_docs=args.round_docs,
|
|
)
|
|
if not args.full_haystack:
|
|
print(f"Qrel-referenced sessions: {conv.session_count} (use --full-haystack for all)")
|
|
|
|
out = args.output_dir
|
|
write_outputs(
|
|
out,
|
|
conv,
|
|
include_abstention=args.include_abstention,
|
|
full_haystack=args.full_haystack,
|
|
round_docs=args.round_docs,
|
|
)
|
|
|
|
print(f"\nDone! Output: {out}")
|
|
print(f" Corpus: {conv.session_count} sessions, {len(conv.corpus)} documents")
|
|
if args.round_docs:
|
|
print(f" Rounds: {conv.round_doc_count} round documents")
|
|
print(f" Queries: {len(conv.queries)} questions")
|
|
print(f" Qrels: {len(conv.qrels)} judgments")
|
|
print(f" Abstention (excluded): {conv.abstention_count}")
|
|
print(f" Question types: {conv.question_type_counts}")
|
|
if args.full_haystack:
|
|
print(f" Haystacks: {out / 'haystacks.jsonl'}")
|
|
print(
|
|
"\nRun benchmark:\n"
|
|
" YAMS_TEST_SAFE_SINGLE_INSTANCE=1 YAMS_BENCH_DATASET=longmemeval_s "
|
|
"./builddir/tests/benchmarks/retrieval_quality_bench"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|