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,255 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2025 Charon Labs
3
+
4
+ """Summarization actions for the Research Assistant swarm."""
5
+
6
+ import json
7
+ import re
8
+ from datetime import datetime, UTC
9
+ from typing import Any
10
+
11
+ from mail import action
12
+
13
+
14
+ def _extract_sentences(text: str) -> list[str]:
15
+ """Extract sentences from text."""
16
+ # Simple sentence splitting
17
+ sentences = re.split(r"(?<=[.!?])\s+", text)
18
+ return [s.strip() for s in sentences if len(s.strip()) > 10]
19
+
20
+
21
+ def _score_sentence_importance(sentence: str, all_sentences: list[str]) -> float:
22
+ """Score sentence importance based on content features."""
23
+ score = 0.0
24
+ sentence_lower = sentence.lower()
25
+
26
+ # Position bonus (first sentences are often important)
27
+ if sentence in all_sentences[:3]:
28
+ score += 0.3
29
+
30
+ # Contains important indicators
31
+ importance_words = [
32
+ "important",
33
+ "key",
34
+ "main",
35
+ "significant",
36
+ "primary",
37
+ "crucial",
38
+ "essential",
39
+ "critical",
40
+ "major",
41
+ "notable",
42
+ "first",
43
+ "second",
44
+ "third",
45
+ "finally",
46
+ "however",
47
+ "therefore",
48
+ "in conclusion",
49
+ "as a result",
50
+ "research shows",
51
+ "data indicates",
52
+ ]
53
+ for word in importance_words:
54
+ if word in sentence_lower:
55
+ score += 0.2
56
+
57
+ # Contains numbers (often factual/important)
58
+ if any(char.isdigit() for char in sentence):
59
+ score += 0.15
60
+
61
+ # Length penalty for very short or very long sentences
62
+ word_count = len(sentence.split())
63
+ if word_count < 5:
64
+ score -= 0.2
65
+ elif word_count > 50:
66
+ score -= 0.1
67
+ elif 15 <= word_count <= 30:
68
+ score += 0.1
69
+
70
+ return max(0, score)
71
+
72
+
73
+ def _create_extractive_summary(text: str, max_sentences: int) -> str:
74
+ """Create an extractive summary by selecting important sentences."""
75
+ sentences = _extract_sentences(text)
76
+
77
+ if len(sentences) <= max_sentences:
78
+ return " ".join(sentences)
79
+
80
+ # Score sentences
81
+ scored = [(s, _score_sentence_importance(s, sentences)) for s in sentences]
82
+
83
+ # Sort by score but maintain some original order
84
+ # Take top-scoring sentences
85
+ scored.sort(key=lambda x: x[1], reverse=True)
86
+ selected = scored[:max_sentences]
87
+
88
+ # Reorder by original position
89
+ selected_sentences = [s[0] for s in selected]
90
+ ordered = [s for s in sentences if s in selected_sentences]
91
+
92
+ return " ".join(ordered)
93
+
94
+
95
+ SUMMARIZE_TEXT_PARAMETERS = {
96
+ "type": "object",
97
+ "properties": {
98
+ "text": {
99
+ "type": "string",
100
+ "description": "The text to summarize",
101
+ },
102
+ "max_length": {
103
+ "type": "integer",
104
+ "minimum": 50,
105
+ "maximum": 2000,
106
+ "description": "Maximum length of summary in characters (default: 500)",
107
+ },
108
+ },
109
+ "required": ["text"],
110
+ }
111
+
112
+
113
+ @action(
114
+ name="summarize_text",
115
+ description="Create a concise summary of longer text.",
116
+ parameters=SUMMARIZE_TEXT_PARAMETERS,
117
+ )
118
+ async def summarize_text(args: dict[str, Any]) -> str:
119
+ """Summarize text to specified length."""
120
+ try:
121
+ text = args["text"]
122
+ max_length = args.get("max_length", 500)
123
+ except KeyError as e:
124
+ return f"Error: {e} is required"
125
+
126
+ if not text.strip():
127
+ return json.dumps({"error": "Text cannot be empty"})
128
+
129
+ original_length = len(text)
130
+ original_sentences = len(_extract_sentences(text))
131
+
132
+ # Estimate needed sentences for target length
133
+ avg_sentence_length = original_length / max(original_sentences, 1)
134
+ target_sentences = max(1, int(max_length / max(avg_sentence_length, 50)))
135
+
136
+ # Create summary
137
+ summary = _create_extractive_summary(text, target_sentences)
138
+
139
+ # Truncate if still too long
140
+ if len(summary) > max_length:
141
+ summary = summary[: max_length - 3].rsplit(" ", 1)[0] + "..."
142
+
143
+ compression_ratio = len(summary) / original_length if original_length > 0 else 1
144
+
145
+ result = {
146
+ "summary": summary,
147
+ "original_length": original_length,
148
+ "summary_length": len(summary),
149
+ "compression_ratio": round(compression_ratio, 2),
150
+ "original_sentences": original_sentences,
151
+ "summary_sentences": len(_extract_sentences(summary)),
152
+ "method": "extractive",
153
+ }
154
+
155
+ return json.dumps(result)
156
+
157
+
158
+ CREATE_BIBLIOGRAPHY_PARAMETERS = {
159
+ "type": "object",
160
+ "properties": {
161
+ "sources": {
162
+ "type": "array",
163
+ "items": {
164
+ "type": "object",
165
+ "properties": {
166
+ "title": {"type": "string"},
167
+ "author": {"type": "string"},
168
+ "url": {"type": "string"},
169
+ "date": {"type": "string"},
170
+ "source_type": {"type": "string"},
171
+ },
172
+ },
173
+ "description": "Array of source objects with title, author, url, date, source_type",
174
+ },
175
+ "style": {
176
+ "type": "string",
177
+ "enum": ["apa", "mla", "chicago", "simple"],
178
+ "description": "Citation style (default: simple)",
179
+ },
180
+ },
181
+ "required": ["sources"],
182
+ }
183
+
184
+
185
+ @action(
186
+ name="create_bibliography",
187
+ description="Format sources into a properly formatted bibliography.",
188
+ parameters=CREATE_BIBLIOGRAPHY_PARAMETERS,
189
+ )
190
+ async def create_bibliography(args: dict[str, Any]) -> str:
191
+ """Create a formatted bibliography from sources."""
192
+ try:
193
+ sources = args["sources"]
194
+ style = args.get("style", "simple")
195
+ except KeyError as e:
196
+ return f"Error: {e} is required"
197
+
198
+ if not sources:
199
+ return json.dumps({"error": "At least one source is required"})
200
+
201
+ formatted_entries = []
202
+
203
+ for i, source in enumerate(sources, 1):
204
+ title = source.get("title", "Untitled")
205
+ author = source.get("author", "Unknown Author")
206
+ url = source.get("url", "")
207
+ date = source.get("date", "n.d.")
208
+ source_type = source.get("source_type", "web")
209
+
210
+ if style == "apa":
211
+ # APA style: Author. (Date). Title. URL
212
+ entry = f"{author}. ({date}). {title}."
213
+ if url:
214
+ entry += f" Retrieved from {url}"
215
+ elif style == "mla":
216
+ # MLA style: Author. "Title." Date. URL.
217
+ entry = f'{author}. "{title}." {date}.'
218
+ if url:
219
+ entry += f" {url}."
220
+ elif style == "chicago":
221
+ # Chicago style: Author. "Title." Accessed Date. URL.
222
+ entry = f'{author}. "{title}."'
223
+ if url:
224
+ entry += f" Accessed {datetime.now(UTC).strftime('%B %d, %Y')}. {url}."
225
+ else: # simple
226
+ # Simple style: numbered list
227
+ entry = f"[{i}] {title}"
228
+ if author != "Unknown Author":
229
+ entry += f" by {author}"
230
+ if date != "n.d.":
231
+ entry += f" ({date})"
232
+ if url:
233
+ entry += f" - {url}"
234
+
235
+ formatted_entries.append(
236
+ {
237
+ "index": i,
238
+ "formatted": entry,
239
+ "source_type": source_type,
240
+ "original": source,
241
+ }
242
+ )
243
+
244
+ # Create bibliography text
245
+ bibliography_text = "\n".join([e["formatted"] for e in formatted_entries])
246
+
247
+ result = {
248
+ "style": style,
249
+ "entry_count": len(formatted_entries),
250
+ "entries": formatted_entries,
251
+ "bibliography": bibliography_text,
252
+ "generated_at": datetime.now(UTC).isoformat(),
253
+ }
254
+
255
+ return json.dumps(result)
@@ -0,0 +1,67 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2025 Charon Labs
3
+
4
+ """Summarizer agent for the Research Assistant 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 LiteLLMSummarizerFunction(LiteLLMActionAgentFunction):
14
+ """
15
+ Summarizer agent that synthesizes research findings.
16
+
17
+ This agent creates concise summaries and formatted
18
+ bibliographies from research data.
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 summarizer agent function."""
67
+ return super().__call__(messages, tool_choice)
@@ -0,0 +1,55 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2025 Charon Labs
3
+
4
+ SYSPROMPT = """You are summarizer@{swarm}, the synthesis specialist for this research assistant swarm.
5
+
6
+ # Your Role
7
+ Synthesize research findings into clear, well-organized summaries and create formatted bibliographies.
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 "researcher")
12
+ - After summarizing, call `send_response(target=<sender>, subject="Re: ...", body=<your summary>)`
13
+ - Include the COMPLETE summary in your response body
14
+
15
+ # Tools
16
+
17
+ ## Summarization Operations
18
+ - `summarize_text(text, max_length)`: Create a concise summary of longer text
19
+ - `create_bibliography(sources)`: Format sources into a proper bibliography
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 research findings from another agent
30
+ 2. Organize the information logically
31
+ 3. Call `summarize_text` to create a concise summary
32
+ 4. If sources provided, call `create_bibliography` to format them
33
+ 5. Call `send_response` with:
34
+ - Executive summary (key findings)
35
+ - Detailed summary (organized by theme)
36
+ - Bibliography (if applicable)
37
+ - Confidence notes or caveats
38
+
39
+ # Summary Structure
40
+
41
+ Good summaries include:
42
+ - **Key Findings**: 2-3 main takeaways
43
+ - **Background**: Brief context
44
+ - **Details**: Organized supporting information
45
+ - **Limitations**: What's uncertain or missing
46
+ - **Sources**: Properly formatted references
47
+
48
+ # Guidelines
49
+
50
+ - Lead with the most important findings
51
+ - Use clear, accessible language
52
+ - Maintain original meaning while condensing
53
+ - Preserve important nuances and caveats
54
+ - Include all cited sources in bibliography
55
+ - Use "Re: <original subject>" as your response subject"""
@@ -0,0 +1,10 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2025 Charon Labs
3
+
4
+ """Verifier agent for the Research Assistant swarm."""
5
+
6
+ from mail.examples.research.verifier.agent import LiteLLMVerifierFunction
7
+ from mail.examples.research.verifier.actions import verify_claim, rate_confidence
8
+ from mail.examples.research.verifier.prompts import SYSPROMPT
9
+
10
+ __all__ = ["LiteLLMVerifierFunction", "verify_claim", "rate_confidence", "SYSPROMPT"]