Compare commits

...
2 Commits
Author SHA1 Message Date
Mervin Praison 6df3e32640 Release v3.8.2 2026-01-13 04:16:08 +00:00
github-actions[bot] fb36e1c50c docs: auto-update api.md [skip ci] 2026-01-12 17:00:39 +00:00
30 changed files with 522 additions and 119 deletions

No files matched your search

+12
View File
@@ -315,6 +315,8 @@ Methods:
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">compute_quality_score</a>(completeness: float, relevance: float, clarity: float, accuracy: float, weights: Dict[str, float] = None) -> float</code>
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">finalize_task_output</a>(content: str, agent_name: str, quality_score: float, threshold: float = 0.7, metrics: Dict[str, Any] = None, task_id: str = None)</code>
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">get_all_memories</a>() -> List[Dict[str, Any]]</code>
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">get_learn_context</a>() -> str</code>
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">learn</a>()</code>
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">reset_all</a>()</code>
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">reset_entity_only</a>()</code>
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">reset_long_term</a>()</code>
@@ -616,8 +618,11 @@ Methods:
* <code title="cli">praisonai context clear <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context compact <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context export <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context grep <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context list <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context show <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context stats <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context tail <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai debug acp <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
* <code title="cli">praisonai debug debug-callback <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
* <code title="cli">praisonai debug interactive <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
@@ -672,14 +677,19 @@ Methods:
* <code title="cli">praisonai lsp status <a href="./src/praisonai/praisonai/cli/commands/lsp.py">--help</a></code>
* <code title="cli">praisonai lsp stop <a href="./src/praisonai/praisonai/cli/commands/lsp.py">--help</a></code>
* <code title="cli">praisonai mcp add <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai mcp describe <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai mcp list <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai mcp mcp-callback <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai mcp remove <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai mcp status <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai mcp sync <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai mcp test <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai mcp tools <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
* <code title="cli">praisonai memory add <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
* <code title="cli">praisonai memory clear <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
* <code title="cli">praisonai memory search <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
* <code title="cli">praisonai memory show <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
* <code title="cli">praisonai memory status <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
* <code title="cli">praisonai package install <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
* <code title="cli">praisonai package list <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
* <code title="cli">praisonai package uninstall <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
@@ -730,8 +740,10 @@ Methods:
* <code title="cli">praisonai session resume <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
* <code title="cli">praisonai session show <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
* <code title="cli">praisonai skills create <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
* <code title="cli">praisonai skills info <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
* <code title="cli">praisonai skills install <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
* <code title="cli">praisonai skills list <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
* <code title="cli">praisonai skills search <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
* <code title="cli">praisonai skills validate <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
* <code title="cli">praisonai standardise check <a href="./src/praisonai/praisonai/cli/commands/standardise.py">--help</a></code>
* <code title="cli">praisonai standardise fix <a href="./src/praisonai/praisonai/cli/commands/standardise.py">--help</a></code>
+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>=3.8.1" \
"praisonai>=3.8.2" \
"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>=3.8.1" \
"praisonai>=3.8.2" \
"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>=3.8.1" \
"praisonai>=3.8.2" \
"praisonai[ui]" \
"praisonai[crewai]"
+2 -12
View File
@@ -1,14 +1,4 @@
#!/usr/bin/env python3
from praisonaiagents import Agent
print("Creating agent...")
agent = Agent(
instructions="You are a helpful assistant"
)
print("Starting agent...")
agent = Agent(instructions="You are a helpful assistant")
result = agent.start("Why is the sky blue?")
print("Agent completed successfully!")
print(f"Result: {result}")
print(result)
@@ -297,7 +297,7 @@ class Agent:
backstory: Background context shaping personality and decisions.
instructions: Direct instructions (overrides role/goal/backstory). Recommended for simple agents.
llm: Model name string ("gpt-4o", "anthropic/claude-3-sonnet") or LLM object.
Defaults to OPENAI_MODEL_NAME env var or "gpt-5-nano".
Defaults to OPENAI_MODEL_NAME env var or "gpt-4o-mini".
model: Alias for llm parameter.
function_calling_llm: Dedicated LLM for function calling. Deprecated: use llm=.
llm_config: LLM configuration dict. Deprecated: use llm=.
@@ -844,7 +844,7 @@ class Agent:
self.llm_instance = LLM(**llm_config)
else:
# Create LLM with model string and base_url
model_name = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-5-nano')
model_name = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
self.llm_instance = LLM(
model=model_name,
base_url=base_url,
@@ -907,7 +907,7 @@ class Agent:
) from e
# Otherwise, fall back to OpenAI environment/name
else:
self.llm = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-5-nano')
self.llm = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
# Handle tools parameter - ensure it's always a list
if callable(tools):
# If a single function/callable is passed, wrap it in a list
@@ -953,7 +953,7 @@ class Agent:
self.min_reflect = min_reflect
self.reflect_prompt = reflect_prompt
# Use the same model selection logic for reflect_llm
self.reflect_llm = reflect_llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-5-nano')
self.reflect_llm = reflect_llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
self._console = None # Lazy load console when needed
# Initialize system prompt
@@ -1743,7 +1743,7 @@ Your Goal: {self.goal}
elif hasattr(self, 'llm') and self.llm:
model_name = self.llm
else:
model_name = "gpt-5-nano"
model_name = "gpt-4o-mini"
return supports_web_search(model_name)
@@ -1765,7 +1765,7 @@ Your Goal: {self.goal}
elif hasattr(self, 'llm') and self.llm:
model_name = self.llm
else:
model_name = "gpt-5-nano"
model_name = "gpt-4o-mini"
return supports_web_fetch(model_name)
@@ -2009,7 +2009,7 @@ Your Goal: {self.goal}
Returns:
The LLM model/instance being used by this agent.
- For standard models: returns the model string (e.g., "gpt-5-nano")
- For standard models: returns the model string (e.g., "gpt-4o-mini")
- For custom LLM instances: returns the LLM instance object
- For provider models: returns the LLM instance object
"""
@@ -2019,7 +2019,7 @@ Your Goal: {self.goal}
return self.llm
else:
# Default fallback
return "gpt-5-nano"
return "gpt-4o-mini"
def _ensure_knowledge_processed(self):
"""Ensure knowledge is initialized and processed when first accessed."""
@@ -3719,7 +3719,7 @@ Your Goal: {self.goal}"""
if self.verbose:
logging.debug(f"Agent {self.name} final response: {response_text}")
# Return only reasoning content if reasoning_steps is True
if reasoning_steps and hasattr(response.choices[0].message, 'reasoning_content'):
if reasoning_steps and hasattr(response.choices[0].message, 'reasoning_content') and response.choices[0].message.reasoning_content:
# Apply guardrail to reasoning content
try:
validated_reasoning = self._apply_guardrail_with_retry(response.choices[0].message.reasoning_content, original_prompt, temperature, tools, task_name, task_description, task_id)
@@ -4244,7 +4244,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
final_response = process_stream_chunks(chunks)
# Return only reasoning content if reasoning_steps is True
if reasoning_steps and hasattr(final_response.choices[0].message, 'reasoning_content'):
if reasoning_steps and hasattr(final_response.choices[0].message, 'reasoning_content') and final_response.choices[0].message.reasoning_content:
return final_response.choices[0].message.reasoning_content
return final_response.choices[0].message.content if final_response else full_response_text
@@ -434,7 +434,7 @@ This report contains all agent interactions and outputs from a complete ContextA
Provide comprehensive analysis that follows the PRD template principles and enables
AI assistants to implement features that perfectly match existing codebase patterns.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -515,7 +515,7 @@ codebase style and architecture following PRD template principles."""
role="Expert Manual Codebase Analysis Specialist",
goal="Perform comprehensive manual codebase analysis following PRD methodology",
instructions="""Analyze the codebase samples following PRD template methodology for complete understanding.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -566,7 +566,7 @@ Analyze following PRD principles to extract patterns, conventions, and architect
6. Design pattern implementations
7. Code complexity metrics
8. API and interface patterns""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -624,7 +624,7 @@ Extract comprehensive patterns that follow PRD template principles for implement
For each pattern, provide the pattern name, where it's used, and how to replicate it
following PRD template principles.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -674,7 +674,7 @@ patterns and best practices for first-try success."""
goal="Analyze testing patterns for comprehensive validation framework design",
instructions="""Analyze testing patterns to understand validation approaches and create
comprehensive test frameworks following PRD methodology.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -801,7 +801,7 @@ Extract testing patterns for validation framework creation following PRD princip
Confidence level for one-pass implementation
Generate PRPs following this EXACT structure for first-try implementation success.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -850,7 +850,7 @@ on the first try following PRD template principles."""
6. CODE QUALITY: Complexity analysis, maintainability
7. DOCUMENTATION VALIDATION: Documentation completeness
8. DEPENDENCY VALIDATION: Dependency analysis and security""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -897,7 +897,7 @@ following PRD template principles."""
instructions="""Compile all available documentation following PRD methodology including:
README files, API documentation, setup guides, architecture docs, and any other
relevant documentation that provides context for implementation.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -943,7 +943,7 @@ following PRD template principles."""
instructions="""Analyze integration points following PRD methodology including:
APIs, databases, external services, configuration points, and any other
integration requirements that affect implementation.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -1004,7 +1004,7 @@ following PRD template principles."""
8. DOCUMENTATION UPDATES: Documentation to create/update
9. INTEGRATION STEPS: How to integrate with existing systems
10. VALIDATION CHECKPOINTS: Validation steps at each phase""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -1302,7 +1302,7 @@ Every agent interaction has been saved for full audit trail and reproducibility.
GOAL: [extracted implementation goal]
Be precise and extract only what is explicitly mentioned or clearly implied.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -1536,7 +1536,7 @@ Note: Detailed function/class metadata not available due to content access limit
5. Documentation topics
Make the output easy for a file selection agent to understand which files contain what functionality.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -1770,7 +1770,7 @@ Focus on creating clear, structured metadata that will help with intelligent fil
["README.md", "src/auth/login.py", "config/settings.py", ...]
Maximum 50 files for efficient analysis.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -1923,7 +1923,7 @@ Maximum 50 files.""".format(goal=goal)
8. EXAMPLES: Similar features that can guide {goal} implementation
Since these files were pre-selected for relevance, provide deep analysis of how each contributes to implementing: {goal}""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -2024,7 +2024,7 @@ Since these files were pre-selected for relevance, explain how each contributes
- Success criteria for {goal}
Focus everything on successfully implementing: {goal}""",
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
@@ -2303,13 +2303,13 @@ def create_context_agent(llm: Optional[Union[str, Any]] = None, **kwargs) -> Con
Factory function to create a ContextAgent following Context Engineering and PRD methodology.
Args:
llm: Language model to use (e.g., "gpt-5-nano", "claude-3-haiku")
llm: Language model to use (e.g., "gpt-4o-mini", "claude-3-haiku")
**kwargs: Additional arguments to pass to ContextAgent constructor
Returns:
ContextAgent: Configured ContextAgent for comprehensive context generation following PRD principles
"""
if llm is None:
llm = "gpt-5-nano"
llm = "gpt-4o-mini"
return ContextAgent(llm=llm, **kwargs)
@@ -44,7 +44,7 @@ class RouterAgent(Agent):
# Initialize model router
self.model_router = model_router or ModelRouter()
self.routing_strategy = routing_strategy
self.fallback_model = fallback_model or os.getenv('OPENAI_MODEL_NAME', 'gpt-5-nano')
self.fallback_model = fallback_model or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
# Process models configuration
self.available_models = self._process_models_config(models)
@@ -363,7 +363,7 @@ class Agents:
self.variables = variables if variables else {}
# Check for manager_llm in environment variable if not provided
self.manager_llm = manager_llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-5-nano')
self.manager_llm = manager_llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
# Set logger level based on verbose
if _verbose >= 5:
@@ -103,7 +103,7 @@ class AutoAgents(Agents):
self.self_reflect = self_reflect
self.max_reflect = max_reflect
self.min_reflect = min_reflect
self.llm = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-5-nano')
self.llm = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
self.function_calling_llm = function_calling_llm
self.code_execution_mode = code_execution_mode
self.embedder_config = embedder_config
@@ -45,11 +45,13 @@ class MemoryBackend(str, Enum):
class LearnScope(str, Enum):
"""Scope for continuous learning."""
USER = "user" # Per-user learning
TEAM = "team" # Team-shared learning
ORG = "org" # Organization-wide learning
GLOBAL = "global" # Global learning
"""Scope for learning data visibility.
PRIVATE: Learning data is private to this user/agent (default, safest)
SHARED: Learning data is shared with all agents
"""
PRIVATE = "private" # Private to this user/agent
SHARED = "shared" # Shared with all agents
@dataclass
@@ -83,7 +85,7 @@ class LearnConfig:
improvements: bool = False # Self-improvement proposals
# Scope configuration
scope: Union[str, LearnScope] = LearnScope.USER
scope: Union[str, LearnScope] = LearnScope.PRIVATE
# Storage configuration
store_path: Optional[str] = None # Custom storage path
@@ -68,8 +68,8 @@ class LLM:
MODEL_WINDOWS = {
# OpenAI
"gpt-4": 6144, # 8,192 actual
"gpt-5-nano": 96000, # 128,000 actual
"gpt-5-nano": 96000, # 128,000 actual
"gpt-4o-mini": 96000, # 128,000 actual
"gpt-4o-mini": 96000, # 128,000 actual
"gpt-4-turbo": 96000, # 128,000 actual
"o1-preview": 96000, # 128,000 actual
"o1-mini": 96000, # 128,000 actual
@@ -51,7 +51,7 @@ class ModelRouter:
DEFAULT_MODELS = [
# Lightweight/cheap models for simple tasks
ModelProfile(
name="gpt-5-nano",
name="gpt-4o-mini",
provider="openai",
complexity_range=(TaskComplexity.SIMPLE, TaskComplexity.MODERATE),
cost_per_1k_tokens=0.00075, # Average of $0.00015 input, $0.0006 output
@@ -80,7 +80,7 @@ class ModelRouter:
# Mid-tier models for moderate complexity
ModelProfile(
name="gpt-5-nano",
name="gpt-4o-mini",
provider="openai",
complexity_range=(TaskComplexity.MODERATE, TaskComplexity.COMPLEX),
cost_per_1k_tokens=0.0075, # Average of $0.0025 input, $0.01 output
@@ -145,7 +145,7 @@ class ModelRouter:
preferred_providers: List of preferred providers in order
"""
self.models = models or self.DEFAULT_MODELS
self.default_model = default_model or os.getenv('OPENAI_MODEL_NAME', 'gpt-5-nano')
self.default_model = default_model or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
self.cost_threshold = cost_threshold
self.preferred_providers = preferred_providers or []
@@ -988,7 +988,7 @@ class OpenAIClient:
def create_completion(
self,
messages: List[Dict[str, Any]],
model: str = "gpt-5-nano",
model: str = "gpt-4o-mini",
temperature: float = 1.0,
stream: bool = False,
tools: Optional[List[Dict[str, Any]]] = None,
@@ -1033,7 +1033,7 @@ class OpenAIClient:
async def acreate_completion(
self,
messages: List[Dict[str, Any]],
model: str = "gpt-5-nano",
model: str = "gpt-4o-mini",
temperature: float = 1.0,
stream: bool = False,
tools: Optional[List[Dict[str, Any]]] = None,
@@ -1078,7 +1078,7 @@ class OpenAIClient:
def chat_completion_with_tools(
self,
messages: List[Dict[str, Any]],
model: str = "gpt-5-nano",
model: str = "gpt-4o-mini",
temperature: float = 1.0,
tools: Optional[List[Any]] = None,
execute_tool_fn: Optional[Callable] = None,
@@ -1266,7 +1266,7 @@ class OpenAIClient:
async def achat_completion_with_tools(
self,
messages: List[Dict[str, Any]],
model: str = "gpt-5-nano",
model: str = "gpt-4o-mini",
temperature: float = 1.0,
tools: Optional[List[Any]] = None,
execute_tool_fn: Optional[Callable] = None,
@@ -1450,7 +1450,7 @@ class OpenAIClient:
def chat_completion_with_tools_stream(
self,
messages: List[Dict[str, Any]],
model: str = "gpt-5-nano",
model: str = "gpt-4o-mini",
temperature: float = 1.0,
tools: Optional[List[Any]] = None,
execute_tool_fn: Optional[Callable] = None,
@@ -1602,7 +1602,7 @@ class OpenAIClient:
self,
messages: List[Dict[str, Any]],
response_format: BaseModel,
model: str = "gpt-5-nano",
model: str = "gpt-4o-mini",
temperature: float = 1.0,
**kwargs
) -> Any:
@@ -1636,7 +1636,7 @@ class OpenAIClient:
self,
messages: List[Dict[str, Any]],
response_format: BaseModel,
model: str = "gpt-5-nano",
model: str = "gpt-4o-mini",
temperature: float = 1.0,
**kwargs
) -> Any:
@@ -162,7 +162,7 @@ class MCP:
# Method 1: Using command and args separately
agent = Agent(
instructions="You are a helpful assistant...",
llm="gpt-5-nano",
llm="gpt-4o-mini",
tools=MCP(
command="/path/to/python",
args=["/path/to/app.py"]
@@ -172,14 +172,14 @@ class MCP:
# Method 2: Using a single command string
agent = Agent(
instructions="You are a helpful assistant...",
llm="gpt-5-nano",
llm="gpt-4o-mini",
tools=MCP("/path/to/python /path/to/app.py")
)
# Method 3: Using an SSE endpoint
agent = Agent(
instructions="You are a helpful assistant...",
llm="gpt-5-nano",
llm="gpt-4o-mini",
tools=MCP("http://localhost:8080/sse")
)
@@ -548,7 +548,7 @@ class MCP:
"""Convert the MCP tool to an OpenAI-compatible tool definition.
This method is specifically invoked by the Agent class when using
provider/model format (e.g., "openai/gpt-5-nano").
provider/model format (e.g., "openai/gpt-4o-mini").
Returns:
dict or list: OpenAI-compatible tool definition(s)
@@ -56,7 +56,7 @@ class BaseStore(ABC):
self,
store_path: Optional[str] = None,
user_id: Optional[str] = None,
scope: str = "user",
scope: str = "private",
):
self.user_id = user_id or "default"
self.scope = scope
@@ -109,7 +109,7 @@ class Memory:
},
"llm": {
"provider": "openai",
"config": {"model": "gpt-5-nano", "api_key": "..."}
"config": {"model": "gpt-4o-mini", "api_key": "..."}
},
"embedder": {
"provider": "openai",
@@ -1444,7 +1444,7 @@ class Memory:
import litellm
# Convert model name if it's in litellm format
model_name = llm or "gpt-5-nano"
model_name = llm or "gpt-4o-mini"
response = litellm.completion(
model=model_name,
@@ -1461,7 +1461,7 @@ class Memory:
client = OpenAI()
response = client.chat.completions.create(
model=llm or "gpt-5-nano",
model=llm or "gpt-4o-mini",
messages=[{
"role": "user",
"content": custom_prompt or default_prompt
@@ -326,7 +326,7 @@ class Task:
if hasattr(self.agent.llm_instance, 'model'):
llm_model = self.agent.llm_instance.model
else:
llm_model = "gpt-5-nano" # Default fallback
llm_model = "gpt-4o-mini" # Default fallback
elif hasattr(self.agent, 'llm') and self.agent.llm:
# For standard model strings
llm_model = self.agent.llm
@@ -27,7 +27,7 @@ class GenerateCOT:
def __init__(
self,
qa_pairs: Optional[Dict[str, str]] = None,
model: str = "gpt-5-nano",
model: str = "gpt-4o-mini",
api_key: Optional[str] = None,
max_attempts: int = 3,
verbose: bool = True,
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.11.7"
version = "0.11.8"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
readme = "README.md"
requires-python = ">=3.10"
@@ -0,0 +1,156 @@
"""
Unit tests for Agent output parameter with verbose preset.
Tests the fix for the bug where output="verbose" would return None
because reasoning_steps=True in the verbose preset was causing the code
to try to return reasoning_content, but this attribute exists as None
for non-reasoning models.
"""
import pytest
from unittest.mock import Mock
class TestReasoningContentLogic:
"""Test the reasoning_content check logic directly."""
def test_condition_with_none_reasoning_content(self):
"""Verify the fix: condition should be False when reasoning_content is None."""
mock_message = Mock()
mock_message.reasoning_content = None
reasoning_steps = True
# Old buggy condition:
# if reasoning_steps and hasattr(mock_message, 'reasoning_content'):
old_condition = reasoning_steps and hasattr(mock_message, 'reasoning_content')
assert old_condition == True, "Old condition would be True (the bug)"
# Fixed condition:
# if reasoning_steps and hasattr(mock_message, 'reasoning_content') and mock_message.reasoning_content:
# The truthiness of the whole expression should be False
fixed_condition = reasoning_steps and hasattr(mock_message, 'reasoning_content') and mock_message.reasoning_content
assert not fixed_condition, "Fixed condition should be falsy when reasoning_content is None"
def test_condition_with_empty_string_reasoning_content(self):
"""Verify the fix: condition should be False when reasoning_content is empty string."""
mock_message = Mock()
mock_message.reasoning_content = ""
reasoning_steps = True
# Old condition would True (bug)
old_condition = reasoning_steps and hasattr(mock_message, 'reasoning_content')
assert old_condition == True
# Fixed condition should be falsy
fixed_condition = reasoning_steps and hasattr(mock_message, 'reasoning_content') and mock_message.reasoning_content
assert not fixed_condition, "Fixed condition should be falsy when reasoning_content is empty string"
def test_condition_with_actual_reasoning_content(self):
"""Verify the fix: condition should be True when reasoning_content has content."""
mock_message = Mock()
mock_message.reasoning_content = "This is my reasoning process..."
reasoning_steps = True
# Both old and new condition should be truthy
old_condition = reasoning_steps and hasattr(mock_message, 'reasoning_content')
assert old_condition
fixed_condition = reasoning_steps and hasattr(mock_message, 'reasoning_content') and mock_message.reasoning_content
assert fixed_condition, "Fixed condition should be truthy when reasoning_content has content"
def test_condition_when_reasoning_steps_false(self):
"""Verify that when reasoning_steps is False, condition is always False."""
mock_message = Mock()
mock_message.reasoning_content = "This is my reasoning process..."
reasoning_steps = False
fixed_condition = reasoning_steps and hasattr(mock_message, 'reasoning_content') and mock_message.reasoning_content
assert not fixed_condition, "Condition should be falsy when reasoning_steps is False"
def test_if_statement_behavior_with_none(self):
"""Verify that an if statement using the condition skips when reasoning_content is None."""
mock_message = Mock()
mock_message.reasoning_content = None
reasoning_steps = True
execution_path = "not_taken"
# This is how the fixed code behaves
if reasoning_steps and hasattr(mock_message, 'reasoning_content') and mock_message.reasoning_content:
execution_path = "reasoning_path"
else:
execution_path = "regular_path"
assert execution_path == "regular_path", "Should take regular path when reasoning_content is None"
def test_if_statement_behavior_with_reasoning(self):
"""Verify that an if statement using the condition enters when reasoning_content has value."""
mock_message = Mock()
mock_message.reasoning_content = "Reasoning steps here..."
reasoning_steps = True
execution_path = "not_taken"
if reasoning_steps and hasattr(mock_message, 'reasoning_content') and mock_message.reasoning_content:
execution_path = "reasoning_path"
else:
execution_path = "regular_path"
assert execution_path == "reasoning_path", "Should take reasoning path when reasoning_content has value"
class TestVerbosePresetConfig:
"""Test that the verbose preset has the expected configuration."""
def test_verbose_preset_has_reasoning_steps_true(self):
"""Verify that the verbose preset enables reasoning_steps."""
from praisonaiagents.config.presets import OUTPUT_PRESETS
verbose_preset = OUTPUT_PRESETS.get("verbose")
assert verbose_preset is not None, "verbose preset should exist"
assert verbose_preset.get("reasoning_steps") == True, "verbose preset should have reasoning_steps=True"
def test_verbose_preset_has_verbose_true(self):
"""Verify that the verbose preset enables verbose output."""
from praisonaiagents.config.presets import OUTPUT_PRESETS
verbose_preset = OUTPUT_PRESETS.get("verbose")
assert verbose_preset.get("verbose") == True, "verbose preset should have verbose=True"
def test_verbose_preset_has_stream_false(self):
"""Verify that the verbose preset does not enable streaming by default."""
from praisonaiagents.config.presets import OUTPUT_PRESETS
verbose_preset = OUTPUT_PRESETS.get("verbose")
assert verbose_preset.get("stream") == False, "verbose preset should have stream=False"
class TestChatCompletionMessageDataclass:
"""Test the ChatCompletionMessage dataclass has reasoning_content attribute."""
def test_chat_completion_message_has_reasoning_content(self):
"""Verify ChatCompletionMessage has reasoning_content attribute that defaults to None."""
from praisonaiagents.llm.openai_client import ChatCompletionMessage
# Create without reasoning_content
message = ChatCompletionMessage(content="Test", role="assistant")
# Should have the attribute
assert hasattr(message, 'reasoning_content')
# Should default to None
assert message.reasoning_content is None
def test_chat_completion_message_with_reasoning(self):
"""Verify ChatCompletionMessage can have reasoning_content set."""
from praisonaiagents.llm.openai_client import ChatCompletionMessage
message = ChatCompletionMessage(
content="Test",
role="assistant",
reasoning_content="This is my reasoning..."
)
assert message.reasoning_content == "This is my reasoning..."
@@ -0,0 +1,104 @@
"""
Tests for LearnScope enum - verifying PRIVATE/SHARED values only.
These tests verify that:
1. LearnScope only has PRIVATE and SHARED values
2. TEAM and ORG values do NOT exist
3. LearnConfig defaults to PRIVATE scope
4. BaseStore defaults to "private" scope
"""
import pytest
class TestLearnScopeValues:
"""Test LearnScope enum has exactly the right values."""
def test_learn_scope_has_private(self):
"""LearnScope should have PRIVATE value."""
from praisonaiagents.config.feature_configs import LearnScope
assert hasattr(LearnScope, 'PRIVATE')
assert LearnScope.PRIVATE.value == "private"
def test_learn_scope_has_shared(self):
"""LearnScope should have SHARED value."""
from praisonaiagents.config.feature_configs import LearnScope
assert hasattr(LearnScope, 'SHARED')
assert LearnScope.SHARED.value == "shared"
def test_learn_scope_does_not_have_team(self):
"""LearnScope should NOT have TEAM value."""
from praisonaiagents.config.feature_configs import LearnScope
assert not hasattr(LearnScope, 'TEAM'), "TEAM should be removed from LearnScope"
def test_learn_scope_does_not_have_org(self):
"""LearnScope should NOT have ORG value."""
from praisonaiagents.config.feature_configs import LearnScope
assert not hasattr(LearnScope, 'ORG'), "ORG should be removed from LearnScope"
def test_learn_scope_does_not_have_user(self):
"""LearnScope should NOT have USER value (replaced by PRIVATE)."""
from praisonaiagents.config.feature_configs import LearnScope
assert not hasattr(LearnScope, 'USER'), "USER should be replaced by PRIVATE"
def test_learn_scope_does_not_have_global(self):
"""LearnScope should NOT have GLOBAL value (replaced by SHARED)."""
from praisonaiagents.config.feature_configs import LearnScope
assert not hasattr(LearnScope, 'GLOBAL'), "GLOBAL should be replaced by SHARED"
def test_learn_scope_has_exactly_two_values(self):
"""LearnScope should have exactly 2 values: PRIVATE and SHARED."""
from praisonaiagents.config.feature_configs import LearnScope
all_values = list(LearnScope)
assert len(all_values) == 2, f"Expected 2 values, got {len(all_values)}: {all_values}"
class TestLearnConfigDefaults:
"""Test LearnConfig uses correct default scope."""
def test_learn_config_default_scope_is_private(self):
"""LearnConfig should default to PRIVATE scope."""
from praisonaiagents.config.feature_configs import LearnConfig, LearnScope
config = LearnConfig()
assert config.scope == LearnScope.PRIVATE
def test_learn_config_scope_can_be_set_to_shared(self):
"""LearnConfig scope can be set to SHARED."""
from praisonaiagents.config.feature_configs import LearnConfig, LearnScope
config = LearnConfig(scope=LearnScope.SHARED)
assert config.scope == LearnScope.SHARED
def test_learn_config_to_dict_includes_scope(self):
"""LearnConfig.to_dict() should include scope with correct value."""
from praisonaiagents.config.feature_configs import LearnConfig, LearnScope
config = LearnConfig()
d = config.to_dict()
assert d['scope'] == 'private'
config2 = LearnConfig(scope=LearnScope.SHARED)
d2 = config2.to_dict()
assert d2['scope'] == 'shared'
class TestBaseStoreDefaults:
"""Test BaseStore uses correct default scope."""
def test_base_store_default_scope_is_private(self):
"""BaseStore should default to 'private' scope."""
import inspect
from praisonaiagents.memory.learn.stores import BaseStore
sig = inspect.signature(BaseStore.__init__)
scope_param = sig.parameters.get('scope')
assert scope_param is not None
assert scope_param.default == 'private', f"Expected 'private', got '{scope_param.default}'"
+2 -2
View File
@@ -1,5 +1,5 @@
version = 1
revision = 2
revision = 3
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.13'",
@@ -2422,7 +2422,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.11.7"
version = "0.11.8"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
+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/v3.8.1.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.8.1.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.8.2.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.8.2.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
@@ -117,7 +117,7 @@ app.add_typer(learn_app, name="learn")
@learn_app.command("status")
def learn_status(
user_id: str = typer.Option("default", "--user-id", "-u", help="User ID"),
scope: str = typer.Option("user", "--scope", "-s", help="Scope (user/team/org)"),
scope: str = typer.Option("private", "--scope", "-s", help="Scope (private/shared)"),
):
"""Show learning status and statistics."""
from praisonaiagents.memory.learn import LearnManager
+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==3.8.1 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==3.8.2 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__ = "3.8.1"
__version__ = "3.8.2"
+1 -1
View File
@@ -12,7 +12,7 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
"praisonaiagents>=0.11.7",
"praisonaiagents>=0.11.8",
"python-dotenv>=0.19.0",
"instructor>=1.3.3",
"PyYAML>=6.0",
+172 -33
View File
@@ -3,26 +3,35 @@
Combined bump version and release script for PraisonAI package.
Usage:
python src/praisonai/scripts/bump_and_release.py 2.2.99
# Basic: bump praisonai to 3.8.2 with agents 0.11.8
python scripts/bump_and_release.py --agents 0.11.8 3.8.2
Or with optional praisonaiagents version:
python src/praisonai/scripts/bump_and_release.py 2.2.99 --agents 0.0.169
# Wait for agents version to propagate to PyPI (recommended after publishing agents)
python scripts/bump_and_release.py --agents 0.11.8 --wait 3.8.2
# Auto-detect latest agents version from PyPI
python scripts/bump_and_release.py --auto 3.8.2
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)
1. Validates pre-flight conditions (clean git state, versions)
2. Optionally waits for praisonaiagents to be available on PyPI
3. Bumps version in all required files
4. Copies root README.md to package dir
5. Runs uv lock & uv build
6. Commits changes
7. Creates git tag
8. Pushes to GitHub (with rebase if needed)
9. Creates GitHub release (latest)
"""
import re
import sys
import time
import json
import argparse
import subprocess
import shutil
import urllib.request
from pathlib import Path
from typing import Optional
@@ -37,20 +46,63 @@ def get_praisonai_dir() -> Path:
return get_project_root() / "src/praisonai"
def run(cmd: list[str], cwd: Optional[Path] = None, check: bool = True) -> subprocess.CompletedProcess:
def run(cmd: list[str], cwd: Optional[Path] = None, check: bool = True, silent: bool = False) -> subprocess.CompletedProcess:
"""Run a command and print it."""
print(f"$ {' '.join(cmd)}")
if not silent:
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 not silent:
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 get_pypi_version(package: str) -> Optional[str]:
"""Get the latest version of a package from PyPI."""
try:
url = f"https://pypi.org/pypi/{package}/json"
with urllib.request.urlopen(url, timeout=10) as response:
data = json.loads(response.read().decode())
return data["info"]["version"]
except Exception as e:
print(f" ⚠️ Failed to fetch PyPI version: {e}")
return None
def wait_for_pypi_version(package: str, version: str, max_wait: int = 300, interval: int = 30) -> bool:
"""Wait for a specific version to be available on PyPI."""
print(f"\n⏳ Waiting for {package}=={version} to be available on PyPI...")
start = time.time()
while time.time() - start < max_wait:
current = get_pypi_version(package)
if current == version:
print(f" ✅ {package}=={version} is now available on PyPI")
return True
elapsed = int(time.time() - start)
remaining = max_wait - elapsed
print(f" ⏳ Current: {current}, waiting for: {version} ({remaining}s remaining...)")
time.sleep(interval)
print(f" ❌ Timeout waiting for {package}=={version} on PyPI")
return False
def check_git_status() -> bool:
"""Check if there are uncommitted changes."""
root = get_project_root()
result = run(["git", "status", "--porcelain"], cwd=root, check=False, silent=True)
if result.stdout.strip():
return True # Has uncommitted changes
return False
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():
@@ -130,17 +182,27 @@ def bump_version(new_version: str, agents_version: Optional[str] = None):
print("\n✨ Version bump complete!")
def validate_dependencies() -> bool:
"""Validate that uv lock will succeed before making any changes."""
def validate_dependencies(max_retries: int = 3, retry_interval: int = 60) -> bool:
"""Validate that uv lock will succeed, with retry logic for PyPI propagation."""
praisonai_dir = get_praisonai_dir()
print("\n🔍 Validating dependencies (dry-run)...")
result = run(["uv", "lock", "--dry-run"], cwd=praisonai_dir, check=False)
if result.returncode != 0:
print("\n❌ Dependency validation failed. Fix the issues above before releasing.")
return False
print(" ✅ Dependencies validated successfully")
return True
for attempt in range(max_retries):
print(f"\n🔍 Validating dependencies (attempt {attempt + 1}/{max_retries})...")
# Clear cache before validation
run(["uv", "cache", "clean"], cwd=praisonai_dir, silent=True)
result = run(["uv", "lock", "--dry-run"], cwd=praisonai_dir, check=False)
if result.returncode == 0:
print(" ✅ Dependencies validated successfully")
return True
if attempt < max_retries - 1:
print(f"\n⏳ Waiting {retry_interval}s for PyPI propagation before retry...")
time.sleep(retry_interval)
print("\n❌ Dependency validation failed after all retries.")
return False
def release(version: str):
@@ -179,8 +241,17 @@ def release(version: str):
print(f"\n🏷️ Creating tag {tag}...")
run(["git", "tag", "-f", tag], cwd=root)
# 6. Push to GitHub
# 6. Pull rebase and push to GitHub
print("\n⬆️ Pushing to GitHub...")
# First fetch and rebase to handle any remote changes (e.g., auto-generated api.md)
result = run(["git", "pull", "--rebase", "origin", "main"], cwd=root, check=False)
if result.returncode != 0:
print(" ⚠️ Rebase failed, trying to continue...")
# Recreate tag after rebase (commit hash may have changed)
run(["git", "tag", "-f", tag], cwd=root)
# Push changes
run(["git", "push"], cwd=root)
run(["git", "push", "--tags", "-f"], cwd=root)
@@ -200,37 +271,105 @@ def release(version: str):
def main():
parser = argparse.ArgumentParser(
description="Bump version and release PraisonAI package"
description="Bump version and release PraisonAI package",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Bump to 3.8.2 with agents 0.11.8
python scripts/bump_and_release.py --agents 0.11.8 3.8.2
# Wait for agents to propagate to PyPI first (recommended)
python scripts/bump_and_release.py --agents 0.11.8 --wait 3.8.2
# Auto-detect latest agents version from PyPI
python scripts/bump_and_release.py --auto 3.8.2
"""
)
parser.add_argument(
"version",
help="New version number (e.g., 2.2.99)"
help="New version number (e.g., 3.8.2)"
)
parser.add_argument(
"--agents", "-a",
help="Optional: Update praisonaiagents dependency version (e.g., 0.0.169)",
help="Update praisonaiagents dependency version (e.g., 0.11.8)",
default=None
)
parser.add_argument(
"--auto",
action="store_true",
help="Auto-detect latest praisonaiagents version from PyPI"
)
parser.add_argument(
"--wait", "-w",
action="store_true",
help="Wait for the specified agents version to be available on PyPI"
)
parser.add_argument(
"--max-wait",
type=int,
default=300,
help="Maximum seconds to wait for PyPI propagation (default: 300)"
)
parser.add_argument(
"--retries", "-r",
type=int,
default=3,
help="Number of retries for dependency validation (default: 3)"
)
parser.add_argument(
"--force", "-f",
action="store_true",
help="Skip pre-flight checks (use with caution)"
)
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)")
print(" Expected format: X.Y.Z (e.g., 3.8.2)")
sys.exit(1)
# Handle --auto flag
if args.auto:
print("\n🔍 Auto-detecting latest praisonaiagents version from PyPI...")
agents_version = get_pypi_version("praisonaiagents")
if not agents_version:
print("❌ Failed to auto-detect praisonaiagents version")
sys.exit(1)
print(f" ✅ Latest version: {agents_version}")
args.agents = agents_version
# Validate agents version format if provided
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)")
print(" Expected format: X.Y.Z (e.g., 0.11.8)")
sys.exit(1)
# Pre-flight checks
if not args.force:
print("\n🔍 Pre-flight checks...")
# Check for uncommitted changes (warning only)
if check_git_status():
print(" ⚠️ Warning: You have uncommitted changes")
else:
print(" ✅ Git working directory is clean")
# Wait for PyPI propagation if requested
if args.wait and args.agents:
if not wait_for_pypi_version("praisonaiagents", args.agents, max_wait=args.max_wait):
print("\n💡 Tip: Check if the package was published successfully")
print("💡 Tip: You can retry without --wait if the package is confirmed published")
sys.exit(1)
# Run bump version
bump_version(args.version, args.agents)
# Validate dependencies after version bump (before git operations)
if not validate_dependencies():
# Validate dependencies after version bump (with retries)
if not validate_dependencies(max_retries=args.retries):
print("\n💡 Tip: Revert changes with 'git checkout .' if needed")
print("💡 Tip: The package may need more time to propagate to PyPI")
sys.exit(1)
# Run release
+4 -4
View File
@@ -4276,7 +4276,7 @@ requires-dist = [
{ name = "praisonai-tools", marker = "extra == 'autogen'", specifier = ">=0.1.0" },
{ name = "praisonai-tools", marker = "extra == 'autogen-v4'", specifier = ">=0.1.0" },
{ name = "praisonai-tools", marker = "extra == 'crewai'", specifier = ">=0.1.0" },
{ name = "praisonaiagents", specifier = ">=0.11.7" },
{ name = "praisonaiagents", specifier = ">=0.11.8" },
{ 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" },
@@ -4324,7 +4324,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.11.7"
version = "0.11.8"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@@ -4333,9 +4333,9 @@ dependencies = [
{ name = "pydantic" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ca/9a/fb687274c729813f9ce8f7fbfc5effb9a7194f88c3f36f56f276cf0fb43b/praisonaiagents-0.11.7.tar.gz", hash = "sha256:11b9c00b9b7d0eb34ea0faf58fdce524d6c935daf872be9ba6d47bee876c594c", size = 993605, upload-time = "2026-01-12T16:54:06.17Z" }
sdist = { url = "https://files.pythonhosted.org/packages/92/e8/e0a52140b3c09be369dd48dbb71addf8bb711c3267d05abf352ef6717f55/praisonaiagents-0.11.8.tar.gz", hash = "sha256:16599d391838d456bb4933c5778049ff366eb2fb812f8e5f645b2d60c9c919c5", size = 993464, upload-time = "2026-01-13T04:11:31.609Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e1/d4/67b6e90e9c949f380ec1cae21005b398865ce927585845364ecfb6e7048e/praisonaiagents-0.11.7-py3-none-any.whl", hash = "sha256:b20653e209b16c3ecd788c348bdec99dc97cad5e85129b1061a44a86fd580bee", size = 977510, upload-time = "2026-01-12T16:54:03.334Z" },
{ url = "https://files.pythonhosted.org/packages/d3/4a/edf2cffa71f3f76d3f1d393ce9bff64a4873e54a8a2abed254cd7494d0bf/praisonaiagents-0.11.8-py3-none-any.whl", hash = "sha256:2c5b74d1bbe7f8f835ca698748103f5d7ab97fca2b57ec40eaec597df620e888", size = 977507, upload-time = "2026-01-13T04:11:29.481Z" },
]
[[package]]