AI_Chatbot / tests /test_eval_v1.py
Hamza-Naimat's picture
tests: tenant-scoped analytics + CSAT-scale charts, eval sqlite-first rows
c40cb1d
Raw
History Blame Contribute Delete
36.9 kB
from pathlib import Path
import tempfile
from evals import eval_v1
from evals.llm_judge import JudgeVerdict
def _write_json(path: Path, payload) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(eval_v1.json.dumps(payload, indent=2), encoding="utf-8")
def _scratch_dir() -> Path:
path = Path("tests") / "_tmp" / next(tempfile._get_candidate_names())
path.mkdir(parents=True, exist_ok=True)
return path
def test_collect_seed_items_filters_generic_noise_and_keeps_curated(monkeypatch):
temp_path = _scratch_dir()
monkeypatch.chdir(temp_path)
monkeypatch.setattr(eval_v1, "DATABASES_DIR", temp_path / "databases")
monkeypatch.setattr(eval_v1, "_load_document_rows", lambda _db_name: [])
db_dir = temp_path / "databases" / "tenant_a"
_write_json(
db_dir / "faqs.json",
[{"question": "What is AgentFactory?", "answer": "An AI agent learning platform."}],
)
_write_json(
db_dir / "analytics.json",
{
"questions": {
"What is the weather today?": 8,
"What is AgentFactory?": 5,
"How does the curriculum work?": 4,
},
"history": [
{"q": "What is the capital of Japan?"},
{"q": "How does the curriculum work?"},
],
},
)
_write_json(
db_dir / "knowledge_gaps.json",
[
{"question": "What is 2+2?"},
{"question": "Who teaches the course?"},
],
)
items = eval_v1._collect_seed_items("tenant_a", 10)
questions = [item.q for item in items]
assert "What is AgentFactory?" in questions
assert "How does the curriculum work?" in questions
assert "Who teaches the course?" in questions
assert "What is the weather today?" not in questions
assert "What is the capital of Japan?" not in questions
assert "What is 2+2?" not in questions
def test_tenant_scope_tokens_infers_from_sources_when_config_missing(monkeypatch):
monkeypatch.setattr(eval_v1, "get_config", lambda _db_name="": {"business_name": "", "topics": [], "business_description": ""})
monkeypatch.setattr(
eval_v1,
"_load_document_rows",
lambda _db_name: [
("1", "Laptop product details and pricing.", "https://webscraper.io/test-sites/e-commerce/allinone/computers/laptops"),
("2", "Tablet specs and prices.", "https://webscraper.io/test-sites/e-commerce/allinone/computers/tablets"),
],
)
tokens = eval_v1._tenant_scope_tokens("mystery_store")
assert "mystery" in tokens or "store" in tokens
assert "webscraper" in tokens
assert "laptop" in tokens or "tablet" in tokens
def test_is_tenant_relevant_question_does_not_treat_course_words_as_global_scope():
store_scope = {"store", "webscraper", "laptops", "phones", "electronics"}
assert not eval_v1._is_tenant_relevant_question(
"What does Give Your Employee An Identity cover?",
"store",
store_scope,
)
def test_extract_chunk_topic_handles_product_chunks():
text = (
'Product: IdeaTab A3500L Black Price: $88.99 RAM: 8GB Display: 7" IPS '
'OS: Android 4.2 Full specs: IdeaTab A3500L Black, 7" IPS, Quad-Core 1.2GHz, 8GB, Android 4.2'
)
topic = eval_v1._extract_chunk_topic(text, "https://webscraper.io/test-sites/e-commerce/allinone/computers/tablets")
question = eval_v1._make_chunk_question(topic, text)
assert topic == "IdeaTab A3500L Black"
assert question == "What is the pricing for IdeaTab A3500L Black?"
def test_load_gap_candidates_filters_cross_tenant_pollution(monkeypatch):
temp_path = _scratch_dir()
monkeypatch.setattr(eval_v1, "DATABASES_DIR", temp_path / "databases")
db_dir = temp_path / "databases" / "agentfactory"
_write_json(
db_dir / "config.json",
{
"business_name": "AgentFactory by Panaversity",
"topics": "AI agent development, courses, pricing, curriculum, team",
"business_description": "Platform for learning to build AI agents.",
},
)
_write_json(
db_dir / "knowledge_gaps.json",
[
{"question": "What is spec-driven development?"},
{"question": "What premium fountain pens do you stock?"},
{"question": "What educational toys do you have for a 2-year-old under 3000 PKR?"},
],
)
items = eval_v1._load_gap_candidates("agentfactory")
assert [item.q for item in items] == ["What is spec-driven development?"]
def test_load_doc_qa_fallbacks_filters_cross_tenant_embedded_qa(monkeypatch):
temp_path = _scratch_dir()
monkeypatch.setattr(eval_v1, "DATABASES_DIR", temp_path / "databases")
db_dir = temp_path / "databases" / "agentfactory"
_write_json(
db_dir / "config.json",
{
"business_name": "AgentFactory by Panaversity",
"topics": "AI agent development, courses, pricing, curriculum, team",
"business_description": "Platform for learning to build AI agents.",
},
)
monkeypatch.setattr(
eval_v1,
"_load_document_rows",
lambda _db_name: [
("1", "Q: What is AgentFactory? A: AgentFactory is an AI agent learning platform."),
("2", "Q: What premium fountain pens do you stock? A: We carry luxury fountain pens and journals."),
],
)
items = eval_v1._load_doc_qa_fallbacks("agentfactory", 10)
assert [item.q for item in items] == ["What is AgentFactory?"]
def test_subscription_questions_are_not_globally_filtered(monkeypatch):
temp_path = _scratch_dir()
monkeypatch.chdir(temp_path)
monkeypatch.setattr(eval_v1, "DATABASES_DIR", temp_path / "databases")
monkeypatch.setattr(eval_v1, "_load_document_rows", lambda _db_name: [])
db_dir = temp_path / "databases" / "tenant_subs"
_write_json(
db_dir / "analytics.json",
{"questions": {"How do I cancel my subscription plan?": 5}},
)
items = eval_v1._collect_seed_items("tenant_subs", 10)
assert [item.q for item in items] == ["How do I cancel my subscription plan?"]
def test_load_analytics_candidates_filters_cross_tenant_pollution(monkeypatch):
temp_path = _scratch_dir()
monkeypatch.setattr(eval_v1, "DATABASES_DIR", temp_path / "databases")
db_dir = temp_path / "databases" / "agentfactory"
_write_json(
db_dir / "config.json",
{
"business_name": "AgentFactory by Panaversity",
"topics": "AI agent development, courses, pricing, curriculum, team",
"business_description": "Platform for learning to build AI agents.",
},
)
_write_json(
db_dir / "analytics.json",
{
"questions": {
"What is AgentFactory?": 5,
"What courses are available?": 4,
"Which laptop has the most RAM under $700?": 9,
"What are the top 3 highest rated anime of all time on MAL?": 7,
}
},
)
items = eval_v1._load_analytics_candidates("agentfactory")
questions = [item.q for item in items]
assert "What is AgentFactory?" in questions
assert "What courses are available?" in questions
assert "Which laptop has the most RAM under $700?" not in questions
assert "What are the top 3 highest rated anime of all time on MAL?" not in questions
def test_is_tenant_relevant_eval_question_rejects_cross_tenant_queries(monkeypatch):
temp_path = _scratch_dir()
monkeypatch.setattr(eval_v1, "DATABASES_DIR", temp_path / "databases")
db_dir = temp_path / "databases" / "agentfactory"
_write_json(
db_dir / "config.json",
{
"business_name": "AgentFactory by Panaversity",
"topics": "AI agent development, courses, pricing, curriculum, team",
"business_description": "Platform for learning to build AI agents.",
},
)
assert eval_v1._is_tenant_relevant_eval_question("What is AgentFactory?", "agentfactory") is True
assert eval_v1._is_tenant_relevant_eval_question("What premium fountain pens do you stock?", "agentfactory") is False
def test_collect_eval_items_keeps_only_grounded_candidates(monkeypatch):
temp_path = _scratch_dir().resolve()
monkeypatch.chdir(temp_path)
monkeypatch.setattr(eval_v1, "DATABASES_DIR", (temp_path / "databases").resolve())
monkeypatch.setattr(eval_v1, "_load_document_rows", lambda _db_name: [])
db_dir = eval_v1.DATABASES_DIR / "tenant_b"
_write_json(
db_dir / "config.json",
{
"business_name": "Tenant B Logistics",
"topics": "refund policy, shipping, delivery, returns",
"business_description": "Support help for refunds and shipping questions.",
},
)
_write_json(
db_dir / "analytics.json",
{
"questions": {
"How does the refund policy work?": 5,
"What is your CEO's favorite color?": 4,
"Compare standard and express shipping.": 3,
}
},
)
def fake_preflight(_base_url, _password, item):
if "refund" in item.q.lower():
item.retrieve_doc_count = 3
item.retrieve_context_length = 420
item.retrieve_context_preview = "The refund policy allows refunds within 7 days and requires proof of purchase."
elif "shipping" in item.q.lower():
item.retrieve_doc_count = 2
item.retrieve_context_length = 360
item.retrieve_context_preview = "Compare standard and express shipping by delivery speed and cost."
else:
item.retrieve_doc_count = 0
item.retrieve_context_length = 0
item.retrieve_context_preview = ""
return item
monkeypatch.setattr(eval_v1, "_preflight_retrieve", fake_preflight)
items = eval_v1._collect_eval_items("http://example.test", "pw", "tenant_b", 10)
questions = [item.q for item in items]
assert "How does the refund policy work?" in questions
assert "Compare standard and express shipping." in questions
assert "What is your CEO's favorite color?" not in questions
def test_is_grounded_requires_some_preview_relevance():
item = eval_v1.EvalItem(
q="How do refunds work?",
source="analytics",
retrieve_doc_count=5,
retrieve_context_length=500,
retrieve_context_preview="Our company values teamwork, innovation, and long-term learning culture.",
)
assert eval_v1._is_grounded(item) is False
def test_is_grounded_accepts_relevant_preview():
item = eval_v1.EvalItem(
q="How do refunds work?",
source="analytics",
retrieve_doc_count=5,
retrieve_context_length=500,
retrieve_context_preview="Refunds work within 7 days for unused items with proof of purchase.",
)
assert eval_v1._is_grounded(item) is True
def test_faq_items_still_need_retrieval_support(monkeypatch):
temp_path = _scratch_dir()
monkeypatch.chdir(temp_path)
monkeypatch.setattr(eval_v1, "DATABASES_DIR", temp_path / "databases")
monkeypatch.setattr(eval_v1, "_load_document_rows", lambda _db_name: [])
db_dir = temp_path / "databases" / "tenant_c"
_write_json(
db_dir / "faqs.json",
[{"question": "What is your refund policy?", "answer": "Refunds are allowed within 7 days."}],
)
def fake_preflight(_base_url, _password, item):
item.retrieve_doc_count = 0
item.retrieve_context_length = 0
return item
monkeypatch.setattr(eval_v1, "_preflight_retrieve", fake_preflight)
items = eval_v1._collect_eval_items("http://example.test", "pw", "tenant_c", 5)
assert items == []
def test_grade_answer_fails_long_but_ungrounded_answer():
item = eval_v1.EvalItem(
q="How does the refund policy work?",
expect="ANSWER",
source="analytics",
reference_answer="Refunds are allowed within 7 days for unused items with proof of purchase.",
retrieve_doc_count=3,
retrieve_context_length=240,
retrieve_context_preview="Refunds are allowed within 7 days for unused items with proof of purchase.",
)
status, reason, overlap = eval_v1._grade_answer(
item,
"Our company also runs luxury travel tours and celebrity meetups across Europe all summer long.",
)
assert status == "FAIL"
assert "faithful enough" in reason or "align closely enough" in reason
assert overlap == 0.0
def test_grade_answer_passes_grounded_answer():
item = eval_v1.EvalItem(
q="How does the refund policy work?",
expect="ANSWER",
source="analytics",
reference_answer="Refunds are allowed within 7 days for unused items with proof of purchase.",
retrieve_doc_count=3,
retrieve_context_length=240,
retrieve_context_preview="Refunds are allowed within 7 days for unused items with proof of purchase.",
)
status, reason, overlap = eval_v1._grade_answer(
item,
"Refunds are allowed within 7 days for unused items, and you need proof of purchase.",
)
assert status == "PASS"
assert reason is None
assert overlap >= 0.12
def test_grade_retrieval_rewards_good_top_rank():
item = eval_v1.EvalItem(
q="How do refunds work?",
reference_answer="Refunds are allowed within 7 days for unused items with proof of purchase.",
retrieve_context_preview="Refunds are allowed within 7 days for unused items with proof of purchase.",
retrieve_sources=["https://example.test/refunds"],
)
result = eval_v1._grade_retrieval(
item,
[
{
"source": "https://example.test/refunds",
"preview": "Refunds are allowed within 7 days for unused items with proof of purchase.",
},
{
"source": "https://example.test/about",
"preview": "Our company was founded in 2020 and serves online businesses.",
},
],
expected_source="https://example.test/refunds",
)
assert result["status"] == "PASS"
assert result["average_precision"] >= 0.9
assert result["first_relevant_rank"] == 1
def test_grade_retrieval_fails_when_context_is_incomplete():
item = eval_v1.EvalItem(
q="How does the refund policy work?",
reference_answer="Refunds are allowed within 7 days. Unused items require proof of purchase. Damaged goods need support approval first.",
retrieve_context_preview="Refunds are allowed within 7 days for unused items with proof of purchase.",
retrieve_sources=["https://example.test/refunds"],
)
result = eval_v1._grade_retrieval(
item,
[
{
"source": "https://example.test/refunds",
"preview": "Refunds are allowed within 7 days for unused items with proof of purchase.",
},
],
expected_source="https://example.test/refunds",
)
assert result["status"] == "FAIL"
assert result["diagnosis"] == "retrieval_incomplete"
assert result["context_recall"] < 0.8
def test_grade_retrieval_passes_product_url_hit_even_with_noisy_reference():
item = eval_v1.EvalItem(
q="What is the price of Daler Rowney Turpentine Oil?",
expect="ANSWER",
source="chunk_topic",
reference_answer=(
"Name: Daler Rowney Turpentine Oil Category: Turpentine Oil "
"Daler Rowney Turpentine Oil Rs.2,045 You may also like Car Stationery set 5 in one Rs.450 "
"M&G Stationery Tape 97322 Rs.560 Wooden Pen Stand Rs.4,085"
),
retrieve_context_preview="Daler Rowney Turpentine Oil Rs.2,045 Dilutes oil color and cleans brushes.",
retrieve_sources=["https://www.thestationerycompany.pk/products/daler-rowney-turpentine-oil"],
)
result = eval_v1._grade_retrieval(
item,
[
{
"source": "https://www.thestationerycompany.pk/products/daler-rowney-turpentine-oil",
"preview": "Daler Rowney Turpentine Oil Rs.2,045 Dilutes oil color and cleans brushes.",
}
],
expected_source="https://www.thestationerycompany.pk/products/daler-rowney-turpentine-oil",
)
assert result["hit"] is True
assert result["context_recall"] == 0.0
assert result["status"] == "PASS"
assert result["diagnosis"] == "retrieval_ok"
def test_grade_retrieval_fails_when_relevant_chunk_is_buried():
item = eval_v1.EvalItem(
q="How do refunds work?",
reference_answer="Refunds are allowed within 7 days for unused items with proof of purchase.",
retrieve_context_preview="Refunds are allowed within 7 days for unused items with proof of purchase.",
retrieve_sources=["https://example.test/refunds"],
)
result = eval_v1._grade_retrieval(
item,
[
{"source": "https://example.test/about", "preview": "We are a growing company with a remote team."},
{"source": "https://example.test/blog", "preview": "Read our latest marketing trends and AI updates."},
{
"source": "https://example.test/refunds",
"preview": "Refunds are allowed within 7 days for unused items with proof of purchase.",
},
],
expected_source="https://example.test/refunds",
)
assert result["status"] == "FAIL"
assert result["diagnosis"] == "weak_top_k"
assert result["average_precision"] < 0.55
def test_grade_answer_accepts_short_pricing_answer_when_judge_is_perfect():
item = eval_v1.EvalItem(
q="What is the price of Daler Rowney Turpentine Oil?",
expect="ANSWER",
source="chunk_topic",
reference_answer="Daler Rowney Turpentine Oil Rs.2,045",
retrieve_doc_count=2,
retrieve_context_length=160,
retrieve_context_preview="Daler Rowney Turpentine Oil Rs.2,045 Dilutes oil color to a thin wash.",
retrieve_sources=["https://www.thestationerycompany.pk/products/daler-rowney-turpentine-oil"],
)
verdict = JudgeVerdict(
faithfulness_score=1.0,
answer_relevance_score=1.0,
likely_failure_source="none",
)
status, reason, _ = eval_v1._grade_answer(item, "Rs.2,045", judge_verdict=verdict)
assert status == "PASS"
assert reason is None
def test_grade_answer_classifies_refuse_consistently():
item = eval_v1.EvalItem(
q="What is the weather today?",
expect="REFUSE",
source="analytics",
)
status, reason, _ = eval_v1._grade_answer(
item,
"I can't help with that because it falls outside my scope. I specialize in helping with the tenant knowledge base.",
)
assert status == "PASS"
assert reason is None
def test_grade_answer_classifies_idk_consistently():
item = eval_v1.EvalItem(
q="What is your CEO's favorite color?",
expect="IDK",
source="analytics",
)
status, reason, _ = eval_v1._grade_answer(
item,
"I don't have that information in my knowledge base right now.",
)
assert status == "PASS"
assert reason is None
def test_grade_answer_fails_unfaithful_supported_sounding_answer():
item = eval_v1.EvalItem(
q="What is the refund policy?",
expect="ANSWER",
source="faq",
reference_answer="Refunds are allowed within 7 days for unused items with proof of purchase.",
retrieve_doc_count=3,
retrieve_context_length=240,
retrieve_context_preview="Refunds are allowed within 7 days for unused items with proof of purchase.",
)
status, reason, _ = eval_v1._grade_answer(
item,
"Refunds are only available within 30 days, and opened items are always accepted without proof of purchase.",
)
assert status == "FAIL"
assert "faithful enough" in reason
def test_statement_supported_rejects_wrong_numeric_claim():
ok, score = eval_v1._statement_supported(
"Refunds are allowed within 3 days for unused items.",
["Refunds are allowed within 7 days for unused items with proof of purchase."],
)
assert ok is False
assert score == 0.0
def test_answer_metrics_penalize_irrelevant_rambling():
item = eval_v1.EvalItem(
q="How do I register for the program?",
expect="ANSWER",
source="faq",
reference_answer="Create an account, verify your email, and complete the checkout form.",
retrieve_doc_count=2,
retrieve_context_length=200,
retrieve_context_preview="Create an account, verify your email, and complete the checkout form.",
)
metrics = eval_v1._answer_metrics(
item,
"Our team cares deeply about student success and community values. We believe in practical learning and innovation.",
)
assert metrics["answer_relevance"] < 0.5
def test_answer_metrics_include_context_recall():
item = eval_v1.EvalItem(
q="How does the refund policy work?",
expect="ANSWER",
source="faq",
reference_answer="Refunds are allowed within 7 days. Proof of purchase is required. Support approval is needed for damaged goods.",
retrieve_doc_count=2,
retrieve_context_length=200,
retrieve_context_preview="Refunds are allowed within 7 days. Proof of purchase is required.",
)
metrics = eval_v1._answer_metrics(item, "Refunds are allowed within 7 days and require proof of purchase.")
assert metrics["context_recall"] is not None
assert metrics["context_recall"] < 1.0
def test_statement_relevant_is_stricter_for_long_domain_overlap():
ok, score = eval_v1._statement_relevant(
"The course learning dashboard offers a friendly community experience for members.",
"How do I cancel my subscription plan?",
"Cancel a subscription from the billing settings page.",
)
assert ok is False
assert score < 0.35
def test_build_summary_does_not_fake_idk_score():
results = [
{"retrieval_status": "PASS", "answer_status": "PASS", "idk_status": "SKIP", "overall_status": "PASS"},
{"retrieval_status": "FAIL", "answer_status": "FAIL", "idk_status": "SKIP", "overall_status": "FAIL"},
]
summary = eval_v1._build_summary(results)
assert summary["overall_score"] == 5.0
assert summary["retrieval_score"] == 5.0
assert summary["answer_score"] == 5.0
assert summary["idk_score"] is None
assert summary["score_meanings"]["idk"] == "Not exercised in this run."
def test_build_summary_includes_failure_breakdown():
results = [
{"retrieval_status": "FAIL", "answer_status": "SKIP", "idk_status": "SKIP", "overall_status": "FAIL", "retrieval_diagnosis": "weak_top_k"},
{"retrieval_status": "PASS", "answer_status": "FAIL", "idk_status": "SKIP", "overall_status": "FAIL", "answer_diagnosis": "grounded_but_answered_idk"},
]
summary = eval_v1._build_summary(results)
assert summary["failure_breakdown"] == {
"weak_top_k": 1,
"grounded_but_answered_idk": 1,
}
def test_build_summary_includes_failure_source_mix():
results = [
{
"retrieval_status": "PASS",
"answer_status": "FAIL",
"idk_status": "SKIP",
"overall_status": "FAIL",
"answer_diagnosis": "answer_not_faithful",
"judge": {"likely_failure_source": "prompt_overconstraint", "root_cause_note": "Tier 3 IDK rule is too aggressive.", "fix_hint": "Relax scope guard for grounded answers."},
},
{
"retrieval_status": "PASS",
"answer_status": "FAIL",
"idk_status": "SKIP",
"overall_status": "FAIL",
"answer_diagnosis": "answer_irrelevant",
"judge": {"likely_failure_source": "answer_generation_drift", "root_cause_note": "Answer ignored the main question focus.", "fix_hint": "Tighten response-format prompt."},
},
]
summary = eval_v1._build_summary(results)
assert summary["failure_source_mix"] == {
"prompt_overconstraint": 1,
"answer_generation_drift": 1,
}
assert summary["root_cause_mix"] == {
"Tier 3 IDK rule is too aggressive.": 1,
"Answer ignored the main question focus.": 1,
}
assert summary["fix_hint_mix"] == {
"Relax scope guard for grounded answers.": 1,
"Tighten response-format prompt.": 1,
}
def test_grade_answer_uses_judge_scores_when_available():
item = eval_v1.EvalItem(
q="What is the refund policy?",
expect="ANSWER",
source="faq",
reference_answer="Refunds are allowed within 7 days for unused items with proof of purchase.",
retrieve_doc_count=3,
retrieve_context_length=240,
retrieve_context_preview="Refunds are allowed within 7 days for unused items with proof of purchase.",
)
verdict = JudgeVerdict(
faithfulness_score=0.92,
answer_relevance_score=0.88,
likely_failure_source="none",
)
metrics = eval_v1._answer_metrics(
item,
"You have a week to return unused items, and proof of purchase is required.",
judge_verdict=verdict,
)
assert metrics["judge_used"] is True
assert metrics["faithfulness"] == 0.92
assert metrics["answer_relevance"] == 0.88
assert metrics["deterministic_faithfulness"] != metrics["faithfulness"] or metrics["deterministic_answer_relevance"] != metrics["answer_relevance"]
def test_grade_answer_falls_back_when_judge_errors():
item = eval_v1.EvalItem(
q="What is the refund policy?",
expect="ANSWER",
source="faq",
reference_answer="Refunds are allowed within 7 days for unused items with proof of purchase.",
retrieve_doc_count=3,
retrieve_context_length=240,
retrieve_context_preview="Refunds are allowed within 7 days for unused items with proof of purchase.",
)
verdict = JudgeVerdict(error="judge_http_429")
metrics = eval_v1._answer_metrics(
item,
"Refunds are allowed within 7 days for unused items, and you need proof of purchase.",
judge_verdict=verdict,
)
assert metrics["judge_used"] is False
assert metrics["faithfulness"] == metrics["deterministic_faithfulness"]
assert metrics["answer_relevance"] == metrics["deterministic_answer_relevance"]
def test_finalize_selection_uses_stable_core_and_rotates_discovery(monkeypatch):
temp_path = _scratch_dir()
monkeypatch.chdir(temp_path)
monkeypatch.setattr(eval_v1, "DATABASES_DIR", temp_path / "databases")
monkeypatch.setattr(eval_v1, "_load_document_rows", lambda _db_name: [])
core_pool = [
eval_v1.EvalItem(q="What is AgentFactory?", source="faq", frequency=1000, difficulty="easy", candidate_key="faq::1"),
eval_v1.EvalItem(q="How does the curriculum work?", source="analytics", frequency=20, difficulty="medium", candidate_key="analytics::1"),
eval_v1.EvalItem(q="Who teaches the course?", source="analytics", frequency=10, difficulty="hard", candidate_key="analytics::2"),
]
discovery_pool = [
eval_v1.EvalItem(q="What are the key details about Chapter 19?", source="chunk_topic", difficulty="easy", candidate_key="chunk::1"),
eval_v1.EvalItem(q="Summarize Chapter 20.", source="chunk_topic", difficulty="medium", candidate_key="chunk::2"),
eval_v1.EvalItem(q="How does Chapter 21 work?", source="chunk_topic", difficulty="hard", candidate_key="chunk::3"),
]
first = eval_v1._finalize_selection(core_pool, discovery_pool, 4, "tenant_rotate")
second = eval_v1._finalize_selection(core_pool, discovery_pool, 4, "tenant_rotate")
first_core = [item.q for item in first if item.selection_bucket == "stable_core"]
second_core = [item.q for item in second if item.selection_bucket == "stable_core"]
first_discovery = [item.q for item in first if item.selection_bucket == "rotating_discovery"]
second_discovery = [item.q for item in second if item.selection_bucket == "rotating_discovery"]
assert first_core == second_core
assert first_discovery != second_discovery
assert len(first_discovery) == 2
assert len(second_discovery) == 2
def test_chunk_state_file_is_module_relative():
path = eval_v1._chunk_state_file("tenant_x")
assert path.parent == eval_v1.EVALS_DIR / "state"
assert path.name == "tenant_x_rotation.json"
def test_preflight_retrieve_raises_on_owner_auth_failure(monkeypatch):
class FakeResponse:
status_code = 403
monkeypatch.setattr(eval_v1.requests, "post", lambda *args, **kwargs: FakeResponse())
item = eval_v1.EvalItem(q="What is AgentFactory?")
try:
eval_v1._preflight_retrieve("http://example.test", "wrong", item)
assert False, "Expected EvalAuthError"
except eval_v1.EvalAuthError as exc:
assert "Owner auth failed" in str(exc)
def test_load_document_rows_prefers_sqlite_then_falls_back_to_chroma(monkeypatch):
chroma_rows = [("doc-a", "alpha")]
sqlite_rows = [("doc-b", "beta")]
monkeypatch.setattr(eval_v1, "_load_document_rows_via_chroma", lambda _db_name: chroma_rows)
monkeypatch.setattr(eval_v1, "_load_document_rows_via_sqlite", lambda _db_name: sqlite_rows)
assert eval_v1._load_document_rows("tenant_x") == sqlite_rows
monkeypatch.setattr(eval_v1, "_load_document_rows_via_sqlite", lambda _db_name: [])
assert eval_v1._load_document_rows("tenant_x") == chroma_rows
def test_doc_qa_fallback_filters_malformed_prompt_blobs(monkeypatch):
monkeypatch.setattr(
eval_v1,
"_load_document_rows",
lambda _db_name: [
(
"doc-1",
'Q: [The question as an employee would ask it]** A: [Your verified plain-language answer from /policy-lookup]',
),
(
"doc-2",
"Q: What is AgentFactory? A: AgentFactory is an AI agent learning platform.",
),
],
)
items = eval_v1._load_doc_qa_fallbacks("tenant_x", 10)
assert [item.q for item in items] == ["What is AgentFactory?"]
assert items[0].expect == "ANSWER"
def test_doc_qa_fallback_marks_missing_info_as_idk(monkeypatch):
monkeypatch.setattr(
eval_v1,
"_load_document_rows",
lambda _db_name: [
(
"doc-1",
"Q: Can TaskManager integrate with Jira? A: I searched the documentation but couldn't find information about Jira integration and escalated this to support.",
),
],
)
items = eval_v1._load_doc_qa_fallbacks("tenant_x", 10)
assert len(items) == 1
assert items[0].expect == "IDK"
def test_chunk_topic_candidates_are_filtered_and_not_stuck_on_first_rows(monkeypatch):
rows = [
("1", "Meeting notes: use these codes while taking notes. D: decision. A: action."),
("2", "def build_agent(): return workflow"),
("3", "Chapter 19: File Processing Workflows explains how files are transformed, validated, and routed through an agent workflow with structured outputs and review steps."),
("4", "Computation and Data Extraction covers pulling structured values from messy text and converting them into reliable fields for downstream use.", "https://example.test/computation-and-data-extraction"),
]
monkeypatch.setattr(eval_v1, "_load_document_rows", lambda _db_name: rows)
monkeypatch.setattr(eval_v1, "_candidate_row_order", lambda rows: rows)
items = eval_v1._load_chunk_topic_candidates("tenant_x", 1)
questions = [item.q for item in items]
assert questions
assert all("Meeting notes" not in q for q in questions)
assert all("def build_agent" not in q for q in questions)
assert any("Chapter 19" in q or "Computation and Data Extraction" in q for q in questions)
def test_chunk_topic_candidates_filter_transfer_log_topics(monkeypatch):
rows = [
("1", "objects: 100% (245/245), 89.42 KiB | 1.29 MiB/s, done. Create LEARNING-SPEC.md with your notes."),
]
monkeypatch.setattr(eval_v1, "_load_document_rows", lambda _db_name: rows)
monkeypatch.setattr(eval_v1, "_candidate_row_order", lambda rows: rows)
items = eval_v1._load_chunk_topic_candidates("tenant_x", 10)
assert items == []
def test_make_chunk_question_prefers_process_wording_for_step_chunks():
q = eval_v1._make_chunk_question(
"Spec And Build Your First Tool",
"Step 1: write the spec. Step 2: review it. Step 3: build and verify the tool.",
)
assert q == "What is the process for Spec And Build Your First Tool?"
def test_make_chunk_question_uses_prerequisite_wording_when_reference_supports_it():
q = eval_v1._make_chunk_question(
"Chapter 65: Enable File Checkpointing",
"Chapter 65: Enable File Checkpointing. Prerequisites include a configured storage backend and an enabled checkpoint worker before you start the setup.",
)
assert q == "What are the prerequisites for Chapter 65: Enable File Checkpointing?"
def test_make_chunk_question_skips_quiz_like_topics():
q = eval_v1._make_chunk_question(
"Chapter Quiz",
"Prompt 1: answer the quiz questions and review your work.",
)
assert q == ""
def test_trim_seed_pool_does_not_force_minimum_of_ten():
items = [
eval_v1.EvalItem(q="What is AgentFactory?", source="faq", frequency=1000, difficulty="easy"),
eval_v1.EvalItem(q="How does the curriculum work?", source="analytics", frequency=10, difficulty="medium"),
eval_v1.EvalItem(q="What does Chapter 1 cover?", source="chunk_topic", frequency=2, difficulty="hard"),
]
trimmed = eval_v1._trim_seed_pool(items, 3)
assert 3 <= len(trimmed) <= 6
def test_chunk_reference_answer_prefers_clean_semantic_sentences():
ref = eval_v1._chunk_reference_answer(
"Refund Policy",
"""
What you are learning: this exercise builds judgment.
The refund policy allows refunds within 7 days for unused items with proof of purchase.
Customers must contact support before returning damaged goods.
Prompt 3: compare your answer with a partner.
""",
)
assert "refund policy allows refunds within 7 days" in ref.lower()
assert "prompt 3" not in ref.lower()
assert "what you are learning" not in ref.lower()
def test_database_dir_is_project_relative():
expected = eval_v1.EVALS_DIR.parent / "databases"
assert eval_v1.DATABASES_DIR == expected
def test_collect_eval_items_end_to_end_with_seeded_sources(monkeypatch):
monkeypatch.setattr(
eval_v1,
"_collect_seed_items",
lambda _db_name, _desired_count: [
eval_v1.EvalItem(
q="What is AgentFactory?",
source="faq",
difficulty="easy",
reference_answer="AgentFactory is an AI agent learning platform.",
frequency=1000,
candidate_key="faq::agentfactory",
),
eval_v1.EvalItem(
q="How does the curriculum work?",
source="analytics",
difficulty="medium",
reference_answer="The curriculum is delivered in guided chapters with projects.",
frequency=10,
candidate_key="analytics::curriculum",
),
eval_v1.EvalItem(
q="Summarize Chapter 24.",
source="chunk_topic",
difficulty="hard",
reference_answer="Chapter 24 is about building a proactive AI employee.",
frequency=2,
candidate_key="chunk::24",
),
],
)
def fake_preflight(_base_url, _password, item):
item.retrieve_doc_count = 3
item.retrieve_context_length = 300
item.retrieve_context_preview = item.reference_answer
item.retrieve_sources = ["seeded-source"]
return item
monkeypatch.setattr(eval_v1, "_preflight_retrieve", fake_preflight)
monkeypatch.setattr(eval_v1, "_save_rotation_state", lambda *_args, **_kwargs: None)
monkeypatch.setattr(eval_v1, "_load_rotation_state", lambda _db_name: {"cursor": 0})
items = eval_v1._collect_eval_items("http://example.test", "ownerpw", "tenant_seeded", 3)
assert len(items) == 3
assert {item.source for item in items} == {"faq", "analytics", "chunk_topic"}
assert all(item.retrieve_doc_count > 0 for item in items)