Compare commits

...
4 Commits
Author SHA1 Message Date
Mervin Praison d63b437eca feat: add workflow visualization with FlowDisplay and bump version to 2.2.77 2025-07-25 12:05:41 +01:00
Mervin Praison 04aac1c2d5 fix: skip display output for empty Gemini responses and bump version to 0.0.150 2025-07-25 09:51:56 +01:00
Mervin Praison eca66f9213 feat: bump praisonai to v2.2.76 and praisonaiagents to v0.0.149 with callback refactor 2025-07-25 09:41:04 +01:00
Mervin PraisonandClaude f83f3eae0b fix: ensure consistent display output between OpenAI and Gemini models
- Added execute_sync_callback call in custom LLM path to match OpenAI behavior
- Fixed issue where Gemini/custom LLM responses were not triggering callbacks
- Prevents duplicate display by relying on LLM class to handle verbose display
- Ensures both paths execute callbacks for consistent behavior

The issue was that custom LLM providers (like Gemini) were not calling
_execute_callback_and_display after guardrail validation, while OpenAI
path was. This caused missing Task/Response display boxes for Gemini.

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

Co-Authored-By: Claude <[email protected]>
2025-07-25 08:31:38 +01:00
16 changed files with 267 additions and 21 deletions

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
flask \
"praisonai>=2.2.75" \
"praisonai>=2.2.77" \
"praisonai[api]" \
gunicorn \
markdown
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.2.75" \
"praisonai>=2.2.77" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.2.75" \
"praisonai>=2.2.77" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.2.75" \
"praisonai>=2.2.77" \
"praisonai[ui]" \
"praisonai[crewai]"
+2 -2
View File
@@ -121,7 +121,7 @@ healthcheck:
## 📦 Package Versions
All Docker images use consistent, up-to-date versions:
- PraisonAI: `>=2.2.75`
- PraisonAI: `>=2.2.77`
- PraisonAI Agents: `>=0.0.92`
- Python: `3.11-slim`
@@ -218,7 +218,7 @@ docker-compose up -d
### Version Pinning
To use specific versions, update the Dockerfile:
```dockerfile
RUN pip install "praisonai==2.2.75" "praisonaiagents==0.0.92"
RUN pip install "praisonai==2.2.77" "praisonaiagents==0.0.92"
```
## 🌐 Production Deployment
@@ -32,6 +32,12 @@ from .session import Session
from .memory.memory import Memory
from .guardrails import GuardrailResult, LLMGuardrail
from .agent.handoff import Handoff, handoff, handoff_filters, RECOMMENDED_PROMPT_PREFIX, prompt_with_handoff_instructions
# Flow display
try:
from .flow_display import FlowDisplay, track_workflow
except ImportError:
FlowDisplay = None
track_workflow = None
from .main import (
TaskOutput,
ReflectionOutput,
@@ -135,4 +141,9 @@ __all__ = [
# Add MCP to __all__ if available
if _mcp_available:
__all__.append('MCP')
__all__.append('MCP')
# Add flow display if available
if FlowDisplay is not None:
__all__.extend(['FlowDisplay', 'track_workflow'])
@@ -1360,6 +1360,8 @@ Your Goal: {self.goal}"""
# Apply guardrail validation for custom LLM response
try:
validated_response = self._apply_guardrail_with_retry(response_text, prompt, temperature, tools, task_name, task_description, task_id)
# Execute callback and display after validation
self._execute_callback_and_display(prompt, validated_response, time.time() - start_time, task_name, task_description, task_id)
return validated_response
except Exception as e:
logging.error(f"Agent {self.name}: Guardrail validation failed for custom LLM: {e}")
@@ -0,0 +1,194 @@
"""
Flow Display for PraisonAI Agents
Visual display with agents in center and tools on sides.
"""
from typing import Dict, List, Set, Tuple
from collections import defaultdict
import threading
from rich.console import Console
from rich.panel import Panel
from rich.text import Text
from rich.align import Align
from rich.columns import Columns
from rich.table import Table
from rich import box
class FlowDisplay:
"""Displays agent workflow with agents centered and tools on sides."""
def __init__(self):
self.console = Console()
self.agents = [] # List of agents in order
self.agent_tools = defaultdict(list) # agent -> [tools]
self.tracking = False
self.lock = threading.Lock()
def start(self):
"""Start tracking workflow."""
self.tracking = True
self.agents.clear()
self.agent_tools.clear()
# Register callbacks
try:
from praisonaiagents.main import register_display_callback
def on_interaction(**kwargs):
if self.tracking:
agent = kwargs.get('agent_name', 'Unknown')
self._add_agent(agent)
def on_tool_call(message, **kwargs):
if self.tracking and "called function" in message:
parts = message.split("'")
if len(parts) > 1:
tool_name = parts[1]
agent = kwargs.get('agent_name', 'Unknown')
self._add_tool(agent, tool_name)
register_display_callback('interaction', on_interaction)
register_display_callback('tool_call', on_tool_call)
except ImportError:
pass
def stop(self):
"""Stop tracking and display the flow."""
self.tracking = False
self.display()
def _add_agent(self, name: str):
"""Add an agent if not already present."""
with self.lock:
if name not in self.agents:
self.agents.append(name)
def _add_tool(self, agent_name: str, tool_name: str):
"""Add a tool to an agent."""
with self.lock:
if agent_name not in self.agents:
self.agents.append(agent_name)
if tool_name not in self.agent_tools[agent_name]:
self.agent_tools[agent_name].append(tool_name)
def display(self):
"""Display the flow chart with agents in center and tools on sides."""
if not self.agents:
return
self.console.print("\n[bold cyan]🔄 Agent Workflow Flow[/bold cyan]\n")
# Display start
self._display_centered_node("── start ──", "grey35")
self._display_arrow_down()
# Display each agent with their tools
for i, agent in enumerate(self.agents):
self._display_agent_with_tools(agent)
# Add arrow to next agent or end
if i < len(self.agents) - 1:
self._display_arrow_down()
# Display end
self._display_arrow_down()
self._display_centered_node("── end ──", "grey35")
def _display_agent_with_tools(self, agent: str):
"""Display agent with tools on the sides."""
tools = self.agent_tools.get(agent, [])
if not tools:
# No tools - just agent
self._display_centered_node(agent, "purple")
else:
# Split tools between left and right
left_tools = tools[::2] # Even indices
right_tools = tools[1::2] # Odd indices
# Create the layout
table = Table(show_header=False, show_edge=False, box=None, padding=0)
table.add_column(justify="center", min_width=20) # Left tools
table.add_column(justify="center", min_width=5) # Space
table.add_column(justify="center", min_width=20) # Agent
table.add_column(justify="center", min_width=5) # Space
table.add_column(justify="center", min_width=20) # Right tools
# Create panels
left_panel = self._create_tools_panel(left_tools) if left_tools else ""
agent_panel = Panel(
Text(agent, style="white on purple", justify="center"),
style="white on purple",
box=box.ROUNDED,
padding=(0, 2)
)
right_panel = self._create_tools_panel(right_tools) if right_tools else ""
# Add row
table.add_row(left_panel, "", agent_panel, "", right_panel)
# Display centered
self.console.print(Align.center(table))
# Show arrows
if left_tools or right_tools:
arrow_parts = []
if left_tools:
arrow_parts.append("←→")
else:
arrow_parts.append(" ")
arrow_parts.append(" ") # Center space
if right_tools:
arrow_parts.append("←→")
else:
arrow_parts.append(" ")
self.console.print(Align.center(Text("".join(arrow_parts))))
def _create_tools_panel(self, tools: List[str]) -> Panel:
"""Create a panel for tools."""
if not tools:
return ""
if len(tools) == 1:
return Panel(
Text(tools[0], style="black on yellow", justify="center"),
style="black on yellow",
box=box.ROUNDED,
padding=(0, 1)
)
else:
# Multiple tools
content = "\n".join(tools)
return Panel(
Text(content, style="black on yellow", justify="center"),
style="black on yellow",
box=box.ROUNDED,
padding=(0, 1)
)
def _display_centered_node(self, label: str, color: str):
"""Display a centered node."""
panel = Panel(
Text(label, style=f"white on {color}", justify="center"),
style=f"white on {color}",
box=box.ROUNDED,
padding=(0, 2)
)
self.console.print(Align.center(panel))
def _display_arrow_down(self):
"""Display a downward arrow."""
self.console.print()
self.console.print(Align.center("↓"))
self.console.print()
# Simple function to create and use
def track_workflow():
"""Create a flow display tracker."""
return FlowDisplay()
@@ -154,6 +154,10 @@ def display_interaction(message, response, markdown=True, generation_time=None,
message = _clean_display_content(str(message))
response = _clean_display_content(str(response))
# Skip display if response is empty (common with Gemini tool calls)
if not response or not response.strip():
return
# Execute synchronous callbacks
execute_sync_callback(
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.0.148"
version = "0.0.151"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
requires-python = ">=3.10"
authors = [
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env python
"""
Test the flow display with agents centered and tools on sides.
"""
from praisonaiagents.flow_display import track_workflow
# Create flow tracker
flow = track_workflow()
flow.tracking = True
# Simulate a workflow
# Agent 1 with multiple tools
flow._add_agent("Researcher")
flow._add_tool("Researcher", "web_search")
flow._add_tool("Researcher", "fetch_data")
flow._add_tool("Researcher", "parse_html")
# Agent 2 with one tool
flow._add_agent("Analyst")
flow._add_tool("Analyst", "analyze_data")
# Agent 3 with multiple tools
flow._add_agent("Reporter")
flow._add_tool("Reporter", "format_report")
flow._add_tool("Reporter", "send_email")
flow._add_tool("Reporter", "save_file")
flow._add_tool("Reporter", "upload_cloud")
# Agent 4 with no tools
flow._add_agent("Reviewer")
# Display the flow
flow.tracking = False
flow.display()
+1 -1
View File
@@ -2422,7 +2422,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.148"
version = "0.0.151"
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/v2.2.75.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.75.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.77.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.77.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
+1 -1
View File
@@ -57,7 +57,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==2.2.75 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.2.77 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+4 -4
View File
@@ -1,6 +1,6 @@
[project]
name = "PraisonAI"
version = "2.2.75"
version = "2.2.77"
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration."
readme = "README.md"
license = ""
@@ -12,7 +12,7 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
"praisonaiagents>=0.0.148",
"praisonaiagents>=0.0.151",
"python-dotenv>=0.19.0",
"instructor>=1.3.3",
"PyYAML>=6.0",
@@ -102,7 +102,7 @@ autogen-v4 = [
[tool.poetry]
name = "PraisonAI"
version = "2.2.75"
version = "2.2.77"
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration."
authors = ["Mervin Praison"]
license = ""
@@ -120,7 +120,7 @@ python = ">=3.10,<3.13"
rich = ">=13.7"
markdown = ">=3.5"
pyparsing = ">=3.0.0"
praisonaiagents = ">=0.0.148"
praisonaiagents = ">=0.0.151"
python-dotenv = ">=0.19.0"
instructor = ">=1.3.3"
PyYAML = ">=6.0"
+5 -5
View File
@@ -4031,7 +4031,7 @@ wheels = [
[[package]]
name = "praisonai"
version = "2.2.75"
version = "2.2.77"
source = { editable = "." }
dependencies = [
{ name = "instructor" },
@@ -4185,7 +4185,7 @@ requires-dist = [
{ name = "praisonai-tools", marker = "extra == 'autogen'", specifier = ">=0.0.22" },
{ name = "praisonai-tools", marker = "extra == 'autogen-v4'", specifier = ">=0.0.22" },
{ name = "praisonai-tools", marker = "extra == 'crewai'", specifier = ">=0.0.22" },
{ name = "praisonaiagents", specifier = ">=0.0.148" },
{ name = "praisonaiagents", specifier = ">=0.0.151" },
{ name = "pyautogen", marker = "extra == 'autogen'", specifier = "==0.2.29" },
{ name = "pydantic", marker = "extra == 'chat'", specifier = "<=2.10.1" },
{ name = "pydantic", marker = "extra == 'code'", specifier = "<=2.10.1" },
@@ -4240,7 +4240,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.148"
version = "0.0.151"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@@ -4249,9 +4249,9 @@ dependencies = [
{ name = "pydantic" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5f/29/091fbf799e1a9c350bbce1148dd57e0b9142fed62c651ac7aec0f5027a08/praisonaiagents-0.0.148.tar.gz", hash = "sha256:05466b206837387a2c8617e031eaebf2b782c38cd8bd824dac698cabc8ee6aab", size = 270726, upload-time = "2025-07-24T08:59:51.275Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/79/a0ba9dad14ea401cb2e7f7d38d2ffbbc29b5c0ed7a31b592733886256df2/praisonaiagents-0.0.151.tar.gz", hash = "sha256:f34e211b1b4554f6d74ce5f7c7eb54ea6488b71d2f3a8d9f9f76aac21d290a15", size = 272547, upload-time = "2025-07-25T11:04:32.747Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8d/95/fdbddfb89bc464d6968f9fefeb5157c6934aa7c38b6409d8d22c8eefb411/praisonaiagents-0.0.148-py3-none-any.whl", hash = "sha256:8e365f789dd2858bf41dafedf28a8f50c999a6d85152b131d5d570a18b7f9139", size = 288729, upload-time = "2025-07-24T08:59:49.836Z" },
{ url = "https://files.pythonhosted.org/packages/1d/7d/35c4846768aadaa3278583962a9951076b4b1ae615769715a329df752702/praisonaiagents-0.0.151-py3-none-any.whl", hash = "sha256:ad88eb8a93153a4a329b431547bd101456dfa2aa559b8daf4aee98dcc79616c8", size = 290849, upload-time = "2025-07-25T11:04:31.21Z" },
]
[[package]]