mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-08 08:11:07 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eca66f9213 | ||
|
|
f83f3eae0b | ||
|
|
68dd9fb5f1 | ||
|
|
e0531cedf7 | ||
|
|
197e98d3ff | ||
|
|
3b7fd3688c | ||
|
|
dd7bfa39f4 |
No files matched your search
+1
-1
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
flask \
|
||||
"praisonai>=2.2.73" \
|
||||
"praisonai>=2.2.76" \
|
||||
"praisonai[api]" \
|
||||
gunicorn \
|
||||
markdown
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.2.73" \
|
||||
"praisonai>=2.2.76" \
|
||||
"praisonai[chat]" \
|
||||
"embedchain[github,youtube]"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.2.73" \
|
||||
"praisonai>=2.2.76" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[chat]" \
|
||||
"praisonai[realtime]" \
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.2.73" \
|
||||
"praisonai>=2.2.76" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[crewai]"
|
||||
|
||||
|
||||
+2
-2
@@ -121,7 +121,7 @@ healthcheck:
|
||||
## 📦 Package Versions
|
||||
|
||||
All Docker images use consistent, up-to-date versions:
|
||||
- PraisonAI: `>=2.2.73`
|
||||
- PraisonAI: `>=2.2.76`
|
||||
- PraisonAI Agents: `>=0.0.92`
|
||||
- Python: `3.11-slim`
|
||||
|
||||
@@ -218,7 +218,7 @@ docker-compose up -d
|
||||
### Version Pinning
|
||||
To use specific versions, update the Dockerfile:
|
||||
```dockerfile
|
||||
RUN pip install "praisonai==2.2.73" "praisonaiagents==0.0.92"
|
||||
RUN pip install "praisonai==2.2.76" "praisonaiagents==0.0.92"
|
||||
```
|
||||
|
||||
## 🌐 Production Deployment
|
||||
|
||||
@@ -2,67 +2,16 @@
|
||||
Praison AI Agents - A package for hierarchical AI agent task execution
|
||||
"""
|
||||
|
||||
# Configure logging before any other imports
|
||||
import os
|
||||
import logging
|
||||
import warnings
|
||||
import re
|
||||
from rich.logging import RichHandler
|
||||
# Apply warning patch BEFORE any imports to intercept warnings at the source
|
||||
from . import _warning_patch
|
||||
|
||||
# Set environment variables to suppress warnings at the source
|
||||
os.environ["LITELLM_TELEMETRY"] = "False"
|
||||
os.environ["LITELLM_DROP_PARAMS"] = "True"
|
||||
# Disable httpx warnings
|
||||
os.environ["HTTPX_DISABLE_WARNINGS"] = "True"
|
||||
# Import centralized logging configuration FIRST
|
||||
from . import _logging
|
||||
|
||||
# Get log level from environment variable
|
||||
LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper()
|
||||
|
||||
# Determine if warnings should be suppressed (not in DEBUG mode and not in tests)
|
||||
def _should_suppress_warnings():
|
||||
import sys
|
||||
return (LOGLEVEL != 'DEBUG' and
|
||||
not hasattr(sys, '_called_from_test') and
|
||||
'pytest' not in sys.modules and
|
||||
os.environ.get('PYTEST_CURRENT_TEST') is None)
|
||||
|
||||
# Configure root logger
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, LOGLEVEL, logging.INFO),
|
||||
format="%(asctime)s %(filename)s:%(lineno)d %(levelname)s %(message)s",
|
||||
datefmt="[%X]",
|
||||
handlers=[RichHandler(rich_tracebacks=True)]
|
||||
)
|
||||
|
||||
# Suppress specific noisy loggers - more aggressive suppression (only when not in DEBUG mode)
|
||||
if _should_suppress_warnings():
|
||||
logging.getLogger("litellm").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("litellm_logging").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("httpx").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("httpcore").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("pydantic").setLevel(logging.WARNING)
|
||||
logging.getLogger("markdown_it").setLevel(logging.WARNING)
|
||||
logging.getLogger("rich.markdown").setLevel(logging.WARNING)
|
||||
|
||||
# Note: litellm child loggers (litellm.utils, litellm.proxy, etc.) automatically inherit
|
||||
# the CRITICAL level from the parent litellm logger due to Python's hierarchical logging
|
||||
|
||||
# Comprehensive warning suppression for litellm and dependencies (issue #1033)
|
||||
# These warnings clutter output and are not actionable for users
|
||||
|
||||
# Set warning filter to suppress all warnings from problematic modules at import time
|
||||
if _should_suppress_warnings():
|
||||
# Module-specific warning suppression - applied before imports (only when not in DEBUG mode)
|
||||
for module in ['litellm', 'httpx', 'httpcore', 'pydantic']:
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning, module=module)
|
||||
warnings.filterwarnings("ignore", category=UserWarning, module=module)
|
||||
|
||||
# Specific filters for known problematic warnings
|
||||
warnings.filterwarnings("ignore", message="There is no current event loop")
|
||||
warnings.filterwarnings("ignore", message=".*Use 'content=<...>' to upload raw bytes/text content.*")
|
||||
warnings.filterwarnings("ignore", message=".*The `dict` method is deprecated; use `model_dump` instead.*")
|
||||
warnings.filterwarnings("ignore", message=".*model_dump.*deprecated.*")
|
||||
# Configure root logger after logging is initialized
|
||||
_logging.configure_root_logger()
|
||||
|
||||
# Now import everything else
|
||||
from .agent.agent import Agent
|
||||
from .agent.image_agent import ImageAgent
|
||||
from .agent.context_agent import ContextAgent, create_context_agent
|
||||
@@ -131,30 +80,6 @@ except ImportError:
|
||||
# Add Agents as an alias for PraisonAIAgents
|
||||
Agents = PraisonAIAgents
|
||||
|
||||
# Additional warning suppression after all imports (runtime suppression)
|
||||
if _should_suppress_warnings():
|
||||
# Try to import and configure litellm to suppress its warnings
|
||||
try:
|
||||
import litellm
|
||||
# Disable all litellm logging and telemetry
|
||||
litellm.telemetry = False
|
||||
litellm.drop_params = True
|
||||
# Set litellm to suppress warnings
|
||||
litellm.suppress_debug_info = True
|
||||
if hasattr(litellm, '_logging_obj'):
|
||||
litellm._logging_obj.setLevel(logging.CRITICAL)
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
# Suppress pydantic warnings that might occur at runtime (safer approach)
|
||||
try:
|
||||
warnings.filterwarnings("ignore", category=UserWarning, module="pydantic",
|
||||
message=".*model_dump.*deprecated.*")
|
||||
warnings.filterwarnings("ignore", category=UserWarning, module="pydantic",
|
||||
message=".*dict.*method.*deprecated.*")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Apply telemetry auto-instrumentation after all imports
|
||||
if _telemetry_available:
|
||||
try:
|
||||
@@ -210,4 +135,4 @@ __all__ = [
|
||||
|
||||
# Add MCP to __all__ if available
|
||||
if _mcp_available:
|
||||
__all__.append('MCP')
|
||||
__all__.append('MCP')
|
||||
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
Centralized logging configuration for PraisonAI Agents.
|
||||
This module consolidates all logging configuration in one place to avoid duplication.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
# ========================================================================
|
||||
# ENVIRONMENT CONFIGURATION
|
||||
# ========================================================================
|
||||
def _configure_environment():
|
||||
"""Set environment variables to suppress debug messages at the source."""
|
||||
env_vars = {
|
||||
# LiteLLM configuration
|
||||
"LITELLM_TELEMETRY": "False",
|
||||
"LITELLM_DROP_PARAMS": "True",
|
||||
"LITELLM_LOG": "ERROR",
|
||||
"LITELLM_DEBUG": "False",
|
||||
"LITELLM_SUPPRESS_DEBUG_INFO": "True",
|
||||
"LITELLM_VERBOSE": "False",
|
||||
"LITELLM_SET_VERBOSE": "False",
|
||||
# HTTPX configuration
|
||||
"HTTPX_DISABLE_WARNINGS": "True",
|
||||
"HTTPX_LOG_LEVEL": "ERROR",
|
||||
# Pydantic configuration
|
||||
"PYDANTIC_WARNINGS_ENABLED": "False",
|
||||
}
|
||||
|
||||
for key, value in env_vars.items():
|
||||
os.environ[key] = value
|
||||
|
||||
|
||||
# ========================================================================
|
||||
# LOGGER CONFIGURATION
|
||||
# ========================================================================
|
||||
def _get_all_noisy_loggers() -> List[str]:
|
||||
"""Get list of all loggers that should be suppressed."""
|
||||
return [
|
||||
# LiteLLM and variants
|
||||
"litellm", "LiteLLM", "LiteLLM Router", "LiteLLM Proxy",
|
||||
# HTTP libraries
|
||||
"httpx", "httpx._trace", "httpx._client",
|
||||
"httpcore", "httpcore._trace",
|
||||
# OpenAI
|
||||
"openai._base_client", "openai._client",
|
||||
# Markdown
|
||||
"markdown_it", "rich.markdown",
|
||||
# System
|
||||
"asyncio", "selector_events", "pydantic",
|
||||
"praisonaiagents.telemetry.telemetry",
|
||||
]
|
||||
|
||||
|
||||
def _configure_loggers():
|
||||
"""Configure all loggers based on LOGLEVEL environment variable."""
|
||||
loglevel = os.environ.get('LOGLEVEL', 'INFO').upper()
|
||||
|
||||
# When DEBUG is set, allow some HTTP logging for API endpoints
|
||||
if loglevel == 'DEBUG':
|
||||
allowed_debug_loggers = {"httpx", "httpx._client", "openai._client"}
|
||||
|
||||
for logger_name in _get_all_noisy_loggers():
|
||||
if logger_name not in allowed_debug_loggers:
|
||||
logger = logging.getLogger(logger_name)
|
||||
logger.setLevel(logging.CRITICAL)
|
||||
logger.handlers = []
|
||||
logger.propagate = False
|
||||
|
||||
# Ensure allowed loggers are at INFO level to show API calls
|
||||
for logger_name in allowed_debug_loggers:
|
||||
logger = logging.getLogger(logger_name)
|
||||
logger.setLevel(logging.INFO)
|
||||
else:
|
||||
# Suppress all noisy loggers when not in DEBUG mode
|
||||
for logger_name in _get_all_noisy_loggers():
|
||||
logger = logging.getLogger(logger_name)
|
||||
logger.setLevel(logging.CRITICAL)
|
||||
logger.handlers = []
|
||||
logger.propagate = False
|
||||
|
||||
|
||||
# ========================================================================
|
||||
# LITELLM CONFIGURATION
|
||||
# ========================================================================
|
||||
def _configure_litellm():
|
||||
"""Configure litellm after it's imported."""
|
||||
try:
|
||||
import litellm
|
||||
litellm.telemetry = False
|
||||
litellm.drop_params = True
|
||||
litellm.suppress_debug_info = True
|
||||
|
||||
if hasattr(litellm, '_logging_obj'):
|
||||
litellm._logging_obj.setLevel(logging.CRITICAL)
|
||||
|
||||
if hasattr(litellm, 'set_verbose'):
|
||||
litellm.set_verbose = False
|
||||
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
|
||||
# ========================================================================
|
||||
# ROOT LOGGER CONFIGURATION
|
||||
# ========================================================================
|
||||
def configure_root_logger():
|
||||
"""Configure the root logger with RichHandler."""
|
||||
from rich.logging import RichHandler
|
||||
|
||||
loglevel = os.environ.get('LOGLEVEL', 'INFO').upper()
|
||||
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, loglevel, logging.INFO),
|
||||
format="%(asctime)s %(filename)s:%(lineno)d %(levelname)s %(message)s",
|
||||
datefmt="[%X]",
|
||||
handlers=[RichHandler(rich_tracebacks=True)],
|
||||
force=True
|
||||
)
|
||||
|
||||
|
||||
# ========================================================================
|
||||
# INITIALIZATION
|
||||
# ========================================================================
|
||||
def initialize_logging():
|
||||
"""Initialize all logging configuration."""
|
||||
_configure_environment()
|
||||
_configure_loggers()
|
||||
_configure_litellm()
|
||||
|
||||
|
||||
# Auto-initialize on import
|
||||
initialize_logging()
|
||||
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Minimal warning patch to suppress specific third-party warnings.
|
||||
This module patches the warnings module to intercept specific messages.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
import functools
|
||||
import sys
|
||||
|
||||
# Apply aggressive warning filters first
|
||||
warnings.filterwarnings("ignore", message=".*Pydantic serializer warnings.*")
|
||||
warnings.filterwarnings("ignore", message=".*PydanticSerializationUnexpectedValue.*")
|
||||
warnings.filterwarnings("ignore", message=".*Expected 9 fields but got.*")
|
||||
warnings.filterwarnings("ignore", message=".*Expected `StreamingChoices` but got.*")
|
||||
warnings.filterwarnings("ignore", message=".*serialized value may not be as expected.*")
|
||||
warnings.filterwarnings("ignore", message=".*Use 'content=<...>' to upload raw bytes/text content.*")
|
||||
warnings.filterwarnings("ignore", message=".*The `dict` method is deprecated.*")
|
||||
warnings.filterwarnings("ignore", category=UserWarning, module="pydantic.*")
|
||||
|
||||
# Store the original warn function
|
||||
_original_warn = warnings.warn
|
||||
_original_warn_explicit = warnings.warn_explicit
|
||||
|
||||
# Messages to suppress (partial matches)
|
||||
SUPPRESSED_PATTERNS = [
|
||||
"Use 'content=<...>' to upload raw bytes/text content",
|
||||
"The `dict` method is deprecated; use `model_dump` instead",
|
||||
"Pydantic serializer warnings",
|
||||
"PydanticSerializationUnexpectedValue",
|
||||
"Expected 9 fields but got 5 for type `Message`",
|
||||
"Expected `StreamingChoices` but got `Choices`",
|
||||
"serialized value may not be as expected"
|
||||
]
|
||||
|
||||
@functools.wraps(_original_warn)
|
||||
def _patched_warn(message, category=None, stacklevel=1, source=None):
|
||||
"""Patched warn function that suppresses specific messages."""
|
||||
msg_str = str(message)
|
||||
|
||||
for pattern in SUPPRESSED_PATTERNS:
|
||||
if pattern in msg_str:
|
||||
return
|
||||
|
||||
if category == UserWarning and "pydantic" in msg_str.lower():
|
||||
return
|
||||
|
||||
_original_warn(message, category, stacklevel, source)
|
||||
|
||||
@functools.wraps(_original_warn_explicit)
|
||||
def _patched_warn_explicit(message, category, filename, lineno, module=None, registry=None, module_globals=None, source=None):
|
||||
"""Patched warn_explicit function that suppresses specific messages."""
|
||||
msg_str = str(message)
|
||||
|
||||
for pattern in SUPPRESSED_PATTERNS:
|
||||
if pattern in msg_str:
|
||||
return
|
||||
|
||||
if category == UserWarning and "pydantic" in msg_str.lower():
|
||||
return
|
||||
|
||||
if module and "pydantic" in str(module):
|
||||
return
|
||||
|
||||
_original_warn_explicit(message, category, filename, lineno, module, registry, module_globals, source)
|
||||
|
||||
# Apply the patches
|
||||
warnings.warn = _patched_warn
|
||||
warnings.warn_explicit = _patched_warn_explicit
|
||||
|
||||
# Also patch sys.modules warnings if it exists
|
||||
if 'warnings' in sys.modules:
|
||||
sys.modules['warnings'].warn = _patched_warn
|
||||
sys.modules['warnings'].warn_explicit = _patched_warn_explicit
|
||||
@@ -331,8 +331,15 @@ class Agent:
|
||||
|
||||
# Configure logging to suppress unwanted outputs
|
||||
logging.getLogger("litellm").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
||||
|
||||
# Allow httpx logging when LOGLEVEL=debug, otherwise suppress it
|
||||
loglevel = os.environ.get('LOGLEVEL', 'INFO').upper()
|
||||
if loglevel == 'DEBUG':
|
||||
logging.getLogger("httpx").setLevel(logging.INFO)
|
||||
logging.getLogger("httpcore").setLevel(logging.INFO)
|
||||
else:
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
||||
|
||||
# If instructions are provided, use them to set role, goal, and backstory
|
||||
if instructions:
|
||||
@@ -1229,14 +1236,14 @@ Your Goal: {self.goal}"""
|
||||
border_style="green",
|
||||
expand=False
|
||||
)
|
||||
else:
|
||||
# No content yet: show generating message
|
||||
return Panel(
|
||||
f"[bold cyan]Generating response...[/bold cyan]",
|
||||
title=f"[bold]{self.name}[/bold] - {elapsed:.1f}s",
|
||||
border_style="cyan",
|
||||
expand=False
|
||||
)
|
||||
# else:
|
||||
# # No content yet: show generating message
|
||||
# return Panel(
|
||||
# f"[bold cyan]Generating response...[/bold cyan]",
|
||||
# title=f"[bold]{self.name}[/bold] - {elapsed:.1f}s",
|
||||
# border_style="cyan",
|
||||
# expand=False
|
||||
# )
|
||||
|
||||
def chat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pydantic=None, reasoning_steps=False, stream=None, task_name=None, task_description=None, task_id=None):
|
||||
# Reset the final display flag for each new conversation
|
||||
@@ -1353,6 +1360,8 @@ Your Goal: {self.goal}"""
|
||||
# Apply guardrail validation for custom LLM response
|
||||
try:
|
||||
validated_response = self._apply_guardrail_with_retry(response_text, prompt, temperature, tools, task_name, task_description, task_id)
|
||||
# Execute callback and display after validation
|
||||
self._execute_callback_and_display(prompt, validated_response, time.time() - start_time, task_name, task_description, task_id)
|
||||
return validated_response
|
||||
except Exception as e:
|
||||
logging.error(f"Agent {self.name}: Guardrail validation failed for custom LLM: {e}")
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Ollama Handling in LLM.py
|
||||
|
||||
The `ollama_handled` flag is used in `llm.py` (not in `agent.py`) to track whether special Ollama model handling has been applied for tool results. Here's what I found:
|
||||
|
||||
## Where it's used in llm.py:
|
||||
- **Line 863**: `ollama_handled = False` - initialized at the beginning of the tool execution loop
|
||||
- **Line 864**: Special handling logic via `_handle_ollama_model()` method
|
||||
- **Line 907**: `ollama_handled = True` - set when Ollama special handling is applied
|
||||
- **Line 928 & 959**: Checks `if not ollama_handled` to avoid duplicate processing
|
||||
|
||||
## Why it's needed:
|
||||
The flag prevents duplicate processing when Ollama models require special handling. Some Ollama models return only the tool call JSON without processing the results, requiring a follow-up prompt to get the final answer. The `ollama_handled` flag ensures this special handling happens only once.
|
||||
|
||||
## Does this contradict the requirement?
|
||||
**No, it doesn't.** The issue states:
|
||||
|
||||
> "ollama/xxxx model is already handled via llm.py via litellm, so no special condition or handling required for ollama inside agent.py"
|
||||
|
||||
✅ **Current implementation is correct:**
|
||||
|
||||
- `agent.py` has NO ollama-specific conditions (only one documentation mention as an example)
|
||||
- All ollama handling is contained within `llm.py` as intended
|
||||
- The `ollama_handled` flag is an internal implementation detail within `llm.py`
|
||||
|
||||
The codebase already follows the desired pattern where ollama models are handled through the generic LLM interface via litellm without special conditions in agent.py.
|
||||
@@ -1,38 +1,9 @@
|
||||
import logging
|
||||
import warnings
|
||||
import os
|
||||
import re
|
||||
|
||||
# Disable litellm telemetry before any imports
|
||||
# Ensure litellm telemetry is disabled before imports
|
||||
os.environ["LITELLM_TELEMETRY"] = "False"
|
||||
|
||||
# Check if warnings should be suppressed (consistent with main __init__.py)
|
||||
def _should_suppress_warnings():
|
||||
import sys
|
||||
LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper()
|
||||
return (LOGLEVEL != 'DEBUG' and
|
||||
not hasattr(sys, '_called_from_test') and
|
||||
'pytest' not in sys.modules and
|
||||
os.environ.get('PYTEST_CURRENT_TEST') is None)
|
||||
|
||||
# Suppress all relevant logs at module level - more aggressive suppression consistent with main __init__.py (only when not in DEBUG mode)
|
||||
if _should_suppress_warnings():
|
||||
logging.getLogger("litellm").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("openai").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpx").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("httpcore").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("pydantic").setLevel(logging.WARNING)
|
||||
|
||||
# Note: litellm child loggers automatically inherit the CRITICAL level from the parent logger
|
||||
|
||||
# Warning filters are centrally managed in the main __init__.py file
|
||||
# Apply additional local suppression for safety during LLM imports (only when not in DEBUG mode)
|
||||
if _should_suppress_warnings():
|
||||
for module in ['litellm', 'httpx', 'httpcore', 'pydantic']:
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning, module=module)
|
||||
warnings.filterwarnings("ignore", category=UserWarning, module=module)
|
||||
|
||||
# Import after suppressing warnings
|
||||
# Import modules
|
||||
from .llm import LLM, LLMContextLengthExceededException
|
||||
from .openai_client import (
|
||||
OpenAIClient,
|
||||
@@ -57,22 +28,6 @@ from .model_router import (
|
||||
create_routing_agent
|
||||
)
|
||||
|
||||
# Ensure comprehensive litellm configuration after import (only when not in DEBUG mode)
|
||||
if _should_suppress_warnings():
|
||||
try:
|
||||
import litellm
|
||||
# Disable all litellm logging and telemetry features
|
||||
litellm.telemetry = False
|
||||
litellm.drop_params = True
|
||||
if hasattr(litellm, 'suppress_debug_info'):
|
||||
litellm.suppress_debug_info = True
|
||||
# Set all litellm loggers to CRITICAL level
|
||||
if hasattr(litellm, '_logging_obj'):
|
||||
litellm._logging_obj.setLevel(logging.CRITICAL)
|
||||
# Note: Child loggers inherit from parent, no need to iterate over all loggers
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
__all__ = [
|
||||
"LLM",
|
||||
"LLMContextLengthExceededException",
|
||||
@@ -92,4 +47,4 @@ __all__ = [
|
||||
"ModelProfile",
|
||||
"TaskComplexity",
|
||||
"create_routing_agent"
|
||||
]
|
||||
]
|
||||
@@ -20,8 +20,7 @@ from ..main import (
|
||||
from rich.console import Console
|
||||
from rich.live import Live
|
||||
|
||||
# Disable litellm telemetry before any imports
|
||||
os.environ["LITELLM_TELEMETRY"] = "False"
|
||||
# Logging is already configured in _logging.py via __init__.py
|
||||
|
||||
# TODO: Include in-build tool calling in LLM class
|
||||
# TODO: Restructure so that duplicate calls are not made (Sync with agent.py)
|
||||
@@ -95,7 +94,7 @@ class LLM:
|
||||
OLLAMA_FINAL_ANSWER_PROMPT = "Based on the tool results above, please provide the final answer to the original question."
|
||||
|
||||
# Ollama iteration threshold for summary generation
|
||||
OLLAMA_SUMMARY_ITERATION_THRESHOLD = 3
|
||||
OLLAMA_SUMMARY_ITERATION_THRESHOLD = 1
|
||||
|
||||
def _log_llm_config(self, method_name: str, **config):
|
||||
"""Centralized debug logging for LLM configuration and parameters.
|
||||
@@ -191,26 +190,35 @@ class LLM:
|
||||
litellm._async_success_callback = []
|
||||
litellm.callbacks = []
|
||||
|
||||
# Suppress all litellm debug info
|
||||
litellm.suppress_debug_info = True
|
||||
if hasattr(litellm, '_logging'):
|
||||
litellm._logging._disable_debugging()
|
||||
|
||||
verbose = extra_settings.get('verbose', True)
|
||||
|
||||
# Only suppress logs if not in debug mode
|
||||
if not isinstance(verbose, bool) and verbose >= 10:
|
||||
# Enable detailed debug logging
|
||||
logging.getLogger("asyncio").setLevel(logging.DEBUG)
|
||||
logging.getLogger("selector_events").setLevel(logging.DEBUG)
|
||||
logging.getLogger("litellm.utils").setLevel(logging.DEBUG)
|
||||
logging.getLogger("litellm.main").setLevel(logging.DEBUG)
|
||||
litellm.suppress_debug_messages = False
|
||||
litellm.set_verbose = True
|
||||
# Always suppress litellm's internal debug messages
|
||||
# These are from external libraries and not useful for debugging user code
|
||||
logging.getLogger("litellm.utils").setLevel(logging.WARNING)
|
||||
logging.getLogger("litellm.main").setLevel(logging.WARNING)
|
||||
|
||||
# Allow httpx logging when LOGLEVEL=debug, otherwise suppress it
|
||||
loglevel = os.environ.get('LOGLEVEL', 'INFO').upper()
|
||||
if loglevel == 'DEBUG':
|
||||
logging.getLogger("litellm.llms.custom_httpx.http_handler").setLevel(logging.INFO)
|
||||
else:
|
||||
# Suppress debug logging for normal operation
|
||||
logging.getLogger("asyncio").setLevel(logging.WARNING)
|
||||
logging.getLogger("selector_events").setLevel(logging.WARNING)
|
||||
logging.getLogger("litellm.utils").setLevel(logging.WARNING)
|
||||
logging.getLogger("litellm.main").setLevel(logging.WARNING)
|
||||
litellm.suppress_debug_messages = True
|
||||
logging.getLogger("litellm.llms.custom_httpx.http_handler").setLevel(logging.WARNING)
|
||||
|
||||
logging.getLogger("litellm.litellm_logging").setLevel(logging.WARNING)
|
||||
logging.getLogger("litellm.transformation").setLevel(logging.WARNING)
|
||||
litellm.suppress_debug_messages = True
|
||||
if hasattr(litellm, '_logging'):
|
||||
litellm._logging._disable_debugging()
|
||||
warnings.filterwarnings("ignore", category=RuntimeWarning)
|
||||
warnings.filterwarnings("ignore", category=RuntimeWarning)
|
||||
|
||||
# Keep asyncio at WARNING unless explicitly in high debug mode
|
||||
logging.getLogger("asyncio").setLevel(logging.WARNING)
|
||||
logging.getLogger("selector_events").setLevel(logging.WARNING)
|
||||
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
@@ -329,29 +337,47 @@ class LLM:
|
||||
# For Ollama, always generate summary when we have tool results
|
||||
# This prevents infinite loops caused by empty/minimal responses
|
||||
|
||||
# Build tool summary more naturally to match OpenAI-style responses
|
||||
if len(tool_results) == 1:
|
||||
# Filter out error results first
|
||||
valid_results = []
|
||||
for result in tool_results:
|
||||
# Skip error responses
|
||||
if isinstance(result, dict) and 'error' in result:
|
||||
continue
|
||||
valid_results.append(result)
|
||||
|
||||
# If no valid results, return None to continue
|
||||
if not valid_results:
|
||||
return None
|
||||
|
||||
# Generate a natural summary based on the tool results
|
||||
if len(valid_results) == 1:
|
||||
# Single tool result - create natural response
|
||||
result = tool_results[0]
|
||||
if isinstance(result, dict) and 'result' in result:
|
||||
return str(result['result'])
|
||||
else:
|
||||
return str(result)
|
||||
result = valid_results[0]
|
||||
# For simple numeric results, create a more natural response
|
||||
if isinstance(result, (int, float)):
|
||||
return f"The result is {result}."
|
||||
return str(result)
|
||||
else:
|
||||
# Multiple tool results - create coherent summary
|
||||
summary_lines = []
|
||||
for i, result in enumerate(tool_results):
|
||||
if isinstance(result, dict) and 'result' in result:
|
||||
function_name = result.get('function_name', 'Tool')
|
||||
summary_lines.append(f"{function_name}: {result['result']}")
|
||||
else:
|
||||
summary_lines.append(f"Tool {i+1}: {result}")
|
||||
summary_parts = []
|
||||
|
||||
# Create more natural summary text
|
||||
if len(summary_lines) == 2:
|
||||
return f"{summary_lines[0]}. {summary_lines[1]}."
|
||||
else:
|
||||
return "Based on the tool execution: " + ". ".join(summary_lines) + "."
|
||||
for result in valid_results:
|
||||
result_str = str(result)
|
||||
# Clean up the result string
|
||||
result_str = result_str.strip()
|
||||
|
||||
# If result is just a number, keep it simple
|
||||
if isinstance(result, (int, float)):
|
||||
# Don't add extra context, let the LLM's response provide that
|
||||
pass
|
||||
# Ensure string results end with proper punctuation
|
||||
elif result_str and not result_str[-1] in '.!?':
|
||||
result_str += '.'
|
||||
|
||||
summary_parts.append(result_str)
|
||||
|
||||
# Join the parts naturally
|
||||
return " ".join(summary_parts)
|
||||
|
||||
def _format_ollama_tool_result_message(self, function_name: str, tool_result: Any) -> Dict[str, str]:
|
||||
"""
|
||||
@@ -474,7 +500,22 @@ class LLM:
|
||||
|
||||
for param_name, param_value in arguments.items():
|
||||
if param_name in valid_params:
|
||||
filtered_args[param_name] = param_value
|
||||
# Cast parameter value to the expected type
|
||||
param = sig.parameters[param_name]
|
||||
if param.annotation != inspect.Parameter.empty:
|
||||
try:
|
||||
if param.annotation == int and isinstance(param_value, str):
|
||||
filtered_args[param_name] = int(param_value)
|
||||
elif param.annotation == float and isinstance(param_value, str):
|
||||
filtered_args[param_name] = float(param_value)
|
||||
elif param.annotation == bool and isinstance(param_value, str):
|
||||
filtered_args[param_name] = param_value.lower() in ('true', '1', 'yes')
|
||||
else:
|
||||
filtered_args[param_name] = param_value
|
||||
except (ValueError, TypeError):
|
||||
filtered_args[param_name] = param_value
|
||||
else:
|
||||
filtered_args[param_name] = param_value
|
||||
else:
|
||||
invalid_params.append(param_name)
|
||||
|
||||
@@ -514,19 +555,10 @@ class LLM:
|
||||
if not (self._is_ollama_provider() and iteration_count >= self.OLLAMA_SUMMARY_ITERATION_THRESHOLD):
|
||||
return False, None, iteration_count
|
||||
|
||||
# For Ollama: if we have meaningful tool results but empty responses,
|
||||
# give LLM one final chance with explicit prompt for final answer
|
||||
if accumulated_tool_results and iteration_count == self.OLLAMA_SUMMARY_ITERATION_THRESHOLD:
|
||||
# Add explicit prompt asking for final answer
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": self.OLLAMA_FINAL_ANSWER_PROMPT
|
||||
})
|
||||
# Continue to next iteration to get the final response
|
||||
iteration_count += 1
|
||||
return False, None, iteration_count
|
||||
else:
|
||||
# If still no response after final answer prompt, generate summary
|
||||
# For Ollama: if we have meaningful tool results, generate summary immediately
|
||||
# Don't wait for more iterations as Ollama tends to repeat tool calls
|
||||
if accumulated_tool_results and iteration_count >= self.OLLAMA_SUMMARY_ITERATION_THRESHOLD:
|
||||
# Generate summary from tool results
|
||||
tool_summary = self._generate_ollama_tool_summary(accumulated_tool_results, response_text)
|
||||
if tool_summary:
|
||||
return True, tool_summary, iteration_count
|
||||
@@ -874,7 +906,7 @@ class LLM:
|
||||
if display_text and str(display_text).strip():
|
||||
display_instruction(
|
||||
f"Agent {agent_name} is processing prompt: {display_text}",
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools
|
||||
@@ -920,7 +952,7 @@ class LLM:
|
||||
f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{response_text}",
|
||||
markdown=markdown,
|
||||
generation_time=generation_time_val,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -936,7 +968,7 @@ class LLM:
|
||||
response_text,
|
||||
markdown=markdown,
|
||||
generation_time=generation_time_val,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -971,6 +1003,11 @@ class LLM:
|
||||
# Provider doesn't support streaming with tools, use non-streaming
|
||||
use_streaming = False
|
||||
|
||||
# Gemini has issues with streaming + tools, disable streaming for Gemini when tools are present
|
||||
if use_streaming and formatted_tools and self._is_gemini_model():
|
||||
logging.debug("Disabling streaming for Gemini model with tools due to JSON parsing issues")
|
||||
use_streaming = False
|
||||
|
||||
# Track whether fallback was successful to avoid duplicate API calls
|
||||
fallback_completed = False
|
||||
|
||||
@@ -984,7 +1021,7 @@ class LLM:
|
||||
try:
|
||||
if verbose:
|
||||
# Verbose streaming: show display_generating during streaming
|
||||
with Live(display_generating("", current_time), console=console, refresh_per_second=4) as live:
|
||||
with Live(display_generating("", current_time), console=self.console, refresh_per_second=4) as live:
|
||||
for chunk in litellm.completion(
|
||||
**self._build_completion_params(
|
||||
messages=messages,
|
||||
@@ -1031,8 +1068,9 @@ class LLM:
|
||||
try:
|
||||
if verbose:
|
||||
# When verbose=True, always use streaming for better UX
|
||||
with Live(display_generating("", current_time), console=console, refresh_per_second=4, transient=True) as live:
|
||||
with Live(display_generating("", current_time), console=self.console, refresh_per_second=4, transient=True) as live:
|
||||
response_text = ""
|
||||
tool_calls = []
|
||||
# Use streaming when verbose for progressive display
|
||||
for chunk in litellm.completion(
|
||||
**self._build_completion_params(
|
||||
@@ -1047,19 +1085,20 @@ class LLM:
|
||||
):
|
||||
if chunk and chunk.choices and chunk.choices[0].delta:
|
||||
delta = chunk.choices[0].delta
|
||||
if hasattr(delta, "content") and delta.content:
|
||||
response_text += delta.content
|
||||
live.update(display_generating(response_text, current_time))
|
||||
response_text, tool_calls = self._process_stream_delta(
|
||||
delta, response_text, tool_calls, formatted_tools
|
||||
)
|
||||
live.update(display_generating(response_text, current_time))
|
||||
|
||||
# Clear the live display after completion
|
||||
console.print()
|
||||
self.console.print()
|
||||
|
||||
# Create final response structure
|
||||
final_response = {
|
||||
"choices": [{
|
||||
"message": {
|
||||
"content": response_text,
|
||||
"tool_calls": None
|
||||
"tool_calls": tool_calls if tool_calls else None
|
||||
}
|
||||
}]
|
||||
}
|
||||
@@ -1076,7 +1115,9 @@ class LLM:
|
||||
**kwargs
|
||||
)
|
||||
)
|
||||
response_text = final_response["choices"][0]["message"]["content"]
|
||||
# Handle None content from Gemini
|
||||
response_content = final_response["choices"][0]["message"].get("content")
|
||||
response_text = response_content if response_content is not None else ""
|
||||
|
||||
# Execute callbacks and display based on verbose setting
|
||||
if verbose and not interaction_displayed:
|
||||
@@ -1086,7 +1127,7 @@ class LLM:
|
||||
response_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - current_time,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -1173,8 +1214,9 @@ class LLM:
|
||||
# Non-streaming approach (when tools require it, streaming is disabled, or streaming fallback)
|
||||
if verbose:
|
||||
# When verbose=True, always use streaming for better UX
|
||||
with Live(display_generating("", current_time), console=console, refresh_per_second=4, transient=True) as live:
|
||||
with Live(display_generating("", current_time), console=self.console, refresh_per_second=4, transient=True) as live:
|
||||
response_text = ""
|
||||
tool_calls = []
|
||||
# Use streaming when verbose for progressive display
|
||||
for chunk in litellm.completion(
|
||||
**self._build_completion_params(
|
||||
@@ -1189,19 +1231,20 @@ class LLM:
|
||||
):
|
||||
if chunk and chunk.choices and chunk.choices[0].delta:
|
||||
delta = chunk.choices[0].delta
|
||||
if hasattr(delta, "content") and delta.content:
|
||||
response_text += delta.content
|
||||
live.update(display_generating(response_text, current_time))
|
||||
response_text, tool_calls = self._process_stream_delta(
|
||||
delta, response_text, tool_calls, formatted_tools
|
||||
)
|
||||
live.update(display_generating(response_text, current_time))
|
||||
|
||||
# Clear the live display after completion
|
||||
console.print()
|
||||
self.console.print()
|
||||
|
||||
# Create final response structure
|
||||
final_response = {
|
||||
"choices": [{
|
||||
"message": {
|
||||
"content": response_text,
|
||||
"tool_calls": None
|
||||
"tool_calls": tool_calls if tool_calls else None
|
||||
}
|
||||
}]
|
||||
}
|
||||
@@ -1218,7 +1261,9 @@ class LLM:
|
||||
**kwargs
|
||||
)
|
||||
)
|
||||
response_text = final_response["choices"][0]["message"]["content"]
|
||||
# Handle None content from Gemini
|
||||
response_content = final_response["choices"][0]["message"].get("content")
|
||||
response_text = response_content if response_content is not None else ""
|
||||
|
||||
# Execute callbacks and display based on verbose setting
|
||||
if verbose and not interaction_displayed:
|
||||
@@ -1228,7 +1273,7 @@ class LLM:
|
||||
response_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - current_time,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -1257,6 +1302,41 @@ class LLM:
|
||||
|
||||
tool_calls = final_response["choices"][0]["message"].get("tool_calls")
|
||||
|
||||
|
||||
# For Ollama, parse tool calls from response text if not in tool_calls field
|
||||
if self._is_ollama_provider() and not tool_calls and response_text and formatted_tools:
|
||||
# Try to parse JSON tool call from response text
|
||||
try:
|
||||
response_json = json.loads(response_text.strip())
|
||||
if isinstance(response_json, dict) and "name" in response_json:
|
||||
# Convert Ollama format to standard tool_calls format
|
||||
tool_calls = [{
|
||||
"id": f"tool_{iteration_count}",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": response_json["name"],
|
||||
"arguments": json.dumps(response_json.get("arguments", {}))
|
||||
}
|
||||
}]
|
||||
logging.debug(f"Parsed Ollama tool call from response: {tool_calls}")
|
||||
elif isinstance(response_json, list):
|
||||
# Handle multiple tool calls
|
||||
tool_calls = []
|
||||
for idx, tool_json in enumerate(response_json):
|
||||
if isinstance(tool_json, dict) and "name" in tool_json:
|
||||
tool_calls.append({
|
||||
"id": f"tool_{iteration_count}_{idx}",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": tool_json["name"],
|
||||
"arguments": json.dumps(tool_json.get("arguments", {}))
|
||||
}
|
||||
})
|
||||
if tool_calls:
|
||||
logging.debug(f"Parsed multiple Ollama tool calls from response: {tool_calls}")
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
logging.debug(f"Could not parse Ollama tool call from response: {e}")
|
||||
|
||||
# For Ollama, if response is empty but we have tools, prompt for tool usage
|
||||
if self._is_ollama_provider() and (not response_text or response_text.strip() == "") and formatted_tools and iteration_count == 0:
|
||||
messages.append({
|
||||
@@ -1287,6 +1367,8 @@ class LLM:
|
||||
|
||||
should_continue = False
|
||||
tool_results = [] # Store current iteration tool results
|
||||
tool_result_mapping = {} # Store function results by name for Ollama chaining
|
||||
|
||||
for tool_call in tool_calls:
|
||||
# Handle both object and dict access patterns
|
||||
is_ollama = self._is_ollama_provider()
|
||||
@@ -1294,6 +1376,15 @@ class LLM:
|
||||
|
||||
# Validate and filter arguments for Ollama provider
|
||||
if is_ollama and tools:
|
||||
# First check if any argument references a previous tool result
|
||||
if is_ollama and tool_result_mapping:
|
||||
# Replace function names with their results in arguments
|
||||
for arg_name, arg_value in list(arguments.items()):
|
||||
if isinstance(arg_value, str) and arg_value in tool_result_mapping:
|
||||
# Replace function name with its result
|
||||
arguments[arg_name] = tool_result_mapping[arg_value]
|
||||
logging.debug(f"[OLLAMA_FIX] Replaced {arg_value} with {tool_result_mapping[arg_value]} in {function_name} arguments")
|
||||
|
||||
arguments = self._validate_and_filter_ollama_arguments(function_name, arguments, tools)
|
||||
|
||||
logging.debug(f"[TOOL_EXEC_DEBUG] About to execute tool {function_name} with args: {arguments}")
|
||||
@@ -1301,6 +1392,19 @@ class LLM:
|
||||
logging.debug(f"[TOOL_EXEC_DEBUG] Tool execution result: {tool_result}")
|
||||
tool_results.append(tool_result) # Store the result
|
||||
accumulated_tool_results.append(tool_result) # Accumulate across iterations
|
||||
|
||||
# For Ollama, store the result for potential chaining
|
||||
if is_ollama:
|
||||
# Extract numeric value from result if it contains one
|
||||
if isinstance(tool_result, (int, float)):
|
||||
tool_result_mapping[function_name] = tool_result
|
||||
elif isinstance(tool_result, str):
|
||||
import re
|
||||
match = re.search(r'\b(\d+)\b', tool_result)
|
||||
if match:
|
||||
tool_result_mapping[function_name] = int(match.group(1))
|
||||
else:
|
||||
tool_result_mapping[function_name] = tool_result
|
||||
|
||||
if verbose:
|
||||
display_message = f"Agent {agent_name} called function '{function_name}' with arguments: {arguments}\n"
|
||||
@@ -1312,7 +1416,7 @@ class LLM:
|
||||
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)
|
||||
display_tool_call(display_message, console=self.console)
|
||||
|
||||
# Check if this is Ollama provider
|
||||
if self._is_ollama_provider():
|
||||
@@ -1336,10 +1440,11 @@ class LLM:
|
||||
iteration_count += 1
|
||||
continue
|
||||
|
||||
# Check if the LLM provided a final answer alongside the tool calls
|
||||
# If response_text contains substantive content, treat it as the final answer
|
||||
if response_text and len(response_text.strip()) > 10:
|
||||
# LLM provided a final answer after tool execution, don't continue
|
||||
# For most providers (including Gemini), we need to continue the loop
|
||||
# to get a final response that incorporates the tool results
|
||||
# Only break if the response explicitly indicates completion
|
||||
if response_text and len(response_text.strip()) > 50 and "final answer" in response_text.lower():
|
||||
# LLM provided an explicit final answer, don't continue
|
||||
final_response_text = response_text.strip()
|
||||
break
|
||||
|
||||
@@ -1364,8 +1469,10 @@ class LLM:
|
||||
final_response_text = response_text.strip() if response_text else "Task completed."
|
||||
break
|
||||
|
||||
# Otherwise, continue the loop to check if more tools are needed
|
||||
# Otherwise, continue the loop to get final response with tool results
|
||||
iteration_count += 1
|
||||
# Clear response_text so we don't accidentally use the initial response
|
||||
response_text = ""
|
||||
continue
|
||||
else:
|
||||
# No tool calls, we're done with this iteration
|
||||
@@ -1381,8 +1488,12 @@ class LLM:
|
||||
break
|
||||
|
||||
# If we've executed tools in previous iterations, this response contains the final answer
|
||||
if iteration_count > 0 and not final_response_text:
|
||||
if iteration_count > 0:
|
||||
final_response_text = response_text.strip() if response_text else ""
|
||||
break
|
||||
|
||||
# First iteration with no tool calls - just return the response
|
||||
final_response_text = response_text.strip() if response_text else ""
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
@@ -1391,6 +1502,40 @@ class LLM:
|
||||
|
||||
# End of while loop - return final response
|
||||
if final_response_text:
|
||||
# Display the final response if verbose mode is enabled
|
||||
if verbose and not interaction_displayed:
|
||||
generation_time_val = time.time() - start_time
|
||||
display_interaction(
|
||||
original_prompt,
|
||||
final_response_text,
|
||||
markdown=markdown,
|
||||
generation_time=generation_time_val,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
task_name=task_name,
|
||||
task_description=task_description,
|
||||
task_id=task_id
|
||||
)
|
||||
interaction_displayed = True
|
||||
callback_executed = True
|
||||
elif not callback_executed:
|
||||
# Execute callback if not already done
|
||||
execute_sync_callback(
|
||||
'interaction',
|
||||
message=original_prompt,
|
||||
response=final_response_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
task_name=task_name,
|
||||
task_description=task_description,
|
||||
task_id=task_id
|
||||
)
|
||||
callback_executed = True
|
||||
return final_response_text
|
||||
|
||||
# No tool calls were made in this iteration, return the response
|
||||
@@ -1405,7 +1550,7 @@ class LLM:
|
||||
f"Reasoning:\n{stored_reasoning_content}\n\nAnswer:\n{response_text}",
|
||||
markdown=markdown,
|
||||
generation_time=generation_time_val,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -1419,7 +1564,7 @@ class LLM:
|
||||
response_text,
|
||||
markdown=markdown,
|
||||
generation_time=generation_time_val,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -1459,7 +1604,7 @@ class LLM:
|
||||
|
||||
if verbose and not interaction_displayed:
|
||||
display_interaction(original_prompt, response_text, markdown=markdown,
|
||||
generation_time=time.time() - start_time, console=console,
|
||||
generation_time=time.time() - start_time, console=self.console,
|
||||
agent_name=agent_name, agent_role=agent_role, agent_tools=agent_tools,
|
||||
task_name=task_name, task_description=task_description, task_id=task_id)
|
||||
interaction_displayed = True
|
||||
@@ -1485,7 +1630,7 @@ class LLM:
|
||||
if not self_reflect:
|
||||
if verbose and not interaction_displayed:
|
||||
display_interaction(original_prompt, response_text, markdown=markdown,
|
||||
generation_time=time.time() - start_time, console=console,
|
||||
generation_time=time.time() - start_time, console=self.console,
|
||||
agent_name=agent_name, agent_role=agent_role, agent_tools=agent_tools,
|
||||
task_name=task_name, task_description=task_description, task_id=task_id)
|
||||
interaction_displayed = True
|
||||
@@ -1551,7 +1696,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
f"{reasoning_content}\n\nReflection result:\n{reflection_text}",
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -1565,7 +1710,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
reflection_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -1576,7 +1721,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
else:
|
||||
# Existing streaming approach
|
||||
if verbose:
|
||||
with Live(display_generating("", start_time), console=console, refresh_per_second=4) as live:
|
||||
with Live(display_generating("", start_time), console=self.console, refresh_per_second=4) as live:
|
||||
reflection_text = ""
|
||||
for chunk in litellm.completion(
|
||||
**self._build_completion_params(
|
||||
@@ -1616,13 +1761,13 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
if verbose:
|
||||
display_self_reflection(
|
||||
f"Agent {agent_name} self reflection: reflection='{reflection_data['reflection']}' satisfactory='{reflection_data['satisfactory']}'",
|
||||
console=console
|
||||
console=self.console
|
||||
)
|
||||
|
||||
if satisfactory and reflection_count >= min_reflect - 1:
|
||||
if verbose and not interaction_displayed:
|
||||
display_interaction(prompt, response_text, markdown=markdown,
|
||||
generation_time=time.time() - start_time, console=console,
|
||||
generation_time=time.time() - start_time, console=self.console,
|
||||
agent_name=agent_name, agent_role=agent_role, agent_tools=agent_tools,
|
||||
task_name=task_name, task_description=task_description, task_id=task_id)
|
||||
interaction_displayed = True
|
||||
@@ -1631,7 +1776,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
if reflection_count >= max_reflect - 1:
|
||||
if verbose and not interaction_displayed:
|
||||
display_interaction(prompt, response_text, markdown=markdown,
|
||||
generation_time=time.time() - start_time, console=console,
|
||||
generation_time=time.time() - start_time, console=self.console,
|
||||
agent_name=agent_name, agent_role=agent_role, agent_tools=agent_tools,
|
||||
task_name=task_name, task_description=task_description, task_id=task_id)
|
||||
interaction_displayed = True
|
||||
@@ -1647,7 +1792,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
|
||||
# Get new response after reflection
|
||||
if verbose:
|
||||
with Live(display_generating("", time.time()), console=console, refresh_per_second=4) as live:
|
||||
with Live(display_generating("", time.time()), console=self.console, refresh_per_second=4) as live:
|
||||
response_text = ""
|
||||
for chunk in litellm.completion(
|
||||
**self._build_completion_params(
|
||||
@@ -1688,7 +1833,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
if reflection_count >= max_reflect:
|
||||
if verbose and not interaction_displayed:
|
||||
display_interaction(prompt, response_text, markdown=markdown,
|
||||
generation_time=time.time() - start_time, console=console,
|
||||
generation_time=time.time() - start_time, console=self.console,
|
||||
agent_name=agent_name, agent_role=agent_role, agent_tools=agent_tools,
|
||||
task_name=task_name, task_description=task_description, task_id=task_id)
|
||||
interaction_displayed = True
|
||||
@@ -1701,7 +1846,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
# If we've exhausted reflection attempts
|
||||
if verbose and not interaction_displayed:
|
||||
display_interaction(prompt, response_text, markdown=markdown,
|
||||
generation_time=time.time() - start_time, console=console)
|
||||
generation_time=time.time() - start_time, console=self.console)
|
||||
interaction_displayed = True
|
||||
return response_text
|
||||
|
||||
@@ -2119,7 +2264,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{response_text}",
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -2134,7 +2279,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
response_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -2214,9 +2359,16 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
|
||||
)
|
||||
)
|
||||
response_text = tool_response.choices[0].message.get("content", "")
|
||||
# Handle None content from Gemini
|
||||
response_content = tool_response.choices[0].message.get("content")
|
||||
response_text = response_content if response_content is not None else ""
|
||||
tool_calls = tool_response.choices[0].message.get("tool_calls", [])
|
||||
|
||||
# Debug logging for Gemini responses
|
||||
if self._is_gemini_model():
|
||||
logging.debug(f"Gemini response content: {response_content} -> {response_text}")
|
||||
logging.debug(f"Gemini tool calls: {tool_calls}")
|
||||
|
||||
if verbose and not interaction_displayed:
|
||||
# Display the complete response at once
|
||||
display_interaction(
|
||||
@@ -2224,7 +2376,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
response_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -2282,7 +2434,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
display_message += f"Function returned: {tool_result}"
|
||||
else:
|
||||
display_message += "Function returned no output"
|
||||
display_tool_call(display_message, console=console)
|
||||
display_tool_call(display_message, console=self.console)
|
||||
# Check if it's Ollama provider
|
||||
if self._is_ollama_provider():
|
||||
# For Ollama, use user role and format as natural language
|
||||
@@ -2329,7 +2481,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{response_text}",
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -2344,7 +2496,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
response_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -2455,7 +2607,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
self.chat_history.append({"role": "assistant", "content": response_text})
|
||||
if verbose and not interaction_displayed:
|
||||
display_interaction(original_prompt, response_text, markdown=markdown,
|
||||
generation_time=time.time() - start_time, console=console,
|
||||
generation_time=time.time() - start_time, console=self.console,
|
||||
agent_name=agent_name, agent_role=agent_role, agent_tools=agent_tools,
|
||||
task_name=task_name, task_description=task_description, task_id=task_id)
|
||||
interaction_displayed = True
|
||||
@@ -2473,7 +2625,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
f"Reasoning:\n{stored_reasoning_content}\n\nAnswer:\n{display_text}",
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -2483,7 +2635,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
)
|
||||
else:
|
||||
display_interaction(original_prompt, display_text, markdown=markdown,
|
||||
generation_time=time.time() - start_time, console=console,
|
||||
generation_time=time.time() - start_time, console=self.console,
|
||||
agent_name=agent_name, agent_role=agent_role, agent_tools=agent_tools,
|
||||
task_name=task_name, task_description=task_description, task_id=task_id)
|
||||
interaction_displayed = True
|
||||
@@ -2530,7 +2682,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
f"{reasoning_content}\n\nReflection result:\n{reflection_text}",
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -2544,7 +2696,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
reflection_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console,
|
||||
console=self.console,
|
||||
agent_name=agent_name,
|
||||
agent_role=agent_role,
|
||||
agent_tools=agent_tools,
|
||||
@@ -2555,7 +2707,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
else:
|
||||
# Existing streaming approach
|
||||
if verbose:
|
||||
with Live(display_generating("", start_time), console=console, refresh_per_second=4) as live:
|
||||
with Live(display_generating("", start_time), console=self.console, refresh_per_second=4) as live:
|
||||
reflection_text = ""
|
||||
async for chunk in await litellm.acompletion(
|
||||
**self._build_completion_params(
|
||||
@@ -2596,13 +2748,13 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
if verbose:
|
||||
display_self_reflection(
|
||||
f"Agent {agent_name} self reflection: reflection='{reflection_data['reflection']}' satisfactory='{reflection_data['satisfactory']}'",
|
||||
console=console
|
||||
console=self.console
|
||||
)
|
||||
|
||||
if satisfactory and reflection_count >= min_reflect - 1:
|
||||
if verbose and not interaction_displayed:
|
||||
display_interaction(prompt, response_text, markdown=markdown,
|
||||
generation_time=time.time() - start_time, console=console,
|
||||
generation_time=time.time() - start_time, console=self.console,
|
||||
agent_name=agent_name, agent_role=agent_role, agent_tools=agent_tools,
|
||||
task_name=task_name, task_description=task_description, task_id=task_id)
|
||||
interaction_displayed = True
|
||||
@@ -2611,7 +2763,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
if reflection_count >= max_reflect - 1:
|
||||
if verbose and not interaction_displayed:
|
||||
display_interaction(prompt, response_text, markdown=markdown,
|
||||
generation_time=time.time() - start_time, console=console,
|
||||
generation_time=time.time() - start_time, console=self.console,
|
||||
agent_name=agent_name, agent_role=agent_role, agent_tools=agent_tools,
|
||||
task_name=task_name, task_description=task_description, task_id=task_id)
|
||||
interaction_displayed = True
|
||||
|
||||
@@ -12,11 +12,7 @@ from rich.markdown import Markdown
|
||||
from rich.live import Live
|
||||
import asyncio
|
||||
|
||||
# Logging is already configured in __init__.py, just clean up handlers for litellm
|
||||
logging.getLogger("litellm").handlers = []
|
||||
logging.getLogger("litellm.utils").handlers = []
|
||||
logging.getLogger("litellm").propagate = False
|
||||
logging.getLogger("litellm.utils").propagate = False
|
||||
# Logging is already configured in _logging.py via __init__.py
|
||||
|
||||
# Global list to store error logs
|
||||
error_logs = []
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "praisonaiagents"
|
||||
version = "0.0.146"
|
||||
version = "0.0.149"
|
||||
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
|
||||
requires-python = ">=3.10"
|
||||
authors = [
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test script to compare display output between OpenAI and Gemini models."""
|
||||
|
||||
import os
|
||||
from praisonaiagents import Agent
|
||||
|
||||
def test_model_display(model_name, api_key_env=None):
|
||||
"""Test display output for a specific model."""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Testing {model_name}")
|
||||
print('='*60)
|
||||
|
||||
# Set API key if provided
|
||||
if api_key_env:
|
||||
if not os.getenv(api_key_env):
|
||||
print(f"Skipping {model_name} - {api_key_env} not set")
|
||||
return
|
||||
|
||||
try:
|
||||
# Create agent with the specified model
|
||||
agent = Agent(
|
||||
name=f"{model_name.split('/')[-1]}_agent",
|
||||
role="Math Assistant",
|
||||
goal="Help with simple calculations",
|
||||
backstory="You are a helpful math assistant.",
|
||||
llm=model_name,
|
||||
verbose=True, # This should trigger display_interaction
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# Simple test prompt
|
||||
response = agent.chat("What is 5 + 3? Please provide a brief answer.")
|
||||
|
||||
print(f"\nRaw response: {response}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error with {model_name}: {e}")
|
||||
|
||||
# Test with different models
|
||||
if __name__ == "__main__":
|
||||
print("Testing display output for different LLM models...")
|
||||
|
||||
# Test OpenAI
|
||||
test_model_display("gpt-4o-mini", "OPENAI_API_KEY")
|
||||
|
||||
# Test Gemini
|
||||
test_model_display("gemini/gemini-1.5-flash", "GEMINI_API_KEY")
|
||||
|
||||
# Test Anthropic (if available)
|
||||
test_model_display("claude-3-haiku-20240307", "ANTHROPIC_API_KEY")
|
||||
|
||||
print("\n\nTest completed!")
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test script to verify the refactored Gemini tools logic."""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Setup logging to see debug messages
|
||||
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s')
|
||||
|
||||
# Add the src directory to the Python path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src', 'praisonai-agents'))
|
||||
|
||||
try:
|
||||
from praisonaiagents.llm.llm import LLM
|
||||
from praisonaiagents.llm.openai_client import OpenAIClient
|
||||
|
||||
print("Testing LLM tool formatting...")
|
||||
llm = LLM(model="gpt-4o-mini")
|
||||
tools = [
|
||||
{'googleSearch': {}}, # Valid Gemini tool
|
||||
{'urlContext': {}}, # Valid Gemini tool
|
||||
{'codeExecution': {}}, # Valid Gemini tool
|
||||
{'unknown': {}} # Invalid tool - should be skipped
|
||||
]
|
||||
|
||||
formatted = llm._format_tools_for_litellm(tools)
|
||||
print(f"LLM formatted tools ({len(formatted)} tools):", formatted)
|
||||
|
||||
print("\nTesting OpenAI client tool formatting...")
|
||||
client = OpenAIClient(api_key="not-needed")
|
||||
formatted = client.format_tools(tools)
|
||||
print(f"OpenAI client formatted tools ({len(formatted)} tools):", formatted)
|
||||
|
||||
print("\nTest completed successfully!")
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(f"Error: {e}")
|
||||
traceback.print_exc()
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test LLM class directly to verify self-reflection fix"""
|
||||
|
||||
from praisonaiagents.llm import LLM
|
||||
from praisonaiagents.tools import evaluate
|
||||
|
||||
def test_llm_direct():
|
||||
"""Test LLM class directly with self-reflection and tools"""
|
||||
print("=== Testing LLM Direct with Self-Reflection and Tools ===")
|
||||
|
||||
# Create LLM instance
|
||||
llm = LLM(model="gpt-4o-mini")
|
||||
|
||||
# Test with self-reflection and tools
|
||||
response = llm.get_response(
|
||||
prompt="Calculate 15 * 23 and verify your answer",
|
||||
system_prompt="You are a helpful math assistant. Use tools when needed.",
|
||||
tools=[evaluate],
|
||||
self_reflect=True,
|
||||
min_reflect=1,
|
||||
max_reflect=2,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
print(f"\nResponse: {response}")
|
||||
|
||||
if response:
|
||||
print("\n✅ SUCCESS: LLM self-reflection with tools is working!")
|
||||
return True
|
||||
else:
|
||||
print("\n❌ FAILED: LLM self-reflection with tools is not working")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_llm_direct()
|
||||
Generated
+1
-1
@@ -2422,7 +2422,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "0.0.146"
|
||||
version = "0.0.149"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
||||
@@ -3,8 +3,8 @@ 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/v2.2.73.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.73.tar.gz | shasum -a 256`.split.first
|
||||
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.76.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.76.tar.gz | shasum -a 256`.split.first
|
||||
license "MIT"
|
||||
|
||||
depends_on "[email protected]"
|
||||
|
||||
@@ -57,7 +57,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.2.73 gunicorn markdown\n")
|
||||
file.write("RUN pip install flask praisonai==2.2.76 gunicorn markdown\n")
|
||||
file.write("EXPOSE 8080\n")
|
||||
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "PraisonAI"
|
||||
version = "2.2.73"
|
||||
version = "2.2.76"
|
||||
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.146",
|
||||
"praisonaiagents>=0.0.149",
|
||||
"python-dotenv>=0.19.0",
|
||||
"instructor>=1.3.3",
|
||||
"PyYAML>=6.0",
|
||||
@@ -102,7 +102,7 @@ autogen-v4 = [
|
||||
|
||||
[tool.poetry]
|
||||
name = "PraisonAI"
|
||||
version = "2.2.73"
|
||||
version = "2.2.76"
|
||||
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 = ""
|
||||
@@ -120,7 +120,7 @@ python = ">=3.10,<3.13"
|
||||
rich = ">=13.7"
|
||||
markdown = ">=3.5"
|
||||
pyparsing = ">=3.0.0"
|
||||
praisonaiagents = ">=0.0.146"
|
||||
praisonaiagents = ">=0.0.149"
|
||||
python-dotenv = ">=0.19.0"
|
||||
instructor = ">=1.3.3"
|
||||
PyYAML = ">=6.0"
|
||||
|
||||
Generated
+5
-5
@@ -4031,7 +4031,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonai"
|
||||
version = "2.2.73"
|
||||
version = "2.2.76"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "instructor" },
|
||||
@@ -4185,7 +4185,7 @@ requires-dist = [
|
||||
{ name = "praisonai-tools", marker = "extra == 'autogen'", specifier = ">=0.0.22" },
|
||||
{ name = "praisonai-tools", marker = "extra == 'autogen-v4'", specifier = ">=0.0.22" },
|
||||
{ name = "praisonai-tools", marker = "extra == 'crewai'", specifier = ">=0.0.22" },
|
||||
{ name = "praisonaiagents", specifier = ">=0.0.146" },
|
||||
{ name = "praisonaiagents", specifier = ">=0.0.149" },
|
||||
{ name = "pyautogen", marker = "extra == 'autogen'", specifier = "==0.2.29" },
|
||||
{ name = "pydantic", marker = "extra == 'chat'", specifier = "<=2.10.1" },
|
||||
{ name = "pydantic", marker = "extra == 'code'", specifier = "<=2.10.1" },
|
||||
@@ -4240,7 +4240,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "0.0.146"
|
||||
version = "0.0.149"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
@@ -4249,9 +4249,9 @@ dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "rich" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a0/49/4542900208f5f63b56c64afd2a7fe5bb967ef2f75a1191ce6ed542454a1e/praisonaiagents-0.0.146.tar.gz", hash = "sha256:264fbd93dc291f1ee5b8853481c439a85c96ec5124161cea8a0a2d39890d3001", size = 268919, upload-time = "2025-07-23T22:55:05.915Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6e/ec/be31b9a2b40977a993870adfa00dc4cbb54c347e6a689e3f01a9ab4843eb/praisonaiagents-0.0.149.tar.gz", hash = "sha256:2365c1fbc298397713cdb5aafd45fb1f543083274dd9827e589061e89e441b63", size = 270788, upload-time = "2025-07-25T08:39:56.201Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/14/4b/55a2dae2a9f65e34a1021648c746d1386859a44065e13e129e5ae6cc7d2f/praisonaiagents-0.0.146-py3-none-any.whl", hash = "sha256:10144163cdf3b75f8cfa751d5317a22ee0b65d0ff9449a9354e3f33b3019e844", size = 286525, upload-time = "2025-07-23T22:55:04.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/08/69bdea25d05d9faa98ea2eaefa63007b46e2092c865d1d5d04e40cd97208/praisonaiagents-0.0.149-py3-none-any.whl", hash = "sha256:bef4d2688759ecbcb84e897f3b3bd701f24672987e0f816829a6f1be74be29fa", size = 288759, upload-time = "2025-07-25T08:39:54.598Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in new issue
Block a user