Skip to content

Commit c55c30c

Browse files
feat: add true-or-false qa generation
1 parent f18d165 commit c55c30c

File tree

11 files changed

+239
-0
lines changed

11 files changed

+239
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Generate True-False QAs
2+
3+
True-False QAs are a type of question-answer pair where the answer is either "True" or "False". They are useful for assessing understanding of specific facts or concepts.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
python3 -m graphgen.run \
2+
--config_file examples/generate/generate_true_false_qa/true_false_config.yaml
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
global_params:
2+
working_dir: cache
3+
graph_backend: kuzu # graph database backend, support: kuzu, networkx
4+
kv_backend: rocksdb # key-value store backend, support: rocksdb, json_kv
5+
6+
nodes:
7+
- id: read
8+
op_name: read
9+
type: source
10+
dependencies: []
11+
params:
12+
input_path:
13+
- examples/input_examples/json_demo.json
14+
15+
- id: chunk
16+
op_name: chunk
17+
type: map_batch
18+
dependencies:
19+
- read
20+
execution_params:
21+
replicas: 4
22+
params:
23+
chunk_size: 1024
24+
chunk_overlap: 100
25+
26+
- id: build_kg
27+
op_name: build_kg
28+
type: map_batch
29+
execution_params:
30+
replicas: 1
31+
batch_size: 128
32+
dependencies:
33+
- chunk
34+
35+
- id: partition
36+
op_name: partition
37+
type: aggregate
38+
dependencies:
39+
- build_kg
40+
params:
41+
method: dfs
42+
method_params:
43+
max_units_per_community: 1
44+
45+
- id: generate
46+
op_name: generate
47+
type: map_batch
48+
dependencies:
49+
- partition
50+
execution_params:
51+
replicas: 1
52+
batch_size: 128
53+
save_output: true
54+
params:
55+
method: true_false
56+
data_format: Alpaca

graphgen/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
MultiChoiceGenerator,
1717
MultiHopGenerator,
1818
QuizGenerator,
19+
TrueFalseGenerator,
1920
VQAGenerator,
2021
)
2122
from .kg_builder import LightRAGKGBuilder, MMKGBuilder

graphgen/models/generator/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
from .multi_choice_generator import MultiChoiceGenerator
77
from .multi_hop_generator import MultiHopGenerator
88
from .quiz_generator import QuizGenerator
9+
from .true_false_generator import TrueFalseGenerator
910
from .vqa_generator import VQAGenerator
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import re
2+
from typing import Any
3+
4+
from graphgen.bases import BaseGenerator
5+
from graphgen.templates import TF_GENERATION_PROMPT
6+
from graphgen.utils import compute_content_hash, detect_main_language, logger
7+
8+
9+
class TrueFalseGenerator(BaseGenerator):
10+
def __init__(self, llm_client, num_of_questions) -> None:
11+
super().__init__(llm_client)
12+
self.num_of_questions = num_of_questions
13+
14+
@staticmethod
15+
def parse_response(response: str) -> Any:
16+
"""
17+
Parse true/false QA pairs from the LLM response.
18+
Each QA pair contains a statement question and True/False answer.
19+
20+
:param response: The LLM response containing XML-formatted QA pairs
21+
:return: Dictionary mapping question hash to question data, where each
22+
value is a dict with "question", "options", and "answer" keys
23+
"""
24+
qa_pairs: dict[str, dict[str, Any]] = {}
25+
26+
# Extract all QA pair blocks
27+
qa_blocks = re.findall(r"<qa_pair>(.*?)</qa_pair>", response, re.DOTALL)
28+
29+
if not qa_blocks:
30+
logger.warning("No QA pairs found in response: %s", response)
31+
return {}
32+
33+
for block in qa_blocks:
34+
# Extract and clean question text
35+
q_match = re.search(r"<question>(.*?)</question>", block, re.DOTALL)
36+
if not q_match:
37+
logger.warning("Failed to parse question from block: %s", block)
38+
continue
39+
question = q_match.group(1).strip().strip('"').strip("'")
40+
41+
# Extract and validate answer
42+
ans_match = re.search(r"<answer>(.*?)</answer>", block, re.DOTALL)
43+
if not ans_match:
44+
logger.warning("Failed to parse answer from block: %s", block)
45+
continue
46+
answer = ans_match.group(1).strip().strip('"').strip("'")
47+
48+
# Ensure answer exists in options
49+
if answer.lower() not in ["true", "false"]:
50+
logger.warning("Invalid answer '%s' in block: %s", answer, block)
51+
continue
52+
53+
# Build result entry with question hash as key
54+
question_hash = compute_content_hash(question)
55+
qa_pairs[question_hash] = {
56+
"question": question,
57+
"answer": answer, # "True" or "False"
58+
}
59+
60+
logger.debug("Successfully parsed TF question: %s", question[:50])
61+
62+
if not qa_pairs:
63+
logger.error("Failed to parse any valid true/false pairs from response")
64+
65+
return qa_pairs
66+
67+
# pylint: disable=W0221
68+
def build_prompt(
69+
self, batch: tuple[list[tuple[str, dict]], list[tuple[Any, Any, dict]]]
70+
) -> str:
71+
nodes, edges = batch
72+
entities_str = "\n".join(
73+
[
74+
f"{index + 1}. {node[0]}: {node[1]['description']}"
75+
for index, node in enumerate(nodes)
76+
]
77+
)
78+
79+
relationships_str = "\n".join(
80+
[
81+
f"{index + 1}. {edge[0]} -- {edge[1]}: {edge[2]['description']}"
82+
for index, edge in enumerate(edges)
83+
]
84+
)
85+
context = entities_str + "\n" + relationships_str
86+
language = detect_main_language(entities_str + relationships_str)
87+
prompt = TF_GENERATION_PROMPT[language].format(
88+
context=context,
89+
num_of_questions=self.num_of_questions,
90+
)
91+
return prompt

graphgen/operators/generate/generate_service.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ def __init__(
6464
self.llm_client,
6565
num_of_questions=generate_kwargs.get("num_of_questions", 5),
6666
)
67+
elif self.method == "true_false":
68+
from graphgen.models import TrueFalseGenerator
69+
70+
self.generator = TrueFalseGenerator(
71+
self.llm_client,
72+
num_of_questions=generate_kwargs.get("num_of_questions", 5),
73+
)
6774
else:
6875
raise ValueError(f"Unsupported generation mode: {method}")
6976

graphgen/templates/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
MAQ_GENERATION_PROMPT,
1111
MCQ_GENERATION_PROMPT,
1212
MULTI_HOP_GENERATION_PROMPT,
13+
TF_GENERATION_PROMPT,
1314
VQA_GENERATION_PROMPT,
1415
)
1516
from .kg import KG_EXTRACTION_PROMPT, KG_SUMMARIZATION_PROMPT, MMKG_EXTRACTION_PROMPT

graphgen/templates/generation/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
from .multi_answer_generation import MAQ_GENERATION_PROMPT
66
from .multi_choice_generation import MCQ_GENERATION_PROMPT
77
from .multi_hop_generation import MULTI_HOP_GENERATION_PROMPT
8+
from .true_false_generation import TF_GENERATION_PROMPT
89
from .vqa_generation import VQA_GENERATION_PROMPT
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
TEMPLATE_TF_ZH: str = """请根据上下文资料生成独立的知识判断题,每个判断题包含一个陈述句,答案只能是正确(True)或错误(False)。
2+
3+
生成要求:
4+
1. **语言一致性**:若上下文资料为中文,则生成中文问题;若为英文,则生成英文问题
5+
2. **数量**:每个上下文资料生成{num_of_questions}个判断题
6+
3. **独立性**:每个问题必须完整独立,不依赖其他问题
7+
4. **准确性**:正确答案必须能从原文直接得出,陈述需有明确的判断依据
8+
9+
输出格式:
10+
<qa_pairs>
11+
<qa_pair>
12+
<question>陈述句文本</question>
13+
<answer>True或False</answer>
14+
</qa_pair>
15+
</qa_pairs>
16+
17+
示例(根据iPad Air 2生成2题):
18+
<qa_pairs>
19+
<qa_pair>
20+
<question>iPad Air 2于2014年发布。</question>
21+
<answer>True</answer>
22+
</qa_pair>
23+
<qa_pair>
24+
<question>iPad Air 2搭载的是A10处理器。</question>
25+
<answer>False</answer>
26+
</qa_pair>
27+
</qa_pairs>
28+
29+
30+
上下文资料:
31+
{context}
32+
33+
请为以下资料生成{num_of_questions}个判断题:
34+
"""
35+
36+
37+
TEMPLATE_TF_EN: str = """Generate independent true/false questions based on the provided context. \
38+
Each question should be a factual statement that can be clearly determined as true or false.
39+
40+
Requirements:
41+
1. **Language Consistency**: Generate in the same language as the context (Chinese/English)
42+
2. **Quantity**: Generate {num_of_questions} true/false questions per context
43+
3. **Independence**: Each question must be self-contained
44+
4. **Accuracy**: Correct answer must be directly derivable from the text with clear evidence
45+
46+
Output Format:
47+
<qa_pairs>
48+
<qa_pair>
49+
<question>Statement text</question>
50+
<answer>True or False</answer>
51+
</qa_pair>
52+
</qa_pairs>
53+
54+
Example (2 questions):
55+
<qa_pairs>
56+
<qa_pair>
57+
<question>The iPad Air 2 was released in 2014.</question>
58+
<answer>True</answer>
59+
</qa_pair>
60+
<qa_pair>
61+
<question>The iPad Air 2 uses an A10 processor.</question>
62+
<options>True
63+
False</options>
64+
<answer>False</answer>
65+
</qa_pair>
66+
</qa_pairs>
67+
68+
Context:
69+
{context}
70+
71+
Please generate {num_of_questions} true/false questions for the following context:
72+
"""
73+
74+
75+
TF_GENERATION_PROMPT = {"zh": TEMPLATE_TF_ZH, "en": TEMPLATE_TF_EN}

0 commit comments

Comments
 (0)