-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathretrieval_planner.py
More file actions
197 lines (173 loc) · 6.5 KB
/
Copy pathretrieval_planner.py
File metadata and controls
197 lines (173 loc) · 6.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
from __future__ import annotations
import re
from dataclasses import asdict, dataclass, field
from typing import Any
from intent_router import IntentResult
@dataclass
class RetrievalPlan:
intent: str
queries: list[str]
raw_top_k: int = 3
wiki_top_k: int = 1
fetch_k: int = 12
use_mmr: bool = True
use_rerank: bool = False
need_coverage_check: bool = True
min_score: float = 0.2
source_filter: dict[str, Any] = field(default_factory=dict)
reason: str = ""
@property
def total_top_k(self) -> int:
return self.raw_top_k + self.wiki_top_k
def to_dict(self) -> dict[str, Any]:
payload = asdict(self)
payload["total_top_k"] = self.total_top_k
return payload
def _clean_query_fragment(text: str) -> str:
return text.strip(" ::,,??。.!!;;\"'`()[]{}")
def _dedupe_keep_order(values: list[str]) -> list[str]:
seen: set[str] = set()
deduped: list[str] = []
for value in values:
candidate = value.strip()
if candidate and candidate not in seen:
seen.add(candidate)
deduped.append(candidate)
return deduped
def build_queries(question: str, intent: str) -> list[str]:
question = question.strip()
queries = [question]
if intent == "comparison":
lowered = question.lower()
separators = ["和", "与", "vs", "versus", "区别", "difference between", "compare"]
for separator in separators:
if separator.lower() in lowered:
parts = re.split(separator, question, maxsplit=1, flags=re.IGNORECASE)
if len(parts) == 2:
left = _clean_query_fragment(parts[0])
right = _clean_query_fragment(parts[1])
right = re.sub(
r"^(有什么区别|有什么不同|区别是什么|difference|compare)\s*",
"",
right,
flags=re.IGNORECASE,
)
right = re.sub(
r"\s*(有什么区别|有什么不同|区别是什么|difference|compare)\s*$",
"",
right,
flags=re.IGNORECASE,
)
queries = [left, _clean_query_fragment(right), question, f"{left} {right} difference"]
return _dedupe_keep_order(queries)
if intent == "definition":
stripped = question
for phrase in ["什么是", "啥是", "what is", "define", "meaning of", "解释一下", "介绍一下"]:
stripped = re.sub(phrase, "", stripped, flags=re.IGNORECASE).strip()
stripped = _clean_query_fragment(stripped)
if stripped and stripped != question:
queries.append(stripped)
elif intent == "summary":
queries.extend([f"{question} main points", f"{question} chapter summary"])
elif intent == "quiz":
queries.extend([f"{question} key concepts", f"{question} exam points"])
elif intent == "diagnosis":
queries.extend([f"{question} coverage", f"{question} course material"])
elif intent == "study_plan":
queries.extend([f"{question} roadmap", f"{question} key concepts"])
else:
queries.append(question)
return _dedupe_keep_order(queries)
def create_retrieval_plan(question: str, intent_result: IntentResult) -> RetrievalPlan:
intent = intent_result.intent
queries = build_queries(question, intent)
if intent == "definition":
return RetrievalPlan(
intent=intent,
queries=queries,
raw_top_k=2,
wiki_top_k=2,
fetch_k=10,
use_mmr=True,
use_rerank=False,
need_coverage_check=True,
min_score=0.18,
reason="Definition questions benefit from a compact wiki definition plus a small amount of grounded raw evidence.",
)
if intent == "comparison":
return RetrievalPlan(
intent=intent,
queries=queries,
raw_top_k=3,
wiki_top_k=2,
fetch_k=18,
use_mmr=True,
use_rerank=True,
need_coverage_check=True,
min_score=0.18,
reason="Comparison questions should decompose the concepts, retrieve both sides, and rerank to avoid over-focusing on one term.",
)
if intent == "summary":
return RetrievalPlan(
intent=intent,
queries=queries,
raw_top_k=6,
wiki_top_k=2,
fetch_k=24,
use_mmr=True,
use_rerank=False,
need_coverage_check=False,
min_score=0.14,
reason="Summaries need broader raw coverage so the answer reflects the course structure rather than a single chunk.",
)
if intent == "quiz":
return RetrievalPlan(
intent=intent,
queries=queries,
raw_top_k=5,
wiki_top_k=2,
fetch_k=22,
use_mmr=True,
use_rerank=False,
need_coverage_check=True,
min_score=0.14,
reason="Quiz generation needs multiple raw chunks and diversified retrieval so questions cover several concepts.",
)
if intent == "diagnosis":
return RetrievalPlan(
intent=intent,
queries=queries,
raw_top_k=4,
wiki_top_k=2,
fetch_k=20,
use_mmr=False,
use_rerank=True,
need_coverage_check=True,
min_score=0.2,
reason="Diagnosis is not direct QA; it should inspect whether the course material really covers the request before answering.",
)
if intent == "study_plan":
return RetrievalPlan(
intent=intent,
queries=queries,
raw_top_k=5,
wiki_top_k=2,
fetch_k=20,
use_mmr=True,
use_rerank=False,
need_coverage_check=False,
min_score=0.14,
reason="Study plans need broader coverage and concept ordering, so the planner keeps more raw context than normal QA.",
)
return RetrievalPlan(
intent="general_qa",
queries=queries,
raw_top_k=3,
wiki_top_k=1,
fetch_k=12,
use_mmr=True,
use_rerank=False,
need_coverage_check=True,
min_score=0.18,
reason="General QA keeps a modest mix of raw evidence and wiki support while avoiding unnecessary context.",
)