lollms-client 1.5.8__py3-none-any.whl → 1.6.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/lollms_core.py +271 -311
- lollms_client/tti_bindings/diffusers/__init__.py +50 -12
- {lollms_client-1.5.8.dist-info → lollms_client-1.6.0.dist-info}/METADATA +105 -3
- {lollms_client-1.5.8.dist-info → lollms_client-1.6.0.dist-info}/RECORD +8 -8
- {lollms_client-1.5.8.dist-info → lollms_client-1.6.0.dist-info}/WHEEL +0 -0
- {lollms_client-1.5.8.dist-info → lollms_client-1.6.0.dist-info}/licenses/LICENSE +0 -0
- {lollms_client-1.5.8.dist-info → lollms_client-1.6.0.dist-info}/top_level.txt +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__ = "1.
|
|
11
|
+
__version__ = "1.6.0" # Updated version
|
|
12
12
|
|
|
13
13
|
# Optionally, you could define __all__ if you want to be explicit about exports
|
|
14
14
|
__all__ = [
|
lollms_client/lollms_core.py
CHANGED
|
@@ -5732,357 +5732,317 @@ Provide the final aggregated answer in {output_format} format, directly addressi
|
|
|
5732
5732
|
callback("Deep analysis complete.", MSG_TYPE.MSG_TYPE_STEP_END)
|
|
5733
5733
|
return final_output
|
|
5734
5734
|
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5735
|
+
def long_context_processing(
|
|
5736
|
+
self,
|
|
5737
|
+
text_to_process: str,
|
|
5738
|
+
contextual_prompt: Optional[str] = None,
|
|
5739
|
+
system_prompt: str | None = None,
|
|
5740
|
+
context_fill_percentage: float = 0.75,
|
|
5741
|
+
overlap_tokens: int = 150, # Added a default for better context continuity
|
|
5742
|
+
expected_generation_tokens: int = 1500,
|
|
5743
|
+
streaming_callback: Optional[Callable] = None,
|
|
5744
|
+
return_scratchpad_only: bool = False,
|
|
5745
|
+
debug: bool = True,
|
|
5746
|
+
**kwargs
|
|
5747
|
+
) -> str:
|
|
5748
|
+
"""
|
|
5749
|
+
Processes long text by breaking it down into chunks, analyzing each one incrementally,
|
|
5750
|
+
and synthesizing the results into a comprehensive final response based on a user-defined objective.
|
|
5751
|
+
"""
|
|
5752
|
+
|
|
5753
|
+
if debug:
|
|
5754
|
+
print(f"\n🔧 DEBUG: Starting processing with {len(text_to_process):,} characters")
|
|
5755
|
+
|
|
5756
|
+
# Validate context fill percentage
|
|
5757
|
+
if not (0.1 <= context_fill_percentage <= 0.9):
|
|
5758
|
+
raise ValueError(f"context_fill_percentage must be between 0.1 and 0.9, got {context_fill_percentage}")
|
|
5759
|
+
|
|
5760
|
+
# Get context size
|
|
5761
|
+
try:
|
|
5762
|
+
context_size = self.llm.get_context_size() or 8192 # Using a more modern default
|
|
5763
|
+
except:
|
|
5764
|
+
context_size = 8192
|
|
5765
|
+
|
|
5766
|
+
if debug:
|
|
5767
|
+
print(f"🔧 DEBUG: Context size: {context_size}, Fill %: {context_fill_percentage}")
|
|
5768
|
+
|
|
5769
|
+
# Handle empty input
|
|
5770
|
+
if not text_to_process:
|
|
5771
|
+
return ""
|
|
5772
|
+
|
|
5773
|
+
# Use a simple word-based split for token estimation
|
|
5774
|
+
tokens = text_to_process.split()
|
|
5775
|
+
if debug:
|
|
5776
|
+
print(f"🔧 DEBUG: Tokenized into {len(tokens):,} word tokens")
|
|
5777
|
+
|
|
5778
|
+
# Dynamic token budget calculation
|
|
5779
|
+
def calculate_token_budgets(scratchpad_content: str = "", step_num: int = 0) -> dict:
|
|
5780
|
+
# Generic prompt templates are more concise
|
|
5781
|
+
base_system_tokens = 150
|
|
5782
|
+
user_template_tokens = 250
|
|
5783
|
+
scratchpad_tokens = len(scratchpad_content.split()) * 1.3 if scratchpad_content else 0
|
|
5784
|
+
|
|
5785
|
+
used_tokens = base_system_tokens + user_template_tokens + scratchpad_tokens + expected_generation_tokens
|
|
5786
|
+
total_budget = int(context_size * context_fill_percentage)
|
|
5787
|
+
available_for_chunk = max(500, int(total_budget - used_tokens)) # Ensure a reasonable minimum chunk size
|
|
5788
|
+
|
|
5789
|
+
budget_info = {
|
|
5790
|
+
"total_budget": total_budget,
|
|
5791
|
+
"chunk_budget": available_for_chunk,
|
|
5792
|
+
"efficiency_ratio": available_for_chunk / total_budget if total_budget > 0 else 0,
|
|
5793
|
+
"scratchpad_tokens": int(scratchpad_tokens),
|
|
5794
|
+
"used_tokens": int(used_tokens)
|
|
5795
|
+
}
|
|
5749
5796
|
|
|
5750
5797
|
if debug:
|
|
5751
|
-
print(f"
|
|
5798
|
+
print(f"🔧 DEBUG Step {step_num}: Budget = {available_for_chunk}/{total_budget} tokens, "
|
|
5799
|
+
f"Scratchpad = {int(scratchpad_tokens)} tokens")
|
|
5752
5800
|
|
|
5753
|
-
|
|
5754
|
-
if not (0.1 <= context_fill_percentage <= 0.9):
|
|
5755
|
-
raise ValueError(f"context_fill_percentage must be between 0.1 and 0.9, got {context_fill_percentage}")
|
|
5801
|
+
return budget_info
|
|
5756
5802
|
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
except:
|
|
5761
|
-
context_size = 4096
|
|
5803
|
+
# Initial budget calculation
|
|
5804
|
+
initial_budget = calculate_token_budgets()
|
|
5805
|
+
chunk_size_tokens = initial_budget["chunk_budget"]
|
|
5762
5806
|
|
|
5763
|
-
|
|
5764
|
-
|
|
5807
|
+
if debug:
|
|
5808
|
+
print(f"🔧 DEBUG: Initial chunk size: {chunk_size_tokens} word tokens")
|
|
5765
5809
|
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5810
|
+
if streaming_callback:
|
|
5811
|
+
streaming_callback(
|
|
5812
|
+
f"Context Budget: {initial_budget['chunk_budget']:,}/{initial_budget['total_budget']:,} tokens "
|
|
5813
|
+
f"({initial_budget['efficiency_ratio']:.1%} efficiency)",
|
|
5814
|
+
MSG_TYPE.MSG_TYPE_STEP,
|
|
5815
|
+
{"budget_info": initial_budget}
|
|
5816
|
+
)
|
|
5769
5817
|
|
|
5770
|
-
|
|
5771
|
-
|
|
5818
|
+
# Single pass for short content
|
|
5819
|
+
if len(tokens) <= chunk_size_tokens:
|
|
5772
5820
|
if debug:
|
|
5773
|
-
print(
|
|
5774
|
-
|
|
5775
|
-
# Dynamic token budget calculation
|
|
5776
|
-
def calculate_token_budgets(scratchpad_content: str = "", step_num: int = 0) -> dict:
|
|
5777
|
-
base_system_tokens = 250 # Increased for literary-specific prompts
|
|
5778
|
-
user_template_tokens = 300 # Increased for detailed instructions
|
|
5779
|
-
scratchpad_tokens = len(scratchpad_content.split()) * 1.3 if scratchpad_content else 0
|
|
5780
|
-
|
|
5781
|
-
used_tokens = base_system_tokens + user_template_tokens + scratchpad_tokens + expected_generation_tokens
|
|
5782
|
-
total_budget = int(context_size * context_fill_percentage)
|
|
5783
|
-
available_for_chunk = max(400, int(total_budget - used_tokens)) # Increased minimum for better context
|
|
5784
|
-
|
|
5785
|
-
budget_info = {
|
|
5786
|
-
"total_budget": total_budget,
|
|
5787
|
-
"chunk_budget": available_for_chunk,
|
|
5788
|
-
"efficiency_ratio": available_for_chunk / total_budget,
|
|
5789
|
-
"scratchpad_tokens": int(scratchpad_tokens),
|
|
5790
|
-
"used_tokens": int(used_tokens)
|
|
5791
|
-
}
|
|
5821
|
+
print("🔧 DEBUG: Content is short enough for single-pass processing")
|
|
5792
5822
|
|
|
5823
|
+
if streaming_callback:
|
|
5824
|
+
streaming_callback("Content fits in a single pass", MSG_TYPE.MSG_TYPE_STEP, {})
|
|
5825
|
+
|
|
5826
|
+
# Generic single-pass system prompt
|
|
5827
|
+
system_prompt = (
|
|
5828
|
+
"You are an expert AI assistant for text analysis and summarization. "
|
|
5829
|
+
"Your task is to carefully analyze the provided text and generate a comprehensive, "
|
|
5830
|
+
"accurate, and well-structured response that directly addresses the user's objective. "
|
|
5831
|
+
"Focus on extracting key information, identifying main themes, and synthesizing the content effectively."
|
|
5832
|
+
)
|
|
5833
|
+
|
|
5834
|
+
prompt_objective = contextual_prompt or "Provide a comprehensive summary and analysis of the provided text."
|
|
5835
|
+
final_prompt = f"Objective: {prompt_objective}\n\n--- Full Text Content ---\n{text_to_process}"
|
|
5836
|
+
|
|
5837
|
+
try:
|
|
5838
|
+
result = self.remove_thinking_blocks(self.llm.generate_text(final_prompt, system_prompt=system_prompt, **kwargs))
|
|
5793
5839
|
if debug:
|
|
5794
|
-
print(f"🔧 DEBUG
|
|
5795
|
-
|
|
5840
|
+
print(f"🔧 DEBUG: Single-pass result: {len(result):,} characters")
|
|
5841
|
+
return result
|
|
5842
|
+
except Exception as e:
|
|
5843
|
+
if debug:
|
|
5844
|
+
print(f"🔧 DEBUG: Single-pass processing failed: {e}")
|
|
5845
|
+
return f"Error in single-pass processing: {e}"
|
|
5846
|
+
|
|
5847
|
+
# Multi-chunk processing for long content
|
|
5848
|
+
if debug:
|
|
5849
|
+
print("🔧 DEBUG: Using multi-chunk processing for long content")
|
|
5796
5850
|
|
|
5797
|
-
|
|
5851
|
+
chunk_summaries = []
|
|
5852
|
+
current_position = 0
|
|
5853
|
+
step_number = 1
|
|
5798
5854
|
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5855
|
+
while current_position < len(tokens):
|
|
5856
|
+
# Recalculate budget for each step for dynamic adaptation
|
|
5857
|
+
current_scratchpad = "\n\n---\n\n".join(chunk_summaries)
|
|
5858
|
+
current_budget = calculate_token_budgets(current_scratchpad, step_number)
|
|
5859
|
+
adaptive_chunk_size = max(500, current_budget["chunk_budget"])
|
|
5860
|
+
|
|
5861
|
+
# Extract the next chunk of text
|
|
5862
|
+
chunk_end = min(current_position + adaptive_chunk_size, len(tokens))
|
|
5863
|
+
chunk_tokens = tokens[current_position:chunk_end]
|
|
5864
|
+
chunk_text = " ".join(chunk_tokens)
|
|
5802
5865
|
|
|
5803
5866
|
if debug:
|
|
5804
|
-
print(f"🔧 DEBUG:
|
|
5867
|
+
print(f"\n🔧 DEBUG Step {step_number}: Processing chunk from {current_position} to {chunk_end} "
|
|
5868
|
+
f"({len(chunk_tokens)} tokens)")
|
|
5869
|
+
|
|
5870
|
+
# Progress calculation
|
|
5871
|
+
remaining_tokens = len(tokens) - current_position
|
|
5872
|
+
estimated_remaining_steps = max(1, -(-remaining_tokens // adaptive_chunk_size)) # Ceiling division
|
|
5873
|
+
total_estimated_steps = step_number + estimated_remaining_steps -1
|
|
5874
|
+
progress = (current_position / len(tokens)) * 90 if len(tokens) > 0 else 0
|
|
5805
5875
|
|
|
5806
5876
|
if streaming_callback:
|
|
5807
5877
|
streaming_callback(
|
|
5808
|
-
f"
|
|
5809
|
-
f"
|
|
5810
|
-
MSG_TYPE.
|
|
5811
|
-
{"
|
|
5878
|
+
f"Processing chunk {step_number}/{total_estimated_steps} - "
|
|
5879
|
+
f"Budget: {adaptive_chunk_size:,} tokens",
|
|
5880
|
+
MSG_TYPE.MSG_TYPE_STEP_START,
|
|
5881
|
+
{"step": step_number, "progress": progress}
|
|
5812
5882
|
)
|
|
5813
5883
|
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
if debug:
|
|
5817
|
-
print("🔧 DEBUG: Using single-pass processing")
|
|
5818
|
-
|
|
5819
|
-
if streaming_callback:
|
|
5820
|
-
streaming_callback("Content fits in single pass", MSG_TYPE.MSG_TYPE_STEP, {})
|
|
5821
|
-
|
|
5822
|
-
# FIXED: Moby Dick-specific single-pass system prompt
|
|
5884
|
+
try:
|
|
5885
|
+
# Generic, state-aware system prompt
|
|
5823
5886
|
system_prompt = (
|
|
5824
|
-
"You are a
|
|
5825
|
-
"
|
|
5826
|
-
"
|
|
5827
|
-
"
|
|
5828
|
-
"
|
|
5887
|
+
f"You are a component in a multi-step text processing pipeline. Your role is to analyze a chunk of text and extract key information relevant to a global objective.\n\n"
|
|
5888
|
+
f"**Current Status:** You are on step {step_number} of approximately {total_estimated_steps} steps. Progress is at {progress:.1f}%.\n\n"
|
|
5889
|
+
f"**Your Task:**\n"
|
|
5890
|
+
f"Analyze the 'New Text Chunk' provided below. Extract and summarize any information, data points, or key ideas that are relevant to the 'Global Objective'.\n"
|
|
5891
|
+
f"Review the 'Existing Scratchpad Content' to understand what has already been found. Your goal is to add *new* insights that are not already captured.\n\n"
|
|
5892
|
+
f"**CRITICAL:** Do NOT repeat information already present in the scratchpad. Focus only on new, relevant details from the current chunk. If the chunk contains no new relevant information, respond with '[No new information found in this chunk.]'."
|
|
5829
5893
|
)
|
|
5830
5894
|
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
print("🔧 DEBUG: Using multi-chunk processing with Moby Dick-optimized prompts")
|
|
5847
|
-
|
|
5848
|
-
chunk_summaries = []
|
|
5849
|
-
current_position = 0
|
|
5850
|
-
step_number = 1
|
|
5895
|
+
# Generic, context-aware user prompt
|
|
5896
|
+
summarization_objective = contextual_prompt or "Create a comprehensive summary by extracting all key facts, concepts, and conclusions from the text."
|
|
5897
|
+
scratchpad_status = "The analysis is just beginning; this is the first chunk." if not chunk_summaries else f"Building on existing analysis with {len(chunk_summaries)} sections already completed."
|
|
5898
|
+
|
|
5899
|
+
user_prompt = (
|
|
5900
|
+
f"--- Global Objective ---\n{summarization_objective}\n\n"
|
|
5901
|
+
f"--- Current Progress ---\n"
|
|
5902
|
+
f"{scratchpad_status} (Step {step_number}/{total_estimated_steps})\n\n"
|
|
5903
|
+
f"--- Existing Scratchpad Content (for context) ---\n{current_scratchpad}\n\n"
|
|
5904
|
+
f"--- New Text Chunk to Analyze ---\n{chunk_text}\n\n"
|
|
5905
|
+
f"--- Your Instructions ---\n"
|
|
5906
|
+
f"Extract key information from the 'New Text Chunk' that aligns with the 'Global Objective'. "
|
|
5907
|
+
f"Provide a concise summary of the new findings. Do not repeat what is already in the scratchpad. "
|
|
5908
|
+
f"If no new relevant information is found, state that clearly."
|
|
5909
|
+
)
|
|
5851
5910
|
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
current_scratchpad = "\n\n---\n\n".join(chunk_summaries) if chunk_summaries else "[Empty]"
|
|
5855
|
-
current_budget = calculate_token_budgets(current_scratchpad, step_number)
|
|
5856
|
-
adaptive_chunk_size = max(400, current_budget["chunk_budget"]) # Increased minimum
|
|
5911
|
+
if debug:
|
|
5912
|
+
print(f"🔧 DEBUG: Sending {len(user_prompt)} char prompt to LLM")
|
|
5857
5913
|
|
|
5858
|
-
|
|
5859
|
-
chunk_end = min(current_position + adaptive_chunk_size, len(tokens))
|
|
5860
|
-
chunk_tokens = tokens[current_position:chunk_end]
|
|
5861
|
-
chunk_text = " ".join(chunk_tokens)
|
|
5914
|
+
chunk_summary = self.remove_thinking_blocks(self.llm.generate_text(user_prompt, system_prompt=system_prompt, **kwargs))
|
|
5862
5915
|
|
|
5863
5916
|
if debug:
|
|
5864
|
-
print(f"
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5917
|
+
print(f"🔧 DEBUG: Received {len(chunk_summary)} char response preview: {chunk_summary[:200]}...")
|
|
5918
|
+
|
|
5919
|
+
# Generic content filtering
|
|
5920
|
+
filter_out = False
|
|
5921
|
+
filter_reason = "content accepted"
|
|
5922
|
+
|
|
5923
|
+
# Check for explicit rejection signals
|
|
5924
|
+
if (chunk_summary.strip().lower().startswith('[no new') or
|
|
5925
|
+
chunk_summary.strip().lower().startswith('no new information')):
|
|
5926
|
+
filter_out = True
|
|
5927
|
+
filter_reason = "explicit rejection signal"
|
|
5928
|
+
# Check for overly short or generic refusal responses
|
|
5929
|
+
elif len(chunk_summary.strip()) < 25:
|
|
5930
|
+
filter_out = True
|
|
5931
|
+
filter_reason = "response too short to be useful"
|
|
5932
|
+
# Check for common error phrases
|
|
5933
|
+
elif any(error_phrase in chunk_summary.lower()[:150] for error_phrase in [
|
|
5934
|
+
'error', 'failed', 'cannot provide', 'unable to analyze', 'not possible', 'insufficient information']):
|
|
5935
|
+
filter_out = True
|
|
5936
|
+
filter_reason = "error or refusal response detected"
|
|
5937
|
+
|
|
5938
|
+
if not filter_out:
|
|
5939
|
+
chunk_summaries.append(chunk_summary.strip())
|
|
5940
|
+
content_added = True
|
|
5941
|
+
if debug:
|
|
5942
|
+
print(f"🔧 DEBUG: ✅ Content added to scratchpad (total sections: {len(chunk_summaries)})")
|
|
5943
|
+
else:
|
|
5944
|
+
content_added = False
|
|
5945
|
+
if debug:
|
|
5946
|
+
print(f"🔧 DEBUG: ❌ Content filtered out - {filter_reason}: {chunk_summary[:100]}...")
|
|
5872
5947
|
|
|
5948
|
+
# Update progress via callback
|
|
5873
5949
|
if streaming_callback:
|
|
5950
|
+
updated_scratchpad = "\n\n---\n\n".join(chunk_summaries)
|
|
5874
5951
|
streaming_callback(
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
{"step": step_number, "progress": progress}
|
|
5879
|
-
)
|
|
5880
|
-
|
|
5881
|
-
try:
|
|
5882
|
-
# FIXED: Moby Dick-specific system prompt that prevents false filtering
|
|
5883
|
-
system_prompt = (
|
|
5884
|
-
f"You are analyzing Herman Melville's \"Moby Dick\" - a complex literary work where EVERY passage contains literary value.\n\n"
|
|
5885
|
-
f"**Critical Understanding:**\n"
|
|
5886
|
-
f"- Melville's detailed descriptions of whaling culture ARE literary techniques\n"
|
|
5887
|
-
f"- Technical passages reveal themes about knowledge, obsession, and human industry\n"
|
|
5888
|
-
f"- Social customs and maritime protocols reflect broader themes of hierarchy and civilization\n"
|
|
5889
|
-
f"- Even seemingly mundane details contribute to Melville's encyclopedic narrative style\n\n"
|
|
5890
|
-
f"**Current Status:** Step {step_number} of ~{total_estimated_steps} | Progress: {progress:.1f}%\n\n"
|
|
5891
|
-
f"**Your Task:**\n"
|
|
5892
|
-
f"Extract literary insights from this text chunk, focusing on:\n"
|
|
5893
|
-
f"1. **Themes** (obsession, knowledge, nature vs civilization, social hierarchy)\n"
|
|
5894
|
-
f"2. **Narrative Technique** (Melville's encyclopedic style, detailed realism)\n"
|
|
5895
|
-
f"3. **Cultural Commentary** (maritime society, American industry, social structures)\n"
|
|
5896
|
-
f"4. **Character Insights** (authority, dignity, social roles)\n"
|
|
5897
|
-
f"5. **Symbolic Elements** (ships, sea, whaling practices as metaphors)\n\n"
|
|
5898
|
-
f"**CRITICAL:** The scratchpad shows '{current_scratchpad[:20]}...' - if it shows '[Empty]', you are analyzing early content and everything you find is 'new' information. "
|
|
5899
|
-
f"Do NOT say '[No new information]' unless the chunk is literally empty or corrupted.\n\n"
|
|
5900
|
-
f"Be specific and extract concrete insights. Melville's detailed realism IS his literary technique."
|
|
5952
|
+
updated_scratchpad,
|
|
5953
|
+
MSG_TYPE.MSG_TYPE_SCRATCHPAD,
|
|
5954
|
+
{"step": step_number, "sections": len(chunk_summaries), "content_added": content_added, "filter_reason": filter_reason}
|
|
5901
5955
|
)
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
scratchpad_status = "The analysis is just beginning - this is among the first substantial content to be processed." if current_scratchpad == "[Empty]" else f"Building on existing analysis with {len(chunk_summaries)} sections already completed."
|
|
5908
|
-
|
|
5909
|
-
user_prompt = (
|
|
5910
|
-
f"--- Global Objective ---\n{summarization_objective}\n\n"
|
|
5911
|
-
f"--- Current Progress ---\n"
|
|
5912
|
-
f"Step {step_number} of ~{total_estimated_steps} | Progress: {progress:.1f}% | Token Budget: {adaptive_chunk_size:,}\n\n"
|
|
5913
|
-
f"--- Current Analysis State ---\n{scratchpad_status}\n\n"
|
|
5914
|
-
f"--- Existing Scratchpad Content ---\n{current_scratchpad}\n\n"
|
|
5915
|
-
f"--- New Text Chunk from Moby Dick ---\n{chunk_text}\n\n"
|
|
5916
|
-
f"--- Analysis Instructions ---\n"
|
|
5917
|
-
f"This is Melville's \"Moby Dick\" - extract literary insights from this passage. Consider:\n\n"
|
|
5918
|
-
f"• **What themes** does this passage develop? (obsession with knowledge, social hierarchy, maritime culture)\n"
|
|
5919
|
-
f"• **What narrative techniques** does Melville use? (detailed realism, encyclopedic style, technical precision)\n"
|
|
5920
|
-
f"• **What cultural commentary** is present? (whaling society, American industry, social protocols)\n"
|
|
5921
|
-
f"• **What character insights** emerge? (authority, dignity, social roles and expectations)\n"
|
|
5922
|
-
f"• **What symbolic elements** appear? (ships, maritime customs, hierarchical structures)\n\n"
|
|
5923
|
-
f"**Remember:** In Moby Dick, even technical descriptions serve literary purposes. Melville's detailed realism and cultural documentation ARE his narrative techniques.\n\n"
|
|
5924
|
-
f"Provide specific, concrete analysis with examples from the text. Extract insights that are not already captured in the scratchpad above."
|
|
5956
|
+
progress_after = ((current_position + len(chunk_tokens)) / len(tokens)) * 90 if len(tokens) > 0 else 90
|
|
5957
|
+
streaming_callback(
|
|
5958
|
+
f"Step {step_number} completed - {'Content added' if content_added else f'Filtered: {filter_reason}'}",
|
|
5959
|
+
MSG_TYPE.MSG_TYPE_STEP_END,
|
|
5960
|
+
{"progress": progress_after}
|
|
5925
5961
|
)
|
|
5926
5962
|
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
if debug:
|
|
5934
|
-
print(f"🔧 DEBUG: Received {len(chunk_summary)} char response")
|
|
5935
|
-
print(f"🔧 DEBUG: Response preview: {chunk_summary[:200]}...")
|
|
5936
|
-
|
|
5937
|
-
# FIXED: More intelligent content filtering specifically for literary analysis
|
|
5938
|
-
filter_out = False
|
|
5939
|
-
|
|
5940
|
-
# Check for explicit rejection signals
|
|
5941
|
-
if (chunk_summary.strip().startswith('[No new insights]') or
|
|
5942
|
-
chunk_summary.strip().startswith('[No new information]') or
|
|
5943
|
-
chunk_summary.strip().startswith('[No significant') or
|
|
5944
|
-
'cannot provide' in chunk_summary.lower()[:100] or
|
|
5945
|
-
'unable to analyze' in chunk_summary.lower()[:100]):
|
|
5946
|
-
filter_out = True
|
|
5947
|
-
filter_reason = "explicit rejection signal"
|
|
5948
|
-
|
|
5949
|
-
# Check for too short responses
|
|
5950
|
-
elif len(chunk_summary.strip()) < 50:
|
|
5951
|
-
filter_out = True
|
|
5952
|
-
filter_reason = "response too short"
|
|
5953
|
-
|
|
5954
|
-
# Check for error responses
|
|
5955
|
-
elif any(error_phrase in chunk_summary.lower()[:150] for error_phrase in [
|
|
5956
|
-
'error', 'failed', 'cannot', 'unable', 'not possible', 'insufficient']):
|
|
5957
|
-
filter_out = True
|
|
5958
|
-
filter_reason = "error response detected"
|
|
5959
|
-
|
|
5960
|
-
else:
|
|
5961
|
-
filter_reason = "content accepted"
|
|
5962
|
-
|
|
5963
|
-
if not filter_out:
|
|
5964
|
-
chunk_summaries.append(chunk_summary.strip())
|
|
5965
|
-
content_added = True
|
|
5966
|
-
if debug:
|
|
5967
|
-
print(f"🔧 DEBUG: ✅ Content added to scratchpad (total sections: {len(chunk_summaries)})")
|
|
5968
|
-
else:
|
|
5969
|
-
content_added = False
|
|
5970
|
-
if debug:
|
|
5971
|
-
print(f"🔧 DEBUG: ❌ Content filtered out - {filter_reason}: {chunk_summary[:100]}...")
|
|
5972
|
-
|
|
5973
|
-
# Update progress
|
|
5974
|
-
if streaming_callback:
|
|
5975
|
-
updated_scratchpad = "\n\n---\n\n".join(chunk_summaries)
|
|
5976
|
-
streaming_callback(
|
|
5977
|
-
updated_scratchpad,
|
|
5978
|
-
MSG_TYPE.MSG_TYPE_SCRATCHPAD,
|
|
5979
|
-
{
|
|
5980
|
-
"step": step_number,
|
|
5981
|
-
"sections": len(chunk_summaries),
|
|
5982
|
-
"content_added": content_added,
|
|
5983
|
-
"filter_reason": filter_reason
|
|
5984
|
-
}
|
|
5985
|
-
)
|
|
5986
|
-
|
|
5987
|
-
progress_after = ((current_position + len(chunk_tokens)) / len(tokens)) * 90
|
|
5988
|
-
if streaming_callback:
|
|
5989
|
-
streaming_callback(
|
|
5990
|
-
f"Step {step_number} completed - {'Content added' if content_added else f'Filtered: {filter_reason}'}",
|
|
5991
|
-
MSG_TYPE.MSG_TYPE_STEP_END,
|
|
5992
|
-
{"progress": progress_after}
|
|
5993
|
-
)
|
|
5994
|
-
|
|
5995
|
-
except Exception as e:
|
|
5996
|
-
error_msg = f"Step {step_number} failed: {str(e)}"
|
|
5997
|
-
if debug:
|
|
5998
|
-
print(f"🔧 DEBUG: ❌ {error_msg}")
|
|
5999
|
-
self.trace_exception(e)
|
|
6000
|
-
if streaming_callback:
|
|
6001
|
-
streaming_callback(error_msg, MSG_TYPE.MSG_TYPE_EXCEPTION)
|
|
6002
|
-
chunk_summaries.append(f"[Error in step {step_number}: {str(e)[:100]}]")
|
|
6003
|
-
|
|
6004
|
-
# Move to next chunk
|
|
6005
|
-
current_position += max(1, adaptive_chunk_size - overlap_tokens)
|
|
6006
|
-
step_number += 1
|
|
6007
|
-
|
|
6008
|
-
# Safety break
|
|
6009
|
-
if step_number > 50:
|
|
6010
|
-
if debug:
|
|
6011
|
-
print(f"🔧 DEBUG: Breaking after {step_number-1} steps for safety")
|
|
6012
|
-
break
|
|
6013
|
-
|
|
6014
|
-
if debug:
|
|
6015
|
-
print(f"\n🔧 DEBUG: Completed chunking. Total sections: {len(chunk_summaries)}")
|
|
6016
|
-
|
|
6017
|
-
# Return scratchpad if requested
|
|
6018
|
-
if return_scratchpad_only:
|
|
6019
|
-
final_scratchpad = "\n\n---\n\n".join(chunk_summaries)
|
|
5963
|
+
except Exception as e:
|
|
5964
|
+
error_msg = f"Step {step_number} failed: {str(e)}"
|
|
5965
|
+
if debug:
|
|
5966
|
+
print(f"🔧 DEBUG: ❌ {error_msg}")
|
|
5967
|
+
self.trace_exception(e)
|
|
6020
5968
|
if streaming_callback:
|
|
6021
|
-
streaming_callback(
|
|
6022
|
-
|
|
5969
|
+
streaming_callback(error_msg, MSG_TYPE.MSG_TYPE_EXCEPTION)
|
|
5970
|
+
chunk_summaries.append(f"[Error processing chunk at step {step_number}: {str(e)[:150]}]")
|
|
6023
5971
|
|
|
6024
|
-
#
|
|
5972
|
+
# Move to the next chunk, allowing for overlap
|
|
5973
|
+
current_position += max(1, adaptive_chunk_size - overlap_tokens)
|
|
5974
|
+
step_number += 1
|
|
5975
|
+
|
|
5976
|
+
# Safety break for excessively long documents
|
|
5977
|
+
if step_number > 200:
|
|
5978
|
+
if debug: print(f"🔧 DEBUG: Safety break after {step_number-1} steps.")
|
|
5979
|
+
chunk_summaries.append("[Processing halted due to exceeding maximum step limit.]")
|
|
5980
|
+
break
|
|
5981
|
+
|
|
5982
|
+
if debug:
|
|
5983
|
+
print(f"\n🔧 DEBUG: Chunk processing complete. Total sections gathered: {len(chunk_summaries)}")
|
|
5984
|
+
|
|
5985
|
+
# Return only the scratchpad content if requested
|
|
5986
|
+
if return_scratchpad_only:
|
|
5987
|
+
final_scratchpad = "\n\n---\n\n".join(chunk_summaries)
|
|
6025
5988
|
if streaming_callback:
|
|
6026
|
-
streaming_callback("
|
|
6027
|
-
|
|
6028
|
-
if not chunk_summaries:
|
|
6029
|
-
error_msg = "No content was successfully processed. The text may not contain recognizable literary elements, or there may be an issue with the processing."
|
|
6030
|
-
if debug:
|
|
6031
|
-
print(f"🔧 DEBUG: ❌ {error_msg}")
|
|
6032
|
-
return error_msg
|
|
5989
|
+
streaming_callback("Returning scratchpad content as final output.", MSG_TYPE.MSG_TYPE_STEP, {})
|
|
5990
|
+
return final_scratchpad.strip()
|
|
6033
5991
|
|
|
6034
|
-
|
|
6035
|
-
|
|
5992
|
+
# Final Synthesis Step
|
|
5993
|
+
if streaming_callback:
|
|
5994
|
+
streaming_callback("Synthesizing final comprehensive response...", MSG_TYPE.MSG_TYPE_STEP_START, {"progress": 95})
|
|
6036
5995
|
|
|
5996
|
+
if not chunk_summaries:
|
|
5997
|
+
error_msg = "No content was successfully processed or extracted from the document. The input might be empty or an issue occurred during processing."
|
|
6037
5998
|
if debug:
|
|
6038
|
-
print(f"🔧 DEBUG:
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
5999
|
+
print(f"🔧 DEBUG: ❌ {error_msg}")
|
|
6000
|
+
return error_msg
|
|
6001
|
+
|
|
6002
|
+
combined_scratchpad = "\n\n---\n\n".join(chunk_summaries)
|
|
6003
|
+
synthesis_objective = contextual_prompt or "Provide a comprehensive, well-structured summary and analysis of the provided text."
|
|
6004
|
+
|
|
6005
|
+
if debug:
|
|
6006
|
+
print(f"🔧 DEBUG: Synthesizing from {len(combined_scratchpad):,} char scratchpad with {len(chunk_summaries)} sections.")
|
|
6007
|
+
|
|
6008
|
+
# Generic synthesis prompts
|
|
6009
|
+
synthesis_system_prompt = (
|
|
6010
|
+
"You are an expert AI assistant specializing in synthesizing information. "
|
|
6011
|
+
"Your task is to consolidate a series of text analysis sections from a scratchpad into a single, coherent, and well-structured final response. "
|
|
6012
|
+
"Eliminate redundancy, organize the content logically, and ensure the final output directly and comprehensively addresses the user's primary objective. "
|
|
6013
|
+
"Use markdown for clear formatting (e.g., headers, lists, bold text)."
|
|
6014
|
+
)
|
|
6015
|
+
|
|
6016
|
+
synthesis_user_prompt = (
|
|
6017
|
+
f"--- Final Objective ---\n{synthesis_objective}\n\n"
|
|
6018
|
+
f"--- Collected Analysis Sections (Scratchpad) ---\n{combined_scratchpad}\n\n"
|
|
6019
|
+
f"--- Your Final Task ---\n"
|
|
6020
|
+
f"Synthesize all the information from the 'Collected Analysis Sections' into a single, high-quality, and comprehensive response. "
|
|
6021
|
+
f"Your response must directly address the 'Final Objective'. "
|
|
6022
|
+
f"Organize your answer logically with clear sections using markdown headers. "
|
|
6023
|
+
f"Ensure all key information is included, remove any repetitive statements, and produce a polished, final document."
|
|
6024
|
+
)
|
|
6025
|
+
|
|
6026
|
+
try:
|
|
6027
|
+
final_answer = self.remove_thinking_blocks(self.llm.generate_text(synthesis_user_prompt, system_prompt=synthesis_system_prompt, **kwargs))
|
|
6028
|
+
if debug:
|
|
6029
|
+
print(f"🔧 DEBUG: Final synthesis generated: {len(final_answer):,} characters")
|
|
6030
|
+
if streaming_callback:
|
|
6031
|
+
streaming_callback("Final synthesis complete.", MSG_TYPE.MSG_TYPE_STEP_END, {"progress": 100})
|
|
6032
|
+
return final_answer.strip()
|
|
6048
6033
|
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
f"
|
|
6056
|
-
f"
|
|
6057
|
-
f"
|
|
6058
|
-
f"
|
|
6059
|
-
f"Include specific textual examples and maintain academic depth throughout."
|
|
6034
|
+
except Exception as e:
|
|
6035
|
+
error_msg = f"The final synthesis step failed: {str(e)}. Returning the organized scratchpad content as a fallback."
|
|
6036
|
+
if debug: print(f"🔧 DEBUG: ❌ {error_msg}")
|
|
6037
|
+
|
|
6038
|
+
# Fallback to returning the organized scratchpad
|
|
6039
|
+
organized_scratchpad = (
|
|
6040
|
+
f"# Analysis Summary\n\n"
|
|
6041
|
+
f"*Note: The final synthesis process encountered an error. The raw, organized analysis sections are provided below.*\n\n"
|
|
6042
|
+
f"## Collected Sections\n\n"
|
|
6043
|
+
f"{combined_scratchpad}"
|
|
6060
6044
|
)
|
|
6061
|
-
|
|
6062
|
-
try:
|
|
6063
|
-
final_answer = self.remove_thinking_blocks(self.llm.generate_text(synthesis_user_prompt, system_prompt=synthesis_system_prompt, **kwargs))
|
|
6064
|
-
|
|
6065
|
-
if debug:
|
|
6066
|
-
print(f"🔧 DEBUG: Final analysis: {len(final_answer):,} characters")
|
|
6067
|
-
|
|
6068
|
-
if streaming_callback:
|
|
6069
|
-
streaming_callback(f"Final synthesis completed - {len(final_answer):,} characters generated", MSG_TYPE.MSG_TYPE_STEP_END, {"progress": 100})
|
|
6070
|
-
|
|
6071
|
-
return final_answer.strip()
|
|
6072
|
-
|
|
6073
|
-
except Exception as e:
|
|
6074
|
-
error_msg = f"Synthesis failed: {str(e)}. Returning organized scratchpad content."
|
|
6075
|
-
if debug:
|
|
6076
|
-
print(f"🔧 DEBUG: ❌ {error_msg}")
|
|
6077
|
-
|
|
6078
|
-
# Return organized scratchpad as fallback
|
|
6079
|
-
organized_scratchpad = (
|
|
6080
|
-
f"# Literary Analysis of Moby Dick\n\n"
|
|
6081
|
-
f"*Note: Synthesis process encountered issues, presenting organized analysis sections:*\n\n"
|
|
6082
|
-
f"## Analysis Sections\n\n"
|
|
6083
|
-
f"{combined_scratchpad}"
|
|
6084
|
-
)
|
|
6085
|
-
return organized_scratchpad
|
|
6045
|
+
return organized_scratchpad
|
|
6086
6046
|
|
|
6087
6047
|
|
|
6088
6048
|
def chunk_text(text, tokenizer, detokenizer, chunk_size, overlap, use_separators=True):
|
|
@@ -35,7 +35,8 @@ try:
|
|
|
35
35
|
AutoPipelineForInpainting,
|
|
36
36
|
DiffusionPipeline,
|
|
37
37
|
StableDiffusionPipeline,
|
|
38
|
-
|
|
38
|
+
QwenImageEditPipeline,
|
|
39
|
+
QwenImageEditPlusPipeline
|
|
39
40
|
)
|
|
40
41
|
from diffusers.utils import load_image
|
|
41
42
|
from PIL import Image
|
|
@@ -47,6 +48,8 @@ except ImportError:
|
|
|
47
48
|
AutoPipelineForInpainting = None
|
|
48
49
|
DiffusionPipeline = None
|
|
49
50
|
StableDiffusionPipeline = None
|
|
51
|
+
QwenImageEditPipeline = None
|
|
52
|
+
QwenImageEditPlusPipeline = None
|
|
50
53
|
Image = None
|
|
51
54
|
load_image = None
|
|
52
55
|
DIFFUSERS_AVAILABLE = False
|
|
@@ -359,7 +362,12 @@ class ModelManager:
|
|
|
359
362
|
common_args["safety_checker"] = None
|
|
360
363
|
if self.config.get("hf_cache_path"):
|
|
361
364
|
common_args["cache_dir"] = str(self.config["hf_cache_path"])
|
|
362
|
-
|
|
365
|
+
|
|
366
|
+
if "Qwen-Image-Edit-2509" in str(model_path):
|
|
367
|
+
self.pipeline = QwenImageEditPlusPipeline.from_pretrained(model_path, **common_args)
|
|
368
|
+
elif "Qwen-Image-Edit" in str(model_path):
|
|
369
|
+
self.pipeline = QwenImageEditPipeline.from_pretrained(model_path, **common_args)
|
|
370
|
+
elif task == "text2image":
|
|
363
371
|
self.pipeline = AutoPipelineForText2Image.from_pretrained(model_path, **common_args)
|
|
364
372
|
elif task == "image2image":
|
|
365
373
|
self.pipeline = AutoPipelineForImage2Image.from_pretrained(model_path, **common_args)
|
|
@@ -498,9 +506,10 @@ class DiffusersTTIBinding_Impl(LollmsTTIBinding):
|
|
|
498
506
|
{"family": "SD 1.x", "model_name": "runwayml/stable-diffusion-v1-5", "display_name": "Stable Diffusion 1.5", "desc": "Classic SD1.5."},
|
|
499
507
|
{"family": "SD 2.x", "model_name": "stabilityai/stable-diffusion-2-1", "display_name": "Stable Diffusion 2.1", "desc": "SD2.1 base."},
|
|
500
508
|
{"family": "SD3", "model_name": "stabilityai/stable-diffusion-3-medium-diffusers", "display_name": "Stable Diffusion 3 Medium", "desc": "SD3 medium."},
|
|
501
|
-
{"family": "Qwen", "model_name": "Qwen/Qwen-Image", "display_name": "Qwen Image
|
|
509
|
+
{"family": "Qwen", "model_name": "Qwen/Qwen-Image", "display_name": "Qwen Image", "desc": "Dedicated image generation."},
|
|
502
510
|
{"family": "Specialized", "model_name": "playgroundai/playground-v2.5-1024px-aesthetic", "display_name": "Playground v2.5", "desc": "High aesthetic 1024."},
|
|
503
|
-
{"family": "Editors", "model_name": "Qwen/Qwen-Image-Edit", "display_name": "Qwen Image Edit", "desc": "Dedicated image editing."}
|
|
511
|
+
{"family": "Editors", "model_name": "Qwen/Qwen-Image-Edit", "display_name": "Qwen Image Edit", "desc": "Dedicated image editing."},
|
|
512
|
+
{"family": "Editors", "model_name": "Qwen/Qwen-Image-Edit-2509", "display_name": "Qwen Image Edit Plus (Multi-Image)", "desc": "Advanced multi-image editing, fusion, and pose transfer."}
|
|
504
513
|
]
|
|
505
514
|
|
|
506
515
|
def __init__(self, **kwargs):
|
|
@@ -679,6 +688,25 @@ class DiffusersTTIBinding_Impl(LollmsTTIBinding):
|
|
|
679
688
|
generator = self._prepare_seed(kwargs)
|
|
680
689
|
steps = kwargs.pop("num_inference_steps", self.config["num_inference_steps"])
|
|
681
690
|
guidance = kwargs.pop("guidance_scale", self.config["guidance_scale"])
|
|
691
|
+
|
|
692
|
+
# Handle multi-image fusion for Qwen-Image-Edit-2509
|
|
693
|
+
if "Qwen-Image-Edit-2509" in self.model_name and len(pil_images) > 1:
|
|
694
|
+
pipeline_args = {
|
|
695
|
+
"image": pil_images,
|
|
696
|
+
"prompt": prompt,
|
|
697
|
+
"negative_prompt": negative_prompt or " ",
|
|
698
|
+
"width": out_w, "height": out_h,
|
|
699
|
+
"num_inference_steps": steps,
|
|
700
|
+
"true_cfg_scale": guidance,
|
|
701
|
+
"generator": generator
|
|
702
|
+
}
|
|
703
|
+
pipeline_args.update(kwargs)
|
|
704
|
+
future = Future()
|
|
705
|
+
self.manager.queue.put((future, "image2image", pipeline_args))
|
|
706
|
+
ASCIIColors.info(f"Job (multi-image fusion with {len(pil_images)} images) queued.")
|
|
707
|
+
return future.result()
|
|
708
|
+
|
|
709
|
+
# Handle inpainting (single image with mask)
|
|
682
710
|
if mask is not None and len(pil_images) == 1:
|
|
683
711
|
try:
|
|
684
712
|
mask_img = self._decode_image_input(mask).convert("L")
|
|
@@ -689,36 +717,46 @@ class DiffusersTTIBinding_Impl(LollmsTTIBinding):
|
|
|
689
717
|
"mask_image": mask_img,
|
|
690
718
|
"prompt": prompt,
|
|
691
719
|
"negative_prompt": negative_prompt or None,
|
|
692
|
-
"width": out_w,
|
|
693
|
-
"height": out_h,
|
|
720
|
+
"width": out_w, "height": out_h,
|
|
694
721
|
"num_inference_steps": steps,
|
|
695
722
|
"guidance_scale": guidance,
|
|
696
723
|
"generator": generator
|
|
697
724
|
}
|
|
698
725
|
pipeline_args.update(kwargs)
|
|
726
|
+
if "Qwen-Image-Edit" in self.model_name:
|
|
727
|
+
pipeline_args["true_cfg_scale"] = pipeline_args.pop("guidance_scale", 7.0)
|
|
728
|
+
if not pipeline_args.get("negative_prompt"): pipeline_args["negative_prompt"] = " "
|
|
729
|
+
|
|
699
730
|
future = Future()
|
|
700
731
|
self.manager.queue.put((future, "inpainting", pipeline_args))
|
|
701
732
|
ASCIIColors.info("Job (inpaint) queued.")
|
|
702
733
|
return future.result()
|
|
734
|
+
|
|
735
|
+
# Handle standard image-to-image (single image)
|
|
703
736
|
try:
|
|
704
737
|
pipeline_args = {
|
|
705
|
-
"image": pil_images
|
|
738
|
+
"image": pil_images[0],
|
|
706
739
|
"prompt": prompt,
|
|
707
740
|
"negative_prompt": negative_prompt or None,
|
|
708
741
|
"strength": kwargs.pop("strength", 0.6),
|
|
709
|
-
"width": out_w,
|
|
710
|
-
"height": out_h,
|
|
742
|
+
"width": out_w, "height": out_h,
|
|
711
743
|
"num_inference_steps": steps,
|
|
712
744
|
"guidance_scale": guidance,
|
|
713
745
|
"generator": generator
|
|
714
746
|
}
|
|
715
747
|
pipeline_args.update(kwargs)
|
|
748
|
+
if "Qwen-Image-Edit" in self.model_name:
|
|
749
|
+
pipeline_args["true_cfg_scale"] = pipeline_args.pop("guidance_scale", 7.0)
|
|
750
|
+
if not pipeline_args.get("negative_prompt"): pipeline_args["negative_prompt"] = " "
|
|
751
|
+
|
|
716
752
|
future = Future()
|
|
717
753
|
self.manager.queue.put((future, "image2image", pipeline_args))
|
|
718
754
|
ASCIIColors.info("Job (i2i) queued.")
|
|
719
755
|
return future.result()
|
|
720
756
|
except Exception:
|
|
721
757
|
pass
|
|
758
|
+
|
|
759
|
+
# Fallback to latent-based generation if i2i fails for some reason
|
|
722
760
|
try:
|
|
723
761
|
base = pil_images[0]
|
|
724
762
|
latents, _ = self._encode_image_to_latents(base, out_w, out_h)
|
|
@@ -729,8 +767,7 @@ class DiffusersTTIBinding_Impl(LollmsTTIBinding):
|
|
|
729
767
|
"num_inference_steps": steps,
|
|
730
768
|
"guidance_scale": guidance,
|
|
731
769
|
"generator": generator,
|
|
732
|
-
"width": out_w,
|
|
733
|
-
"height": out_h
|
|
770
|
+
"width": out_w, "height": out_h
|
|
734
771
|
}
|
|
735
772
|
pipeline_args.update(kwargs)
|
|
736
773
|
future = Future()
|
|
@@ -740,6 +777,7 @@ class DiffusersTTIBinding_Impl(LollmsTTIBinding):
|
|
|
740
777
|
except Exception as e:
|
|
741
778
|
raise Exception(f"Image edit failed: {e}") from e
|
|
742
779
|
|
|
780
|
+
|
|
743
781
|
def list_local_models(self) -> List[str]:
|
|
744
782
|
if not self.models_path.exists():
|
|
745
783
|
return []
|
|
@@ -846,4 +884,4 @@ if __name__ == '__main__':
|
|
|
846
884
|
ASCIIColors.cyan("\nCleaning up temporary directories...")
|
|
847
885
|
if temp_paths_dir.exists():
|
|
848
886
|
shutil.rmtree(temp_paths_dir)
|
|
849
|
-
ASCIIColors.magenta("--- Diffusers TTI Binding Test Finished ---")
|
|
887
|
+
ASCIIColors.magenta("--- Diffusers TTI Binding Test Finished ---")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lollms_client
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.0
|
|
4
4
|
Summary: A client library for LoLLMs generate endpoint
|
|
5
5
|
Author-email: ParisNeo <parisneoai@gmail.com>
|
|
6
6
|
License: Apache License
|
|
@@ -249,7 +249,8 @@ Whether you're connecting to a remote LoLLMs server, an Ollama instance, the Ope
|
|
|
249
249
|
## Key Features
|
|
250
250
|
|
|
251
251
|
* 🔌 **Versatile Binding System:** Seamlessly switch between different LLM backends (LoLLMs, Ollama, OpenAI, Llama.cpp, Transformers, vLLM, OpenLLM, Gemini, Claude, Groq, OpenRouter, Hugging Face Inference API) using a unified `llm_binding_config` dictionary for all parameters.
|
|
252
|
-
* 🗣️ **Multimodal Support:** Interact with models capable of processing images and generate various outputs like speech (TTS),
|
|
252
|
+
* 🗣️ **Comprehensive Multimodal Support:** Interact with models capable of processing images and generate various outputs like speech (TTS), video (TTV), and music (TTM).
|
|
253
|
+
* 🎨 **Advanced Image Generation and Editing:** A new `diffusers` binding provides powerful text-to-image capabilities. It supports a wide range of models from Hugging Face and Civitai, including specialized models like `Qwen-Image-Edit` for single-image editing and the cutting-edge `Qwen-Image-Edit-2509` for **multi-image fusion, pose transfer, and character swapping**.
|
|
253
254
|
* 🖼️ **Selective Image Activation:** Control which images in a message are active and sent to the model, allowing for fine-grained multimodal context management without deleting the original data.
|
|
254
255
|
* 🤖 **Agentic Workflows with MCP:** Empower LLMs to act as sophisticated agents, breaking down complex tasks, selecting and executing external tools (e.g., internet search, code interpreter, file I/O, image generation) through the Model Context Protocol (MCP) using a robust "observe-think-act" loop.
|
|
255
256
|
* 🎭 **Personalities as Agents:** Personalities can now define their own set of required tools (MCPs) and have access to static or dynamic knowledge bases (`data_source`), turning them into self-contained, ready-to-use agents.
|
|
@@ -1300,9 +1301,110 @@ try:
|
|
|
1300
1301
|
|
|
1301
1302
|
except Exception as e:
|
|
1302
1303
|
ASCIIColors.error(f"Error initializing Hugging Face Inference API binding: {e}")
|
|
1303
|
-
ASCIIColors.info("Please ensure your Hugging Face API token is correctly set and you have access to the specified model.")
|
|
1304
|
+
ASCIIColors.info("Please ensure your Hugging Face API token is correctly set and you have access to the specified model.")```
|
|
1305
|
+
|
|
1306
|
+
---
|
|
1307
|
+
|
|
1308
|
+
### 4. Local Multimodal and Advanced Bindings
|
|
1309
|
+
|
|
1310
|
+
#### **Diffusers (Local Text-to-Image Generation and Editing)**
|
|
1311
|
+
|
|
1312
|
+
The `diffusers` binding leverages the Hugging Face `diffusers` library to run a vast array of text-to-image models locally on your own hardware (CPU or GPU). It supports models from Hugging Face and Civitai, providing everything from basic image generation to advanced, state-of-the-art image editing.
|
|
1313
|
+
|
|
1314
|
+
**Prerequisites:**
|
|
1315
|
+
* `torch` and `torchvision` must be installed. For GPU acceleration, it's critical to install the version that matches your CUDA toolkit.
|
|
1316
|
+
* The binding will attempt to auto-install other requirements like `diffusers`, `transformers`, and `safetensors`.
|
|
1317
|
+
|
|
1318
|
+
**Usage:**
|
|
1319
|
+
|
|
1320
|
+
**Example 1: Basic Text-to-Image Generation**
|
|
1321
|
+
This example shows how to generate an image from a simple text prompt using a classic Stable Diffusion model.
|
|
1322
|
+
|
|
1323
|
+
```python
|
|
1324
|
+
from lollms_client import LollmsClient
|
|
1325
|
+
from ascii_colors import ASCIIColors
|
|
1326
|
+
from pathlib import Path
|
|
1327
|
+
|
|
1328
|
+
try:
|
|
1329
|
+
# Initialize the client with the diffusers TTI binding
|
|
1330
|
+
# Let's use a classic Stable Diffusion model for this example
|
|
1331
|
+
lc = LollmsClient(
|
|
1332
|
+
tti_binding_name="diffusers",
|
|
1333
|
+
tti_binding_config={
|
|
1334
|
+
"model_name": "runwayml/stable-diffusion-v1-5",
|
|
1335
|
+
# Other options: "device", "torch_dtype_str", "enable_xformers"
|
|
1336
|
+
}
|
|
1337
|
+
)
|
|
1338
|
+
|
|
1339
|
+
prompt = "A high-quality photograph of an astronaut riding a horse on Mars."
|
|
1340
|
+
ASCIIColors.yellow(f"Generating image for prompt: '{prompt}'")
|
|
1341
|
+
|
|
1342
|
+
# Generate the image. The result is returned as bytes.
|
|
1343
|
+
image_bytes = lc.generate_image(prompt, width=512, height=512)
|
|
1344
|
+
|
|
1345
|
+
if image_bytes:
|
|
1346
|
+
output_path = Path("./astronaut_on_mars.png")
|
|
1347
|
+
with open(output_path, "wb") as f:
|
|
1348
|
+
f.write(image_bytes)
|
|
1349
|
+
ASCIIColors.green(f"Image saved successfully to: {output_path.resolve()}")
|
|
1350
|
+
else:
|
|
1351
|
+
ASCIIColors.error("Image generation failed.")
|
|
1352
|
+
|
|
1353
|
+
except Exception as e:
|
|
1354
|
+
ASCIIColors.error(f"An error occurred with the Diffusers binding: {e}")
|
|
1355
|
+
ASCIIColors.info("Please ensure torch is installed correctly for your hardware (CPU/GPU).")
|
|
1304
1356
|
```
|
|
1305
1357
|
|
|
1358
|
+
**Example 2: Advanced Multi-Image Fusion with Qwen-Image-Edit-2509**
|
|
1359
|
+
This example demonstrates a cutting-edge capability: using a specialized model to fuse elements from multiple input images based on a text prompt. Here, we'll ask the model to take a person from one image and place them in the background of another.
|
|
1360
|
+
|
|
1361
|
+
```python
|
|
1362
|
+
from lollms_client import LollmsClient
|
|
1363
|
+
from ascii_colors import ASCIIColors
|
|
1364
|
+
from pathlib import Path
|
|
1365
|
+
|
|
1366
|
+
# --- IMPORTANT ---
|
|
1367
|
+
# Replace these with actual paths to your local images
|
|
1368
|
+
path_to_person_image = "./path/to/your/person.jpg"
|
|
1369
|
+
path_to_background_image = "./path/to/your/background.jpg"
|
|
1370
|
+
|
|
1371
|
+
if not Path(path_to_person_image).exists() or not Path(path_to_background_image).exists():
|
|
1372
|
+
ASCIIColors.warning("Input images not found. Skipping multi-image fusion example.")
|
|
1373
|
+
ASCIIColors.warning(f"Please update 'path_to_person_image' and 'path_to_background_image'.")
|
|
1374
|
+
else:
|
|
1375
|
+
try:
|
|
1376
|
+
# Initialize with the advanced Qwen multi-image editing model
|
|
1377
|
+
lc = LollmsClient(
|
|
1378
|
+
tti_binding_name="diffusers",
|
|
1379
|
+
tti_binding_config={
|
|
1380
|
+
"model_name": "Qwen/Qwen-Image-Edit-2509",
|
|
1381
|
+
"torch_dtype_str": "bfloat16" # Recommended for this model
|
|
1382
|
+
}
|
|
1383
|
+
)
|
|
1384
|
+
|
|
1385
|
+
# The prompt guides how the images are combined
|
|
1386
|
+
prompt = "Place the person from the first image into the scenic background of the second image."
|
|
1387
|
+
ASCIIColors.yellow(f"Fusing images with prompt: '{prompt}'")
|
|
1388
|
+
|
|
1389
|
+
# The edit_image method can accept a list of image paths for fusion
|
|
1390
|
+
fused_image_bytes = lc.edit_image(
|
|
1391
|
+
images=[path_to_person_image, path_to_background_image],
|
|
1392
|
+
prompt=prompt,
|
|
1393
|
+
num_inference_steps=50
|
|
1394
|
+
)
|
|
1395
|
+
|
|
1396
|
+
if fused_image_bytes:
|
|
1397
|
+
output_path = Path("./fused_image_result.png")
|
|
1398
|
+
with open(output_path, "wb") as f:
|
|
1399
|
+
f.write(fused_image_bytes)
|
|
1400
|
+
ASCIIColors.green(f"Fused image saved successfully to: {output_path.resolve()}")
|
|
1401
|
+
else:
|
|
1402
|
+
ASCIIColors.error("Multi-image editing failed.")
|
|
1403
|
+
|
|
1404
|
+
except Exception as e:
|
|
1405
|
+
ASCIIColors.error(f"An error occurred during multi-image fusion: {e}")
|
|
1406
|
+
```This powerful feature allows for complex creative tasks like character swapping, background replacement, and style transfer directly through the `lollms_client` library.
|
|
1407
|
+
|
|
1306
1408
|
### Listing Available Models
|
|
1307
1409
|
|
|
1308
1410
|
You can query the active LLM binding to get a list of models it supports or has available. The exact information returned depends on the binding (e.g., Ollama lists local models, OpenAI lists all its API models).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
lollms_client/__init__.py,sha256=
|
|
1
|
+
lollms_client/__init__.py,sha256=xI7gZKUDt8hWzRJuATINMJMA2w0uYLBf202yS1o2diY,1146
|
|
2
2
|
lollms_client/lollms_agentic.py,sha256=pQiMEuB_XkG29-SW6u4KTaMFPr6eKqacInggcCuCW3k,13914
|
|
3
3
|
lollms_client/lollms_config.py,sha256=goEseDwDxYJf3WkYJ4IrLXwg3Tfw73CXV2Avg45M_hE,21876
|
|
4
|
-
lollms_client/lollms_core.py,sha256=
|
|
4
|
+
lollms_client/lollms_core.py,sha256=M-zLRrGlG_cFIExxRcMVN8tADFyp-y6L7mO9VKJDTwI,320416
|
|
5
5
|
lollms_client/lollms_discussion.py,sha256=LZc9jYbUMRTovehiFJKEp-NXuCl_WnrqUtT3t4Nzayk,123922
|
|
6
6
|
lollms_client/lollms_js_analyzer.py,sha256=01zUvuO2F_lnUe_0NLxe1MF5aHE1hO8RZi48mNPv-aw,8361
|
|
7
7
|
lollms_client/lollms_llm_binding.py,sha256=tXuc3gxe6UrP36OBGsR-ESvQ9LpsB_nqtqL-GsEj6Uc,25019
|
|
@@ -52,7 +52,7 @@ lollms_client/stt_bindings/lollms/__init__.py,sha256=9Vmn1sQQZKLGLe7nZnc-0LnNeSY
|
|
|
52
52
|
lollms_client/stt_bindings/whisper/__init__.py,sha256=1Ej67GdRKBy1bba14jMaYDYHiZkxJASkWm5eF07ztDQ,15363
|
|
53
53
|
lollms_client/stt_bindings/whispercpp/__init__.py,sha256=xSAQRjAhljak3vWCpkP0Vmdb6WmwTzPjXyaIB85KLGU,21439
|
|
54
54
|
lollms_client/tti_bindings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
55
|
-
lollms_client/tti_bindings/diffusers/__init__.py,sha256=
|
|
55
|
+
lollms_client/tti_bindings/diffusers/__init__.py,sha256=IB4yZpYOY7bq9l1rRNM9SZzy5EbKqGsY2BlEY3hX_Pg,43892
|
|
56
56
|
lollms_client/tti_bindings/gemini/__init__.py,sha256=f9fPuqnrBZ1Z-obcoP6EVvbEXNbNCSg21cd5efLCk8U,16707
|
|
57
57
|
lollms_client/tti_bindings/leonardo_ai/__init__.py,sha256=EvjKyV8kM7-tmLwC1agSQ-v7thrNgflrmFxhDLqzT8U,5884
|
|
58
58
|
lollms_client/tti_bindings/lollms/__init__.py,sha256=5Tnsn4b17djvieQkcjtIDBm3qf0pg5ZWWov-4_2wmo0,8762
|
|
@@ -80,8 +80,8 @@ lollms_client/tts_bindings/xtts/server/main.py,sha256=T-Kn5NM-u1FJMygeV8rOoZKlqn
|
|
|
80
80
|
lollms_client/tts_bindings/xtts/server/setup_voices.py,sha256=UdHaPa5aNcw8dR-aRGkZr2OfSFFejH79lXgfwT0P3ss,1964
|
|
81
81
|
lollms_client/ttv_bindings/__init__.py,sha256=UZ8o2izQOJLQgtZ1D1cXoNST7rzqW22rL2Vufc7ddRc,3141
|
|
82
82
|
lollms_client/ttv_bindings/lollms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
83
|
-
lollms_client-1.
|
|
84
|
-
lollms_client-1.
|
|
85
|
-
lollms_client-1.
|
|
86
|
-
lollms_client-1.
|
|
87
|
-
lollms_client-1.
|
|
83
|
+
lollms_client-1.6.0.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
84
|
+
lollms_client-1.6.0.dist-info/METADATA,sha256=OmxnZ9K0GlxOGVMyiuxKF0TkSfCCDWGvQ8nuVGBbJO0,76825
|
|
85
|
+
lollms_client-1.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
86
|
+
lollms_client-1.6.0.dist-info/top_level.txt,sha256=Bk_kz-ri6Arwsk7YG-T5VsRorV66uVhcHGvb_g2WqgE,14
|
|
87
|
+
lollms_client-1.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|