Compare commits

...
10 Commits
Author SHA1 Message Date
Mervin Praison 904e21ca87 Merge pull request #396 from MervinPraison/develop
v2.0.77
2025-02-27 09:12:33 +00:00
MervinPraison 526665ea11 Bump version to 2.0.77 and update praisonaiagents dependency
- Updated version to 2.0.77 across multiple files including praisonai.rb, pyproject.toml, uv.lock, Dockerfile, and docs
- Upgraded praisonaiagents dependency from 0.0.61 to 0.0.62
- Synchronized version updates across project configuration files
2025-02-27 09:11:18 +00:00
MervinPraison c2e97d2432 adding litellm tools fix 2025-02-27 09:08:22 +00:00
MervinPraison 60be6a1f99 Add Comprehensive Debug Logging to LLM and Agent Components
- Enhanced LLM and Agent classes with detailed debug logging capabilities
- Added parameter and configuration logging for get_response and chat methods
- Implemented time tracking for method execution in debug mode
- Masked sensitive information like API keys in logs
- Created new Wikipedia agent example script demonstrating agent initialization
2025-02-27 06:54:32 +00:00
Mervin Praison 0ba7d2e834 Merge pull request #385 from MervinPraison/develop
Add Vision Model Upload Functionality and Enhance Training Workflow
2025-02-09 21:23:38 +00:00
MervinPraison 184b241395 Add Vision Model Upload Functionality and Enhance Training Workflow
- Introduced new upload_vision.py script for comprehensive model upload capabilities
- Extended train_vision.py with additional upload methods for Hugging Face and Ollama
- Added support for saving merged models, pushing GGUF models, and creating Ollama models
- Implemented flexible upload targeting with configurable options
- Enhanced model preparation and deployment workflow for vision models
2025-02-09 21:23:24 +00:00
Mervin Praison 4354e76fe5 Merge pull request #383 from MervinPraison/develop
Enhance Vision Model Training with Robust Dataset Conversion and Memo…
2025-02-09 02:46:01 +00:00
MervinPraison 4dd77efb75 Enhance Vision Model Training with Robust Dataset Conversion and Memory Optimization
- Significantly improved train_vision.py with more robust dataset conversion process
- Added detailed debugging and logging for dataset loading and conversion
- Implemented advanced memory management techniques for vision model training
- Enhanced tokenizer handling with fallback mechanisms
- Updated version to 2.0.76 across all relevant files
2025-02-09 02:45:47 +00:00
Mervin Praison 11db6e18ba Merge pull request #382 from MervinPraison/develop
Add Vision Model Training Support and Update Unsloth Dependency
2025-02-09 00:31:40 +00:00
MervinPraison 0d6f698e6c Add Vision Model Training Support and Update Unsloth Dependency
- Enhanced CLI to dynamically select training script based on model type (vision vs. standard)
- Added support for detecting vision models through flexible name matching
- Updated Unsloth dependency to latest commit
- Bumped version to 2.0.75 across all relevant files
2025-02-09 00:31:20 +00:00
15 changed files with 872 additions and 21 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install flask praisonai==2.0.74 gunicorn markdown
RUN pip install flask praisonai==2.0.77 gunicorn markdown
EXPOSE 8080
CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]
+1 -1
View File
@@ -110,7 +110,7 @@ Loads environment variables from .env file or system and sets them.</p>
file.write(&#34;FROM python:3.11-slim\n&#34;)
file.write(&#34;WORKDIR /app\n&#34;)
file.write(&#34;COPY . .\n&#34;)
file.write(&#34;RUN pip install flask praisonai==2.0.74 gunicorn markdown\n&#34;)
file.write(&#34;RUN pip install flask praisonai==2.0.77 gunicorn markdown\n&#34;)
file.write(&#34;EXPOSE 8080\n&#34;)
file.write(&#39;CMD [&#34;gunicorn&#34;, &#34;-b&#34;, &#34;0.0.0.0:8080&#34;, &#34;api:app&#34;]\n&#39;)
+1 -1
View File
@@ -3,7 +3,7 @@ class Praisonai < Formula
desc "AI tools for various AI applications"
homepage "https://github.com/MervinPraison/PraisonAI"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/2.0.74.tar.gz"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/2.0.77.tar.gz"
sha256 "1828fb9227d10f991522c3f24f061943a254b667196b40b1a3e4a54a8d30ce32" # Replace with actual SHA256 checksum
license "MIT"
+12 -1
View File
@@ -240,7 +240,18 @@ class PraisonAI:
print("All packages installed.")
train_args = sys.argv[2:] # Get all arguments after 'train'
train_script_path = os.path.join(package_root, 'train.py')
# Check if this is a vision model - handle all case variations
model_name = config.get("model_name", "").lower()
is_vision_model = any(x in model_name for x in ["-vl-", "-vl", "vl-", "-vision-", "-vision", "vision-", "visionmodel"])
# Choose appropriate training script
if is_vision_model:
train_script_path = os.path.join(package_root, 'train_vision.py')
print("Using vision training script for VL model...")
else:
train_script_path = os.path.join(package_root, 'train.py')
print("Using standard training script...")
# Set environment variables
env = os.environ.copy()
+1 -1
View File
@@ -56,7 +56,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==2.0.74 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.0.77 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+1 -1
View File
@@ -96,7 +96,7 @@ PIP_FULL_PATH=$(conda run -n "$ENV_NAME" which pip)
# Install other packages using pip
$PIP_FULL_PATH install --upgrade pip
$PIP_FULL_PATH install "xformers==0.0.26.post1"
$PIP_FULL_PATH install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git@038e6d4c8d40207a87297ab3aaf787c19b1006d1"
$PIP_FULL_PATH install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git@53a773e4fbc53a1d96c7ba107e5fe75dab07027b"
$PIP_FULL_PATH install --no-deps "trl<0.9.0" peft accelerate bitsandbytes
$PIP_FULL_PATH install unsloth_zoo
$PIP_FULL_PATH install cut_cross_entropy
+306
View File
@@ -0,0 +1,306 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This script finetunes a vision language model using Unsloth's fast training framework.
It supports vision tasks by converting raw image-caption samples into a conversation format,
adding vision-specific LoRA adapters, and training using TRL's SFTTrainer with UnslothVisionDataCollator.
"""
import os
import sys
import yaml
import torch
import shutil
import subprocess
import gc # For garbage collection
from datasets import load_dataset, concatenate_datasets, Dataset
from unsloth import FastVisionModel, is_bf16_supported
from unsloth.trainer import UnslothVisionDataCollator
from transformers import TrainingArguments
from trl import SFTTrainer
from tqdm import tqdm # Add progress bar
class TrainVisionModel:
def __init__(self, config_path="config.yaml"):
self.load_config(config_path)
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.model = None
self.hf_tokenizer = None # The underlying tokenizer
def load_config(self, path):
with open(path, "r") as file:
self.config = yaml.safe_load(file)
print("DEBUG: Loaded config:", self.config)
def print_system_info(self):
print("DEBUG: PyTorch version:", torch.__version__)
print("DEBUG: CUDA version:", torch.version.cuda)
if torch.cuda.is_available():
print("DEBUG: CUDA Device Capability:", torch.cuda.get_device_capability())
else:
print("DEBUG: CUDA is not available")
print("DEBUG: Python Version:", sys.version)
print("DEBUG: Python Path:", sys.executable)
def check_gpu(self):
gpu_stats = torch.cuda.get_device_properties(0)
print(f"DEBUG: GPU = {gpu_stats.name}. Max memory = {round(gpu_stats.total_memory/(1024**3),3)} GB.")
def check_ram(self):
from psutil import virtual_memory
ram_gb = virtual_memory().total / 1e9
print(f"DEBUG: Your runtime has {ram_gb:.1f} gigabytes of available RAM")
if ram_gb < 20:
print("DEBUG: Not using a high-RAM runtime")
else:
print("DEBUG: You are using a high-RAM runtime!")
def prepare_model(self):
print("DEBUG: Preparing vision model and tokenizer...")
self.model, original_tokenizer = FastVisionModel.from_pretrained(
model_name=self.config["model_name"],
load_in_4bit=self.config["load_in_4bit"],
use_gradient_checkpointing="unsloth"
)
print("DEBUG: Vision model and original tokenizer loaded.")
# Use the full processor that supports image inputs.
self.hf_tokenizer = original_tokenizer
# Set pad token if needed
if not hasattr(self.hf_tokenizer, 'pad_token') or self.hf_tokenizer.pad_token is None:
if hasattr(self.hf_tokenizer, 'eos_token'):
self.hf_tokenizer.pad_token = self.hf_tokenizer.eos_token
elif hasattr(self.hf_tokenizer, 'bos_token'):
self.hf_tokenizer.pad_token = self.hf_tokenizer.bos_token
# Set max length
if hasattr(self.hf_tokenizer, 'model_max_length'):
self.hf_tokenizer.model_max_length = self.config.get("max_seq_length", 2048)
# Add vision-specific LoRA adapters
self.model = FastVisionModel.get_peft_model(
self.model,
finetune_vision_layers=self.config.get("finetune_vision_layers", False),
finetune_language_layers=self.config.get("finetune_language_layers", True),
finetune_attention_modules=self.config.get("finetune_attention_modules", True),
finetune_mlp_modules=self.config.get("finetune_mlp_modules", True),
r=16,
lora_alpha=16,
lora_dropout=0,
bias="none",
random_state=3407,
use_rslora=False,
loftq_config=None
)
print("DEBUG: Vision LoRA adapters added.")
def convert_sample(self, sample):
instruction = self.config.get(
"vision_instruction",
"You are an expert radiographer. Describe accurately what you see in this image."
)
conversation = [
{
"role": "user",
"content": [
{"type": "text", "text": instruction},
{"type": "image", "image": sample["image"]}
]
},
{
"role": "assistant",
"content": [
{"type": "text", "text": sample["caption"]}
]
},
]
return {"messages": conversation}
def load_datasets(self):
all_converted = []
for dataset_info in self.config["dataset"]:
print("\nDEBUG: Loading vision dataset:", dataset_info)
ds = load_dataset(
dataset_info["name"],
split=dataset_info.get("split_type", "train")
)
print("DEBUG: Dataset size:", len(ds))
print("DEBUG: First raw sample:", ds[0])
print("DEBUG: Dataset features:", ds.features)
print("\nDEBUG: Converting dataset to vision conversation format...")
converted_ds = [self.convert_sample(sample) for sample in ds]
# Debug first converted sample
print("\nDEBUG: First converted sample structure:")
first = converted_ds[0]
print("DEBUG: Message keys:", first["messages"][0]["content"][1].keys())
print("DEBUG: Image type in converted:", type(first["messages"][0]["content"][1].get("image")))
all_converted.extend(converted_ds)
print("\nDEBUG: Combined vision dataset has", len(all_converted), "examples.")
return all_converted
def train_model(self):
print("DEBUG: Starting vision training...")
raw_dataset = self.load_datasets()
# Build training arguments using TrainingArguments
training_args = TrainingArguments(
per_device_train_batch_size=self.config.get("per_device_train_batch_size", 1),
gradient_accumulation_steps=self.config.get("gradient_accumulation_steps", 4),
warmup_steps=self.config.get("warmup_steps", 5),
max_steps=self.config.get("max_steps", 30),
learning_rate=self.config.get("learning_rate", 2e-4),
fp16=self.config.get("fp16", not is_bf16_supported()),
bf16=self.config.get("bf16", is_bf16_supported()),
logging_steps=self.config.get("logging_steps", 1),
optim=self.config.get("optim", "adamw_8bit"),
weight_decay=self.config.get("weight_decay", 0.01),
lr_scheduler_type=self.config.get("lr_scheduler_type", "linear"),
seed=self.config.get("seed", 3407),
output_dir=self.config.get("output_dir", "outputs"),
report_to="none" if not os.getenv("PRAISON_WANDB") else "wandb",
remove_unused_columns=False,
# Add memory optimization settings
gradient_checkpointing=True,
max_grad_norm=1.0,
)
trainer = SFTTrainer(
model=self.model,
tokenizer=self.hf_tokenizer,
data_collator=UnslothVisionDataCollator(self.model, self.hf_tokenizer),
train_dataset=raw_dataset,
args=training_args,
max_seq_length=self.config.get("max_seq_length", 2048),
dataset_text_field="", # Required for vision training
dataset_kwargs={"skip_prepare_dataset": True}, # Required for vision training
packing=False # Explicitly set packing to False
)
print("DEBUG: Beginning vision trainer.train() ...")
trainer.train()
print("DEBUG: Vision training complete. Saving model and tokenizer locally...")
self.model.save_pretrained("lora_vision_model")
self.hf_tokenizer.save_pretrained("lora_vision_model")
print("DEBUG: Saved vision model and tokenizer to 'lora_vision_model'.")
def vision_inference(self, instruction, image):
FastVisionModel.for_inference(self.model)
messages = [
{"role": "user", "content": [
{"type": "image"},
{"type": "text", "text": instruction}
]}
]
input_text = self.hf_tokenizer.apply_chat_template(messages, add_generation_prompt=True)
inputs = self.hf_tokenizer(
image,
input_text,
add_special_tokens=False,
return_tensors="pt"
).to("cuda")
outputs = self.model.generate(
**inputs,
max_new_tokens=128,
use_cache=True,
temperature=1.5,
min_p=0.1
)
print("DEBUG: Vision inference output:", self.hf_tokenizer.batch_decode(outputs))
def save_model_merged(self):
if os.path.exists(self.config["hf_model_name"]):
shutil.rmtree(self.config["hf_model_name"])
self.model.push_to_hub_merged(
self.config["hf_model_name"],
self.hf_tokenizer,
save_method="merged_16bit",
token=os.getenv("HF_TOKEN")
)
def push_model_gguf(self):
self.model.push_to_hub_gguf(
self.config["hf_model_name"],
self.hf_tokenizer,
quantization_method=self.config.get("quantization_method", "q4_k_m"),
token=os.getenv("HF_TOKEN")
)
def save_model_gguf(self):
self.model.save_pretrained_gguf(
self.config["hf_model_name"],
self.hf_tokenizer,
quantization_method="q4_k_m"
)
def prepare_modelfile_content(self):
output_model = self.config["hf_model_name"]
template = '''{{- range $index, $_ := .Messages }}<|start_header_id|>{{ .Role }}<|end_header_id|>
{{ .Content }}
{{- if gt (len (slice $.Messages $index)) 1 }}<|eot_id|>
{{- else if ne .Role "assistant" }}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
{{ end }}
{{- end }}'''
return f"""FROM {output_model}
TEMPLATE {template}
PARAMETER temperature 0.6
PARAMETER top_p 0.9
"""
def create_and_push_ollama_model(self):
modelfile_content = self.prepare_modelfile_content()
with open("Modelfile", "w") as file:
file.write(modelfile_content)
subprocess.run(["ollama", "serve"])
subprocess.run(["ollama", "create", f"{self.config['ollama_model']}:{self.config['model_parameters']}", "-f", "Modelfile"])
subprocess.run(["ollama", "push", f"{self.config['ollama_model']}:{self.config['model_parameters']}"])
def run(self):
self.print_system_info()
self.check_gpu()
self.check_ram()
if self.config.get("train", "true").lower() == "true":
self.prepare_model()
self.train_model()
if self.config.get("huggingface_save", "true").lower() == "true":
self.save_model_merged()
if self.config.get("huggingface_save_gguf", "true").lower() == "true":
self.push_model_gguf()
if self.config.get("ollama_save", "true").lower() == "true":
self.create_and_push_ollama_model()
def main():
import argparse
parser = argparse.ArgumentParser(description="PraisonAI Vision Training Script")
parser.add_argument("command", choices=["train", "inference"], help="Command to execute")
parser.add_argument("--config", default="config.yaml", help="Path to configuration file")
args = parser.parse_args()
trainer_obj = TrainVisionModel(config_path=args.config)
if args.command == "train":
trainer_obj.run()
elif args.command == "inference":
# For inference, we load a sample image from the first dataset
instr = trainer_obj.config.get("vision_instruction", "You are an expert radiographer. Describe accurately what you see in this image.")
ds_info = trainer_obj.config["dataset"][0]
ds = load_dataset(ds_info["name"], split=ds_info.get("split_type", "train"))
sample_image = ds[0]["image"]
if trainer_obj.model is None or trainer_obj.hf_tokenizer is None:
trainer_obj.prepare_model()
trainer_obj.vision_inference(instr, sample_image)
if __name__ == "__main__":
main()
+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This script handles uploading trained vision models to Hugging Face and Ollama.
It reads configuration from config.yaml and provides options to upload in different formats.
"""
import os
import yaml
import torch
import shutil
import subprocess
from unsloth import FastVisionModel
class UploadVisionModel:
def __init__(self, config_path="config.yaml"):
self.load_config(config_path)
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.model = None
self.hf_tokenizer = None
def load_config(self, path):
"""Load configuration from yaml file."""
with open(path, "r") as file:
self.config = yaml.safe_load(file)
print("DEBUG: Loaded config:", self.config)
def prepare_model(self):
"""Load the trained model for uploading."""
print("DEBUG: Loading trained model and tokenizer...")
self.model, original_tokenizer = FastVisionModel.from_pretrained(
model_name=self.config.get("output_dir", "lora_model"),
load_in_4bit=self.config.get("load_in_4bit", True)
)
self.hf_tokenizer = original_tokenizer
print("DEBUG: Model and tokenizer loaded successfully.")
def save_model_merged(self):
"""Save merged model to Hugging Face Hub."""
print(f"DEBUG: Saving merged model to Hugging Face Hub: {self.config['hf_model_name']}")
if os.path.exists(self.config["hf_model_name"]):
shutil.rmtree(self.config["hf_model_name"])
self.model.push_to_hub_merged(
self.config["hf_model_name"],
self.hf_tokenizer,
save_method="merged_16bit",
token=os.getenv("HF_TOKEN")
)
print("DEBUG: Model saved to Hugging Face Hub successfully.")
def push_model_gguf(self):
"""Push model in GGUF format to Hugging Face Hub."""
print(f"DEBUG: Pushing GGUF model to Hugging Face Hub: {self.config['hf_model_name']}")
self.model.push_to_hub_gguf(
self.config["hf_model_name"],
self.hf_tokenizer,
quantization_method=self.config.get("quantization_method", "q4_k_m"),
token=os.getenv("HF_TOKEN")
)
print("DEBUG: GGUF model pushed to Hugging Face Hub successfully.")
def prepare_modelfile_content(self):
"""Prepare Ollama modelfile content using Llama 3.2 vision template."""
output_model = self.config["hf_model_name"]
# Using Llama 3.2 vision template format
template = """{{- range $index, $_ := .Messages }}<|start_header_id|>{{ .Role }}<|end_header_id|>
{{ .Content }}
{{- if gt (len (slice $.Messages $index)) 1 }}<|eot_id|>
{{- else if ne .Role "assistant" }}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
{{ end }}
{{- end }}"""
# Assemble the modelfile content with Llama 3.2 vision parameters
modelfile = f"FROM {output_model}\n"
modelfile += "TEMPLATE \"""" + template + "\"""\n"
modelfile += "PARAMETER temperature 0.6\n"
modelfile += "PARAMETER top_p 0.9\n"
return modelfile
def create_and_push_ollama_model(self):
"""Create and push model to Ollama."""
print(f"DEBUG: Creating Ollama model: {self.config['ollama_model']}:{self.config['model_parameters']}")
modelfile_content = self.prepare_modelfile_content()
with open("Modelfile", "w") as file:
file.write(modelfile_content)
print("DEBUG: Starting Ollama server...")
subprocess.run(["ollama", "serve"])
print("DEBUG: Creating Ollama model...")
subprocess.run([
"ollama", "create",
f"{self.config['ollama_model']}:{self.config['model_parameters']}",
"-f", "Modelfile"
])
print("DEBUG: Pushing model to Ollama...")
subprocess.run([
"ollama", "push",
f"{self.config['ollama_model']}:{self.config['model_parameters']}"
])
print("DEBUG: Model pushed to Ollama successfully.")
def upload(self, target="all"):
"""
Upload the model to specified targets.
Args:
target (str): One of 'all', 'huggingface', 'huggingface_gguf', or 'ollama'
"""
self.prepare_model()
if target in ["all", "huggingface"]:
self.save_model_merged()
if target in ["all", "huggingface_gguf"]:
self.push_model_gguf()
if target in ["all", "ollama"]:
self.create_and_push_ollama_model()
def main():
import argparse
parser = argparse.ArgumentParser(description="Upload Vision Model to Various Platforms")
parser.add_argument("--config", default="config.yaml", help="Path to configuration file")
parser.add_argument(
"--target",
choices=["all", "huggingface", "huggingface_gguf", "ollama"],
default="all",
help="Target platform to upload to"
)
args = parser.parse_args()
uploader = UploadVisionModel(config_path=args.config)
uploader.upload(target=args.target)
if __name__ == "__main__":
main()
+4 -4
View File
@@ -1,6 +1,6 @@
[project]
name = "PraisonAI"
version = "2.0.74"
version = "2.0.77"
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration."
readme = "README.md"
license = ""
@@ -12,7 +12,7 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
"praisonaiagents>=0.0.61",
"praisonaiagents>=0.0.62",
"python-dotenv>=0.19.0",
"instructor>=1.3.3",
"PyYAML>=6.0",
@@ -84,7 +84,7 @@ autogen = ["pyautogen>=0.2.19", "praisonai-tools>=0.0.7", "crewai"]
[tool.poetry]
name = "PraisonAI"
version = "2.0.74"
version = "2.0.77"
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human–agent collaboration."
authors = ["Mervin Praison"]
license = ""
@@ -102,7 +102,7 @@ python = ">=3.10,<3.13"
rich = ">=13.7"
markdown = ">=3.5"
pyparsing = ">=3.0.0"
praisonaiagents = ">=0.0.61"
praisonaiagents = ">=0.0.62"
python-dotenv = ">=0.19.0"
instructor = ">=1.3.3"
PyYAML = ">=6.0"
+18
View File
@@ -0,0 +1,18 @@
from praisonaiagents import Agent
from praisonaiagents.tools import wiki_search, wiki_summary, wiki_page, wiki_random, wiki_language
agent1 = Agent(
instructions="You are a Wikipedia Agent",
tools=[wiki_search, wiki_summary, wiki_page, wiki_random, wiki_language],
llm="openai/gpt-4o-mini",
verbose=10
)
agent1.start("history of AI in 1 line")
agent2 = Agent(
instructions="You are a Wikipedia Agent",
tools=[wiki_search, wiki_summary, wiki_page, wiki_random, wiki_language],
llm="gpt-4o-mini",
verbose=10
)
agent2.start("history of AI in 1 line")
@@ -714,6 +714,22 @@ Your Goal: {self.goal}
return None
def chat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pydantic=None, reasoning_steps=False):
# Log all parameter values when in debug mode
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
param_info = {
"prompt": str(prompt)[:100] + "..." if isinstance(prompt, str) and len(str(prompt)) > 100 else str(prompt),
"temperature": temperature,
"tools": [t.__name__ if hasattr(t, "__name__") else str(t) for t in tools] if tools else None,
"output_json": str(output_json.__class__.__name__) if output_json else None,
"output_pydantic": str(output_pydantic.__class__.__name__) if output_pydantic else None,
"reasoning_steps": reasoning_steps,
"agent_name": self.name,
"agent_role": self.role,
"agent_goal": self.goal
}
logging.debug(f"Agent.chat parameters: {json.dumps(param_info, indent=2, default=str)}")
start_time = time.time()
reasoning_steps = reasoning_steps or self.reasoning_steps
# Search for existing knowledge if any knowledge is provided
if self.knowledge:
@@ -738,7 +754,7 @@ Your Goal: {self.goal}
system_prompt=f"{self.backstory}\n\nYour Role: {self.role}\n\nYour Goal: {self.goal}" if self.use_system_prompt else None,
chat_history=self.chat_history,
temperature=temperature,
tools=tools,
tools=self.tools if tools is None else tools,
output_json=output_json,
output_pydantic=output_pydantic,
verbose=self.verbose,
@@ -749,7 +765,7 @@ Your Goal: {self.goal}
console=self.console,
agent_name=self.name,
agent_role=self.role,
agent_tools=[t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools],
agent_tools=[t.__name__ if hasattr(t, '__name__') else str(t) for t in (tools if tools is not None else self.tools)],
execute_tool_fn=self.execute_tool, # Pass tool execution function
reasoning_steps=reasoning_steps
)
@@ -757,6 +773,11 @@ Your Goal: {self.goal}
self.chat_history.append({"role": "user", "content": prompt})
self.chat_history.append({"role": "assistant", "content": response_text})
# Log completion time if in debug mode
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
total_time = time.time() - start_time
logging.debug(f"Agent.chat completed in {total_time:.2f} seconds")
return response_text
except Exception as e:
display_error(f"Error in LLM chat: {e}")
@@ -944,6 +965,13 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
display_error(f"Error in chat: {e}", console=self.console)
return None
# Log completion time if in debug mode
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
total_time = time.time() - start_time
logging.debug(f"Agent.chat completed in {total_time:.2f} seconds")
return response_text
def clean_json_output(self, output: str) -> str:
"""Clean and extract JSON from response text."""
cleaned = output.strip()
@@ -958,6 +986,22 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
async def achat(self, prompt: str, temperature=0.2, tools=None, output_json=None, output_pydantic=None, reasoning_steps=False):
"""Async version of chat method. TODO: Requires Syncing with chat method."""
# Log all parameter values when in debug mode
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
param_info = {
"prompt": str(prompt)[:100] + "..." if isinstance(prompt, str) and len(str(prompt)) > 100 else str(prompt),
"temperature": temperature,
"tools": [t.__name__ if hasattr(t, "__name__") else str(t) for t in tools] if tools else None,
"output_json": str(output_json.__class__.__name__) if output_json else None,
"output_pydantic": str(output_pydantic.__class__.__name__) if output_pydantic else None,
"reasoning_steps": reasoning_steps,
"agent_name": self.name,
"agent_role": self.role,
"agent_goal": self.goal
}
logging.debug(f"Agent.achat parameters: {json.dumps(param_info, indent=2, default=str)}")
start_time = time.time()
reasoning_steps = reasoning_steps or self.reasoning_steps
try:
# Search for existing knowledge if any knowledge is provided
@@ -996,9 +1040,15 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
self.chat_history.append({"role": "user", "content": prompt})
self.chat_history.append({"role": "assistant", "content": response_text})
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
total_time = time.time() - start_time
logging.debug(f"Agent.achat completed in {total_time:.2f} seconds")
return response_text
except Exception as e:
display_error(f"Error in LLM chat: {e}")
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
total_time = time.time() - start_time
logging.debug(f"Agent.achat failed in {total_time:.2f} seconds: {str(e)}")
return None
# For OpenAI client
@@ -1081,7 +1131,11 @@ Your Goal: {self.goal}
temperature=temperature,
tools=formatted_tools
)
return await self._achat_completion(response, tools)
result = await self._achat_completion(response, tools)
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
total_time = time.time() - start_time
logging.debug(f"Agent.achat completed in {total_time:.2f} seconds")
return result
elif output_json or output_pydantic:
response = await async_client.chat.completions.create(
model=self.llm,
@@ -1090,6 +1144,9 @@ Your Goal: {self.goal}
response_format={"type": "json_object"}
)
# Return the raw response
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
total_time = time.time() - start_time
logging.debug(f"Agent.achat completed in {total_time:.2f} seconds")
return response.choices[0].message.content
else:
response = await async_client.chat.completions.create(
@@ -1097,12 +1154,21 @@ Your Goal: {self.goal}
messages=messages,
temperature=temperature
)
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
total_time = time.time() - start_time
logging.debug(f"Agent.achat completed in {total_time:.2f} seconds")
return response.choices[0].message.content
except Exception as e:
display_error(f"Error in chat completion: {e}")
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
total_time = time.time() - start_time
logging.debug(f"Agent.achat failed in {total_time:.2f} seconds: {str(e)}")
return None
except Exception as e:
display_error(f"Error in achat: {e}")
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
total_time = time.time() - start_time
logging.debug(f"Agent.achat failed in {total_time:.2f} seconds: {str(e)}")
return None
async def _achat_completion(self, response, tools, reasoning_steps=False):
+311 -1
View File
@@ -172,6 +172,36 @@ class LLM:
# Enable error dropping for cleaner output
litellm.drop_params = True
self._setup_event_tracking(events)
# Log all initialization parameters when in debug mode
if not isinstance(verbose, bool) and verbose >= 10:
debug_info = {
"model": self.model,
"timeout": self.timeout,
"temperature": self.temperature,
"top_p": self.top_p,
"n": self.n,
"max_tokens": self.max_tokens,
"presence_penalty": self.presence_penalty,
"frequency_penalty": self.frequency_penalty,
"logit_bias": self.logit_bias,
"response_format": self.response_format,
"seed": self.seed,
"logprobs": self.logprobs,
"top_logprobs": self.top_logprobs,
"api_version": self.api_version,
"stop_phrases": self.stop_phrases,
"api_key": "***" if self.api_key else None, # Mask API key for security
"base_url": self.base_url,
"verbose": self.verbose,
"markdown": self.markdown,
"self_reflect": self.self_reflect,
"max_reflect": self.max_reflect,
"min_reflect": self.min_reflect,
"reasoning_steps": self.reasoning_steps,
"extra_settings": {k: v for k, v in self.extra_settings.items() if k not in ["api_key"]}
}
logging.debug(f"LLM instance initialized with: {json.dumps(debug_info, indent=2, default=str)}")
def get_response(
self,
@@ -195,6 +225,56 @@ class LLM:
**kwargs
) -> str:
"""Enhanced get_response with all OpenAI-like features"""
logging.info(f"Getting response from {self.model}")
# Log all self values when in debug mode
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
debug_info = {
"model": self.model,
"timeout": self.timeout,
"temperature": self.temperature,
"top_p": self.top_p,
"n": self.n,
"max_tokens": self.max_tokens,
"presence_penalty": self.presence_penalty,
"frequency_penalty": self.frequency_penalty,
"logit_bias": self.logit_bias,
"response_format": self.response_format,
"seed": self.seed,
"logprobs": self.logprobs,
"top_logprobs": self.top_logprobs,
"api_version": self.api_version,
"stop_phrases": self.stop_phrases,
"api_key": "***" if self.api_key else None, # Mask API key for security
"base_url": self.base_url,
"verbose": self.verbose,
"markdown": self.markdown,
"self_reflect": self.self_reflect,
"max_reflect": self.max_reflect,
"min_reflect": self.min_reflect,
"reasoning_steps": self.reasoning_steps
}
logging.debug(f"LLM instance configuration: {json.dumps(debug_info, indent=2, default=str)}")
# Log the parameter values passed to get_response
param_info = {
"prompt": str(prompt)[:100] + "..." if isinstance(prompt, str) and len(str(prompt)) > 100 else str(prompt),
"system_prompt": system_prompt[:100] + "..." if system_prompt and len(system_prompt) > 100 else system_prompt,
"chat_history": f"[{len(chat_history)} messages]" if chat_history else None,
"temperature": temperature,
"tools": [t.__name__ if hasattr(t, "__name__") else str(t) for t in tools] if tools else None,
"output_json": str(output_json.__class__.__name__) if output_json else None,
"output_pydantic": str(output_pydantic.__class__.__name__) if output_pydantic else None,
"verbose": verbose,
"markdown": markdown,
"self_reflect": self_reflect,
"max_reflect": max_reflect,
"min_reflect": min_reflect,
"agent_name": agent_name,
"agent_role": agent_role,
"agent_tools": agent_tools,
"kwargs": str(kwargs)
}
logging.debug(f"get_response parameters: {json.dumps(param_info, indent=2, default=str)}")
try:
import litellm
# This below **kwargs** is passed to .completion() directly. so reasoning_steps has to be popped. OR find alternate best way of handling this.
@@ -202,6 +282,23 @@ class LLM:
# Disable litellm debug messages
litellm.set_verbose = False
# Format tools if provided
formatted_tools = None
if tools:
formatted_tools = []
for tool in tools:
if callable(tool):
tool_def = self._generate_tool_definition(tool.__name__)
elif isinstance(tool, str):
tool_def = self._generate_tool_definition(tool)
else:
continue
if tool_def:
formatted_tools.append(tool_def)
if not formatted_tools:
formatted_tools = None
# Build messages list
messages = []
if system_prompt:
@@ -260,6 +357,7 @@ class LLM:
messages=messages,
temperature=temperature,
stream=False, # force non-streaming
tools=formatted_tools,
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
)
reasoning_content = resp["choices"][0]["message"].get("provider_specific_fields", {}).get("reasoning_content")
@@ -291,6 +389,7 @@ class LLM:
for chunk in litellm.completion(
model=self.model,
messages=messages,
tools=formatted_tools,
temperature=temperature,
stream=True,
**kwargs
@@ -305,6 +404,7 @@ class LLM:
for chunk in litellm.completion(
model=self.model,
messages=messages,
tools=formatted_tools,
temperature=temperature,
stream=True,
**kwargs
@@ -318,6 +418,7 @@ class LLM:
final_response = litellm.completion(
model=self.model,
messages=messages,
tools=formatted_tools,
temperature=temperature,
stream=False, # No streaming for tool call check
**kwargs
@@ -552,6 +653,11 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
except Exception as error:
display_error(f"Error in get_response: {str(error)}")
raise
# Log completion time if in debug mode
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
total_time = time.time() - start_time
logging.debug(f"get_response completed in {total_time:.2f} seconds")
async def get_response_async(
self,
@@ -577,6 +683,56 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
"""Async version of get_response with identical functionality."""
try:
import litellm
logging.info(f"Getting async response from {self.model}")
# Log all self values when in debug mode
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
debug_info = {
"model": self.model,
"timeout": self.timeout,
"temperature": self.temperature,
"top_p": self.top_p,
"n": self.n,
"max_tokens": self.max_tokens,
"presence_penalty": self.presence_penalty,
"frequency_penalty": self.frequency_penalty,
"logit_bias": self.logit_bias,
"response_format": self.response_format,
"seed": self.seed,
"logprobs": self.logprobs,
"top_logprobs": self.top_logprobs,
"api_version": self.api_version,
"stop_phrases": self.stop_phrases,
"api_key": "***" if self.api_key else None, # Mask API key for security
"base_url": self.base_url,
"verbose": self.verbose,
"markdown": self.markdown,
"self_reflect": self.self_reflect,
"max_reflect": self.max_reflect,
"min_reflect": self.min_reflect,
"reasoning_steps": self.reasoning_steps
}
logging.debug(f"LLM async instance configuration: {json.dumps(debug_info, indent=2, default=str)}")
# Log the parameter values passed to get_response_async
param_info = {
"prompt": str(prompt)[:100] + "..." if isinstance(prompt, str) and len(str(prompt)) > 100 else str(prompt),
"system_prompt": system_prompt[:100] + "..." if system_prompt and len(system_prompt) > 100 else system_prompt,
"chat_history": f"[{len(chat_history)} messages]" if chat_history else None,
"temperature": temperature,
"tools": [t.__name__ if hasattr(t, "__name__") else str(t) for t in tools] if tools else None,
"output_json": str(output_json.__class__.__name__) if output_json else None,
"output_pydantic": str(output_pydantic.__class__.__name__) if output_pydantic else None,
"verbose": verbose,
"markdown": markdown,
"self_reflect": self_reflect,
"max_reflect": max_reflect,
"min_reflect": min_reflect,
"agent_name": agent_name,
"agent_role": agent_role,
"agent_tools": agent_tools,
"kwargs": str(kwargs)
}
logging.debug(f"get_response_async parameters: {json.dumps(param_info, indent=2, default=str)}")
reasoning_steps = kwargs.pop('reasoning_steps', self.reasoning_steps)
litellm.set_verbose = False
@@ -983,6 +1139,11 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
raise LLMContextLengthExceededException(str(error))
display_error(f"Error in get_response_async: {str(error)}")
raise
# Log completion time if in debug mode
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
total_time = time.time() - start_time
logging.debug(f"get_response_async completed in {total_time:.2f} seconds")
def can_use_tools(self) -> bool:
"""Check if this model can use tool functions"""
@@ -1065,6 +1226,24 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
logger.debug("Using synchronous response function")
# Log all self values when in debug mode
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
debug_info = {
"model": self.model,
"timeout": self.timeout,
"temperature": temperature,
"top_p": self.top_p,
"n": self.n,
"max_tokens": self.max_tokens,
"presence_penalty": self.presence_penalty,
"frequency_penalty": self.frequency_penalty,
"stream": stream,
"verbose": verbose,
"markdown": markdown,
"kwargs": str(kwargs)
}
logger.debug(f"Response method configuration: {json.dumps(debug_info, indent=2, default=str)}")
# Build messages list
messages = []
if system_prompt:
@@ -1150,6 +1329,24 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
logger.debug("Using asynchronous response function")
# Log all self values when in debug mode
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
debug_info = {
"model": self.model,
"timeout": self.timeout,
"temperature": temperature,
"top_p": self.top_p,
"n": self.n,
"max_tokens": self.max_tokens,
"presence_penalty": self.presence_penalty,
"frequency_penalty": self.frequency_penalty,
"stream": stream,
"verbose": verbose,
"markdown": markdown,
"kwargs": str(kwargs)
}
logger.debug(f"Async response method configuration: {json.dumps(debug_info, indent=2, default=str)}")
# Build messages list
messages = []
if system_prompt:
@@ -1210,4 +1407,117 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
except Exception as error:
display_error(f"Error in response_async: {str(error)}")
raise
raise
def _generate_tool_definition(self, function_name: str) -> Optional[Dict]:
"""Generate a tool definition from a function name."""
logging.debug(f"Attempting to generate tool definition for: {function_name}")
# First try to get the tool definition if it exists
tool_def_name = f"{function_name}_definition"
tool_def = globals().get(tool_def_name)
logging.debug(f"Looking for {tool_def_name} in globals: {tool_def is not None}")
if not tool_def:
import __main__
tool_def = getattr(__main__, tool_def_name, None)
logging.debug(f"Looking for {tool_def_name} in __main__: {tool_def is not None}")
if tool_def:
logging.debug(f"Found tool definition: {tool_def}")
return tool_def
# Try to find the function
func = globals().get(function_name)
logging.debug(f"Looking for {function_name} in globals: {func is not None}")
if not func:
import __main__
func = getattr(__main__, function_name, None)
logging.debug(f"Looking for {function_name} in __main__: {func is not None}")
if not func or not callable(func):
logging.debug(f"Function {function_name} not found or not callable")
return None
import inspect
# Handle Langchain and CrewAI tools
if inspect.isclass(func) and hasattr(func, 'run') and not hasattr(func, '_run'):
original_func = func
func = func.run
function_name = original_func.__name__
elif inspect.isclass(func) and hasattr(func, '_run'):
original_func = func
func = func._run
function_name = original_func.__name__
sig = inspect.signature(func)
logging.debug(f"Function signature: {sig}")
# Skip self, *args, **kwargs
parameters_list = []
for name, param in sig.parameters.items():
if name == "self":
continue
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
continue
parameters_list.append((name, param))
parameters = {
"type": "object",
"properties": {},
"required": []
}
# Parse docstring for parameter descriptions
docstring = inspect.getdoc(func)
logging.debug(f"Function docstring: {docstring}")
param_descriptions = {}
if docstring:
import re
param_section = re.split(r'\s*Args:\s*', docstring)
logging.debug(f"Param section split: {param_section}")
if len(param_section) > 1:
param_lines = param_section[1].split('\n')
for line in param_lines:
line = line.strip()
if line and ':' in line:
param_name, param_desc = line.split(':', 1)
param_descriptions[param_name.strip()] = param_desc.strip()
logging.debug(f"Parameter descriptions: {param_descriptions}")
for name, param in parameters_list:
param_type = "string" # Default type
if param.annotation != inspect.Parameter.empty:
if param.annotation == int:
param_type = "integer"
elif param.annotation == float:
param_type = "number"
elif param.annotation == bool:
param_type = "boolean"
elif param.annotation == list:
param_type = "array"
elif param.annotation == dict:
param_type = "object"
parameters["properties"][name] = {
"type": param_type,
"description": param_descriptions.get(name, "Parameter description not available")
}
if param.default == inspect.Parameter.empty:
parameters["required"].append(name)
logging.debug(f"Generated parameters: {parameters}")
tool_def = {
"type": "function",
"function": {
"name": function_name,
"description": docstring.split('\n\n')[0] if docstring else "No description available",
"parameters": parameters
}
}
logging.debug(f"Generated tool definition: {tool_def}")
return tool_def
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.0.61"
version = "0.0.62"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
authors = [
{ name="Mervin Praison" }
+1 -1
View File
@@ -1868,7 +1868,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.61"
version = "0.0.62"
source = { editable = "." }
dependencies = [
{ name = "openai" },
Generated
+5 -5
View File
@@ -3060,7 +3060,7 @@ wheels = [
[[package]]
name = "praisonai"
version = "2.0.74"
version = "2.0.77"
source = { editable = "." }
dependencies = [
{ name = "instructor" },
@@ -3197,7 +3197,7 @@ requires-dist = [
{ name = "plotly", marker = "extra == 'realtime'", specifier = ">=5.24.0" },
{ name = "praisonai-tools", marker = "extra == 'autogen'", specifier = ">=0.0.7" },
{ name = "praisonai-tools", marker = "extra == 'crewai'", specifier = ">=0.0.7" },
{ name = "praisonaiagents", specifier = ">=0.0.61" },
{ name = "praisonaiagents", specifier = ">=0.0.62" },
{ name = "pyautogen", marker = "extra == 'autogen'", specifier = ">=0.2.19" },
{ name = "pydantic", marker = "extra == 'chat'", specifier = "<=2.10.1" },
{ name = "pydantic", marker = "extra == 'code'", specifier = "<=2.10.1" },
@@ -3250,16 +3250,16 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.61"
version = "0.0.62"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "openai" },
{ name = "pydantic" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/be/09af17ea9eee5135c7b091bca77e1fc7a8ff732a6148497276ff5a06bd2e/praisonaiagents-0.0.61.tar.gz", hash = "sha256:9e178b99a1d2ffe23acd8413810e5d937d910a72fbd851e04d10b0bcb129b48a", size = 102057 }
sdist = { url = "https://files.pythonhosted.org/packages/26/44/528286f187d8be35839382251407a3b5ec7ecf5fc9b1fafd7e4197b436cb/praisonaiagents-0.0.62.tar.gz", hash = "sha256:d461553d7dc64441544a6ad92e6e446531f1be8e5cbb95261407540756b2cb8a", size = 104636 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/12/1b/5656b79ae6c9f4c4e9e7b22db6800841c3bbab7fbd73c6c7f3381c936e64/praisonaiagents-0.0.61-py3-none-any.whl", hash = "sha256:835ec8a6fb1b2805a150db3d38bd4a1fe88333f078ee00190b102a3a4b2687a6", size = 121044 },
{ url = "https://files.pythonhosted.org/packages/42/34/c389ba1a9a45b0f7637b073e25717d2f9e0e6e727cc4946182fcf86e8c12/praisonaiagents-0.0.62-py3-none-any.whl", hash = "sha256:f6883ba12642bb3a7af277bfc1c0020276c3ac31bc251ed381f0388b3a61e73b", size = 123702 },
]
[[package]]