mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-09 17:31:52 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3bfc6f795 | ||
|
|
ebc130854d | ||
|
|
f40a5c1359 | ||
|
|
3284f7bbc6 | ||
|
|
87928611f7 | ||
|
|
2727bf5992 | ||
|
|
45d04163c3 | ||
|
|
477d0d2e26 | ||
|
|
77394f5e5f | ||
|
|
29e533c18c | ||
|
|
48dbea0566 | ||
|
|
969d6697e1 | ||
|
|
787d2508b1 | ||
|
|
ec42dc92e4 | ||
|
|
5b7bae2f43 | ||
|
|
69a1efac47 | ||
|
|
e6381139ec | ||
|
|
aca2a58971 | ||
|
|
7a07ebd05e | ||
|
|
418d6c810b | ||
|
|
8bcfdfe713 | ||
|
|
57f2fa0471 | ||
|
|
2fdffd2d8d | ||
|
|
471a6a106c | ||
|
|
7c51c3aad1 |
No files matched your search
+1
-1
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
flask \
|
||||
"praisonai>=2.2.21" \
|
||||
"praisonai>=2.2.22" \
|
||||
"praisonai[api]" \
|
||||
gunicorn \
|
||||
markdown
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.2.21" \
|
||||
"praisonai>=2.2.22" \
|
||||
"praisonai[chat]" \
|
||||
"embedchain[github,youtube]"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.2.21" \
|
||||
"praisonai>=2.2.22" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[chat]" \
|
||||
"praisonai[realtime]" \
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.2.21" \
|
||||
"praisonai>=2.2.22" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[crewai]"
|
||||
|
||||
|
||||
+2
-2
@@ -121,7 +121,7 @@ healthcheck:
|
||||
## 📦 Package Versions
|
||||
|
||||
All Docker images use consistent, up-to-date versions:
|
||||
- PraisonAI: `>=2.2.21`
|
||||
- PraisonAI: `>=2.2.22`
|
||||
- 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.21" "praisonaiagents==0.0.92"
|
||||
RUN pip install "praisonai==2.2.22" "praisonaiagents==0.0.92"
|
||||
```
|
||||
|
||||
## 🌐 Production Deployment
|
||||
|
||||
@@ -110,7 +110,7 @@ Loads environment variables from .env file or system and sets them.</p>
|
||||
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.21 gunicorn markdown\n")
|
||||
file.write("RUN pip install flask praisonai==2.2.22 gunicorn markdown\n")
|
||||
file.write("EXPOSE 8080\n")
|
||||
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN pip install flask praisonai==2.2.21 watchdog
|
||||
RUN pip install flask praisonai==2.2.22 watchdog
|
||||
|
||||
EXPOSE 5555
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: 'Playwright'
|
||||
description: 'Using Playwright MCP with PraisonAI'
|
||||
---
|
||||
|
||||
# Playwright MCP
|
||||
|
||||
[Playwright](https://playwright.dev/) is a powerful browser automation tool that can be used with the Model Context Protocol (MCP) to enable agents to interact with web browsers.
|
||||
|
||||
## Installation
|
||||
|
||||
To use Playwright with MCP, you'll need to install the Playwright MCP package:
|
||||
|
||||
```bash
|
||||
npm install @playwright/mcp
|
||||
```
|
||||
|
||||
## Running the Playwright MCP Server
|
||||
|
||||
You need to run the Playwright MCP server in a separate terminal before your agent can use it:
|
||||
|
||||
```bash
|
||||
npx @playwright/mcp@latest --port 8931
|
||||
```
|
||||
|
||||
This will start a Playwright MCP server on port 8931 that your agent can connect to.
|
||||
|
||||
## Using Playwright MCP with PraisonAI
|
||||
|
||||
Once the Playwright MCP server is running, you can create an agent that uses it as a tool:
|
||||
|
||||
```python
|
||||
from praisonaiagents import Agent, MCP
|
||||
|
||||
search_agent = Agent(
|
||||
instructions="""You help search the web.""",
|
||||
llm="gpt-4o-mini",
|
||||
tools=MCP("http://localhost:8931/sse")
|
||||
)
|
||||
|
||||
search_agent.start("Find about Praison AI")
|
||||
```
|
||||
|
||||
The agent will be able to use Playwright to automate browser interactions and search the web.
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
You can customize the Playwright MCP server by passing additional options:
|
||||
|
||||
```bash
|
||||
npx @playwright/mcp@latest --port 8931 --browser chromium
|
||||
```
|
||||
|
||||
For more information on available options, refer to the [Playwright MCP documentation](https://github.com/microsoft/playwright/tree/main/packages/mcp).
|
||||
@@ -251,6 +251,7 @@
|
||||
"mcp/anthropic",
|
||||
"mcp/gemini",
|
||||
"mcp/mistral",
|
||||
"mcp/playwright",
|
||||
"mcp/whatsapp",
|
||||
"mcp/perplexity",
|
||||
"mcp/ollama-python",
|
||||
|
||||
+1
-1
@@ -155,7 +155,7 @@ To facilitate local development with live reload, you can use Docker. Follow the
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN pip install flask praisonai==2.2.21 watchdog
|
||||
RUN pip install flask praisonai==2.2.22 watchdog
|
||||
|
||||
EXPOSE 5555
|
||||
|
||||
|
||||
+1
-1
@@ -208,7 +208,7 @@ To facilitate local development with live reload, you can use Docker. Follow the
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN pip install flask praisonai==2.2.21 watchdog
|
||||
RUN pip install flask praisonai==2.2.22 watchdog
|
||||
|
||||
EXPOSE 5555
|
||||
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
---
|
||||
title: "MCP with Streamlit"
|
||||
description: "How to properly integrate MCP (Model Context Protocol) tools with PraisonAI Agents in Streamlit applications"
|
||||
---
|
||||
|
||||
# MCP with Streamlit
|
||||
|
||||
This guide explains how to properly integrate MCP (Model Context Protocol) tools with PraisonAI Agents in Streamlit applications, addressing common issues and providing working solutions.
|
||||
|
||||
## Common Issues
|
||||
|
||||
When integrating MCP tools with Streamlit, users often encounter these problems:
|
||||
|
||||
### Issue #1: Agent Re-initialization
|
||||
**Problem**: Agent gets re-initialized on every Streamlit interaction, causing MCP tools to fail.
|
||||
|
||||
**Solution**: Use Streamlit's session state to initialize the agent only once.
|
||||
|
||||
### Issue #2: LLM Provider Format
|
||||
**Problem**: Using provider/model format like `"ollama/llama3.2"` can cause tool calling issues in Streamlit environments.
|
||||
|
||||
**Solution**: Use standard model names like `"gpt-4o-mini"` instead of provider/model format.
|
||||
|
||||
### Issue #3: Missing Error Handling
|
||||
**Problem**: No user feedback when MCP initialization fails.
|
||||
|
||||
**Solution**: Implement comprehensive error handling with user-friendly messages.
|
||||
|
||||
## Working Example
|
||||
|
||||
Here's a complete working example that demonstrates the correct approach:
|
||||
|
||||
```python
|
||||
import streamlit as st
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.mcp import MCP
|
||||
import traceback
|
||||
|
||||
st.title("🏠 AI Airbnb Assistant")
|
||||
|
||||
# Configuration in sidebar
|
||||
with st.sidebar:
|
||||
st.header("⚙️ Configuration")
|
||||
|
||||
# Use standard model names, not provider/model format
|
||||
llm_model = st.selectbox(
|
||||
"Choose LLM Model",
|
||||
options=[
|
||||
"gpt-4o-mini", # ✅ Correct format
|
||||
"gpt-4o",
|
||||
"claude-3-5-sonnet-20241022"
|
||||
],
|
||||
index=0
|
||||
)
|
||||
|
||||
debug_mode = st.checkbox("Enable Debug Mode", value=False)
|
||||
|
||||
# Initialize session state (CRITICAL for Streamlit)
|
||||
if "agent_initialized" not in st.session_state:
|
||||
st.session_state.agent_initialized = False
|
||||
st.session_state.agent = None
|
||||
st.session_state.init_error = None
|
||||
|
||||
# Function to initialize agent with proper error handling
|
||||
def initialize_agent():
|
||||
try:
|
||||
with st.spinner("🔄 Initializing AI agent..."):
|
||||
# Create MCP tools
|
||||
mcp_tools = MCP(
|
||||
"npx -y @openbnb/mcp-server-airbnb --ignore-robots-txt",
|
||||
timeout=60,
|
||||
debug=debug_mode
|
||||
)
|
||||
|
||||
# Create agent - this should only happen ONCE
|
||||
agent = Agent(
|
||||
instructions="You are a helpful Airbnb assistant...",
|
||||
llm=llm_model, # Use standard format
|
||||
tools=mcp_tools,
|
||||
verbose=debug_mode
|
||||
)
|
||||
|
||||
return agent, None
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to initialize agent: {str(e)}"
|
||||
if debug_mode:
|
||||
error_msg += f"\n\nFull traceback:\n{traceback.format_exc()}"
|
||||
return None, error_msg
|
||||
|
||||
# Agent initialization (only runs once)
|
||||
if not st.session_state.agent_initialized:
|
||||
if st.button("🚀 Initialize AI Assistant", type="primary"):
|
||||
agent, error = initialize_agent()
|
||||
|
||||
if agent:
|
||||
st.session_state.agent = agent
|
||||
st.session_state.agent_initialized = True
|
||||
st.session_state.init_error = None
|
||||
st.success("✅ AI Assistant initialized successfully!")
|
||||
st.rerun()
|
||||
else:
|
||||
st.session_state.init_error = error
|
||||
st.error(f"❌ Initialization failed: {error}")
|
||||
|
||||
# Main interface (only show if agent is ready)
|
||||
if st.session_state.agent_initialized and st.session_state.agent:
|
||||
query = st.text_input("🔍 What are you looking for?")
|
||||
|
||||
if st.button("Search") and query:
|
||||
try:
|
||||
with st.spinner("🔍 Searching..."):
|
||||
# Use the SAME agent instance from session state
|
||||
result = st.session_state.agent.start(query)
|
||||
st.write(result)
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"❌ Search failed: {str(e)}")
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Session State Management
|
||||
Always use Streamlit's session state to manage agent lifecycle:
|
||||
|
||||
```python
|
||||
# ✅ Correct - Initialize once in session state
|
||||
if "agent" not in st.session_state:
|
||||
st.session_state.agent = Agent(...)
|
||||
|
||||
# ❌ Wrong - Re-initializes on every interaction
|
||||
agent = Agent(...)
|
||||
```
|
||||
|
||||
### 2. LLM Model Selection
|
||||
Use standard model names, avoid provider/model format in Streamlit:
|
||||
|
||||
```python
|
||||
# ✅ Correct - Standard model names
|
||||
llm="gpt-4o-mini"
|
||||
llm="claude-3-5-sonnet-20241022"
|
||||
|
||||
# ❌ Problematic in Streamlit - Provider/model format
|
||||
llm="ollama/llama3.2"
|
||||
llm="anthropic/claude-3-5-sonnet"
|
||||
```
|
||||
|
||||
### 3. Error Handling
|
||||
Implement comprehensive error handling:
|
||||
|
||||
```python
|
||||
try:
|
||||
# MCP initialization
|
||||
mcp_tools = MCP("your-mcp-command")
|
||||
agent = Agent(tools=mcp_tools, ...)
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Initialization failed: {str(e)}")
|
||||
# Provide troubleshooting tips
|
||||
```
|
||||
|
||||
### 4. User Feedback
|
||||
Provide clear feedback during initialization:
|
||||
|
||||
```python
|
||||
with st.spinner("🔄 Initializing AI agent and MCP tools..."):
|
||||
# Initialization code here
|
||||
pass
|
||||
|
||||
if initialization_successful:
|
||||
st.success("✅ AI Assistant initialized successfully!")
|
||||
else:
|
||||
st.error("❌ Initialization failed")
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### MCP Tools Not Found
|
||||
If you get "MCP tool cannot be found" errors:
|
||||
|
||||
1. **Check MCP Server Command**: Ensure the MCP server command is correct and the server starts successfully
|
||||
2. **Verify Dependencies**: Make sure all required dependencies (Node.js, npm, etc.) are installed
|
||||
3. **Test Manually**: Try running the MCP server command manually first
|
||||
4. **Enable Debug Mode**: Set `debug=True` in MCP constructor for detailed logs
|
||||
|
||||
### Tool Calling Issues
|
||||
If tools aren't being called properly:
|
||||
|
||||
1. **Use Standard LLM Format**: Avoid provider/model format like `"ollama/llama3.2"`
|
||||
2. **Check API Keys**: Ensure proper environment variables are set for your chosen LLM
|
||||
3. **Session State**: Verify agent is properly stored in session state
|
||||
4. **Timeout Settings**: Increase timeout if needed: `MCP(command, timeout=120)`
|
||||
|
||||
### Threading Conflicts
|
||||
If you encounter threading-related errors:
|
||||
|
||||
1. **Single Agent Instance**: Use only one agent instance per session
|
||||
2. **Proper Cleanup**: Let Streamlit handle cleanup naturally
|
||||
3. **Avoid Manual Threading**: Don't create additional threads in Streamlit
|
||||
|
||||
## Complete Working Example
|
||||
|
||||
For a complete, production-ready example, see:
|
||||
- [`examples/python/ui/mcp-streamlit-airbnb.py`](https://github.com/MervinPraison/PraisonAI/blob/main/examples/python/ui/mcp-streamlit-airbnb.py)
|
||||
|
||||
This example includes:
|
||||
- ✅ Proper session state management
|
||||
- ✅ Comprehensive error handling
|
||||
- ✅ User-friendly interface
|
||||
- ✅ Debug mode support
|
||||
- ✅ Troubleshooting guidance
|
||||
- ✅ Configuration options
|
||||
|
||||
## Environment Setup
|
||||
|
||||
Make sure your environment has the required dependencies:
|
||||
|
||||
```bash
|
||||
# Install PraisonAI Agents
|
||||
pip install praisonaiagents
|
||||
|
||||
# Install Streamlit
|
||||
pip install streamlit
|
||||
|
||||
# For Airbnb MCP server example
|
||||
npm install -g @openbnb/mcp-server-airbnb
|
||||
|
||||
# Set environment variables for your chosen LLM
|
||||
export OPENAI_API_KEY="your-key"
|
||||
# or
|
||||
export ANTHROPIC_API_KEY="your-key"
|
||||
```
|
||||
|
||||
## Running the Example
|
||||
|
||||
```bash
|
||||
# Run the working example
|
||||
streamlit run examples/python/ui/mcp-streamlit-airbnb.py
|
||||
|
||||
# Or create your own based on the patterns above
|
||||
streamlit run your-mcp-app.py
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
The key to successfully using MCP with Streamlit is:
|
||||
|
||||
1. **Proper session state management** - Initialize agent only once
|
||||
2. **Standard LLM format** - Avoid provider/model format in Streamlit
|
||||
3. **Comprehensive error handling** - Provide user feedback
|
||||
4. **Correct usage patterns** - Follow Streamlit best practices
|
||||
|
||||
By following these guidelines, MCP tools will work reliably in your Streamlit applications without requiring any modifications to the core MCP implementation.
|
||||
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"source": [
|
||||
"# Article Generation Agents\n",
|
||||
"[](https://colab.research.google.com/github/MervinPraison/PraisonAI/blob/main/cookbooks/notebooks/article_generation_agents.ipynb)"
|
||||
"[](https://colab.research.google.com/github/MervinPraison/PraisonAI/blob/main/examples/cookbooks/yaml/article_generation_agents.ipynb)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -609,4 +609,4 @@
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
from praisonaiagents import Agent, MCP
|
||||
|
||||
search_agent = Agent(
|
||||
instructions="""You help search the web.""",
|
||||
llm="gpt-4o-mini",
|
||||
tools=MCP("http://localhost:8931/sse")
|
||||
)
|
||||
|
||||
search_agent.start("Find about Praison AI")
|
||||
@@ -0,0 +1,252 @@
|
||||
import streamlit as st
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.mcp import MCP
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
# Configure page
|
||||
st.set_page_config(
|
||||
page_title="AI Airbnb Assistant",
|
||||
page_icon="🏠",
|
||||
layout="wide"
|
||||
)
|
||||
|
||||
st.title("🏠 AI Airbnb Assistant")
|
||||
st.write("Enter your accommodation search query below to get started!")
|
||||
|
||||
# Add configuration options in sidebar
|
||||
with st.sidebar:
|
||||
st.header("⚙️ Configuration")
|
||||
|
||||
# LLM Model Selection
|
||||
llm_model = st.selectbox(
|
||||
"Choose LLM Model",
|
||||
options=[
|
||||
"gpt-4o-mini",
|
||||
"gpt-4o",
|
||||
"gpt-3.5-turbo",
|
||||
"claude-3-5-sonnet-20241022",
|
||||
"claude-3-5-haiku-20241022"
|
||||
],
|
||||
index=0,
|
||||
help="Select the language model to use. Avoid provider/model format like 'ollama/llama3.2' in Streamlit."
|
||||
)
|
||||
|
||||
# Debug Mode
|
||||
debug_mode = st.checkbox(
|
||||
"Enable Debug Mode",
|
||||
value=False,
|
||||
help="Enable detailed logging for troubleshooting"
|
||||
)
|
||||
|
||||
# MCP Server Command
|
||||
mcp_command = st.text_input(
|
||||
"MCP Server Command",
|
||||
value="npx -y @openbnb/mcp-server-airbnb --ignore-robots-txt",
|
||||
help="The command to start the MCP server"
|
||||
)
|
||||
|
||||
# Initialize session state
|
||||
if "agent_initialized" not in st.session_state:
|
||||
st.session_state.agent_initialized = False
|
||||
st.session_state.agent = None
|
||||
st.session_state.init_error = None
|
||||
|
||||
# Function to initialize agent
|
||||
def initialize_agent():
|
||||
"""Initialize the agent with proper error handling."""
|
||||
try:
|
||||
with st.spinner("🔄 Initializing AI agent and MCP tools..."):
|
||||
if debug_mode:
|
||||
st.info("🐛 Debug mode enabled - check console for detailed logs")
|
||||
|
||||
# Create MCP tools
|
||||
mcp_tools = MCP(
|
||||
mcp_command,
|
||||
timeout=60,
|
||||
debug=debug_mode
|
||||
)
|
||||
|
||||
# Create agent with session state management
|
||||
agent = Agent(
|
||||
instructions="""You are a helpful Airbnb assistant. Help users find and book apartments.
|
||||
Use the available tools to search for accommodations, get property details, and provide helpful recommendations.
|
||||
Always be polite and provide comprehensive information about properties.""",
|
||||
llm=llm_model,
|
||||
tools=mcp_tools,
|
||||
verbose=debug_mode
|
||||
)
|
||||
|
||||
return agent, None
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to initialize agent: {str(e)}"
|
||||
if debug_mode:
|
||||
error_msg += f"\n\nFull traceback:\n{traceback.format_exc()}"
|
||||
return None, error_msg
|
||||
|
||||
# Agent initialization section
|
||||
if not st.session_state.agent_initialized:
|
||||
if st.button("🚀 Initialize AI Assistant", type="primary"):
|
||||
agent, error = initialize_agent()
|
||||
|
||||
if agent:
|
||||
st.session_state.agent = agent
|
||||
st.session_state.agent_initialized = True
|
||||
st.session_state.init_error = None
|
||||
st.success("✅ AI Assistant initialized successfully!")
|
||||
st.rerun()
|
||||
else:
|
||||
st.session_state.init_error = error
|
||||
st.error(f"❌ Initialization failed: {error}")
|
||||
|
||||
# Show initialization status
|
||||
if st.session_state.init_error:
|
||||
st.error(f"❌ Initialization Error: {st.session_state.init_error}")
|
||||
|
||||
with st.expander("🔧 Troubleshooting Tips"):
|
||||
st.markdown("""
|
||||
**Common issues and solutions:**
|
||||
|
||||
1. **MCP Server Issues:**
|
||||
- Ensure Node.js and npm are installed
|
||||
- Check if the MCP server command is correct
|
||||
- Try running the MCP command manually first
|
||||
|
||||
2. **LLM Model Issues:**
|
||||
- Make sure you have API keys set as environment variables
|
||||
- Avoid provider/model formats (e.g., "ollama/llama3.2") in Streamlit
|
||||
- Use standard model names (e.g., "gpt-4o-mini")
|
||||
|
||||
3. **Network/Timeout Issues:**
|
||||
- Check your internet connection
|
||||
- Increase timeout if needed
|
||||
- Enable debug mode for more details
|
||||
|
||||
4. **Environment Issues:**
|
||||
- Make sure all required packages are installed
|
||||
- Check Python version compatibility
|
||||
""")
|
||||
|
||||
# Main chat interface (only show if agent is initialized)
|
||||
if st.session_state.agent_initialized and st.session_state.agent:
|
||||
st.success("✅ AI Assistant is ready!")
|
||||
|
||||
# Create two columns for better layout
|
||||
col1, col2 = st.columns([2, 1])
|
||||
|
||||
with col1:
|
||||
# Query input
|
||||
query = st.text_area(
|
||||
"🔍 What are you looking for?",
|
||||
placeholder="e.g., 'Find a 2-bedroom apartment in Paris for next weekend' or 'Show me luxury stays in Tokyo under $200/night'",
|
||||
height=100
|
||||
)
|
||||
|
||||
# Search button
|
||||
search_col1, search_col2 = st.columns([1, 4])
|
||||
with search_col1:
|
||||
search_button = st.button("🔍 Search", type="primary", use_container_width=True)
|
||||
|
||||
with col2:
|
||||
# Tips and examples
|
||||
with st.container():
|
||||
st.markdown("### 💡 Tips")
|
||||
st.markdown("""
|
||||
- Be specific about location and dates
|
||||
- Mention budget preferences
|
||||
- Include number of guests
|
||||
- Ask about amenities
|
||||
""")
|
||||
|
||||
with st.expander("📝 Example Queries"):
|
||||
st.markdown("""
|
||||
- "Find a beachfront apartment in Barcelona for 4 people"
|
||||
- "Show me pet-friendly stays in London under £150/night"
|
||||
- "I need a place with a kitchen near downtown Tokyo"
|
||||
- "Find luxury accommodations in NYC for a business trip"
|
||||
""")
|
||||
|
||||
# Handle search
|
||||
if search_button and query:
|
||||
if not query.strip():
|
||||
st.warning("⚠️ Please enter a search query")
|
||||
else:
|
||||
try:
|
||||
with st.spinner("🔍 Searching for accommodations..."):
|
||||
# Add query to chat history
|
||||
if "chat_history" not in st.session_state:
|
||||
st.session_state.chat_history = []
|
||||
|
||||
st.session_state.chat_history.append({"role": "user", "content": query})
|
||||
|
||||
# Get response from agent
|
||||
result = st.session_state.agent.start(query)
|
||||
|
||||
st.session_state.chat_history.append({"role": "assistant", "content": result})
|
||||
|
||||
# Display result
|
||||
st.markdown("### 🏠 Search Results")
|
||||
st.markdown(result)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Search failed: {str(e)}"
|
||||
st.error(f"❌ {error_msg}")
|
||||
|
||||
if debug_mode:
|
||||
st.error(f"Full traceback:\n{traceback.format_exc()}")
|
||||
|
||||
# Chat history section
|
||||
if "chat_history" in st.session_state and st.session_state.chat_history:
|
||||
st.markdown("---")
|
||||
st.markdown("### 💬 Conversation History")
|
||||
|
||||
for i, message in enumerate(st.session_state.chat_history):
|
||||
if message["role"] == "user":
|
||||
st.markdown(f"**🙋 You:** {message['content']}")
|
||||
else:
|
||||
st.markdown(f"**🤖 Assistant:** {message['content']}")
|
||||
|
||||
if i < len(st.session_state.chat_history) - 1:
|
||||
st.markdown("---")
|
||||
|
||||
# Clear history button
|
||||
if st.button("🗑️ Clear History"):
|
||||
st.session_state.chat_history = []
|
||||
st.rerun()
|
||||
|
||||
else:
|
||||
# Show instructions for first-time users
|
||||
st.info("👆 Please initialize the AI Assistant first using the button above.")
|
||||
|
||||
with st.expander("ℹ️ About this Application"):
|
||||
st.markdown("""
|
||||
This Streamlit application demonstrates how to properly integrate MCP (Model Context Protocol)
|
||||
tools with PraisonAI Agents in a Streamlit environment.
|
||||
|
||||
**Key Features:**
|
||||
- ✅ Proper session state management for Streamlit
|
||||
- ✅ Comprehensive error handling and user feedback
|
||||
- ✅ Debug mode for troubleshooting
|
||||
- ✅ Flexible LLM model selection
|
||||
- ✅ Clean and intuitive user interface
|
||||
|
||||
**Technical Implementation:**
|
||||
- Uses session state to prevent agent re-initialization
|
||||
- Implements proper error handling for MCP setup
|
||||
- Provides detailed troubleshooting guidance
|
||||
- Demonstrates best practices for Streamlit + MCP integration
|
||||
""")
|
||||
|
||||
# Footer
|
||||
st.markdown("---")
|
||||
st.markdown(
|
||||
"""
|
||||
<div style='text-align: center; color: gray;'>
|
||||
Built with PraisonAI Agents + MCP •
|
||||
<a href='https://github.com/MervinPraison/PraisonAI' target='_blank'>GitHub</a> •
|
||||
<a href='https://docs.praisonai.com' target='_blank'>Documentation</a>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
@@ -0,0 +1,54 @@
|
||||
import streamlit as st
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.mcp import MCP
|
||||
|
||||
st.title("AI Airbnb")
|
||||
st.write("Enter your research query below to get started!")
|
||||
|
||||
# CRITICAL: Initialize agent in session state to prevent re-initialization
|
||||
if "agent" not in st.session_state:
|
||||
try:
|
||||
with st.spinner("Initializing AI agent..."):
|
||||
# Initialize agent only once and store in session state
|
||||
st.session_state.agent = Agent(
|
||||
instructions="You help book apartments on Airbnb.",
|
||||
llm="gpt-4o-mini", # ✅ Use standard format instead of "ollama/llama3.2"
|
||||
tools=MCP("npx -y @openbnb/mcp-server-airbnb --ignore-robots-txt"),
|
||||
verbose=True # Enable debugging
|
||||
)
|
||||
st.success("✅ Agent initialized successfully!")
|
||||
except Exception as e:
|
||||
st.error(f"❌ Failed to initialize agent: {str(e)}")
|
||||
st.stop()
|
||||
|
||||
# Create the input field
|
||||
query = st.text_input("Query")
|
||||
|
||||
# Add a search button
|
||||
if st.button("Search"):
|
||||
if query:
|
||||
try:
|
||||
with st.spinner("Researching..."):
|
||||
# Use the agent from session state (already initialized)
|
||||
result = st.session_state.agent.start(query)
|
||||
st.write(result)
|
||||
except Exception as e:
|
||||
st.error(f"❌ Search failed: {str(e)}")
|
||||
else:
|
||||
st.warning("Please enter a query")
|
||||
|
||||
# Add troubleshooting information
|
||||
with st.expander("🔧 Troubleshooting"):
|
||||
st.markdown("""
|
||||
**Key differences from problematic code:**
|
||||
|
||||
1. **Session State**: Agent is initialized once in `st.session_state.agent`
|
||||
2. **LLM Format**: Using `"gpt-4o-mini"` instead of `"ollama/llama3.2"`
|
||||
3. **Error Handling**: Proper try-catch blocks with user feedback
|
||||
4. **Initialization Check**: Agent only created once, not on every interaction
|
||||
|
||||
**If you still have issues:**
|
||||
- Make sure you have Node.js installed for the MCP server
|
||||
- Set your OpenAI API key: `export OPENAI_API_KEY="your-key"`
|
||||
- Try the debug mode by setting `verbose=True`
|
||||
""")
|
||||
@@ -864,7 +864,11 @@ Your Goal: {self.goal}
|
||||
if self._using_custom_llm:
|
||||
try:
|
||||
# Special handling for MCP tools when using provider/model format
|
||||
tool_param = self.tools if tools is None else tools
|
||||
# Fix: Handle empty tools list properly - use self.tools if tools is None or empty
|
||||
if tools is None or (isinstance(tools, list) and len(tools) == 0):
|
||||
tool_param = self.tools
|
||||
else:
|
||||
tool_param = tools
|
||||
|
||||
# Convert MCP tool objects to OpenAI format if needed
|
||||
if tool_param is not None:
|
||||
|
||||
@@ -205,6 +205,58 @@ class LLM:
|
||||
}
|
||||
logging.debug(f"LLM instance initialized with: {json.dumps(debug_info, indent=2, default=str)}")
|
||||
|
||||
def _is_ollama_provider(self) -> bool:
|
||||
"""Detect if this is an Ollama provider regardless of naming convention"""
|
||||
if not self.model:
|
||||
return False
|
||||
|
||||
# Direct ollama/ prefix
|
||||
if self.model.startswith("ollama/"):
|
||||
return True
|
||||
|
||||
# Check environment variables for Ollama base URL
|
||||
base_url = os.getenv("OPENAI_BASE_URL", "")
|
||||
api_base = os.getenv("OPENAI_API_BASE", "")
|
||||
|
||||
# Common Ollama endpoints
|
||||
ollama_endpoints = ["localhost:11434", "127.0.0.1:11434", ":11434"]
|
||||
|
||||
return any(endpoint in base_url or endpoint in api_base for endpoint in ollama_endpoints)
|
||||
|
||||
def _parse_tool_call_arguments(self, tool_call: Dict, is_ollama: bool = False) -> tuple:
|
||||
"""
|
||||
Safely parse tool call arguments with proper error handling
|
||||
|
||||
Returns:
|
||||
tuple: (function_name, arguments, tool_call_id)
|
||||
"""
|
||||
try:
|
||||
if is_ollama:
|
||||
# Special handling for Ollama provider which may have different structure
|
||||
if "function" in tool_call and isinstance(tool_call["function"], dict):
|
||||
function_name = tool_call["function"]["name"]
|
||||
arguments = json.loads(tool_call["function"]["arguments"])
|
||||
else:
|
||||
# Try alternative format that Ollama might return
|
||||
function_name = tool_call.get("name", "unknown_function")
|
||||
arguments_str = tool_call.get("arguments", "{}")
|
||||
arguments = json.loads(arguments_str) if arguments_str else {}
|
||||
tool_call_id = tool_call.get("id", f"tool_{id(tool_call)}")
|
||||
else:
|
||||
# Standard format for other providers with error handling
|
||||
function_name = tool_call["function"]["name"]
|
||||
arguments_str = tool_call["function"]["arguments"]
|
||||
arguments = json.loads(arguments_str) if arguments_str else {}
|
||||
tool_call_id = tool_call["id"]
|
||||
|
||||
except (KeyError, json.JSONDecodeError, TypeError) as e:
|
||||
logging.error(f"Error parsing tool call arguments: {e}")
|
||||
function_name = tool_call.get("name", "unknown_function")
|
||||
arguments = {}
|
||||
tool_call_id = tool_call.get("id", f"tool_{id(tool_call)}")
|
||||
|
||||
return function_name, arguments, tool_call_id
|
||||
|
||||
def _needs_system_message_skip(self) -> bool:
|
||||
"""Check if this model requires skipping system messages"""
|
||||
if not self.model:
|
||||
@@ -486,32 +538,19 @@ class LLM:
|
||||
for tool_call in tool_calls:
|
||||
# Handle both object and dict access patterns
|
||||
if isinstance(tool_call, dict):
|
||||
# Special handling for Ollama provider which may have a different structure
|
||||
if self.model and self.model.startswith("ollama/"):
|
||||
try:
|
||||
# Try standard format first
|
||||
if "function" in tool_call and isinstance(tool_call["function"], dict):
|
||||
function_name = tool_call["function"]["name"]
|
||||
arguments = json.loads(tool_call["function"]["arguments"])
|
||||
else:
|
||||
# Try alternative format that Ollama might return
|
||||
function_name = tool_call.get("name", "unknown_function")
|
||||
arguments = json.loads(tool_call.get("arguments", "{}"))
|
||||
tool_call_id = tool_call.get("id", f"tool_{id(tool_call)}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing Ollama tool call: {e}")
|
||||
function_name = "unknown_function"
|
||||
arguments = {}
|
||||
tool_call_id = f"tool_{id(tool_call)}"
|
||||
else:
|
||||
# Standard format for other providers
|
||||
function_name = tool_call["function"]["name"]
|
||||
arguments = json.loads(tool_call["function"]["arguments"])
|
||||
tool_call_id = tool_call["id"]
|
||||
is_ollama = self._is_ollama_provider()
|
||||
function_name, arguments, tool_call_id = self._parse_tool_call_arguments(tool_call, is_ollama)
|
||||
else:
|
||||
function_name = tool_call.function.name
|
||||
arguments = json.loads(tool_call.function.arguments)
|
||||
tool_call_id = tool_call.id
|
||||
# Handle object-style tool calls
|
||||
try:
|
||||
function_name = tool_call.function.name
|
||||
arguments = json.loads(tool_call.function.arguments) if tool_call.function.arguments else {}
|
||||
tool_call_id = tool_call.id
|
||||
except (json.JSONDecodeError, AttributeError) as e:
|
||||
logging.error(f"Error parsing object-style tool call: {e}")
|
||||
function_name = "unknown_function"
|
||||
arguments = {}
|
||||
tool_call_id = f"tool_{id(tool_call)}"
|
||||
|
||||
logging.debug(f"[TOOL_EXEC_DEBUG] About to execute tool {function_name} with args: {arguments}")
|
||||
tool_result = execute_tool_fn(function_name, arguments)
|
||||
@@ -1083,32 +1122,19 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
for tool_call in tool_calls:
|
||||
# Handle both object and dict access patterns
|
||||
if isinstance(tool_call, dict):
|
||||
# Special handling for Ollama provider which may have a different structure
|
||||
if self.model and self.model.startswith("ollama/"):
|
||||
try:
|
||||
# Try standard format first
|
||||
if "function" in tool_call and isinstance(tool_call["function"], dict):
|
||||
function_name = tool_call["function"]["name"]
|
||||
arguments = json.loads(tool_call["function"]["arguments"])
|
||||
else:
|
||||
# Try alternative format that Ollama might return
|
||||
function_name = tool_call.get("name", "unknown_function")
|
||||
arguments = json.loads(tool_call.get("arguments", "{}"))
|
||||
tool_call_id = tool_call.get("id", f"tool_{id(tool_call)}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing Ollama tool call: {e}")
|
||||
function_name = "unknown_function"
|
||||
arguments = {}
|
||||
tool_call_id = f"tool_{id(tool_call)}"
|
||||
else:
|
||||
# Standard format for other providers
|
||||
function_name = tool_call["function"]["name"]
|
||||
arguments = json.loads(tool_call["function"]["arguments"])
|
||||
tool_call_id = tool_call["id"]
|
||||
is_ollama = self._is_ollama_provider()
|
||||
function_name, arguments, tool_call_id = self._parse_tool_call_arguments(tool_call, is_ollama)
|
||||
else:
|
||||
function_name = tool_call.function.name
|
||||
arguments = json.loads(tool_call.function.arguments)
|
||||
tool_call_id = tool_call.id
|
||||
# Handle object-style tool calls
|
||||
try:
|
||||
function_name = tool_call.function.name
|
||||
arguments = json.loads(tool_call.function.arguments) if tool_call.function.arguments else {}
|
||||
tool_call_id = tool_call.id
|
||||
except (json.JSONDecodeError, AttributeError) as e:
|
||||
logging.error(f"Error parsing object-style tool call: {e}")
|
||||
function_name = "unknown_function"
|
||||
arguments = {}
|
||||
tool_call_id = f"tool_{id(tool_call)}"
|
||||
|
||||
tool_result = await execute_tool_fn(function_name, arguments)
|
||||
|
||||
@@ -1129,7 +1155,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
response_text = ""
|
||||
|
||||
# Special handling for Ollama models that don't automatically process tool results
|
||||
if self.model and self.model.startswith("ollama/") and tool_result:
|
||||
if self._is_ollama_provider() and tool_result:
|
||||
# For Ollama models, we need to explicitly ask the model to process the tool results
|
||||
# First, check if the response is just a JSON tool call
|
||||
try:
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "praisonaiagents"
|
||||
version = "0.0.92"
|
||||
version = "0.0.93"
|
||||
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
|
||||
requires-python = ">=3.10"
|
||||
authors = [
|
||||
|
||||
Binary file not shown.
Generated
+1
-1
@@ -2233,7 +2233,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "0.0.92"
|
||||
version = "0.0.93"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "mcp" },
|
||||
|
||||
@@ -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.21.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.21.tar.gz | shasum -a 256`.split.first
|
||||
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.22.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.22.tar.gz | shasum -a 256`.split.first
|
||||
license "MIT"
|
||||
|
||||
depends_on "[email protected]"
|
||||
|
||||
@@ -5,6 +5,7 @@ import argparse
|
||||
from .version import __version__
|
||||
import yaml
|
||||
import os
|
||||
import time
|
||||
from rich import print
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
@@ -25,6 +26,7 @@ CALL_MODULE_AVAILABLE = False
|
||||
CREWAI_AVAILABLE = False
|
||||
AUTOGEN_AVAILABLE = False
|
||||
PRAISONAI_AVAILABLE = False
|
||||
TRAIN_AVAILABLE = False
|
||||
try:
|
||||
# Create necessary directories and set CHAINLIT_APP_ROOT
|
||||
if "CHAINLIT_APP_ROOT" not in os.environ:
|
||||
@@ -71,6 +73,12 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from unsloth import FastLanguageModel
|
||||
TRAIN_AVAILABLE = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
logging.basicConfig(level=os.environ.get('LOGLEVEL', 'INFO'), format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logging.getLogger('alembic').setLevel(logging.ERROR)
|
||||
logging.getLogger('gradio').setLevel(logging.ERROR)
|
||||
@@ -217,9 +225,66 @@ class PraisonAI:
|
||||
# If no command or direct_prompt, preserve agent_file from constructor (don't overwrite)
|
||||
|
||||
if args.deploy:
|
||||
from .deploy import CloudDeployer
|
||||
deployer = CloudDeployer()
|
||||
deployer.run_commands()
|
||||
if args.schedule or args.schedule_config:
|
||||
# Scheduled deployment
|
||||
from .scheduler import create_scheduler
|
||||
|
||||
# Load configuration from file if provided
|
||||
config = {"max_retries": args.max_retries}
|
||||
schedule_expr = args.schedule
|
||||
provider = args.provider
|
||||
|
||||
if args.schedule_config:
|
||||
try:
|
||||
with open(args.schedule_config, 'r') as f:
|
||||
file_config = yaml.safe_load(f)
|
||||
|
||||
# Extract deployment config
|
||||
deploy_config = file_config.get('deployment', {})
|
||||
schedule_expr = schedule_expr or deploy_config.get('schedule')
|
||||
provider = deploy_config.get('provider', provider)
|
||||
config['max_retries'] = deploy_config.get('max_retries', config['max_retries'])
|
||||
|
||||
# Apply environment variables if specified
|
||||
env_vars = file_config.get('environment', {})
|
||||
for key, value in env_vars.items():
|
||||
os.environ[key] = str(value)
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"Configuration file not found: {args.schedule_config}")
|
||||
sys.exit(1)
|
||||
except yaml.YAMLError as e:
|
||||
print(f"Error parsing configuration file: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if not schedule_expr:
|
||||
print("Error: Schedule expression required. Use --schedule or specify in config file.")
|
||||
sys.exit(1)
|
||||
|
||||
scheduler = create_scheduler(provider=provider, config=config)
|
||||
|
||||
print(f"Starting scheduled deployment with schedule: {schedule_expr}")
|
||||
print(f"Provider: {provider}")
|
||||
print(f"Max retries: {config['max_retries']}")
|
||||
print("Press Ctrl+C to stop the scheduler")
|
||||
|
||||
if scheduler.start(schedule_expr, config['max_retries']):
|
||||
try:
|
||||
# Keep the main thread alive
|
||||
while scheduler.is_running:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
print("\nStopping scheduler...")
|
||||
scheduler.stop()
|
||||
print("Scheduler stopped successfully")
|
||||
else:
|
||||
print("Failed to start scheduler")
|
||||
sys.exit(1)
|
||||
else:
|
||||
# One-time deployment (backward compatible)
|
||||
from .deploy import CloudDeployer
|
||||
deployer = CloudDeployer()
|
||||
deployer.run_commands()
|
||||
return
|
||||
|
||||
if getattr(args, 'chat', False):
|
||||
@@ -242,6 +307,11 @@ class PraisonAI:
|
||||
return
|
||||
|
||||
if args.command == 'train':
|
||||
if not TRAIN_AVAILABLE:
|
||||
print("[red]ERROR: Training dependencies not installed. Install with:[/red]")
|
||||
print("\npip install \"praisonai[train]\"")
|
||||
print("Or run: praisonai train init\n")
|
||||
sys.exit(1)
|
||||
package_root = os.path.dirname(os.path.abspath(__file__))
|
||||
config_yaml_destination = os.path.join(os.getcwd(), 'config.yaml')
|
||||
|
||||
@@ -416,6 +486,10 @@ class PraisonAI:
|
||||
parser.add_argument("--init", nargs=argparse.REMAINDER, help="Initialize agents with optional topic")
|
||||
parser.add_argument("command", nargs="?", help="Command to run or direct prompt")
|
||||
parser.add_argument("--deploy", action="store_true", help="Deploy the application")
|
||||
parser.add_argument("--schedule", type=str, help="Schedule deployment (e.g., 'daily', 'hourly', '*/6h', '3600')")
|
||||
parser.add_argument("--schedule-config", type=str, help="Path to scheduling configuration file")
|
||||
parser.add_argument("--provider", type=str, default="gcp", help="Deployment provider (gcp, aws, azure)")
|
||||
parser.add_argument("--max-retries", type=int, default=3, help="Maximum retry attempts for scheduled deployments")
|
||||
parser.add_argument("--model", type=str, help="Model name")
|
||||
parser.add_argument("--llm", type=str, help="LLM model to use for direct prompts")
|
||||
parser.add_argument("--hf", type=str, help="Hugging Face model name")
|
||||
@@ -509,6 +583,11 @@ class PraisonAI:
|
||||
sys.exit(0)
|
||||
|
||||
elif args.command == 'train':
|
||||
if not TRAIN_AVAILABLE:
|
||||
print("[red]ERROR: Training dependencies not installed. Install with:[/red]")
|
||||
print("\npip install \"praisonai[train]\"")
|
||||
print("Or run: praisonai train init\n")
|
||||
sys.exit(1)
|
||||
package_root = os.path.dirname(os.path.abspath(__file__))
|
||||
config_yaml_destination = os.path.join(os.getcwd(), 'config.yaml')
|
||||
|
||||
|
||||
@@ -56,7 +56,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.21 gunicorn markdown\n")
|
||||
file.write("RUN pip install flask praisonai==2.2.22 gunicorn markdown\n")
|
||||
file.write("EXPOSE 8080\n")
|
||||
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
|
||||
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Union, Optional, Callable, Dict, Any
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DeployerInterface(ABC):
|
||||
"""Abstract interface for deployers to ensure provider compatibility."""
|
||||
|
||||
@abstractmethod
|
||||
def deploy(self) -> bool:
|
||||
"""Execute deployment. Returns True on success, False on failure."""
|
||||
pass
|
||||
|
||||
class CloudDeployerAdapter(DeployerInterface):
|
||||
"""Adapter for existing CloudDeployer to match interface."""
|
||||
|
||||
def __init__(self):
|
||||
from .deploy import CloudDeployer
|
||||
self._deployer = CloudDeployer()
|
||||
|
||||
def deploy(self) -> bool:
|
||||
"""Execute deployment using CloudDeployer."""
|
||||
try:
|
||||
self._deployer.run_commands()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Deployment failed: {e}")
|
||||
return False
|
||||
|
||||
class ScheduleParser:
|
||||
"""Parse schedule expressions into intervals."""
|
||||
|
||||
@staticmethod
|
||||
def parse(schedule_expr: str) -> int:
|
||||
"""
|
||||
Parse schedule expression and return interval in seconds.
|
||||
|
||||
Supported formats:
|
||||
- "daily" -> 86400 seconds
|
||||
- "hourly" -> 3600 seconds
|
||||
- "*/30m" -> 1800 seconds (every 30 minutes)
|
||||
- "*/6h" -> 21600 seconds (every 6 hours)
|
||||
- "60" -> 60 seconds (plain number)
|
||||
"""
|
||||
schedule_expr = schedule_expr.strip().lower()
|
||||
|
||||
if schedule_expr == "daily":
|
||||
return 86400
|
||||
elif schedule_expr == "hourly":
|
||||
return 3600
|
||||
elif schedule_expr.isdigit():
|
||||
return int(schedule_expr)
|
||||
elif schedule_expr.startswith("*/"):
|
||||
# Handle */30m, */6h patterns
|
||||
interval_part = schedule_expr[2:]
|
||||
if interval_part.endswith("m"):
|
||||
minutes = int(interval_part[:-1])
|
||||
return minutes * 60
|
||||
elif interval_part.endswith("h"):
|
||||
hours = int(interval_part[:-1])
|
||||
return hours * 3600
|
||||
elif interval_part.endswith("s"):
|
||||
return int(interval_part[:-1])
|
||||
else:
|
||||
return int(interval_part)
|
||||
else:
|
||||
raise ValueError(f"Unsupported schedule format: {schedule_expr}")
|
||||
|
||||
class DeploymentScheduler:
|
||||
"""
|
||||
Minimal deployment scheduler with provider-agnostic design.
|
||||
|
||||
Features:
|
||||
- Simple interval-based scheduling
|
||||
- Thread-safe operation
|
||||
- Extensible deployer factory pattern
|
||||
- Minimal dependencies
|
||||
"""
|
||||
|
||||
def __init__(self, schedule_config: Optional[Dict[str, Any]] = None):
|
||||
self.config = schedule_config or {}
|
||||
self.is_running = False
|
||||
self._stop_event = threading.Event()
|
||||
self._thread = None
|
||||
self._deployer = None
|
||||
|
||||
def set_deployer(self, deployer: DeployerInterface):
|
||||
"""Set custom deployer implementation."""
|
||||
self._deployer = deployer
|
||||
|
||||
def _get_deployer(self) -> DeployerInterface:
|
||||
"""Get deployer instance using factory pattern."""
|
||||
if self._deployer:
|
||||
return self._deployer
|
||||
|
||||
# Default to CloudDeployer for backward compatibility
|
||||
return CloudDeployerAdapter()
|
||||
|
||||
def start(self, schedule_expr: str, max_retries: int = 3) -> bool:
|
||||
"""
|
||||
Start scheduled deployment.
|
||||
|
||||
Args:
|
||||
schedule_expr: Schedule expression (e.g., "daily", "*/6h", "3600")
|
||||
max_retries: Maximum retry attempts on failure
|
||||
|
||||
Returns:
|
||||
True if scheduler started successfully
|
||||
"""
|
||||
if self.is_running:
|
||||
logger.warning("Scheduler is already running")
|
||||
return False
|
||||
|
||||
try:
|
||||
interval = ScheduleParser.parse(schedule_expr)
|
||||
self.is_running = True
|
||||
self._stop_event.clear()
|
||||
|
||||
self._thread = threading.Thread(
|
||||
target=self._run_schedule,
|
||||
args=(interval, max_retries),
|
||||
daemon=True
|
||||
)
|
||||
self._thread.start()
|
||||
|
||||
logger.info(f"Deployment scheduler started with {interval}s interval")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start scheduler: {e}")
|
||||
self.is_running = False
|
||||
return False
|
||||
|
||||
def stop(self) -> bool:
|
||||
"""Stop the scheduler."""
|
||||
if not self.is_running:
|
||||
return True
|
||||
|
||||
self._stop_event.set()
|
||||
if self._thread and self._thread.is_alive():
|
||||
self._thread.join(timeout=5)
|
||||
|
||||
self.is_running = False
|
||||
logger.info("Deployment scheduler stopped")
|
||||
return True
|
||||
|
||||
def _run_schedule(self, interval: int, max_retries: int):
|
||||
"""Internal method to run scheduled deployments."""
|
||||
deployer = self._get_deployer()
|
||||
|
||||
while not self._stop_event.is_set():
|
||||
logger.info("Starting scheduled deployment")
|
||||
|
||||
success = False
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
if deployer.deploy():
|
||||
logger.info(f"Deployment successful on attempt {attempt + 1}")
|
||||
success = True
|
||||
break
|
||||
else:
|
||||
logger.warning(f"Deployment failed on attempt {attempt + 1}")
|
||||
except Exception as e:
|
||||
logger.error(f"Deployment error on attempt {attempt + 1}: {e}")
|
||||
|
||||
if attempt < max_retries - 1:
|
||||
time.sleep(30) # Wait before retry
|
||||
|
||||
if not success:
|
||||
logger.error(f"Deployment failed after {max_retries} attempts")
|
||||
|
||||
# Wait for next scheduled time
|
||||
self._stop_event.wait(interval)
|
||||
|
||||
def deploy_once(self) -> bool:
|
||||
"""Execute a single deployment immediately."""
|
||||
deployer = self._get_deployer()
|
||||
try:
|
||||
return deployer.deploy()
|
||||
except Exception as e:
|
||||
logger.error(f"One-time deployment failed: {e}")
|
||||
return False
|
||||
|
||||
def create_scheduler(provider: str = "gcp", config: Optional[Dict[str, Any]] = None) -> DeploymentScheduler:
|
||||
"""
|
||||
Factory function to create scheduler for different providers.
|
||||
|
||||
Args:
|
||||
provider: Deployment provider ("gcp", "aws", "azure", etc.)
|
||||
config: Optional configuration dict
|
||||
|
||||
Returns:
|
||||
Configured DeploymentScheduler instance
|
||||
"""
|
||||
scheduler = DeploymentScheduler(config)
|
||||
|
||||
# Provider-specific deployer setup can be added here
|
||||
if provider == "gcp":
|
||||
# Default CloudDeployer for GCP
|
||||
pass
|
||||
elif provider == "aws":
|
||||
# Future: AWS deployer implementation
|
||||
logger.warning("AWS provider not yet implemented, using default")
|
||||
elif provider == "azure":
|
||||
# Future: Azure deployer implementation
|
||||
logger.warning("Azure provider not yet implemented, using default")
|
||||
else:
|
||||
logger.warning(f"Unknown provider {provider}, using default")
|
||||
|
||||
return scheduler
|
||||
@@ -108,7 +108,7 @@ async def tavily_web_search_handler(query):
|
||||
try:
|
||||
response = tavily_client.search(query_with_date)
|
||||
logger.debug(f"Tavily search response: {response}")
|
||||
results = process_tavily_results(response)
|
||||
results = await process_tavily_results(response)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in Tavily search: {str(e)}")
|
||||
results = await fallback_to_duckduckgo(query_with_date)
|
||||
@@ -121,27 +121,27 @@ async def tavily_web_search_handler(query):
|
||||
"results": results
|
||||
})
|
||||
|
||||
def process_tavily_results(response):
|
||||
crawler = AsyncWebCrawler()
|
||||
crawler.warmup()
|
||||
results = []
|
||||
for result in response.get('results', []):
|
||||
url = result.get('url')
|
||||
if url:
|
||||
try:
|
||||
crawl_result = crawler.run(url=url)
|
||||
results.append({
|
||||
"content": result.get('content'),
|
||||
"url": url,
|
||||
"full_content": crawl_result.markdown
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Error crawling {url}: {str(e)}")
|
||||
results.append({
|
||||
"content": result.get('content'),
|
||||
"url": url,
|
||||
"full_content": "Error: Unable to crawl this URL"
|
||||
})
|
||||
async def process_tavily_results(response):
|
||||
async with AsyncWebCrawler() as crawler:
|
||||
results = []
|
||||
for result in response.get('results', []):
|
||||
url = result.get('url')
|
||||
if url:
|
||||
try:
|
||||
crawl_result = await crawler.arun(url=url)
|
||||
full_content = crawl_result.markdown if crawl_result and hasattr(crawl_result, 'markdown') and crawl_result.markdown else "No content available"
|
||||
results.append({
|
||||
"content": result.get('content'),
|
||||
"url": url,
|
||||
"full_content": full_content
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Error crawling {url}: {str(e)}")
|
||||
results.append({
|
||||
"content": result.get('content'),
|
||||
"url": url,
|
||||
"full_content": "Error: Unable to crawl this URL"
|
||||
})
|
||||
return results
|
||||
|
||||
async def fallback_to_duckduckgo(query):
|
||||
@@ -151,33 +151,33 @@ async def fallback_to_duckduckgo(query):
|
||||
|
||||
logger.debug(f"DuckDuckGo search results: {ddg_results}")
|
||||
|
||||
crawler = AsyncWebCrawler()
|
||||
crawler.warmup()
|
||||
results = []
|
||||
|
||||
for result in ddg_results:
|
||||
url = result.get('href')
|
||||
if url:
|
||||
try:
|
||||
crawl_result = crawler.run(url=url)
|
||||
async with AsyncWebCrawler() as crawler:
|
||||
results = []
|
||||
|
||||
for result in ddg_results:
|
||||
url = result.get('href')
|
||||
if url:
|
||||
try:
|
||||
crawl_result = await crawler.arun(url=url)
|
||||
full_content = crawl_result.markdown if crawl_result and hasattr(crawl_result, 'markdown') and crawl_result.markdown else "No content available"
|
||||
results.append({
|
||||
"content": result.get('body'),
|
||||
"url": url,
|
||||
"full_content": full_content
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Error crawling {url}: {str(e)}")
|
||||
results.append({
|
||||
"content": result.get('body'),
|
||||
"url": url,
|
||||
"full_content": "Error: Unable to crawl this URL"
|
||||
})
|
||||
else:
|
||||
results.append({
|
||||
"content": result.get('body'),
|
||||
"url": url,
|
||||
"full_content": crawl_result.markdown
|
||||
"url": "N/A",
|
||||
"full_content": "No URL provided for crawling"
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Error crawling {url}: {str(e)}")
|
||||
results.append({
|
||||
"content": result.get('body'),
|
||||
"url": url,
|
||||
"full_content": "Error: Unable to crawl this URL"
|
||||
})
|
||||
else:
|
||||
results.append({
|
||||
"content": result.get('body'),
|
||||
"url": "N/A",
|
||||
"full_content": "No URL provided for crawling"
|
||||
})
|
||||
|
||||
return results
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "PraisonAI"
|
||||
version = "2.2.21"
|
||||
version = "2.2.22"
|
||||
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 = ""
|
||||
@@ -89,12 +89,13 @@ call = [
|
||||
"rich",
|
||||
"openai>=1.54.0",
|
||||
]
|
||||
train = []
|
||||
crewai = ["crewai>=0.32.0", "praisonai-tools>=0.0.15"]
|
||||
autogen = ["pyautogen>=0.2.19", "praisonai-tools>=0.0.15", "crewai"]
|
||||
|
||||
[tool.poetry]
|
||||
name = "PraisonAI"
|
||||
version = "2.2.21"
|
||||
version = "2.2.22"
|
||||
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 = ""
|
||||
@@ -146,6 +147,7 @@ sqlalchemy = {version = ">=2.0.36", optional = true}
|
||||
playwright = {version = ">=1.47.0", optional = true}
|
||||
openai = {version = ">=1.54.0", optional = true}
|
||||
pydantic = {version = "<=2.10.1", optional = true}
|
||||
# unsloth = {version = ">=2024.11.7", extras = ["colab-new"], optional = true}
|
||||
|
||||
[tool.poetry.group.docs.dependencies]
|
||||
mkdocs = "*"
|
||||
@@ -249,7 +251,7 @@ code = [
|
||||
"playwright",
|
||||
"pydantic"
|
||||
]
|
||||
train = ["setup-conda-env"]
|
||||
train = []
|
||||
realtime = [
|
||||
"chainlit",
|
||||
"litellm",
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# PraisonAI Deployment Scheduler Configuration
|
||||
# This is an example configuration file for scheduled deployments
|
||||
|
||||
deployment:
|
||||
# Schedule expression (e.g., "daily", "hourly", "*/6h", "3600")
|
||||
schedule: "daily"
|
||||
|
||||
# Deployment provider (gcp, aws, azure)
|
||||
provider: "gcp"
|
||||
|
||||
# Maximum retry attempts on deployment failure
|
||||
max_retries: 3
|
||||
|
||||
# Optional: timezone for scheduling (future feature)
|
||||
# timezone: "UTC"
|
||||
|
||||
# Optional: retry delay in seconds
|
||||
# retry_delay: 30
|
||||
|
||||
# Optional: Environment variables for deployment
|
||||
# environment:
|
||||
# OPENAI_MODEL_NAME: "gpt-4o"
|
||||
# OPENAI_API_KEY: "your-api-key"
|
||||
# OPENAI_API_BASE: "https://api.openai.com/v1"
|
||||
|
||||
# Optional: Notification settings (future feature)
|
||||
# notifications:
|
||||
# on_success: true
|
||||
# on_failure: true
|
||||
# webhook_url: "https://your-webhook-url.com"
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify Ollama tool-call fixes
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the source directory to Python path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'praisonai-agents'))
|
||||
|
||||
from praisonaiagents.llm.llm import LLM
|
||||
from praisonaiagents.agent.agent import Agent
|
||||
|
||||
def test_ollama_provider_detection():
|
||||
"""Test the new Ollama provider detection logic"""
|
||||
print("Testing Ollama provider detection...")
|
||||
|
||||
# Test 1: Direct ollama/ prefix
|
||||
llm1 = LLM(model="ollama/qwen3:32b")
|
||||
assert llm1._is_ollama_provider() == True, "Should detect ollama/ prefix"
|
||||
print("✓ Direct ollama/ prefix detection works")
|
||||
|
||||
# Test 2: Environment variable detection
|
||||
os.environ["OPENAI_BASE_URL"] = "http://localhost:11434/v1"
|
||||
llm2 = LLM(model="qwen3:32b")
|
||||
assert llm2._is_ollama_provider() == True, "Should detect via env var"
|
||||
print("✓ Environment variable detection works")
|
||||
|
||||
# Test 3: Non-Ollama model
|
||||
os.environ.pop("OPENAI_BASE_URL", None)
|
||||
llm3 = LLM(model="gpt-4o-mini")
|
||||
assert llm3._is_ollama_provider() == False, "Should not detect non-Ollama"
|
||||
print("✓ Non-Ollama detection works")
|
||||
|
||||
def test_tool_call_parsing():
|
||||
"""Test the new tool call parsing logic"""
|
||||
print("\nTesting tool call parsing...")
|
||||
|
||||
llm = LLM(model="ollama/qwen3:32b")
|
||||
|
||||
# Test 1: Standard format
|
||||
tool_call_std = {
|
||||
"id": "call_123",
|
||||
"function": {
|
||||
"name": "hello_world",
|
||||
"arguments": '{"name": "test"}'
|
||||
}
|
||||
}
|
||||
name, args, id = llm._parse_tool_call_arguments(tool_call_std, is_ollama=True)
|
||||
assert name == "hello_world", f"Expected 'hello_world', got '{name}'"
|
||||
assert args == {"name": "test"}, f"Expected {{'name': 'test'}}, got {args}"
|
||||
print("✓ Standard format parsing works")
|
||||
|
||||
# Test 2: Ollama alternative format
|
||||
tool_call_alt = {
|
||||
"name": "hello_world",
|
||||
"arguments": '{"name": "test"}'
|
||||
}
|
||||
name, args, id = llm._parse_tool_call_arguments(tool_call_alt, is_ollama=True)
|
||||
assert name == "hello_world", f"Expected 'hello_world', got '{name}'"
|
||||
assert args == {"name": "test"}, f"Expected {{'name': 'test'}}, got {args}"
|
||||
print("✓ Alternative format parsing works")
|
||||
|
||||
# Test 3: Error handling - malformed JSON
|
||||
tool_call_bad = {
|
||||
"function": {
|
||||
"name": "hello_world",
|
||||
"arguments": 'invalid json'
|
||||
}
|
||||
}
|
||||
name, args, id = llm._parse_tool_call_arguments(tool_call_bad, is_ollama=False)
|
||||
assert name == "unknown_function", f"Expected 'unknown_function' for malformed JSON, got '{name}'"
|
||||
assert args == {}, f"Expected empty dict, got {args}"
|
||||
print("✓ Error handling works")
|
||||
|
||||
def test_agent_tool_parameter_logic():
|
||||
"""Test the fixed tool parameter logic in Agent"""
|
||||
print("\nTesting agent tool parameter logic...")
|
||||
|
||||
def dummy_tool():
|
||||
"""Dummy tool for testing"""
|
||||
return "test"
|
||||
|
||||
# Test 1: None tools should use agent tools
|
||||
agent = Agent(name="test", tools=[dummy_tool])
|
||||
# Simulate the fixed logic
|
||||
tools = None
|
||||
if tools is None or (isinstance(tools, list) and len(tools) == 0):
|
||||
tool_param = agent.tools
|
||||
else:
|
||||
tool_param = tools
|
||||
|
||||
assert tool_param == [dummy_tool], "Should use agent tools when tools=None"
|
||||
print("✓ None tools handling works")
|
||||
|
||||
# Test 2: Empty list should use agent tools
|
||||
tools = []
|
||||
if tools is None or (isinstance(tools, list) and len(tools) == 0):
|
||||
tool_param = agent.tools
|
||||
else:
|
||||
tool_param = tools
|
||||
|
||||
assert tool_param == [dummy_tool], "Should use agent tools when tools=[]"
|
||||
print("✓ Empty list handling works")
|
||||
|
||||
# Test 3: Non-empty list should use provided tools
|
||||
custom_tools = [lambda: "custom"]
|
||||
tools = custom_tools
|
||||
if tools is None or (isinstance(tools, list) and len(tools) == 0):
|
||||
tool_param = agent.tools
|
||||
else:
|
||||
tool_param = tools
|
||||
|
||||
assert tool_param == custom_tools, "Should use provided tools when not empty"
|
||||
print("✓ Non-empty list handling works")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Running Ollama tool-call fix tests...\n")
|
||||
|
||||
try:
|
||||
test_ollama_provider_detection()
|
||||
test_tool_call_parsing()
|
||||
test_agent_tool_parameter_logic()
|
||||
|
||||
print("\n🎉 All tests passed! The Ollama tool-call fixes are working correctly.")
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Test failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for the deployment scheduler functionality.
|
||||
Tests the core components without actually deploying.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import tempfile
|
||||
import yaml
|
||||
|
||||
# Add the src directory to the path so we can import modules
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src/praisonai'))
|
||||
|
||||
def test_schedule_parser():
|
||||
"""Test the schedule parser functionality."""
|
||||
try:
|
||||
from praisonai.scheduler import ScheduleParser
|
||||
|
||||
# Test various schedule expressions
|
||||
test_cases = [
|
||||
("daily", 86400),
|
||||
("hourly", 3600),
|
||||
("*/30m", 1800),
|
||||
("*/6h", 21600),
|
||||
("60", 60),
|
||||
("3600", 3600)
|
||||
]
|
||||
|
||||
print("Testing ScheduleParser...")
|
||||
for expr, expected in test_cases:
|
||||
result = ScheduleParser.parse(expr)
|
||||
assert result == expected, f"Expected {expected}, got {result} for '{expr}'"
|
||||
print(f" ✓ '{expr}' -> {result} seconds")
|
||||
|
||||
print("ScheduleParser tests passed!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"ScheduleParser test failed: {e}")
|
||||
return False
|
||||
|
||||
def test_scheduler_creation():
|
||||
"""Test scheduler creation and basic functionality."""
|
||||
try:
|
||||
from praisonai.scheduler import create_scheduler, DeploymentScheduler
|
||||
|
||||
print("Testing scheduler creation...")
|
||||
|
||||
# Test default scheduler
|
||||
scheduler = create_scheduler()
|
||||
assert isinstance(scheduler, DeploymentScheduler)
|
||||
print(" ✓ Default scheduler created")
|
||||
|
||||
# Test with different providers
|
||||
for provider in ["gcp", "aws", "azure"]:
|
||||
scheduler = create_scheduler(provider=provider)
|
||||
assert isinstance(scheduler, DeploymentScheduler)
|
||||
print(f" ✓ {provider} scheduler created")
|
||||
|
||||
print("Scheduler creation tests passed!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Scheduler creation test failed: {e}")
|
||||
return False
|
||||
|
||||
def test_config_file_parsing():
|
||||
"""Test configuration file parsing."""
|
||||
try:
|
||||
# Create a temporary config file
|
||||
config_data = {
|
||||
'deployment': {
|
||||
'schedule': 'daily',
|
||||
'provider': 'gcp',
|
||||
'max_retries': 5
|
||||
},
|
||||
'environment': {
|
||||
'TEST_VAR': 'test_value'
|
||||
}
|
||||
}
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
|
||||
yaml.dump(config_data, f)
|
||||
config_file = f.name
|
||||
|
||||
print("Testing config file parsing...")
|
||||
|
||||
# Test loading the config
|
||||
with open(config_file, 'r') as f:
|
||||
loaded_config = yaml.safe_load(f)
|
||||
|
||||
assert loaded_config['deployment']['schedule'] == 'daily'
|
||||
assert loaded_config['deployment']['provider'] == 'gcp'
|
||||
assert loaded_config['deployment']['max_retries'] == 5
|
||||
print(" ✓ Config file parsed correctly")
|
||||
|
||||
# Clean up
|
||||
os.unlink(config_file)
|
||||
|
||||
print("Config file parsing tests passed!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Config file parsing test failed: {e}")
|
||||
return False
|
||||
|
||||
def test_cli_argument_parsing():
|
||||
"""Test CLI argument parsing for scheduling options."""
|
||||
try:
|
||||
from praisonai.cli import PraisonAI
|
||||
|
||||
print("Testing CLI argument parsing...")
|
||||
|
||||
# Test basic CLI instantiation
|
||||
praison = PraisonAI()
|
||||
assert praison is not None
|
||||
print(" ✓ PraisonAI CLI instance created")
|
||||
|
||||
print("CLI argument parsing tests passed!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"CLI argument parsing test failed: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Run all tests."""
|
||||
print("Running PraisonAI Scheduler Tests")
|
||||
print("=" * 40)
|
||||
|
||||
tests = [
|
||||
test_schedule_parser,
|
||||
test_scheduler_creation,
|
||||
test_config_file_parsing,
|
||||
test_cli_argument_parsing
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test in tests:
|
||||
print()
|
||||
if test():
|
||||
passed += 1
|
||||
else:
|
||||
print("❌ Test failed")
|
||||
|
||||
print()
|
||||
print("=" * 40)
|
||||
print(f"Tests passed: {passed}/{total}")
|
||||
|
||||
if passed == total:
|
||||
print("🎉 All tests passed!")
|
||||
return 0
|
||||
else:
|
||||
print("❌ Some tests failed")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Generated
+1
-1
@@ -3931,7 +3931,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonai"
|
||||
version = "2.2.21"
|
||||
version = "2.2.22"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "instructor" },
|
||||
|
||||
Reference in new issue
Block a user