lollms-client 0.24.2__py3-none-any.whl → 0.27.0__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.
Potentially problematic release.
This version of lollms-client might be problematic. Click here for more details.
- lollms_client/__init__.py +3 -2
- lollms_client/llm_bindings/azure_openai/__init__.py +364 -0
- lollms_client/llm_bindings/claude/__init__.py +549 -0
- lollms_client/llm_bindings/gemini/__init__.py +501 -0
- lollms_client/llm_bindings/grok/__init__.py +536 -0
- lollms_client/llm_bindings/groq/__init__.py +292 -0
- lollms_client/llm_bindings/hugging_face_inference_api/__init__.py +307 -0
- lollms_client/llm_bindings/litellm/__init__.py +201 -0
- lollms_client/llm_bindings/lollms/__init__.py +2 -0
- lollms_client/llm_bindings/mistral/__init__.py +298 -0
- lollms_client/llm_bindings/open_router/__init__.py +304 -0
- lollms_client/llm_bindings/openai/__init__.py +30 -9
- lollms_client/lollms_core.py +338 -162
- lollms_client/lollms_discussion.py +135 -37
- lollms_client/lollms_llm_binding.py +4 -0
- lollms_client/lollms_types.py +9 -1
- lollms_client/lollms_utilities.py +68 -0
- lollms_client/mcp_bindings/remote_mcp/__init__.py +82 -4
- lollms_client-0.27.0.dist-info/METADATA +604 -0
- {lollms_client-0.24.2.dist-info → lollms_client-0.27.0.dist-info}/RECORD +23 -14
- lollms_client-0.24.2.dist-info/METADATA +0 -239
- {lollms_client-0.24.2.dist-info → lollms_client-0.27.0.dist-info}/WHEEL +0 -0
- {lollms_client-0.24.2.dist-info → lollms_client-0.27.0.dist-info}/licenses/LICENSE +0 -0
- {lollms_client-0.24.2.dist-info → lollms_client-0.27.0.dist-info}/top_level.txt +0 -0
lollms_client/lollms_core.py
CHANGED
|
@@ -13,6 +13,8 @@ from lollms_client.lollms_ttm_binding import LollmsTTMBinding, LollmsTTMBindingM
|
|
|
13
13
|
from lollms_client.lollms_mcp_binding import LollmsMCPBinding, LollmsMCPBindingManager
|
|
14
14
|
|
|
15
15
|
from lollms_client.lollms_discussion import LollmsDiscussion
|
|
16
|
+
|
|
17
|
+
from lollms_client.lollms_utilities import build_image_dicts, dict_to_markdown
|
|
16
18
|
import json, re
|
|
17
19
|
from enum import Enum
|
|
18
20
|
import base64
|
|
@@ -846,7 +848,7 @@ Don't forget encapsulate the code inside a html code tag. This is mandatory.
|
|
|
846
848
|
"2. **Check for a Single-Step Solution:** Scrutinize the available tools. Can a single tool call directly achieve the user's current goal? \n"
|
|
847
849
|
"3. **Formulate a Plan:** Based on your analysis, create a concise, numbered list of steps to achieve the goal. If the goal is simple, this may be only one step. If it is complex or multi-turn, it may be several steps.\n\n"
|
|
848
850
|
"**CRITICAL RULES:**\n"
|
|
849
|
-
"* **MANDATORY:
|
|
851
|
+
"* **MANDATORY: Be helpful, curious and creative.\n"
|
|
850
852
|
"* **Focus on the Goal:** Your plan should directly address the user's request as it stands now in the conversation.\n\n"
|
|
851
853
|
"---\n"
|
|
852
854
|
"**Available Tools:**\n"
|
|
@@ -877,7 +879,7 @@ Don't forget encapsulate the code inside a html code tag. This is mandatory.
|
|
|
877
879
|
|
|
878
880
|
formatted_agent_history = "No actions taken yet in this turn."
|
|
879
881
|
if agent_work_history:
|
|
880
|
-
history_parts = [ f"### Step {i+1}:\n**Thought
|
|
882
|
+
history_parts = [ f"### Step {i+1}:\n**Thought:**\n{entry['thought']}\n**Action:** Called tool `{entry['tool_name']}` with parameters `{json.dumps(entry['tool_params'])}`\n**Observation (Tool Output):**\n```json\n{json.dumps(entry['tool_result'], indent=2)}\n```" for i, entry in enumerate(agent_work_history)]
|
|
881
883
|
formatted_agent_history = "\n\n".join(history_parts)
|
|
882
884
|
|
|
883
885
|
llm_decision = None
|
|
@@ -1078,7 +1080,7 @@ Provide your response as a single JSON object with one key, "query".
|
|
|
1078
1080
|
"""
|
|
1079
1081
|
try:
|
|
1080
1082
|
raw_initial_query_response = self.generate_code(initial_query_gen_prompt, system_prompt="You are a query generation expert.", temperature=0.0)
|
|
1081
|
-
initial_plan =
|
|
1083
|
+
initial_plan = robust_json_parser(raw_initial_query_response)
|
|
1082
1084
|
current_query_for_rag = initial_plan.get("query")
|
|
1083
1085
|
if not current_query_for_rag:
|
|
1084
1086
|
raise ValueError("LLM returned an empty initial query.")
|
|
@@ -1434,7 +1436,6 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1434
1436
|
new_scratchpad_text = self.generate_text(prompt=synthesis_prompt, n_predict=1024, temperature=0.0)
|
|
1435
1437
|
return self.remove_thinking_blocks(new_scratchpad_text).strip()
|
|
1436
1438
|
|
|
1437
|
-
# In lollms_client/lollms_discussion.py -> LollmsClient class
|
|
1438
1439
|
|
|
1439
1440
|
def generate_with_mcp_rag(
|
|
1440
1441
|
self,
|
|
@@ -1442,15 +1443,16 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1442
1443
|
use_mcps: Union[None, bool, List[str]] = None,
|
|
1443
1444
|
use_data_store: Union[None, Dict[str, Callable]] = None,
|
|
1444
1445
|
system_prompt: str = None,
|
|
1445
|
-
reasoning_system_prompt: str = "You are a logical and
|
|
1446
|
+
reasoning_system_prompt: str = "You are a logical AI assistant. Your task is to achieve the user's goal by thinking step-by-step and using the available tools.",
|
|
1446
1447
|
images: Optional[List[str]] = None,
|
|
1447
|
-
max_reasoning_steps: int =
|
|
1448
|
-
decision_temperature: float =
|
|
1448
|
+
max_reasoning_steps: int = None,
|
|
1449
|
+
decision_temperature: float = None,
|
|
1449
1450
|
final_answer_temperature: float = None,
|
|
1450
1451
|
streaming_callback: Optional[Callable[[str, 'MSG_TYPE', Optional[Dict], Optional[List]], bool]] = None,
|
|
1451
|
-
rag_top_k: int =
|
|
1452
|
-
rag_min_similarity_percent: float =
|
|
1453
|
-
output_summarization_threshold: int =
|
|
1452
|
+
rag_top_k: int = None,
|
|
1453
|
+
rag_min_similarity_percent: float = None,
|
|
1454
|
+
output_summarization_threshold: int = None, # In tokens
|
|
1455
|
+
debug: bool = False,
|
|
1454
1456
|
**llm_generation_kwargs
|
|
1455
1457
|
) -> Dict[str, Any]:
|
|
1456
1458
|
"""Generates a response using a dynamic agent with stateful, ID-based step tracking.
|
|
@@ -1483,6 +1485,7 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1483
1485
|
rag_min_similarity_percent: Minimum similarity for RAG results.
|
|
1484
1486
|
output_summarization_threshold: The token count that triggers automatic
|
|
1485
1487
|
summarization of a tool's text output.
|
|
1488
|
+
debug : If true, we'll report the detailed promptin and response information
|
|
1486
1489
|
**llm_generation_kwargs: Additional keyword arguments for LLM calls.
|
|
1487
1490
|
|
|
1488
1491
|
Returns:
|
|
@@ -1490,12 +1493,28 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1490
1493
|
answer, the complete internal scratchpad, a log of tool calls,
|
|
1491
1494
|
any retrieved RAG sources, and other metadata.
|
|
1492
1495
|
"""
|
|
1496
|
+
reasoning_step_id = None
|
|
1493
1497
|
if not self.binding:
|
|
1494
1498
|
return {"final_answer": "", "tool_calls": [], "sources": [], "error": "LLM binding not initialized."}
|
|
1495
1499
|
|
|
1500
|
+
if not max_reasoning_steps:
|
|
1501
|
+
max_reasoning_steps= 10
|
|
1502
|
+
if not rag_min_similarity_percent:
|
|
1503
|
+
rag_min_similarity_percent= 50
|
|
1504
|
+
if not rag_top_k:
|
|
1505
|
+
rag_top_k = 5
|
|
1506
|
+
if not decision_temperature:
|
|
1507
|
+
decision_temperature = 0.7
|
|
1508
|
+
if not output_summarization_threshold:
|
|
1509
|
+
output_summarization_threshold = 500
|
|
1510
|
+
|
|
1511
|
+
events = []
|
|
1512
|
+
|
|
1513
|
+
|
|
1496
1514
|
# --- Initialize Agent State ---
|
|
1497
1515
|
sources_this_turn: List[Dict[str, Any]] = []
|
|
1498
1516
|
tool_calls_this_turn: List[Dict[str, Any]] = []
|
|
1517
|
+
generated_code_store: Dict[str, str] = {} # NEW: Store for UUID -> code
|
|
1499
1518
|
original_user_prompt = prompt
|
|
1500
1519
|
|
|
1501
1520
|
initial_state_parts = [
|
|
@@ -1507,41 +1526,50 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1507
1526
|
initial_state_parts.append(f"- The user has provided {len(images)} image(s) for context.")
|
|
1508
1527
|
current_scratchpad = "\n".join(initial_state_parts)
|
|
1509
1528
|
|
|
1510
|
-
|
|
1511
|
-
|
|
1529
|
+
def log_prompt(prompt, type="prompt"):
|
|
1530
|
+
ASCIIColors.cyan(f"** DEBUG: {type} **")
|
|
1531
|
+
ASCIIColors.magenta(prompt[-15000:])
|
|
1532
|
+
prompt_size = self.count_tokens(prompt)
|
|
1533
|
+
ASCIIColors.red(f"Prompt size:{prompt_size}/{self.default_ctx_size}")
|
|
1534
|
+
ASCIIColors.cyan(f"** DEBUG: DONE **")
|
|
1535
|
+
|
|
1536
|
+
# --- Define Inner Helper Functions ---
|
|
1537
|
+
def log_event(
|
|
1512
1538
|
description: str,
|
|
1513
|
-
|
|
1539
|
+
event_type: MSG_TYPE = MSG_TYPE.MSG_TYPE_CHUNK,
|
|
1514
1540
|
metadata: Optional[Dict] = None,
|
|
1515
|
-
|
|
1541
|
+
event_id=None
|
|
1516
1542
|
) -> Optional[str]:
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
streaming_callback(description, MSG_TYPE.MSG_TYPE_STEP, params)
|
|
1539
|
-
return None
|
|
1543
|
+
if not streaming_callback: return None
|
|
1544
|
+
event_id = str(uuid.uuid4()) if event_type==MSG_TYPE.MSG_TYPE_STEP_START else event_id
|
|
1545
|
+
params = {"type": event_type, "description": description, **(metadata or {})}
|
|
1546
|
+
params["id"] = event_id
|
|
1547
|
+
streaming_callback(description, event_type, params)
|
|
1548
|
+
return event_id
|
|
1549
|
+
|
|
1550
|
+
def _substitute_code_uuids_recursive(data: Any, code_store: Dict[str, str]):
|
|
1551
|
+
"""Recursively finds and replaces code UUIDs in tool parameters."""
|
|
1552
|
+
if isinstance(data, dict):
|
|
1553
|
+
for key, value in data.items():
|
|
1554
|
+
if isinstance(value, str) and value in code_store:
|
|
1555
|
+
data[key] = code_store[value]
|
|
1556
|
+
else:
|
|
1557
|
+
_substitute_code_uuids_recursive(value, code_store)
|
|
1558
|
+
elif isinstance(data, list):
|
|
1559
|
+
for i, item in enumerate(data):
|
|
1560
|
+
if isinstance(item, str) and item in code_store:
|
|
1561
|
+
data[i] = code_store[item]
|
|
1562
|
+
else:
|
|
1563
|
+
_substitute_code_uuids_recursive(item, code_store)
|
|
1540
1564
|
|
|
1565
|
+
discovery_step_id = log_event("**Discovering tools**",MSG_TYPE.MSG_TYPE_STEP_START)
|
|
1541
1566
|
# --- 1. Discover Available Tools ---
|
|
1542
1567
|
available_tools = []
|
|
1543
1568
|
if use_mcps and self.mcp:
|
|
1544
|
-
|
|
1569
|
+
discovered_tools = self.mcp.discover_tools(force_refresh=True)
|
|
1570
|
+
if isinstance(use_mcps, list):
|
|
1571
|
+
available_tools.extend([t for t in discovered_tools if t["name"] in use_mcps])
|
|
1572
|
+
|
|
1545
1573
|
if use_data_store:
|
|
1546
1574
|
for store_name in use_data_store:
|
|
1547
1575
|
available_tools.append({
|
|
@@ -1550,20 +1578,33 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1550
1578
|
"input_schema": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}
|
|
1551
1579
|
})
|
|
1552
1580
|
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1581
|
+
# Add the new put_code_in_buffer tool definition
|
|
1582
|
+
available_tools.append({
|
|
1583
|
+
"name": "put_code_in_buffer",
|
|
1584
|
+
"description": """Generates and stores code into a buffer to be used by another tool. You can put the uuid of the generated code into the fields that require long code among the tools. If no tool requires code as input do not use put_code_in_buffer. put_code_in_buffer do not execute the code nor does it audit it.""",
|
|
1585
|
+
"input_schema": {"type": "object", "properties": {"prompt": {"type": "string", "description": "A detailed natural language description of the code's purpose and requirements."}, "language": {"type": "string", "description": "The programming language of the generated code. By default it uses python."}}, "required": ["prompt"]}
|
|
1586
|
+
})
|
|
1587
|
+
# Add the new refactor_scratchpad tool definition
|
|
1588
|
+
available_tools.append({
|
|
1589
|
+
"name": "refactor_scratchpad",
|
|
1590
|
+
"description": "Rewrites the scratchpad content to clean it and reorganize it. Only use if the scratchpad is messy or contains too much information compared to what you need.",
|
|
1591
|
+
"input_schema": {"type": "object", "properties": {}}
|
|
1592
|
+
})
|
|
1593
|
+
|
|
1594
|
+
formatted_tools_list = "\n".join([f"**{t['name']}**:\n{t['description']}\ninput schema:\n{json.dumps(t['input_schema'])}" for t in available_tools])
|
|
1595
|
+
formatted_tools_list += "\n**request_clarification**:\nUse if the user's request is ambiguous and you can not infer a clear idea of his intent. this tool has no parameters."
|
|
1596
|
+
formatted_tools_list += "\n**final_answer**:\nUse when you are ready to respond to the user. this tool has no parameters."
|
|
1597
|
+
|
|
1598
|
+
if discovery_step_id: log_event("**Discovering tools**",MSG_TYPE.MSG_TYPE_STEP_END, event_id=discovery_step_id)
|
|
1556
1599
|
|
|
1557
1600
|
# --- 2. Dynamic Reasoning Loop ---
|
|
1558
1601
|
for i in range(max_reasoning_steps):
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
reasoning_prompt_template = f"""You are a logical AI assistant. Your task is to achieve the user's goal by thinking step-by-step and using the available tools.
|
|
1566
|
-
|
|
1602
|
+
try:
|
|
1603
|
+
reasoning_step_id = log_event(f"**Reasoning Step {i+1}/{max_reasoning_steps}**", MSG_TYPE.MSG_TYPE_STEP_START)
|
|
1604
|
+
user_context = f'Original User Request: "{original_user_prompt}"'
|
|
1605
|
+
if images: user_context += f'\n(Note: {len(images)} image(s) were provided with this request.)'
|
|
1606
|
+
|
|
1607
|
+
reasoning_prompt_template = f"""
|
|
1567
1608
|
--- AVAILABLE TOOLS ---
|
|
1568
1609
|
{formatted_tools_list}
|
|
1569
1610
|
--- CONTEXT ---
|
|
@@ -1577,122 +1618,155 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1577
1618
|
2. **THINK:**
|
|
1578
1619
|
- Does the latest observation completely fulfill the user's original request?
|
|
1579
1620
|
- If YES, your next action MUST be to use the `final_answer` tool.
|
|
1580
|
-
- If NO, what is the single next logical step needed?
|
|
1621
|
+
- If NO, what is the single next logical step needed? This may involve writing code first with `put_code_in_buffer`, then using another tool.
|
|
1581
1622
|
- If you are stuck or the request is ambiguous, use `request_clarification`.
|
|
1582
1623
|
3. **ACT:** Formulate your decision as a JSON object.
|
|
1583
1624
|
"""
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1625
|
+
action_template = {
|
|
1626
|
+
"thought": "My detailed analysis of the last observation and my reasoning for the next action and how it integrates with my global plan.",
|
|
1627
|
+
"action": {
|
|
1628
|
+
"tool_name": "The single tool to use (e.g., 'put_code_in_buffer', 'time_machine::get_current_time', 'final_answer').",
|
|
1629
|
+
"tool_params": {"param1": "value1"},
|
|
1630
|
+
"clarification_question": "(string, ONLY if tool_name is 'request_clarification')"
|
|
1631
|
+
}
|
|
1590
1632
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
try:
|
|
1602
|
-
action_data = json.loads(structured_action_response)
|
|
1603
|
-
thought = action_data.get("thought", "No thought was generated.")
|
|
1604
|
-
action = action_data.get("action", {})
|
|
1605
|
-
tool_name = action.get("tool_name")
|
|
1606
|
-
tool_params = action.get("tool_params", {})
|
|
1607
|
-
except (json.JSONDecodeError, TypeError) as e:
|
|
1608
|
-
current_scratchpad += f"\n\n### Step {i+1} Failure\n- **Error:** Failed to generate a valid JSON action: {e}"
|
|
1609
|
-
log_step(f"\n\n### Step {i+1} Failure\n- **Error:** Failed to generate a valid JSON action: {e}", "scratchpad", is_start=False)
|
|
1610
|
-
if reasoning_step_id:
|
|
1611
|
-
log_step(f"Reasoning Step {i+1}/{max_reasoning_steps}", "reasoning_step", metadata={"id": reasoning_step_id, "error": str(e)}, is_start=False)
|
|
1612
|
-
break
|
|
1633
|
+
if debug: log_prompt(reasoning_prompt_template, f"REASONING PROMPT (Step {i+1})")
|
|
1634
|
+
structured_action_response = self.generate_code(
|
|
1635
|
+
prompt=reasoning_prompt_template, template=json.dumps(action_template, indent=2),
|
|
1636
|
+
system_prompt=reasoning_system_prompt, temperature=decision_temperature,
|
|
1637
|
+
images=images if i == 0 else None
|
|
1638
|
+
)
|
|
1639
|
+
if structured_action_response is None:
|
|
1640
|
+
log_event("**Error generating thought.** Retrying..", MSG_TYPE.MSG_TYPE_EXCEPTION)
|
|
1641
|
+
continue
|
|
1642
|
+
if debug: log_prompt(structured_action_response, f"RAW REASONING RESPONSE (Step {i+1})")
|
|
1613
1643
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1644
|
+
try:
|
|
1645
|
+
action_data = robust_json_parser(structured_action_response)
|
|
1646
|
+
thought = action_data.get("thought", "No thought was generated.")
|
|
1647
|
+
action = action_data.get("action", {})
|
|
1648
|
+
if isinstance(action,str):
|
|
1649
|
+
tool_name = action
|
|
1650
|
+
tool_params = {}
|
|
1651
|
+
else:
|
|
1652
|
+
tool_name = action.get("tool_name")
|
|
1653
|
+
tool_params = action.get("tool_params", {})
|
|
1654
|
+
except (json.JSONDecodeError, TypeError) as e:
|
|
1655
|
+
current_scratchpad += f"\n\n### Step {i+1} Failure\n- **Error:** Failed to generate a valid JSON action: {e}"
|
|
1656
|
+
log_event(f"Step Failure: Invalid JSON action.", MSG_TYPE.MSG_TYPE_EXCEPTION, metadata={"details": str(e)})
|
|
1657
|
+
if reasoning_step_id: log_event(f"**Reasoning Step {i+1}/{max_reasoning_steps}**", MSG_TYPE.MSG_TYPE_STEP_END, metadata={"error": str(e)}, event_id=reasoning_step_id)
|
|
1658
|
+
|
|
1616
1659
|
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
log_step(f"\n\n### Step {i+1} Failure\n- **Error:** Did not specify a tool name.", "scratchpad", is_start=False)
|
|
1620
|
-
if reasoning_step_id:
|
|
1621
|
-
log_step(f"Reasoning Step {i+1}/{max_reasoning_steps}", "reasoning_step", metadata={"id": reasoning_step_id}, is_start=False)
|
|
1622
|
-
break
|
|
1660
|
+
current_scratchpad += f"\n\n### Step {i+1}: Thought\n{thought}"
|
|
1661
|
+
log_event(f"**Thought**:\n{thought}", MSG_TYPE.MSG_TYPE_THOUGHT_CONTENT)
|
|
1623
1662
|
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1663
|
+
if not tool_name:
|
|
1664
|
+
# Handle error...
|
|
1665
|
+
break
|
|
1666
|
+
|
|
1667
|
+
# --- Handle special, non-executing tools ---
|
|
1668
|
+
if tool_name == "request_clarification":
|
|
1669
|
+
# Handle clarification...
|
|
1670
|
+
return {"final_answer": action.get("clarification_question", "Could you please provide more details?"), "final_scratchpad": current_scratchpad, "tool_calls": tool_calls_this_turn, "sources": sources_this_turn, "clarification_required": True, "error": None}
|
|
1671
|
+
|
|
1672
|
+
if tool_name == "final_answer":
|
|
1673
|
+
current_scratchpad += f"\n\n### Step {i+1}: Action\n- **Action:** Decided to formulate the final answer."
|
|
1674
|
+
log_event("**Action**: Formulate final answer.", MSG_TYPE.MSG_TYPE_THOUGHT_CHUNK)
|
|
1675
|
+
if reasoning_step_id: log_event(f"**Reasoning Step {i+1}/{max_reasoning_steps}**",MSG_TYPE.MSG_TYPE_STEP_END, event_id=reasoning_step_id)
|
|
1676
|
+
break
|
|
1638
1677
|
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1678
|
+
# --- Handle the `put_code_in_buffer` tool specifically ---
|
|
1679
|
+
if tool_name == 'put_code_in_buffer':
|
|
1680
|
+
code_gen_id = log_event(f"Generating code...", MSG_TYPE.MSG_TYPE_STEP_START, metadata={"name": "put_code_in_buffer", "id": "gencode"})
|
|
1681
|
+
code_prompt = tool_params.get("prompt", "Generate the requested code.")
|
|
1682
|
+
|
|
1683
|
+
# Use a specific system prompt to get raw code
|
|
1684
|
+
code_generation_system_prompt = "You are a code generation assistant. Generate ONLY the raw code based on the user's request. Do not add any explanations, markdown code fences, or other text outside of the code itself."
|
|
1685
|
+
generated_code = self.generate_code(prompt=code_prompt, system_prompt=code_generation_system_prompt + "\n----\n" + reasoning_prompt_template, **llm_generation_kwargs)
|
|
1686
|
+
|
|
1687
|
+
code_uuid = str(uuid.uuid4())
|
|
1688
|
+
generated_code_store[code_uuid] = generated_code
|
|
1689
|
+
|
|
1690
|
+
tool_result = {"status": "success", "code_id": code_uuid, "summary": f"Code generated successfully. Use this ID in the next tool call that requires code."}
|
|
1691
|
+
tool_calls_this_turn.append({"name": "put_code_in_buffer", "params": tool_params, "result": tool_result})
|
|
1692
|
+
observation_text = f"```json\n{json.dumps(tool_result, indent=2)}\n```"
|
|
1693
|
+
current_scratchpad += f"\n\n### Step {i+1}: Observation\n- **Action:** Called `{tool_name}`\n- **Result:**\n{observation_text}"
|
|
1694
|
+
log_event(f"**Observation**:Code generated with ID: {code_uuid}", MSG_TYPE.MSG_TYPE_OBSERVATION)
|
|
1695
|
+
if code_gen_id: log_event(f"Generating code...", MSG_TYPE.MSG_TYPE_TOOL_CALL, metadata={"id": code_gen_id, "result": tool_result})
|
|
1696
|
+
if reasoning_step_id: log_event(f"**Reasoning Step {i+1}/{max_reasoning_steps}**", MSG_TYPE.MSG_TYPE_STEP_END, event_id= reasoning_step_id)
|
|
1697
|
+
continue # Go to the next reasoning step immediately
|
|
1698
|
+
if tool_name == 'refactor_scratchpad':
|
|
1699
|
+
scratchpad_cleaning_prompt = f"""Enhance this scratchpad content to be more organized and comprehensive. Keep relevant experience information and remove any useless redundancies. Try to log learned things from the context so that you won't make the same mistakes again. Do not remove the main objective information or any crucial information that may be useful for the next iterations. Answer directly with the new scratchpad content without any comments.
|
|
1700
|
+
--- YOUR INTERNAL SCRATCHPAD (Work History & Analysis) ---
|
|
1701
|
+
{current_scratchpad}
|
|
1702
|
+
--- END OF SCRATCHPAD ---"""
|
|
1703
|
+
current_scratchpad = self.generate_text(scratchpad_cleaning_prompt)
|
|
1704
|
+
log_event(f"**New scratchpad**:\n{current_scratchpad}", MSG_TYPE.MSG_TYPE_SCRATCHPAD)
|
|
1705
|
+
|
|
1706
|
+
# --- Substitute UUIDs and Execute Standard Tools ---
|
|
1707
|
+
log_event(f"**Calling tool**: `{tool_name}` with params:\n{dict_to_markdown(tool_params)}", MSG_TYPE.MSG_TYPE_TOOL_CALL)
|
|
1708
|
+
_substitute_code_uuids_recursive(tool_params, generated_code_store)
|
|
1709
|
+
|
|
1710
|
+
tool_call_id = log_event(f"**Executing tool**: {tool_name}",MSG_TYPE.MSG_TYPE_STEP_START, metadata={"name": tool_name, "parameters": tool_params, "id":"executing tool"})
|
|
1711
|
+
tool_result = None
|
|
1712
|
+
try:
|
|
1713
|
+
if tool_name.startswith("research::") and use_data_store:
|
|
1714
|
+
store_name = tool_name.split("::")[1]
|
|
1715
|
+
rag_callable = use_data_store.get(store_name, {}).get("callable")
|
|
1716
|
+
query = tool_params.get("query", "")
|
|
1717
|
+
retrieved_chunks = rag_callable(query, rag_top_k=rag_top_k, rag_min_similarity_percent=rag_min_similarity_percent)
|
|
1718
|
+
if retrieved_chunks:
|
|
1719
|
+
sources_this_turn.extend(retrieved_chunks)
|
|
1720
|
+
tool_result = {"status": "success", "summary": f"Found {len(retrieved_chunks)} relevant chunks.", "chunks": retrieved_chunks}
|
|
1721
|
+
else:
|
|
1722
|
+
tool_result = {"status": "success", "summary": "No relevant documents found."}
|
|
1723
|
+
elif use_mcps and self.mcp:
|
|
1724
|
+
mcp_result = self.mcp.execute_tool(tool_name, tool_params, lollms_client_instance=self)
|
|
1725
|
+
tool_result = {"status": "success", "output": mcp_result} if not (isinstance(mcp_result, dict) and "error" in mcp_result) else {"status": "failure", **mcp_result}
|
|
1650
1726
|
else:
|
|
1651
|
-
tool_result = {"status": "
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
tool_result = {"status": "
|
|
1727
|
+
tool_result = {"status": "failure", "error": f"Tool '{tool_name}' not found."}
|
|
1728
|
+
except Exception as e:
|
|
1729
|
+
trace_exception(e)
|
|
1730
|
+
tool_result = {"status": "failure", "error": f"Exception executing tool: {str(e)}"}
|
|
1731
|
+
|
|
1732
|
+
if tool_call_id: log_event(f"**Executing tool**: {tool_name}", MSG_TYPE.MSG_TYPE_STEP_END, metadata={"result": tool_result}, event_id= tool_call_id)
|
|
1733
|
+
|
|
1734
|
+
observation_text = ""
|
|
1735
|
+
sanitized_result = {}
|
|
1736
|
+
if isinstance(tool_result, dict):
|
|
1737
|
+
sanitized_result = tool_result.copy()
|
|
1738
|
+
summarized_fields = {}
|
|
1739
|
+
for key, value in tool_result.items():
|
|
1740
|
+
if isinstance(value, str) and key.endswith("_base64") and len(value) > 256:
|
|
1741
|
+
sanitized_result[key] = f"[Image was generated. Size: {len(value)} bytes]"
|
|
1742
|
+
continue
|
|
1743
|
+
if isinstance(value, str) and len(self.tokenize(value)) > output_summarization_threshold:
|
|
1744
|
+
if streaming_callback: streaming_callback(f"Summarizing long output from field '{key}'...", MSG_TYPE.MSG_TYPE_STEP, {"type": "summarization"})
|
|
1745
|
+
summary = self.sequential_summarize(text=value, chunk_processing_prompt=f"Summarize key info from this chunk of '{key}'.", callback=streaming_callback)
|
|
1746
|
+
summarized_fields[key] = summary
|
|
1747
|
+
sanitized_result[key] = f"[Content summarized, see summary below. Original length: {len(value)} chars]"
|
|
1748
|
+
observation_text = f"```json\n{json.dumps(sanitized_result, indent=2)}\n```"
|
|
1749
|
+
if summarized_fields:
|
|
1750
|
+
observation_text += "\n\n**Summaries of Long Outputs:**"
|
|
1751
|
+
for key, summary in summarized_fields.items():
|
|
1752
|
+
observation_text += f"\n- **Summary of '{key}':**\n{summary}"
|
|
1655
1753
|
else:
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
for key, value in tool_result.items():
|
|
1669
|
-
if isinstance(value, str) and key.endswith("_base64") and len(value) > 256:
|
|
1670
|
-
sanitized_result[key] = f"[Image was generated. Size: {len(value)} bytes]"
|
|
1671
|
-
continue
|
|
1672
|
-
if isinstance(value, str) and len(self.tokenize(value)) > output_summarization_threshold:
|
|
1673
|
-
if streaming_callback: streaming_callback(f"Summarizing long output from field '{key}'...", MSG_TYPE.MSG_TYPE_STEP, {"type": "summarization"})
|
|
1674
|
-
summary = self.sequential_summarize(text=value, chunk_processing_prompt=f"Summarize key info from this chunk of '{key}'.", callback=streaming_callback)
|
|
1675
|
-
summarized_fields[key] = summary
|
|
1676
|
-
sanitized_result[key] = f"[Content summarized, see summary below. Original length: {len(value)} chars]"
|
|
1677
|
-
observation_text = f"```json\n{json.dumps(sanitized_result, indent=2)}\n```"
|
|
1678
|
-
if summarized_fields:
|
|
1679
|
-
observation_text += "\n\n**Summaries of Long Outputs:**"
|
|
1680
|
-
for key, summary in summarized_fields.items():
|
|
1681
|
-
observation_text += f"\n- **Summary of '{key}':**\n{summary}"
|
|
1682
|
-
else:
|
|
1683
|
-
observation_text = f"Tool returned non-dictionary output: {str(tool_result)}"
|
|
1684
|
-
|
|
1685
|
-
tool_calls_this_turn.append({"name": tool_name, "params": tool_params, "result": tool_result})
|
|
1686
|
-
current_scratchpad += f"\n\n### Step {i+1}: Observation\n- **Action:** Called `{tool_name}`\n- **Result:**\n{observation_text}"
|
|
1687
|
-
log_step(f"### Step {i+1}: Observation\n- **Action:** Called `{tool_name}`\n", "scratchpad", is_start=False)
|
|
1688
|
-
|
|
1689
|
-
if reasoning_step_id:
|
|
1690
|
-
log_step(f"Reasoning Step {i+1}/{max_reasoning_steps}", "reasoning_step", metadata={"id": reasoning_step_id}, is_start=False)
|
|
1691
|
-
|
|
1754
|
+
observation_text = f"Tool returned non-dictionary output: {str(tool_result)}"
|
|
1755
|
+
|
|
1756
|
+
tool_calls_this_turn.append({"name": tool_name, "params": tool_params, "result": tool_result})
|
|
1757
|
+
current_scratchpad += f"\n\n### Step {i+1}: Observation\n- **Action:** Called `{tool_name}`\n- **Result:**\n{observation_text}"
|
|
1758
|
+
log_event(f"**Observation**: Result from `{tool_name}`:\n{dict_to_markdown(sanitized_result)}", MSG_TYPE.MSG_TYPE_OBSERVATION)
|
|
1759
|
+
|
|
1760
|
+
if reasoning_step_id: log_event(f"**Reasoning Step {i+1}/{max_reasoning_steps}**", MSG_TYPE.MSG_TYPE_STEP_END, event_id = reasoning_step_id)
|
|
1761
|
+
except Exception as ex:
|
|
1762
|
+
trace_exception(ex)
|
|
1763
|
+
current_scratchpad += f"\n\n### Error : {ex}"
|
|
1764
|
+
if reasoning_step_id: log_event(f"**Reasoning Step {i+1}/{max_reasoning_steps}**", MSG_TYPE.MSG_TYPE_STEP_END, event_id = reasoning_step_id)
|
|
1765
|
+
|
|
1692
1766
|
# --- Final Answer Synthesis ---
|
|
1693
|
-
synthesis_id =
|
|
1767
|
+
synthesis_id = log_event("Synthesizing final answer...", MSG_TYPE.MSG_TYPE_STEP_START)
|
|
1694
1768
|
|
|
1695
|
-
final_answer_prompt = f"""
|
|
1769
|
+
final_answer_prompt = f"""
|
|
1696
1770
|
--- Original User Request ---
|
|
1697
1771
|
"{original_user_prompt}"
|
|
1698
1772
|
--- Your Internal Scratchpad (Actions Taken & Findings) ---
|
|
@@ -1702,11 +1776,25 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1702
1776
|
- If images were provided by the user, incorporate your analysis of them into the answer.
|
|
1703
1777
|
- Do not talk about your internal process unless it's necessary to explain why you couldn't find an answer.
|
|
1704
1778
|
"""
|
|
1779
|
+
if debug: log_prompt(final_answer_prompt, "FINAL ANSWER SYNTHESIS PROMPT")
|
|
1780
|
+
|
|
1781
|
+
|
|
1705
1782
|
final_answer_text = self.generate_text(prompt=final_answer_prompt, system_prompt=system_prompt, images=images, stream=streaming_callback is not None, streaming_callback=streaming_callback, temperature=final_answer_temperature, **llm_generation_kwargs)
|
|
1783
|
+
if type(final_answer_text) is dict:
|
|
1784
|
+
if streaming_callback:
|
|
1785
|
+
streaming_callback(final_answer_text["error"], MSG_TYPE.MSG_TYPE_EXCEPTION)
|
|
1786
|
+
return {
|
|
1787
|
+
"final_answer": "",
|
|
1788
|
+
"final_scratchpad": current_scratchpad,
|
|
1789
|
+
"tool_calls": tool_calls_this_turn,
|
|
1790
|
+
"sources": sources_this_turn,
|
|
1791
|
+
"clarification_required": False,
|
|
1792
|
+
"error": final_answer_text["error"]
|
|
1793
|
+
}
|
|
1706
1794
|
final_answer = self.remove_thinking_blocks(final_answer_text)
|
|
1795
|
+
if debug: log_prompt(final_answer_text, "FINAL ANSWER RESPONSE")
|
|
1707
1796
|
|
|
1708
|
-
if synthesis_id:
|
|
1709
|
-
log_step("Synthesizing final answer...", "final_answer_synthesis", metadata={"id": synthesis_id}, is_start=False)
|
|
1797
|
+
if synthesis_id: log_event("Synthesizing final answer...", MSG_TYPE.MSG_TYPE_STEP_END, event_id= synthesis_id)
|
|
1710
1798
|
|
|
1711
1799
|
return {
|
|
1712
1800
|
"final_answer": final_answer,
|
|
@@ -1716,7 +1804,6 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1716
1804
|
"clarification_required": False,
|
|
1717
1805
|
"error": None
|
|
1718
1806
|
}
|
|
1719
|
-
|
|
1720
1807
|
def generate_code(
|
|
1721
1808
|
self,
|
|
1722
1809
|
prompt,
|
|
@@ -1795,7 +1882,7 @@ Do not split the code in multiple tags.
|
|
|
1795
1882
|
while not last_code["is_complete"] and retries < max_retries:
|
|
1796
1883
|
retries += 1
|
|
1797
1884
|
ASCIIColors.info(f"Code block seems incomplete. Attempting continuation ({retries}/{max_retries})...")
|
|
1798
|
-
continuation_prompt = f"{
|
|
1885
|
+
continuation_prompt = f"{prompt}\n\nAssistant:\n{code_content}\n\n{self.user_full_header}The previous code block was incomplete. Continue the code exactly from where it left off. Do not repeat the previous part. Only provide the continuation inside a single {code_tag_format} code tag.\n{self.ai_full_header}"
|
|
1799
1886
|
|
|
1800
1887
|
continuation_response = self.generate_text(
|
|
1801
1888
|
continuation_prompt,
|
|
@@ -1831,6 +1918,97 @@ Do not split the code in multiple tags.
|
|
|
1831
1918
|
|
|
1832
1919
|
return code_content # Return the (potentially completed) code content or None
|
|
1833
1920
|
|
|
1921
|
+
def generate_structured_content(
|
|
1922
|
+
self,
|
|
1923
|
+
prompt,
|
|
1924
|
+
output_format,
|
|
1925
|
+
extra_system_prompt=None,
|
|
1926
|
+
**kwargs
|
|
1927
|
+
):
|
|
1928
|
+
"""
|
|
1929
|
+
Generates structured data (a dict) from a prompt using a JSON template.
|
|
1930
|
+
|
|
1931
|
+
This method is a high-level wrapper around `generate_code`, specializing it
|
|
1932
|
+
for JSON output. It ensures the LLM sticks to a predefined structure,
|
|
1933
|
+
and then parses the output into a Python dictionary.
|
|
1934
|
+
|
|
1935
|
+
Args:
|
|
1936
|
+
prompt (str):
|
|
1937
|
+
The user's request (e.g., "Extract the name, age, and city of the person described").
|
|
1938
|
+
output_format (dict or str):
|
|
1939
|
+
A Python dictionary or a JSON string representing the desired output
|
|
1940
|
+
structure. This will be used as a template for the LLM.
|
|
1941
|
+
Example: {"name": "string", "age": "integer", "city": "string"}
|
|
1942
|
+
extra_system_prompt (str, optional):
|
|
1943
|
+
Additional instructions for the system prompt, to be appended to the
|
|
1944
|
+
main instructions. Defaults to None.
|
|
1945
|
+
**kwargs:
|
|
1946
|
+
Additional keyword arguments to be passed directly to the
|
|
1947
|
+
`generate_code` method (e.g., temperature, max_size, top_k, debug).
|
|
1948
|
+
|
|
1949
|
+
Returns:
|
|
1950
|
+
dict: The parsed JSON data as a Python dictionary, or None if
|
|
1951
|
+
generation or parsing fails.
|
|
1952
|
+
"""
|
|
1953
|
+
# 1. Validate and prepare the template string from the output_format
|
|
1954
|
+
if isinstance(output_format, dict):
|
|
1955
|
+
# Convert the dictionary to a nicely formatted JSON string for the template
|
|
1956
|
+
template_str = json.dumps(output_format, indent=2)
|
|
1957
|
+
elif isinstance(output_format, str):
|
|
1958
|
+
# Assume it's already a valid JSON string template
|
|
1959
|
+
template_str = output_format
|
|
1960
|
+
else:
|
|
1961
|
+
# It's good practice to fail early for invalid input types
|
|
1962
|
+
raise TypeError("output_format must be a dict or a JSON string.")
|
|
1963
|
+
|
|
1964
|
+
# 2. Construct a specialized system prompt for structured data generation
|
|
1965
|
+
system_prompt = (
|
|
1966
|
+
"You are a highly skilled AI assistant that processes user requests "
|
|
1967
|
+
"and returns structured data in JSON format. You must strictly adhere "
|
|
1968
|
+
"to the provided JSON template, filling in the values accurately based "
|
|
1969
|
+
"on the user's prompt. Do not add any commentary, explanations, or text "
|
|
1970
|
+
"outside of the final JSON code block. Your entire response must be a single "
|
|
1971
|
+
"valid JSON object within a markdown code block."
|
|
1972
|
+
)
|
|
1973
|
+
if extra_system_prompt:
|
|
1974
|
+
system_prompt += f"\n\nAdditional instructions:\n{extra_system_prompt}"
|
|
1975
|
+
|
|
1976
|
+
# 3. Call the underlying generate_code method with JSON-specific settings
|
|
1977
|
+
if kwargs.get('debug'):
|
|
1978
|
+
ASCIIColors.info("Generating structured content...")
|
|
1979
|
+
|
|
1980
|
+
json_string = self.generate_code(
|
|
1981
|
+
prompt=prompt,
|
|
1982
|
+
system_prompt=system_prompt,
|
|
1983
|
+
template=template_str,
|
|
1984
|
+
language="json",
|
|
1985
|
+
code_tag_format="markdown", # Sticking to markdown is generally more reliable
|
|
1986
|
+
**kwargs # Pass other params like temperature, top_k, etc.
|
|
1987
|
+
)
|
|
1988
|
+
|
|
1989
|
+
# 4. Parse the result and return
|
|
1990
|
+
if not json_string:
|
|
1991
|
+
# generate_code already logs the error, so no need for another message
|
|
1992
|
+
return None
|
|
1993
|
+
|
|
1994
|
+
if kwargs.get('debug'):
|
|
1995
|
+
ASCIIColors.info("Parsing generated JSON string...")
|
|
1996
|
+
print(f"--- Raw JSON String ---\n{json_string}\n-----------------------")
|
|
1997
|
+
|
|
1998
|
+
try:
|
|
1999
|
+
# Use the provided robust parser
|
|
2000
|
+
parsed_json = self.robust_json_parser(json_string)
|
|
2001
|
+
|
|
2002
|
+
if parsed_json is None:
|
|
2003
|
+
ASCIIColors.warning("Failed to robustly parse the generated JSON.")
|
|
2004
|
+
return None
|
|
2005
|
+
|
|
2006
|
+
return parsed_json
|
|
2007
|
+
|
|
2008
|
+
except Exception as e:
|
|
2009
|
+
ASCIIColors.error(f"An unexpected error occurred during JSON parsing: {e}")
|
|
2010
|
+
return None
|
|
2011
|
+
|
|
1834
2012
|
|
|
1835
2013
|
def extract_code_blocks(self, text: str, format: str = "markdown") -> List[dict]:
|
|
1836
2014
|
"""
|
|
@@ -2065,7 +2243,7 @@ Do not split the code in multiple tags.
|
|
|
2065
2243
|
response_json_str = re.sub(r",\s*}", "}", response_json_str)
|
|
2066
2244
|
response_json_str = re.sub(r",\s*]", "]", response_json_str)
|
|
2067
2245
|
|
|
2068
|
-
parsed_response =
|
|
2246
|
+
parsed_response = robust_json_parser(response_json_str)
|
|
2069
2247
|
answer = parsed_response.get("answer")
|
|
2070
2248
|
explanation = parsed_response.get("explanation", "")
|
|
2071
2249
|
|
|
@@ -2159,7 +2337,7 @@ Do not split the code in multiple tags.
|
|
|
2159
2337
|
response_json_str = re.sub(r",\s*}", "}", response_json_str)
|
|
2160
2338
|
response_json_str = re.sub(r",\s*]", "]", response_json_str)
|
|
2161
2339
|
|
|
2162
|
-
result =
|
|
2340
|
+
result = robust_json_parser(response_json_str)
|
|
2163
2341
|
index = result.get("index")
|
|
2164
2342
|
explanation = result.get("explanation", "")
|
|
2165
2343
|
|
|
@@ -2232,7 +2410,7 @@ Do not split the code in multiple tags.
|
|
|
2232
2410
|
response_json_str = re.sub(r",\s*}", "}", response_json_str)
|
|
2233
2411
|
response_json_str = re.sub(r",\s*]", "]", response_json_str)
|
|
2234
2412
|
|
|
2235
|
-
result =
|
|
2413
|
+
result = robust_json_parser(response_json_str)
|
|
2236
2414
|
ranking = result.get("ranking")
|
|
2237
2415
|
explanations = result.get("explanations", []) if return_explanation else None
|
|
2238
2416
|
|
|
@@ -2856,5 +3034,3 @@ def chunk_text(text, tokenizer, detokenizer, chunk_size, overlap, use_separators
|
|
|
2856
3034
|
break
|
|
2857
3035
|
|
|
2858
3036
|
return chunks
|
|
2859
|
-
|
|
2860
|
-
|