Files
siemens_ragas/tests/test_dataset_build.py

780 lines
31 KiB
Python
Raw Normal View History

2026-06-12 14:02:15 +08:00
import csv
import json
import shutil
import unittest
from pathlib import Path
from unittest import mock
from pydantic import ValidationError
from rag_eval.dataset_builder.generator.question_generator import OpenAIQuestionGenerator
from rag_eval.dataset_builder.generator.validators import dedupe_samples, validate_draft_sample
from rag_eval.dataset_builder.models import DraftQuestionSample, ParsedDocument, SourceChunk
from rag_eval.dataset_builder.parser.aliyun_document_parser import AliyunDocumentParser
from rag_eval.dataset_builder.parser.aliyun_docmind_gateway import AliyunDocmindGateway
from rag_eval.dataset_builder.parser.aliyun_layout_normalizer import normalize_layouts
from rag_eval.dataset_builder.runner import load_dataset_build_job, run_dataset_build
from rag_eval.dataset_builder.schema import DatasetBuildConfigModel
from rag_eval.dataset_builder.sources import discover_pdf_files
from rag_eval.settings import EvaluationSettings
class FakeParser:
def __init__(self, documents_by_name, failures=None):
self.documents_by_name = documents_by_name
self.failures = failures or set()
def parse(self, pdf_path: Path):
if pdf_path.name in self.failures:
raise RuntimeError(f"parse failed for {pdf_path.name}")
return self.documents_by_name[pdf_path.name]
class FakeGenerator:
def __init__(self, outputs_by_doc_id):
self.outputs_by_doc_id = outputs_by_doc_id
def generate(self, document, *, max_questions, max_chunks_per_question, job_name):
return list(self.outputs_by_doc_id.get(document.doc_id, []))
class FakeGateway(AliyunDocmindGateway):
def __init__(self, settings, *, statuses=None, layouts=None):
super().__init__(settings)
self.statuses = list(statuses or [])
self.layouts = list(layouts or [])
def submit_parse_task(self, pdf_path: Path) -> str:
return "task-1"
def get_task_status(self, task_id: str):
if self.statuses:
return self.statuses.pop(0)
return {"status": "succeeded", "doc_id": "doc-1", "doc_name": "doc1.pdf"}
def fetch_layouts(self, task_id: str):
return list(self.layouts)
class DatasetBuildTests(unittest.TestCase):
def setUp(self) -> None:
root = Path("tests/.tmp").resolve()
root.mkdir(parents=True, exist_ok=True)
self.temp_dir = root / self._testMethodName
shutil.rmtree(self.temp_dir, ignore_errors=True)
self.temp_dir.mkdir(parents=True, exist_ok=True)
self.input_dir = self.temp_dir / "pdfs"
self.input_dir.mkdir(parents=True, exist_ok=True)
(self.input_dir / "doc1.pdf").write_bytes(b"%PDF-1.4 doc1")
(self.input_dir / "doc2.pdf").write_bytes(b"%PDF-1.4 doc2")
self.config_path = self.temp_dir / "dataset-build.yaml"
self.config_path.write_text(
"\n".join(
[
"job_name: sample-build",
"input:",
f" path: {self.input_dir.as_posix()}",
" glob: '*.pdf'",
"parser:",
" provider: aliyun_docmind",
" failure_mode: skip",
"generation:",
" output_type: online_question_bank",
" review_mode: draft_with_manual_review",
" max_questions_per_document: 3",
" max_source_chunks_per_question: 2",
"output:",
f" dataset_path: {(self.temp_dir / 'generated' / 'draft.csv').as_posix()}",
f" artifact_dir: {(self.temp_dir / 'outputs').as_posix()}",
"runtime:",
" max_documents: 2",
]
),
encoding="utf-8",
)
def tearDown(self) -> None:
shutil.rmtree(self.temp_dir, ignore_errors=True)
def _make_document(self, doc_id: str, doc_name: str) -> ParsedDocument:
chunk = SourceChunk(
chunk_id=f"{doc_id}-chunk-1",
doc_id=doc_id,
doc_name=doc_name,
text="Section content for review.",
page_start=1,
page_end=2,
section_path="Chapter 1 > Scope",
section_title="Scope",
source_layout_ids=["layout-1"],
)
return ParsedDocument(
doc_id=doc_id,
doc_name=doc_name,
raw_text=chunk.text,
structure_nodes=[],
semantic_blocks=[],
source_chunks=[chunk],
metadata={},
)
def test_load_dataset_build_job_resolves_paths_and_defaults(self) -> None:
settings = EvaluationSettings.model_construct(dataset_generator_model="env-model")
job = load_dataset_build_job(self.config_path, settings=settings)
self.assertEqual(job.job_name, "sample-build")
self.assertEqual(job.generation_model, "env-model")
self.assertTrue(job.dataset_path.is_absolute())
self.assertEqual(job.failure_mode, "skip")
def test_load_dataset_build_job_prefers_yaml_generation_model(self) -> None:
config_path = self.temp_dir / "dataset-build-with-model.yaml"
config_path.write_text(
self.config_path.read_text(encoding="utf-8").replace(
"generation:\n",
"generation:\n model: yaml-model\n",
),
encoding="utf-8",
)
settings = EvaluationSettings.model_construct(dataset_generator_model="env-model")
job = load_dataset_build_job(config_path, settings=settings)
self.assertEqual(job.generation_model, "yaml-model")
def test_load_dataset_build_job_uses_env_default_failure_mode(self) -> None:
config_path = self.temp_dir / "dataset-build-without-failure-mode.yaml"
config_path.write_text(
self.config_path.read_text(encoding="utf-8").replace(" failure_mode: skip\n", ""),
encoding="utf-8",
)
settings = EvaluationSettings.model_construct(
dataset_generator_model="env-model",
parser_failure_mode="skip",
)
job = load_dataset_build_job(config_path, settings=settings)
self.assertEqual(job.failure_mode, "skip")
def test_discover_pdf_files_rejects_missing_or_empty_input(self) -> None:
with self.assertRaises(FileNotFoundError):
discover_pdf_files(self.temp_dir / "missing")
empty_dir = self.temp_dir / "empty"
empty_dir.mkdir()
with self.assertRaises(ValueError):
discover_pdf_files(empty_dir)
def test_discover_pdf_files_accepts_single_pdf_file(self) -> None:
pdf_path = self.input_dir / "doc1.pdf"
files = discover_pdf_files(pdf_path)
self.assertEqual(files, [pdf_path])
def test_dataset_build_schema_rejects_missing_required_fields(self) -> None:
with self.assertRaises(ValidationError):
DatasetBuildConfigModel.model_validate(
{
"job_name": "sample-build",
"parser": {"provider": "aliyun_docmind"},
"generation": {
"output_type": "online_question_bank",
"review_mode": "draft_with_manual_review",
},
"output": {
"dataset_path": "draft.csv",
"artifact_dir": "outputs",
},
}
)
def test_dataset_build_schema_rejects_invalid_enums(self) -> None:
with self.assertRaises(ValidationError):
DatasetBuildConfigModel.model_validate(
{
"job_name": "sample-build",
"input": {"path": self.input_dir.as_posix()},
"parser": {"provider": "other-provider", "failure_mode": "ignore"},
"generation": {
"output_type": "other-output",
"review_mode": "auto_publish",
},
"output": {
"dataset_path": "draft.csv",
"artifact_dir": "outputs",
},
}
)
def test_normalize_layouts_applies_core_rules(self) -> None:
layouts = [
{"type": "toc", "text": "目录", "page": 1, "layout_id": "toc-1"},
{"type": "heading", "text": "第一章 总则", "page": 2, "layout_id": "h1", "level": 1},
{"type": "paragraph", "text": "第一段。", "page": 2, "layout_id": "p1"},
{"type": "caption", "text": "系统示意图", "page": 2, "layout_id": "c1"},
{
"type": "table",
"rows": [["字段", "说明"], ["名称", "项目名称"]],
"page": 3,
"layout_id": "t1",
},
]
document = normalize_layouts(doc_id="doc-1", doc_name="sample.pdf", layouts=layouts, max_chunk_chars=80, overlap_chars=10)
self.assertEqual(len(document.structure_nodes), 1)
self.assertEqual(document.structure_nodes[0].section_path, "第一章 总则")
self.assertEqual(len(document.semantic_blocks), 1)
self.assertIn("图注:", document.semantic_blocks[0].text)
self.assertIn("字段 | 说明", document.semantic_blocks[0].text)
self.assertEqual(document.source_chunks[0].page_start, 2)
self.assertEqual(document.source_chunks[0].page_end, 3)
def test_normalize_layouts_splits_long_text_into_multiple_chunks(self) -> None:
long_text = "A" * 220
layouts = [
{"type": "heading", "text": "Chapter 1", "page": 1, "layout_id": "h1", "level": 1},
{"type": "paragraph", "text": long_text, "page": 1, "layout_id": "p1"},
]
document = normalize_layouts(
doc_id="doc-1",
doc_name="sample.pdf",
layouts=layouts,
max_chunk_chars=100,
overlap_chars=20,
)
self.assertGreaterEqual(len(document.source_chunks), 3)
self.assertTrue(all(chunk.section_title == "Chapter 1" for chunk in document.source_chunks))
def test_validate_and_dedupe_generated_samples(self) -> None:
document = self._make_document("doc-1", "doc1.pdf")
valid = DraftQuestionSample(
sample_id="doc-1-q1",
question="这份文档的范围是什么?",
ground_truth="文档说明了适用范围。",
scenario="sample-build",
language="zh",
doc_id="doc-1",
doc_name="doc1.pdf",
section_path="Chapter 1 > Scope",
page_start=1,
page_end=2,
source_chunk_ids=["doc-1-chunk-1"],
question_type="summary",
difficulty="easy",
)
invalid = DraftQuestionSample(
sample_id="doc-1-q2",
question="",
ground_truth="",
scenario="sample-build",
language="zh",
doc_id="doc-2",
doc_name="doc1.pdf",
section_path="",
page_start=0,
page_end=0,
source_chunk_ids=["missing-chunk"],
question_type="invalid",
difficulty="invalid",
)
duplicate = DraftQuestionSample(
sample_id="doc-1-q3",
question=" 这份文档的范围是什么? ",
ground_truth="文档说明了适用范围",
scenario="sample-build",
language="zh",
doc_id="doc-1",
doc_name="doc1.pdf",
section_path="Chapter 1 > Scope",
page_start=1,
page_end=2,
source_chunk_ids=["doc-1-chunk-1"],
question_type="summary",
difficulty="easy",
)
self.assertEqual(validate_draft_sample(valid, document=document), [])
self.assertTrue(validate_draft_sample(invalid, document=document))
self.assertEqual(len(dedupe_samples([valid, duplicate])), 1)
def test_validate_rejects_too_many_source_chunks(self) -> None:
document = self._make_document("doc-1", "doc1.pdf")
document.source_chunks.append(
SourceChunk(
chunk_id="doc-1-chunk-2",
doc_id="doc-1",
doc_name="doc1.pdf",
text="More content",
page_start=2,
page_end=3,
section_path="Chapter 1 > Scope",
section_title="Scope",
source_layout_ids=["layout-2"],
)
)
sample = DraftQuestionSample(
sample_id="doc-1-q1",
question="What is the scope?",
ground_truth="It defines scope.",
scenario="sample-build",
language="en",
doc_id="doc-1",
doc_name="doc1.pdf",
section_path="Chapter 1 > Scope",
page_start=1,
page_end=3,
source_chunk_ids=["doc-1-chunk-1", "doc-1-chunk-2"],
question_type="fact",
difficulty="easy",
)
errors = validate_draft_sample(
sample,
document=document,
max_source_chunks_per_question=1,
)
self.assertTrue(any("exceeds limit" in error for error in errors))
def test_dedupe_keeps_only_one_question_per_chunk_group(self) -> None:
sample_a = DraftQuestionSample(
sample_id="doc-1-q1",
question="What is the scope?",
ground_truth="It defines the scope.",
scenario="sample-build",
language="en",
doc_id="doc-1",
doc_name="doc1.pdf",
section_path="Chapter 1 > Scope",
page_start=1,
page_end=2,
source_chunk_ids=["doc-1-chunk-1"],
question_type="fact",
difficulty="easy",
)
sample_b = DraftQuestionSample(
sample_id="doc-1-q2",
question="How is the scope described?",
ground_truth="The scope is described in the first section.",
scenario="sample-build",
language="en",
doc_id="doc-1",
doc_name="doc1.pdf",
section_path="Chapter 1 > Scope",
page_start=1,
page_end=2,
source_chunk_ids=["doc-1-chunk-1"],
question_type="summary",
difficulty="medium",
)
self.assertEqual(len(dedupe_samples([sample_a, sample_b])), 1)
def test_aliyun_gateway_parse_success_failure_and_timeout(self) -> None:
settings = EvaluationSettings.model_construct(
aliyun_parse_poll_interval_seconds=1,
aliyun_parse_timeout_seconds=1,
)
pdf_path = self.input_dir / "doc1.pdf"
success_gateway = FakeGateway(
settings,
statuses=[{"status": "running"}, {"status": "succeeded", "doc_id": "doc-1", "doc_name": "doc1.pdf"}],
layouts=[{"type": "paragraph", "text": "hello", "page": 1, "layout_id": "p1"}],
)
with mock.patch("rag_eval.dataset_builder.parser.aliyun_docmind_gateway.time.sleep", return_value=None), mock.patch(
"rag_eval.dataset_builder.parser.aliyun_docmind_gateway.time.monotonic",
side_effect=[0.0, 0.1, 0.2],
):
payload = success_gateway.parse_document(pdf_path)
self.assertEqual(payload["doc_id"], "doc-1")
self.assertEqual(len(payload["layouts"]), 1)
failure_gateway = FakeGateway(settings, statuses=[{"status": "failed", "message": "bad file"}])
with self.assertRaises(RuntimeError):
failure_gateway.parse_document(pdf_path)
timeout_gateway = FakeGateway(settings, statuses=[{"status": "running"}, {"status": "running"}])
with mock.patch("rag_eval.dataset_builder.parser.aliyun_docmind_gateway.time.sleep", return_value=None), mock.patch(
"rag_eval.dataset_builder.parser.aliyun_docmind_gateway.time.monotonic",
side_effect=[0.0, 2.0],
):
with self.assertRaises(TimeoutError):
timeout_gateway.parse_document(pdf_path)
def test_aliyun_gateway_reports_missing_sdk(self) -> None:
settings = EvaluationSettings.model_construct(
aliyun_parse_poll_interval_seconds=1,
aliyun_parse_timeout_seconds=1,
)
gateway = AliyunDocmindGateway(settings)
with mock.patch("rag_eval.dataset_builder.parser.aliyun_docmind_gateway.DocmindClient", None), mock.patch(
"rag_eval.dataset_builder.parser.aliyun_docmind_gateway.docmind_models", None
), mock.patch("rag_eval.dataset_builder.parser.aliyun_docmind_gateway.openapi_models", None), mock.patch(
"rag_eval.dataset_builder.parser.aliyun_docmind_gateway.runtime_models", None
):
with self.assertRaises(ImportError):
gateway._load_sdk()
def test_document_parser_rejects_empty_layouts(self) -> None:
settings = EvaluationSettings.model_construct(
aliyun_parse_poll_interval_seconds=1,
aliyun_parse_timeout_seconds=1,
)
gateway = FakeGateway(
settings,
statuses=[{"status": "succeeded", "doc_id": "doc-1", "doc_name": "doc1.pdf"}],
layouts=[],
)
parser = AliyunDocumentParser(gateway)
with self.assertRaises(ValueError):
parser.parse(self.input_dir / "doc1.pdf")
def test_run_dataset_build_skip_mode_writes_all_artifacts(self) -> None:
doc1 = self._make_document("doc-1", "doc1.pdf")
parser = FakeParser(
{"doc1.pdf": doc1},
failures={"doc2.pdf"},
)
generator = FakeGenerator(
{
"doc-1": [
DraftQuestionSample(
sample_id="doc-1-q1",
question="What is the scope?",
ground_truth="It defines the scope.",
scenario="sample-build",
language="en",
doc_id="doc-1",
doc_name="doc1.pdf",
section_path="Chapter 1 > Scope",
page_start=1,
page_end=2,
source_chunk_ids=["doc-1-chunk-1"],
question_type="fact",
difficulty="easy",
)
]
}
)
result = run_dataset_build(
self.config_path,
settings=EvaluationSettings.model_construct(dataset_generator_model="stub-model"),
parser=parser,
generator=generator,
)
self.assertEqual(len(result.documents), 1)
self.assertEqual(len(result.parse_failures), 1)
self.assertEqual(len(result.draft_samples), 1)
self.assertTrue(result.artifact_paths.documents_jsonl.exists())
self.assertTrue(result.artifact_paths.semantic_blocks_jsonl.exists())
self.assertTrue(result.artifact_paths.source_chunks_jsonl.exists())
self.assertTrue(result.artifact_paths.dataset_draft_csv.exists())
self.assertTrue(result.artifact_paths.parse_failures_csv.exists())
self.assertTrue(result.artifact_paths.metadata_json.exists())
self.assertTrue(result.job.dataset_path.exists())
latest_dir = result.job.artifact_dir / "latest"
self.assertTrue((latest_dir / "source_chunks.jsonl").exists())
self.assertTrue((latest_dir / "dataset_draft.csv").exists())
self.assertTrue((latest_dir / "metadata.json").exists())
with result.artifact_paths.parse_failures_csv.open(encoding="utf-8") as handle:
rows = list(csv.DictReader(handle))
self.assertEqual(len(rows), 1)
self.assertIn("doc2.pdf", rows[0]["file_path"])
metadata = json.loads(result.artifact_paths.metadata_json.read_text(encoding="utf-8"))
self.assertEqual(metadata["stats"]["documents_processed"], 1)
self.assertEqual(metadata["stats"]["parse_failures"], 1)
latest_metadata = json.loads((latest_dir / "metadata.json").read_text(encoding="utf-8"))
self.assertEqual(latest_metadata["run_id"], result.run_id)
with result.artifact_paths.source_chunks_jsonl.open(encoding="utf-8") as handle:
run_chunks = handle.read()
with (latest_dir / "source_chunks.jsonl").open(encoding="utf-8") as handle:
latest_chunks = handle.read()
self.assertEqual(latest_chunks, run_chunks)
def test_run_dataset_build_single_pdf_input(self) -> None:
single_pdf_config = self.temp_dir / "single-pdf-build.yaml"
single_pdf_config.write_text(
self.config_path.read_text(encoding="utf-8").replace(
f" path: {self.input_dir.as_posix()}",
f" path: {(self.input_dir / 'doc1.pdf').as_posix()}",
),
encoding="utf-8",
)
parser = FakeParser({"doc1.pdf": self._make_document("doc-1", "doc1.pdf")})
generator = FakeGenerator(
{
"doc-1": [
DraftQuestionSample(
sample_id="doc-1-q1",
question="What is the scope?",
ground_truth="It defines the scope.",
scenario="sample-build",
language="en",
doc_id="doc-1",
doc_name="doc1.pdf",
section_path="Chapter 1 > Scope",
page_start=1,
page_end=2,
source_chunk_ids=["doc-1-chunk-1"],
question_type="fact",
difficulty="easy",
)
]
}
)
result = run_dataset_build(
single_pdf_config,
settings=EvaluationSettings.model_construct(dataset_generator_model="stub-model"),
parser=parser,
generator=generator,
)
self.assertEqual(len(result.documents), 1)
self.assertEqual(result.documents[0].doc_name, "doc1.pdf")
self.assertEqual(len(result.draft_samples), 1)
def test_run_dataset_build_caps_questions_per_document(self) -> None:
doc1 = self._make_document("doc-1", "doc1.pdf")
parser = FakeParser({"doc1.pdf": doc1, "doc2.pdf": self._make_document("doc-2", "doc2.pdf")})
generator = FakeGenerator(
{
"doc-1": [
DraftQuestionSample(
sample_id=f"doc-1-q{index}",
question=f"Question {index}?",
ground_truth=f"Answer {index}.",
scenario="sample-build",
language="en",
doc_id="doc-1",
doc_name="doc1.pdf",
section_path="Chapter 1 > Scope",
page_start=1,
page_end=2,
source_chunk_ids=[f"doc-1-chunk-{index}"],
question_type="fact",
difficulty="easy",
)
for index in range(1, 5)
]
}
)
# Rebuild the doc with enough chunk ids for validation to pass.
doc1.source_chunks = [
SourceChunk(
chunk_id=f"doc-1-chunk-{index}",
doc_id="doc-1",
doc_name="doc1.pdf",
text=f"Chunk {index}",
page_start=index,
page_end=index,
section_path="Chapter 1 > Scope",
section_title="Scope",
source_layout_ids=[f"layout-{index}"],
)
for index in range(1, 5)
]
result = run_dataset_build(
self.config_path,
settings=EvaluationSettings.model_construct(dataset_generator_model="stub-model"),
parser=parser,
generator=generator,
)
self.assertLessEqual(len([item for item in result.draft_samples if item.doc_id == "doc-1"]), 3)
def test_run_dataset_build_filters_questions_exceeding_chunk_limit(self) -> None:
doc1 = self._make_document("doc-1", "doc1.pdf")
doc1.source_chunks.append(
SourceChunk(
chunk_id="doc-1-chunk-2",
doc_id="doc-1",
doc_name="doc1.pdf",
text="Chunk 2",
page_start=2,
page_end=2,
section_path="Chapter 1 > Scope",
section_title="Scope",
source_layout_ids=["layout-2"],
)
)
parser = FakeParser({"doc1.pdf": doc1}, failures={"doc2.pdf"})
generator = FakeGenerator(
{
"doc-1": [
DraftQuestionSample(
sample_id="doc-1-q1",
question="Too many chunks?",
ground_truth="This cites two chunks.",
scenario="sample-build",
language="en",
doc_id="doc-1",
doc_name="doc1.pdf",
section_path="Chapter 1 > Scope",
page_start=1,
page_end=2,
source_chunk_ids=["doc-1-chunk-1", "doc-1-chunk-2"],
question_type="fact",
difficulty="easy",
)
]
}
)
strict_config = self.temp_dir / "dataset-build-strict.yaml"
strict_config.write_text(
self.config_path.read_text(encoding="utf-8").replace(
" max_source_chunks_per_question: 2",
" max_source_chunks_per_question: 1",
),
encoding="utf-8",
)
result = run_dataset_build(
strict_config,
settings=EvaluationSettings.model_construct(dataset_generator_model="stub-model"),
parser=parser,
generator=generator,
)
self.assertEqual(len(result.draft_samples), 0)
def test_run_dataset_build_fail_mode_raises(self) -> None:
fail_config = self.temp_dir / "dataset-build-fail.yaml"
fail_config.write_text(self.config_path.read_text(encoding="utf-8").replace("failure_mode: skip", "failure_mode: fail"), encoding="utf-8")
parser = FakeParser({}, failures={"doc1.pdf"})
generator = FakeGenerator({})
with self.assertRaises(RuntimeError):
run_dataset_build(
fail_config,
settings=EvaluationSettings.model_construct(dataset_generator_model="stub-model"),
parser=parser,
generator=generator,
)
class QuestionGeneratorTests(unittest.TestCase):
def _make_document(self) -> ParsedDocument:
return ParsedDocument(
doc_id="doc-1",
doc_name="doc1.pdf",
raw_text="source text",
structure_nodes=[],
semantic_blocks=[],
source_chunks=[
SourceChunk(
chunk_id="doc-1-chunk-1",
doc_id="doc-1",
doc_name="doc1.pdf",
text="Scope content",
page_start=1,
page_end=1,
section_path="Chapter 1 > Scope",
section_title="Scope",
source_layout_ids=["layout-1"],
),
SourceChunk(
chunk_id="doc-1-chunk-2",
doc_id="doc-1",
doc_name="doc1.pdf",
text="Procedure content",
page_start=2,
page_end=2,
section_path="Chapter 2 > Process",
section_title="Process",
source_layout_ids=["layout-2"],
),
],
metadata={},
)
def _make_fake_client(self, content: str):
class FakeResponse:
def __init__(self, payload: str):
self.choices = [type("Choice", (), {"message": type("Message", (), {"content": payload})()})()]
class FakeCompletions:
def __init__(self, payload: str):
self.payload = payload
def create(self, **kwargs):
return FakeResponse(self.payload)
return type(
"FakeClient",
(),
{"chat": type("Chat", (), {"completions": FakeCompletions(content)})()},
)()
def test_question_generator_builds_samples_from_json_response(self) -> None:
settings = EvaluationSettings.model_construct(openai_api_key="test-key")
content = json.dumps(
{
"samples": [
{
"question": "What is the scope?",
"ground_truth": "It defines the scope.",
"source_chunk_ids": ["doc-1-chunk-1"],
"question_type": "fact",
"difficulty": "easy",
},
{
"question": "Summarize the process.",
"ground_truth": "It explains the process.",
"source_chunk_ids": ["doc-1-chunk-2"],
"question_type": "summary",
"difficulty": "medium",
},
]
}
)
generator = OpenAIQuestionGenerator(
settings=settings,
model="stub-model",
client=self._make_fake_client(content),
)
samples = generator.generate(
self._make_document(),
max_questions=1,
max_chunks_per_question=2,
job_name="sample-build",
)
self.assertEqual(len(samples), 1)
self.assertEqual(samples[0].sample_id, "doc-1-q1")
self.assertEqual(samples[0].section_path, "Chapter 1 > Scope")
def test_question_generator_rejects_invalid_json(self) -> None:
settings = EvaluationSettings.model_construct(openai_api_key="test-key")
generator = OpenAIQuestionGenerator(
settings=settings,
model="stub-model",
client=self._make_fake_client("not-json"),
)
with self.assertRaises(ValueError):
generator.generate(
self._make_document(),
max_questions=1,
max_chunks_per_question=2,
job_name="sample-build",
)
def test_question_generator_rejects_non_list_samples(self) -> None:
settings = EvaluationSettings.model_construct(openai_api_key="test-key")
content = json.dumps({"samples": {"question": "bad-shape"}})
generator = OpenAIQuestionGenerator(
settings=settings,
model="stub-model",
client=self._make_fake_client(content),
)
with self.assertRaises(ValueError):
generator.generate(
self._make_document(),
max_questions=1,
max_chunks_per_question=2,
job_name="sample-build",
)
class MainCliParseTests(unittest.TestCase):
def test_cli_options_are_mutually_exclusive(self) -> None:
import main
with mock.patch("sys.argv", ["main.py", "--scenario", "a.yaml", "--dataset-build-config", "b.yaml"]):
with self.assertRaises(SystemExit):
main.parse_args()