lollms-client 0.29.2__py3-none-any.whl → 0.31.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 +1 -1
- lollms_client/llm_bindings/ollama/__init__.py +19 -0
- lollms_client/lollms_core.py +141 -50
- lollms_client/lollms_discussion.py +479 -58
- lollms_client/lollms_llm_binding.py +17 -0
- lollms_client/lollms_utilities.py +136 -0
- {lollms_client-0.29.2.dist-info → lollms_client-0.31.0.dist-info}/METADATA +61 -222
- {lollms_client-0.29.2.dist-info → lollms_client-0.31.0.dist-info}/RECORD +12 -11
- {lollms_client-0.29.2.dist-info → lollms_client-0.31.0.dist-info}/top_level.txt +1 -0
- test/test_lollms_discussion.py +368 -0
- {lollms_client-0.29.2.dist-info → lollms_client-0.31.0.dist-info}/WHEEL +0 -0
- {lollms_client-0.29.2.dist-info → lollms_client-0.31.0.dist-info}/licenses/LICENSE +0 -0
lollms_client/__init__.py
CHANGED
|
@@ -8,7 +8,7 @@ from lollms_client.lollms_utilities import PromptReshaper # Keep general utiliti
|
|
|
8
8
|
from lollms_client.lollms_mcp_binding import LollmsMCPBinding, LollmsMCPBindingManager
|
|
9
9
|
from lollms_client.lollms_llm_binding import LollmsLLMBindingManager
|
|
10
10
|
|
|
11
|
-
__version__ = "0.
|
|
11
|
+
__version__ = "0.31.0" # Updated version
|
|
12
12
|
|
|
13
13
|
# Optionally, you could define __all__ if you want to be explicit about exports
|
|
14
14
|
__all__ = [
|
|
@@ -11,6 +11,7 @@ from typing import Optional, Callable, List, Union, Dict
|
|
|
11
11
|
|
|
12
12
|
from ascii_colors import ASCIIColors, trace_exception
|
|
13
13
|
import pipmaster as pm
|
|
14
|
+
from lollms_client.lollms_utilities import ImageTokenizer
|
|
14
15
|
pm.ensure_packages(["ollama","pillow","tiktoken"])
|
|
15
16
|
|
|
16
17
|
|
|
@@ -468,6 +469,24 @@ class OllamaBinding(LollmsLLMBinding):
|
|
|
468
469
|
return -1
|
|
469
470
|
#return count_tokens_ollama(text, self.model_name, self.ollama_client)
|
|
470
471
|
return len(self.tokenize(text))
|
|
472
|
+
|
|
473
|
+
def count_image_tokens(self, image: str) -> int:
|
|
474
|
+
"""
|
|
475
|
+
Estimate the number of tokens for an image using ImageTokenizer based on self.model_name.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
image (str): Image to count tokens from. Either base64 string, path to image file, or URL.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
int: Estimated number of tokens for the image. Returns -1 on error.
|
|
482
|
+
"""
|
|
483
|
+
try:
|
|
484
|
+
# Delegate token counting to ImageTokenizer
|
|
485
|
+
return ImageTokenizer(self.model_name).count_image_tokens(image)
|
|
486
|
+
except Exception as e:
|
|
487
|
+
ASCIIColors.warning(f"Could not estimate image tokens: {e}")
|
|
488
|
+
return -1
|
|
489
|
+
|
|
471
490
|
def embed(self, text: str, **kwargs) -> List[float]:
|
|
472
491
|
"""
|
|
473
492
|
Get embeddings for the input text using Ollama API.
|
lollms_client/lollms_core.py
CHANGED
|
@@ -147,9 +147,6 @@ class LollmsClient():
|
|
|
147
147
|
available = self.binding_manager.get_available_bindings()
|
|
148
148
|
raise ValueError(f"Failed to create LLM binding: {binding_name}. Available: {available}")
|
|
149
149
|
|
|
150
|
-
# Determine the effective host address (use LLM binding's if initial was None)
|
|
151
|
-
effective_host_address = self.host_address
|
|
152
|
-
|
|
153
150
|
# --- Modality Binding Setup ---
|
|
154
151
|
self.tts_binding_manager = LollmsTTSBindingManager(tts_bindings_dir)
|
|
155
152
|
self.tti_binding_manager = LollmsTTIBindingManager(tti_bindings_dir)
|
|
@@ -433,7 +430,21 @@ class LollmsClient():
|
|
|
433
430
|
if self.binding:
|
|
434
431
|
return self.binding.count_tokens(text)
|
|
435
432
|
raise RuntimeError("LLM binding not initialized.")
|
|
436
|
-
|
|
433
|
+
|
|
434
|
+
def count_image_tokens(self, image: str) -> int:
|
|
435
|
+
"""
|
|
436
|
+
Estimate the number of tokens for an image using ImageTokenizer based on self.model_name.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
image (str): Image to count tokens from. Either base64 string, path to image file, or URL.
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
int: Estimated number of tokens for the image. Returns -1 on error.
|
|
443
|
+
"""
|
|
444
|
+
if self.binding:
|
|
445
|
+
return self.binding.count_image_tokens(image)
|
|
446
|
+
raise RuntimeError("LLM binding not initialized.")
|
|
447
|
+
|
|
437
448
|
def get_model_details(self) -> dict:
|
|
438
449
|
"""
|
|
439
450
|
Get model information from the active LLM binding.
|
|
@@ -1577,25 +1588,25 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1577
1588
|
|
|
1578
1589
|
# Add the new put_code_in_buffer tool definition
|
|
1579
1590
|
available_tools.append({
|
|
1580
|
-
"name": "put_code_in_buffer",
|
|
1591
|
+
"name": "local_tools::put_code_in_buffer",
|
|
1581
1592
|
"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.""",
|
|
1582
1593
|
"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"]}
|
|
1583
1594
|
})
|
|
1584
1595
|
available_tools.append({
|
|
1585
|
-
"name": "view_generated_code",
|
|
1596
|
+
"name": "local_tools::view_generated_code",
|
|
1586
1597
|
"description": """Views the code that was generated and stored to the buffer. You need to have a valid uuid of the generated code.""",
|
|
1587
1598
|
"input_schema": {"type": "object", "properties": {"code_id": {"type": "string", "description": "The case sensitive uuid of the generated code."}}, "required": ["uuid"]}
|
|
1588
1599
|
})
|
|
1589
1600
|
# Add the new refactor_scratchpad tool definition
|
|
1590
1601
|
available_tools.append({
|
|
1591
|
-
"name": "refactor_scratchpad",
|
|
1602
|
+
"name": "local_tools::refactor_scratchpad",
|
|
1592
1603
|
"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.",
|
|
1593
1604
|
"input_schema": {"type": "object", "properties": {}}
|
|
1594
1605
|
})
|
|
1595
1606
|
|
|
1596
1607
|
formatted_tools_list = "\n".join([f"**{t['name']}**:\n{t['description']}\ninput schema:\n{json.dumps(t['input_schema'])}" for t in available_tools])
|
|
1597
|
-
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."
|
|
1598
|
-
formatted_tools_list += "\n**final_answer**:\nUse when you are ready to respond to the user. this tool has no parameters."
|
|
1608
|
+
formatted_tools_list += "\n**local_tools::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."
|
|
1609
|
+
formatted_tools_list += "\n**local_tools::final_answer**:\nUse when you are ready to respond to the user. this tool has no parameters."
|
|
1599
1610
|
|
|
1600
1611
|
if discovery_step_id: log_event(f"**Discovering tools** found {len(available_tools)} tools",MSG_TYPE.MSG_TYPE_STEP_END, event_id=discovery_step_id)
|
|
1601
1612
|
|
|
@@ -1621,15 +1632,16 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1621
1632
|
- Does the latest observation completely fulfill the user's original request?
|
|
1622
1633
|
- If YES, your next action MUST be to use the `final_answer` tool.
|
|
1623
1634
|
- 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.
|
|
1624
|
-
- If you are stuck or the request is ambiguous, use `request_clarification`.
|
|
1635
|
+
- If you are stuck or the request is ambiguous, use `local_tools::request_clarification`.
|
|
1625
1636
|
3. **ACT:** Formulate your decision as a JSON object.
|
|
1637
|
+
** Important ** Always use this format alias::tool_name to call the tool
|
|
1626
1638
|
"""
|
|
1627
1639
|
action_template = {
|
|
1628
1640
|
"thought": "My detailed analysis of the last observation and my reasoning for the next action and how it integrates with my global plan.",
|
|
1629
1641
|
"action": {
|
|
1630
|
-
"tool_name": "The single tool to use (e.g., 'put_code_in_buffer', '
|
|
1642
|
+
"tool_name": "The single tool to use (e.g., 'local_tools::put_code_in_buffer', 'local_tools::final_answer').",
|
|
1631
1643
|
"tool_params": {"param1": "value1"},
|
|
1632
|
-
"clarification_question": "(string, ONLY if tool_name is 'request_clarification')"
|
|
1644
|
+
"clarification_question": "(string, ONLY if tool_name is 'local_tools::request_clarification')"
|
|
1633
1645
|
}
|
|
1634
1646
|
}
|
|
1635
1647
|
if debug: log_prompt(reasoning_prompt_template, f"REASONING PROMPT (Step {i+1})")
|
|
@@ -1667,18 +1679,22 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1667
1679
|
break
|
|
1668
1680
|
|
|
1669
1681
|
# --- Handle special, non-executing tools ---
|
|
1670
|
-
if tool_name == "request_clarification":
|
|
1682
|
+
if tool_name == "local_tools::request_clarification":
|
|
1671
1683
|
# Handle clarification...
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1684
|
+
if isinstance(action, dict):
|
|
1685
|
+
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}
|
|
1686
|
+
elif isinstance(action, str):
|
|
1687
|
+
return {"final_answer": action, "final_scratchpad": current_scratchpad, "tool_calls": tool_calls_this_turn, "sources": sources_this_turn, "clarification_required": True, "error": None}
|
|
1688
|
+
else:
|
|
1689
|
+
return {"final_answer": "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}
|
|
1690
|
+
if tool_name == "local_tools::final_answer":
|
|
1675
1691
|
current_scratchpad += f"\n\n### Step {i+1}: Action\n- **Action:** Decided to formulate the final answer."
|
|
1676
1692
|
log_event("**Action**: Formulate final answer.", MSG_TYPE.MSG_TYPE_THOUGHT_CHUNK)
|
|
1677
1693
|
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)
|
|
1678
1694
|
break
|
|
1679
1695
|
|
|
1680
1696
|
# --- Handle the `put_code_in_buffer` tool specifically ---
|
|
1681
|
-
if tool_name == 'put_code_in_buffer':
|
|
1697
|
+
if tool_name == 'local_tools::put_code_in_buffer':
|
|
1682
1698
|
code_gen_id = log_event(f"Generating code...", MSG_TYPE.MSG_TYPE_STEP_START, metadata={"name": "put_code_in_buffer", "id": "gencode"})
|
|
1683
1699
|
code_prompt = tool_params.get("prompt", "Generate the requested code.")
|
|
1684
1700
|
|
|
@@ -1697,7 +1713,7 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1697
1713
|
if code_gen_id: log_event(f"Generating code...", MSG_TYPE.MSG_TYPE_TOOL_CALL, metadata={"id": code_gen_id, "result": tool_result})
|
|
1698
1714
|
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)
|
|
1699
1715
|
continue # Go to the next reasoning step immediately
|
|
1700
|
-
if tool_name == 'view_generated_code':
|
|
1716
|
+
if tool_name == 'local_tools::view_generated_code':
|
|
1701
1717
|
code_id = tool_params.get("code_id")
|
|
1702
1718
|
if code_id:
|
|
1703
1719
|
tool_result = {"status": "success", "code_id": code_id, "generated_code":generated_code_store[code_uuid]}
|
|
@@ -1707,7 +1723,7 @@ Provide your response as a single JSON object inside a JSON markdown tag. Use th
|
|
|
1707
1723
|
current_scratchpad += f"\n\n### Step {i+1}: Observation\n- **Action:** Called `{tool_name}`\n- **Result:**\n{observation_text}"
|
|
1708
1724
|
log_event(f"Result from `{tool_name}`:\n```\n{generated_code_store[code_uuid]}\n```\n", MSG_TYPE.MSG_TYPE_TOOL_CALL, metadata={"id": code_gen_id, "result": tool_result})
|
|
1709
1725
|
continue
|
|
1710
|
-
if tool_name == 'refactor_scratchpad':
|
|
1726
|
+
if tool_name == 'local_tools::refactor_scratchpad':
|
|
1711
1727
|
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.
|
|
1712
1728
|
--- YOUR INTERNAL SCRATCHPAD (Work History & Analysis) ---
|
|
1713
1729
|
{current_scratchpad}
|
|
@@ -2961,13 +2977,12 @@ Provide the final aggregated answer in {output_format} format, directly addressi
|
|
|
2961
2977
|
callback("Deep analysis complete.", MSG_TYPE.MSG_TYPE_STEP_END)
|
|
2962
2978
|
return final_output
|
|
2963
2979
|
|
|
2964
|
-
|
|
2965
|
-
def summarize(
|
|
2980
|
+
def long_context_processing(
|
|
2966
2981
|
self,
|
|
2967
|
-
|
|
2982
|
+
text_to_process: str,
|
|
2968
2983
|
contextual_prompt: Optional[str] = None,
|
|
2969
|
-
chunk_size_tokens: int =
|
|
2970
|
-
overlap_tokens: int =
|
|
2984
|
+
chunk_size_tokens: int|None = None,
|
|
2985
|
+
overlap_tokens: int = 0,
|
|
2971
2986
|
streaming_callback: Optional[Callable] = None,
|
|
2972
2987
|
**kwargs
|
|
2973
2988
|
) -> str:
|
|
@@ -2979,7 +2994,7 @@ Provide the final aggregated answer in {output_format} format, directly addressi
|
|
|
2979
2994
|
2. **Synthesize:** It then takes all the chunk summaries and performs a final summarization pass to create a single, coherent, and comprehensive summary.
|
|
2980
2995
|
|
|
2981
2996
|
Args:
|
|
2982
|
-
|
|
2997
|
+
text_to_process (str): The long text content to be summarized.
|
|
2983
2998
|
contextual_prompt (Optional[str], optional): A specific instruction to guide the summary's focus.
|
|
2984
2999
|
For example, "Summarize the text focusing on the financial implications."
|
|
2985
3000
|
Defaults to None.
|
|
@@ -2990,26 +3005,47 @@ Provide the final aggregated answer in {output_format} format, directly addressi
|
|
|
2990
3005
|
is not lost at the boundaries. Defaults to 250.
|
|
2991
3006
|
streaming_callback (Optional[Callable], optional): A callback function to receive real-time updates
|
|
2992
3007
|
on the process (e.g., which chunk is being processed).
|
|
3008
|
+
It receives a message, a message type, and optional metadata.
|
|
2993
3009
|
Defaults to None.
|
|
2994
3010
|
**kwargs: Additional keyword arguments to be passed to the generation method (e.g., temperature, top_p).
|
|
2995
3011
|
|
|
2996
3012
|
Returns:
|
|
2997
3013
|
str: The final, comprehensive summary of the text.
|
|
2998
3014
|
"""
|
|
2999
|
-
if not
|
|
3015
|
+
if not text_to_process and len(kwargs.get("images",[]))==0:
|
|
3000
3016
|
return ""
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3017
|
+
if not text_to_process:
|
|
3018
|
+
text_to_process=""
|
|
3019
|
+
tokens = []
|
|
3020
|
+
else:
|
|
3021
|
+
# Use the binding's tokenizer for accurate chunking
|
|
3022
|
+
tokens = self.binding.tokenize(text_to_process)
|
|
3023
|
+
if chunk_size_tokens is None:
|
|
3024
|
+
chunk_size_tokens = self.default_ctx_size//2
|
|
3004
3025
|
|
|
3005
3026
|
if len(tokens) <= chunk_size_tokens:
|
|
3006
3027
|
if streaming_callback:
|
|
3007
|
-
streaming_callback("Text is short enough for a single
|
|
3028
|
+
streaming_callback("Text is short enough for a single process.", MSG_TYPE.MSG_TYPE_STEP, {"progress": 0})
|
|
3029
|
+
system_prompt = ("You are a content processor expert.\n"
|
|
3030
|
+
"You perform tasks on the content as requested by the user.\n\n"
|
|
3031
|
+
"--- Content ---\n"
|
|
3032
|
+
f"{text_to_process}\n\n"
|
|
3033
|
+
"** Important **\n"
|
|
3034
|
+
"Strictly adhere to the user prompt.\n"
|
|
3035
|
+
"Do not add comments unless asked to do so.\n"
|
|
3036
|
+
)
|
|
3037
|
+
if "system_prompt" in kwargs:
|
|
3038
|
+
system_prompt += "-- Extra instructions --\n"+ kwargs["system_prompt"] +"\n"
|
|
3039
|
+
del kwargs["system_prompt"]
|
|
3040
|
+
prompt_objective = contextual_prompt or "Provide a comprehensive summary of the content."
|
|
3041
|
+
final_prompt = f"{prompt_objective}"
|
|
3008
3042
|
|
|
3009
|
-
|
|
3010
|
-
final_prompt = f"{prompt_objective}\n\n--- Text to Summarize ---\n{text_to_summarize}"
|
|
3043
|
+
processed_output = self.generate_text(final_prompt, system_prompt=system_prompt, **kwargs)
|
|
3011
3044
|
|
|
3012
|
-
|
|
3045
|
+
if streaming_callback:
|
|
3046
|
+
streaming_callback("Content processed.", MSG_TYPE.MSG_TYPE_STEP, {"progress": 100})
|
|
3047
|
+
|
|
3048
|
+
return processed_output
|
|
3013
3049
|
|
|
3014
3050
|
# --- Stage 1: Chunking and Independent Summarization ---
|
|
3015
3051
|
chunks = []
|
|
@@ -3021,52 +3057,107 @@ Provide the final aggregated answer in {output_format} format, directly addressi
|
|
|
3021
3057
|
|
|
3022
3058
|
chunk_summaries = []
|
|
3023
3059
|
|
|
3060
|
+
# Total steps include each chunk plus the final synthesis step
|
|
3061
|
+
total_steps = len(chunks) + 1
|
|
3062
|
+
|
|
3024
3063
|
# Define the prompt for summarizing each chunk
|
|
3025
3064
|
summarization_objective = contextual_prompt or "Summarize the key points of the following text excerpt."
|
|
3026
|
-
|
|
3065
|
+
system_prompt = ("You are a sequential document processing agent.\n"
|
|
3066
|
+
"The process is done in two phases:\n"
|
|
3067
|
+
"** Phase1 : **\n"
|
|
3068
|
+
"Sequencially extracting information from the text chunks and adding them to the scratchpad.\n"
|
|
3069
|
+
"** Phase2: **\n"
|
|
3070
|
+
"Synthesizing a comprehensive Response using the scratchpad content given the objective formatting instructions if applicable.\n"
|
|
3071
|
+
"We are now performing ** Phase 1 **, and we are processing chunk number {{chunk_id}}.\n"
|
|
3072
|
+
"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"
|
|
3073
|
+
"Add the information to the scratchpad while strictly adhering to the Global objective extraction instructions:\n"
|
|
3074
|
+
"-- Sequencial Scratchpad --\n"
|
|
3075
|
+
"{{scratchpad}}\n"
|
|
3076
|
+
"** Important **\n"
|
|
3077
|
+
"Respond only with the extracted information from the current chunk without repeating things that are already in the scratchpad.\n"
|
|
3078
|
+
"Strictly adhere to the Global objective content for the extraction phase.\n"
|
|
3079
|
+
"Do not add comments.\n"
|
|
3080
|
+
)
|
|
3081
|
+
if "system_prompt" in kwargs:
|
|
3082
|
+
system_prompt += "-- Extra instructions --\n"+ kwargs["system_prompt"] +"\n"
|
|
3083
|
+
del kwargs["system_prompt"]
|
|
3084
|
+
chunk_summary_prompt_template = f"--- Global objective ---\n{summarization_objective}\n\n--- Text Excerpt ---\n{{chunk_text}}"
|
|
3027
3085
|
|
|
3028
3086
|
for i, chunk in enumerate(chunks):
|
|
3087
|
+
progress_before = (i / total_steps) * 100
|
|
3029
3088
|
if streaming_callback:
|
|
3030
|
-
streaming_callback(
|
|
3089
|
+
streaming_callback(
|
|
3090
|
+
f"Processing chunk {i + 1} of {len(chunks)}...",
|
|
3091
|
+
MSG_TYPE.MSG_TYPE_STEP_START,
|
|
3092
|
+
{"id": f"chunk_{i+1}", "progress": progress_before}
|
|
3093
|
+
)
|
|
3031
3094
|
|
|
3032
3095
|
prompt = chunk_summary_prompt_template.format(chunk_text=chunk)
|
|
3033
|
-
|
|
3096
|
+
processed_system_prompt = system_prompt.format(chunk_id=i,scratchpad="\n\n---\n\n".join(chunk_summaries))
|
|
3034
3097
|
try:
|
|
3035
3098
|
# Generate summary for the current chunk
|
|
3036
|
-
chunk_summary = self.generate_text(prompt, **kwargs)
|
|
3099
|
+
chunk_summary = self.generate_text(prompt, system_prompt=processed_system_prompt, **kwargs)
|
|
3037
3100
|
chunk_summaries.append(chunk_summary)
|
|
3101
|
+
|
|
3102
|
+
progress_after = ((i + 1) / total_steps) * 100
|
|
3038
3103
|
if streaming_callback:
|
|
3039
|
-
streaming_callback(
|
|
3104
|
+
streaming_callback(
|
|
3105
|
+
f"Chunk {i + 1} processed. Progress: {progress_after:.0f}%",
|
|
3106
|
+
MSG_TYPE.MSG_TYPE_STEP_END,
|
|
3107
|
+
{"id": f"chunk_{i+1}", "output_snippet": chunk_summary[:100], "progress": progress_after}
|
|
3108
|
+
)
|
|
3040
3109
|
except Exception as e:
|
|
3041
3110
|
trace_exception(e)
|
|
3042
3111
|
if streaming_callback:
|
|
3043
|
-
streaming_callback(f"Failed to
|
|
3112
|
+
streaming_callback(f"Failed to process chunk {i+1}: {e}", MSG_TYPE.MSG_TYPE_EXCEPTION)
|
|
3044
3113
|
# Still add a placeholder to not break the chain
|
|
3045
|
-
chunk_summaries.append(f"[Error
|
|
3114
|
+
chunk_summaries.append(f"[Error processing chunk {i+1}]")
|
|
3046
3115
|
|
|
3047
3116
|
# --- Stage 2: Final Synthesis of All Chunk Summaries ---
|
|
3117
|
+
progress_before_synthesis = (len(chunks) / total_steps) * 100
|
|
3048
3118
|
if streaming_callback:
|
|
3049
|
-
streaming_callback(
|
|
3119
|
+
streaming_callback(
|
|
3120
|
+
"Processing the scratchpad content into a final version...",
|
|
3121
|
+
MSG_TYPE.MSG_TYPE_STEP_START,
|
|
3122
|
+
{"id": "final_synthesis", "progress": progress_before_synthesis}
|
|
3123
|
+
)
|
|
3050
3124
|
|
|
3051
3125
|
combined_summaries = "\n\n---\n\n".join(chunk_summaries)
|
|
3052
3126
|
|
|
3053
3127
|
# Define the prompt for the final synthesis
|
|
3054
3128
|
synthesis_objective = contextual_prompt or "Create a single, final, coherent, and comprehensive summary."
|
|
3129
|
+
system_prompt = ("You are a sequential document processing agent.\n"
|
|
3130
|
+
"The process is done in two phases:\n"
|
|
3131
|
+
"** Phase1 : **\n"
|
|
3132
|
+
"Sequencially extracting information from the text chunks and adding them to the scratchpad.\n"
|
|
3133
|
+
"** Phase2: **\n"
|
|
3134
|
+
"Synthesizing a comprehensive Response using the scratchpad content given the objective formatting instructions if applicable.\n"
|
|
3135
|
+
"\n"
|
|
3136
|
+
"We are now performing ** Phase 2 **.\n"
|
|
3137
|
+
"Your job is to use the extracted information to fulfill the user prompt objectives.\n"
|
|
3138
|
+
"Make sure you respect the user formatting if provided and if not, then use markdown output format."
|
|
3139
|
+
"-- Sequencial Scratchpad --\n"
|
|
3140
|
+
f"{combined_summaries}\n"
|
|
3141
|
+
"** Important **\n"
|
|
3142
|
+
"Respond only with the requested task without extra comments unless told to.\n"
|
|
3143
|
+
"Strictly adhere to the Global objective content for the extraction phase.\n"
|
|
3144
|
+
"Do not add comments.\n"
|
|
3145
|
+
)
|
|
3055
3146
|
final_synthesis_prompt = (
|
|
3056
|
-
"
|
|
3057
|
-
|
|
3058
|
-
"Please remove any redundancy and ensure a smooth, logical flow.\n\n"
|
|
3059
|
-
"--- Collection of Summaries ---\n"
|
|
3060
|
-
f"{combined_summaries}\n\n"
|
|
3061
|
-
"--- Final Comprehensive Summary ---"
|
|
3147
|
+
f"--- Global objective ---\n{synthesis_objective}\n\n"
|
|
3148
|
+
"--- Final Response ---"
|
|
3062
3149
|
)
|
|
3063
3150
|
|
|
3064
|
-
|
|
3151
|
+
final_answer = self.generate_text(final_synthesis_prompt, system_prompt=system_prompt, **kwargs)
|
|
3065
3152
|
|
|
3066
3153
|
if streaming_callback:
|
|
3067
|
-
streaming_callback(
|
|
3154
|
+
streaming_callback(
|
|
3155
|
+
"Final summary synthesized.",
|
|
3156
|
+
MSG_TYPE.MSG_TYPE_STEP_END,
|
|
3157
|
+
{"id": "final_synthesis", "progress": 100}
|
|
3158
|
+
)
|
|
3068
3159
|
|
|
3069
|
-
return
|
|
3160
|
+
return final_answer.strip()
|
|
3070
3161
|
|
|
3071
3162
|
def chunk_text(text, tokenizer, detokenizer, chunk_size, overlap, use_separators=True):
|
|
3072
3163
|
"""
|