lollms-client 1.6.6__py3-none-any.whl → 1.6.7__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 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.6.6" # Updated version
11
+ __version__ = "1.6.7" # Updated version
12
12
 
13
13
  # Optionally, you could define __all__ if you want to be explicit about exports
14
14
  __all__ = [
@@ -4307,16 +4307,17 @@ Provide the final aggregated answer in {output_format} format, directly addressi
4307
4307
  contextual_prompt: Optional[str] = None,
4308
4308
  system_prompt: str | None = None,
4309
4309
  context_fill_percentage: float = 0.75,
4310
- overlap_tokens: int = 150, # Added a default for better context continuity
4310
+ overlap_tokens: int = 150,
4311
4311
  expected_generation_tokens: int = 1500,
4312
+ max_scratchpad_tokens: int = 4000, # NEW: Hard limit for scratchpad
4313
+ scratchpad_compression_threshold: int = 3000, # NEW: When to compress
4312
4314
  streaming_callback: Optional[Callable] = None,
4313
4315
  return_scratchpad_only: bool = False,
4314
4316
  debug: bool = True,
4315
4317
  **kwargs
4316
4318
  ) -> str:
4317
4319
  """
4318
- Processes long text by breaking it down into chunks, analyzing each one incrementally,
4319
- and synthesizing the results into a comprehensive final response based on a user-defined objective.
4320
+ Processes long text with FIXED chunk sizing and managed scratchpad growth.
4320
4321
  """
4321
4322
 
4322
4323
  if debug:
@@ -4328,7 +4329,7 @@ Provide the final aggregated answer in {output_format} format, directly addressi
4328
4329
 
4329
4330
  # Get context size
4330
4331
  try:
4331
- context_size = self.llm.get_context_size() or 8192 # Using a more modern default
4332
+ context_size = self.llm.get_context_size() or 8192
4332
4333
  except:
4333
4334
  context_size = 8192
4334
4335
 
@@ -4339,65 +4340,50 @@ Provide the final aggregated answer in {output_format} format, directly addressi
4339
4340
  if not text_to_process:
4340
4341
  return ""
4341
4342
 
4342
- # Use a simple word-based split for token estimation
4343
+ # Use word-based split for token estimation
4343
4344
  tokens = text_to_process.split()
4344
4345
  if debug:
4345
4346
  print(f"🔧 DEBUG: Tokenized into {len(tokens):,} word tokens")
4346
4347
 
4347
- # Dynamic token budget calculation
4348
- def calculate_token_budgets(scratchpad_content: str = "", step_num: int = 0) -> dict:
4349
- # Generic prompt templates are more concise
4350
- base_system_tokens = 150
4351
- user_template_tokens = 250
4352
- scratchpad_tokens = len(scratchpad_content.split()) * 1.3 if scratchpad_content else 0
4353
-
4354
- used_tokens = base_system_tokens + user_template_tokens + scratchpad_tokens + expected_generation_tokens
4355
- total_budget = int(context_size * context_fill_percentage)
4356
- available_for_chunk = max(500, int(total_budget - used_tokens)) # Ensure a reasonable minimum chunk size
4357
-
4358
- budget_info = {
4359
- "total_budget": total_budget,
4360
- "chunk_budget": available_for_chunk,
4361
- "efficiency_ratio": available_for_chunk / total_budget if total_budget > 0 else 0,
4362
- "scratchpad_tokens": int(scratchpad_tokens),
4363
- "used_tokens": int(used_tokens)
4364
- }
4365
-
4366
- if debug:
4367
- print(f"🔧 DEBUG Step {step_num}: Budget = {available_for_chunk}/{total_budget} tokens, "
4368
- f"Scratchpad = {int(scratchpad_tokens)} tokens")
4369
-
4370
- return budget_info
4371
-
4372
- # Initial budget calculation
4373
- initial_budget = calculate_token_budgets()
4374
- chunk_size_tokens = initial_budget["chunk_budget"]
4375
-
4348
+ # ========================================
4349
+ # FIXED: Calculate chunk size ONCE upfront
4350
+ # ========================================
4351
+ base_system_tokens = 150
4352
+ user_template_tokens = 250
4353
+
4354
+ # Reserve space for maximum expected scratchpad size
4355
+ reserved_scratchpad_tokens = max_scratchpad_tokens
4356
+
4357
+ total_budget = int(context_size * context_fill_percentage)
4358
+ used_tokens = base_system_tokens + user_template_tokens + reserved_scratchpad_tokens + expected_generation_tokens
4359
+
4360
+ # FIXED chunk size - never changes during processing
4361
+ FIXED_CHUNK_SIZE = max(500, int(total_budget - used_tokens))
4362
+
4376
4363
  if debug:
4377
- print(f"🔧 DEBUG: Initial chunk size: {chunk_size_tokens} word tokens")
4364
+ print(f"🔧 DEBUG: FIXED chunk size: {FIXED_CHUNK_SIZE} tokens (will not change)")
4365
+ print(f"🔧 DEBUG: Reserved scratchpad space: {reserved_scratchpad_tokens} tokens")
4366
+ print(f"🔧 DEBUG: Total budget: {total_budget} tokens")
4378
4367
 
4379
4368
  if streaming_callback:
4380
4369
  streaming_callback(
4381
- f"Context Budget: {initial_budget['chunk_budget']:,}/{initial_budget['total_budget']:,} tokens "
4382
- f"({initial_budget['efficiency_ratio']:.1%} efficiency)",
4370
+ f"Context Budget: {FIXED_CHUNK_SIZE:,}/{total_budget:,} tokens per chunk (fixed)",
4383
4371
  MSG_TYPE.MSG_TYPE_STEP,
4384
- {"budget_info": initial_budget}
4372
+ {"fixed_chunk_size": FIXED_CHUNK_SIZE, "total_budget": total_budget}
4385
4373
  )
4386
4374
 
4387
4375
  # Single pass for short content
4388
- if len(tokens) <= chunk_size_tokens:
4376
+ if len(tokens) <= FIXED_CHUNK_SIZE:
4389
4377
  if debug:
4390
- print("🔧 DEBUG: Content is short enough for single-pass processing")
4378
+ print("🔧 DEBUG: Content fits in single pass")
4391
4379
 
4392
4380
  if streaming_callback:
4393
4381
  streaming_callback("Content fits in a single pass", MSG_TYPE.MSG_TYPE_STEP, {})
4394
4382
 
4395
- # Generic single-pass system prompt
4396
4383
  system_prompt = (
4397
4384
  "You are an expert AI assistant for text analysis and summarization. "
4398
4385
  "Your task is to carefully analyze the provided text and generate a comprehensive, "
4399
- "accurate, and well-structured response that directly addresses the user's objective. "
4400
- "Focus on extracting key information, identifying main themes, and synthesizing the content effectively."
4386
+ "accurate, and well-structured response that directly addresses the user's objective."
4401
4387
  )
4402
4388
 
4403
4389
  prompt_objective = contextual_prompt or "Provide a comprehensive summary and analysis of the provided text."
@@ -4413,120 +4399,164 @@ Provide the final aggregated answer in {output_format} format, directly addressi
4413
4399
  print(f"🔧 DEBUG: Single-pass processing failed: {e}")
4414
4400
  return f"Error in single-pass processing: {e}"
4415
4401
 
4416
- # Multi-chunk processing for long content
4402
+ # ========================================
4403
+ # FIXED: Multi-chunk processing with static sizing
4404
+ # ========================================
4417
4405
  if debug:
4418
- print("🔧 DEBUG: Using multi-chunk processing for long content")
4406
+ print("🔧 DEBUG: Using multi-chunk processing with FIXED chunk size")
4419
4407
 
4420
4408
  chunk_summaries = []
4421
4409
  current_position = 0
4422
4410
  step_number = 1
4411
+
4412
+ # Pre-calculate total steps (won't change since chunk size is fixed)
4413
+ total_steps = -(-len(tokens) // (FIXED_CHUNK_SIZE - overlap_tokens)) # Ceiling division
4414
+
4415
+ if debug:
4416
+ print(f"🔧 DEBUG: Total estimated steps: {total_steps}")
4417
+
4418
+ # ========================================
4419
+ # NEW: Scratchpad compression helper
4420
+ # ========================================
4421
+ def compress_scratchpad(scratchpad_sections: list) -> list:
4422
+ """Compress scratchpad when it gets too large"""
4423
+ if len(scratchpad_sections) <= 2:
4424
+ return scratchpad_sections
4425
+
4426
+ combined = "\n\n---\n\n".join(scratchpad_sections)
4427
+ current_size = len(combined.split())
4428
+
4429
+ if current_size <= scratchpad_compression_threshold:
4430
+ return scratchpad_sections
4431
+
4432
+ if debug:
4433
+ print(f"🔧 DEBUG: Compressing scratchpad from {current_size} tokens")
4434
+
4435
+ compression_prompt = (
4436
+ f"Consolidate the following analysis sections into a more concise summary. "
4437
+ f"Retain all key facts, data points, and conclusions, but eliminate redundancy:\n\n"
4438
+ f"{combined}"
4439
+ )
4440
+
4441
+ try:
4442
+ compressed = self.remove_thinking_blocks(
4443
+ self.llm.generate_text(
4444
+ compression_prompt,
4445
+ system_prompt="You are a text consolidation expert. Create concise summaries that preserve all important information.",
4446
+ **kwargs
4447
+ )
4448
+ )
4449
+
4450
+ if debug:
4451
+ compressed_size = len(compressed.split())
4452
+ print(f"🔧 DEBUG: Compressed to {compressed_size} tokens (reduction: {100*(1-compressed_size/current_size):.1f}%)")
4453
+
4454
+ return [compressed]
4455
+ except Exception as e:
4456
+ if debug:
4457
+ print(f"🔧 DEBUG: Compression failed: {e}, keeping last 3 sections")
4458
+ # Fallback: keep only recent sections
4459
+ return scratchpad_sections[-3:]
4423
4460
 
4461
+ # Main processing loop with FIXED chunk size
4424
4462
  while current_position < len(tokens):
4425
- # Recalculate budget for each step for dynamic adaptation
4426
- current_scratchpad = "\n\n---\n\n".join(chunk_summaries)
4427
- current_budget = calculate_token_budgets(current_scratchpad, step_number)
4428
- adaptive_chunk_size = max(500, current_budget["chunk_budget"])
4429
-
4430
- # Extract the next chunk of text
4431
- chunk_end = min(current_position + adaptive_chunk_size, len(tokens))
4463
+ # Extract chunk using FIXED size
4464
+ chunk_end = min(current_position + FIXED_CHUNK_SIZE, len(tokens))
4432
4465
  chunk_tokens = tokens[current_position:chunk_end]
4433
4466
  chunk_text = " ".join(chunk_tokens)
4434
4467
 
4435
4468
  if debug:
4436
- print(f"\n🔧 DEBUG Step {step_number}: Processing chunk from {current_position} to {chunk_end} "
4437
- f"({len(chunk_tokens)} tokens)")
4469
+ print(f"\n🔧 DEBUG Step {step_number}/{total_steps}: Processing chunk from {current_position} to {chunk_end} "
4470
+ f"({len(chunk_tokens)} tokens)")
4438
4471
 
4439
- # Progress calculation
4440
- remaining_tokens = len(tokens) - current_position
4441
- estimated_remaining_steps = max(1, -(-remaining_tokens // adaptive_chunk_size)) # Ceiling division
4442
- total_estimated_steps = step_number + estimated_remaining_steps -1
4443
- progress = (current_position / len(tokens)) * 90 if len(tokens) > 0 else 0
4472
+ # Progress calculation (based on fixed steps)
4473
+ progress = (step_number / total_steps) * 90
4444
4474
 
4445
4475
  if streaming_callback:
4446
4476
  streaming_callback(
4447
- f"Processing chunk {step_number}/{total_estimated_steps} - "
4448
- f"Budget: {adaptive_chunk_size:,} tokens",
4477
+ f"Processing chunk {step_number}/{total_steps} - Fixed size: {FIXED_CHUNK_SIZE:,} tokens",
4449
4478
  MSG_TYPE.MSG_TYPE_STEP_START,
4450
- {"step": step_number, "progress": progress}
4479
+ {"step": step_number, "total_steps": total_steps, "progress": progress}
4451
4480
  )
4452
4481
 
4482
+ # Check and compress scratchpad if needed
4483
+ current_scratchpad = "\n\n---\n\n".join(chunk_summaries)
4484
+ scratchpad_size = len(current_scratchpad.split())
4485
+
4486
+ if scratchpad_size > scratchpad_compression_threshold:
4487
+ if debug:
4488
+ print(f"🔧 DEBUG: Scratchpad size ({scratchpad_size}) exceeds threshold, compressing...")
4489
+ chunk_summaries = compress_scratchpad(chunk_summaries)
4490
+ current_scratchpad = "\n\n---\n\n".join(chunk_summaries)
4491
+ scratchpad_size = len(current_scratchpad.split())
4492
+
4453
4493
  try:
4454
- # Generic, state-aware system prompt
4455
4494
  system_prompt = (
4456
- 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"
4457
- f"**Current Status:** You are on step {step_number} of approximately {total_estimated_steps} steps. Progress is at {progress:.1f}%.\n\n"
4458
- f"**Your Task:**\n"
4459
- 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"
4460
- 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"
4461
- 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.]'."
4495
+ f"You are a component in a multi-step text processing pipeline analyzing step {step_number} of {total_steps}.\n\n"
4496
+ f"**Your Task:** Analyze the 'New Text Chunk' and extract key information relevant to the 'Global Objective'. "
4497
+ f"Review the 'Existing Scratchpad' to avoid repetition. Add ONLY new insights.\n\n"
4498
+ f"**CRITICAL:** Do NOT repeat information already in the scratchpad. "
4499
+ f"If no new relevant information exists, respond with '[No new information found in this chunk.]'"
4462
4500
  )
4463
4501
 
4464
- # Generic, context-aware user prompt
4465
- summarization_objective = contextual_prompt or "Create a comprehensive summary by extracting all key facts, concepts, and conclusions from the text."
4466
- 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."
4502
+ summarization_objective = contextual_prompt or "Create a comprehensive summary by extracting all key facts, concepts, and conclusions."
4503
+ scratchpad_status = "First chunk analysis" if not chunk_summaries else f"{len(chunk_summaries)} sections completed, {scratchpad_size} tokens"
4467
4504
 
4468
4505
  user_prompt = (
4469
4506
  f"--- Global Objective ---\n{summarization_objective}\n\n"
4470
- f"--- Current Progress ---\n"
4471
- f"{scratchpad_status} (Step {step_number}/{total_estimated_steps})\n\n"
4472
- f"--- Existing Scratchpad Content (for context) ---\n{current_scratchpad}\n\n"
4473
- f"--- New Text Chunk to Analyze ---\n{chunk_text}\n\n"
4474
- f"--- Your Instructions ---\n"
4475
- f"Extract key information from the 'New Text Chunk' that aligns with the 'Global Objective'. "
4476
- f"Provide a concise summary of the new findings. Do not repeat what is already in the scratchpad. "
4477
- f"If no new relevant information is found, state that clearly."
4507
+ f"--- Progress ---\nStep {step_number}/{total_steps} | {scratchpad_status}\n\n"
4508
+ f"--- Existing Scratchpad (for context) ---\n{current_scratchpad}\n\n"
4509
+ f"--- New Text Chunk ---\n{chunk_text}\n\n"
4510
+ f"--- Instructions ---\n"
4511
+ f"Extract NEW key information from this chunk that aligns with the objective. "
4512
+ f"Be concise. Avoid repeating scratchpad content."
4478
4513
  )
4479
4514
 
4480
4515
  if debug:
4481
- print(f"🔧 DEBUG: Sending {len(user_prompt)} char prompt to LLM")
4516
+ print(f"🔧 DEBUG: Prompt size: {len(user_prompt)} chars, Scratchpad: {scratchpad_size} tokens")
4482
4517
 
4483
4518
  chunk_summary = self.remove_thinking_blocks(self.llm.generate_text(user_prompt, system_prompt=system_prompt, **kwargs))
4484
4519
 
4485
4520
  if debug:
4486
- print(f"🔧 DEBUG: Received {len(chunk_summary)} char response preview: {chunk_summary[:200]}...")
4521
+ print(f"🔧 DEBUG: Received {len(chunk_summary)} char response")
4487
4522
 
4488
- # Generic content filtering
4523
+ # Filter logic
4489
4524
  filter_out = False
4490
4525
  filter_reason = "content accepted"
4491
4526
 
4492
- # Check for explicit rejection signals
4493
4527
  if (chunk_summary.strip().lower().startswith('[no new') or
4494
4528
  chunk_summary.strip().lower().startswith('no new information')):
4495
4529
  filter_out = True
4496
4530
  filter_reason = "explicit rejection signal"
4497
- # Check for overly short or generic refusal responses
4498
4531
  elif len(chunk_summary.strip()) < 25:
4499
4532
  filter_out = True
4500
- filter_reason = "response too short to be useful"
4501
- # Check for common error phrases
4502
- elif any(error_phrase in chunk_summary.lower()[:150] for error_phrase in [
4503
- 'error', 'failed', 'cannot provide', 'unable to analyze', 'not possible', 'insufficient information']):
4533
+ filter_reason = "response too short"
4534
+ elif any(error in chunk_summary.lower()[:150] for error in [
4535
+ 'error', 'failed', 'cannot provide', 'unable to analyze']):
4504
4536
  filter_out = True
4505
- filter_reason = "error or refusal response detected"
4537
+ filter_reason = "error response"
4506
4538
 
4507
4539
  if not filter_out:
4508
4540
  chunk_summaries.append(chunk_summary.strip())
4509
4541
  content_added = True
4510
4542
  if debug:
4511
- print(f"🔧 DEBUG: ✅ Content added to scratchpad (total sections: {len(chunk_summaries)})")
4543
+ print(f"🔧 DEBUG: ✅ Content added (total sections: {len(chunk_summaries)})")
4512
4544
  else:
4513
4545
  content_added = False
4514
4546
  if debug:
4515
- print(f"🔧 DEBUG: ❌ Content filtered out - {filter_reason}: {chunk_summary[:100]}...")
4547
+ print(f"🔧 DEBUG: ❌ Filtered: {filter_reason}")
4516
4548
 
4517
- # Update progress via callback
4518
4549
  if streaming_callback:
4519
4550
  updated_scratchpad = "\n\n---\n\n".join(chunk_summaries)
4520
4551
  streaming_callback(
4521
4552
  updated_scratchpad,
4522
4553
  MSG_TYPE.MSG_TYPE_SCRATCHPAD,
4523
- {"step": step_number, "sections": len(chunk_summaries), "content_added": content_added, "filter_reason": filter_reason}
4554
+ {"step": step_number, "sections": len(chunk_summaries), "content_added": content_added}
4524
4555
  )
4525
- progress_after = ((current_position + len(chunk_tokens)) / len(tokens)) * 90 if len(tokens) > 0 else 90
4526
4556
  streaming_callback(
4527
4557
  f"Step {step_number} completed - {'Content added' if content_added else f'Filtered: {filter_reason}'}",
4528
4558
  MSG_TYPE.MSG_TYPE_STEP_END,
4529
- {"progress": progress_after}
4559
+ {"progress": progress}
4530
4560
  )
4531
4561
 
4532
4562
  except Exception as e:
@@ -4536,82 +4566,79 @@ Provide the final aggregated answer in {output_format} format, directly addressi
4536
4566
  self.trace_exception(e)
4537
4567
  if streaming_callback:
4538
4568
  streaming_callback(error_msg, MSG_TYPE.MSG_TYPE_EXCEPTION)
4539
- chunk_summaries.append(f"[Error processing chunk at step {step_number}: {str(e)[:150]}]")
4569
+ chunk_summaries.append(f"[Error at step {step_number}: {str(e)[:150]}]")
4540
4570
 
4541
- # Move to the next chunk, allowing for overlap
4542
- current_position += max(1, adaptive_chunk_size - overlap_tokens)
4571
+ # Move to next chunk with FIXED size
4572
+ current_position += max(1, FIXED_CHUNK_SIZE - overlap_tokens)
4543
4573
  step_number += 1
4544
4574
 
4545
- # Safety break for excessively long documents
4575
+ # Safety break
4546
4576
  if step_number > 200:
4547
- if debug: print(f"🔧 DEBUG: Safety break after {step_number-1} steps.")
4548
- chunk_summaries.append("[Processing halted due to exceeding maximum step limit.]")
4577
+ if debug:
4578
+ print(f"🔧 DEBUG: Safety break at step {step_number}")
4579
+ chunk_summaries.append("[Processing halted: exceeded maximum steps]")
4549
4580
  break
4550
4581
 
4551
4582
  if debug:
4552
- print(f"\n🔧 DEBUG: Chunk processing complete. Total sections gathered: {len(chunk_summaries)}")
4583
+ print(f"\n🔧 DEBUG: Processing complete. Sections: {len(chunk_summaries)}")
4553
4584
 
4554
- # Return only the scratchpad content if requested
4585
+ # Return scratchpad only if requested
4555
4586
  if return_scratchpad_only:
4556
4587
  final_scratchpad = "\n\n---\n\n".join(chunk_summaries)
4557
4588
  if streaming_callback:
4558
- streaming_callback("Returning scratchpad content as final output.", MSG_TYPE.MSG_TYPE_STEP, {})
4589
+ streaming_callback("Returning scratchpad content", MSG_TYPE.MSG_TYPE_STEP, {})
4559
4590
  return final_scratchpad.strip()
4560
4591
 
4561
- # Final Synthesis Step
4592
+ # Final synthesis
4562
4593
  if streaming_callback:
4563
- streaming_callback("Synthesizing final comprehensive response...", MSG_TYPE.MSG_TYPE_STEP_START, {"progress": 95})
4594
+ streaming_callback("Synthesizing final response...", MSG_TYPE.MSG_TYPE_STEP_START, {"progress": 95})
4564
4595
 
4565
4596
  if not chunk_summaries:
4566
- error_msg = "No content was successfully processed or extracted from the document. The input might be empty or an issue occurred during processing."
4597
+ error_msg = "No content was successfully processed."
4567
4598
  if debug:
4568
4599
  print(f"🔧 DEBUG: ❌ {error_msg}")
4569
4600
  return error_msg
4570
4601
 
4571
4602
  combined_scratchpad = "\n\n---\n\n".join(chunk_summaries)
4572
- synthesis_objective = contextual_prompt or "Provide a comprehensive, well-structured summary and analysis of the provided text."
4603
+ synthesis_objective = contextual_prompt or "Provide a comprehensive, well-structured summary and analysis."
4573
4604
 
4574
4605
  if debug:
4575
- print(f"🔧 DEBUG: Synthesizing from {len(combined_scratchpad):,} char scratchpad with {len(chunk_summaries)} sections.")
4606
+ print(f"🔧 DEBUG: Synthesizing from {len(combined_scratchpad):,} chars, {len(chunk_summaries)} sections")
4576
4607
 
4577
- # Generic synthesis prompts
4578
4608
  synthesis_system_prompt = (
4579
- "You are an expert AI assistant specializing in synthesizing information. "
4580
- "Your task is to consolidate a series of text analysis sections from a scratchpad into a single, coherent, and well-structured final response. "
4581
- "Eliminate redundancy, organize the content logically, and ensure the final output directly and comprehensively addresses the user's primary objective. "
4582
- "Use markdown for clear formatting (e.g., headers, lists, bold text)."
4609
+ "You are an expert at synthesizing information. "
4610
+ "Consolidate the analysis sections into a coherent final response. "
4611
+ "Eliminate redundancy, organize logically, and use markdown formatting."
4583
4612
  )
4584
4613
 
4585
4614
  synthesis_user_prompt = (
4586
4615
  f"--- Final Objective ---\n{synthesis_objective}\n\n"
4587
- f"--- Collected Analysis Sections (Scratchpad) ---\n{combined_scratchpad}\n\n"
4588
- f"--- Your Final Task ---\n"
4589
- f"Synthesize all the information from the 'Collected Analysis Sections' into a single, high-quality, and comprehensive response. "
4590
- f"Your response must directly address the 'Final Objective'. "
4591
- f"Organize your answer logically with clear sections using markdown headers. "
4592
- f"Ensure all key information is included, remove any repetitive statements, and produce a polished, final document."
4616
+ f"--- Collected Analysis Sections ---\n{combined_scratchpad}\n\n"
4617
+ f"--- Instructions ---\n"
4618
+ f"Synthesize all information into a comprehensive response addressing the objective. "
4619
+ f"Organize with markdown headers, remove repetition, create a polished final document."
4593
4620
  )
4594
4621
 
4595
4622
  try:
4596
4623
  final_answer = self.remove_thinking_blocks(self.llm.generate_text(synthesis_user_prompt, system_prompt=synthesis_system_prompt, **kwargs))
4597
4624
  if debug:
4598
- print(f"🔧 DEBUG: Final synthesis generated: {len(final_answer):,} characters")
4625
+ print(f"🔧 DEBUG: Final synthesis: {len(final_answer):,} characters")
4599
4626
  if streaming_callback:
4600
- streaming_callback("Final synthesis complete.", MSG_TYPE.MSG_TYPE_STEP_END, {"progress": 100})
4627
+ streaming_callback("Final synthesis complete", MSG_TYPE.MSG_TYPE_STEP_END, {"progress": 100})
4601
4628
  return final_answer.strip()
4602
4629
 
4603
4630
  except Exception as e:
4604
- error_msg = f"The final synthesis step failed: {str(e)}. Returning the organized scratchpad content as a fallback."
4605
- if debug: print(f"🔧 DEBUG: ❌ {error_msg}")
4631
+ error_msg = f"Synthesis failed: {str(e)}. Returning scratchpad."
4632
+ if debug:
4633
+ print(f"🔧 DEBUG: ❌ {error_msg}")
4606
4634
 
4607
- # Fallback to returning the organized scratchpad
4608
4635
  organized_scratchpad = (
4609
4636
  f"# Analysis Summary\n\n"
4610
- f"*Note: The final synthesis process encountered an error. The raw, organized analysis sections are provided below.*\n\n"
4611
- f"## Collected Sections\n\n"
4612
- f"{combined_scratchpad}"
4637
+ f"*Note: Final synthesis failed. Raw analysis sections below.*\n\n"
4638
+ f"## Collected Sections\n\n{combined_scratchpad}"
4613
4639
  )
4614
4640
  return organized_scratchpad
4641
+
4615
4642
 
4616
4643
 
4617
4644
  def chunk_text(text, tokenizer, detokenizer, chunk_size, overlap, use_separators=True):
@@ -62,7 +62,7 @@ MODELS_PATH = Path("./models")
62
62
  CIVITAI_MODELS = {
63
63
  "realistic-vision-v6": {
64
64
  "display_name": "Realistic Vision V6.0", "url": "https://civitai.com/api/download/models/501240?type=Model&format=SafeTensor&size=pruned&fp=fp16",
65
- "filename": "realisticVisionV60_v60B1.safensors", "description": "Photorealistic SD1.5 checkpoint.", "owned_by": "civitai"
65
+ "filename": "realisticVisionV60_v60B1.safetensors", "description": "Photorealistic SD1.5 checkpoint.", "owned_by": "civitai"
66
66
  },
67
67
  "absolute-reality": {
68
68
  "display_name": "Absolute Reality", "url": "https://civitai.com/api/download/models/132760?type=Model&format=SafeTensor&size=pruned&fp=fp16",
@@ -145,8 +145,11 @@ HF_PUBLIC_MODELS = {
145
145
  ],
146
146
  "Image Editing Tools": [
147
147
  {"model_name": "stabilityai/stable-diffusion-xl-refiner-1.0", "display_name": "SDXL Refiner 1.0", "desc": "A dedicated refiner model to improve details in SDXL generations."},
148
- {"model_name": "Qwen/Qwen-Image-Edit", "display_name": "Qwen Image Edit", "desc": "An instruction-based model for various image editing tasks."},
149
- {"model_name": "Qwen/Qwen-Image-Edit-2509", "display_name": "Qwen Image Edit Plus", "desc": "Advanced multi-image editing, fusion, and pose transfer."},
148
+ {"model_name": "timbrooks/instruct-pix2pix", "display_name": "Instruct-Pix2Pix", "desc": "The original instruction-based image editing model (SD 1.5)."},
149
+ {"model_name": "kandinsky-community/kandinsky-2-2-instruct-pix2pix", "display_name": "Kandinsky 2.2 Instruct", "desc": "An instruction-based model with strong prompt adherence, based on Kandinsky 2.2."},
150
+ {"model_name": "diffusers/stable-diffusion-xl-1.0-inpainting-0.1", "display_name": "SDXL Inpainting", "desc": "A dedicated inpainting model based on SDXL 1.0 for filling in masked areas."},
151
+ {"model_name": "Qwen/Qwen-Image-Edit", "display_name": "Qwen Image Edit", "desc": "An instruction-based model for various image editing tasks. (Review License)."},
152
+ {"model_name": "Qwen/Qwen-Image-Edit-2509", "display_name": "Qwen Image Edit Plus", "desc": "Advanced multi-image editing and fusion. (Review License)."},
150
153
  ],
151
154
  "Legacy & Base Models": [
152
155
  {"model_name": "runwayml/stable-diffusion-v1-5", "display_name": "Stable Diffusion 1.5", "desc": "The classic and versatile SD1.5 base model."},
@@ -728,36 +731,33 @@ async def generate_image(request: T2IRequest):
728
731
  manager = None
729
732
  temp_config = None
730
733
  try:
731
- params = request.params
732
-
733
734
  # Determine which model manager to use for this specific request
734
- if "model_name" in params and params["model_name"]:
735
+ if "model_name" in request.params and request.params["model_name"]:
735
736
  temp_config = state.config.copy()
736
- temp_config["model_name"] = params.pop("model_name") # Remove from params to avoid being passed to pipeline
737
+ temp_config["model_name"] = request.params.pop("model_name") # Remove from params to avoid being passed to pipeline
737
738
  manager = state.registry.get_manager(temp_config, state.models_path)
738
739
  ASCIIColors.info(f"Using per-request model: {temp_config['model_name']}")
739
740
  else:
740
741
  manager = state.get_active_manager()
741
742
  ASCIIColors.info(f"Using session-configured model: {manager.config.get('model_name')}")
742
743
 
743
- seed = int(params.get("seed", manager.config.get("seed", -1)))
744
- generator = None
744
+ # Start with the manager's config (base settings)
745
+ pipeline_args = manager.config.copy()
746
+ # Override with per-request parameters
747
+ pipeline_args.update(request.params)
748
+
749
+ # Add prompts and ensure types for specific args
750
+ pipeline_args["prompt"] = request.prompt
751
+ pipeline_args["negative_prompt"] = request.negative_prompt
752
+ pipeline_args["width"] = int(pipeline_args.get("width", 512))
753
+ pipeline_args["height"] = int(pipeline_args.get("height", 512))
754
+ pipeline_args["num_inference_steps"] = int(pipeline_args.get("num_inference_steps", 25))
755
+ pipeline_args["guidance_scale"] = float(pipeline_args.get("guidance_scale", 7.0))
756
+
757
+ seed = int(pipeline_args.get("seed", -1))
758
+ pipeline_args["generator"] = None
745
759
  if seed != -1:
746
- generator = torch.Generator(device=manager.config["device"]).manual_seed(seed)
747
-
748
- width = int(params.get("width", manager.config.get("width", 512)))
749
- height = int(params.get("height", manager.config.get("height", 512)))
750
-
751
- pipeline_args = {
752
- "prompt": request.prompt,
753
- "negative_prompt": request.negative_prompt,
754
- "width": width,
755
- "height": height,
756
- "num_inference_steps": int(params.get("num_inference_steps", manager.config.get("num_inference_steps", 25))),
757
- "guidance_scale": float(params.get("guidance_scale", manager.config.get("guidance_scale", 7.0))),
758
- "generator": generator
759
- }
760
- pipeline_args.update(params)
760
+ pipeline_args["generator"] = torch.Generator(device=manager.config["device"]).manual_seed(seed)
761
761
 
762
762
  model_name = manager.config.get("model_name", "")
763
763
  task = "text2image"
@@ -765,12 +765,24 @@ async def generate_image(request: T2IRequest):
765
765
  if "Qwen-Image-Edit" in model_name:
766
766
  rng_seed = seed if seed != -1 else None
767
767
  rng = np.random.default_rng(seed=rng_seed)
768
- random_pixels = rng.integers(0, 256, size=(height, width, 3), dtype=np.uint8)
768
+ random_pixels = rng.integers(0, 256, size=(pipeline_args["height"], pipeline_args["width"], 3), dtype=np.uint8)
769
769
  placeholder_image = Image.fromarray(random_pixels, 'RGB')
770
770
  pipeline_args["image"] = placeholder_image
771
- pipeline_args["strength"] = float(params.get("strength", 1.0))
771
+ pipeline_args["strength"] = float(pipeline_args.get("strength", 1.0))
772
772
  task = "image2image"
773
773
 
774
+ log_args = {k: v for k, v in pipeline_args.items() if k not in ['generator', 'image']}
775
+ if pipeline_args.get("generator"): log_args['generator'] = f"<torch.Generator(seed={seed})>"
776
+ if pipeline_args.get("image"): log_args['image'] = "<PIL Image object>"
777
+
778
+ ASCIIColors.cyan("--- Generating Image with Settings ---")
779
+ try:
780
+ print(json.dumps(log_args, indent=2, default=str))
781
+ except Exception as e:
782
+ ASCIIColors.warning(f"Could not print all settings: {e}")
783
+ print(log_args)
784
+ ASCIIColors.cyan("------------------------------------")
785
+
774
786
  future = Future()
775
787
  manager.queue.put((future, task, pipeline_args))
776
788
  result_bytes = future.result()
@@ -789,17 +801,20 @@ async def edit_image(request: EditRequestJSON):
789
801
  manager = None
790
802
  temp_config = None
791
803
  try:
792
- params = request.params
793
-
794
- if "model_name" in params and params["model_name"]:
804
+ if "model_name" in request.params and request.params["model_name"]:
795
805
  temp_config = state.config.copy()
796
- temp_config["model_name"] = params.pop("model_name")
806
+ temp_config["model_name"] = request.params.pop("model_name")
797
807
  manager = state.registry.get_manager(temp_config, state.models_path)
798
808
  ASCIIColors.info(f"Using per-request model: {temp_config['model_name']}")
799
809
  else:
800
810
  manager = state.get_active_manager()
801
811
  ASCIIColors.info(f"Using session-configured model: {manager.config.get('model_name')}")
802
812
 
813
+ # Start with manager's config, then override with request params
814
+ pipeline_args = manager.config.copy()
815
+ pipeline_args.update(request.params)
816
+
817
+ pipeline_args["prompt"] = request.prompt
803
818
  model_name = manager.config.get("model_name", "")
804
819
 
805
820
  pil_images = []
@@ -810,27 +825,38 @@ async def edit_image(request: EditRequestJSON):
810
825
 
811
826
  if not pil_images: raise HTTPException(status_code=400, detail="No valid images provided.")
812
827
 
813
- pipeline_args = {"prompt": request.prompt}
814
- seed = int(params.get("seed", -1))
828
+ seed = int(pipeline_args.get("seed", -1))
829
+ pipeline_args["generator"] = None
815
830
  if seed != -1: pipeline_args["generator"] = torch.Generator(device=manager.config["device"]).manual_seed(seed)
816
831
 
817
- if "mask_image" in params and params["mask_image"]:
818
- b64_mask = params["mask_image"]
832
+ if "mask_image" in pipeline_args and pipeline_args["mask_image"]:
833
+ b64_mask = pipeline_args["mask_image"]
819
834
  b64_data = b64_mask.split(";base64,")[1] if ";base64," in b64_mask else b64_mask
820
835
  mask_bytes = base64.b64decode(b64_data)
821
836
  pipeline_args["mask_image"] = Image.open(BytesIO(mask_bytes)).convert("L")
822
837
 
823
- task = "inpainting" if "mask_image" in pipeline_args else "image2image"
838
+ task = "inpainting" if "mask_image" in pipeline_args and pipeline_args["mask_image"] else "image2image"
824
839
 
825
840
  if "Qwen-Image-Edit-2509" in model_name:
826
841
  task = "image2image"
827
842
  pipeline_args.update({"true_cfg_scale": 4.0, "guidance_scale": 1.0, "num_inference_steps": 40, "negative_prompt": " "})
828
- edit_mode = params.get("edit_mode", "fusion")
843
+ edit_mode = pipeline_args.get("edit_mode", "fusion")
829
844
  if edit_mode == "fusion": pipeline_args["image"] = pil_images
830
845
  else:
831
846
  pipeline_args.update({"image": pil_images[0], "strength": 0.8, "guidance_scale": 7.5, "num_inference_steps": 25})
832
847
 
833
- pipeline_args.update(params)
848
+ log_args = {k: v for k, v in pipeline_args.items() if k not in ['generator', 'image', 'mask_image']}
849
+ if pipeline_args.get("generator"): log_args['generator'] = f"<torch.Generator(seed={seed})>"
850
+ if 'image' in pipeline_args: log_args['image'] = f"[<{len(pil_images)} PIL Image(s)>]"
851
+ if 'mask_image' in pipeline_args and pipeline_args['mask_image']: log_args['mask_image'] = "<PIL Mask Image>"
852
+
853
+ ASCIIColors.cyan("--- Editing Image with Settings ---")
854
+ try:
855
+ print(json.dumps(log_args, indent=2, default=str))
856
+ except Exception as e:
857
+ ASCIIColors.warning(f"Could not print all settings: {e}")
858
+ print(log_args)
859
+ ASCIIColors.cyan("---------------------------------")
834
860
 
835
861
  future = Future(); manager.queue.put((future, task, pipeline_args))
836
862
  return Response(content=future.result(), media_type="image/png")
@@ -6,13 +6,6 @@ import time
6
6
  from pathlib import Path
7
7
  from typing import Optional, List
8
8
 
9
- # Ensure pipmaster is available.
10
- try:
11
- import pipmaster as pm
12
- except ImportError:
13
- print("FATAL: pipmaster is not installed. Please install it using: pip install pipmaster")
14
- sys.exit(1)
15
-
16
9
  # Ensure filelock is available for process-safe server startup.
17
10
  try:
18
11
  from filelock import FileLock, Timeout
@@ -97,6 +90,12 @@ class XTTSClientBinding(LollmsTTSBinding):
97
90
  using pipmaster, which handles complex packages like PyTorch.
98
91
  """
99
92
  ASCIIColors.info(f"Setting up virtual environment in: {self.venv_dir}")
93
+ # Ensure pipmaster is available.
94
+ try:
95
+ import pipmaster as pm
96
+ except ImportError:
97
+ print("FATAL: pipmaster is not installed. Please install it using: pip install pipmaster")
98
+ raise Exception("pipmaster not found")
100
99
  pm_v = pm.PackageManager(venv_path=str(self.venv_dir))
101
100
 
102
101
  requirements_file = self.server_dir / "requirements.txt"
@@ -141,7 +140,7 @@ class XTTSClientBinding(LollmsTTSBinding):
141
140
  self.server_process = subprocess.Popen(command, creationflags=creationflags)
142
141
  ASCIIColors.info("XTTS server process launched in the background.")
143
142
 
144
- def _wait_for_server(self, timeout=120):
143
+ def _wait_for_server(self, timeout=1):
145
144
  """Waits for the server to become responsive."""
146
145
  ASCIIColors.info("Waiting for XTTS server to become available...")
147
146
  start_time = time.time()
@@ -272,4 +272,4 @@ except Exception as e:
272
272
  from ascii_colors import ASCIIColors
273
273
  ASCIIColors.red(f"Server: CRITICAL ERROR during startup: {e}")
274
274
  import traceback
275
- ASCIIColors.red(f"Server: Traceback:\n{traceback.format_exc()}")```
275
+ ASCIIColors.red(f"Server: Traceback:\n{traceback.format_exc()}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lollms_client
3
- Version: 1.6.6
3
+ Version: 1.6.7
4
4
  Summary: A client library for LoLLMs generate endpoint
5
5
  Author-email: ParisNeo <parisneoai@gmail.com>
6
6
  License: Apache License
@@ -1,7 +1,7 @@
1
- lollms_client/__init__.py,sha256=51YtCHNJCmroyA9htiIgjui1ZSFfkn_zhhe0USpE8nc,1146
1
+ lollms_client/__init__.py,sha256=CrN8dkGE49W-rpFHEln-GE74Rp-Ezq3zbu5sRAcnvXc,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=Un74iLbnnn2yZYH6HBNRz1mTZ454NEMBEndS4nvh3ZI,244887
4
+ lollms_client/lollms_core.py,sha256=kF42KKd9UCOr_-ME0vgB0_1Ae00B4ZWXjfTvFymeRP0,244203
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=_6d0q9g9lk8FRZ1oYnLpuqG7Y_WLyBJBn4ANdk-C8gU,25020
@@ -53,7 +53,7 @@ lollms_client/stt_bindings/whisper/__init__.py,sha256=1Ej67GdRKBy1bba14jMaYDYHiZ
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
55
  lollms_client/tti_bindings/diffusers/__init__.py,sha256=esrcyy_z_6HVCFKMVXl1h_qY_pX3kMHwO81M2C8hSIg,17706
56
- lollms_client/tti_bindings/diffusers/server/main.py,sha256=PQ3WXhkQzEzyT100k7nu1ZHQtkGphvpWNGl7Bcg26eY,49593
56
+ lollms_client/tti_bindings/diffusers/server/main.py,sha256=7xWANWnxHeDAF_NQTbJD4QToxoVtaAEdxGHMXOotz5s,51907
57
57
  lollms_client/tti_bindings/gemini/__init__.py,sha256=eYGz6gnOxWGdJu2O0H-EwGG-Hg7Yo3Hzsgn4neqx29Q,12963
58
58
  lollms_client/tti_bindings/leonardo_ai/__init__.py,sha256=pUbF1rKPZib1x0Kn2Bk1A7sTFWmZzNG02kmW6Iu1j2w,5885
59
59
  lollms_client/tti_bindings/lollms/__init__.py,sha256=5Tnsn4b17djvieQkcjtIDBm3qf0pg5ZWWov-4_2wmo0,8762
@@ -76,13 +76,13 @@ lollms_client/tts_bindings/piper_tts/__init__.py,sha256=7LQUuWV8I3IEdacc65NRHmDf
76
76
  lollms_client/tts_bindings/piper_tts/server/install_piper.py,sha256=g71Ne2T18wAytOPipfQ9DNeTAOD9PrII5qC-vr9DtLA,3256
77
77
  lollms_client/tts_bindings/piper_tts/server/main.py,sha256=DMozfSR1aCbrlmOXltRFjtXhYhXajsGcNKQjsWgRwZk,17402
78
78
  lollms_client/tts_bindings/piper_tts/server/setup_voices.py,sha256=UdHaPa5aNcw8dR-aRGkZr2OfSFFejH79lXgfwT0P3ss,1964
79
- lollms_client/tts_bindings/xtts/__init__.py,sha256=lTlExBPZ97FPaf9DoqxE4ilwwO5y88dPOHeRaR5BCnc,8002
80
- lollms_client/tts_bindings/xtts/server/main.py,sha256=JYKUzg4qFOGW8O_QDb9ChEdhcPRSccdwOlR3q-kJX7I,12306
79
+ lollms_client/tts_bindings/xtts/__init__.py,sha256=sQnmlXbFb5r6mX-4DfExuM7YJ_aSv551NM8ZzTrMauo,8073
80
+ lollms_client/tts_bindings/xtts/server/main.py,sha256=feTAX4eAo2HY6PpcDTrgRMak5AXocO7UIhKPuGuWpxY,12303
81
81
  lollms_client/tts_bindings/xtts/server/setup_voices.py,sha256=UdHaPa5aNcw8dR-aRGkZr2OfSFFejH79lXgfwT0P3ss,1964
82
82
  lollms_client/ttv_bindings/__init__.py,sha256=UZ8o2izQOJLQgtZ1D1cXoNST7rzqW22rL2Vufc7ddRc,3141
83
83
  lollms_client/ttv_bindings/lollms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
- lollms_client-1.6.6.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
85
- lollms_client-1.6.6.dist-info/METADATA,sha256=i6Gb5wKrXNF6OPUCz41s5YbpBY5HEvLdAD5a6ONZV84,76835
86
- lollms_client-1.6.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
87
- lollms_client-1.6.6.dist-info/top_level.txt,sha256=Bk_kz-ri6Arwsk7YG-T5VsRorV66uVhcHGvb_g2WqgE,14
88
- lollms_client-1.6.6.dist-info/RECORD,,
84
+ lollms_client-1.6.7.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
85
+ lollms_client-1.6.7.dist-info/METADATA,sha256=c5Bud1Xae1bMbN5IZVYYJNva_f7DPvFaxrNnaHcRsSE,76835
86
+ lollms_client-1.6.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
87
+ lollms_client-1.6.7.dist-info/top_level.txt,sha256=Bk_kz-ri6Arwsk7YG-T5VsRorV66uVhcHGvb_g2WqgE,14
88
+ lollms_client-1.6.7.dist-info/RECORD,,