Skip to content

Commit 2b7206a

Browse files
feat: add cot data generation pipeline
1 parent 0756638 commit 2b7206a

16 files changed

Lines changed: 450 additions & 22 deletions

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ See [analysis](https://deepwiki.com/open-sciencelab/GraphGen) by deepwiki for a
176176
## 🍀 Acknowledgements
177177
- [SiliconFlow](https://siliconflow.cn) Abundant LLM API, some models are free
178178
- [LightRAG](https://github.com/HKUDS/LightRAG) Simple and efficient graph retrieval solution
179-
- [ROGRAG](https://github.com/tpoisonooo/ROGRAG) ROGRAG: A Robustly Optimized GraphRAG Framework
179+
- [ROGRAG](https://github.com/tpoisonooo/ROGRAG) A robustly optimized GraphRAG framework
180+
- [DB-GPT](https://github.com/eosphoros-ai/DB-GPT) An AI native data app development framework
180181

181182

182183
## 📚 Citation

graphgen/models/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from .community.community_detector import CommunityDetector
12
from .evaluate.length_evaluator import LengthEvaluator
23
from .evaluate.mtld_evaluator import MTLDEvaluator
34
from .evaluate.reward_evaluator import RewardEvaluator
@@ -38,4 +39,6 @@
3839
"UniEvaluator",
3940
# strategy models
4041
"TraverseStrategy",
42+
# community models
43+
"CommunityDetector",
4144
]

graphgen/models/community/__init__.py

Whitespace-only changes.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from dataclasses import dataclass
2+
from typing import Any, Dict
3+
4+
from graphgen.models.storage.networkx_storage import NetworkXStorage
5+
6+
7+
@dataclass
8+
class CommunityDetector:
9+
"""Class for community detection algorithms."""
10+
11+
graph_storage: NetworkXStorage = None
12+
method: str = "leiden"
13+
method_params: Dict[str, Any] = None
14+
15+
async def detect_communities(self) -> Dict[str, int]:
16+
"""
17+
Detect communities based on the chosen method.
18+
"""
19+
if self.method == "leiden":
20+
return await self._leiden_communities(**self.method_params or {})
21+
raise ValueError(f"Unknown community detection method: {self.method}")
22+
23+
async def get_graph(self):
24+
"""
25+
Asynchronously get the graph from the storage.
26+
"""
27+
return await self.graph_storage.get_graph()
28+
29+
async def _leiden_communities(self, **kwargs) -> Dict[str, int]:
30+
"""
31+
Detect communities using the Leiden algorithm.
32+
"""
33+
import igraph as ig
34+
import networkx as nx
35+
from leidenalg import ModularityVertexPartition, find_partition
36+
37+
graph = await self.get_graph()
38+
# Filter out isolated nodes
39+
graph.remove_nodes_from(list(nx.isolates(graph)))
40+
41+
# Convert NetworkX graph to igraph graph
42+
ig_graph = ig.Graph.TupleList(graph.edges(), directed=False)
43+
44+
random_seed = kwargs.get("random_seed", 42)
45+
use_lcc = kwargs.get("use_lcc", False)
46+
47+
communities = {}
48+
if use_lcc:
49+
# Use the largest connected component
50+
lcc = ig_graph.components().giant()
51+
partition = find_partition(lcc, ModularityVertexPartition, seed=random_seed)
52+
for part, cluster in enumerate(partition):
53+
for v in cluster:
54+
communities[v] = part
55+
else:
56+
offset = 0
57+
for component in ig_graph.components():
58+
subgraph = ig_graph.induced_subgraph(component)
59+
partition = find_partition(
60+
subgraph, ModularityVertexPartition, seed=random_seed
61+
)
62+
for part, cluster in enumerate(partition):
63+
for v in cluster:
64+
original_node = subgraph.vs[v]["name"]
65+
communities[original_node] = part + offset
66+
offset += len(partition)
67+
return communities

graphgen/models/vis/__init__.py

Whitespace-only changes.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from dataclasses import dataclass
2+
from typing import Dict
3+
4+
import matplotlib.pyplot as plt
5+
import networkx as nx
6+
7+
8+
@dataclass
9+
class Visualizer:
10+
"""
11+
Class for visualizing graphs using NetworkX and Matplotlib.
12+
"""
13+
14+
graph: nx.Graph = None
15+
communities: Dict[str, int] = None
16+
layout: str = "spring"
17+
max_nodes: int = 1000
18+
node_size: int = 10
19+
alpha: float = 0.6
20+
21+
def visualize(self, save_path: str = None):
22+
n = self.graph.number_of_nodes()
23+
print(f"Loaded graph: {n} nodes, {self.graph.number_of_edges()} edges")
24+
25+
if self.layout == "spring":
26+
k = max(0.1, 1.0 / (n**0.5))
27+
pos = nx.spring_layout(self.graph, k=k, seed=42)
28+
else:
29+
raise ValueError(f"Unknown layout: {self.layout}")
30+
31+
plt.figure(figsize=(10, 10))
32+
33+
node_colors = [self.communities.get(node, 0) for node in self.graph.nodes()]
34+
print(node_colors)
35+
36+
nx.draw_networkx_nodes(
37+
self.graph,
38+
pos,
39+
node_size=self.node_size,
40+
node_color=node_colors,
41+
cmap="viridis",
42+
alpha=self.alpha,
43+
)
44+
nx.draw_networkx_edges(self.graph, pos, alpha=0.3, width=0.2)
45+
plt.axis("off")
46+
47+
if save_path:
48+
plt.savefig(save_path, dpi=300, bbox_inches="tight")
49+
print("Saved to", save_path)
50+
else:
51+
plt.show()

graphgen/operators/community/__init__.py

Whitespace-only changes.
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import asyncio
2+
from typing import Dict, List
3+
4+
from tqdm.asyncio import tqdm as tqdm_async
5+
6+
from graphgen.models import CommunityDetector, NetworkXStorage, OpenAIModel
7+
from graphgen.templates import COT_GENERATION_PROMPT, COT_TEMPLATE_DESIGN_PROMPT
8+
from graphgen.utils import detect_main_language
9+
10+
11+
async def generate_cot(
12+
graph_storage: NetworkXStorage,
13+
synthesizer_llm_client: OpenAIModel,
14+
method: str = "leiden",
15+
):
16+
detector = CommunityDetector(graph_storage=graph_storage, method=method)
17+
18+
results = await detector.detect_communities()
19+
20+
# Convert results to a format suitable for summarization
21+
communities = {}
22+
for node, community_id in results.items():
23+
if community_id not in communities:
24+
communities[community_id] = []
25+
communities[community_id].append(node)
26+
27+
if not communities:
28+
return {}
29+
30+
semaphore = asyncio.Semaphore(value=1000)
31+
32+
async def _generate_from_single_community(
33+
community_id: int, nodes: List[str]
34+
) -> tuple[int, tuple]:
35+
"""Summarize a single community."""
36+
async with semaphore:
37+
entities: List[str] = []
38+
relationships: List[str] = []
39+
40+
for node in nodes:
41+
node_data = await graph_storage.get_node(node)
42+
if node_data is not None:
43+
entities.append(f"({node}: {node_data.get('description')})")
44+
45+
edges = await graph_storage.get_node_edges(node)
46+
for edge in edges:
47+
target = edge[1]
48+
if target in nodes:
49+
edge_data = await graph_storage.get_edge(node, target)
50+
relationships.append(
51+
f"({node}) - [{edge_data['description']}] -> ({target})"
52+
)
53+
54+
entities_str = "\n".join(entities)
55+
relationships_str = "\n".join(relationships)
56+
57+
language = (
58+
"English"
59+
if detect_main_language(entities_str + relationships_str) == "en"
60+
else "Chinese"
61+
)
62+
63+
prompt = COT_TEMPLATE_DESIGN_PROMPT[language]["TEMPLATE"].format(
64+
entities=entities_str,
65+
relationships=relationships_str,
66+
)
67+
68+
cot_template = await synthesizer_llm_client.generate_answer(prompt)
69+
70+
if "问题:" in cot_template and "推理路径设计:" in cot_template:
71+
question = cot_template.split("问题:")[1].split("推理路径设计:")[0].strip()
72+
reasoning_path = cot_template.split("推理路径设计:")[1].strip()
73+
elif (
74+
"Question:" in cot_template and "Reasoning-Path Design:" in cot_template
75+
):
76+
question = (
77+
cot_template.split("Question:")[1]
78+
.split("Reasoning-Path Design:")[0]
79+
.strip()
80+
)
81+
reasoning_path = cot_template.split("Reasoning-Path Design:")[1].strip()
82+
else:
83+
raise ValueError("COT template format is incorrect.")
84+
85+
prompt = COT_GENERATION_PROMPT[language]["TEMPLATE"].format(
86+
entities=entities_str,
87+
relationships=relationships_str,
88+
question=question,
89+
reasoning_template=reasoning_path,
90+
)
91+
92+
cot_answer = await synthesizer_llm_client.generate_answer(prompt)
93+
94+
return community_id, (question, reasoning_path, cot_answer)
95+
96+
cid_nodes = list(communities.items())
97+
98+
templates: Dict[int, (str, str)] = {}
99+
async for coro in tqdm_async(
100+
asyncio.as_completed(
101+
[_generate_from_single_community(cid, nodes) for cid, nodes in cid_nodes]
102+
),
103+
total=len(cid_nodes),
104+
desc="[Generate COT] Generating COT templates for communities",
105+
unit="community",
106+
):
107+
cid, (q, r, a) = await coro
108+
templates[cid] = (q, r, a)
109+
110+
return templates

graphgen/templates/__init__.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
from .answer_rephrasing import ANSWER_REPHRASING_PROMPT
2+
from .community import COT_GENERATION_PROMPT, COT_TEMPLATE_DESIGN_PROMPT
3+
from .coreference_resolution import COREFERENCE_RESOLUTION_PROMPT
4+
from .description_rephrasing import DESCRIPTION_REPHRASING_PROMPT
15
from .kg_extraction import KG_EXTRACTION_PROMPT
26
from .kg_summarization import KG_SUMMARIZATION_PROMPT
7+
from .multi_hop_generation import MULTI_HOP_GENERATION_PROMPT
8+
from .question_generation import QUESTION_GENERATION_PROMPT
39
from .search_judgement import SEARCH_JUDGEMENT_PROMPT
4-
from .description_rephrasing import DESCRIPTION_REPHRASING_PROMPT
510
from .statement_judgement import STATEMENT_JUDGEMENT_PROMPT
6-
from .answer_rephrasing import ANSWER_REPHRASING_PROMPT
7-
from .question_generation import QUESTION_GENERATION_PROMPT
8-
from .multi_hop_generation import MULTI_HOP_GENERATION_PROMPT
9-
from .coreference_resolution import COREFERENCE_RESOLUTION_TEMPLATE

graphgen/templates/answer_rephrasing.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
TEMPLATE_CONTEXT_EN: str = """---Role---
2-
32
You are an NLP expert responsible for generating a logically structured and coherent rephrased version of the TEXT based on ENTITIES and RELATIONSHIPS provided below. You may refer to the original text to assist in generating the rephrased version, but ensure that the final output text meets the requirements.
43
Use {language} as output language.
54
@@ -51,12 +50,10 @@
5150
"""
5251

5352
TEMPLATE_CONTEXT_ZH: str = """---角色---
54-
5553
你是一位NLP专家,负责根据下面提供的实体和关系生成逻辑结构清晰且连贯的文本重述版本。你可以参考原始文本辅助生成,但需要确保最终输出的文本符合要求。
5654
使用{language}作为输出语言。
5755
5856
---目标---
59-
6057
生成文本的重述版本,使其传达与原始实体和关系描述相同的含义,同时:
6158
1. 遵循清晰的逻辑流和结构
6259
2. 建立适当的因果关系
@@ -101,7 +98,6 @@
10198
"""
10299

103100
TEMPLATE_EN: str = """---Role---
104-
105101
You are an NLP expert responsible for generating a logically structured and coherent rephrased version of the TEXT based on ENTITIES and RELATIONSHIPS provided below.
106102
Use {language} as output language.
107103
@@ -148,12 +144,10 @@
148144
"""
149145

150146
TEMPLATE_ZH: str = """---角色---
151-
152147
你是一位NLP专家,负责根据下面提供的实体和关系生成逻辑结构清晰且连贯的文本重述版本。
153148
使用{language}作为输出语言。
154149
155150
---目标---
156-
157151
生成文本的重述版本,使其传达与原始实体和关系描述相同的含义,同时:
158152
1. 遵循清晰的逻辑流和结构
159153
2. 建立适当的因果关系
@@ -207,13 +201,13 @@
207201
"""
208202

209203

210-
ANSWER_REPHRASING_PROMPT= {
204+
ANSWER_REPHRASING_PROMPT = {
211205
"English": {
212206
"TEMPLATE": TEMPLATE_EN + REQUIREMENT_EN,
213-
"CONTEXT_TEMPLATE": TEMPLATE_CONTEXT_EN + REQUIREMENT_EN
207+
"CONTEXT_TEMPLATE": TEMPLATE_CONTEXT_EN + REQUIREMENT_EN,
214208
},
215209
"Chinese": {
216210
"TEMPLATE": TEMPLATE_ZH + REQUIREMENT_ZH,
217-
"CONTEXT_TEMPLATE": TEMPLATE_CONTEXT_ZH + REQUIREMENT_ZH
218-
}
211+
"CONTEXT_TEMPLATE": TEMPLATE_CONTEXT_ZH + REQUIREMENT_ZH,
212+
},
219213
}

0 commit comments

Comments
 (0)