mail-swarms 1.3.2__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.
- mail/__init__.py +35 -0
- mail/api.py +1964 -0
- mail/cli.py +432 -0
- mail/client.py +1657 -0
- mail/config/__init__.py +8 -0
- mail/config/client.py +87 -0
- mail/config/server.py +165 -0
- mail/core/__init__.py +72 -0
- mail/core/actions.py +69 -0
- mail/core/agents.py +73 -0
- mail/core/message.py +366 -0
- mail/core/runtime.py +3537 -0
- mail/core/tasks.py +311 -0
- mail/core/tools.py +1206 -0
- mail/db/__init__.py +0 -0
- mail/db/init.py +182 -0
- mail/db/types.py +65 -0
- mail/db/utils.py +523 -0
- mail/examples/__init__.py +27 -0
- mail/examples/analyst_dummy/__init__.py +15 -0
- mail/examples/analyst_dummy/agent.py +136 -0
- mail/examples/analyst_dummy/prompts.py +44 -0
- mail/examples/consultant_dummy/__init__.py +15 -0
- mail/examples/consultant_dummy/agent.py +136 -0
- mail/examples/consultant_dummy/prompts.py +42 -0
- mail/examples/data_analysis/__init__.py +40 -0
- mail/examples/data_analysis/analyst/__init__.py +9 -0
- mail/examples/data_analysis/analyst/agent.py +67 -0
- mail/examples/data_analysis/analyst/prompts.py +53 -0
- mail/examples/data_analysis/processor/__init__.py +13 -0
- mail/examples/data_analysis/processor/actions.py +293 -0
- mail/examples/data_analysis/processor/agent.py +67 -0
- mail/examples/data_analysis/processor/prompts.py +48 -0
- mail/examples/data_analysis/reporter/__init__.py +10 -0
- mail/examples/data_analysis/reporter/actions.py +187 -0
- mail/examples/data_analysis/reporter/agent.py +67 -0
- mail/examples/data_analysis/reporter/prompts.py +49 -0
- mail/examples/data_analysis/statistics/__init__.py +18 -0
- mail/examples/data_analysis/statistics/actions.py +343 -0
- mail/examples/data_analysis/statistics/agent.py +67 -0
- mail/examples/data_analysis/statistics/prompts.py +60 -0
- mail/examples/mafia/__init__.py +0 -0
- mail/examples/mafia/game.py +1537 -0
- mail/examples/mafia/narrator_tools.py +396 -0
- mail/examples/mafia/personas.py +240 -0
- mail/examples/mafia/prompts.py +489 -0
- mail/examples/mafia/roles.py +147 -0
- mail/examples/mafia/spec.md +350 -0
- mail/examples/math_dummy/__init__.py +23 -0
- mail/examples/math_dummy/actions.py +252 -0
- mail/examples/math_dummy/agent.py +136 -0
- mail/examples/math_dummy/prompts.py +46 -0
- mail/examples/math_dummy/types.py +5 -0
- mail/examples/research/__init__.py +39 -0
- mail/examples/research/researcher/__init__.py +9 -0
- mail/examples/research/researcher/agent.py +67 -0
- mail/examples/research/researcher/prompts.py +54 -0
- mail/examples/research/searcher/__init__.py +10 -0
- mail/examples/research/searcher/actions.py +324 -0
- mail/examples/research/searcher/agent.py +67 -0
- mail/examples/research/searcher/prompts.py +53 -0
- mail/examples/research/summarizer/__init__.py +18 -0
- mail/examples/research/summarizer/actions.py +255 -0
- mail/examples/research/summarizer/agent.py +67 -0
- mail/examples/research/summarizer/prompts.py +55 -0
- mail/examples/research/verifier/__init__.py +10 -0
- mail/examples/research/verifier/actions.py +337 -0
- mail/examples/research/verifier/agent.py +67 -0
- mail/examples/research/verifier/prompts.py +52 -0
- mail/examples/supervisor/__init__.py +11 -0
- mail/examples/supervisor/agent.py +4 -0
- mail/examples/supervisor/prompts.py +93 -0
- mail/examples/support/__init__.py +33 -0
- mail/examples/support/classifier/__init__.py +10 -0
- mail/examples/support/classifier/actions.py +307 -0
- mail/examples/support/classifier/agent.py +68 -0
- mail/examples/support/classifier/prompts.py +56 -0
- mail/examples/support/coordinator/__init__.py +9 -0
- mail/examples/support/coordinator/agent.py +67 -0
- mail/examples/support/coordinator/prompts.py +48 -0
- mail/examples/support/faq/__init__.py +10 -0
- mail/examples/support/faq/actions.py +182 -0
- mail/examples/support/faq/agent.py +67 -0
- mail/examples/support/faq/prompts.py +42 -0
- mail/examples/support/sentiment/__init__.py +15 -0
- mail/examples/support/sentiment/actions.py +341 -0
- mail/examples/support/sentiment/agent.py +67 -0
- mail/examples/support/sentiment/prompts.py +54 -0
- mail/examples/weather_dummy/__init__.py +23 -0
- mail/examples/weather_dummy/actions.py +75 -0
- mail/examples/weather_dummy/agent.py +136 -0
- mail/examples/weather_dummy/prompts.py +35 -0
- mail/examples/weather_dummy/types.py +5 -0
- mail/factories/__init__.py +27 -0
- mail/factories/action.py +223 -0
- mail/factories/base.py +1531 -0
- mail/factories/supervisor.py +241 -0
- mail/net/__init__.py +7 -0
- mail/net/registry.py +712 -0
- mail/net/router.py +728 -0
- mail/net/server_utils.py +114 -0
- mail/net/types.py +247 -0
- mail/server.py +1605 -0
- mail/stdlib/__init__.py +0 -0
- mail/stdlib/anthropic/__init__.py +0 -0
- mail/stdlib/fs/__init__.py +15 -0
- mail/stdlib/fs/actions.py +209 -0
- mail/stdlib/http/__init__.py +19 -0
- mail/stdlib/http/actions.py +333 -0
- mail/stdlib/interswarm/__init__.py +11 -0
- mail/stdlib/interswarm/actions.py +208 -0
- mail/stdlib/mcp/__init__.py +19 -0
- mail/stdlib/mcp/actions.py +294 -0
- mail/stdlib/openai/__init__.py +13 -0
- mail/stdlib/openai/agents.py +451 -0
- mail/summarizer.py +234 -0
- mail/swarms_json/__init__.py +27 -0
- mail/swarms_json/types.py +87 -0
- mail/swarms_json/utils.py +255 -0
- mail/url_scheme.py +51 -0
- mail/utils/__init__.py +53 -0
- mail/utils/auth.py +194 -0
- mail/utils/context.py +17 -0
- mail/utils/logger.py +73 -0
- mail/utils/openai.py +212 -0
- mail/utils/parsing.py +89 -0
- mail/utils/serialize.py +292 -0
- mail/utils/store.py +49 -0
- mail/utils/string_builder.py +119 -0
- mail/utils/version.py +20 -0
- mail_swarms-1.3.2.dist-info/METADATA +237 -0
- mail_swarms-1.3.2.dist-info/RECORD +137 -0
- mail_swarms-1.3.2.dist-info/WHEEL +4 -0
- mail_swarms-1.3.2.dist-info/entry_points.txt +2 -0
- mail_swarms-1.3.2.dist-info/licenses/LICENSE +202 -0
- mail_swarms-1.3.2.dist-info/licenses/NOTICE +10 -0
- mail_swarms-1.3.2.dist-info/licenses/THIRD_PARTY_NOTICES.md +12334 -0
mail/config/__init__.py
ADDED
mail/config/client.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Addison Kline
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
from functools import lru_cache
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
try: # Python 3.11+
|
|
15
|
+
import tomllib
|
|
16
|
+
except ModuleNotFoundError: # pragma: no cover - fallback for older runtimes
|
|
17
|
+
try:
|
|
18
|
+
import tomli as tomllib # type: ignore[no-redef]
|
|
19
|
+
except ModuleNotFoundError: # pragma: no cover - hard fallback
|
|
20
|
+
tomllib = None # type: ignore[assignment]
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _resolve_mail_config_path() -> Path | None:
|
|
26
|
+
"""
|
|
27
|
+
Determine the best candidate path for `mail.toml`.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
env_path = os.getenv("MAIL_CONFIG_PATH")
|
|
31
|
+
if env_path:
|
|
32
|
+
candidate = Path(env_path).expanduser()
|
|
33
|
+
if candidate.is_file():
|
|
34
|
+
return candidate
|
|
35
|
+
logger.debug(f"MAIL_CONFIG_PATH set to {candidate} but file missing")
|
|
36
|
+
|
|
37
|
+
cwd_candidate = Path.cwd() / "mail.toml"
|
|
38
|
+
if cwd_candidate.is_file():
|
|
39
|
+
return cwd_candidate
|
|
40
|
+
|
|
41
|
+
for ancestor in Path(__file__).resolve().parents:
|
|
42
|
+
candidate = ancestor / "mail.toml"
|
|
43
|
+
if candidate.is_file():
|
|
44
|
+
return candidate
|
|
45
|
+
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@lru_cache(maxsize=1)
|
|
50
|
+
def _client_defaults() -> dict[str, Any]:
|
|
51
|
+
"""
|
|
52
|
+
Resolve client defaults from `mail.toml`, falling back to literals.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
defaults: dict[str, Any] = {
|
|
56
|
+
"timeout": 3600.0,
|
|
57
|
+
"verbose": False,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if tomllib is None:
|
|
61
|
+
logger.debug("tomllib not available; using built-in client defaults")
|
|
62
|
+
return defaults
|
|
63
|
+
|
|
64
|
+
config_path = _resolve_mail_config_path()
|
|
65
|
+
if config_path is None:
|
|
66
|
+
logger.debug("mail.toml not found; using built-in client defaults")
|
|
67
|
+
return defaults
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
with config_path.open("rb") as config_file:
|
|
71
|
+
raw_config = tomllib.load(config_file)
|
|
72
|
+
except Exception as e: # pragma: no cover - uncommon failure
|
|
73
|
+
logger.warning(f"failed to load {config_path}: {e}")
|
|
74
|
+
return defaults
|
|
75
|
+
|
|
76
|
+
client_section = raw_config.get("client")
|
|
77
|
+
if isinstance(client_section, dict):
|
|
78
|
+
if "timeout" in client_section:
|
|
79
|
+
defaults["timeout"] = float(client_section["timeout"])
|
|
80
|
+
if "verbose" in client_section:
|
|
81
|
+
defaults["verbose"] = bool(client_section["verbose"])
|
|
82
|
+
return defaults
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ClientConfig(BaseModel):
|
|
86
|
+
timeout: float = Field(default_factory=lambda: _client_defaults()["timeout"])
|
|
87
|
+
verbose: bool = Field(default_factory=lambda: _client_defaults()["verbose"])
|
mail/config/server.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Addison Kline
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
from functools import lru_cache
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
try: # Python 3.11+
|
|
15
|
+
import tomllib
|
|
16
|
+
except ModuleNotFoundError: # pragma: no cover - fallback for older runtimes
|
|
17
|
+
try:
|
|
18
|
+
import tomli as tomllib # type: ignore[no-redef]
|
|
19
|
+
except ModuleNotFoundError: # pragma: no cover - hard fallback
|
|
20
|
+
tomllib = None # type: ignore[assignment]
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _resolve_mail_config_path() -> Path | None:
|
|
26
|
+
"""
|
|
27
|
+
Determine the best candidate path for `mail.toml`.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
env_path = os.getenv("MAIL_CONFIG_PATH")
|
|
31
|
+
if env_path:
|
|
32
|
+
candidate = Path(env_path).expanduser()
|
|
33
|
+
if candidate.is_file():
|
|
34
|
+
return candidate
|
|
35
|
+
logger.warning(f"MAIL_CONFIG_PATH set to {candidate} but file missing")
|
|
36
|
+
|
|
37
|
+
cwd_candidate = Path.cwd() / "mail.toml"
|
|
38
|
+
if cwd_candidate.is_file():
|
|
39
|
+
return cwd_candidate
|
|
40
|
+
|
|
41
|
+
for ancestor in Path(__file__).resolve().parents:
|
|
42
|
+
candidate = ancestor / "mail.toml"
|
|
43
|
+
if candidate.is_file():
|
|
44
|
+
return candidate
|
|
45
|
+
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@lru_cache(maxsize=1)
|
|
50
|
+
def _load_defaults_from_toml() -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
|
|
51
|
+
"""
|
|
52
|
+
Read default server + swarm fields from `mail.toml` if available.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
server_defaults: dict[str, Any] = {
|
|
56
|
+
"port": 8000,
|
|
57
|
+
"host": "0.0.0.0",
|
|
58
|
+
"reload": False,
|
|
59
|
+
"debug": False,
|
|
60
|
+
}
|
|
61
|
+
swarm_defaults: dict[str, Any] = {
|
|
62
|
+
"name": "example-no-proxy",
|
|
63
|
+
"source": "swarms.json",
|
|
64
|
+
"registry_file": "registries/example-no-proxy.json",
|
|
65
|
+
"description": "",
|
|
66
|
+
"keywords": [],
|
|
67
|
+
"public": False,
|
|
68
|
+
}
|
|
69
|
+
settings_defaults: dict[str, Any] = {
|
|
70
|
+
"task_message_limit": 15,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if tomllib is None:
|
|
74
|
+
logger.warning("tomllib not available; using built-in defaults")
|
|
75
|
+
return server_defaults, swarm_defaults, settings_defaults
|
|
76
|
+
|
|
77
|
+
config_path = _resolve_mail_config_path()
|
|
78
|
+
if config_path is None:
|
|
79
|
+
logger.warning("mail.toml not found; using built-in defaults")
|
|
80
|
+
return server_defaults, swarm_defaults, settings_defaults
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
with config_path.open("rb") as config_file:
|
|
84
|
+
raw_config = tomllib.load(config_file)
|
|
85
|
+
except Exception as e: # pragma: no cover - uncommon failure
|
|
86
|
+
logger.warning(f"failed to load {config_path}: {e}")
|
|
87
|
+
return server_defaults, swarm_defaults, settings_defaults
|
|
88
|
+
|
|
89
|
+
server_section = raw_config.get("server")
|
|
90
|
+
if isinstance(server_section, dict):
|
|
91
|
+
server_defaults = {
|
|
92
|
+
"port": server_section.get("port", server_defaults["port"]),
|
|
93
|
+
"host": server_section.get("host", server_defaults["host"]),
|
|
94
|
+
"reload": server_section.get("reload", server_defaults["reload"]),
|
|
95
|
+
"debug": server_section.get("debug", server_defaults["debug"]),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
swarm_section = server_section.get("swarm")
|
|
99
|
+
if isinstance(swarm_section, dict):
|
|
100
|
+
registry_value = swarm_section.get("registry")
|
|
101
|
+
if registry_value is None:
|
|
102
|
+
registry_value = swarm_section.get("registry_file")
|
|
103
|
+
|
|
104
|
+
swarm_defaults = {
|
|
105
|
+
"name": swarm_section.get("name", swarm_defaults["name"]),
|
|
106
|
+
"source": swarm_section.get("source", swarm_defaults["source"]),
|
|
107
|
+
"registry_file": registry_value or swarm_defaults["registry_file"],
|
|
108
|
+
"description": swarm_section.get(
|
|
109
|
+
"description", swarm_defaults["description"]
|
|
110
|
+
),
|
|
111
|
+
"keywords": swarm_section.get("keywords", swarm_defaults["keywords"]),
|
|
112
|
+
"public": swarm_section.get("public", swarm_defaults["public"]),
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
settings_section = server_section.get("settings")
|
|
116
|
+
if isinstance(settings_section, dict):
|
|
117
|
+
settings_defaults = {
|
|
118
|
+
"task_message_limit": settings_section.get(
|
|
119
|
+
"task_message_limit", settings_defaults["task_message_limit"]
|
|
120
|
+
),
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
logger.info(
|
|
124
|
+
f"server defaults resolved to {server_defaults} with swarm defaults {swarm_defaults}",
|
|
125
|
+
)
|
|
126
|
+
return server_defaults, swarm_defaults, settings_defaults
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _server_defaults() -> dict[str, Any]:
|
|
130
|
+
return _load_defaults_from_toml()[0]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _swarm_defaults() -> dict[str, Any]:
|
|
134
|
+
return _load_defaults_from_toml()[1]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _settings_defaults() -> dict[str, Any]:
|
|
138
|
+
return _load_defaults_from_toml()[2]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class SwarmConfig(BaseModel):
|
|
142
|
+
name: str = Field(default_factory=lambda: _swarm_defaults()["name"])
|
|
143
|
+
description: str = Field(default_factory=lambda: _swarm_defaults()["description"])
|
|
144
|
+
keywords: list[str] = Field(default_factory=lambda: _swarm_defaults()["keywords"])
|
|
145
|
+
public: bool = Field(default_factory=lambda: _swarm_defaults()["public"])
|
|
146
|
+
source: str = Field(default_factory=lambda: _swarm_defaults()["source"])
|
|
147
|
+
registry_file: str = Field(
|
|
148
|
+
default_factory=lambda: _swarm_defaults()["registry_file"]
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class SettingsConfig(BaseModel):
|
|
153
|
+
task_message_limit: int = Field(
|
|
154
|
+
default_factory=lambda: _settings_defaults()["task_message_limit"]
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class ServerConfig(BaseModel):
|
|
159
|
+
port: int = Field(default_factory=lambda: _server_defaults()["port"])
|
|
160
|
+
host: str = Field(default_factory=lambda: _server_defaults()["host"])
|
|
161
|
+
reload: bool = Field(default_factory=lambda: _server_defaults()["reload"])
|
|
162
|
+
debug: bool = Field(default_factory=lambda: _server_defaults()["debug"])
|
|
163
|
+
|
|
164
|
+
swarm: SwarmConfig = Field(default_factory=SwarmConfig)
|
|
165
|
+
settings: SettingsConfig = Field(default_factory=SettingsConfig)
|
mail/core/__init__.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from .actions import ActionFunction, ActionOverrideFunction
|
|
2
|
+
from .agents import AgentCore, AgentFunction
|
|
3
|
+
from .message import (
|
|
4
|
+
MAILAddress,
|
|
5
|
+
MAILBroadcast,
|
|
6
|
+
MAILInterrupt,
|
|
7
|
+
MAILInterswarmMessage,
|
|
8
|
+
MAILMessage,
|
|
9
|
+
MAILRequest,
|
|
10
|
+
MAILResponse,
|
|
11
|
+
create_admin_address,
|
|
12
|
+
create_agent_address,
|
|
13
|
+
create_system_address,
|
|
14
|
+
create_user_address,
|
|
15
|
+
format_agent_address,
|
|
16
|
+
parse_agent_address,
|
|
17
|
+
)
|
|
18
|
+
from .runtime import MAILRuntime
|
|
19
|
+
from .tools import (
|
|
20
|
+
MAIL_TOOL_NAMES,
|
|
21
|
+
AgentToolCall,
|
|
22
|
+
convert_call_to_mail_message,
|
|
23
|
+
create_acknowledge_broadcast_tool,
|
|
24
|
+
create_broadcast_tool,
|
|
25
|
+
create_ignore_broadcast_tool,
|
|
26
|
+
create_interrupt_tool,
|
|
27
|
+
create_interswarm_broadcast_tool,
|
|
28
|
+
create_mail_tools,
|
|
29
|
+
create_request_tool,
|
|
30
|
+
create_response_tool,
|
|
31
|
+
create_supervisor_tools,
|
|
32
|
+
create_swarm_discovery_tool,
|
|
33
|
+
create_task_complete_tool,
|
|
34
|
+
pydantic_model_to_tool,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"ActionFunction",
|
|
39
|
+
"ActionOverrideFunction",
|
|
40
|
+
"AgentFunction",
|
|
41
|
+
"AgentCore",
|
|
42
|
+
"AgentToolCall",
|
|
43
|
+
"MAILAddress",
|
|
44
|
+
"MAILBroadcast",
|
|
45
|
+
"MAILInterrupt",
|
|
46
|
+
"MAILInterswarmMessage",
|
|
47
|
+
"MAILMessage",
|
|
48
|
+
"MAILRequest",
|
|
49
|
+
"MAILResponse",
|
|
50
|
+
"create_agent_address",
|
|
51
|
+
"create_admin_address",
|
|
52
|
+
"create_system_address",
|
|
53
|
+
"create_user_address",
|
|
54
|
+
"format_agent_address",
|
|
55
|
+
"parse_agent_address",
|
|
56
|
+
"MAILRuntime",
|
|
57
|
+
"MAIL_TOOL_NAMES",
|
|
58
|
+
"AgentToolCall",
|
|
59
|
+
"convert_call_to_mail_message",
|
|
60
|
+
"create_acknowledge_broadcast_tool",
|
|
61
|
+
"create_broadcast_tool",
|
|
62
|
+
"create_ignore_broadcast_tool",
|
|
63
|
+
"create_interrupt_tool",
|
|
64
|
+
"create_interswarm_broadcast_tool",
|
|
65
|
+
"create_mail_tools",
|
|
66
|
+
"create_request_tool",
|
|
67
|
+
"create_response_tool",
|
|
68
|
+
"create_supervisor_tools",
|
|
69
|
+
"create_swarm_discovery_tool",
|
|
70
|
+
"create_task_complete_tool",
|
|
71
|
+
"pydantic_model_to_tool",
|
|
72
|
+
]
|
mail/core/actions.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Addison Kline, Ryan Heaton
|
|
3
|
+
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from mail.core.tools import AgentToolCall
|
|
9
|
+
|
|
10
|
+
ActionFunction = Callable[[dict[str, Any]], Awaitable[str]]
|
|
11
|
+
"""
|
|
12
|
+
A function that executes an action tool and returns the response.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
ActionOverrideFunction = Callable[[dict[str, Any]], Awaitable[dict[str, Any] | str]]
|
|
16
|
+
"""
|
|
17
|
+
A function that overrides an action tool and returns the response.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ActionCore:
|
|
22
|
+
"""
|
|
23
|
+
A bare-bones action structure.
|
|
24
|
+
Contains only the action function and essential metadata.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
function: ActionFunction,
|
|
30
|
+
name: str,
|
|
31
|
+
parameters: dict[str, Any],
|
|
32
|
+
):
|
|
33
|
+
self.name = name
|
|
34
|
+
self.parameters = parameters
|
|
35
|
+
self.function = function
|
|
36
|
+
|
|
37
|
+
async def execute(
|
|
38
|
+
self,
|
|
39
|
+
call: AgentToolCall,
|
|
40
|
+
actions: dict[str, "ActionCore"] | None = None,
|
|
41
|
+
action_override: ActionOverrideFunction | None = None,
|
|
42
|
+
) -> tuple[Literal["success", "error"], dict[str, str]]:
|
|
43
|
+
"""
|
|
44
|
+
Execute an action tool and return the response within a MAIL runtime.
|
|
45
|
+
"""
|
|
46
|
+
logger = logging.getLogger("mail.actions")
|
|
47
|
+
if actions:
|
|
48
|
+
action = actions.get(self.name)
|
|
49
|
+
if action:
|
|
50
|
+
return await action.execute(call, action_override=action_override)
|
|
51
|
+
|
|
52
|
+
if not action_override:
|
|
53
|
+
try:
|
|
54
|
+
content = await self.function(call.tool_args)
|
|
55
|
+
return ("success", {"content": content})
|
|
56
|
+
except Exception as e:
|
|
57
|
+
return ("error", {"content": f"failed to execute action tool: {e}"})
|
|
58
|
+
else:
|
|
59
|
+
try:
|
|
60
|
+
response = await action_override(call) # type: ignore
|
|
61
|
+
logger.info(f"action override response: {response}")
|
|
62
|
+
if isinstance(response, str):
|
|
63
|
+
return ("success", {"content": response})
|
|
64
|
+
return ("success", response)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
return (
|
|
67
|
+
"error",
|
|
68
|
+
{"content": f"failed to execute action override tool: {e}"},
|
|
69
|
+
)
|
mail/core/agents.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Addison Kline, Ryan Heaton
|
|
3
|
+
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .actions import ActionCore
|
|
8
|
+
from .tools import AgentToolCall
|
|
9
|
+
|
|
10
|
+
AgentOutput = tuple[str | None, list[AgentToolCall]]
|
|
11
|
+
"""
|
|
12
|
+
Response type of a MAIL agent containing a response and tool calls.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
AgentFunction = Callable[
|
|
16
|
+
[list[dict[str, Any]], str | dict[str, str]],
|
|
17
|
+
Awaitable[AgentOutput],
|
|
18
|
+
]
|
|
19
|
+
"""
|
|
20
|
+
A function that takes a chat history and returns a response and tool calls.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AgentCore:
|
|
25
|
+
"""
|
|
26
|
+
A bare-bones agent structure.
|
|
27
|
+
Contains only the agent function and essential metadata.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
function: AgentFunction,
|
|
33
|
+
comm_targets: list[str],
|
|
34
|
+
actions: dict[str, ActionCore] | None = None,
|
|
35
|
+
enable_entrypoint: bool = False,
|
|
36
|
+
enable_interswarm: bool = False,
|
|
37
|
+
can_complete_tasks: bool = False,
|
|
38
|
+
):
|
|
39
|
+
self.function = function
|
|
40
|
+
self.comm_targets = comm_targets
|
|
41
|
+
if actions is None:
|
|
42
|
+
self.actions = {}
|
|
43
|
+
else:
|
|
44
|
+
self.actions = actions
|
|
45
|
+
self.enable_entrypoint = enable_entrypoint
|
|
46
|
+
self.enable_interswarm = enable_interswarm
|
|
47
|
+
self.can_complete_tasks = can_complete_tasks
|
|
48
|
+
|
|
49
|
+
def can_access_action_or_tool(self, name: str) -> bool:
|
|
50
|
+
"""
|
|
51
|
+
Check if the agent can access a tool by name.
|
|
52
|
+
"""
|
|
53
|
+
match name:
|
|
54
|
+
case (
|
|
55
|
+
"send_request"
|
|
56
|
+
| "send_response"
|
|
57
|
+
| "send_interrupt"
|
|
58
|
+
| "send_broadcast"
|
|
59
|
+
| "acknowledge_broadcast"
|
|
60
|
+
| "ignore_broadcast"
|
|
61
|
+
| "help"
|
|
62
|
+
):
|
|
63
|
+
return True
|
|
64
|
+
case "task_complete":
|
|
65
|
+
return self.can_complete_tasks
|
|
66
|
+
case _:
|
|
67
|
+
return name in self.actions
|
|
68
|
+
|
|
69
|
+
def can_access_action(self, action_name: str) -> bool:
|
|
70
|
+
"""
|
|
71
|
+
Check if the agent can access an action by name.
|
|
72
|
+
"""
|
|
73
|
+
return action_name in self.actions
|