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,307 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Charon Labs
|
|
3
|
+
|
|
4
|
+
"""Ticket classification action for the Customer Support swarm."""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from mail import action
|
|
11
|
+
|
|
12
|
+
# Keyword mappings for classification
|
|
13
|
+
CATEGORY_KEYWORDS = {
|
|
14
|
+
"billing": [
|
|
15
|
+
"payment",
|
|
16
|
+
"pay",
|
|
17
|
+
"charge",
|
|
18
|
+
"charged",
|
|
19
|
+
"invoice",
|
|
20
|
+
"bill",
|
|
21
|
+
"refund",
|
|
22
|
+
"subscription",
|
|
23
|
+
"cancel",
|
|
24
|
+
"price",
|
|
25
|
+
"pricing",
|
|
26
|
+
"cost",
|
|
27
|
+
"credit card",
|
|
28
|
+
"card",
|
|
29
|
+
"money",
|
|
30
|
+
"fee",
|
|
31
|
+
"upgrade",
|
|
32
|
+
"downgrade",
|
|
33
|
+
"renew",
|
|
34
|
+
"renewal",
|
|
35
|
+
],
|
|
36
|
+
"technical": [
|
|
37
|
+
"bug",
|
|
38
|
+
"error",
|
|
39
|
+
"crash",
|
|
40
|
+
"not working",
|
|
41
|
+
"broken",
|
|
42
|
+
"issue",
|
|
43
|
+
"problem",
|
|
44
|
+
"feature",
|
|
45
|
+
"slow",
|
|
46
|
+
"loading",
|
|
47
|
+
"api",
|
|
48
|
+
"integration",
|
|
49
|
+
"code",
|
|
50
|
+
"export",
|
|
51
|
+
"import",
|
|
52
|
+
"sync",
|
|
53
|
+
"download",
|
|
54
|
+
"upload",
|
|
55
|
+
"connect",
|
|
56
|
+
"connection",
|
|
57
|
+
],
|
|
58
|
+
"account": [
|
|
59
|
+
"login",
|
|
60
|
+
"password",
|
|
61
|
+
"reset",
|
|
62
|
+
"account",
|
|
63
|
+
"profile",
|
|
64
|
+
"email",
|
|
65
|
+
"locked",
|
|
66
|
+
"access",
|
|
67
|
+
"sign in",
|
|
68
|
+
"sign up",
|
|
69
|
+
"register",
|
|
70
|
+
"2fa",
|
|
71
|
+
"authentication",
|
|
72
|
+
"security",
|
|
73
|
+
"verify",
|
|
74
|
+
"verification",
|
|
75
|
+
"username",
|
|
76
|
+
"settings",
|
|
77
|
+
],
|
|
78
|
+
"general": [
|
|
79
|
+
"question",
|
|
80
|
+
"help",
|
|
81
|
+
"support",
|
|
82
|
+
"information",
|
|
83
|
+
"info",
|
|
84
|
+
"how to",
|
|
85
|
+
"what is",
|
|
86
|
+
"where",
|
|
87
|
+
"when",
|
|
88
|
+
"feedback",
|
|
89
|
+
"suggestion",
|
|
90
|
+
"thanks",
|
|
91
|
+
"thank you",
|
|
92
|
+
"appreciate",
|
|
93
|
+
"curious",
|
|
94
|
+
"wondering",
|
|
95
|
+
],
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
PRIORITY_KEYWORDS = {
|
|
99
|
+
"urgent": [
|
|
100
|
+
"urgent",
|
|
101
|
+
"emergency",
|
|
102
|
+
"asap",
|
|
103
|
+
"immediately",
|
|
104
|
+
"critical",
|
|
105
|
+
"outage",
|
|
106
|
+
"down",
|
|
107
|
+
"breach",
|
|
108
|
+
"hacked",
|
|
109
|
+
"compromised",
|
|
110
|
+
"lost all",
|
|
111
|
+
"cannot access",
|
|
112
|
+
"locked out",
|
|
113
|
+
"deadline",
|
|
114
|
+
"now",
|
|
115
|
+
"right now",
|
|
116
|
+
],
|
|
117
|
+
"high": [
|
|
118
|
+
"important",
|
|
119
|
+
"serious",
|
|
120
|
+
"major",
|
|
121
|
+
"significant",
|
|
122
|
+
"blocking",
|
|
123
|
+
"stuck",
|
|
124
|
+
"cannot",
|
|
125
|
+
"can't",
|
|
126
|
+
"unable",
|
|
127
|
+
"frustrated",
|
|
128
|
+
"angry",
|
|
129
|
+
"unacceptable",
|
|
130
|
+
"terrible",
|
|
131
|
+
"awful",
|
|
132
|
+
"horrible",
|
|
133
|
+
"worst",
|
|
134
|
+
],
|
|
135
|
+
"medium": [
|
|
136
|
+
"issue",
|
|
137
|
+
"problem",
|
|
138
|
+
"help",
|
|
139
|
+
"need",
|
|
140
|
+
"want",
|
|
141
|
+
"would like",
|
|
142
|
+
"please",
|
|
143
|
+
"soon",
|
|
144
|
+
"when",
|
|
145
|
+
"how long",
|
|
146
|
+
"waiting",
|
|
147
|
+
"expected",
|
|
148
|
+
],
|
|
149
|
+
"low": [
|
|
150
|
+
"question",
|
|
151
|
+
"curious",
|
|
152
|
+
"wondering",
|
|
153
|
+
"just",
|
|
154
|
+
"maybe",
|
|
155
|
+
"might",
|
|
156
|
+
"feedback",
|
|
157
|
+
"suggestion",
|
|
158
|
+
"idea",
|
|
159
|
+
"thanks",
|
|
160
|
+
"fyi",
|
|
161
|
+
"minor",
|
|
162
|
+
],
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _count_keyword_matches(text: str, keywords: list[str]) -> int:
|
|
167
|
+
"""Count how many keywords appear in the text."""
|
|
168
|
+
text_lower = text.lower()
|
|
169
|
+
count = 0
|
|
170
|
+
for keyword in keywords:
|
|
171
|
+
# Use word boundary matching for better accuracy
|
|
172
|
+
pattern = r"\b" + re.escape(keyword) + r"\b"
|
|
173
|
+
if re.search(pattern, text_lower):
|
|
174
|
+
count += 1
|
|
175
|
+
return count
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _classify_category(text: str) -> tuple[str, float]:
|
|
179
|
+
"""Classify the ticket category based on keyword matching."""
|
|
180
|
+
scores = {}
|
|
181
|
+
for category, keywords in CATEGORY_KEYWORDS.items():
|
|
182
|
+
scores[category] = _count_keyword_matches(text, keywords)
|
|
183
|
+
|
|
184
|
+
# Find the category with highest score
|
|
185
|
+
max_category = max(scores, key=lambda x: scores[x])
|
|
186
|
+
max_score = scores[max_category]
|
|
187
|
+
|
|
188
|
+
# Calculate confidence based on score differential
|
|
189
|
+
total_score = sum(scores.values())
|
|
190
|
+
if total_score == 0:
|
|
191
|
+
return "general", 0.3 # Default to general with low confidence
|
|
192
|
+
|
|
193
|
+
confidence = max_score / total_score if total_score > 0 else 0.5
|
|
194
|
+
return max_category, round(confidence, 2)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _classify_priority(text: str) -> tuple[str, float]:
|
|
198
|
+
"""Classify the ticket priority based on keyword matching."""
|
|
199
|
+
scores = {}
|
|
200
|
+
for priority, keywords in PRIORITY_KEYWORDS.items():
|
|
201
|
+
scores[priority] = _count_keyword_matches(text, keywords)
|
|
202
|
+
|
|
203
|
+
# Apply priority weighting (urgent keywords should have more weight)
|
|
204
|
+
weighted_scores = {
|
|
205
|
+
"urgent": scores["urgent"] * 4,
|
|
206
|
+
"high": scores["high"] * 3,
|
|
207
|
+
"medium": scores["medium"] * 2,
|
|
208
|
+
"low": scores["low"] * 1,
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# Find the priority with highest weighted score
|
|
212
|
+
max_priority = max(weighted_scores, key=lambda x: weighted_scores[x])
|
|
213
|
+
max_score = weighted_scores[max_priority]
|
|
214
|
+
|
|
215
|
+
# Default to medium if no clear signal
|
|
216
|
+
if max_score == 0:
|
|
217
|
+
return "medium", 0.5
|
|
218
|
+
|
|
219
|
+
total_score = sum(weighted_scores.values())
|
|
220
|
+
confidence = max_score / total_score if total_score > 0 else 0.5
|
|
221
|
+
return max_priority, round(confidence, 2)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
CLASSIFY_TICKET_PARAMETERS = {
|
|
225
|
+
"type": "object",
|
|
226
|
+
"properties": {
|
|
227
|
+
"text": {
|
|
228
|
+
"type": "string",
|
|
229
|
+
"description": "The customer support ticket text to classify",
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
"required": ["text"],
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@action(
|
|
237
|
+
name="classify_ticket",
|
|
238
|
+
description="Classify a customer support ticket by category and priority level.",
|
|
239
|
+
parameters=CLASSIFY_TICKET_PARAMETERS,
|
|
240
|
+
)
|
|
241
|
+
async def classify_ticket(args: dict[str, Any]) -> str:
|
|
242
|
+
"""Classify a support ticket and return category and priority."""
|
|
243
|
+
try:
|
|
244
|
+
text = args["text"]
|
|
245
|
+
except KeyError as e:
|
|
246
|
+
return f"Error: {e} is required"
|
|
247
|
+
|
|
248
|
+
if not text.strip():
|
|
249
|
+
return json.dumps({"error": "Ticket text cannot be empty"})
|
|
250
|
+
|
|
251
|
+
# Perform classification
|
|
252
|
+
category, category_confidence = _classify_category(text)
|
|
253
|
+
priority, priority_confidence = _classify_priority(text)
|
|
254
|
+
|
|
255
|
+
# Generate reasoning
|
|
256
|
+
reasoning_parts = []
|
|
257
|
+
if category_confidence >= 0.6:
|
|
258
|
+
reasoning_parts.append(f"Strong match for '{category}' category")
|
|
259
|
+
else:
|
|
260
|
+
reasoning_parts.append(f"Moderate match for '{category}' category")
|
|
261
|
+
|
|
262
|
+
if priority == "urgent":
|
|
263
|
+
reasoning_parts.append("Contains urgent/critical language")
|
|
264
|
+
elif priority == "high":
|
|
265
|
+
reasoning_parts.append(
|
|
266
|
+
"Indicates significant user frustration or blocking issue"
|
|
267
|
+
)
|
|
268
|
+
elif priority == "low":
|
|
269
|
+
reasoning_parts.append("Appears to be a general inquiry or minor issue")
|
|
270
|
+
|
|
271
|
+
result = {
|
|
272
|
+
"category": category,
|
|
273
|
+
"category_confidence": category_confidence,
|
|
274
|
+
"priority": priority,
|
|
275
|
+
"priority_confidence": priority_confidence,
|
|
276
|
+
"reasoning": ". ".join(reasoning_parts) + ".",
|
|
277
|
+
"suggested_actions": _get_suggested_actions(category, priority),
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return json.dumps(result)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _get_suggested_actions(category: str, priority: str) -> list[str]:
|
|
284
|
+
"""Get suggested actions based on classification."""
|
|
285
|
+
actions = []
|
|
286
|
+
|
|
287
|
+
if priority == "urgent":
|
|
288
|
+
actions.append("Escalate to senior support immediately")
|
|
289
|
+
actions.append("Consider immediate callback if phone available")
|
|
290
|
+
elif priority == "high":
|
|
291
|
+
actions.append("Prioritize in support queue")
|
|
292
|
+
actions.append("Provide detailed response within 4 hours")
|
|
293
|
+
|
|
294
|
+
if category == "billing":
|
|
295
|
+
actions.append("Check customer's billing history")
|
|
296
|
+
actions.append("Verify subscription status")
|
|
297
|
+
elif category == "technical":
|
|
298
|
+
actions.append("Check for known issues or outages")
|
|
299
|
+
actions.append("Gather technical details if needed")
|
|
300
|
+
elif category == "account":
|
|
301
|
+
actions.append("Verify customer identity")
|
|
302
|
+
actions.append("Check account security flags")
|
|
303
|
+
|
|
304
|
+
if not actions:
|
|
305
|
+
actions.append("Respond with standard FAQ or support resources")
|
|
306
|
+
|
|
307
|
+
return actions
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Charon Labs
|
|
3
|
+
|
|
4
|
+
"""Classifier 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 LiteLLMClassifierFunction(LiteLLMActionAgentFunction):
|
|
14
|
+
"""
|
|
15
|
+
Ticket classifier agent that categorizes and prioritizes support tickets.
|
|
16
|
+
|
|
17
|
+
This agent analyzes customer messages to determine the appropriate
|
|
18
|
+
category (billing, technical, account, general) and priority level
|
|
19
|
+
(low, medium, high, urgent).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
name: str,
|
|
25
|
+
comm_targets: list[str],
|
|
26
|
+
tools: list[dict[str, Any]],
|
|
27
|
+
llm: str,
|
|
28
|
+
system: str,
|
|
29
|
+
user_token: str = "",
|
|
30
|
+
enable_entrypoint: bool = False,
|
|
31
|
+
enable_interswarm: bool = False,
|
|
32
|
+
can_complete_tasks: bool = False,
|
|
33
|
+
tool_format: Literal["completions", "responses"] = "responses",
|
|
34
|
+
exclude_tools: list[str] = [],
|
|
35
|
+
reasoning_effort: Literal["minimal", "low", "medium", "high"] | None = None,
|
|
36
|
+
thinking_budget: int | None = None,
|
|
37
|
+
max_tokens: int | None = None,
|
|
38
|
+
memory: bool = True,
|
|
39
|
+
use_proxy: bool = True,
|
|
40
|
+
_debug_include_mail_tools: bool = True,
|
|
41
|
+
) -> None:
|
|
42
|
+
super().__init__(
|
|
43
|
+
name=name,
|
|
44
|
+
comm_targets=comm_targets,
|
|
45
|
+
tools=tools,
|
|
46
|
+
llm=llm,
|
|
47
|
+
system=system,
|
|
48
|
+
user_token=user_token,
|
|
49
|
+
enable_entrypoint=enable_entrypoint,
|
|
50
|
+
enable_interswarm=enable_interswarm,
|
|
51
|
+
can_complete_tasks=can_complete_tasks,
|
|
52
|
+
tool_format=tool_format,
|
|
53
|
+
exclude_tools=exclude_tools,
|
|
54
|
+
reasoning_effort=reasoning_effort,
|
|
55
|
+
thinking_budget=thinking_budget,
|
|
56
|
+
max_tokens=max_tokens,
|
|
57
|
+
memory=memory,
|
|
58
|
+
use_proxy=use_proxy,
|
|
59
|
+
_debug_include_mail_tools=_debug_include_mail_tools,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def __call__(
|
|
63
|
+
self,
|
|
64
|
+
messages: list[dict[str, Any]],
|
|
65
|
+
tool_choice: str | dict[str, str] = "required",
|
|
66
|
+
) -> Awaitable[AgentOutput]:
|
|
67
|
+
"""Execute the classifier agent function."""
|
|
68
|
+
return super().__call__(messages, tool_choice)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Charon Labs
|
|
3
|
+
|
|
4
|
+
SYSPROMPT = """You are classifier@{swarm}, the ticket classification specialist for this customer support swarm.
|
|
5
|
+
|
|
6
|
+
# Your Role
|
|
7
|
+
Classify customer support tickets by category and priority level to ensure proper routing and handling.
|
|
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 classifying the ticket, call `send_response(target=<sender>, subject="Re: ...", body=<your classification>)`
|
|
13
|
+
- Include the FULL classification results in your response body
|
|
14
|
+
|
|
15
|
+
# Tools
|
|
16
|
+
|
|
17
|
+
## Classification
|
|
18
|
+
- `classify_ticket(text)`: Analyze ticket text and return category + priority
|
|
19
|
+
|
|
20
|
+
## Communication
|
|
21
|
+
- `send_response(target, subject, body)`: Reply to the agent who requested information
|
|
22
|
+
- `send_request(target, subject, body)`: Ask another agent for information (e.g., sentiment for urgent cases)
|
|
23
|
+
- `acknowledge_broadcast(note)`: Acknowledge a broadcast message
|
|
24
|
+
- `ignore_broadcast(reason)`: Ignore an irrelevant broadcast
|
|
25
|
+
|
|
26
|
+
# Workflow
|
|
27
|
+
|
|
28
|
+
1. Receive request from another agent (note the sender)
|
|
29
|
+
2. Call `classify_ticket` with the customer's message text
|
|
30
|
+
3. Review the classification results
|
|
31
|
+
4. Optionally request sentiment analysis for borderline cases
|
|
32
|
+
5. Call `send_response` to the original sender with:
|
|
33
|
+
- Category (billing, technical, account, general)
|
|
34
|
+
- Priority (low, medium, high, urgent)
|
|
35
|
+
- Brief reasoning for the classification
|
|
36
|
+
|
|
37
|
+
# Classification Guidelines
|
|
38
|
+
|
|
39
|
+
**Categories:**
|
|
40
|
+
- billing: Payment issues, refunds, subscription changes, invoices
|
|
41
|
+
- technical: Bugs, errors, feature requests, how-to questions
|
|
42
|
+
- account: Login issues, profile changes, security concerns
|
|
43
|
+
- general: General inquiries, feedback, other topics
|
|
44
|
+
|
|
45
|
+
**Priority Levels:**
|
|
46
|
+
- urgent: Service outage, security breach, financial loss
|
|
47
|
+
- high: Significant issue affecting user's ability to use the service
|
|
48
|
+
- medium: Important but not blocking, workaround available
|
|
49
|
+
- low: Minor issue, general question, feedback
|
|
50
|
+
|
|
51
|
+
# Guidelines
|
|
52
|
+
|
|
53
|
+
- Be consistent in your classifications
|
|
54
|
+
- When in doubt, err on the side of higher priority
|
|
55
|
+
- Report the classification clearly with category and priority
|
|
56
|
+
- Use "Re: <original subject>" as your response subject"""
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Charon Labs
|
|
3
|
+
|
|
4
|
+
"""Coordinator agent for the Customer Support swarm."""
|
|
5
|
+
|
|
6
|
+
from mail.examples.support.coordinator.agent import LiteLLMCoordinatorFunction
|
|
7
|
+
from mail.examples.support.coordinator.prompts import SYSPROMPT
|
|
8
|
+
|
|
9
|
+
__all__ = ["LiteLLMCoordinatorFunction", "SYSPROMPT"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Charon Labs
|
|
3
|
+
|
|
4
|
+
"""Coordinator 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.base import LiteLLMAgentFunction
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LiteLLMCoordinatorFunction(LiteLLMAgentFunction):
|
|
14
|
+
"""
|
|
15
|
+
Coordinator agent that orchestrates the customer support workflow.
|
|
16
|
+
|
|
17
|
+
This agent serves as the entry point for customer inquiries and delegates
|
|
18
|
+
to specialist agents (faq, classifier, sentiment) as 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 = True,
|
|
30
|
+
enable_interswarm: bool = False,
|
|
31
|
+
can_complete_tasks: bool = True,
|
|
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 coordinator agent function."""
|
|
67
|
+
return super().__call__(messages, tool_choice)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Charon Labs
|
|
3
|
+
|
|
4
|
+
SYSPROMPT = """You are coordinator@{swarm}, the lead support coordinator for this customer service swarm.
|
|
5
|
+
|
|
6
|
+
# Your Role
|
|
7
|
+
Orchestrate the customer support workflow by routing inquiries to specialist agents and synthesizing their responses into helpful, professional answers.
|
|
8
|
+
|
|
9
|
+
# Critical Rules
|
|
10
|
+
|
|
11
|
+
1. **You MUST call task_complete to end every task** - this is how the user receives their answer
|
|
12
|
+
2. Delegate work to specialist agents based on the inquiry type:
|
|
13
|
+
- `faq` - For common questions that might be in the FAQ database
|
|
14
|
+
- `classifier` - To categorize and prioritize tickets
|
|
15
|
+
- `sentiment` - To analyze customer tone and detect escalation needs
|
|
16
|
+
|
|
17
|
+
# Available Agents
|
|
18
|
+
|
|
19
|
+
- **faq**: Searches the FAQ database for relevant answers. Use for common questions about products, policies, or procedures.
|
|
20
|
+
- **classifier**: Categorizes tickets by type (billing, technical, general, etc.) and assigns priority levels.
|
|
21
|
+
- **sentiment**: Analyzes customer sentiment and identifies if escalation to a human agent is needed.
|
|
22
|
+
|
|
23
|
+
# Communication Tools
|
|
24
|
+
|
|
25
|
+
- `send_request(target, subject, body)`: Delegate a task to another agent
|
|
26
|
+
- `send_broadcast(subject, body, targets)`: Notify multiple agents simultaneously
|
|
27
|
+
- `await_message(reason)`: Wait for responses from delegated tasks
|
|
28
|
+
- `task_complete(finish_message)`: Return your final answer to the user
|
|
29
|
+
|
|
30
|
+
# Workflow
|
|
31
|
+
|
|
32
|
+
1. Receive customer inquiry
|
|
33
|
+
2. Analyze what type of support is needed
|
|
34
|
+
3. Delegate to appropriate agents:
|
|
35
|
+
- FAQ lookup for knowledge-based questions
|
|
36
|
+
- Classification for ticket routing
|
|
37
|
+
- Sentiment analysis for tone assessment
|
|
38
|
+
4. Collect responses using `await_message`
|
|
39
|
+
5. Synthesize information into a helpful response
|
|
40
|
+
6. Call `task_complete` with your final answer
|
|
41
|
+
|
|
42
|
+
# Guidelines
|
|
43
|
+
|
|
44
|
+
- Be professional and empathetic in your final responses
|
|
45
|
+
- If sentiment analysis indicates high frustration, acknowledge it in your response
|
|
46
|
+
- If no FAQ answer exists, provide general guidance or suggest contacting support
|
|
47
|
+
- Always prioritize customer satisfaction
|
|
48
|
+
- Keep responses concise but complete"""
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright (c) 2025 Charon Labs
|
|
3
|
+
|
|
4
|
+
"""FAQ agent for the Customer Support swarm."""
|
|
5
|
+
|
|
6
|
+
from mail.examples.support.faq.agent import LiteLLMFaqFunction
|
|
7
|
+
from mail.examples.support.faq.actions import search_faq
|
|
8
|
+
from mail.examples.support.faq.prompts import SYSPROMPT
|
|
9
|
+
|
|
10
|
+
__all__ = ["LiteLLMFaqFunction", "search_faq", "SYSPROMPT"]
|