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.
@@ -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,3 @@
1
+ # Marker file for PEP 561
2
+ # Indicates this package supports type checking
3
+
@@ -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
+