import path_setup # noqa: F401 — adds project root to sys.path
%load_ext autoreload
%autoreload 2
import pandas as pd
df_ground_truth = pd.read_csv("data/ground_truth.csv")
ground_truth = df_ground_truth.to_dict(orient="records")
from ingest import load_faq_data, build_index, build_vector_index
documents = load_faq_data()
doc_idx = {}
for doc in documents:
doc_idx[doc["id"]] = doc
text_index = build_index(documents)
from embedder import Embedder
embedder = Embedder()
index = build_vector_index(embedder, documents)
Generating Embeddings in Batches...
0%| | 0/56 [00:00<?, ?it/s]
from dotenv import load_dotenv
from config import make_llm_client, MODEL_NAME
load_dotenv()
# 1. Initialize your client object with a model list
litellm_client = make_llm_client()
from evaluation.evaluation_utils import RAGWithUsage
assistant = RAGWithUsage(
index=index,
llm_client=litellm_client,
text_index=text_index,
vector_index=index,
embedder=embedder,
)
from evaluation.evaluation import generate_rag_answer
from concurrent.futures import ThreadPoolExecutor
from evaluation.evaluation_utils import map_progress_answers
answer_record = generate_rag_answer(ground_truth[1], doc_idx, assistant)
answer_record
{'question': 'Should I move a person if the scene is unsafe but no immediate threats like fire or traffic?',
'answer_llm': 'No, you should **not** move the person if there is no immediate danger (such as fire, oncoming traffic, or toxic fumes). The best course of action is to stay with them, administer first aid on the spot, and wait for professional medical help to arrive.',
'answer_orig': "You should only move an injured person if there is immediate danger such as a fire, oncoming traffic, or toxic fumes. Otherwise, it's best to leave them where they are, administer first aid on the spot, and wait for professional medical help to arrive.",
'document': 'a220806f-b207-480c-9a25-2787503e7efe'}
with ThreadPoolExecutor(max_workers=6) as pool:
results = map_progress_answers(pool, ground_truth, generate_rag_answer, doc_idx, assistant)
0%| | 0/13874 [00:00<?, ?it/s]
len(results)
13874
answers = []
for answer_record in results:
answers.append(answer_record)
assistant.total_cost()
1.3239971000000008
#df_answers = pd.DataFrame(answers)
#df_answers.to_csv("data/rag-answers.csv", index=False)
import pandas as pd
df_answers = pd.read_csv("data/rag-answers.csv")
answers = df_answers.to_dict(orient="records")
from pydantic import BaseModel, Field
from typing import Literal
class AnswerEvaluation(BaseModel):
reasoning: str = Field(
description="Reasoning about the quality of the answer."
)
score: Literal["good", "bad"] = Field(
description="'good' if the answer is correct and complete, 'bad' otherwise."
)
aqa_judge_instructions = """
You are an expert evaluator. You will be given:
1. A question from a person at accident site
2. The original answer from the FAQ (ground truth)
3. An answer generated by an AI assistant
Your task is to decide if the AI answer is semantically equivalent to
the original answer.
Rules:
- The AI answer does NOT need to be word-for-word identical
- It should convey the same key information
- Extra detail is fine as long as the core answer is correct
- Mark 'bad' only if the AI answer is wrong or misses the key point
Be fair and focus on correctness, not style.
""".strip()
aqa_judge_prompt = """
Question:
{question}
Original Answer (ground truth):
{answer_orig}
AI Answer:
{answer_llm}
""".strip()
rec = answers[0]
from evaluation.evaluation_utils import calc_price, calc_total_price, llm_structured_retry, map_progress
from evaluation.evaluation import evaluate_aqa
eval_result, usage = evaluate_aqa(
rec["question"],
rec["answer_orig"],
rec["answer_llm"],
litellm_client,
aqa_judge_instructions,
aqa_judge_prompt,
AnswerEvaluation,
)
eval_result
AnswerEvaluation(reasoning="The AI answer correctly conveys the core principle of the original FAQ: **do not move an injured person if there is no immediate danger** (e.g., fire, traffic, toxic fumes). It retains the key points—leaving them in place, administering first aid on-site, and waiting for professional help—while adding clarity with an example ('no fire, no traffic, no toxic fumes'). The phrasing is slightly more concise but still accurate. The only minor deviation is the inclusion of the phrase *'unless absolutely necessary'* (which is not explicitly stated in the original), but this does not undermine the correctness of the core advice. The AI answer is semantically equivalent and well-structured.", score='good')
from concurrent.futures import ThreadPoolExecutor
from evaluation.evaluation_utils import map_progress_judge
from evaluation.evaluation import judge_record
with ThreadPoolExecutor(max_workers=6) as pool:
results, usage = map_progress_judge(
pool,
answers,
judge_record,
litellm_client,
aqa_judge_instructions,
aqa_judge_prompt,
AnswerEvaluation,
)
0%| | 0/13874 [00:00<?, ?it/s]
[92m22:49:00 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m22:49:20 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m22:49:26 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m22:50:05 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m22:50:57 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m22:54:40 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m22:55:55 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m22:56:50 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m22:57:19 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m22:57:56 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m22:58:20 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m22:58:23 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m22:58:44 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m22:59:14 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:00:02 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:00:22 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:00:27 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:01:48 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:01:57 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:03:17 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:04:52 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:10:06 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:11:20 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:11:24 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:13:34 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:14:03 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:14:14 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:16:01 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:16:32 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:16:43 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:19:19 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:20:01 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:23:00 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:23:36 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
[92m23:23:40 - LiteLLM:WARNING[0m: core_helpers.py:110 - Unmapped finish_reason 'error', defaulting to 'stop'
evaluations = []
for evaluation in results:
evaluations.append(evaluation)
df_eval = pd.DataFrame(evaluations)
len(evaluations)
13874
good_count = (df_eval["score"] == "good").sum()
total_count = len(df_eval)
print(f"Good: {good_count}/{total_count} = {good_count/total_count:.2%}")
Good: 13234/13874 = 95.39%
df_eval[df_eval["score"] == "bad"].head()
|
question |
document |
score |
reasoning |
| 19 |
What’s the safest height to move them at? |
144f2d0d-2a4f-4163-824b-a858b14f6354 |
bad |
The AI answer is incorrect and does not provid... |
| 28 |
Is the human crutch technique best for people ... |
0eb1534a-9340-4571-ba52-f4b190a2bc06 |
bad |
The AI answer incorrectly states uncertainty (... |
| 38 |
What’s the first thing to do if someone’s leg ... |
757b0832-8ad4-4a72-9660-141cdecafa90 |
bad |
The AI answer expands on the original ground t... |
| 42 |
Should I wrap my arm around a casualty’s waist... |
c3fd2aaf-bb6c-44b4-a434-ee56aca06728 |
bad |
The AI answer is **not semantically equivalent... |
| 72 |
Should you move a casualty immediately when fu... |
0eb6039b-d6db-4640-9bf0-6447fec59964 |
bad |
The AI answer introduces a nuanced but incorre... |