Merge pull request #1538 from danielaskdd/ignore-chat-history-in-vector-search
Browse files- lightrag/operate.py +82 -97
lightrag/operate.py
CHANGED
@@ -1198,71 +1198,9 @@ async def mix_kg_vector_query(
|
|
1198 |
traceback.print_exc()
|
1199 |
return None
|
1200 |
|
1201 |
-
async def get_vector_context():
|
1202 |
-
# Consider conversation history in vector search
|
1203 |
-
augmented_query = query
|
1204 |
-
if history_context:
|
1205 |
-
augmented_query = f"{history_context}\n{query}"
|
1206 |
-
|
1207 |
-
try:
|
1208 |
-
# Reduce top_k for vector search in hybrid mode since we have structured information from KG
|
1209 |
-
mix_topk = min(10, query_param.top_k)
|
1210 |
-
results = await chunks_vdb.query(
|
1211 |
-
augmented_query, top_k=mix_topk, ids=query_param.ids
|
1212 |
-
)
|
1213 |
-
if not results:
|
1214 |
-
return None
|
1215 |
-
|
1216 |
-
valid_chunks = []
|
1217 |
-
for result in results:
|
1218 |
-
if "content" in result:
|
1219 |
-
# Directly use content from chunks_vdb.query result
|
1220 |
-
chunk_with_time = {
|
1221 |
-
"content": result["content"],
|
1222 |
-
"created_at": result.get("created_at", None),
|
1223 |
-
"file_path": result.get("file_path", None),
|
1224 |
-
}
|
1225 |
-
valid_chunks.append(chunk_with_time)
|
1226 |
-
|
1227 |
-
if not valid_chunks:
|
1228 |
-
return None
|
1229 |
-
|
1230 |
-
maybe_trun_chunks = truncate_list_by_token_size(
|
1231 |
-
valid_chunks,
|
1232 |
-
key=lambda x: x["content"],
|
1233 |
-
max_token_size=query_param.max_token_for_text_unit,
|
1234 |
-
tokenizer=tokenizer,
|
1235 |
-
)
|
1236 |
-
|
1237 |
-
logger.debug(
|
1238 |
-
f"Truncate chunks from {len(valid_chunks)} to {len(maybe_trun_chunks)} (max tokens:{query_param.max_token_for_text_unit})"
|
1239 |
-
)
|
1240 |
-
logger.info(
|
1241 |
-
f"Naive query: {len(maybe_trun_chunks)} chunks, top_k: {mix_topk}"
|
1242 |
-
)
|
1243 |
-
|
1244 |
-
if not maybe_trun_chunks:
|
1245 |
-
return None
|
1246 |
-
|
1247 |
-
# Include time information in content
|
1248 |
-
formatted_chunks = []
|
1249 |
-
for c in maybe_trun_chunks:
|
1250 |
-
chunk_text = "File path: " + c["file_path"] + "\r\n\r\n" + c["content"]
|
1251 |
-
if c["created_at"]:
|
1252 |
-
chunk_text = f"[Created at: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(c['created_at']))}]\r\n\r\n{chunk_text}"
|
1253 |
-
formatted_chunks.append(chunk_text)
|
1254 |
-
|
1255 |
-
logger.debug(
|
1256 |
-
f"Truncate chunks from {len(valid_chunks)} to {len(formatted_chunks)} (max tokens:{query_param.max_token_for_text_unit})"
|
1257 |
-
)
|
1258 |
-
return "\r\n\r\n--New Chunk--\r\n\r\n".join(formatted_chunks)
|
1259 |
-
except Exception as e:
|
1260 |
-
logger.error(f"Error in get_vector_context: {e}")
|
1261 |
-
return None
|
1262 |
-
|
1263 |
# 3. Execute both retrievals in parallel
|
1264 |
kg_context, vector_context = await asyncio.gather(
|
1265 |
-
get_kg_context(),
|
1266 |
)
|
1267 |
|
1268 |
# 4. Merge contexts
|
@@ -2043,44 +1981,12 @@ async def naive_query(
|
|
2043 |
if cached_response is not None:
|
2044 |
return cached_response
|
2045 |
|
2046 |
-
results = await chunks_vdb.query(
|
2047 |
-
query, top_k=query_param.top_k, ids=query_param.ids
|
2048 |
-
)
|
2049 |
-
if not len(results):
|
2050 |
-
return PROMPTS["fail_response"]
|
2051 |
-
|
2052 |
-
valid_chunks = [result for result in results if "content" in result]
|
2053 |
-
|
2054 |
-
if not valid_chunks:
|
2055 |
-
logger.warning("No valid chunks found after filtering")
|
2056 |
-
return PROMPTS["fail_response"]
|
2057 |
-
|
2058 |
tokenizer: Tokenizer = global_config["tokenizer"]
|
2059 |
-
|
2060 |
-
valid_chunks,
|
2061 |
-
key=lambda x: x["content"],
|
2062 |
-
max_token_size=query_param.max_token_for_text_unit,
|
2063 |
-
tokenizer=tokenizer,
|
2064 |
-
)
|
2065 |
|
2066 |
-
if
|
2067 |
-
logger.warning("No chunks left after truncation")
|
2068 |
return PROMPTS["fail_response"]
|
2069 |
|
2070 |
-
logger.debug(
|
2071 |
-
f"Truncate chunks from {len(valid_chunks)} to {len(maybe_trun_chunks)} (max tokens:{query_param.max_token_for_text_unit})"
|
2072 |
-
)
|
2073 |
-
logger.info(
|
2074 |
-
f"Naive query: {len(maybe_trun_chunks)} chunks, top_k: {query_param.top_k}"
|
2075 |
-
)
|
2076 |
-
|
2077 |
-
section = "\r\n\r\n--New Chunk--\r\n\r\n".join(
|
2078 |
-
[
|
2079 |
-
"File path: " + c["file_path"] + "\r\n\r\n" + c["content"]
|
2080 |
-
for c in maybe_trun_chunks
|
2081 |
-
]
|
2082 |
-
)
|
2083 |
-
|
2084 |
if query_param.only_need_context:
|
2085 |
return section
|
2086 |
|
@@ -2292,6 +2198,85 @@ async def kg_query_with_keywords(
|
|
2292 |
return response
|
2293 |
|
2294 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2295 |
async def query_with_keywords(
|
2296 |
query: str,
|
2297 |
prompt: str,
|
|
|
1198 |
traceback.print_exc()
|
1199 |
return None
|
1200 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1201 |
# 3. Execute both retrievals in parallel
|
1202 |
kg_context, vector_context = await asyncio.gather(
|
1203 |
+
get_kg_context(), _get_vector_context(query, chunks_vdb, query_param, tokenizer)
|
1204 |
)
|
1205 |
|
1206 |
# 4. Merge contexts
|
|
|
1981 |
if cached_response is not None:
|
1982 |
return cached_response
|
1983 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1984 |
tokenizer: Tokenizer = global_config["tokenizer"]
|
1985 |
+
section = await _get_vector_context(query, chunks_vdb, query_param, tokenizer)
|
|
|
|
|
|
|
|
|
|
|
1986 |
|
1987 |
+
if section is None:
|
|
|
1988 |
return PROMPTS["fail_response"]
|
1989 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1990 |
if query_param.only_need_context:
|
1991 |
return section
|
1992 |
|
|
|
2198 |
return response
|
2199 |
|
2200 |
|
2201 |
+
async def _get_vector_context(
|
2202 |
+
query: str,
|
2203 |
+
chunks_vdb: BaseVectorStorage,
|
2204 |
+
query_param: QueryParam,
|
2205 |
+
tokenizer: Tokenizer,
|
2206 |
+
) -> str | None:
|
2207 |
+
"""
|
2208 |
+
Retrieve vector context from the vector database.
|
2209 |
+
|
2210 |
+
This function performs vector search to find relevant text chunks for a query,
|
2211 |
+
formats them with file path and creation time information, and truncates
|
2212 |
+
the results to fit within token limits.
|
2213 |
+
|
2214 |
+
Args:
|
2215 |
+
query: The query string to search for
|
2216 |
+
chunks_vdb: Vector database containing document chunks
|
2217 |
+
query_param: Query parameters including top_k and ids
|
2218 |
+
tokenizer: Tokenizer for counting tokens
|
2219 |
+
|
2220 |
+
Returns:
|
2221 |
+
Formatted string containing relevant text chunks, or None if no results found
|
2222 |
+
"""
|
2223 |
+
try:
|
2224 |
+
# Reduce top_k for vector search in hybrid mode since we have structured information from KG
|
2225 |
+
mix_topk = (
|
2226 |
+
min(10, query_param.top_k)
|
2227 |
+
if hasattr(query_param, "mode") and query_param.mode == "mix"
|
2228 |
+
else query_param.top_k
|
2229 |
+
)
|
2230 |
+
results = await chunks_vdb.query(query, top_k=mix_topk, ids=query_param.ids)
|
2231 |
+
if not results:
|
2232 |
+
return None
|
2233 |
+
|
2234 |
+
valid_chunks = []
|
2235 |
+
for result in results:
|
2236 |
+
if "content" in result:
|
2237 |
+
# Directly use content from chunks_vdb.query result
|
2238 |
+
chunk_with_time = {
|
2239 |
+
"content": result["content"],
|
2240 |
+
"created_at": result.get("created_at", None),
|
2241 |
+
"file_path": result.get("file_path", None),
|
2242 |
+
}
|
2243 |
+
valid_chunks.append(chunk_with_time)
|
2244 |
+
|
2245 |
+
if not valid_chunks:
|
2246 |
+
return None
|
2247 |
+
|
2248 |
+
maybe_trun_chunks = truncate_list_by_token_size(
|
2249 |
+
valid_chunks,
|
2250 |
+
key=lambda x: x["content"],
|
2251 |
+
max_token_size=query_param.max_token_for_text_unit,
|
2252 |
+
tokenizer=tokenizer,
|
2253 |
+
)
|
2254 |
+
|
2255 |
+
logger.debug(
|
2256 |
+
f"Truncate chunks from {len(valid_chunks)} to {len(maybe_trun_chunks)} (max tokens:{query_param.max_token_for_text_unit})"
|
2257 |
+
)
|
2258 |
+
logger.info(f"Vector query: {len(maybe_trun_chunks)} chunks, top_k: {mix_topk}")
|
2259 |
+
|
2260 |
+
if not maybe_trun_chunks:
|
2261 |
+
return None
|
2262 |
+
|
2263 |
+
# Include time information in content
|
2264 |
+
formatted_chunks = []
|
2265 |
+
for c in maybe_trun_chunks:
|
2266 |
+
chunk_text = "File path: " + c["file_path"] + "\r\n\r\n" + c["content"]
|
2267 |
+
if c["created_at"]:
|
2268 |
+
chunk_text = f"[Created at: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(c['created_at']))}]\r\n\r\n{chunk_text}"
|
2269 |
+
formatted_chunks.append(chunk_text)
|
2270 |
+
|
2271 |
+
logger.debug(
|
2272 |
+
f"Truncate chunks from {len(valid_chunks)} to {len(formatted_chunks)} (max tokens:{query_param.max_token_for_text_unit})"
|
2273 |
+
)
|
2274 |
+
return "\r\n\r\n--New Chunk--\r\n\r\n".join(formatted_chunks)
|
2275 |
+
except Exception as e:
|
2276 |
+
logger.error(f"Error in _get_vector_context: {e}")
|
2277 |
+
return None
|
2278 |
+
|
2279 |
+
|
2280 |
async def query_with_keywords(
|
2281 |
query: str,
|
2282 |
prompt: str,
|