ai-agentswarm 0.1.1__tar.gz
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.
- ai_agentswarm-0.1.1/LICENSE +21 -0
- ai_agentswarm-0.1.1/MANIFEST.in +2 -0
- ai_agentswarm-0.1.1/PKG-INFO +105 -0
- ai_agentswarm-0.1.1/README.md +46 -0
- ai_agentswarm-0.1.1/pyproject.toml +56 -0
- ai_agentswarm-0.1.1/setup.cfg +4 -0
- ai_agentswarm-0.1.1/src/agentswarm/__init__.py +0 -0
- ai_agentswarm-0.1.1/src/agentswarm/agents/__init__.py +26 -0
- ai_agentswarm-0.1.1/src/agentswarm/agents/base_agent.py +53 -0
- ai_agentswarm-0.1.1/src/agentswarm/agents/gathering_agent.py +25 -0
- ai_agentswarm-0.1.1/src/agentswarm/agents/map_reduce_agent.py +60 -0
- ai_agentswarm-0.1.1/src/agentswarm/agents/mcp_agent.py +99 -0
- ai_agentswarm-0.1.1/src/agentswarm/agents/merge_agent.py +25 -0
- ai_agentswarm-0.1.1/src/agentswarm/agents/react_agent.py +189 -0
- ai_agentswarm-0.1.1/src/agentswarm/agents/thinking_agent.py +27 -0
- ai_agentswarm-0.1.1/src/agentswarm/agents/transformer_agent.py +47 -0
- ai_agentswarm-0.1.1/src/agentswarm/datamodels/__init__.py +16 -0
- ai_agentswarm-0.1.1/src/agentswarm/datamodels/context.py +139 -0
- ai_agentswarm-0.1.1/src/agentswarm/datamodels/local_store.py +21 -0
- ai_agentswarm-0.1.1/src/agentswarm/datamodels/message.py +10 -0
- ai_agentswarm-0.1.1/src/agentswarm/datamodels/responses.py +17 -0
- ai_agentswarm-0.1.1/src/agentswarm/datamodels/store.py +34 -0
- ai_agentswarm-0.1.1/src/agentswarm/llms/__init__.py +11 -0
- ai_agentswarm-0.1.1/src/agentswarm/llms/gemini.py +94 -0
- ai_agentswarm-0.1.1/src/agentswarm/llms/llm.py +31 -0
- ai_agentswarm-0.1.1/src/agentswarm/utils/__init__.py +3 -0
- ai_agentswarm-0.1.1/src/agentswarm/utils/trace_view.py +907 -0
- ai_agentswarm-0.1.1/src/agentswarm/utils/tracing.py +135 -0
- ai_agentswarm-0.1.1/src/ai_agentswarm.egg-info/PKG-INFO +105 -0
- ai_agentswarm-0.1.1/src/ai_agentswarm.egg-info/SOURCES.txt +31 -0
- ai_agentswarm-0.1.1/src/ai_agentswarm.egg-info/dependency_links.txt +1 -0
- ai_agentswarm-0.1.1/src/ai_agentswarm.egg-info/requires.txt +19 -0
- ai_agentswarm-0.1.1/src/ai_agentswarm.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Luca Roverelli
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ai-agentswarm
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A recursive, functional, and state-isolated Multi-Agent Framework.
|
|
5
|
+
Author-email: Lucas Roverelli <lucas.roverelli@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Luca Roverelli
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
Project-URL: Homepage, https://github.com/ai-agentswarm/agentswarm
|
|
28
|
+
Project-URL: Bug Tracker, https://github.com/ai-agentswarm/agentswarm/issues
|
|
29
|
+
Project-URL: Documentation, https://github.com/ai-agentswarm/agentswarm#readme
|
|
30
|
+
Keywords: ai,agents,llm,framework,recursive,react
|
|
31
|
+
Classifier: Development Status :: 3 - Alpha
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
38
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
39
|
+
Requires-Python: >=3.10
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
License-File: LICENSE
|
|
42
|
+
Requires-Dist: pydantic>=2.12.4
|
|
43
|
+
Requires-Dist: google-genai>=1.50.1
|
|
44
|
+
Requires-Dist: mcp
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: pytest; extra == "dev"
|
|
47
|
+
Requires-Dist: twine; extra == "dev"
|
|
48
|
+
Requires-Dist: build; extra == "dev"
|
|
49
|
+
Requires-Dist: black; extra == "dev"
|
|
50
|
+
Requires-Dist: isort; extra == "dev"
|
|
51
|
+
Provides-Extra: examples
|
|
52
|
+
Requires-Dist: httpx; extra == "examples"
|
|
53
|
+
Requires-Dist: python-dotenv; extra == "examples"
|
|
54
|
+
Provides-Extra: docs
|
|
55
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
56
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
57
|
+
Requires-Dist: mkdocstrings[python]; extra == "docs"
|
|
58
|
+
Dynamic: license-file
|
|
59
|
+
|
|
60
|
+
# 🐝 AgentSwarm
|
|
61
|
+
|
|
62
|
+
**A Recursive, Functional, and Type-Safe Framework for Autonomous AI Agents.**
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
**AgentSwarm** is a next-generation agentic orchestration framework designed to solve the critical issues of **Context Pollution** and **Complex Orchestration** in multi-agent systems.
|
|
67
|
+
|
|
68
|
+
Unlike chat-based frameworks (like AutoGen) or static graph frameworks (like LangGraph), AgentSwarm treats agents as **Strongly Typed Asynchronous Functions** that operate in isolated environments ("Tabula Rasa"), natively supporting recursion and Map-Reduce patterns.
|
|
69
|
+
|
|
70
|
+
## 🚀 Why AgentSwarm?
|
|
71
|
+
|
|
72
|
+
Current architectures suffer from two main problems:
|
|
73
|
+
1. **Context Pollution:** Sub-agents inherit the parent's entire chat history, wasting tokens, increasing latency, and confusing the model.
|
|
74
|
+
2. **Boilerplate Hell:** Defining dynamic recursive graphs requires complex configuration and rigid state definitions.
|
|
75
|
+
|
|
76
|
+
**AgentSwarm solves these problems:**
|
|
77
|
+
|
|
78
|
+
* **🧠 Tabula Rasa Execution:** Every invoked agent starts with a *clean* context. The sub-agent receives only the specific input required, executes the task, and returns the output. No noise, no context window overflow.
|
|
79
|
+
* **📦 Native Blackboard Pattern:** Agents don't pass massive raw text strings back and forth. They use a shared **Key-Value Store** to manipulate, transform, and merge data, exchanging only references (Keys).
|
|
80
|
+
* **🛡️ Type-Safe by Design:** Built on Pydantic and Python Generics. Agent inputs and outputs are validated at runtime, and JSON schemas for the LLM are generated automatically from type hints.
|
|
81
|
+
* **⚡ Implicit Parallelism:** The `ReActAgent` engine automatically handles parallel tool execution (Map) and result aggregation (Reduce) without complex graph definitions.
|
|
82
|
+
|
|
83
|
+
## 🛠️ Architecture
|
|
84
|
+
|
|
85
|
+
AgentSwarm is built on three fundamental concepts:
|
|
86
|
+
|
|
87
|
+
### 1. Agent-as-a-Function
|
|
88
|
+
Every agent is a class inheriting from `BaseAgent[Input, Output]`. There are no "nodes" or "edges" to manually define. Orchestration emerges naturally from the functional calls between agents.
|
|
89
|
+
Moreover, agents are **strongly typed** and **asynchronous** by design, making them easy to compose and debug. When errors occur, they are propagated as standard exceptions, that can be managed by agents at different levels.
|
|
90
|
+
|
|
91
|
+
### 2. The "Store" (Shared Memory)
|
|
92
|
+
Instead of overloading the chat context, agents use the `Store` to manage information. We provide a set of agents to ease the management of the store:
|
|
93
|
+
* **GatheringAgent:** Retrieves data from the store for display or processing.
|
|
94
|
+
* **TransformerAgent:** Transforms data in the store (e.g., summarize, filter) using natural language commands and returns a new Key, keeping the context light.
|
|
95
|
+
* **MergeAgent:** Combines multiple data keys into a single entity.
|
|
96
|
+
|
|
97
|
+
### 3. Recursive Map-Reduce
|
|
98
|
+
The Map-Reduce pattern is a powerful way to decompose complex tasks into smaller, parallelizable subtasks. It is used since decades in parallel computing. With AgentSwarm, we borrow this pattern to ease the orchestration of complex tasks, preventing context pullution or exosting, prefering to use the store as a shared memory.
|
|
99
|
+
A `MapReduceAgent` can decompose complex tasks, dynamically instantiate clones of itself or other agents, and execute work in parallel, avoiding deadlocks via instance isolation.
|
|
100
|
+
|
|
101
|
+
### 4. Interoperability & Hybrid Execution
|
|
102
|
+
AgentSwarm is designed to be an open ecosystem, not a walled garden.
|
|
103
|
+
* **Protocol Agnostic:** It (will) supports the **Model Context Protocol (MCP)** and **Agent-to-Agent (A2A)** standards, allowing your agents to interact seamlessly with external tools and third-party agent networks. You can also easily wrap existing **LangChain** tools and run them within the Swarm.
|
|
104
|
+
* **Hybrid Runtime:** Each agent can be executed locally, or remotly, exploiting **AWS** or **Google Cloud** services. Also the Store can be a remote key-value store, like Redis.
|
|
105
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# 🐝 AgentSwarm
|
|
2
|
+
|
|
3
|
+
**A Recursive, Functional, and Type-Safe Framework for Autonomous AI Agents.**
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
**AgentSwarm** is a next-generation agentic orchestration framework designed to solve the critical issues of **Context Pollution** and **Complex Orchestration** in multi-agent systems.
|
|
8
|
+
|
|
9
|
+
Unlike chat-based frameworks (like AutoGen) or static graph frameworks (like LangGraph), AgentSwarm treats agents as **Strongly Typed Asynchronous Functions** that operate in isolated environments ("Tabula Rasa"), natively supporting recursion and Map-Reduce patterns.
|
|
10
|
+
|
|
11
|
+
## 🚀 Why AgentSwarm?
|
|
12
|
+
|
|
13
|
+
Current architectures suffer from two main problems:
|
|
14
|
+
1. **Context Pollution:** Sub-agents inherit the parent's entire chat history, wasting tokens, increasing latency, and confusing the model.
|
|
15
|
+
2. **Boilerplate Hell:** Defining dynamic recursive graphs requires complex configuration and rigid state definitions.
|
|
16
|
+
|
|
17
|
+
**AgentSwarm solves these problems:**
|
|
18
|
+
|
|
19
|
+
* **🧠 Tabula Rasa Execution:** Every invoked agent starts with a *clean* context. The sub-agent receives only the specific input required, executes the task, and returns the output. No noise, no context window overflow.
|
|
20
|
+
* **📦 Native Blackboard Pattern:** Agents don't pass massive raw text strings back and forth. They use a shared **Key-Value Store** to manipulate, transform, and merge data, exchanging only references (Keys).
|
|
21
|
+
* **🛡️ Type-Safe by Design:** Built on Pydantic and Python Generics. Agent inputs and outputs are validated at runtime, and JSON schemas for the LLM are generated automatically from type hints.
|
|
22
|
+
* **⚡ Implicit Parallelism:** The `ReActAgent` engine automatically handles parallel tool execution (Map) and result aggregation (Reduce) without complex graph definitions.
|
|
23
|
+
|
|
24
|
+
## 🛠️ Architecture
|
|
25
|
+
|
|
26
|
+
AgentSwarm is built on three fundamental concepts:
|
|
27
|
+
|
|
28
|
+
### 1. Agent-as-a-Function
|
|
29
|
+
Every agent is a class inheriting from `BaseAgent[Input, Output]`. There are no "nodes" or "edges" to manually define. Orchestration emerges naturally from the functional calls between agents.
|
|
30
|
+
Moreover, agents are **strongly typed** and **asynchronous** by design, making them easy to compose and debug. When errors occur, they are propagated as standard exceptions, that can be managed by agents at different levels.
|
|
31
|
+
|
|
32
|
+
### 2. The "Store" (Shared Memory)
|
|
33
|
+
Instead of overloading the chat context, agents use the `Store` to manage information. We provide a set of agents to ease the management of the store:
|
|
34
|
+
* **GatheringAgent:** Retrieves data from the store for display or processing.
|
|
35
|
+
* **TransformerAgent:** Transforms data in the store (e.g., summarize, filter) using natural language commands and returns a new Key, keeping the context light.
|
|
36
|
+
* **MergeAgent:** Combines multiple data keys into a single entity.
|
|
37
|
+
|
|
38
|
+
### 3. Recursive Map-Reduce
|
|
39
|
+
The Map-Reduce pattern is a powerful way to decompose complex tasks into smaller, parallelizable subtasks. It is used since decades in parallel computing. With AgentSwarm, we borrow this pattern to ease the orchestration of complex tasks, preventing context pullution or exosting, prefering to use the store as a shared memory.
|
|
40
|
+
A `MapReduceAgent` can decompose complex tasks, dynamically instantiate clones of itself or other agents, and execute work in parallel, avoiding deadlocks via instance isolation.
|
|
41
|
+
|
|
42
|
+
### 4. Interoperability & Hybrid Execution
|
|
43
|
+
AgentSwarm is designed to be an open ecosystem, not a walled garden.
|
|
44
|
+
* **Protocol Agnostic:** It (will) supports the **Model Context Protocol (MCP)** and **Agent-to-Agent (A2A)** standards, allowing your agents to interact seamlessly with external tools and third-party agent networks. You can also easily wrap existing **LangChain** tools and run them within the Swarm.
|
|
45
|
+
* **Hybrid Runtime:** Each agent can be executed locally, or remotly, exploiting **AWS** or **Google Cloud** services. Also the Store can be a remote key-value store, like Redis.
|
|
46
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ai-agentswarm"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "A recursive, functional, and state-isolated Multi-Agent Framework."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Lucas Roverelli", email = "lucas.roverelli@gmail.com" },
|
|
12
|
+
]
|
|
13
|
+
license = { file = "LICENSE" }
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"pydantic>=2.12.4",
|
|
27
|
+
"google-genai>=1.50.1",
|
|
28
|
+
"mcp"
|
|
29
|
+
]
|
|
30
|
+
keywords = ["ai", "agents", "llm", "framework", "recursive", "react"]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest",
|
|
35
|
+
"twine",
|
|
36
|
+
"build",
|
|
37
|
+
"black",
|
|
38
|
+
"isort"
|
|
39
|
+
]
|
|
40
|
+
examples = [
|
|
41
|
+
"httpx",
|
|
42
|
+
"python-dotenv"
|
|
43
|
+
]
|
|
44
|
+
docs = [
|
|
45
|
+
"mkdocs",
|
|
46
|
+
"mkdocs-material",
|
|
47
|
+
"mkdocstrings[python]"
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[project.urls]
|
|
51
|
+
"Homepage" = "https://github.com/ai-agentswarm/agentswarm"
|
|
52
|
+
"Bug Tracker" = "https://github.com/ai-agentswarm/agentswarm/issues"
|
|
53
|
+
"Documentation" = "https://github.com/ai-agentswarm/agentswarm#readme"
|
|
54
|
+
|
|
55
|
+
[tool.setuptools.packages.find]
|
|
56
|
+
where = ["src"]
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from .react_agent import ReActAgent
|
|
2
|
+
from .map_reduce_agent import MapReduceAgent, MapReduceInput
|
|
3
|
+
from .gathering_agent import GatheringAgent, GatheringAgentInput
|
|
4
|
+
from .merge_agent import MergeAgent, MergeAgentInput
|
|
5
|
+
from .transformer_agent import TransformerAgent, TransformerAgentInput
|
|
6
|
+
from .thinking_agent import ThinkingAgent, ThinkingInput
|
|
7
|
+
from .base_agent import BaseAgent
|
|
8
|
+
|
|
9
|
+
from .mcp_agent import MCPBaseAgent, MCPToolAgent
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ReActAgent",
|
|
13
|
+
"MapReduceAgent",
|
|
14
|
+
"MapReduceInput",
|
|
15
|
+
"GatheringAgent",
|
|
16
|
+
"GatheringAgentInput",
|
|
17
|
+
"MergeAgent",
|
|
18
|
+
"MergeAgentInput",
|
|
19
|
+
"TransformerAgent",
|
|
20
|
+
"TransformerAgentInput",
|
|
21
|
+
"ThinkingAgent",
|
|
22
|
+
"ThinkingInput",
|
|
23
|
+
"BaseAgent",
|
|
24
|
+
"MCPBaseAgent",
|
|
25
|
+
"MCPToolAgent",
|
|
26
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from typing import TypeVar, Generic, get_args
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from ..datamodels import Context
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
InputType = TypeVar('InputType', bound=BaseModel)
|
|
9
|
+
OutputType = TypeVar('OutputType', bound=BaseModel)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseAgent(Generic[InputType, OutputType]):
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def id(self) -> str:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def description(self, user_id: str) -> str:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
async def execute(self, user_id: str, context: Context, input: InputType = None) -> OutputType:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def _get_generic_type(self, index: int):
|
|
27
|
+
"""
|
|
28
|
+
Obtains the concrete type of the generic at the specified index (0 for InputType, 1 for OutputType).
|
|
29
|
+
Works by inspecting __orig_bases__ of the class at runtime.
|
|
30
|
+
"""
|
|
31
|
+
for base in getattr(self.__class__, "__orig_bases__", []):
|
|
32
|
+
origin = getattr(base, "__origin__", None)
|
|
33
|
+
if origin is BaseAgent or issubclass(origin, BaseAgent):
|
|
34
|
+
args = get_args(base)
|
|
35
|
+
if args and len(args) > index:
|
|
36
|
+
return args[index]
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def input_parameters(self) -> dict:
|
|
40
|
+
input_type = self._get_generic_type(0)
|
|
41
|
+
if input_type and hasattr(input_type, 'model_json_schema'):
|
|
42
|
+
schema = input_type.model_json_schema()
|
|
43
|
+
schema.pop('title', None)
|
|
44
|
+
return schema
|
|
45
|
+
return {}
|
|
46
|
+
|
|
47
|
+
def output_parameters(self) -> dict:
|
|
48
|
+
output_type = self._get_generic_type(1)
|
|
49
|
+
if output_type and hasattr(output_type, 'model_json_schema'):
|
|
50
|
+
schema = output_type.model_json_schema()
|
|
51
|
+
schema.pop('title', None)
|
|
52
|
+
return schema
|
|
53
|
+
return {}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from .base_agent import BaseAgent
|
|
3
|
+
from ..datamodels import Context
|
|
4
|
+
|
|
5
|
+
class GatheringAgentInput(BaseModel):
|
|
6
|
+
key: str = Field(description="The key of the information to gather")
|
|
7
|
+
|
|
8
|
+
class GatheringAgent(BaseAgent[GatheringAgentInput, str]):
|
|
9
|
+
def id(self) -> str:
|
|
10
|
+
return "gathering-agent"
|
|
11
|
+
|
|
12
|
+
def description(self, user_id: str) -> str:
|
|
13
|
+
return """
|
|
14
|
+
I'm able to gather an information from the store and add to the current context.
|
|
15
|
+
USE THIS AGENT ONLY WHEN YOU NEED TO DISPLAY THE INFORMATION TO THE USER.
|
|
16
|
+
Whenever possible, if you need to filter or process the data, use the "transformer-agent".
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
async def execute(self, user_id: str, context: Context, input: GatheringAgentInput) -> str:
|
|
20
|
+
if not context.store.has(input.key):
|
|
21
|
+
raise Exception(f"Information from the store with key {input.key} not found")
|
|
22
|
+
value = context.store.get(input.key)
|
|
23
|
+
return value
|
|
24
|
+
|
|
25
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
from ..llms import LLM, GeminiLLM
|
|
6
|
+
from .base_agent import BaseAgent
|
|
7
|
+
from ..datamodels import Context, Message, KeyStoreResponse
|
|
8
|
+
from .react_agent import ReActAgent
|
|
9
|
+
|
|
10
|
+
class MapReduceInput(BaseModel):
|
|
11
|
+
task: str = Field(description="The task to solve, as a string")
|
|
12
|
+
|
|
13
|
+
class MapReduceAgent(ReActAgent[MapReduceInput, KeyStoreResponse]):
|
|
14
|
+
def __init__(self, max_iterations: int = 100, agents: List[BaseAgent] = []):
|
|
15
|
+
super().__init__(max_iterations)
|
|
16
|
+
self.agents = agents
|
|
17
|
+
|
|
18
|
+
def get_llm(self, user_id: str) -> LLM:
|
|
19
|
+
# TODO: Better LLM consiguration
|
|
20
|
+
return GeminiLLM(api_key=os.getenv("GEMINI_API_KEY"))
|
|
21
|
+
|
|
22
|
+
def id(self) -> str:
|
|
23
|
+
return "map-reduce"
|
|
24
|
+
|
|
25
|
+
def description(self, user_id: str) -> str:
|
|
26
|
+
return """USE THIS AGENT FOR:
|
|
27
|
+
1. COMPLEX CONTENT GENERATION: Creating multi-part artifacts like software libraries, full books, or extensive reports where each chapter/module needs to be generated in parallel.
|
|
28
|
+
2. RECURSIVE TASKS: Exploration of hierarchical structures (directories, org charts) or recursive problem solving.
|
|
29
|
+
3. LARGE DATASETS: Processing data that exceeds context limits by splitting (Map) and summarizing (Reduce).
|
|
30
|
+
4. PARALLEL EXECUTION: Any task requiring massive parallel execution of sub-tasks.
|
|
31
|
+
|
|
32
|
+
DO NOT USE FOR: Simple, linear tasks or single-file creation that fit in a single context window.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def prompt(self, user_id: str) -> str:
|
|
36
|
+
return """You are a recursive Map-Reduce agent. Your GOAL is to solve the given 'task' completely and return the FINAL RESULT.
|
|
37
|
+
|
|
38
|
+
ALGORITHM:
|
|
39
|
+
1. ANALYZE: Understand the task (e.g., analyze a folder).
|
|
40
|
+
2. MAP: Break it down into sub-tasks (e.g., read files in current dir, recursively call 'map-reduce' for sub-directories).
|
|
41
|
+
3. EXECUTE: Run these sub-tasks using the available tools. Parallelize where possible.
|
|
42
|
+
4. REDUCE: Collect ALL results from the sub-tasks.
|
|
43
|
+
5. SYNTHESIZE: Combine them into a single, comprehensive final report.
|
|
44
|
+
|
|
45
|
+
CRITICAL RULES:
|
|
46
|
+
- ANTI-RECURSION: If your assigned task is T, DO NOT call 'map-reduce' with task T again. You must BREAK DOWN task T into smaller sub-tasks (t1, t2...) and delegate THOSE.
|
|
47
|
+
- You are the one responsible for executing the atomic actions for T (e.g., gathering data, listing items) before delegating sub-parts.
|
|
48
|
+
- Do NOT return "I am working on it". You must work on it UNTIL IT IS DONE.
|
|
49
|
+
- Do NOT return partial results unless you have hit a hard limit.
|
|
50
|
+
- Your final 'assistant' message MUST contain the complete answer/report.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def available_agents(self, user_id: str) -> List[BaseAgent]:
|
|
54
|
+
# IMPORTANT: Return a NEW instance of MapReduceAgent instead of self.
|
|
55
|
+
return [MapReduceAgent(max_iterations=self.max_iterations, agents=self.agents)] + self.agents
|
|
56
|
+
|
|
57
|
+
def generate_messages_context(self, user_id: str, context: Context, input: MapReduceInput = None) -> List[Message]:
|
|
58
|
+
msgs = super().generate_messages_context(user_id, context, input)
|
|
59
|
+
msgs.append(Message(type="user", content=f"CURRENT TASK: {input.task}\n\nWARNING: Do not call 'map-reduce' with this exact same task '{input.task}' again, as it would cause an infinite loop. decompose it into smaller sub-tasks."))
|
|
60
|
+
return msgs
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import List, Optional, Any, Dict
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
|
|
6
|
+
from mcp import ClientSession, StdioServerParameters
|
|
7
|
+
from mcp.client.stdio import stdio_client
|
|
8
|
+
from mcp.types import Tool
|
|
9
|
+
|
|
10
|
+
from .base_agent import BaseAgent
|
|
11
|
+
from ..datamodels import Context
|
|
12
|
+
|
|
13
|
+
class MCPToolAgent(BaseAgent[dict, Any]):
|
|
14
|
+
"""
|
|
15
|
+
An agent that wraps a specific tool from an MCP server.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self, session: ClientSession, tool: Tool):
|
|
18
|
+
self._session = session
|
|
19
|
+
self._tool = tool
|
|
20
|
+
|
|
21
|
+
def id(self) -> str:
|
|
22
|
+
return self._tool.name
|
|
23
|
+
|
|
24
|
+
def description(self, user_id: str) -> str:
|
|
25
|
+
return self._tool.description or f"Tool {self._tool.name} from MCP server"
|
|
26
|
+
|
|
27
|
+
def input_parameters(self) -> dict:
|
|
28
|
+
# MCP tools define inputSchema in a way compatible with JSON Schema
|
|
29
|
+
schema = self._tool.inputSchema
|
|
30
|
+
if schema:
|
|
31
|
+
# Ensure title is removed if present as per convention in other agents, though optional
|
|
32
|
+
schema.pop('title', None)
|
|
33
|
+
return schema
|
|
34
|
+
return {}
|
|
35
|
+
|
|
36
|
+
def output_parameters(self) -> dict:
|
|
37
|
+
# MCP tools return generic content lists (text, images, etc.)
|
|
38
|
+
# We don't have a strict schema for the output content structure here yet
|
|
39
|
+
return {"type": "object", "description": "The result of the tool execution"}
|
|
40
|
+
|
|
41
|
+
async def execute(self, user_id: str, context: Context, input: dict = None) -> Any:
|
|
42
|
+
if input is None:
|
|
43
|
+
input = {}
|
|
44
|
+
|
|
45
|
+
# Call the tool on the MCP server
|
|
46
|
+
result = await self._session.call_tool(self._tool.name, arguments=input)
|
|
47
|
+
|
|
48
|
+
# Process result.content which is a list of TextContent | ImageContent | EmbeddedResource
|
|
49
|
+
# For simplicity, we return the raw list or a simplified text representation
|
|
50
|
+
# depending on what the framework expects. BaseAgent expects output_type.
|
|
51
|
+
# Since we defined OutputType as Any, we return the result object or content.
|
|
52
|
+
return result.content
|
|
53
|
+
|
|
54
|
+
class MCPBaseAgent:
|
|
55
|
+
"""
|
|
56
|
+
A base class for connecting to an MCP server and discovering tools.
|
|
57
|
+
This acts as a factory/manager for MCPToolAgents.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self):
|
|
61
|
+
self._session: Optional[ClientSession] = None
|
|
62
|
+
self._exit_stack = None
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def get_server_params(self) -> StdioServerParameters:
|
|
66
|
+
"""
|
|
67
|
+
Define the connection parameters for the MCP server.
|
|
68
|
+
"""
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
@asynccontextmanager
|
|
72
|
+
async def connect(self):
|
|
73
|
+
"""
|
|
74
|
+
Async context manager to establish connection to the MCP server.
|
|
75
|
+
"""
|
|
76
|
+
server_params = self.get_server_params()
|
|
77
|
+
|
|
78
|
+
# We manually manage the nested context managers to keep the session alive appropriately
|
|
79
|
+
async with stdio_client(server_params) as (read, write):
|
|
80
|
+
async with ClientSession(read, write) as session:
|
|
81
|
+
self._session = session
|
|
82
|
+
await session.initialize()
|
|
83
|
+
yield self
|
|
84
|
+
self._session = None
|
|
85
|
+
|
|
86
|
+
async def get_agents(self) -> List[MCPToolAgent]:
|
|
87
|
+
"""
|
|
88
|
+
Discovers tools on the connected MCP server and returns them as a list of MCPToolAgent.
|
|
89
|
+
Must be called within the 'connect' context.
|
|
90
|
+
"""
|
|
91
|
+
if not self._session:
|
|
92
|
+
raise RuntimeError("MCP session is not active. Use 'async with agent.connect():'")
|
|
93
|
+
|
|
94
|
+
response = await self._session.list_tools()
|
|
95
|
+
agents = []
|
|
96
|
+
for tool in response.tools:
|
|
97
|
+
agents.append(MCPToolAgent(self._session, tool))
|
|
98
|
+
|
|
99
|
+
return agents
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
import uuid
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
from .base_agent import BaseAgent
|
|
6
|
+
from ..datamodels import Message, Context, KeyStoreResponse
|
|
7
|
+
|
|
8
|
+
class MergeAgentInput(BaseModel):
|
|
9
|
+
keys: List[str] = Field(description="The list of keys to merge")
|
|
10
|
+
|
|
11
|
+
class MergeAgent(BaseAgent[MergeAgentInput, KeyStoreResponse]):
|
|
12
|
+
def id(self) -> str:
|
|
13
|
+
return "merge-agent"
|
|
14
|
+
|
|
15
|
+
def description(self, user_id: str) -> str:
|
|
16
|
+
return "I'm able to merge different informations into a single one."
|
|
17
|
+
|
|
18
|
+
async def execute(self, user_id: str, context: Context, input: MergeAgentInput) -> KeyStoreResponse:
|
|
19
|
+
values = [context.store.get(key) for key in input.keys]
|
|
20
|
+
value = "\n".join(values)
|
|
21
|
+
key = f"merged_{uuid.uuid4()}"
|
|
22
|
+
context.store.set(key, value)
|
|
23
|
+
return KeyStoreResponse(key=key, description=f"Merged information from keys {input.keys}")
|
|
24
|
+
|
|
25
|
+
|