Spaces:
Runtime error
Runtime error
File size: 5,402 Bytes
8f1601b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | from __future__ import annotations
import argparse
import json
import random
import re
from pathlib import Path
from typing import Any
from tools.query_knowledge import RAW_DIR, iter_source_files, load_source_file
KEY_TERMS = [
"volatility smile",
"implied volatility",
"local volatility",
"stochastic volatility",
"Black-Scholes",
"delta",
"gamma",
"vega",
"theta",
"rho",
"skew",
"straddle",
"correlation",
"at-the-money",
"forward",
"risk-neutral",
]
PROJECT_ROOT = Path(__file__).resolve().parents[1]
OUTPUT_PATH = PROJECT_ROOT / "eval" / "local_options_eval.jsonl"
def normalize_space(text: str) -> str:
return re.sub(r"\s+", " ", text).strip()
def extract_keywords(text: str, max_keywords: int = 4) -> list[str]:
lowered = text.lower()
keywords = [term for term in KEY_TERMS if term.lower() in lowered]
equation_ids = re.findall(r"\(\d+\.\d+[a-z]?\)", text)
formulas = re.findall(r"[A-Za-z𝜎𝜇𝜌𝜃𝛴][A-Za-z0-9𝜎𝜇𝜌𝜃𝛴_{}^]*\s*=", text)
keywords.extend(equation_ids[:2])
keywords.extend(item.strip() for item in formulas[:2])
if not keywords:
candidates = [
word
for word in re.findall(r"[A-Za-z][A-Za-z-]{4,}", text)
if word.lower() not in {"there", "where", "which", "would", "could", "should", "chapter"}
]
keywords.extend(candidates[:max_keywords])
deduped = []
banned = {"id=", "FORMULA", "value ="}
for keyword in keywords:
if keyword and keyword not in banned and keyword not in deduped:
deduped.append(keyword)
return deduped[:max_keywords]
def is_sane_section(section: str | None) -> bool:
if not section:
return False
section = section.strip()
if not 6 <= len(section) <= 90:
return False
if section.count(",") >= 2:
return False
digit_count = sum(char.isdigit() for char in section)
letter_count = sum(char.isalpha() for char in section)
if digit_count > max(2, letter_count // 3):
return False
if re.search(r"\b(figure|table|printed|united states|amount unit price|call price|under)$", section, re.I):
return False
if "figure" in section.lower() or "table" in section.lower():
return False
if re.search(r"\b(figure|table|printed|united states|amount unit price|call price)\b", section, re.I):
return False
words = section.split()
if len(words) > 12:
return False
return True
def make_case(document: Any, index: int) -> dict[str, Any] | None:
metadata = document.metadata
text = normalize_space(document.text)
if len(text) < 80:
return None
page = metadata.get("page_number")
if isinstance(page, int) and (page < 25 or page > 500):
return None
section = metadata.get("section_path") or metadata.get("section_title")
content_type = metadata.get("content_type", "text")
formula_id = metadata.get("formula_id")
keywords = extract_keywords(text)
if not keywords and not section:
return None
if content_type == "formula" or formula_id:
question = f"What formula or equation is described on page {page}?"
answer_type = "formula"
elif is_sane_section(section):
question = f"What does the section {section} discuss?"
answer_type = "section"
keywords.append(section.split(">")[-1].strip())
else:
if not keywords:
return None
term = keywords[0]
if term.lower() in {"formula", "id=", "value ="}:
return None
question = f"Where does the options reference discuss {term}?"
answer_type = "concept"
expected_pages = [page] if page is not None else []
return {
"id": f"auto_options_{index:03d}",
"question": question,
"expected_pages": expected_pages,
"expected_keywords": keywords[:5],
"answer_type": answer_type,
}
def generate_cases(count: int, seed: int) -> list[dict[str, Any]]:
documents = []
for source_file in iter_source_files(RAW_DIR):
documents.extend(load_source_file(source_file))
random.Random(seed).shuffle(documents)
cases = []
seen_questions = set()
for document in documents:
case = make_case(document, len(cases) + 1)
if not case:
continue
if case["question"] in seen_questions:
continue
seen_questions.add(case["question"])
cases.append(case)
if len(cases) >= count:
break
if len(cases) < count:
raise RuntimeError(f"Only generated {len(cases)} cases; requested {count}.")
return cases
def main() -> None:
parser = argparse.ArgumentParser(description="Generate local options RAG eval cases.")
parser.add_argument("--count", type=int, default=40)
parser.add_argument("--seed", type=int, default=20260525)
parser.add_argument("--output", type=Path, default=OUTPUT_PATH)
args = parser.parse_args()
cases = generate_cases(args.count, args.seed)
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(
"\n".join(json.dumps(case, ensure_ascii=False) for case in cases) + "\n",
encoding="utf-8",
)
print(f"Wrote {len(cases)} cases to {args.output}")
if __name__ == "__main__":
main()
|