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.
Files changed (137) hide show
  1. mail/__init__.py +35 -0
  2. mail/api.py +1964 -0
  3. mail/cli.py +432 -0
  4. mail/client.py +1657 -0
  5. mail/config/__init__.py +8 -0
  6. mail/config/client.py +87 -0
  7. mail/config/server.py +165 -0
  8. mail/core/__init__.py +72 -0
  9. mail/core/actions.py +69 -0
  10. mail/core/agents.py +73 -0
  11. mail/core/message.py +366 -0
  12. mail/core/runtime.py +3537 -0
  13. mail/core/tasks.py +311 -0
  14. mail/core/tools.py +1206 -0
  15. mail/db/__init__.py +0 -0
  16. mail/db/init.py +182 -0
  17. mail/db/types.py +65 -0
  18. mail/db/utils.py +523 -0
  19. mail/examples/__init__.py +27 -0
  20. mail/examples/analyst_dummy/__init__.py +15 -0
  21. mail/examples/analyst_dummy/agent.py +136 -0
  22. mail/examples/analyst_dummy/prompts.py +44 -0
  23. mail/examples/consultant_dummy/__init__.py +15 -0
  24. mail/examples/consultant_dummy/agent.py +136 -0
  25. mail/examples/consultant_dummy/prompts.py +42 -0
  26. mail/examples/data_analysis/__init__.py +40 -0
  27. mail/examples/data_analysis/analyst/__init__.py +9 -0
  28. mail/examples/data_analysis/analyst/agent.py +67 -0
  29. mail/examples/data_analysis/analyst/prompts.py +53 -0
  30. mail/examples/data_analysis/processor/__init__.py +13 -0
  31. mail/examples/data_analysis/processor/actions.py +293 -0
  32. mail/examples/data_analysis/processor/agent.py +67 -0
  33. mail/examples/data_analysis/processor/prompts.py +48 -0
  34. mail/examples/data_analysis/reporter/__init__.py +10 -0
  35. mail/examples/data_analysis/reporter/actions.py +187 -0
  36. mail/examples/data_analysis/reporter/agent.py +67 -0
  37. mail/examples/data_analysis/reporter/prompts.py +49 -0
  38. mail/examples/data_analysis/statistics/__init__.py +18 -0
  39. mail/examples/data_analysis/statistics/actions.py +343 -0
  40. mail/examples/data_analysis/statistics/agent.py +67 -0
  41. mail/examples/data_analysis/statistics/prompts.py +60 -0
  42. mail/examples/mafia/__init__.py +0 -0
  43. mail/examples/mafia/game.py +1537 -0
  44. mail/examples/mafia/narrator_tools.py +396 -0
  45. mail/examples/mafia/personas.py +240 -0
  46. mail/examples/mafia/prompts.py +489 -0
  47. mail/examples/mafia/roles.py +147 -0
  48. mail/examples/mafia/spec.md +350 -0
  49. mail/examples/math_dummy/__init__.py +23 -0
  50. mail/examples/math_dummy/actions.py +252 -0
  51. mail/examples/math_dummy/agent.py +136 -0
  52. mail/examples/math_dummy/prompts.py +46 -0
  53. mail/examples/math_dummy/types.py +5 -0
  54. mail/examples/research/__init__.py +39 -0
  55. mail/examples/research/researcher/__init__.py +9 -0
  56. mail/examples/research/researcher/agent.py +67 -0
  57. mail/examples/research/researcher/prompts.py +54 -0
  58. mail/examples/research/searcher/__init__.py +10 -0
  59. mail/examples/research/searcher/actions.py +324 -0
  60. mail/examples/research/searcher/agent.py +67 -0
  61. mail/examples/research/searcher/prompts.py +53 -0
  62. mail/examples/research/summarizer/__init__.py +18 -0
  63. mail/examples/research/summarizer/actions.py +255 -0
  64. mail/examples/research/summarizer/agent.py +67 -0
  65. mail/examples/research/summarizer/prompts.py +55 -0
  66. mail/examples/research/verifier/__init__.py +10 -0
  67. mail/examples/research/verifier/actions.py +337 -0
  68. mail/examples/research/verifier/agent.py +67 -0
  69. mail/examples/research/verifier/prompts.py +52 -0
  70. mail/examples/supervisor/__init__.py +11 -0
  71. mail/examples/supervisor/agent.py +4 -0
  72. mail/examples/supervisor/prompts.py +93 -0
  73. mail/examples/support/__init__.py +33 -0
  74. mail/examples/support/classifier/__init__.py +10 -0
  75. mail/examples/support/classifier/actions.py +307 -0
  76. mail/examples/support/classifier/agent.py +68 -0
  77. mail/examples/support/classifier/prompts.py +56 -0
  78. mail/examples/support/coordinator/__init__.py +9 -0
  79. mail/examples/support/coordinator/agent.py +67 -0
  80. mail/examples/support/coordinator/prompts.py +48 -0
  81. mail/examples/support/faq/__init__.py +10 -0
  82. mail/examples/support/faq/actions.py +182 -0
  83. mail/examples/support/faq/agent.py +67 -0
  84. mail/examples/support/faq/prompts.py +42 -0
  85. mail/examples/support/sentiment/__init__.py +15 -0
  86. mail/examples/support/sentiment/actions.py +341 -0
  87. mail/examples/support/sentiment/agent.py +67 -0
  88. mail/examples/support/sentiment/prompts.py +54 -0
  89. mail/examples/weather_dummy/__init__.py +23 -0
  90. mail/examples/weather_dummy/actions.py +75 -0
  91. mail/examples/weather_dummy/agent.py +136 -0
  92. mail/examples/weather_dummy/prompts.py +35 -0
  93. mail/examples/weather_dummy/types.py +5 -0
  94. mail/factories/__init__.py +27 -0
  95. mail/factories/action.py +223 -0
  96. mail/factories/base.py +1531 -0
  97. mail/factories/supervisor.py +241 -0
  98. mail/net/__init__.py +7 -0
  99. mail/net/registry.py +712 -0
  100. mail/net/router.py +728 -0
  101. mail/net/server_utils.py +114 -0
  102. mail/net/types.py +247 -0
  103. mail/server.py +1605 -0
  104. mail/stdlib/__init__.py +0 -0
  105. mail/stdlib/anthropic/__init__.py +0 -0
  106. mail/stdlib/fs/__init__.py +15 -0
  107. mail/stdlib/fs/actions.py +209 -0
  108. mail/stdlib/http/__init__.py +19 -0
  109. mail/stdlib/http/actions.py +333 -0
  110. mail/stdlib/interswarm/__init__.py +11 -0
  111. mail/stdlib/interswarm/actions.py +208 -0
  112. mail/stdlib/mcp/__init__.py +19 -0
  113. mail/stdlib/mcp/actions.py +294 -0
  114. mail/stdlib/openai/__init__.py +13 -0
  115. mail/stdlib/openai/agents.py +451 -0
  116. mail/summarizer.py +234 -0
  117. mail/swarms_json/__init__.py +27 -0
  118. mail/swarms_json/types.py +87 -0
  119. mail/swarms_json/utils.py +255 -0
  120. mail/url_scheme.py +51 -0
  121. mail/utils/__init__.py +53 -0
  122. mail/utils/auth.py +194 -0
  123. mail/utils/context.py +17 -0
  124. mail/utils/logger.py +73 -0
  125. mail/utils/openai.py +212 -0
  126. mail/utils/parsing.py +89 -0
  127. mail/utils/serialize.py +292 -0
  128. mail/utils/store.py +49 -0
  129. mail/utils/string_builder.py +119 -0
  130. mail/utils/version.py +20 -0
  131. mail_swarms-1.3.2.dist-info/METADATA +237 -0
  132. mail_swarms-1.3.2.dist-info/RECORD +137 -0
  133. mail_swarms-1.3.2.dist-info/WHEEL +4 -0
  134. mail_swarms-1.3.2.dist-info/entry_points.txt +2 -0
  135. mail_swarms-1.3.2.dist-info/licenses/LICENSE +202 -0
  136. mail_swarms-1.3.2.dist-info/licenses/NOTICE +10 -0
  137. 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,5 @@
1
+ from mail.examples.weather_dummy.actions import get_weather_forecast
2
+
3
+ action_get_weather_forecast = get_weather_forecast
4
+
5
+ __all__ = ["action_get_weather_forecast"]
@@ -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
+ ]
@@ -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
+ )