@@ -33,14 +33,25 @@ def filter_chunks(
3333 ) -> List [Dict [str , Any ]]:
3434 """
3535 LLM Re-ranking: Asks Gemini to filter out irrelevant chunks before generation.
36+ Uses sequential chunk_N IDs (chunk_1, chunk_2, ...) for the filter prompt so
37+ the LLM and the matching logic agree on the same ID format.
3638 """
3739 if not chunks :
3840 return []
3941
40- prompt = prompts .get_filter_prompt (chunks , user_question )
42+ # Remap chunks to sequential IDs for the filter prompt so the LLM
43+ # returns predictable IDs like "chunk_1, chunk_3" instead of UUIDs.
44+ sequential_id_map : Dict [str , Dict [str , Any ]] = {}
45+ remapped_chunks = []
46+ for i , chunk in enumerate (chunks , start = 1 ):
47+ seq_id = f"chunk_{ i } "
48+ sequential_id_map [seq_id ] = chunk
49+ remapped_chunks .append ({** chunk , "chunk_id" : seq_id })
50+
51+ prompt = prompts .get_filter_prompt (remapped_chunks , user_question )
4152 cfg = types .GenerateContentConfig (
4253 system_instruction = prompts .FILTER_SYSTEM_INSTRUCTION ,
43- temperature = 0.0 , # Zero precision for extraction
54+ temperature = 0.0 ,
4455 max_output_tokens = 100 ,
4556 )
4657
@@ -51,40 +62,37 @@ def filter_chunks(
5162 config = cfg ,
5263 )
5364 output = getattr (response , "text" , "" ) or ""
54- print (f"🧠 Re-ranker Output: { output } " )
55-
65+ print (f"Re-ranker Output: { output } " )
66+
5667 if "NONE" in output .upper ():
57- print ("🧠 Re-ranker kept 0 chunks." )
68+ print ("Re-ranker kept 0 chunks." )
5869 return []
59-
60- # Parse the output safely: sometimes Gemini returns a clean comma-separated list of UUIDs
61- # But sometimes it's lazy and returns just the first part e.g. "30, 4f" instead of "302f24..."
62- filtered_chunks = []
70+
71+ # Match returned IDs (e.g. "chunk_1, chunk_3") back to original chunks.
6372 output_parts = [p .strip () for p in output .split ("," ) if p .strip ()]
64-
65- for chunk in chunks :
66- chunk_id = str ( chunk [ "chunk_id" ])
67-
68- # Check 1: Is the full chunk_id anywhere in the raw output string?
69- if chunk_id in output :
70- filtered_chunks . append ( chunk )
73+ filtered_chunks = []
74+ seen = set ()
75+ for part in output_parts :
76+ # Exact match: "chunk_3"
77+ if part in sequential_id_map and part not in seen :
78+ filtered_chunks . append ( sequential_id_map [ part ])
79+ seen . add ( part )
7180 continue
72-
73- # Check 2: Did the LLM abbreviate the IDs? Check each comma-separated part.
74- for part in output_parts :
75- if len (part ) >= 2 and chunk_id .startswith (part ):
76- filtered_chunks .append (chunk )
77- break
78-
79- print (f"🧠 Re-ranker kept { len (filtered_chunks )} /{ len (chunks )} chunks." )
80-
81- # Fallback to all chunks if zero were matched but it didn't explicitly say "NONE"
81+ # Plain number match: LLM returned "3" instead of "chunk_3"
82+ candidate = f"chunk_{ part } "
83+ if candidate in sequential_id_map and candidate not in seen :
84+ filtered_chunks .append (sequential_id_map [candidate ])
85+ seen .add (candidate )
86+
87+ print (f"Re-ranker kept { len (filtered_chunks )} /{ len (chunks )} chunks." )
88+
89+ # Fallback: if nothing matched but LLM didn't say NONE, return all chunks.
8290 if not filtered_chunks :
83- return chunks
91+ return chunks
8492 return filtered_chunks
85-
93+
8694 except Exception as e :
87- logger .warning (f"⚠️ Re-ranking failed (falling back to all chunks): { e } " )
95+ logger .warning (f"Re-ranking failed (falling back to all chunks): { e } " )
8896 return chunks
8997
9098 def ask_workmate (
0 commit comments