mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-09 11:51:37 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e938fe63ea | ||
|
|
ef96e53843 | ||
|
|
036c463a43 | ||
|
|
0e6b4aefc3 | ||
|
|
bbb9f677ae | ||
|
|
0da201b3b6 | ||
|
|
a7b85043e1 | ||
|
|
aa6b6ac62e |
No files matched your search
@@ -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>
|
||||
@@ -238,6 +238,7 @@
|
||||
"group": "MCP",
|
||||
"pages": [
|
||||
"mcp/airbnb",
|
||||
"mcp/stdio",
|
||||
"mcp/sse",
|
||||
"mcp/ollama",
|
||||
"mcp/groq",
|
||||
|
||||
@@ -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?")
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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]"
|
||||
]
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
Generated
+10
-1
@@ -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]]
|
||||
|
||||
Reference in new issue
Block a user