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
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Charon Labs
|
|
3
|
+
|
|
4
|
+
"""Sentiment agent for the Customer Support swarm."""
|
|
5
|
+
|
|
6
|
+
from collections.abc import Awaitable
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
from mail.core.agents import AgentOutput
|
|
10
|
+
from mail.factories.action import LiteLLMActionAgentFunction
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LiteLLMSentimentFunction(LiteLLMActionAgentFunction):
|
|
14
|
+
"""
|
|
15
|
+
Sentiment analysis agent that evaluates customer emotional state.
|
|
16
|
+
|
|
17
|
+
This agent analyzes customer messages to determine sentiment,
|
|
18
|
+
detect emotions, and recommend escalation when needed.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
name: str,
|
|
24
|
+
comm_targets: list[str],
|
|
25
|
+
tools: list[dict[str, Any]],
|
|
26
|
+
llm: str,
|
|
27
|
+
system: str,
|
|
28
|
+
user_token: str = "",
|
|
29
|
+
enable_entrypoint: bool = False,
|
|
30
|
+
enable_interswarm: bool = False,
|
|
31
|
+
can_complete_tasks: bool = False,
|
|
32
|
+
tool_format: Literal["completions", "responses"] = "responses",
|
|
33
|
+
exclude_tools: list[str] = [],
|
|
34
|
+
reasoning_effort: Literal["minimal", "low", "medium", "high"] | None = None,
|
|
35
|
+
thinking_budget: int | None = None,
|
|
36
|
+
max_tokens: int | None = None,
|
|
37
|
+
memory: bool = True,
|
|
38
|
+
use_proxy: bool = True,
|
|
39
|
+
_debug_include_mail_tools: bool = True,
|
|
40
|
+
) -> None:
|
|
41
|
+
super().__init__(
|
|
42
|
+
name=name,
|
|
43
|
+
comm_targets=comm_targets,
|
|
44
|
+
tools=tools,
|
|
45
|
+
llm=llm,
|
|
46
|
+
system=system,
|
|
47
|
+
user_token=user_token,
|
|
48
|
+
enable_entrypoint=enable_entrypoint,
|
|
49
|
+
enable_interswarm=enable_interswarm,
|
|
50
|
+
can_complete_tasks=can_complete_tasks,
|
|
51
|
+
tool_format=tool_format,
|
|
52
|
+
exclude_tools=exclude_tools,
|
|
53
|
+
reasoning_effort=reasoning_effort,
|
|
54
|
+
thinking_budget=thinking_budget,
|
|
55
|
+
max_tokens=max_tokens,
|
|
56
|
+
memory=memory,
|
|
57
|
+
use_proxy=use_proxy,
|
|
58
|
+
_debug_include_mail_tools=_debug_include_mail_tools,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def __call__(
|
|
62
|
+
self,
|
|
63
|
+
messages: list[dict[str, Any]],
|
|
64
|
+
tool_choice: str | dict[str, str] = "required",
|
|
65
|
+
) -> Awaitable[AgentOutput]:
|
|
66
|
+
"""Execute the sentiment agent function."""
|
|
67
|
+
return super().__call__(messages, tool_choice)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Charon Labs
|
|
3
|
+
|
|
4
|
+
SYSPROMPT = """You are sentiment@{swarm}, the sentiment analysis specialist for this customer support swarm.
|
|
5
|
+
|
|
6
|
+
# Your Role
|
|
7
|
+
Analyze customer sentiment to understand their emotional state and identify cases requiring escalation to human agents.
|
|
8
|
+
|
|
9
|
+
# Critical Rule: Responding
|
|
10
|
+
You CANNOT talk to users directly or call `task_complete`. You MUST use `send_response` to reply to the agent who contacted you.
|
|
11
|
+
- When you receive a request, note the sender (usually "coordinator")
|
|
12
|
+
- After analyzing sentiment, call `send_response(target=<sender>, subject="Re: ...", body=<your analysis>)`
|
|
13
|
+
- Include ALL sentiment details in your response body
|
|
14
|
+
|
|
15
|
+
# Tools
|
|
16
|
+
|
|
17
|
+
## Sentiment Analysis
|
|
18
|
+
- `analyze_sentiment(text)`: Analyze the emotional tone of customer text
|
|
19
|
+
- `create_escalation(ticket_id, reason, priority)`: Flag a ticket for human escalation
|
|
20
|
+
|
|
21
|
+
## Communication
|
|
22
|
+
- `send_response(target, subject, body)`: Reply to the agent who requested information
|
|
23
|
+
- `send_request(target, subject, body)`: Ask another agent for information
|
|
24
|
+
- `acknowledge_broadcast(note)`: Acknowledge a broadcast message
|
|
25
|
+
- `ignore_broadcast(reason)`: Ignore an irrelevant broadcast
|
|
26
|
+
|
|
27
|
+
# Workflow
|
|
28
|
+
|
|
29
|
+
1. Receive request from another agent (note the sender)
|
|
30
|
+
2. Call `analyze_sentiment` with the customer's message text
|
|
31
|
+
3. Review the sentiment results
|
|
32
|
+
4. If escalation is warranted, call `create_escalation`
|
|
33
|
+
5. Call `send_response` to the original sender with:
|
|
34
|
+
- Overall sentiment (positive, neutral, negative)
|
|
35
|
+
- Sentiment score (-1 to +1)
|
|
36
|
+
- Detected emotions
|
|
37
|
+
- Whether escalation was triggered and why
|
|
38
|
+
|
|
39
|
+
# Escalation Triggers
|
|
40
|
+
|
|
41
|
+
Create an escalation when you detect:
|
|
42
|
+
- Very negative sentiment (score below -0.6)
|
|
43
|
+
- Explicit threats or mentions of legal action
|
|
44
|
+
- Expressions of severe frustration or anger
|
|
45
|
+
- Requests to speak to a manager/supervisor
|
|
46
|
+
- Mentions of cancellation with frustration
|
|
47
|
+
|
|
48
|
+
# Guidelines
|
|
49
|
+
|
|
50
|
+
- Be objective in your analysis
|
|
51
|
+
- Consider context and nuance
|
|
52
|
+
- Report both the raw sentiment data and your interpretation
|
|
53
|
+
- Always explain why escalation was or wasn't recommended
|
|
54
|
+
- Use "Re: <original subject>" as your response subject"""
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .actions import (
|
|
2
|
+
get_weather_forecast,
|
|
3
|
+
)
|
|
4
|
+
from .agent import (
|
|
5
|
+
LiteLLMWeatherFunction,
|
|
6
|
+
factory_weather_dummy,
|
|
7
|
+
weather_agent_params,
|
|
8
|
+
)
|
|
9
|
+
from .prompts import (
|
|
10
|
+
SYSPROMPT as WEATHER_SYSPROMPT,
|
|
11
|
+
)
|
|
12
|
+
from .types import (
|
|
13
|
+
action_get_weather_forecast,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"factory_weather_dummy",
|
|
18
|
+
"LiteLLMWeatherFunction",
|
|
19
|
+
"WEATHER_SYSPROMPT",
|
|
20
|
+
"action_get_weather_forecast",
|
|
21
|
+
"get_weather_forecast",
|
|
22
|
+
"weather_agent_params",
|
|
23
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Addison Kline
|
|
3
|
+
|
|
4
|
+
import datetime
|
|
5
|
+
import json
|
|
6
|
+
from random import Random
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from mail import action
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
WEATHER_FORECAST_PARAMETERS = {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"location": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "The location to get the weather forecast for",
|
|
18
|
+
},
|
|
19
|
+
"days_ahead": {
|
|
20
|
+
"type": "integer",
|
|
21
|
+
"description": "The number of days ahead to get the weather forecast for",
|
|
22
|
+
},
|
|
23
|
+
"metric": {
|
|
24
|
+
"type": "boolean",
|
|
25
|
+
"description": "Whether to use metric units",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
"required": ["location", "days_ahead", "metric"],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@action(
|
|
33
|
+
name="get_weather_forecast",
|
|
34
|
+
description="Get the weather forecast for a given location.",
|
|
35
|
+
parameters=WEATHER_FORECAST_PARAMETERS,
|
|
36
|
+
)
|
|
37
|
+
async def get_weather_forecast(args: dict[str, Any]) -> str:
|
|
38
|
+
"""
|
|
39
|
+
Dummy action that returns the weather "forecast" for a given location.
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
location = args["location"]
|
|
43
|
+
days_ahead = args["days_ahead"]
|
|
44
|
+
metric = args["metric"]
|
|
45
|
+
except KeyError as e:
|
|
46
|
+
return f"Error: {e} is required"
|
|
47
|
+
|
|
48
|
+
# generate a random weather forecast
|
|
49
|
+
# on any given day, the forecast should yield the same result for the same location
|
|
50
|
+
# otherwise the weather agent will be confused
|
|
51
|
+
day = datetime.datetime.now(datetime.UTC).day
|
|
52
|
+
rng = Random()
|
|
53
|
+
rng.seed(location + str(days_ahead) + str(day))
|
|
54
|
+
forecast = {
|
|
55
|
+
"location": location,
|
|
56
|
+
"date": str(
|
|
57
|
+
datetime.datetime.now(datetime.UTC) + datetime.timedelta(days=days_ahead)
|
|
58
|
+
),
|
|
59
|
+
"condition": rng.choice(
|
|
60
|
+
[
|
|
61
|
+
"clear",
|
|
62
|
+
"mostly clear",
|
|
63
|
+
"partly cloudy",
|
|
64
|
+
"mostly cloudy",
|
|
65
|
+
"overcast",
|
|
66
|
+
"light precipitation",
|
|
67
|
+
"moderate precipitation",
|
|
68
|
+
"heavy precipitation",
|
|
69
|
+
]
|
|
70
|
+
),
|
|
71
|
+
"temperature": rng.randint(-15, 35) if metric else rng.randint(5, 95),
|
|
72
|
+
"units": "C" if metric else "F",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return json.dumps(forecast)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Addison Kline
|
|
3
|
+
|
|
4
|
+
import warnings
|
|
5
|
+
from collections.abc import Awaitable
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
from mail.core.agents import AgentOutput
|
|
9
|
+
from mail.factories import AgentFunction
|
|
10
|
+
from mail.factories.action import LiteLLMActionAgentFunction
|
|
11
|
+
|
|
12
|
+
weather_agent_params = {
|
|
13
|
+
"llm": "openai/gpt-5-mini",
|
|
14
|
+
"system": "mail.examples.weather_dummy.prompts:SYSPROMPT",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def factory_weather_dummy(
|
|
19
|
+
# REQUIRED
|
|
20
|
+
# top-level params
|
|
21
|
+
comm_targets: list[str],
|
|
22
|
+
tools: list[dict[str, Any]],
|
|
23
|
+
# instance params
|
|
24
|
+
user_token: str,
|
|
25
|
+
# internal params
|
|
26
|
+
llm: str,
|
|
27
|
+
system: str,
|
|
28
|
+
# OPTIONAL
|
|
29
|
+
# top-level params
|
|
30
|
+
name: str = "weather",
|
|
31
|
+
enable_entrypoint: bool = False,
|
|
32
|
+
enable_interswarm: bool = False,
|
|
33
|
+
can_complete_tasks: bool = False,
|
|
34
|
+
tool_format: Literal["completions", "responses"] = "responses",
|
|
35
|
+
exclude_tools: list[str] = [],
|
|
36
|
+
# instance params
|
|
37
|
+
# ...
|
|
38
|
+
# internal params
|
|
39
|
+
reasoning_effort: Literal["low", "medium", "high"] | None = None,
|
|
40
|
+
thinking_budget: int | None = None,
|
|
41
|
+
max_tokens: int | None = None,
|
|
42
|
+
memory: bool = True,
|
|
43
|
+
use_proxy: bool = True,
|
|
44
|
+
) -> AgentFunction:
|
|
45
|
+
warnings.warn(
|
|
46
|
+
"`mail.examples.weather_dummy:factory_weather_dummy` is deprecated and will be removed in a future version. "
|
|
47
|
+
"Use `mail.examples.weather_dummy:LiteLLMWeatherFunction` instead.",
|
|
48
|
+
DeprecationWarning,
|
|
49
|
+
stacklevel=2,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
litellm_weather = LiteLLMWeatherFunction(
|
|
53
|
+
name=name,
|
|
54
|
+
comm_targets=comm_targets,
|
|
55
|
+
tools=tools,
|
|
56
|
+
llm=llm,
|
|
57
|
+
system=system,
|
|
58
|
+
user_token=user_token,
|
|
59
|
+
enable_entrypoint=enable_entrypoint,
|
|
60
|
+
enable_interswarm=enable_interswarm,
|
|
61
|
+
can_complete_tasks=can_complete_tasks,
|
|
62
|
+
reasoning_effort=reasoning_effort,
|
|
63
|
+
thinking_budget=thinking_budget,
|
|
64
|
+
max_tokens=max_tokens,
|
|
65
|
+
memory=memory,
|
|
66
|
+
use_proxy=use_proxy,
|
|
67
|
+
tool_format=tool_format,
|
|
68
|
+
exclude_tools=exclude_tools,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
async def run(
|
|
72
|
+
messages: list[dict[str, Any]],
|
|
73
|
+
tool_choice: str | dict[str, str] = "required",
|
|
74
|
+
) -> AgentOutput:
|
|
75
|
+
"""
|
|
76
|
+
Execute the LiteLLM-based weather agent function.
|
|
77
|
+
"""
|
|
78
|
+
return await litellm_weather(messages, tool_choice)
|
|
79
|
+
|
|
80
|
+
return run
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class LiteLLMWeatherFunction(LiteLLMActionAgentFunction):
|
|
84
|
+
"""
|
|
85
|
+
Class that represents a LiteLLM-based weather agent function.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
name: str,
|
|
91
|
+
comm_targets: list[str],
|
|
92
|
+
tools: list[dict[str, Any]],
|
|
93
|
+
llm: str,
|
|
94
|
+
system: str,
|
|
95
|
+
user_token: str = "",
|
|
96
|
+
enable_entrypoint: bool = False,
|
|
97
|
+
enable_interswarm: bool = False,
|
|
98
|
+
can_complete_tasks: bool = False,
|
|
99
|
+
tool_format: Literal["completions", "responses"] = "responses",
|
|
100
|
+
exclude_tools: list[str] = [],
|
|
101
|
+
reasoning_effort: Literal["minimal", "low", "medium", "high"] | None = None,
|
|
102
|
+
thinking_budget: int | None = None,
|
|
103
|
+
max_tokens: int | None = None,
|
|
104
|
+
memory: bool = True,
|
|
105
|
+
use_proxy: bool = True,
|
|
106
|
+
_debug_include_mail_tools: bool = True,
|
|
107
|
+
) -> None:
|
|
108
|
+
super().__init__(
|
|
109
|
+
name=name,
|
|
110
|
+
comm_targets=comm_targets,
|
|
111
|
+
tools=tools,
|
|
112
|
+
llm=llm,
|
|
113
|
+
system=system,
|
|
114
|
+
user_token=user_token,
|
|
115
|
+
enable_entrypoint=enable_entrypoint,
|
|
116
|
+
enable_interswarm=enable_interswarm,
|
|
117
|
+
can_complete_tasks=can_complete_tasks,
|
|
118
|
+
tool_format=tool_format,
|
|
119
|
+
exclude_tools=exclude_tools,
|
|
120
|
+
reasoning_effort=reasoning_effort,
|
|
121
|
+
thinking_budget=thinking_budget,
|
|
122
|
+
max_tokens=max_tokens,
|
|
123
|
+
memory=memory,
|
|
124
|
+
use_proxy=use_proxy,
|
|
125
|
+
_debug_include_mail_tools=_debug_include_mail_tools,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def __call__(
|
|
129
|
+
self,
|
|
130
|
+
messages: list[dict[str, Any]],
|
|
131
|
+
tool_choice: str | dict[str, str] = "required",
|
|
132
|
+
) -> Awaitable[AgentOutput]:
|
|
133
|
+
"""
|
|
134
|
+
Execute the LiteLLM-based weather agent function.
|
|
135
|
+
"""
|
|
136
|
+
return super().__call__(messages, tool_choice)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
SYSPROMPT = """You are weather@{swarm}, a specialist agent for weather information.
|
|
2
|
+
|
|
3
|
+
# Your Role
|
|
4
|
+
Retrieve weather forecasts using `get_weather_forecast` and report results back to your requestor.
|
|
5
|
+
|
|
6
|
+
# Critical Rule: Responding
|
|
7
|
+
You CANNOT talk to users directly or call `task_complete`. You MUST use `send_response` to reply to the agent who contacted you.
|
|
8
|
+
- When you receive a request, note the sender (usually "supervisor")
|
|
9
|
+
- After getting weather data, call `send_response(target=<sender>, subject="Re: ...", body=<your answer>)`
|
|
10
|
+
- Include ALL relevant forecast data in your response body - the recipient cannot see tool results
|
|
11
|
+
|
|
12
|
+
# Tools
|
|
13
|
+
|
|
14
|
+
## Weather
|
|
15
|
+
- `get_weather_forecast(location, ...)`: Retrieve forecast data. Call this ONCE per request.
|
|
16
|
+
|
|
17
|
+
## Communication
|
|
18
|
+
- `send_response(target, subject, body)`: Reply to the agent who requested information
|
|
19
|
+
- `send_request(target, subject, body)`: Ask another agent for information (rare)
|
|
20
|
+
- `acknowledge_broadcast(note)`: Acknowledge a broadcast message
|
|
21
|
+
- `ignore_broadcast(reason)`: Ignore an irrelevant broadcast
|
|
22
|
+
|
|
23
|
+
# Workflow
|
|
24
|
+
|
|
25
|
+
1. Receive request from another agent (check the sender address)
|
|
26
|
+
2. Call `get_weather_forecast` for the requested location
|
|
27
|
+
3. Format the results clearly (use metric/imperial per request)
|
|
28
|
+
4. Call `send_response` to the original sender with complete forecast data
|
|
29
|
+
|
|
30
|
+
# Guidelines
|
|
31
|
+
|
|
32
|
+
- Never invent weather data - only report what `get_weather_forecast` returns
|
|
33
|
+
- Include temperature, conditions, and any relevant details in your response
|
|
34
|
+
- Use "Re: <original subject>" as your response subject
|
|
35
|
+
- Be concise but complete"""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from .action import (
|
|
2
|
+
ActionAgentFunction,
|
|
3
|
+
LiteLLMActionAgentFunction,
|
|
4
|
+
action_agent_factory,
|
|
5
|
+
)
|
|
6
|
+
from .base import (
|
|
7
|
+
AgentFunction,
|
|
8
|
+
LiteLLMAgentFunction,
|
|
9
|
+
base_agent_factory,
|
|
10
|
+
)
|
|
11
|
+
from .supervisor import (
|
|
12
|
+
LiteLLMSupervisorFunction,
|
|
13
|
+
SupervisorFunction,
|
|
14
|
+
supervisor_factory,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"action_agent_factory",
|
|
19
|
+
"ActionAgentFunction",
|
|
20
|
+
"LiteLLMActionAgentFunction",
|
|
21
|
+
"AgentFunction",
|
|
22
|
+
"base_agent_factory",
|
|
23
|
+
"LiteLLMAgentFunction",
|
|
24
|
+
"supervisor_factory",
|
|
25
|
+
"SupervisorFunction",
|
|
26
|
+
"LiteLLMSupervisorFunction",
|
|
27
|
+
]
|
mail/factories/action.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Addison Kline, Ryan Heaton
|
|
3
|
+
|
|
4
|
+
import warnings
|
|
5
|
+
from abc import abstractmethod
|
|
6
|
+
from collections.abc import Awaitable
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
from openai import pydantic_function_tool
|
|
10
|
+
from openai.resources.responses.responses import _make_tools
|
|
11
|
+
|
|
12
|
+
from mail.core.agents import AgentFunction, AgentOutput
|
|
13
|
+
from mail.factories.base import (
|
|
14
|
+
LiteLLMAgentFunction,
|
|
15
|
+
MAILAgentFunction,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def action_agent_factory(
|
|
20
|
+
# REQUIRED
|
|
21
|
+
# top-level params
|
|
22
|
+
comm_targets: list[str],
|
|
23
|
+
tools: list[dict[str, Any]],
|
|
24
|
+
# instance params
|
|
25
|
+
user_token: str,
|
|
26
|
+
# internal params
|
|
27
|
+
llm: str,
|
|
28
|
+
system: str,
|
|
29
|
+
# OPTIONAL
|
|
30
|
+
# top-level params
|
|
31
|
+
name: str = "action",
|
|
32
|
+
enable_entrypoint: bool = False,
|
|
33
|
+
enable_interswarm: bool = False,
|
|
34
|
+
can_complete_tasks: bool = False,
|
|
35
|
+
tool_format: Literal["completions", "responses"] = "responses",
|
|
36
|
+
exclude_tools: list[str] = [],
|
|
37
|
+
# instance params
|
|
38
|
+
# ...
|
|
39
|
+
# internal params
|
|
40
|
+
reasoning_effort: Literal["low", "medium", "high"] | None = None,
|
|
41
|
+
thinking_budget: int | None = None,
|
|
42
|
+
max_tokens: int | None = None,
|
|
43
|
+
memory: bool = True,
|
|
44
|
+
use_proxy: bool = True,
|
|
45
|
+
_debug_include_mail_tools: bool = True,
|
|
46
|
+
) -> AgentFunction:
|
|
47
|
+
warnings.warn(
|
|
48
|
+
"`mail.factories.action:action_agent_factory` is deprecated and will be removed in a future version. "
|
|
49
|
+
"Use `mail.factories.action:LiteLLMActionAgentFunction` instead.",
|
|
50
|
+
DeprecationWarning,
|
|
51
|
+
stacklevel=2,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
litellm_action_agent = LiteLLMActionAgentFunction(
|
|
55
|
+
name=name,
|
|
56
|
+
comm_targets=comm_targets,
|
|
57
|
+
tools=tools,
|
|
58
|
+
llm=llm,
|
|
59
|
+
system=system,
|
|
60
|
+
user_token=user_token,
|
|
61
|
+
enable_entrypoint=enable_entrypoint,
|
|
62
|
+
enable_interswarm=enable_interswarm,
|
|
63
|
+
can_complete_tasks=can_complete_tasks,
|
|
64
|
+
tool_format=tool_format,
|
|
65
|
+
exclude_tools=exclude_tools,
|
|
66
|
+
reasoning_effort=reasoning_effort,
|
|
67
|
+
thinking_budget=thinking_budget,
|
|
68
|
+
max_tokens=max_tokens,
|
|
69
|
+
memory=memory,
|
|
70
|
+
use_proxy=use_proxy,
|
|
71
|
+
_debug_include_mail_tools=_debug_include_mail_tools,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
async def run(
|
|
75
|
+
messages: list[dict[str, Any]],
|
|
76
|
+
tool_choice: str | dict[str, str] = "required",
|
|
77
|
+
) -> AgentOutput:
|
|
78
|
+
"""
|
|
79
|
+
Execute the LiteLLM-based action agent function.
|
|
80
|
+
"""
|
|
81
|
+
return await litellm_action_agent(messages, tool_choice)
|
|
82
|
+
|
|
83
|
+
return run
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ActionAgentFunction(MAILAgentFunction):
|
|
87
|
+
"""
|
|
88
|
+
Class representing a MAIL-compatible action agent function.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
name: str,
|
|
94
|
+
comm_targets: list[str],
|
|
95
|
+
tools: list[dict[str, Any]],
|
|
96
|
+
enable_entrypoint: bool = False,
|
|
97
|
+
enable_interswarm: bool = False,
|
|
98
|
+
can_complete_tasks: bool = False,
|
|
99
|
+
tool_format: Literal["completions", "responses"] = "responses",
|
|
100
|
+
exclude_tools: list[str] = [],
|
|
101
|
+
**kwargs: Any,
|
|
102
|
+
) -> None:
|
|
103
|
+
# ensure that the action tools are in the correct format
|
|
104
|
+
parsed_tools: list[dict[str, Any]] = []
|
|
105
|
+
if not isinstance(tools[0], dict):
|
|
106
|
+
parsed_tools = [pydantic_function_tool(tool) for tool in tools] # type: ignore
|
|
107
|
+
if tool_format == "responses":
|
|
108
|
+
parsed_tools = _make_tools(parsed_tools) # type: ignore
|
|
109
|
+
|
|
110
|
+
else:
|
|
111
|
+
parsed_tools = tools # type: ignore
|
|
112
|
+
|
|
113
|
+
super().__init__(
|
|
114
|
+
name=name,
|
|
115
|
+
comm_targets=comm_targets,
|
|
116
|
+
tools=parsed_tools,
|
|
117
|
+
enable_entrypoint=enable_entrypoint,
|
|
118
|
+
enable_interswarm=enable_interswarm,
|
|
119
|
+
can_complete_tasks=can_complete_tasks,
|
|
120
|
+
tool_format=tool_format,
|
|
121
|
+
exclude_tools=exclude_tools,
|
|
122
|
+
**kwargs,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
def __call__(
|
|
127
|
+
self,
|
|
128
|
+
messages: list[dict[str, Any]],
|
|
129
|
+
tool_choice: str | dict[str, str] = "required",
|
|
130
|
+
) -> Awaitable[AgentOutput]:
|
|
131
|
+
"""
|
|
132
|
+
Execute the MAIL-compatible agent function.
|
|
133
|
+
"""
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class LiteLLMActionAgentFunction(ActionAgentFunction):
|
|
138
|
+
"""
|
|
139
|
+
Class representing a MAIL-compatible, LiteLLM-based action agent function.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(
|
|
143
|
+
self,
|
|
144
|
+
name: str,
|
|
145
|
+
comm_targets: list[str],
|
|
146
|
+
tools: list[dict[str, Any]],
|
|
147
|
+
llm: str,
|
|
148
|
+
system: str,
|
|
149
|
+
user_token: str = "",
|
|
150
|
+
enable_entrypoint: bool = False,
|
|
151
|
+
enable_interswarm: bool = False,
|
|
152
|
+
can_complete_tasks: bool = False,
|
|
153
|
+
tool_format: Literal["completions", "responses"] = "responses",
|
|
154
|
+
exclude_tools: list[str] = [],
|
|
155
|
+
reasoning_effort: Literal["minimal", "low", "medium", "high"] | None = None,
|
|
156
|
+
thinking_budget: int | None = None,
|
|
157
|
+
max_tokens: int | None = None,
|
|
158
|
+
memory: bool = True,
|
|
159
|
+
use_proxy: bool = True,
|
|
160
|
+
_debug_include_mail_tools: bool = True,
|
|
161
|
+
stream_tokens: bool = False,
|
|
162
|
+
default_tool_choice: str | dict[str, str] | None = None,
|
|
163
|
+
) -> None:
|
|
164
|
+
super().__init__(
|
|
165
|
+
name=name,
|
|
166
|
+
comm_targets=comm_targets,
|
|
167
|
+
tools=tools,
|
|
168
|
+
enable_entrypoint=enable_entrypoint,
|
|
169
|
+
enable_interswarm=enable_interswarm,
|
|
170
|
+
can_complete_tasks=can_complete_tasks,
|
|
171
|
+
tool_format=tool_format,
|
|
172
|
+
exclude_tools=exclude_tools,
|
|
173
|
+
)
|
|
174
|
+
self.llm = llm
|
|
175
|
+
self.system = system
|
|
176
|
+
self.user_token = user_token
|
|
177
|
+
self.reasoning_effort = reasoning_effort
|
|
178
|
+
self.thinking_budget = thinking_budget
|
|
179
|
+
self.max_tokens = max_tokens
|
|
180
|
+
self.memory = memory
|
|
181
|
+
self.use_proxy = use_proxy
|
|
182
|
+
self._debug_include_mail_tools = _debug_include_mail_tools
|
|
183
|
+
self.stream_tokens = stream_tokens
|
|
184
|
+
self.default_tool_choice = default_tool_choice
|
|
185
|
+
self.action_agent_fn = LiteLLMAgentFunction(
|
|
186
|
+
llm=self.llm,
|
|
187
|
+
comm_targets=self.comm_targets,
|
|
188
|
+
tools=self.tools,
|
|
189
|
+
system=self.system,
|
|
190
|
+
user_token=self.user_token,
|
|
191
|
+
reasoning_effort=self.reasoning_effort,
|
|
192
|
+
thinking_budget=self.thinking_budget,
|
|
193
|
+
max_tokens=self.max_tokens,
|
|
194
|
+
memory=self.memory,
|
|
195
|
+
use_proxy=self.use_proxy,
|
|
196
|
+
can_complete_tasks=self.can_complete_tasks,
|
|
197
|
+
tool_format=self.tool_format,
|
|
198
|
+
name=self.name,
|
|
199
|
+
enable_entrypoint=self.enable_entrypoint,
|
|
200
|
+
enable_interswarm=self.enable_interswarm,
|
|
201
|
+
exclude_tools=self.exclude_tools,
|
|
202
|
+
_debug_include_mail_tools=self._debug_include_mail_tools,
|
|
203
|
+
stream_tokens=self.stream_tokens,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def __call__(
|
|
207
|
+
self,
|
|
208
|
+
messages: list[dict[str, Any]],
|
|
209
|
+
tool_choice: str | dict[str, str] = "required",
|
|
210
|
+
) -> Awaitable[AgentOutput]:
|
|
211
|
+
"""
|
|
212
|
+
Execute a LiteLLM-based action agent function.
|
|
213
|
+
"""
|
|
214
|
+
# Use default_tool_choice if set, otherwise use the passed tool_choice
|
|
215
|
+
effective_tool_choice = (
|
|
216
|
+
self.default_tool_choice
|
|
217
|
+
if self.default_tool_choice is not None
|
|
218
|
+
else tool_choice
|
|
219
|
+
)
|
|
220
|
+
return self.action_agent_fn(
|
|
221
|
+
messages=messages,
|
|
222
|
+
tool_choice=effective_tool_choice,
|
|
223
|
+
)
|