financial-rag / src /evaluator.py
anasxs's picture
Deploy financial RAG Gradio demo
9e3003d
Raw
History Blame Contribute Delete
6.84 kB
from __future__ import annotations
import json
import logging
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Any
from pipeline import FinancialRAGPipeline, QueryFilters, build_default_pipeline
PROJECT_ROOT = Path(__file__).resolve().parents[1]
DEFAULT_REPORTS_DIR = PROJECT_ROOT / "reports"
DEFAULT_RESULTS_PATH = DEFAULT_REPORTS_DIR / "ragas_results.json"
LOGGER = logging.getLogger(__name__)
@dataclass(frozen=True)
class EvaluationExample:
question: str
ground_truth: str
company_name: str | None = None
filing_type: str | None = None
DEFAULT_EVALUATION_SET: tuple[EvaluationExample, ...] = (
EvaluationExample("What were Apple's main net sales categories?", "Apple reports net sales by products and services, with products including iPhone, Mac, iPad, and Wearables, Home and Accessories.", "Apple", "10-K"),
EvaluationExample("What does Amazon report as its major business segments?", "Amazon reports segments including North America, International, and AWS.", "Amazon", "10-K"),
EvaluationExample("What are Alphabet's primary revenue sources?", "Alphabet primarily earns revenue from advertising, subscriptions, platforms, devices, and Google Cloud.", "Alphabet", "10-K"),
EvaluationExample("What are Berkshire Hathaway's major operating groups?", "Berkshire Hathaway reports diversified operations including insurance, rail, utilities and energy, manufacturing, service, and retailing.", "Berkshire Hathaway", "10-K"),
EvaluationExample("What does Johnson and Johnson report as major business segments?", "Johnson and Johnson reports business through healthcare-focused segments such as Innovative Medicine and MedTech.", "Johnson and Johnson", "10-K"),
EvaluationExample("What table shows Apple's net sales by category?", "Apple's filing includes a table presenting net sales by category and reportable segment.", "Apple", "10-K"),
EvaluationExample("What table shows Amazon AWS net sales?", "Amazon's filing includes segment tables that show AWS net sales and operating income.", "Amazon", "10-K"),
EvaluationExample("What table shows Alphabet revenues by type?", "Alphabet's filing includes revenue tables by Google advertising, subscriptions, platforms, devices, and cloud.", "Alphabet", "10-K"),
EvaluationExample("What does Berkshire's quarterly filing say about insurance underwriting?", "Berkshire's 10-Q discusses insurance underwriting results and investment income as part of insurance operations.", "Berkshire Hathaway", "10-Q"),
EvaluationExample("What does Johnson and Johnson's quarterly filing say about MedTech?", "Johnson and Johnson's 10-Q discusses MedTech sales and operating performance.", "Johnson and Johnson", "10-Q"),
EvaluationExample("Which company reports AWS as a segment?", "Amazon reports AWS as one of its operating segments.", None, "10-K"),
EvaluationExample("Which company reports iPhone net sales?", "Apple reports iPhone net sales.", None, "10-K"),
EvaluationExample("Which company reports Google Cloud revenue?", "Alphabet reports Google Cloud revenue.", None, "10-K"),
EvaluationExample("How do filings discuss foreign currency risk?", "The filings discuss foreign currency risk as exchange-rate movements that can affect revenue, costs, assets, liabilities, or cash flows.", None, "10-K"),
EvaluationExample("How do filings discuss supply chain risk?", "The filings discuss supply chain risk as disruptions or shortages that may affect production, costs, availability, or customer demand.", None, "10-K"),
EvaluationExample("How do filings discuss regulatory risk?", "The filings discuss regulatory risk as laws, investigations, compliance duties, or policy changes that can affect operations and financial results.", None, "10-K"),
EvaluationExample("How do filings discuss AI infrastructure investment?", "The filings discuss AI infrastructure investment through data centers, servers, chips, cloud infrastructure, or capital expenditures.", None, "10-Q"),
EvaluationExample("How do filings discuss liquidity?", "The filings discuss liquidity through cash, cash equivalents, marketable securities, operating cash flow, debt, and capital resources.", None, "10-K"),
)
def ensure_reports_dir(path: Path) -> None:
path.mkdir(parents=True, exist_ok=True)
def build_ragas_records(pipeline: FinancialRAGPipeline, examples: tuple[EvaluationExample, ...]) -> list[dict[str,Any]]:
records: list[dict[str,Any]] = []
for example in examples:
filters = QueryFilters(example.company_name, example.filing_type)
response = pipeline.answer_question(example.question, filters)
contexts = [source.text for source in response.sources]
records.append(build_record(example, response.answer, contexts))
return records
def build_record(example: EvaluationExample, answer: str, contexts: list[str]) -> dict[Any, str]:
return {
"question" : example.question,
"answer": answer,
"ground_truth" : example.ground_truth,
"contexts": contexts,
}
def run_ragas(records: list[dict[str,Any]]) -> Any:
try:
from datasets import Dataset
from ragas import evaluate
from ragas.metrics import answer_correctness, context_precision, context_recall, faithfulness
except ImportError as exc:
raise ImportError("Install ragas and datasets before running evaluation.") from exc
dataset = Dataset.from_list(records)
metrics = [faithfulness, answer_correctness, context_precision, context_recall]
return evaluate(dataset,metrics=metrics)
def save_results(result: Any, records: list[dict[str,Any]], path:Path) -> None:
ensure_reports_dir(path.parent)
payload = {"scores": result_to_dict(result), "examples": records}
try:
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
except OSError as exc:
raise OSError(f"Could not save RAGAS results to {path}") from exc
def result_to_dict(result: Any) -> dict[str, Any]:
if hasattr(result, "to_pandas"):
return result.to_pandas().mean(numeric_only=True).to_dict()
return dict(result)
def save_evaluation_set(path: Path = DEFAULT_REPORTS_DIR / "evaluation_set.json") -> None :
ensure_reports_dir(path.parent)
payload = [ asdict(example) for example in DEFAULT_EVALUATION_SET]
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
def evaluate_pipeline(
pipeline: FinancialRAGPipeline | None = None,
results_path: Path = DEFAULT_RESULTS_PATH,
) -> Any:
active_pipeline = pipeline or build_default_pipeline()
save_evaluation_set()
records = build_ragas_records(active_pipeline, DEFAULT_EVALUATION_SET)
result = run_ragas(records)
save_results(result, records, results_path)
return result