Compare commits

...
3 Commits
Author SHA1 Message Date
Mervin Praison 8e2dd09b3b Release v3.8.4 2026-01-13 06:17:09 +00:00
github-actions[bot] 8ecfad5c5a docs: auto-update api.md [skip ci] 2026-01-13 06:01:35 +00:00
Mervin Praison 35a88a0913 Release v3.8.3 2026-01-13 06:01:20 +00:00
42 changed files with 1087 additions and 214 deletions

No files matched your search

+5 -5
View File
@@ -77,7 +77,7 @@ Methods:
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">run_autonomous</a>(prompt: str, max_iterations: Optional[int] = None, timeout_seconds: Optional[float] = None)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">session_id</a>() -> Optional[str]</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">skill_manager</a>()</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">start</a>(prompt: str, **kwargs)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">start</a>(prompt: str = None, **kwargs)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">store_memory</a>(content: str, memory_type: str = 'short_term', **kwargs)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">switch_model</a>(new_model: str) -> None</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">thinking_budget</a>()</code>
@@ -117,7 +117,7 @@ Methods:
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_output_to_file</a>(task, task_output)</code>
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_session_state</a>(session_id: str, include_memory: bool = True) -> None</code>
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">set_state</a>(key: str, value: Any) -> None</code>
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">start</a>(content = None, return_dict = False, **kwargs)</code>
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">start</a>(content = None, return_dict = False, output = None, **kwargs)</code>
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">todo_list</a>()</code>
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_plan_step_status</a>(step_id: str, status: str) -> bool</code>
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_state</a>(updates: Dict) -> None</code>
@@ -194,7 +194,7 @@ Methods:
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_output_to_file</a>(task, task_output)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_session_state</a>(session_id: str, include_memory: bool = True) -> None</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">set_state</a>(key: str, value: Any) -> None</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">start</a>(content = None, return_dict = False, **kwargs)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">start</a>(content = None, return_dict = False, output = None, **kwargs)</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">todo_list</a>()</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_plan_step_status</a>(step_id: str, status: str) -> bool</code>
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_state</a>(updates: Dict) -> None</code>
@@ -546,9 +546,9 @@ Methods:
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_error</a>(message: str, console = None)</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_generating</a>(content: str = '', start_time: Optional[float] = None)</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_instruction</a>(message: str, console = None, agent_name: str = None, agent_role: str = None, agent_tools: List[str] = None)</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_interaction</a>(message, response, markdown = True, generation_time = None, console = None, agent_name = None, agent_role = None, agent_tools = None, task_name = None, task_description = None, task_id = None)</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_interaction</a>(message, response, markdown = True, generation_time = None, console = None, agent_name = None, agent_role = None, agent_tools = None, task_name = None, task_description = None, task_id = None, metrics = None)</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_self_reflection</a>(message: str, console = None)</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_tool_call</a>(message: str, console = None, tool_name: str = None, tool_input: dict = None, tool_output: str = None)</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_tool_call</a>(message: str, console = None, tool_name: str = None, tool_input: dict = None, tool_output: str = None, elapsed_time: float = None, success: bool = True)</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">register_display_callback</a>(display_type: str, callback_fn, is_async: bool = False)</code>
# Other
+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.2" \
"praisonai>=3.8.4" \
"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.2" \
"praisonai>=3.8.4" \
"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.2" \
"praisonai>=3.8.4" \
"praisonai[ui]" \
"praisonai[crewai]"
@@ -13,7 +13,7 @@ def main():
role="Research Specialist",
goal="Find information about a topic",
backstory="You are an expert researcher who finds comprehensive information.",
verbose=True,
output="verbose",
llm="gpt-5-nano", # Use a cost-effective model for examples
metrics=True # 🎯 SIMPLIFIED: Just add this parameter!
)
@@ -29,7 +29,7 @@ def main():
agents = Agents(
agents=[research_agent],
tasks=[research_task],
verbose=True
output="verbose"
)
print("🚀 Running agent task...")
@@ -104,8 +104,7 @@ def main():
workflow = Agents(
agents=[researcher, analyst, writer],
tasks=[research_task, analysis_task, report_task],
process="sequential",
verbose=True
process="sequential"
)
result = workflow.start()
@@ -11,7 +11,7 @@ Shows how to monitor agent performance when using external tools.
"""
from praisonaiagents import Agent
from praisonaiagents.tools import duckduckgo, wikipedia
from praisonaiagents.tools import duckduckgo, wikipedia_tools
from praisonaiagents.telemetry import (
monitor_function, track_api_call, get_api_stats,
get_slowest_functions, performance_monitor
@@ -49,7 +49,7 @@ def main():
Use the available tools to search for information and provide comprehensive answers.
Be thorough in your research and cite your sources.""",
llm="gpt-5-nano",
tools=[duckduckgo, wikipedia] # Add search tools
tools=[duckduckgo, wikipedia_tools] # Add search tools
)
# Test queries to demonstrate tool performance monitoring
@@ -97,7 +97,9 @@ def main():
# Slowest functions analysis
print("\n🐌 Slowest Functions:")
slowest = get_slowest_functions()
for func_name, avg_time in slowest:
for item in slowest[:5]:
func_name = item.get('function', 'unknown')
avg_time = item.get('average_time', 0)
print(f" {func_name}: {avg_time:.3f}s average")
# Real-time performance data
@@ -165,7 +165,9 @@ def main():
# Slowest operations
print("\n🐌 Slowest Memory Operations:")
slowest = get_slowest_functions()
for func_name, avg_time in slowest[:5]: # Top 5 slowest
for item in slowest[:5]: # Top 5 slowest
func_name = item.get('function', 'unknown')
avg_time = item.get('average_time', 0)
print(f" {func_name}: {avg_time:.3f}s average")
# Memory operation breakdown
@@ -263,7 +263,7 @@ def main():
# Slowest operations
print("\n🐌 Slowest Tool Operations:")
slowest = get_slowest_functions()
tool_slowest = [(name, time) for name, time in slowest if 'tool' in name.lower()]
tool_slowest = [(item.get('function', 'unknown'), item.get('average_time', 0)) for item in slowest if 'tool' in item.get('function', '').lower()]
for tool_name, avg_time in tool_slowest[:3]:
print(f" {tool_name}: {avg_time:.3f}s")
@@ -302,13 +302,17 @@ def display_dashboard(dashboard_data, benchmark_results):
perf_data = dashboard_data['performance_data']
print(" Fastest Functions:")
fastest_functions = sorted(perf_data['slowest_functions'], key=lambda x: x[1])[:3]
for name, time in fastest_functions:
print(f" {name}: {time:.3f}s")
fastest_functions = sorted(perf_data['slowest_functions'], key=lambda x: x.get('average_time', float('inf')))[:3]
for item in fastest_functions:
name = item.get('function', 'unknown')
time_val = item.get('average_time', 0)
print(f" {name}: {time_val:.3f}s")
print(" Slowest Functions:")
for name, time in perf_data['slowest_functions'][:3]:
print(f" {name}: {time:.3f}s")
for item in perf_data['slowest_functions'][:3]:
name = item.get('function', 'unknown')
time_val = item.get('average_time', 0)
print(f" {name}: {time_val:.3f}s")
# Benchmark Results
print(f"\n⚡ BENCHMARK RESULTS")
@@ -21,7 +21,7 @@ article_agent = Agent(
goal="Fetch the user's favorite morning article",
tools=[fetch_favorite_article],
llm="gemini/gemini-2.5-flash-lite-preview-06-17",
verbose=True
output="verbose"
)
news_agent = Agent(
@@ -30,7 +30,7 @@ news_agent = Agent(
goal="Search for trending news in Kenya",
tools=[search_trending_kenya],
llm="gemini/gemini-2.5-flash-lite-preview-06-17",
verbose=True
output="verbose"
)
twitter_agent = Agent(
@@ -39,7 +39,7 @@ twitter_agent = Agent(
goal="Fetch the latest Twitter feed",
tools=[fetch_twitter_feed],
llm="gemini/gemini-2.5-flash-lite-preview-06-17",
verbose=True
output="verbose"
)
aggregator = Agent(
@@ -47,7 +47,7 @@ aggregator = Agent(
role="Result Aggregator",
goal="Aggregate and summarize all results",
llm="gemini/gemini-2.5-flash-lite-preview-06-17",
verbose=True
output="verbose"
)
# Tasks for each agent
@@ -92,7 +92,7 @@ async def main():
agents=[article_agent, news_agent, twitter_agent, aggregator],
tasks=[article_task, news_task, twitter_task, aggregate_task],
process="workflow",
verbose=True
output="verbose"
)
results = await workflow.astart(dict_output=True)
+2 -2
View File
@@ -5,8 +5,8 @@ async def main():
agent = Agent(
instructions="You are a helpful assistant",
llm="gemini/gemini-1.5-flash-8b",
self_reflect=True,
verbose=True
reflection=True,
output="verbose"
)
# Use achat instead of start/chat for async operation
@@ -79,7 +79,7 @@ async def run_parallel_searches():
backstory="Expert in web search and information retrieval. You have access to the tavily_search tool to search the web.",
tools=[tavily_search],
llm=llm_config,
verbose=True
output="verbose"
)
# Create summary agent
+1 -2
View File
@@ -1,4 +1,3 @@
from praisonaiagents import Agent
agent = Agent(instructions="You are a helpful assistant")
result = agent.start("Why is the sky blue?")
print(result)
result = agent.start("Why is the sky blue?")
@@ -1,6 +1,6 @@
# PraisonAI Agents - Real Execution Benchmark
**Generated:** 2025-12-18 14:43:52
**Generated:** 2026-01-13 06:07:28
**Model:** gpt-4o-mini
**Iterations:** 3
**Prompt:** "What is 2+2? Reply with just the number."
@@ -9,18 +9,18 @@
| Framework | Method | Avg Time | Relative |
|-----------|--------|----------|----------|
| **PraisonAI** | `agent.start()` | **0.45s** | **1.00x (fastest)** |
| PraisonAI (LiteLLM) | `agent.start()` | 0.55s | 1.22x |
| CrewAI | `crew.kickoff()` | 0.58s | 1.28x |
| Agno | `agent.run()` | 0.92s | 2.05x |
| **PraisonAI (LiteLLM)** | `agent.start()` | **0.55s** | **1.00x (fastest)** |
| PraisonAI | `agent.start()` | 0.64s | 1.15x |
| CrewAI | `crew.kickoff()` | 0.95s | 1.71x |
| Agno | `agent.run()` | 1.05s | 1.90x |
## Package Versions
| Package | Version |
|---------|--------|
| PraisonAI | 0.1.5 |
| Agno | 2.3.14 |
| CrewAI | 1.6.1 |
| PraisonAI | 0.11.7 |
| Agno | 2.3.25 |
| CrewAI | 1.8.0 |
## How to Reproduce
@@ -1,6 +1,6 @@
# PraisonAI Agents - Real-World Execution Benchmark
**Generated:** 2025-12-18 14:33:49
**Generated:** 2026-01-13 06:05:09
**Model:** gpt-4o-mini
**Iterations:** 3 per test
@@ -8,60 +8,60 @@
| Test | PraisonAI (avg) | Agno (avg) | Difference |
|------|-----------------|------------|------------|
| Single Agent | 2.04s | 3.18s | PraisonAI 1.56x faster |
| 2 Agents | 2.49s | 20.49s | PraisonAI 8.21x faster |
| 3 Agents | 6.52s | 13.97s | PraisonAI 2.14x faster |
| Single Agent | 2.09s | 3.25s | PraisonAI 1.55x faster |
| 2 Agents | 2.50s | 9.73s | PraisonAI 3.89x faster |
| 3 Agents | 6.25s | 13.56s | PraisonAI 2.17x faster |
## Detailed Results
### praisonai_single
- Average: 2.04s
- Min: 1.95s
- Max: 2.19s
- Std Dev: 0.13s
- Times: ['2.19s', '1.95s', '2.00s']
- Average: 2.09s
- Min: 1.64s
- Max: 2.84s
- Std Dev: 0.66s
- Times: ['2.84s', '1.79s', '1.64s']
### agno_single
- Average: 3.18s
- Min: 3.00s
- Max: 3.51s
- Std Dev: 0.28s
- Times: ['3.51s', '3.00s', '3.04s']
- Average: 3.25s
- Min: 3.14s
- Max: 3.35s
- Std Dev: 0.10s
- Times: ['3.35s', '3.25s', '3.14s']
### praisonai_two_agents
- Average: 2.49s
- Min: 2.22s
- Max: 2.65s
- Std Dev: 0.24s
- Times: ['2.65s', '2.22s', '2.62s']
- Average: 2.50s
- Min: 2.38s
- Max: 2.75s
- Std Dev: 0.21s
- Times: ['2.38s', '2.75s', '2.38s']
### agno_two_agents
- Average: 20.49s
- Min: 9.96s
- Max: 35.05s
- Std Dev: 13.02s
- Times: ['16.46s', '9.96s', '35.05s']
- Average: 9.73s
- Min: 9.34s
- Max: 10.53s
- Std Dev: 0.69s
- Times: ['10.53s', '9.34s', '9.34s']
### praisonai_three_agents
- Average: 6.52s
- Min: 5.62s
- Max: 7.39s
- Std Dev: 0.88s
- Times: ['5.62s', '6.54s', '7.39s']
- Average: 6.25s
- Min: 5.86s
- Max: 6.80s
- Std Dev: 0.49s
- Times: ['6.80s', '6.09s', '5.86s']
### agno_three_agents
- Average: 13.97s
- Min: 11.69s
- Max: 15.32s
- Std Dev: 1.99s
- Times: ['14.91s', '11.69s', '15.32s']
- Average: 13.56s
- Min: 12.26s
- Max: 14.90s
- Std Dev: 1.32s
- Times: ['14.90s', '13.51s', '12.26s']
## Package Versions
| Package | Version |
|---------|--------|
| PraisonAI | 0.1.5 |
| Agno | 2.3.14 |
| PraisonAI | 0.11.7 |
| Agno | 2.3.25 |
## How to Reproduce
@@ -52,12 +52,11 @@ def benchmark_praisonai(model, iterations, prompt):
agent = Agent(
name="Calculator",
instructions="You are a helpful assistant. Be very brief.",
llm=model,
verbose=False
llm=model
)
start = time.perf_counter()
response = agent.start(prompt)
response = agent.start(prompt, output="silent")
elapsed = time.perf_counter() - start
times.append(elapsed)
@@ -80,12 +79,11 @@ def benchmark_praisonai_litellm(model, iterations, prompt):
agent = Agent(
name="Calculator",
instructions="You are a helpful assistant. Be very brief.",
llm=f"openai/{model}",
verbose=False
llm=f"openai/{model}"
)
start = time.perf_counter()
response = agent.start(prompt)
response = agent.start(prompt, output="silent")
elapsed = time.perf_counter() - start
times.append(elapsed)
@@ -103,12 +103,11 @@ def benchmark_praisonai_single(iterations: int = 3) -> ExecutionResult:
name="Assistant",
instructions="You are a helpful assistant. Be very brief.",
llm="gpt-4o-mini",
tools=[get_weather],
verbose=False
tools=[get_weather]
)
start = time.perf_counter()
response = agent.start("What's the weather in NYC? Reply in one sentence.")
response = agent.start("What's the weather in NYC? Reply in one sentence.", output="silent")
elapsed = time.perf_counter() - start
result.times.append(elapsed)
@@ -132,8 +131,7 @@ def benchmark_praisonai_two_agents(iterations: int = 3) -> ExecutionResult:
goal="Gather weather information",
instructions="Get weather data and pass to reporter. Be brief.",
llm="gpt-4o-mini",
tools=[get_weather],
verbose=False
tools=[get_weather]
)
reporter = Agent(
@@ -141,18 +139,16 @@ def benchmark_praisonai_two_agents(iterations: int = 3) -> ExecutionResult:
role="News Reporter",
goal="Create weather report",
instructions="Create a one-sentence weather summary.",
llm="gpt-4o-mini",
verbose=False
llm="gpt-4o-mini"
)
agents = Agents(
agents=[researcher, reporter],
process="sequential",
verbose=False
process="sequential"
)
start = time.perf_counter()
response = agents.start("Get NYC weather and create a brief report.")
response = agents.start("Get NYC weather and create a brief report.", output="silent")
elapsed = time.perf_counter() - start
result.times.append(elapsed)
@@ -176,8 +172,7 @@ def benchmark_praisonai_three_agents(iterations: int = 3) -> ExecutionResult:
goal="Get weather data",
instructions="Fetch weather information. Be brief.",
llm="gpt-4o-mini",
tools=[get_weather],
verbose=False
tools=[get_weather]
)
time_agent = Agent(
@@ -186,8 +181,7 @@ def benchmark_praisonai_three_agents(iterations: int = 3) -> ExecutionResult:
goal="Get time information",
instructions="Fetch timezone information. Be brief.",
llm="gpt-4o-mini",
tools=[get_time],
verbose=False
tools=[get_time]
)
summarizer = Agent(
@@ -195,18 +189,16 @@ def benchmark_praisonai_three_agents(iterations: int = 3) -> ExecutionResult:
role="Summary Writer",
goal="Combine information",
instructions="Create a one-sentence summary combining weather and time.",
llm="gpt-4o-mini",
verbose=False
llm="gpt-4o-mini"
)
agents = Agents(
agents=[weather_agent, time_agent, summarizer],
process="sequential",
verbose=False
process="sequential"
)
start = time.perf_counter()
response = agents.start("Get NYC weather and EST time, then summarize.")
response = agents.start("Get NYC weather and EST time, then summarize.", output="silent")
elapsed = time.perf_counter() - start
result.times.append(elapsed)
@@ -14,7 +14,7 @@ llm_config = {
agent = Agent(
instructions="You are a helpful assistant. Be concise and clear.",
llm=llm_config,
verbose=True,
output="verbose",
self_reflect=True,
max_reflect=2,
min_reflect=1
@@ -7,8 +7,7 @@ def validate_content(data):
agent = Agent(
instructions="You are a writer",
guardrail=validate_content,
max_guardrail_retries=1
guardrails=validate_content
)
agent.start("Write a welcome message with 5 words")
+1 -1
View File
@@ -32,7 +32,7 @@ agent = Agent(
instructions="You are a helpful Assistant specialized in scientific explanations. "
"Provide clear, accurate, and engaging responses.",
llm=llm_config, # Pass the detailed configuration
verbose=True, # Enable detailed output
output="verbose", # Enable detailed output
markdown=True, # Format responses in markdown
self_reflect=True, # Enable self-reflection
max_reflect=3, # Maximum reflection iterations
@@ -55,10 +55,8 @@ def main():
retrieve information from a comprehensive MongoDB knowledge base. You excel
at finding relevant information, synthesizing knowledge from multiple sources,
and providing accurate, context-aware responses.""",
knowledge_config=mongodb_knowledge_config,
knowledge=[os.path.join(os.path.dirname(__file__), "llms.md")],
memory=True,
verbose=True,
llm="gpt-5-nano"
)
@@ -72,7 +70,6 @@ def main():
knowledge base. You ensure information is accurate, well-structured,
and properly categorized.""",
memory=True,
verbose=True,
llm="gpt-5-nano"
)
@@ -128,7 +125,7 @@ def main():
agents=[research_agent, knowledge_agent],
tasks=knowledge_tasks,
memory=True,
verbose=True
output="verbose"
)
# Execute the knowledge management pipeline
+7
View File
@@ -0,0 +1,7 @@
from praisonaiagents import Agent, Agents
research_agent = Agent(instructions="Research about AI")
summarise_agent = Agent(instructions="Summarise research agent's findings")
agents = Agents(agents=[research_agent, summarise_agent])
response = agents.start()
@@ -775,7 +775,21 @@ class Agent:
# If instructions are provided, use them to set role, goal, and backstory
if instructions:
self.name = name or "Agent"
# Auto-generate descriptive name from instructions if not provided
if name:
self.name = name
else:
# Generate name from first 2 meaningful words of instructions
# "Research about AI" → "Research Agent"
# "Summarise research agent's findings" → "Summarise Agent"
words = instructions.split()
# Filter out common stop words
stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'about'}
meaningful = [w for w in words if w.lower() not in stop_words][:2]
if meaningful:
self.name = " ".join(meaningful).title() + " Agent"
else:
self.name = words[0].title() + " Agent" if words else "Agent"
self.role = role or "Assistant"
self.goal = goal or instructions
self.backstory = backstory or instructions
@@ -2840,6 +2854,10 @@ Your Goal: {self.goal}"""
"""
logging.debug(f"{self.name} executing tool {function_name} with arguments: {arguments}")
# Notify callbacks that tool is being called (for dynamic status updates)
from ..main import execute_sync_callback
execute_sync_callback('tool_call', tool_name=function_name, tool_input=arguments)
# Set up injection context for tools with Injected parameters
from ..tools.injected import AgentState, with_injection_context
state = AgentState(
@@ -4301,15 +4319,15 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
def run(self, prompt: str, **kwargs):
"""Execute agent silently and return structured result.
Production-friendly execution. Does not stream or display output by default.
Use this for programmatic/scripted usage where you want the result only.
Production-friendly execution. Always uses silent mode with no streaming
or verbose display, regardless of TTY status. Use this for programmatic,
scripted, or automated usage where you want just the result.
Args:
prompt: The input prompt to process
**kwargs: Additional arguments:
- stream (bool): Force streaming if True. Default: False
- display (bool): Force terminal display if True. Default: False
- output (str): Output preset override
- output (str): Output preset override (rarely needed)
Returns:
The agent's response as a string
@@ -4320,6 +4338,14 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
result = agent.run("What is 2+2?") # Silent, returns "4"
print(result)
```
Note:
Unlike .start() which enables verbose output in TTY for interactive
use, .run() is always silent. This makes it suitable for:
- Production pipelines
- Automated scripts
- Background processing
- API endpoints
"""
# Production defaults: no streaming, no display
if 'stream' not in kwargs:
@@ -4471,18 +4497,20 @@ Write the complete compiled report:"""
# Chat history is preserved in self.chat_history (no action needed)
def start(self, prompt: str, **kwargs):
"""Start the agent interactively with streaming output.
def start(self, prompt: str = None, **kwargs):
"""Start the agent interactively with verbose output.
Beginner-friendly execution. Streams output by default when running in a TTY.
Use this for interactive/terminal usage where you want to see output in real-time.
Beginner-friendly execution. Defaults to verbose output with streaming
when running in a TTY. Use this for interactive/terminal usage where
you want to see output in real-time with rich formatting.
Args:
prompt: The input prompt to process
prompt: The input prompt to process. If not provided, uses the
agent's instructions as the task (useful when instructions
already describe what the agent should do).
**kwargs: Additional arguments:
- stream (bool | None): Override streaming. None = auto-detect TTY
- display (bool | None): Override display. None = auto (True if streaming)
- output (str): Output preset override
- output (str): Output preset override (e.g., "silent", "verbose")
Returns:
- If streaming: Generator yielding response chunks
@@ -4490,21 +4518,32 @@ Write the complete compiled report:"""
Example:
```python
agent = Agent(instructions="You are helpful")
# Minimal usage - instructions IS the task
agent = Agent(instructions="Research AI trends and summarize")
result = agent.start() # Uses instructions as task
# Interactive use - streams by default in terminal
for chunk in agent.start("Tell me a story"):
print(chunk, end="", flush=True)
# Or let it handle display automatically
result = agent.start("What is 2+2?") # Streams if TTY
# With explicit prompt (overrides/adds to instructions)
agent = Agent(instructions="You are a helpful assistant")
result = agent.start("What is 2+2?") # Uses prompt as task
```
Note:
Unlike .run() which is always silent (production use), .start()
enables verbose output by default when in a TTY for beginner-friendly
interactive use. Use .run() for programmatic/scripted usage.
"""
import sys
# If no prompt provided, use instructions as the task
if prompt is None:
prompt = self.instructions or "Hello"
# Load history from past sessions
self._load_history_context()
# Determine if we're in an interactive TTY
is_tty = sys.stdout.isatty()
# Determine streaming behavior
# Priority: explicit kwarg > agent's stream attribute > TTY detection
stream_requested = kwargs.get('stream')
@@ -4514,24 +4553,192 @@ Write the complete compiled report:"""
stream_requested = self.stream
else:
# Auto-detect: stream if stdout is a TTY (interactive terminal)
stream_requested = sys.stdout.isatty()
stream_requested = is_tty
# Check if planning mode is enabled
if self.planning:
result = self._start_with_planning(prompt, **kwargs)
elif stream_requested:
# Return a generator for streaming response
kwargs['stream'] = True
result = self._start_stream(prompt, **kwargs)
else:
# Return regular chat response
kwargs['stream'] = False
result = self.chat(prompt, **kwargs)
# ─────────────────────────────────────────────────────────────────────
# Enable verbose output in TTY for beginner-friendly interactive use
# Priority: explicit output= kwarg > TTY auto-verbose > agent's default
# ─────────────────────────────────────────────────────────────────────
original_verbose = self.verbose
original_markdown = self.markdown
output_override = kwargs.pop('output', None) # Pop to prevent passing to chat()
# Auto-save session if enabled
self._auto_save_session()
return result
try:
# If running in TTY and no explicit output override, enable verbose
if is_tty and output_override is None:
self.verbose = True
self.markdown = True
elif output_override:
# Apply explicit output preset for this call
from ..config.presets import OUTPUT_PRESETS
if output_override in OUTPUT_PRESETS:
preset = OUTPUT_PRESETS[output_override]
self.verbose = preset.get('verbose', False)
self.markdown = preset.get('markdown', False)
# Check if planning mode is enabled
if self.planning:
result = self._start_with_planning(prompt, **kwargs)
elif stream_requested:
# Return a generator for streaming response
kwargs['stream'] = True
result = self._start_stream(prompt, **kwargs)
else:
# Return regular chat response with animated working status
kwargs['stream'] = False
# Show animated status during LLM call if verbose
if self.verbose and is_tty:
from rich.live import Live
from rich.console import Group
from rich.status import Status
from ..main import PRAISON_COLORS, sync_display_callbacks
import threading
import time as time_module
console = Console()
start_time = time_module.time()
# ─────────────────────────────────────────────────────────────
# Shared state for dynamic status messages (thread-safe)
# Updated by callbacks during tool execution
# ─────────────────────────────────────────────────────────────
current_status = ["Analyzing query..."]
tools_called = []
# Register a temporary callback to track tool calls
def status_tool_callback(**kwargs):
tool_name = kwargs.get('tool_name', '')
if tool_name:
tools_called.append(tool_name)
current_status[0] = f"Calling tool: {tool_name}..."
# Store original callback and register ours
original_tool_callback = sync_display_callbacks.get('tool_call')
sync_display_callbacks['tool_call'] = status_tool_callback
# Animation state
result_holder = [None]
error_holder = [None]
# Temporarily disable verbose in chat to prevent duplicate output
original_verbose_chat = self.verbose
def run_chat():
try:
# Suppress verbose during animation - we'll display result ourselves
self.verbose = False
current_status[0] = "Sending to LLM..."
result_holder[0] = self.chat(prompt, **kwargs)
current_status[0] = "Finalizing response..."
except Exception as e:
error_holder[0] = e
finally:
self.verbose = original_verbose_chat
# Restore original callback
if original_tool_callback:
sync_display_callbacks['tool_call'] = original_tool_callback
elif 'tool_call' in sync_display_callbacks:
del sync_display_callbacks['tool_call']
# Start chat in background thread
chat_thread = threading.Thread(target=run_chat)
chat_thread.start()
from rich.panel import Panel
from rich.text import Text
from rich.markdown import Markdown
# ─────────────────────────────────────────────────────────────
# Smart Agent Info: Only show if user provided meaningful info
# Skip if using defaults ("Agent"/"Assistant") with single agent
# ─────────────────────────────────────────────────────────────
has_custom_name = self.name and self.name not in ("Agent", "agent", None, "")
has_custom_role = self.role and self.role not in ("Assistant", "AI Assistant", "assistant", None, "")
has_tools = bool(self.tools)
show_agent_info = has_custom_name or has_custom_role or has_tools
agent_panel = None
if show_agent_info:
agent_info_parts = []
if has_custom_name:
agent_info_parts.append(f"[bold {PRAISON_COLORS['task']}]👤 Agent:[/] [{PRAISON_COLORS['agent_text']}]{self.name}[/]")
if has_custom_role:
agent_info_parts.append(f"[bold {PRAISON_COLORS['metrics']}]Role:[/] [{PRAISON_COLORS['agent_text']}]{self.role}[/]")
if has_tools:
tools_list = [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools][:5]
tools_str = ", ".join(f"[italic {PRAISON_COLORS['response']}]{tool}[/]" for tool in tools_list)
agent_info_parts.append(f"[bold {PRAISON_COLORS['agent']}]Tools:[/] {tools_str}")
agent_panel = Panel(
"\n".join(agent_info_parts),
border_style=PRAISON_COLORS["agent"],
title="[bold]Agent Info[/]",
title_align="left",
padding=(1, 2)
)
# Create task panel
task_panel = Panel.fit(
Markdown(prompt) if self.markdown else Text(prompt),
title="Task",
border_style=PRAISON_COLORS["task"]
)
# Show initial panels (agent info if applicable, then task)
if agent_panel:
console.print(agent_panel)
console.print(task_panel)
# ─────────────────────────────────────────────────────────────
# Animate with Rich.Status showing DYNAMIC status from callbacks
# ─────────────────────────────────────────────────────────────
with console.status(
f"[bold yellow]Working...[/] {current_status[0]}",
spinner="dots",
spinner_style="yellow"
) as status:
last_status = current_status[0]
while chat_thread.is_alive():
time_module.sleep(0.15) # More responsive updates
# Only update if status changed (reduces flicker)
if current_status[0] != last_status:
last_status = current_status[0]
status.update(f"[bold yellow]Working...[/] {current_status[0]}")
# Calculate elapsed time
elapsed = time_module.time() - start_time
# Re-raise any error from chat
if error_holder[0]:
raise error_holder[0]
result = result_holder[0]
# Display response panel
response_panel = Panel.fit(
Markdown(str(result)) if self.markdown else Text(str(result)),
title=f"Response ({elapsed:.1f}s)",
border_style=PRAISON_COLORS["response"]
)
console.print(response_panel)
# Show tool activity summary if tools were called
if tools_called:
tools_summary = ", ".join(tools_called)
console.print(f"[dim]🔧 Tools used: {tools_summary}[/dim]")
else:
result = self.chat(prompt, **kwargs)
# Auto-save session if enabled
self._auto_save_session()
return result
finally:
# Restore original output settings
self.verbose = original_verbose
self.markdown = original_markdown
def iter_stream(self, prompt: str, **kwargs):
"""Stream agent response as an iterator of chunks.
@@ -435,7 +435,6 @@ 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-4o-mini",
verbose=getattr(self, 'verbose', True)
)
prompt = f"""Analyze this gitingest codebase digest following PRD template methodology:
@@ -516,7 +515,6 @@ codebase style and architecture following PRD template principles."""
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-4o-mini",
verbose=getattr(self, 'verbose', True)
)
# Format comprehensive sample
@@ -567,7 +565,6 @@ Analyze following PRD principles to extract patterns, conventions, and architect
7. Code complexity metrics
8. API and interface patterns""",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
# Perform AST analysis on Python files
@@ -625,7 +622,6 @@ 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-4o-mini",
verbose=getattr(self, 'verbose', True)
)
# Get representative code samples
@@ -675,7 +671,6 @@ patterns and best practices for first-try success."""
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-4o-mini",
verbose=getattr(self, 'verbose', True)
)
# Get test files
@@ -802,7 +797,6 @@ Extract testing patterns for validation framework creation following PRD princip
Generate PRPs following this EXACT structure for first-try implementation success.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
prompt = f"""Generate a comprehensive Product Requirements Prompt (PRP) following the EXACT PRD template structure:
@@ -851,7 +845,6 @@ on the first try following PRD template principles."""
7. DOCUMENTATION VALIDATION: Documentation completeness
8. DEPENDENCY VALIDATION: Dependency analysis and security""",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
# Analyze existing validation patterns
@@ -898,7 +891,6 @@ following PRD template principles."""
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-4o-mini",
verbose=getattr(self, 'verbose', True)
)
# Get documentation files
@@ -944,7 +936,6 @@ following PRD template principles."""
APIs, databases, external services, configuration points, and any other
integration requirements that affect implementation.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
# Get configuration and dependency files
@@ -1005,7 +996,6 @@ following PRD template principles."""
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-4o-mini",
verbose=getattr(self, 'verbose', True)
)
prompt = f"""Create detailed implementation blueprint following PRD methodology:
@@ -1303,7 +1293,6 @@ Every agent interaction has been saved for full audit trail and reproducibility.
Be precise and extract only what is explicitly mentioned or clearly implied.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
prompt = f"""Parse this user input to extract codebase and goal:
@@ -1537,7 +1526,6 @@ Note: Detailed function/class metadata not available due to content access limit
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-4o-mini",
verbose=getattr(self, 'verbose', True)
)
structure_text = basic_structure.get('structure', '')
@@ -1771,7 +1759,6 @@ Focus on creating clear, structured metadata that will help with intelligent fil
Maximum 50 files for efficient analysis.""",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
# Prepare enhanced structure information
@@ -1924,7 +1911,6 @@ Maximum 50 files.""".format(goal=goal)
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-4o-mini",
verbose=getattr(self, 'verbose', True)
)
prompt = f"""Analyze these carefully selected repository files for implementing: {goal}
@@ -2025,7 +2011,6 @@ Since these files were pre-selected for relevance, explain how each contributes
Focus everything on successfully implementing: {goal}""",
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
verbose=getattr(self, 'verbose', True)
)
chunk_info = ""
@@ -1193,30 +1193,143 @@ Context:
return str(agent[0])
return None
def start(self, content=None, return_dict=False, **kwargs):
"""Start agent execution with optional content and config
def start(self, content=None, return_dict=False, output=None, **kwargs):
"""Start agent execution with verbose output (beginner-friendly).
Shows Rich panels with workflow progress when in TTY. Use .run() for
silent execution in production/scripts.
Args:
content: Optional content to add to all tasks' context
return_dict: If True, returns the full results dictionary instead of only the final response
return_dict: If True, returns the full results dictionary
output: Output preset - "silent", "verbose", "normal", etc.
Default in TTY: "verbose" (shows progress)
Default non-TTY: "silent"
**kwargs: Additional arguments
Example:
```python
# Interactive - shows Rich panels
agents = Agents(agents=[agent1, agent2])
result = agents.start() # Verbose output by default
# Force silent mode
result = agents.start(output="silent")
```
"""
import sys
from ..main import PRAISON_COLORS
# Determine if we're in an interactive TTY
is_tty = sys.stdout.isatty()
# Resolve output mode (TTY-aware)
if output is None:
# Default: verbose in TTY (beginner-friendly), silent otherwise
# Note: Don't check self.verbose here - start() is for interactive use
show_verbose = is_tty
elif output == "silent":
show_verbose = False
elif output in ("verbose", "debug", "normal"):
show_verbose = True
else:
show_verbose = is_tty
# Add content to context if provided
if content:
# Add content to context of all tasks
for task in self.tasks.values():
if isinstance(content, (str, list)):
# If context is empty, initialize it
if not task.context:
task.context = []
# Add content to context
task.context.append(content)
# Planning Mode: Create plan and todo list before execution
if self.planning:
self._run_with_planning()
# ─────────────────────────────────────────────────────────────
# Verbose Mode: Show Rich panels for multi-agent workflow
# ─────────────────────────────────────────────────────────────
if show_verbose and is_tty:
from rich.panel import Panel
from rich.text import Text
console = Console()
import time as time_module
# Show workflow overview panel
agent_names = " → ".join([a.name for a in self.agents])
workflow_info = f"[bold {PRAISON_COLORS['metrics']}]Process:[/] {self.process}\n"
workflow_info += f"[bold {PRAISON_COLORS['metrics']}]Agents:[/] {agent_names}"
console.print(Panel(
workflow_info,
title="[bold]Multi-Agent Workflow[/]",
border_style=PRAISON_COLORS["agent"],
padding=(1, 2)
))
console.print()
# Execute tasks with verbose output
total_agents = len(self.agents)
workflow_start_time = time_module.time()
for idx, (task_id, task) in enumerate(self.tasks.items(), 1):
agent = task.agent
agent_name = agent.name if agent else "Unknown"
agent_model = getattr(agent, 'llm', 'gpt-4o-mini') if agent else "unknown"
# Show agent task panel with model info
task_desc = task.description[:100] + "..." if len(task.description) > 100 else task.description
panel_content = f"[bold {PRAISON_COLORS['task_text']}]📋 Task:[/] {task_desc}\n"
panel_content += f"[dim]🤖 Model: {agent_model}[/dim]"
console.print(Panel.fit(
panel_content,
title=f"[bold]Agent [{idx}/{total_agents}]: {agent_name}[/]",
border_style=PRAISON_COLORS["task"]
))
# Execute with timing and status
start_time = time_module.time()
# Show working spinner
with console.status(
f"[bold yellow]Working...[/] {agent_name} generating response...",
spinner="dots",
spinner_style="yellow"
):
# Run the task
if self.planning:
self._run_with_planning()
break # Planning mode handles all tasks
else:
self.run_task(task_id)
elapsed = time_module.time() - start_time
# Show response panel - FULL response, no truncation
result = self.get_task_result(task_id)
if result:
response_text = str(result.raw)
# No truncation - show full response in verbose mode
console.print(Panel(
Text(response_text),
title=f"[bold]Agent [{idx}/{total_agents}] Complete ({elapsed:.1f}s)[/]",
border_style=PRAISON_COLORS["response"],
padding=(1, 2)
))
console.print()
# Workflow summary panel
total_elapsed = time_module.time() - workflow_start_time
console.print(Panel.fit(
f"[bold green]Total Time:[/] {total_elapsed:.1f}s\n"
f"[bold green]Agents Run:[/] {total_agents}/{total_agents}",
title="[bold]✅ Workflow Complete[/]",
border_style="green"
))
console.print()
else:
# Run tasks as before
self.run_all_tasks()
# Silent mode: Run tasks without display
if self.planning:
self._run_with_planning()
else:
self.run_all_tasks()
# Auto-display token metrics if any agent has metrics=True
metrics_enabled = any(getattr(agent, 'metrics', False) for agent in self.agents)
@@ -1224,10 +1337,8 @@ Context:
try:
self.display_token_usage()
except (ImportError, AttributeError) as e:
# Token tracking not available or not properly configured
logging.debug(f"Could not auto-display token usage: {e}")
except Exception as e:
# Log unexpected errors for debugging
logging.debug(f"Unexpected error in token metrics display: {e}")
# Get results
@@ -1238,7 +1349,6 @@ Context:
# By default, return only the final agent's response
if not return_dict:
# Get the last task (assuming sequential processing)
task_ids = list(self.tasks.keys())
if task_ids:
last_task_id = task_ids[-1]
@@ -1246,12 +1356,21 @@ Context:
if last_result:
return last_result.raw
# Return full results dict if return_dict is True or if no final result was found
return results
def run(self, content=None, return_dict=False, **kwargs):
"""Alias for start() method to provide consistent API with Agent class"""
return self.start(content=content, return_dict=return_dict, **kwargs)
"""Run agents silently (production use).
Unlike .start() which shows verbose output, .run() executes silently
for programmatic/production use.
Args:
content: Optional content to add to all tasks' context
return_dict: If True, returns the full results dictionary
**kwargs: Additional arguments
"""
# Always run silently - no verbose output
return self.start(content=content, return_dict=return_dict, output="silent", **kwargs)
def set_state(self, key: str, value: Any) -> None:
"""Set a state value"""
@@ -1540,6 +1540,7 @@ Now provide your final answer using this result."""
max_iterations = 10 # Prevent infinite loops
iteration_count = 0
final_response_text = ""
response_text = "" # Initialize to prevent UnboundLocalError on API errors
stored_reasoning_content = None # Store reasoning content from tool execution
accumulated_tool_results = [] # Store all tool results across iterations
@@ -739,7 +739,8 @@ class OpenAIClient:
content=content
))
first_token_emitted = True
else:
elif _emit:
# Emit DELTA_TEXT for subsequent tokens
stream_callback(StreamEvent(
type=StreamEventType.DELTA_TEXT,
timestamp=last_content_time,
+207 -22
View File
@@ -30,6 +30,53 @@ async_display_callbacks = {}
# Global approval callback registry
approval_callback = None
# ─────────────────────────────────────────────────────────────────────────────
# PraisonAI Unique Color Palette: "Elegant Intelligence"
# Creates a visual narrative flow: Agent → Task → Working → Response
# ─────────────────────────────────────────────────────────────────────────────
PRAISON_COLORS = {
# Agent identity - grounded, stable
"agent": "#86A789", # Soft Sage Green
"agent_text": "#D2E3C8", # Light sage for text
# Task/Question - input, attention-grabbing
"task": "#FF9B9B", # Warm Coral
"task_text": "#FFE5E5", # Light coral for text
# Working/Processing - action, energy
"working": "#FFB347", # Amber
"working_text": "#FFF3E0", # Light amber for text
# Response/Output - completion, calm
"response": "#4A90D9", # Ocean Blue
"response_text": "#E3F2FD", # Light blue for text
# Tool calls - special action
"tool": "#9B7EDE", # Violet Accent
"tool_text": "#EDE7F6", # Light violet for text
# Reasoning - thought process
"reasoning": "#78909C", # Blue Gray
"reasoning_text": "#ECEFF1", # Light gray for text
# Error/Warning - alert
"error": "#E57373", # Alert Red
"error_text": "#FFEBEE", # Light red for text
# Metrics - meta information
"metrics": "#B4B4B3", # Cool Gray
"metrics_text": "#FAFAFA", # Near white for text
}
# Status animation frames for "Working" indicator
WORKING_FRAMES = ["●○○", "○●○", "○○●", "○●○"]
WORKING_PHASES = [
"Analyzing query...",
"Processing context...",
"Generating response...",
"Finalizing output...",
]
# At the top of the file, add display_callbacks to __all__
__all__ = [
'error_logs',
@@ -39,7 +86,19 @@ __all__ = [
'async_display_callbacks',
'execute_callback',
'approval_callback',
# ... other exports
# Color palette and animation constants
'PRAISON_COLORS',
'WORKING_FRAMES',
'WORKING_PHASES',
# Display functions
'display_interaction',
'display_instruction',
'display_tool_call',
'display_error',
'display_generating',
'display_reasoning_steps',
'display_working_status',
'display_self_reflection',
]
def register_display_callback(display_type: str, callback_fn, is_async: bool = False):
@@ -149,10 +208,14 @@ def _clean_display_content(content: str, max_length: int = 20000) -> str:
return content.strip()
def display_interaction(message, response, markdown=True, generation_time=None, console=None, agent_name=None, agent_role=None, agent_tools=None, task_name=None, task_description=None, task_id=None):
def display_interaction(message, response, markdown=True, generation_time=None, console=None, agent_name=None, agent_role=None, agent_tools=None, task_name=None, task_description=None, task_id=None, metrics=None):
"""Synchronous version of display_interaction.
Displays the task/message and response in clean panels with response time in title.
Displays the task/message and response in clean panels with semantic colors
and optional metrics footer. Uses PraisonAI's unique color palette.
Args:
metrics: Optional dict with token_in, token_out, cost, model for footer display
"""
if console is None:
console = Console()
@@ -180,20 +243,38 @@ def display_interaction(message, response, markdown=True, generation_time=None,
agent_tools=agent_tools,
task_name=task_name,
task_description=task_description,
task_id=task_id
task_id=task_id,
metrics=metrics
)
# Build response title with time (Agno-style: "Response (1.2s)")
# Build response title with time
response_title = "Response"
if generation_time:
response_title = f"Response ({generation_time:.1f}s)"
# Build response content with optional metrics footer
response_content = response
if metrics and isinstance(metrics, dict):
# Add dashed separator and compact metrics line
tokens_in = metrics.get('tokens_in', 0)
tokens_out = metrics.get('tokens_out', 0)
cost = metrics.get('cost', 0)
model = metrics.get('model', '')
metrics_line = f"\n\n─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─\n📊 {tokens_in} tokens in · {tokens_out} out"
if cost > 0:
metrics_line += f" · ${cost:.4f}"
if model:
metrics_line += f" · {model}"
response_content = response + metrics_line
# Use semantic colors: Task (coral), Response (ocean blue)
if markdown:
console.print(Panel.fit(Markdown(message), title="Task", border_style="cyan"))
console.print(Panel.fit(Markdown(response), title=response_title, border_style="cyan"))
console.print(Panel.fit(Markdown(message), title="Task", border_style=PRAISON_COLORS["task"]))
console.print(Panel.fit(Markdown(response_content), title=response_title, border_style=PRAISON_COLORS["response"]))
else:
console.print(Panel.fit(Text(message, style="bold green"), title="Task", border_style="cyan"))
console.print(Panel.fit(Text(response, style="bold blue"), title=response_title, border_style="cyan"))
console.print(Panel.fit(Text(message, style=f"bold {PRAISON_COLORS['task_text']}"), title="Task", border_style=PRAISON_COLORS["task"]))
console.print(Panel.fit(Text(response_content, style=f"bold {PRAISON_COLORS['response_text']}"), title=response_title, border_style=PRAISON_COLORS["response"]))
def display_self_reflection(message: str, console=None):
if not message or not message.strip():
@@ -231,8 +312,11 @@ def display_instruction(message: str, console=None, agent_name: str = None, agen
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
def display_tool_call(message: str, console=None, tool_name: str = None, tool_input: dict = None, tool_output: str = None):
"""Display tool call information in a consolidated panel.
def display_tool_call(message: str, console=None, tool_name: str = None, tool_input: dict = None, tool_output: str = None, elapsed_time: float = None, success: bool = True):
"""Display tool call information in PraisonAI's unique timeline format.
Uses ▸ prefix, inline timing [X.Xs], and status icons ✓/✗ for a clean,
scannable tool activity display.
Args:
message: The tool call message (legacy format)
@@ -240,6 +324,8 @@ def display_tool_call(message: str, console=None, tool_name: str = None, tool_in
tool_name: Name of the tool being called
tool_input: Input arguments to the tool
tool_output: Output from the tool (if available)
elapsed_time: Time taken for tool execution in seconds
success: Whether the tool call succeeded
"""
logging.debug(f"display_tool_call called with message: {repr(message)}")
if not message or not message.strip():
@@ -253,17 +339,45 @@ def display_tool_call(message: str, console=None, tool_name: str = None, tool_in
# Only print panel if console is provided (verbose mode)
if console is not None:
# Build consolidated tool call display (Agno-style)
if tool_name and tool_input is not None:
# Format: tool_name(arg1=val1, arg2=val2)
args_str = ", ".join(f"{k}={v}" for k, v in tool_input.items()) if tool_input else ""
tool_display = f"• {tool_name}({args_str})"
# Build unique PraisonAI timeline format
if tool_name:
# Format: ▸ tool_name(args) [X.Xs] ✓
args_str = ""
if tool_input:
# Truncate long values for display
args_parts = []
for k, v in tool_input.items():
v_str = str(v)
if len(v_str) > 50:
v_str = v_str[:47] + "..."
args_parts.append(f"{k}={repr(v_str) if isinstance(v, str) else v_str}")
args_str = ", ".join(args_parts)
# Build the timeline entry
status_icon = "✓" if success else "✗"
time_str = f"[{elapsed_time:.1f}s]" if elapsed_time else ""
tool_display = f"▸ {tool_name}({args_str}) {time_str} {status_icon}"
# Add output on next line with tree connector
if tool_output:
tool_display += f"\n → {tool_output}"
console.print(Panel.fit(Text(tool_display, style="bold cyan"), title="Tool Call", border_style="green"))
output_str = str(tool_output)
if len(output_str) > 100:
output_str = output_str[:97] + "..."
tool_display += f"\n └─ {output_str}"
console.print(Panel.fit(
Text(tool_display, style=f"bold {PRAISON_COLORS['tool_text']}"),
title="Tool Activity",
border_style=PRAISON_COLORS["tool"]
))
else:
# Legacy format
console.print(Panel.fit(Text(message, style="bold cyan"), title="Tool Call", border_style="green"))
# Legacy format with new colors
console.print(Panel.fit(
Text(message, style=f"bold {PRAISON_COLORS['tool_text']}"),
title="Tool Activity",
border_style=PRAISON_COLORS["tool"]
))
def display_error(message: str, console=None):
if not message or not message.strip():
@@ -275,7 +389,12 @@ def display_error(message: str, console=None):
# Execute synchronous callbacks
execute_sync_callback('error', message=message)
console.print(Panel.fit(Text(message, style="bold red"), title="Error", border_style="red"))
# Use semantic error color
console.print(Panel.fit(
Text(message, style=f"bold {PRAISON_COLORS['error_text']}"),
title="⚠ Error",
border_style=PRAISON_COLORS["error"]
))
error_logs.append(message)
def display_generating(content: str = "", start_time: Optional[float] = None):
@@ -293,7 +412,73 @@ def display_generating(content: str = "", start_time: Optional[float] = None):
# Execute synchronous callbacks
execute_sync_callback('generating', content=content, elapsed_time=elapsed_str.strip() if elapsed_str else None)
return Panel(Markdown(content), title=f"Generating...{elapsed_str}", border_style="green")
# Use semantic response color
return Panel(Markdown(content), title=f"Generating...{elapsed_str}", border_style=PRAISON_COLORS["response"])
def display_reasoning_steps(steps: List[str], console=None):
"""Display reasoning steps with unique numbered circles.
Uses ①②③ numbered circles for a distinctive, scannable format
that shows the agent's thought process.
Args:
steps: List of reasoning step descriptions
console: Rich console for output
"""
if not steps:
return
if console is None:
console = Console()
# Circle number mapping (supports up to 20 steps)
circle_numbers = "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳"
# Build reasoning display with numbered circles
reasoning_lines = []
for i, step in enumerate(steps):
circle = circle_numbers[i] if i < len(circle_numbers) else f"({i+1})"
step_text = _clean_display_content(str(step))
if len(step_text) > 80:
step_text = step_text[:77] + "..."
reasoning_lines.append(f"{circle} {step_text}")
reasoning_display = "\n".join(reasoning_lines)
console.print(Panel.fit(
Text(reasoning_display, style=f"italic {PRAISON_COLORS['reasoning_text']}"),
title="Reasoning",
border_style=PRAISON_COLORS["reasoning"]
))
def display_working_status(phase: int = 0, status_text: str = None, console=None):
"""Display animated working status with pulsing dots.
Shows a unique "Working ●○○" indicator with phase-specific status.
This is PraisonAI's distinctive approach to showing processing status.
Args:
phase: Current animation phase (0-3)
status_text: Optional status description
console: Rich console for output
Returns:
Panel object for use with Rich.Live
"""
if console is None:
console = Console()
# Get current frame and phase text
frame = WORKING_FRAMES[phase % len(WORKING_FRAMES)]
phase_text = status_text or WORKING_PHASES[phase % len(WORKING_PHASES)]
# Build working status display
working_display = f"Working {frame} {phase_text}"
return Panel.fit(
Text(working_display, style=f"bold {PRAISON_COLORS['working_text']}"),
title="Status",
border_style=PRAISON_COLORS["working"]
)
# Async versions with 'a' prefix
async def adisplay_interaction(message, response, markdown=True, generation_time=None, console=None, agent_name=None, agent_role=None, agent_tools=None, task_name=None, task_description=None, task_id=None):
@@ -26,16 +26,41 @@ _telemetry_executor = None
_telemetry_queue = None
_queue_processor_running = False
_queue_lock = threading.Lock()
_atexit_registered = False
def _atexit_cleanup():
"""Cleanup handler registered with atexit to prevent hanging on exit."""
global _queue_processor_running, _telemetry_executor, _telemetry_queue
# Signal queue processor to stop
_queue_processor_running = False
# Shutdown executor without waiting (to prevent hang)
if _telemetry_executor is not None:
try:
_telemetry_executor.shutdown(wait=False)
except:
pass
_telemetry_executor = None
_telemetry_queue = None
def _get_telemetry_executor():
"""Get or create the shared telemetry thread pool executor."""
global _telemetry_executor
global _telemetry_executor, _atexit_registered
if _telemetry_executor is None:
# Use a small thread pool to avoid resource overhead
_telemetry_executor = concurrent.futures.ThreadPoolExecutor(
max_workers=2,
thread_name_prefix="telemetry"
)
# Register atexit cleanup to prevent hanging on exit
if not _atexit_registered:
import atexit
atexit.register(_atexit_cleanup)
_atexit_registered = True
return _telemetry_executor
def _get_telemetry_queue():
@@ -48,8 +73,13 @@ def _get_telemetry_queue():
with _queue_lock:
if not _queue_processor_running:
_queue_processor_running = True
executor = _get_telemetry_executor()
executor.submit(_process_telemetry_queue)
# Use a daemon thread directly instead of executor to prevent hang
processor_thread = threading.Thread(
target=_process_telemetry_queue,
name="telemetry-queue-processor",
daemon=True # Daemon thread will be killed on exit
)
processor_thread.start()
return _telemetry_queue
@@ -61,12 +91,22 @@ def _process_telemetry_queue():
try:
while _queue_processor_running:
# Check if interpreter is shutting down
try:
import sys
if hasattr(sys, 'is_finalizing') and sys.is_finalizing():
break
except:
break
events = []
deadline = time.time() + batch_timeout
# Collect events until batch size or timeout
while len(events) < batch_size and time.time() < deadline:
try:
if _telemetry_queue is None:
break
event = _telemetry_queue.get(timeout=0.1)
events.append(event)
_telemetry_queue.task_done()
@@ -381,6 +381,10 @@ class PerformanceMonitor:
Returns:
List of flow events showing function execution order and timing
"""
# If monitoring is disabled, return empty results
if self._monitoring_disabled:
return []
with self._lock:
flow = []
for event in self._function_flow.copy():
@@ -400,6 +404,10 @@ class PerformanceMonitor:
def get_active_calls(self) -> Dict[str, Any]:
"""Get information about currently executing functions."""
# If monitoring is disabled, return empty results
if self._monitoring_disabled:
return {}
with self._lock:
active = {}
current_time = time.time()
@@ -417,6 +425,10 @@ class PerformanceMonitor:
def get_slowest_functions(self, limit: int = 10) -> List[Dict[str, Any]]:
"""Get the slowest performing functions."""
# If monitoring is disabled, return empty results
if self._monitoring_disabled:
return []
with self._lock:
functions = []
for name, stats in self._function_stats.items():
@@ -435,6 +447,10 @@ class PerformanceMonitor:
def get_slowest_api_calls(self, limit: int = 10) -> List[Dict[str, Any]]:
"""Get the slowest performing API calls."""
# If monitoring is disabled, return empty results
if self._monitoring_disabled:
return []
with self._lock:
apis = []
for name, stats in self._api_calls.items():
@@ -539,6 +555,10 @@ class PerformanceMonitor:
def clear_statistics(self):
"""Clear all performance statistics."""
# If monitoring is disabled, nothing to clear
if self._monitoring_disabled:
return
with self._lock:
self._function_stats.clear()
self._api_calls.clear()
@@ -691,7 +691,7 @@ def force_shutdown_telemetry():
def enable_telemetry():
"""Programmatically enable telemetry (if not disabled by environment)."""
global _telemetry_instance
if not _TELEMETRY_DISABLED:
if not _is_telemetry_disabled():
if _telemetry_instance:
_telemetry_instance.enabled = True
else:
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.11.8"
version = "0.11.14"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
readme = "README.md"
requires-python = ">=3.10"
+22
View File
@@ -0,0 +1,22 @@
"""Test full agent flow with new verbose UI."""
from praisonaiagents import Agent
# Create agent - use output preset for verbose mode
agent = Agent(
name="AIAssistant",
role="Helpful AI",
instructions="You are a helpful assistant. Be concise and give short answers.",
output="verbose", # Use output preset instead of verbose kwarg
)
if __name__ == "__main__":
print("\n" + "=" * 60)
print("TESTING FULL AGENT FLOW WITH NEW UI")
print("=" * 60 + "\n")
# Use start() which should show verbose output
result = agent.start("What is the capital of France? One sentence only.")
print("\n" + "=" * 60)
print(f"FINAL RESULT: {result}")
print("=" * 60)
+112
View File
@@ -0,0 +1,112 @@
"""Test script with forced verbose mode to verify PraisonAI's unique output UI."""
import sys
from rich.console import Console
# Force Rich to think we're in a TTY for testing
console = Console(force_terminal=True)
from praisonaiagents import Agent
from praisonaiagents.main import (
display_interaction,
display_instruction,
display_tool_call,
display_reasoning_steps,
display_working_status,
display_error,
PRAISON_COLORS
)
print("=" * 70)
print("TESTING PRAISONAI UNIQUE VERBOSE OUTPUT UI")
print("=" * 70)
# Test 1: Display Agent Info panel
print("\n[TEST 1] Agent Info Panel (Sage Green border)")
display_instruction(
message="Test instruction",
console=console,
agent_name="ResearchAgent",
agent_role="Data Analyst",
agent_tools=["search_web", "calculate", "read_file"]
)
# Test 2: Display interaction with semantic colors
print("\n[TEST 2] Task (Coral) + Response (Ocean Blue) panels")
display_interaction(
message="What is 2+2?",
response="2+2 equals 4. This is a basic arithmetic operation.",
markdown=True,
generation_time=1.5,
console=console
)
# Test 3: Display interaction with metrics footer
print("\n[TEST 3] Response with Metrics Footer")
display_interaction(
message="Explain quantum computing",
response="Quantum computing uses qubits instead of classical bits.",
markdown=True,
generation_time=3.2,
console=console,
metrics={
'tokens_in': 45,
'tokens_out': 128,
'cost': 0.0023,
'model': 'gpt-4o-mini'
}
)
# Test 4: Display tool call with timeline format
print("\n[TEST 4] Tool Activity Panel (Violet border, timeline format)")
display_tool_call(
message="search_web(query='quantum computing')",
console=console,
tool_name="search_web",
tool_input={"query": "quantum computing"},
tool_output="Found 5 relevant articles",
elapsed_time=0.8,
success=True
)
# Test 5: Display tool call failure
print("\n[TEST 5] Tool Activity Panel (Failed call)")
display_tool_call(
message="api_call failed",
console=console,
tool_name="api_call",
tool_input={"endpoint": "/users", "method": "GET"},
tool_output="Connection timeout",
elapsed_time=5.0,
success=False
)
# Test 6: Display reasoning steps with numbered circles
print("\n[TEST 6] Reasoning Steps (Numbered circles ①②③)")
display_reasoning_steps(
steps=[
"Identified query about arithmetic",
"Retrieved basic math rules",
"Calculated result: 2+2=4",
"Formulated concise response"
],
console=console
)
# Test 7: Display working status (static, shows format)
print("\n[TEST 7] Working Status (Amber border, pulsing dots)")
for phase in range(4):
panel = display_working_status(phase=phase, console=console)
console.print(panel)
# Test 8: Display error
print("\n[TEST 8] Error Panel (Red border)")
display_error(
message="API rate limit exceeded. Please try again in 60 seconds.",
console=console
)
print("\n" + "=" * 70)
print("COLORS BEING USED:")
for name, color in PRAISON_COLORS.items():
console.print(f" {name}: [bold {color}]████████[/] {color}")
print("=" * 70)
@@ -0,0 +1,182 @@
"""
Unit tests for Agent.start() method defaulting to verbose output in TTY.
Tests the behavior that:
- .start() defaults to verbose output when running in a TTY
- .start() uses agent's default (silent) when NOT in TTY
- .run() always uses the agent's default (silent)
- Explicit output= kwarg overrides TTY detection
This implements Option B: Make .start() default to verbose output in TTY.
"""
import pytest
from unittest.mock import Mock, patch
import sys
class TestStartVerboseInTTY:
"""Test that .start() defaults to verbose output in TTY."""
def test_start_sets_verbose_in_tty(self):
"""Verify .start() sets verbose=True when running in TTY."""
# Mock isatty to return True
with patch.object(sys.stdout, 'isatty', return_value=True):
# Create agent with default (silent) output
from praisonaiagents import Agent
agent = Agent(instructions="Test agent")
# Verify default is silent
assert agent.verbose == False, "Default should be silent (verbose=False)"
# Track what happens in start
original_chat = agent.chat
captured_verbose = []
def mock_chat(*args, **kwargs):
captured_verbose.append(agent.verbose)
return "mock response"
agent.chat = mock_chat
agent._load_history_context = Mock()
agent._auto_save_session = Mock()
# Call start
result = agent.start("Hello")
# Verbose should have been True during the call
assert len(captured_verbose) == 1, "chat() should have been called"
assert captured_verbose[0] == True, ".start() should set verbose=True in TTY"
# Verbose should be restored after
assert agent.verbose == False, "verbose should be restored after .start()"
def test_start_respects_explicit_output_kwarg(self):
"""Verify .start() respects explicit output='silent' even in TTY."""
with patch.object(sys.stdout, 'isatty', return_value=True):
from praisonaiagents import Agent
agent = Agent(instructions="Test agent")
captured_verbose = []
def mock_chat(*args, **kwargs):
captured_verbose.append(agent.verbose)
return "mock response"
agent.chat = mock_chat
agent._load_history_context = Mock()
agent._auto_save_session = Mock()
# Call start with explicit output='silent'
result = agent.start("Hello", output="silent")
# Should respect explicit silent
assert captured_verbose[0] == False, "explicit output='silent' should be respected"
def test_start_silent_when_not_tty(self):
"""Verify .start() stays silent when not in TTY."""
with patch.object(sys.stdout, 'isatty', return_value=False):
from praisonaiagents import Agent
agent = Agent(instructions="Test agent")
captured_verbose = []
def mock_chat(*args, **kwargs):
captured_verbose.append(agent.verbose)
return "mock response"
agent.chat = mock_chat
agent._load_history_context = Mock()
agent._auto_save_session = Mock()
# Call start in non-TTY
result = agent.start("Hello")
# Should remain silent
assert captured_verbose[0] == False, ".start() should stay silent when not TTY"
def test_run_always_silent(self):
"""Verify .run() is always silent regardless of TTY."""
with patch.object(sys.stdout, 'isatty', return_value=True):
from praisonaiagents import Agent
agent = Agent(instructions="Test agent")
captured_verbose = []
def mock_chat(*args, **kwargs):
captured_verbose.append(agent.verbose)
return "mock response"
agent.chat = mock_chat
agent._load_history_context = Mock()
agent._auto_save_session = Mock()
# Call run (should stay silent even in TTY)
result = agent.run("Hello")
# Should be silent
assert captured_verbose[0] == False, ".run() should always be silent"
class TestStartVerbosePresets:
"""Test the verbose preset values applied by .start()."""
def test_start_enables_markdown_in_tty(self):
"""Verify .start() also enables markdown=True in TTY."""
with patch.object(sys.stdout, 'isatty', return_value=True):
from praisonaiagents import Agent
agent = Agent(instructions="Test agent")
# Verify default is no markdown
assert agent.markdown == False, "Default should be no markdown"
captured_markdown = []
def mock_chat(*args, **kwargs):
captured_markdown.append(agent.markdown)
return "mock response"
agent.chat = mock_chat
agent._load_history_context = Mock()
agent._auto_save_session = Mock()
# Call start
result = agent.start("Hello")
# Markdown should have been True during the call
assert captured_markdown[0] == True, ".start() should set markdown=True in TTY"
# Should be restored after
assert agent.markdown == False, "markdown should be restored after .start()"
class TestAgentWithExplicitVerbose:
"""Test that agents created with output='verbose' work correctly."""
def test_agent_with_verbose_output(self):
"""Agent created with output='verbose' should have verbose=True."""
from praisonaiagents import Agent
agent = Agent(instructions="Test", output="verbose")
assert agent.verbose == True, "output='verbose' should set verbose=True"
assert agent.markdown == True, "output='verbose' should set markdown=True"
def test_start_with_already_verbose_agent(self):
"""Start on already-verbose agent should stay verbose."""
with patch.object(sys.stdout, 'isatty', return_value=True):
from praisonaiagents import Agent
agent = Agent(instructions="Test", output="verbose")
captured_verbose = []
def mock_chat(*args, **kwargs):
captured_verbose.append(agent.verbose)
return "mock response"
agent.chat = mock_chat
agent._load_history_context = Mock()
agent._auto_save_session = Mock()
result = agent.start("Hello")
assert captured_verbose[0] == True
assert agent.verbose == True, "Already-verbose agent should stay verbose"
+1 -1
View File
@@ -2422,7 +2422,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.11.8"
version = "0.11.14"
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.2.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.8.2.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.8.4.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v3.8.4.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
+1 -1
View File
@@ -57,7 +57,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==3.8.2 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==3.8.4 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.2"
__version__ = "3.8.4"
+1 -1
View File
@@ -12,7 +12,7 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
"praisonaiagents>=0.11.8",
"praisonaiagents>=0.11.14",
"python-dotenv>=0.19.0",
"instructor>=1.3.3",
"PyYAML>=6.0",
+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.8" },
{ name = "praisonaiagents", specifier = ">=0.11.14" },
{ 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.8"
version = "0.11.14"
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/92/e8/e0a52140b3c09be369dd48dbb71addf8bb711c3267d05abf352ef6717f55/praisonaiagents-0.11.8.tar.gz", hash = "sha256:16599d391838d456bb4933c5778049ff366eb2fb812f8e5f645b2d60c9c919c5", size = 993464, upload-time = "2026-01-13T04:11:31.609Z" }
sdist = { url = "https://files.pythonhosted.org/packages/dd/1f/15725e91e2ea2944c37b6018758af642c072442775aab832822d92768620/praisonaiagents-0.11.14.tar.gz", hash = "sha256:80a501f154694eeb53ff299f0ddea1f69e1424eb49db62a4ecffab5590024e20", size = 1000104, upload-time = "2026-01-13T06:09:16.581Z" }
wheels = [
{ 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" },
{ url = "https://files.pythonhosted.org/packages/7b/16/847492cb578547db7d375c3158302de00ae43f897ff85fc7dc3ecb1f1cdb/praisonaiagents-0.11.14-py3-none-any.whl", hash = "sha256:119c454f4db066e55030461e373b2d495d2691f5be7ce3912648dfe3340190a2", size = 984141, upload-time = "2026-01-13T06:09:14.054Z" },
]
[[package]]