Compare commits

...
8 Commits
Author SHA1 Message Date
Mervin Praison e938fe63ea Merge pull request #473 from emmanuel-ferdman/main
Resolve Python Logger warnings
2025-05-14 06:33:25 +01:00
Mervin Praison ef96e53843 Merge pull request #474 from MervinPraison/develop
Increment version to 0.0.78 in `pyproject.toml`, add API dependencies…
2025-05-14 06:32:05 +01:00
MervinPraison 036c463a43 Increment version to 0.0.78 in pyproject.toml, add API dependencies for FastAPI and Uvicorn, and implement HTTP API launch functionality in agent.py for enhanced agent interaction. 2025-05-14 06:30:42 +01:00
Emmanuel Ferdman 0e6b4aefc3 Resolve Python Logger warnings
Signed-off-by: Emmanuel Ferdman <[email protected]>
2025-05-13 14:08:22 -07:00
Mervin Praison bbb9f677ae Merge pull request #472 from MervinPraison/develop
Add STDIO MCP documentation and update navigation menu
2025-05-13 16:40:35 +01:00
MervinPraison 0da201b3b6 Add STDIO MCP documentation and update navigation menu 2025-05-13 16:40:20 +01:00
Mervin Praison a7b85043e1 Merge pull request #470 from elandau1260/add_repot_path_to_env
Added PRAISONAI_CODE_REPO_PATH env variable to point code to differen…
2025-05-13 14:55:28 +01:00
Ed Landau aa6b6ac62e Added PRAISONAI_CODE_REPO_PATH env variable to point code to different repot 2025-05-02 13:01:21 -07:00
11 changed files with 460 additions and 10 deletions

No files matched your search

+221
View File
@@ -0,0 +1,221 @@
---
title: "MCP STDIO Integration"
sidebarTitle: "MCP STDIO"
description: "Guide for integrating Standard Input/Output (STDIO) with PraisonAI agents using MCP"
icon: "terminal"
---
## Add STDIO Tool to AI Agent
```mermaid
flowchart LR
In[In] --> Agent[AI Agent]
Agent --> Tool[STDIO MCP]
Tool --> Agent
Agent --> Out[Out]
style In fill:#8B0000,color:#fff
style Agent fill:#2E8B57,color:#fff
style Tool fill:#000000,color:#fff
style Out fill:#8B0000,color:#fff
```
## Quick Start
<Steps>
<Step title="Create a client file">
Create a new file `calculator_client.py` with the following code:
```python
from praisonaiagents import Agent, MCP
calculator_agent = Agent(
instructions="""You are a calculator agent that can perform basic arithmetic operations.""",
llm="gpt-4o-mini",
tools=MCP("python calculator_server.py")
)
calculator_agent.start("What is 25 * 16?")
```
</Step>
<Step title="Set Up STDIO MCP Server">
Create a file `calculator_server.py` with the following code:
```python
# calculator_server.py
from mcp.server.fastmcp import FastMCP
import logging
import sys
# Set up logging
logging.basicConfig(level=logging.INFO, filename="calculator_server.log")
logger = logging.getLogger("calculator-server")
# Initialize FastMCP server for simple tools
mcp = FastMCP("calculator-tools")
@mcp.tool()
def add(a: float, b: float) -> float:
"""Add two numbers.
Args:
a: First number
b: Second number
Returns:
The sum of a and b
"""
logger.info(f"Adding {a} + {b}")
return a + b
@mcp.tool()
def subtract(a: float, b: float) -> float:
"""Subtract b from a.
Args:
a: First number
b: Second number
Returns:
The result of a - b
"""
logger.info(f"Subtracting {b} from {a}")
return a - b
@mcp.tool()
def multiply(a: float, b: float) -> float:
"""Multiply two numbers.
Args:
a: First number
b: Second number
Returns:
The product of a and b
"""
logger.info(f"Multiplying {a} * {b}")
return a * b
@mcp.tool()
def divide(a: float, b: float) -> float:
"""Divide a by b.
Args:
a: First number (numerator)
b: Second number (denominator)
Returns:
The result of a / b
"""
if b == 0:
raise ValueError("Cannot divide by zero")
logger.info(f"Dividing {a} / {b}")
return a / b
if __name__ == "__main__":
# Run the MCP server using STDIO transport
mcp.run()
```
</Step>
<Step title="Install Dependencies">
Make sure you have the required packages installed:
```bash
pip install "praisonaiagents[llm]" mcp
```
</Step>
<Step title="Export API Key">
```bash
export OPENAI_API_KEY="your_api_key"
```
</Step>
<Step title="Run the Agent">
Run the agent which will automatically start the calculator server:
```bash
python calculator_client.py
```
</Step>
</Steps>
<Note>
**Requirements**
- Python 3.10 or higher
- MCP package
</Note>
## Alternative LLM Integrations
### Using Groq with STDIO
```python
from praisonaiagents import Agent, MCP
calculator_agent = Agent(
instructions="""You are a calculator agent that can perform basic arithmetic operations.""",
llm="groq/llama-3.2-90b-vision-preview",
tools=MCP("python calculator_server.py")
)
calculator_agent.start("What is 144 divided by 12?")
```
### Using Ollama with STDIO
```python
from praisonaiagents import Agent, MCP
calculator_agent = Agent(
instructions="""You are a calculator agent that can perform basic arithmetic operations.""",
llm="ollama/llama3.2",
tools=MCP("python calculator_server.py")
)
calculator_agent.start("What is 15 + 27? Use the add tool with parameters a and b.")
```
## Gradio UI Integration
Create a Gradio UI for your calculator service:
```python
from praisonaiagents import Agent, MCP
import gradio as gr
def calculate(query):
calculator_agent = Agent(
instructions="""You are a calculator agent that can perform basic arithmetic operations.""",
llm="gpt-4o-mini",
tools=MCP("python calculator_server.py")
)
result = calculator_agent.start(query)
return f"## Calculation Result\n\n{result}"
demo = gr.Interface(
fn=calculate,
inputs=gr.Textbox(placeholder="What is 25 * 16?"),
outputs=gr.Markdown(),
title="Calculator MCP Agent",
description="Ask any arithmetic question:"
)
if __name__ == "__main__":
demo.launch()
```
## Features
<CardGroup cols={2}>
<Card title="Simple Integration" icon="plug">
Use standard input/output for easy integration with any tool or service.
</Card>
<Card title="Cross-Platform" icon="laptop">
Works on any operating system that supports Python.
</Card>
<Card title="Multiple LLM Options" icon="brain">
Use with OpenAI, Groq, Ollama, or other supported LLMs.
</Card>
<Card title="Gradio UI" icon="window">
Create user-friendly interfaces for your STDIO integrations.
</Card>
</CardGroup>
+1
View File
@@ -238,6 +238,7 @@
"group": "MCP",
"pages": [
"mcp/airbnb",
"mcp/stdio",
"mcp/sse",
"mcp/ollama",
"mcp/groq",
+9
View File
@@ -0,0 +1,9 @@
from praisonaiagents import Agent, MCP
search_agent = Agent(
instructions="""You are a weather agent that can provide weather information for a given city.""",
llm="openai/gpt-4o-mini",
tools=MCP("http://localhost:8080/sse")
)
search_agent.start("What is the weather in London?")
+4 -2
View File
@@ -100,7 +100,8 @@ async def start():
)
cl.user_session.set("settings", settings)
await settings.send()
gatherer = ContextGatherer()
repo_path_to_use = os.environ.get("PRAISONAI_CODE_REPO_PATH", ".")
gatherer = ContextGatherer(directory=repo_path_to_use)
context, token_count, context_tree = gatherer.run()
msg = cl.Message(content="""Token Count: {token_count},
Files include: \n```bash\n{context_tree}\n"""
@@ -200,7 +201,8 @@ tools = [{
async def main(message: cl.Message):
model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-4o-mini"
message_history = cl.user_session.get("message_history", [])
gatherer = ContextGatherer()
repo_path_to_use = os.environ.get("PRAISONAI_CODE_REPO_PATH", ".")
gatherer = ContextGatherer(directory=repo_path_to_use)
context, token_count, context_tree = gatherer.run()
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+4 -4
View File
@@ -70,7 +70,7 @@ class SQLAlchemyDataLayer(BaseDataLayer):
logger.info("SQLAlchemyDataLayer storage client initialized")
else:
self.storage_provider = None
logger.warn(
logger.warning(
"SQLAlchemyDataLayer storage client is not initialized and elements will not be persisted!"
)
@@ -98,11 +98,11 @@ class SQLAlchemyDataLayer(BaseDataLayer):
return result.rowcount
except SQLAlchemyError as e:
await session.rollback()
logger.warn(f"An error occurred: {e}")
logger.warning(f"An error occurred: {e}")
return None
except Exception as e:
await session.rollback()
logger.warn(f"An unexpected error occurred: {e}")
logger.warning(f"An unexpected error occurred: {e}")
return None
async def get_current_timestamp(self) -> str:
@@ -456,7 +456,7 @@ class SQLAlchemyDataLayer(BaseDataLayer):
logger.info(f"SQLAlchemy: create_element, element_id = {element.id}")
if not self.storage_provider:
logger.warn("SQLAlchemy: create_element error. No storage client!")
logger.warning("SQLAlchemy: create_element error. No storage client!")
return
if not element.for_id:
return
+29
View File
@@ -0,0 +1,29 @@
from praisonaiagents import Agent
# Create a weather agent
weather_agent = Agent(
instructions="""You are a weather agent that can provide weather information for a given city.""",
llm="gpt-4o-mini"
)
# Create a stock market agent
stock_agent = Agent(
instructions="""You are a stock market agent that can provide information about stock prices and market trends.""",
llm="gpt-4o-mini"
)
# Create a travel agent
travel_agent = Agent(
instructions="""You are a travel agent that can provide recommendations for destinations, hotels, and activities.""",
llm="gpt-4o-mini"
)
# Register the first two agents with blocking=False so they don't block execution
weather_agent.launch(path="/weather", port=3030, blocking=False)
stock_agent.launch(path="/stock", port=3030, blocking=False)
# Register the last agent with blocking=True to keep the server running
# This must be the last launch() call
travel_agent.launch(path="/travel", port=3030, blocking=True)
# The script will block at the last launch() call until the user presses Ctrl+C
@@ -22,9 +22,16 @@ import inspect
import uuid
from dataclasses import dataclass
# Don't import FastAPI dependencies here - use lazy loading instead
if TYPE_CHECKING:
from ..task.task import Task
# Shared variables for API server
_shared_app = None
_server_started = False
_registered_agents = {}
@dataclass
class ChatCompletionMessage:
content: str
@@ -1399,4 +1406,157 @@ Your Goal: {self.goal}
except Exception as e:
logging.error(f"Error in execute_tool_async: {str(e)}", exc_info=True)
return {"error": f"Error in execute_tool_async: {str(e)}"}
return {"error": f"Error in execute_tool_async: {str(e)}"}
def launch(self, path: str = '/', port: int = 8000, host: str = '0.0.0.0', autostart: bool = True, debug: bool = False, blocking: bool = True):
"""
Launch the agent as an HTTP API endpoint.
Args:
path: API endpoint path (default: '/')
port: Server port (default: 8000)
host: Server host (default: '0.0.0.0')
autostart: Whether to start the server automatically (default: True)
debug: Enable debug mode for uvicorn (default: False)
blocking: If True, blocks the main thread to keep the server running (default: True)
Returns:
None
"""
global _server_started, _registered_agents, _shared_app
# Try to import FastAPI dependencies - lazy loading
try:
import uvicorn
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel
# Define the request model here since we need pydantic
class AgentQuery(BaseModel):
query: str
except ImportError as e:
# Check which specific module is missing
missing_module = str(e).split("No module named '")[-1].rstrip("'")
display_error(f"Missing dependency: {missing_module}. Required for launch() method.")
logging.error(f"Missing dependency: {missing_module}. Required for launch() method.")
print(f"\nTo add API capabilities, install the required dependencies:")
print(f"pip install {missing_module}")
print("\nOr install all API dependencies with:")
print("pip install 'praisonaiagents[api]'")
return None
# Initialize shared FastAPI app if not already created
if _shared_app is None:
_shared_app = FastAPI(
title="PraisonAI Agents API",
description="API for interacting with PraisonAI Agents"
)
# Add a root endpoint with a welcome message
@_shared_app.get("/")
async def root():
return {"message": "Welcome to PraisonAI Agents API. See /docs for usage."}
# Normalize path to ensure it starts with /
if not path.startswith('/'):
path = f'/{path}'
# Check if path is already registered by another agent
if path in _registered_agents and _registered_agents[path] != self.agent_id:
existing_agent = _registered_agents[path]
logging.warning(f"Path '{path}' is already registered by another agent. Please use a different path.")
print(f"⚠️ Warning: Path '{path}' is already registered by another agent.")
# Use a modified path to avoid conflicts
original_path = path
path = f"{path}_{self.agent_id[:6]}"
logging.warning(f"Using '{path}' instead of '{original_path}'")
print(f"🔄 Using '{path}' instead")
# Register the agent to this path
_registered_agents[path] = self.agent_id
# Define the endpoint handler
@_shared_app.post(path)
async def handle_agent_query(request: Request, query_data: Optional[AgentQuery] = None):
# Handle both direct JSON with query field and form data
if query_data is None:
try:
request_data = await request.json()
if "query" not in request_data:
raise HTTPException(status_code=400, detail="Missing 'query' field in request")
query = request_data["query"]
except:
# Fallback to form data or query params
form_data = await request.form()
if "query" in form_data:
query = form_data["query"]
else:
raise HTTPException(status_code=400, detail="Missing 'query' field in request")
else:
query = query_data.query
try:
# Use async version if available, otherwise use sync version
if asyncio.iscoroutinefunction(self.chat):
response = await self.achat(query)
else:
# Run sync function in a thread to avoid blocking
loop = asyncio.get_event_loop()
response = await loop.run_in_executor(None, lambda: self.chat(query))
return {"response": response}
except Exception as e:
logging.error(f"Error processing query: {str(e)}", exc_info=True)
return JSONResponse(
status_code=500,
content={"error": f"Error processing query: {str(e)}"}
)
print(f"🚀 Agent '{self.name}' available at http://{host}:{port}{path}")
# Start the server if this is the first launch call and autostart is True
if autostart and not _server_started:
_server_started = True
# Add healthcheck endpoint
@_shared_app.get("/health")
async def healthcheck():
return {"status": "ok", "agents": list(_registered_agents.keys())}
# Start the server in a separate thread to not block execution
import threading
def run_server():
try:
uvicorn.run(_shared_app, host=host, port=port, log_level="debug" if debug else "info")
except Exception as e:
logging.error(f"Error starting server: {str(e)}", exc_info=True)
print(f"❌ Error starting server: {str(e)}")
server_thread = threading.Thread(target=run_server, daemon=True)
server_thread.start()
# Give the server a moment to start up
import time
time.sleep(0.5)
print(f"✅ FastAPI server started at http://{host}:{port}")
print(f"📚 API documentation available at http://{host}:{port}/docs")
# If blocking is True, keep the main thread alive
if blocking:
print("\nServer is running in blocking mode. Press Ctrl+C to stop...")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\nServer stopped")
else:
# Note for non-blocking mode
print("\nNote: Server is running in a background thread. To keep it alive, either:")
print("1. Set blocking=True when calling launch()")
print("2. Keep your main application running")
print("3. Use a loop in your code to prevent the program from exiting")
return None
+9 -2
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.0.77"
version = "0.0.78"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
authors = [
{ name="Mervin Praison" }
@@ -38,10 +38,17 @@ llm = [
"pydantic>=2.4.2"
]
# Add API dependencies
api = [
"fastapi>=0.115.0",
"uvicorn>=0.34.0"
]
# Combined features
all = [
"praisonaiagents[memory]",
"praisonaiagents[knowledge]",
"praisonaiagents[llm]",
"praisonaiagents[mcp]"
"praisonaiagents[mcp]",
"praisonaiagents[api]"
]
+8
View File
@@ -0,0 +1,8 @@
from praisonaiagents import Agent, MCP
search_agent = Agent(
instructions="""You are a weather agent that can provide weather information for a given city.""",
llm="openai/gpt-4o-mini",
tools=MCP("http://localhost:8080/sse")
)
search_agent.launch(path="/weather", port=3030)
+4
View File
@@ -0,0 +1,4 @@
from praisonaiagents import Agent
agent = Agent(instructions="""You are a helpful assistant.""", llm="gpt-4o-mini")
agent.launch(path="/ask", port=3030)
+10 -1
View File
@@ -1883,7 +1883,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.77"
version = "0.0.78"
source = { editable = "." }
dependencies = [
{ name = "mcp" },
@@ -1896,11 +1896,17 @@ dependencies = [
all = [
{ name = "chonkie" },
{ name = "chromadb" },
{ name = "fastapi" },
{ name = "litellm" },
{ name = "markitdown" },
{ name = "mcp" },
{ name = "mem0ai" },
{ name = "pydantic" },
{ name = "uvicorn" },
]
api = [
{ name = "fastapi" },
{ name = "uvicorn" },
]
knowledge = [
{ name = "chonkie" },
@@ -1924,12 +1930,14 @@ requires-dist = [
{ name = "chonkie", marker = "extra == 'knowledge'", specifier = ">=1.0.2" },
{ name = "chromadb", marker = "extra == 'knowledge'", specifier = "==0.5.23" },
{ name = "chromadb", marker = "extra == 'memory'", specifier = ">=0.5.23" },
{ name = "fastapi", marker = "extra == 'api'", specifier = ">=0.115.0" },
{ name = "litellm", marker = "extra == 'llm'", specifier = ">=1.50.0" },
{ name = "markitdown", extras = ["all"], marker = "extra == 'knowledge'" },
{ name = "mcp", specifier = ">=1.6.0" },
{ name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.6.0" },
{ name = "mem0ai", marker = "extra == 'knowledge'", specifier = ">=0.1.0" },
{ name = "openai" },
{ name = "praisonaiagents", extras = ["api"], marker = "extra == 'all'" },
{ name = "praisonaiagents", extras = ["knowledge"], marker = "extra == 'all'" },
{ name = "praisonaiagents", extras = ["llm"], marker = "extra == 'all'" },
{ name = "praisonaiagents", extras = ["mcp"], marker = "extra == 'all'" },
@@ -1937,6 +1945,7 @@ requires-dist = [
{ name = "pydantic" },
{ name = "pydantic", marker = "extra == 'llm'", specifier = ">=2.4.2" },
{ name = "rich" },
{ name = "uvicorn", marker = "extra == 'api'", specifier = ">=0.34.0" },
]
[[package]]