lollms-client 1.4.1__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.
Files changed (64) hide show
  1. lollms_client/__init__.py +1 -1
  2. lollms_client/llm_bindings/azure_openai/__init__.py +2 -2
  3. lollms_client/llm_bindings/claude/__init__.py +125 -34
  4. lollms_client/llm_bindings/gemini/__init__.py +261 -159
  5. lollms_client/llm_bindings/grok/__init__.py +52 -14
  6. lollms_client/llm_bindings/groq/__init__.py +2 -2
  7. lollms_client/llm_bindings/hugging_face_inference_api/__init__.py +2 -2
  8. lollms_client/llm_bindings/litellm/__init__.py +1 -1
  9. lollms_client/llm_bindings/llamacpp/__init__.py +18 -11
  10. lollms_client/llm_bindings/lollms/__init__.py +151 -32
  11. lollms_client/llm_bindings/lollms_webui/__init__.py +1 -1
  12. lollms_client/llm_bindings/mistral/__init__.py +2 -2
  13. lollms_client/llm_bindings/novita_ai/__init__.py +439 -0
  14. lollms_client/llm_bindings/ollama/__init__.py +309 -93
  15. lollms_client/llm_bindings/open_router/__init__.py +2 -2
  16. lollms_client/llm_bindings/openai/__init__.py +148 -29
  17. lollms_client/llm_bindings/openllm/__init__.py +362 -506
  18. lollms_client/llm_bindings/openwebui/__init__.py +465 -0
  19. lollms_client/llm_bindings/perplexity/__init__.py +326 -0
  20. lollms_client/llm_bindings/pythonllamacpp/__init__.py +3 -3
  21. lollms_client/llm_bindings/tensor_rt/__init__.py +1 -1
  22. lollms_client/llm_bindings/transformers/__init__.py +428 -632
  23. lollms_client/llm_bindings/vllm/__init__.py +1 -1
  24. lollms_client/lollms_agentic.py +4 -2
  25. lollms_client/lollms_base_binding.py +61 -0
  26. lollms_client/lollms_core.py +516 -1890
  27. lollms_client/lollms_discussion.py +55 -18
  28. lollms_client/lollms_llm_binding.py +112 -261
  29. lollms_client/lollms_mcp_binding.py +34 -75
  30. lollms_client/lollms_personality.py +5 -2
  31. lollms_client/lollms_stt_binding.py +85 -52
  32. lollms_client/lollms_tti_binding.py +23 -37
  33. lollms_client/lollms_ttm_binding.py +24 -42
  34. lollms_client/lollms_tts_binding.py +28 -17
  35. lollms_client/lollms_ttv_binding.py +24 -42
  36. lollms_client/lollms_types.py +4 -2
  37. lollms_client/stt_bindings/whisper/__init__.py +108 -23
  38. lollms_client/stt_bindings/whispercpp/__init__.py +7 -1
  39. lollms_client/tti_bindings/diffusers/__init__.py +418 -810
  40. lollms_client/tti_bindings/diffusers/server/main.py +1051 -0
  41. lollms_client/tti_bindings/gemini/__init__.py +182 -239
  42. lollms_client/tti_bindings/leonardo_ai/__init__.py +127 -0
  43. lollms_client/tti_bindings/lollms/__init__.py +4 -1
  44. lollms_client/tti_bindings/novita_ai/__init__.py +105 -0
  45. lollms_client/tti_bindings/openai/__init__.py +10 -11
  46. lollms_client/tti_bindings/stability_ai/__init__.py +178 -0
  47. lollms_client/ttm_bindings/audiocraft/__init__.py +7 -12
  48. lollms_client/ttm_bindings/beatoven_ai/__init__.py +129 -0
  49. lollms_client/ttm_bindings/lollms/__init__.py +4 -17
  50. lollms_client/ttm_bindings/replicate/__init__.py +115 -0
  51. lollms_client/ttm_bindings/stability_ai/__init__.py +117 -0
  52. lollms_client/ttm_bindings/topmediai/__init__.py +96 -0
  53. lollms_client/tts_bindings/bark/__init__.py +7 -10
  54. lollms_client/tts_bindings/lollms/__init__.py +6 -1
  55. lollms_client/tts_bindings/piper_tts/__init__.py +8 -11
  56. lollms_client/tts_bindings/xtts/__init__.py +157 -74
  57. lollms_client/tts_bindings/xtts/server/main.py +241 -280
  58. {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/METADATA +316 -6
  59. lollms_client-1.7.10.dist-info/RECORD +89 -0
  60. lollms_client/ttm_bindings/bark/__init__.py +0 -339
  61. lollms_client-1.4.1.dist-info/RECORD +0 -78
  62. {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/WHEEL +0 -0
  63. {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/licenses/LICENSE +0 -0
  64. {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/top_level.txt +0 -0
@@ -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
- self.tts = self.tts_binding_manager.create_binding(
147
- binding_name=tts_binding_name,
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 (tti_binding_config or {}).items()
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
- else:
168
- self.tti = self.tti_binding_manager.create_binding(
169
- binding_name=tti_binding_name
170
- )
171
- if self.tti is None:
172
- ASCIIColors.warning(f"Failed to create TTI binding: {tti_binding_name}. Available: {self.tti_binding_manager.get_available_bindings()}")
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
- if stt_binding_config:
176
- self.stt = self.stt_binding_manager.create_binding(
177
- binding_name=stt_binding_name,
178
- **{
179
- k: v
180
- for k, v in (stt_binding_config or {}).items()
181
- if k != "binding_name"
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
- else:
186
- self.stt = self.stt_binding_manager.create_binding(
187
- binding_name=stt_binding_name,
188
- )
189
- if self.stt is None:
190
- ASCIIColors.warning(f"Failed to create STT binding: {stt_binding_name}. Available: {self.stt_binding_manager.get_available_bindings()}")
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
- if ttv_binding_config:
193
- self.ttv = self.ttv_binding_manager.create_binding(
194
- binding_name=ttv_binding_name,
195
- **{
196
- k: v
197
- for k, v in ttv_binding_config.items()
198
- if k != "binding_name"
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
- else:
203
- self.ttv = self.ttv_binding_manager.create_binding(
204
- binding_name=ttv_binding_name
205
- )
206
- if self.ttv is None:
207
- ASCIIColors.warning(f"Failed to create TTV binding: {ttv_binding_name}. Available: {self.ttv_binding_manager.get_available_bindings()}")
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
- if ttm_binding_config:
211
- self.ttm = self.ttm_binding_manager.create_binding(
212
- binding_name=ttm_binding_name,
213
- **{
214
- k: v
215
- for k, v in (ttm_binding_config or {}).items()
216
- if k != "binding_name"
217
- }
218
- )
219
- else:
220
- self.ttm = self.ttm_binding_manager.create_binding(
221
- binding_name=ttm_binding_name
222
- )
223
- if self.ttm is None:
224
- ASCIIColors.warning(f"Failed to create TTM binding: {ttm_binding_name}. Available: {self.ttm_binding_manager.get_available_bindings()}")
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
- if mcp_binding_config:
228
- self.mcp = self.mcp_binding_manager.create_binding(
229
- binding_name=mcp_binding_name,
230
- **{
231
- k: v
232
- for k, v in (mcp_binding_config or {}).items()
233
- if k != "binding_name"
234
- }
235
- )
236
- else:
237
- self.mcp = self.mcp_binding_manager.create_binding(
238
- mcp_binding_name
239
- )
240
- if self.mcp is None:
241
- ASCIIColors.warning(f"Failed to create MCP binding: {mcp_binding_name}. Available: {self.mcp_binding_manager.get_available_bindings()}")
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
- prompt: str,
474
- images: Optional[List[str]] = None,
475
- system_prompt: str = "",
476
- n_predict: Optional[int] = None,
477
- stream: Optional[bool] = None,
478
- temperature: Optional[float] = None,
479
- top_k: Optional[int] = None,
480
- top_p: Optional[float] = None,
481
- repeat_penalty: Optional[float] = None,
482
- repeat_last_n: Optional[int] = None,
483
- seed: Optional[int] = None,
484
- n_threads: Optional[int] = None,
485
- ctx_size: int | None = None,
486
- streaming_callback: Optional[Callable[[str, MSG_TYPE], None]] = None,
487
- split:Optional[bool]=False, # put to true if the prompt is a discussion
488
- user_keyword:Optional[str]="!@>user:",
489
- ai_keyword:Optional[str]="!@>assistant:",
490
- **kwargs
491
- ) -> Union[str, dict]:
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
- discussion: LollmsDiscussion,
604
- branch_tip_id: Optional[str] = None,
605
- n_predict: Optional[int] = None,
606
- stream: Optional[bool] = None,
607
- temperature: Optional[float] = None,
608
- top_k: Optional[int] = None,
609
- top_p: Optional[float] = None,
610
- repeat_penalty: Optional[float] = None,
611
- repeat_last_n: Optional[int] = None,
612
- seed: Optional[int] = None,
613
- n_threads: Optional[int] = None,
614
- ctx_size: Optional[int] = None,
615
- streaming_callback: Optional[Callable[[str, MSG_TYPE, Dict], bool]] = None,
616
- **kwargs
617
- ) -> Union[str, dict]:
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 listModels(self):
717
+ def list_models(self):
678
718
  """Lists models available to the current LLM binding."""
679
719
  if self.llm:
680
- return self.llm.listModels()
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
- debug=False
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 "research::" in tool_name:
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
- tool_name, description, call_fn = f"research::{name}", f"Queries the '{name}' knowledge base.", None
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
- log_event(f" 📖 Ready: {name}", MSG_TYPE.MSG_TYPE_INFO)
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
- USER REQUEST: "{prompt}"
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": 0.8, "text_output": "Direct answer or clarification question if applicable", "required_tool_name": "specific tool name if SINGLE_TOOL strategy", "estimated_steps": 3}}"""
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", 0.5),
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
  })
@@ -1653,1553 +1668,90 @@ Provide your analysis in JSON format:
1653
1668
  raise ValueError(f"Strategy analysis selected unavailable tool: '{tool_name}'")
1654
1669
 
1655
1670
  log_event(f"Selected tool: {tool_name}", MSG_TYPE.MSG_TYPE_INFO)
1656
-
1657
- # 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 = [{"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
- }}"""
1671
+
1672
+ # Enhanced parameter generation prompt
1673
+ param_prompt = f"""Generate the optimal parameters for the selected tool to fulfill the user's request.
3080
1674
 
3081
- reflection_data = self.generate_structured_content(
3082
- prompt=reflection_prompt,
3083
- schema={
3084
- "goal_achieved": "boolean",
3085
- "effectiveness_score": "number",
3086
- "process_efficiency": "number",
3087
- "information_quality": "number",
3088
- "decision_making": "number",
3089
- "adaptability": "number",
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
- if reflection_data:
3101
- current_scratchpad += f"\n\n### Comprehensive Self-Assessment\n"
3102
- current_scratchpad += f"**Goal Achieved**: {reflection_data.get('goal_achieved', False)}\n"
3103
- current_scratchpad += f"**Overall Confidence**: {reflection_data.get('overall_confidence', 0.5):.2f}\n"
3104
- current_scratchpad += f"**Effectiveness Score**: {reflection_data.get('effectiveness_score', 0.5):.2f}\n"
3105
- current_scratchpad += f"**Key Strengths**: {', '.join(reflection_data.get('strengths', []))}\n"
3106
- current_scratchpad += f"**Improvement Areas**: {', '.join(reflection_data.get('areas_for_improvement', []))}\n"
3107
- current_scratchpad += f"**Summary**: {reflection_data.get('summary', '')}\n"
3108
-
3109
- log_event_fn(f" Self-assessment completed", MSG_TYPE.MSG_TYPE_STEP_END, event_id=reflection_id, meta={
3110
- "overall_confidence": reflection_data.get('overall_confidence', 0.5),
3111
- "goal_achieved": reflection_data.get('goal_achieved', False),
3112
- "effectiveness_score": reflection_data.get('effectiveness_score', 0.5)
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
- log_event_fn("Self-assessment data generation failed", MSG_TYPE.MSG_TYPE_WARNING, event_id=reflection_id)
3116
-
3117
- except Exception as e:
3118
- log_event_fn(f"Self-assessment failed: {e}", MSG_TYPE.MSG_TYPE_WARNING, event_id=reflection_id)
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
- # Enhanced final synthesis
3121
- synthesis_id = log_event_fn("📝 Synthesizing comprehensive final response...", MSG_TYPE.MSG_TYPE_STEP_START)
3122
-
3123
- final_answer_prompt = f"""Create a comprehensive, well-structured final response that fully addresses the user's request.
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
- ORIGINAL REQUEST: "{original_user_prompt}"
3126
- CONTEXT: {context or "No additional context"}
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
- COMPLETE ANALYSIS AND WORK:
3129
- {current_scratchpad}
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
- GUIDELINES for your response:
3132
- 1. **Be Complete**: Address all aspects of the user's request
3133
- 2. **Be Clear**: Organize your response logically and use clear language
3134
- 3. **Be Helpful**: Provide actionable information and insights
3135
- 4. **Be Honest**: If there were limitations or uncertainties, mention them appropriately
3136
- 5. **Be Concise**: While being thorough, avoid unnecessary verbosity
3137
- 6. **Cite Sources**: If you used research tools, reference the information appropriately
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
- Your response should feel natural and conversational while being informative and valuable.
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
- FINAL RESPONSE:"""
3142
-
3143
- log_prompt_fn("Final Synthesis Prompt", final_answer_prompt)
3144
-
3145
- final_answer_text = self.generate_text(
3146
- prompt=final_answer_prompt,
3147
- system_prompt=system_prompt,
3148
- stream=streaming_callback is not None,
3149
- streaming_callback=streaming_callback,
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 "research::" in tool_name:
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": 0.8
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", 0.5)
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)
@@ -3922,7 +2502,8 @@ FINAL RESPONSE:"""
3922
2502
  repeat_last_n:int|None=None,
3923
2503
  callback=None,
3924
2504
  debug:bool=False,
3925
- override_all_prompts:bool=False ):
2505
+ override_all_prompts:bool=False,
2506
+ **kwargs ):
3926
2507
  """
3927
2508
  Generates a single code block based on a prompt.
3928
2509
  Uses the underlying LLM binding via `generate_text`.
@@ -5734,184 +4315,229 @@ Provide the final aggregated answer in {output_format} format, directly addressi
5734
4315
  def long_context_processing(
5735
4316
  self,
5736
4317
  text_to_process: str,
5737
- contextual_prompt: Optional[str] = None,
5738
- chunk_size_tokens: int|None = None,
5739
- overlap_tokens: int = 0,
4318
+ contextual_prompt: str,
4319
+ system_prompt: str | None = None,
4320
+ strategy_override: str | None = None, # 'narrative' or 'structured'
5740
4321
  streaming_callback: Optional[Callable] = None,
4322
+ debug: bool = True,
5741
4323
  **kwargs
5742
4324
  ) -> str:
5743
4325
  """
5744
- Summarizes a long text that may not fit into the model's context window.
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
+ """
5745
4330
 
5746
- This method works in two stages:
5747
- 1. **Chunk & Summarize:** It breaks the text into overlapping chunks and summarizes each one individually.
5748
- 2. **Synthesize:** It then takes all the chunk summaries and performs a final summarization pass to create a single, coherent, and comprehensive summary.
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
5749
4356
 
5750
- Args:
5751
- text_to_process (str): The long text content to be summarized.
5752
- contextual_prompt (Optional[str], optional): A specific instruction to guide the summary's focus.
5753
- For example, "Summarize the text focusing on the financial implications."
5754
- Defaults to None.
5755
- chunk_size_tokens (int, optional): The number of tokens in each text chunk. This should be well
5756
- within the model's context limit to allow space for prompts.
5757
- Defaults to 1500.
5758
- overlap_tokens (int, optional): The number of tokens to overlap between chunks to ensure context
5759
- is not lost at the boundaries. Defaults to 250.
5760
- streaming_callback (Optional[Callable], optional): A callback function to receive real-time updates
5761
- on the process (e.g., which chunk is being processed).
5762
- It receives a message, a message type, and optional metadata.
5763
- Defaults to None.
5764
- **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
5765
4368
 
5766
- Returns:
5767
- str: The final, comprehensive summary of the text.
5768
- """
5769
- if not text_to_process and len(kwargs.get("images",[]))==0 and contextual_prompt is None:
5770
- return ""
5771
- if not text_to_process:
5772
- text_to_process=""
5773
- tokens = []
5774
- else:
5775
- # Use the binding's tokenizer for accurate chunking
5776
- tokens = self.llm.tokenize(text_to_process)
5777
- if chunk_size_tokens is None:
5778
- 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)
5779
4372
 
5780
- if len(tokens) <= chunk_size_tokens:
5781
- if streaming_callback:
5782
- streaming_callback("Text is short enough for a single pass.", MSG_TYPE.MSG_TYPE_STEP, {"progress": 0})
5783
- system_prompt = ("You are a content processor expert.\n"
5784
- "You perform tasks on the content as requested by the user.\n\n"
5785
- "--- Content ---\n"
5786
- f"{text_to_process}\n\n"
5787
- "** Important **\n"
5788
- "Strictly adhere to the user prompt.\n"
5789
- "Do not add comments unless asked to do so.\n"
5790
- )
5791
- if "system_prompt" in kwargs:
5792
- system_prompt += "-- Extra instructions --\n"+ kwargs["system_prompt"] +"\n"
5793
- del kwargs["system_prompt"]
5794
- prompt_objective = contextual_prompt or "Provide a comprehensive summary of the content."
5795
- final_prompt = f"{prompt_objective}"
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)
5796
4391
 
5797
- processed_output = self.generate_text(final_prompt, system_prompt=system_prompt, **kwargs)
4392
+ # Peek at the first 1000 characters (enough to see CSV headers or JSON braces)
4393
+ sample_text = text_to_process[:4000]
5798
4394
 
5799
- if streaming_callback:
5800
- streaming_callback("Content processed.", MSG_TYPE.MSG_TYPE_STEP, {"progress": 100})
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
+ )
5801
4402
 
5802
- return processed_output
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
5803
4413
 
5804
- # --- Stage 1: Chunking and Independent Summarization ---
5805
- chunks = []
5806
- step = chunk_size_tokens - overlap_tokens
5807
- for i in range(0, len(tokens), step):
5808
- chunk_tokens = tokens[i:i + chunk_size_tokens]
5809
- chunk_text = self.llm.detokenize(chunk_tokens)
5810
- chunks.append(chunk_text)
4414
+ if debug: print(f"🔧 DEBUG: Strategy Selected: {content_type.upper()}")
5811
4415
 
5812
- chunk_summaries = []
4416
+ # --- 2. Phase 2: Execution ---
5813
4417
 
5814
- # Total steps include each chunk plus the final synthesis step
5815
- total_steps = len(chunks) + 1
4418
+ # Calculate Chunk Size (Leave room for prompts and outputs)
4419
+ reserved_overhead = 2500
4420
+ safe_chunk_size = context_size - reserved_overhead
5816
4421
 
5817
- # Define the prompt for summarizing each chunk
5818
- summarization_objective = contextual_prompt or "Summarize the key points of the following text excerpt."
5819
- system_prompt = ("You are a sequential document processing agent.\n"
5820
- "The process is done in two phases:\n"
5821
- "** Phase1 : **\n"
5822
- "Sequencially extracting information from the text chunks and adding them to the scratchpad.\n"
5823
- "** Phase2: **\n"
5824
- "Synthesizing a comprehensive Response using the scratchpad content given the objective formatting instructions if applicable.\n"
5825
- "We are now performing ** Phase 1 **, and we are processing chunk number {{chunk_id}}.\n"
5826
- "Your job is to extract information from the current chunk given previous chunks extracted information placed in scratchpad as well as the current chunk content.\n"
5827
- "Add the information to the scratchpad while strictly adhering to the Global objective extraction instructions:\n"
5828
- "-- Sequencial Scratchpad --\n"
5829
- "{{scratchpad}}\n"
5830
- "** Important **\n"
5831
- "Respond only with the extracted information from the current chunk without repeating things that are already in the scratchpad.\n"
5832
- "Strictly adhere to the Global objective content for the extraction phase.\n"
5833
- "Do not add comments.\n"
5834
- )
5835
- if "system_prompt" in kwargs:
5836
- system_prompt += "-- Extra instructions --\n"+ kwargs["system_prompt"] +"\n"
5837
- del kwargs["system_prompt"]
5838
- 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))
5839
4436
 
5840
- for i, chunk in enumerate(chunks):
5841
- progress_before = (i / total_steps) * 100
5842
- if streaming_callback:
5843
- streaming_callback(
5844
- f"Processing chunk {i + 1} of {len(chunks)}...",
5845
- MSG_TYPE.MSG_TYPE_STEP_START,
5846
- {"id": f"chunk_{i+1}", "progress": progress_before}
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>"
5847
4450
  )
5848
-
5849
- prompt = chunk_summary_prompt_template.format(chunk_text=chunk)
5850
- processed_system_prompt = system_prompt.format(chunk_id=i,scratchpad="\n\n---\n\n".join(chunk_summaries))
5851
- try:
5852
- # Generate summary for the current chunk
5853
- chunk_summary = self.generate_text(prompt, system_prompt=processed_system_prompt, **kwargs)
5854
- chunk_summaries.append(chunk_summary)
5855
4451
 
5856
- progress_after = ((i + 1) / total_steps) * 100
5857
- if streaming_callback:
5858
- streaming_callback(
5859
- f"Chunk {i + 1} processed. Progress: {progress_after:.0f}%",
5860
- MSG_TYPE.MSG_TYPE_STEP_END,
5861
- {"id": f"chunk_{i+1}", "output_snippet": chunk_summary[:100], "progress": progress_after}
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."
5862
4514
  )
5863
- except Exception as e:
5864
- trace_exception(e)
5865
- if streaming_callback:
5866
- streaming_callback(f"Failed to process chunk {i+1}: {e}", MSG_TYPE.MSG_TYPE_EXCEPTION)
5867
- # Still add a placeholder to not break the chain
5868
- chunk_summaries.append(f"[Error processing chunk {i+1}]")
5869
-
5870
- # --- Stage 2: Final Synthesis of All Chunk Summaries ---
5871
- progress_before_synthesis = (len(chunks) / total_steps) * 100
5872
- if streaming_callback:
5873
- streaming_callback(
5874
- "Processing the scratchpad content into a final version...",
5875
- MSG_TYPE.MSG_TYPE_STEP_START,
5876
- {"id": "final_synthesis", "progress": progress_before_synthesis}
5877
- )
4515
+ reduced = self.remove_thinking_blocks(self.llm.generate_text(reduce_prompt, **kwargs))
4516
+ extraction_accumulator = [reduced]
5878
4517
 
5879
- combined_summaries = "\n\n---\n\n".join(chunk_summaries)
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})
5880
4521
 
5881
- # Define the prompt for the final synthesis
5882
- synthesis_objective = contextual_prompt or "Create a single, final, coherent, and comprehensive summary."
5883
- system_prompt = ("You are a sequential document processing agent.\n"
5884
- "The process is done in two phases:\n"
5885
- "** Phase1 : **\n"
5886
- "Sequencially extracting information from the text chunks and adding them to the scratchpad.\n"
5887
- "** Phase2: **\n"
5888
- "Synthesizing a comprehensive Response using the scratchpad content given the objective formatting instructions if applicable.\n"
5889
- "\n"
5890
- "We are now performing ** Phase 2 **.\n"
5891
- "Your job is to use the extracted information to fulfill the user prompt objectives.\n"
5892
- "Make sure you respect the user formatting if provided and if not, then use markdown output format."
5893
- "-- Sequencial Scratchpad --\n"
5894
- f"{combined_summaries}\n"
5895
- "** Important **\n"
5896
- "Respond only with the requested task without extra comments unless told to.\n"
5897
- "Strictly adhere to the Global objective content for the extraction phase.\n"
5898
- "Do not add comments.\n"
5899
- )
5900
- final_synthesis_prompt = (
5901
- f"--- Global objective ---\n{synthesis_objective}\n\n"
5902
- "--- 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."
5903
4532
  )
5904
-
5905
- final_answer = self.generate_text(final_synthesis_prompt, system_prompt=system_prompt, **kwargs)
5906
4533
 
5907
- if streaming_callback:
5908
- streaming_callback(
5909
- "Final summary synthesized.",
5910
- MSG_TYPE.MSG_TYPE_STEP_END,
5911
- {"id": "final_synthesis", "progress": 100}
5912
- )
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
5913
4539
 
5914
- return final_answer.strip()
4540
+
5915
4541
 
5916
4542
  def chunk_text(text, tokenizer, detokenizer, chunk_size, overlap, use_separators=True):
5917
4543
  """