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,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentEnsemble - Orchestrate AI agents in perfect harmony
|
|
3
|
+
|
|
4
|
+
A comprehensive framework for building, testing, and orchestrating multi-agent AI systems.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
__author__ = "Irfan Ali"
|
|
9
|
+
|
|
10
|
+
# Core imports
|
|
11
|
+
from agentensemble.agents import (
|
|
12
|
+
ReActAgent,
|
|
13
|
+
StateGraphAgent,
|
|
14
|
+
RAGAgent,
|
|
15
|
+
HybridAgent,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Structured output agent (optional, requires langchain>=1.1)
|
|
19
|
+
try:
|
|
20
|
+
from agentensemble.agents import StructuredAgent
|
|
21
|
+
STRUCTURED_AVAILABLE = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
STRUCTURED_AVAILABLE = False
|
|
24
|
+
StructuredAgent = None
|
|
25
|
+
|
|
26
|
+
from agentensemble.orchestration import (
|
|
27
|
+
Ensemble,
|
|
28
|
+
SupervisorOrchestrator,
|
|
29
|
+
SwarmOrchestrator,
|
|
30
|
+
PipelineOrchestrator,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from agentensemble.tools import (
|
|
34
|
+
ToolRegistry,
|
|
35
|
+
SearchTool,
|
|
36
|
+
ScraperTool,
|
|
37
|
+
RAGTool,
|
|
38
|
+
ValidationTool,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
from agentensemble.testing import (
|
|
42
|
+
AgentComparison,
|
|
43
|
+
Benchmark,
|
|
44
|
+
Metrics,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
# Version
|
|
49
|
+
"__version__",
|
|
50
|
+
"__author__",
|
|
51
|
+
# Agents
|
|
52
|
+
"ReActAgent",
|
|
53
|
+
"StateGraphAgent",
|
|
54
|
+
"RAGAgent",
|
|
55
|
+
"HybridAgent",
|
|
56
|
+
# Orchestration
|
|
57
|
+
"Ensemble",
|
|
58
|
+
"SupervisorOrchestrator",
|
|
59
|
+
"SwarmOrchestrator",
|
|
60
|
+
"PipelineOrchestrator",
|
|
61
|
+
# Tools
|
|
62
|
+
"ToolRegistry",
|
|
63
|
+
"SearchTool",
|
|
64
|
+
"ScraperTool",
|
|
65
|
+
"RAGTool",
|
|
66
|
+
"ValidationTool",
|
|
67
|
+
# Testing
|
|
68
|
+
"AgentComparison",
|
|
69
|
+
"Benchmark",
|
|
70
|
+
"Metrics",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
# Add StructuredAgent if available
|
|
74
|
+
if STRUCTURED_AVAILABLE:
|
|
75
|
+
__all__.append("StructuredAgent")
|
|
76
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reference Agent Implementations
|
|
3
|
+
|
|
4
|
+
Provides production-ready agent architectures:
|
|
5
|
+
- ReActAgent: Simple reasoning + acting pattern
|
|
6
|
+
- StateGraphAgent: Custom nodes with intelligent routing
|
|
7
|
+
- RAGAgent: RAG-enhanced with fallback strategies
|
|
8
|
+
- HybridAgent: Advanced iterative refinement
|
|
9
|
+
- StructuredAgent: Structured output support (langchain>=1.1 or with_structured_output)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from agentensemble.agents.react_agent import ReActAgent
|
|
13
|
+
from agentensemble.agents.stategraph_agent import StateGraphAgent
|
|
14
|
+
from agentensemble.agents.rag_agent import RAGAgent
|
|
15
|
+
from agentensemble.agents.hybrid_agent import HybridAgent
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from agentensemble.agents.structured_agent import StructuredAgent
|
|
19
|
+
STRUCTURED_AVAILABLE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
STRUCTURED_AVAILABLE = False
|
|
22
|
+
StructuredAgent = None
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"ReActAgent",
|
|
26
|
+
"StateGraphAgent",
|
|
27
|
+
"RAGAgent",
|
|
28
|
+
"HybridAgent",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
if STRUCTURED_AVAILABLE:
|
|
32
|
+
__all__.append("StructuredAgent")
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Agent Class
|
|
3
|
+
|
|
4
|
+
Defines the interface and common functionality for all agents.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AgentState(BaseModel):
|
|
13
|
+
"""Base state model for agents"""
|
|
14
|
+
|
|
15
|
+
query: str
|
|
16
|
+
context: Dict[str, Any] = Field(default_factory=dict)
|
|
17
|
+
iteration_count: int = 0
|
|
18
|
+
tool_calls: List[Dict[str, Any]] = Field(default_factory=list)
|
|
19
|
+
result: Optional[str] = None
|
|
20
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BaseAgent(ABC):
|
|
24
|
+
"""
|
|
25
|
+
Base class for all agents in AgentEnsemble.
|
|
26
|
+
|
|
27
|
+
All agent implementations should inherit from this class.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
name: str,
|
|
33
|
+
tools: Optional[List[Any]] = None,
|
|
34
|
+
max_iterations: int = 10,
|
|
35
|
+
**kwargs
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Initialize the agent.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
name: Name of the agent
|
|
42
|
+
tools: List of tools available to the agent
|
|
43
|
+
max_iterations: Maximum number of iterations
|
|
44
|
+
**kwargs: Additional configuration
|
|
45
|
+
"""
|
|
46
|
+
self.name = name
|
|
47
|
+
self.tools = tools or []
|
|
48
|
+
self.max_iterations = max_iterations
|
|
49
|
+
self.config = kwargs
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def run(self, query: str, **kwargs) -> Dict[str, Any]:
|
|
53
|
+
"""
|
|
54
|
+
Execute the agent with a given query.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
query: The input query
|
|
58
|
+
**kwargs: Additional parameters
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Dictionary containing the result and metadata
|
|
62
|
+
"""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def _validate_state(self, state: AgentState) -> bool:
|
|
66
|
+
"""Validate agent state"""
|
|
67
|
+
return state.iteration_count < self.max_iterations
|
|
68
|
+
|
|
69
|
+
def _update_state(
|
|
70
|
+
self,
|
|
71
|
+
state: AgentState,
|
|
72
|
+
result: Optional[str] = None,
|
|
73
|
+
tool_calls: Optional[List[Dict]] = None,
|
|
74
|
+
**metadata
|
|
75
|
+
) -> AgentState:
|
|
76
|
+
"""Update agent state"""
|
|
77
|
+
if result:
|
|
78
|
+
state.result = result
|
|
79
|
+
if tool_calls:
|
|
80
|
+
state.tool_calls.extend(tool_calls)
|
|
81
|
+
state.iteration_count += 1
|
|
82
|
+
state.metadata.update(metadata)
|
|
83
|
+
return state
|
|
84
|
+
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hybrid Agent Implementation
|
|
3
|
+
|
|
4
|
+
Advanced agent with iterative refinement and early stopping.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
from agentensemble.agents.base import BaseAgent, AgentState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HybridAgent(BaseAgent):
|
|
12
|
+
"""
|
|
13
|
+
Hybrid Agent with intelligent routing and iterative refinement.
|
|
14
|
+
|
|
15
|
+
Combines multiple strategies: analysis → search → RAG → validation
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
name: str = "hybrid_agent",
|
|
21
|
+
tools: Optional[list] = None,
|
|
22
|
+
max_iterations: int = 15,
|
|
23
|
+
early_stopping: bool = True,
|
|
24
|
+
**kwargs
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Initialize Hybrid agent.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
name: Agent name
|
|
31
|
+
tools: Available tools
|
|
32
|
+
max_iterations: Maximum iterations
|
|
33
|
+
early_stopping: Enable early stopping when answer found
|
|
34
|
+
**kwargs: Additional configuration
|
|
35
|
+
"""
|
|
36
|
+
super().__init__(name, tools, max_iterations, **kwargs)
|
|
37
|
+
self.early_stopping = early_stopping
|
|
38
|
+
|
|
39
|
+
def run(self, query: str, **kwargs) -> Dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Execute Hybrid agent with intelligent routing.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
query: Input query
|
|
45
|
+
**kwargs: Additional parameters
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Result dictionary
|
|
49
|
+
"""
|
|
50
|
+
state = AgentState(query=query, context=kwargs.get("context", {}))
|
|
51
|
+
|
|
52
|
+
# Hybrid workflow: Analyze → Search → RAG → Validate
|
|
53
|
+
for iteration in range(self.max_iterations):
|
|
54
|
+
if not self._validate_state(state):
|
|
55
|
+
break
|
|
56
|
+
|
|
57
|
+
# Determine next action
|
|
58
|
+
action = self._decide_action(state)
|
|
59
|
+
|
|
60
|
+
if action == "ANSWER":
|
|
61
|
+
if self.early_stopping:
|
|
62
|
+
break
|
|
63
|
+
elif action == "SEARCH":
|
|
64
|
+
result = self._search(state)
|
|
65
|
+
state.context["search_results"] = result
|
|
66
|
+
elif action == "RAG":
|
|
67
|
+
result = self._rag(state)
|
|
68
|
+
state.context["rag_results"] = result
|
|
69
|
+
elif action == "VALIDATE":
|
|
70
|
+
validation = self._validate(state)
|
|
71
|
+
state.context["validation"] = validation
|
|
72
|
+
else:
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
state = self._update_state(state, iteration=iteration)
|
|
76
|
+
|
|
77
|
+
# Final answer synthesis
|
|
78
|
+
if not state.result:
|
|
79
|
+
state.result = self._synthesize_answer(state)
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
"result": state.result or "No result generated",
|
|
83
|
+
"metadata": {
|
|
84
|
+
"iterations": state.iteration_count,
|
|
85
|
+
"actions_taken": state.context.get("actions", []),
|
|
86
|
+
"agent": self.name,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
def _decide_action(self, state: AgentState) -> str:
|
|
91
|
+
"""
|
|
92
|
+
Intelligent action decision.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Next action: "SEARCH", "RAG", "VALIDATE", "ANSWER"
|
|
96
|
+
"""
|
|
97
|
+
# Simplified logic - in production, use LLM for routing
|
|
98
|
+
if state.result:
|
|
99
|
+
return "ANSWER"
|
|
100
|
+
if state.iteration_count == 0:
|
|
101
|
+
return "SEARCH"
|
|
102
|
+
if state.iteration_count == 1:
|
|
103
|
+
return "RAG"
|
|
104
|
+
if state.iteration_count == 2:
|
|
105
|
+
return "VALIDATE"
|
|
106
|
+
return "ANSWER"
|
|
107
|
+
|
|
108
|
+
def _search(self, state: AgentState) -> str:
|
|
109
|
+
"""Execute search"""
|
|
110
|
+
search_tool = next((t for t in self.tools if hasattr(t, "name") and t.name == "search"), None)
|
|
111
|
+
if search_tool:
|
|
112
|
+
return search_tool.run(state.query)
|
|
113
|
+
return "Search tool not available"
|
|
114
|
+
|
|
115
|
+
def _rag(self, state: AgentState) -> str:
|
|
116
|
+
"""Execute RAG"""
|
|
117
|
+
rag_tool = next((t for t in self.tools if hasattr(t, "name") and t.name == "rag"), None)
|
|
118
|
+
if rag_tool:
|
|
119
|
+
# Handle search_results as either string or dict
|
|
120
|
+
search_results = state.context.get("search_results", "")
|
|
121
|
+
if isinstance(search_results, dict):
|
|
122
|
+
urls = search_results.get("urls", [])
|
|
123
|
+
else:
|
|
124
|
+
urls = [] # No URLs from search
|
|
125
|
+
return rag_tool.run(state.query, urls=urls)
|
|
126
|
+
return "RAG tool not available"
|
|
127
|
+
|
|
128
|
+
def _validate(self, state: AgentState) -> Dict[str, Any]:
|
|
129
|
+
"""Execute validation"""
|
|
130
|
+
validator = next((t for t in self.tools if hasattr(t, "name") and t.name == "validator"), None)
|
|
131
|
+
if validator:
|
|
132
|
+
result = state.context.get("rag_results", "")
|
|
133
|
+
return validator.run(result, context=state.context)
|
|
134
|
+
return {"valid": True, "confidence": 0.8}
|
|
135
|
+
|
|
136
|
+
def _synthesize_answer(self, state: AgentState) -> str:
|
|
137
|
+
"""Synthesize final answer from all results"""
|
|
138
|
+
# Combine search and RAG results
|
|
139
|
+
search_result = state.context.get("search_results", "")
|
|
140
|
+
rag_result = state.context.get("rag_results", "")
|
|
141
|
+
|
|
142
|
+
if rag_result and rag_result != "RAG tool not available":
|
|
143
|
+
return rag_result
|
|
144
|
+
if search_result and search_result != "Search tool not available":
|
|
145
|
+
return search_result
|
|
146
|
+
return "Unable to generate answer"
|
|
147
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RAG-Enhanced Agent
|
|
3
|
+
|
|
4
|
+
Agent with RAG capabilities and fallback strategies.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
from agentensemble.agents.base import BaseAgent, AgentState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RAGAgent(BaseAgent):
|
|
12
|
+
"""
|
|
13
|
+
RAG-Enhanced Agent with fallback strategies.
|
|
14
|
+
|
|
15
|
+
Implements scraping → embedding → retrieval with query variations.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
name: str = "rag_agent",
|
|
21
|
+
tools: Optional[list] = None,
|
|
22
|
+
max_iterations: int = 10,
|
|
23
|
+
fallback_strategies: int = 3,
|
|
24
|
+
**kwargs
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Initialize RAG agent.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
name: Agent name
|
|
31
|
+
tools: Available tools (should include RAG tool)
|
|
32
|
+
max_iterations: Maximum iterations
|
|
33
|
+
fallback_strategies: Number of fallback query variations
|
|
34
|
+
**kwargs: Additional configuration
|
|
35
|
+
"""
|
|
36
|
+
super().__init__(name, tools, max_iterations, **kwargs)
|
|
37
|
+
self.fallback_strategies = fallback_strategies
|
|
38
|
+
|
|
39
|
+
def run(self, query: str, urls: Optional[List[str]] = None, **kwargs) -> Dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Execute RAG agent with fallback strategies.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
query: Input query
|
|
45
|
+
urls: Optional URLs to use for RAG
|
|
46
|
+
**kwargs: Additional parameters
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Result dictionary
|
|
50
|
+
"""
|
|
51
|
+
state = AgentState(query=query, context=kwargs.get("context", {}))
|
|
52
|
+
|
|
53
|
+
# Try primary query
|
|
54
|
+
result = self._query_rag(state.query, urls)
|
|
55
|
+
|
|
56
|
+
if result and result != "not found":
|
|
57
|
+
state.result = result
|
|
58
|
+
else:
|
|
59
|
+
# Try fallback strategies
|
|
60
|
+
for i in range(self.fallback_strategies):
|
|
61
|
+
fallback_query = self._generate_fallback_query(state.query, i)
|
|
62
|
+
result = self._query_rag(fallback_query, urls)
|
|
63
|
+
if result and result != "not found":
|
|
64
|
+
state.result = result
|
|
65
|
+
break
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
"result": state.result or "No result found",
|
|
69
|
+
"metadata": {
|
|
70
|
+
"fallback_attempts": min(self.fallback_strategies, state.iteration_count),
|
|
71
|
+
"agent": self.name,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
def _query_rag(self, query: str, urls: Optional[List[str]]) -> str:
|
|
76
|
+
"""Query RAG system"""
|
|
77
|
+
# Find RAG tool
|
|
78
|
+
rag_tool = next((t for t in self.tools if hasattr(t, "name") and t.name == "rag"), None)
|
|
79
|
+
if rag_tool:
|
|
80
|
+
return rag_tool.run(query, urls=urls)
|
|
81
|
+
return "RAG tool not available"
|
|
82
|
+
|
|
83
|
+
def _generate_fallback_query(self, original_query: str, attempt: int) -> str:
|
|
84
|
+
"""Generate fallback query variation"""
|
|
85
|
+
# Simplified - in production, use LLM to rephrase
|
|
86
|
+
variations = [
|
|
87
|
+
f"More details about: {original_query}",
|
|
88
|
+
f"Alternative phrasing: {original_query}",
|
|
89
|
+
f"Related information: {original_query}",
|
|
90
|
+
]
|
|
91
|
+
return variations[attempt % len(variations)]
|
|
92
|
+
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ReAct Agent Implementation
|
|
3
|
+
|
|
4
|
+
Simple reasoning + acting pattern with tool calling.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
from agentensemble.agents.base import BaseAgent, AgentState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ReActAgent(BaseAgent):
|
|
12
|
+
"""
|
|
13
|
+
ReAct (Reasoning + Acting) Agent
|
|
14
|
+
|
|
15
|
+
A simple agent that reasons about actions and uses tools to accomplish tasks.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
name: str = "react_agent",
|
|
21
|
+
tools: Optional[List[Any]] = None,
|
|
22
|
+
max_iterations: int = 10,
|
|
23
|
+
**kwargs
|
|
24
|
+
):
|
|
25
|
+
"""
|
|
26
|
+
Initialize ReAct agent.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
name: Agent name
|
|
30
|
+
tools: Available tools
|
|
31
|
+
max_iterations: Maximum reasoning/acting cycles
|
|
32
|
+
**kwargs: Additional configuration
|
|
33
|
+
"""
|
|
34
|
+
super().__init__(name, tools, max_iterations, **kwargs)
|
|
35
|
+
|
|
36
|
+
def run(self, query: str, **kwargs) -> Dict[str, Any]:
|
|
37
|
+
"""
|
|
38
|
+
Execute ReAct agent.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
query: Input query
|
|
42
|
+
**kwargs: Additional parameters
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Result dictionary with answer and metadata
|
|
46
|
+
"""
|
|
47
|
+
state = AgentState(query=query, context=kwargs.get("context", {}))
|
|
48
|
+
|
|
49
|
+
# Simple ReAct loop: Think -> Act -> Observe
|
|
50
|
+
for iteration in range(self.max_iterations):
|
|
51
|
+
if not self._validate_state(state):
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
# Think: Reason about what to do
|
|
55
|
+
action = self._think(state)
|
|
56
|
+
|
|
57
|
+
if action == "ANSWER":
|
|
58
|
+
break
|
|
59
|
+
|
|
60
|
+
# Act: Use a tool
|
|
61
|
+
if action == "TOOL":
|
|
62
|
+
tool_result = self._act(state)
|
|
63
|
+
state.context["last_tool_result"] = tool_result
|
|
64
|
+
|
|
65
|
+
# If we got a good result, use it as the answer
|
|
66
|
+
if tool_result and tool_result != "No tools available" and tool_result != "Tool execution failed":
|
|
67
|
+
state.result = tool_result
|
|
68
|
+
|
|
69
|
+
state = self._update_state(
|
|
70
|
+
state,
|
|
71
|
+
tool_calls=[{"iteration": iteration, "result": tool_result}]
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
break
|
|
75
|
+
|
|
76
|
+
# If no result yet, try to synthesize from context
|
|
77
|
+
if not state.result and state.context.get("last_tool_result"):
|
|
78
|
+
state.result = state.context["last_tool_result"]
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
"result": state.result or state.context.get("last_tool_result", "No result generated"),
|
|
82
|
+
"metadata": {
|
|
83
|
+
"iterations": state.iteration_count,
|
|
84
|
+
"tool_calls": len(state.tool_calls),
|
|
85
|
+
"agent": self.name,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
def _think(self, state: AgentState) -> str:
|
|
90
|
+
"""
|
|
91
|
+
Reasoning step: Decide what action to take.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
"TOOL" to use a tool, "ANSWER" to provide final answer
|
|
95
|
+
"""
|
|
96
|
+
# Simplified logic - in production, this would use an LLM
|
|
97
|
+
if state.result:
|
|
98
|
+
return "ANSWER"
|
|
99
|
+
if state.iteration_count < 3 and self.tools:
|
|
100
|
+
return "TOOL"
|
|
101
|
+
return "ANSWER"
|
|
102
|
+
|
|
103
|
+
def _act(self, state: AgentState) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Acting step: Execute a tool.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Tool result
|
|
109
|
+
"""
|
|
110
|
+
if not self.tools:
|
|
111
|
+
return "No tools available"
|
|
112
|
+
|
|
113
|
+
# Use first available tool (simplified)
|
|
114
|
+
tool = self.tools[0]
|
|
115
|
+
if hasattr(tool, "run"):
|
|
116
|
+
return tool.run(state.query)
|
|
117
|
+
return "Tool execution failed"
|
|
118
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
StateGraph Agent Implementation
|
|
3
|
+
|
|
4
|
+
Custom StateGraph with intelligent routing between nodes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
from agentensemble.agents.base import BaseAgent, AgentState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StateGraphAgent(BaseAgent):
|
|
12
|
+
"""
|
|
13
|
+
StateGraph Agent with custom nodes and routing.
|
|
14
|
+
|
|
15
|
+
Implements a graph-based workflow with intelligent routing.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
name: str = "stategraph_agent",
|
|
21
|
+
nodes: Optional[Dict[str, Any]] = None,
|
|
22
|
+
tools: Optional[list] = None,
|
|
23
|
+
max_iterations: int = 15,
|
|
24
|
+
**kwargs
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Initialize StateGraph agent.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
name: Agent name
|
|
31
|
+
nodes: Dictionary of node name -> node function
|
|
32
|
+
tools: Available tools
|
|
33
|
+
max_iterations: Maximum graph iterations
|
|
34
|
+
**kwargs: Additional configuration
|
|
35
|
+
"""
|
|
36
|
+
super().__init__(name, tools, max_iterations, **kwargs)
|
|
37
|
+
self.nodes = nodes or {}
|
|
38
|
+
|
|
39
|
+
def run(self, query: str, **kwargs) -> Dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Execute StateGraph agent.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
query: Input query
|
|
45
|
+
**kwargs: Additional parameters
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Result dictionary
|
|
49
|
+
"""
|
|
50
|
+
state = AgentState(query=query, context=kwargs.get("context", {}))
|
|
51
|
+
|
|
52
|
+
# Simplified StateGraph execution
|
|
53
|
+
current_node = "start"
|
|
54
|
+
|
|
55
|
+
for iteration in range(self.max_iterations):
|
|
56
|
+
if not self._validate_state(state):
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
# Execute current node
|
|
60
|
+
if current_node in self.nodes:
|
|
61
|
+
node_result = self.nodes[current_node](state)
|
|
62
|
+
state = self._update_state(state, **node_result)
|
|
63
|
+
|
|
64
|
+
# Route to next node
|
|
65
|
+
current_node = self._route(state, current_node)
|
|
66
|
+
if current_node == "end":
|
|
67
|
+
break
|
|
68
|
+
else:
|
|
69
|
+
break
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
"result": state.result or "No result generated",
|
|
73
|
+
"metadata": {
|
|
74
|
+
"iterations": state.iteration_count,
|
|
75
|
+
"nodes_visited": list(self.nodes.keys()),
|
|
76
|
+
"agent": self.name,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
def _route(self, state: AgentState, current_node: str) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Intelligent routing logic.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
state: Current state
|
|
86
|
+
current_node: Current node name
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Next node name or "end"
|
|
90
|
+
"""
|
|
91
|
+
# Simplified routing - in production, this would use LLM or rules
|
|
92
|
+
if state.result:
|
|
93
|
+
return "end"
|
|
94
|
+
if current_node == "start":
|
|
95
|
+
return "analyze" if "analyze" in self.nodes else "end"
|
|
96
|
+
return "end"
|
|
97
|
+
|