Compare commits

...
7 Commits
Author SHA1 Message Date
Mervin Praison 3aa1b825e4 Release v2.3.0 2025-12-15 11:24:46 +00:00
Mervin Praison b4ee30d837 chore: bump praisonaiagents version to 0.0.170 2025-12-15 11:11:46 +00:00
Mervin Praison 825648aead docs: add PromptExpanderAgent to README 2025-12-15 11:11:04 +00:00
Mervin Praison 1532c31e72 feat: add PromptExpanderAgent for detailed prompt expansion
- Add PromptExpanderAgent class with expansion strategies (BASIC, DETAILED, STRUCTURED, CREATIVE, AUTO)
- Add ExpandStrategy enum and ExpandResult dataclass
- Add CLI flags --expand-prompt and --expand-tools
- Add 20 unit tests for PromptExpanderAgent
- Export from praisonaiagents package

Usage:
  praisonai 'write a movie script' --expand-prompt
  praisonai 'AI blog' --expand-prompt --expand-tools tools.py
2025-12-15 11:09:25 +00:00
Mervin Praison b313fefd86 fix: QueryRewriterAgent preserves task intent instead of converting to questions 2025-12-15 10:57:53 +00:00
Mervin Praison 435e1afb55 test: add OPENAI_API_KEY environment variable to comprehensive test workflow and verify its presence 2025-12-15 10:47:29 +00:00
Mervin Praison 4cb3c0565a test: add skipif decorator for OPENAI_API_KEY check in CrewAI environment test 2025-12-15 10:42:00 +00:00
24 changed files with 1143 additions and 67 deletions

No files matched your search

+9 -4
View File
@@ -27,6 +27,9 @@ jobs:
comprehensive-test:
runs-on: ubuntu-latest
timeout-minutes: 15
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_MODEL_NAME: gpt-4o-mini
strategy:
matrix:
python-version: ["3.11"]
@@ -55,11 +58,13 @@ jobs:
# Install knowledge dependencies from praisonai-agents
uv pip install --system "praisonaiagents[knowledge]"
- name: Set environment variables
- name: Verify environment variables
run: |
echo "OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY || 'sk-test-key-for-github-actions-testing-only-not-real' }}" >> $GITHUB_ENV
echo "OPENAI_API_BASE=${{ secrets.OPENAI_API_BASE || 'https://api.openai.com/v1' }}" >> $GITHUB_ENV
echo "OPENAI_MODEL_NAME=${{ secrets.OPENAI_MODEL_NAME || 'gpt-5-nano' }}" >> $GITHUB_ENV
if [ -n "$OPENAI_API_KEY" ]; then
echo "✅ OPENAI_API_KEY is set (length: ${#OPENAI_API_KEY})"
else
echo "⚠️ OPENAI_API_KEY is NOT set"
fi
- name: Run Comprehensive Test Suite
run: |
+2 -2
View File
@@ -34,7 +34,7 @@ jobs:
cd src/praisonai
uv pip install --system ."[ui,gradio,api,agentops,google,openai,anthropic,cohere,chat,code,realtime,call,crewai,autogen]"
uv pip install --system duckduckgo_search
uv pip install --system pytest pytest-asyncio pytest-cov
uv pip install --system pytest pytest-asyncio pytest-cov pytest-timeout
# Install knowledge dependencies from praisonai-agents
uv pip install --system "praisonaiagents[knowledge]"
@@ -262,7 +262,7 @@ jobs:
export OPENAI_API_KEY="$OPENAI_API_KEY"
export OPENAI_API_BASE="$OPENAI_API_BASE"
export OPENAI_MODEL_NAME="$OPENAI_MODEL_NAME"
cd src/praisonai && python -m pytest tests/test.py -v --tb=short --disable-warnings
cd src/praisonai && python -m pytest tests/test.py -v --tb=short --disable-warnings -m "not real" --timeout=60
- name: Upload coverage reports to Codecov
if: matrix.python-version == '3.11'
+2 -2
View File
@@ -27,7 +27,7 @@ jobs:
cd src/praisonai
uv pip install --system ."[ui,gradio,api,agentops,google,openai,anthropic,cohere,chat,code,realtime,call,crewai,autogen]"
uv pip install --system duckduckgo_search
uv pip install --system pytest pytest-asyncio pytest-cov
uv pip install --system pytest pytest-asyncio pytest-cov pytest-timeout
# Install knowledge dependencies from praisonai-agents
uv pip install --system "praisonaiagents[knowledge]"
@@ -489,7 +489,7 @@ jobs:
- name: Run Legacy Example Tests
run: |
cd src/praisonai && python -m pytest tests/test.py -v --tb=short --disable-warnings
cd src/praisonai && python -m pytest tests/test.py -v --tb=short --disable-warnings -m "not real" --timeout=60
continue-on-error: true
- name: Upload coverage reports to Codecov
+45 -11
View File
@@ -718,6 +718,47 @@ agent = Agent(tools=["my_tool"]) # Works automatically!
```
## Prompt Expansion
Expand short prompts into detailed, actionable prompts:
### CLI Usage
```bash
# Expand a short prompt into detailed prompt
praisonai "write a movie script in 3 lines" --expand-prompt
# With verbose output
praisonai "blog about AI" --expand-prompt -v
# With tools for context gathering
praisonai "latest AI trends" --expand-prompt --expand-tools tools.py
# Combine with query rewrite
praisonai "AI news" --query-rewrite --expand-prompt
```
### Programmatic Usage
```python
from praisonaiagents import PromptExpanderAgent, ExpandStrategy
# Basic usage
agent = PromptExpanderAgent()
result = agent.expand("write a movie script in 3 lines")
print(result.expanded_prompt)
# With specific strategy
result = agent.expand("blog about AI", strategy=ExpandStrategy.DETAILED)
# Available strategies: BASIC, DETAILED, STRUCTURED, CREATIVE, AUTO
```
**Key Difference:**
- `--query-rewrite`: Optimizes queries for **search/retrieval** (RAG)
- `--expand-prompt`: Expands prompts for **detailed task execution**
## Development:
Below is used for development only.
@@ -735,21 +776,14 @@ uv pip install -r pyproject.toml --extra code
uv pip install -r pyproject.toml --extra "crewai,autogen"
```
### Version Bump
### Bump and Release
```bash
# From project root
python src/praisonai/scripts/bump_version.py 2.2.96
# From project root - bumps version and releases in one command
python src/praisonai/scripts/bump_and_release.py 2.2.99
# With praisonaiagents dependency
python src/praisonai/scripts/bump_version.py 2.2.96 --agents 0.0.167
```
### Release
```bash
# From project root (runs uv lock, build, git tag, gh release)
python src/praisonai/scripts/release.py
python src/praisonai/scripts/bump_and_release.py 2.2.99 --agents 0.0.169
# Then publish
cd src/praisonai && uv publish
+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.99" \
"praisonai>=2.3.0" \
"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.99" \
"praisonai>=2.3.0" \
"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.99" \
"praisonai>=2.3.0" \
"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.99" \
"praisonai>=2.3.0" \
"praisonai[ui]" \
"praisonai[crewai]"
@@ -31,6 +31,11 @@ from .agent.query_rewriter_agent import (
RewriteStrategy,
RewriteResult
)
from .agent.prompt_expander_agent import (
PromptExpanderAgent,
ExpandStrategy,
ExpandResult
)
from .agents.agents import PraisonAIAgents
from .task.task import Task
from .tools.tools import Tools
@@ -228,7 +233,10 @@ __all__ = [
'Provider',
'QueryRewriterAgent',
'RewriteStrategy',
'RewriteResult'
'RewriteResult',
'PromptExpanderAgent',
'ExpandStrategy',
'ExpandResult'
]
# Add MCP to __all__ if available
@@ -20,6 +20,11 @@ from .query_rewriter_agent import (
RewriteStrategy,
RewriteResult
)
from .prompt_expander_agent import (
PromptExpanderAgent,
ExpandStrategy,
ExpandResult
)
__all__ = [
'Agent',
@@ -43,5 +48,8 @@ __all__ = [
'Provider',
'QueryRewriterAgent',
'RewriteStrategy',
'RewriteResult'
'RewriteResult',
'PromptExpanderAgent',
'ExpandStrategy',
'ExpandResult'
]
@@ -0,0 +1,348 @@
"""
Prompt Expander Agent Module
This module provides the PromptExpanderAgent class for expanding short prompts
into detailed, comprehensive prompts for better task execution.
Unlike QueryRewriterAgent (which optimizes queries for search/retrieval),
PromptExpanderAgent focuses on enriching prompts for task execution.
Supported Expansion Strategies:
- **BASIC**: Simple expansion with clarity improvements
- **DETAILED**: Rich expansion with context, constraints, and examples
- **STRUCTURED**: Expansion with clear structure (task, format, requirements)
- **CREATIVE**: Expansion with creative flair and vivid language
- **AUTO**: Automatically selects the best strategy based on prompt analysis
Example:
from praisonaiagents import PromptExpanderAgent, ExpandStrategy
agent = PromptExpanderAgent(verbose=True)
# Basic expansion
result = agent.expand("write a movie script in 3 lines")
print(result.expanded_prompt)
# Detailed expansion
result = agent.expand("blog about AI", strategy=ExpandStrategy.DETAILED)
print(result.expanded_prompt)
# With tools for context gathering
from praisonaiagents.tools import internet_search
agent = PromptExpanderAgent(tools=[internet_search], verbose=True)
result = agent.expand("latest AI developments")
"""
import logging
from typing import List, Optional, Any, Dict
from dataclasses import dataclass, field
from enum import Enum
class ExpandStrategy(Enum):
"""Enumeration of available prompt expansion strategies."""
BASIC = "basic"
DETAILED = "detailed"
STRUCTURED = "structured"
CREATIVE = "creative"
AUTO = "auto"
@dataclass
class ExpandResult:
"""Result of a prompt expansion operation."""
original_prompt: str
expanded_prompt: str
strategy_used: ExpandStrategy
metadata: Dict[str, Any] = field(default_factory=dict)
def __repr__(self):
return f"ExpandResult(strategy={self.strategy_used.value}, original_len={len(self.original_prompt)}, expanded_len={len(self.expanded_prompt)})"
class PromptExpanderAgent:
"""
Agent for expanding short prompts into detailed, comprehensive prompts.
This agent transforms brief task descriptions into rich, detailed prompts
that provide better context and guidance for task execution.
Key difference from QueryRewriterAgent:
- QueryRewriterAgent: Optimizes queries for search/retrieval (RAG)
- PromptExpanderAgent: Expands prompts for detailed task execution
Attributes:
name: Name of the agent
model: LLM model to use for expansion
verbose: Whether to print detailed logs
temperature: Temperature for LLM generation (higher = more creative)
tools: Optional tools for context gathering
Example:
agent = PromptExpanderAgent(model="gpt-4o-mini", verbose=True)
result = agent.expand("write a poem", strategy=ExpandStrategy.CREATIVE)
print(result.expanded_prompt)
"""
# Default prompts for each strategy
PROMPTS = {
"basic": """You are an expert at improving task prompts.
Given the following task prompt, expand it to be clearer and more actionable while preserving the original intent.
Guidelines:
- Keep it as a TASK/COMMAND (do NOT convert to a question)
- Add clarity without changing the core request
- Fix any ambiguity
- Keep it concise but complete
Original prompt: {prompt}
Expanded prompt:""",
"detailed": """You are an expert at creating comprehensive task prompts.
Given the following brief task prompt, expand it into a detailed, actionable prompt that provides rich context and clear guidance.
Guidelines:
- Keep it as a TASK/COMMAND (do NOT convert to a question)
- Add specific details about what to include
- Specify quality expectations
- Add relevant constraints or requirements
- Include format guidance if applicable
- Mention style or tone if relevant
Original prompt: {prompt}
Detailed expanded prompt:""",
"structured": """You are an expert at creating well-structured task prompts.
Given the following task prompt, expand it into a structured format with clear sections.
Guidelines:
- Keep it as a TASK/COMMAND (do NOT convert to a question)
- Use a clear structure:
* Task: What to do
* Format: Expected output format
* Requirements: Specific requirements
* Style: Tone or style guidance
* Constraints: Any limitations
- Be specific and actionable
Original prompt: {prompt}
Structured expanded prompt:""",
"creative": """You are an expert at crafting inspiring, creative task prompts.
Given the following task prompt, expand it with vivid language and creative direction while keeping it actionable.
Guidelines:
- Keep it as a TASK/COMMAND (do NOT convert to a question)
- Use evocative, inspiring language
- Paint a picture of the desired outcome
- Add creative direction and artistic guidance
- Make it exciting and engaging
- Preserve the core task intent
Original prompt: {prompt}
Creative expanded prompt:""",
"auto": """You are an expert at analyzing and expanding task prompts.
Analyze the following prompt and expand it appropriately based on its nature.
For short/vague prompts: Add significant detail and context
For task prompts: Preserve the action-oriented nature
For creative tasks: Add artistic direction
For technical tasks: Add precision and requirements
IMPORTANT: Keep it as a TASK/COMMAND. Never convert to a question.
Original prompt: {prompt}
{context}
Expanded prompt:"""
}
def __init__(
self,
name: str = "PromptExpanderAgent",
model: str = "gpt-4o-mini",
instructions: Optional[str] = None,
verbose: bool = False,
temperature: float = 0.7, # Higher for creativity
max_tokens: int = 1000,
tools: Optional[List[Any]] = None
):
"""
Initialize the PromptExpanderAgent.
Args:
name: Name of the agent
model: LLM model to use (default: gpt-4o-mini)
instructions: Custom instructions for the agent
verbose: Whether to print detailed logs
temperature: Temperature for LLM generation (default: 0.7 for creativity)
max_tokens: Maximum tokens for LLM response
tools: Optional list of tools for context gathering
"""
self.name = name
self.model = model
self.instructions = instructions
self.verbose = verbose
self.temperature = temperature
self.max_tokens = max_tokens
self.tools = tools or []
self._agent = None # Lazy initialized
# Setup logging
self.logger = logging.getLogger(__name__)
if self.verbose:
self.logger.setLevel(logging.DEBUG)
@property
def agent(self):
"""Lazy initialization of internal Agent."""
if self._agent is None:
from .agent import Agent
self._agent = Agent(
name=self.name,
role="Prompt Expansion Specialist",
goal="Expand brief prompts into detailed, actionable prompts for better task execution. Use available tools when needed to gather context.",
backstory="You are an expert at understanding user intent and transforming brief requests into comprehensive, detailed prompts.",
tools=self.tools,
llm=self.model,
verbose=self.verbose,
markdown=False
)
return self._agent
def _call_agent(self, prompt: str) -> str:
"""Call the internal Agent with the given prompt."""
try:
response = self.agent.chat(prompt)
return response if response else ""
except Exception as e:
self.logger.error(f"Agent call failed: {e}")
raise
def _is_short_prompt(self, prompt: str, threshold: int = 10) -> bool:
"""Check if prompt is considered short."""
return len(prompt.split()) < threshold
def _is_task_prompt(self, prompt: str) -> bool:
"""Check if prompt is a task/command (vs a question)."""
task_verbs = [
"write", "create", "generate", "build", "make", "design",
"develop", "implement", "compose", "draft", "produce",
"craft", "construct", "prepare", "draw", "paint", "code"
]
prompt_lower = prompt.lower().strip()
return any(prompt_lower.startswith(verb) for verb in task_verbs)
def _is_creative_task(self, prompt: str) -> bool:
"""Check if prompt is a creative task."""
creative_indicators = [
"poem", "story", "script", "song", "art", "creative",
"fiction", "novel", "essay", "blog", "article", "write"
]
prompt_lower = prompt.lower()
return any(ind in prompt_lower for ind in creative_indicators)
def _detect_strategy(self, prompt: str) -> ExpandStrategy:
"""Auto-detect the best expansion strategy for a prompt."""
# Very short prompts need detailed expansion
if self._is_short_prompt(prompt, threshold=5):
return ExpandStrategy.DETAILED
# Creative tasks benefit from creative expansion
if self._is_creative_task(prompt):
return ExpandStrategy.CREATIVE
# Task prompts get structured expansion
if self._is_task_prompt(prompt):
return ExpandStrategy.STRUCTURED
# Default to basic
return ExpandStrategy.BASIC
def expand(
self,
prompt: str,
strategy: ExpandStrategy = ExpandStrategy.AUTO,
context: Optional[str] = None
) -> ExpandResult:
"""
Expand a prompt using the specified strategy.
All operations go through an internal Agent which automatically handles
tool calling when tools are provided. The agent decides when to use tools
based on the prompt context.
Args:
prompt: The original user prompt
strategy: Expansion strategy to use
context: Additional context to help with expansion
Returns:
ExpandResult containing the expanded prompt
"""
if self.verbose:
self.logger.debug(f"Expanding prompt: '{prompt}' with strategy: {strategy.value}")
if self.tools:
print(f"[cyan]Agent has {len(self.tools)} tools available (will use if needed)...[/cyan]")
# Auto-detect strategy if needed
if strategy == ExpandStrategy.AUTO:
strategy = self._detect_strategy(prompt)
if self.verbose:
self.logger.debug(f"Auto-detected strategy: {strategy.value}")
# Build the expansion prompt
if strategy == ExpandStrategy.AUTO:
# Fallback if auto-detect returns AUTO
strategy = ExpandStrategy.DETAILED
expansion_prompt = self.PROMPTS[strategy.value].format(
prompt=prompt,
context=context or ""
)
# Call the agent
expanded = self._call_agent(expansion_prompt).strip()
# If expansion failed, return original
if not expanded:
expanded = prompt
return ExpandResult(
original_prompt=prompt,
expanded_prompt=expanded,
strategy_used=strategy,
metadata={
"original_length": len(prompt),
"expanded_length": len(expanded),
"has_context": bool(context),
"has_tools": bool(self.tools)
}
)
# Convenience methods for direct strategy access
def expand_basic(self, prompt: str, context: Optional[str] = None) -> ExpandResult:
"""Convenience method for basic expansion."""
return self.expand(prompt, strategy=ExpandStrategy.BASIC, context=context)
def expand_detailed(self, prompt: str, context: Optional[str] = None) -> ExpandResult:
"""Convenience method for detailed expansion."""
return self.expand(prompt, strategy=ExpandStrategy.DETAILED, context=context)
def expand_structured(self, prompt: str, context: Optional[str] = None) -> ExpandResult:
"""Convenience method for structured expansion."""
return self.expand(prompt, strategy=ExpandStrategy.STRUCTURED, context=context)
def expand_creative(self, prompt: str, context: Optional[str] = None) -> ExpandResult:
"""Convenience method for creative expansion."""
return self.expand(prompt, strategy=ExpandStrategy.CREATIVE, context=context)
@@ -110,20 +110,23 @@ class QueryRewriterAgent:
# Default prompts for each strategy
PROMPTS = {
"basic": """You are an expert at rewriting search queries for better retrieval.
"basic": """You are an expert at improving prompts and queries.
Given the following query, rewrite it to be clearer, more specific, and better suited for searching a knowledge base.
Given the following input, improve it while preserving its original intent and type.
Guidelines:
- If it's a TASK (e.g., "write X", "create Y", "generate Z"), keep it as a task - do NOT convert to a question
- If it's a QUESTION (e.g., "what is X?", "how does Y work?"), keep it as a question
- Expand abbreviations and acronyms
- Fix typos and grammatical errors
- Add relevant context if the query is too short
- Transform keyword searches into natural questions
- Keep the original intent intact
- Add relevant context if too short
- Keep the original intent and action intact
Query: {query}
IMPORTANT: If the input is a command/task to DO something, the output must also be a command/task to DO that thing.
Rewritten query:""",
Input: {query}
Improved version:""",
"hyde": """You are an expert at generating hypothetical documents.
@@ -198,21 +201,23 @@ Follow-up query: {query}
Standalone question:""",
"auto": """You are an expert at analyzing and rewriting queries.
"auto": """You are an expert at analyzing and improving prompts and queries.
Analyze the following query and determine the best rewriting strategy, then apply it.
Analyze the following input and improve it while preserving its original intent and type.
Query types to consider:
1. Short/keyword queries → Expand into full questions
2. Ambiguous queries → Clarify with context
3. Complex multi-part queries → Break into sub-queries
4. Follow-up queries → Resolve references from context
5. Well-formed queries → Minor optimization only
Input types to consider:
1. TASK/COMMAND (write, create, generate, build) → Keep as a task, make it clearer
2. QUESTION (what, how, why, when) → Keep as a question, make it more specific
3. Short/keyword inputs → Expand with context but preserve type
4. Complex multi-part inputs → Clarify structure
5. Well-formed inputs → Minor optimization only
Query: {query}
IMPORTANT: Never convert a task into a question. "Write X" should remain "Write X", not "How to write X?"
Input: {query}
{context}
Analysis and rewritten query:"""
Improved version:"""
}
def __init__(
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.0.169"
version = "0.0.170"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
requires-python = ">=3.10"
authors = [
@@ -0,0 +1,316 @@
"""
Tests for PromptExpanderAgent
Run with: python -m pytest tests/test_prompt_expander_agent.py -v
"""
import pytest
from unittest.mock import patch, MagicMock
class TestPromptExpanderImports:
"""Test that all imports work correctly."""
def test_import_from_package(self):
"""Test importing from main package."""
from praisonaiagents import PromptExpanderAgent, ExpandStrategy, ExpandResult
assert PromptExpanderAgent is not None
assert ExpandStrategy is not None
assert ExpandResult is not None
def test_import_from_agent_module(self):
"""Test importing from agent module."""
from praisonaiagents.agent import PromptExpanderAgent, ExpandStrategy, ExpandResult
assert PromptExpanderAgent is not None
assert ExpandStrategy is not None
assert ExpandResult is not None
class TestExpandStrategy:
"""Test ExpandStrategy enum."""
def test_strategy_values(self):
"""Test all strategy values exist."""
from praisonaiagents import ExpandStrategy
assert ExpandStrategy.BASIC.value == "basic"
assert ExpandStrategy.DETAILED.value == "detailed"
assert ExpandStrategy.STRUCTURED.value == "structured"
assert ExpandStrategy.CREATIVE.value == "creative"
assert ExpandStrategy.AUTO.value == "auto"
class TestExpandResult:
"""Test ExpandResult dataclass."""
def test_result_creation(self):
"""Test creating an ExpandResult."""
from praisonaiagents import ExpandResult, ExpandStrategy
result = ExpandResult(
original_prompt="write a movie script",
expanded_prompt="Write a compelling movie script...",
strategy_used=ExpandStrategy.BASIC
)
assert result.original_prompt == "write a movie script"
assert result.expanded_prompt == "Write a compelling movie script..."
assert result.strategy_used == ExpandStrategy.BASIC
def test_result_with_metadata(self):
"""Test ExpandResult with metadata."""
from praisonaiagents import ExpandResult, ExpandStrategy
result = ExpandResult(
original_prompt="test",
expanded_prompt="expanded test",
strategy_used=ExpandStrategy.DETAILED,
metadata={"word_count": 50, "has_context": True}
)
assert result.metadata["word_count"] == 50
assert result.metadata["has_context"] is True
class TestPromptExpanderAgentInit:
"""Test PromptExpanderAgent initialization."""
def test_default_initialization(self):
"""Test agent initializes with defaults."""
from praisonaiagents import PromptExpanderAgent
agent = PromptExpanderAgent()
assert agent.name == "PromptExpanderAgent"
assert agent.model == "gpt-4o-mini"
assert not agent.verbose
assert agent.temperature == 0.7 # Higher for creativity
def test_custom_initialization(self):
"""Test agent initializes with custom values."""
from praisonaiagents import PromptExpanderAgent
agent = PromptExpanderAgent(
name="CustomExpander",
model="gpt-4o",
verbose=True,
temperature=0.5
)
assert agent.name == "CustomExpander"
assert agent.model == "gpt-4o"
assert agent.verbose
assert agent.temperature == 0.5
def test_initialization_with_tools(self):
"""Test agent initializes with tools."""
from praisonaiagents import PromptExpanderAgent
def mock_tool(query: str) -> str:
return "mock result"
agent = PromptExpanderAgent(tools=[mock_tool])
assert len(agent.tools) == 1
class TestPromptExpansion:
"""Test prompt expansion functionality."""
def test_expand_preserves_task_intent(self):
"""Test that expansion preserves task intent (not converting to question)."""
from praisonaiagents import PromptExpanderAgent, ExpandResult
agent = PromptExpanderAgent()
# Mock the internal agent call
with patch.object(agent, '_call_agent') as mock_call:
mock_call.return_value = "Write a compelling three-line movie script that captures a dramatic moment with vivid imagery and emotional depth."
result = agent.expand("write a movie script in 3 lines")
assert result.original_prompt == "write a movie script in 3 lines"
assert "Write" in result.expanded_prompt # Should start with action verb
assert "?" not in result.expanded_prompt # Should NOT be a question
def test_expand_adds_detail(self):
"""Test that expansion adds meaningful detail."""
from praisonaiagents import PromptExpanderAgent
agent = PromptExpanderAgent()
with patch.object(agent, '_call_agent') as mock_call:
mock_call.return_value = "Create a detailed blog post about artificial intelligence trends in 2025, covering key developments in large language models, multimodal AI, and autonomous agents. Include specific examples and expert predictions."
result = agent.expand("write about AI")
# Expanded should be longer than original
assert len(result.expanded_prompt) > len(result.original_prompt)
class TestExpandStrategies:
"""Test different expansion strategies."""
def test_basic_strategy(self):
"""Test BASIC expansion strategy."""
from praisonaiagents import PromptExpanderAgent, ExpandStrategy
agent = PromptExpanderAgent()
with patch.object(agent, '_call_agent') as mock_call:
mock_call.return_value = "Write a movie script in three lines."
result = agent.expand("movie script 3 lines", strategy=ExpandStrategy.BASIC)
assert result.strategy_used == ExpandStrategy.BASIC
mock_call.assert_called_once()
def test_detailed_strategy(self):
"""Test DETAILED expansion strategy."""
from praisonaiagents import PromptExpanderAgent, ExpandStrategy
agent = PromptExpanderAgent()
with patch.object(agent, '_call_agent') as mock_call:
mock_call.return_value = """Write a compelling three-line movie script that:
- Opens with a hook that immediately grabs attention
- Builds tension or emotion in the middle line
- Delivers a powerful, memorable conclusion
Include vivid imagery, strong verbs, and emotional resonance."""
result = agent.expand("movie script 3 lines", strategy=ExpandStrategy.DETAILED)
assert result.strategy_used == ExpandStrategy.DETAILED
def test_structured_strategy(self):
"""Test STRUCTURED expansion strategy."""
from praisonaiagents import PromptExpanderAgent, ExpandStrategy
agent = PromptExpanderAgent()
with patch.object(agent, '_call_agent') as mock_call:
mock_call.return_value = """Task: Write a three-line movie script
Format: Three lines, each serving a narrative purpose
Requirements:
- Line 1: Setup/Hook
- Line 2: Conflict/Development
- Line 3: Resolution/Twist
Style: Cinematic, impactful"""
result = agent.expand("movie script 3 lines", strategy=ExpandStrategy.STRUCTURED)
assert result.strategy_used == ExpandStrategy.STRUCTURED
def test_creative_strategy(self):
"""Test CREATIVE expansion strategy."""
from praisonaiagents import PromptExpanderAgent, ExpandStrategy
agent = PromptExpanderAgent()
with patch.object(agent, '_call_agent') as mock_call:
mock_call.return_value = "Craft an unforgettable three-line movie script that transports the reader into a vivid world. Let each line breathe with cinematic tension - a hook that grabs, a twist that surprises, and a finale that lingers in the mind long after reading."
result = agent.expand("movie script 3 lines", strategy=ExpandStrategy.CREATIVE)
assert result.strategy_used == ExpandStrategy.CREATIVE
def test_auto_strategy_detection(self):
"""Test AUTO strategy detects appropriate expansion type."""
from praisonaiagents import PromptExpanderAgent, ExpandStrategy
agent = PromptExpanderAgent()
# Short prompts should get DETAILED expansion
strategy = agent._detect_strategy("AI")
assert strategy in [ExpandStrategy.BASIC, ExpandStrategy.DETAILED]
# Task prompts should preserve task nature
strategy = agent._detect_strategy("write a poem about love")
assert strategy is not None
class TestToolsIntegration:
"""Test tools integration with PromptExpanderAgent."""
def test_expand_with_tools(self):
"""Test expansion with tools for context gathering."""
from praisonaiagents import PromptExpanderAgent
def search_tool(query: str) -> str:
return "Latest AI trends: LLMs, multimodal, agents"
agent = PromptExpanderAgent(tools=[search_tool])
assert len(agent.tools) == 1
def test_tools_passed_to_internal_agent(self):
"""Test that tools are passed to the internal agent."""
from praisonaiagents import PromptExpanderAgent
def mock_tool(query: str) -> str:
return "result"
agent = PromptExpanderAgent(tools=[mock_tool])
# Access the lazy-initialized agent
internal_agent = agent.agent
# The internal agent should have the tools
assert internal_agent.tools is not None
class TestConvenienceMethods:
"""Test convenience methods."""
def test_convenience_methods_exist(self):
"""Test all convenience methods exist."""
from praisonaiagents import PromptExpanderAgent
agent = PromptExpanderAgent()
assert hasattr(agent, 'expand')
assert hasattr(agent, 'expand_basic')
assert hasattr(agent, 'expand_detailed')
assert hasattr(agent, 'expand_structured')
assert hasattr(agent, 'expand_creative')
class TestEdgeCases:
"""Test edge cases."""
def test_empty_prompt(self):
"""Test handling of empty prompt."""
from praisonaiagents import PromptExpanderAgent
agent = PromptExpanderAgent()
with patch.object(agent, '_call_agent') as mock_call:
mock_call.return_value = ""
result = agent.expand("")
# Should return original if expansion fails
assert result.expanded_prompt == "" or result.original_prompt == ""
def test_already_detailed_prompt(self):
"""Test that already detailed prompts are minimally modified."""
from praisonaiagents import PromptExpanderAgent
agent = PromptExpanderAgent()
detailed_prompt = """Write a comprehensive blog post about machine learning,
covering supervised learning, unsupervised learning, and reinforcement learning.
Include code examples in Python and real-world applications."""
with patch.object(agent, '_call_agent') as mock_call:
# For detailed prompts, minimal expansion expected
mock_call.return_value = detailed_prompt + " Ensure clarity and technical accuracy."
result = agent.expand(detailed_prompt)
# Should not drastically change already detailed prompts
assert result.original_prompt == detailed_prompt
if __name__ == "__main__":
pytest.main([__file__, "-v"])
+45 -11
View File
@@ -718,6 +718,47 @@ agent = Agent(tools=["my_tool"]) # Works automatically!
```
## Prompt Expansion
Expand short prompts into detailed, actionable prompts:
### CLI Usage
```bash
# Expand a short prompt into detailed prompt
praisonai "write a movie script in 3 lines" --expand-prompt
# With verbose output
praisonai "blog about AI" --expand-prompt -v
# With tools for context gathering
praisonai "latest AI trends" --expand-prompt --expand-tools tools.py
# Combine with query rewrite
praisonai "AI news" --query-rewrite --expand-prompt
```
### Programmatic Usage
```python
from praisonaiagents import PromptExpanderAgent, ExpandStrategy
# Basic usage
agent = PromptExpanderAgent()
result = agent.expand("write a movie script in 3 lines")
print(result.expanded_prompt)
# With specific strategy
result = agent.expand("blog about AI", strategy=ExpandStrategy.DETAILED)
# Available strategies: BASIC, DETAILED, STRUCTURED, CREATIVE, AUTO
```
**Key Difference:**
- `--query-rewrite`: Optimizes queries for **search/retrieval** (RAG)
- `--expand-prompt`: Expands prompts for **detailed task execution**
## Development:
Below is used for development only.
@@ -735,21 +776,14 @@ uv pip install -r pyproject.toml --extra code
uv pip install -r pyproject.toml --extra "crewai,autogen"
```
### Version Bump
### Bump and Release
```bash
# From project root
python src/praisonai/scripts/bump_version.py 2.2.96
# From project root - bumps version and releases in one command
python src/praisonai/scripts/bump_and_release.py 2.2.99
# With praisonaiagents dependency
python src/praisonai/scripts/bump_version.py 2.2.96 --agents 0.0.167
```
### Release
```bash
# From project root (runs uv lock, build, git tag, gh release)
python src/praisonai/scripts/release.py
python src/praisonai/scripts/bump_and_release.py 2.2.99 --agents 0.0.169
# Then publish
cd src/praisonai && uv publish
+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.99.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.99.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.0.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.3.0.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
+96
View File
@@ -579,6 +579,8 @@ class PraisonAI:
parser.add_argument("--research", action="store_true", help="Run deep research on a topic")
parser.add_argument("--query-rewrite", action="store_true", help="Rewrite query for better results (works with any command)")
parser.add_argument("--rewrite-tools", type=str, help="Tools for query rewriter (e.g., 'internet_search' or path to tools.py)")
parser.add_argument("--expand-prompt", action="store_true", help="Expand short prompt into detailed prompt (works with any command)")
parser.add_argument("--expand-tools", type=str, help="Tools for prompt expander (e.g., 'internet_search' or path to tools.py)")
parser.add_argument("--tools", "-t", type=str, help="Path to tools.py file for research agent")
parser.add_argument("--save", "-s", action="store_true", help="Save research output to file (output/research/)")
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output for research")
@@ -839,12 +841,106 @@ class PraisonAI:
verbose = getattr(self.args, 'verbose', False)
return self._rewrite_query(query, rewrite_tools, verbose)
def _expand_prompt(self, prompt: str, expand_tools: str = None, verbose: bool = False) -> str:
"""
Expand prompt using PromptExpanderAgent.
Args:
prompt: The prompt to expand
expand_tools: Tool names (comma-separated) or path to tools.py
verbose: Enable verbose output
Returns:
Expanded prompt or original if expansion fails
"""
try:
from praisonaiagents import PromptExpanderAgent, ExpandStrategy
from rich import print
print("[bold cyan]Expanding prompt for detailed execution...[/bold cyan]")
# Load expand tools if specified
expand_tools_list = []
if expand_tools:
if os.path.isfile(expand_tools):
# Load from file
try:
import inspect
import importlib.util
spec = importlib.util.spec_from_file_location("expand_tools_module", expand_tools)
if spec and spec.loader:
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
for name, obj in inspect.getmembers(module):
if inspect.isfunction(obj) and not name.startswith('_'):
expand_tools_list.append(obj)
if expand_tools_list:
print(f"[cyan]Loaded {len(expand_tools_list)} tools for prompt expander[/cyan]")
except Exception as e:
print(f"[yellow]Warning: Failed to load expand tools: {e}[/yellow]")
else:
# Treat as comma-separated tool names
try:
from praisonaiagents.tools import TOOL_MAPPINGS
import praisonaiagents.tools as tools_module
tool_names = [t.strip() for t in expand_tools.split(',')]
for tool_name in tool_names:
if tool_name in TOOL_MAPPINGS:
try:
tool = getattr(tools_module, tool_name)
expand_tools_list.append(tool)
except Exception as e:
print(f"[yellow]Warning: Failed to load expand tool '{tool_name}': {e}[/yellow]")
else:
print(f"[yellow]Warning: Unknown expand tool '{tool_name}'[/yellow]")
if expand_tools_list:
print(f"[cyan]Using expand tools: {', '.join(tool_names)}[/cyan]")
except ImportError:
print("[yellow]Warning: Could not import tools module[/yellow]")
expander = PromptExpanderAgent(
model="gpt-4o-mini",
verbose=verbose,
tools=expand_tools_list if expand_tools_list else None
)
result = expander.expand(prompt, strategy=ExpandStrategy.AUTO)
expanded = result.expanded_prompt
print(f"[cyan]Original:[/cyan] {prompt}")
print(f"[cyan]Expanded:[/cyan] {expanded}")
return expanded
except ImportError:
from rich import print
print("[yellow]Warning: PromptExpanderAgent not available, using original prompt[/yellow]")
return prompt
except Exception as e:
from rich import print
print(f"[yellow]Warning: Prompt expansion failed ({e}), using original prompt[/yellow]")
return prompt
def _expand_prompt_if_enabled(self, prompt: str) -> str:
"""
Expand prompt using PromptExpanderAgent if --expand-prompt is enabled.
Returns the expanded prompt or original if expansion is disabled/fails.
"""
if not hasattr(self, 'args') or not getattr(self.args, 'expand_prompt', False):
return prompt
expand_tools = getattr(self.args, 'expand_tools', None)
verbose = getattr(self.args, 'verbose', False)
return self._expand_prompt(prompt, expand_tools, verbose)
def handle_direct_prompt(self, prompt):
"""
Handle direct prompt by creating a single agent and running it.
"""
# Apply query rewriting if enabled
prompt = self._rewrite_query_if_enabled(prompt)
# Apply prompt expansion if enabled
prompt = self._expand_prompt_if_enabled(prompt)
if PRAISONAI_AVAILABLE:
agent_config = {
+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.99 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.3.0 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+1 -1
View File
@@ -1 +1 @@
__version__ = "2.2.99"
__version__ = "2.3.0"
+1 -1
View File
@@ -12,7 +12,7 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
"praisonaiagents>=0.0.169",
"praisonaiagents>=0.0.170",
"python-dotenv>=0.19.0",
"instructor>=1.3.3",
"PyYAML>=6.0",
+219
View File
@@ -0,0 +1,219 @@
#!/usr/bin/env python3
"""
Combined bump version and release script for PraisonAI package.
Usage:
python src/praisonai/scripts/bump_and_release.py 2.2.99
Or with optional praisonaiagents version:
python src/praisonai/scripts/bump_and_release.py 2.2.99 --agents 0.0.169
This script:
1. Bumps version in all required files
2. Copies root README.md to package dir
3. Runs uv lock & uv build
4. Commits changes
5. Creates git tag
6. Pushes to GitHub
7. Creates GitHub release (latest)
"""
import re
import sys
import argparse
import subprocess
import shutil
from pathlib import Path
def get_project_root() -> Path:
"""Get the project root directory (praisonai-package)."""
return Path(__file__).parent.parent.parent.parent
def get_praisonai_dir() -> Path:
"""Get the praisonai package directory."""
return get_project_root() / "src/praisonai"
def run(cmd: list[str], cwd: Path | None = None, check: bool = True) -> subprocess.CompletedProcess:
"""Run a command and print it."""
print(f"$ {' '.join(cmd)}")
result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True)
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
if check and result.returncode != 0:
print(f"❌ Command failed with exit code {result.returncode}")
sys.exit(1)
return result
def update_file(filepath: Path, patterns: list[tuple[str, str]], root: Path) -> bool:
"""Update a file with the given regex patterns and replacements."""
if not filepath.exists():
print(f" ⚠️ File not found: {filepath}")
return False
content = filepath.read_text()
original = content
for pattern, replacement in patterns:
content = re.sub(pattern, replacement, content)
if content != original:
filepath.write_text(content)
print(f" ✅ Updated: {filepath.relative_to(root)}")
return True
else:
print(f" ⏭️ No changes: {filepath.relative_to(root)}")
return False
def bump_version(new_version: str, agents_version: str | None = None):
"""Bump version in all required files."""
root = get_project_root()
praisonai_dir = get_praisonai_dir()
print(f"\n🚀 Bumping PraisonAI version to {new_version}\n")
# 1. Update version.py (single source of truth)
print("📦 Python Package:")
update_file(
praisonai_dir / "praisonai/version.py",
[(r'__version__ = "[^"]+"', f'__version__ = "{new_version}"')],
root
)
# 2. Update deploy.py (Dockerfile template)
print("\n🐳 Deploy Script:")
update_file(
praisonai_dir / "praisonai/deploy.py",
[(r'praisonai==[0-9.]+', f'praisonai=={new_version}')],
root
)
# 3. Update Dockerfiles
print("\n🐳 Dockerfiles:")
dockerfiles = [
"docker/Dockerfile",
"docker/Dockerfile.chat",
"docker/Dockerfile.dev",
"docker/Dockerfile.ui",
]
for dockerfile in dockerfiles:
update_file(
root / dockerfile,
[(r'"praisonai>=[0-9.]+"', f'"praisonai>={new_version}"')],
root
)
# 4. Update Homebrew formula
print("\n🍺 Homebrew Formula:")
update_file(
praisonai_dir / "praisonai.rb",
[(r'v[0-9]+\.[0-9]+\.[0-9]+', f'v{new_version}')],
root
)
# 5. Update praisonaiagents dependency if specified
if agents_version:
print(f"\n📦 Updating praisonaiagents dependency to {agents_version}:")
update_file(
praisonai_dir / "pyproject.toml",
[(r'praisonaiagents>=[0-9.]+', f'praisonaiagents>={agents_version}')],
root
)
print("\n✨ Version bump complete!")
def release(version: str):
"""Run the release process."""
root = get_project_root()
praisonai_dir = get_praisonai_dir()
tag = f"v{version}"
print(f"\n🚀 Releasing PraisonAI {tag}\n")
# 1. Copy root README.md to package dir for PyPI
print("📄 Copying README.md...")
root_readme = root / "README.md"
pkg_readme = praisonai_dir / "README.md"
if root_readme.exists():
shutil.copy(root_readme, pkg_readme)
print(f" ✅ Copied {root_readme} -> {pkg_readme}")
# 2. uv lock
print("\n📦 Running uv lock...")
run(["uv", "lock"], cwd=praisonai_dir)
# 3. uv build
print("\n🔨 Running uv build...")
run(["uv", "build"], cwd=praisonai_dir)
# 4. Git add and commit
print("\n📝 Committing changes...")
run(["git", "add", "-A"], cwd=root)
run(["git", "commit", "-m", f"Release {tag}"], cwd=root, check=False)
# 5. Create git tag
print(f"\n🏷️ Creating tag {tag}...")
run(["git", "tag", "-f", tag], cwd=root)
# 6. Push to GitHub
print("\n⬆️ Pushing to GitHub...")
run(["git", "push"], cwd=root)
run(["git", "push", "--tags", "-f"], cwd=root)
# 7. Create GitHub release
print(f"\n🎉 Creating GitHub release {tag}...")
run([
"gh", "release", "create", tag,
"--title", f"PraisonAI {tag}",
"--notes", f"Release {tag}",
"--latest"
], cwd=root)
print(f"\n✅ Released PraisonAI {tag}")
print("\nNext step:")
print(" cd src/praisonai && uv publish")
def main():
parser = argparse.ArgumentParser(
description="Bump version and release PraisonAI package"
)
parser.add_argument(
"version",
help="New version number (e.g., 2.2.99)"
)
parser.add_argument(
"--agents", "-a",
help="Optional: Update praisonaiagents dependency version (e.g., 0.0.169)",
default=None
)
args = parser.parse_args()
# Validate version format
if not re.match(r'^\d+\.\d+\.\d+$', args.version):
print(f"❌ Invalid version format: {args.version}")
print(" Expected format: X.Y.Z (e.g., 2.2.99)")
sys.exit(1)
if args.agents and not re.match(r'^\d+\.\d+\.\d+$', args.agents):
print(f"❌ Invalid agents version format: {args.agents}")
print(" Expected format: X.Y.Z (e.g., 0.0.169)")
sys.exit(1)
# Run bump version
bump_version(args.version, args.agents)
# Run release
release(args.version)
if __name__ == "__main__":
main()
@@ -72,10 +72,10 @@ roles:
except Exception as e:
pytest.fail(f"CrewAI real test failed: {e}")
@pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="OPENAI_API_KEY not set")
def test_crewai_environment_check(self):
"""Verify CrewAI environment is properly configured"""
# Check API key is available
assert os.getenv("OPENAI_API_KEY"), "OPENAI_API_KEY required for real tests"
# Check API key is available (already checked by skipif)
# Check CrewAI can be imported
try:
+5 -2
View File
@@ -59,8 +59,11 @@ class TestPraisonAICommandLine(unittest.TestCase):
# In CI, it should be set by the workflow
print("Warning: OPENAI_API_KEY not found in CLI test environment. API calls might fail if not mocked.")
result = subprocess.run(command, shell=True, capture_output=True, text=True, env=env)
return result.stdout + result.stderr
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True, env=env, timeout=120)
return result.stdout + result.stderr
except subprocess.TimeoutExpired:
return "TIMEOUT: Command exceeded 120 seconds"
@pytest.mark.real
def test_praisonai_command(self):
+4 -4
View File
@@ -4197,7 +4197,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.169" },
{ name = "praisonaiagents", specifier = ">=0.0.170" },
{ 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" },
@@ -4252,7 +4252,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.169"
version = "0.0.170"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@@ -4261,9 +4261,9 @@ dependencies = [
{ name = "pydantic" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4e/0c/94820a01306d53ce99f2b8d0f424566e74fe883aaca0e1a5c81f8364dddf/praisonaiagents-0.0.169.tar.gz", hash = "sha256:0774dd1fd8a5066e4ab2d612153d07bf1ac275105c10396730a9b542d4060ff3", size = 319791, upload-time = "2025-12-15T10:09:24.429Z" }
sdist = { url = "https://files.pythonhosted.org/packages/20/35/a45a1f616bc0a661b9816b7cd7afdace803bacdc91cbacc6080960e7d1fb/praisonaiagents-0.0.170.tar.gz", hash = "sha256:b0114255b82184657af074f55316238c1ddcb7105aab6b23817d0315830d2082", size = 324448, upload-time = "2025-12-15T11:12:25.135Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d6/f4/9342ee5340a2fdf1e3b9b8c1d10d9f2962c4fc577925c41b81db7b9beea1/praisonaiagents-0.0.169-py3-none-any.whl", hash = "sha256:ea491a2097224544f7ebb19b19daaabfc814c22bf92fb2adeb8e7d3f33d1233c", size = 333017, upload-time = "2025-12-15T10:09:23.016Z" },
{ url = "https://files.pythonhosted.org/packages/b1/cc/6a6c8da467f400ab51201bd0008948326483ccae89d370a32b46e66f87ae/praisonaiagents-0.0.170-py3-none-any.whl", hash = "sha256:5930c1a9a4d1b6ae5f03e471351701b5c0af21a8312fdb2c9b756a89e88cdd4d", size = 337210, upload-time = "2025-12-15T11:12:23.518Z" },
]
[[package]]