lollms-client 1.5.6__py3-none-any.whl → 1.7.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lollms_client/__init__.py +1 -1
- lollms_client/llm_bindings/azure_openai/__init__.py +2 -2
- lollms_client/llm_bindings/claude/__init__.py +125 -34
- lollms_client/llm_bindings/gemini/__init__.py +261 -159
- lollms_client/llm_bindings/grok/__init__.py +52 -14
- lollms_client/llm_bindings/groq/__init__.py +2 -2
- lollms_client/llm_bindings/hugging_face_inference_api/__init__.py +2 -2
- lollms_client/llm_bindings/litellm/__init__.py +1 -1
- lollms_client/llm_bindings/llamacpp/__init__.py +18 -11
- lollms_client/llm_bindings/lollms/__init__.py +76 -21
- lollms_client/llm_bindings/lollms_webui/__init__.py +1 -1
- lollms_client/llm_bindings/mistral/__init__.py +2 -2
- lollms_client/llm_bindings/novita_ai/__init__.py +142 -6
- lollms_client/llm_bindings/ollama/__init__.py +307 -89
- lollms_client/llm_bindings/open_router/__init__.py +2 -2
- lollms_client/llm_bindings/openai/__init__.py +81 -20
- lollms_client/llm_bindings/openllm/__init__.py +362 -506
- lollms_client/llm_bindings/openwebui/__init__.py +333 -171
- lollms_client/llm_bindings/perplexity/__init__.py +2 -2
- lollms_client/llm_bindings/pythonllamacpp/__init__.py +3 -3
- lollms_client/llm_bindings/tensor_rt/__init__.py +1 -1
- lollms_client/llm_bindings/transformers/__init__.py +428 -632
- lollms_client/llm_bindings/vllm/__init__.py +1 -1
- lollms_client/lollms_agentic.py +4 -2
- lollms_client/lollms_base_binding.py +61 -0
- lollms_client/lollms_core.py +512 -1890
- lollms_client/lollms_discussion.py +25 -11
- lollms_client/lollms_llm_binding.py +112 -261
- lollms_client/lollms_mcp_binding.py +34 -75
- lollms_client/lollms_stt_binding.py +85 -52
- lollms_client/lollms_tti_binding.py +23 -37
- lollms_client/lollms_ttm_binding.py +24 -42
- lollms_client/lollms_tts_binding.py +28 -17
- lollms_client/lollms_ttv_binding.py +24 -42
- lollms_client/lollms_types.py +4 -2
- lollms_client/stt_bindings/whisper/__init__.py +108 -23
- lollms_client/stt_bindings/whispercpp/__init__.py +7 -1
- lollms_client/tti_bindings/diffusers/__init__.py +418 -810
- lollms_client/tti_bindings/diffusers/server/main.py +1051 -0
- lollms_client/tti_bindings/gemini/__init__.py +182 -239
- lollms_client/tti_bindings/leonardo_ai/__init__.py +6 -3
- lollms_client/tti_bindings/lollms/__init__.py +4 -1
- lollms_client/tti_bindings/novita_ai/__init__.py +5 -2
- lollms_client/tti_bindings/openai/__init__.py +10 -11
- lollms_client/tti_bindings/stability_ai/__init__.py +5 -3
- lollms_client/ttm_bindings/audiocraft/__init__.py +7 -12
- lollms_client/ttm_bindings/beatoven_ai/__init__.py +7 -3
- lollms_client/ttm_bindings/lollms/__init__.py +4 -17
- lollms_client/ttm_bindings/replicate/__init__.py +7 -4
- lollms_client/ttm_bindings/stability_ai/__init__.py +7 -4
- lollms_client/ttm_bindings/topmediai/__init__.py +6 -3
- lollms_client/tts_bindings/bark/__init__.py +7 -10
- lollms_client/tts_bindings/lollms/__init__.py +6 -1
- lollms_client/tts_bindings/piper_tts/__init__.py +8 -11
- lollms_client/tts_bindings/xtts/__init__.py +157 -74
- lollms_client/tts_bindings/xtts/server/main.py +241 -280
- {lollms_client-1.5.6.dist-info → lollms_client-1.7.10.dist-info}/METADATA +113 -5
- lollms_client-1.7.10.dist-info/RECORD +89 -0
- lollms_client-1.5.6.dist-info/RECORD +0 -87
- {lollms_client-1.5.6.dist-info → lollms_client-1.7.10.dist-info}/WHEEL +0 -0
- {lollms_client-1.5.6.dist-info → lollms_client-1.7.10.dist-info}/licenses/LICENSE +0 -0
- {lollms_client-1.5.6.dist-info → lollms_client-1.7.10.dist-info}/top_level.txt +0 -0
lollms_client/lollms_core.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lollms_client/lollms_core.py
|
|
2
|
+
# author: ParisNeo
|
|
3
|
+
# description: LollmsClient definition file
|
|
2
4
|
import requests
|
|
3
5
|
from ascii_colors import ASCIIColors, trace_exception
|
|
4
6
|
from lollms_client.lollms_types import MSG_TYPE, ELF_COMPLETION_FORMAT
|
|
@@ -89,21 +91,6 @@ class LollmsClient():
|
|
|
89
91
|
stt_binding_config (Optional[Dict]): Additional config for the STT binding.
|
|
90
92
|
ttv_binding_config (Optional[Dict]): Additional config for the TTV binding.
|
|
91
93
|
ttm_binding_config (Optional[Dict]): Additional config for the TTM binding.
|
|
92
|
-
service_key (Optional[str]): Shared authentication key or client_id.
|
|
93
|
-
verify_ssl_certificate (bool): Whether to verify SSL certificates.
|
|
94
|
-
ctx_size (Optional[int]): Default context size for LLM.
|
|
95
|
-
n_predict (Optional[int]): Default max tokens for LLM.
|
|
96
|
-
stream (bool): Default streaming mode for LLM.
|
|
97
|
-
temperature (float): Default temperature for LLM.
|
|
98
|
-
top_k (int): Default top_k for LLM.
|
|
99
|
-
top_p (float): Default top_p for LLM.
|
|
100
|
-
repeat_penalty (float): Default repeat penalty for LLM.
|
|
101
|
-
repeat_last_n (int): Default repeat last n for LLM.
|
|
102
|
-
seed (Optional[int]): Default seed for LLM.
|
|
103
|
-
n_threads (int): Default threads for LLM.
|
|
104
|
-
streaming_callback (Optional[Callable]): Default streaming callback for LLM.
|
|
105
|
-
user_name (str): Default user name for prompts.
|
|
106
|
-
ai_name (str): Default AI name for prompts.
|
|
107
94
|
|
|
108
95
|
Raises:
|
|
109
96
|
ValueError: If the primary LLM binding cannot be created.
|
|
@@ -143,103 +130,134 @@ class LollmsClient():
|
|
|
143
130
|
ASCIIColors.warning(f"Failed to create LLM binding: {llm_binding_name}. Available: {available}")
|
|
144
131
|
|
|
145
132
|
if tts_binding_name:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
**{
|
|
149
|
-
k: v
|
|
150
|
-
for k, v in (tts_binding_config or {}).items()
|
|
151
|
-
if k != "binding_name"
|
|
152
|
-
}
|
|
153
|
-
)
|
|
154
|
-
if self.tts is None:
|
|
155
|
-
ASCIIColors.warning(f"Failed to create TTS binding: {tts_binding_name}. Available: {self.tts_binding_manager.get_available_bindings()}")
|
|
156
|
-
|
|
157
|
-
if tti_binding_name:
|
|
158
|
-
if tti_binding_config:
|
|
159
|
-
self.tti = self.tti_binding_manager.create_binding(
|
|
160
|
-
binding_name=tti_binding_name,
|
|
161
|
-
**{
|
|
133
|
+
try:
|
|
134
|
+
params = {
|
|
162
135
|
k: v
|
|
163
|
-
for k, v in (
|
|
136
|
+
for k, v in (tts_binding_config or {}).items()
|
|
164
137
|
if k != "binding_name"
|
|
165
138
|
}
|
|
139
|
+
self.tts = self.tts_binding_manager.create_binding(
|
|
140
|
+
binding_name=tts_binding_name,
|
|
141
|
+
**params
|
|
166
142
|
)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
|
|
143
|
+
if self.tts is None:
|
|
144
|
+
ASCIIColors.warning(f"Failed to create TTS binding: {tts_binding_name}. Available: {self.tts_binding_manager.get_available_bindings()}")
|
|
145
|
+
except Exception as e:
|
|
146
|
+
trace_exception(e)
|
|
147
|
+
ASCIIColors.warning(f"Exception occurred while creating TTS binding: {str(e)}")
|
|
148
|
+
self.tts = None
|
|
173
149
|
|
|
150
|
+
if tti_binding_name:
|
|
151
|
+
try:
|
|
152
|
+
if tti_binding_config:
|
|
153
|
+
self.tti = self.tti_binding_manager.create_binding(
|
|
154
|
+
binding_name=tti_binding_name,
|
|
155
|
+
**{
|
|
156
|
+
k: v
|
|
157
|
+
for k, v in (tti_binding_config or {}).items()
|
|
158
|
+
if k != "binding_name"
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
self.tti = self.tti_binding_manager.create_binding(
|
|
163
|
+
binding_name=tti_binding_name
|
|
164
|
+
)
|
|
165
|
+
if self.tti is None:
|
|
166
|
+
ASCIIColors.warning(f"Failed to create TTI binding: {tti_binding_name}. Available: {self.tti_binding_manager.get_available_bindings()}")
|
|
167
|
+
except Exception as e:
|
|
168
|
+
trace_exception(e)
|
|
169
|
+
ASCIIColors.warning(f"Exception occurred while creating TTI binding: {str(e)}")
|
|
170
|
+
self.tti = None
|
|
171
|
+
|
|
174
172
|
if stt_binding_name:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
173
|
+
try:
|
|
174
|
+
if stt_binding_config:
|
|
175
|
+
self.stt = self.stt_binding_manager.create_binding(
|
|
176
|
+
binding_name=stt_binding_name,
|
|
177
|
+
**{
|
|
178
|
+
k: v
|
|
179
|
+
for k, v in (stt_binding_config or {}).items()
|
|
180
|
+
if k != "binding_name"
|
|
181
|
+
}
|
|
182
|
+
)
|
|
184
183
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
184
|
+
else:
|
|
185
|
+
self.stt = self.stt_binding_manager.create_binding(
|
|
186
|
+
binding_name=stt_binding_name,
|
|
187
|
+
)
|
|
188
|
+
if self.stt is None:
|
|
189
|
+
ASCIIColors.warning(f"Failed to create STT binding: {stt_binding_name}. Available: {self.stt_binding_manager.get_available_bindings()}")
|
|
190
|
+
except Exception as e:
|
|
191
|
+
trace_exception(e)
|
|
192
|
+
ASCIIColors.warning(f"Exception occurred while creating STT binding: {str(e)}")
|
|
193
|
+
self.stt = None
|
|
194
|
+
|
|
191
195
|
if ttv_binding_name:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
196
|
+
try:
|
|
197
|
+
if ttv_binding_config:
|
|
198
|
+
self.ttv = self.ttv_binding_manager.create_binding(
|
|
199
|
+
binding_name=ttv_binding_name,
|
|
200
|
+
**{
|
|
201
|
+
k: v
|
|
202
|
+
for k, v in ttv_binding_config.items()
|
|
203
|
+
if k != "binding_name"
|
|
204
|
+
}
|
|
205
|
+
)
|
|
201
206
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
else:
|
|
208
|
+
self.ttv = self.ttv_binding_manager.create_binding(
|
|
209
|
+
binding_name=ttv_binding_name
|
|
210
|
+
)
|
|
211
|
+
if self.ttv is None:
|
|
212
|
+
ASCIIColors.warning(f"Failed to create TTV binding: {ttv_binding_name}. Available: {self.ttv_binding_manager.get_available_bindings()}")
|
|
213
|
+
except Exception as e:
|
|
214
|
+
trace_exception(e)
|
|
215
|
+
ASCIIColors.warning(f"Exception occurred while creating TTV binding: {str(e)}")
|
|
216
|
+
self.ttv = None
|
|
208
217
|
|
|
209
218
|
if ttm_binding_name:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
219
|
+
try:
|
|
220
|
+
if ttm_binding_config:
|
|
221
|
+
self.ttm = self.ttm_binding_manager.create_binding(
|
|
222
|
+
binding_name=ttm_binding_name,
|
|
223
|
+
**{
|
|
224
|
+
k: v
|
|
225
|
+
for k, v in (ttm_binding_config or {}).items()
|
|
226
|
+
if k != "binding_name"
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
else:
|
|
230
|
+
self.ttm = self.ttm_binding_manager.create_binding(
|
|
231
|
+
binding_name=ttm_binding_name
|
|
232
|
+
)
|
|
233
|
+
if self.ttm is None:
|
|
234
|
+
ASCIIColors.warning(f"Failed to create TTM binding: {ttm_binding_name}. Available: {self.ttm_binding_manager.get_available_bindings()}")
|
|
235
|
+
except Exception as e:
|
|
236
|
+
trace_exception(e)
|
|
237
|
+
ASCIIColors.warning(f"Exception occurred while creating TTM binding: {str(e)}")
|
|
238
|
+
self.ttm = None
|
|
225
239
|
|
|
226
240
|
if mcp_binding_name:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
241
|
+
try:
|
|
242
|
+
if mcp_binding_config:
|
|
243
|
+
self.mcp = self.mcp_binding_manager.create_binding(
|
|
244
|
+
binding_name=mcp_binding_name,
|
|
245
|
+
**{
|
|
246
|
+
k: v
|
|
247
|
+
for k, v in (mcp_binding_config or {}).items()
|
|
248
|
+
if k != "binding_name"
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
else:
|
|
252
|
+
self.mcp = self.mcp_binding_manager.create_binding(
|
|
253
|
+
mcp_binding_name
|
|
254
|
+
)
|
|
255
|
+
if self.mcp is None:
|
|
256
|
+
ASCIIColors.warning(f"Failed to create MCP binding: {mcp_binding_name}. Available: {self.mcp_binding_manager.get_available_bindings()}")
|
|
257
|
+
except Exception as e:
|
|
258
|
+
trace_exception(e)
|
|
259
|
+
ASCIIColors.warning(f"Exception occurred while creating MCP binding: {str(e)}")
|
|
260
|
+
self.mcp = None
|
|
243
261
|
# --- Store Default Generation Parameters ---
|
|
244
262
|
|
|
245
263
|
# --- Prompt Formatting Attributes ---
|
|
@@ -268,8 +286,8 @@ class LollmsClient():
|
|
|
268
286
|
raise ValueError(f"Failed to update LLM binding: {binding_name}. Available: {available}")
|
|
269
287
|
|
|
270
288
|
def get_ctx_size(self, model_name:str|None=None):
|
|
271
|
-
if self.llm:
|
|
272
|
-
ctx_size = self.llm.get_ctx_size(model_name)
|
|
289
|
+
if self.llm and self.llm.model_name:
|
|
290
|
+
ctx_size = self.llm.get_ctx_size(model_name or self.llm.model_name)
|
|
273
291
|
return ctx_size if ctx_size else self.llm.default_ctx_size
|
|
274
292
|
else:
|
|
275
293
|
return None
|
|
@@ -470,25 +488,28 @@ class LollmsClient():
|
|
|
470
488
|
return self.llm_binding_manager.get_available_bindings()
|
|
471
489
|
|
|
472
490
|
def generate_text(self,
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
491
|
+
prompt: str,
|
|
492
|
+
images: Optional[List[str]] = None,
|
|
493
|
+
system_prompt: str = "",
|
|
494
|
+
n_predict: Optional[int] = None,
|
|
495
|
+
stream: Optional[bool] = None,
|
|
496
|
+
temperature: Optional[float] = None,
|
|
497
|
+
top_k: Optional[int] = None,
|
|
498
|
+
top_p: Optional[float] = None,
|
|
499
|
+
repeat_penalty: Optional[float] = None,
|
|
500
|
+
repeat_last_n: Optional[int] = None,
|
|
501
|
+
seed: Optional[int] = None,
|
|
502
|
+
n_threads: Optional[int] = None,
|
|
503
|
+
ctx_size: int | None = None,
|
|
504
|
+
streaming_callback: Optional[Callable[[str, MSG_TYPE], None]] = None,
|
|
505
|
+
split:Optional[bool]=False, # put to true if the prompt is a discussion
|
|
506
|
+
user_keyword:Optional[str]="!@>user:",
|
|
507
|
+
ai_keyword:Optional[str]="!@>assistant:",
|
|
508
|
+
think: Optional[bool] = False,
|
|
509
|
+
reasoning_effort: Optional[bool] = "low", # low, medium, high
|
|
510
|
+
reasoning_summary: Optional[bool] = "auto", # auto
|
|
511
|
+
**kwargs
|
|
512
|
+
) -> Union[str, dict]:
|
|
492
513
|
"""
|
|
493
514
|
Generate text using the active LLM binding, using instance defaults if parameters are not provided.
|
|
494
515
|
|
|
@@ -509,12 +530,15 @@ class LollmsClient():
|
|
|
509
530
|
split:Optional[bool]: put to true if the prompt is a discussion
|
|
510
531
|
user_keyword:Optional[str]: when splitting we use this to extract user prompt
|
|
511
532
|
ai_keyword:Optional[str]": when splitting we use this to extract ai prompt
|
|
533
|
+
think: Optional[bool]: Activate thinking or deactivate it
|
|
534
|
+
reasoning_effort: Optional[bool]: If think is active, this specifies what effort to put into the thinking
|
|
535
|
+
reasoning_summary: Optional[bool]: If think is active, this specifies if a summary will be generated
|
|
512
536
|
|
|
513
537
|
Returns:
|
|
514
538
|
Union[str, dict]: Generated text or error dictionary if failed.
|
|
515
539
|
"""
|
|
516
540
|
if self.llm:
|
|
517
|
-
|
|
541
|
+
images = [str(image) for image in images] if images else None
|
|
518
542
|
ctx_size = ctx_size if ctx_size is not None else self.llm.default_ctx_size if self.llm.default_ctx_size else None
|
|
519
543
|
if ctx_size is None:
|
|
520
544
|
ctx_size = self.llm.get_ctx_size()
|
|
@@ -543,7 +567,11 @@ class LollmsClient():
|
|
|
543
567
|
streaming_callback=streaming_callback if streaming_callback is not None else self.llm.default_streaming_callback,
|
|
544
568
|
split= split,
|
|
545
569
|
user_keyword=user_keyword,
|
|
546
|
-
ai_keyword=ai_keyword
|
|
570
|
+
ai_keyword=ai_keyword,
|
|
571
|
+
think=think,
|
|
572
|
+
reasoning_effort = reasoning_effort,
|
|
573
|
+
reasoning_summary=reasoning_summary
|
|
574
|
+
|
|
547
575
|
)
|
|
548
576
|
raise RuntimeError("LLM binding not initialized.")
|
|
549
577
|
|
|
@@ -560,6 +588,9 @@ class LollmsClient():
|
|
|
560
588
|
n_threads: Optional[int] = None,
|
|
561
589
|
ctx_size: int | None = None,
|
|
562
590
|
streaming_callback: Optional[Callable[[str, MSG_TYPE], None]] = None,
|
|
591
|
+
think: Optional[bool] = False,
|
|
592
|
+
reasoning_effort: Optional[bool] = "low", # low, medium, high
|
|
593
|
+
reasoning_summary: Optional[bool] = "auto", # auto
|
|
563
594
|
**kwargs
|
|
564
595
|
) -> Union[str, dict]:
|
|
565
596
|
"""
|
|
@@ -596,25 +627,31 @@ class LollmsClient():
|
|
|
596
627
|
n_threads=n_threads if n_threads is not None else self.llm.default_n_threads,
|
|
597
628
|
ctx_size = ctx_size if ctx_size is not None else self.llm.default_ctx_size,
|
|
598
629
|
streaming_callback=streaming_callback if streaming_callback is not None else self.llm.default_streaming_callback,
|
|
630
|
+
think=think,
|
|
631
|
+
reasoning_effort=reasoning_effort,
|
|
632
|
+
reasoning_summary=reasoning_summary
|
|
599
633
|
)
|
|
600
634
|
raise RuntimeError("LLM binding not initialized.")
|
|
601
635
|
|
|
602
636
|
def chat(self,
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
637
|
+
discussion: LollmsDiscussion,
|
|
638
|
+
branch_tip_id: Optional[str] = None,
|
|
639
|
+
n_predict: Optional[int] = None,
|
|
640
|
+
stream: Optional[bool] = None,
|
|
641
|
+
temperature: Optional[float] = None,
|
|
642
|
+
top_k: Optional[int] = None,
|
|
643
|
+
top_p: Optional[float] = None,
|
|
644
|
+
repeat_penalty: Optional[float] = None,
|
|
645
|
+
repeat_last_n: Optional[int] = None,
|
|
646
|
+
seed: Optional[int] = None,
|
|
647
|
+
n_threads: Optional[int] = None,
|
|
648
|
+
ctx_size: Optional[int] = None,
|
|
649
|
+
streaming_callback: Optional[Callable[[str, MSG_TYPE, Dict], bool]] = None,
|
|
650
|
+
think: Optional[bool] = False,
|
|
651
|
+
reasoning_effort: Optional[bool] = "low", # low, medium, high
|
|
652
|
+
reasoning_summary: Optional[bool] = "auto", # auto
|
|
653
|
+
**kwargs
|
|
654
|
+
) -> Union[str, dict]:
|
|
618
655
|
"""
|
|
619
656
|
High-level method to perform a chat generation using a LollmsDiscussion object.
|
|
620
657
|
|
|
@@ -654,7 +691,10 @@ class LollmsClient():
|
|
|
654
691
|
seed=seed if seed is not None else self.llm.default_seed,
|
|
655
692
|
n_threads=n_threads if n_threads is not None else self.llm.default_n_threads,
|
|
656
693
|
ctx_size = ctx_size if ctx_size is not None else self.llm.default_ctx_size,
|
|
657
|
-
streaming_callback=streaming_callback if streaming_callback is not None else self.llm.default_streaming_callback
|
|
694
|
+
streaming_callback=streaming_callback if streaming_callback is not None else self.llm.default_streaming_callback,
|
|
695
|
+
think = think,
|
|
696
|
+
reasoning_effort = reasoning_effort,
|
|
697
|
+
reasoning_summary = reasoning_summary,
|
|
658
698
|
)
|
|
659
699
|
raise RuntimeError("LLM binding not initialized.")
|
|
660
700
|
|
|
@@ -674,10 +714,10 @@ class LollmsClient():
|
|
|
674
714
|
raise RuntimeError("LLM binding not initialized.")
|
|
675
715
|
|
|
676
716
|
|
|
677
|
-
def
|
|
717
|
+
def list_models(self):
|
|
678
718
|
"""Lists models available to the current LLM binding."""
|
|
679
719
|
if self.llm:
|
|
680
|
-
return self.llm.
|
|
720
|
+
return self.llm.list_models()
|
|
681
721
|
raise RuntimeError("LLM binding not initialized.")
|
|
682
722
|
|
|
683
723
|
# --- Convenience Methods for Lollms LLM Binding Features ---
|
|
@@ -709,7 +749,11 @@ class LollmsClient():
|
|
|
709
749
|
repeat_penalty=None,
|
|
710
750
|
repeat_last_n=None,
|
|
711
751
|
callback=None,
|
|
712
|
-
|
|
752
|
+
think: Optional[bool] = False,
|
|
753
|
+
reasoning_effort: Optional[bool] = "low", # low, medium, high
|
|
754
|
+
reasoning_summary: Optional[bool] = "auto", # auto
|
|
755
|
+
debug=False,
|
|
756
|
+
**kwargs
|
|
713
757
|
):
|
|
714
758
|
"""
|
|
715
759
|
Generates multiple code blocks based on a prompt.
|
|
@@ -751,7 +795,10 @@ Don't forget encapsulate the code inside a html code tag. This is mandatory.
|
|
|
751
795
|
top_p=top_p,
|
|
752
796
|
repeat_penalty=repeat_penalty,
|
|
753
797
|
repeat_last_n=repeat_last_n,
|
|
754
|
-
streaming_callback=callback # Assuming generate_text handles streaming callback
|
|
798
|
+
streaming_callback=callback, # Assuming generate_text handles streaming callback
|
|
799
|
+
think=think,
|
|
800
|
+
reasoning_effort = reasoning_effort,
|
|
801
|
+
reasoning_summary=reasoning_summary
|
|
755
802
|
)
|
|
756
803
|
|
|
757
804
|
if isinstance(response, dict) and not response.get("status", True): # Check for error dict
|
|
@@ -1408,44 +1455,6 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1408
1455
|
)
|
|
1409
1456
|
new_scratchpad_text = self.generate_text(prompt=synthesis_prompt, n_predict=1024, temperature=0.0)
|
|
1410
1457
|
return self.remove_thinking_blocks(new_scratchpad_text).strip()
|
|
1411
|
-
def _synthesize_knowledge(
|
|
1412
|
-
self,
|
|
1413
|
-
previous_scratchpad: str,
|
|
1414
|
-
tool_name: str,
|
|
1415
|
-
tool_params: dict,
|
|
1416
|
-
tool_result: dict
|
|
1417
|
-
) -> str:
|
|
1418
|
-
"""
|
|
1419
|
-
A dedicated LLM call to interpret a tool's output and update the knowledge scratchpad.
|
|
1420
|
-
"""
|
|
1421
|
-
# Sanitize tool_result for LLM to avoid sending large binary/base64 data
|
|
1422
|
-
sanitized_result = tool_result.copy()
|
|
1423
|
-
if 'image_path' in sanitized_result:
|
|
1424
|
-
sanitized_result['summary'] = f"An image was successfully generated and saved to '{sanitized_result['image_path']}'."
|
|
1425
|
-
# Remove keys that might contain large data if they exist
|
|
1426
|
-
sanitized_result.pop('image_base64', None)
|
|
1427
|
-
elif 'file_path' in sanitized_result and 'content' in sanitized_result:
|
|
1428
|
-
sanitized_result['summary'] = f"Content was successfully written to '{sanitized_result['file_path']}'."
|
|
1429
|
-
sanitized_result.pop('content', None)
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
synthesis_prompt = (
|
|
1433
|
-
"You are a data analyst assistant. Your sole job is to interpret the output of a tool and integrate it into the existing research summary (knowledge scratchpad).\n\n"
|
|
1434
|
-
"--- PREVIOUS KNOWLEDGE SCRATCHPAD ---\n"
|
|
1435
|
-
f"{previous_scratchpad}\n\n"
|
|
1436
|
-
"--- ACTION JUST TAKEN ---\n"
|
|
1437
|
-
f"Tool Called: `{tool_name}`\n"
|
|
1438
|
-
f"Parameters: {json.dumps(tool_params)}\n\n"
|
|
1439
|
-
"--- RAW TOOL OUTPUT ---\n"
|
|
1440
|
-
f"```json\n{json.dumps(sanitized_result, indent=2)}\n```\n\n"
|
|
1441
|
-
"--- YOUR TASK ---\n"
|
|
1442
|
-
"Read the 'RAW TOOL OUTPUT' and explain what it means in plain language. Then, integrate this new information with the 'PREVIOUS KNOWLEDGE SCRATCHPAD' to create a new, complete, and self-contained summary.\n"
|
|
1443
|
-
"Your output should be ONLY the text of the new scratchpad, with no extra commentary or formatting.\n\n"
|
|
1444
|
-
"--- NEW KNOWLEDGE SCRATCHPAD ---\n"
|
|
1445
|
-
)
|
|
1446
|
-
new_scratchpad_text = self.generate_text(prompt=synthesis_prompt, n_predict=1024, temperature=0.0)
|
|
1447
|
-
return self.remove_thinking_blocks(new_scratchpad_text).strip()
|
|
1448
|
-
|
|
1449
1458
|
|
|
1450
1459
|
def _get_friendly_action_description(self, tool_name: str, requires_code: bool, requires_image: bool) -> str:
|
|
1451
1460
|
"""Convert technical tool names to user-friendly descriptions for logging."""
|
|
@@ -1458,7 +1467,7 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1458
1467
|
return "🎨 Creating an image based on your request"
|
|
1459
1468
|
|
|
1460
1469
|
# Handle RAG (data store) tools by their pattern
|
|
1461
|
-
elif "
|
|
1470
|
+
elif "rag::" in tool_name:
|
|
1462
1471
|
# Extract the friendly name of the data source
|
|
1463
1472
|
source_name = tool_name.split("::")[-1].replace("_", " ").title()
|
|
1464
1473
|
return f"🔍 Searching {source_name} for relevant information"
|
|
@@ -1474,7 +1483,7 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1474
1483
|
# Clean up the technical tool name for a more readable display
|
|
1475
1484
|
clean_name = tool_name.replace("_", " ").replace("::", " - ").title()
|
|
1476
1485
|
return f"🔧 Using the {clean_name} tool"
|
|
1477
|
-
|
|
1486
|
+
|
|
1478
1487
|
def generate_with_mcp_rag(
|
|
1479
1488
|
self,
|
|
1480
1489
|
prompt: str,
|
|
@@ -1509,7 +1518,8 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1509
1518
|
final_answer_temperature=0.7
|
|
1510
1519
|
if rag_top_k is None:
|
|
1511
1520
|
rag_top_k=5
|
|
1512
|
-
|
|
1521
|
+
|
|
1522
|
+
tools_infos = []
|
|
1513
1523
|
def log_event(desc, event_type=MSG_TYPE.MSG_TYPE_CHUNK, meta=None, event_id=None) -> Optional[str]:
|
|
1514
1524
|
if not streaming_callback: return None
|
|
1515
1525
|
is_start = event_type == MSG_TYPE.MSG_TYPE_STEP_START
|
|
@@ -1536,38 +1546,44 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1536
1546
|
mcp_tools = self.mcp.discover_tools(force_refresh=True)
|
|
1537
1547
|
if isinstance(use_mcps, list):
|
|
1538
1548
|
filtered_tools = [t for t in mcp_tools if t["name"] in use_mcps]
|
|
1549
|
+
tools_infos+=[f" 🛠️{f['name']}" for f in filtered_tools]
|
|
1539
1550
|
all_discovered_tools.extend(filtered_tools)
|
|
1540
1551
|
log_event(f" ✅ Loaded {len(filtered_tools)} specific MCP tools: {', '.join(use_mcps)}", MSG_TYPE.MSG_TYPE_INFO)
|
|
1541
1552
|
elif use_mcps is True:
|
|
1553
|
+
tools_infos+=[f" 🛠️{f['name']}" for f in mcp_tools]
|
|
1542
1554
|
all_discovered_tools.extend(mcp_tools)
|
|
1543
1555
|
log_event(f" ✅ Loaded {len(mcp_tools)} MCP tools", MSG_TYPE.MSG_TYPE_INFO)
|
|
1544
1556
|
|
|
1545
1557
|
if use_data_store:
|
|
1546
1558
|
log_event(f" 📚 Setting up {len(use_data_store)} knowledge bases...", MSG_TYPE.MSG_TYPE_INFO)
|
|
1547
1559
|
for name, info in use_data_store.items():
|
|
1548
|
-
|
|
1560
|
+
ASCIIColors.info(f"use_data_store item:\n{name}\n{info}")
|
|
1561
|
+
tool_name, description, call_fn = f"rag::{name}", f"Queries the '{name}' knowledge base.", None
|
|
1549
1562
|
if callable(info): call_fn = info
|
|
1550
1563
|
elif isinstance(info, dict):
|
|
1551
1564
|
if "callable" in info and callable(info["callable"]): call_fn = info["callable"]
|
|
1552
|
-
description = info.get("description", description)
|
|
1565
|
+
description = info.get("description", "This is a datastore with the following description: \n" + description)
|
|
1553
1566
|
if call_fn:
|
|
1554
1567
|
visible_tools.append({"name": tool_name, "description": description, "input_schema": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}})
|
|
1555
1568
|
rag_registry[tool_name] = call_fn
|
|
1556
1569
|
rag_tool_specs[tool_name] = {"default_top_k": rag_top_k, "default_min_sim": rag_min_similarity_percent}
|
|
1557
|
-
|
|
1558
|
-
|
|
1570
|
+
tools_infos.append(f" 📖 {name}")
|
|
1559
1571
|
visible_tools.extend(all_discovered_tools)
|
|
1560
1572
|
built_in_tools = [
|
|
1561
1573
|
{"name": "local_tools::final_answer", "description": "Provide the final answer directly to the user.", "input_schema": {}},
|
|
1562
1574
|
{"name": "local_tools::request_clarification", "description": "Ask the user for more specific information when the request is ambiguous.", "input_schema": {"type": "object", "properties": {"question": {"type": "string"}}, "required": ["question"]}},
|
|
1563
1575
|
{"name": "local_tools::revise_plan", "description": "Update the execution plan based on new discoveries or changing requirements.", "input_schema": {"type": "object", "properties": {"reason": {"type": "string"}, "new_plan": {"type": "array"}}, "required": ["reason", "new_plan"]}}
|
|
1564
1576
|
]
|
|
1577
|
+
tools_infos+=[f" 🔨 final_answer"," 🔨 request_clarification"," 🔨 revise_plan"]
|
|
1578
|
+
|
|
1579
|
+
|
|
1565
1580
|
if getattr(self, "tti", None):
|
|
1566
1581
|
built_in_tools.append({"name": "local_tools::generate_image", "description": "Generate an image from a text description.", "input_schema": {"type": "object", "properties": {"prompt": {"type": "string"}}, "required": ["prompt"]}})
|
|
1567
1582
|
|
|
1568
1583
|
all_visible_tools = visible_tools + built_in_tools
|
|
1569
1584
|
tool_summary = "\n".join([f"- **{t['name']}**: {t['description']}" for t in all_visible_tools[:20]])
|
|
1570
1585
|
|
|
1586
|
+
log_event("\n".join(tools_infos), MSG_TYPE.MSG_TYPE_INFO)
|
|
1571
1587
|
log_event(f"✅ Ready with {len(all_visible_tools)} total capabilities", MSG_TYPE.MSG_TYPE_STEP_END, event_id=discovery_step_id, meta={"tool_count": len(all_visible_tools), "mcp_tools": len(all_discovered_tools), "rag_tools": len(rag_registry)})
|
|
1572
1588
|
|
|
1573
1589
|
# Enhanced triage with better prompting
|
|
@@ -1577,7 +1593,8 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1577
1593
|
try:
|
|
1578
1594
|
triage_prompt = f"""Analyze this user request to determine the most efficient execution strategy.
|
|
1579
1595
|
|
|
1580
|
-
|
|
1596
|
+
DISCUSSION and Final Prompt:
|
|
1597
|
+
{prompt}
|
|
1581
1598
|
CONTEXT: {context or "No additional context provided"}
|
|
1582
1599
|
IMAGES PROVIDED: {"Yes" if images else "No"}
|
|
1583
1600
|
|
|
@@ -1587,7 +1604,7 @@ AVAILABLE CAPABILITIES:
|
|
|
1587
1604
|
Based on the request complexity and available tools, choose the optimal strategy:
|
|
1588
1605
|
|
|
1589
1606
|
1. **DIRECT_ANSWER**: For simple greetings, basic questions, or requests that don't require any tools
|
|
1590
|
-
- Use when: The request can be fully answered with your existing knowledge
|
|
1607
|
+
- Use when: The request can be fully answered with your existing knowledge with confidence, and no tool seems to add any significant value to the answer
|
|
1591
1608
|
- Example: "Hello", "What is Python?", "Explain quantum physics"
|
|
1592
1609
|
|
|
1593
1610
|
2. **REQUEST_CLARIFICATION**: When the request is too vague or ambiguous
|
|
@@ -1603,20 +1620,18 @@ Based on the request complexity and available tools, choose the optimal strategy
|
|
|
1603
1620
|
- Example: "Research X, then create a report comparing it to Y"
|
|
1604
1621
|
|
|
1605
1622
|
Provide your analysis in JSON format:
|
|
1606
|
-
{{"thought": "Detailed reasoning about the request complexity and requirements", "strategy": "ONE_OF_THE_FOUR_OPTIONS", "confidence":
|
|
1607
|
-
|
|
1608
|
-
log_prompt("Triage Prompt", triage_prompt)
|
|
1623
|
+
{{"thought": "Detailed reasoning about the request complexity and requirements", "strategy": "ONE_OF_THE_FOUR_OPTIONS", "confidence": percentage float value, eg 80, "text_output": "Direct answer or clarification question if applicable", "required_tool_name": "specific tool name if SINGLE_TOOL strategy", "estimated_steps": 3}}"""
|
|
1609
1624
|
|
|
1610
1625
|
triage_schema = {
|
|
1611
1626
|
"thought": "string", "strategy": "string", "confidence": "number",
|
|
1612
1627
|
"text_output": "string", "required_tool_name": "string", "estimated_steps": "number"
|
|
1613
1628
|
}
|
|
1614
|
-
strategy_data = self.generate_structured_content(prompt=triage_prompt, schema=triage_schema, temperature=0.1, **llm_generation_kwargs)
|
|
1629
|
+
strategy_data = self.generate_structured_content(prompt=triage_prompt, schema=triage_schema, temperature=0.1, system_prompt=system_prompt, **llm_generation_kwargs)
|
|
1615
1630
|
strategy = strategy_data.get("strategy") if strategy_data else "COMPLEX_PLAN"
|
|
1616
1631
|
|
|
1617
|
-
log_event(f"Strategy analysis complete", MSG_TYPE.MSG_TYPE_INFO, meta={
|
|
1632
|
+
log_event(f"Strategy analysis complete.\n**confidence**: {strategy_data.get('confidence', 0.5)}\n**reasoning**: {strategy_data.get('thought', 'None')}", MSG_TYPE.MSG_TYPE_INFO, meta={
|
|
1618
1633
|
"strategy": strategy,
|
|
1619
|
-
"confidence": strategy_data.get("confidence",
|
|
1634
|
+
"confidence": strategy_data.get("confidence", 50),
|
|
1620
1635
|
"estimated_steps": strategy_data.get("estimated_steps", 1),
|
|
1621
1636
|
"reasoning": strategy_data.get("thought", "")
|
|
1622
1637
|
})
|
|
@@ -1655,1551 +1670,88 @@ Provide your analysis in JSON format:
|
|
|
1655
1670
|
log_event(f"Selected tool: {tool_name}", MSG_TYPE.MSG_TYPE_INFO)
|
|
1656
1671
|
|
|
1657
1672
|
# Enhanced parameter generation prompt
|
|
1658
|
-
param_prompt = f"""Generate the optimal parameters for the selected tool to fulfill the user's request.
|
|
1659
|
-
|
|
1660
|
-
USER REQUEST: "{prompt}"
|
|
1661
|
-
SELECTED TOOL: {json.dumps(tool_spec, indent=2)}
|
|
1662
|
-
CONTEXT: {context or "None"}
|
|
1663
|
-
|
|
1664
|
-
Analyze the user's request carefully and provide the most appropriate parameters.
|
|
1665
|
-
If the request has implicit requirements, infer them intelligently.
|
|
1666
|
-
|
|
1667
|
-
Output the parameters as JSON: {{"tool_params": {{...}}}}"""
|
|
1668
|
-
|
|
1669
|
-
log_prompt("Parameter Generation Prompt", param_prompt)
|
|
1670
|
-
param_data = self.generate_structured_content(prompt=param_prompt, schema={"tool_params": "object"}, temperature=0.1, **llm_generation_kwargs)
|
|
1671
|
-
tool_params = param_data.get("tool_params", {}) if param_data else {}
|
|
1672
|
-
|
|
1673
|
-
log_event(f"Generated parameters: {json.dumps(tool_params)}", MSG_TYPE.MSG_TYPE_INFO)
|
|
1674
|
-
|
|
1675
|
-
start_time, sources, tool_result = time.time(), [], {}
|
|
1676
|
-
if tool_name in rag_registry:
|
|
1677
|
-
query = tool_params.get("query", prompt)
|
|
1678
|
-
log_event(f"Searching knowledge base with query: '{query}'", MSG_TYPE.MSG_TYPE_INFO)
|
|
1679
|
-
rag_fn = rag_registry[tool_name]
|
|
1680
|
-
raw_results = rag_fn(query=query, rag_top_k=rag_top_k, rag_min_similarity_percent=rag_min_similarity_percent)
|
|
1681
|
-
docs = [d for d in (raw_results.get("results", []) if isinstance(raw_results, dict) else raw_results or [])]
|
|
1682
|
-
tool_result = {"status": "success", "results": docs}
|
|
1683
|
-
sources = [{"title":d["title"], "content":d["content"], "source": tool_name, "metadata": d.get("metadata", {}), "score": d.get("score", 0.0)} for d in docs]
|
|
1684
|
-
log_event(f"Retrieved {len(docs)} relevant documents", MSG_TYPE.MSG_TYPE_INFO)
|
|
1685
|
-
elif hasattr(self, "mcp") and "local_tools" not in tool_name:
|
|
1686
|
-
log_event(f"Executing MCP tool: {tool_name}", MSG_TYPE.MSG_TYPE_TOOL_CALL, meta={"tool_name": tool_name, "params": tool_params})
|
|
1687
|
-
tool_result = self.mcp.execute_tool(tool_name, tool_params, lollms_client_instance=self)
|
|
1688
|
-
log_event(f"Tool execution completed", MSG_TYPE.MSG_TYPE_TOOL_OUTPUT, meta={"result_status": tool_result.get("status", "unknown")})
|
|
1689
|
-
else:
|
|
1690
|
-
tool_result = {"status": "failure", "error": f"Tool '{tool_name}' could not be executed in single-step mode."}
|
|
1691
|
-
|
|
1692
|
-
if tool_result.get("status","success") != "success" or "error" in tool_result:
|
|
1693
|
-
error_detail = tool_result.get("error", "Unknown tool error in single-step mode.")
|
|
1694
|
-
raise RuntimeError(error_detail)
|
|
1695
|
-
|
|
1696
|
-
response_time = time.time() - start_time
|
|
1697
|
-
tool_calls_this_turn = [{"name": tool_name, "params": tool_params, "result": tool_result, "response_time": response_time}]
|
|
1698
|
-
|
|
1699
|
-
# Enhanced synthesis prompt
|
|
1700
|
-
synthesis_prompt = f"""Create a comprehensive and user-friendly response based on the tool execution results.
|
|
1701
|
-
|
|
1702
|
-
USER REQUEST: "{prompt}"
|
|
1703
|
-
TOOL USED: {tool_name}
|
|
1704
|
-
TOOL RESULT: {json.dumps(tool_result, indent=2)}
|
|
1705
|
-
|
|
1706
|
-
Guidelines for your response:
|
|
1707
|
-
1. Be direct and helpful
|
|
1708
|
-
2. Synthesize the information clearly
|
|
1709
|
-
3. Address the user's specific needs
|
|
1710
|
-
4. If the tool provided data, present it in an organized way
|
|
1711
|
-
5. If relevant, mention any limitations or additional context
|
|
1712
|
-
|
|
1713
|
-
RESPONSE:"""
|
|
1714
|
-
|
|
1715
|
-
log_event("Synthesizing final response", MSG_TYPE.MSG_TYPE_INFO)
|
|
1716
|
-
final_answer = self.generate_text(prompt=synthesis_prompt, system_prompt=system_prompt, stream=streaming_callback is not None, streaming_callback=streaming_callback, temperature=final_answer_temperature, **llm_generation_kwargs)
|
|
1717
|
-
final_answer = self.remove_thinking_blocks(final_answer)
|
|
1718
|
-
|
|
1719
|
-
log_event("✅ Single-tool execution completed successfully", MSG_TYPE.MSG_TYPE_STEP_END, event_id=synthesis_id)
|
|
1720
|
-
return {"final_answer": final_answer, "tool_calls": tool_calls_this_turn, "sources": sources, "error": None, "clarification_required": False, "final_scratchpad": f"Strategy: SINGLE_TOOL\nTool: {tool_name}\nResult: Success\nResponse Time: {response_time:.2f}s"}
|
|
1721
|
-
|
|
1722
|
-
except Exception as e:
|
|
1723
|
-
log_event(f"Single-tool execution failed: {e}", MSG_TYPE.MSG_TYPE_EXCEPTION, event_id=synthesis_id)
|
|
1724
|
-
log_event("Escalating to complex planning approach", MSG_TYPE.MSG_TYPE_INFO)
|
|
1725
|
-
|
|
1726
|
-
# Execute complex reasoning with enhanced capabilities
|
|
1727
|
-
return self._execute_complex_reasoning_loop(
|
|
1728
|
-
prompt=prompt, context=context, system_prompt=system_prompt,
|
|
1729
|
-
reasoning_system_prompt=reasoning_system_prompt, images=images,
|
|
1730
|
-
max_reasoning_steps=max_reasoning_steps, decision_temperature=decision_temperature,
|
|
1731
|
-
final_answer_temperature=final_answer_temperature, streaming_callback=streaming_callback,
|
|
1732
|
-
debug=debug, enable_self_reflection=enable_self_reflection,
|
|
1733
|
-
all_visible_tools=all_visible_tools, rag_registry=rag_registry, rag_tool_specs=rag_tool_specs,
|
|
1734
|
-
log_event_fn=log_event, log_prompt_fn=log_prompt, max_scratchpad_size=max_scratchpad_size,
|
|
1735
|
-
**llm_generation_kwargs
|
|
1736
|
-
)
|
|
1737
|
-
|
|
1738
|
-
def _execute_complex_reasoning_loop(
|
|
1739
|
-
self, prompt, context, system_prompt, reasoning_system_prompt, images,
|
|
1740
|
-
max_reasoning_steps, decision_temperature, final_answer_temperature,
|
|
1741
|
-
streaming_callback, debug, enable_self_reflection, all_visible_tools,
|
|
1742
|
-
rag_registry, rag_tool_specs, log_event_fn, log_prompt_fn, max_scratchpad_size, **llm_generation_kwargs
|
|
1743
|
-
) -> Dict[str, Any]:
|
|
1744
|
-
|
|
1745
|
-
planner, memory_manager, performance_tracker = TaskPlanner(self), MemoryManager(), ToolPerformanceTracker()
|
|
1746
|
-
|
|
1747
|
-
def _get_friendly_action_description(tool_name, requires_code, requires_image):
|
|
1748
|
-
descriptions = {
|
|
1749
|
-
"local_tools::final_answer": "📋 Preparing final answer",
|
|
1750
|
-
"local_tools::request_clarification": "❓ Requesting clarification",
|
|
1751
|
-
"local_tools::generate_image": "🎨 Creating image",
|
|
1752
|
-
"local_tools::revise_plan": "📝 Revising execution plan"
|
|
1753
|
-
}
|
|
1754
|
-
if tool_name in descriptions:
|
|
1755
|
-
return descriptions[tool_name]
|
|
1756
|
-
if "research::" in tool_name:
|
|
1757
|
-
return f"🔍 Searching {tool_name.split('::')[-1]} knowledge base"
|
|
1758
|
-
if requires_code:
|
|
1759
|
-
return "💻 Processing code"
|
|
1760
|
-
if requires_image:
|
|
1761
|
-
return "🖼️ Analyzing images"
|
|
1762
|
-
return f"🔧 Using {tool_name.replace('_', ' ').replace('::', ' - ').title()}"
|
|
1763
|
-
|
|
1764
|
-
def _compress_scratchpad_intelligently(scratchpad: str, original_request: str, target_size: int) -> str:
|
|
1765
|
-
"""Enhanced scratchpad compression that preserves key decisions and recent context"""
|
|
1766
|
-
if len(scratchpad) <= target_size:
|
|
1767
|
-
return scratchpad
|
|
1768
|
-
|
|
1769
|
-
log_event_fn("📝 Compressing scratchpad to maintain focus...", MSG_TYPE.MSG_TYPE_INFO)
|
|
1770
|
-
|
|
1771
|
-
# Extract key components
|
|
1772
|
-
lines = scratchpad.split('\n')
|
|
1773
|
-
plan_section = []
|
|
1774
|
-
decisions = []
|
|
1775
|
-
recent_observations = []
|
|
1776
|
-
|
|
1777
|
-
current_section = None
|
|
1778
|
-
for i, line in enumerate(lines):
|
|
1779
|
-
if "### Execution Plan" in line or "### Updated Plan" in line:
|
|
1780
|
-
current_section = "plan"
|
|
1781
|
-
elif "### Step" in line and ("Thought" in line or "Decision" in line):
|
|
1782
|
-
current_section = "decision"
|
|
1783
|
-
elif "### Step" in line and "Observation" in line:
|
|
1784
|
-
current_section = "observation"
|
|
1785
|
-
elif line.startswith("###"):
|
|
1786
|
-
current_section = None
|
|
1787
|
-
|
|
1788
|
-
if current_section == "plan" and line.strip():
|
|
1789
|
-
plan_section.append(line)
|
|
1790
|
-
elif current_section == "decision" and line.strip():
|
|
1791
|
-
decisions.append((i, line))
|
|
1792
|
-
elif current_section == "observation" and line.strip():
|
|
1793
|
-
recent_observations.append((i, line))
|
|
1794
|
-
|
|
1795
|
-
# Keep most recent items and important decisions
|
|
1796
|
-
recent_decisions = decisions[-3:] if len(decisions) > 3 else decisions
|
|
1797
|
-
recent_obs = recent_observations[-5:] if len(recent_observations) > 5 else recent_observations
|
|
1798
|
-
|
|
1799
|
-
compressed_parts = [
|
|
1800
|
-
f"### Original Request\n{original_request}",
|
|
1801
|
-
f"### Current Plan\n" + '\n'.join(plan_section[-10:]),
|
|
1802
|
-
f"### Recent Key Decisions"
|
|
1803
|
-
]
|
|
1804
|
-
|
|
1805
|
-
for _, decision in recent_decisions:
|
|
1806
|
-
compressed_parts.append(decision)
|
|
1807
|
-
|
|
1808
|
-
compressed_parts.append("### Recent Observations")
|
|
1809
|
-
for _, obs in recent_obs:
|
|
1810
|
-
compressed_parts.append(obs)
|
|
1811
|
-
|
|
1812
|
-
compressed = '\n'.join(compressed_parts)
|
|
1813
|
-
if len(compressed) > target_size:
|
|
1814
|
-
# Final trim if still too long
|
|
1815
|
-
compressed = compressed[:target_size-100] + "\n...[content compressed for focus]"
|
|
1816
|
-
|
|
1817
|
-
return compressed
|
|
1818
|
-
|
|
1819
|
-
original_user_prompt, tool_calls_this_turn, sources_this_turn = prompt, [], []
|
|
1820
|
-
asset_store: Dict[str, Dict] = {}
|
|
1821
|
-
decision_history = [] # Track all decisions made
|
|
1822
|
-
|
|
1823
|
-
# Enhanced planning phase
|
|
1824
|
-
planning_step_id = log_event_fn("📋 Creating adaptive execution plan...", MSG_TYPE.MSG_TYPE_STEP_START)
|
|
1825
|
-
execution_plan = planner.decompose_task(original_user_prompt, context or "")
|
|
1826
|
-
current_plan_version = 1
|
|
1827
|
-
|
|
1828
|
-
log_event_fn(f"Initial plan created with {len(execution_plan.tasks)} tasks", MSG_TYPE.MSG_TYPE_INFO, meta={
|
|
1829
|
-
"plan_version": current_plan_version,
|
|
1830
|
-
"total_tasks": len(execution_plan.tasks),
|
|
1831
|
-
"estimated_complexity": "medium" if len(execution_plan.tasks) <= 5 else "high"
|
|
1832
|
-
})
|
|
1833
|
-
|
|
1834
|
-
for i, task in enumerate(execution_plan.tasks):
|
|
1835
|
-
log_event_fn(f"Task {i+1}: {task.description}", MSG_TYPE.MSG_TYPE_INFO)
|
|
1836
|
-
|
|
1837
|
-
log_event_fn("✅ Adaptive plan ready", MSG_TYPE.MSG_TYPE_STEP_END, event_id=planning_step_id)
|
|
1838
|
-
|
|
1839
|
-
# Enhanced initial state
|
|
1840
|
-
initial_state_parts = [
|
|
1841
|
-
f"### Original User Request\n{original_user_prompt}",
|
|
1842
|
-
f"### Context\n{context or 'No additional context provided'}",
|
|
1843
|
-
f"### Execution Plan (Version {current_plan_version})\n- Total tasks: {len(execution_plan.tasks)}",
|
|
1844
|
-
f"- Estimated complexity: {'High' if len(execution_plan.tasks) > 5 else 'Medium'}"
|
|
1845
|
-
]
|
|
1846
|
-
|
|
1847
|
-
for i, task in enumerate(execution_plan.tasks):
|
|
1848
|
-
initial_state_parts.append(f" {i+1}. {task.description} [Status: {task.status.value}]")
|
|
1849
|
-
|
|
1850
|
-
if images:
|
|
1851
|
-
initial_state_parts.append(f"### Provided Assets")
|
|
1852
|
-
for img_b64 in images:
|
|
1853
|
-
img_uuid = str(uuid.uuid4())
|
|
1854
|
-
asset_store[img_uuid] = {"type": "image", "content": img_b64, "source": "user"}
|
|
1855
|
-
initial_state_parts.append(f"- Image asset: {img_uuid}")
|
|
1856
|
-
|
|
1857
|
-
current_scratchpad = "\n".join(initial_state_parts)
|
|
1858
|
-
log_event_fn("Initial analysis complete", MSG_TYPE.MSG_TYPE_SCRATCHPAD, meta={"scratchpad_size": len(current_scratchpad)})
|
|
1859
|
-
|
|
1860
|
-
formatted_tools_list = "\n".join([f"**{t['name']}**: {t['description']}" for t in all_visible_tools])
|
|
1861
|
-
completed_tasks, current_task_index = set(), 0
|
|
1862
|
-
plan_revision_count = 0
|
|
1863
|
-
|
|
1864
|
-
# Main reasoning loop with enhanced decision tracking
|
|
1865
|
-
for i in range(max_reasoning_steps):
|
|
1866
|
-
current_task_desc = execution_plan.tasks[current_task_index].description if current_task_index < len(execution_plan.tasks) else "Finalizing analysis"
|
|
1867
|
-
step_desc = f"🤔 Step {i+1}: {current_task_desc}"
|
|
1868
|
-
reasoning_step_id = log_event_fn(step_desc, MSG_TYPE.MSG_TYPE_STEP_START)
|
|
1869
|
-
|
|
1870
|
-
try:
|
|
1871
|
-
# Enhanced scratchpad management
|
|
1872
|
-
if len(current_scratchpad) > max_scratchpad_size:
|
|
1873
|
-
log_event_fn(f"Scratchpad size ({len(current_scratchpad)}) exceeds limit, compressing...", MSG_TYPE.MSG_TYPE_INFO)
|
|
1874
|
-
current_scratchpad = _compress_scratchpad_intelligently(current_scratchpad, original_user_prompt, max_scratchpad_size // 2)
|
|
1875
|
-
log_event_fn(f"Scratchpad compressed to {len(current_scratchpad)} characters", MSG_TYPE.MSG_TYPE_INFO)
|
|
1876
|
-
|
|
1877
|
-
# Enhanced reasoning prompt with better decision tracking
|
|
1878
|
-
reasoning_prompt = f"""You are working on: "{original_user_prompt}"
|
|
1879
|
-
|
|
1880
|
-
=== AVAILABLE ACTIONS ===
|
|
1881
|
-
{formatted_tools_list}
|
|
1882
|
-
|
|
1883
|
-
=== YOUR COMPLETE ANALYSIS HISTORY ===
|
|
1884
|
-
{current_scratchpad}
|
|
1885
|
-
=== END ANALYSIS HISTORY ===
|
|
1886
|
-
|
|
1887
|
-
=== DECISION GUIDELINES ===
|
|
1888
|
-
1. **Review your progress**: Look at what you've already discovered and accomplished
|
|
1889
|
-
2. **Consider your current task**: Focus on the next logical step in your plan
|
|
1890
|
-
3. **Remember your decisions**: If you previously decided to use a tool, follow through unless you have a good reason to change
|
|
1891
|
-
4. **Be adaptive**: If you discover new information that changes the situation, consider revising your plan
|
|
1892
|
-
5. **Stay focused**: Each action should clearly advance toward the final goal
|
|
1893
|
-
|
|
1894
|
-
=== YOUR NEXT DECISION ===
|
|
1895
|
-
Choose the single most appropriate action to take right now. Consider:
|
|
1896
|
-
- What specific step are you currently working on?
|
|
1897
|
-
- What information do you still need?
|
|
1898
|
-
- What would be most helpful for the user?
|
|
1899
|
-
|
|
1900
|
-
Provide your decision as JSON:
|
|
1901
|
-
{{
|
|
1902
|
-
"reasoning": "Explain your current thinking and why this action makes sense now",
|
|
1903
|
-
"action": {{
|
|
1904
|
-
"tool_name": "exact_tool_name",
|
|
1905
|
-
"requires_code_input": false,
|
|
1906
|
-
"requires_image_input": false,
|
|
1907
|
-
"confidence": 0.8
|
|
1908
|
-
}},
|
|
1909
|
-
"plan_status": "on_track" // or "needs_revision" if you want to change the plan
|
|
1910
|
-
}}"""
|
|
1911
|
-
|
|
1912
|
-
log_prompt_fn(f"Reasoning Prompt Step {i+1}", reasoning_prompt)
|
|
1913
|
-
decision_data = self.generate_structured_content(
|
|
1914
|
-
prompt=reasoning_prompt,
|
|
1915
|
-
schema={
|
|
1916
|
-
"reasoning": "string",
|
|
1917
|
-
"action": "object",
|
|
1918
|
-
"plan_status": "string"
|
|
1919
|
-
},
|
|
1920
|
-
system_prompt=reasoning_system_prompt,
|
|
1921
|
-
temperature=decision_temperature,
|
|
1922
|
-
**llm_generation_kwargs
|
|
1923
|
-
)
|
|
1924
|
-
|
|
1925
|
-
if not (decision_data and isinstance(decision_data.get("action"), dict)):
|
|
1926
|
-
log_event_fn("⚠️ Invalid decision format from AI", MSG_TYPE.MSG_TYPE_WARNING, event_id=reasoning_step_id)
|
|
1927
|
-
current_scratchpad += f"\n\n### Step {i+1}: Decision Error\n- Error: AI produced invalid decision JSON\n- Continuing with fallback approach"
|
|
1928
|
-
continue
|
|
1929
|
-
|
|
1930
|
-
action = decision_data.get("action", {})
|
|
1931
|
-
reasoning = decision_data.get("reasoning", "No reasoning provided")
|
|
1932
|
-
plan_status = decision_data.get("plan_status", "on_track")
|
|
1933
|
-
tool_name = action.get("tool_name")
|
|
1934
|
-
requires_code = action.get("requires_code_input", False)
|
|
1935
|
-
requires_image = action.get("requires_image_input", False)
|
|
1936
|
-
confidence = action.get("confidence", 0.5)
|
|
1937
|
-
|
|
1938
|
-
# Track the decision
|
|
1939
|
-
decision_history.append({
|
|
1940
|
-
"step": i+1,
|
|
1941
|
-
"tool_name": tool_name,
|
|
1942
|
-
"reasoning": reasoning,
|
|
1943
|
-
"confidence": confidence,
|
|
1944
|
-
"plan_status": plan_status
|
|
1945
|
-
})
|
|
1946
|
-
|
|
1947
|
-
current_scratchpad += f"\n\n### Step {i+1}: Decision & Reasoning\n**Reasoning**: {reasoning}\n**Chosen Action**: {tool_name}\n**Confidence**: {confidence}\n**Plan Status**: {plan_status}"
|
|
1948
|
-
|
|
1949
|
-
log_event_fn(_get_friendly_action_description(tool_name, requires_code, requires_image), MSG_TYPE.MSG_TYPE_STEP, meta={
|
|
1950
|
-
"tool_name": tool_name,
|
|
1951
|
-
"confidence": confidence,
|
|
1952
|
-
"reasoning": reasoning[:100] + "..." if len(reasoning) > 100 else reasoning
|
|
1953
|
-
})
|
|
1954
|
-
|
|
1955
|
-
# Handle plan revision
|
|
1956
|
-
if plan_status == "needs_revision" and tool_name != "local_tools::revise_plan":
|
|
1957
|
-
log_event_fn("🔄 AI indicates plan needs revision", MSG_TYPE.MSG_TYPE_INFO)
|
|
1958
|
-
tool_name = "local_tools::revise_plan" # Force plan revision
|
|
1959
|
-
|
|
1960
|
-
# Handle final answer
|
|
1961
|
-
if tool_name == "local_tools::final_answer":
|
|
1962
|
-
log_event_fn("🎯 Ready to provide final answer", MSG_TYPE.MSG_TYPE_INFO)
|
|
1963
|
-
break
|
|
1964
|
-
|
|
1965
|
-
# Handle clarification request
|
|
1966
|
-
if tool_name == "local_tools::request_clarification":
|
|
1967
|
-
clarification_prompt = f"""Based on your analysis, what specific information do you need from the user?
|
|
1968
|
-
|
|
1969
|
-
CURRENT ANALYSIS:
|
|
1970
|
-
{current_scratchpad}
|
|
1971
|
-
|
|
1972
|
-
Generate a clear, specific question that will help you proceed effectively:"""
|
|
1973
|
-
|
|
1974
|
-
question = self.generate_text(clarification_prompt, temperature=0.3)
|
|
1975
|
-
question = self.remove_thinking_blocks(question)
|
|
1976
|
-
|
|
1977
|
-
log_event_fn("❓ Clarification needed from user", MSG_TYPE.MSG_TYPE_INFO)
|
|
1978
|
-
return {
|
|
1979
|
-
"final_answer": question,
|
|
1980
|
-
"clarification_required": True,
|
|
1981
|
-
"final_scratchpad": current_scratchpad,
|
|
1982
|
-
"tool_calls": tool_calls_this_turn,
|
|
1983
|
-
"sources": sources_this_turn,
|
|
1984
|
-
"error": None,
|
|
1985
|
-
"decision_history": decision_history
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
# Handle final answer
|
|
1989
|
-
if tool_name == "local_tools::final_answer":
|
|
1990
|
-
log_event_fn("🎯 Ready to provide final answer", MSG_TYPE.MSG_TYPE_INFO)
|
|
1991
|
-
break
|
|
1992
|
-
|
|
1993
|
-
# Handle clarification request
|
|
1994
|
-
if tool_name == "local_tools::request_clarification":
|
|
1995
|
-
clarification_prompt = f"""Based on your analysis, what specific information do you need from the user?
|
|
1996
|
-
|
|
1997
|
-
CURRENT ANALYSIS:
|
|
1998
|
-
{current_scratchpad}
|
|
1999
|
-
|
|
2000
|
-
Generate a clear, specific question that will help you proceed effectively:"""
|
|
2001
|
-
|
|
2002
|
-
question = self.generate_text(clarification_prompt, temperature=0.3)
|
|
2003
|
-
question = self.remove_thinking_blocks(question)
|
|
2004
|
-
|
|
2005
|
-
log_event_fn("❓ Clarification needed from user", MSG_TYPE.MSG_TYPE_INFO)
|
|
2006
|
-
return {
|
|
2007
|
-
"final_answer": question,
|
|
2008
|
-
"clarification_required": True,
|
|
2009
|
-
"final_scratchpad": current_scratchpad,
|
|
2010
|
-
"tool_calls": tool_calls_this_turn,
|
|
2011
|
-
"sources": sources_this_turn,
|
|
2012
|
-
"error": None,
|
|
2013
|
-
"decision_history": decision_history
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
# Handle plan revision
|
|
2017
|
-
if tool_name == "local_tools::revise_plan":
|
|
2018
|
-
plan_revision_count += 1
|
|
2019
|
-
revision_id = log_event_fn(f"📝 Revising execution plan (revision #{plan_revision_count})", MSG_TYPE.MSG_TYPE_STEP_START)
|
|
2020
|
-
|
|
2021
|
-
try:
|
|
2022
|
-
revision_prompt = f"""Based on your current analysis and discoveries, create an updated execution plan.
|
|
2023
|
-
|
|
2024
|
-
ORIGINAL REQUEST: "{original_user_prompt}"
|
|
2025
|
-
CURRENT ANALYSIS:
|
|
2026
|
-
{current_scratchpad}
|
|
2027
|
-
|
|
2028
|
-
REASON FOR REVISION: {reasoning}
|
|
2029
|
-
|
|
2030
|
-
Create a new plan that reflects your current understanding. Consider:
|
|
2031
|
-
1. What have you already accomplished?
|
|
2032
|
-
2. What new information have you discovered?
|
|
2033
|
-
3. What steps are still needed?
|
|
2034
|
-
4. How can you be more efficient?
|
|
2035
|
-
|
|
2036
|
-
Provide your revision as JSON:
|
|
2037
|
-
{{
|
|
2038
|
-
"revision_reason": "Clear explanation of why the plan needed to change",
|
|
2039
|
-
"new_plan": [
|
|
2040
|
-
{{"step": 1, "description": "First revised step", "status": "pending"}},
|
|
2041
|
-
{{"step": 2, "description": "Second revised step", "status": "pending"}}
|
|
2042
|
-
],
|
|
2043
|
-
"confidence": 0.8
|
|
2044
|
-
}}"""
|
|
2045
|
-
|
|
2046
|
-
revision_data = self.generate_structured_content(
|
|
2047
|
-
prompt=revision_prompt,
|
|
2048
|
-
schema={
|
|
2049
|
-
"revision_reason": "string",
|
|
2050
|
-
"new_plan": "array",
|
|
2051
|
-
"confidence": "number"
|
|
2052
|
-
},
|
|
2053
|
-
temperature=0.3,
|
|
2054
|
-
**llm_generation_kwargs
|
|
2055
|
-
)
|
|
2056
|
-
|
|
2057
|
-
if revision_data and revision_data.get("new_plan"):
|
|
2058
|
-
# Update the plan
|
|
2059
|
-
current_plan_version += 1
|
|
2060
|
-
new_tasks = []
|
|
2061
|
-
for task_data in revision_data["new_plan"]:
|
|
2062
|
-
task = TaskDecomposition() # Assuming this class exists
|
|
2063
|
-
task.description = task_data.get("description", "Undefined step")
|
|
2064
|
-
task.status = TaskStatus.PENDING # Reset all to pending
|
|
2065
|
-
new_tasks.append(task)
|
|
2066
|
-
|
|
2067
|
-
execution_plan.tasks = new_tasks
|
|
2068
|
-
current_task_index = 0 # Reset to beginning
|
|
2069
|
-
|
|
2070
|
-
# Update scratchpad with new plan
|
|
2071
|
-
current_scratchpad += f"\n\n### Updated Plan (Version {current_plan_version})\n"
|
|
2072
|
-
current_scratchpad += f"**Revision Reason**: {revision_data.get('revision_reason', 'Plan needed updating')}\n"
|
|
2073
|
-
current_scratchpad += f"**New Tasks**:\n"
|
|
2074
|
-
for i, task in enumerate(execution_plan.tasks):
|
|
2075
|
-
current_scratchpad += f" {i+1}. {task.description}\n"
|
|
2076
|
-
|
|
2077
|
-
log_event_fn(f"✅ Plan revised with {len(execution_plan.tasks)} updated tasks", MSG_TYPE.MSG_TYPE_STEP_END, event_id=revision_id, meta={
|
|
2078
|
-
"plan_version": current_plan_version,
|
|
2079
|
-
"new_task_count": len(execution_plan.tasks),
|
|
2080
|
-
"revision_reason": revision_data.get("revision_reason", "")
|
|
2081
|
-
})
|
|
2082
|
-
|
|
2083
|
-
# Continue with the new plan
|
|
2084
|
-
continue
|
|
2085
|
-
else:
|
|
2086
|
-
raise ValueError("Failed to generate valid plan revision")
|
|
2087
|
-
|
|
2088
|
-
except Exception as e:
|
|
2089
|
-
log_event_fn(f"Plan revision failed: {e}", MSG_TYPE.MSG_TYPE_WARNING, event_id=revision_id)
|
|
2090
|
-
current_scratchpad += f"\n**Plan Revision Failed**: {str(e)}\nContinuing with original plan."
|
|
2091
|
-
|
|
2092
|
-
# Prepare parameters for tool execution
|
|
2093
|
-
param_assets = {}
|
|
2094
|
-
if requires_code:
|
|
2095
|
-
log_event_fn("💻 Generating code for task", MSG_TYPE.MSG_TYPE_INFO)
|
|
2096
|
-
code_prompt = f"""Generate the specific code needed for the current step.
|
|
2097
|
-
|
|
2098
|
-
CURRENT CONTEXT:
|
|
2099
|
-
{current_scratchpad}
|
|
2100
|
-
|
|
2101
|
-
CURRENT TASK: {tool_name}
|
|
2102
|
-
USER REQUEST: "{original_user_prompt}"
|
|
2103
|
-
|
|
2104
|
-
Generate clean, functional code that addresses the specific requirements. Focus on:
|
|
2105
|
-
1. Solving the immediate problem
|
|
2106
|
-
2. Being clear and readable
|
|
2107
|
-
3. Including necessary imports and dependencies
|
|
2108
|
-
4. Adding helpful comments where appropriate
|
|
2109
|
-
|
|
2110
|
-
CODE:"""
|
|
2111
|
-
|
|
2112
|
-
code_content = self.generate_code(prompt=code_prompt, **llm_generation_kwargs)
|
|
2113
|
-
code_uuid = f"code_asset_{uuid.uuid4()}"
|
|
2114
|
-
asset_store[code_uuid] = {"type": "code", "content": code_content}
|
|
2115
|
-
param_assets['code_asset_id'] = code_uuid
|
|
2116
|
-
log_event_fn(f"Code asset created: {code_uuid[:8]}...", MSG_TYPE.MSG_TYPE_INFO)
|
|
2117
|
-
|
|
2118
|
-
if requires_image:
|
|
2119
|
-
image_assets = [asset_id for asset_id, asset in asset_store.items() if asset['type'] == 'image' and asset.get('source') == 'user']
|
|
2120
|
-
if image_assets:
|
|
2121
|
-
param_assets['image_asset_id'] = image_assets[0]
|
|
2122
|
-
log_event_fn(f"Using image asset: {image_assets[0][:8]}...", MSG_TYPE.MSG_TYPE_INFO)
|
|
2123
|
-
else:
|
|
2124
|
-
log_event_fn("⚠️ Image required but none available", MSG_TYPE.MSG_TYPE_WARNING)
|
|
2125
|
-
|
|
2126
|
-
# Enhanced parameter generation
|
|
2127
|
-
param_prompt = f"""Generate the optimal parameters for this tool execution.
|
|
2128
|
-
|
|
2129
|
-
TOOL: {tool_name}
|
|
2130
|
-
CURRENT CONTEXT: {current_scratchpad}
|
|
2131
|
-
CURRENT REASONING: {reasoning}
|
|
2132
|
-
AVAILABLE ASSETS: {json.dumps(param_assets) if param_assets else "None"}
|
|
2133
|
-
|
|
2134
|
-
Based on your analysis and the current step you're working on, provide the most appropriate parameters.
|
|
2135
|
-
Be specific and purposeful in your parameter choices.
|
|
2136
|
-
|
|
2137
|
-
Output format: {{"tool_params": {{...}}}}"""
|
|
2138
|
-
|
|
2139
|
-
log_prompt_fn(f"Parameter Generation Step {i+1}", param_prompt)
|
|
2140
|
-
param_data = self.generate_structured_content(
|
|
2141
|
-
prompt=param_prompt,
|
|
2142
|
-
schema={"tool_params": "object"},
|
|
2143
|
-
temperature=decision_temperature,
|
|
2144
|
-
**llm_generation_kwargs
|
|
2145
|
-
)
|
|
2146
|
-
tool_params = param_data.get("tool_params", {}) if param_data else {}
|
|
2147
|
-
|
|
2148
|
-
current_scratchpad += f"\n**Parameters Generated**: {json.dumps(tool_params, indent=2)}"
|
|
2149
|
-
|
|
2150
|
-
# Hydrate parameters with assets
|
|
2151
|
-
def _hydrate(data: Any, store: Dict) -> Any:
|
|
2152
|
-
if isinstance(data, dict): return {k: _hydrate(v, store) for k, v in data.items()}
|
|
2153
|
-
if isinstance(data, list): return [_hydrate(item, store) for item in data]
|
|
2154
|
-
if isinstance(data, str) and "asset_" in data and data in store: return store[data].get("content", data)
|
|
2155
|
-
return data
|
|
2156
|
-
|
|
2157
|
-
hydrated_params = _hydrate(tool_params, asset_store)
|
|
2158
|
-
|
|
2159
|
-
# Execute the tool with detailed logging
|
|
2160
|
-
start_time = time.time()
|
|
2161
|
-
tool_result = {"status": "failure", "error": f"Tool '{tool_name}' failed to execute."}
|
|
2162
|
-
|
|
2163
|
-
try:
|
|
2164
|
-
if tool_name in rag_registry:
|
|
2165
|
-
query = hydrated_params.get("query", "")
|
|
2166
|
-
if not query:
|
|
2167
|
-
# Fall back to using reasoning as query
|
|
2168
|
-
query = reasoning[:200] + "..." if len(reasoning) > 200 else reasoning
|
|
2169
|
-
|
|
2170
|
-
log_event_fn(f"🔍 Searching knowledge base with query: '{query[:50]}...'", MSG_TYPE.MSG_TYPE_INFO)
|
|
2171
|
-
|
|
2172
|
-
top_k = rag_tool_specs[tool_name]["default_top_k"]
|
|
2173
|
-
min_sim = rag_tool_specs[tool_name]["default_min_sim"]
|
|
2174
|
-
|
|
2175
|
-
raw_results = rag_registry[tool_name](query=query, rag_top_k=top_k)
|
|
2176
|
-
raw_iter = raw_results["results"] if isinstance(raw_results, dict) and "results" in raw_results else raw_results
|
|
2177
|
-
|
|
2178
|
-
docs = []
|
|
2179
|
-
for d in raw_iter or []:
|
|
2180
|
-
doc_data = {
|
|
2181
|
-
"text": d.get("text", str(d)),
|
|
2182
|
-
"score": d.get("score", 0) * 100,
|
|
2183
|
-
"metadata": d.get("metadata", {})
|
|
2184
|
-
}
|
|
2185
|
-
docs.append(doc_data)
|
|
2186
|
-
|
|
2187
|
-
kept = [x for x in docs if x['score'] >= min_sim]
|
|
2188
|
-
tool_result = {
|
|
2189
|
-
"status": "success",
|
|
2190
|
-
"results": kept,
|
|
2191
|
-
"total_found": len(docs),
|
|
2192
|
-
"kept_after_filtering": len(kept),
|
|
2193
|
-
"query_used": query
|
|
2194
|
-
}
|
|
2195
|
-
|
|
2196
|
-
sources_this_turn.extend([{
|
|
2197
|
-
"source": tool_name,
|
|
2198
|
-
"metadata": x["metadata"],
|
|
2199
|
-
"score": x["score"]
|
|
2200
|
-
} for x in kept])
|
|
2201
|
-
|
|
2202
|
-
log_event_fn(f"📚 Retrieved {len(kept)} relevant documents (from {len(docs)} total)", MSG_TYPE.MSG_TYPE_INFO)
|
|
2203
|
-
|
|
2204
|
-
elif hasattr(self, "mcp") and "local_tools" not in tool_name:
|
|
2205
|
-
log_event_fn(f"🔧 Executing MCP tool: {tool_name}", MSG_TYPE.MSG_TYPE_TOOL_CALL, meta={
|
|
2206
|
-
"tool_name": tool_name,
|
|
2207
|
-
"params": {k: str(v)[:100] for k, v in hydrated_params.items()} # Truncate for logging
|
|
2208
|
-
})
|
|
2209
|
-
|
|
2210
|
-
tool_result = self.mcp.execute_tool(tool_name, hydrated_params, lollms_client_instance=self)
|
|
2211
|
-
|
|
2212
|
-
log_event_fn(f"Tool execution completed", MSG_TYPE.MSG_TYPE_TOOL_OUTPUT, meta={
|
|
2213
|
-
"result_status": tool_result.get("status", "unknown"),
|
|
2214
|
-
"has_error": "error" in tool_result
|
|
2215
|
-
})
|
|
2216
|
-
|
|
2217
|
-
elif tool_name == "local_tools::generate_image" and hasattr(self, "tti"):
|
|
2218
|
-
image_prompt = hydrated_params.get("prompt", "")
|
|
2219
|
-
log_event_fn(f"🎨 Generating image with prompt: '{image_prompt[:50]}...'", MSG_TYPE.MSG_TYPE_INFO)
|
|
2220
|
-
|
|
2221
|
-
# This would call your text-to-image functionality
|
|
2222
|
-
image_result = self.tti.generate_image(image_prompt) # Assuming this method exists
|
|
2223
|
-
if image_result:
|
|
2224
|
-
image_uuid = f"generated_image_{uuid.uuid4()}"
|
|
2225
|
-
asset_store[image_uuid] = {"type": "image", "content": image_result, "source": "generated"}
|
|
2226
|
-
tool_result = {"status": "success", "image_id": image_uuid, "prompt_used": image_prompt}
|
|
2227
|
-
else:
|
|
2228
|
-
tool_result = {"status": "failure", "error": "Image generation failed"}
|
|
2229
|
-
|
|
2230
|
-
else:
|
|
2231
|
-
tool_result = {"status": "failure", "error": f"Tool '{tool_name}' is not available or supported in this context."}
|
|
2232
|
-
|
|
2233
|
-
except Exception as e:
|
|
2234
|
-
error_msg = f"Exception during '{tool_name}' execution: {str(e)}"
|
|
2235
|
-
log_event_fn(error_msg, MSG_TYPE.MSG_TYPE_EXCEPTION)
|
|
2236
|
-
tool_result = {"status": "failure", "error": error_msg}
|
|
2237
|
-
|
|
2238
|
-
response_time = time.time() - start_time
|
|
2239
|
-
success = tool_result.get("status") == "success"
|
|
2240
|
-
|
|
2241
|
-
# Record performance
|
|
2242
|
-
performance_tracker.record_tool_usage(tool_name, success, confidence, response_time, tool_result.get("error"))
|
|
2243
|
-
|
|
2244
|
-
# Update task status
|
|
2245
|
-
if success and current_task_index < len(execution_plan.tasks):
|
|
2246
|
-
execution_plan.tasks[current_task_index].status = TaskStatus.COMPLETED
|
|
2247
|
-
completed_tasks.add(current_task_index)
|
|
2248
|
-
current_task_index += 1
|
|
2249
|
-
|
|
2250
|
-
# Enhanced observation logging
|
|
2251
|
-
observation_text = json.dumps(tool_result, indent=2)
|
|
2252
|
-
if len(observation_text) > 1000:
|
|
2253
|
-
# Truncate very long results for scratchpad
|
|
2254
|
-
truncated_result = {k: (str(v)[:200] + "..." if len(str(v)) > 200 else v) for k, v in tool_result.items()}
|
|
2255
|
-
observation_text = json.dumps(truncated_result, indent=2)
|
|
2256
|
-
|
|
2257
|
-
current_scratchpad += f"\n\n### Step {i+1}: Execution & Observation\n"
|
|
2258
|
-
current_scratchpad += f"**Tool Used**: {tool_name}\n"
|
|
2259
|
-
current_scratchpad += f"**Success**: {success}\n"
|
|
2260
|
-
current_scratchpad += f"**Response Time**: {response_time:.2f}s\n"
|
|
2261
|
-
current_scratchpad += f"**Result**:\n```json\n{observation_text}\n```"
|
|
2262
|
-
|
|
2263
|
-
# Track tool call
|
|
2264
|
-
tool_calls_this_turn.append({
|
|
2265
|
-
"name": tool_name,
|
|
2266
|
-
"params": tool_params,
|
|
2267
|
-
"result": tool_result,
|
|
2268
|
-
"response_time": response_time,
|
|
2269
|
-
"confidence": confidence,
|
|
2270
|
-
"reasoning": reasoning
|
|
2271
|
-
})
|
|
2272
|
-
|
|
2273
|
-
if success:
|
|
2274
|
-
log_event_fn(f"✅ Step {i+1} completed successfully", MSG_TYPE.MSG_TYPE_STEP_END, event_id=reasoning_step_id, meta={
|
|
2275
|
-
"tool_name": tool_name,
|
|
2276
|
-
"response_time": response_time,
|
|
2277
|
-
"confidence": confidence
|
|
2278
|
-
})
|
|
2279
|
-
else:
|
|
2280
|
-
error_detail = tool_result.get("error", "No error detail provided.")
|
|
2281
|
-
log_event_fn(f"⚠️ Step {i+1} completed with issues: {error_detail}", MSG_TYPE.MSG_TYPE_STEP_END, event_id=reasoning_step_id, meta={
|
|
2282
|
-
"tool_name": tool_name,
|
|
2283
|
-
"error": error_detail,
|
|
2284
|
-
"confidence": confidence
|
|
2285
|
-
})
|
|
2286
|
-
|
|
2287
|
-
# Add failure handling to scratchpad
|
|
2288
|
-
current_scratchpad += f"\n**Failure Analysis**: {error_detail}"
|
|
2289
|
-
current_scratchpad += f"\n**Next Steps**: Consider alternative approaches or tools"
|
|
2290
|
-
|
|
2291
|
-
# Log current progress
|
|
2292
|
-
completed_count = len(completed_tasks)
|
|
2293
|
-
total_tasks = len(execution_plan.tasks)
|
|
2294
|
-
if total_tasks > 0:
|
|
2295
|
-
progress = (completed_count / total_tasks) * 100
|
|
2296
|
-
log_event_fn(f"Progress: {completed_count}/{total_tasks} tasks completed ({progress:.1f}%)", MSG_TYPE.MSG_TYPE_STEP_PROGRESS, meta={"progress": progress})
|
|
2297
|
-
|
|
2298
|
-
# Check if all tasks are completed
|
|
2299
|
-
if completed_count >= total_tasks:
|
|
2300
|
-
log_event_fn("🎯 All planned tasks completed", MSG_TYPE.MSG_TYPE_INFO)
|
|
2301
|
-
break
|
|
2302
|
-
|
|
2303
|
-
except Exception as ex:
|
|
2304
|
-
log_event_fn(f"💥 Unexpected error in reasoning step {i+1}: {str(ex)}", MSG_TYPE.MSG_TYPE_ERROR, event_id=reasoning_step_id)
|
|
2305
|
-
trace_exception(ex)
|
|
2306
|
-
|
|
2307
|
-
# Add error to scratchpad for context
|
|
2308
|
-
current_scratchpad += f"\n\n### Step {i+1}: Unexpected Error\n**Error**: {str(ex)}\n**Recovery**: Continuing with adjusted approach"
|
|
2309
|
-
|
|
2310
|
-
log_event_fn("🔄 Recovering and continuing with next step", MSG_TYPE.MSG_TYPE_STEP_END, event_id=reasoning_step_id)
|
|
2311
|
-
|
|
2312
|
-
# Enhanced self-reflection
|
|
2313
|
-
if enable_self_reflection and len(tool_calls_this_turn) > 0:
|
|
2314
|
-
reflection_id = log_event_fn("🤔 Conducting comprehensive self-assessment...", MSG_TYPE.MSG_TYPE_STEP_START)
|
|
2315
|
-
try:
|
|
2316
|
-
reflection_prompt = f"""Conduct a thorough review of your work and assess the quality of your response to the user's request.
|
|
2317
|
-
|
|
2318
|
-
ORIGINAL REQUEST: "{original_user_prompt}"
|
|
2319
|
-
TOOLS USED: {len(tool_calls_this_turn)}
|
|
2320
|
-
PLAN REVISIONS: {plan_revision_count}
|
|
2321
|
-
|
|
2322
|
-
COMPLETE ANALYSIS:
|
|
2323
|
-
{current_scratchpad}
|
|
2324
|
-
|
|
2325
|
-
Evaluate your performance on multiple dimensions:
|
|
2326
|
-
|
|
2327
|
-
1. **Goal Achievement**: Did you fully address the user's request?
|
|
2328
|
-
2. **Process Efficiency**: Was your approach optimal given the available tools?
|
|
2329
|
-
3. **Information Quality**: Is the information you gathered accurate and relevant?
|
|
2330
|
-
4. **Decision Making**: Were your tool choices and parameters appropriate?
|
|
2331
|
-
5. **Adaptability**: How well did you handle unexpected results or plan changes?
|
|
2332
|
-
|
|
2333
|
-
Provide your assessment as JSON:
|
|
2334
|
-
{{
|
|
2335
|
-
"goal_achieved": true,
|
|
2336
|
-
"effectiveness_score": 0.85,
|
|
2337
|
-
"process_efficiency": 0.8,
|
|
2338
|
-
"information_quality": 0.9,
|
|
2339
|
-
"decision_making": 0.85,
|
|
2340
|
-
"adaptability": 0.7,
|
|
2341
|
-
"overall_confidence": 0.82,
|
|
2342
|
-
"strengths": ["Clear reasoning", "Good tool selection"],
|
|
2343
|
-
"areas_for_improvement": ["Could have been more efficient"],
|
|
2344
|
-
"summary": "Successfully completed the user's request with high quality results",
|
|
2345
|
-
"key_insights": ["Discovered that X was more important than initially thought"]
|
|
2346
|
-
}}"""
|
|
2347
|
-
|
|
2348
|
-
reflection_data = self.generate_structured_content(
|
|
2349
|
-
prompt=reflection_prompt,
|
|
2350
|
-
schema={
|
|
2351
|
-
"goal_achieved": "boolean",
|
|
2352
|
-
"effectiveness_score": "number",
|
|
2353
|
-
"process_efficiency": "number",
|
|
2354
|
-
"information_quality": "number",
|
|
2355
|
-
"decision_making": "number",
|
|
2356
|
-
"adaptability": "number",
|
|
2357
|
-
"overall_confidence": "number",
|
|
2358
|
-
"strengths": "array",
|
|
2359
|
-
"areas_for_improvement": "array",
|
|
2360
|
-
"summary": "string",
|
|
2361
|
-
"key_insights": "array"
|
|
2362
|
-
},
|
|
2363
|
-
temperature=0.3,
|
|
2364
|
-
**llm_generation_kwargs
|
|
2365
|
-
)
|
|
2366
|
-
|
|
2367
|
-
if reflection_data:
|
|
2368
|
-
current_scratchpad += f"\n\n### Comprehensive Self-Assessment\n"
|
|
2369
|
-
current_scratchpad += f"**Goal Achieved**: {reflection_data.get('goal_achieved', False)}\n"
|
|
2370
|
-
current_scratchpad += f"**Overall Confidence**: {reflection_data.get('overall_confidence', 0.5):.2f}\n"
|
|
2371
|
-
current_scratchpad += f"**Effectiveness Score**: {reflection_data.get('effectiveness_score', 0.5):.2f}\n"
|
|
2372
|
-
current_scratchpad += f"**Key Strengths**: {', '.join(reflection_data.get('strengths', []))}\n"
|
|
2373
|
-
current_scratchpad += f"**Improvement Areas**: {', '.join(reflection_data.get('areas_for_improvement', []))}\n"
|
|
2374
|
-
current_scratchpad += f"**Summary**: {reflection_data.get('summary', '')}\n"
|
|
2375
|
-
|
|
2376
|
-
log_event_fn(f"✅ Self-assessment completed", MSG_TYPE.MSG_TYPE_STEP_END, event_id=reflection_id, meta={
|
|
2377
|
-
"overall_confidence": reflection_data.get('overall_confidence', 0.5),
|
|
2378
|
-
"goal_achieved": reflection_data.get('goal_achieved', False),
|
|
2379
|
-
"effectiveness_score": reflection_data.get('effectiveness_score', 0.5)
|
|
2380
|
-
})
|
|
2381
|
-
else:
|
|
2382
|
-
log_event_fn("Self-assessment data generation failed", MSG_TYPE.MSG_TYPE_WARNING, event_id=reflection_id)
|
|
2383
|
-
|
|
2384
|
-
except Exception as e:
|
|
2385
|
-
log_event_fn(f"Self-assessment failed: {e}", MSG_TYPE.MSG_TYPE_WARNING, event_id=reflection_id)
|
|
2386
|
-
|
|
2387
|
-
# Enhanced final synthesis
|
|
2388
|
-
synthesis_id = log_event_fn("📝 Synthesizing comprehensive final response...", MSG_TYPE.MSG_TYPE_STEP_START)
|
|
2389
|
-
|
|
2390
|
-
final_answer_prompt = f"""Create a comprehensive, well-structured final response that fully addresses the user's request.
|
|
2391
|
-
|
|
2392
|
-
ORIGINAL REQUEST: "{original_user_prompt}"
|
|
2393
|
-
CONTEXT: {context or "No additional context"}
|
|
2394
|
-
|
|
2395
|
-
COMPLETE ANALYSIS AND WORK:
|
|
2396
|
-
{current_scratchpad}
|
|
2397
|
-
|
|
2398
|
-
GUIDELINES for your response:
|
|
2399
|
-
1. **Be Complete**: Address all aspects of the user's request
|
|
2400
|
-
2. **Be Clear**: Organize your response logically and use clear language
|
|
2401
|
-
3. **Be Helpful**: Provide actionable information and insights
|
|
2402
|
-
4. **Be Honest**: If there were limitations or uncertainties, mention them appropriately
|
|
2403
|
-
5. **Be Concise**: While being thorough, avoid unnecessary verbosity
|
|
2404
|
-
6. **Cite Sources**: If you used research tools, reference the information appropriately
|
|
2405
|
-
|
|
2406
|
-
Your response should feel natural and conversational while being informative and valuable.
|
|
2407
|
-
|
|
2408
|
-
FINAL RESPONSE:"""
|
|
2409
|
-
|
|
2410
|
-
log_prompt_fn("Final Synthesis Prompt", final_answer_prompt)
|
|
2411
|
-
|
|
2412
|
-
final_answer_text = self.generate_text(
|
|
2413
|
-
prompt=final_answer_prompt,
|
|
2414
|
-
system_prompt=system_prompt,
|
|
2415
|
-
stream=streaming_callback is not None,
|
|
2416
|
-
streaming_callback=streaming_callback,
|
|
2417
|
-
temperature=final_answer_temperature,
|
|
2418
|
-
**llm_generation_kwargs
|
|
2419
|
-
)
|
|
2420
|
-
|
|
2421
|
-
if isinstance(final_answer_text, dict) and "error" in final_answer_text:
|
|
2422
|
-
log_event_fn(f"Final synthesis failed: {final_answer_text['error']}", MSG_TYPE.MSG_TYPE_ERROR, event_id=synthesis_id)
|
|
2423
|
-
return {
|
|
2424
|
-
"final_answer": "I encountered an issue while preparing my final response. Please let me know if you'd like me to try again.",
|
|
2425
|
-
"error": final_answer_text["error"],
|
|
2426
|
-
"final_scratchpad": current_scratchpad,
|
|
2427
|
-
"tool_calls": tool_calls_this_turn,
|
|
2428
|
-
"sources": sources_this_turn,
|
|
2429
|
-
"decision_history": decision_history
|
|
2430
|
-
}
|
|
2431
|
-
|
|
2432
|
-
final_answer = self.remove_thinking_blocks(final_answer_text)
|
|
2433
|
-
|
|
2434
|
-
# Calculate overall performance metrics
|
|
2435
|
-
overall_confidence = sum(call.get('confidence', 0.5) for call in tool_calls_this_turn) / max(len(tool_calls_this_turn), 1)
|
|
2436
|
-
successful_calls = sum(1 for call in tool_calls_this_turn if call.get('result', {}).get('status') == 'success')
|
|
2437
|
-
success_rate = successful_calls / max(len(tool_calls_this_turn), 1)
|
|
2438
|
-
|
|
2439
|
-
log_event_fn("✅ Comprehensive response ready", MSG_TYPE.MSG_TYPE_STEP_END, event_id=synthesis_id, meta={
|
|
2440
|
-
"final_answer_length": len(final_answer),
|
|
2441
|
-
"total_tools_used": len(tool_calls_this_turn),
|
|
2442
|
-
"success_rate": success_rate,
|
|
2443
|
-
"overall_confidence": overall_confidence
|
|
2444
|
-
})
|
|
2445
|
-
|
|
2446
|
-
return {
|
|
2447
|
-
"final_answer": final_answer,
|
|
2448
|
-
"final_scratchpad": current_scratchpad,
|
|
2449
|
-
"tool_calls": tool_calls_this_turn,
|
|
2450
|
-
"sources": sources_this_turn,
|
|
2451
|
-
"decision_history": decision_history,
|
|
2452
|
-
"performance_stats": {
|
|
2453
|
-
"total_steps": len(tool_calls_this_turn),
|
|
2454
|
-
"successful_steps": successful_calls,
|
|
2455
|
-
"success_rate": success_rate,
|
|
2456
|
-
"average_confidence": overall_confidence,
|
|
2457
|
-
"plan_revisions": plan_revision_count,
|
|
2458
|
-
"total_reasoning_steps": len(decision_history)
|
|
2459
|
-
},
|
|
2460
|
-
"plan_evolution": {
|
|
2461
|
-
"initial_tasks": len(execution_plan.tasks),
|
|
2462
|
-
"final_version": current_plan_version,
|
|
2463
|
-
"total_revisions": plan_revision_count
|
|
2464
|
-
},
|
|
2465
|
-
"clarification_required": False,
|
|
2466
|
-
"overall_confidence": overall_confidence,
|
|
2467
|
-
"error": None
|
|
2468
|
-
}
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
def _execute_complex_reasoning_loop(
|
|
2472
|
-
self, prompt, context, system_prompt, reasoning_system_prompt, images,
|
|
2473
|
-
max_reasoning_steps, decision_temperature, final_answer_temperature,
|
|
2474
|
-
streaming_callback, debug, enable_self_reflection, all_visible_tools,
|
|
2475
|
-
rag_registry, rag_tool_specs, log_event_fn, log_prompt_fn, max_scratchpad_size, **llm_generation_kwargs
|
|
2476
|
-
) -> Dict[str, Any]:
|
|
2477
|
-
|
|
2478
|
-
planner, memory_manager, performance_tracker = TaskPlanner(self), MemoryManager(), ToolPerformanceTracker()
|
|
2479
|
-
|
|
2480
|
-
def _get_friendly_action_description(tool_name, requires_code, requires_image):
|
|
2481
|
-
descriptions = {
|
|
2482
|
-
"local_tools::final_answer": "📋 Preparing final answer",
|
|
2483
|
-
"local_tools::request_clarification": "❓ Requesting clarification",
|
|
2484
|
-
"local_tools::generate_image": "🎨 Creating image",
|
|
2485
|
-
"local_tools::revise_plan": "📝 Revising execution plan"
|
|
2486
|
-
}
|
|
2487
|
-
if tool_name in descriptions:
|
|
2488
|
-
return descriptions[tool_name]
|
|
2489
|
-
if "research::" in tool_name:
|
|
2490
|
-
return f"🔍 Searching {tool_name.split('::')[-1]} knowledge base"
|
|
2491
|
-
if requires_code:
|
|
2492
|
-
return "💻 Processing code"
|
|
2493
|
-
if requires_image:
|
|
2494
|
-
return "🖼️ Analyzing images"
|
|
2495
|
-
return f"🔧 Using {tool_name.replace('_', ' ').replace('::', ' - ').title()}"
|
|
2496
|
-
|
|
2497
|
-
def _compress_scratchpad_intelligently(scratchpad: str, original_request: str, target_size: int) -> str:
|
|
2498
|
-
"""Enhanced scratchpad compression that preserves key decisions and recent context"""
|
|
2499
|
-
if len(scratchpad) <= target_size:
|
|
2500
|
-
return scratchpad
|
|
2501
|
-
|
|
2502
|
-
log_event_fn("📝 Compressing scratchpad to maintain focus...", MSG_TYPE.MSG_TYPE_INFO)
|
|
2503
|
-
|
|
2504
|
-
# Extract key components
|
|
2505
|
-
lines = scratchpad.split('\n')
|
|
2506
|
-
plan_section = []
|
|
2507
|
-
decisions = []
|
|
2508
|
-
recent_observations = []
|
|
2509
|
-
|
|
2510
|
-
current_section = None
|
|
2511
|
-
for i, line in enumerate(lines):
|
|
2512
|
-
if "### Execution Plan" in line or "### Updated Plan" in line:
|
|
2513
|
-
current_section = "plan"
|
|
2514
|
-
elif "### Step" in line and ("Thought" in line or "Decision" in line):
|
|
2515
|
-
current_section = "decision"
|
|
2516
|
-
elif "### Step" in line and "Observation" in line:
|
|
2517
|
-
current_section = "observation"
|
|
2518
|
-
elif line.startswith("###"):
|
|
2519
|
-
current_section = None
|
|
2520
|
-
|
|
2521
|
-
if current_section == "plan" and line.strip():
|
|
2522
|
-
plan_section.append(line)
|
|
2523
|
-
elif current_section == "decision" and line.strip():
|
|
2524
|
-
decisions.append((i, line))
|
|
2525
|
-
elif current_section == "observation" and line.strip():
|
|
2526
|
-
recent_observations.append((i, line))
|
|
2527
|
-
|
|
2528
|
-
# Keep most recent items and important decisions
|
|
2529
|
-
recent_decisions = decisions[-3:] if len(decisions) > 3 else decisions
|
|
2530
|
-
recent_obs = recent_observations[-5:] if len(recent_observations) > 5 else recent_observations
|
|
2531
|
-
|
|
2532
|
-
compressed_parts = [
|
|
2533
|
-
f"### Original Request\n{original_request}",
|
|
2534
|
-
f"### Current Plan\n" + '\n'.join(plan_section[-10:]),
|
|
2535
|
-
f"### Recent Key Decisions"
|
|
2536
|
-
]
|
|
2537
|
-
|
|
2538
|
-
for _, decision in recent_decisions:
|
|
2539
|
-
compressed_parts.append(decision)
|
|
2540
|
-
|
|
2541
|
-
compressed_parts.append("### Recent Observations")
|
|
2542
|
-
for _, obs in recent_obs:
|
|
2543
|
-
compressed_parts.append(obs)
|
|
2544
|
-
|
|
2545
|
-
compressed = '\n'.join(compressed_parts)
|
|
2546
|
-
if len(compressed) > target_size:
|
|
2547
|
-
# Final trim if still too long
|
|
2548
|
-
compressed = compressed[:target_size-100] + "\n...[content compressed for focus]"
|
|
2549
|
-
|
|
2550
|
-
return compressed
|
|
2551
|
-
|
|
2552
|
-
original_user_prompt, tool_calls_this_turn, sources_this_turn = prompt, [], []
|
|
2553
|
-
asset_store: Dict[str, Dict] = {}
|
|
2554
|
-
decision_history = [] # Track all decisions made
|
|
2555
|
-
|
|
2556
|
-
# Enhanced planning phase
|
|
2557
|
-
planning_step_id = log_event_fn("📋 Creating adaptive execution plan...", MSG_TYPE.MSG_TYPE_STEP_START)
|
|
2558
|
-
execution_plan = planner.decompose_task(original_user_prompt, context or "")
|
|
2559
|
-
current_plan_version = 1
|
|
2560
|
-
|
|
2561
|
-
log_event_fn(f"Initial plan created with {len(execution_plan.tasks)} tasks", MSG_TYPE.MSG_TYPE_INFO, meta={
|
|
2562
|
-
"plan_version": current_plan_version,
|
|
2563
|
-
"total_tasks": len(execution_plan.tasks),
|
|
2564
|
-
"estimated_complexity": "medium" if len(execution_plan.tasks) <= 5 else "high"
|
|
2565
|
-
})
|
|
2566
|
-
|
|
2567
|
-
for i, task in enumerate(execution_plan.tasks):
|
|
2568
|
-
log_event_fn(f"Task {i+1}: {task.description}", MSG_TYPE.MSG_TYPE_INFO)
|
|
2569
|
-
|
|
2570
|
-
log_event_fn("✅ Adaptive plan ready", MSG_TYPE.MSG_TYPE_STEP_END, event_id=planning_step_id)
|
|
2571
|
-
|
|
2572
|
-
# Enhanced initial state
|
|
2573
|
-
initial_state_parts = [
|
|
2574
|
-
f"### Original User Request\n{original_user_prompt}",
|
|
2575
|
-
f"### Context\n{context or 'No additional context provided'}",
|
|
2576
|
-
f"### Execution Plan (Version {current_plan_version})\n- Total tasks: {len(execution_plan.tasks)}",
|
|
2577
|
-
f"- Estimated complexity: {'High' if len(execution_plan.tasks) > 5 else 'Medium'}"
|
|
2578
|
-
]
|
|
2579
|
-
|
|
2580
|
-
for i, task in enumerate(execution_plan.tasks):
|
|
2581
|
-
initial_state_parts.append(f" {i+1}. {task.description} [Status: {task.status.value}]")
|
|
2582
|
-
|
|
2583
|
-
if images:
|
|
2584
|
-
initial_state_parts.append(f"### Provided Assets")
|
|
2585
|
-
for img_b64 in images:
|
|
2586
|
-
img_uuid = str(uuid.uuid4())
|
|
2587
|
-
asset_store[img_uuid] = {"type": "image", "content": img_b64, "source": "user"}
|
|
2588
|
-
initial_state_parts.append(f"- Image asset: {img_uuid}")
|
|
2589
|
-
|
|
2590
|
-
current_scratchpad = "\n".join(initial_state_parts)
|
|
2591
|
-
log_event_fn("Initial analysis complete", MSG_TYPE.MSG_TYPE_SCRATCHPAD, meta={"scratchpad_size": len(current_scratchpad)})
|
|
2592
|
-
|
|
2593
|
-
formatted_tools_list = "\n".join([f"**{t['name']}**: {t['description']}" for t in all_visible_tools])
|
|
2594
|
-
completed_tasks, current_task_index = set(), 0
|
|
2595
|
-
plan_revision_count = 0
|
|
2596
|
-
|
|
2597
|
-
# Main reasoning loop with enhanced decision tracking
|
|
2598
|
-
for i in range(max_reasoning_steps):
|
|
2599
|
-
current_task_desc = execution_plan.tasks[current_task_index].description if current_task_index < len(execution_plan.tasks) else "Finalizing analysis"
|
|
2600
|
-
step_desc = f"🤔 Step {i+1}: {current_task_desc}"
|
|
2601
|
-
reasoning_step_id = log_event_fn(step_desc, MSG_TYPE.MSG_TYPE_STEP_START)
|
|
2602
|
-
|
|
2603
|
-
try:
|
|
2604
|
-
# Enhanced scratchpad management
|
|
2605
|
-
if len(current_scratchpad) > max_scratchpad_size:
|
|
2606
|
-
log_event_fn(f"Scratchpad size ({len(current_scratchpad)}) exceeds limit, compressing...", MSG_TYPE.MSG_TYPE_INFO)
|
|
2607
|
-
current_scratchpad = _compress_scratchpad_intelligently(current_scratchpad, original_user_prompt, max_scratchpad_size // 2)
|
|
2608
|
-
log_event_fn(f"Scratchpad compressed to {len(current_scratchpad)} characters", MSG_TYPE.MSG_TYPE_INFO)
|
|
2609
|
-
|
|
2610
|
-
# Enhanced reasoning prompt with better decision tracking
|
|
2611
|
-
reasoning_prompt = f"""You are working on: "{original_user_prompt}"
|
|
2612
|
-
|
|
2613
|
-
=== AVAILABLE ACTIONS ===
|
|
2614
|
-
{formatted_tools_list}
|
|
2615
|
-
|
|
2616
|
-
=== YOUR COMPLETE ANALYSIS HISTORY ===
|
|
2617
|
-
{current_scratchpad}
|
|
2618
|
-
=== END ANALYSIS HISTORY ===
|
|
2619
|
-
|
|
2620
|
-
=== DECISION GUIDELINES ===
|
|
2621
|
-
1. **Review your progress**: Look at what you've already discovered and accomplished
|
|
2622
|
-
2. **Consider your current task**: Focus on the next logical step in your plan
|
|
2623
|
-
3. **Remember your decisions**: If you previously decided to use a tool, follow through unless you have a good reason to change
|
|
2624
|
-
4. **Be adaptive**: If you discover new information that changes the situation, consider revising your plan
|
|
2625
|
-
5. **Stay focused**: Each action should clearly advance toward the final goal
|
|
2626
|
-
|
|
2627
|
-
=== YOUR NEXT DECISION ===
|
|
2628
|
-
Choose the single most appropriate action to take right now. Consider:
|
|
2629
|
-
- What specific step are you currently working on?
|
|
2630
|
-
- What information do you still need?
|
|
2631
|
-
- What would be most helpful for the user?
|
|
2632
|
-
|
|
2633
|
-
Provide your decision as JSON:
|
|
2634
|
-
{{
|
|
2635
|
-
"reasoning": "Explain your current thinking and why this action makes sense now",
|
|
2636
|
-
"action": {{
|
|
2637
|
-
"tool_name": "exact_tool_name",
|
|
2638
|
-
"requires_code_input": false,
|
|
2639
|
-
"requires_image_input": false,
|
|
2640
|
-
"confidence": 0.8
|
|
2641
|
-
}},
|
|
2642
|
-
"plan_status": "on_track" // or "needs_revision" if you want to change the plan
|
|
2643
|
-
}}"""
|
|
2644
|
-
|
|
2645
|
-
log_prompt_fn(f"Reasoning Prompt Step {i+1}", reasoning_prompt)
|
|
2646
|
-
decision_data = self.generate_structured_content(
|
|
2647
|
-
prompt=reasoning_prompt,
|
|
2648
|
-
schema={
|
|
2649
|
-
"reasoning": "string",
|
|
2650
|
-
"action": "object",
|
|
2651
|
-
"plan_status": "string"
|
|
2652
|
-
},
|
|
2653
|
-
system_prompt=reasoning_system_prompt,
|
|
2654
|
-
temperature=decision_temperature,
|
|
2655
|
-
**llm_generation_kwargs
|
|
2656
|
-
)
|
|
2657
|
-
|
|
2658
|
-
if not (decision_data and isinstance(decision_data.get("action"), dict)):
|
|
2659
|
-
log_event_fn("⚠️ Invalid decision format from AI", MSG_TYPE.MSG_TYPE_WARNING, event_id=reasoning_step_id)
|
|
2660
|
-
current_scratchpad += f"\n\n### Step {i+1}: Decision Error\n- Error: AI produced invalid decision JSON\n- Continuing with fallback approach"
|
|
2661
|
-
continue
|
|
2662
|
-
|
|
2663
|
-
action = decision_data.get("action", {})
|
|
2664
|
-
reasoning = decision_data.get("reasoning", "No reasoning provided")
|
|
2665
|
-
plan_status = decision_data.get("plan_status", "on_track")
|
|
2666
|
-
tool_name = action.get("tool_name")
|
|
2667
|
-
requires_code = action.get("requires_code_input", False)
|
|
2668
|
-
requires_image = action.get("requires_image_input", False)
|
|
2669
|
-
confidence = action.get("confidence", 0.5)
|
|
2670
|
-
|
|
2671
|
-
# Track the decision
|
|
2672
|
-
decision_history.append({
|
|
2673
|
-
"step": i+1,
|
|
2674
|
-
"tool_name": tool_name,
|
|
2675
|
-
"reasoning": reasoning,
|
|
2676
|
-
"confidence": confidence,
|
|
2677
|
-
"plan_status": plan_status
|
|
2678
|
-
})
|
|
2679
|
-
|
|
2680
|
-
current_scratchpad += f"\n\n### Step {i+1}: Decision & Reasoning\n**Reasoning**: {reasoning}\n**Chosen Action**: {tool_name}\n**Confidence**: {confidence}\n**Plan Status**: {plan_status}"
|
|
2681
|
-
|
|
2682
|
-
log_event_fn(_get_friendly_action_description(tool_name, requires_code, requires_image), MSG_TYPE.MSG_TYPE_STEP, meta={
|
|
2683
|
-
"tool_name": tool_name,
|
|
2684
|
-
"confidence": confidence,
|
|
2685
|
-
"reasoning": reasoning[:100] + "..." if len(reasoning) > 100 else reasoning
|
|
2686
|
-
})
|
|
2687
|
-
|
|
2688
|
-
# Handle plan revision
|
|
2689
|
-
if plan_status == "needs_revision" and tool_name != "local_tools::revise_plan":
|
|
2690
|
-
log_event_fn("🔄 AI indicates plan needs revision", MSG_TYPE.MSG_TYPE_INFO)
|
|
2691
|
-
tool_name = "local_tools::revise_plan" # Force plan revision
|
|
2692
|
-
|
|
2693
|
-
# Handle final answer
|
|
2694
|
-
if tool_name == "local_tools::final_answer":
|
|
2695
|
-
log_event_fn("🎯 Ready to provide final answer", MSG_TYPE.MSG_TYPE_INFO)
|
|
2696
|
-
break
|
|
2697
|
-
|
|
2698
|
-
# Handle clarification request
|
|
2699
|
-
if tool_name == "local_tools::request_clarification":
|
|
2700
|
-
clarification_prompt = f"""Based on your analysis, what specific information do you need from the user?
|
|
2701
|
-
|
|
2702
|
-
CURRENT ANALYSIS:
|
|
2703
|
-
{current_scratchpad}
|
|
2704
|
-
|
|
2705
|
-
Generate a clear, specific question that will help you proceed effectively:"""
|
|
2706
|
-
|
|
2707
|
-
question = self.generate_text(clarification_prompt, temperature=0.3)
|
|
2708
|
-
question = self.remove_thinking_blocks(question)
|
|
2709
|
-
|
|
2710
|
-
log_event_fn("❓ Clarification needed from user", MSG_TYPE.MSG_TYPE_INFO)
|
|
2711
|
-
return {
|
|
2712
|
-
"final_answer": question,
|
|
2713
|
-
"clarification_required": True,
|
|
2714
|
-
"final_scratchpad": current_scratchpad,
|
|
2715
|
-
"tool_calls": tool_calls_this_turn,
|
|
2716
|
-
"sources": sources_this_turn,
|
|
2717
|
-
"error": None,
|
|
2718
|
-
"decision_history": decision_history
|
|
2719
|
-
}
|
|
2720
|
-
|
|
2721
|
-
# Handle final answer
|
|
2722
|
-
if tool_name == "local_tools::final_answer":
|
|
2723
|
-
log_event_fn("🎯 Ready to provide final answer", MSG_TYPE.MSG_TYPE_INFO)
|
|
2724
|
-
break
|
|
2725
|
-
|
|
2726
|
-
# Handle clarification request
|
|
2727
|
-
if tool_name == "local_tools::request_clarification":
|
|
2728
|
-
clarification_prompt = f"""Based on your analysis, what specific information do you need from the user?
|
|
2729
|
-
|
|
2730
|
-
CURRENT ANALYSIS:
|
|
2731
|
-
{current_scratchpad}
|
|
2732
|
-
|
|
2733
|
-
Generate a clear, specific question that will help you proceed effectively:"""
|
|
2734
|
-
|
|
2735
|
-
question = self.generate_text(clarification_prompt, temperature=0.3)
|
|
2736
|
-
question = self.remove_thinking_blocks(question)
|
|
2737
|
-
|
|
2738
|
-
log_event_fn("❓ Clarification needed from user", MSG_TYPE.MSG_TYPE_INFO)
|
|
2739
|
-
return {
|
|
2740
|
-
"final_answer": question,
|
|
2741
|
-
"clarification_required": True,
|
|
2742
|
-
"final_scratchpad": current_scratchpad,
|
|
2743
|
-
"tool_calls": tool_calls_this_turn,
|
|
2744
|
-
"sources": sources_this_turn,
|
|
2745
|
-
"error": None,
|
|
2746
|
-
"decision_history": decision_history
|
|
2747
|
-
}
|
|
2748
|
-
|
|
2749
|
-
# Handle plan revision
|
|
2750
|
-
if tool_name == "local_tools::revise_plan":
|
|
2751
|
-
plan_revision_count += 1
|
|
2752
|
-
revision_id = log_event_fn(f"📝 Revising execution plan (revision #{plan_revision_count})", MSG_TYPE.MSG_TYPE_STEP_START)
|
|
2753
|
-
|
|
2754
|
-
try:
|
|
2755
|
-
revision_prompt = f"""Based on your current analysis and discoveries, create an updated execution plan.
|
|
2756
|
-
|
|
2757
|
-
ORIGINAL REQUEST: "{original_user_prompt}"
|
|
2758
|
-
CURRENT ANALYSIS:
|
|
2759
|
-
{current_scratchpad}
|
|
2760
|
-
|
|
2761
|
-
REASON FOR REVISION: {reasoning}
|
|
2762
|
-
|
|
2763
|
-
Create a new plan that reflects your current understanding. Consider:
|
|
2764
|
-
1. What have you already accomplished?
|
|
2765
|
-
2. What new information have you discovered?
|
|
2766
|
-
3. What steps are still needed?
|
|
2767
|
-
4. How can you be more efficient?
|
|
2768
|
-
|
|
2769
|
-
Provide your revision as JSON:
|
|
2770
|
-
{{
|
|
2771
|
-
"revision_reason": "Clear explanation of why the plan needed to change",
|
|
2772
|
-
"new_plan": [
|
|
2773
|
-
{{"step": 1, "description": "First revised step", "status": "pending"}},
|
|
2774
|
-
{{"step": 2, "description": "Second revised step", "status": "pending"}}
|
|
2775
|
-
],
|
|
2776
|
-
"confidence": 0.8
|
|
2777
|
-
}}"""
|
|
2778
|
-
|
|
2779
|
-
revision_data = self.generate_structured_content(
|
|
2780
|
-
prompt=revision_prompt,
|
|
2781
|
-
schema={
|
|
2782
|
-
"revision_reason": "string",
|
|
2783
|
-
"new_plan": "array",
|
|
2784
|
-
"confidence": "number"
|
|
2785
|
-
},
|
|
2786
|
-
temperature=0.3,
|
|
2787
|
-
**llm_generation_kwargs
|
|
2788
|
-
)
|
|
2789
|
-
|
|
2790
|
-
if revision_data and revision_data.get("new_plan"):
|
|
2791
|
-
# Update the plan
|
|
2792
|
-
current_plan_version += 1
|
|
2793
|
-
new_tasks = []
|
|
2794
|
-
for task_data in revision_data["new_plan"]:
|
|
2795
|
-
task = TaskDecomposition() # Assuming this class exists
|
|
2796
|
-
task.description = task_data.get("description", "Undefined step")
|
|
2797
|
-
task.status = TaskStatus.PENDING # Reset all to pending
|
|
2798
|
-
new_tasks.append(task)
|
|
2799
|
-
|
|
2800
|
-
execution_plan.tasks = new_tasks
|
|
2801
|
-
current_task_index = 0 # Reset to beginning
|
|
2802
|
-
|
|
2803
|
-
# Update scratchpad with new plan
|
|
2804
|
-
current_scratchpad += f"\n\n### Updated Plan (Version {current_plan_version})\n"
|
|
2805
|
-
current_scratchpad += f"**Revision Reason**: {revision_data.get('revision_reason', 'Plan needed updating')}\n"
|
|
2806
|
-
current_scratchpad += f"**New Tasks**:\n"
|
|
2807
|
-
for i, task in enumerate(execution_plan.tasks):
|
|
2808
|
-
current_scratchpad += f" {i+1}. {task.description}\n"
|
|
2809
|
-
|
|
2810
|
-
log_event_fn(f"✅ Plan revised with {len(execution_plan.tasks)} updated tasks", MSG_TYPE.MSG_TYPE_STEP_END, event_id=revision_id, meta={
|
|
2811
|
-
"plan_version": current_plan_version,
|
|
2812
|
-
"new_task_count": len(execution_plan.tasks),
|
|
2813
|
-
"revision_reason": revision_data.get("revision_reason", "")
|
|
2814
|
-
})
|
|
2815
|
-
|
|
2816
|
-
# Continue with the new plan
|
|
2817
|
-
continue
|
|
2818
|
-
else:
|
|
2819
|
-
raise ValueError("Failed to generate valid plan revision")
|
|
2820
|
-
|
|
2821
|
-
except Exception as e:
|
|
2822
|
-
log_event_fn(f"Plan revision failed: {e}", MSG_TYPE.MSG_TYPE_WARNING, event_id=revision_id)
|
|
2823
|
-
current_scratchpad += f"\n**Plan Revision Failed**: {str(e)}\nContinuing with original plan."
|
|
2824
|
-
|
|
2825
|
-
# Prepare parameters for tool execution
|
|
2826
|
-
param_assets = {}
|
|
2827
|
-
if requires_code:
|
|
2828
|
-
log_event_fn("💻 Generating code for task", MSG_TYPE.MSG_TYPE_INFO)
|
|
2829
|
-
code_prompt = f"""Generate the specific code needed for the current step.
|
|
2830
|
-
|
|
2831
|
-
CURRENT CONTEXT:
|
|
2832
|
-
{current_scratchpad}
|
|
2833
|
-
|
|
2834
|
-
CURRENT TASK: {tool_name}
|
|
2835
|
-
USER REQUEST: "{original_user_prompt}"
|
|
2836
|
-
|
|
2837
|
-
Generate clean, functional code that addresses the specific requirements. Focus on:
|
|
2838
|
-
1. Solving the immediate problem
|
|
2839
|
-
2. Being clear and readable
|
|
2840
|
-
3. Including necessary imports and dependencies
|
|
2841
|
-
4. Adding helpful comments where appropriate
|
|
2842
|
-
|
|
2843
|
-
CODE:"""
|
|
2844
|
-
|
|
2845
|
-
code_content = self.generate_code(prompt=code_prompt, **llm_generation_kwargs)
|
|
2846
|
-
code_uuid = f"code_asset_{uuid.uuid4()}"
|
|
2847
|
-
asset_store[code_uuid] = {"type": "code", "content": code_content}
|
|
2848
|
-
param_assets['code_asset_id'] = code_uuid
|
|
2849
|
-
log_event_fn(f"Code asset created: {code_uuid[:8]}...", MSG_TYPE.MSG_TYPE_INFO)
|
|
2850
|
-
|
|
2851
|
-
if requires_image:
|
|
2852
|
-
image_assets = [asset_id for asset_id, asset in asset_store.items() if asset['type'] == 'image' and asset.get('source') == 'user']
|
|
2853
|
-
if image_assets:
|
|
2854
|
-
param_assets['image_asset_id'] = image_assets[0]
|
|
2855
|
-
log_event_fn(f"Using image asset: {image_assets[0][:8]}...", MSG_TYPE.MSG_TYPE_INFO)
|
|
2856
|
-
else:
|
|
2857
|
-
log_event_fn("⚠️ Image required but none available", MSG_TYPE.MSG_TYPE_WARNING)
|
|
2858
|
-
|
|
2859
|
-
# Enhanced parameter generation
|
|
2860
|
-
param_prompt = f"""Generate the optimal parameters for this tool execution.
|
|
2861
|
-
|
|
2862
|
-
TOOL: {tool_name}
|
|
2863
|
-
CURRENT CONTEXT: {current_scratchpad}
|
|
2864
|
-
CURRENT REASONING: {reasoning}
|
|
2865
|
-
AVAILABLE ASSETS: {json.dumps(param_assets) if param_assets else "None"}
|
|
2866
|
-
|
|
2867
|
-
Based on your analysis and the current step you're working on, provide the most appropriate parameters.
|
|
2868
|
-
Be specific and purposeful in your parameter choices.
|
|
2869
|
-
|
|
2870
|
-
Output format: {{"tool_params": {{...}}}}"""
|
|
2871
|
-
|
|
2872
|
-
log_prompt_fn(f"Parameter Generation Step {i+1}", param_prompt)
|
|
2873
|
-
param_data = self.generate_structured_content(
|
|
2874
|
-
prompt=param_prompt,
|
|
2875
|
-
schema={"tool_params": "object"},
|
|
2876
|
-
temperature=decision_temperature,
|
|
2877
|
-
**llm_generation_kwargs
|
|
2878
|
-
)
|
|
2879
|
-
tool_params = param_data.get("tool_params", {}) if param_data else {}
|
|
2880
|
-
|
|
2881
|
-
current_scratchpad += f"\n**Parameters Generated**: {json.dumps(tool_params, indent=2)}"
|
|
2882
|
-
|
|
2883
|
-
# Hydrate parameters with assets
|
|
2884
|
-
def _hydrate(data: Any, store: Dict) -> Any:
|
|
2885
|
-
if isinstance(data, dict): return {k: _hydrate(v, store) for k, v in data.items()}
|
|
2886
|
-
if isinstance(data, list): return [_hydrate(item, store) for item in data]
|
|
2887
|
-
if isinstance(data, str) and "asset_" in data and data in store: return store[data].get("content", data)
|
|
2888
|
-
return data
|
|
2889
|
-
|
|
2890
|
-
hydrated_params = _hydrate(tool_params, asset_store)
|
|
2891
|
-
|
|
2892
|
-
# Execute the tool with detailed logging
|
|
2893
|
-
start_time = time.time()
|
|
2894
|
-
tool_result = {"status": "failure", "error": f"Tool '{tool_name}' failed to execute."}
|
|
2895
|
-
|
|
2896
|
-
try:
|
|
2897
|
-
if tool_name in rag_registry:
|
|
2898
|
-
query = hydrated_params.get("query", "")
|
|
2899
|
-
if not query:
|
|
2900
|
-
# Fall back to using reasoning as query
|
|
2901
|
-
query = reasoning[:200] + "..." if len(reasoning) > 200 else reasoning
|
|
2902
|
-
|
|
2903
|
-
log_event_fn(f"🔍 Searching knowledge base with query: '{query[:50]}...'", MSG_TYPE.MSG_TYPE_INFO)
|
|
2904
|
-
|
|
2905
|
-
top_k = rag_tool_specs[tool_name]["default_top_k"]
|
|
2906
|
-
min_sim = rag_tool_specs[tool_name]["default_min_sim"]
|
|
2907
|
-
|
|
2908
|
-
raw_results = rag_registry[tool_name](query=query, rag_top_k=top_k)
|
|
2909
|
-
raw_iter = raw_results["results"] if isinstance(raw_results, dict) and "results" in raw_results else raw_results
|
|
2910
|
-
|
|
2911
|
-
docs = []
|
|
2912
|
-
for d in raw_iter or []:
|
|
2913
|
-
doc_data = {
|
|
2914
|
-
"text": d.get("text", str(d)),
|
|
2915
|
-
"score": d.get("score", 0) * 100,
|
|
2916
|
-
"metadata": d.get("metadata", {})
|
|
2917
|
-
}
|
|
2918
|
-
docs.append(doc_data)
|
|
2919
|
-
|
|
2920
|
-
kept = [x for x in docs if x['score'] >= min_sim]
|
|
2921
|
-
tool_result = {
|
|
2922
|
-
"status": "success",
|
|
2923
|
-
"results": kept,
|
|
2924
|
-
"total_found": len(docs),
|
|
2925
|
-
"kept_after_filtering": len(kept),
|
|
2926
|
-
"query_used": query
|
|
2927
|
-
}
|
|
2928
|
-
|
|
2929
|
-
sources_this_turn.extend([{
|
|
2930
|
-
"source": tool_name,
|
|
2931
|
-
"metadata": x["metadata"],
|
|
2932
|
-
"score": x["score"]
|
|
2933
|
-
} for x in kept])
|
|
2934
|
-
|
|
2935
|
-
log_event_fn(f"📚 Retrieved {len(kept)} relevant documents (from {len(docs)} total)", MSG_TYPE.MSG_TYPE_INFO)
|
|
2936
|
-
|
|
2937
|
-
elif hasattr(self, "mcp") and "local_tools" not in tool_name:
|
|
2938
|
-
log_event_fn(f"🔧 Executing MCP tool: {tool_name}", MSG_TYPE.MSG_TYPE_TOOL_CALL, meta={
|
|
2939
|
-
"tool_name": tool_name,
|
|
2940
|
-
"params": {k: str(v)[:100] for k, v in hydrated_params.items()} # Truncate for logging
|
|
2941
|
-
})
|
|
2942
|
-
|
|
2943
|
-
tool_result = self.mcp.execute_tool(tool_name, hydrated_params, lollms_client_instance=self)
|
|
2944
|
-
|
|
2945
|
-
log_event_fn(f"Tool execution completed", MSG_TYPE.MSG_TYPE_TOOL_OUTPUT, meta={
|
|
2946
|
-
"result_status": tool_result.get("status", "unknown"),
|
|
2947
|
-
"has_error": "error" in tool_result
|
|
2948
|
-
})
|
|
2949
|
-
|
|
2950
|
-
elif tool_name == "local_tools::generate_image" and hasattr(self, "tti"):
|
|
2951
|
-
image_prompt = hydrated_params.get("prompt", "")
|
|
2952
|
-
log_event_fn(f"🎨 Generating image with prompt: '{image_prompt[:50]}...'", MSG_TYPE.MSG_TYPE_INFO)
|
|
2953
|
-
|
|
2954
|
-
# This would call your text-to-image functionality
|
|
2955
|
-
image_result = self.tti.generate_image(image_prompt) # Assuming this method exists
|
|
2956
|
-
if image_result:
|
|
2957
|
-
image_uuid = f"generated_image_{uuid.uuid4()}"
|
|
2958
|
-
asset_store[image_uuid] = {"type": "image", "content": image_result, "source": "generated"}
|
|
2959
|
-
tool_result = {"status": "success", "image_id": image_uuid, "prompt_used": image_prompt}
|
|
2960
|
-
else:
|
|
2961
|
-
tool_result = {"status": "failure", "error": "Image generation failed"}
|
|
2962
|
-
|
|
2963
|
-
else:
|
|
2964
|
-
tool_result = {"status": "failure", "error": f"Tool '{tool_name}' is not available or supported in this context."}
|
|
2965
|
-
|
|
2966
|
-
except Exception as e:
|
|
2967
|
-
error_msg = f"Exception during '{tool_name}' execution: {str(e)}"
|
|
2968
|
-
log_event_fn(error_msg, MSG_TYPE.MSG_TYPE_EXCEPTION)
|
|
2969
|
-
tool_result = {"status": "failure", "error": error_msg}
|
|
2970
|
-
|
|
2971
|
-
response_time = time.time() - start_time
|
|
2972
|
-
success = tool_result.get("status") == "success"
|
|
2973
|
-
|
|
2974
|
-
# Record performance
|
|
2975
|
-
performance_tracker.record_tool_usage(tool_name, success, confidence, response_time, tool_result.get("error"))
|
|
2976
|
-
|
|
2977
|
-
# Update task status
|
|
2978
|
-
if success and current_task_index < len(execution_plan.tasks):
|
|
2979
|
-
execution_plan.tasks[current_task_index].status = TaskStatus.COMPLETED
|
|
2980
|
-
completed_tasks.add(current_task_index)
|
|
2981
|
-
current_task_index += 1
|
|
2982
|
-
|
|
2983
|
-
# Enhanced observation logging
|
|
2984
|
-
observation_text = json.dumps(tool_result, indent=2)
|
|
2985
|
-
if len(observation_text) > 1000:
|
|
2986
|
-
# Truncate very long results for scratchpad
|
|
2987
|
-
truncated_result = {k: (str(v)[:200] + "..." if len(str(v)) > 200 else v) for k, v in tool_result.items()}
|
|
2988
|
-
observation_text = json.dumps(truncated_result, indent=2)
|
|
2989
|
-
|
|
2990
|
-
current_scratchpad += f"\n\n### Step {i+1}: Execution & Observation\n"
|
|
2991
|
-
current_scratchpad += f"**Tool Used**: {tool_name}\n"
|
|
2992
|
-
current_scratchpad += f"**Success**: {success}\n"
|
|
2993
|
-
current_scratchpad += f"**Response Time**: {response_time:.2f}s\n"
|
|
2994
|
-
current_scratchpad += f"**Result**:\n```json\n{observation_text}\n```"
|
|
2995
|
-
|
|
2996
|
-
# Track tool call
|
|
2997
|
-
tool_calls_this_turn.append({
|
|
2998
|
-
"name": tool_name,
|
|
2999
|
-
"params": tool_params,
|
|
3000
|
-
"result": tool_result,
|
|
3001
|
-
"response_time": response_time,
|
|
3002
|
-
"confidence": confidence,
|
|
3003
|
-
"reasoning": reasoning
|
|
3004
|
-
})
|
|
3005
|
-
|
|
3006
|
-
if success:
|
|
3007
|
-
log_event_fn(f"✅ Step {i+1} completed successfully", MSG_TYPE.MSG_TYPE_STEP_END, event_id=reasoning_step_id, meta={
|
|
3008
|
-
"tool_name": tool_name,
|
|
3009
|
-
"response_time": response_time,
|
|
3010
|
-
"confidence": confidence
|
|
3011
|
-
})
|
|
3012
|
-
else:
|
|
3013
|
-
error_detail = tool_result.get("error", "No error detail provided.")
|
|
3014
|
-
log_event_fn(f"⚠️ Step {i+1} completed with issues: {error_detail}", MSG_TYPE.MSG_TYPE_STEP_END, event_id=reasoning_step_id, meta={
|
|
3015
|
-
"tool_name": tool_name,
|
|
3016
|
-
"error": error_detail,
|
|
3017
|
-
"confidence": confidence
|
|
3018
|
-
})
|
|
3019
|
-
|
|
3020
|
-
# Add failure handling to scratchpad
|
|
3021
|
-
current_scratchpad += f"\n**Failure Analysis**: {error_detail}"
|
|
3022
|
-
current_scratchpad += f"\n**Next Steps**: Consider alternative approaches or tools"
|
|
3023
|
-
|
|
3024
|
-
# Log current progress
|
|
3025
|
-
completed_count = len(completed_tasks)
|
|
3026
|
-
total_tasks = len(execution_plan.tasks)
|
|
3027
|
-
if total_tasks > 0:
|
|
3028
|
-
progress = (completed_count / total_tasks) * 100
|
|
3029
|
-
log_event_fn(f"Progress: {completed_count}/{total_tasks} tasks completed ({progress:.1f}%)", MSG_TYPE.MSG_TYPE_STEP_PROGRESS, meta={"progress": progress})
|
|
3030
|
-
|
|
3031
|
-
# Check if all tasks are completed
|
|
3032
|
-
if completed_count >= total_tasks:
|
|
3033
|
-
log_event_fn("🎯 All planned tasks completed", MSG_TYPE.MSG_TYPE_INFO)
|
|
3034
|
-
break
|
|
3035
|
-
|
|
3036
|
-
except Exception as ex:
|
|
3037
|
-
log_event_fn(f"💥 Unexpected error in reasoning step {i+1}: {str(ex)}", MSG_TYPE.MSG_TYPE_ERROR, event_id=reasoning_step_id)
|
|
3038
|
-
trace_exception(ex)
|
|
3039
|
-
|
|
3040
|
-
# Add error to scratchpad for context
|
|
3041
|
-
current_scratchpad += f"\n\n### Step {i+1}: Unexpected Error\n**Error**: {str(ex)}\n**Recovery**: Continuing with adjusted approach"
|
|
3042
|
-
|
|
3043
|
-
log_event_fn("🔄 Recovering and continuing with next step", MSG_TYPE.MSG_TYPE_STEP_END, event_id=reasoning_step_id)
|
|
3044
|
-
|
|
3045
|
-
# Enhanced self-reflection
|
|
3046
|
-
if enable_self_reflection and len(tool_calls_this_turn) > 0:
|
|
3047
|
-
reflection_id = log_event_fn("🤔 Conducting comprehensive self-assessment...", MSG_TYPE.MSG_TYPE_STEP_START)
|
|
3048
|
-
try:
|
|
3049
|
-
reflection_prompt = f"""Conduct a thorough review of your work and assess the quality of your response to the user's request.
|
|
3050
|
-
|
|
3051
|
-
ORIGINAL REQUEST: "{original_user_prompt}"
|
|
3052
|
-
TOOLS USED: {len(tool_calls_this_turn)}
|
|
3053
|
-
PLAN REVISIONS: {plan_revision_count}
|
|
3054
|
-
|
|
3055
|
-
COMPLETE ANALYSIS:
|
|
3056
|
-
{current_scratchpad}
|
|
3057
|
-
|
|
3058
|
-
Evaluate your performance on multiple dimensions:
|
|
3059
|
-
|
|
3060
|
-
1. **Goal Achievement**: Did you fully address the user's request?
|
|
3061
|
-
2. **Process Efficiency**: Was your approach optimal given the available tools?
|
|
3062
|
-
3. **Information Quality**: Is the information you gathered accurate and relevant?
|
|
3063
|
-
4. **Decision Making**: Were your tool choices and parameters appropriate?
|
|
3064
|
-
5. **Adaptability**: How well did you handle unexpected results or plan changes?
|
|
3065
|
-
|
|
3066
|
-
Provide your assessment as JSON:
|
|
3067
|
-
{{
|
|
3068
|
-
"goal_achieved": true,
|
|
3069
|
-
"effectiveness_score": 0.85,
|
|
3070
|
-
"process_efficiency": 0.8,
|
|
3071
|
-
"information_quality": 0.9,
|
|
3072
|
-
"decision_making": 0.85,
|
|
3073
|
-
"adaptability": 0.7,
|
|
3074
|
-
"overall_confidence": 0.82,
|
|
3075
|
-
"strengths": ["Clear reasoning", "Good tool selection"],
|
|
3076
|
-
"areas_for_improvement": ["Could have been more efficient"],
|
|
3077
|
-
"summary": "Successfully completed the user's request with high quality results",
|
|
3078
|
-
"key_insights": ["Discovered that X was more important than initially thought"]
|
|
3079
|
-
}}"""
|
|
1673
|
+
param_prompt = f"""Generate the optimal parameters for the selected tool to fulfill the user's request.
|
|
3080
1674
|
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
"overall_confidence": "number",
|
|
3091
|
-
"strengths": "array",
|
|
3092
|
-
"areas_for_improvement": "array",
|
|
3093
|
-
"summary": "string",
|
|
3094
|
-
"key_insights": "array"
|
|
3095
|
-
},
|
|
3096
|
-
temperature=0.3,
|
|
3097
|
-
**llm_generation_kwargs
|
|
3098
|
-
)
|
|
1675
|
+
FULL discussion and USER REQUEST:
|
|
1676
|
+
{prompt}
|
|
1677
|
+
SELECTED TOOL: {json.dumps(tool_spec, indent=2)}
|
|
1678
|
+
CONTEXT: {context or "None"}
|
|
1679
|
+
|
|
1680
|
+
Analyze the user's request carefully and provide the most appropriate parameters.
|
|
1681
|
+
If the request has implicit requirements, infer them intelligently.
|
|
1682
|
+
|
|
1683
|
+
Output the parameters as JSON: {{"tool_params": {{...}}}}"""
|
|
3099
1684
|
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
}
|
|
1685
|
+
log_prompt("Parameter Generation Prompt", param_prompt)
|
|
1686
|
+
param_data = self.generate_structured_content(prompt=param_prompt, schema={"tool_params": "object"}, temperature=0.1, **llm_generation_kwargs)
|
|
1687
|
+
tool_params = param_data.get("tool_params", {}) if param_data else {}
|
|
1688
|
+
|
|
1689
|
+
log_event(f"Generated parameters: {json.dumps(tool_params)}", MSG_TYPE.MSG_TYPE_INFO)
|
|
1690
|
+
|
|
1691
|
+
start_time, sources, tool_result = time.time(), [], {}
|
|
1692
|
+
if tool_name in rag_registry:
|
|
1693
|
+
query = tool_params.get("query", prompt)
|
|
1694
|
+
log_event(f"Searching knowledge base with query: '{query}'", MSG_TYPE.MSG_TYPE_INFO)
|
|
1695
|
+
rag_fn = rag_registry[tool_name]
|
|
1696
|
+
raw_results = rag_fn(query=query, rag_top_k=rag_top_k, rag_min_similarity_percent=rag_min_similarity_percent)
|
|
1697
|
+
docs = [d for d in (raw_results.get("results", []) if isinstance(raw_results, dict) else raw_results or [])]
|
|
1698
|
+
tool_result = {"status": "success", "results": docs}
|
|
1699
|
+
sources = [{"title":d["title"], "content":d["content"], "source": tool_name, "metadata": d.get("metadata", {}), "score": d.get("score", 0.0)} for d in docs]
|
|
1700
|
+
log_event(sources, MSG_TYPE.MSG_TYPE_SOURCES_LIST)
|
|
1701
|
+
log_event(f"Retrieved {len(docs)} relevant documents", MSG_TYPE.MSG_TYPE_INFO)
|
|
1702
|
+
elif hasattr(self, "mcp") and "local_tools" not in tool_name:
|
|
1703
|
+
log_event(f"Executing MCP tool: {tool_name}", MSG_TYPE.MSG_TYPE_TOOL_CALL, meta={"tool_name": tool_name, "params": tool_params})
|
|
1704
|
+
tool_result = self.mcp.execute_tool(tool_name, tool_params, lollms_client_instance=self)
|
|
1705
|
+
log_event(f"Tool execution completed", MSG_TYPE.MSG_TYPE_TOOL_OUTPUT, meta={"result_status": tool_result.get("status", "unknown")})
|
|
3114
1706
|
else:
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
1707
|
+
tool_result = {"status": "failure", "error": f"Tool '{tool_name}' could not be executed in single-step mode."}
|
|
1708
|
+
|
|
1709
|
+
if tool_result.get("status","success") != "success" or "error" in tool_result:
|
|
1710
|
+
error_detail = tool_result.get("error", "Unknown tool error in single-step mode.")
|
|
1711
|
+
raise RuntimeError(error_detail)
|
|
3119
1712
|
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
1713
|
+
response_time = time.time() - start_time
|
|
1714
|
+
tool_calls_this_turn = [{"name": tool_name, "params": tool_params, "result": tool_result, "response_time": response_time}]
|
|
1715
|
+
|
|
1716
|
+
# Enhanced synthesis prompt
|
|
1717
|
+
synthesis_prompt = f"""Create a comprehensive and user-friendly response based on the tool execution results.
|
|
3124
1718
|
|
|
3125
|
-
|
|
3126
|
-
|
|
1719
|
+
FULL DISCUSSON and USER REQUEST:
|
|
1720
|
+
{prompt}
|
|
1721
|
+
TOOL USED: {tool_name}
|
|
1722
|
+
TOOL RESULT: {json.dumps(tool_result, indent=2)}
|
|
3127
1723
|
|
|
3128
|
-
|
|
3129
|
-
|
|
1724
|
+
Guidelines for your response:
|
|
1725
|
+
1. Be direct and helpful
|
|
1726
|
+
2. Synthesize the information clearly
|
|
1727
|
+
3. Address the user's specific needs
|
|
1728
|
+
4. If the tool provided data, present it in an organized way
|
|
1729
|
+
5. If relevant, mention any limitations or additional context
|
|
3130
1730
|
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
1731
|
+
RESPONSE:"""
|
|
1732
|
+
|
|
1733
|
+
log_event("Synthesizing final response", MSG_TYPE.MSG_TYPE_INFO)
|
|
1734
|
+
final_answer = self.generate_text(prompt=synthesis_prompt, system_prompt=system_prompt, stream=streaming_callback is not None, streaming_callback=streaming_callback, temperature=final_answer_temperature, **llm_generation_kwargs)
|
|
1735
|
+
final_answer = self.remove_thinking_blocks(final_answer)
|
|
1736
|
+
|
|
1737
|
+
log_event("✅ Single-tool execution completed successfully", MSG_TYPE.MSG_TYPE_STEP_END, event_id=synthesis_id)
|
|
1738
|
+
return {"final_answer": final_answer, "tool_calls": tool_calls_this_turn, "sources": sources, "error": None, "clarification_required": False, "final_scratchpad": f"Strategy: SINGLE_TOOL\nTool: {tool_name}\nResult: Success\nResponse Time: {response_time:.2f}s"}
|
|
3138
1739
|
|
|
3139
|
-
|
|
1740
|
+
except Exception as e:
|
|
1741
|
+
log_event(f"Single-tool execution failed: {e}", MSG_TYPE.MSG_TYPE_EXCEPTION, event_id=synthesis_id)
|
|
1742
|
+
log_event("Escalating to complex planning approach", MSG_TYPE.MSG_TYPE_INFO)
|
|
3140
1743
|
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
temperature=final_answer_temperature,
|
|
1744
|
+
# Execute complex reasoning with enhanced capabilities
|
|
1745
|
+
return self._execute_complex_reasoning_loop(
|
|
1746
|
+
prompt=prompt, context=context, system_prompt=system_prompt,
|
|
1747
|
+
reasoning_system_prompt=reasoning_system_prompt, images=images,
|
|
1748
|
+
max_reasoning_steps=max_reasoning_steps, decision_temperature=decision_temperature,
|
|
1749
|
+
final_answer_temperature=final_answer_temperature, streaming_callback=streaming_callback,
|
|
1750
|
+
debug=debug, enable_self_reflection=enable_self_reflection,
|
|
1751
|
+
all_visible_tools=all_visible_tools, rag_registry=rag_registry, rag_tool_specs=rag_tool_specs,
|
|
1752
|
+
log_event_fn=log_event, log_prompt_fn=log_prompt, max_scratchpad_size=max_scratchpad_size,
|
|
3151
1753
|
**llm_generation_kwargs
|
|
3152
1754
|
)
|
|
3153
|
-
|
|
3154
|
-
if isinstance(final_answer_text, dict) and "error" in final_answer_text:
|
|
3155
|
-
log_event_fn(f"Final synthesis failed: {final_answer_text['error']}", MSG_TYPE.MSG_TYPE_ERROR, event_id=synthesis_id)
|
|
3156
|
-
return {
|
|
3157
|
-
"final_answer": "I encountered an issue while preparing my final response. Please let me know if you'd like me to try again.",
|
|
3158
|
-
"error": final_answer_text["error"],
|
|
3159
|
-
"final_scratchpad": current_scratchpad,
|
|
3160
|
-
"tool_calls": tool_calls_this_turn,
|
|
3161
|
-
"sources": sources_this_turn,
|
|
3162
|
-
"decision_history": decision_history
|
|
3163
|
-
}
|
|
3164
|
-
|
|
3165
|
-
final_answer = self.remove_thinking_blocks(final_answer_text)
|
|
3166
|
-
|
|
3167
|
-
# Calculate overall performance metrics
|
|
3168
|
-
overall_confidence = sum(call.get('confidence', 0.5) for call in tool_calls_this_turn) / max(len(tool_calls_this_turn), 1)
|
|
3169
|
-
successful_calls = sum(1 for call in tool_calls_this_turn if call.get('result', {}).get('status') == 'success')
|
|
3170
|
-
success_rate = successful_calls / max(len(tool_calls_this_turn), 1)
|
|
3171
|
-
|
|
3172
|
-
log_event_fn("✅ Comprehensive response ready", MSG_TYPE.MSG_TYPE_STEP_END, event_id=synthesis_id, meta={
|
|
3173
|
-
"final_answer_length": len(final_answer),
|
|
3174
|
-
"total_tools_used": len(tool_calls_this_turn),
|
|
3175
|
-
"success_rate": success_rate,
|
|
3176
|
-
"overall_confidence": overall_confidence
|
|
3177
|
-
})
|
|
3178
|
-
|
|
3179
|
-
return {
|
|
3180
|
-
"final_answer": final_answer,
|
|
3181
|
-
"final_scratchpad": current_scratchpad,
|
|
3182
|
-
"tool_calls": tool_calls_this_turn,
|
|
3183
|
-
"sources": sources_this_turn,
|
|
3184
|
-
"decision_history": decision_history,
|
|
3185
|
-
"performance_stats": {
|
|
3186
|
-
"total_steps": len(tool_calls_this_turn),
|
|
3187
|
-
"successful_steps": successful_calls,
|
|
3188
|
-
"success_rate": success_rate,
|
|
3189
|
-
"average_confidence": overall_confidence,
|
|
3190
|
-
"plan_revisions": plan_revision_count,
|
|
3191
|
-
"total_reasoning_steps": len(decision_history)
|
|
3192
|
-
},
|
|
3193
|
-
"plan_evolution": {
|
|
3194
|
-
"initial_tasks": len(execution_plan.tasks),
|
|
3195
|
-
"final_version": current_plan_version,
|
|
3196
|
-
"total_revisions": plan_revision_count
|
|
3197
|
-
},
|
|
3198
|
-
"clarification_required": False,
|
|
3199
|
-
"overall_confidence": overall_confidence,
|
|
3200
|
-
"error": None
|
|
3201
|
-
}
|
|
3202
|
-
|
|
3203
1755
|
|
|
3204
1756
|
def _execute_complex_reasoning_loop(
|
|
3205
1757
|
self, prompt, context, system_prompt, reasoning_system_prompt, images,
|
|
@@ -3219,7 +1771,7 @@ FINAL RESPONSE:"""
|
|
|
3219
1771
|
}
|
|
3220
1772
|
if tool_name in descriptions:
|
|
3221
1773
|
return descriptions[tool_name]
|
|
3222
|
-
if "
|
|
1774
|
+
if "rag::" in tool_name:
|
|
3223
1775
|
return f"🔍 Searching {tool_name.split('::')[-1]} knowledge base"
|
|
3224
1776
|
if requires_code:
|
|
3225
1777
|
return "💻 Processing code"
|
|
@@ -3288,7 +1840,7 @@ FINAL RESPONSE:"""
|
|
|
3288
1840
|
|
|
3289
1841
|
# Enhanced planning phase
|
|
3290
1842
|
planning_step_id = log_event_fn("📋 Creating adaptive execution plan...", MSG_TYPE.MSG_TYPE_STEP_START)
|
|
3291
|
-
execution_plan = planner.decompose_task(original_user_prompt, context or "")
|
|
1843
|
+
execution_plan = planner.decompose_task(original_user_prompt, context or "", "\n".join([f"{tool['name']}:{tool['description']}" for tool in all_visible_tools]))
|
|
3292
1844
|
current_plan_version = 1
|
|
3293
1845
|
|
|
3294
1846
|
log_event_fn(f"Initial plan created with {len(execution_plan.tasks)} tasks", MSG_TYPE.MSG_TYPE_INFO, meta={
|
|
@@ -3370,7 +1922,7 @@ Provide your decision as JSON:
|
|
|
3370
1922
|
"tool_name": "exact_tool_name",
|
|
3371
1923
|
"requires_code_input": false,
|
|
3372
1924
|
"requires_image_input": false,
|
|
3373
|
-
"confidence":
|
|
1925
|
+
"confidence": 80
|
|
3374
1926
|
}},
|
|
3375
1927
|
"plan_status": "on_track" // or "needs_revision" if you want to change the plan
|
|
3376
1928
|
}}"""
|
|
@@ -3399,7 +1951,7 @@ Provide your decision as JSON:
|
|
|
3399
1951
|
tool_name = action.get("tool_name")
|
|
3400
1952
|
requires_code = action.get("requires_code_input", False)
|
|
3401
1953
|
requires_image = action.get("requires_image_input", False)
|
|
3402
|
-
confidence = action.get("confidence",
|
|
1954
|
+
confidence = action.get("confidence", 50)
|
|
3403
1955
|
|
|
3404
1956
|
# Track the decision
|
|
3405
1957
|
decision_history.append({
|
|
@@ -3435,6 +1987,34 @@ Provide your decision as JSON:
|
|
|
3435
1987
|
CURRENT ANALYSIS:
|
|
3436
1988
|
{current_scratchpad}
|
|
3437
1989
|
|
|
1990
|
+
Generate a clear, specific question that will help you proceed effectively:"""
|
|
1991
|
+
|
|
1992
|
+
question = self.generate_text(clarification_prompt, temperature=0.3)
|
|
1993
|
+
question = self.remove_thinking_blocks(question)
|
|
1994
|
+
|
|
1995
|
+
log_event_fn("❓ Clarification needed from user", MSG_TYPE.MSG_TYPE_INFO)
|
|
1996
|
+
return {
|
|
1997
|
+
"final_answer": question,
|
|
1998
|
+
"clarification_required": True,
|
|
1999
|
+
"final_scratchpad": current_scratchpad,
|
|
2000
|
+
"tool_calls": tool_calls_this_turn,
|
|
2001
|
+
"sources": sources_this_turn,
|
|
2002
|
+
"error": None,
|
|
2003
|
+
"decision_history": decision_history
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
# Handle final answer
|
|
2007
|
+
if tool_name == "local_tools::final_answer":
|
|
2008
|
+
log_event_fn("🎯 Ready to provide final answer", MSG_TYPE.MSG_TYPE_INFO)
|
|
2009
|
+
break
|
|
2010
|
+
|
|
2011
|
+
# Handle clarification request
|
|
2012
|
+
if tool_name == "local_tools::request_clarification":
|
|
2013
|
+
clarification_prompt = f"""Based on your analysis, what specific information do you need from the user?
|
|
2014
|
+
|
|
2015
|
+
CURRENT ANALYSIS:
|
|
2016
|
+
{current_scratchpad}
|
|
2017
|
+
|
|
3438
2018
|
Generate a clear, specific question that will help you proceed effectively:"""
|
|
3439
2019
|
|
|
3440
2020
|
question = self.generate_text(clarification_prompt, temperature=0.3)
|
|
@@ -5735,187 +4315,229 @@ Provide the final aggregated answer in {output_format} format, directly addressi
|
|
|
5735
4315
|
def long_context_processing(
|
|
5736
4316
|
self,
|
|
5737
4317
|
text_to_process: str,
|
|
5738
|
-
contextual_prompt:
|
|
5739
|
-
|
|
5740
|
-
|
|
4318
|
+
contextual_prompt: str,
|
|
4319
|
+
system_prompt: str | None = None,
|
|
4320
|
+
strategy_override: str | None = None, # 'narrative' or 'structured'
|
|
5741
4321
|
streaming_callback: Optional[Callable] = None,
|
|
4322
|
+
debug: bool = True,
|
|
5742
4323
|
**kwargs
|
|
5743
4324
|
) -> str:
|
|
5744
4325
|
"""
|
|
5745
|
-
|
|
4326
|
+
Adaptive Long Context Processing.
|
|
4327
|
+
Automatically detects if content is Narrative (Prose) or Structured (Data/Logs)
|
|
4328
|
+
and applies the optimal processing strategy.
|
|
4329
|
+
"""
|
|
5746
4330
|
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
4331
|
+
# --- Helper: Token Counting ---
|
|
4332
|
+
def get_tokens(text):
|
|
4333
|
+
return len(self.tokenize(text))
|
|
4334
|
+
|
|
4335
|
+
# --- Helper: Smart Chunking ---
|
|
4336
|
+
def smart_chunk_text(text, max_chunk_tokens, mode='narrative'):
|
|
4337
|
+
# For data, we split by lines to avoid breaking rows. For text, paragraphs.
|
|
4338
|
+
delimiter = '\n' if mode == 'structured' else '\n\n'
|
|
4339
|
+
segments = text.split(delimiter)
|
|
4340
|
+
|
|
4341
|
+
chunks = []
|
|
4342
|
+
current_chunk = []
|
|
4343
|
+
current_len = 0
|
|
4344
|
+
|
|
4345
|
+
for seg in segments:
|
|
4346
|
+
seg_tokens = get_tokens(seg)
|
|
4347
|
+
# Hard limit safety for massive single lines
|
|
4348
|
+
if seg_tokens > max_chunk_tokens:
|
|
4349
|
+
# If a single row/para is massive, we force split it
|
|
4350
|
+
if current_chunk:
|
|
4351
|
+
chunks.append(delimiter.join(current_chunk))
|
|
4352
|
+
current_chunk = []
|
|
4353
|
+
current_len = 0
|
|
4354
|
+
chunks.append(seg)
|
|
4355
|
+
continue
|
|
5750
4356
|
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
on the process (e.g., which chunk is being processed).
|
|
5763
|
-
It receives a message, a message type, and optional metadata.
|
|
5764
|
-
Defaults to None.
|
|
5765
|
-
**kwargs: Additional keyword arguments to be passed to the generation method (e.g., temperature, top_p).
|
|
4357
|
+
if current_len + seg_tokens > max_chunk_tokens and current_chunk:
|
|
4358
|
+
chunks.append(delimiter.join(current_chunk))
|
|
4359
|
+
current_chunk = []
|
|
4360
|
+
current_len = 0
|
|
4361
|
+
|
|
4362
|
+
current_chunk.append(seg)
|
|
4363
|
+
current_len += seg_tokens
|
|
4364
|
+
|
|
4365
|
+
if current_chunk:
|
|
4366
|
+
chunks.append(delimiter.join(current_chunk))
|
|
4367
|
+
return chunks
|
|
5766
4368
|
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
if not text_to_process and len(kwargs.get("images",[]))==0 and contextual_prompt is None:
|
|
5771
|
-
return ""
|
|
5772
|
-
if not text_to_process:
|
|
5773
|
-
text_to_process=""
|
|
5774
|
-
tokens = []
|
|
5775
|
-
else:
|
|
5776
|
-
# Use the binding's tokenizer for accurate chunking
|
|
5777
|
-
tokens = self.llm.tokenize(text_to_process)
|
|
5778
|
-
if chunk_size_tokens is None:
|
|
5779
|
-
chunk_size_tokens = self.llm.default_ctx_size//2
|
|
4369
|
+
# --- 0. Pre-computation ---
|
|
4370
|
+
context_size = self.llm.get_context_size() or 8192
|
|
4371
|
+
total_tokens = get_tokens(text_to_process)
|
|
5780
4372
|
|
|
5781
|
-
if
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
4373
|
+
if debug: print(f"🔧 DEBUG: Input Tokens: {total_tokens:,} | Context: {context_size:,}")
|
|
4374
|
+
|
|
4375
|
+
# One-shot optimization
|
|
4376
|
+
if total_tokens < (context_size * 0.7):
|
|
4377
|
+
if debug: print("🔧 DEBUG: One-shot path.")
|
|
4378
|
+
if streaming_callback: streaming_callback("Processing in single pass...", MSG_TYPE.MSG_TYPE_STEP_START)
|
|
4379
|
+
prompt = f"{contextual_prompt}\n\n--- CONTENT ---\n{text_to_process}"
|
|
4380
|
+
return self.remove_thinking_blocks(self.llm.generate_text(prompt, system_prompt=system_prompt, **kwargs))
|
|
4381
|
+
|
|
4382
|
+
# --- 1. Phase 1: The Scout (Strategy Selection) ---
|
|
4383
|
+
content_type = "narrative"
|
|
4384
|
+
schema_info = ""
|
|
4385
|
+
|
|
4386
|
+
if strategy_override:
|
|
4387
|
+
content_type = strategy_override
|
|
4388
|
+
else:
|
|
4389
|
+
if debug: print("🔧 DEBUG: Scout is analyzing content nature...")
|
|
4390
|
+
if streaming_callback: streaming_callback("Analyzing content structure...", MSG_TYPE.MSG_TYPE_STEP)
|
|
5797
4391
|
|
|
5798
|
-
|
|
4392
|
+
# Peek at the first 1000 characters (enough to see CSV headers or JSON braces)
|
|
4393
|
+
sample_text = text_to_process[:4000]
|
|
5799
4394
|
|
|
5800
|
-
|
|
5801
|
-
|
|
4395
|
+
scout_prompt = (
|
|
4396
|
+
f"Analyze this text sample:\n---\n{sample_text}\n---\n\n"
|
|
4397
|
+
f"Is this primarily:\n"
|
|
4398
|
+
f"A) NARRATIVE (Prose, Articles, Transcripts, Story)\n"
|
|
4399
|
+
f"B) STRUCTURED_DATA (CSV, Markdown Tables, JSON, Logs, Code Lists)\n\n"
|
|
4400
|
+
f"Return JSON with keys: 'type' ('narrative' or 'structured') and 'schema_notes' (brief description of columns/fields if structured)."
|
|
4401
|
+
)
|
|
5802
4402
|
|
|
5803
|
-
|
|
4403
|
+
try:
|
|
4404
|
+
scout_res = self.remove_thinking_blocks(self.llm.generate_text(scout_prompt, **kwargs))
|
|
4405
|
+
# Simple parsing heuristic if JSON fails
|
|
4406
|
+
if "structured" in scout_res.lower() and "narrative" not in scout_res.lower().split("type"):
|
|
4407
|
+
content_type = "structured"
|
|
4408
|
+
schema_info = scout_res # Keep the whole reasoning as context
|
|
4409
|
+
else:
|
|
4410
|
+
content_type = "narrative"
|
|
4411
|
+
except:
|
|
4412
|
+
content_type = "narrative" # Default fail-safe
|
|
5804
4413
|
|
|
5805
|
-
|
|
5806
|
-
chunks = []
|
|
5807
|
-
step = chunk_size_tokens - overlap_tokens
|
|
5808
|
-
for i in range(0, len(tokens), step):
|
|
5809
|
-
chunk_tokens = tokens[i:i + chunk_size_tokens]
|
|
5810
|
-
chunk_text = self.llm.detokenize(chunk_tokens)
|
|
5811
|
-
chunks.append(chunk_text)
|
|
4414
|
+
if debug: print(f"🔧 DEBUG: Strategy Selected: {content_type.upper()}")
|
|
5812
4415
|
|
|
5813
|
-
|
|
4416
|
+
# --- 2. Phase 2: Execution ---
|
|
5814
4417
|
|
|
5815
|
-
#
|
|
5816
|
-
|
|
4418
|
+
# Calculate Chunk Size (Leave room for prompts and outputs)
|
|
4419
|
+
reserved_overhead = 2500
|
|
4420
|
+
safe_chunk_size = context_size - reserved_overhead
|
|
5817
4421
|
|
|
5818
|
-
#
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
"Respond only with the extracted information from the current chunk without repeating things that are already in the scratchpad.\n"
|
|
5833
|
-
"Strictly adhere to the Global objective content for the extraction phase.\n"
|
|
5834
|
-
"Do not add comments.\n"
|
|
5835
|
-
)
|
|
5836
|
-
if "system_prompt" in kwargs:
|
|
5837
|
-
system_prompt += "-- Extra instructions --\n"+ kwargs["system_prompt"] +"\n"
|
|
5838
|
-
del kwargs["system_prompt"]
|
|
5839
|
-
chunk_summary_prompt_template = f"--- Global objective ---\n{summarization_objective}\n\n--- Text Excerpt ---\n{{chunk_text}}"
|
|
4422
|
+
# Split
|
|
4423
|
+
chunks = smart_chunk_text(text_to_process, safe_chunk_size, mode=content_type)
|
|
4424
|
+
total_chunks = len(chunks)
|
|
4425
|
+
extraction_accumulator = []
|
|
4426
|
+
|
|
4427
|
+
# =======================================================
|
|
4428
|
+
# STRATEGY A: NARRATIVE (Context-Aware Dual Stream)
|
|
4429
|
+
# =======================================================
|
|
4430
|
+
if content_type == "narrative":
|
|
4431
|
+
context_scratchpad = "Start of content."
|
|
4432
|
+
|
|
4433
|
+
# Generate Criteria (from previous strategy)
|
|
4434
|
+
strategy_prompt = f"Objective: {contextual_prompt}\nList bullet points of what to extract from this narrative."
|
|
4435
|
+
extraction_criteria = self.remove_thinking_blocks(self.llm.generate_text(strategy_prompt, **kwargs))
|
|
5840
4436
|
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
4437
|
+
for i, chunk in enumerate(chunks):
|
|
4438
|
+
if streaming_callback:
|
|
4439
|
+
streaming_callback(f"Narrative Chunk {i+1}/{total_chunks}", MSG_TYPE.MSG_TYPE_STEP_START, {"progress": (i/total_chunks)*80})
|
|
4440
|
+
|
|
4441
|
+
prompt = (
|
|
4442
|
+
f"### OBJECTIVE\n{contextual_prompt}\n\n"
|
|
4443
|
+
f"### CRITERIA\n{extraction_criteria}\n\n"
|
|
4444
|
+
f"### PREVIOUS CONTEXT\n{context_scratchpad}\n\n"
|
|
4445
|
+
f"### CURRENT CHUNK\n{chunk}\n\n"
|
|
4446
|
+
f"### TASK\n"
|
|
4447
|
+
f"1. Update Context: Summarize flow for next chunk.\n"
|
|
4448
|
+
f"2. Extract Data: Extract facts matching criteria.\n"
|
|
4449
|
+
f"Output format: <context_update>...</context_update> <extracted_data>...</extracted_data>"
|
|
5848
4450
|
)
|
|
5849
|
-
try:
|
|
5850
|
-
prompt = chunk_summary_prompt_template.format(chunk_text=chunk)
|
|
5851
|
-
except Exception as ex:
|
|
5852
|
-
ASCIIColors.warning(ex)
|
|
5853
|
-
prompt = chunk_summary_prompt_template.replace("{chunk_text}", chunk)
|
|
5854
|
-
processed_system_prompt = system_prompt.format(chunk_id=i,scratchpad="\n\n---\n\n".join(chunk_summaries))
|
|
5855
|
-
try:
|
|
5856
|
-
# Generate summary for the current chunk
|
|
5857
|
-
chunk_summary = self.generate_text(prompt, system_prompt=processed_system_prompt, **kwargs)
|
|
5858
|
-
chunk_summaries.append(chunk_summary)
|
|
5859
4451
|
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
4452
|
+
res = self.remove_thinking_blocks(self.llm.generate_text(prompt, **kwargs))
|
|
4453
|
+
|
|
4454
|
+
# Parse
|
|
4455
|
+
new_ctx = "Flow continues..."
|
|
4456
|
+
new_data = res
|
|
4457
|
+
if "<context_update>" in res:
|
|
4458
|
+
new_ctx = res.split("<context_update>")[1].split("</context_update>")[0]
|
|
4459
|
+
if "<extracted_data>" in res:
|
|
4460
|
+
new_data = res.split("<extracted_data>")[1].split("</extracted_data>")[0]
|
|
4461
|
+
|
|
4462
|
+
context_scratchpad = new_ctx
|
|
4463
|
+
extraction_accumulator.append(new_data)
|
|
4464
|
+
|
|
4465
|
+
# Compression check
|
|
4466
|
+
if get_tokens("\n".join(extraction_accumulator)) > context_size * 0.6:
|
|
4467
|
+
if debug: print("🔧 DEBUG: Compressing narrative accumulator...")
|
|
4468
|
+
comp_prompt = f"Summarize these extracted notes without losing facts:\n\n" + "\n".join(extraction_accumulator)
|
|
4469
|
+
compressed = self.remove_thinking_blocks(self.llm.generate_text(comp_prompt, **kwargs))
|
|
4470
|
+
extraction_accumulator = [compressed]
|
|
4471
|
+
|
|
4472
|
+
# =======================================================
|
|
4473
|
+
# STRATEGY B: STRUCTURED DATA (Map-Reduce / Batch)
|
|
4474
|
+
# =======================================================
|
|
4475
|
+
else:
|
|
4476
|
+
# For data, we don't need narrative context. We need SCHEMA context.
|
|
4477
|
+
if debug: print(f"🔧 DEBUG: Schema Context: {schema_info}")
|
|
4478
|
+
|
|
4479
|
+
for i, chunk in enumerate(chunks):
|
|
4480
|
+
if streaming_callback:
|
|
4481
|
+
streaming_callback(f"Data Batch {i+1}/{total_chunks}", MSG_TYPE.MSG_TYPE_STEP_START, {"progress": (i/total_chunks)*80})
|
|
4482
|
+
|
|
4483
|
+
# The Data Prompt is different: It treats the chunk as a standalone dataset
|
|
4484
|
+
prompt = (
|
|
4485
|
+
f"### ROLE\n"
|
|
4486
|
+
f"You are a Data Analyst. You are processing batch {i+1} of {total_chunks}.\n\n"
|
|
4487
|
+
f"### GLOBAL OBJECTIVE\n"
|
|
4488
|
+
f"{contextual_prompt}\n\n"
|
|
4489
|
+
f"### DATA SCHEMA / HINTS\n"
|
|
4490
|
+
f"{schema_info}\n\n"
|
|
4491
|
+
f"### DATA BATCH\n"
|
|
4492
|
+
f"{chunk}\n\n"
|
|
4493
|
+
f"### INSTRUCTIONS\n"
|
|
4494
|
+
f"Analyze this specific batch of data to fulfill the objective.\n"
|
|
4495
|
+
f"- If the objective asks for aggregation (counts, averages), calculate them for THIS BATCH only.\n"
|
|
4496
|
+
f"- If the objective asks for pattern extraction, list patterns found in THIS BATCH.\n"
|
|
4497
|
+
f"- If the objective asks for row-by-row extraction, process only the relevant rows.\n"
|
|
4498
|
+
f"- Output strictly the findings. Do not summarize the 'idea' of the data, extract the ACTUAL data/metrics."
|
|
4499
|
+
)
|
|
4500
|
+
|
|
4501
|
+
res = self.remove_thinking_blocks(self.llm.generate_text(prompt, **kwargs))
|
|
4502
|
+
extraction_accumulator.append(f"--- Batch {i+1} Findings ---\n{res}")
|
|
4503
|
+
|
|
4504
|
+
# Compression for Data (Map-Reduce style)
|
|
4505
|
+
# If we have too many batch findings, we do an intermediate "Reduce" step
|
|
4506
|
+
if get_tokens("\n".join(extraction_accumulator)) > context_size * 0.6:
|
|
4507
|
+
if debug: print("🔧 DEBUG: Reducing intermediate data batches...")
|
|
4508
|
+
reduce_prompt = (
|
|
4509
|
+
f"### OBJECTIVE\n{contextual_prompt}\n\n"
|
|
4510
|
+
f"### INTERMEDIATE BATCH RESULTS\n" + "\n".join(extraction_accumulator) + "\n\n"
|
|
4511
|
+
f"### TASK\n"
|
|
4512
|
+
f"Aggregate these batch results into a consolidated report. "
|
|
4513
|
+
f"Sum up counts, combine lists, and merge patterns. Discard redundant headers."
|
|
5866
4514
|
)
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
if streaming_callback:
|
|
5870
|
-
streaming_callback(f"Failed to process chunk {i+1}: {e}", MSG_TYPE.MSG_TYPE_EXCEPTION)
|
|
5871
|
-
# Still add a placeholder to not break the chain
|
|
5872
|
-
chunk_summaries.append(f"[Error processing chunk {i+1}]")
|
|
5873
|
-
|
|
5874
|
-
# --- Stage 2: Final Synthesis of All Chunk Summaries ---
|
|
5875
|
-
progress_before_synthesis = (len(chunks) / total_steps) * 100
|
|
5876
|
-
if streaming_callback:
|
|
5877
|
-
streaming_callback(
|
|
5878
|
-
"Processing the scratchpad content into a final version...",
|
|
5879
|
-
MSG_TYPE.MSG_TYPE_STEP_START,
|
|
5880
|
-
{"id": "final_synthesis", "progress": progress_before_synthesis}
|
|
5881
|
-
)
|
|
4515
|
+
reduced = self.remove_thinking_blocks(self.llm.generate_text(reduce_prompt, **kwargs))
|
|
4516
|
+
extraction_accumulator = [reduced]
|
|
5882
4517
|
|
|
5883
|
-
|
|
4518
|
+
# --- 3. Phase 3: Final Synthesis ---
|
|
4519
|
+
if debug: print("🔧 DEBUG: Final Synthesis")
|
|
4520
|
+
if streaming_callback: streaming_callback("Synthesizing final report...", MSG_TYPE.MSG_TYPE_STEP_START, {"progress": 95})
|
|
5884
4521
|
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
"Your job is to use the extracted information to fulfill the user prompt objectives.\n"
|
|
5896
|
-
"Make sure you respect the user formatting if provided and if not, then use markdown output format."
|
|
5897
|
-
"-- Sequencial Scratchpad --\n"
|
|
5898
|
-
f"{combined_summaries}\n"
|
|
5899
|
-
"** Important **\n"
|
|
5900
|
-
"Respond only with the requested task without extra comments unless told to.\n"
|
|
5901
|
-
"Strictly adhere to the Global objective content for the extraction phase.\n"
|
|
5902
|
-
"Do not add comments.\n"
|
|
5903
|
-
)
|
|
5904
|
-
final_synthesis_prompt = (
|
|
5905
|
-
f"--- Global objective ---\n{synthesis_objective}\n\n"
|
|
5906
|
-
"--- Final Response ---"
|
|
4522
|
+
final_data_block = "\n".join(extraction_accumulator)
|
|
4523
|
+
|
|
4524
|
+
final_prompt = (
|
|
4525
|
+
f"### GLOBAL OBJECTIVE\n{contextual_prompt}\n\n"
|
|
4526
|
+
f"### PROCESSED DATA/EVIDENCE\n{final_data_block}\n\n"
|
|
4527
|
+
f"### INSTRUCTIONS\n"
|
|
4528
|
+
f"Synthesize the final answer based ONLY on the processed evidence above.\n"
|
|
4529
|
+
f"If this was a data analysis task, provide the final metrics/aggregations.\n"
|
|
4530
|
+
f"If this was a narrative task, provide the final summary.\n"
|
|
4531
|
+
f"Format clearly with Markdown."
|
|
5907
4532
|
)
|
|
5908
|
-
|
|
5909
|
-
final_answer = self.generate_text(final_synthesis_prompt, system_prompt=system_prompt, **kwargs)
|
|
5910
4533
|
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
)
|
|
4534
|
+
final_answer = self.remove_thinking_blocks(self.llm.generate_text(final_prompt, system_prompt=system_prompt, **kwargs))
|
|
4535
|
+
|
|
4536
|
+
if streaming_callback: streaming_callback("Done.", MSG_TYPE.MSG_TYPE_STEP_END, {"progress": 100})
|
|
4537
|
+
|
|
4538
|
+
return final_answer
|
|
5917
4539
|
|
|
5918
|
-
|
|
4540
|
+
|
|
5919
4541
|
|
|
5920
4542
|
def chunk_text(text, tokenizer, detokenizer, chunk_size, overlap, use_separators=True):
|
|
5921
4543
|
"""
|