Compare commits

...
12 Commits
Author SHA1 Message Date
Mervin Praison d278f2d2b2 Merge pull request #399 from MervinPraison/develop
Bump version to 2.0.80 and update praisonaiagents dependency
2025-02-28 10:25:47 +00:00
MervinPraison df4596a699 Bump version to 2.0.80 and update praisonaiagents dependency
- Updated version to 2.0.80 across multiple files including praisonai.rb, pyproject.toml, uv.lock, Dockerfile, and docs
- Upgraded praisonaiagents dependency from 0.0.64 to 0.0.65
- Enhanced agent start methods with optional return_dict parameter
- Improved result handling in start and astart methods to return final response by default
- Synchronized version updates across project configuration files
2025-02-28 10:25:32 +00:00
Mervin Praison dd0f47ec81 Merge pull request #398 from MervinPraison/develop
Bump version to 2.0.79 and update praisonaiagents dependency
2025-02-28 07:26:52 +00:00
MervinPraison e53017a2fe Bump version to 2.0.79 and update praisonaiagents dependency
- Updated version to 2.0.79 across multiple files including praisonai.rb, pyproject.toml, uv.lock, Dockerfile, and docs
- Upgraded praisonaiagents dependency from 0.0.63 to 0.0.64
- Updated example agent scripts for Anthropic and Gemini LLMs
- Added parameter modification support in LLM initialization
- Synchronized version updates across project configuration files
2025-02-28 07:26:22 +00:00
Mervin Praison fe034ba8f4 Merge pull request #397 from MervinPraison/develop
Enhance Agent Tool Execution Logging and Debugging
2025-02-28 07:05:13 +00:00
MervinPraison 063e5780d6 Enhance Agent Tool Execution Logging and Debugging
- Added comprehensive debug logging for tool execution in LLM and Agent components
- Improved display of tool call messages with more detailed information
- Added logging for empty content and tool execution scenarios
- Enhanced error handling and logging in display functions
- Added new Anthropic LLM example script in src/praisonai-agents
- Bumped version to 2.0.78 and updated praisonaiagents dependency to 0.0.63
2025-02-28 07:04:50 +00:00
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
17 changed files with 709 additions and 43 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.76 gunicorn markdown
RUN pip install flask praisonai==2.0.80 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.76 gunicorn markdown\n&#34;)
file.write(&#34;RUN pip install flask praisonai==2.0.80 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.76.tar.gz"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/2.0.80.tar.gz"
sha256 "1828fb9227d10f991522c3f24f061943a254b667196b40b1a3e4a54a8d30ce32" # Replace with actual SHA256 checksum
license "MIT"
+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.76 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.0.80 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+32
View File
@@ -240,6 +240,32 @@ class TrainVisionModel:
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()
@@ -247,6 +273,12 @@ class TrainVisionModel:
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():
+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.76"
version = "2.0.80"
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.65",
"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.76"
version = "2.0.80"
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.65"
python-dotenv = ">=0.19.0"
instructor = ">=1.3.3"
PyYAML = ">=6.0"
+37
View File
@@ -0,0 +1,37 @@
from praisonaiagents import Agent
agent = Agent(
instructions="You are a Wikipedia Agent",
llm="anthropic/claude-3-7-sonnet-20250219"
)
result = agent.start("Why Sky is Blue?")
print(result)
from praisonaiagents import Agent
from praisonaiagents.tools import internet_search
agent = Agent(
instructions="You are a Wikipedia Agent",
tools=[internet_search],
llm="anthropic/claude-3-7-sonnet-20250219"
)
agent.start("What is Praison AI?")
from praisonaiagents import Agent, PraisonAIAgents
from praisonaiagents.tools import internet_search
research_agent = Agent(
instructions="Search Information about Claude Sonnet 3.7",
tools=[internet_search],
llm="anthropic/claude-3-7-sonnet-20250219"
)
editor_agent = Agent(
instructions="Write a Blog Post with the provided information about Claude Sonnet 3.7",
llm="anthropic/claude-3-7-sonnet-20250219"
)
agents = PraisonAIAgents(agents=[research_agent, editor_agent])
result = agents.start()
print(result)
+4 -4
View File
@@ -1,10 +1,10 @@
from praisonaiagents import Agent
from praisonaiagents.tools import internet_search
agent = Agent(
instructions="You are a helpful assistant",
llm="gemini/gemini-1.5-flash-8b",
self_reflect=True,
verbose=True
tools=[internet_search],
llm="gemini/gemini-1.5-flash-8b"
)
agent.start("Why sky is Blue?")
agent.start("What is Praison AI?")
+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):
@@ -477,8 +477,14 @@ Context:
else:
self.run_task(task_id)
async def astart(self, content=None, **kwargs):
"""Async version of start method"""
async def astart(self, content=None, return_dict=False, **kwargs):
"""Async version of start method
Args:
content: Optional content to add to all tasks' context
return_dict: If True, returns the full results dictionary instead of only the final response
**kwargs: Additional arguments
"""
if content:
# Add content to context of all tasks
for task in self.tasks.values():
@@ -488,10 +494,25 @@ Context:
task.context.append(content)
await self.arun_all_tasks()
return {
# Get results
results = {
"task_status": self.get_all_tasks_status(),
"task_results": {task_id: self.get_task_result(task_id) for task_id in self.tasks}
}
# By default, return only the final agent's response
if not return_dict:
# Get the last task (assuming sequential processing)
task_ids = list(self.tasks.keys())
if task_ids:
last_task_id = task_ids[-1]
last_result = self.get_task_result(last_task_id)
if last_result:
return last_result.raw
# Return full results dict if return_dict is True or if no final result was found
return results
def save_output_to_file(self, task, task_output):
if task.output_file:
@@ -801,8 +822,14 @@ Context:
return str(agent[0])
return None
def start(self, content=None, **kwargs):
"""Start agent execution with optional content and config"""
def start(self, content=None, return_dict=False, **kwargs):
"""Start agent execution with optional content and config
Args:
content: Optional content to add to all tasks' context
return_dict: If True, returns the full results dictionary instead of only the final response
**kwargs: Additional arguments
"""
if content:
# Add content to context of all tasks
for task in self.tasks.values():
@@ -815,10 +842,25 @@ Context:
# Run tasks as before
self.run_all_tasks()
return {
# Get results
results = {
"task_status": self.get_all_tasks_status(),
"task_results": {task_id: self.get_task_result(task_id) for task_id in self.tasks}
}
# By default, return only the final agent's response
if not return_dict:
# Get the last task (assuming sequential processing)
task_ids = list(self.tasks.keys())
if task_ids:
last_task_id = task_ids[-1]
last_result = self.get_task_result(last_task_id)
if last_result:
return last_result.raw
# Return full results dict if return_dict is True or if no final result was found
return results
def set_state(self, key: str, value: Any) -> None:
"""Set a state value"""
+335 -13
View File
@@ -171,7 +171,39 @@ class LLM:
# Enable error dropping for cleaner output
litellm.drop_params = True
# Enable parameter modification for providers like Anthropic
litellm.modify_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 +227,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 +284,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 +359,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 +391,7 @@ class LLM:
for chunk in litellm.completion(
model=self.model,
messages=messages,
tools=formatted_tools,
temperature=temperature,
stream=True,
**kwargs
@@ -305,6 +406,7 @@ class LLM:
for chunk in litellm.completion(
model=self.model,
messages=messages,
tools=formatted_tools,
temperature=temperature,
stream=True,
**kwargs
@@ -318,6 +420,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
@@ -337,20 +440,29 @@ class LLM:
function_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
if verbose:
display_tool_call(f"Agent {agent_name} is calling function '{function_name}' with arguments: {arguments}", console=console)
logging.debug(f"[TOOL_EXEC_DEBUG] About to execute tool {function_name} with args: {arguments}")
tool_result = execute_tool_fn(function_name, arguments)
logging.debug(f"[TOOL_EXEC_DEBUG] Tool execution result: {tool_result}")
if tool_result:
if verbose:
display_tool_call(f"Function '{function_name}' returned: {tool_result}", console=console)
if verbose:
display_message = f"Agent {agent_name} called function '{function_name}' with arguments: {arguments}\n"
if tool_result:
display_message += f"Function returned: {tool_result}"
logging.debug(f"[TOOL_EXEC_DEBUG] Display message with result: {display_message}")
else:
display_message += "Function returned no output"
logging.debug("[TOOL_EXEC_DEBUG] Tool returned no output")
logging.debug(f"[TOOL_EXEC_DEBUG] About to display tool call with message: {display_message}")
display_tool_call(display_message, console=console)
messages.append({
"role": "tool",
"tool_call_id": tool_call["id"],
"content": json.dumps(tool_result)
})
else:
logging.debug("[TOOL_EXEC_DEBUG] Verbose mode off, not displaying tool call")
messages.append({
"role": "tool",
"tool_call_id": tool_call["id"],
@@ -552,6 +664,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 +694,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
@@ -767,14 +934,15 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
if verbose:
display_tool_call(f"Agent {agent_name} is calling function '{function_name}' with arguments: {arguments}", console=console)
tool_result = await execute_tool_fn(function_name, arguments)
if tool_result:
if verbose:
display_tool_call(f"Function '{function_name}' returned: {tool_result}", console=console)
if verbose:
display_message = f"Agent {agent_name} called function '{function_name}' with arguments: {arguments}\n"
if tool_result:
display_message += f"Function returned: {tool_result}"
else:
display_message += "Function returned no output"
display_tool_call(display_message, console=console)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
@@ -983,6 +1151,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 +1238,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 +1341,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 +1419,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
+11 -2
View File
@@ -86,6 +86,7 @@ async def execute_callback(display_type: str, **kwargs):
def _clean_display_content(content: str, max_length: int = 20000) -> str:
"""Helper function to clean and truncate content for display."""
if not content or not str(content).strip():
logging.debug(f"Empty content received in _clean_display_content: {repr(content)}")
return ""
content = str(content)
@@ -174,11 +175,14 @@ def display_instruction(message: str, console=None, agent_name: str = None, agen
console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
def display_tool_call(message: str, console=None):
logging.debug(f"display_tool_call called with message: {repr(message)}")
if not message or not message.strip():
logging.debug("Empty message in display_tool_call, returning early")
return
if console is None:
console = Console()
message = _clean_display_content(str(message))
logging.debug(f"Cleaned message in display_tool_call: {repr(message)}")
# Execute callback if registered
if 'tool_call' in sync_display_callbacks:
@@ -202,7 +206,8 @@ def display_error(message: str, console=None):
def display_generating(content: str = "", start_time: Optional[float] = None):
if not content or not str(content).strip():
return Panel("", title="", border_style="green")
logging.debug("Empty content in display_generating, returning early")
return None
elapsed_str = ""
if start_time is not None:
@@ -293,11 +298,14 @@ async def adisplay_instruction(message: str, console=None, agent_name: str = Non
async def adisplay_tool_call(message: str, console=None):
"""Async version of display_tool_call."""
logging.debug(f"adisplay_tool_call called with message: {repr(message)}")
if not message or not message.strip():
logging.debug("Empty message in adisplay_tool_call, returning early")
return
if console is None:
console = Console()
message = _clean_display_content(str(message))
logging.debug(f"Cleaned message in adisplay_tool_call: {repr(message)}")
if 'tool_call' in async_display_callbacks:
await async_display_callbacks['tool_call'](message=message)
@@ -321,7 +329,8 @@ async def adisplay_error(message: str, console=None):
async def adisplay_generating(content: str = "", start_time: Optional[float] = None):
"""Async version of display_generating."""
if not content or not str(content).strip():
return Panel("", title="", border_style="green")
logging.debug("Empty content in adisplay_generating, returning early")
return None
elapsed_str = ""
if start_time is not None:
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.0.61"
version = "0.0.65"
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.65"
source = { editable = "." }
dependencies = [
{ name = "openai" },
Generated
+5 -5
View File
@@ -3060,7 +3060,7 @@ wheels = [
[[package]]
name = "praisonai"
version = "2.0.76"
version = "2.0.80"
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.65" },
{ 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.65"
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/9e/58/6e07c4abf57d78be642f1517fe0692a003dfc253ad91a03c70521d246b5e/praisonaiagents-0.0.65.tar.gz", hash = "sha256:16627166f1b42bd8d21759fbd67015fff0b4730ca224b4a566cffe2b5bb770e8", size = 105128 }
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/04/02/e4f2ef3c008a407d3b945256d1513bf0159989055e9462c691265be3d294/praisonaiagents-0.0.65-py3-none-any.whl", hash = "sha256:f53e9c3ddf31c575dd5f65315db70b35487adb0f9ae908596e74ed315c12afc5", size = 124306 },
]
[[package]]