Spaces:
Running
Running
github-actions[bot]
commited on
Commit
·
caa2d9c
1
Parent(s):
f57aa27
Auto-sync from demo at Fri Jan 16 06:05:51 UTC 2026
Browse files- app.py +1 -0
- graphgen/models/__init__.py +1 -0
- graphgen/models/generator/__init__.py +1 -0
- graphgen/models/generator/true_false_generator.py +91 -0
- graphgen/operators/generate/generate_service.py +7 -0
- graphgen/templates/__init__.py +1 -0
- graphgen/templates/generation/__init__.py +1 -0
- graphgen/templates/generation/true_false_generation.py +75 -0
- webui/app.py +1 -0
app.py
CHANGED
|
@@ -524,6 +524,7 @@ with gr.Blocks(title="GraphGen Demo", theme=gr.themes.Glass(), css=css) as demo:
|
|
| 524 |
"multi_choice",
|
| 525 |
"multi_answer",
|
| 526 |
"fill_in_blank",
|
|
|
|
| 527 |
],
|
| 528 |
label=_("Mode"),
|
| 529 |
value="aggregated",
|
|
|
|
| 524 |
"multi_choice",
|
| 525 |
"multi_answer",
|
| 526 |
"fill_in_blank",
|
| 527 |
+
"true_false",
|
| 528 |
],
|
| 529 |
label=_("Mode"),
|
| 530 |
value="aggregated",
|
graphgen/models/__init__.py
CHANGED
|
@@ -16,6 +16,7 @@ from .generator import (
|
|
| 16 |
MultiChoiceGenerator,
|
| 17 |
MultiHopGenerator,
|
| 18 |
QuizGenerator,
|
|
|
|
| 19 |
VQAGenerator,
|
| 20 |
)
|
| 21 |
from .kg_builder import LightRAGKGBuilder, MMKGBuilder
|
|
|
|
| 16 |
MultiChoiceGenerator,
|
| 17 |
MultiHopGenerator,
|
| 18 |
QuizGenerator,
|
| 19 |
+
TrueFalseGenerator,
|
| 20 |
VQAGenerator,
|
| 21 |
)
|
| 22 |
from .kg_builder import LightRAGKGBuilder, MMKGBuilder
|
graphgen/models/generator/__init__.py
CHANGED
|
@@ -6,4 +6,5 @@ from .multi_answer_generator import MultiAnswerGenerator
|
|
| 6 |
from .multi_choice_generator import MultiChoiceGenerator
|
| 7 |
from .multi_hop_generator import MultiHopGenerator
|
| 8 |
from .quiz_generator import QuizGenerator
|
|
|
|
| 9 |
from .vqa_generator import VQAGenerator
|
|
|
|
| 6 |
from .multi_choice_generator import MultiChoiceGenerator
|
| 7 |
from .multi_hop_generator import MultiHopGenerator
|
| 8 |
from .quiz_generator import QuizGenerator
|
| 9 |
+
from .true_false_generator import TrueFalseGenerator
|
| 10 |
from .vqa_generator import VQAGenerator
|
graphgen/models/generator/true_false_generator.py
ADDED
|
@@ -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
CHANGED
|
@@ -64,6 +64,13 @@ class GenerateService(BaseOperator):
|
|
| 64 |
self.llm_client,
|
| 65 |
num_of_questions=generate_kwargs.get("num_of_questions", 5),
|
| 66 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
else:
|
| 68 |
raise ValueError(f"Unsupported generation mode: {method}")
|
| 69 |
|
|
|
|
| 64 |
self.llm_client,
|
| 65 |
num_of_questions=generate_kwargs.get("num_of_questions", 5),
|
| 66 |
)
|
| 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 |
+
)
|
| 74 |
else:
|
| 75 |
raise ValueError(f"Unsupported generation mode: {method}")
|
| 76 |
|
graphgen/templates/__init__.py
CHANGED
|
@@ -10,6 +10,7 @@ from .generation import (
|
|
| 10 |
MAQ_GENERATION_PROMPT,
|
| 11 |
MCQ_GENERATION_PROMPT,
|
| 12 |
MULTI_HOP_GENERATION_PROMPT,
|
|
|
|
| 13 |
VQA_GENERATION_PROMPT,
|
| 14 |
)
|
| 15 |
from .kg import KG_EXTRACTION_PROMPT, KG_SUMMARIZATION_PROMPT, MMKG_EXTRACTION_PROMPT
|
|
|
|
| 10 |
MAQ_GENERATION_PROMPT,
|
| 11 |
MCQ_GENERATION_PROMPT,
|
| 12 |
MULTI_HOP_GENERATION_PROMPT,
|
| 13 |
+
TF_GENERATION_PROMPT,
|
| 14 |
VQA_GENERATION_PROMPT,
|
| 15 |
)
|
| 16 |
from .kg import KG_EXTRACTION_PROMPT, KG_SUMMARIZATION_PROMPT, MMKG_EXTRACTION_PROMPT
|
graphgen/templates/generation/__init__.py
CHANGED
|
@@ -5,4 +5,5 @@ from .fill_in_blank_generation import FILL_IN_BLANK_GENERATION_PROMPT
|
|
| 5 |
from .multi_answer_generation import MAQ_GENERATION_PROMPT
|
| 6 |
from .multi_choice_generation import MCQ_GENERATION_PROMPT
|
| 7 |
from .multi_hop_generation import MULTI_HOP_GENERATION_PROMPT
|
|
|
|
| 8 |
from .vqa_generation import VQA_GENERATION_PROMPT
|
|
|
|
| 5 |
from .multi_answer_generation import MAQ_GENERATION_PROMPT
|
| 6 |
from .multi_choice_generation import MCQ_GENERATION_PROMPT
|
| 7 |
from .multi_hop_generation import MULTI_HOP_GENERATION_PROMPT
|
| 8 |
+
from .true_false_generation import TF_GENERATION_PROMPT
|
| 9 |
from .vqa_generation import VQA_GENERATION_PROMPT
|
graphgen/templates/generation/true_false_generation.py
ADDED
|
@@ -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}
|
webui/app.py
CHANGED
|
@@ -524,6 +524,7 @@ with gr.Blocks(title="GraphGen Demo", theme=gr.themes.Glass(), css=css) as demo:
|
|
| 524 |
"multi_choice",
|
| 525 |
"multi_answer",
|
| 526 |
"fill_in_blank",
|
|
|
|
| 527 |
],
|
| 528 |
label=_("Mode"),
|
| 529 |
value="aggregated",
|
|
|
|
| 524 |
"multi_choice",
|
| 525 |
"multi_answer",
|
| 526 |
"fill_in_blank",
|
| 527 |
+
"true_false",
|
| 528 |
],
|
| 529 |
label=_("Mode"),
|
| 530 |
value="aggregated",
|