pull/1104/head
parent
a762c79d6e
commit
ede23de101
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,475 @@
|
|||||||
|
# AOP (Agent Orchestration Protocol)
|
||||||
|
|
||||||
|
The Agent Orchestration Protocol (AOP) is a powerful framework for deploying multiple Swarms agents as tools in an MCP (Model Context Protocol) server. This enables you to create a distributed system where agents can be accessed as individual tools, making them available for use by other systems, applications, or clients.
|
||||||
|
|
||||||
|
AOP provides two main classes:
|
||||||
|
|
||||||
|
- **AOP**: Deploy agents as tools in a single MCP server
|
||||||
|
- **AOPCluster**: Connect to and manage multiple MCP servers
|
||||||
|
|
||||||
|
## Core Classes
|
||||||
|
|
||||||
|
### AgentToolConfig
|
||||||
|
|
||||||
|
Configuration dataclass for converting an agent to an MCP tool.
|
||||||
|
|
||||||
|
| Attribute | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `tool_name` | `str` | Required | The name of the tool in the MCP server |
|
||||||
|
| `tool_description` | `str` | Required | Description of what the tool does |
|
||||||
|
| `input_schema` | `Dict[str, Any]` | Required | JSON schema for the tool's input parameters |
|
||||||
|
| `output_schema` | `Dict[str, Any]` | Required | JSON schema for the tool's output |
|
||||||
|
| `timeout` | `int` | `30` | Maximum time to wait for agent execution (seconds) |
|
||||||
|
| `max_retries` | `int` | `3` | Number of retries if agent execution fails |
|
||||||
|
| `verbose` | `bool` | `False` | Enable verbose logging for this tool |
|
||||||
|
| `traceback_enabled` | `bool` | `True` | Enable traceback logging for errors |
|
||||||
|
|
||||||
|
### AOP Class
|
||||||
|
|
||||||
|
Main class for deploying agents as tools in an MCP server.
|
||||||
|
|
||||||
|
#### Constructor Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `server_name` | `str` | `"AOP Cluster"` | Name for the MCP server |
|
||||||
|
| `description` | `str` | `"A cluster that enables you to deploy multiple agents as tools in an MCP server."` | Server description |
|
||||||
|
| `agents` | `List[Agent]` | `None` | Optional list of agents to add initially |
|
||||||
|
| `port` | `int` | `8000` | Port for the MCP server |
|
||||||
|
| `transport` | `str` | `"streamable-http"` | Transport type for the MCP server |
|
||||||
|
| `verbose` | `bool` | `False` | Enable verbose logging |
|
||||||
|
| `traceback_enabled` | `bool` | `True` | Enable traceback logging for errors |
|
||||||
|
| `host` | `str` | `"localhost"` | Host to bind the server to |
|
||||||
|
| `log_level` | `str` | `"INFO"` | Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
|
||||||
|
|
||||||
|
#### Methods
|
||||||
|
|
||||||
|
##### add_agent()
|
||||||
|
|
||||||
|
Add an agent to the MCP server as a tool.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `agent` | `AgentType` | Required | The swarms Agent instance to deploy |
|
||||||
|
| `tool_name` | `str` | `None` | Name for the tool (defaults to agent.agent_name) |
|
||||||
|
| `tool_description` | `str` | `None` | Description of the tool (defaults to agent.agent_description) |
|
||||||
|
| `input_schema` | `Dict[str, Any]` | `None` | JSON schema for input parameters |
|
||||||
|
| `output_schema` | `Dict[str, Any]` | `None` | JSON schema for output |
|
||||||
|
| `timeout` | `int` | `30` | Maximum execution time in seconds |
|
||||||
|
| `max_retries` | `int` | `3` | Number of retries on failure |
|
||||||
|
| `verbose` | `bool` | `None` | Enable verbose logging for this tool |
|
||||||
|
| `traceback_enabled` | `bool` | `None` | Enable traceback logging for this tool |
|
||||||
|
|
||||||
|
**Returns:** `str` - The tool name that was registered
|
||||||
|
|
||||||
|
##### add_agents_batch()
|
||||||
|
|
||||||
|
Add multiple agents to the MCP server as tools in batch.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `agents` | `List[Agent]` | Required | List of swarms Agent instances |
|
||||||
|
| `tool_names` | `List[str]` | `None` | Optional list of tool names |
|
||||||
|
| `tool_descriptions` | `List[str]` | `None` | Optional list of tool descriptions |
|
||||||
|
| `input_schemas` | `List[Dict[str, Any]]` | `None` | Optional list of input schemas |
|
||||||
|
| `output_schemas` | `List[Dict[str, Any]]` | `None` | Optional list of output schemas |
|
||||||
|
| `timeouts` | `List[int]` | `None` | Optional list of timeout values |
|
||||||
|
| `max_retries_list` | `List[int]` | `None` | Optional list of max retry values |
|
||||||
|
| `verbose_list` | `List[bool]` | `None` | Optional list of verbose settings |
|
||||||
|
| `traceback_enabled_list` | `List[bool]` | `None` | Optional list of traceback settings |
|
||||||
|
|
||||||
|
**Returns:** `List[str]` - List of tool names that were registered
|
||||||
|
|
||||||
|
##### remove_agent()
|
||||||
|
|
||||||
|
Remove an agent from the MCP server.
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|-----------|------|-------------|
|
||||||
|
| `tool_name` | `str` | Name of the tool to remove |
|
||||||
|
|
||||||
|
**Returns:** `bool` - True if agent was removed, False if not found
|
||||||
|
|
||||||
|
##### list_agents()
|
||||||
|
|
||||||
|
Get a list of all registered agent tool names.
|
||||||
|
|
||||||
|
**Returns:** `List[str]` - List of tool names
|
||||||
|
|
||||||
|
##### get_agent_info()
|
||||||
|
|
||||||
|
Get information about a specific agent tool.
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|-----------|------|-------------|
|
||||||
|
| `tool_name` | `str` | Name of the tool |
|
||||||
|
|
||||||
|
**Returns:** `Dict[str, Any]` - Agent information, or None if not found
|
||||||
|
|
||||||
|
##### start_server()
|
||||||
|
|
||||||
|
Start the MCP server.
|
||||||
|
|
||||||
|
##### run()
|
||||||
|
|
||||||
|
Run the MCP server (alias for start_server).
|
||||||
|
|
||||||
|
##### get_server_info()
|
||||||
|
|
||||||
|
Get information about the MCP server and registered tools.
|
||||||
|
|
||||||
|
**Returns:** `Dict[str, Any]` - Server information
|
||||||
|
|
||||||
|
### AOPCluster Class
|
||||||
|
|
||||||
|
Class for connecting to and managing multiple MCP servers.
|
||||||
|
|
||||||
|
#### AOPCluster Constructor Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `urls` | `List[str]` | Required | List of MCP server URLs to connect to |
|
||||||
|
| `transport` | `str` | `"streamable-http"` | Transport type for connections |
|
||||||
|
|
||||||
|
#### AOPCluster Methods
|
||||||
|
|
||||||
|
##### get_tools()
|
||||||
|
|
||||||
|
Get tools from all connected MCP servers.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `output_type` | `Literal["json", "dict", "str"]` | `"dict"` | Format of the output |
|
||||||
|
|
||||||
|
**Returns:** `List[Dict[str, Any]]` - List of available tools
|
||||||
|
|
||||||
|
##### find_tool_by_server_name()
|
||||||
|
|
||||||
|
Find a tool by its server name (function name).
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|-----------|------|-------------|
|
||||||
|
| `server_name` | `str` | The name of the tool/function to find |
|
||||||
|
|
||||||
|
**Returns:** `Dict[str, Any]` - Tool information, or None if not found
|
||||||
|
|
||||||
|
## Tool Parameters
|
||||||
|
|
||||||
|
All agent tools accept the following parameters:
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| `task` | `str` | Yes | The main task or prompt to execute |
|
||||||
|
| `img` | `str` | No | Single image to be processed by the agent |
|
||||||
|
| `imgs` | `List[str]` | No | Multiple images to be processed by the agent |
|
||||||
|
| `correct_answer` | `str` | No | Correct answer for validation or comparison |
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
All agent tools return a standardized response format:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": "string", // The agent's response to the task
|
||||||
|
"success": "boolean", // Whether the task was executed successfully
|
||||||
|
"error": "string" // Error message if execution failed (null if successful)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Examples
|
||||||
|
|
||||||
|
### Basic Server Setup
|
||||||
|
|
||||||
|
```python
|
||||||
|
from swarms import Agent
|
||||||
|
from swarms.structs.aop import AOP
|
||||||
|
|
||||||
|
# Create specialized agents
|
||||||
|
research_agent = Agent(
|
||||||
|
agent_name="Research-Agent",
|
||||||
|
agent_description="Expert in research, data collection, and information gathering",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
system_prompt="""You are a research specialist. Your role is to:
|
||||||
|
1. Gather comprehensive information on any given topic
|
||||||
|
2. Analyze data from multiple sources
|
||||||
|
3. Provide well-structured research findings
|
||||||
|
4. Cite sources and maintain accuracy
|
||||||
|
5. Present findings in a clear, organized manner
|
||||||
|
|
||||||
|
Always provide detailed, factual information with proper context.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
analysis_agent = Agent(
|
||||||
|
agent_name="Analysis-Agent",
|
||||||
|
agent_description="Expert in data analysis, pattern recognition, and generating insights",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
system_prompt="""You are an analysis specialist. Your role is to:
|
||||||
|
1. Analyze data and identify patterns
|
||||||
|
2. Generate actionable insights
|
||||||
|
3. Create visualizations and summaries
|
||||||
|
4. Provide statistical analysis
|
||||||
|
5. Make data-driven recommendations
|
||||||
|
|
||||||
|
Focus on extracting meaningful insights from information.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create AOP instance
|
||||||
|
deployer = AOP(
|
||||||
|
server_name="MyAgentServer",
|
||||||
|
port=8000,
|
||||||
|
verbose=True,
|
||||||
|
log_level="INFO"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add agents individually
|
||||||
|
deployer.add_agent(research_agent)
|
||||||
|
deployer.add_agent(analysis_agent)
|
||||||
|
|
||||||
|
# Start the server
|
||||||
|
deployer.run()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Agent Addition
|
||||||
|
|
||||||
|
```python
|
||||||
|
from swarms import Agent
|
||||||
|
from swarms.structs.aop import AOP
|
||||||
|
|
||||||
|
# Create multiple agents
|
||||||
|
agents = [
|
||||||
|
Agent(
|
||||||
|
agent_name="Research-Agent",
|
||||||
|
agent_description="Expert in research and data collection",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
),
|
||||||
|
Agent(
|
||||||
|
agent_name="Writing-Agent",
|
||||||
|
agent_description="Expert in content creation and editing",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
),
|
||||||
|
Agent(
|
||||||
|
agent_name="Code-Agent",
|
||||||
|
agent_description="Expert in programming and code review",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create AOP instance
|
||||||
|
deployer = AOP("MyAgentServer", verbose=True)
|
||||||
|
|
||||||
|
# Add all agents at once
|
||||||
|
tool_names = deployer.add_agents_batch(agents)
|
||||||
|
|
||||||
|
print(f"Added {len(tool_names)} agents: {tool_names}")
|
||||||
|
|
||||||
|
# Start the server
|
||||||
|
deployer.run()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Configuration
|
||||||
|
|
||||||
|
```python
|
||||||
|
from swarms import Agent
|
||||||
|
from swarms.structs.aop import AOP
|
||||||
|
|
||||||
|
# Create agent with custom configuration
|
||||||
|
research_agent = Agent(
|
||||||
|
agent_name="Research-Agent",
|
||||||
|
agent_description="Expert in research and data collection",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create AOP with custom settings
|
||||||
|
deployer = AOP(
|
||||||
|
server_name="AdvancedAgentServer",
|
||||||
|
port=8001,
|
||||||
|
host="0.0.0.0", # Allow external connections
|
||||||
|
verbose=True,
|
||||||
|
traceback_enabled=True,
|
||||||
|
log_level="DEBUG"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add agent with custom tool configuration
|
||||||
|
deployer.add_agent(
|
||||||
|
agent=research_agent,
|
||||||
|
tool_name="custom_research_tool",
|
||||||
|
tool_description="Custom research tool with extended capabilities",
|
||||||
|
timeout=60, # 60 second timeout
|
||||||
|
max_retries=5, # 5 retries
|
||||||
|
verbose=True,
|
||||||
|
traceback_enabled=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add custom input/output schemas
|
||||||
|
custom_input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"task": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The research task to execute"
|
||||||
|
},
|
||||||
|
"sources": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
"description": "Specific sources to research"
|
||||||
|
},
|
||||||
|
"depth": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["shallow", "medium", "deep"],
|
||||||
|
"description": "Research depth level"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["task"]
|
||||||
|
}
|
||||||
|
|
||||||
|
custom_output_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"result": {"type": "string"},
|
||||||
|
"sources": {"type": "array", "items": {"type": "string"}},
|
||||||
|
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
|
||||||
|
"success": {"type": "boolean"},
|
||||||
|
"error": {"type": "string"}
|
||||||
|
},
|
||||||
|
"required": ["result", "success"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add another agent with custom schemas
|
||||||
|
analysis_agent = Agent(
|
||||||
|
agent_name="Analysis-Agent",
|
||||||
|
agent_description="Expert in data analysis",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
deployer.add_agent(
|
||||||
|
agent=analysis_agent,
|
||||||
|
tool_name="custom_analysis_tool",
|
||||||
|
tool_description="Custom analysis tool",
|
||||||
|
input_schema=custom_input_schema,
|
||||||
|
output_schema=custom_output_schema,
|
||||||
|
timeout=45,
|
||||||
|
max_retries=3
|
||||||
|
)
|
||||||
|
|
||||||
|
# List all registered agents
|
||||||
|
print("Registered agents:", deployer.list_agents())
|
||||||
|
|
||||||
|
# Get server information
|
||||||
|
server_info = deployer.get_server_info()
|
||||||
|
print("Server info:", server_info)
|
||||||
|
|
||||||
|
# Start the server
|
||||||
|
deployer.run()
|
||||||
|
```
|
||||||
|
|
||||||
|
### AOPCluster Usage
|
||||||
|
|
||||||
|
```python
|
||||||
|
import json
|
||||||
|
from swarms.structs.aop import AOPCluster
|
||||||
|
|
||||||
|
# Connect to multiple MCP servers
|
||||||
|
cluster = AOPCluster(
|
||||||
|
urls=[
|
||||||
|
"http://localhost:8000/mcp",
|
||||||
|
"http://localhost:8001/mcp",
|
||||||
|
"http://localhost:8002/mcp"
|
||||||
|
],
|
||||||
|
transport="streamable-http"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get all available tools from all servers
|
||||||
|
all_tools = cluster.get_tools(output_type="dict")
|
||||||
|
print(f"Found {len(all_tools)} tools across all servers")
|
||||||
|
|
||||||
|
# Pretty print all tools
|
||||||
|
print(json.dumps(all_tools, indent=2))
|
||||||
|
|
||||||
|
# Find a specific tool by name
|
||||||
|
research_tool = cluster.find_tool_by_server_name("Research-Agent")
|
||||||
|
if research_tool:
|
||||||
|
print("Found Research-Agent tool:")
|
||||||
|
print(json.dumps(research_tool, indent=2))
|
||||||
|
else:
|
||||||
|
print("Research-Agent tool not found")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tool Execution Examples
|
||||||
|
|
||||||
|
Once your AOP server is running, you can call the tools using MCP clients. Here are examples of how the tools would be called:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Example tool calls (these would be made by MCP clients)
|
||||||
|
|
||||||
|
# Basic task execution
|
||||||
|
result = research_tool(task="Research the latest AI trends in 2024")
|
||||||
|
|
||||||
|
# Task with single image
|
||||||
|
result = analysis_tool(
|
||||||
|
task="Analyze this chart and provide insights",
|
||||||
|
img="path/to/chart.png"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Task with multiple images
|
||||||
|
result = writing_tool(
|
||||||
|
task="Write a comprehensive report based on these images",
|
||||||
|
imgs=["image1.jpg", "image2.jpg", "image3.jpg"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Task with validation
|
||||||
|
result = code_tool(
|
||||||
|
task="Debug this Python function",
|
||||||
|
correct_answer="Expected output: Hello World"
|
||||||
|
)
|
||||||
|
|
||||||
|
# The response format for all calls:
|
||||||
|
# {
|
||||||
|
# "result": "The agent's response...",
|
||||||
|
# "success": true,
|
||||||
|
# "error": null
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
AOP provides comprehensive error handling:
|
||||||
|
|
||||||
|
- **Timeout Protection**: Each agent has configurable timeout limits
|
||||||
|
- **Retry Logic**: Automatic retries on failure with configurable retry counts
|
||||||
|
- **Detailed Logging**: Verbose logging with traceback information
|
||||||
|
- **Graceful Degradation**: Failed agents don't crash the entire server
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
| Best Practice | Description |
|
||||||
|
|------------------------------|--------------------------------------------------------------------|
|
||||||
|
| **Use Descriptive Names** | Choose clear, descriptive tool names |
|
||||||
|
| **Set Appropriate Timeouts** | Configure timeouts based on expected task complexity |
|
||||||
|
| **Enable Logging** | Use verbose logging for debugging and monitoring |
|
||||||
|
| **Handle Errors** | Always check the `success` field in tool responses |
|
||||||
|
| **Resource Management** | Monitor server resources when running multiple agents |
|
||||||
|
| **Security** | Use appropriate host/port settings for your deployment environment |
|
||||||
|
|
||||||
|
## Integration with Other Systems
|
||||||
|
|
||||||
|
AOP servers can be integrated with:
|
||||||
|
|
||||||
|
| Integration Target | Description |
|
||||||
|
|------------------------|--------------------------------------------------|
|
||||||
|
| **MCP Clients** | Any MCP-compatible client |
|
||||||
|
| **Web Applications** | Via HTTP transport |
|
||||||
|
| **Other Swarms** | As part of larger multi-agent systems |
|
||||||
|
| **External APIs** | Through MCP protocol |
|
||||||
|
|
||||||
|
This makes AOP a powerful tool for creating distributed, scalable agent systems that can be easily integrated into existing workflows and applications.
|
@ -0,0 +1,336 @@
|
|||||||
|
# AOP Cluster Example
|
||||||
|
|
||||||
|
This example demonstrates how to use AOPCluster to connect to and manage multiple MCP servers running AOP agents.
|
||||||
|
|
||||||
|
## Basic Cluster Setup
|
||||||
|
|
||||||
|
```python
|
||||||
|
import json
|
||||||
|
from swarms.structs.aop import AOPCluster
|
||||||
|
|
||||||
|
# Connect to multiple MCP servers
|
||||||
|
cluster = AOPCluster(
|
||||||
|
urls=[
|
||||||
|
"http://localhost:8000/mcp", # Research and Analysis server
|
||||||
|
"http://localhost:8001/mcp", # Writing and Code server
|
||||||
|
"http://localhost:8002/mcp" # Financial server
|
||||||
|
],
|
||||||
|
transport="streamable-http"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get all available tools from all servers
|
||||||
|
all_tools = cluster.get_tools(output_type="dict")
|
||||||
|
print(f"Found {len(all_tools)} tools across all servers")
|
||||||
|
|
||||||
|
# Pretty print all tools
|
||||||
|
print(json.dumps(all_tools, indent=2))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Finding Specific Tools
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Find a specific tool by name
|
||||||
|
research_tool = cluster.find_tool_by_server_name("Research-Agent")
|
||||||
|
if research_tool:
|
||||||
|
print("Found Research-Agent tool:")
|
||||||
|
print(json.dumps(research_tool, indent=2))
|
||||||
|
else:
|
||||||
|
print("Research-Agent tool not found")
|
||||||
|
|
||||||
|
# Find multiple tools
|
||||||
|
tool_names = ["Research-Agent", "Analysis-Agent", "Writing-Agent", "Code-Agent"]
|
||||||
|
found_tools = {}
|
||||||
|
|
||||||
|
for tool_name in tool_names:
|
||||||
|
tool = cluster.find_tool_by_server_name(tool_name)
|
||||||
|
if tool:
|
||||||
|
found_tools[tool_name] = tool
|
||||||
|
print(f"✓ Found {tool_name}")
|
||||||
|
else:
|
||||||
|
print(f"✗ {tool_name} not found")
|
||||||
|
|
||||||
|
print(f"Found {len(found_tools)} out of {len(tool_names)} tools")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tool Discovery and Management
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Get tools in different formats
|
||||||
|
json_tools = cluster.get_tools(output_type="json")
|
||||||
|
dict_tools = cluster.get_tools(output_type="dict")
|
||||||
|
str_tools = cluster.get_tools(output_type="str")
|
||||||
|
|
||||||
|
print(f"JSON format: {len(json_tools)} tools")
|
||||||
|
print(f"Dict format: {len(dict_tools)} tools")
|
||||||
|
print(f"String format: {len(str_tools)} tools")
|
||||||
|
|
||||||
|
# Analyze tool distribution across servers
|
||||||
|
server_tools = {}
|
||||||
|
for tool in dict_tools:
|
||||||
|
server_name = tool.get("server", "unknown")
|
||||||
|
if server_name not in server_tools:
|
||||||
|
server_tools[server_name] = []
|
||||||
|
server_tools[server_name].append(tool.get("function", {}).get("name", "unknown"))
|
||||||
|
|
||||||
|
print("\nTools by server:")
|
||||||
|
for server, tools in server_tools.items():
|
||||||
|
print(f" {server}: {len(tools)} tools - {tools}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Cluster Management
|
||||||
|
|
||||||
|
```python
|
||||||
|
class AOPClusterManager:
|
||||||
|
def __init__(self, urls, transport="streamable-http"):
|
||||||
|
self.cluster = AOPCluster(urls, transport)
|
||||||
|
self.tools_cache = {}
|
||||||
|
self.last_update = None
|
||||||
|
|
||||||
|
def refresh_tools(self):
|
||||||
|
"""Refresh the tools cache"""
|
||||||
|
self.tools_cache = {}
|
||||||
|
tools = self.cluster.get_tools(output_type="dict")
|
||||||
|
for tool in tools:
|
||||||
|
tool_name = tool.get("function", {}).get("name")
|
||||||
|
if tool_name:
|
||||||
|
self.tools_cache[tool_name] = tool
|
||||||
|
self.last_update = time.time()
|
||||||
|
return len(self.tools_cache)
|
||||||
|
|
||||||
|
def get_tool(self, tool_name):
|
||||||
|
"""Get a specific tool by name"""
|
||||||
|
if not self.tools_cache or time.time() - self.last_update > 300: # 5 min cache
|
||||||
|
self.refresh_tools()
|
||||||
|
return self.tools_cache.get(tool_name)
|
||||||
|
|
||||||
|
def list_tools_by_category(self):
|
||||||
|
"""Categorize tools by their names"""
|
||||||
|
categories = {
|
||||||
|
"research": [],
|
||||||
|
"analysis": [],
|
||||||
|
"writing": [],
|
||||||
|
"code": [],
|
||||||
|
"financial": [],
|
||||||
|
"other": []
|
||||||
|
}
|
||||||
|
|
||||||
|
for tool_name in self.tools_cache.keys():
|
||||||
|
tool_name_lower = tool_name.lower()
|
||||||
|
if "research" in tool_name_lower:
|
||||||
|
categories["research"].append(tool_name)
|
||||||
|
elif "analysis" in tool_name_lower:
|
||||||
|
categories["analysis"].append(tool_name)
|
||||||
|
elif "writing" in tool_name_lower:
|
||||||
|
categories["writing"].append(tool_name)
|
||||||
|
elif "code" in tool_name_lower:
|
||||||
|
categories["code"].append(tool_name)
|
||||||
|
elif "financial" in tool_name_lower:
|
||||||
|
categories["financial"].append(tool_name)
|
||||||
|
else:
|
||||||
|
categories["other"].append(tool_name)
|
||||||
|
|
||||||
|
return categories
|
||||||
|
|
||||||
|
def get_available_servers(self):
|
||||||
|
"""Get list of available servers"""
|
||||||
|
servers = set()
|
||||||
|
for tool in self.tools_cache.values():
|
||||||
|
server = tool.get("server", "unknown")
|
||||||
|
servers.add(server)
|
||||||
|
return list(servers)
|
||||||
|
|
||||||
|
# Usage example
|
||||||
|
import time
|
||||||
|
|
||||||
|
manager = AOPClusterManager([
|
||||||
|
"http://localhost:8000/mcp",
|
||||||
|
"http://localhost:8001/mcp",
|
||||||
|
"http://localhost:8002/mcp"
|
||||||
|
])
|
||||||
|
|
||||||
|
# Refresh and display tools
|
||||||
|
tool_count = manager.refresh_tools()
|
||||||
|
print(f"Loaded {tool_count} tools")
|
||||||
|
|
||||||
|
# Categorize tools
|
||||||
|
categories = manager.list_tools_by_category()
|
||||||
|
for category, tools in categories.items():
|
||||||
|
if tools:
|
||||||
|
print(f"{category.title()}: {tools}")
|
||||||
|
|
||||||
|
# Get available servers
|
||||||
|
servers = manager.get_available_servers()
|
||||||
|
print(f"Available servers: {servers}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling and Resilience
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ResilientAOPCluster:
|
||||||
|
def __init__(self, urls, transport="streamable-http"):
|
||||||
|
self.urls = urls
|
||||||
|
self.transport = transport
|
||||||
|
self.cluster = AOPCluster(urls, transport)
|
||||||
|
self.failed_servers = set()
|
||||||
|
|
||||||
|
def get_tools_with_fallback(self, output_type="dict"):
|
||||||
|
"""Get tools with fallback for failed servers"""
|
||||||
|
try:
|
||||||
|
return self.cluster.get_tools(output_type=output_type)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting tools: {e}")
|
||||||
|
# Try individual servers
|
||||||
|
all_tools = []
|
||||||
|
for url in self.urls:
|
||||||
|
if url in self.failed_servers:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
single_cluster = AOPCluster([url], self.transport)
|
||||||
|
tools = single_cluster.get_tools(output_type=output_type)
|
||||||
|
all_tools.extend(tools)
|
||||||
|
except Exception as server_error:
|
||||||
|
print(f"Server {url} failed: {server_error}")
|
||||||
|
self.failed_servers.add(url)
|
||||||
|
return all_tools
|
||||||
|
|
||||||
|
def find_tool_with_retry(self, tool_name, max_retries=3):
|
||||||
|
"""Find tool with retry logic"""
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
try:
|
||||||
|
return self.cluster.find_tool_by_server_name(tool_name)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Attempt {attempt + 1} failed: {e}")
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
time.sleep(2 ** attempt) # Exponential backoff
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
resilient_cluster = ResilientAOPCluster([
|
||||||
|
"http://localhost:8000/mcp",
|
||||||
|
"http://localhost:8001/mcp",
|
||||||
|
"http://localhost:8002/mcp"
|
||||||
|
])
|
||||||
|
|
||||||
|
# Get tools with error handling
|
||||||
|
tools = resilient_cluster.get_tools_with_fallback()
|
||||||
|
print(f"Retrieved {len(tools)} tools")
|
||||||
|
|
||||||
|
# Find tool with retry
|
||||||
|
research_tool = resilient_cluster.find_tool_with_retry("Research-Agent")
|
||||||
|
if research_tool:
|
||||||
|
print("Found Research-Agent tool")
|
||||||
|
else:
|
||||||
|
print("Research-Agent tool not found after retries")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring and Health Checks
|
||||||
|
|
||||||
|
```python
|
||||||
|
class AOPClusterMonitor:
|
||||||
|
def __init__(self, cluster_manager):
|
||||||
|
self.manager = cluster_manager
|
||||||
|
self.health_status = {}
|
||||||
|
|
||||||
|
def check_server_health(self, url):
|
||||||
|
"""Check if a server is healthy"""
|
||||||
|
try:
|
||||||
|
single_cluster = AOPCluster([url], self.manager.cluster.transport)
|
||||||
|
tools = single_cluster.get_tools(output_type="dict")
|
||||||
|
return {
|
||||||
|
"status": "healthy",
|
||||||
|
"tool_count": len(tools),
|
||||||
|
"timestamp": time.time()
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"status": "unhealthy",
|
||||||
|
"error": str(e),
|
||||||
|
"timestamp": time.time()
|
||||||
|
}
|
||||||
|
|
||||||
|
def check_all_servers(self):
|
||||||
|
"""Check health of all servers"""
|
||||||
|
for url in self.manager.cluster.urls:
|
||||||
|
health = self.check_server_health(url)
|
||||||
|
self.health_status[url] = health
|
||||||
|
status_icon = "✓" if health["status"] == "healthy" else "✗"
|
||||||
|
print(f"{status_icon} {url}: {health['status']}")
|
||||||
|
if health["status"] == "healthy":
|
||||||
|
print(f" Tools available: {health['tool_count']}")
|
||||||
|
else:
|
||||||
|
print(f" Error: {health['error']}")
|
||||||
|
|
||||||
|
def get_health_summary(self):
|
||||||
|
"""Get summary of server health"""
|
||||||
|
healthy_count = sum(1 for status in self.health_status.values()
|
||||||
|
if status["status"] == "healthy")
|
||||||
|
total_count = len(self.health_status)
|
||||||
|
return {
|
||||||
|
"healthy_servers": healthy_count,
|
||||||
|
"total_servers": total_count,
|
||||||
|
"health_percentage": (healthy_count / total_count) * 100 if total_count > 0 else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
monitor = AOPClusterMonitor(manager)
|
||||||
|
monitor.check_all_servers()
|
||||||
|
|
||||||
|
summary = monitor.get_health_summary()
|
||||||
|
print(f"Health Summary: {summary['healthy_servers']}/{summary['total_servers']} servers healthy ({summary['health_percentage']:.1f}%)")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from swarms.structs.aop import AOPCluster
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Initialize cluster
|
||||||
|
cluster = AOPCluster(
|
||||||
|
urls=[
|
||||||
|
"http://localhost:8000/mcp",
|
||||||
|
"http://localhost:8001/mcp",
|
||||||
|
"http://localhost:8002/mcp"
|
||||||
|
],
|
||||||
|
transport="streamable-http"
|
||||||
|
)
|
||||||
|
|
||||||
|
print("AOP Cluster Management System")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
# Get all tools
|
||||||
|
print("\n1. Discovering tools...")
|
||||||
|
tools = cluster.get_tools(output_type="dict")
|
||||||
|
print(f"Found {len(tools)} tools across all servers")
|
||||||
|
|
||||||
|
# List all tool names
|
||||||
|
tool_names = [tool.get("function", {}).get("name") for tool in tools]
|
||||||
|
print(f"Available tools: {tool_names}")
|
||||||
|
|
||||||
|
# Find specific tools
|
||||||
|
print("\n2. Finding specific tools...")
|
||||||
|
target_tools = ["Research-Agent", "Analysis-Agent", "Writing-Agent", "Code-Agent", "Financial-Agent"]
|
||||||
|
|
||||||
|
for tool_name in target_tools:
|
||||||
|
tool = cluster.find_tool_by_server_name(tool_name)
|
||||||
|
if tool:
|
||||||
|
print(f"✓ {tool_name}: Available")
|
||||||
|
else:
|
||||||
|
print(f"✗ {tool_name}: Not found")
|
||||||
|
|
||||||
|
# Display tool details
|
||||||
|
print("\n3. Tool details:")
|
||||||
|
for tool in tools[:3]: # Show first 3 tools
|
||||||
|
print(f"\nTool: {tool.get('function', {}).get('name')}")
|
||||||
|
print(f"Description: {tool.get('function', {}).get('description')}")
|
||||||
|
print(f"Parameters: {list(tool.get('function', {}).get('parameters', {}).get('properties', {}).keys())}")
|
||||||
|
|
||||||
|
print("\nAOP Cluster setup complete!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
This example demonstrates comprehensive AOP cluster management including tool discovery, error handling, health monitoring, and advanced cluster operations.
|
@ -0,0 +1,11 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from swarms.structs.aop import AOPCluster
|
||||||
|
|
||||||
|
aop_cluster = AOPCluster(
|
||||||
|
urls=["http://localhost:8000/mcp"],
|
||||||
|
transport="streamable-http",
|
||||||
|
)
|
||||||
|
|
||||||
|
print(json.dumps(aop_cluster.get_tools(output_type="dict"), indent=4))
|
||||||
|
print(aop_cluster.find_tool_by_server_name("Research-Agent"))
|
@ -0,0 +1,318 @@
|
|||||||
|
# AOP Server Setup Example
|
||||||
|
|
||||||
|
This example demonstrates how to set up an AOP (Agent Orchestration Protocol) server with multiple specialized agents.
|
||||||
|
|
||||||
|
## Complete Server Setup
|
||||||
|
|
||||||
|
```python
|
||||||
|
from swarms import Agent
|
||||||
|
from swarms.structs.aop import AOP
|
||||||
|
|
||||||
|
# Create specialized agents
|
||||||
|
research_agent = Agent(
|
||||||
|
agent_name="Research-Agent",
|
||||||
|
agent_description="Expert in research, data collection, and information gathering",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
system_prompt="""You are a research specialist. Your role is to:
|
||||||
|
1. Gather comprehensive information on any given topic
|
||||||
|
2. Analyze data from multiple sources
|
||||||
|
3. Provide well-structured research findings
|
||||||
|
4. Cite sources and maintain accuracy
|
||||||
|
5. Present findings in a clear, organized manner
|
||||||
|
|
||||||
|
Always provide detailed, factual information with proper context.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
analysis_agent = Agent(
|
||||||
|
agent_name="Analysis-Agent",
|
||||||
|
agent_description="Expert in data analysis, pattern recognition, and generating insights",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
system_prompt="""You are an analysis specialist. Your role is to:
|
||||||
|
1. Analyze data and identify patterns
|
||||||
|
2. Generate actionable insights
|
||||||
|
3. Create visualizations and summaries
|
||||||
|
4. Provide statistical analysis
|
||||||
|
5. Make data-driven recommendations
|
||||||
|
|
||||||
|
Focus on extracting meaningful insights from information.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
writing_agent = Agent(
|
||||||
|
agent_name="Writing-Agent",
|
||||||
|
agent_description="Expert in content creation, editing, and communication",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
system_prompt="""You are a writing specialist. Your role is to:
|
||||||
|
1. Create engaging, well-structured content
|
||||||
|
2. Edit and improve existing text
|
||||||
|
3. Adapt tone and style for different audiences
|
||||||
|
4. Ensure clarity and coherence
|
||||||
|
5. Follow best practices in writing
|
||||||
|
|
||||||
|
Always produce high-quality, professional content.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
code_agent = Agent(
|
||||||
|
agent_name="Code-Agent",
|
||||||
|
agent_description="Expert in programming, code review, and software development",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
system_prompt="""You are a coding specialist. Your role is to:
|
||||||
|
1. Write clean, efficient code
|
||||||
|
2. Debug and fix issues
|
||||||
|
3. Review and optimize code
|
||||||
|
4. Explain programming concepts
|
||||||
|
5. Follow best practices and standards
|
||||||
|
|
||||||
|
Always provide working, well-documented code.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
financial_agent = Agent(
|
||||||
|
agent_name="Financial-Agent",
|
||||||
|
agent_description="Expert in financial analysis, market research, and investment insights",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
system_prompt="""You are a financial specialist. Your role is to:
|
||||||
|
1. Analyze financial data and markets
|
||||||
|
2. Provide investment insights
|
||||||
|
3. Assess risk and opportunities
|
||||||
|
4. Create financial reports
|
||||||
|
5. Explain complex financial concepts
|
||||||
|
|
||||||
|
Always provide accurate, well-reasoned financial analysis.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create AOP instance
|
||||||
|
deployer = AOP(
|
||||||
|
server_name="MyAgentServer",
|
||||||
|
port=8000,
|
||||||
|
verbose=True,
|
||||||
|
log_level="INFO"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add all agents at once
|
||||||
|
agents = [
|
||||||
|
research_agent,
|
||||||
|
analysis_agent,
|
||||||
|
writing_agent,
|
||||||
|
code_agent,
|
||||||
|
financial_agent,
|
||||||
|
]
|
||||||
|
|
||||||
|
tool_names = deployer.add_agents_batch(agents)
|
||||||
|
print(f"Added {len(tool_names)} agents: {tool_names}")
|
||||||
|
|
||||||
|
# Display server information
|
||||||
|
server_info = deployer.get_server_info()
|
||||||
|
print(f"Server: {server_info['server_name']}")
|
||||||
|
print(f"Total tools: {server_info['total_tools']}")
|
||||||
|
print(f"Available tools: {server_info['tools']}")
|
||||||
|
|
||||||
|
# Start the server
|
||||||
|
print("Starting AOP server...")
|
||||||
|
deployer.run()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the Server
|
||||||
|
|
||||||
|
1. Save the code above to a file (e.g., `aop_server.py`)
|
||||||
|
2. Install required dependencies:
|
||||||
|
```bash
|
||||||
|
pip install swarms
|
||||||
|
```
|
||||||
|
3. Run the server:
|
||||||
|
```bash
|
||||||
|
python aop_server.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The server will start on `http://localhost:8000` and make all agents available as MCP tools.
|
||||||
|
|
||||||
|
## Tool Usage Examples
|
||||||
|
|
||||||
|
Once the server is running, you can call the tools using MCP clients:
|
||||||
|
|
||||||
|
### Research Agent
|
||||||
|
```python
|
||||||
|
# Call the research agent
|
||||||
|
result = research_tool(task="Research the latest AI trends in 2024")
|
||||||
|
print(result)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Analysis Agent with Image
|
||||||
|
```python
|
||||||
|
# Call the analysis agent with an image
|
||||||
|
result = analysis_tool(
|
||||||
|
task="Analyze this chart and provide insights",
|
||||||
|
img="path/to/chart.png"
|
||||||
|
)
|
||||||
|
print(result)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Writing Agent with Multiple Images
|
||||||
|
```python
|
||||||
|
# Call the writing agent with multiple images
|
||||||
|
result = writing_tool(
|
||||||
|
task="Write a comprehensive report based on these images",
|
||||||
|
imgs=["image1.jpg", "image2.jpg", "image3.jpg"]
|
||||||
|
)
|
||||||
|
print(result)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Agent with Validation
|
||||||
|
```python
|
||||||
|
# Call the code agent with expected output
|
||||||
|
result = code_tool(
|
||||||
|
task="Debug this Python function",
|
||||||
|
correct_answer="Expected output: Hello World"
|
||||||
|
)
|
||||||
|
print(result)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Financial Agent
|
||||||
|
```python
|
||||||
|
# Call the financial agent
|
||||||
|
result = financial_tool(task="Analyze the current market trends for tech stocks")
|
||||||
|
print(result)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Response Format
|
||||||
|
|
||||||
|
All tools return a standardized response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": "The agent's response to the task",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Configuration
|
||||||
|
|
||||||
|
### Custom Timeouts and Retries
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Add agent with custom configuration
|
||||||
|
deployer.add_agent(
|
||||||
|
agent=research_agent,
|
||||||
|
tool_name="custom_research_tool",
|
||||||
|
tool_description="Research tool with extended timeout",
|
||||||
|
timeout=120, # 2 minutes
|
||||||
|
max_retries=5,
|
||||||
|
verbose=True
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Input/Output Schemas
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Define custom schemas
|
||||||
|
custom_input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"task": {"type": "string", "description": "The research task"},
|
||||||
|
"sources": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
"description": "Specific sources to research"
|
||||||
|
},
|
||||||
|
"depth": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["shallow", "medium", "deep"],
|
||||||
|
"description": "Research depth level"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["task"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add agent with custom schemas
|
||||||
|
deployer.add_agent(
|
||||||
|
agent=research_agent,
|
||||||
|
tool_name="advanced_research_tool",
|
||||||
|
input_schema=custom_input_schema,
|
||||||
|
timeout=60
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring and Debugging
|
||||||
|
|
||||||
|
### Enable Verbose Logging
|
||||||
|
|
||||||
|
```python
|
||||||
|
deployer = AOP(
|
||||||
|
server_name="DebugServer",
|
||||||
|
verbose=True,
|
||||||
|
traceback_enabled=True,
|
||||||
|
log_level="DEBUG"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Server Status
|
||||||
|
|
||||||
|
```python
|
||||||
|
# List all registered agents
|
||||||
|
agents = deployer.list_agents()
|
||||||
|
print(f"Registered agents: {agents}")
|
||||||
|
|
||||||
|
# Get detailed agent information
|
||||||
|
for agent_name in agents:
|
||||||
|
info = deployer.get_agent_info(agent_name)
|
||||||
|
print(f"Agent {agent_name}: {info}")
|
||||||
|
|
||||||
|
# Get server information
|
||||||
|
server_info = deployer.get_server_info()
|
||||||
|
print(f"Server info: {server_info}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production Deployment
|
||||||
|
|
||||||
|
### External Access
|
||||||
|
|
||||||
|
```python
|
||||||
|
deployer = AOP(
|
||||||
|
server_name="ProductionServer",
|
||||||
|
host="0.0.0.0", # Allow external connections
|
||||||
|
port=8000,
|
||||||
|
verbose=False, # Disable verbose logging in production
|
||||||
|
log_level="WARNING"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Servers
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Server 1: Research and Analysis
|
||||||
|
research_deployer = AOP("ResearchServer", port=8000)
|
||||||
|
research_deployer.add_agent(research_agent)
|
||||||
|
research_deployer.add_agent(analysis_agent)
|
||||||
|
|
||||||
|
# Server 2: Writing and Code
|
||||||
|
content_deployer = AOP("ContentServer", port=8001)
|
||||||
|
content_deployer.add_agent(writing_agent)
|
||||||
|
content_deployer.add_agent(code_agent)
|
||||||
|
|
||||||
|
# Server 3: Financial
|
||||||
|
finance_deployer = AOP("FinanceServer", port=8002)
|
||||||
|
finance_deployer.add_agent(financial_agent)
|
||||||
|
|
||||||
|
# Start all servers
|
||||||
|
import threading
|
||||||
|
|
||||||
|
threading.Thread(target=research_deployer.run).start()
|
||||||
|
threading.Thread(target=content_deployer.run).start()
|
||||||
|
threading.Thread(target=finance_deployer.run).start()
|
||||||
|
```
|
||||||
|
|
||||||
|
This example demonstrates a complete AOP server setup with multiple specialized agents, proper configuration, and production-ready deployment options.
|
@ -0,0 +1,10 @@
|
|||||||
|
import json
|
||||||
|
from swarms.tools.mcp_client_tools import get_mcp_tools_sync
|
||||||
|
|
||||||
|
|
||||||
|
print(
|
||||||
|
json.dumps(
|
||||||
|
get_mcp_tools_sync(server_path="http://0.0.0.0:8000/mcp"),
|
||||||
|
indent=4,
|
||||||
|
)
|
||||||
|
)
|
@ -0,0 +1,105 @@
|
|||||||
|
import json
|
||||||
|
from swarms.tools.mcp_client_tools import (
|
||||||
|
get_mcp_tools_sync,
|
||||||
|
execute_tool_call_simple,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_tools(
|
||||||
|
server_path: str = "http://localhost:8000/mcp",
|
||||||
|
) -> list:
|
||||||
|
"""
|
||||||
|
Get all available MCP tools from the server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_path: URL of the MCP server
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of available tools
|
||||||
|
"""
|
||||||
|
tools = get_mcp_tools_sync(server_path=server_path)
|
||||||
|
return tools
|
||||||
|
|
||||||
|
|
||||||
|
def call_agent_tool(
|
||||||
|
tool_name: str,
|
||||||
|
task: str,
|
||||||
|
server_path: str = "http://localhost:8000/mcp",
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Call a specific agent tool with a task.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_name: Name of the agent tool to call
|
||||||
|
task: Task or prompt to send to the agent
|
||||||
|
server_path: URL of the MCP server
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response from the agent tool
|
||||||
|
"""
|
||||||
|
call = {
|
||||||
|
"function": {
|
||||||
|
"name": tool_name,
|
||||||
|
"arguments": {"task": task},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
result = asyncio.run(
|
||||||
|
execute_tool_call_simple(
|
||||||
|
response=call, server_path=server_path
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
Main function to demonstrate MCP tool usage.
|
||||||
|
"""
|
||||||
|
server_path = "http://localhost:8000/mcp"
|
||||||
|
|
||||||
|
# Step 1: Get available tools
|
||||||
|
tools = get_available_tools(server_path)
|
||||||
|
|
||||||
|
if not tools:
|
||||||
|
return {
|
||||||
|
"error": "No tools available. Make sure the MCP server is running."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Step 2: Find an agent tool to call
|
||||||
|
agent_tools = [
|
||||||
|
tool
|
||||||
|
for tool in tools
|
||||||
|
if "agent" in tool.get("function", {}).get("name", "").lower()
|
||||||
|
]
|
||||||
|
|
||||||
|
if not agent_tools:
|
||||||
|
return {
|
||||||
|
"error": "No agent tools found",
|
||||||
|
"available_tools": [
|
||||||
|
tool.get("function", {}).get("name", "Unknown")
|
||||||
|
for tool in tools
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Step 3: Call the first available agent tool
|
||||||
|
agent_tool = agent_tools[0]
|
||||||
|
tool_name = agent_tool.get("function", {}).get("name")
|
||||||
|
|
||||||
|
# Example task
|
||||||
|
task = "Hello! Can you help me understand what you do and what capabilities you have?"
|
||||||
|
|
||||||
|
# Call the agent
|
||||||
|
result = call_agent_tool(tool_name, task, server_path)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
result = main()
|
||||||
|
print(json.dumps(result, indent=4))
|
@ -0,0 +1,118 @@
|
|||||||
|
from swarms import Agent
|
||||||
|
from swarms.structs.aop import (
|
||||||
|
AOP,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create specialized agents
|
||||||
|
research_agent = Agent(
|
||||||
|
agent_name="Research-Agent",
|
||||||
|
agent_description="Expert in research, data collection, and information gathering",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
system_prompt="""You are a research specialist. Your role is to:
|
||||||
|
1. Gather comprehensive information on any given topic
|
||||||
|
2. Analyze data from multiple sources
|
||||||
|
3. Provide well-structured research findings
|
||||||
|
4. Cite sources and maintain accuracy
|
||||||
|
5. Present findings in a clear, organized manner
|
||||||
|
|
||||||
|
Always provide detailed, factual information with proper context.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
analysis_agent = Agent(
|
||||||
|
agent_name="Analysis-Agent",
|
||||||
|
agent_description="Expert in data analysis, pattern recognition, and generating insights",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
system_prompt="""You are an analysis specialist. Your role is to:
|
||||||
|
1. Analyze data and identify patterns
|
||||||
|
2. Generate actionable insights
|
||||||
|
3. Create visualizations and summaries
|
||||||
|
4. Provide statistical analysis
|
||||||
|
5. Make data-driven recommendations
|
||||||
|
|
||||||
|
Focus on extracting meaningful insights from information.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
writing_agent = Agent(
|
||||||
|
agent_name="Writing-Agent",
|
||||||
|
agent_description="Expert in content creation, editing, and communication",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
system_prompt="""You are a writing specialist. Your role is to:
|
||||||
|
1. Create engaging, well-structured content
|
||||||
|
2. Edit and improve existing text
|
||||||
|
3. Adapt tone and style for different audiences
|
||||||
|
4. Ensure clarity and coherence
|
||||||
|
5. Follow best practices in writing
|
||||||
|
|
||||||
|
Always produce high-quality, professional content.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
code_agent = Agent(
|
||||||
|
agent_name="Code-Agent",
|
||||||
|
agent_description="Expert in programming, code review, and software development",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
system_prompt="""You are a coding specialist. Your role is to:
|
||||||
|
1. Write clean, efficient code
|
||||||
|
2. Debug and fix issues
|
||||||
|
3. Review and optimize code
|
||||||
|
4. Explain programming concepts
|
||||||
|
5. Follow best practices and standards
|
||||||
|
|
||||||
|
Always provide working, well-documented code.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
financial_agent = Agent(
|
||||||
|
agent_name="Financial-Agent",
|
||||||
|
agent_description="Expert in financial analysis, market research, and investment insights",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
max_loops=1,
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
system_prompt="""You are a financial specialist. Your role is to:
|
||||||
|
1. Analyze financial data and markets
|
||||||
|
2. Provide investment insights
|
||||||
|
3. Assess risk and opportunities
|
||||||
|
4. Create financial reports
|
||||||
|
5. Explain complex financial concepts
|
||||||
|
|
||||||
|
Always provide accurate, well-reasoned financial analysis.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Basic usage - individual agent addition
|
||||||
|
deployer = AOP("MyAgentServer", verbose=True)
|
||||||
|
|
||||||
|
agents = [
|
||||||
|
research_agent,
|
||||||
|
analysis_agent,
|
||||||
|
writing_agent,
|
||||||
|
code_agent,
|
||||||
|
financial_agent,
|
||||||
|
]
|
||||||
|
|
||||||
|
deployer.add_agents_batch(agents)
|
||||||
|
|
||||||
|
# Example usage with different parameters
|
||||||
|
# The tools now accept: task, img, imgs, correct_answer parameters
|
||||||
|
# task: str (required) - The main task or prompt
|
||||||
|
# img: str (optional) - Single image to process
|
||||||
|
# imgs: List[str] (optional) - Multiple images to process
|
||||||
|
# correct_answer: str (optional) - Correct answer for validation
|
||||||
|
|
||||||
|
# Example calls that would be made to the MCP tools:
|
||||||
|
# research_tool(task="Research the latest AI trends")
|
||||||
|
# analysis_tool(task="Analyze this data", img="path/to/image.jpg")
|
||||||
|
# writing_tool(task="Write a blog post", imgs=["img1.jpg", "img2.jpg"])
|
||||||
|
# code_tool(task="Debug this code", correct_answer="expected_output")
|
||||||
|
|
||||||
|
deployer.run()
|
@ -0,0 +1,28 @@
|
|||||||
|
from swarms import Agent, SequentialWorkflow
|
||||||
|
|
||||||
|
# Agent 1: The Researcher
|
||||||
|
researcher = Agent(
|
||||||
|
agent_name="Researcher",
|
||||||
|
system_prompt="Your job is to research the provided topic and provide a detailed summary.",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Agent 2: The Writer
|
||||||
|
writer = Agent(
|
||||||
|
agent_name="Writer",
|
||||||
|
system_prompt="Your job is to take the research summary and write a beautiful, engaging blog post about it.",
|
||||||
|
model_name="anthropic/claude-sonnet-4-5",
|
||||||
|
top_p=None,
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a sequential workflow where the researcher's output feeds into the writer's input
|
||||||
|
workflow = SequentialWorkflow(agents=[researcher, writer])
|
||||||
|
|
||||||
|
# Run the workflow on a task
|
||||||
|
final_post = workflow.run(
|
||||||
|
"Create a comprehensive and detailed report on Gold ETFs"
|
||||||
|
)
|
||||||
|
print(final_post)
|
@ -0,0 +1,708 @@
|
|||||||
|
import asyncio
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, Dict, List, Literal, Optional
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
|
from swarms.structs.agent import Agent
|
||||||
|
from swarms.structs.omni_agent_types import AgentType
|
||||||
|
from swarms.tools.mcp_client_tools import (
|
||||||
|
get_tools_for_multiple_mcp_servers,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AgentToolConfig:
|
||||||
|
"""
|
||||||
|
Configuration for converting an agent to an MCP tool.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
tool_name: The name of the tool in the MCP server
|
||||||
|
tool_description: Description of what the tool does
|
||||||
|
input_schema: JSON schema for the tool's input parameters
|
||||||
|
output_schema: JSON schema for the tool's output
|
||||||
|
timeout: Maximum time to wait for agent execution (seconds)
|
||||||
|
max_retries: Number of retries if agent execution fails
|
||||||
|
verbose: Enable verbose logging for this tool
|
||||||
|
traceback_enabled: Enable traceback logging for errors
|
||||||
|
"""
|
||||||
|
|
||||||
|
tool_name: str
|
||||||
|
tool_description: str
|
||||||
|
input_schema: Dict[str, Any]
|
||||||
|
output_schema: Dict[str, Any]
|
||||||
|
timeout: int = 30
|
||||||
|
max_retries: int = 3
|
||||||
|
verbose: bool = False
|
||||||
|
traceback_enabled: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
class AOP:
|
||||||
|
"""
|
||||||
|
A class that takes a list of agents and deploys them as unique tools in an MCP server.
|
||||||
|
|
||||||
|
This class provides functionality to:
|
||||||
|
1. Convert swarms agents into MCP tools
|
||||||
|
2. Deploy multiple agents as individual tools
|
||||||
|
3. Handle tool execution with proper error handling
|
||||||
|
4. Manage the MCP server lifecycle
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
mcp_server: The FastMCP server instance
|
||||||
|
agents: Dictionary mapping tool names to agent instances
|
||||||
|
tool_configs: Dictionary mapping tool names to their configurations
|
||||||
|
server_name: Name of the MCP server
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
server_name: str = "AOP Cluster",
|
||||||
|
description: str = "A cluster that enables you to deploy multiple agents as tools in an MCP server.",
|
||||||
|
agents: any = None,
|
||||||
|
port: int = 8000,
|
||||||
|
transport: str = "streamable-http",
|
||||||
|
verbose: bool = False,
|
||||||
|
traceback_enabled: bool = True,
|
||||||
|
host: str = "localhost",
|
||||||
|
log_level: str = "INFO",
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize the AOP.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_name: Name for the MCP server
|
||||||
|
agents: Optional list of agents to add initially
|
||||||
|
port: Port for the MCP server
|
||||||
|
transport: Transport type for the MCP server
|
||||||
|
verbose: Enable verbose logging
|
||||||
|
traceback_enabled: Enable traceback logging for errors
|
||||||
|
log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||||
|
"""
|
||||||
|
self.server_name = server_name
|
||||||
|
self.verbose = verbose
|
||||||
|
self.traceback_enabled = traceback_enabled
|
||||||
|
self.log_level = log_level
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.agents: Dict[str, Agent] = {}
|
||||||
|
self.tool_configs: Dict[str, AgentToolConfig] = {}
|
||||||
|
self.transport = transport
|
||||||
|
self.mcp_server = FastMCP(
|
||||||
|
name=server_name, port=port, *args, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure logger
|
||||||
|
logger.remove() # Remove default handler
|
||||||
|
logger.add(
|
||||||
|
sys.stderr,
|
||||||
|
level=log_level,
|
||||||
|
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
|
||||||
|
colorize=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Initialized AOP with server name: {server_name}, verbose: {verbose}, traceback: {traceback_enabled}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add initial agents if provided
|
||||||
|
if agents:
|
||||||
|
logger.info(f"Adding {len(agents)} initial agents")
|
||||||
|
self.add_agents_batch(agents)
|
||||||
|
|
||||||
|
def add_agent(
|
||||||
|
self,
|
||||||
|
agent: AgentType,
|
||||||
|
tool_name: Optional[str] = None,
|
||||||
|
tool_description: Optional[str] = None,
|
||||||
|
input_schema: Optional[Dict[str, Any]] = None,
|
||||||
|
output_schema: Optional[Dict[str, Any]] = None,
|
||||||
|
timeout: int = 30,
|
||||||
|
max_retries: int = 3,
|
||||||
|
verbose: Optional[bool] = None,
|
||||||
|
traceback_enabled: Optional[bool] = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Add an agent to the MCP server as a tool.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
agent: The swarms Agent instance to deploy
|
||||||
|
tool_name: Name for the tool (defaults to agent.agent_name)
|
||||||
|
tool_description: Description of the tool (defaults to agent.agent_description)
|
||||||
|
input_schema: JSON schema for input parameters
|
||||||
|
output_schema: JSON schema for output
|
||||||
|
timeout: Maximum execution time in seconds
|
||||||
|
max_retries: Number of retries on failure
|
||||||
|
verbose: Enable verbose logging for this tool (defaults to deployer's verbose setting)
|
||||||
|
traceback_enabled: Enable traceback logging for this tool (defaults to deployer's setting)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The tool name that was registered
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If agent is None or tool_name already exists
|
||||||
|
"""
|
||||||
|
if agent is None:
|
||||||
|
logger.error("Cannot add None agent")
|
||||||
|
raise ValueError("Agent cannot be None")
|
||||||
|
|
||||||
|
# Use agent name as tool name if not provided
|
||||||
|
if tool_name is None:
|
||||||
|
tool_name = (
|
||||||
|
agent.agent_name or f"agent_{len(self.agents)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if tool_name in self.agents:
|
||||||
|
logger.error(f"Tool name '{tool_name}' already exists")
|
||||||
|
raise ValueError(
|
||||||
|
f"Tool name '{tool_name}' already exists"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Use deployer defaults if not specified
|
||||||
|
if verbose is None:
|
||||||
|
verbose = self.verbose
|
||||||
|
if traceback_enabled is None:
|
||||||
|
traceback_enabled = self.traceback_enabled
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Adding agent '{agent.agent_name}' as tool '{tool_name}' with verbose={verbose}, traceback={traceback_enabled}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Use agent description if not provided
|
||||||
|
if tool_description is None:
|
||||||
|
tool_description = (
|
||||||
|
agent.agent_description
|
||||||
|
or f"Agent tool: {agent.agent_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Default input schema for task-based agents
|
||||||
|
if input_schema is None:
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"task": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The task or prompt to execute with this agent",
|
||||||
|
},
|
||||||
|
"img": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional image to be processed by the agent",
|
||||||
|
},
|
||||||
|
"imgs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
"description": "Optional list of images to be processed by the agent",
|
||||||
|
},
|
||||||
|
"correct_answer": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional correct answer for validation or comparison",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["task"],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Default output schema
|
||||||
|
if output_schema is None:
|
||||||
|
output_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"result": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The agent's response to the task",
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the task was executed successfully",
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Error message if execution failed",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["result", "success"],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Store agent and configuration
|
||||||
|
self.agents[tool_name] = agent
|
||||||
|
self.tool_configs[tool_name] = AgentToolConfig(
|
||||||
|
tool_name=tool_name,
|
||||||
|
tool_description=tool_description,
|
||||||
|
input_schema=input_schema,
|
||||||
|
output_schema=output_schema,
|
||||||
|
timeout=timeout,
|
||||||
|
max_retries=max_retries,
|
||||||
|
verbose=verbose,
|
||||||
|
traceback_enabled=traceback_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register the tool with the MCP server
|
||||||
|
self._register_tool(tool_name, agent)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Added agent '{agent.agent_name}' as tool '{tool_name}' (verbose={verbose}, traceback={traceback_enabled})"
|
||||||
|
)
|
||||||
|
return tool_name
|
||||||
|
|
||||||
|
def add_agents_batch(
|
||||||
|
self,
|
||||||
|
agents: List[Agent],
|
||||||
|
tool_names: Optional[List[str]] = None,
|
||||||
|
tool_descriptions: Optional[List[str]] = None,
|
||||||
|
input_schemas: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
output_schemas: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
timeouts: Optional[List[int]] = None,
|
||||||
|
max_retries_list: Optional[List[int]] = None,
|
||||||
|
verbose_list: Optional[List[bool]] = None,
|
||||||
|
traceback_enabled_list: Optional[List[bool]] = None,
|
||||||
|
) -> List[str]:
|
||||||
|
"""
|
||||||
|
Add multiple agents to the MCP server as tools in batch.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
agents: List of swarms Agent instances
|
||||||
|
tool_names: Optional list of tool names (defaults to agent names)
|
||||||
|
tool_descriptions: Optional list of tool descriptions
|
||||||
|
input_schemas: Optional list of input schemas
|
||||||
|
output_schemas: Optional list of output schemas
|
||||||
|
timeouts: Optional list of timeout values
|
||||||
|
max_retries_list: Optional list of max retry values
|
||||||
|
verbose_list: Optional list of verbose settings for each agent
|
||||||
|
traceback_enabled_list: Optional list of traceback settings for each agent
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[str]: List of tool names that were registered
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If agents list is empty or contains None values
|
||||||
|
"""
|
||||||
|
if not agents:
|
||||||
|
logger.error("Cannot add empty agents list")
|
||||||
|
raise ValueError("Agents list cannot be empty")
|
||||||
|
|
||||||
|
if None in agents:
|
||||||
|
logger.error("Agents list contains None values")
|
||||||
|
raise ValueError("Agents list cannot contain None values")
|
||||||
|
|
||||||
|
logger.info(f"Adding {len(agents)} agents in batch")
|
||||||
|
registered_tools = []
|
||||||
|
|
||||||
|
for i, agent in enumerate(agents):
|
||||||
|
tool_name = (
|
||||||
|
tool_names[i]
|
||||||
|
if tool_names and i < len(tool_names)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
tool_description = (
|
||||||
|
tool_descriptions[i]
|
||||||
|
if tool_descriptions and i < len(tool_descriptions)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
input_schema = (
|
||||||
|
input_schemas[i]
|
||||||
|
if input_schemas and i < len(input_schemas)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
output_schema = (
|
||||||
|
output_schemas[i]
|
||||||
|
if output_schemas and i < len(output_schemas)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
timeout = (
|
||||||
|
timeouts[i] if timeouts and i < len(timeouts) else 30
|
||||||
|
)
|
||||||
|
max_retries = (
|
||||||
|
max_retries_list[i]
|
||||||
|
if max_retries_list and i < len(max_retries_list)
|
||||||
|
else 3
|
||||||
|
)
|
||||||
|
verbose = (
|
||||||
|
verbose_list[i]
|
||||||
|
if verbose_list and i < len(verbose_list)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
traceback_enabled = (
|
||||||
|
traceback_enabled_list[i]
|
||||||
|
if traceback_enabled_list
|
||||||
|
and i < len(traceback_enabled_list)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
tool_name = self.add_agent(
|
||||||
|
agent=agent,
|
||||||
|
tool_name=tool_name,
|
||||||
|
tool_description=tool_description,
|
||||||
|
input_schema=input_schema,
|
||||||
|
output_schema=output_schema,
|
||||||
|
timeout=timeout,
|
||||||
|
max_retries=max_retries,
|
||||||
|
verbose=verbose,
|
||||||
|
traceback_enabled=traceback_enabled,
|
||||||
|
)
|
||||||
|
registered_tools.append(tool_name)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Added {len(agents)} agents as tools: {registered_tools}"
|
||||||
|
)
|
||||||
|
return registered_tools
|
||||||
|
|
||||||
|
def _register_tool(
|
||||||
|
self, tool_name: str, agent: AgentType
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Register a single agent as an MCP tool.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_name: Name of the tool to register
|
||||||
|
agent: The agent instance to register
|
||||||
|
"""
|
||||||
|
config = self.tool_configs[tool_name]
|
||||||
|
|
||||||
|
@self.mcp_server.tool(
|
||||||
|
name=tool_name, description=config.tool_description
|
||||||
|
)
|
||||||
|
def agent_tool(
|
||||||
|
task: str = None,
|
||||||
|
img: str = None,
|
||||||
|
imgs: List[str] = None,
|
||||||
|
correct_answer: str = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Execute the agent with the provided parameters.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task: The task or prompt to execute with this agent
|
||||||
|
img: Optional image to be processed by the agent
|
||||||
|
imgs: Optional list of images to be processed by the agent
|
||||||
|
correct_answer: Optional correct answer for validation or comparison
|
||||||
|
**kwargs: Additional parameters passed to the agent
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict containing the agent's response and execution status
|
||||||
|
"""
|
||||||
|
start_time = None
|
||||||
|
if config.verbose:
|
||||||
|
start_time = (
|
||||||
|
asyncio.get_event_loop().time()
|
||||||
|
if asyncio.get_event_loop().is_running()
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
f"Starting execution of tool '{tool_name}' with task: {task[:100] if task else 'None'}..."
|
||||||
|
)
|
||||||
|
if img:
|
||||||
|
logger.debug(f"Processing single image: {img}")
|
||||||
|
if imgs:
|
||||||
|
logger.debug(
|
||||||
|
f"Processing {len(imgs)} images: {imgs}"
|
||||||
|
)
|
||||||
|
if correct_answer:
|
||||||
|
logger.debug(
|
||||||
|
f"Using correct answer for validation: {correct_answer[:50]}..."
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Validate required parameters
|
||||||
|
if not task:
|
||||||
|
error_msg = "No task provided"
|
||||||
|
logger.warning(
|
||||||
|
f"Tool '{tool_name}' called without task parameter"
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"result": "",
|
||||||
|
"success": False,
|
||||||
|
"error": error_msg,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute the agent with timeout and all parameters
|
||||||
|
result = self._execute_agent_with_timeout(
|
||||||
|
agent,
|
||||||
|
task,
|
||||||
|
config.timeout,
|
||||||
|
img,
|
||||||
|
imgs,
|
||||||
|
correct_answer,
|
||||||
|
)
|
||||||
|
|
||||||
|
if config.verbose and start_time:
|
||||||
|
execution_time = (
|
||||||
|
asyncio.get_event_loop().time() - start_time
|
||||||
|
if asyncio.get_event_loop().is_running()
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
f"Tool '{tool_name}' completed successfully in {execution_time:.2f}s"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"result": str(result),
|
||||||
|
"success": True,
|
||||||
|
"error": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
logger.error(
|
||||||
|
f"Error executing agent '{tool_name}': {error_msg}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if config.traceback_enabled:
|
||||||
|
logger.error(f"Traceback for tool '{tool_name}':")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
if config.verbose and start_time:
|
||||||
|
execution_time = (
|
||||||
|
asyncio.get_event_loop().time() - start_time
|
||||||
|
if asyncio.get_event_loop().is_running()
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
f"Tool '{tool_name}' failed after {execution_time:.2f}s"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"result": "",
|
||||||
|
"success": False,
|
||||||
|
"error": error_msg,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _execute_agent_with_timeout(
|
||||||
|
self,
|
||||||
|
agent: AgentType,
|
||||||
|
task: str,
|
||||||
|
timeout: int,
|
||||||
|
img: str = None,
|
||||||
|
imgs: List[str] = None,
|
||||||
|
correct_answer: str = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Execute an agent with a timeout and all run method parameters.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
agent: The agent to execute
|
||||||
|
task: The task to execute
|
||||||
|
timeout: Maximum execution time in seconds
|
||||||
|
img: Optional image to be processed by the agent
|
||||||
|
imgs: Optional list of images to be processed by the agent
|
||||||
|
correct_answer: Optional correct answer for validation or comparison
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The agent's response
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TimeoutError: If execution exceeds timeout
|
||||||
|
Exception: If agent execution fails
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.debug(
|
||||||
|
f"Executing agent '{agent.agent_name}' with timeout {timeout}s"
|
||||||
|
)
|
||||||
|
|
||||||
|
out = agent.run(
|
||||||
|
task=task,
|
||||||
|
img=img,
|
||||||
|
imgs=imgs,
|
||||||
|
correct_answer=correct_answer,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Agent '{agent.agent_name}' execution completed successfully"
|
||||||
|
)
|
||||||
|
return out
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Agent execution failed: {str(e)}"
|
||||||
|
logger.error(
|
||||||
|
f"Execution error for agent '{agent.agent_name}': {error_msg}"
|
||||||
|
)
|
||||||
|
if self.traceback_enabled:
|
||||||
|
logger.error(
|
||||||
|
f"Traceback for agent '{agent.agent_name}':"
|
||||||
|
)
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
def remove_agent(self, tool_name: str) -> bool:
|
||||||
|
"""
|
||||||
|
Remove an agent from the MCP server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_name: Name of the tool to remove
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if agent was removed, False if not found
|
||||||
|
"""
|
||||||
|
if tool_name in self.agents:
|
||||||
|
del self.agents[tool_name]
|
||||||
|
del self.tool_configs[tool_name]
|
||||||
|
logger.info(f"Removed agent tool '{tool_name}'")
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def list_agents(self) -> List[str]:
|
||||||
|
"""
|
||||||
|
Get a list of all registered agent tool names.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[str]: List of tool names
|
||||||
|
"""
|
||||||
|
agent_list = list(self.agents.keys())
|
||||||
|
if self.verbose:
|
||||||
|
logger.debug(
|
||||||
|
f"Listing {len(agent_list)} registered agents: {agent_list}"
|
||||||
|
)
|
||||||
|
return agent_list
|
||||||
|
|
||||||
|
def get_agent_info(
|
||||||
|
self, tool_name: str
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Get information about a specific agent tool.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_name: Name of the tool
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict containing agent information, or None if not found
|
||||||
|
"""
|
||||||
|
if tool_name not in self.agents:
|
||||||
|
if self.verbose:
|
||||||
|
logger.debug(
|
||||||
|
f"Requested info for non-existent agent tool '{tool_name}'"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
agent = self.agents[tool_name]
|
||||||
|
config = self.tool_configs[tool_name]
|
||||||
|
|
||||||
|
info = {
|
||||||
|
"tool_name": tool_name,
|
||||||
|
"agent_name": agent.agent_name,
|
||||||
|
"agent_description": agent.agent_description,
|
||||||
|
"model_name": getattr(agent, "model_name", "Unknown"),
|
||||||
|
"max_loops": getattr(agent, "max_loops", 1),
|
||||||
|
"tool_description": config.tool_description,
|
||||||
|
"timeout": config.timeout,
|
||||||
|
"max_retries": config.max_retries,
|
||||||
|
"verbose": config.verbose,
|
||||||
|
"traceback_enabled": config.traceback_enabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
logger.debug(
|
||||||
|
f"Retrieved info for agent tool '{tool_name}': {info}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
def start_server(self) -> None:
|
||||||
|
"""
|
||||||
|
Start the MCP server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
host: Host to bind the server to
|
||||||
|
port: Port to bind the server to
|
||||||
|
"""
|
||||||
|
logger.info(
|
||||||
|
f"Starting MCP server '{self.server_name}' on {self.host}:{self.port}"
|
||||||
|
)
|
||||||
|
logger.info(f"Transport: {self.transport}")
|
||||||
|
logger.info(f"Log level: {self.log_level}")
|
||||||
|
logger.info(f"Verbose mode: {self.verbose}")
|
||||||
|
logger.info(f"Traceback enabled: {self.traceback_enabled}")
|
||||||
|
logger.info(f"Available tools: {self.list_agents()}")
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
logger.debug("Server configuration:")
|
||||||
|
logger.debug(f" - Server name: {self.server_name}")
|
||||||
|
logger.debug(f" - Host: {self.host}")
|
||||||
|
logger.debug(f" - Port: {self.port}")
|
||||||
|
logger.debug(f" - Transport: {self.transport}")
|
||||||
|
logger.debug(f" - Total agents: {len(self.agents)}")
|
||||||
|
for tool_name, config in self.tool_configs.items():
|
||||||
|
logger.debug(
|
||||||
|
f" - Tool '{tool_name}': timeout={config.timeout}s, verbose={config.verbose}, traceback={config.traceback_enabled}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mcp_server.run(transport=self.transport)
|
||||||
|
|
||||||
|
# Note: FastMCP doesn't have a direct start method in the current implementation
|
||||||
|
# This would need to be implemented based on the specific MCP server setup
|
||||||
|
print(
|
||||||
|
f"MCP Server '{self.server_name}' is ready with {len(self.agents)} tools"
|
||||||
|
)
|
||||||
|
print(f"Tools available: {', '.join(self.list_agents())}")
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
"""
|
||||||
|
Run the MCP server.
|
||||||
|
"""
|
||||||
|
self.start_server()
|
||||||
|
|
||||||
|
def get_server_info(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Get information about the MCP server and registered tools.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict containing server information
|
||||||
|
"""
|
||||||
|
info = {
|
||||||
|
"server_name": self.server_name,
|
||||||
|
"total_tools": len(self.agents),
|
||||||
|
"tools": self.list_agents(),
|
||||||
|
"verbose": self.verbose,
|
||||||
|
"traceback_enabled": self.traceback_enabled,
|
||||||
|
"log_level": self.log_level,
|
||||||
|
"transport": self.transport,
|
||||||
|
"tool_details": {
|
||||||
|
tool_name: self.get_agent_info(tool_name)
|
||||||
|
for tool_name in self.agents.keys()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
logger.debug(f"Retrieved server info: {info}")
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
class AOPCluster:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
urls: List[str],
|
||||||
|
transport: str = "streamable-http",
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
self.urls = urls
|
||||||
|
self.transport = transport
|
||||||
|
|
||||||
|
def get_tools(
|
||||||
|
self, output_type: Literal["json", "dict", "str"] = "dict"
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
return get_tools_for_multiple_mcp_servers(
|
||||||
|
urls=self.urls,
|
||||||
|
format="openai",
|
||||||
|
output_type=output_type,
|
||||||
|
transport=self.transport,
|
||||||
|
)
|
||||||
|
|
||||||
|
def find_tool_by_server_name(
|
||||||
|
self, server_name: str
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Find a tool by its server name (function name).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_name: The name of the tool/function to find
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict containing the tool information, or None if not found
|
||||||
|
"""
|
||||||
|
for tool in self.get_tools(output_type="dict"):
|
||||||
|
if tool.get("function", {}).get("name") == server_name:
|
||||||
|
return tool
|
||||||
|
return None
|
Loading…
Reference in new issue