parent
e62f2e9947
commit
8aa35976f9
@ -0,0 +1,284 @@
|
|||||||
|
import asyncio
|
||||||
|
import concurrent.futures
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import psutil
|
||||||
|
import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
from swarms.structs.agent import Agent
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
|
class AgentBenchmark:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
num_iterations: int = 5,
|
||||||
|
output_dir: str = "benchmark_results",
|
||||||
|
):
|
||||||
|
self.num_iterations = num_iterations
|
||||||
|
self.output_dir = Path(output_dir)
|
||||||
|
self.output_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Use process pool for CPU-bound tasks
|
||||||
|
self.process_pool = concurrent.futures.ProcessPoolExecutor(
|
||||||
|
max_workers=min(os.cpu_count(), 4)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Use thread pool for I/O-bound tasks
|
||||||
|
self.thread_pool = concurrent.futures.ThreadPoolExecutor(
|
||||||
|
max_workers=min(os.cpu_count() * 2, 8)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.default_queries = [
|
||||||
|
"Conduct an analysis of the best real undervalued ETFs",
|
||||||
|
"What are the top performing tech stocks this quarter?",
|
||||||
|
"Analyze current market trends in renewable energy sector",
|
||||||
|
"Compare Bitcoin and Ethereum investment potential",
|
||||||
|
"Evaluate the risk factors in emerging markets",
|
||||||
|
]
|
||||||
|
|
||||||
|
self.agent = self._initialize_agent()
|
||||||
|
self.process = psutil.Process()
|
||||||
|
|
||||||
|
# Cache for storing repeated query results
|
||||||
|
self._query_cache = {}
|
||||||
|
|
||||||
|
def _initialize_agent(self) -> Agent:
|
||||||
|
return Agent(
|
||||||
|
agent_name="Financial-Analysis-Agent",
|
||||||
|
agent_description="Personal finance advisor agent",
|
||||||
|
# system_prompt=FINANCIAL_AGENT_SYS_PROMPT,
|
||||||
|
max_loops=1,
|
||||||
|
model_name="gpt-4o-mini",
|
||||||
|
dynamic_temperature_enabled=True,
|
||||||
|
interactive=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_system_metrics(self) -> Dict[str, float]:
|
||||||
|
# Optimized system metrics collection
|
||||||
|
return {
|
||||||
|
"cpu_percent": self.process.cpu_percent(),
|
||||||
|
"memory_mb": self.process.memory_info().rss / 1024 / 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _calculate_statistics(
|
||||||
|
self, values: List[float]
|
||||||
|
) -> Dict[str, float]:
|
||||||
|
if not values:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
sorted_values = sorted(values)
|
||||||
|
n = len(sorted_values)
|
||||||
|
mean_val = sum(values) / n
|
||||||
|
|
||||||
|
stats = {
|
||||||
|
"mean": mean_val,
|
||||||
|
"median": sorted_values[n // 2],
|
||||||
|
"min": sorted_values[0],
|
||||||
|
"max": sorted_values[-1],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only calculate stdev if we have enough values
|
||||||
|
if n > 1:
|
||||||
|
stats["std_dev"] = (
|
||||||
|
sum((x - mean_val) ** 2 for x in values) / n
|
||||||
|
) ** 0.5
|
||||||
|
|
||||||
|
return {k: round(v, 3) for k, v in stats.items()}
|
||||||
|
|
||||||
|
async def process_iteration(
|
||||||
|
self, query: str, iteration: int
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""Process a single iteration of a query"""
|
||||||
|
try:
|
||||||
|
# Check cache for repeated queries
|
||||||
|
cache_key = f"{query}_{iteration}"
|
||||||
|
if cache_key in self._query_cache:
|
||||||
|
return self._query_cache[cache_key]
|
||||||
|
|
||||||
|
iteration_start = datetime.datetime.now()
|
||||||
|
pre_metrics = self._get_system_metrics()
|
||||||
|
|
||||||
|
# Run the agent
|
||||||
|
try:
|
||||||
|
self.agent.run(query)
|
||||||
|
success = True
|
||||||
|
except Exception as e:
|
||||||
|
str(e)
|
||||||
|
success = False
|
||||||
|
|
||||||
|
execution_time = (
|
||||||
|
datetime.datetime.now() - iteration_start
|
||||||
|
).total_seconds()
|
||||||
|
post_metrics = self._get_system_metrics()
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"execution_time": execution_time,
|
||||||
|
"success": success,
|
||||||
|
"pre_metrics": pre_metrics,
|
||||||
|
"post_metrics": post_metrics,
|
||||||
|
"iteration_data": {
|
||||||
|
"iteration": iteration + 1,
|
||||||
|
"execution_time": round(execution_time, 3),
|
||||||
|
"success": success,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": pre_metrics,
|
||||||
|
"post": post_metrics,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache the result
|
||||||
|
self._query_cache[cache_key] = result
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in iteration {iteration}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def run_benchmark(
|
||||||
|
self, queries: Optional[List[str]] = None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""Run the benchmark asynchronously"""
|
||||||
|
queries = queries or self.default_queries
|
||||||
|
benchmark_data = {
|
||||||
|
"metadata": {
|
||||||
|
"timestamp": datetime.datetime.now().isoformat(),
|
||||||
|
"num_iterations": self.num_iterations,
|
||||||
|
"agent_config": {
|
||||||
|
"model_name": self.agent.model_name,
|
||||||
|
"max_loops": self.agent.max_loops,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"results": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
async def process_query(query: str):
|
||||||
|
query_results = {
|
||||||
|
"execution_times": [],
|
||||||
|
"system_metrics": [],
|
||||||
|
"iterations": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process iterations concurrently
|
||||||
|
tasks = [
|
||||||
|
self.process_iteration(query, i)
|
||||||
|
for i in range(self.num_iterations)
|
||||||
|
]
|
||||||
|
iteration_results = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
for result in iteration_results:
|
||||||
|
query_results["execution_times"].append(
|
||||||
|
result["execution_time"]
|
||||||
|
)
|
||||||
|
query_results["system_metrics"].append(
|
||||||
|
result["post_metrics"]
|
||||||
|
)
|
||||||
|
query_results["iterations"].append(
|
||||||
|
result["iteration_data"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calculate statistics
|
||||||
|
query_results["statistics"] = {
|
||||||
|
"execution_time": self._calculate_statistics(
|
||||||
|
query_results["execution_times"]
|
||||||
|
),
|
||||||
|
"memory_usage": self._calculate_statistics(
|
||||||
|
[
|
||||||
|
m["memory_mb"]
|
||||||
|
for m in query_results["system_metrics"]
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"cpu_usage": self._calculate_statistics(
|
||||||
|
[
|
||||||
|
m["cpu_percent"]
|
||||||
|
for m in query_results["system_metrics"]
|
||||||
|
]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
return query, query_results
|
||||||
|
|
||||||
|
# Execute all queries concurrently
|
||||||
|
query_tasks = [process_query(query) for query in queries]
|
||||||
|
query_results = await asyncio.gather(*query_tasks)
|
||||||
|
|
||||||
|
for query, results in query_results:
|
||||||
|
benchmark_data["results"][query] = results
|
||||||
|
|
||||||
|
return benchmark_data
|
||||||
|
|
||||||
|
def save_results(self, benchmark_data: Dict[str, Any]) -> str:
|
||||||
|
"""Save benchmark results efficiently"""
|
||||||
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
filename = (
|
||||||
|
self.output_dir / f"benchmark_results_{timestamp}.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write results in a single operation
|
||||||
|
with open(filename, "w") as f:
|
||||||
|
json.dump(benchmark_data, f, indent=2)
|
||||||
|
|
||||||
|
logger.info(f"Benchmark results saved to: {filename}")
|
||||||
|
return str(filename)
|
||||||
|
|
||||||
|
def print_summary(self, results: Dict[str, Any]):
|
||||||
|
"""Print a summary of the benchmark results"""
|
||||||
|
print("\n=== Benchmark Summary ===")
|
||||||
|
for query, data in results["results"].items():
|
||||||
|
print(f"\nQuery: {query[:50]}...")
|
||||||
|
stats = data["statistics"]["execution_time"]
|
||||||
|
print(f"Average time: {stats['mean']:.2f}s")
|
||||||
|
print(
|
||||||
|
f"Memory usage (avg): {data['statistics']['memory_usage']['mean']:.1f}MB"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"CPU usage (avg): {data['statistics']['cpu_usage']['mean']:.1f}%"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def run_with_timeout(
|
||||||
|
self, timeout: int = 300
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""Run benchmark with timeout"""
|
||||||
|
try:
|
||||||
|
return await asyncio.wait_for(
|
||||||
|
self.run_benchmark(), timeout
|
||||||
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
logger.error(
|
||||||
|
f"Benchmark timed out after {timeout} seconds"
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""Cleanup resources"""
|
||||||
|
self.process_pool.shutdown()
|
||||||
|
self.thread_pool.shutdown()
|
||||||
|
self._query_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
try:
|
||||||
|
# Create and run benchmark
|
||||||
|
benchmark = AgentBenchmark(num_iterations=1)
|
||||||
|
|
||||||
|
# Run benchmark with timeout
|
||||||
|
results = await benchmark.run_with_timeout(timeout=300)
|
||||||
|
|
||||||
|
# Save results
|
||||||
|
benchmark.save_results(results)
|
||||||
|
|
||||||
|
# Print summary
|
||||||
|
benchmark.print_summary(results)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Benchmark failed: {e}")
|
||||||
|
finally:
|
||||||
|
# Cleanup resources
|
||||||
|
benchmark.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Run the async main function
|
||||||
|
asyncio.run(main())
|
@ -0,0 +1,56 @@
|
|||||||
|
from swarms.structs.aop import AOP
|
||||||
|
|
||||||
|
aop = AOP(
|
||||||
|
name="example_system",
|
||||||
|
description="A simple example of tools, agents, and swarms",
|
||||||
|
url="http://localhost:8000/sse",
|
||||||
|
)
|
||||||
|
|
||||||
|
# print(
|
||||||
|
# aop.call_tool_or_agent(
|
||||||
|
# url="http://localhost:8000/sse",
|
||||||
|
# name="calculator",
|
||||||
|
# arguments={"operation": "add", "x": 1, "y": 2},
|
||||||
|
# output_type="list",
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
# print(
|
||||||
|
# aop.call_tool_or_agent_batched(
|
||||||
|
# url="http://localhost:8000/sse",
|
||||||
|
# names=["calculator", "calculator"],
|
||||||
|
# arguments=[{"operation": "add", "x": 1, "y": 2}, {"operation": "multiply", "x": 3, "y": 4}],
|
||||||
|
# output_type="list",
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
# print(
|
||||||
|
# aop.call_tool_or_agent_concurrently(
|
||||||
|
# url="http://localhost:8000/sse",
|
||||||
|
# names=["calculator", "calculator"],
|
||||||
|
# arguments=[{"operation": "add", "x": 1, "y": 2}, {"operation": "multiply", "x": 3, "y": 4}],
|
||||||
|
# output_type="list",
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
# print(aop.list_agents())
|
||||||
|
|
||||||
|
# print(aop.list_tools())
|
||||||
|
|
||||||
|
# print(aop.list_swarms())
|
||||||
|
|
||||||
|
# print(aop.list_all(url="http://localhost:8000/sse"))
|
||||||
|
|
||||||
|
# print(any_to_str(aop.list_all()))
|
||||||
|
|
||||||
|
# print(aop.search_if_tool_exists(name="calculator"))
|
||||||
|
|
||||||
|
# out = aop.list_tool_parameters(name="calculator")
|
||||||
|
# print(type(out))
|
||||||
|
# print(out)
|
||||||
|
|
||||||
|
print(aop.list_agents())
|
||||||
|
print(aop.list_swarms())
|
@ -0,0 +1,66 @@
|
|||||||
|
from swarms.structs.aop import AOP
|
||||||
|
|
||||||
|
# Initialize the AOP instance
|
||||||
|
aop = AOP(
|
||||||
|
name="example_system",
|
||||||
|
description="A simple example of tools, agents, and swarms",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Define a simple tool
|
||||||
|
@aop.tool(name="calculator", description="A simple calculator tool")
|
||||||
|
async def calculator(operation: str, x: float, y: float):
|
||||||
|
"""
|
||||||
|
Performs basic arithmetic operations
|
||||||
|
"""
|
||||||
|
if operation == "add":
|
||||||
|
return x + y
|
||||||
|
elif operation == "multiply":
|
||||||
|
return x * y
|
||||||
|
else:
|
||||||
|
raise ValueError("Unsupported operation")
|
||||||
|
|
||||||
|
|
||||||
|
# Define an agent that uses the calculator tool
|
||||||
|
@aop.agent(
|
||||||
|
name="math_agent",
|
||||||
|
description="Agent that performs mathematical operations",
|
||||||
|
)
|
||||||
|
async def math_agent(operation: str, numbers: list[float]):
|
||||||
|
"""
|
||||||
|
Agent that chains multiple calculations together
|
||||||
|
"""
|
||||||
|
result = numbers[0]
|
||||||
|
for num in numbers[1:]:
|
||||||
|
# Using the calculator tool within the agent
|
||||||
|
result = await aop.call_tool_or_agent(
|
||||||
|
"calculator",
|
||||||
|
{"operation": operation, "x": result, "y": num},
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Define a swarm that coordinates multiple agents
|
||||||
|
@aop.swarm(
|
||||||
|
name="math_swarm",
|
||||||
|
description="Swarm that coordinates mathematical operations",
|
||||||
|
)
|
||||||
|
async def math_swarm(numbers: list[float]):
|
||||||
|
"""
|
||||||
|
Swarm that performs multiple operations on a set of numbers
|
||||||
|
"""
|
||||||
|
# Perform addition and multiplication in parallel
|
||||||
|
results = await aop.call_tool_or_agent_concurrently(
|
||||||
|
names=["math_agent", "math_agent"],
|
||||||
|
arguments=[
|
||||||
|
{"operation": "add", "numbers": numbers},
|
||||||
|
{"operation": "multiply", "numbers": numbers},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"sum": results[0], "product": results[1]}
|
||||||
|
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
if __name__ == "__main__":
|
||||||
|
aop.run_sse()
|
@ -0,0 +1,512 @@
|
|||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"timestamp": "2025-04-18T13:25:17.781535",
|
||||||
|
"num_iterations": 3,
|
||||||
|
"agent_config": {
|
||||||
|
"model_name": "gpt-4o-mini",
|
||||||
|
"max_loops": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"Conduct an analysis of the best real undervalued ETFs": {
|
||||||
|
"execution_times": [
|
||||||
|
3.394164800643921,
|
||||||
|
0.2887423038482666,
|
||||||
|
0.15843510627746582
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 27.1,
|
||||||
|
"memory_percent": 84.4,
|
||||||
|
"process_memory_mb": 175.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 30.4,
|
||||||
|
"memory_percent": 84.8,
|
||||||
|
"process_memory_mb": 175.984375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 24.9,
|
||||||
|
"memory_percent": 84.8,
|
||||||
|
"process_memory_mb": 176.125
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 3.394,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 35.3,
|
||||||
|
"memory_percent": 84.2,
|
||||||
|
"process_memory_mb": 185.453125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 27.1,
|
||||||
|
"memory_percent": 84.4,
|
||||||
|
"process_memory_mb": 175.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 0.289,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 84.4,
|
||||||
|
"process_memory_mb": 175.53125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 30.4,
|
||||||
|
"memory_percent": 84.8,
|
||||||
|
"process_memory_mb": 175.984375
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 0.158,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 84.8,
|
||||||
|
"process_memory_mb": 175.984375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 24.9,
|
||||||
|
"memory_percent": 84.8,
|
||||||
|
"process_memory_mb": 176.125
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 1.28,
|
||||||
|
"median": 0.289,
|
||||||
|
"min": 0.158,
|
||||||
|
"max": 3.394,
|
||||||
|
"std_dev": 1.832
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 175.87,
|
||||||
|
"median": 175.984,
|
||||||
|
"min": 175.5,
|
||||||
|
"max": 176.125,
|
||||||
|
"std_dev": 0.328
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 27.467,
|
||||||
|
"median": 27.1,
|
||||||
|
"min": 24.9,
|
||||||
|
"max": 30.4,
|
||||||
|
"std_dev": 2.768
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"What are the top performing tech stocks this quarter?": {
|
||||||
|
"execution_times": [
|
||||||
|
0.6481029987335205,
|
||||||
|
0.22909188270568848,
|
||||||
|
0.24907231330871582
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 21.2,
|
||||||
|
"memory_percent": 84.7,
|
||||||
|
"process_memory_mb": 176.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.8,
|
||||||
|
"process_memory_mb": 176.40625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.9,
|
||||||
|
"process_memory_mb": 176.765625
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 0.648,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 50.0,
|
||||||
|
"memory_percent": 84.8,
|
||||||
|
"process_memory_mb": 176.125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 21.2,
|
||||||
|
"memory_percent": 84.7,
|
||||||
|
"process_memory_mb": 176.25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 0.229,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 84.7,
|
||||||
|
"process_memory_mb": 176.25
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.8,
|
||||||
|
"process_memory_mb": 176.40625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 0.249,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.8,
|
||||||
|
"process_memory_mb": 176.40625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.9,
|
||||||
|
"process_memory_mb": 176.765625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 0.375,
|
||||||
|
"median": 0.249,
|
||||||
|
"min": 0.229,
|
||||||
|
"max": 0.648,
|
||||||
|
"std_dev": 0.236
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 176.474,
|
||||||
|
"median": 176.406,
|
||||||
|
"min": 176.25,
|
||||||
|
"max": 176.766,
|
||||||
|
"std_dev": 0.264
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 7.067,
|
||||||
|
"median": 0.0,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 21.2,
|
||||||
|
"std_dev": 12.24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Analyze current market trends in renewable energy sector": {
|
||||||
|
"execution_times": [
|
||||||
|
2.0344760417938232,
|
||||||
|
0.48967909812927246,
|
||||||
|
0.08599114418029785
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 22.9,
|
||||||
|
"memory_percent": 83.7,
|
||||||
|
"process_memory_mb": 168.40625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 21.9,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 168.3125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 166.328125
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 2.034,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.9,
|
||||||
|
"process_memory_mb": 176.78125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 22.9,
|
||||||
|
"memory_percent": 83.7,
|
||||||
|
"process_memory_mb": 168.40625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 0.49,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 33.3,
|
||||||
|
"memory_percent": 83.7,
|
||||||
|
"process_memory_mb": 168.421875
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 21.9,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 168.3125
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 0.086,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 168.3125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 166.328125
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 0.87,
|
||||||
|
"median": 0.49,
|
||||||
|
"min": 0.086,
|
||||||
|
"max": 2.034,
|
||||||
|
"std_dev": 1.028
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 167.682,
|
||||||
|
"median": 168.312,
|
||||||
|
"min": 166.328,
|
||||||
|
"max": 168.406,
|
||||||
|
"std_dev": 1.174
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 14.933,
|
||||||
|
"median": 21.9,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 22.9,
|
||||||
|
"std_dev": 12.942
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Compare Bitcoin and Ethereum investment potential": {
|
||||||
|
"execution_times": [
|
||||||
|
0.08068418502807617,
|
||||||
|
0.08303999900817871,
|
||||||
|
0.08367633819580078
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 159.078125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 159.21875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 143.015625
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 0.081,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 166.4375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 159.078125
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 0.083,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 159.078125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 159.21875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 0.084,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 159.21875
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 143.015625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 0.082,
|
||||||
|
"median": 0.083,
|
||||||
|
"min": 0.081,
|
||||||
|
"max": 0.084,
|
||||||
|
"std_dev": 0.002
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 153.771,
|
||||||
|
"median": 159.078,
|
||||||
|
"min": 143.016,
|
||||||
|
"max": 159.219,
|
||||||
|
"std_dev": 9.315
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 0.0,
|
||||||
|
"median": 0.0,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 0.0,
|
||||||
|
"std_dev": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Evaluate the risk factors in emerging markets": {
|
||||||
|
"execution_times": [
|
||||||
|
0.08391690254211426,
|
||||||
|
0.08319473266601562,
|
||||||
|
0.08199191093444824
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 143.28125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 130.984375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 24.1,
|
||||||
|
"memory_percent": 83.5,
|
||||||
|
"process_memory_mb": 77.046875
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 0.084,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 143.015625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 143.28125
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 0.083,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 143.28125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 130.984375
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 0.082,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 83.6,
|
||||||
|
"process_memory_mb": 130.984375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 24.1,
|
||||||
|
"memory_percent": 83.5,
|
||||||
|
"process_memory_mb": 77.046875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 0.083,
|
||||||
|
"median": 0.083,
|
||||||
|
"min": 0.082,
|
||||||
|
"max": 0.084,
|
||||||
|
"std_dev": 0.001
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 117.104,
|
||||||
|
"median": 130.984,
|
||||||
|
"min": 77.047,
|
||||||
|
"max": 143.281,
|
||||||
|
"std_dev": 35.231
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 8.033,
|
||||||
|
"median": 0.0,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 24.1,
|
||||||
|
"std_dev": 13.914
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,267 @@
|
|||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"timestamp": "2025-04-18T13:25:33.415938",
|
||||||
|
"num_iterations": 1,
|
||||||
|
"agent_config": {
|
||||||
|
"model_name": "gpt-4o-mini",
|
||||||
|
"max_loops": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"Conduct an analysis of the best real undervalued ETFs": {
|
||||||
|
"execution_times": [
|
||||||
|
14.138006687164307
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 20.7,
|
||||||
|
"memory_percent": 84.4,
|
||||||
|
"process_memory_mb": 65.859375
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 14.138,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 37.6,
|
||||||
|
"memory_percent": 84.2,
|
||||||
|
"process_memory_mb": 239.515625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 20.7,
|
||||||
|
"memory_percent": 84.4,
|
||||||
|
"process_memory_mb": 65.859375
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 14.138,
|
||||||
|
"median": 14.138,
|
||||||
|
"min": 14.138,
|
||||||
|
"max": 14.138
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 65.859,
|
||||||
|
"median": 65.859,
|
||||||
|
"min": 65.859,
|
||||||
|
"max": 65.859
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 20.7,
|
||||||
|
"median": 20.7,
|
||||||
|
"min": 20.7,
|
||||||
|
"max": 20.7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"What are the top performing tech stocks this quarter?": {
|
||||||
|
"execution_times": [
|
||||||
|
17.769688844680786
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 18.1,
|
||||||
|
"memory_percent": 83.0,
|
||||||
|
"process_memory_mb": 56.75
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 17.77,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 100.0,
|
||||||
|
"memory_percent": 84.4,
|
||||||
|
"process_memory_mb": 66.234375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 18.1,
|
||||||
|
"memory_percent": 83.0,
|
||||||
|
"process_memory_mb": 56.75
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 17.77,
|
||||||
|
"median": 17.77,
|
||||||
|
"min": 17.77,
|
||||||
|
"max": 17.77
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 56.75,
|
||||||
|
"median": 56.75,
|
||||||
|
"min": 56.75,
|
||||||
|
"max": 56.75
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 18.1,
|
||||||
|
"median": 18.1,
|
||||||
|
"min": 18.1,
|
||||||
|
"max": 18.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Analyze current market trends in renewable energy sector": {
|
||||||
|
"execution_times": [
|
||||||
|
14.471845149993896
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 15.5,
|
||||||
|
"memory_percent": 82.3,
|
||||||
|
"process_memory_mb": 56.671875
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 14.472,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 100.0,
|
||||||
|
"memory_percent": 83.0,
|
||||||
|
"process_memory_mb": 57.015625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 15.5,
|
||||||
|
"memory_percent": 82.3,
|
||||||
|
"process_memory_mb": 56.671875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 14.472,
|
||||||
|
"median": 14.472,
|
||||||
|
"min": 14.472,
|
||||||
|
"max": 14.472
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 56.672,
|
||||||
|
"median": 56.672,
|
||||||
|
"min": 56.672,
|
||||||
|
"max": 56.672
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 15.5,
|
||||||
|
"median": 15.5,
|
||||||
|
"min": 15.5,
|
||||||
|
"max": 15.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Compare Bitcoin and Ethereum investment potential": {
|
||||||
|
"execution_times": [
|
||||||
|
15.340633869171143
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 16.5,
|
||||||
|
"memory_percent": 81.8,
|
||||||
|
"process_memory_mb": 54.5625
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 15.341,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 82.4,
|
||||||
|
"process_memory_mb": 56.953125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 16.5,
|
||||||
|
"memory_percent": 81.8,
|
||||||
|
"process_memory_mb": 54.5625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 15.341,
|
||||||
|
"median": 15.341,
|
||||||
|
"min": 15.341,
|
||||||
|
"max": 15.341
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 54.562,
|
||||||
|
"median": 54.562,
|
||||||
|
"min": 54.562,
|
||||||
|
"max": 54.562
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 16.5,
|
||||||
|
"median": 16.5,
|
||||||
|
"min": 16.5,
|
||||||
|
"max": 16.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Evaluate the risk factors in emerging markets": {
|
||||||
|
"execution_times": [
|
||||||
|
19.98606514930725
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 18.5,
|
||||||
|
"memory_percent": 82.2,
|
||||||
|
"process_memory_mb": 56.15625
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 19.986,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_percent": 81.8,
|
||||||
|
"process_memory_mb": 55.046875
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 18.5,
|
||||||
|
"memory_percent": 82.2,
|
||||||
|
"process_memory_mb": 56.15625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 19.986,
|
||||||
|
"median": 19.986,
|
||||||
|
"min": 19.986,
|
||||||
|
"max": 19.986
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 56.156,
|
||||||
|
"median": 56.156,
|
||||||
|
"min": 56.156,
|
||||||
|
"max": 56.156
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 18.5,
|
||||||
|
"median": 18.5,
|
||||||
|
"min": 18.5,
|
||||||
|
"max": 18.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,467 @@
|
|||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"timestamp": "2025-04-18T13:30:16.543562",
|
||||||
|
"num_iterations": 3,
|
||||||
|
"agent_config": {
|
||||||
|
"model_name": "gpt-4o-mini",
|
||||||
|
"max_loops": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"Conduct an analysis of the best real undervalued ETFs": {
|
||||||
|
"execution_times": [
|
||||||
|
14.789254,
|
||||||
|
13.413338,
|
||||||
|
13.084335
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 5.1,
|
||||||
|
"memory_mb": 67.765625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 3.7,
|
||||||
|
"memory_mb": 199.875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 16.2,
|
||||||
|
"memory_mb": 203.453125
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 14.789,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_mb": 243.046875
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 5.1,
|
||||||
|
"memory_mb": 67.765625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 13.413,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 202.0,
|
||||||
|
"memory_mb": 243.109375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 3.7,
|
||||||
|
"memory_mb": 199.875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 13.084,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 107.1,
|
||||||
|
"memory_mb": 243.21875
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 16.2,
|
||||||
|
"memory_mb": 203.453125
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 13.762,
|
||||||
|
"median": 13.413,
|
||||||
|
"min": 13.084,
|
||||||
|
"max": 14.789,
|
||||||
|
"std_dev": 0.738
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 157.031,
|
||||||
|
"median": 199.875,
|
||||||
|
"min": 67.766,
|
||||||
|
"max": 203.453,
|
||||||
|
"std_dev": 63.137
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 8.333,
|
||||||
|
"median": 5.1,
|
||||||
|
"min": 3.7,
|
||||||
|
"max": 16.2,
|
||||||
|
"std_dev": 5.592
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"What are the top performing tech stocks this quarter?": {
|
||||||
|
"execution_times": [
|
||||||
|
14.40021,
|
||||||
|
7.619928,
|
||||||
|
9.870042
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 2.5,
|
||||||
|
"memory_mb": 69.734375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.5,
|
||||||
|
"memory_mb": 204.46875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.8,
|
||||||
|
"memory_mb": 204.640625
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 14.4,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 109.3,
|
||||||
|
"memory_mb": 243.1875
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 2.5,
|
||||||
|
"memory_mb": 69.734375
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 7.62,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 194.6,
|
||||||
|
"memory_mb": 243.328125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.5,
|
||||||
|
"memory_mb": 204.46875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 9.87,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 187.1,
|
||||||
|
"memory_mb": 243.734375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.8,
|
||||||
|
"memory_mb": 204.640625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 10.63,
|
||||||
|
"median": 9.87,
|
||||||
|
"min": 7.62,
|
||||||
|
"max": 14.4,
|
||||||
|
"std_dev": 2.82
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 159.615,
|
||||||
|
"median": 204.469,
|
||||||
|
"min": 69.734,
|
||||||
|
"max": 204.641,
|
||||||
|
"std_dev": 63.555
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 1.267,
|
||||||
|
"median": 0.8,
|
||||||
|
"min": 0.5,
|
||||||
|
"max": 2.5,
|
||||||
|
"std_dev": 0.881
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Analyze current market trends in renewable energy sector": {
|
||||||
|
"execution_times": [
|
||||||
|
3.193721,
|
||||||
|
11.01429,
|
||||||
|
13.543417
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 5.7,
|
||||||
|
"memory_mb": 223.109375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 1.4,
|
||||||
|
"memory_mb": 203.46875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 9.9,
|
||||||
|
"memory_mb": 199.1875
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 3.194,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 223.0,
|
||||||
|
"memory_mb": 243.8125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 5.7,
|
||||||
|
"memory_mb": 223.109375
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 11.014,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 270.1,
|
||||||
|
"memory_mb": 243.953125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 1.4,
|
||||||
|
"memory_mb": 203.46875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 13.543,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 340.8,
|
||||||
|
"memory_mb": 244.0625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 9.9,
|
||||||
|
"memory_mb": 199.1875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 9.25,
|
||||||
|
"median": 11.014,
|
||||||
|
"min": 3.194,
|
||||||
|
"max": 13.543,
|
||||||
|
"std_dev": 4.405
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 208.589,
|
||||||
|
"median": 203.469,
|
||||||
|
"min": 199.188,
|
||||||
|
"max": 223.109,
|
||||||
|
"std_dev": 10.415
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 5.667,
|
||||||
|
"median": 5.7,
|
||||||
|
"min": 1.4,
|
||||||
|
"max": 9.9,
|
||||||
|
"std_dev": 3.47
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Compare Bitcoin and Ethereum investment potential": {
|
||||||
|
"execution_times": [
|
||||||
|
3.424122,
|
||||||
|
12.162575,
|
||||||
|
9.831582
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 1.9,
|
||||||
|
"memory_mb": 217.640625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 2.9,
|
||||||
|
"memory_mb": 203.171875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 31.2,
|
||||||
|
"memory_mb": 204.765625
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 3.424,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 364.2,
|
||||||
|
"memory_mb": 245.671875
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 1.9,
|
||||||
|
"memory_mb": 217.640625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 12.163,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 659.7,
|
||||||
|
"memory_mb": 245.734375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 2.9,
|
||||||
|
"memory_mb": 203.171875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 9.832,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 612.4,
|
||||||
|
"memory_mb": 245.953125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 31.2,
|
||||||
|
"memory_mb": 204.765625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 8.473,
|
||||||
|
"median": 9.832,
|
||||||
|
"min": 3.424,
|
||||||
|
"max": 12.163,
|
||||||
|
"std_dev": 3.695
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 208.526,
|
||||||
|
"median": 204.766,
|
||||||
|
"min": 203.172,
|
||||||
|
"max": 217.641,
|
||||||
|
"std_dev": 6.478
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 12.0,
|
||||||
|
"median": 2.9,
|
||||||
|
"min": 1.9,
|
||||||
|
"max": 31.2,
|
||||||
|
"std_dev": 13.583
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Evaluate the risk factors in emerging markets": {
|
||||||
|
"execution_times": [
|
||||||
|
2.948636,
|
||||||
|
12.942413,
|
||||||
|
11.361344
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 402.2,
|
||||||
|
"memory_mb": 246.078125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 2.2,
|
||||||
|
"memory_mb": 203.34375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 4.5,
|
||||||
|
"memory_mb": 203.59375
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 2.949,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 1494.6,
|
||||||
|
"memory_mb": 246.140625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 402.2,
|
||||||
|
"memory_mb": 246.078125
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 12.942,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 529.1,
|
||||||
|
"memory_mb": 246.265625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 2.2,
|
||||||
|
"memory_mb": 203.34375
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 11.361,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 578.8,
|
||||||
|
"memory_mb": 246.65625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 4.5,
|
||||||
|
"memory_mb": 203.59375
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 9.084,
|
||||||
|
"median": 11.361,
|
||||||
|
"min": 2.949,
|
||||||
|
"max": 12.942,
|
||||||
|
"std_dev": 4.386
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 217.672,
|
||||||
|
"median": 203.594,
|
||||||
|
"min": 203.344,
|
||||||
|
"max": 246.078,
|
||||||
|
"std_dev": 20.087
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 136.3,
|
||||||
|
"median": 4.5,
|
||||||
|
"min": 2.2,
|
||||||
|
"max": 402.2,
|
||||||
|
"std_dev": 188.022
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,467 @@
|
|||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"timestamp": "2025-04-18T13:30:51.812685",
|
||||||
|
"num_iterations": 3,
|
||||||
|
"agent_config": {
|
||||||
|
"model_name": "gpt-4o-mini",
|
||||||
|
"max_loops": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"Conduct an analysis of the best real undervalued ETFs": {
|
||||||
|
"execution_times": [
|
||||||
|
8.791961,
|
||||||
|
15.974623,
|
||||||
|
10.00903
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 3.9,
|
||||||
|
"memory_mb": 73.96875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 4.6,
|
||||||
|
"memory_mb": 71.171875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 21.5,
|
||||||
|
"memory_mb": 76.015625
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 8.792,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_mb": 133.765625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 3.9,
|
||||||
|
"memory_mb": 73.96875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 15.975,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 96.7,
|
||||||
|
"memory_mb": 133.8125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 4.6,
|
||||||
|
"memory_mb": 71.171875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 10.009,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 76.2,
|
||||||
|
"memory_mb": 137.15625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 21.5,
|
||||||
|
"memory_mb": 76.015625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 11.592,
|
||||||
|
"median": 10.009,
|
||||||
|
"min": 8.792,
|
||||||
|
"max": 15.975,
|
||||||
|
"std_dev": 3.139
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 73.719,
|
||||||
|
"median": 73.969,
|
||||||
|
"min": 71.172,
|
||||||
|
"max": 76.016,
|
||||||
|
"std_dev": 1.985
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 10.0,
|
||||||
|
"median": 4.6,
|
||||||
|
"min": 3.9,
|
||||||
|
"max": 21.5,
|
||||||
|
"std_dev": 8.137
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"What are the top performing tech stocks this quarter?": {
|
||||||
|
"execution_times": [
|
||||||
|
10.980763,
|
||||||
|
11.800057,
|
||||||
|
18.108609
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 2.8,
|
||||||
|
"memory_mb": 76.203125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 2.4,
|
||||||
|
"memory_mb": 76.65625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.4,
|
||||||
|
"memory_mb": 69.515625
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 10.981,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 40.8,
|
||||||
|
"memory_mb": 137.40625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 2.8,
|
||||||
|
"memory_mb": 76.203125
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 11.8,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 87.4,
|
||||||
|
"memory_mb": 137.4375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 2.4,
|
||||||
|
"memory_mb": 76.65625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 18.109,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 180.9,
|
||||||
|
"memory_mb": 137.640625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.4,
|
||||||
|
"memory_mb": 69.515625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 13.63,
|
||||||
|
"median": 11.8,
|
||||||
|
"min": 10.981,
|
||||||
|
"max": 18.109,
|
||||||
|
"std_dev": 3.185
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 74.125,
|
||||||
|
"median": 76.203,
|
||||||
|
"min": 69.516,
|
||||||
|
"max": 76.656,
|
||||||
|
"std_dev": 3.265
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 1.867,
|
||||||
|
"median": 2.4,
|
||||||
|
"min": 0.4,
|
||||||
|
"max": 2.8,
|
||||||
|
"std_dev": 1.05
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Analyze current market trends in renewable energy sector": {
|
||||||
|
"execution_times": [
|
||||||
|
15.015125,
|
||||||
|
9.916293,
|
||||||
|
6.958686
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 1.3,
|
||||||
|
"memory_mb": 69.953125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 14.6,
|
||||||
|
"memory_mb": 74.765625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 5.0,
|
||||||
|
"memory_mb": 72.90625
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 15.015,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 169.6,
|
||||||
|
"memory_mb": 137.9375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 1.3,
|
||||||
|
"memory_mb": 69.953125
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 9.916,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 212.3,
|
||||||
|
"memory_mb": 138.171875
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 14.6,
|
||||||
|
"memory_mb": 74.765625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 6.959,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 149.0,
|
||||||
|
"memory_mb": 138.4375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 5.0,
|
||||||
|
"memory_mb": 72.90625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 10.63,
|
||||||
|
"median": 9.916,
|
||||||
|
"min": 6.959,
|
||||||
|
"max": 15.015,
|
||||||
|
"std_dev": 3.328
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 72.542,
|
||||||
|
"median": 72.906,
|
||||||
|
"min": 69.953,
|
||||||
|
"max": 74.766,
|
||||||
|
"std_dev": 1.982
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 6.967,
|
||||||
|
"median": 5.0,
|
||||||
|
"min": 1.3,
|
||||||
|
"max": 14.6,
|
||||||
|
"std_dev": 5.605
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Compare Bitcoin and Ethereum investment potential": {
|
||||||
|
"execution_times": [
|
||||||
|
15.44115,
|
||||||
|
13.797926,
|
||||||
|
8.355462
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 2.4,
|
||||||
|
"memory_mb": 70.59375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 1.0,
|
||||||
|
"memory_mb": 69.875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 1.1,
|
||||||
|
"memory_mb": 73.5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 15.441,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 218.0,
|
||||||
|
"memory_mb": 138.515625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 2.4,
|
||||||
|
"memory_mb": 70.59375
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 13.798,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 298.8,
|
||||||
|
"memory_mb": 138.59375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 1.0,
|
||||||
|
"memory_mb": 69.875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 8.355,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 226.1,
|
||||||
|
"memory_mb": 139.984375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 1.1,
|
||||||
|
"memory_mb": 73.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 12.532,
|
||||||
|
"median": 13.798,
|
||||||
|
"min": 8.355,
|
||||||
|
"max": 15.441,
|
||||||
|
"std_dev": 3.028
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 71.323,
|
||||||
|
"median": 70.594,
|
||||||
|
"min": 69.875,
|
||||||
|
"max": 73.5,
|
||||||
|
"std_dev": 1.567
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 1.5,
|
||||||
|
"median": 1.1,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 2.4,
|
||||||
|
"std_dev": 0.638
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Evaluate the risk factors in emerging markets": {
|
||||||
|
"execution_times": [
|
||||||
|
6.380516,
|
||||||
|
9.943111,
|
||||||
|
9.821866
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 126.4,
|
||||||
|
"memory_mb": 118.28125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 18.0,
|
||||||
|
"memory_mb": 75.28125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cpu_percent": 1.8,
|
||||||
|
"memory_mb": 74.1875
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 6.381,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 83.4,
|
||||||
|
"memory_mb": 140.046875
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 126.4,
|
||||||
|
"memory_mb": 118.28125
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 2,
|
||||||
|
"execution_time": 9.943,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 197.8,
|
||||||
|
"memory_mb": 140.109375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 18.0,
|
||||||
|
"memory_mb": 75.28125
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iteration": 3,
|
||||||
|
"execution_time": 9.822,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 181.7,
|
||||||
|
"memory_mb": 140.171875
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 1.8,
|
||||||
|
"memory_mb": 74.1875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 8.715,
|
||||||
|
"median": 9.822,
|
||||||
|
"min": 6.381,
|
||||||
|
"max": 9.943,
|
||||||
|
"std_dev": 1.652
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 89.25,
|
||||||
|
"median": 75.281,
|
||||||
|
"min": 74.188,
|
||||||
|
"max": 118.281,
|
||||||
|
"std_dev": 20.533
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 48.733,
|
||||||
|
"median": 18.0,
|
||||||
|
"min": 1.8,
|
||||||
|
"max": 126.4,
|
||||||
|
"std_dev": 55.315
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,252 @@
|
|||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"timestamp": "2025-04-18T13:31:55.055663",
|
||||||
|
"num_iterations": 1,
|
||||||
|
"agent_config": {
|
||||||
|
"model_name": "gpt-4o-mini",
|
||||||
|
"max_loops": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"Conduct an analysis of the best real undervalued ETFs": {
|
||||||
|
"execution_times": [
|
||||||
|
11.214983
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 1.2,
|
||||||
|
"memory_mb": 187.140625
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 11.215,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_mb": 192.546875
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 1.2,
|
||||||
|
"memory_mb": 187.140625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 11.215,
|
||||||
|
"median": 11.215,
|
||||||
|
"min": 11.215,
|
||||||
|
"max": 11.215
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 187.141,
|
||||||
|
"median": 187.141,
|
||||||
|
"min": 187.141,
|
||||||
|
"max": 187.141
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 1.2,
|
||||||
|
"median": 1.2,
|
||||||
|
"min": 1.2,
|
||||||
|
"max": 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"What are the top performing tech stocks this quarter?": {
|
||||||
|
"execution_times": [
|
||||||
|
13.182044
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.3,
|
||||||
|
"memory_mb": 57.671875
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 13.182,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 96.3,
|
||||||
|
"memory_mb": 187.140625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.3,
|
||||||
|
"memory_mb": 57.671875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 13.182,
|
||||||
|
"median": 13.182,
|
||||||
|
"min": 13.182,
|
||||||
|
"max": 13.182
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 57.672,
|
||||||
|
"median": 57.672,
|
||||||
|
"min": 57.672,
|
||||||
|
"max": 57.672
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 0.3,
|
||||||
|
"median": 0.3,
|
||||||
|
"min": 0.3,
|
||||||
|
"max": 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Analyze current market trends in renewable energy sector": {
|
||||||
|
"execution_times": [
|
||||||
|
11.858239
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.3,
|
||||||
|
"memory_mb": 56.53125
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 11.858,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 98.1,
|
||||||
|
"memory_mb": 57.8125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.3,
|
||||||
|
"memory_mb": 56.53125
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 11.858,
|
||||||
|
"median": 11.858,
|
||||||
|
"min": 11.858,
|
||||||
|
"max": 11.858
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 56.531,
|
||||||
|
"median": 56.531,
|
||||||
|
"min": 56.531,
|
||||||
|
"max": 56.531
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 0.3,
|
||||||
|
"median": 0.3,
|
||||||
|
"min": 0.3,
|
||||||
|
"max": 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Compare Bitcoin and Ethereum investment potential": {
|
||||||
|
"execution_times": [
|
||||||
|
25.299971
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.1,
|
||||||
|
"memory_mb": 55.734375
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 25.3,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 89.8,
|
||||||
|
"memory_mb": 56.671875
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.1,
|
||||||
|
"memory_mb": 55.734375
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 25.3,
|
||||||
|
"median": 25.3,
|
||||||
|
"min": 25.3,
|
||||||
|
"max": 25.3
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 55.734,
|
||||||
|
"median": 55.734,
|
||||||
|
"min": 55.734,
|
||||||
|
"max": 55.734
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 0.1,
|
||||||
|
"median": 0.1,
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 0.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Evaluate the risk factors in emerging markets": {
|
||||||
|
"execution_times": [
|
||||||
|
11.951775
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.3,
|
||||||
|
"memory_mb": 55.5625
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 11.952,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 56.3,
|
||||||
|
"memory_mb": 55.890625
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.3,
|
||||||
|
"memory_mb": 55.5625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 11.952,
|
||||||
|
"median": 11.952,
|
||||||
|
"min": 11.952,
|
||||||
|
"max": 11.952
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 55.562,
|
||||||
|
"median": 55.562,
|
||||||
|
"min": 55.562,
|
||||||
|
"max": 55.562
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 0.3,
|
||||||
|
"median": 0.3,
|
||||||
|
"min": 0.3,
|
||||||
|
"max": 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,252 @@
|
|||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"timestamp": "2025-04-18T13:34:14.487430",
|
||||||
|
"num_iterations": 1,
|
||||||
|
"agent_config": {
|
||||||
|
"model_name": "gpt-4o-mini",
|
||||||
|
"max_loops": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"Conduct an analysis of the best real undervalued ETFs": {
|
||||||
|
"execution_times": [
|
||||||
|
9.132072
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 1.8,
|
||||||
|
"memory_mb": 66.5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 9.132,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"memory_mb": 229.375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 1.8,
|
||||||
|
"memory_mb": 66.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 9.132,
|
||||||
|
"median": 9.132,
|
||||||
|
"min": 9.132,
|
||||||
|
"max": 9.132
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 66.5,
|
||||||
|
"median": 66.5,
|
||||||
|
"min": 66.5,
|
||||||
|
"max": 66.5
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 1.8,
|
||||||
|
"median": 1.8,
|
||||||
|
"min": 1.8,
|
||||||
|
"max": 1.8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"What are the top performing tech stocks this quarter?": {
|
||||||
|
"execution_times": [
|
||||||
|
8.669393
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.3,
|
||||||
|
"memory_mb": 73.859375
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 8.669,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 86.7,
|
||||||
|
"memory_mb": 66.609375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.3,
|
||||||
|
"memory_mb": 73.859375
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 8.669,
|
||||||
|
"median": 8.669,
|
||||||
|
"min": 8.669,
|
||||||
|
"max": 8.669
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 73.859,
|
||||||
|
"median": 73.859,
|
||||||
|
"min": 73.859,
|
||||||
|
"max": 73.859
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 0.3,
|
||||||
|
"median": 0.3,
|
||||||
|
"min": 0.3,
|
||||||
|
"max": 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Analyze current market trends in renewable energy sector": {
|
||||||
|
"execution_times": [
|
||||||
|
10.922691
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.4,
|
||||||
|
"memory_mb": 55.6875
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 10.923,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 108.3,
|
||||||
|
"memory_mb": 73.859375
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.4,
|
||||||
|
"memory_mb": 55.6875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 10.923,
|
||||||
|
"median": 10.923,
|
||||||
|
"min": 10.923,
|
||||||
|
"max": 10.923
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 55.688,
|
||||||
|
"median": 55.688,
|
||||||
|
"min": 55.688,
|
||||||
|
"max": 55.688
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 0.4,
|
||||||
|
"median": 0.4,
|
||||||
|
"min": 0.4,
|
||||||
|
"max": 0.4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Compare Bitcoin and Ethereum investment potential": {
|
||||||
|
"execution_times": [
|
||||||
|
13.72297
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.3,
|
||||||
|
"memory_mb": 55.671875
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 13.723,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 87.7,
|
||||||
|
"memory_mb": 55.828125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.3,
|
||||||
|
"memory_mb": 55.671875
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 13.723,
|
||||||
|
"median": 13.723,
|
||||||
|
"min": 13.723,
|
||||||
|
"max": 13.723
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 55.672,
|
||||||
|
"median": 55.672,
|
||||||
|
"min": 55.672,
|
||||||
|
"max": 55.672
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 0.3,
|
||||||
|
"median": 0.3,
|
||||||
|
"min": 0.3,
|
||||||
|
"max": 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Evaluate the risk factors in emerging markets": {
|
||||||
|
"execution_times": [
|
||||||
|
14.099555
|
||||||
|
],
|
||||||
|
"system_metrics": [
|
||||||
|
{
|
||||||
|
"cpu_percent": 0.3,
|
||||||
|
"memory_mb": 56.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iterations": [
|
||||||
|
{
|
||||||
|
"iteration": 1,
|
||||||
|
"execution_time": 14.1,
|
||||||
|
"success": true,
|
||||||
|
"system_metrics": {
|
||||||
|
"pre": {
|
||||||
|
"cpu_percent": 61.7,
|
||||||
|
"memory_mb": 55.8125
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"cpu_percent": 0.3,
|
||||||
|
"memory_mb": 56.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"execution_time": {
|
||||||
|
"mean": 14.1,
|
||||||
|
"median": 14.1,
|
||||||
|
"min": 14.1,
|
||||||
|
"max": 14.1
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"mean": 56.0,
|
||||||
|
"median": 56.0,
|
||||||
|
"min": 56.0,
|
||||||
|
"max": 56.0
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"mean": 0.3,
|
||||||
|
"median": 0.3,
|
||||||
|
"min": 0.3,
|
||||||
|
"max": 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,291 @@
|
|||||||
|
import concurrent.futures
|
||||||
|
from typing import Dict, Optional
|
||||||
|
import secrets
|
||||||
|
import string
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from swarms import Agent
|
||||||
|
|
||||||
|
import replicate
|
||||||
|
|
||||||
|
from swarms.utils.str_to_dict import str_to_dict
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_key(prefix: str = "run") -> str:
|
||||||
|
"""
|
||||||
|
Generates an API key similar to OpenAI's format (sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix (str): The prefix for the API key. Defaults to "sk".
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: An API key string in format: prefix-<48 random characters>
|
||||||
|
"""
|
||||||
|
# Create random string of letters and numbers
|
||||||
|
alphabet = string.ascii_letters + string.digits
|
||||||
|
random_part = "".join(secrets.choice(alphabet) for _ in range(28))
|
||||||
|
return f"{prefix}-{random_part}"
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_media(
|
||||||
|
prompt: str = None, modalities: list = None
|
||||||
|
) -> Dict[str, str]:
|
||||||
|
"""
|
||||||
|
Generate media content (images or videos) based on text prompts using AI models.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prompt (str): Text description of the content to be generated
|
||||||
|
modalities (list): List of media types to generate (e.g., ["image", "video"])
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, str]: Dictionary containing file paths of generated media
|
||||||
|
"""
|
||||||
|
if not prompt or not modalities:
|
||||||
|
raise ValueError("Prompt and modalities must be provided")
|
||||||
|
|
||||||
|
input = {"prompt": prompt}
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
def _generate_image(input: Dict) -> str:
|
||||||
|
"""Generate an image and return the file path."""
|
||||||
|
output = replicate.run(
|
||||||
|
"black-forest-labs/flux-dev", input=input
|
||||||
|
)
|
||||||
|
file_paths = []
|
||||||
|
|
||||||
|
for index, item in enumerate(output):
|
||||||
|
unique_id = str(uuid.uuid4())
|
||||||
|
artifact = item.read()
|
||||||
|
file_path = f"output_{unique_id}_{index}.webp"
|
||||||
|
|
||||||
|
with open(file_path, "wb") as file:
|
||||||
|
file.write(artifact)
|
||||||
|
|
||||||
|
file_paths.append(file_path)
|
||||||
|
|
||||||
|
return file_paths
|
||||||
|
|
||||||
|
def _generate_video(input: Dict) -> str:
|
||||||
|
"""Generate a video and return the file path."""
|
||||||
|
output = replicate.run("luma/ray", input=input)
|
||||||
|
unique_id = str(uuid.uuid4())
|
||||||
|
artifact = output.read()
|
||||||
|
file_path = f"output_{unique_id}.mp4"
|
||||||
|
|
||||||
|
with open(file_path, "wb") as file:
|
||||||
|
file.write(artifact)
|
||||||
|
|
||||||
|
return file_path
|
||||||
|
|
||||||
|
for modality in modalities:
|
||||||
|
if modality == "image":
|
||||||
|
results["images"] = _generate_image(input)
|
||||||
|
elif modality == "video":
|
||||||
|
results["video"] = _generate_video(input)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported modality: {modality}")
|
||||||
|
|
||||||
|
print(results)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def generate_media(
|
||||||
|
modalities: list,
|
||||||
|
prompt: Optional[str] = None,
|
||||||
|
count: int = 1,
|
||||||
|
) -> Dict:
|
||||||
|
with concurrent.futures.ThreadPoolExecutor(
|
||||||
|
max_workers=count
|
||||||
|
) as executor:
|
||||||
|
# Create list of identical tasks to run concurrently
|
||||||
|
futures = [
|
||||||
|
executor.submit(
|
||||||
|
_generate_media,
|
||||||
|
prompt=prompt, # Fix: Pass as keyword arguments
|
||||||
|
modalities=modalities,
|
||||||
|
)
|
||||||
|
for _ in range(count)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Wait for all tasks to complete and collect results
|
||||||
|
results = [
|
||||||
|
future.result()
|
||||||
|
for future in concurrent.futures.as_completed(futures)
|
||||||
|
]
|
||||||
|
|
||||||
|
return {"results": results}
|
||||||
|
|
||||||
|
|
||||||
|
tools = [
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "generate_media",
|
||||||
|
"description": "Generate different types of media content (image, video, or music) based on text prompts using AI models.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"modality": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["image", "video", "music"],
|
||||||
|
},
|
||||||
|
"description": "The type of media content to generate",
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Text description of the content to be generated. Specialize it for the modality at hand. For example, if you are generating an image, the prompt should be a description of the image you want to see. If you are generating a video, the prompt should be a description of the video you want to see. If you are generating music, the prompt should be a description of the music you want to hear.",
|
||||||
|
},
|
||||||
|
"count": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Number of outputs to generate (1-4)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"modality",
|
||||||
|
"prompt",
|
||||||
|
"count",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
MEDIA_GENERATION_SYSTEM_PROMPT = """
|
||||||
|
You are an expert AI Media Generation Assistant, specialized in crafting precise and effective prompts for generating images, videos, and music. Your role is to help users create high-quality media content by understanding their requests and translating them into optimal prompts.
|
||||||
|
|
||||||
|
GENERAL GUIDELINES:
|
||||||
|
- Always analyze the user's request carefully to determine the appropriate modality (image, video, or music)
|
||||||
|
- Maintain a balanced level of detail in prompts - specific enough to capture the desired outcome but not overly verbose
|
||||||
|
- Consider the technical limitations and capabilities of AI generation systems
|
||||||
|
- When unclear, ask for clarification about specific details or preferences
|
||||||
|
|
||||||
|
MODALITY-SPECIFIC GUIDELINES:
|
||||||
|
|
||||||
|
1. IMAGE GENERATION:
|
||||||
|
- Structure prompts with primary subject first, followed by style, mood, and technical specifications
|
||||||
|
- Include relevant art styles when specified (e.g., "digital art", "oil painting", "watercolor", "photorealistic")
|
||||||
|
- Consider composition elements (foreground, background, lighting, perspective)
|
||||||
|
- Use specific adjectives for clarity (instead of "beautiful", specify "vibrant", "ethereal", "gritty", etc.)
|
||||||
|
|
||||||
|
Example image prompts:
|
||||||
|
- "A serene Japanese garden at sunset, with cherry blossoms falling, painted in traditional ukiyo-e style, soft pastel colors"
|
||||||
|
- "Cyberpunk cityscape at night, neon lights reflecting in rain puddles, hyper-realistic digital art style"
|
||||||
|
|
||||||
|
2. VIDEO GENERATION:
|
||||||
|
- Describe the sequence of events clearly
|
||||||
|
- Specify camera movements if relevant (pan, zoom, tracking shot)
|
||||||
|
- Include timing and transitions when necessary
|
||||||
|
- Focus on dynamic elements and motion
|
||||||
|
|
||||||
|
Example video prompts:
|
||||||
|
- "Timelapse of a flower blooming in a garden, close-up shot, soft natural lighting, 10-second duration"
|
||||||
|
- "Drone shot flying through autumn forest, camera slowly rising above the canopy, revealing mountains in the distance"
|
||||||
|
|
||||||
|
3. MUSIC GENERATION:
|
||||||
|
- Specify genre, tempo, and mood
|
||||||
|
- Mention key instruments or sounds
|
||||||
|
- Include emotional qualities and intensity
|
||||||
|
- Reference similar artists or styles if relevant
|
||||||
|
|
||||||
|
Example music prompts:
|
||||||
|
- "Calm ambient electronic music with soft synthesizer pads, gentle piano melodies, 80 BPM, suitable for meditation"
|
||||||
|
- "Upbeat jazz fusion track with prominent bass line, dynamic drums, and horn section, inspired by Weather Report"
|
||||||
|
|
||||||
|
COUNT HANDLING:
|
||||||
|
- When multiple outputs are requested (1-4), maintain consistency while introducing subtle variations
|
||||||
|
- For images: Vary composition or perspective while maintaining style
|
||||||
|
- For videos: Adjust camera angles or timing while keeping the core concept
|
||||||
|
- For music: Modify instrument arrangements or tempo while preserving the genre and mood
|
||||||
|
|
||||||
|
PROMPT OPTIMIZATION PROCESS:
|
||||||
|
1. Identify core requirements from user input
|
||||||
|
2. Determine appropriate modality
|
||||||
|
3. Add necessary style and technical specifications
|
||||||
|
4. Adjust detail level based on complexity
|
||||||
|
5. Consider count and create variations if needed
|
||||||
|
|
||||||
|
EXAMPLES OF HANDLING USER REQUESTS:
|
||||||
|
|
||||||
|
User: "I want a fantasy landscape"
|
||||||
|
Assistant response: {
|
||||||
|
"modality": "image",
|
||||||
|
"prompt": "Majestic fantasy landscape with floating islands, crystal waterfalls, and ancient magical ruins, ethereal lighting, digital art style with rich colors",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
User: "Create 3 variations of a peaceful nature scene"
|
||||||
|
Assistant response: {
|
||||||
|
"modality": "image",
|
||||||
|
"prompt": "Tranquil forest clearing with morning mist, sunbeams filtering through ancient trees, photorealistic style with soft natural lighting",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
IMPORTANT CONSIDERATIONS:
|
||||||
|
- Avoid harmful, unethical, or inappropriate content
|
||||||
|
- Respect copyright and intellectual property guidelines
|
||||||
|
- Maintain consistency with brand guidelines when specified
|
||||||
|
- Consider technical limitations of current AI generation systems
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Initialize the agent with the new system prompt
|
||||||
|
agent = Agent(
|
||||||
|
agent_name="Media-Generation-Agent",
|
||||||
|
agent_description="AI Media Generation Assistant",
|
||||||
|
system_prompt=MEDIA_GENERATION_SYSTEM_PROMPT,
|
||||||
|
max_loops=1,
|
||||||
|
tools_list_dictionary=tools,
|
||||||
|
output_type="final",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_agent(task: str):
|
||||||
|
output = str_to_dict(agent.run(task))
|
||||||
|
|
||||||
|
print(output)
|
||||||
|
print(type(output))
|
||||||
|
|
||||||
|
prompt = output["prompt"]
|
||||||
|
count = output["count"]
|
||||||
|
modalities = output["modality"]
|
||||||
|
|
||||||
|
output = generate_media(
|
||||||
|
modalities=modalities,
|
||||||
|
prompt=prompt,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
|
|
||||||
|
run_id = generate_key()
|
||||||
|
|
||||||
|
total_cost = 0
|
||||||
|
|
||||||
|
for modality in modalities:
|
||||||
|
if modality == "image":
|
||||||
|
total_cost += 0.1
|
||||||
|
elif modality == "video":
|
||||||
|
total_cost += 1
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"id": run_id,
|
||||||
|
"success": True,
|
||||||
|
"prompt": prompt,
|
||||||
|
"count": count,
|
||||||
|
"modality": modalities,
|
||||||
|
"total_cost": total_cost,
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
task = "Create 3 super kawaii variations of a magical Chinese mountain garden scene in anime style! 🌸✨ Include adorable elements like: cute koi fish swimming in crystal ponds, fluffy clouds floating around misty peaks, tiny pagodas with twinkling lights, and playful pandas hiding in bamboo groves. Make it extra magical with sparkles and soft pastel colors! Create both a video and an image for each variation. Just 1."
|
||||||
|
output = create_agent(task)
|
||||||
|
print("✨ Yay! Here's your super cute creation! ✨")
|
||||||
|
print(output)
|
@ -1,278 +0,0 @@
|
|||||||
# AsyncWorkflow Documentation
|
|
||||||
|
|
||||||
The `AsyncWorkflow` class represents an asynchronous workflow that executes tasks concurrently using multiple agents. It allows for efficient task management, leveraging Python's `asyncio` for concurrent execution.
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
- **Concurrent Task Execution**: Distribute tasks across multiple agents asynchronously.
|
|
||||||
- **Configurable Workers**: Limit the number of concurrent workers (agents) for better resource management.
|
|
||||||
- **Autosave Results**: Optionally save the task execution results automatically.
|
|
||||||
- **Verbose Logging**: Enable detailed logging to monitor task execution.
|
|
||||||
- **Error Handling**: Gracefully handles exceptions raised by agents during task execution.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Attributes
|
|
||||||
| Attribute | Type | Description |
|
|
||||||
|-------------------|---------------------|-----------------------------------------------------------------------------|
|
|
||||||
| `name` | `str` | The name of the workflow. |
|
|
||||||
| `agents` | `List[Agent]` | A list of agents participating in the workflow. |
|
|
||||||
| `max_workers` | `int` | The maximum number of concurrent workers (default: 5). |
|
|
||||||
| `dashboard` | `bool` | Whether to display a dashboard (currently not implemented). |
|
|
||||||
| `autosave` | `bool` | Whether to autosave task results (default: `False`). |
|
|
||||||
| `verbose` | `bool` | Whether to enable detailed logging (default: `False`). |
|
|
||||||
| `task_pool` | `List` | A pool of tasks to be executed. |
|
|
||||||
| `results` | `List` | A list to store results of executed tasks. |
|
|
||||||
| `loop` | `asyncio.EventLoop` | The event loop for asynchronous execution. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Description**:
|
|
||||||
Initializes the `AsyncWorkflow` with specified agents, configuration, and options.
|
|
||||||
|
|
||||||
**Parameters**:
|
|
||||||
- `name` (`str`): Name of the workflow. Default: "AsyncWorkflow".
|
|
||||||
- `agents` (`List[Agent]`): A list of agents. Default: `None`.
|
|
||||||
- `max_workers` (`int`): The maximum number of workers. Default: `5`.
|
|
||||||
- `dashboard` (`bool`): Enable dashboard visualization (placeholder for future implementation).
|
|
||||||
- `autosave` (`bool`): Enable autosave of task results. Default: `False`.
|
|
||||||
- `verbose` (`bool`): Enable detailed logging. Default: `False`.
|
|
||||||
- `**kwargs`: Additional parameters for `BaseWorkflow`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### `_execute_agent_task`
|
|
||||||
```python
|
|
||||||
async def _execute_agent_task(self, agent: Agent, task: str) -> Any:
|
|
||||||
```
|
|
||||||
**Description**:
|
|
||||||
Executes a single task asynchronously using a given agent.
|
|
||||||
|
|
||||||
**Parameters**:
|
|
||||||
- `agent` (`Agent`): The agent responsible for executing the task.
|
|
||||||
- `task` (`str`): The task to be executed.
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
- `Any`: The result of the task execution or an error message in case of an exception.
|
|
||||||
|
|
||||||
**Example**:
|
|
||||||
```python
|
|
||||||
result = await workflow._execute_agent_task(agent, "Sample Task")
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### `run`
|
|
||||||
```python
|
|
||||||
async def run(self, task: str) -> List[Any]:
|
|
||||||
```
|
|
||||||
**Description**:
|
|
||||||
Executes the specified task concurrently across all agents.
|
|
||||||
|
|
||||||
**Parameters**:
|
|
||||||
- `task` (`str`): The task to be executed by all agents.
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
- `List[Any]`: A list of results or error messages returned by the agents.
|
|
||||||
|
|
||||||
**Raises**:
|
|
||||||
- `ValueError`: If no agents are provided in the workflow.
|
|
||||||
|
|
||||||
**Example**:
|
|
||||||
```python
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
agents = [Agent("Agent1"), Agent("Agent2")]
|
|
||||||
workflow = AsyncWorkflow(agents=agents, verbose=True)
|
|
||||||
|
|
||||||
results = asyncio.run(workflow.run("Process Data"))
|
|
||||||
print(results)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Production-Grade Financial Example: Multiple Agents
|
|
||||||
### Example: Stock Analysis and Investment Strategy
|
|
||||||
```python
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from swarm_models import OpenAIChat
|
|
||||||
|
|
||||||
from swarms.structs.async_workflow import (
|
|
||||||
SpeakerConfig,
|
|
||||||
SpeakerRole,
|
|
||||||
create_default_workflow,
|
|
||||||
run_workflow_with_retry,
|
|
||||||
)
|
|
||||||
from swarms.prompts.finance_agent_sys_prompt import (
|
|
||||||
FINANCIAL_AGENT_SYS_PROMPT,
|
|
||||||
)
|
|
||||||
from swarms.structs.agent import Agent
|
|
||||||
|
|
||||||
|
|
||||||
async def create_specialized_agents() -> List[Agent]:
|
|
||||||
"""Create a set of specialized agents for financial analysis"""
|
|
||||||
|
|
||||||
# Base model configuration
|
|
||||||
model = OpenAIChat(model_name="gpt-4o")
|
|
||||||
|
|
||||||
# Financial Analysis Agent
|
|
||||||
financial_agent = Agent(
|
|
||||||
agent_name="Financial-Analysis-Agent",
|
|
||||||
agent_description="Personal finance advisor agent",
|
|
||||||
system_prompt=FINANCIAL_AGENT_SYS_PROMPT
|
|
||||||
+ "Output the <DONE> token when you're done creating a portfolio of etfs, index, funds, and more for AI",
|
|
||||||
max_loops=1,
|
|
||||||
llm=model,
|
|
||||||
dynamic_temperature_enabled=True,
|
|
||||||
user_name="Kye",
|
|
||||||
retry_attempts=3,
|
|
||||||
context_length=8192,
|
|
||||||
return_step_meta=False,
|
|
||||||
output_type="str",
|
|
||||||
auto_generate_prompt=False,
|
|
||||||
max_tokens=4000,
|
|
||||||
stopping_token="<DONE>",
|
|
||||||
saved_state_path="financial_agent.json",
|
|
||||||
interactive=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Risk Assessment Agent
|
|
||||||
risk_agent = Agent(
|
|
||||||
agent_name="Risk-Assessment-Agent",
|
|
||||||
agent_description="Investment risk analysis specialist",
|
|
||||||
system_prompt="Analyze investment risks and provide risk scores. Output <DONE> when analysis is complete.",
|
|
||||||
max_loops=1,
|
|
||||||
llm=model,
|
|
||||||
dynamic_temperature_enabled=True,
|
|
||||||
user_name="Kye",
|
|
||||||
retry_attempts=3,
|
|
||||||
context_length=8192,
|
|
||||||
output_type="str",
|
|
||||||
max_tokens=4000,
|
|
||||||
stopping_token="<DONE>",
|
|
||||||
saved_state_path="risk_agent.json",
|
|
||||||
interactive=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Market Research Agent
|
|
||||||
research_agent = Agent(
|
|
||||||
agent_name="Market-Research-Agent",
|
|
||||||
agent_description="AI and tech market research specialist",
|
|
||||||
system_prompt="Research AI market trends and growth opportunities. Output <DONE> when research is complete.",
|
|
||||||
max_loops=1,
|
|
||||||
llm=model,
|
|
||||||
dynamic_temperature_enabled=True,
|
|
||||||
user_name="Kye",
|
|
||||||
retry_attempts=3,
|
|
||||||
context_length=8192,
|
|
||||||
output_type="str",
|
|
||||||
max_tokens=4000,
|
|
||||||
stopping_token="<DONE>",
|
|
||||||
saved_state_path="research_agent.json",
|
|
||||||
interactive=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
return [financial_agent, risk_agent, research_agent]
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
# Create specialized agents
|
|
||||||
agents = await create_specialized_agents()
|
|
||||||
|
|
||||||
# Create workflow with group chat enabled
|
|
||||||
workflow = create_default_workflow(
|
|
||||||
agents=agents,
|
|
||||||
name="AI-Investment-Analysis-Workflow",
|
|
||||||
enable_group_chat=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Configure speaker roles
|
|
||||||
workflow.speaker_system.add_speaker(
|
|
||||||
SpeakerConfig(
|
|
||||||
role=SpeakerRole.COORDINATOR,
|
|
||||||
agent=agents[0], # Financial agent as coordinator
|
|
||||||
priority=1,
|
|
||||||
concurrent=False,
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
workflow.speaker_system.add_speaker(
|
|
||||||
SpeakerConfig(
|
|
||||||
role=SpeakerRole.CRITIC,
|
|
||||||
agent=agents[1], # Risk agent as critic
|
|
||||||
priority=2,
|
|
||||||
concurrent=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
workflow.speaker_system.add_speaker(
|
|
||||||
SpeakerConfig(
|
|
||||||
role=SpeakerRole.EXECUTOR,
|
|
||||||
agent=agents[2], # Research agent as executor
|
|
||||||
priority=2,
|
|
||||||
concurrent=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Investment analysis task
|
|
||||||
investment_task = """
|
|
||||||
Create a comprehensive investment analysis for a $40k portfolio focused on AI growth opportunities:
|
|
||||||
1. Identify high-growth AI ETFs and index funds
|
|
||||||
2. Analyze risks and potential returns
|
|
||||||
3. Create a diversified portfolio allocation
|
|
||||||
4. Provide market trend analysis
|
|
||||||
Present the results in a structured markdown format.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Run workflow with retry
|
|
||||||
result = await run_workflow_with_retry(
|
|
||||||
workflow=workflow, task=investment_task, max_retries=3
|
|
||||||
)
|
|
||||||
|
|
||||||
print("\nWorkflow Results:")
|
|
||||||
print("================")
|
|
||||||
|
|
||||||
# Process and display agent outputs
|
|
||||||
for output in result.agent_outputs:
|
|
||||||
print(f"\nAgent: {output.agent_name}")
|
|
||||||
print("-" * (len(output.agent_name) + 8))
|
|
||||||
print(output.output)
|
|
||||||
|
|
||||||
# Display group chat history if enabled
|
|
||||||
if workflow.enable_group_chat:
|
|
||||||
print("\nGroup Chat Discussion:")
|
|
||||||
print("=====================")
|
|
||||||
for msg in workflow.speaker_system.message_history:
|
|
||||||
print(f"\n{msg.role} ({msg.agent_name}):")
|
|
||||||
print(msg.content)
|
|
||||||
|
|
||||||
# Save detailed results
|
|
||||||
if result.metadata.get("shared_memory_keys"):
|
|
||||||
print("\nShared Insights:")
|
|
||||||
print("===============")
|
|
||||||
for key in result.metadata["shared_memory_keys"]:
|
|
||||||
value = workflow.shared_memory.get(key)
|
|
||||||
if value:
|
|
||||||
print(f"\n{key}:")
|
|
||||||
print(value)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Workflow failed: {str(e)}")
|
|
||||||
|
|
||||||
finally:
|
|
||||||
await workflow.cleanup()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Run the example
|
|
||||||
asyncio.run(main())
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
@ -0,0 +1,58 @@
|
|||||||
|
from swarms import Agent
|
||||||
|
from swarms.prompts.finance_agent_sys_prompt import (
|
||||||
|
FINANCIAL_AGENT_SYS_PROMPT,
|
||||||
|
)
|
||||||
|
|
||||||
|
tools = [
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "get_stock_price",
|
||||||
|
"description": "Retrieve the current stock price and related information for a specified company.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ticker": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The stock ticker symbol of the company, e.g. AAPL for Apple Inc.",
|
||||||
|
},
|
||||||
|
"include_history": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Indicates whether to include historical price data along with the current price.",
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "Optional parameter to specify the time for which the stock data is requested, in ISO 8601 format.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"ticker",
|
||||||
|
"include_history",
|
||||||
|
"time",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Initialize the agent
|
||||||
|
agent = Agent(
|
||||||
|
agent_name="Financial-Analysis-Agent",
|
||||||
|
agent_description="Personal finance advisor agent",
|
||||||
|
system_prompt=FINANCIAL_AGENT_SYS_PROMPT,
|
||||||
|
max_loops=1,
|
||||||
|
# tools_list_dictionary=tools,
|
||||||
|
# mcp_servers=["http://localhost:8000/sse"],
|
||||||
|
# output_type="dict-all-except-first",
|
||||||
|
# "dict-final",
|
||||||
|
# "dict-all-except-first",
|
||||||
|
# "str-all-except-first",
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
agent.run(
|
||||||
|
"What is the current stock price for Apple Inc. (AAPL)? Include historical price data.",
|
||||||
|
)
|
||||||
|
)
|
@ -1,729 +0,0 @@
|
|||||||
"""
|
|
||||||
TalkHier: A hierarchical multi-agent framework for content generation and refinement.
|
|
||||||
Implements structured communication and evaluation protocols.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from dataclasses import dataclass, asdict
|
|
||||||
from datetime import datetime
|
|
||||||
from enum import Enum
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Dict, List, Optional, Union
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
|
|
||||||
from swarms import Agent
|
|
||||||
from swarms.structs.conversation import Conversation
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class AgentRole(Enum):
|
|
||||||
"""Defines the possible roles for agents in the system."""
|
|
||||||
|
|
||||||
SUPERVISOR = "supervisor"
|
|
||||||
GENERATOR = "generator"
|
|
||||||
EVALUATOR = "evaluator"
|
|
||||||
REVISOR = "revisor"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CommunicationEvent:
|
|
||||||
"""Represents a structured communication event between agents."""
|
|
||||||
|
|
||||||
message: str
|
|
||||||
background: Optional[str] = None
|
|
||||||
intermediate_output: Optional[Dict[str, Any]] = None
|
|
||||||
sender: str = ""
|
|
||||||
receiver: str = ""
|
|
||||||
timestamp: str = str(datetime.now())
|
|
||||||
|
|
||||||
|
|
||||||
class TalkHier:
|
|
||||||
"""
|
|
||||||
A hierarchical multi-agent system for content generation and refinement.
|
|
||||||
|
|
||||||
Implements the TalkHier framework with structured communication protocols
|
|
||||||
and hierarchical refinement processes.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
max_iterations: Maximum number of refinement iterations
|
|
||||||
quality_threshold: Minimum score required for content acceptance
|
|
||||||
model_name: Name of the LLM model to use
|
|
||||||
base_path: Path for saving agent states
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
max_iterations: int = 3,
|
|
||||||
quality_threshold: float = 0.8,
|
|
||||||
model_name: str = "gpt-4",
|
|
||||||
base_path: Optional[str] = None,
|
|
||||||
return_string: bool = False,
|
|
||||||
):
|
|
||||||
"""Initialize the TalkHier system."""
|
|
||||||
self.max_iterations = max_iterations
|
|
||||||
self.quality_threshold = quality_threshold
|
|
||||||
self.model_name = model_name
|
|
||||||
self.return_string = return_string
|
|
||||||
self.base_path = (
|
|
||||||
Path(base_path) if base_path else Path("./agent_states")
|
|
||||||
)
|
|
||||||
self.base_path.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
# Initialize agents
|
|
||||||
self._init_agents()
|
|
||||||
|
|
||||||
# Create conversation
|
|
||||||
self.conversation = Conversation()
|
|
||||||
|
|
||||||
def _safely_parse_json(self, json_str: str) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Safely parse JSON string, handling various formats and potential errors.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
json_str: String containing JSON data
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Parsed dictionary
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Try direct JSON parsing
|
|
||||||
return json.loads(json_str)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
try:
|
|
||||||
# Try extracting JSON from potential text wrapper
|
|
||||||
import re
|
|
||||||
|
|
||||||
json_match = re.search(r"\{.*\}", json_str, re.DOTALL)
|
|
||||||
if json_match:
|
|
||||||
return json.loads(json_match.group())
|
|
||||||
# Try extracting from markdown code blocks
|
|
||||||
code_block_match = re.search(
|
|
||||||
r"```(?:json)?\s*(\{.*?\})\s*```",
|
|
||||||
json_str,
|
|
||||||
re.DOTALL,
|
|
||||||
)
|
|
||||||
if code_block_match:
|
|
||||||
return json.loads(code_block_match.group(1))
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Failed to extract JSON: {str(e)}")
|
|
||||||
|
|
||||||
# Fallback: create structured dict from text
|
|
||||||
return {
|
|
||||||
"content": json_str,
|
|
||||||
"metadata": {
|
|
||||||
"parsed": False,
|
|
||||||
"timestamp": str(datetime.now()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def _get_criteria_generator_prompt(self) -> str:
|
|
||||||
"""Get the prompt for the criteria generator agent."""
|
|
||||||
return """You are a Criteria Generator agent responsible for creating task-specific evaluation criteria.
|
|
||||||
Analyze the task and generate appropriate evaluation criteria based on:
|
|
||||||
- Task type and complexity
|
|
||||||
- Required domain knowledge
|
|
||||||
- Target audience expectations
|
|
||||||
- Quality requirements
|
|
||||||
|
|
||||||
Output all responses in strict JSON format:
|
|
||||||
{
|
|
||||||
"criteria": {
|
|
||||||
"criterion_name": {
|
|
||||||
"description": "Detailed description of what this criterion measures",
|
|
||||||
"importance": "Weight from 0.0-1.0 indicating importance",
|
|
||||||
"evaluation_guide": "Guidelines for how to evaluate this criterion"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"metadata": {
|
|
||||||
"task_type": "Classification of the task type",
|
|
||||||
"complexity_level": "Assessment of task complexity",
|
|
||||||
"domain_focus": "Primary domain or field of the task"
|
|
||||||
}
|
|
||||||
}"""
|
|
||||||
|
|
||||||
def _init_agents(self) -> None:
|
|
||||||
"""Initialize all agents with their specific roles and prompts."""
|
|
||||||
# Main supervisor agent
|
|
||||||
self.main_supervisor = Agent(
|
|
||||||
agent_name="Main-Supervisor",
|
|
||||||
system_prompt=self._get_supervisor_prompt(),
|
|
||||||
model_name=self.model_name,
|
|
||||||
max_loops=1,
|
|
||||||
saved_state_path=str(
|
|
||||||
self.base_path / "main_supervisor.json"
|
|
||||||
),
|
|
||||||
verbose=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generator agent
|
|
||||||
self.generator = Agent(
|
|
||||||
agent_name="Content-Generator",
|
|
||||||
system_prompt=self._get_generator_prompt(),
|
|
||||||
model_name=self.model_name,
|
|
||||||
max_loops=1,
|
|
||||||
saved_state_path=str(self.base_path / "generator.json"),
|
|
||||||
verbose=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Criteria Generator agent
|
|
||||||
self.criteria_generator = Agent(
|
|
||||||
agent_name="Criteria-Generator",
|
|
||||||
system_prompt=self._get_criteria_generator_prompt(),
|
|
||||||
model_name=self.model_name,
|
|
||||||
max_loops=1,
|
|
||||||
saved_state_path=str(
|
|
||||||
self.base_path / "criteria_generator.json"
|
|
||||||
),
|
|
||||||
verbose=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Evaluators without criteria (will be set during run)
|
|
||||||
self.evaluators = []
|
|
||||||
for i in range(3):
|
|
||||||
self.evaluators.append(
|
|
||||||
Agent(
|
|
||||||
agent_name=f"Evaluator-{i}",
|
|
||||||
system_prompt=self._get_evaluator_prompt(i),
|
|
||||||
model_name=self.model_name,
|
|
||||||
max_loops=1,
|
|
||||||
saved_state_path=str(
|
|
||||||
self.base_path / f"evaluator_{i}.json"
|
|
||||||
),
|
|
||||||
verbose=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Revisor agent
|
|
||||||
self.revisor = Agent(
|
|
||||||
agent_name="Content-Revisor",
|
|
||||||
system_prompt=self._get_revisor_prompt(),
|
|
||||||
model_name=self.model_name,
|
|
||||||
max_loops=1,
|
|
||||||
saved_state_path=str(self.base_path / "revisor.json"),
|
|
||||||
verbose=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _generate_dynamic_criteria(self, task: str) -> Dict[str, str]:
|
|
||||||
"""
|
|
||||||
Generate dynamic evaluation criteria based on the task.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task: Content generation task description
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dictionary containing dynamic evaluation criteria
|
|
||||||
"""
|
|
||||||
# Example dynamic criteria generation logic
|
|
||||||
if "technical" in task.lower():
|
|
||||||
return {
|
|
||||||
"accuracy": "Technical correctness and source reliability",
|
|
||||||
"clarity": "Readability and logical structure",
|
|
||||||
"depth": "Comprehensive coverage of technical details",
|
|
||||||
"engagement": "Interest level and relevance to the audience",
|
|
||||||
"technical_quality": "Grammar, spelling, and formatting",
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
return {
|
|
||||||
"accuracy": "Factual correctness and source reliability",
|
|
||||||
"clarity": "Readability and logical structure",
|
|
||||||
"coherence": "Logical consistency and argument structure",
|
|
||||||
"engagement": "Interest level and relevance to the audience",
|
|
||||||
"completeness": "Coverage of the topic and depth",
|
|
||||||
"technical_quality": "Grammar, spelling, and formatting",
|
|
||||||
}
|
|
||||||
|
|
||||||
def _get_supervisor_prompt(self) -> str:
|
|
||||||
"""Get the prompt for the supervisor agent."""
|
|
||||||
return """You are a Supervisor agent responsible for orchestrating the content generation process and selecting the best evaluation criteria.
|
|
||||||
|
|
||||||
You must:
|
|
||||||
1. Analyze tasks and develop strategies
|
|
||||||
2. Review multiple evaluator feedback
|
|
||||||
3. Select the most appropriate evaluation based on:
|
|
||||||
- Completeness of criteria
|
|
||||||
- Relevance to task
|
|
||||||
- Quality of feedback
|
|
||||||
4. Provide clear instructions for content revision
|
|
||||||
|
|
||||||
Output all responses in strict JSON format:
|
|
||||||
{
|
|
||||||
"thoughts": {
|
|
||||||
"task_analysis": "Analysis of requirements, audience, scope",
|
|
||||||
"strategy": "Step-by-step plan and success metrics",
|
|
||||||
"evaluation_selection": {
|
|
||||||
"chosen_evaluator": "ID of selected evaluator",
|
|
||||||
"reasoning": "Why this evaluation was chosen",
|
|
||||||
"key_criteria": ["List of most important criteria"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"next_action": {
|
|
||||||
"agent": "Next agent to engage",
|
|
||||||
"instruction": "Detailed instructions with context"
|
|
||||||
}
|
|
||||||
}"""
|
|
||||||
|
|
||||||
def _get_generator_prompt(self) -> str:
|
|
||||||
"""Get the prompt for the generator agent."""
|
|
||||||
return """You are a Generator agent responsible for creating high-quality, original content. Your role is to produce content that is engaging, informative, and tailored to the target audience.
|
|
||||||
|
|
||||||
When generating content:
|
|
||||||
- Thoroughly research and fact-check all information
|
|
||||||
- Structure content logically with clear flow
|
|
||||||
- Use appropriate tone and language for the target audience
|
|
||||||
- Include relevant examples and explanations
|
|
||||||
- Ensure content is original and plagiarism-free
|
|
||||||
- Consider SEO best practices where applicable
|
|
||||||
|
|
||||||
Output all responses in strict JSON format:
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"main_body": "The complete generated content with proper formatting and structure",
|
|
||||||
"metadata": {
|
|
||||||
"word_count": "Accurate word count of main body",
|
|
||||||
"target_audience": "Detailed audience description",
|
|
||||||
"key_points": ["List of main points covered"],
|
|
||||||
"sources": ["List of reference sources if applicable"],
|
|
||||||
"readability_level": "Estimated reading level",
|
|
||||||
"tone": "Description of content tone"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}"""
|
|
||||||
|
|
||||||
def _get_evaluator_prompt(self, evaluator_id: int) -> str:
|
|
||||||
"""Get the base prompt for an evaluator agent."""
|
|
||||||
return f"""You are Evaluator {evaluator_id}, responsible for critically assessing content quality. Your evaluation must be thorough, objective, and constructive.
|
|
||||||
|
|
||||||
When receiving content to evaluate:
|
|
||||||
1. First analyze the task description to determine appropriate evaluation criteria
|
|
||||||
2. Generate specific criteria based on task requirements
|
|
||||||
3. Evaluate content against these criteria
|
|
||||||
4. Provide detailed feedback for each criterion
|
|
||||||
|
|
||||||
Output all responses in strict JSON format:
|
|
||||||
{{
|
|
||||||
"generated_criteria": {{
|
|
||||||
"criteria_name": "description of what this criterion measures",
|
|
||||||
// Add more criteria based on task analysis
|
|
||||||
}},
|
|
||||||
"scores": {{
|
|
||||||
"overall": "0.0-1.0 composite score",
|
|
||||||
"categories": {{
|
|
||||||
// Scores for each generated criterion
|
|
||||||
"criterion_name": "0.0-1.0 score with evidence"
|
|
||||||
}}
|
|
||||||
}},
|
|
||||||
"feedback": [
|
|
||||||
"Specific, actionable improvement suggestions per criterion"
|
|
||||||
],
|
|
||||||
"strengths": ["Notable positive aspects"],
|
|
||||||
"weaknesses": ["Areas needing improvement"]
|
|
||||||
}}"""
|
|
||||||
|
|
||||||
def _get_revisor_prompt(self) -> str:
|
|
||||||
"""Get the prompt for the revisor agent."""
|
|
||||||
return """You are a Revisor agent responsible for improving content based on evaluator feedback. Your role is to enhance content while maintaining its core message and purpose.
|
|
||||||
|
|
||||||
When revising content:
|
|
||||||
- Address all evaluator feedback systematically
|
|
||||||
- Maintain consistency in tone and style
|
|
||||||
- Preserve accurate information
|
|
||||||
- Enhance clarity and flow
|
|
||||||
- Fix technical issues
|
|
||||||
- Optimize for target audience
|
|
||||||
- Track all changes made
|
|
||||||
|
|
||||||
Output all responses in strict JSON format:
|
|
||||||
{
|
|
||||||
"revised_content": {
|
|
||||||
"main_body": "Complete revised content incorporating all improvements",
|
|
||||||
"metadata": {
|
|
||||||
"word_count": "Updated word count",
|
|
||||||
"changes_made": [
|
|
||||||
"Detailed list of specific changes and improvements",
|
|
||||||
"Reasoning for each major revision",
|
|
||||||
"Feedback points addressed"
|
|
||||||
],
|
|
||||||
"improvement_summary": "Overview of main enhancements",
|
|
||||||
"preserved_elements": ["Key elements maintained from original"],
|
|
||||||
"revision_approach": "Strategy used for revisions"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}"""
|
|
||||||
|
|
||||||
def _generate_criteria_for_task(
|
|
||||||
self, task: str
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""Generate evaluation criteria for the given task."""
|
|
||||||
try:
|
|
||||||
criteria_input = {
|
|
||||||
"task": task,
|
|
||||||
"instruction": "Generate specific evaluation criteria for this task.",
|
|
||||||
}
|
|
||||||
|
|
||||||
criteria_response = self.criteria_generator.run(
|
|
||||||
json.dumps(criteria_input)
|
|
||||||
)
|
|
||||||
self.conversation.add(
|
|
||||||
role="Criteria-Generator", content=criteria_response
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._safely_parse_json(criteria_response)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error generating criteria: {str(e)}")
|
|
||||||
return {"criteria": {}}
|
|
||||||
|
|
||||||
def _create_comm_event(
|
|
||||||
self, sender: Agent, receiver: Agent, response: Dict
|
|
||||||
) -> CommunicationEvent:
|
|
||||||
"""Create a structured communication event between agents."""
|
|
||||||
return CommunicationEvent(
|
|
||||||
message=response.get("message", ""),
|
|
||||||
background=response.get("background", ""),
|
|
||||||
intermediate_output=response.get(
|
|
||||||
"intermediate_output", {}
|
|
||||||
),
|
|
||||||
sender=sender.agent_name,
|
|
||||||
receiver=receiver.agent_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _evaluate_content(
|
|
||||||
self, content: Union[str, Dict], task: str
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""Coordinate evaluation process with parallel evaluator execution."""
|
|
||||||
try:
|
|
||||||
content_dict = (
|
|
||||||
self._safely_parse_json(content)
|
|
||||||
if isinstance(content, str)
|
|
||||||
else content
|
|
||||||
)
|
|
||||||
criteria_data = self._generate_criteria_for_task(task)
|
|
||||||
|
|
||||||
def run_evaluator(evaluator, eval_input):
|
|
||||||
response = evaluator.run(json.dumps(eval_input))
|
|
||||||
return {
|
|
||||||
"evaluator_id": evaluator.agent_name,
|
|
||||||
"evaluation": self._safely_parse_json(response),
|
|
||||||
}
|
|
||||||
|
|
||||||
eval_inputs = [
|
|
||||||
{
|
|
||||||
"task": task,
|
|
||||||
"content": content_dict,
|
|
||||||
"criteria": criteria_data.get("criteria", {}),
|
|
||||||
}
|
|
||||||
for _ in self.evaluators
|
|
||||||
]
|
|
||||||
|
|
||||||
with ThreadPoolExecutor() as executor:
|
|
||||||
evaluations = list(
|
|
||||||
executor.map(
|
|
||||||
lambda x: run_evaluator(*x),
|
|
||||||
zip(self.evaluators, eval_inputs),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
supervisor_input = {
|
|
||||||
"evaluations": evaluations,
|
|
||||||
"task": task,
|
|
||||||
"instruction": "Synthesize feedback",
|
|
||||||
}
|
|
||||||
supervisor_response = self.main_supervisor.run(
|
|
||||||
json.dumps(supervisor_input)
|
|
||||||
)
|
|
||||||
aggregated_eval = self._safely_parse_json(
|
|
||||||
supervisor_response
|
|
||||||
)
|
|
||||||
|
|
||||||
# Track communication
|
|
||||||
comm_event = self._create_comm_event(
|
|
||||||
self.main_supervisor, self.revisor, aggregated_eval
|
|
||||||
)
|
|
||||||
self.conversation.add(
|
|
||||||
role="Communication",
|
|
||||||
content=json.dumps(asdict(comm_event)),
|
|
||||||
)
|
|
||||||
|
|
||||||
return aggregated_eval
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Evaluation error: {str(e)}")
|
|
||||||
return self._get_fallback_evaluation()
|
|
||||||
|
|
||||||
def _get_fallback_evaluation(self) -> Dict[str, Any]:
|
|
||||||
"""Get a safe fallback evaluation result."""
|
|
||||||
return {
|
|
||||||
"scores": {
|
|
||||||
"overall": 0.5,
|
|
||||||
"categories": {
|
|
||||||
"accuracy": 0.5,
|
|
||||||
"clarity": 0.5,
|
|
||||||
"coherence": 0.5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"feedback": ["Evaluation failed"],
|
|
||||||
"metadata": {
|
|
||||||
"timestamp": str(datetime.now()),
|
|
||||||
"is_fallback": True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def _aggregate_evaluations(
|
|
||||||
self, evaluations: List[Dict[str, Any]]
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""Aggregate multiple evaluation results into a single evaluation."""
|
|
||||||
try:
|
|
||||||
# Collect all unique criteria from evaluators
|
|
||||||
all_criteria = set()
|
|
||||||
for eval_data in evaluations:
|
|
||||||
categories = eval_data.get("scores", {}).get(
|
|
||||||
"categories", {}
|
|
||||||
)
|
|
||||||
all_criteria.update(categories.keys())
|
|
||||||
|
|
||||||
# Initialize score aggregation
|
|
||||||
aggregated_scores = {
|
|
||||||
criterion: [] for criterion in all_criteria
|
|
||||||
}
|
|
||||||
overall_scores = []
|
|
||||||
all_feedback = []
|
|
||||||
|
|
||||||
# Collect scores and feedback
|
|
||||||
for eval_data in evaluations:
|
|
||||||
scores = eval_data.get("scores", {})
|
|
||||||
overall_scores.append(scores.get("overall", 0.5))
|
|
||||||
|
|
||||||
categories = scores.get("categories", {})
|
|
||||||
for criterion in all_criteria:
|
|
||||||
if criterion in categories:
|
|
||||||
aggregated_scores[criterion].append(
|
|
||||||
categories.get(criterion, 0.5)
|
|
||||||
)
|
|
||||||
|
|
||||||
all_feedback.extend(eval_data.get("feedback", []))
|
|
||||||
|
|
||||||
# Calculate means
|
|
||||||
def safe_mean(scores: List[float]) -> float:
|
|
||||||
return sum(scores) / len(scores) if scores else 0.5
|
|
||||||
|
|
||||||
return {
|
|
||||||
"scores": {
|
|
||||||
"overall": safe_mean(overall_scores),
|
|
||||||
"categories": {
|
|
||||||
criterion: safe_mean(scores)
|
|
||||||
for criterion, scores in aggregated_scores.items()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"feedback": list(set(all_feedback)),
|
|
||||||
"metadata": {
|
|
||||||
"evaluator_count": len(evaluations),
|
|
||||||
"criteria_used": list(all_criteria),
|
|
||||||
"timestamp": str(datetime.now()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error in evaluation aggregation: {str(e)}")
|
|
||||||
return self._get_fallback_evaluation()
|
|
||||||
|
|
||||||
def _evaluate_and_revise(
|
|
||||||
self, content: Union[str, Dict], task: str
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""Coordinate evaluation and revision process."""
|
|
||||||
try:
|
|
||||||
# Get evaluations and supervisor selection
|
|
||||||
evaluation_result = self._evaluate_content(content, task)
|
|
||||||
|
|
||||||
# Extract selected evaluation and supervisor reasoning
|
|
||||||
selected_evaluation = evaluation_result.get(
|
|
||||||
"selected_evaluation", {}
|
|
||||||
)
|
|
||||||
supervisor_reasoning = evaluation_result.get(
|
|
||||||
"supervisor_reasoning", {}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Prepare revision input with selected evaluation
|
|
||||||
revision_input = {
|
|
||||||
"content": content,
|
|
||||||
"evaluation": selected_evaluation,
|
|
||||||
"supervisor_feedback": supervisor_reasoning,
|
|
||||||
"instruction": "Revise the content based on the selected evaluation feedback",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get revision from content generator
|
|
||||||
revision_response = self.generator.run(
|
|
||||||
json.dumps(revision_input)
|
|
||||||
)
|
|
||||||
revised_content = self._safely_parse_json(
|
|
||||||
revision_response
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"content": revised_content,
|
|
||||||
"evaluation": evaluation_result,
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Evaluation and revision error: {str(e)}")
|
|
||||||
return {
|
|
||||||
"content": content,
|
|
||||||
"evaluation": self._get_fallback_evaluation(),
|
|
||||||
}
|
|
||||||
|
|
||||||
def run(self, task: str) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Generate and iteratively refine content based on the given task.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task: Content generation task description
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dictionary containing final content and metadata
|
|
||||||
"""
|
|
||||||
logger.info(f"Starting content generation for task: {task}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Get initial direction from supervisor
|
|
||||||
supervisor_response = self.main_supervisor.run(task)
|
|
||||||
|
|
||||||
self.conversation.add(
|
|
||||||
role=self.main_supervisor.agent_name,
|
|
||||||
content=supervisor_response,
|
|
||||||
)
|
|
||||||
|
|
||||||
supervisor_data = self._safely_parse_json(
|
|
||||||
supervisor_response
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate initial content
|
|
||||||
generator_response = self.generator.run(
|
|
||||||
json.dumps(supervisor_data.get("next_action", {}))
|
|
||||||
)
|
|
||||||
|
|
||||||
self.conversation.add(
|
|
||||||
role=self.generator.agent_name,
|
|
||||||
content=generator_response,
|
|
||||||
)
|
|
||||||
|
|
||||||
current_content = self._safely_parse_json(
|
|
||||||
generator_response
|
|
||||||
)
|
|
||||||
|
|
||||||
for iteration in range(self.max_iterations):
|
|
||||||
logger.info(f"Starting iteration {iteration + 1}")
|
|
||||||
|
|
||||||
# Evaluate and revise content
|
|
||||||
result = self._evaluate_and_revise(
|
|
||||||
current_content, task
|
|
||||||
)
|
|
||||||
evaluation = result["evaluation"]
|
|
||||||
current_content = result["content"]
|
|
||||||
|
|
||||||
# Check if quality threshold is met
|
|
||||||
selected_eval = evaluation.get(
|
|
||||||
"selected_evaluation", {}
|
|
||||||
)
|
|
||||||
overall_score = selected_eval.get("scores", {}).get(
|
|
||||||
"overall", 0.0
|
|
||||||
)
|
|
||||||
|
|
||||||
if overall_score >= self.quality_threshold:
|
|
||||||
logger.info(
|
|
||||||
"Quality threshold met, returning content"
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"content": current_content.get(
|
|
||||||
"content", {}
|
|
||||||
).get("main_body", ""),
|
|
||||||
"final_score": overall_score,
|
|
||||||
"iterations": iteration + 1,
|
|
||||||
"metadata": {
|
|
||||||
"content_metadata": current_content.get(
|
|
||||||
"content", {}
|
|
||||||
).get("metadata", {}),
|
|
||||||
"evaluation": evaluation,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add to conversation history
|
|
||||||
self.conversation.add(
|
|
||||||
role=self.generator.agent_name,
|
|
||||||
content=json.dumps(current_content),
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.warning(
|
|
||||||
"Max iterations reached without meeting quality threshold"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error in generate_and_refine: {str(e)}")
|
|
||||||
current_content = {
|
|
||||||
"content": {"main_body": f"Error: {str(e)}"}
|
|
||||||
}
|
|
||||||
evaluation = self._get_fallback_evaluation()
|
|
||||||
|
|
||||||
if self.return_string:
|
|
||||||
return self.conversation.return_history_as_string()
|
|
||||||
else:
|
|
||||||
return {
|
|
||||||
"content": current_content.get("content", {}).get(
|
|
||||||
"main_body", ""
|
|
||||||
),
|
|
||||||
"final_score": evaluation["scores"]["overall"],
|
|
||||||
"iterations": self.max_iterations,
|
|
||||||
"metadata": {
|
|
||||||
"content_metadata": current_content.get(
|
|
||||||
"content", {}
|
|
||||||
).get("metadata", {}),
|
|
||||||
"evaluation": evaluation,
|
|
||||||
"error": "Max iterations reached",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def save_state(self) -> None:
|
|
||||||
"""Save the current state of all agents."""
|
|
||||||
for agent in [
|
|
||||||
self.main_supervisor,
|
|
||||||
self.generator,
|
|
||||||
*self.evaluators,
|
|
||||||
self.revisor,
|
|
||||||
]:
|
|
||||||
try:
|
|
||||||
agent.save_state()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"Error saving state for {agent.agent_name}: {str(e)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def load_state(self) -> None:
|
|
||||||
"""Load the saved state of all agents."""
|
|
||||||
for agent in [
|
|
||||||
self.main_supervisor,
|
|
||||||
self.generator,
|
|
||||||
*self.evaluators,
|
|
||||||
self.revisor,
|
|
||||||
]:
|
|
||||||
try:
|
|
||||||
agent.load_state()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"Error loading state for {agent.agent_name}: {str(e)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# if __name__ == "__main__":
|
|
||||||
# try:
|
|
||||||
# talkhier = TalkHier(
|
|
||||||
# max_iterations=1,
|
|
||||||
# quality_threshold=0.8,
|
|
||||||
# model_name="gpt-4o",
|
|
||||||
# return_string=False,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Ask for user input
|
|
||||||
# task = input("Enter the content generation task description: ")
|
|
||||||
# result = talkhier.run(task)
|
|
||||||
|
|
||||||
# except Exception as e:
|
|
||||||
# logger.error(f"Error in main execution: {str(e)}")
|
|
@ -0,0 +1,557 @@
|
|||||||
|
import asyncio
|
||||||
|
import inspect
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
from functools import wraps
|
||||||
|
from typing import Any, Callable, Literal, Optional
|
||||||
|
|
||||||
|
from fastmcp import FastMCP, Client
|
||||||
|
from loguru import logger
|
||||||
|
from swarms.utils.any_to_str import any_to_str
|
||||||
|
|
||||||
|
|
||||||
|
class AOP:
|
||||||
|
"""
|
||||||
|
Agent-Orchestration Protocol (AOP) class for managing tools, agents, and swarms.
|
||||||
|
|
||||||
|
This class provides decorators and methods for registering and running various components
|
||||||
|
in a Swarms environment. It handles logging, metadata management, and execution control.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
name (str): The name of the AOP instance
|
||||||
|
description (str): A description of the AOP instance
|
||||||
|
mcp (FastMCP): The underlying FastMCP instance for managing components
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
url: Optional[str] = "http://localhost:8000/sse",
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize the AOP instance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The name of the AOP instance
|
||||||
|
description (str): A description of the AOP instance
|
||||||
|
url (str): The URL of the MCP instance
|
||||||
|
*args: Additional positional arguments passed to FastMCP
|
||||||
|
**kwargs: Additional keyword arguments passed to FastMCP
|
||||||
|
"""
|
||||||
|
logger.info(f"[AOP] Initializing AOP instance: {name}")
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.url = url
|
||||||
|
|
||||||
|
self.tools = {}
|
||||||
|
self.swarms = {}
|
||||||
|
|
||||||
|
self.mcp = FastMCP(name=name, *args, **kwargs)
|
||||||
|
|
||||||
|
logger.success(
|
||||||
|
f"[AOP] Successfully initialized AOP instance: {name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def tool(
|
||||||
|
self,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Decorator to register an MCP tool with optional metadata.
|
||||||
|
|
||||||
|
This decorator registers a function as a tool in the MCP system. It handles
|
||||||
|
logging, metadata management, and execution tracking.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (Optional[str]): Custom name for the tool. If None, uses function name
|
||||||
|
description (Optional[str]): Custom description. If None, uses function docstring
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Callable: A decorator function that registers the tool
|
||||||
|
"""
|
||||||
|
logger.debug(
|
||||||
|
f"[AOP] Creating tool decorator with name={name}, description={description}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def decorator(func: Callable):
|
||||||
|
tool_name = name or func.__name__
|
||||||
|
tool_description = description or (
|
||||||
|
inspect.getdoc(func) or ""
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"[AOP] Registering tool: {tool_name} - {tool_description}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.tools[tool_name] = {
|
||||||
|
"name": tool_name,
|
||||||
|
"description": tool_description,
|
||||||
|
"function": func,
|
||||||
|
}
|
||||||
|
|
||||||
|
@self.mcp.tool(
|
||||||
|
name=f"tool_{tool_name}", description=tool_description
|
||||||
|
)
|
||||||
|
@wraps(func)
|
||||||
|
async def wrapper(*args, **kwargs) -> Any:
|
||||||
|
logger.info(
|
||||||
|
f"[TOOL:{tool_name}] ➤ called with args={args}, kwargs={kwargs}"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
result = await func(*args, **kwargs)
|
||||||
|
logger.success(f"[TOOL:{tool_name}] ✅ completed")
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"[TOOL:{tool_name}] ❌ failed with error: {str(e)}"
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def agent(
|
||||||
|
self,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Decorator to define an agent entry point.
|
||||||
|
|
||||||
|
This decorator registers a function as an agent in the MCP system. It handles
|
||||||
|
logging, metadata management, and execution tracking for agent operations.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (Optional[str]): Custom name for the agent. If None, uses 'agent_' + function name
|
||||||
|
description (Optional[str]): Custom description. If None, uses function docstring
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Callable: A decorator function that registers the agent
|
||||||
|
"""
|
||||||
|
logger.debug(
|
||||||
|
f"[AOP] Creating agent decorator with name={name}, description={description}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def decorator(func: Callable):
|
||||||
|
agent_name = name or f"agent_{func.__name__}"
|
||||||
|
agent_description = description or (
|
||||||
|
inspect.getdoc(func) or ""
|
||||||
|
)
|
||||||
|
|
||||||
|
@self.mcp.tool(
|
||||||
|
name=agent_name, description=agent_description
|
||||||
|
)
|
||||||
|
@wraps(func)
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
logger.info(f"[AGENT:{agent_name}] 👤 Starting")
|
||||||
|
try:
|
||||||
|
result = await func(*args, **kwargs)
|
||||||
|
logger.success(
|
||||||
|
f"[AGENT:{agent_name}] ✅ Finished"
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"[AGENT:{agent_name}] ❌ failed with error: {str(e)}"
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
wrapper._is_agent = True
|
||||||
|
wrapper._agent_name = agent_name
|
||||||
|
wrapper._agent_description = agent_description
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def swarm(
|
||||||
|
self,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Decorator to define a swarm controller.
|
||||||
|
|
||||||
|
This decorator registers a function as a swarm controller in the MCP system.
|
||||||
|
It handles logging, metadata management, and execution tracking for swarm operations.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (Optional[str]): Custom name for the swarm. If None, uses 'swarm_' + function name
|
||||||
|
description (Optional[str]): Custom description. If None, uses function docstring
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Callable: A decorator function that registers the swarm
|
||||||
|
"""
|
||||||
|
logger.debug(
|
||||||
|
f"[AOP] Creating swarm decorator with name={name}, description={description}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def decorator(func: Callable):
|
||||||
|
swarm_name = name or f"swarm_{func.__name__}"
|
||||||
|
swarm_description = description or (
|
||||||
|
inspect.getdoc(func) or ""
|
||||||
|
)
|
||||||
|
|
||||||
|
@self.mcp.tool(
|
||||||
|
name=swarm_name, description=swarm_description
|
||||||
|
)
|
||||||
|
@wraps(func)
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
logger.info(
|
||||||
|
f"[SWARM:{swarm_name}] 🐝 Spawning swarm..."
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
result = await func(*args, **kwargs)
|
||||||
|
logger.success(
|
||||||
|
f"[SWARM:{swarm_name}] 🐝 Completed"
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"[SWARM:{swarm_name}] ❌ failed with error: {str(e)}"
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
wrapper._is_swarm = True
|
||||||
|
wrapper._swarm_name = swarm_name
|
||||||
|
wrapper._swarm_description = swarm_description
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def run(self, method: Literal["stdio", "sse"], *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Run the MCP with the specified method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
method (Literal['stdio', 'sse']): The execution method to use
|
||||||
|
*args: Additional positional arguments for the run method
|
||||||
|
**kwargs: Additional keyword arguments for the run method
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: The result of the MCP run operation
|
||||||
|
"""
|
||||||
|
logger.info(f"[AOP] Running MCP with method: {method}")
|
||||||
|
try:
|
||||||
|
result = self.mcp.run(method, *args, **kwargs)
|
||||||
|
logger.success(
|
||||||
|
f"[AOP] Successfully ran MCP with method: {method}"
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"[AOP] Failed to run MCP with method {method}: {str(e)}"
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def run_stdio(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Run the MCP using standard I/O method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*args: Additional positional arguments for the run method
|
||||||
|
**kwargs: Additional keyword arguments for the run method
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: The result of the MCP run operation
|
||||||
|
"""
|
||||||
|
logger.info("[AOP] Running MCP with stdio method")
|
||||||
|
return self.run("stdio", *args, **kwargs)
|
||||||
|
|
||||||
|
def run_sse(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Run the MCP using Server-Sent Events method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*args: Additional positional arguments for the run method
|
||||||
|
**kwargs: Additional keyword arguments for the run method
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: The result of the MCP run operation
|
||||||
|
"""
|
||||||
|
logger.info("[AOP] Running MCP with SSE method")
|
||||||
|
return self.run("sse", *args, **kwargs)
|
||||||
|
|
||||||
|
def list_available(
|
||||||
|
self, output_type: Literal["str", "list"] = "str"
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
List all available tools in the MCP.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: A list of all registered tools
|
||||||
|
"""
|
||||||
|
if output_type == "str":
|
||||||
|
return any_to_str(self.mcp.list_tools())
|
||||||
|
elif output_type == "list":
|
||||||
|
return self.mcp.list_tools()
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid output type: {output_type}")
|
||||||
|
|
||||||
|
async def check_utility_exists(
|
||||||
|
self, url: str, name: str, *args, **kwargs
|
||||||
|
):
|
||||||
|
async with Client(url, *args, **kwargs) as client:
|
||||||
|
if any(tool.name == name for tool in client.list_tools()):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _call_tool(
|
||||||
|
self, url: str, name: str, arguments: dict, *args, **kwargs
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
async with Client(url, *args, **kwargs) as client:
|
||||||
|
result = await client.call_tool(name, arguments)
|
||||||
|
logger.info(
|
||||||
|
f"Client connected: {client.is_connected()}"
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calling tool: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def call_tool(
|
||||||
|
self,
|
||||||
|
url: str,
|
||||||
|
name: str,
|
||||||
|
arguments: dict,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
return asyncio.run(
|
||||||
|
self._call_tool(url, name, arguments, *args, **kwargs)
|
||||||
|
)
|
||||||
|
|
||||||
|
def call_tool_or_agent(
|
||||||
|
self,
|
||||||
|
url: str,
|
||||||
|
name: str,
|
||||||
|
arguments: dict,
|
||||||
|
output_type: Literal["str", "list"] = "str",
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Execute a tool or agent by name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The name of the tool or agent to execute
|
||||||
|
arguments (dict): The arguments to pass to the tool or agent
|
||||||
|
"""
|
||||||
|
if output_type == "str":
|
||||||
|
return any_to_str(
|
||||||
|
self.call_tool(
|
||||||
|
url=url, name=name, arguments=arguments
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif output_type == "list":
|
||||||
|
return self.call_tool(
|
||||||
|
url=url, name=name, arguments=arguments
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid output type: {output_type}")
|
||||||
|
|
||||||
|
def call_tool_or_agent_batched(
|
||||||
|
self,
|
||||||
|
url: str,
|
||||||
|
names: list[str],
|
||||||
|
arguments: list[dict],
|
||||||
|
output_type: Literal["str", "list"] = "str",
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Execute a list of tools or agents by name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
names (list[str]): The names of the tools or agents to execute
|
||||||
|
"""
|
||||||
|
if output_type == "str":
|
||||||
|
return [
|
||||||
|
any_to_str(
|
||||||
|
self.call_tool_or_agent(
|
||||||
|
url=url,
|
||||||
|
name=name,
|
||||||
|
arguments=argument,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for name, argument in zip(names, arguments)
|
||||||
|
]
|
||||||
|
elif output_type == "list":
|
||||||
|
return [
|
||||||
|
self.call_tool_or_agent(
|
||||||
|
url=url,
|
||||||
|
name=name,
|
||||||
|
arguments=argument,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
for name, argument in zip(names, arguments)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid output type: {output_type}")
|
||||||
|
|
||||||
|
def call_tool_or_agent_concurrently(
|
||||||
|
self,
|
||||||
|
url: str,
|
||||||
|
names: list[str],
|
||||||
|
arguments: list[dict],
|
||||||
|
output_type: Literal["str", "list"] = "str",
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Execute a list of tools or agents by name concurrently.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
names (list[str]): The names of the tools or agents to execute
|
||||||
|
arguments (list[dict]): The arguments to pass to the tools or agents
|
||||||
|
"""
|
||||||
|
outputs = []
|
||||||
|
with ThreadPoolExecutor(max_workers=len(names)) as executor:
|
||||||
|
futures = [
|
||||||
|
executor.submit(
|
||||||
|
self.call_tool_or_agent,
|
||||||
|
url=url,
|
||||||
|
name=name,
|
||||||
|
arguments=argument,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
for name, argument in zip(names, arguments)
|
||||||
|
]
|
||||||
|
for future in as_completed(futures):
|
||||||
|
outputs.append(future.result())
|
||||||
|
|
||||||
|
if output_type == "str":
|
||||||
|
return any_to_str(outputs)
|
||||||
|
elif output_type == "list":
|
||||||
|
return outputs
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid output type: {output_type}")
|
||||||
|
|
||||||
|
def call_swarm(
|
||||||
|
self,
|
||||||
|
url: str,
|
||||||
|
name: str,
|
||||||
|
arguments: dict,
|
||||||
|
output_type: Literal["str", "list"] = "str",
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Execute a swarm by name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The name of the swarm to execute
|
||||||
|
"""
|
||||||
|
if output_type == "str":
|
||||||
|
return any_to_str(
|
||||||
|
asyncio.run(
|
||||||
|
self._call_tool(
|
||||||
|
url=url,
|
||||||
|
name=name,
|
||||||
|
arguments=arguments,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif output_type == "list":
|
||||||
|
return asyncio.run(
|
||||||
|
self._call_tool(
|
||||||
|
url=url,
|
||||||
|
name=name,
|
||||||
|
arguments=arguments,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid output type: {output_type}")
|
||||||
|
|
||||||
|
def list_agents(
|
||||||
|
self, output_type: Literal["str", "list"] = "str"
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
List all available agents in the MCP.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: A list of all registered agents
|
||||||
|
"""
|
||||||
|
|
||||||
|
out = self.list_all()
|
||||||
|
agents = []
|
||||||
|
for item in out:
|
||||||
|
if "agent" in item["name"]:
|
||||||
|
agents.append(item)
|
||||||
|
return agents
|
||||||
|
|
||||||
|
def list_swarms(
|
||||||
|
self, output_type: Literal["str", "list"] = "str"
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
List all available swarms in the MCP.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: A list of all registered swarms
|
||||||
|
"""
|
||||||
|
out = self.list_all()
|
||||||
|
agents = []
|
||||||
|
for item in out:
|
||||||
|
if "swarm" in item["name"]:
|
||||||
|
agents.append(item)
|
||||||
|
return agents
|
||||||
|
|
||||||
|
async def _list_all(self):
|
||||||
|
async with Client(self.url) as client:
|
||||||
|
return await client.list_tools()
|
||||||
|
|
||||||
|
def list_all(self):
|
||||||
|
out = asyncio.run(self._list_all())
|
||||||
|
|
||||||
|
outputs = []
|
||||||
|
for tool in out:
|
||||||
|
outputs.append(tool.model_dump())
|
||||||
|
|
||||||
|
return outputs
|
||||||
|
|
||||||
|
def list_tool_parameters(self, name: str):
|
||||||
|
out = self.list_all()
|
||||||
|
|
||||||
|
# Find the tool by name
|
||||||
|
for tool in out:
|
||||||
|
if tool["name"] == name:
|
||||||
|
return tool
|
||||||
|
return None
|
||||||
|
|
||||||
|
def search_if_tool_exists(self, name: str):
|
||||||
|
out = self.list_all()
|
||||||
|
for tool in out:
|
||||||
|
if tool["name"] == name:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def search(
|
||||||
|
self,
|
||||||
|
type: Literal["tool", "agent", "swarm"],
|
||||||
|
name: str,
|
||||||
|
output_type: Literal["str", "list"] = "str",
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Search for a tool, agent, or swarm by name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
type (Literal["tool", "agent", "swarm"]): The type of the item to search for
|
||||||
|
name (str): The name of the item to search for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The item if found, otherwise None
|
||||||
|
"""
|
||||||
|
all_items = self.list_all()
|
||||||
|
for item in all_items:
|
||||||
|
if item["name"] == name:
|
||||||
|
return item
|
||||||
|
return None
|
@ -1,818 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import threading
|
|
||||||
import uuid
|
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
from dataclasses import asdict, dataclass
|
|
||||||
from datetime import datetime
|
|
||||||
from enum import Enum
|
|
||||||
from logging.handlers import RotatingFileHandler
|
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
from swarms.structs.agent import Agent
|
|
||||||
from swarms.structs.base_workflow import BaseWorkflow
|
|
||||||
from swarms.utils.loguru_logger import initialize_logger
|
|
||||||
|
|
||||||
# Base logger initialization
|
|
||||||
logger = initialize_logger("async_workflow")
|
|
||||||
|
|
||||||
|
|
||||||
# Pydantic models for structured data
|
|
||||||
class AgentOutput(BaseModel):
|
|
||||||
agent_id: str
|
|
||||||
agent_name: str
|
|
||||||
task_id: str
|
|
||||||
input: str
|
|
||||||
output: Any
|
|
||||||
start_time: datetime
|
|
||||||
end_time: datetime
|
|
||||||
status: str
|
|
||||||
error: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class WorkflowOutput(BaseModel):
|
|
||||||
workflow_id: str
|
|
||||||
workflow_name: str
|
|
||||||
start_time: datetime
|
|
||||||
end_time: datetime
|
|
||||||
total_agents: int
|
|
||||||
successful_tasks: int
|
|
||||||
failed_tasks: int
|
|
||||||
agent_outputs: List[AgentOutput]
|
|
||||||
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
||||||
|
|
||||||
|
|
||||||
class SpeakerRole(str, Enum):
|
|
||||||
COORDINATOR = "coordinator"
|
|
||||||
CRITIC = "critic"
|
|
||||||
EXECUTOR = "executor"
|
|
||||||
VALIDATOR = "validator"
|
|
||||||
DEFAULT = "default"
|
|
||||||
|
|
||||||
|
|
||||||
class SpeakerMessage(BaseModel):
|
|
||||||
role: SpeakerRole
|
|
||||||
content: Any
|
|
||||||
timestamp: datetime
|
|
||||||
agent_name: str
|
|
||||||
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
||||||
|
|
||||||
|
|
||||||
class GroupChatConfig(BaseModel):
|
|
||||||
max_loops: int = 10
|
|
||||||
timeout_per_turn: float = 30.0
|
|
||||||
require_all_speakers: bool = False
|
|
||||||
allow_concurrent: bool = True
|
|
||||||
save_history: bool = True
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class SharedMemoryItem:
|
|
||||||
key: str
|
|
||||||
value: Any
|
|
||||||
timestamp: datetime
|
|
||||||
author: str
|
|
||||||
metadata: Dict[str, Any] = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class SpeakerConfig:
|
|
||||||
role: SpeakerRole
|
|
||||||
agent: Any
|
|
||||||
priority: int = 0
|
|
||||||
concurrent: bool = True
|
|
||||||
timeout: float = 30.0
|
|
||||||
required: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
class SharedMemory:
|
|
||||||
"""Thread-safe shared memory implementation with persistence"""
|
|
||||||
|
|
||||||
def __init__(self, persistence_path: Optional[str] = None):
|
|
||||||
self._memory = {}
|
|
||||||
self._lock = threading.Lock()
|
|
||||||
self._persistence_path = persistence_path
|
|
||||||
self._load_from_disk()
|
|
||||||
|
|
||||||
def set(
|
|
||||||
self,
|
|
||||||
key: str,
|
|
||||||
value: Any,
|
|
||||||
author: str,
|
|
||||||
metadata: Dict[str, Any] = None,
|
|
||||||
) -> None:
|
|
||||||
with self._lock:
|
|
||||||
item = SharedMemoryItem(
|
|
||||||
key=key,
|
|
||||||
value=value,
|
|
||||||
timestamp=datetime.utcnow(),
|
|
||||||
author=author,
|
|
||||||
metadata=metadata or {},
|
|
||||||
)
|
|
||||||
self._memory[key] = item
|
|
||||||
self._persist_to_disk()
|
|
||||||
|
|
||||||
def get(self, key: str) -> Optional[Any]:
|
|
||||||
with self._lock:
|
|
||||||
item = self._memory.get(key)
|
|
||||||
return item.value if item else None
|
|
||||||
|
|
||||||
def get_with_metadata(
|
|
||||||
self, key: str
|
|
||||||
) -> Optional[SharedMemoryItem]:
|
|
||||||
with self._lock:
|
|
||||||
return self._memory.get(key)
|
|
||||||
|
|
||||||
def _persist_to_disk(self) -> None:
|
|
||||||
if self._persistence_path:
|
|
||||||
with open(self._persistence_path, "w") as f:
|
|
||||||
json.dump(
|
|
||||||
{k: asdict(v) for k, v in self._memory.items()}, f
|
|
||||||
)
|
|
||||||
|
|
||||||
def _load_from_disk(self) -> None:
|
|
||||||
if self._persistence_path and os.path.exists(
|
|
||||||
self._persistence_path
|
|
||||||
):
|
|
||||||
with open(self._persistence_path, "r") as f:
|
|
||||||
data = json.load(f)
|
|
||||||
self._memory = {
|
|
||||||
k: SharedMemoryItem(**v) for k, v in data.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SpeakerSystem:
|
|
||||||
"""Manages speaker interactions and group chat functionality"""
|
|
||||||
|
|
||||||
def __init__(self, default_timeout: float = 30.0):
|
|
||||||
self.speakers: Dict[SpeakerRole, SpeakerConfig] = {}
|
|
||||||
self.message_history: List[SpeakerMessage] = []
|
|
||||||
self.default_timeout = default_timeout
|
|
||||||
self._lock = threading.Lock()
|
|
||||||
|
|
||||||
def add_speaker(self, config: SpeakerConfig) -> None:
|
|
||||||
with self._lock:
|
|
||||||
self.speakers[config.role] = config
|
|
||||||
|
|
||||||
def remove_speaker(self, role: SpeakerRole) -> None:
|
|
||||||
with self._lock:
|
|
||||||
self.speakers.pop(role, None)
|
|
||||||
|
|
||||||
async def _execute_speaker(
|
|
||||||
self,
|
|
||||||
config: SpeakerConfig,
|
|
||||||
input_data: Any,
|
|
||||||
context: Dict[str, Any] = None,
|
|
||||||
) -> SpeakerMessage:
|
|
||||||
try:
|
|
||||||
result = await asyncio.wait_for(
|
|
||||||
config.agent.arun(input_data), timeout=config.timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
return SpeakerMessage(
|
|
||||||
role=config.role,
|
|
||||||
content=result,
|
|
||||||
timestamp=datetime.utcnow(),
|
|
||||||
agent_name=config.agent.agent_name,
|
|
||||||
metadata={"context": context or {}},
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
return SpeakerMessage(
|
|
||||||
role=config.role,
|
|
||||||
content=None,
|
|
||||||
timestamp=datetime.utcnow(),
|
|
||||||
agent_name=config.agent.agent_name,
|
|
||||||
metadata={"error": "Timeout"},
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
return SpeakerMessage(
|
|
||||||
role=config.role,
|
|
||||||
content=None,
|
|
||||||
timestamp=datetime.utcnow(),
|
|
||||||
agent_name=config.agent.agent_name,
|
|
||||||
metadata={"error": str(e)},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AsyncWorkflow(BaseWorkflow):
|
|
||||||
"""Enhanced asynchronous workflow with advanced speaker system"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str = "AsyncWorkflow",
|
|
||||||
agents: List[Agent] = None,
|
|
||||||
max_workers: int = 5,
|
|
||||||
dashboard: bool = False,
|
|
||||||
autosave: bool = False,
|
|
||||||
verbose: bool = False,
|
|
||||||
log_path: str = "workflow.log",
|
|
||||||
shared_memory_path: Optional[str] = "shared_memory.json",
|
|
||||||
enable_group_chat: bool = False,
|
|
||||||
group_chat_config: Optional[GroupChatConfig] = None,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
super().__init__(agents=agents, **kwargs)
|
|
||||||
self.workflow_id = str(uuid.uuid4())
|
|
||||||
self.name = name
|
|
||||||
self.agents = agents or []
|
|
||||||
self.max_workers = max_workers
|
|
||||||
self.dashboard = dashboard
|
|
||||||
self.autosave = autosave
|
|
||||||
self.verbose = verbose
|
|
||||||
self.task_pool = []
|
|
||||||
self.results = []
|
|
||||||
self.shared_memory = SharedMemory(shared_memory_path)
|
|
||||||
self.speaker_system = SpeakerSystem()
|
|
||||||
self.enable_group_chat = enable_group_chat
|
|
||||||
self.group_chat_config = (
|
|
||||||
group_chat_config or GroupChatConfig()
|
|
||||||
)
|
|
||||||
self._setup_logging(log_path)
|
|
||||||
self.metadata = {}
|
|
||||||
|
|
||||||
def _setup_logging(self, log_path: str) -> None:
|
|
||||||
"""Configure rotating file logger"""
|
|
||||||
self.logger = logging.getLogger(
|
|
||||||
f"workflow_{self.workflow_id}"
|
|
||||||
)
|
|
||||||
self.logger.setLevel(
|
|
||||||
logging.DEBUG if self.verbose else logging.INFO
|
|
||||||
)
|
|
||||||
|
|
||||||
handler = RotatingFileHandler(
|
|
||||||
log_path, maxBytes=10 * 1024 * 1024, backupCount=5
|
|
||||||
)
|
|
||||||
formatter = logging.Formatter(
|
|
||||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
||||||
)
|
|
||||||
handler.setFormatter(formatter)
|
|
||||||
self.logger.addHandler(handler)
|
|
||||||
|
|
||||||
def add_default_speakers(self) -> None:
|
|
||||||
"""Add all agents as default concurrent speakers"""
|
|
||||||
for agent in self.agents:
|
|
||||||
config = SpeakerConfig(
|
|
||||||
role=SpeakerRole.DEFAULT,
|
|
||||||
agent=agent,
|
|
||||||
concurrent=True,
|
|
||||||
timeout=30.0,
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
self.speaker_system.add_speaker(config)
|
|
||||||
|
|
||||||
async def run_concurrent_speakers(
|
|
||||||
self, task: str, context: Dict[str, Any] = None
|
|
||||||
) -> List[SpeakerMessage]:
|
|
||||||
"""Run all concurrent speakers in parallel"""
|
|
||||||
concurrent_tasks = [
|
|
||||||
self.speaker_system._execute_speaker(
|
|
||||||
config, task, context
|
|
||||||
)
|
|
||||||
for config in self.speaker_system.speakers.values()
|
|
||||||
if config.concurrent
|
|
||||||
]
|
|
||||||
|
|
||||||
results = await asyncio.gather(
|
|
||||||
*concurrent_tasks, return_exceptions=True
|
|
||||||
)
|
|
||||||
return [r for r in results if isinstance(r, SpeakerMessage)]
|
|
||||||
|
|
||||||
async def run_sequential_speakers(
|
|
||||||
self, task: str, context: Dict[str, Any] = None
|
|
||||||
) -> List[SpeakerMessage]:
|
|
||||||
"""Run non-concurrent speakers in sequence"""
|
|
||||||
results = []
|
|
||||||
for config in sorted(
|
|
||||||
self.speaker_system.speakers.values(),
|
|
||||||
key=lambda x: x.priority,
|
|
||||||
):
|
|
||||||
if not config.concurrent:
|
|
||||||
result = await self.speaker_system._execute_speaker(
|
|
||||||
config, task, context
|
|
||||||
)
|
|
||||||
results.append(result)
|
|
||||||
return results
|
|
||||||
|
|
||||||
async def run_group_chat(
|
|
||||||
self, initial_message: str, context: Dict[str, Any] = None
|
|
||||||
) -> List[SpeakerMessage]:
|
|
||||||
"""Run a group chat discussion among speakers"""
|
|
||||||
if not self.enable_group_chat:
|
|
||||||
raise ValueError(
|
|
||||||
"Group chat is not enabled for this workflow"
|
|
||||||
)
|
|
||||||
|
|
||||||
messages: List[SpeakerMessage] = []
|
|
||||||
current_turn = 0
|
|
||||||
|
|
||||||
while current_turn < self.group_chat_config.max_loops:
|
|
||||||
turn_context = {
|
|
||||||
"turn": current_turn,
|
|
||||||
"history": messages,
|
|
||||||
**(context or {}),
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.group_chat_config.allow_concurrent:
|
|
||||||
turn_messages = await self.run_concurrent_speakers(
|
|
||||||
(
|
|
||||||
initial_message
|
|
||||||
if current_turn == 0
|
|
||||||
else messages[-1].content
|
|
||||||
),
|
|
||||||
turn_context,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
turn_messages = await self.run_sequential_speakers(
|
|
||||||
(
|
|
||||||
initial_message
|
|
||||||
if current_turn == 0
|
|
||||||
else messages[-1].content
|
|
||||||
),
|
|
||||||
turn_context,
|
|
||||||
)
|
|
||||||
|
|
||||||
messages.extend(turn_messages)
|
|
||||||
|
|
||||||
# Check if we should continue the conversation
|
|
||||||
if self._should_end_group_chat(messages):
|
|
||||||
break
|
|
||||||
|
|
||||||
current_turn += 1
|
|
||||||
|
|
||||||
if self.group_chat_config.save_history:
|
|
||||||
self.speaker_system.message_history.extend(messages)
|
|
||||||
|
|
||||||
return messages
|
|
||||||
|
|
||||||
def _should_end_group_chat(
|
|
||||||
self, messages: List[SpeakerMessage]
|
|
||||||
) -> bool:
|
|
||||||
"""Determine if group chat should end based on messages"""
|
|
||||||
if not messages:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Check if all required speakers have participated
|
|
||||||
if self.group_chat_config.require_all_speakers:
|
|
||||||
participating_roles = {msg.role for msg in messages}
|
|
||||||
required_roles = {
|
|
||||||
role
|
|
||||||
for role, config in self.speaker_system.speakers.items()
|
|
||||||
if config.required
|
|
||||||
}
|
|
||||||
if not required_roles.issubset(participating_roles):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def task_context(self):
|
|
||||||
"""Context manager for task execution with proper cleanup"""
|
|
||||||
start_time = datetime.utcnow()
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
end_time = datetime.utcnow()
|
|
||||||
if self.autosave:
|
|
||||||
await self._save_results(start_time, end_time)
|
|
||||||
|
|
||||||
async def _execute_agent_task(
|
|
||||||
self, agent: Agent, task: str
|
|
||||||
) -> AgentOutput:
|
|
||||||
"""Execute a single agent task with enhanced error handling and monitoring"""
|
|
||||||
start_time = datetime.utcnow()
|
|
||||||
task_id = str(uuid.uuid4())
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.logger.info(
|
|
||||||
f"Agent {agent.agent_name} starting task {task_id}: {task}"
|
|
||||||
)
|
|
||||||
|
|
||||||
result = await agent.arun(task)
|
|
||||||
|
|
||||||
end_time = datetime.utcnow()
|
|
||||||
self.logger.info(
|
|
||||||
f"Agent {agent.agent_name} completed task {task_id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return AgentOutput(
|
|
||||||
agent_id=str(id(agent)),
|
|
||||||
agent_name=agent.agent_name,
|
|
||||||
task_id=task_id,
|
|
||||||
input=task,
|
|
||||||
output=result,
|
|
||||||
start_time=start_time,
|
|
||||||
end_time=end_time,
|
|
||||||
status="success",
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
end_time = datetime.utcnow()
|
|
||||||
self.logger.error(
|
|
||||||
f"Error in agent {agent.agent_name} task {task_id}: {str(e)}",
|
|
||||||
exc_info=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return AgentOutput(
|
|
||||||
agent_id=str(id(agent)),
|
|
||||||
agent_name=agent.agent_name,
|
|
||||||
task_id=task_id,
|
|
||||||
input=task,
|
|
||||||
output=None,
|
|
||||||
start_time=start_time,
|
|
||||||
end_time=end_time,
|
|
||||||
status="error",
|
|
||||||
error=str(e),
|
|
||||||
)
|
|
||||||
|
|
||||||
async def run(self, task: str) -> WorkflowOutput:
|
|
||||||
"""Enhanced workflow execution with speaker system integration"""
|
|
||||||
if not self.agents:
|
|
||||||
raise ValueError("No agents provided to the workflow")
|
|
||||||
|
|
||||||
async with self.task_context():
|
|
||||||
start_time = datetime.utcnow()
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Run speakers first if enabled
|
|
||||||
speaker_outputs = []
|
|
||||||
if self.enable_group_chat:
|
|
||||||
speaker_outputs = await self.run_group_chat(task)
|
|
||||||
else:
|
|
||||||
concurrent_outputs = (
|
|
||||||
await self.run_concurrent_speakers(task)
|
|
||||||
)
|
|
||||||
sequential_outputs = (
|
|
||||||
await self.run_sequential_speakers(task)
|
|
||||||
)
|
|
||||||
speaker_outputs = (
|
|
||||||
concurrent_outputs + sequential_outputs
|
|
||||||
)
|
|
||||||
|
|
||||||
# Store speaker outputs in shared memory
|
|
||||||
self.shared_memory.set(
|
|
||||||
"speaker_outputs",
|
|
||||||
[msg.dict() for msg in speaker_outputs],
|
|
||||||
"workflow",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create tasks for all agents
|
|
||||||
tasks = [
|
|
||||||
self._execute_agent_task(agent, task)
|
|
||||||
for agent in self.agents
|
|
||||||
]
|
|
||||||
|
|
||||||
# Execute all tasks concurrently
|
|
||||||
agent_outputs = await asyncio.gather(
|
|
||||||
*tasks, return_exceptions=True
|
|
||||||
)
|
|
||||||
|
|
||||||
end_time = datetime.utcnow()
|
|
||||||
|
|
||||||
# Calculate success/failure counts
|
|
||||||
successful_tasks = sum(
|
|
||||||
1
|
|
||||||
for output in agent_outputs
|
|
||||||
if isinstance(output, AgentOutput)
|
|
||||||
and output.status == "success"
|
|
||||||
)
|
|
||||||
failed_tasks = len(agent_outputs) - successful_tasks
|
|
||||||
|
|
||||||
return WorkflowOutput(
|
|
||||||
workflow_id=self.workflow_id,
|
|
||||||
workflow_name=self.name,
|
|
||||||
start_time=start_time,
|
|
||||||
end_time=end_time,
|
|
||||||
total_agents=len(self.agents),
|
|
||||||
successful_tasks=successful_tasks,
|
|
||||||
failed_tasks=failed_tasks,
|
|
||||||
agent_outputs=[
|
|
||||||
output
|
|
||||||
for output in agent_outputs
|
|
||||||
if isinstance(output, AgentOutput)
|
|
||||||
],
|
|
||||||
metadata={
|
|
||||||
"max_workers": self.max_workers,
|
|
||||||
"shared_memory_keys": list(
|
|
||||||
self.shared_memory._memory.keys()
|
|
||||||
),
|
|
||||||
"group_chat_enabled": self.enable_group_chat,
|
|
||||||
"total_speaker_messages": len(
|
|
||||||
speaker_outputs
|
|
||||||
),
|
|
||||||
"speaker_outputs": [
|
|
||||||
msg.dict() for msg in speaker_outputs
|
|
||||||
],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(
|
|
||||||
f"Critical workflow error: {str(e)}",
|
|
||||||
exc_info=True,
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
|
|
||||||
async def _save_results(
|
|
||||||
self, start_time: datetime, end_time: datetime
|
|
||||||
) -> None:
|
|
||||||
"""Save workflow results to disk"""
|
|
||||||
if not self.autosave:
|
|
||||||
return
|
|
||||||
|
|
||||||
output_dir = "workflow_outputs"
|
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
|
|
||||||
filename = f"{output_dir}/workflow_{self.workflow_id}_{end_time.strftime('%Y%m%d_%H%M%S')}.json"
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(filename, "w") as f:
|
|
||||||
json.dump(
|
|
||||||
{
|
|
||||||
"workflow_id": self.workflow_id,
|
|
||||||
"start_time": start_time.isoformat(),
|
|
||||||
"end_time": end_time.isoformat(),
|
|
||||||
"results": [
|
|
||||||
(
|
|
||||||
asdict(result)
|
|
||||||
if hasattr(result, "__dict__")
|
|
||||||
else (
|
|
||||||
result.dict()
|
|
||||||
if hasattr(result, "dict")
|
|
||||||
else str(result)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for result in self.results
|
|
||||||
],
|
|
||||||
"speaker_history": [
|
|
||||||
msg.dict()
|
|
||||||
for msg in self.speaker_system.message_history
|
|
||||||
],
|
|
||||||
"metadata": self.metadata,
|
|
||||||
},
|
|
||||||
f,
|
|
||||||
default=str,
|
|
||||||
indent=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.logger.info(f"Workflow results saved to {filename}")
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(
|
|
||||||
f"Error saving workflow results: {str(e)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _validate_config(self) -> None:
|
|
||||||
"""Validate workflow configuration"""
|
|
||||||
if self.max_workers < 1:
|
|
||||||
raise ValueError("max_workers must be at least 1")
|
|
||||||
|
|
||||||
if (
|
|
||||||
self.enable_group_chat
|
|
||||||
and not self.speaker_system.speakers
|
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
"Group chat enabled but no speakers configured"
|
|
||||||
)
|
|
||||||
|
|
||||||
for config in self.speaker_system.speakers.values():
|
|
||||||
if config.timeout <= 0:
|
|
||||||
raise ValueError(
|
|
||||||
f"Invalid timeout for speaker {config.role}"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def cleanup(self) -> None:
|
|
||||||
"""Cleanup workflow resources"""
|
|
||||||
try:
|
|
||||||
# Close any open file handlers
|
|
||||||
for handler in self.logger.handlers[:]:
|
|
||||||
handler.close()
|
|
||||||
self.logger.removeHandler(handler)
|
|
||||||
|
|
||||||
# Persist final state
|
|
||||||
if self.autosave:
|
|
||||||
end_time = datetime.utcnow()
|
|
||||||
await self._save_results(
|
|
||||||
(
|
|
||||||
self.results[0].start_time
|
|
||||||
if self.results
|
|
||||||
else end_time
|
|
||||||
),
|
|
||||||
end_time,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Clear shared memory if configured
|
|
||||||
self.shared_memory._memory.clear()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Error during cleanup: {str(e)}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
# Utility functions for the workflow
|
|
||||||
def create_default_workflow(
|
|
||||||
agents: List[Agent],
|
|
||||||
name: str = "DefaultWorkflow",
|
|
||||||
enable_group_chat: bool = False,
|
|
||||||
) -> AsyncWorkflow:
|
|
||||||
"""Create a workflow with default configuration"""
|
|
||||||
workflow = AsyncWorkflow(
|
|
||||||
name=name,
|
|
||||||
agents=agents,
|
|
||||||
max_workers=len(agents),
|
|
||||||
dashboard=True,
|
|
||||||
autosave=True,
|
|
||||||
verbose=True,
|
|
||||||
enable_group_chat=enable_group_chat,
|
|
||||||
group_chat_config=GroupChatConfig(
|
|
||||||
max_loops=5,
|
|
||||||
allow_concurrent=True,
|
|
||||||
require_all_speakers=False,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
workflow.add_default_speakers()
|
|
||||||
return workflow
|
|
||||||
|
|
||||||
|
|
||||||
async def run_workflow_with_retry(
|
|
||||||
workflow: AsyncWorkflow,
|
|
||||||
task: str,
|
|
||||||
max_retries: int = 3,
|
|
||||||
retry_delay: float = 1.0,
|
|
||||||
) -> WorkflowOutput:
|
|
||||||
"""Run workflow with retry logic"""
|
|
||||||
for attempt in range(max_retries):
|
|
||||||
try:
|
|
||||||
return await workflow.run(task)
|
|
||||||
except Exception as e:
|
|
||||||
if attempt == max_retries - 1:
|
|
||||||
raise
|
|
||||||
workflow.logger.warning(
|
|
||||||
f"Attempt {attempt + 1} failed, retrying in {retry_delay} seconds: {str(e)}"
|
|
||||||
)
|
|
||||||
await asyncio.sleep(retry_delay)
|
|
||||||
retry_delay *= 2 # Exponential backoff
|
|
||||||
|
|
||||||
|
|
||||||
# async def create_specialized_agents() -> List[Agent]:
|
|
||||||
# """Create a set of specialized agents for financial analysis"""
|
|
||||||
|
|
||||||
# # Base model configuration
|
|
||||||
# model = OpenAIChat(model_name="gpt-4o")
|
|
||||||
|
|
||||||
# # Financial Analysis Agent
|
|
||||||
# financial_agent = Agent(
|
|
||||||
# agent_name="Financial-Analysis-Agent",
|
|
||||||
# agent_description="Personal finance advisor agent",
|
|
||||||
# system_prompt=FINANCIAL_AGENT_SYS_PROMPT +
|
|
||||||
# "Output the <DONE> token when you're done creating a portfolio of etfs, index, funds, and more for AI",
|
|
||||||
# max_loops=1,
|
|
||||||
# llm=model,
|
|
||||||
# dynamic_temperature_enabled=True,
|
|
||||||
# user_name="Kye",
|
|
||||||
# retry_attempts=3,
|
|
||||||
# context_length=8192,
|
|
||||||
# return_step_meta=False,
|
|
||||||
# output_type="str",
|
|
||||||
# auto_generate_prompt=False,
|
|
||||||
# max_tokens=4000,
|
|
||||||
# stopping_token="<DONE>",
|
|
||||||
# saved_state_path="financial_agent.json",
|
|
||||||
# interactive=False,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Risk Assessment Agent
|
|
||||||
# risk_agent = Agent(
|
|
||||||
# agent_name="Risk-Assessment-Agent",
|
|
||||||
# agent_description="Investment risk analysis specialist",
|
|
||||||
# system_prompt="Analyze investment risks and provide risk scores. Output <DONE> when analysis is complete.",
|
|
||||||
# max_loops=1,
|
|
||||||
# llm=model,
|
|
||||||
# dynamic_temperature_enabled=True,
|
|
||||||
# user_name="Kye",
|
|
||||||
# retry_attempts=3,
|
|
||||||
# context_length=8192,
|
|
||||||
# output_type="str",
|
|
||||||
# max_tokens=4000,
|
|
||||||
# stopping_token="<DONE>",
|
|
||||||
# saved_state_path="risk_agent.json",
|
|
||||||
# interactive=False,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Market Research Agent
|
|
||||||
# research_agent = Agent(
|
|
||||||
# agent_name="Market-Research-Agent",
|
|
||||||
# agent_description="AI and tech market research specialist",
|
|
||||||
# system_prompt="Research AI market trends and growth opportunities. Output <DONE> when research is complete.",
|
|
||||||
# max_loops=1,
|
|
||||||
# llm=model,
|
|
||||||
# dynamic_temperature_enabled=True,
|
|
||||||
# user_name="Kye",
|
|
||||||
# retry_attempts=3,
|
|
||||||
# context_length=8192,
|
|
||||||
# output_type="str",
|
|
||||||
# max_tokens=4000,
|
|
||||||
# stopping_token="<DONE>",
|
|
||||||
# saved_state_path="research_agent.json",
|
|
||||||
# interactive=False,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# return [financial_agent, risk_agent, research_agent]
|
|
||||||
|
|
||||||
# async def main():
|
|
||||||
# # Create specialized agents
|
|
||||||
# agents = await create_specialized_agents()
|
|
||||||
|
|
||||||
# # Create workflow with group chat enabled
|
|
||||||
# workflow = create_default_workflow(
|
|
||||||
# agents=agents,
|
|
||||||
# name="AI-Investment-Analysis-Workflow",
|
|
||||||
# enable_group_chat=True
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Configure speaker roles
|
|
||||||
# workflow.speaker_system.add_speaker(
|
|
||||||
# SpeakerConfig(
|
|
||||||
# role=SpeakerRole.COORDINATOR,
|
|
||||||
# agent=agents[0], # Financial agent as coordinator
|
|
||||||
# priority=1,
|
|
||||||
# concurrent=False,
|
|
||||||
# required=True
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
|
|
||||||
# workflow.speaker_system.add_speaker(
|
|
||||||
# SpeakerConfig(
|
|
||||||
# role=SpeakerRole.CRITIC,
|
|
||||||
# agent=agents[1], # Risk agent as critic
|
|
||||||
# priority=2,
|
|
||||||
# concurrent=True
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
|
|
||||||
# workflow.speaker_system.add_speaker(
|
|
||||||
# SpeakerConfig(
|
|
||||||
# role=SpeakerRole.EXECUTOR,
|
|
||||||
# agent=agents[2], # Research agent as executor
|
|
||||||
# priority=2,
|
|
||||||
# concurrent=True
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Investment analysis task
|
|
||||||
# investment_task = """
|
|
||||||
# Create a comprehensive investment analysis for a $40k portfolio focused on AI growth opportunities:
|
|
||||||
# 1. Identify high-growth AI ETFs and index funds
|
|
||||||
# 2. Analyze risks and potential returns
|
|
||||||
# 3. Create a diversified portfolio allocation
|
|
||||||
# 4. Provide market trend analysis
|
|
||||||
# Present the results in a structured markdown format.
|
|
||||||
# """
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# # Run workflow with retry
|
|
||||||
# result = await run_workflow_with_retry(
|
|
||||||
# workflow=workflow,
|
|
||||||
# task=investment_task,
|
|
||||||
# max_retries=3
|
|
||||||
# )
|
|
||||||
|
|
||||||
# print("\nWorkflow Results:")
|
|
||||||
# print("================")
|
|
||||||
|
|
||||||
# # Process and display agent outputs
|
|
||||||
# for output in result.agent_outputs:
|
|
||||||
# print(f"\nAgent: {output.agent_name}")
|
|
||||||
# print("-" * (len(output.agent_name) + 8))
|
|
||||||
# print(output.output)
|
|
||||||
|
|
||||||
# # Display group chat history if enabled
|
|
||||||
# if workflow.enable_group_chat:
|
|
||||||
# print("\nGroup Chat Discussion:")
|
|
||||||
# print("=====================")
|
|
||||||
# for msg in workflow.speaker_system.message_history:
|
|
||||||
# print(f"\n{msg.role} ({msg.agent_name}):")
|
|
||||||
# print(msg.content)
|
|
||||||
|
|
||||||
# # Save detailed results
|
|
||||||
# if result.metadata.get("shared_memory_keys"):
|
|
||||||
# print("\nShared Insights:")
|
|
||||||
# print("===============")
|
|
||||||
# for key in result.metadata["shared_memory_keys"]:
|
|
||||||
# value = workflow.shared_memory.get(key)
|
|
||||||
# if value:
|
|
||||||
# print(f"\n{key}:")
|
|
||||||
# print(value)
|
|
||||||
|
|
||||||
# except Exception as e:
|
|
||||||
# print(f"Workflow failed: {str(e)}")
|
|
||||||
|
|
||||||
# finally:
|
|
||||||
# await workflow.cleanup()
|
|
||||||
|
|
||||||
# if __name__ == "__main__":
|
|
||||||
# # Run the example
|
|
||||||
# asyncio.run(main())
|
|
@ -1,844 +0,0 @@
|
|||||||
"""
|
|
||||||
OctoToolsSwarm: A multi-agent system for complex reasoning.
|
|
||||||
Implements the OctoTools framework using swarms.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from enum import Enum
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Callable, Dict, List, Optional
|
|
||||||
import math # Import the math module
|
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from swarms import Agent
|
|
||||||
from swarms.structs.conversation import Conversation
|
|
||||||
|
|
||||||
# from exa_search import exa_search as web_search_execute
|
|
||||||
|
|
||||||
|
|
||||||
# Load environment variables
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
# Setup logging
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class ToolType(Enum):
|
|
||||||
"""Defines the types of tools available."""
|
|
||||||
|
|
||||||
IMAGE_CAPTIONER = "image_captioner"
|
|
||||||
OBJECT_DETECTOR = "object_detector"
|
|
||||||
WEB_SEARCH = "web_search"
|
|
||||||
PYTHON_CALCULATOR = "python_calculator"
|
|
||||||
# Add more tool types as needed
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Tool:
|
|
||||||
"""
|
|
||||||
Represents an external tool.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
name: Unique name of the tool.
|
|
||||||
description: Description of the tool's function.
|
|
||||||
metadata: Dictionary containing tool metadata.
|
|
||||||
execute_func: Callable function that executes the tool's logic.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
description: str
|
|
||||||
metadata: Dict[str, Any]
|
|
||||||
execute_func: Callable
|
|
||||||
|
|
||||||
def execute(self, **kwargs):
|
|
||||||
"""Executes the tool's logic, handling potential errors."""
|
|
||||||
try:
|
|
||||||
return self.execute_func(**kwargs)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"Error executing tool {self.name}: {str(e)}"
|
|
||||||
)
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
|
|
||||||
class AgentRole(Enum):
|
|
||||||
"""Defines the roles for agents in the OctoTools system."""
|
|
||||||
|
|
||||||
PLANNER = "planner"
|
|
||||||
VERIFIER = "verifier"
|
|
||||||
SUMMARIZER = "summarizer"
|
|
||||||
|
|
||||||
|
|
||||||
class OctoToolsSwarm:
|
|
||||||
"""
|
|
||||||
A multi-agent system implementing the OctoTools framework.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
model_name: Name of the LLM model to use.
|
|
||||||
max_iterations: Maximum number of action-execution iterations.
|
|
||||||
base_path: Path for saving agent states.
|
|
||||||
tools: List of available Tool objects.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
tools: List[Tool],
|
|
||||||
model_name: str = "gemini/gemini-2.0-flash",
|
|
||||||
max_iterations: int = 10,
|
|
||||||
base_path: Optional[str] = None,
|
|
||||||
):
|
|
||||||
"""Initialize the OctoToolsSwarm system."""
|
|
||||||
self.model_name = model_name
|
|
||||||
self.max_iterations = max_iterations
|
|
||||||
self.base_path = (
|
|
||||||
Path(base_path)
|
|
||||||
if base_path
|
|
||||||
else Path("./octotools_states")
|
|
||||||
)
|
|
||||||
self.base_path.mkdir(exist_ok=True)
|
|
||||||
self.tools = {
|
|
||||||
tool.name: tool for tool in tools
|
|
||||||
} # Store tools in a dictionary
|
|
||||||
|
|
||||||
# Initialize agents
|
|
||||||
self._init_agents()
|
|
||||||
|
|
||||||
# Create conversation tracker and memory
|
|
||||||
self.conversation = Conversation()
|
|
||||||
self.memory = [] # Store the trajectory
|
|
||||||
|
|
||||||
def _init_agents(self) -> None:
|
|
||||||
"""Initialize all agents with their specific roles and prompts."""
|
|
||||||
# Planner agent
|
|
||||||
self.planner = Agent(
|
|
||||||
agent_name="OctoTools-Planner",
|
|
||||||
system_prompt=self._get_planner_prompt(),
|
|
||||||
model_name=self.model_name,
|
|
||||||
max_loops=3,
|
|
||||||
saved_state_path=str(self.base_path / "planner.json"),
|
|
||||||
verbose=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verifier agent
|
|
||||||
self.verifier = Agent(
|
|
||||||
agent_name="OctoTools-Verifier",
|
|
||||||
system_prompt=self._get_verifier_prompt(),
|
|
||||||
model_name=self.model_name,
|
|
||||||
max_loops=1,
|
|
||||||
saved_state_path=str(self.base_path / "verifier.json"),
|
|
||||||
verbose=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Summarizer agent
|
|
||||||
self.summarizer = Agent(
|
|
||||||
agent_name="OctoTools-Summarizer",
|
|
||||||
system_prompt=self._get_summarizer_prompt(),
|
|
||||||
model_name=self.model_name,
|
|
||||||
max_loops=1,
|
|
||||||
saved_state_path=str(self.base_path / "summarizer.json"),
|
|
||||||
verbose=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_planner_prompt(self) -> str:
|
|
||||||
"""Get the prompt for the planner agent (Improved with few-shot examples)."""
|
|
||||||
tool_descriptions = "\n".join(
|
|
||||||
[
|
|
||||||
f"- {tool_name}: {self.tools[tool_name].description}"
|
|
||||||
for tool_name in self.tools
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return f"""You are the Planner in the OctoTools framework. Your role is to analyze the user's query,
|
|
||||||
identify required skills, suggest relevant tools, and plan the steps to solve the problem.
|
|
||||||
|
|
||||||
1. **Analyze the user's query:** Understand the requirements and identify the necessary skills and potentially relevant tools.
|
|
||||||
2. **Perform high-level planning:** Create a rough outline of how tools might be used to solve the problem.
|
|
||||||
3. **Perform low-level planning (action prediction):** At each step, select the best tool to use and formulate a specific sub-goal for that tool, considering the current context.
|
|
||||||
|
|
||||||
Available Tools:
|
|
||||||
{tool_descriptions}
|
|
||||||
|
|
||||||
Output your response in JSON format. Here are examples for different stages:
|
|
||||||
|
|
||||||
**Query Analysis (High-Level Planning):**
|
|
||||||
Example Input:
|
|
||||||
Query: "What is the capital of France?"
|
|
||||||
|
|
||||||
Example Output:
|
|
||||||
```json
|
|
||||||
{{
|
|
||||||
"summary": "The user is asking for the capital of France.",
|
|
||||||
"required_skills": ["knowledge retrieval"],
|
|
||||||
"relevant_tools": ["Web_Search_Tool"]
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Action Prediction (Low-Level Planning):**
|
|
||||||
Example Input:
|
|
||||||
Context: {{ "query": "What is the capital of France?", "available_tools": ["Web_Search_Tool"] }}
|
|
||||||
|
|
||||||
Example Output:
|
|
||||||
```json
|
|
||||||
{{
|
|
||||||
"justification": "The Web_Search_Tool can be used to directly find the capital of France.",
|
|
||||||
"context": {{}},
|
|
||||||
"sub_goal": "Search the web for 'capital of France'.",
|
|
||||||
"tool_name": "Web_Search_Tool"
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
Another Example:
|
|
||||||
Context: {{"query": "How many objects are in the image?", "available_tools": ["Image_Captioner_Tool", "Object_Detector_Tool"], "image": "objects.png"}}
|
|
||||||
|
|
||||||
Example Output:
|
|
||||||
```json
|
|
||||||
{{
|
|
||||||
"justification": "First, get a general description of the image to understand the context.",
|
|
||||||
"context": {{ "image": "objects.png" }},
|
|
||||||
"sub_goal": "Generate a description of the image.",
|
|
||||||
"tool_name": "Image_Captioner_Tool"
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
|
|
||||||
Example for Finding Square Root:
|
|
||||||
Context: {{"query": "What is the square root of the number of objects in the image?", "available_tools": ["Object_Detector_Tool", "Python_Calculator_Tool"], "image": "objects.png", "Object_Detector_Tool_result": ["object1", "object2", "object3", "object4"]}}
|
|
||||||
|
|
||||||
Example Output:
|
|
||||||
```json
|
|
||||||
{{
|
|
||||||
"justification": "We have detected 4 objects in the image. Now we need to find the square root of 4.",
|
|
||||||
"context": {{}},
|
|
||||||
"sub_goal": "Calculate the square root of 4",
|
|
||||||
"tool_name": "Python_Calculator_Tool"
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
|
|
||||||
Your output MUST be a single, valid JSON object with the following keys:
|
|
||||||
- justification (string): Your reasoning.
|
|
||||||
- context (dict): A dictionary containing relevant information.
|
|
||||||
- sub_goal (string): The specific instruction for the tool.
|
|
||||||
- tool_name (string): The EXACT name of the tool to use.
|
|
||||||
|
|
||||||
Do NOT include any text outside of the JSON object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _get_verifier_prompt(self) -> str:
|
|
||||||
"""Get the prompt for the verifier agent (Improved with few-shot examples)."""
|
|
||||||
return """You are the Context Verifier in the OctoTools framework. Your role is to analyze the current context
|
|
||||||
and memory to determine if the problem is solved, if there are any inconsistencies, or if further steps are needed.
|
|
||||||
|
|
||||||
Output your response in JSON format:
|
|
||||||
|
|
||||||
Expected output structure:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"completeness": "Indicate whether the query is fully, partially, or not answered.",
|
|
||||||
"inconsistencies": "List any inconsistencies found in the context or memory.",
|
|
||||||
"verification_needs": "List any information that needs further verification.",
|
|
||||||
"ambiguities": "List any ambiguities found in the context or memory.",
|
|
||||||
"stop_signal": true/false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Example Input:
|
|
||||||
Context: { "last_result": { "result": "Caption: The image shows a cat." } }
|
|
||||||
Memory: [ { "component": "Action Predictor", "result": { "tool_name": "Image_Captioner_Tool" } } ]
|
|
||||||
|
|
||||||
Example Output:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"completeness": "partial",
|
|
||||||
"inconsistencies": [],
|
|
||||||
"verification_needs": ["Object detection to confirm the presence of a cat."],
|
|
||||||
"ambiguities": [],
|
|
||||||
"stop_signal": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Another Example:
|
|
||||||
Context: { "last_result": { "result": ["Detected object: cat"] } }
|
|
||||||
Memory: [ { "component": "Action Predictor", "result": { "tool_name": "Object_Detector_Tool" } } ]
|
|
||||||
|
|
||||||
Example Output:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"completeness": "yes",
|
|
||||||
"inconsistencies": [],
|
|
||||||
"verification_needs": [],
|
|
||||||
"ambiguities": [],
|
|
||||||
"stop_signal": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Square Root Example:
|
|
||||||
Context: {
|
|
||||||
"query": "What is the square root of the number of objects in the image?",
|
|
||||||
"image": "example.png",
|
|
||||||
"Object_Detector_Tool_result": ["object1", "object2", "object3", "object4"],
|
|
||||||
"Python_Calculator_Tool_result": "Result of 4**0.5 is 2.0"
|
|
||||||
}
|
|
||||||
Memory: [
|
|
||||||
{ "component": "Action Predictor", "result": { "tool_name": "Object_Detector_Tool" } },
|
|
||||||
{ "component": "Action Predictor", "result": { "tool_name": "Python_Calculator_Tool" } }
|
|
||||||
]
|
|
||||||
|
|
||||||
Example Output:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"completeness": "yes",
|
|
||||||
"inconsistencies": [],
|
|
||||||
"verification_needs": [],
|
|
||||||
"ambiguities": [],
|
|
||||||
"stop_signal": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _get_summarizer_prompt(self) -> str:
|
|
||||||
"""Get the prompt for the summarizer agent (Improved with few-shot examples)."""
|
|
||||||
return """You are the Solution Summarizer in the OctoTools framework. Your role is to synthesize the final
|
|
||||||
answer to the user's query based on the complete trajectory of actions and results.
|
|
||||||
|
|
||||||
Output your response in JSON format:
|
|
||||||
|
|
||||||
Expected output structure:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"final_answer": "Provide a clear and concise answer to the original query."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Example Input:
|
|
||||||
Memory: [
|
|
||||||
{"component": "Query Analyzer", "result": {"summary": "Find the capital of France."}},
|
|
||||||
{"component": "Action Predictor", "result": {"tool_name": "Web_Search_Tool"}},
|
|
||||||
{"component": "Tool Execution", "result": {"result": "The capital of France is Paris."}}
|
|
||||||
]
|
|
||||||
|
|
||||||
Example Output:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"final_answer": "The capital of France is Paris."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Square Root Example:
|
|
||||||
Memory: [
|
|
||||||
{"component": "Query Analyzer", "result": {"summary": "Find the square root of the number of objects in the image."}},
|
|
||||||
{"component": "Action Predictor", "result": {"tool_name": "Object_Detector_Tool", "sub_goal": "Detect objects in the image"}},
|
|
||||||
{"component": "Tool Execution", "result": {"result": ["object1", "object2", "object3", "object4"]}},
|
|
||||||
{"component": "Action Predictor", "result": {"tool_name": "Python_Calculator_Tool", "sub_goal": "Calculate the square root of 4"}},
|
|
||||||
{"component": "Tool Execution", "result": {"result": "Result of 4**0.5 is 2.0"}}
|
|
||||||
]
|
|
||||||
|
|
||||||
Example Output:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"final_answer": "The square root of the number of objects in the image is 2.0. There are 4 objects in the image, and the square root of 4 is 2.0."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _safely_parse_json(self, json_str: str) -> Dict[str, Any]:
|
|
||||||
"""Safely parse JSON, handling errors and using recursive descent."""
|
|
||||||
try:
|
|
||||||
return json.loads(json_str)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
logger.warning(
|
|
||||||
f"JSONDecodeError: Attempting to extract JSON from: {json_str}"
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
# More robust JSON extraction with recursive descent
|
|
||||||
def extract_json(s):
|
|
||||||
stack = []
|
|
||||||
start = -1
|
|
||||||
for i, c in enumerate(s):
|
|
||||||
if c == "{":
|
|
||||||
if not stack:
|
|
||||||
start = i
|
|
||||||
stack.append(c)
|
|
||||||
elif c == "}":
|
|
||||||
if stack:
|
|
||||||
stack.pop()
|
|
||||||
if not stack and start != -1:
|
|
||||||
return s[start : i + 1]
|
|
||||||
return None
|
|
||||||
|
|
||||||
extracted_json = extract_json(json_str)
|
|
||||||
if extracted_json:
|
|
||||||
logger.info(f"Extracted JSON: {extracted_json}")
|
|
||||||
return json.loads(extracted_json)
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
"Failed to extract JSON using recursive descent."
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"error": "Failed to parse JSON",
|
|
||||||
"content": json_str,
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(f"Error during JSON extraction: {e}")
|
|
||||||
return {
|
|
||||||
"error": "Failed to parse JSON",
|
|
||||||
"content": json_str,
|
|
||||||
}
|
|
||||||
|
|
||||||
def _execute_tool(
|
|
||||||
self, tool_name: str, context: Dict[str, Any]
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""Executes a tool based on its name and provided context."""
|
|
||||||
if tool_name not in self.tools:
|
|
||||||
return {"error": f"Tool '{tool_name}' not found."}
|
|
||||||
|
|
||||||
tool = self.tools[tool_name]
|
|
||||||
try:
|
|
||||||
# For Python Calculator tool, handle object counts from Object Detector
|
|
||||||
if tool_name == "Python_Calculator_Tool":
|
|
||||||
# Check for object detector results
|
|
||||||
object_detector_result = context.get(
|
|
||||||
"Object_Detector_Tool_result"
|
|
||||||
)
|
|
||||||
if object_detector_result and isinstance(
|
|
||||||
object_detector_result, list
|
|
||||||
):
|
|
||||||
# Calculate the number of objects
|
|
||||||
num_objects = len(object_detector_result)
|
|
||||||
# If sub_goal doesn't already contain an expression, create one
|
|
||||||
if (
|
|
||||||
"sub_goal" in context
|
|
||||||
and "Calculate the square root"
|
|
||||||
in context["sub_goal"]
|
|
||||||
):
|
|
||||||
context["expression"] = f"{num_objects}**0.5"
|
|
||||||
elif "expression" not in context:
|
|
||||||
# Default to square root if no expression is specified
|
|
||||||
context["expression"] = f"{num_objects}**0.5"
|
|
||||||
|
|
||||||
# Filter context: only pass expected inputs to the tool
|
|
||||||
valid_inputs = {
|
|
||||||
k: v
|
|
||||||
for k, v in context.items()
|
|
||||||
if k in tool.metadata.get("input_types", {})
|
|
||||||
}
|
|
||||||
result = tool.execute(**valid_inputs)
|
|
||||||
return {"result": result}
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(f"Error executing tool {tool_name}: {e}")
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
def _run_agent(
|
|
||||||
self, agent: Agent, input_prompt: str
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""Runs a swarms agent, handling output and JSON parsing."""
|
|
||||||
try:
|
|
||||||
# Construct the full input, including the system prompt
|
|
||||||
full_input = f"{agent.system_prompt}\n\n{input_prompt}"
|
|
||||||
|
|
||||||
# Run the agent and capture the output
|
|
||||||
agent_response = agent.run(full_input)
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"DEBUG: Raw agent response: {agent_response}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Extract the LLM's response (remove conversation history, etc.)
|
|
||||||
response_text = agent_response # Assuming direct return
|
|
||||||
|
|
||||||
# Try to parse the response as JSON
|
|
||||||
parsed_response = self._safely_parse_json(response_text)
|
|
||||||
|
|
||||||
return parsed_response
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(
|
|
||||||
f"Error running agent {agent.agent_name}: {e}"
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"error": f"Agent {agent.agent_name} failed: {str(e)}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def run(
|
|
||||||
self, query: str, image: Optional[str] = None
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""Execute the task through the multi-agent workflow."""
|
|
||||||
logger.info(f"Starting task: {query}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Step 1: Query Analysis (High-Level Planning)
|
|
||||||
planner_input = (
|
|
||||||
f"Analyze the following query and determine the necessary skills and"
|
|
||||||
f" relevant tools: {query}"
|
|
||||||
)
|
|
||||||
query_analysis = self._run_agent(
|
|
||||||
self.planner, planner_input
|
|
||||||
)
|
|
||||||
|
|
||||||
if "error" in query_analysis:
|
|
||||||
return {
|
|
||||||
"error": f"Planner query analysis failed: {query_analysis['error']}",
|
|
||||||
"trajectory": self.memory,
|
|
||||||
"conversation": self.conversation.return_history_as_string(),
|
|
||||||
}
|
|
||||||
|
|
||||||
self.memory.append(
|
|
||||||
{
|
|
||||||
"step": 0,
|
|
||||||
"component": "Query Analyzer",
|
|
||||||
"result": query_analysis,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
self.conversation.add(
|
|
||||||
role=self.planner.agent_name,
|
|
||||||
content=json.dumps(query_analysis),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize context with the query and image (if provided)
|
|
||||||
context = {"query": query}
|
|
||||||
if image:
|
|
||||||
context["image"] = image
|
|
||||||
|
|
||||||
# Add available tools to context
|
|
||||||
if "relevant_tools" in query_analysis:
|
|
||||||
context["available_tools"] = query_analysis[
|
|
||||||
"relevant_tools"
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
# If no relevant tools specified, make all tools available
|
|
||||||
context["available_tools"] = list(self.tools.keys())
|
|
||||||
|
|
||||||
step_count = 1
|
|
||||||
|
|
||||||
# Step 2: Iterative Action-Execution Loop
|
|
||||||
while step_count <= self.max_iterations:
|
|
||||||
logger.info(
|
|
||||||
f"Starting iteration {step_count} of {self.max_iterations}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Step 2a: Action Prediction (Low-Level Planning)
|
|
||||||
action_planner_input = (
|
|
||||||
f"Current Context: {json.dumps(context)}\nAvailable Tools:"
|
|
||||||
f" {', '.join(context.get('available_tools', list(self.tools.keys())))}\nPlan the"
|
|
||||||
" next step."
|
|
||||||
)
|
|
||||||
action = self._run_agent(
|
|
||||||
self.planner, action_planner_input
|
|
||||||
)
|
|
||||||
if "error" in action:
|
|
||||||
logger.error(
|
|
||||||
f"Error in action prediction: {action['error']}"
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"error": f"Planner action prediction failed: {action['error']}",
|
|
||||||
"trajectory": self.memory,
|
|
||||||
"conversation": self.conversation.return_history_as_string(),
|
|
||||||
}
|
|
||||||
self.memory.append(
|
|
||||||
{
|
|
||||||
"step": step_count,
|
|
||||||
"component": "Action Predictor",
|
|
||||||
"result": action,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
self.conversation.add(
|
|
||||||
role=self.planner.agent_name,
|
|
||||||
content=json.dumps(action),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Input Validation for Action (Relaxed)
|
|
||||||
if (
|
|
||||||
not isinstance(action, dict)
|
|
||||||
or "tool_name" not in action
|
|
||||||
or "sub_goal" not in action
|
|
||||||
):
|
|
||||||
error_msg = (
|
|
||||||
"Action prediction did not return required fields (tool_name,"
|
|
||||||
" sub_goal) or was not a dictionary."
|
|
||||||
)
|
|
||||||
logger.error(error_msg)
|
|
||||||
self.memory.append(
|
|
||||||
{
|
|
||||||
"step": step_count,
|
|
||||||
"component": "Error",
|
|
||||||
"result": error_msg,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
# Step 2b: Execute Tool
|
|
||||||
tool_execution_context = {
|
|
||||||
**context,
|
|
||||||
**action.get(
|
|
||||||
"context", {}
|
|
||||||
), # Add any additional context
|
|
||||||
"sub_goal": action[
|
|
||||||
"sub_goal"
|
|
||||||
], # Pass sub_goal to tool
|
|
||||||
}
|
|
||||||
|
|
||||||
tool_result = self._execute_tool(
|
|
||||||
action["tool_name"], tool_execution_context
|
|
||||||
)
|
|
||||||
|
|
||||||
self.memory.append(
|
|
||||||
{
|
|
||||||
"step": step_count,
|
|
||||||
"component": "Tool Execution",
|
|
||||||
"result": tool_result,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Step 2c: Context Update - Store result with a descriptive key
|
|
||||||
if "result" in tool_result:
|
|
||||||
context[f"{action['tool_name']}_result"] = (
|
|
||||||
tool_result["result"]
|
|
||||||
)
|
|
||||||
if "error" in tool_result:
|
|
||||||
context[f"{action['tool_name']}_error"] = (
|
|
||||||
tool_result["error"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Step 2d: Context Verification
|
|
||||||
verifier_input = (
|
|
||||||
f"Current Context: {json.dumps(context)}\nMemory:"
|
|
||||||
f" {json.dumps(self.memory)}\nQuery: {query}"
|
|
||||||
)
|
|
||||||
verification = self._run_agent(
|
|
||||||
self.verifier, verifier_input
|
|
||||||
)
|
|
||||||
if "error" in verification:
|
|
||||||
return {
|
|
||||||
"error": f"Verifier failed: {verification['error']}",
|
|
||||||
"trajectory": self.memory,
|
|
||||||
"conversation": self.conversation.return_history_as_string(),
|
|
||||||
}
|
|
||||||
|
|
||||||
self.memory.append(
|
|
||||||
{
|
|
||||||
"step": step_count,
|
|
||||||
"component": "Context Verifier",
|
|
||||||
"result": verification,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
self.conversation.add(
|
|
||||||
role=self.verifier.agent_name,
|
|
||||||
content=json.dumps(verification),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check for stop signal from Verifier
|
|
||||||
if verification.get("stop_signal") is True:
|
|
||||||
logger.info(
|
|
||||||
"Received stop signal from verifier. Stopping iterations."
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
# Safety mechanism - if we've executed the same tool multiple times
|
|
||||||
same_tool_count = sum(
|
|
||||||
1
|
|
||||||
for m in self.memory
|
|
||||||
if m.get("component") == "Action Predictor"
|
|
||||||
and m.get("result", {}).get("tool_name")
|
|
||||||
== action.get("tool_name")
|
|
||||||
)
|
|
||||||
|
|
||||||
if same_tool_count > 3:
|
|
||||||
logger.warning(
|
|
||||||
f"Tool {action.get('tool_name')} used more than 3 times. Forcing stop."
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
step_count += 1
|
|
||||||
|
|
||||||
# Step 3: Solution Summarization
|
|
||||||
summarizer_input = f"Complete Trajectory: {json.dumps(self.memory)}\nOriginal Query: {query}"
|
|
||||||
|
|
||||||
summarization = self._run_agent(
|
|
||||||
self.summarizer, summarizer_input
|
|
||||||
)
|
|
||||||
if "error" in summarization:
|
|
||||||
return {
|
|
||||||
"error": f"Summarizer failed: {summarization['error']}",
|
|
||||||
"trajectory": self.memory,
|
|
||||||
"conversation": self.conversation.return_history_as_string(),
|
|
||||||
}
|
|
||||||
self.conversation.add(
|
|
||||||
role=self.summarizer.agent_name,
|
|
||||||
content=json.dumps(summarization),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"final_answer": summarization.get(
|
|
||||||
"final_answer", "No answer found."
|
|
||||||
),
|
|
||||||
"trajectory": self.memory,
|
|
||||||
"conversation": self.conversation.return_history_as_string(),
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(
|
|
||||||
f"Unexpected error in run method: {e}"
|
|
||||||
) # More detailed
|
|
||||||
return {
|
|
||||||
"error": str(e),
|
|
||||||
"trajectory": self.memory,
|
|
||||||
"conversation": self.conversation.return_history_as_string(),
|
|
||||||
}
|
|
||||||
|
|
||||||
def save_state(self) -> None:
|
|
||||||
"""Save the current state of all agents."""
|
|
||||||
for agent in [self.planner, self.verifier, self.summarizer]:
|
|
||||||
try:
|
|
||||||
agent.save_state()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"Error saving state for {agent.agent_name}: {str(e)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def load_state(self) -> None:
|
|
||||||
"""Load the saved state of all agents."""
|
|
||||||
for agent in [self.planner, self.verifier, self.summarizer]:
|
|
||||||
try:
|
|
||||||
agent.load_state()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"Error loading state for {agent.agent_name}: {str(e)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Example Usage ---
|
|
||||||
|
|
||||||
|
|
||||||
# Define dummy tool functions (replace with actual implementations)
|
|
||||||
def image_captioner_execute(
|
|
||||||
image: str, prompt: str = "Describe the image", **kwargs
|
|
||||||
) -> str:
|
|
||||||
"""Dummy image captioner."""
|
|
||||||
print(
|
|
||||||
f"image_captioner_execute called with image: {image}, prompt: {prompt}"
|
|
||||||
)
|
|
||||||
return f"Caption for {image}: A descriptive caption (dummy)." # Simplified
|
|
||||||
|
|
||||||
|
|
||||||
def object_detector_execute(
|
|
||||||
image: str, labels: List[str] = [], **kwargs
|
|
||||||
) -> List[str]:
|
|
||||||
"""Dummy object detector, handles missing labels gracefully."""
|
|
||||||
print(
|
|
||||||
f"object_detector_execute called with image: {image}, labels: {labels}"
|
|
||||||
)
|
|
||||||
if not labels:
|
|
||||||
return [
|
|
||||||
"object1",
|
|
||||||
"object2",
|
|
||||||
"object3",
|
|
||||||
"object4",
|
|
||||||
] # Return default objects if no labels
|
|
||||||
return [f"Detected {label}" for label in labels] # Simplified
|
|
||||||
|
|
||||||
|
|
||||||
def web_search_execute(query: str, **kwargs) -> str:
|
|
||||||
"""Dummy web search."""
|
|
||||||
print(f"web_search_execute called with query: {query}")
|
|
||||||
return f"Search results for '{query}'..." # Simplified
|
|
||||||
|
|
||||||
|
|
||||||
def python_calculator_execute(expression: str, **kwargs) -> str:
|
|
||||||
"""Python calculator (using math module)."""
|
|
||||||
print(f"python_calculator_execute called with: {expression}")
|
|
||||||
try:
|
|
||||||
# Safely evaluate only simple expressions involving numbers and basic operations
|
|
||||||
if re.match(r"^[0-9+\-*/().\s]+$", expression):
|
|
||||||
result = eval(
|
|
||||||
expression, {"__builtins__": {}, "math": math}
|
|
||||||
)
|
|
||||||
return f"Result of {expression} is {result}"
|
|
||||||
else:
|
|
||||||
return "Error: Invalid expression for calculator."
|
|
||||||
except Exception as e:
|
|
||||||
return f"Error: {e}"
|
|
||||||
|
|
||||||
|
|
||||||
# Create utility function to get default tools
|
|
||||||
def get_default_tools() -> List[Tool]:
|
|
||||||
"""Returns a list of default tools that can be used with OctoToolsSwarm."""
|
|
||||||
image_captioner = Tool(
|
|
||||||
name="Image_Captioner_Tool",
|
|
||||||
description="Generates a caption for an image.",
|
|
||||||
metadata={
|
|
||||||
"input_types": {"image": "str", "prompt": "str"},
|
|
||||||
"output_type": "str",
|
|
||||||
"limitations": "May struggle with complex scenes or ambiguous objects.",
|
|
||||||
"best_practices": "Use with clear, well-lit images. Provide specific prompts for better results.",
|
|
||||||
},
|
|
||||||
execute_func=image_captioner_execute,
|
|
||||||
)
|
|
||||||
|
|
||||||
object_detector = Tool(
|
|
||||||
name="Object_Detector_Tool",
|
|
||||||
description="Detects objects in an image.",
|
|
||||||
metadata={
|
|
||||||
"input_types": {"image": "str", "labels": "list"},
|
|
||||||
"output_type": "list",
|
|
||||||
"limitations": "Accuracy depends on the quality of the image and the clarity of the objects.",
|
|
||||||
"best_practices": "Provide a list of specific object labels to detect. Use high-resolution images.",
|
|
||||||
},
|
|
||||||
execute_func=object_detector_execute,
|
|
||||||
)
|
|
||||||
|
|
||||||
web_search = Tool(
|
|
||||||
name="Web_Search_Tool",
|
|
||||||
description="Performs a web search.",
|
|
||||||
metadata={
|
|
||||||
"input_types": {"query": "str"},
|
|
||||||
"output_type": "str",
|
|
||||||
"limitations": "May not find specific or niche information.",
|
|
||||||
"best_practices": "Use specific and descriptive keywords for better results.",
|
|
||||||
},
|
|
||||||
execute_func=web_search_execute,
|
|
||||||
)
|
|
||||||
|
|
||||||
calculator = Tool(
|
|
||||||
name="Python_Calculator_Tool",
|
|
||||||
description="Evaluates a Python expression.",
|
|
||||||
metadata={
|
|
||||||
"input_types": {"expression": "str"},
|
|
||||||
"output_type": "str",
|
|
||||||
"limitations": "Cannot handle complex mathematical functions or libraries.",
|
|
||||||
"best_practices": "Use for basic arithmetic and simple calculations.",
|
|
||||||
},
|
|
||||||
execute_func=python_calculator_execute,
|
|
||||||
)
|
|
||||||
|
|
||||||
return [image_captioner, object_detector, web_search, calculator]
|
|
||||||
|
|
||||||
|
|
||||||
# Only execute the example when this script is run directly
|
|
||||||
# if __name__ == "__main__":
|
|
||||||
# print("Running OctoToolsSwarm example...")
|
|
||||||
|
|
||||||
# # Create an OctoToolsSwarm agent with default tools
|
|
||||||
# tools = get_default_tools()
|
|
||||||
# agent = OctoToolsSwarm(tools=tools)
|
|
||||||
|
|
||||||
# # Example query
|
|
||||||
# query = "What is the square root of the number of objects in this image?"
|
|
||||||
|
|
||||||
# # Create a dummy image file for testing if it doesn't exist
|
|
||||||
# image_path = "example.png"
|
|
||||||
# if not os.path.exists(image_path):
|
|
||||||
# with open(image_path, "w") as f:
|
|
||||||
# f.write("Dummy image content")
|
|
||||||
# print(f"Created dummy image file: {image_path}")
|
|
||||||
|
|
||||||
# # Run the agent
|
|
||||||
# result = agent.run(query, image=image_path)
|
|
||||||
|
|
||||||
# # Display results
|
|
||||||
# print("\n=== FINAL ANSWER ===")
|
|
||||||
# print(result["final_answer"])
|
|
||||||
|
|
||||||
# print("\n=== TRAJECTORY SUMMARY ===")
|
|
||||||
# for step in result["trajectory"]:
|
|
||||||
# print(f"Step {step.get('step', 'N/A')}: {step.get('component', 'Unknown')}")
|
|
||||||
|
|
||||||
# print("\nOctoToolsSwarm example completed.")
|
|
@ -1,469 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
from concurrent.futures import ThreadPoolExecutor, TimeoutError
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Any, Dict, List, Literal, Optional
|
|
||||||
|
|
||||||
import pulsar
|
|
||||||
from cryptography.fernet import Fernet
|
|
||||||
from loguru import logger
|
|
||||||
from prometheus_client import Counter, Histogram, start_http_server
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
from pydantic.v1 import validator
|
|
||||||
from tenacity import retry, stop_after_attempt, wait_exponential
|
|
||||||
|
|
||||||
# Enhanced metrics
|
|
||||||
TASK_COUNTER = Counter(
|
|
||||||
"swarm_tasks_total", "Total number of tasks processed"
|
|
||||||
)
|
|
||||||
TASK_LATENCY = Histogram(
|
|
||||||
"swarm_task_duration_seconds", "Task processing duration"
|
|
||||||
)
|
|
||||||
TASK_FAILURES = Counter(
|
|
||||||
"swarm_task_failures_total", "Total number of task failures"
|
|
||||||
)
|
|
||||||
AGENT_ERRORS = Counter(
|
|
||||||
"swarm_agent_errors_total", "Total number of agent errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Define types using Literal
|
|
||||||
TaskStatus = Literal["pending", "processing", "completed", "failed"]
|
|
||||||
TaskPriority = Literal["low", "medium", "high", "critical"]
|
|
||||||
|
|
||||||
|
|
||||||
class SecurityConfig(BaseModel):
|
|
||||||
"""Security configuration for the swarm"""
|
|
||||||
|
|
||||||
encryption_key: str = Field(
|
|
||||||
..., description="Encryption key for sensitive data"
|
|
||||||
)
|
|
||||||
tls_cert_path: Optional[str] = Field(
|
|
||||||
None, description="Path to TLS certificate"
|
|
||||||
)
|
|
||||||
tls_key_path: Optional[str] = Field(
|
|
||||||
None, description="Path to TLS private key"
|
|
||||||
)
|
|
||||||
auth_token: Optional[str] = Field(
|
|
||||||
None, description="Authentication token"
|
|
||||||
)
|
|
||||||
max_message_size: int = Field(
|
|
||||||
default=1048576, description="Maximum message size in bytes"
|
|
||||||
)
|
|
||||||
rate_limit: int = Field(
|
|
||||||
default=100, description="Maximum tasks per minute"
|
|
||||||
)
|
|
||||||
|
|
||||||
@validator("encryption_key")
|
|
||||||
def validate_encryption_key(cls, v):
|
|
||||||
if len(v) < 32:
|
|
||||||
raise ValueError(
|
|
||||||
"Encryption key must be at least 32 bytes long"
|
|
||||||
)
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
class Task(BaseModel):
|
|
||||||
"""Enhanced task model with additional metadata and validation"""
|
|
||||||
|
|
||||||
task_id: str = Field(
|
|
||||||
..., description="Unique identifier for the task"
|
|
||||||
)
|
|
||||||
description: str = Field(
|
|
||||||
..., description="Task description or instructions"
|
|
||||||
)
|
|
||||||
output_type: Literal["string", "json", "file"] = Field("string")
|
|
||||||
status: TaskStatus = Field(default="pending")
|
|
||||||
priority: TaskPriority = Field(default="medium")
|
|
||||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
started_at: Optional[datetime] = None
|
|
||||||
completed_at: Optional[datetime] = None
|
|
||||||
retry_count: int = Field(default=0)
|
|
||||||
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
||||||
|
|
||||||
@validator("task_id")
|
|
||||||
def validate_task_id(cls, v):
|
|
||||||
if not v.strip():
|
|
||||||
raise ValueError("task_id cannot be empty")
|
|
||||||
return v
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
json_encoders = {datetime: lambda v: v.isoformat()}
|
|
||||||
|
|
||||||
|
|
||||||
class TaskResult(BaseModel):
|
|
||||||
"""Model for task execution results"""
|
|
||||||
|
|
||||||
task_id: str
|
|
||||||
status: TaskStatus
|
|
||||||
result: Any
|
|
||||||
error_message: Optional[str] = None
|
|
||||||
execution_time: float
|
|
||||||
agent_id: str
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def task_timing():
|
|
||||||
"""Context manager for timing task execution"""
|
|
||||||
start_time = time.time()
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
duration = time.time() - start_time
|
|
||||||
TASK_LATENCY.observe(duration)
|
|
||||||
|
|
||||||
|
|
||||||
class SecurePulsarSwarm:
|
|
||||||
"""
|
|
||||||
Enhanced secure, scalable swarm system with improved reliability and security features.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
description: str,
|
|
||||||
agents: List[Any],
|
|
||||||
pulsar_url: str,
|
|
||||||
subscription_name: str,
|
|
||||||
topic_name: str,
|
|
||||||
security_config: SecurityConfig,
|
|
||||||
max_workers: int = 5,
|
|
||||||
retry_attempts: int = 3,
|
|
||||||
task_timeout: int = 300,
|
|
||||||
metrics_port: int = 8000,
|
|
||||||
):
|
|
||||||
"""Initialize the enhanced Pulsar Swarm"""
|
|
||||||
self.name = name
|
|
||||||
self.description = description
|
|
||||||
self.agents = agents
|
|
||||||
self.pulsar_url = pulsar_url
|
|
||||||
self.subscription_name = subscription_name
|
|
||||||
self.topic_name = topic_name
|
|
||||||
self.security_config = security_config
|
|
||||||
self.max_workers = max_workers
|
|
||||||
self.retry_attempts = retry_attempts
|
|
||||||
self.task_timeout = task_timeout
|
|
||||||
|
|
||||||
# Initialize encryption
|
|
||||||
self.cipher_suite = Fernet(
|
|
||||||
security_config.encryption_key.encode()
|
|
||||||
)
|
|
||||||
|
|
||||||
# Setup metrics server
|
|
||||||
start_http_server(metrics_port)
|
|
||||||
|
|
||||||
# Initialize Pulsar client with security settings
|
|
||||||
client_config = {
|
|
||||||
"authentication": (
|
|
||||||
None
|
|
||||||
if not security_config.auth_token
|
|
||||||
else pulsar.AuthenticationToken(
|
|
||||||
security_config.auth_token
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"operation_timeout_seconds": 30,
|
|
||||||
"connection_timeout_seconds": 30,
|
|
||||||
"use_tls": bool(security_config.tls_cert_path),
|
|
||||||
"tls_trust_certs_file_path": security_config.tls_cert_path,
|
|
||||||
"tls_allow_insecure_connection": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
self.client = pulsar.Client(self.pulsar_url, **client_config)
|
|
||||||
self.producer = self._create_producer()
|
|
||||||
self.consumer = self._create_consumer()
|
|
||||||
self.executor = ThreadPoolExecutor(max_workers=max_workers)
|
|
||||||
|
|
||||||
# Initialize rate limiting
|
|
||||||
self.last_execution_time = time.time()
|
|
||||||
self.execution_count = 0
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"Secure Pulsar Swarm '{self.name}' initialized with enhanced security features"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _create_producer(self):
|
|
||||||
"""Create a secure producer with retry logic"""
|
|
||||||
return self.client.create_producer(
|
|
||||||
self.topic_name,
|
|
||||||
max_pending_messages=1000,
|
|
||||||
compression_type=pulsar.CompressionType.LZ4,
|
|
||||||
block_if_queue_full=True,
|
|
||||||
batching_enabled=True,
|
|
||||||
batching_max_publish_delay_ms=10,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _create_consumer(self):
|
|
||||||
"""Create a secure consumer with retry logic"""
|
|
||||||
return self.client.subscribe(
|
|
||||||
self.topic_name,
|
|
||||||
subscription_name=self.subscription_name,
|
|
||||||
consumer_type=pulsar.ConsumerType.Shared,
|
|
||||||
message_listener=None,
|
|
||||||
receiver_queue_size=1000,
|
|
||||||
max_total_receiver_queue_size_across_partitions=50000,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _encrypt_message(self, data: str) -> bytes:
|
|
||||||
"""Encrypt message data"""
|
|
||||||
return self.cipher_suite.encrypt(data.encode())
|
|
||||||
|
|
||||||
def _decrypt_message(self, data: bytes) -> str:
|
|
||||||
"""Decrypt message data"""
|
|
||||||
return self.cipher_suite.decrypt(data).decode()
|
|
||||||
|
|
||||||
@retry(
|
|
||||||
stop=stop_after_attempt(3),
|
|
||||||
wait=wait_exponential(multiplier=1, min=4, max=10),
|
|
||||||
)
|
|
||||||
def publish_task(self, task: Task) -> None:
|
|
||||||
"""Publish a task with enhanced security and reliability"""
|
|
||||||
try:
|
|
||||||
# Validate message size
|
|
||||||
task_data = task.json()
|
|
||||||
if len(task_data) > self.security_config.max_message_size:
|
|
||||||
raise ValueError(
|
|
||||||
"Task data exceeds maximum message size"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Rate limiting
|
|
||||||
current_time = time.time()
|
|
||||||
if current_time - self.last_execution_time >= 60:
|
|
||||||
self.execution_count = 0
|
|
||||||
self.last_execution_time = current_time
|
|
||||||
|
|
||||||
if (
|
|
||||||
self.execution_count
|
|
||||||
>= self.security_config.rate_limit
|
|
||||||
):
|
|
||||||
raise ValueError("Rate limit exceeded")
|
|
||||||
|
|
||||||
# Encrypt and publish
|
|
||||||
encrypted_data = self._encrypt_message(task_data)
|
|
||||||
message_id = self.producer.send(encrypted_data)
|
|
||||||
|
|
||||||
self.execution_count += 1
|
|
||||||
logger.info(
|
|
||||||
f"Task {task.task_id} published successfully with message ID {message_id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
TASK_FAILURES.inc()
|
|
||||||
logger.error(
|
|
||||||
f"Error publishing task {task.task_id}: {str(e)}"
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
|
|
||||||
async def _process_task(self, task: Task) -> TaskResult:
|
|
||||||
"""Process a task with comprehensive error handling and monitoring"""
|
|
||||||
task.status = "processing"
|
|
||||||
task.started_at = datetime.utcnow()
|
|
||||||
|
|
||||||
with task_timing():
|
|
||||||
try:
|
|
||||||
# Select agent using round-robin
|
|
||||||
agent = self.agents.pop(0)
|
|
||||||
self.agents.append(agent)
|
|
||||||
|
|
||||||
# Execute task with timeout
|
|
||||||
future = self.executor.submit(
|
|
||||||
agent.run, task.description
|
|
||||||
)
|
|
||||||
result = future.result(timeout=self.task_timeout)
|
|
||||||
|
|
||||||
# Handle different output types
|
|
||||||
if task.output_type == "json":
|
|
||||||
result = json.loads(result)
|
|
||||||
elif task.output_type == "file":
|
|
||||||
file_path = f"output_{task.task_id}_{int(time.time())}.txt"
|
|
||||||
with open(file_path, "w") as f:
|
|
||||||
f.write(result)
|
|
||||||
result = {"file_path": file_path}
|
|
||||||
|
|
||||||
task.status = "completed"
|
|
||||||
task.completed_at = datetime.utcnow()
|
|
||||||
TASK_COUNTER.inc()
|
|
||||||
|
|
||||||
return TaskResult(
|
|
||||||
task_id=task.task_id,
|
|
||||||
status="completed",
|
|
||||||
result=result,
|
|
||||||
execution_time=time.time()
|
|
||||||
- task.started_at.timestamp(),
|
|
||||||
agent_id=agent.agent_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
except TimeoutError:
|
|
||||||
TASK_FAILURES.inc()
|
|
||||||
error_msg = f"Task {task.task_id} timed out after {self.task_timeout} seconds"
|
|
||||||
logger.error(error_msg)
|
|
||||||
task.status = "failed"
|
|
||||||
return TaskResult(
|
|
||||||
task_id=task.task_id,
|
|
||||||
status="failed",
|
|
||||||
result=None,
|
|
||||||
error_message=error_msg,
|
|
||||||
execution_time=time.time()
|
|
||||||
- task.started_at.timestamp(),
|
|
||||||
agent_id=agent.agent_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
TASK_FAILURES.inc()
|
|
||||||
AGENT_ERRORS.inc()
|
|
||||||
error_msg = (
|
|
||||||
f"Error processing task {task.task_id}: {str(e)}"
|
|
||||||
)
|
|
||||||
logger.error(error_msg)
|
|
||||||
task.status = "failed"
|
|
||||||
return TaskResult(
|
|
||||||
task_id=task.task_id,
|
|
||||||
status="failed",
|
|
||||||
result=None,
|
|
||||||
error_message=error_msg,
|
|
||||||
execution_time=time.time()
|
|
||||||
- task.started_at.timestamp(),
|
|
||||||
agent_id=agent.agent_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def consume_tasks(self):
|
|
||||||
"""Enhanced task consumption with circuit breaker and backoff"""
|
|
||||||
consecutive_failures = 0
|
|
||||||
backoff_time = 1
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
# Circuit breaker pattern
|
|
||||||
if consecutive_failures >= 5:
|
|
||||||
logger.warning(
|
|
||||||
f"Circuit breaker triggered. Waiting {backoff_time} seconds"
|
|
||||||
)
|
|
||||||
await asyncio.sleep(backoff_time)
|
|
||||||
backoff_time = min(backoff_time * 2, 60)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Receive message with timeout
|
|
||||||
message = await self.consumer.receive_async()
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Decrypt and process message
|
|
||||||
decrypted_data = self._decrypt_message(
|
|
||||||
message.data()
|
|
||||||
)
|
|
||||||
task_data = json.loads(decrypted_data)
|
|
||||||
task = Task(**task_data)
|
|
||||||
|
|
||||||
# Process task
|
|
||||||
result = await self._process_task(task)
|
|
||||||
|
|
||||||
# Handle result
|
|
||||||
if result.status == "completed":
|
|
||||||
await self.consumer.acknowledge_async(message)
|
|
||||||
consecutive_failures = 0
|
|
||||||
backoff_time = 1
|
|
||||||
else:
|
|
||||||
if task.retry_count < self.retry_attempts:
|
|
||||||
task.retry_count += 1
|
|
||||||
await self.consumer.negative_acknowledge(
|
|
||||||
message
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await self.consumer.acknowledge_async(
|
|
||||||
message
|
|
||||||
)
|
|
||||||
logger.error(
|
|
||||||
f"Task {task.task_id} failed after {self.retry_attempts} attempts"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"Error processing message: {str(e)}"
|
|
||||||
)
|
|
||||||
await self.consumer.negative_acknowledge(message)
|
|
||||||
consecutive_failures += 1
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error in consume_tasks: {str(e)}")
|
|
||||||
consecutive_failures += 1
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
"""Context manager entry"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
"""Context manager exit with proper cleanup"""
|
|
||||||
try:
|
|
||||||
self.producer.flush()
|
|
||||||
self.producer.close()
|
|
||||||
self.consumer.close()
|
|
||||||
self.client.close()
|
|
||||||
self.executor.shutdown(wait=True)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error during cleanup: {str(e)}")
|
|
||||||
|
|
||||||
|
|
||||||
# if __name__ == "__main__":
|
|
||||||
# # Example usage with security configuration
|
|
||||||
# security_config = SecurityConfig(
|
|
||||||
# encryption_key=secrets.token_urlsafe(32),
|
|
||||||
# tls_cert_path="/path/to/cert.pem",
|
|
||||||
# tls_key_path="/path/to/key.pem",
|
|
||||||
# auth_token="your-auth-token",
|
|
||||||
# max_message_size=1048576,
|
|
||||||
# rate_limit=100,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Agent factory function
|
|
||||||
# def create_financial_agent() -> Agent:
|
|
||||||
# """Factory function to create a financial analysis agent."""
|
|
||||||
# return Agent(
|
|
||||||
# agent_name="Financial-Analysis-Agent",
|
|
||||||
# system_prompt=FINANCIAL_AGENT_SYS_PROMPT,
|
|
||||||
# model_name="gpt-4o-mini",
|
|
||||||
# max_loops=1,
|
|
||||||
# autosave=True,
|
|
||||||
# dashboard=False,
|
|
||||||
# verbose=True,
|
|
||||||
# dynamic_temperature_enabled=True,
|
|
||||||
# saved_state_path="finance_agent.json",
|
|
||||||
# user_name="swarms_corp",
|
|
||||||
# retry_attempts=1,
|
|
||||||
# context_length=200000,
|
|
||||||
# return_step_meta=False,
|
|
||||||
# output_type="string",
|
|
||||||
# streaming_on=False,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Initialize agents (implementation not shown)
|
|
||||||
# agents = [create_financial_agent() for _ in range(3)]
|
|
||||||
|
|
||||||
# # Initialize the secure swarm
|
|
||||||
# with SecurePulsarSwarm(
|
|
||||||
# name="Secure Financial Swarm",
|
|
||||||
# description="Production-grade financial analysis swarm",
|
|
||||||
# agents=agents,
|
|
||||||
# pulsar_url="pulsar+ssl://localhost:6651",
|
|
||||||
# subscription_name="secure_financial_subscription",
|
|
||||||
# topic_name="secure_financial_tasks",
|
|
||||||
# security_config=security_config,
|
|
||||||
# max_workers=5,
|
|
||||||
# retry_attempts=3,
|
|
||||||
# task_timeout=300,
|
|
||||||
# metrics_port=8000,
|
|
||||||
# ) as swarm:
|
|
||||||
# # Example task
|
|
||||||
# task = Task(
|
|
||||||
# task_id=secrets.token_urlsafe(16),
|
|
||||||
# description="Analyze Q4 financial reports",
|
|
||||||
# output_type="json",
|
|
||||||
# priority="high",
|
|
||||||
# metadata={
|
|
||||||
# "department": "finance",
|
|
||||||
# "requester": "john.doe@company.com",
|
|
||||||
# },
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Run the swarm
|
|
||||||
# swarm.publish_task(task)
|
|
||||||
# asyncio.run(swarm.consume_tasks())
|
|
@ -1,344 +0,0 @@
|
|||||||
import random
|
|
||||||
from threading import Lock
|
|
||||||
from time import sleep
|
|
||||||
from typing import Callable, List, Optional
|
|
||||||
|
|
||||||
from swarms.structs.agent import Agent
|
|
||||||
from swarms.structs.base_swarm import BaseSwarm
|
|
||||||
from swarms.utils.loguru_logger import initialize_logger
|
|
||||||
|
|
||||||
logger = initialize_logger(log_folder="swarm_load_balancer")
|
|
||||||
|
|
||||||
|
|
||||||
class AgentLoadBalancer(BaseSwarm):
|
|
||||||
"""
|
|
||||||
A load balancer class that distributes tasks among a group of agents.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
agents (List[Agent]): The list of agents available for task execution.
|
|
||||||
max_retries (int, optional): The maximum number of retries for a task if it fails. Defaults to 3.
|
|
||||||
max_loops (int, optional): The maximum number of loops to run a task. Defaults to 5.
|
|
||||||
cooldown_time (float, optional): The cooldown time between retries. Defaults to 0.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
agents (List[Agent]): The list of agents available for task execution.
|
|
||||||
agent_status (Dict[str, bool]): The status of each agent, indicating whether it is available or not.
|
|
||||||
max_retries (int): The maximum number of retries for a task if it fails.
|
|
||||||
max_loops (int): The maximum number of loops to run a task.
|
|
||||||
agent_performance (Dict[str, Dict[str, int]]): The performance statistics of each agent.
|
|
||||||
lock (Lock): A lock to ensure thread safety.
|
|
||||||
cooldown_time (float): The cooldown time between retries.
|
|
||||||
|
|
||||||
Methods:
|
|
||||||
get_available_agent: Get an available agent for task execution.
|
|
||||||
set_agent_status: Set the status of an agent.
|
|
||||||
update_performance: Update the performance statistics of an agent.
|
|
||||||
log_performance: Log the performance statistics of all agents.
|
|
||||||
run_task: Run a single task using an available agent.
|
|
||||||
run_multiple_tasks: Run multiple tasks using available agents.
|
|
||||||
run_task_with_loops: Run a task multiple times using an available agent.
|
|
||||||
run_task_with_callback: Run a task with a callback function.
|
|
||||||
run_task_with_timeout: Run a task with a timeout.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
agents: List[Agent],
|
|
||||||
max_retries: int = 3,
|
|
||||||
max_loops: int = 5,
|
|
||||||
cooldown_time: float = 0,
|
|
||||||
):
|
|
||||||
self.agents = agents
|
|
||||||
self.agent_status = {
|
|
||||||
agent.agent_name: True for agent in agents
|
|
||||||
}
|
|
||||||
self.max_retries = max_retries
|
|
||||||
self.max_loops = max_loops
|
|
||||||
self.agent_performance = {
|
|
||||||
agent.agent_name: {"success_count": 0, "failure_count": 0}
|
|
||||||
for agent in agents
|
|
||||||
}
|
|
||||||
self.lock = Lock()
|
|
||||||
self.cooldown_time = cooldown_time
|
|
||||||
self.swarm_initialization()
|
|
||||||
|
|
||||||
def swarm_initialization(self):
|
|
||||||
logger.info(
|
|
||||||
"Initializing AgentLoadBalancer with the following agents:"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Make sure all the agents exist
|
|
||||||
assert self.agents, "No agents provided to the Load Balancer"
|
|
||||||
|
|
||||||
# Assert that all agents are of type Agent
|
|
||||||
for agent in self.agents:
|
|
||||||
assert isinstance(
|
|
||||||
agent, Agent
|
|
||||||
), "All agents should be of type Agent"
|
|
||||||
|
|
||||||
for agent in self.agents:
|
|
||||||
logger.info(f"Agent Name: {agent.agent_name}")
|
|
||||||
|
|
||||||
logger.info("Load Balancer Initialized Successfully!")
|
|
||||||
|
|
||||||
def get_available_agent(self) -> Optional[Agent]:
|
|
||||||
"""
|
|
||||||
Get an available agent for task execution.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Optional[Agent]: An available agent, or None if no agents are available.
|
|
||||||
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
available_agents = [
|
|
||||||
agent
|
|
||||||
for agent in self.agents
|
|
||||||
if self.agent_status[agent.agent_name]
|
|
||||||
]
|
|
||||||
logger.info(
|
|
||||||
f"Available agents: {[agent.agent_name for agent in available_agents]}"
|
|
||||||
)
|
|
||||||
if not available_agents:
|
|
||||||
return None
|
|
||||||
return random.choice(available_agents)
|
|
||||||
|
|
||||||
def set_agent_status(self, agent: Agent, status: bool) -> None:
|
|
||||||
"""
|
|
||||||
Set the status of an agent.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
agent (Agent): The agent whose status needs to be set.
|
|
||||||
status (bool): The status to set for the agent.
|
|
||||||
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
self.agent_status[agent.agent_name] = status
|
|
||||||
|
|
||||||
def update_performance(self, agent: Agent, success: bool) -> None:
|
|
||||||
"""
|
|
||||||
Update the performance statistics of an agent.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
agent (Agent): The agent whose performance statistics need to be updated.
|
|
||||||
success (bool): Whether the task executed by the agent was successful or not.
|
|
||||||
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
if success:
|
|
||||||
self.agent_performance[agent.agent_name][
|
|
||||||
"success_count"
|
|
||||||
] += 1
|
|
||||||
else:
|
|
||||||
self.agent_performance[agent.agent_name][
|
|
||||||
"failure_count"
|
|
||||||
] += 1
|
|
||||||
|
|
||||||
def log_performance(self) -> None:
|
|
||||||
"""
|
|
||||||
Log the performance statistics of all agents.
|
|
||||||
|
|
||||||
"""
|
|
||||||
logger.info("Agent Performance:")
|
|
||||||
for agent_name, stats in self.agent_performance.items():
|
|
||||||
logger.info(f"{agent_name}: {stats}")
|
|
||||||
|
|
||||||
def run(self, task: str, *args, **kwargs) -> str:
|
|
||||||
"""
|
|
||||||
Run a single task using an available agent.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task (str): The task to be executed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: The output of the task execution.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
RuntimeError: If no available agents are found to handle the request.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
retries = 0
|
|
||||||
while retries < self.max_retries:
|
|
||||||
agent = self.get_available_agent()
|
|
||||||
if not agent:
|
|
||||||
raise RuntimeError(
|
|
||||||
"No available agents to handle the request."
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.set_agent_status(agent, False)
|
|
||||||
output = agent.run(task, *args, **kwargs)
|
|
||||||
self.update_performance(agent, True)
|
|
||||||
return output
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"Error with agent {agent.agent_name}: {e}"
|
|
||||||
)
|
|
||||||
self.update_performance(agent, False)
|
|
||||||
retries += 1
|
|
||||||
sleep(self.cooldown_time)
|
|
||||||
if retries >= self.max_retries:
|
|
||||||
raise e
|
|
||||||
finally:
|
|
||||||
self.set_agent_status(agent, True)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"Task failed: {e} try again by optimizing the code."
|
|
||||||
)
|
|
||||||
raise RuntimeError(f"Task failed: {e}")
|
|
||||||
|
|
||||||
def run_multiple_tasks(self, tasks: List[str]) -> List[str]:
|
|
||||||
"""
|
|
||||||
Run multiple tasks using available agents.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tasks (List[str]): The list of tasks to be executed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[str]: The list of outputs corresponding to each task execution.
|
|
||||||
|
|
||||||
"""
|
|
||||||
results = []
|
|
||||||
for task in tasks:
|
|
||||||
result = self.run(task)
|
|
||||||
results.append(result)
|
|
||||||
return results
|
|
||||||
|
|
||||||
def run_task_with_loops(self, task: str) -> List[str]:
|
|
||||||
"""
|
|
||||||
Run a task multiple times using an available agent.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task (str): The task to be executed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[str]: The list of outputs corresponding to each task execution.
|
|
||||||
|
|
||||||
"""
|
|
||||||
results = []
|
|
||||||
for _ in range(self.max_loops):
|
|
||||||
result = self.run(task)
|
|
||||||
results.append(result)
|
|
||||||
return results
|
|
||||||
|
|
||||||
def run_task_with_callback(
|
|
||||||
self, task: str, callback: Callable[[str], None]
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Run a task with a callback function.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task (str): The task to be executed.
|
|
||||||
callback (Callable[[str], None]): The callback function to be called with the task result.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
result = self.run(task)
|
|
||||||
callback(result)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Task failed: {e}")
|
|
||||||
callback(str(e))
|
|
||||||
|
|
||||||
def run_task_with_timeout(self, task: str, timeout: float) -> str:
|
|
||||||
"""
|
|
||||||
Run a task with a timeout.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task (str): The task to be executed.
|
|
||||||
timeout (float): The maximum time (in seconds) to wait for the task to complete.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: The output of the task execution.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
TimeoutError: If the task execution exceeds the specified timeout.
|
|
||||||
Exception: If the task execution raises an exception.
|
|
||||||
|
|
||||||
"""
|
|
||||||
import threading
|
|
||||||
|
|
||||||
result = [None]
|
|
||||||
exception = [None]
|
|
||||||
|
|
||||||
def target():
|
|
||||||
try:
|
|
||||||
result[0] = self.run(task)
|
|
||||||
except Exception as e:
|
|
||||||
exception[0] = e
|
|
||||||
|
|
||||||
thread = threading.Thread(target=target)
|
|
||||||
thread.start()
|
|
||||||
thread.join(timeout)
|
|
||||||
|
|
||||||
if thread.is_alive():
|
|
||||||
raise TimeoutError(
|
|
||||||
f"Task timed out after {timeout} seconds."
|
|
||||||
)
|
|
||||||
|
|
||||||
if exception[0]:
|
|
||||||
raise exception[0]
|
|
||||||
|
|
||||||
return result[0]
|
|
||||||
|
|
||||||
|
|
||||||
# if __name__ == "__main__":
|
|
||||||
# from swarms import llama3Hosted()
|
|
||||||
# # User initializes the agents
|
|
||||||
# agents = [
|
|
||||||
# Agent(
|
|
||||||
# agent_name="Transcript Generator 1",
|
|
||||||
# agent_description="Generate a transcript for a youtube video on what swarms are!",
|
|
||||||
# llm=llama3Hosted(),
|
|
||||||
# max_loops="auto",
|
|
||||||
# autosave=True,
|
|
||||||
# dashboard=False,
|
|
||||||
# streaming_on=True,
|
|
||||||
# verbose=True,
|
|
||||||
# stopping_token="<DONE>",
|
|
||||||
# interactive=True,
|
|
||||||
# state_save_file_type="json",
|
|
||||||
# saved_state_path="transcript_generator_1.json",
|
|
||||||
# ),
|
|
||||||
# Agent(
|
|
||||||
# agent_name="Transcript Generator 2",
|
|
||||||
# agent_description="Generate a transcript for a youtube video on what swarms are!",
|
|
||||||
# llm=llama3Hosted(),
|
|
||||||
# max_loops="auto",
|
|
||||||
# autosave=True,
|
|
||||||
# dashboard=False,
|
|
||||||
# streaming_on=True,
|
|
||||||
# verbose=True,
|
|
||||||
# stopping_token="<DONE>",
|
|
||||||
# interactive=True,
|
|
||||||
# state_save_file_type="json",
|
|
||||||
# saved_state_path="transcript_generator_2.json",
|
|
||||||
# )
|
|
||||||
# # Add more agents as needed
|
|
||||||
# ]
|
|
||||||
|
|
||||||
# load_balancer = LoadBalancer(agents)
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# result = load_balancer.run_task("Generate a transcript for a youtube video on what swarms are!")
|
|
||||||
# print(result)
|
|
||||||
|
|
||||||
# # Running multiple tasks
|
|
||||||
# tasks = [
|
|
||||||
# "Generate a transcript for a youtube video on what swarms are!",
|
|
||||||
# "Generate a transcript for a youtube video on AI advancements!"
|
|
||||||
# ]
|
|
||||||
# results = load_balancer.run_multiple_tasks(tasks)
|
|
||||||
# for res in results:
|
|
||||||
# print(res)
|
|
||||||
|
|
||||||
# # Running task with loops
|
|
||||||
# loop_results = load_balancer.run_task_with_loops("Generate a transcript for a youtube video on what swarms are!")
|
|
||||||
# for res in loop_results:
|
|
||||||
# print(res)
|
|
||||||
|
|
||||||
# except RuntimeError as e:
|
|
||||||
# print(f"Error: {e}")
|
|
||||||
|
|
||||||
# # Log performance
|
|
||||||
# load_balancer.log_performance()
|
|
@ -0,0 +1,90 @@
|
|||||||
|
import asyncio
|
||||||
|
from typing import Literal, Dict, Any, Union
|
||||||
|
from fastmcp import Client
|
||||||
|
from swarms.utils.any_to_str import any_to_str
|
||||||
|
from swarms.utils.str_to_dict import str_to_dict
|
||||||
|
|
||||||
|
|
||||||
|
def parse_agent_output(
|
||||||
|
dictionary: Union[str, Dict[Any, Any]]
|
||||||
|
) -> tuple[str, Dict[Any, Any]]:
|
||||||
|
if isinstance(dictionary, str):
|
||||||
|
dictionary = str_to_dict(dictionary)
|
||||||
|
|
||||||
|
elif not isinstance(dictionary, dict):
|
||||||
|
raise ValueError("Invalid dictionary")
|
||||||
|
|
||||||
|
# Handle OpenAI function call format
|
||||||
|
if "function_call" in dictionary:
|
||||||
|
name = dictionary["function_call"]["name"]
|
||||||
|
# arguments is a JSON string, so we need to parse it
|
||||||
|
params = str_to_dict(dictionary["function_call"]["arguments"])
|
||||||
|
return name, params
|
||||||
|
|
||||||
|
# Handle OpenAI tool calls format
|
||||||
|
if "tool_calls" in dictionary:
|
||||||
|
# Get the first tool call (or you could handle multiple if needed)
|
||||||
|
tool_call = dictionary["tool_calls"][0]
|
||||||
|
name = tool_call["function"]["name"]
|
||||||
|
params = str_to_dict(tool_call["function"]["arguments"])
|
||||||
|
return name, params
|
||||||
|
|
||||||
|
# Handle regular dictionary format
|
||||||
|
if "name" in dictionary:
|
||||||
|
name = dictionary["name"]
|
||||||
|
params = dictionary.get("arguments", {})
|
||||||
|
return name, params
|
||||||
|
|
||||||
|
raise ValueError("Invalid function call format")
|
||||||
|
|
||||||
|
|
||||||
|
async def _execute_mcp_tool(
|
||||||
|
url: str,
|
||||||
|
method: Literal["stdio", "sse"] = "sse",
|
||||||
|
parameters: Dict[Any, Any] = None,
|
||||||
|
output_type: Literal["str", "dict"] = "str",
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
) -> Dict[Any, Any]:
|
||||||
|
|
||||||
|
if "sse" or "stdio" not in url:
|
||||||
|
raise ValueError("Invalid URL")
|
||||||
|
|
||||||
|
url = f"{url}/{method}"
|
||||||
|
|
||||||
|
name, params = parse_agent_output(parameters)
|
||||||
|
|
||||||
|
if output_type == "str":
|
||||||
|
async with Client(url, *args, **kwargs) as client:
|
||||||
|
out = await client.call_tool(
|
||||||
|
name=name,
|
||||||
|
arguments=params,
|
||||||
|
)
|
||||||
|
return any_to_str(out)
|
||||||
|
elif output_type == "dict":
|
||||||
|
async with Client(url, *args, **kwargs) as client:
|
||||||
|
out = await client.call_tool(
|
||||||
|
name=name,
|
||||||
|
arguments=params,
|
||||||
|
)
|
||||||
|
return out
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid output type: {output_type}")
|
||||||
|
|
||||||
|
|
||||||
|
def execute_mcp_tool(
|
||||||
|
url: str,
|
||||||
|
tool_name: str = None,
|
||||||
|
method: Literal["stdio", "sse"] = "sse",
|
||||||
|
parameters: Dict[Any, Any] = None,
|
||||||
|
output_type: Literal["str", "dict"] = "str",
|
||||||
|
) -> Dict[Any, Any]:
|
||||||
|
return asyncio.run(
|
||||||
|
_execute_mcp_tool(
|
||||||
|
url=url,
|
||||||
|
tool_name=tool_name,
|
||||||
|
method=method,
|
||||||
|
parameters=parameters,
|
||||||
|
output_type=output_type,
|
||||||
|
)
|
||||||
|
)
|
Loading…
Reference in new issue