agentensemble 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentensemble/__init__.py +76 -0
- agentensemble/agents/__init__.py +32 -0
- agentensemble/agents/base.py +84 -0
- agentensemble/agents/hybrid_agent.py +147 -0
- agentensemble/agents/rag_agent.py +92 -0
- agentensemble/agents/react_agent.py +118 -0
- agentensemble/agents/stategraph_agent.py +97 -0
- agentensemble/agents/structured_agent.py +210 -0
- agentensemble/orchestration/__init__.py +22 -0
- agentensemble/orchestration/ensemble.py +145 -0
- agentensemble/orchestration/pipeline.py +27 -0
- agentensemble/orchestration/supervisor.py +27 -0
- agentensemble/orchestration/swarm.py +27 -0
- agentensemble/py.typed +3 -0
- agentensemble/testing/__init__.py +16 -0
- agentensemble/testing/benchmark.py +87 -0
- agentensemble/testing/comparison.py +82 -0
- agentensemble/testing/metrics.py +81 -0
- agentensemble/tools/__init__.py +20 -0
- agentensemble/tools/rag.py +226 -0
- agentensemble/tools/registry.py +83 -0
- agentensemble/tools/scraper.py +123 -0
- agentensemble/tools/search.py +155 -0
- agentensemble/tools/validator.py +51 -0
- agentensemble/utils/__init__.py +11 -0
- agentensemble/utils/llm.py +109 -0
- agentensemble-0.1.0.dist-info/METADATA +407 -0
- agentensemble-0.1.0.dist-info/RECORD +30 -0
- agentensemble-0.1.0.dist-info/WHEEL +5 -0
- agentensemble-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Structured Output Agent Implementation
|
|
3
|
+
|
|
4
|
+
Agent with structured output support using LangChain's structured output feature.
|
|
5
|
+
Supports both langchain>=1.1 (create_agent with response_format) and
|
|
6
|
+
langchain<1.1 (with_structured_output method).
|
|
7
|
+
|
|
8
|
+
Based on: https://docs.langchain.com/oss/python/langchain/structured-output
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Any, Dict, Optional, Type, Union
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from agentensemble.utils.llm import get_mistral_model
|
|
16
|
+
LLM_AVAILABLE = True
|
|
17
|
+
except ImportError:
|
|
18
|
+
LLM_AVAILABLE = False
|
|
19
|
+
get_mistral_model = None
|
|
20
|
+
|
|
21
|
+
# Try langchain>=1.1 approach (create_agent with response_format)
|
|
22
|
+
try:
|
|
23
|
+
from langchain.agents import create_agent
|
|
24
|
+
from langchain.agents.structured_output import ToolStrategy, ProviderStrategy
|
|
25
|
+
CREATE_AGENT_AVAILABLE = True
|
|
26
|
+
STRUCTURED_OUTPUT_AVAILABLE = True
|
|
27
|
+
except ImportError:
|
|
28
|
+
CREATE_AGENT_AVAILABLE = False
|
|
29
|
+
create_agent = None
|
|
30
|
+
ToolStrategy = None
|
|
31
|
+
ProviderStrategy = None
|
|
32
|
+
STRUCTURED_OUTPUT_AVAILABLE = False
|
|
33
|
+
|
|
34
|
+
# with_structured_output is available if model has the method
|
|
35
|
+
WITH_STRUCTURED_AVAILABLE = True
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class StructuredAgent:
|
|
39
|
+
"""
|
|
40
|
+
Agent with structured output support.
|
|
41
|
+
|
|
42
|
+
Uses LangChain's structured output feature to return data in a specific format.
|
|
43
|
+
|
|
44
|
+
For langchain>=1.1: Uses create_agent with response_format parameter
|
|
45
|
+
For langchain<1.1: Uses model.with_structured_output() method
|
|
46
|
+
|
|
47
|
+
Based on: https://docs.langchain.com/oss/python/langchain/structured-output
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
name: str = "structured_agent",
|
|
53
|
+
tools: Optional[list] = None,
|
|
54
|
+
model: Optional[Any] = None,
|
|
55
|
+
response_format: Optional[Union[Type[BaseModel], Any]] = None,
|
|
56
|
+
system_prompt: Optional[str] = None,
|
|
57
|
+
**kwargs
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Initialize structured agent.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
name: Agent name
|
|
64
|
+
tools: Available tools
|
|
65
|
+
model: LLM model (defaults to Mistral if not provided)
|
|
66
|
+
response_format: Structured output schema (Pydantic model)
|
|
67
|
+
system_prompt: System prompt for the agent
|
|
68
|
+
**kwargs: Additional configuration
|
|
69
|
+
"""
|
|
70
|
+
if not LLM_AVAILABLE:
|
|
71
|
+
raise ImportError(
|
|
72
|
+
"langchain-mistralai is required for StructuredAgent. "
|
|
73
|
+
"Install with: pip install langchain-mistralai"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
self.name = name
|
|
77
|
+
self.tools = tools or []
|
|
78
|
+
self.model = model or get_mistral_model()
|
|
79
|
+
self.response_format = response_format
|
|
80
|
+
self.system_prompt = system_prompt
|
|
81
|
+
self.config = kwargs
|
|
82
|
+
|
|
83
|
+
# Determine which approach to use
|
|
84
|
+
if CREATE_AGENT_AVAILABLE and response_format:
|
|
85
|
+
# Use create_agent with response_format (langchain>=1.1)
|
|
86
|
+
self._use_create_agent()
|
|
87
|
+
elif response_format and hasattr(self.model, 'with_structured_output'):
|
|
88
|
+
# Use with_structured_output (langchain<1.1)
|
|
89
|
+
self._use_with_structured_output()
|
|
90
|
+
else:
|
|
91
|
+
# Basic agent without structured output
|
|
92
|
+
if CREATE_AGENT_AVAILABLE:
|
|
93
|
+
self._use_create_agent()
|
|
94
|
+
else:
|
|
95
|
+
raise ImportError(
|
|
96
|
+
"langchain>=1.1 is required for StructuredAgent with tools. "
|
|
97
|
+
"Install with: pip install 'langchain>=1.1'"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def _use_create_agent(self):
|
|
101
|
+
"""Use create_agent approach (langchain>=1.1)"""
|
|
102
|
+
if not CREATE_AGENT_AVAILABLE:
|
|
103
|
+
raise ImportError("create_agent not available. Requires langchain>=1.1")
|
|
104
|
+
|
|
105
|
+
agent_kwargs = {
|
|
106
|
+
"model": self.model,
|
|
107
|
+
"tools": self.tools,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if self.system_prompt:
|
|
111
|
+
agent_kwargs["system_prompt"] = self.system_prompt
|
|
112
|
+
|
|
113
|
+
if self.response_format:
|
|
114
|
+
agent_kwargs["response_format"] = self.response_format
|
|
115
|
+
|
|
116
|
+
agent_kwargs.update(self.config)
|
|
117
|
+
self.agent = create_agent(**agent_kwargs)
|
|
118
|
+
self._method = "create_agent"
|
|
119
|
+
|
|
120
|
+
def _use_with_structured_output(self):
|
|
121
|
+
"""Use with_structured_output approach (langchain<1.1)"""
|
|
122
|
+
if not self.response_format:
|
|
123
|
+
raise ValueError("response_format required for with_structured_output")
|
|
124
|
+
|
|
125
|
+
# Bind structured output to model
|
|
126
|
+
self.structured_model = self.model.with_structured_output(self.response_format)
|
|
127
|
+
self._method = "with_structured_output"
|
|
128
|
+
|
|
129
|
+
def run(
|
|
130
|
+
self,
|
|
131
|
+
query: str,
|
|
132
|
+
**kwargs
|
|
133
|
+
) -> Dict[str, Any]:
|
|
134
|
+
"""
|
|
135
|
+
Execute agent with structured output.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
query: Input query
|
|
139
|
+
**kwargs: Additional parameters
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Dictionary containing:
|
|
143
|
+
- 'result': Natural language response
|
|
144
|
+
- 'structured_response': Structured output (if response_format provided)
|
|
145
|
+
- 'metadata': Execution metadata
|
|
146
|
+
"""
|
|
147
|
+
if self._method == "create_agent":
|
|
148
|
+
# Use create_agent approach
|
|
149
|
+
result = self.agent.invoke({
|
|
150
|
+
"messages": [{"role": "user", "content": query}],
|
|
151
|
+
**kwargs
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
# Extract structured response if available
|
|
155
|
+
structured_response = result.get("structured_response")
|
|
156
|
+
|
|
157
|
+
# Get last message content as natural language result
|
|
158
|
+
messages = result.get("messages", [])
|
|
159
|
+
last_message = messages[-1] if messages else None
|
|
160
|
+
natural_result = ""
|
|
161
|
+
|
|
162
|
+
if last_message:
|
|
163
|
+
if hasattr(last_message, "content"):
|
|
164
|
+
natural_result = last_message.content
|
|
165
|
+
elif isinstance(last_message, dict):
|
|
166
|
+
natural_result = last_message.get("content", "")
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
"result": natural_result or "No result generated",
|
|
170
|
+
"structured_response": structured_response,
|
|
171
|
+
"metadata": {
|
|
172
|
+
"agent": self.name,
|
|
173
|
+
"has_structured_output": structured_response is not None,
|
|
174
|
+
"method": "create_agent"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
elif self._method == "with_structured_output":
|
|
179
|
+
# Use with_structured_output approach
|
|
180
|
+
structured_response = self.structured_model.invoke(query)
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
"result": str(structured_response),
|
|
184
|
+
"structured_response": structured_response,
|
|
185
|
+
"metadata": {
|
|
186
|
+
"agent": self.name,
|
|
187
|
+
"has_structured_output": True,
|
|
188
|
+
"method": "with_structured_output"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
else:
|
|
193
|
+
raise ValueError(f"Unknown method: {self._method}")
|
|
194
|
+
|
|
195
|
+
def invoke(self, input: Dict[str, Any], **kwargs) -> Dict[str, Any]:
|
|
196
|
+
"""
|
|
197
|
+
Direct invoke method for compatibility with LangChain agents.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
input: Input dictionary with messages
|
|
201
|
+
**kwargs: Additional parameters
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Agent result with structured_response if available
|
|
205
|
+
"""
|
|
206
|
+
if self._method == "create_agent":
|
|
207
|
+
return self.agent.invoke(input, **kwargs)
|
|
208
|
+
else:
|
|
209
|
+
query = input.get("messages", [{}])[-1].get("content", "") if isinstance(input.get("messages"), list) else str(input)
|
|
210
|
+
return self.run(query, **kwargs)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Orchestration Patterns
|
|
3
|
+
|
|
4
|
+
Provides different patterns for coordinating multiple agents:
|
|
5
|
+
- Ensemble: Full multi-agent coordination
|
|
6
|
+
- Supervisor: Central coordinator pattern
|
|
7
|
+
- Swarm: Decentralized collaboration
|
|
8
|
+
- Pipeline: Sequential workflows
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from agentensemble.orchestration.ensemble import Ensemble
|
|
12
|
+
from agentensemble.orchestration.supervisor import SupervisorOrchestrator
|
|
13
|
+
from agentensemble.orchestration.swarm import SwarmOrchestrator
|
|
14
|
+
from agentensemble.orchestration.pipeline import PipelineOrchestrator
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Ensemble",
|
|
18
|
+
"SupervisorOrchestrator",
|
|
19
|
+
"SwarmOrchestrator",
|
|
20
|
+
"PipelineOrchestrator",
|
|
21
|
+
]
|
|
22
|
+
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ensemble Orchestration Pattern
|
|
3
|
+
|
|
4
|
+
Coordinates multiple agents working together in harmony.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
from agentensemble.agents.base import BaseAgent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Ensemble:
|
|
12
|
+
"""
|
|
13
|
+
Ensemble orchestrator for coordinating multiple agents.
|
|
14
|
+
|
|
15
|
+
Supports different coordination modes:
|
|
16
|
+
- supervisor: Central coordinator manages agents
|
|
17
|
+
- swarm: Decentralized agent collaboration
|
|
18
|
+
- pipeline: Sequential agent workflows
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
agents: Dict[str, BaseAgent],
|
|
24
|
+
conductor: str = "supervisor",
|
|
25
|
+
**kwargs
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Initialize ensemble.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
agents: Dictionary of agent name -> agent instance
|
|
32
|
+
conductor: Coordination mode ("supervisor", "swarm", "pipeline")
|
|
33
|
+
**kwargs: Additional configuration
|
|
34
|
+
"""
|
|
35
|
+
self.agents = agents
|
|
36
|
+
self.conductor = conductor
|
|
37
|
+
self.config = kwargs
|
|
38
|
+
|
|
39
|
+
def perform(self, task: str, data: Any = None, **kwargs) -> Dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Execute ensemble of agents on a task.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
task: Task description
|
|
45
|
+
data: Input data
|
|
46
|
+
**kwargs: Additional parameters
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Combined results from all agents
|
|
50
|
+
"""
|
|
51
|
+
if self.conductor == "supervisor":
|
|
52
|
+
return self._supervisor_coordinate(task, data, **kwargs)
|
|
53
|
+
elif self.conductor == "swarm":
|
|
54
|
+
return self._swarm_coordinate(task, data, **kwargs)
|
|
55
|
+
elif self.conductor == "pipeline":
|
|
56
|
+
return self._pipeline_coordinate(task, data, **kwargs)
|
|
57
|
+
else:
|
|
58
|
+
raise ValueError(f"Unknown conductor mode: {self.conductor}")
|
|
59
|
+
|
|
60
|
+
def _supervisor_coordinate(
|
|
61
|
+
self,
|
|
62
|
+
task: str,
|
|
63
|
+
data: Any,
|
|
64
|
+
**kwargs
|
|
65
|
+
) -> Dict[str, Any]:
|
|
66
|
+
"""Supervisor pattern: Central agent coordinates others"""
|
|
67
|
+
results = {}
|
|
68
|
+
|
|
69
|
+
# Determine which agents to use
|
|
70
|
+
agent_order = self._determine_agent_order(task)
|
|
71
|
+
|
|
72
|
+
for agent_name in agent_order:
|
|
73
|
+
if agent_name in self.agents:
|
|
74
|
+
agent = self.agents[agent_name]
|
|
75
|
+
# Ensure context is always a dict
|
|
76
|
+
context = data if isinstance(data, dict) else {"query": str(data), "original_data": data}
|
|
77
|
+
result = agent.run(task, context=context, **kwargs)
|
|
78
|
+
results[agent_name] = result
|
|
79
|
+
|
|
80
|
+
# Update context for next agent (keep as dict)
|
|
81
|
+
data = {"previous_result": result.get("result", ""), "metadata": result.get("metadata", {})}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
"results": results,
|
|
85
|
+
"conductor": "supervisor",
|
|
86
|
+
"agents_used": agent_order,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
def _swarm_coordinate(
|
|
90
|
+
self,
|
|
91
|
+
task: str,
|
|
92
|
+
data: Any,
|
|
93
|
+
**kwargs
|
|
94
|
+
) -> Dict[str, Any]:
|
|
95
|
+
"""Swarm pattern: Agents collaborate independently"""
|
|
96
|
+
results = {}
|
|
97
|
+
|
|
98
|
+
# All agents work in parallel (simplified - would use async in production)
|
|
99
|
+
context = data if isinstance(data, dict) else {"query": str(data), "original_data": data}
|
|
100
|
+
for agent_name, agent in self.agents.items():
|
|
101
|
+
result = agent.run(task, context=context, **kwargs)
|
|
102
|
+
results[agent_name] = result
|
|
103
|
+
|
|
104
|
+
# Combine results
|
|
105
|
+
return {
|
|
106
|
+
"results": results,
|
|
107
|
+
"conductor": "swarm",
|
|
108
|
+
"agents_used": list(self.agents.keys()),
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def _pipeline_coordinate(
|
|
112
|
+
self,
|
|
113
|
+
task: str,
|
|
114
|
+
data: Any,
|
|
115
|
+
**kwargs
|
|
116
|
+
) -> Dict[str, Any]:
|
|
117
|
+
"""Pipeline pattern: Sequential agent execution"""
|
|
118
|
+
results = {}
|
|
119
|
+
current_data = data
|
|
120
|
+
|
|
121
|
+
# Execute agents in order
|
|
122
|
+
for agent_name, agent in self.agents.items():
|
|
123
|
+
# Ensure context is always a dict
|
|
124
|
+
context = current_data if isinstance(current_data, dict) else {"query": str(current_data), "original_data": current_data}
|
|
125
|
+
result = agent.run(task, context=context, **kwargs)
|
|
126
|
+
results[agent_name] = result
|
|
127
|
+
# Update for next agent (keep as dict)
|
|
128
|
+
current_data = {"previous_result": result.get("result", ""), "metadata": result.get("metadata", {})}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
"results": results,
|
|
132
|
+
"conductor": "pipeline",
|
|
133
|
+
"agents_used": list(self.agents.keys()),
|
|
134
|
+
"final_result": current_data,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
def _determine_agent_order(self, task: str) -> List[str]:
|
|
138
|
+
"""
|
|
139
|
+
Determine which agents to use and in what order.
|
|
140
|
+
|
|
141
|
+
In production, this would use an LLM or routing logic.
|
|
142
|
+
"""
|
|
143
|
+
# Simplified: use all agents in order
|
|
144
|
+
return list(self.agents.keys())
|
|
145
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pipeline Orchestrator
|
|
3
|
+
|
|
4
|
+
Sequential agent workflow pattern.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
from agentensemble.orchestration.ensemble import Ensemble
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PipelineOrchestrator(Ensemble):
|
|
12
|
+
"""
|
|
13
|
+
Pipeline orchestrator - sequential workflow pattern.
|
|
14
|
+
|
|
15
|
+
Agents execute in a defined sequence, passing results to the next agent.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, agents: Dict[str, Any], **kwargs):
|
|
19
|
+
"""
|
|
20
|
+
Initialize pipeline orchestrator.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
agents: Dictionary of agent name -> agent instance (order matters)
|
|
24
|
+
**kwargs: Additional configuration
|
|
25
|
+
"""
|
|
26
|
+
super().__init__(agents, conductor="pipeline", **kwargs)
|
|
27
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Supervisor Orchestrator
|
|
3
|
+
|
|
4
|
+
Central coordinator pattern for managing specialized agents.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List
|
|
8
|
+
from agentensemble.orchestration.ensemble import Ensemble
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SupervisorOrchestrator(Ensemble):
|
|
12
|
+
"""
|
|
13
|
+
Supervisor orchestrator - central coordinator pattern.
|
|
14
|
+
|
|
15
|
+
A supervisor agent manages and delegates tasks to specialized agents.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, agents: Dict[str, Any], **kwargs):
|
|
19
|
+
"""
|
|
20
|
+
Initialize supervisor orchestrator.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
agents: Dictionary of agent name -> agent instance
|
|
24
|
+
**kwargs: Additional configuration
|
|
25
|
+
"""
|
|
26
|
+
super().__init__(agents, conductor="supervisor", **kwargs)
|
|
27
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Swarm Orchestrator
|
|
3
|
+
|
|
4
|
+
Decentralized agent collaboration pattern.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
from agentensemble.orchestration.ensemble import Ensemble
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SwarmOrchestrator(Ensemble):
|
|
12
|
+
"""
|
|
13
|
+
Swarm orchestrator - decentralized collaboration pattern.
|
|
14
|
+
|
|
15
|
+
Agents operate independently and collaborate organically.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, agents: Dict[str, Any], **kwargs):
|
|
19
|
+
"""
|
|
20
|
+
Initialize swarm orchestrator.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
agents: Dictionary of agent name -> agent instance
|
|
24
|
+
**kwargs: Additional configuration
|
|
25
|
+
"""
|
|
26
|
+
super().__init__(agents, conductor="swarm", **kwargs)
|
|
27
|
+
|
agentensemble/py.typed
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Testing & Comparison Framework
|
|
3
|
+
|
|
4
|
+
Provides tools for benchmarking and comparing agents.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from agentensemble.testing.comparison import AgentComparison
|
|
8
|
+
from agentensemble.testing.benchmark import Benchmark
|
|
9
|
+
from agentensemble.testing.metrics import Metrics
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"AgentComparison",
|
|
13
|
+
"Benchmark",
|
|
14
|
+
"Metrics",
|
|
15
|
+
]
|
|
16
|
+
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Benchmark Suite
|
|
3
|
+
|
|
4
|
+
Pre-defined test cases for agent evaluation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Benchmark:
|
|
11
|
+
"""
|
|
12
|
+
Benchmark suite for agent testing.
|
|
13
|
+
|
|
14
|
+
Provides pre-defined test cases for different scenarios.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, test_cases: List[Dict[str, Any]]):
|
|
18
|
+
"""
|
|
19
|
+
Initialize benchmark.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
test_cases: List of test case dictionaries
|
|
23
|
+
"""
|
|
24
|
+
self.test_cases = test_cases
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def research_tasks(cls) -> "Benchmark":
|
|
28
|
+
"""
|
|
29
|
+
Research tasks benchmark.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Benchmark with research-oriented test cases
|
|
33
|
+
"""
|
|
34
|
+
test_cases = [
|
|
35
|
+
{
|
|
36
|
+
"query": "Find the CEO of Stripe",
|
|
37
|
+
"expected_type": "person_name",
|
|
38
|
+
"category": "research",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"query": "What year was OpenAI founded?",
|
|
42
|
+
"expected_type": "year",
|
|
43
|
+
"category": "research",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"query": "Find the official email of Anthropic",
|
|
47
|
+
"expected_type": "email",
|
|
48
|
+
"category": "research",
|
|
49
|
+
},
|
|
50
|
+
]
|
|
51
|
+
return cls(test_cases)
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def data_extraction_tasks(cls) -> "Benchmark":
|
|
55
|
+
"""
|
|
56
|
+
Data extraction tasks benchmark.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Benchmark with extraction-oriented test cases
|
|
60
|
+
"""
|
|
61
|
+
test_cases = [
|
|
62
|
+
{
|
|
63
|
+
"query": "Extract company name and industry from: Stripe is a payments company",
|
|
64
|
+
"expected_type": "structured_data",
|
|
65
|
+
"category": "extraction",
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
return cls(test_cases)
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def load(cls, name: str) -> "Benchmark":
|
|
72
|
+
"""
|
|
73
|
+
Load a benchmark by name.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
name: Benchmark name
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Benchmark instance
|
|
80
|
+
"""
|
|
81
|
+
if name == "research_tasks":
|
|
82
|
+
return cls.research_tasks()
|
|
83
|
+
elif name == "data_extraction_tasks":
|
|
84
|
+
return cls.data_extraction_tasks()
|
|
85
|
+
else:
|
|
86
|
+
raise ValueError(f"Unknown benchmark: {name}")
|
|
87
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Comparison
|
|
3
|
+
|
|
4
|
+
Compare multiple agent implementations on the same tasks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
from agentensemble.agents.base import BaseAgent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AgentComparison:
|
|
12
|
+
"""
|
|
13
|
+
Compare multiple agent implementations.
|
|
14
|
+
|
|
15
|
+
Runs the same tasks on different agents and compares results.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, agents: List[BaseAgent]):
|
|
19
|
+
"""
|
|
20
|
+
Initialize agent comparison.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
agents: List of agent instances to compare
|
|
24
|
+
"""
|
|
25
|
+
self.agents = agents
|
|
26
|
+
|
|
27
|
+
def run(
|
|
28
|
+
self,
|
|
29
|
+
benchmark: Any,
|
|
30
|
+
metrics: Optional[List[str]] = None,
|
|
31
|
+
**kwargs
|
|
32
|
+
) -> Dict[str, Any]:
|
|
33
|
+
"""
|
|
34
|
+
Run comparison benchmark.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
benchmark: Benchmark instance with test cases
|
|
38
|
+
metrics: List of metrics to track
|
|
39
|
+
**kwargs: Additional parameters
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Comparison results
|
|
43
|
+
"""
|
|
44
|
+
metrics = metrics or ["success_rate", "execution_time", "cost"]
|
|
45
|
+
results = {}
|
|
46
|
+
|
|
47
|
+
for agent in self.agents:
|
|
48
|
+
agent_results = []
|
|
49
|
+
|
|
50
|
+
for test_case in benchmark.test_cases:
|
|
51
|
+
result = agent.run(test_case["query"], **kwargs)
|
|
52
|
+
agent_results.append({
|
|
53
|
+
"test_case": test_case,
|
|
54
|
+
"result": result,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
results[agent.name] = agent_results
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
"results": results,
|
|
61
|
+
"metrics": metrics,
|
|
62
|
+
"summary": self._calculate_summary(results, metrics),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def _calculate_summary(
|
|
66
|
+
self,
|
|
67
|
+
results: Dict[str, List[Dict]],
|
|
68
|
+
metrics: List[str]
|
|
69
|
+
) -> Dict[str, Any]:
|
|
70
|
+
"""Calculate summary statistics"""
|
|
71
|
+
summary = {}
|
|
72
|
+
|
|
73
|
+
for agent_name, agent_results in results.items():
|
|
74
|
+
summary[agent_name] = {
|
|
75
|
+
"total_tests": len(agent_results),
|
|
76
|
+
"success_rate": 0.8, # Placeholder
|
|
77
|
+
"avg_execution_time": 2.5, # Placeholder
|
|
78
|
+
"total_cost": 0.05, # Placeholder
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return summary
|
|
82
|
+
|