mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-10 03:31:28 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3aa1b825e4 | ||
|
|
b4ee30d837 | ||
|
|
825648aead | ||
|
|
1532c31e72 | ||
|
|
b313fefd86 | ||
|
|
435e1afb55 | ||
|
|
4cb3c0565a |
No files matched your search
@@ -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: |
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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]"
|
||||
|
||||
|
||||
@@ -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]" \
|
||||
|
||||
@@ -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__(
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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]"
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 @@
|
||||
__version__ = "2.2.99"
|
||||
__version__ = "2.3.0"
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
Generated
+4
-4
@@ -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]]
|
||||
|
||||
Reference in new issue
Block a user