sivan22 commited on
Commit
8d38779
โ€ข
1 Parent(s): 5d6c1fa

Update agent_workflow.py

Browse files
Files changed (1) hide show
  1. agent_workflow.py +317 -317
agent_workflow.py CHANGED
@@ -1,317 +1,317 @@
1
- import os
2
- import logging
3
- from typing import List, Dict, Any, Optional, Tuple, Callable, Union
4
- from dotenv import load_dotenv
5
- from llm_providers import LLMProvider
6
- from langchain.schema import HumanMessage
7
- from tantivy_search_agent import TantivySearchAgent
8
-
9
- load_dotenv()
10
-
11
- class SearchAgent:
12
- def __init__(self, tantivy_agent: TantivySearchAgent, provider_name: str = "Claude"):
13
- """Initialize the search agent with Tantivy agent and LLM client"""
14
- self.tantivy_agent = tantivy_agent
15
- self.logger = logging.getLogger(__name__)
16
-
17
- # Initialize LLM provider
18
- self.llm_provider = LLMProvider()
19
- self.llm = None
20
- self.set_provider(provider_name)
21
-
22
- self.min_confidence_threshold = 0.5
23
-
24
- def set_provider(self, provider_name: str) -> None:
25
- self.llm = self.llm_provider.get_provider(provider_name)
26
- if not self.llm:
27
- raise ValueError(f"Provider {provider_name} not available")
28
- self.current_provider = provider_name
29
-
30
- def get_available_providers(self) -> list[str]:
31
- return self.llm_provider.get_available_providers()
32
-
33
- def get_query(self, query: str, failed_queries: List[Dict[str, str]] = []) -> str:
34
- """Generate a Tantivy query using Claude, considering previously failed queries"""
35
- try:
36
- if not self.llm:
37
- raise ValueError("LLM provider not initialized")
38
-
39
- prompt = (
40
- "Create a query for this search request with the following restrictions:\n"+
41
- self.tantivy_agent.get_query_instructions()+
42
- "\n\nAdditional instructions: \n"
43
- "1. return only the search query without any other text\n"
44
- "2. Use only Hebrew terms for the search query\n"
45
- "3. the corpus to search in is an ancient Hebrew corpus - Tora and Talmud. so Try to use ancient Hebrew terms and or Talmudic expressions."
46
- "4. prevent modern words that are not common in talmudic texts \n"
47
- f"the search request: {query}"
48
- )
49
-
50
- if failed_queries:
51
- prompt += (
52
- f"\n\nPrevious failed queries:\n"+
53
- "------------------------\n"+
54
- '\n'.join(f"Query: {q['query']}, Reason: {q['reason']}" for q in failed_queries)+
55
- "\n\n"
56
- "Please generate an alternative query that:\n"
57
- "1. Uses different Hebrew synonyms or related terms\n"
58
- "2. Tries broader or more general terms\n"
59
- "3. Adjusts proximity values or uses wildcards\n"
60
- "4. Prevents using modern words that are not common in ancient hebrew and talmud texts\n"
61
- )
62
-
63
- response = self.llm.invoke([HumanMessage(content=prompt)])
64
- tantivy_query = response.content.strip()
65
- self.logger.info(f"Generated Tantivy query: {tantivy_query}")
66
- return tantivy_query
67
-
68
- except Exception as e:
69
- self.logger.error(f"Error generating query: {e}")
70
- # Fallback to basic quoted search
71
- return f'"{query}"'
72
-
73
- def _evaluate_results(self, results: List[Dict[str, Any]], query: str) -> Dict[str, Any]:
74
- """Evaluate search results using Claude with confidence scoring"""
75
- if not self.llm:
76
- raise ValueError("LLM provider not initialized")
77
-
78
- # Prepare context from results
79
- context = "\n".join(f"Result {i}. Source: {r.get('reference',[])}\n Text: {r.get('text', [])}"
80
- for i, r in enumerate(results)
81
- )
82
-
83
- try:
84
- message = self.llm.invoke([HumanMessage(content=f"""Evaluate the search results for answering this question:
85
- Question: {query}
86
-
87
- Search Results:
88
- {context}
89
-
90
- Provide evaluation in this format (3 lines):
91
- Confidence score (0.0 to 1.0) indicating how well the results can answer the question. this line should include only the number return, don't include '[line 1]'
92
- ACCEPT if score >= {self.min_confidence_threshold}, REFINE if score < {self.min_confidence_threshold}. return only the word ACCEPT or REFINE.
93
- Detailed explanation of what information is present or missing, don't include '[line 3]'. it should be only in Hebrew
94
- """)])
95
- lines = message.content.strip().replace('\n\n', '\n').split('\n')
96
- confidence = float(lines[0])
97
- decision = lines[1].upper()
98
- explanation = lines[2]
99
-
100
- is_good = decision == 'ACCEPT'
101
-
102
- self.logger.info(f"Evaluation: Confidence={confidence}, Decision={decision}")
103
- self.logger.info(f"Explanation: {explanation}")
104
-
105
- return {
106
- "confidence": confidence,
107
- "is_sufficient": is_good,
108
- "explanation": explanation,
109
-
110
- }
111
-
112
- except Exception as e:
113
- self.logger.error(f"Error evaluating results: {e}")
114
- # Fallback to simple evaluation
115
- return {
116
- "confidence": 0.0,
117
- "is_sufficient": False,
118
- "explanation": "",
119
- }
120
-
121
- def _generate_answer(self, query: str, results: List[Dict[str, Any]]) -> str:
122
- """Generate answer using Claude with improved context utilization"""
123
- if not self.llm:
124
- raise ValueError("LLM provider not initialized")
125
-
126
- if not results:
127
- return "ืœื ื ืžืฆืื• ืชื•ืฆืื•ืช"
128
-
129
- # Prepare context from results
130
- context = "\n".join(f"Result {i+1}. Source: {r.get('reference',[])}\n Text: {r.get('text', [])}"
131
- for i, r in enumerate(results)
132
- )
133
-
134
- try:
135
- message = self.llm.invoke([HumanMessage(content=f"""Based on these search results, answer this question:
136
- Question: {query}
137
-
138
- Search Results:
139
- {context}
140
-
141
- Requirements for your answer:
142
- 1. Use only information from the search results
143
- 2. Be comprehensive but concise
144
- 3. Structure the answer clearly
145
- 4. If any aspect of the question cannot be fully answered, acknowledge this
146
- 5. cite sources for each fact or information you use
147
- 6. The answer should be only in Hebrew
148
- """)])
149
- return message.content.strip()
150
-
151
- except Exception as e:
152
- self.logger.error(f"Error generating answer: {e}")
153
- return f"I encountered an error generating the answer: {str(e)}"
154
-
155
- def search_and_answer(self, query: str, num_results: int = 10, max_iterations: int = 3,
156
- on_step: Optional[Callable[[Dict[str, Any]], None]] = None) -> Dict[str, Any]:
157
- """Execute multi-step search process using Tantivy with streaming updates"""
158
- steps = []
159
- all_results = []
160
-
161
- # Step 1: Generate Tantivy query
162
- initial_query = self.get_query(query)
163
- step = {
164
- 'action': 'ื™ืฆื™ืจืช ืฉืื™ืœืชืช ื—ื™ืคื•ืฉ',
165
- 'description': 'ื ื•ืฆืจื” ืฉืื™ืœืชืช ื—ื™ืคื•ืฉ ืขื‘ื•ืจ ืžื ื•ืข ื”ื—ื™ืคื•ืฉ',
166
- 'results': [{'type': 'query', 'content': initial_query}]
167
- }
168
- steps.append(step)
169
- if on_step:
170
- on_step(step)
171
-
172
- # Step 2: Initial search with Tantivy query
173
- results = self.tantivy_agent.search(initial_query, num_results)
174
-
175
- step = {
176
- 'action': 'ื—ื™ืคื•ืฉ ื‘ืžืื’ืจ',
177
- 'description': f'ื—ื™ืคื•ืฉ ื‘ืžืื’ืจ ืขื‘ื•ืจ ืฉืื™ืœืชืช ื—ื™ืคื•ืฉ: {initial_query}',
178
- 'results': [{'type': 'document', 'content': {
179
- 'title': r['title'],
180
- 'reference': r['reference'],
181
- 'topics': r['topics'],
182
- 'highlights': r['highlights'],
183
- 'score': r['score']
184
- }} for r in results]
185
- }
186
- steps.append(step)
187
- if on_step:
188
- on_step(step)
189
-
190
- failed_queries = []
191
-
192
- if results.__len__() == 0:
193
- failed_queries.append({'query': initial_query, 'reason': 'no results'})
194
- is_sufficient = False
195
- else:
196
- all_results.extend(results)
197
-
198
- # Step 3: Evaluate results
199
- evaluation = self._evaluate_results(results, query)
200
- confidence = evaluation['confidence']
201
- is_sufficient = evaluation['is_sufficient']
202
- explanation = evaluation['explanation']
203
-
204
- step = {
205
- 'action': 'ื“ื™ืจื•ื’ ืชื•ืฆืื•ืช',
206
- 'description': 'ื“ื™ืจื•ื’ ืชื•ืฆืื•ืช ื—ื™ืคื•ืฉ',
207
- 'results': [{
208
- 'type': 'evaluation',
209
- 'content': {
210
- 'status': 'accepted' if is_sufficient else 'insufficient',
211
- 'confidence': confidence,
212
- 'explanation': explanation,
213
- }
214
- }]
215
- }
216
- steps.append(step)
217
- if on_step:
218
- on_step(step)
219
-
220
- if not is_sufficient:
221
- failed_queries.append({'query': initial_query, 'reason': explanation})
222
-
223
- # Step 4: Additional searches if needed
224
- attempt = 2
225
- while not is_sufficient and attempt < max_iterations:
226
- # Generate new query
227
- new_query = self.get_query(query, failed_queries)
228
-
229
- step = {
230
- 'action': f'ื™ืฆื™ืจืช ืฉืื™ืœืชื” ืžื—ื“ืฉ (ื ื™ืกื™ื•ืŸ {attempt})',
231
- 'description': 'ื ื•ืฆืจื” ืฉืื™ืœืชืช ื—ื™ืคื•ืฉ ื ื•ืกืคืช ืขื‘ื•ืจ ืžื ื•ืข ื”ื—ื™ืคื•ืฉ',
232
- 'results': [
233
- {'type': 'new_query', 'content': new_query}
234
- ]
235
- }
236
- steps.append(step)
237
- if on_step:
238
- on_step(step)
239
-
240
- # Search with new query
241
- results = self.tantivy_agent.search(new_query, num_results)
242
-
243
- step = {
244
- 'action': f'ื—ื™ืคื•ืฉ ื ื•ืกืฃ (ื ื™ืกื™ื•ืŸ {attempt}) ',
245
- 'description': f'ืžื—ืคืฉ ื‘ืžืื’ืจ ืขื‘ื•ืจ ืฉืื™ืœืชืช ื—ื™ืคื•ืฉ: {new_query}',
246
- 'results': [{'type': 'document', 'content': {
247
- 'title': r['title'],
248
- 'reference': r['reference'],
249
- 'topics': r['topics'],
250
- 'highlights': r['highlights'],
251
- 'score': r['score']
252
- }} for r in results]
253
- }
254
- steps.append(step)
255
- if on_step:
256
- on_step(step)
257
-
258
- if results.__len__() == 0:
259
- failed_queries.append({'query': new_query, 'reason': 'no results'})
260
-
261
- else:
262
- all_results.extend(results)
263
-
264
- # Re-evaluate with current results
265
- evaluation = self._evaluate_results(results, query)
266
- confidence = evaluation['confidence']
267
- is_sufficient = evaluation['is_sufficient']
268
- explanation = evaluation['explanation']
269
-
270
- step = {
271
- 'action': f'ื“ื™ืจื•ื’ ืชื•ืฆืื•ืช (ื ื™ืกื™ื•ืŸ {attempt})',
272
- 'description': 'ื“ื™ืจื•ื’ ืชื•ืฆืื•ืช ื—ื™ืคื•ืฉ ืœื ื™ืกื™ื•ืŸ ื–ื”',
273
- 'explanation': explanation,
274
- 'results': [{
275
- 'type': 'evaluation',
276
- 'content': {
277
- 'status': 'accepted' if is_sufficient else 'insufficient',
278
- 'confidence': confidence,
279
- 'explanation': explanation,
280
- }
281
- }]
282
- }
283
- steps.append(step)
284
- if on_step:
285
- on_step(step)
286
-
287
- if not is_sufficient:
288
- failed_queries.append({'query': new_query, 'reason': explanation})
289
-
290
- attempt += 1
291
-
292
- # Step 5: Generate final answer
293
- answer = self._generate_answer(query, all_results)
294
-
295
- final_result = {
296
- 'steps': steps,
297
- 'answer': answer,
298
- 'sources': [{
299
- 'title': r['title'],
300
- 'reference': r['reference'],
301
- 'topics': r['topics'],
302
- 'path': r['file_path'],
303
- 'highlights': r['highlights'],
304
- 'text': r['text'],
305
- 'score': r['score']
306
- } for r in all_results]
307
- }
308
-
309
- # Send final result through callback
310
- if on_step:
311
- on_step({
312
- 'action': 'ืกื™ื•ื',
313
- 'description': 'ื”ื—ื™ืคื•ืฉ ื”ื•ืฉืœื',
314
- 'final_result': final_result
315
- })
316
-
317
- return final_result
 
1
+ import os
2
+ import logging
3
+ from typing import List, Dict, Any, Optional, Tuple, Callable, Union
4
+ from dotenv import load_dotenv
5
+ from llm_providers import LLMProvider
6
+ from langchain.schema import HumanMessage
7
+ from tantivy_search_agent import TantivySearchAgent
8
+
9
+ load_dotenv()
10
+
11
+ class SearchAgent:
12
+ def __init__(self, tantivy_agent: TantivySearchAgent, provider_name: str = "Gemini"):
13
+ """Initialize the search agent with Tantivy agent and LLM client"""
14
+ self.tantivy_agent = tantivy_agent
15
+ self.logger = logging.getLogger(__name__)
16
+
17
+ # Initialize LLM provider
18
+ self.llm_provider = LLMProvider()
19
+ self.llm = None
20
+ self.set_provider(provider_name)
21
+
22
+ self.min_confidence_threshold = 0.5
23
+
24
+ def set_provider(self, provider_name: str) -> None:
25
+ self.llm = self.llm_provider.get_provider(provider_name)
26
+ if not self.llm:
27
+ raise ValueError(f"Provider {provider_name} not available")
28
+ self.current_provider = provider_name
29
+
30
+ def get_available_providers(self) -> list[str]:
31
+ return self.llm_provider.get_available_providers()
32
+
33
+ def get_query(self, query: str, failed_queries: List[Dict[str, str]] = []) -> str:
34
+ """Generate a Tantivy query using Claude, considering previously failed queries"""
35
+ try:
36
+ if not self.llm:
37
+ raise ValueError("LLM provider not initialized")
38
+
39
+ prompt = (
40
+ "Create a query for this search request with the following restrictions:\n"+
41
+ self.tantivy_agent.get_query_instructions()+
42
+ "\n\nAdditional instructions: \n"
43
+ "1. return only the search query without any other text\n"
44
+ "2. Use only Hebrew terms for the search query\n"
45
+ "3. the corpus to search in is an ancient Hebrew corpus - Tora and Talmud. so Try to use ancient Hebrew terms and or Talmudic expressions."
46
+ "4. prevent modern words that are not common in talmudic texts \n"
47
+ f"the search request: {query}"
48
+ )
49
+
50
+ if failed_queries:
51
+ prompt += (
52
+ f"\n\nPrevious failed queries:\n"+
53
+ "------------------------\n"+
54
+ '\n'.join(f"Query: {q['query']}, Reason: {q['reason']}" for q in failed_queries)+
55
+ "\n\n"
56
+ "Please generate an alternative query that:\n"
57
+ "1. Uses different Hebrew synonyms or related terms\n"
58
+ "2. Tries broader or more general terms\n"
59
+ "3. Adjusts proximity values or uses wildcards\n"
60
+ "4. Prevents using modern words that are not common in ancient hebrew and talmud texts\n"
61
+ )
62
+
63
+ response = self.llm.invoke([HumanMessage(content=prompt)])
64
+ tantivy_query = response.content.strip()
65
+ self.logger.info(f"Generated Tantivy query: {tantivy_query}")
66
+ return tantivy_query
67
+
68
+ except Exception as e:
69
+ self.logger.error(f"Error generating query: {e}")
70
+ # Fallback to basic quoted search
71
+ return f'"{query}"'
72
+
73
+ def _evaluate_results(self, results: List[Dict[str, Any]], query: str) -> Dict[str, Any]:
74
+ """Evaluate search results using Claude with confidence scoring"""
75
+ if not self.llm:
76
+ raise ValueError("LLM provider not initialized")
77
+
78
+ # Prepare context from results
79
+ context = "\n".join(f"Result {i}. Source: {r.get('reference',[])}\n Text: {r.get('text', [])}"
80
+ for i, r in enumerate(results)
81
+ )
82
+
83
+ try:
84
+ message = self.llm.invoke([HumanMessage(content=f"""Evaluate the search results for answering this question:
85
+ Question: {query}
86
+
87
+ Search Results:
88
+ {context}
89
+
90
+ Provide evaluation in this format (3 lines):
91
+ Confidence score (0.0 to 1.0) indicating how well the results can answer the question. this line should include only the number return, don't include '[line 1]'
92
+ ACCEPT if score >= {self.min_confidence_threshold}, REFINE if score < {self.min_confidence_threshold}. return only the word ACCEPT or REFINE.
93
+ Detailed explanation of what information is present or missing, don't include '[line 3]'. it should be only in Hebrew
94
+ """)])
95
+ lines = message.content.strip().replace('\n\n', '\n').split('\n')
96
+ confidence = float(lines[0])
97
+ decision = lines[1].upper()
98
+ explanation = lines[2]
99
+
100
+ is_good = decision == 'ACCEPT'
101
+
102
+ self.logger.info(f"Evaluation: Confidence={confidence}, Decision={decision}")
103
+ self.logger.info(f"Explanation: {explanation}")
104
+
105
+ return {
106
+ "confidence": confidence,
107
+ "is_sufficient": is_good,
108
+ "explanation": explanation,
109
+
110
+ }
111
+
112
+ except Exception as e:
113
+ self.logger.error(f"Error evaluating results: {e}")
114
+ # Fallback to simple evaluation
115
+ return {
116
+ "confidence": 0.0,
117
+ "is_sufficient": False,
118
+ "explanation": "",
119
+ }
120
+
121
+ def _generate_answer(self, query: str, results: List[Dict[str, Any]]) -> str:
122
+ """Generate answer using Claude with improved context utilization"""
123
+ if not self.llm:
124
+ raise ValueError("LLM provider not initialized")
125
+
126
+ if not results:
127
+ return "ืœื ื ืžืฆืื• ืชื•ืฆืื•ืช"
128
+
129
+ # Prepare context from results
130
+ context = "\n".join(f"Result {i+1}. Source: {r.get('reference',[])}\n Text: {r.get('text', [])}"
131
+ for i, r in enumerate(results)
132
+ )
133
+
134
+ try:
135
+ message = self.llm.invoke([HumanMessage(content=f"""Based on these search results, answer this question:
136
+ Question: {query}
137
+
138
+ Search Results:
139
+ {context}
140
+
141
+ Requirements for your answer:
142
+ 1. Use only information from the search results
143
+ 2. Be comprehensive but concise
144
+ 3. Structure the answer clearly
145
+ 4. If any aspect of the question cannot be fully answered, acknowledge this
146
+ 5. cite sources for each fact or information you use
147
+ 6. The answer should be only in Hebrew
148
+ """)])
149
+ return message.content.strip()
150
+
151
+ except Exception as e:
152
+ self.logger.error(f"Error generating answer: {e}")
153
+ return f"I encountered an error generating the answer: {str(e)}"
154
+
155
+ def search_and_answer(self, query: str, num_results: int = 10, max_iterations: int = 3,
156
+ on_step: Optional[Callable[[Dict[str, Any]], None]] = None) -> Dict[str, Any]:
157
+ """Execute multi-step search process using Tantivy with streaming updates"""
158
+ steps = []
159
+ all_results = []
160
+
161
+ # Step 1: Generate Tantivy query
162
+ initial_query = self.get_query(query)
163
+ step = {
164
+ 'action': 'ื™ืฆื™ืจืช ืฉืื™ืœืชืช ื—ื™ืคื•ืฉ',
165
+ 'description': 'ื ื•ืฆืจื” ืฉืื™ืœืชืช ื—ื™ืคื•ืฉ ืขื‘ื•ืจ ืžื ื•ืข ื”ื—ื™ืคื•ืฉ',
166
+ 'results': [{'type': 'query', 'content': initial_query}]
167
+ }
168
+ steps.append(step)
169
+ if on_step:
170
+ on_step(step)
171
+
172
+ # Step 2: Initial search with Tantivy query
173
+ results = self.tantivy_agent.search(initial_query, num_results)
174
+
175
+ step = {
176
+ 'action': 'ื—ื™ืคื•ืฉ ื‘ืžืื’ืจ',
177
+ 'description': f'ื—ื™ืคื•ืฉ ื‘ืžืื’ืจ ืขื‘ื•ืจ ืฉืื™ืœืชืช ื—ื™ืคื•ืฉ: {initial_query}',
178
+ 'results': [{'type': 'document', 'content': {
179
+ 'title': r['title'],
180
+ 'reference': r['reference'],
181
+ 'topics': r['topics'],
182
+ 'highlights': r['highlights'],
183
+ 'score': r['score']
184
+ }} for r in results]
185
+ }
186
+ steps.append(step)
187
+ if on_step:
188
+ on_step(step)
189
+
190
+ failed_queries = []
191
+
192
+ if results.__len__() == 0:
193
+ failed_queries.append({'query': initial_query, 'reason': 'no results'})
194
+ is_sufficient = False
195
+ else:
196
+ all_results.extend(results)
197
+
198
+ # Step 3: Evaluate results
199
+ evaluation = self._evaluate_results(results, query)
200
+ confidence = evaluation['confidence']
201
+ is_sufficient = evaluation['is_sufficient']
202
+ explanation = evaluation['explanation']
203
+
204
+ step = {
205
+ 'action': 'ื“ื™ืจื•ื’ ืชื•ืฆืื•ืช',
206
+ 'description': 'ื“ื™ืจื•ื’ ืชื•ืฆืื•ืช ื—ื™ืคื•ืฉ',
207
+ 'results': [{
208
+ 'type': 'evaluation',
209
+ 'content': {
210
+ 'status': 'accepted' if is_sufficient else 'insufficient',
211
+ 'confidence': confidence,
212
+ 'explanation': explanation,
213
+ }
214
+ }]
215
+ }
216
+ steps.append(step)
217
+ if on_step:
218
+ on_step(step)
219
+
220
+ if not is_sufficient:
221
+ failed_queries.append({'query': initial_query, 'reason': explanation})
222
+
223
+ # Step 4: Additional searches if needed
224
+ attempt = 2
225
+ while not is_sufficient and attempt < max_iterations:
226
+ # Generate new query
227
+ new_query = self.get_query(query, failed_queries)
228
+
229
+ step = {
230
+ 'action': f'ื™ืฆื™ืจืช ืฉืื™ืœืชื” ืžื—ื“ืฉ (ื ื™ืกื™ื•ืŸ {attempt})',
231
+ 'description': 'ื ื•ืฆืจื” ืฉืื™ืœืชืช ื—ื™ืคื•ืฉ ื ื•ืกืคืช ืขื‘ื•ืจ ืžื ื•ืข ื”ื—ื™ืคื•ืฉ',
232
+ 'results': [
233
+ {'type': 'new_query', 'content': new_query}
234
+ ]
235
+ }
236
+ steps.append(step)
237
+ if on_step:
238
+ on_step(step)
239
+
240
+ # Search with new query
241
+ results = self.tantivy_agent.search(new_query, num_results)
242
+
243
+ step = {
244
+ 'action': f'ื—ื™ืคื•ืฉ ื ื•ืกืฃ (ื ื™ืกื™ื•ืŸ {attempt}) ',
245
+ 'description': f'ืžื—ืคืฉ ื‘ืžืื’ืจ ืขื‘ื•ืจ ืฉืื™ืœืชืช ื—ื™ืคื•ืฉ: {new_query}',
246
+ 'results': [{'type': 'document', 'content': {
247
+ 'title': r['title'],
248
+ 'reference': r['reference'],
249
+ 'topics': r['topics'],
250
+ 'highlights': r['highlights'],
251
+ 'score': r['score']
252
+ }} for r in results]
253
+ }
254
+ steps.append(step)
255
+ if on_step:
256
+ on_step(step)
257
+
258
+ if results.__len__() == 0:
259
+ failed_queries.append({'query': new_query, 'reason': 'no results'})
260
+
261
+ else:
262
+ all_results.extend(results)
263
+
264
+ # Re-evaluate with current results
265
+ evaluation = self._evaluate_results(results, query)
266
+ confidence = evaluation['confidence']
267
+ is_sufficient = evaluation['is_sufficient']
268
+ explanation = evaluation['explanation']
269
+
270
+ step = {
271
+ 'action': f'ื“ื™ืจื•ื’ ืชื•ืฆืื•ืช (ื ื™ืกื™ื•ืŸ {attempt})',
272
+ 'description': 'ื“ื™ืจื•ื’ ืชื•ืฆืื•ืช ื—ื™ืคื•ืฉ ืœื ื™ืกื™ื•ืŸ ื–ื”',
273
+ 'explanation': explanation,
274
+ 'results': [{
275
+ 'type': 'evaluation',
276
+ 'content': {
277
+ 'status': 'accepted' if is_sufficient else 'insufficient',
278
+ 'confidence': confidence,
279
+ 'explanation': explanation,
280
+ }
281
+ }]
282
+ }
283
+ steps.append(step)
284
+ if on_step:
285
+ on_step(step)
286
+
287
+ if not is_sufficient:
288
+ failed_queries.append({'query': new_query, 'reason': explanation})
289
+
290
+ attempt += 1
291
+
292
+ # Step 5: Generate final answer
293
+ answer = self._generate_answer(query, all_results)
294
+
295
+ final_result = {
296
+ 'steps': steps,
297
+ 'answer': answer,
298
+ 'sources': [{
299
+ 'title': r['title'],
300
+ 'reference': r['reference'],
301
+ 'topics': r['topics'],
302
+ 'path': r['file_path'],
303
+ 'highlights': r['highlights'],
304
+ 'text': r['text'],
305
+ 'score': r['score']
306
+ } for r in all_results]
307
+ }
308
+
309
+ # Send final result through callback
310
+ if on_step:
311
+ on_step({
312
+ 'action': 'ืกื™ื•ื',
313
+ 'description': 'ื”ื—ื™ืคื•ืฉ ื”ื•ืฉืœื',
314
+ 'final_result': final_result
315
+ })
316
+
317
+ return final_result