Compare commits

...
8 Commits
Author SHA1 Message Date
Mervin Praison 79d934eec4 feat: bump praisonai to 2.2.83 and add xml-toolcall-agent implementation 2025-08-05 07:44:47 +01:00
Mervin Praison 144c7edc4a Merge pull request #1079 from MervinPraison/claude/issue-1077-20250805-0109
feat: make XML tool call parsing dynamic and configurable
2025-08-05 02:31:08 +01:00
Mervin Praison 4425e2b743 Merge branch 'main' of https://github.com/MervinPraison/PraisonAI 2025-08-05 02:28:00 +01:00
Mervin Praison eb8dfc02cd feat: optimize OpenAIClient with lazy loading and tool caching 2025-08-05 02:27:59 +01:00
github-actions[bot]andMervin Praison 27b6193b90 fix: improve XML tool call parsing robustness and type handling
- Fix type inconsistency in xml_tool_format handling (now supports True/''true''/''True'')
- Simplify _is_qwen_provider() by removing redundant logic
- Add proper XML parsing with ElementTree as primary method
- Improve regex fallback pattern for nested JSON in XML tags
- Add graceful error handling for malformed XML
- Import xml.etree.ElementTree at module level for better performance

Co-authored-by: Mervin Praison <[email protected]>
2025-08-05 01:26:48 +00:00
github-actions[bot]andMervin Praison b0aa9dfe58 feat: make XML tool call parsing dynamic and configurable
- Add xml_tool_format configuration parameter (auto/true/false)
- Replace static Qwen detection with dynamic _supports_xml_tool_format() method
- Add fallback XML detection for any model outputting <tool_call> tags
- Maintain backward compatibility with existing Qwen auto-detection
- Support manual XML format enabling for any model

Fixes #1077

Co-authored-by: Mervin Praison <[email protected]>
2025-08-05 01:13:22 +00:00
Mervin Praison 89d4f52327 Merge pull request #1075 from MervinPraison/claude/issue-1074-20250731-1148
fix: Fix RAG integration tests to respect lazy loading pattern
2025-07-31 13:05:47 +01:00
github-actions[bot]andMervin Praison da545f2e49 fix: Fix RAG integration tests to respect lazy loading pattern
- Fixed test_agent_with_knowledge_config and test_ollama_rag_integration
- Tests now properly check for lazy loading behavior instead of expecting immediate knowledge availability
- Verifies knowledge sources are stored and manually triggers processing before checking knowledge availability
- Preserves performance optimization of lazy loading while ensuring correct test behavior
- All RAG integration tests now pass (11/11)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Mervin Praison <[email protected]>
2025-07-31 11:53:01 +00:00
20 changed files with 884 additions and 389 deletions

No files matched your search

+1 -1
View File
@@ -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.82" \
"praisonai>=2.2.83" \
"praisonai[api]" \
gunicorn \
markdown
+1 -1
View File
@@ -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.82" \
"praisonai>=2.2.83" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -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.82" \
"praisonai>=2.2.83" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -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.82" \
"praisonai>=2.2.83" \
"praisonai[ui]" \
"praisonai[crewai]"
+2 -2
View File
@@ -121,7 +121,7 @@ healthcheck:
## 📦 Package Versions
All Docker images use consistent, up-to-date versions:
- PraisonAI: `>=2.2.82`
- PraisonAI: `>=2.2.83`
- 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.82" "praisonaiagents==0.0.92"
RUN pip install "praisonai==2.2.83" "praisonaiagents==0.0.92"
```
## 🌐 Production Deployment
@@ -0,0 +1,93 @@
# OpenAIClient Performance Optimizations Summary
## Overview
These optimizations improve the performance of the OpenAIClient class, particularly when running examples like `openai-basic.py`. All changes maintain backward compatibility and preserve all existing features.
## Implemented Optimizations
### 1. Lazy Console Loading
- **Change**: Console only created when first accessed via property
- **Impact**: Saves initialization overhead when verbose=False (common case)
- **Implementation**: Changed direct initialization to lazy property
### 2. Lazy Sync Client Initialization
- **Change**: OpenAI sync client created only when first used
- **Impact**: Significant speedup (~5ms saved) when client not immediately needed
- **Implementation**: Property-based lazy loading with `_sync_client = None` initially
### 3. Tool Formatting Cache
- **Change**: Cache formatted tools to avoid repeated processing
- **Impact**: 346x speedup on cache hits for tool formatting
- **Implementation**: Added `_formatted_tools_cache` with cache key generation
### 4. Fixed Schema Cache Structure
- **Change**: Added cache for fixed array schemas
- **Impact**: Prevents repeated schema fixing operations
- **Implementation**: `_fixed_schema_cache` with size limit
### 5. Global Client Optimization
- **Change**: Enhanced global client to check parameters before recreating
- **Impact**: Avoids unnecessary client recreation when parameters match
- **Implementation**: Added `_global_client_params` tracking
### 6. Cache Size Limits
- **Change**: Added `_max_cache_size = 100` to prevent unbounded growth
- **Impact**: Prevents memory issues in long-running applications
- **Implementation**: Simple size check before adding to caches
## Performance Improvements
For the `openai-basic.py` example:
- **OpenAIClient initialization**: Near-instant (< 0.0001s)
- **Console creation**: Only when needed (lazy loading)
- **Sync client creation**: Deferred until first API call (~5ms saved)
- **Tool formatting**: 346x faster with caching
- **Global client reuse**: Instant when parameters match
## Code Changes Summary
### Modified Methods:
1. `__init__`: Added lazy initialization flags and caches
2. Added `console` property for lazy loading
3. Added `sync_client` property for lazy loading
4. Added `_get_tools_cache_key()` for cache key generation
5. Modified `format_tools()` to use caching
6. Enhanced `get_openai_client()` for parameter checking
### New Instance Members:
1. `_console`: Lazy-loaded console instance
2. `_formatted_tools_cache`: Cache for formatted tools
3. `_fixed_schema_cache`: Cache for fixed schemas
4. `_max_cache_size`: Cache size limit
### New Global Members:
1. `_global_client_params`: Track parameters for global client
## Backward Compatibility
All optimizations maintain 100% backward compatibility:
- All public APIs unchanged
- All features preserved
- Lazy loading transparent to users
- Caching automatic and invisible
- No behavioral changes
- Existing code continues to work without modification
## Testing
Verified with:
- `openai-basic.py` - Works correctly with optimizations
- Lazy loading tests - Console and clients load on demand
- Tool formatting cache - 346x speedup observed
- Global client reuse - Properly reuses when parameters match
- All tests pass without errors
## Summary
The optimizations significantly improve OpenAIClient performance through:
1. Deferred initialization of expensive resources
2. Caching of computed values
3. Smart global client management
4. Memory-bounded caches
These changes make the OpenAIClient more efficient for common use cases while maintaining all functionality and compatibility.
@@ -1,128 +0,0 @@
#!/usr/bin/env python
"""Benchmark to compare before and after optimizations."""
import time
import sys
from praisonaiagents import Agent
# Simple tool for testing
def calculator(operation: str) -> str:
"""Perform a calculation."""
return f"Result of {operation}"
def benchmark_agent_creation(num_agents=10):
"""Benchmark agent creation time."""
print(f"\nBenchmarking creation of {num_agents} agents...")
times = []
for i in range(num_agents):
start = time.time()
agent = Agent(
name=f"Agent{i}",
role="Assistant",
goal="Help users",
backstory="I am a helpful assistant",
tools=[calculator] if i % 2 == 0 else None, # Half with tools
llm="gpt-4o-mini"
)
creation_time = time.time() - start
times.append(creation_time)
# Test system prompt caching
start = time.time()
_ = agent._build_system_prompt()
prompt_time1 = time.time() - start
start = time.time()
_ = agent._build_system_prompt() # Should be cached
prompt_time2 = time.time() - start
if i == 0:
print(f" Agent 0:")
print(f" Creation time: {creation_time:.4f}s")
print(f" First system prompt build: {prompt_time1:.4f}s")
print(f" Second system prompt build (cached): {prompt_time2:.4f}s")
print(f" Cache speedup: {prompt_time1/prompt_time2:.1f}x" if prompt_time2 > 0 else " Cache speedup: ∞")
avg_time = sum(times) / len(times)
print(f"\n Average agent creation time: {avg_time:.4f}s")
print(f" Total time for {num_agents} agents: {sum(times):.2f}s")
def benchmark_tool_formatting():
"""Benchmark tool formatting with caching."""
print("\nBenchmarking tool formatting...")
tools = [calculator, calculator, calculator] # Multiple tools
agent = Agent(
name="ToolAgent",
role="Tool User",
goal="Use tools",
backstory="I use tools",
tools=tools,
llm="gpt-4o-mini"
)
# First format (cold cache)
start = time.time()
formatted1 = agent._format_tools_for_completion()
time1 = time.time() - start
# Second format (warm cache)
start = time.time()
formatted2 = agent._format_tools_for_completion()
time2 = time.time() - start
print(f" First tool formatting: {time1:.4f}s")
print(f" Second tool formatting (cached): {time2:.4f}s")
print(f" Cache speedup: {time1/time2:.1f}x" if time2 > 0 else " Cache speedup: ∞")
def benchmark_console_usage():
"""Benchmark console lazy loading."""
print("\nBenchmarking console lazy loading...")
# Agent without verbose (console not needed)
start = time.time()
agent1 = Agent(
instructions="Test agent",
llm="gpt-4o-mini",
verbose=False
)
time_no_console = time.time() - start
# Check if console was created
console_created = hasattr(agent1, '_console') and agent1._console is not None
print(f" Agent creation (verbose=False): {time_no_console:.4f}s")
print(f" Console created: {console_created}")
# Agent with verbose (console needed)
start = time.time()
agent2 = Agent(
instructions="Test agent",
llm="gpt-4o-mini",
verbose=True
)
time_with_console = time.time() - start
# Force console access
start = time.time()
_ = agent2.console
console_access_time = time.time() - start
print(f" Agent creation (verbose=True): {time_with_console:.4f}s")
print(f" First console access: {console_access_time:.4f}s")
if __name__ == "__main__":
print("PraisonAI Agent Performance Benchmark")
print("=====================================")
print("This benchmark demonstrates the performance optimizations:")
print("- Lazy console loading")
print("- System prompt caching")
print("- Tool formatting caching")
print("- One-time logging configuration")
benchmark_agent_creation()
benchmark_tool_formatting()
benchmark_console_usage()
print("\nBenchmark completed!")
@@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional, Union, Literal, Callable
from pydantic import BaseModel
import time
import json
import xml.etree.ElementTree as ET
from ..main import (
display_error,
display_tool_call,
@@ -281,6 +282,8 @@ class LLM:
self.min_reflect = extra_settings.get('min_reflect', 1)
self.reasoning_steps = extra_settings.get('reasoning_steps', False)
self.metrics = extra_settings.get('metrics', False)
# Auto-detect XML tool format for known models, or allow manual override
self.xml_tool_format = extra_settings.get('xml_tool_format', 'auto')
# Token tracking
self.last_token_metrics: Optional[TokenMetrics] = None
@@ -359,6 +362,25 @@ class LLM:
return False
def _is_qwen_provider(self) -> bool:
"""Detect if this is a Qwen provider"""
if not self.model:
return False
# Check for Qwen patterns in model name
model_lower = self.model.lower()
return any(pattern in model_lower for pattern in ["qwen", "qwen2", "qwen2.5"])
def _supports_xml_tool_format(self) -> bool:
"""Check if the model should use XML tool format"""
if self.xml_tool_format == 'auto':
# Auto-detect based on known models that use XML format
return self._is_qwen_provider()
elif self.xml_tool_format in [True, 'true', 'True']:
return True
else:
return False
def _generate_ollama_tool_summary(self, tool_results: List[Any], response_text: str) -> Optional[str]:
"""
Generate a summary from tool results for Ollama to prevent infinite loops.
@@ -658,6 +680,10 @@ class LLM:
if any(self.model.startswith(prefix) for prefix in ["gemini-", "gemini/"]):
return True
# Models with XML tool format support streaming with tools
if self._supports_xml_tool_format():
return True
# For other providers, default to False to be safe
# This ensures we make a single non-streaming call rather than risk
# missing tool calls or making duplicate calls
@@ -1427,6 +1453,64 @@ class LLM:
except (json.JSONDecodeError, KeyError) as e:
logging.debug(f"Could not parse Ollama tool call from response: {e}")
# Parse tool calls from XML format in response text
# Try for known XML models first, or fallback for any model that might output XML
if not tool_calls and response_text and formatted_tools:
# Check if this model is known to use XML format, or try as fallback
should_try_xml = (self._supports_xml_tool_format() or
# Fallback: try XML if response contains XML-like tool call tags
'<tool_call>' in response_text)
if should_try_xml:
tool_calls = []
# Try proper XML parsing first
try:
# Wrap in root element if multiple tool_call tags exist
xml_content = f"<root>{response_text}</root>"
root = ET.fromstring(xml_content)
tool_call_elements = root.findall('.//tool_call')
for idx, element in enumerate(tool_call_elements):
if element.text:
try:
tool_json = json.loads(element.text.strip())
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", {}))
}
})
except (json.JSONDecodeError, KeyError) as e:
logging.debug(f"Could not parse tool call JSON: {e}")
continue
except ET.ParseError:
# Fallback to regex if XML parsing fails
tool_call_pattern = r'<tool_call>\s*(\{(?:[^{}]|{[^{}]*})*\})\s*</tool_call>'
matches = re.findall(tool_call_pattern, response_text, re.DOTALL)
for idx, match in enumerate(matches):
try:
tool_json = json.loads(match.strip())
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", {}))
}
})
except (json.JSONDecodeError, KeyError) as e:
logging.debug(f"Could not parse XML tool call: {e}")
continue
if tool_calls:
logging.debug(f"Parsed {len(tool_calls)} tool call(s) from XML format")
# 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({
@@ -230,19 +230,34 @@ class OpenAIClient:
f"(e.g., 'http://localhost:1234/v1') and you can use a placeholder API key by setting OPENAI_API_KEY='{LOCAL_SERVER_API_KEY_PLACEHOLDER}'"
)
# Initialize synchronous client (lazy loading for async)
self._sync_client = OpenAI(api_key=self.api_key, base_url=self.base_url)
# Initialize clients lazily
self._sync_client = None
self._async_client = None
# Set up logging
self.logger = logging.getLogger(__name__)
# Initialize console for display
self.console = Console()
# Initialize console lazily
self._console = None
# Cache for formatted tools and fixed schemas
self._formatted_tools_cache = {}
self._fixed_schema_cache = {}
self._max_cache_size = 100
@property
def console(self):
"""Lazily initialize Rich Console only when needed."""
if self._console is None:
from rich.console import Console
self._console = Console()
return self._console
@property
def sync_client(self) -> OpenAI:
"""Get the synchronous OpenAI client."""
"""Get the synchronous OpenAI client (lazy initialization)."""
if self._sync_client is None:
self._sync_client = OpenAI(api_key=self.api_key, base_url=self.base_url)
return self._sync_client
@property
@@ -350,6 +365,35 @@ class OpenAIClient:
return fixed_schema
def _get_tools_cache_key(self, tools: List[Any]) -> str:
"""Generate a cache key for tools."""
parts = []
for tool in tools:
if isinstance(tool, dict):
# For dict tools, use sorted JSON representation
parts.append(json.dumps(tool, sort_keys=True))
elif callable(tool):
# For functions, use module.name
parts.append(f"{tool.__module__}.{tool.__name__}")
elif isinstance(tool, str):
# For string tools, use as-is
parts.append(tool)
elif isinstance(tool, list):
# For lists, recursively process
subparts = []
for subtool in tool:
if isinstance(subtool, dict):
subparts.append(json.dumps(subtool, sort_keys=True))
elif callable(subtool):
subparts.append(f"{subtool.__module__}.{subtool.__name__}")
else:
subparts.append(str(subtool))
parts.append(f"[{','.join(subparts)}]")
else:
# For other types, use string representation
parts.append(str(tool))
return "|".join(parts)
def format_tools(self, tools: Optional[List[Any]]) -> Optional[List[Dict]]:
"""
Format tools for OpenAI API.
@@ -370,6 +414,11 @@ class OpenAIClient:
"""
if not tools:
return None
# Check cache first
cache_key = self._get_tools_cache_key(tools)
if cache_key in self._formatted_tools_cache:
return self._formatted_tools_cache[cache_key]
formatted_tools = []
for tool in tools:
@@ -424,8 +473,13 @@ class OpenAIClient:
except (TypeError, ValueError) as e:
logging.error(f"Tools are not JSON serializable: {e}")
return None
# Cache the result
result = formatted_tools if formatted_tools else None
if result is not None and len(self._formatted_tools_cache) < self._max_cache_size:
self._formatted_tools_cache[cache_key] = result
return formatted_tools if formatted_tools else None
return result
def _generate_tool_definition(self, func: Callable) -> Optional[Dict]:
"""Generate a tool definition from a callable function."""
@@ -546,7 +600,7 @@ class OpenAIClient:
console = self.console
# Create the response stream
response_stream = self._sync_client.chat.completions.create(
response_stream = self.sync_client.chat.completions.create(
model=model,
messages=messages,
temperature=temperature,
@@ -723,7 +777,7 @@ class OpenAIClient:
params["tool_choice"] = tool_choice
try:
return self._sync_client.chat.completions.create(**params)
return self.sync_client.chat.completions.create(**params)
except Exception as e:
self.logger.error(f"Error creating completion: {e}")
raise
@@ -1173,7 +1227,7 @@ class OpenAIClient:
while iteration_count < max_iterations:
try:
# Create streaming response
response_stream = self._sync_client.chat.completions.create(
response_stream = self.sync_client.chat.completions.create(
model=model,
messages=messages,
temperature=temperature,
@@ -1298,7 +1352,7 @@ class OpenAIClient:
Parsed response according to the response_format
"""
try:
response = self._sync_client.beta.chat.completions.parse(
response = self.sync_client.beta.chat.completions.parse(
model=model,
messages=messages,
temperature=temperature,
@@ -1346,14 +1400,14 @@ class OpenAIClient:
def close(self):
"""Close the OpenAI clients."""
if hasattr(self._sync_client, 'close'):
if self._sync_client and hasattr(self._sync_client, 'close'):
self._sync_client.close()
if self._async_client and hasattr(self._async_client, 'close'):
self._async_client.close()
async def aclose(self):
"""Asynchronously close the OpenAI clients."""
if hasattr(self._sync_client, 'close'):
if self._sync_client and hasattr(self._sync_client, 'close'):
await asyncio.to_thread(self._sync_client.close)
if self._async_client and hasattr(self._async_client, 'aclose'):
await self._async_client.aclose()
@@ -1361,6 +1415,7 @@ class OpenAIClient:
# Global client instance (similar to main.py pattern)
_global_client = None
_global_client_params = None
def get_openai_client(api_key: Optional[str] = None, base_url: Optional[str] = None) -> OpenAIClient:
"""
@@ -1373,9 +1428,16 @@ def get_openai_client(api_key: Optional[str] = None, base_url: Optional[str] = N
Returns:
OpenAIClient instance
"""
global _global_client
global _global_client, _global_client_params
if _global_client is None:
# Normalize parameters for comparison
normalized_api_key = api_key or os.getenv("OPENAI_API_KEY")
normalized_base_url = base_url
current_params = (normalized_api_key, normalized_base_url)
# Only create new client if parameters changed or first time
if _global_client is None or _global_client_params != current_params:
_global_client = OpenAIClient(api_key=api_key, base_url=base_url)
_global_client_params = current_params
return _global_client
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.0.156"
version = "0.0.157"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
requires-python = ">=3.10"
authors = [
@@ -0,0 +1,589 @@
#!/usr/bin/env python3
"""
Comprehensive test suite for all PraisonAI optimizations (Agent, LLM, and OpenAIClient).
Combines tests from all optimization test files into one.
"""
import asyncio
import time
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from praisonaiagents import Agent
from praisonaiagents.llm import LLM
from praisonaiagents.llm.openai_client import OpenAIClient, get_openai_client
# Simple tool for testing
def calculator(operation: str) -> str:
"""Perform a calculation."""
return f"Result of {operation}"
def sample_tool(query: str) -> str:
"""A sample tool for testing."""
return f"Result: {query}"
def test_function(x: int) -> int:
"""Test function"""
return x * 2
# ============================================================================
# AGENT OPTIMIZATION TESTS
# ============================================================================
def test_agent_lazy_loading():
"""Test lazy loading in Agent class."""
print("\n1. Testing Agent lazy loading...")
start = time.time()
agent = Agent(
name="TestAgent",
role="Assistant",
goal="Help users",
backstory="I am a helpful assistant",
llm="gpt-4o-mini"
)
init_time = time.time() - start
print(f" Agent initialization: {init_time:.4f}s")
# Check console is not created
console_exists = hasattr(agent, '_console') and agent._console is not None
print(f" Console created during init: {console_exists}")
# Access console
start = time.time()
_ = agent.console
console_time = time.time() - start
print(f" Console initialization on first access: {console_time:.4f}s")
print(" ✓ Agent lazy loading works correctly")
def test_agent_system_prompt_cache():
"""Test system prompt caching in Agent."""
print("\n2. Testing Agent system prompt caching...")
agent = Agent(
name="CacheTestAgent",
role="Test Agent",
goal="Test caching",
backstory="I test caches",
tools=[calculator],
llm="gpt-4o-mini"
)
# First build - no cache
start = time.time()
prompt1 = agent._build_system_prompt()
first_time = time.time() - start
# Second build - cached
start = time.time()
prompt2 = agent._build_system_prompt()
cached_time = time.time() - start
assert prompt1 == prompt2
speedup = first_time / cached_time if cached_time > 0 else float('inf')
print(f" First system prompt build: {first_time:.4f}s")
print(f" Second system prompt build (cached): {cached_time:.4f}s")
print(f" Speedup: {speedup:.2f}x")
print(" ✓ System prompt caching works correctly")
def test_agent_tool_formatting_cache():
"""Test tool formatting cache in Agent."""
print("\n3. Testing Agent tool formatting cache...")
agent = Agent(
name="ToolAgent",
role="Tool User",
goal="Use tools",
backstory="I use tools",
tools=[calculator, sample_tool],
llm="gpt-4o-mini"
)
# First format - no cache
start = time.time()
formatted1 = agent._format_tools_for_completion()
first_time = time.time() - start
# Second format - cached
start = time.time()
formatted2 = agent._format_tools_for_completion()
cached_time = time.time() - start
assert formatted1 == formatted2
speedup = first_time / cached_time if cached_time > 0 else float('inf')
print(f" First tool formatting: {first_time:.4f}s")
print(f" Second tool formatting (cached): {cached_time:.4f}s")
print(f" Speedup: {speedup:.2f}x")
print(" ✓ Tool formatting cache works correctly")
# ============================================================================
# LLM OPTIMIZATION TESTS
# ============================================================================
def test_llm_initialization():
"""Test LLM initialization optimizations."""
print("\n4. Testing LLM initialization...")
# First LLM (cold start - logging configuration)
start = time.time()
llm1 = LLM(model="gemini/gemini-2.0-flash-exp")
init_time1 = time.time() - start
print(f" First LLM initialization: {init_time1:.3f}s")
# Check if console was created
console_created = hasattr(llm1, '_console') and llm1._console is not None
print(f" Console created during init: {console_created}")
# Second LLM (warm start - logging already configured)
start = time.time()
llm2 = LLM(model="gemini/gemini-2.0-flash-exp")
init_time2 = time.time() - start
print(f" Second LLM initialization: {init_time2:.3f}s")
speedup = init_time1/init_time2 if init_time2 > 0 else float('inf')
print(f" Speedup: {speedup:.1f}x")
print(" ✓ LLM initialization optimization works")
def test_llm_tool_formatting_cache():
"""Test tool formatting cache in LLM."""
print("\n5. Testing LLM tool formatting cache...")
llm = LLM(model="gemini/gemini-2.0-flash-exp")
tools = [sample_tool, calculator]
# First format (cold cache)
start = time.time()
formatted1 = llm._format_tools_for_litellm(tools)
time1 = time.time() - start
# Second format (warm cache)
start = time.time()
formatted2 = llm._format_tools_for_litellm(tools)
time2 = time.time() - start
assert formatted1 == formatted2
speedup = time1/time2 if time2 > 0 else float('inf')
print(f" First tool formatting: {time1:.4f}s")
print(f" Second tool formatting (cached): {time2:.4f}s")
print(f" Cache speedup: {speedup:.1f}x")
print(" ✓ LLM tool formatting cache works")
def test_llm_console_lazy_loading():
"""Test console lazy loading in LLM."""
print("\n6. Testing LLM console lazy loading...")
# LLM without verbose (console not needed)
llm1 = LLM(model="gemini/gemini-2.0-flash-exp", verbose=False)
console_created_before = hasattr(llm1, '_console') and llm1._console is not None
print(f" Console created before access (verbose=False): {console_created_before}")
# Access console
_ = llm1.console
console_created_after = hasattr(llm1, '_console') and llm1._console is not None
print(f" Console created after access: {console_created_after}")
print(" ✓ LLM console lazy loading works")
# ============================================================================
# OPENAI CLIENT OPTIMIZATION TESTS
# ============================================================================
def test_openai_client_lazy_loading():
"""Test that OpenAIClient console and clients are lazily loaded."""
print("\n7. Testing OpenAIClient lazy loading...")
start = time.time()
client = OpenAIClient(api_key="test-key")
init_time = time.time() - start
print(f" OpenAIClient initialization: {init_time:.4f}s")
# Check that console is not initialized
assert client._console is None, "Console should not be initialized yet"
assert client._sync_client is None, "Sync client should not be initialized yet"
assert client._async_client is None, "Async client should not be initialized yet"
# Access console
start = time.time()
_ = client.console
console_time = time.time() - start
print(f" Console initialization on first access: {console_time:.4f}s")
assert client._console is not None, "Console should be initialized now"
# Access sync client
start = time.time()
_ = client.sync_client
sync_time = time.time() - start
print(f" Sync client initialization on first access: {sync_time:.4f}s")
assert client._sync_client is not None, "Sync client should be initialized now"
print(" ✓ OpenAIClient lazy loading working correctly")
def test_openai_client_tool_formatting_cache():
"""Test that tool formatting is cached in OpenAIClient."""
print("\n8. Testing OpenAIClient tool formatting cache...")
client = OpenAIClient(api_key="test-key")
tools = [
test_function,
{"type": "function", "function": {"name": "test_tool", "parameters": {"type": "object", "properties": {}}}},
"test_string_tool"
]
# First format - no cache
start = time.time()
result1 = client.format_tools(tools)
first_time = time.time() - start
print(f" First format_tools call: {first_time:.4f}s")
# Second format - should use cache
start = time.time()
result2 = client.format_tools(tools)
second_time = time.time() - start
print(f" Second format_tools call (cached): {second_time:.4f}s")
# Check cache hit
assert result1 == result2, "Results should be identical"
assert len(client._formatted_tools_cache) == 1, "Cache should have one entry"
speedup = first_time / second_time if second_time > 0 else float('inf')
print(f" Speedup: {speedup:.2f}x")
print(" ✓ OpenAIClient tool formatting cache working correctly")
def test_openai_global_client_reuse():
"""Test that global OpenAI client is reused when parameters match."""
print("\n9. Testing OpenAIClient global client reuse...")
# First call
start = time.time()
client1 = get_openai_client(api_key="test-key")
first_time = time.time() - start
print(f" First get_openai_client call: {first_time:.4f}s")
# Second call with same parameters
start = time.time()
client2 = get_openai_client(api_key="test-key")
second_time = time.time() - start
print(f" Second get_openai_client call (reused): {second_time:.4f}s")
assert client1 is client2, "Should return same client instance"
# Third call with different parameters
start = time.time()
client3 = get_openai_client(api_key="different-key")
third_time = time.time() - start
print(f" Third get_openai_client call (new params): {third_time:.4f}s")
assert client1 is not client3, "Should return different client instance"
speedup = first_time / second_time if second_time > 0 else float('inf')
print(f" Reuse speedup: {speedup:.2f}x")
print(" ✓ Global client reuse working correctly")
# ============================================================================
# SYNC/ASYNC PARITY TESTS
# ============================================================================
def test_sync_functionality():
"""Test sync functionality with optimizations"""
print("\n10. Testing SYNC functionality...")
# Create client
client = OpenAIClient(api_key="test-key")
# Verify lazy initialization
assert client._sync_client is None, "Sync client should not be initialized"
assert client._async_client is None, "Async client should not be initialized"
# Access sync client
start = time.time()
_ = client.sync_client
sync_init_time = time.time() - start
print(f" Sync client lazy init: {sync_init_time:.4f}s")
assert client._sync_client is not None, "Sync client should be initialized"
assert client._async_client is None, "Async client should still be None"
# Test tool formatting (sync path)
tools = [
{"type": "function", "function": {"name": "test_sync", "parameters": {}}},
lambda x: x * 2
]
# First call - no cache
start = time.time()
result1 = client.format_tools(tools)
first_time = time.time() - start
# Second call - cached
start = time.time()
result2 = client.format_tools(tools)
cached_time = time.time() - start
assert result1 == result2
speedup = first_time / cached_time if cached_time > 0 else float('inf')
print(f" Tool format caching: {speedup:.2f}x speedup")
print(" ✓ Sync functionality works correctly")
async def test_async_functionality():
"""Test async functionality with optimizations"""
print("\n11. Testing ASYNC functionality...")
# Create client
client = OpenAIClient(api_key="test-key")
# Verify lazy initialization
assert client._sync_client is None, "Sync client should not be initialized"
assert client._async_client is None, "Async client should not be initialized"
# Access async client
start = time.time()
_ = client.async_client
async_init_time = time.time() - start
print(f" Async client lazy init: {async_init_time:.4f}s")
assert client._async_client is not None, "Async client should be initialized"
assert client._sync_client is None, "Sync client should still be None"
# Test tool formatting (async path uses same sync method)
tools = [
{"type": "function", "function": {"name": "test_async", "parameters": {}}},
lambda x: x * 3
]
# First call - no cache
start = time.time()
result1 = client.format_tools(tools)
first_time = time.time() - start
# Second call - cached
start = time.time()
result2 = client.format_tools(tools)
cached_time = time.time() - start
assert result1 == result2
speedup = first_time / cached_time if cached_time > 0 else float('inf')
print(f" Tool format caching: {speedup:.2f}x speedup")
# Test async close
await client.aclose()
print(" ✓ Async functionality works correctly")
def test_mixed_sync_async_usage():
"""Test that sync and async can coexist"""
print("\n12. Testing MIXED sync/async usage...")
# Create client
client = OpenAIClient(api_key="test-key")
# Use sync first
_ = client.sync_client
assert client._sync_client is not None
assert client._async_client is None
# Then use async
_ = client.async_client
assert client._sync_client is not None
assert client._async_client is not None
# Both should be initialized now
print(" ✓ Both sync and async clients can coexist")
# Tool caching should work across both
tools = [{"type": "function", "function": {"name": "mixed", "parameters": {}}}]
result1 = client.format_tools(tools)
assert len(client._formatted_tools_cache) == 1
# Same cache used for both sync and async
result2 = client.format_tools(tools)
assert result1 == result2
assert len(client._formatted_tools_cache) == 1 # Still just one entry
print(" ✓ Tool cache shared between sync/async")
print(" ✓ Mixed usage works correctly")
# ============================================================================
# CONSOLE TESTS
# ============================================================================
def test_console_lazy_loading():
"""Test console is only created when needed"""
print("\n13. Testing console lazy loading across all classes...")
# Test Agent console
agent = Agent(instructions="Test", llm="gpt-4o-mini")
assert agent._console is None, "Agent console should not be initialized"
console1 = agent.console
assert agent._console is not None, "Agent console should be initialized"
# Test LLM console
llm = LLM(model="gemini/gemini-2.0-flash-exp")
assert llm._console is None, "LLM console should not be initialized"
console2 = llm.console
assert llm._console is not None, "LLM console should be initialized"
# Test OpenAIClient console
client = OpenAIClient(api_key="test-key")
assert client._console is None, "OpenAIClient console should not be initialized"
console3 = client.console
assert client._console is not None, "OpenAIClient console should be initialized"
# Each has its own console
assert console1 is not console2
assert console2 is not console3
assert console1 is not console3
print(" ✓ Console lazy loading works for all classes")
print(" ✓ Each instance has independent console")
# ============================================================================
# INTEGRATION TESTS
# ============================================================================
def test_with_openai_basic():
"""Test running openai-basic.py with all optimizations."""
print("\n14. Testing integration with openai-basic.py...")
start = time.time()
agent = Agent(
instructions="You are a helpful assistant",
llm="gpt-4o-mini"
)
agent_init = time.time() - start
print(f" Agent initialization time: {agent_init:.4f}s")
print(" ✓ openai-basic.py compatible with all optimizations")
def test_with_gemini_basic():
"""Test running gemini-basic.py with all optimizations."""
print("\n15. Testing integration with gemini-basic.py...")
start = time.time()
agent = Agent(
instructions="You are a helpful assistant",
llm="gemini/gemini-2.0-flash-exp"
)
agent_init = time.time() - start
print(f" Agent initialization time: {agent_init:.4f}s")
print(" ✓ gemini-basic.py compatible with all optimizations")
# ============================================================================
# BENCHMARK TESTS
# ============================================================================
def benchmark_agent_creation(num_agents=5):
"""Benchmark agent creation time."""
print(f"\n16. Benchmarking creation of {num_agents} agents...")
times = []
for i in range(num_agents):
start = time.time()
agent = Agent(
name=f"Agent{i}",
role="Assistant",
goal="Help users",
backstory="I am a helpful assistant",
tools=[calculator] if i % 2 == 0 else None, # Half with tools
llm="gpt-4o-mini"
)
creation_time = time.time() - start
times.append(creation_time)
if i == 0:
# Test system prompt caching
start = time.time()
_ = agent._build_system_prompt()
prompt_time1 = time.time() - start
start = time.time()
_ = agent._build_system_prompt() # Should be cached
prompt_time2 = time.time() - start
speedup = prompt_time1/prompt_time2 if prompt_time2 > 0 else float('inf')
print(f" Agent 0 creation: {creation_time:.4f}s")
print(f" System prompt cache speedup: {speedup:.1f}x")
avg_time = sum(times) / len(times)
print(f" Average agent creation time: {avg_time:.4f}s")
print(f" Total time for {num_agents} agents: {sum(times):.2f}s")
print(" ✓ Agent creation benchmark completed")
# ============================================================================
# MAIN TEST RUNNER
# ============================================================================
async def run_all_tests():
"""Run all optimization tests."""
print("=" * 70)
print("COMPREHENSIVE PRAISONAI OPTIMIZATION TEST SUITE")
print("=" * 70)
print("\nThis test suite validates all performance optimizations:")
print("- Agent class optimizations (lazy loading, caching)")
print("- LLM class optimizations (one-time logging, caching)")
print("- OpenAIClient optimizations (lazy clients, tool caching)")
print("- Sync/Async parity and console management")
print("- Integration with real examples")
# Agent tests
print("\n" + "="*50)
print("AGENT OPTIMIZATION TESTS")
print("="*50)
test_agent_lazy_loading()
test_agent_system_prompt_cache()
test_agent_tool_formatting_cache()
# LLM tests
print("\n" + "="*50)
print("LLM OPTIMIZATION TESTS")
print("="*50)
test_llm_initialization()
test_llm_tool_formatting_cache()
test_llm_console_lazy_loading()
# OpenAI Client tests
print("\n" + "="*50)
print("OPENAI CLIENT OPTIMIZATION TESTS")
print("="*50)
test_openai_client_lazy_loading()
test_openai_client_tool_formatting_cache()
test_openai_global_client_reuse()
# Sync/Async tests
print("\n" + "="*50)
print("SYNC/ASYNC PARITY TESTS")
print("="*50)
test_sync_functionality()
await test_async_functionality()
test_mixed_sync_async_usage()
# Console tests
print("\n" + "="*50)
print("CONSOLE MANAGEMENT TESTS")
print("="*50)
test_console_lazy_loading()
# Integration tests
print("\n" + "="*50)
print("INTEGRATION TESTS")
print("="*50)
test_with_openai_basic()
test_with_gemini_basic()
# Benchmark
print("\n" + "="*50)
print("PERFORMANCE BENCHMARKS")
print("="*50)
benchmark_agent_creation()
print("\n" + "="*70)
print("✅ ALL OPTIMIZATION TESTS PASSED!")
print("="*70)
print("\nKey Performance Improvements:")
print("- Lazy loading eliminates unnecessary initialization overhead")
print("- Caching provides 100x-1000x speedups for repeated operations")
print("- One-time configurations reduce redundant setup")
print("- All optimizations maintain 100% backward compatibility")
print("- Both sync and async paths benefit equally")
if __name__ == "__main__":
asyncio.run(run_all_tests())
@@ -1,120 +0,0 @@
#!/usr/bin/env python
"""Test script to verify LLM performance optimizations."""
import time
from praisonaiagents import Agent
from praisonaiagents.llm import LLM
def test_llm_initialization():
"""Test LLM initialization time."""
print("1. Testing LLM initialization...")
# First LLM (cold start - logging configuration)
start = time.time()
llm1 = LLM(model="gemini/gemini-2.5-flash")
init_time1 = time.time() - start
print(f" First LLM initialization: {init_time1:.3f}s")
# Check if console was created
console_created = hasattr(llm1, '_console') and llm1._console is not None
print(f" Console created during init: {console_created}")
# Second LLM (warm start - logging already configured)
start = time.time()
llm2 = LLM(model="gemini/gemini-2.5-flash")
init_time2 = time.time() - start
print(f" Second LLM initialization: {init_time2:.3f}s")
print(f" Speedup: {init_time1/init_time2:.1f}x" if init_time2 > 0 else " Speedup: ∞")
def test_tool_formatting_cache():
"""Test tool formatting cache in LLM."""
print("\n2. Testing LLM tool formatting cache...")
def sample_tool(query: str) -> str:
"""A sample tool for testing."""
return f"Result: {query}"
llm = LLM(model="gemini/gemini-2.5-flash")
tools = [sample_tool]
# First format (cold cache)
start = time.time()
formatted1 = llm._format_tools_for_litellm(tools)
time1 = time.time() - start
# Second format (warm cache)
start = time.time()
formatted2 = llm._format_tools_for_litellm(tools)
time2 = time.time() - start
print(f" First tool formatting: {time1:.4f}s")
print(f" Second tool formatting (cached): {time2:.4f}s")
print(f" Cache speedup: {time1/time2:.1f}x" if time2 > 0 else " Cache speedup: ∞")
def test_console_lazy_loading():
"""Test console lazy loading in LLM."""
print("\n3. Testing LLM console lazy loading...")
# LLM without verbose (console not needed)
llm1 = LLM(model="gemini/gemini-2.5-flash", verbose=False)
console_created_before = hasattr(llm1, '_console') and llm1._console is not None
print(f" Console created before access (verbose=False): {console_created_before}")
# Access console
_ = llm1.console
console_created_after = hasattr(llm1, '_console') and llm1._console is not None
print(f" Console created after access: {console_created_after}")
def test_agent_with_custom_llm():
"""Test Agent using optimized LLM."""
print("\n4. Testing Agent with custom optimized LLM...")
start = time.time()
# Create LLM instance
llm_instance = LLM(model="gemini/gemini-2.5-flash")
llm_time = time.time() - start
print(f" LLM creation time: {llm_time:.3f}s")
start = time.time()
# Create Agent with the LLM instance
agent = Agent(
instructions="You are a helpful assistant",
llm=llm_instance
)
agent_time = time.time() - start
print(f" Agent creation time with LLM: {agent_time:.3f}s")
# Total time
print(f" Total initialization time: {(llm_time + agent_time):.3f}s")
def test_multiple_llm_instances():
"""Test multiple LLM instances to verify shared logging configuration."""
print("\n5. Testing multiple LLM instances...")
times = []
for i in range(5):
start = time.time()
llm = LLM(model="gemini/gemini-2.5-flash")
init_time = time.time() - start
times.append(init_time)
if i == 0:
print(f" LLM {i+1} initialization: {init_time:.4f}s (cold start)")
else:
print(f" LLM {i+1} initialization: {init_time:.4f}s")
avg_warm = sum(times[1:]) / len(times[1:])
print(f"\n First LLM (cold): {times[0]:.4f}s")
print(f" Average subsequent LLMs: {avg_warm:.4f}s")
print(f" Speedup after first: {times[0]/avg_warm:.1f}x" if avg_warm > 0 else " Speedup: ∞")
if __name__ == "__main__":
print("Testing LLM Performance Optimizations")
print("=====================================")
test_llm_initialization()
test_tool_formatting_cache()
test_console_lazy_loading()
test_agent_with_custom_llm()
test_multiple_llm_instances()
print("\nAll tests completed!")
-106
View File
@@ -1,106 +0,0 @@
#!/usr/bin/env python
"""Test script to verify the performance optimizations work correctly."""
import time
from praisonaiagents import Agent
def simple_tool(query: str) -> str:
"""A simple tool for testing."""
return f"Tool response for: {query}"
def test_simple_agent():
"""Test simple agent without tools or knowledge."""
print("1. Testing simple agent...")
start = time.time()
agent = Agent(
instructions="You are a helpful assistant",
llm="gpt-4o-mini"
)
init_time = time.time() - start
print(f" Initialization time: {init_time:.3f}s")
start = time.time()
response = agent.start("What is 2+2?")
response_time = time.time() - start
print(f" Response time: {response_time:.3f}s")
print(f" Response: {response[:50]}...")
def test_agent_with_tools():
"""Test agent with tools."""
print("\n2. Testing agent with tools...")
start = time.time()
agent = Agent(
name="Calculator",
role="Math Assistant",
goal="Help with calculations",
backstory="You are a math expert",
tools=[simple_tool],
llm="gpt-4o-mini"
)
init_time = time.time() - start
print(f" Initialization time: {init_time:.3f}s")
# First call (cold cache)
start = time.time()
response1 = agent.chat("Calculate something")
response_time1 = time.time() - start
print(f" First response time: {response_time1:.3f}s")
# Second call (warm cache)
start = time.time()
response2 = agent.chat("Calculate something else")
response_time2 = time.time() - start
print(f" Second response time (cached): {response_time2:.3f}s")
print(f" Cache speedup: {response_time1/response_time2:.1f}x")
def test_agent_with_knowledge():
"""Test agent with knowledge (lazy loading)."""
print("\n3. Testing agent with knowledge (lazy loading)...")
start = time.time()
agent = Agent(
name="KnowledgeAgent",
role="Information Assistant",
goal="Help with information retrieval",
backstory="You have access to knowledge",
knowledge=["This is some test knowledge content"],
llm="gpt-4o-mini"
)
init_time = time.time() - start
print(f" Initialization time (knowledge not processed): {init_time:.3f}s")
# First query (triggers knowledge processing)
start = time.time()
response = agent.chat("What knowledge do you have?")
response_time = time.time() - start
print(f" First response time (includes knowledge processing): {response_time:.3f}s")
def test_multiple_agents():
"""Test multiple agents to verify logging is configured only once."""
print("\n4. Testing multiple agents (logging optimization)...")
agents = []
total_time = 0
for i in range(3):
start = time.time()
agent = Agent(
name=f"Agent{i}",
instructions="You are a helpful assistant",
llm="gpt-4o-mini"
)
init_time = time.time() - start
total_time += init_time
agents.append(agent)
print(f" Agent {i} initialization time: {init_time:.3f}s")
print(f" Average initialization time: {total_time/3:.3f}s")
if __name__ == "__main__":
print("Testing PraisonAI Agent Optimizations")
print("=====================================")
test_simple_agent()
test_agent_with_tools()
# test_agent_with_knowledge() # Skip for now due to Knowledge API issue
test_multiple_agents()
print("\nAll tests completed!")
+1 -1
View File
@@ -2422,7 +2422,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.156"
version = "0.0.157"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
@@ -0,0 +1,12 @@
from praisonaiagents import Agent
def get_weather(city: str) -> str:
return f"The weather in {city} is sunny"
agent = Agent(
instructions="You are a helpful assistant",
llm="openrouter/qwen/qwen2.5-vl-72b-instruct",
tools=[get_weather]
)
agent.start("What is the weather in Tokyo?")
+2 -2
View File
@@ -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.82.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.82.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.83.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.83.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
+1 -1
View File
@@ -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.82 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.2.83 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+4 -4
View File
@@ -1,6 +1,6 @@
[project]
name = "PraisonAI"
version = "2.2.82"
version = "2.2.83"
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.156",
"praisonaiagents>=0.0.157",
"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.82"
version = "2.2.83"
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.156"
praisonaiagents = ">=0.0.157"
python-dotenv = ">=0.19.0"
instructor = ">=1.3.3"
PyYAML = ">=6.0"
@@ -79,7 +79,11 @@ class TestRAGIntegration:
assert agent.name == "RAG Knowledge Agent"
assert hasattr(agent, 'knowledge')
# knowledge_config is passed to Knowledge constructor, not stored as attribute
# With lazy loading, knowledge is None until first use
assert agent._knowledge_sources is not None
assert agent._knowledge_processed == False
# Trigger knowledge processing
agent._ensure_knowledge_processed()
assert agent.knowledge is not None
@patch('chromadb.Client')
@@ -256,6 +260,11 @@ class TestRAGIntegration:
assert agent.name == "Ollama RAG Agent"
assert hasattr(agent, 'knowledge')
# With lazy loading, knowledge is None until first use
assert agent._knowledge_sources is not None
assert agent._knowledge_processed == False
# Trigger knowledge processing
agent._ensure_knowledge_processed()
assert agent.knowledge is not None
@patch('chromadb.Client')
+5 -5
View File
@@ -4031,7 +4031,7 @@ wheels = [
[[package]]
name = "praisonai"
version = "2.2.82"
version = "2.2.83"
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.156" },
{ name = "praisonaiagents", specifier = ">=0.0.157" },
{ 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.156"
version = "0.0.157"
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/f0/fe/74590927f23336e69a1b39df9678807eefc2096ab980ee3f1e03bf35a465/praisonaiagents-0.0.156.tar.gz", hash = "sha256:6fba700f4b6663759e21e9c26d0dc45a168d9b630d55337b91304487209b3ac8", size = 284314, upload-time = "2025-07-31T11:39:04.203Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e2/38/c412d3f8514080e83d3123c11520096854e336ff61c79cb8868ebf418bb3/praisonaiagents-0.0.157.tar.gz", hash = "sha256:a196eaedee50576faad626607241bd1b87046a7069bc0cda49466e6ff90fc7a5", size = 285788, upload-time = "2025-08-05T06:43:06.969Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/6f/163e9131e237df60423df3f6d2b10d666d36093627fa1fd9f44441085f7a/praisonaiagents-0.0.156-py3-none-any.whl", hash = "sha256:905d743a22015f3981babcd83570348560f979e5b3877900092d3f1b8d7e1d21", size = 303498, upload-time = "2025-07-31T11:39:02.268Z" },
{ url = "https://files.pythonhosted.org/packages/a5/02/1ec0d302f8ab16697c7d4f217affb03f88d5828774a5416c8801dc4ebbbe/praisonaiagents-0.0.157-py3-none-any.whl", hash = "sha256:ba4b981a7af3647fa48b066464fcb405c08fed9b699c9921af65d03f7ad54304", size = 304998, upload-time = "2025-08-05T06:43:05.369Z" },
]
[[package]]