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,350 @@
|
|
|
1
|
+
# Game flow
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
assign roles to agents (narrator gets special omniscient role)
|
|
5
|
+
loop till end:
|
|
6
|
+
night:
|
|
7
|
+
doctor selects one agent to protect
|
|
8
|
+
detective selects one agent to investigate
|
|
9
|
+
mafia votes on target (revote if tied)
|
|
10
|
+
resolve night actions (deaths, protections, investigation results)
|
|
11
|
+
day:
|
|
12
|
+
narrator narrates night deaths (creative storytelling)
|
|
13
|
+
discussion:
|
|
14
|
+
narrator selects speaker
|
|
15
|
+
agent speaks
|
|
16
|
+
repeat until narrator moves to town hall
|
|
17
|
+
town hall:
|
|
18
|
+
nomination phase:
|
|
19
|
+
each agent may nominate one other agent
|
|
20
|
+
for each nomination:
|
|
21
|
+
all agents vote to second (yes/no)
|
|
22
|
+
if seconded, add to nominees list
|
|
23
|
+
if nominees list empty or max 3 reached, end phase
|
|
24
|
+
defense phase (skip if < 2 nominees):
|
|
25
|
+
narrator introduces each nominee
|
|
26
|
+
each nominee gives defense speech
|
|
27
|
+
trial phase (skip if no nominees):
|
|
28
|
+
all agents vote for one nominee to send to gallows
|
|
29
|
+
highest vote goes to gallows (revote if tied)
|
|
30
|
+
gallows phase:
|
|
31
|
+
narrator narrates walk to gallows
|
|
32
|
+
condemned agent gives final speech
|
|
33
|
+
all agents vote to execute or spare (majority decides)
|
|
34
|
+
if executed, narrator reveals role and narrates death
|
|
35
|
+
check win conditions
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
# Agent turn structure - Mafia game
|
|
39
|
+
|
|
40
|
+
Each agent receives:
|
|
41
|
+
1. Game state context (what they can see)
|
|
42
|
+
2. Action prompt (what they need to do)
|
|
43
|
+
3. Response via send_message_to_user()
|
|
44
|
+
|
|
45
|
+
=== GAME START ===
|
|
46
|
+
|
|
47
|
+
## Narrator Introduction Turn
|
|
48
|
+
Turn: Narrator
|
|
49
|
+
Context:
|
|
50
|
+
- "You are the narrator for this Mafia game"
|
|
51
|
+
- "Players: [list of N players]"
|
|
52
|
+
- "Role assignments: [complete mapping]"
|
|
53
|
+
Prompt: "Welcome the players to the game. Set the scene for the story."
|
|
54
|
+
Response: Narrator creates opening (e.g., "Welcome to the cursed town of Ravensbrook...")
|
|
55
|
+
Broadcast: All agents hear opening narration
|
|
56
|
+
|
|
57
|
+
## Agent Role Assignment Turns
|
|
58
|
+
Turn: All agents (parallel or sequential)
|
|
59
|
+
Context:
|
|
60
|
+
- "You are playing Mafia with N players: [names]"
|
|
61
|
+
- "Your role: [Detective/Doctor/Villager/Mafia/Jester]"
|
|
62
|
+
- "[Role description and win condition]"
|
|
63
|
+
- "Narrator's introduction: [opening narration]"
|
|
64
|
+
- "Alive players: [list]"
|
|
65
|
+
Prompt: "Acknowledge you understand your role."
|
|
66
|
+
Response: Agent sends acknowledgment
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
=== NIGHT PHASE ===
|
|
70
|
+
|
|
71
|
+
## Doctor Turn (if alive)
|
|
72
|
+
Context:
|
|
73
|
+
- "Night X has begun"
|
|
74
|
+
- "Alive players: [list]"
|
|
75
|
+
- "You protected [name] last night" (if not first night)
|
|
76
|
+
Prompt: "Choose one player to protect tonight. They will be saved from death if targeted."
|
|
77
|
+
Response: "I protect [player_name]"
|
|
78
|
+
Validation: Must choose exactly one living player (not themselves)
|
|
79
|
+
|
|
80
|
+
## Detective Turn (if alive)
|
|
81
|
+
Context:
|
|
82
|
+
- "Night X has begun"
|
|
83
|
+
- "Alive players: [list]"
|
|
84
|
+
- "Investigation history: [player -> role, ...]" (your past investigations)
|
|
85
|
+
Prompt: "Choose one player to investigate. You will learn their true role."
|
|
86
|
+
Response: "I investigate [player_name]"
|
|
87
|
+
System reveals: "[Player_name] is a [role]"
|
|
88
|
+
Validation: Must choose exactly one living player (not themselves)
|
|
89
|
+
|
|
90
|
+
## Mafia Turns (all mafia, sequential or parallel)
|
|
91
|
+
Context:
|
|
92
|
+
- "Night X has begun"
|
|
93
|
+
- "Alive players: [list]"
|
|
94
|
+
- "Mafia members: [list of mafia names]"
|
|
95
|
+
- "Previous mafia votes: [if revote needed]"
|
|
96
|
+
Prompt: "Vote for one player to kill tonight. The player with most mafia votes dies."
|
|
97
|
+
Response: "I vote to kill [player_name]"
|
|
98
|
+
Validation: Must choose exactly one living non-mafia player
|
|
99
|
+
Note: If tie, repeat mafia turns for revote
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
=== DAY PHASE ===
|
|
103
|
+
|
|
104
|
+
## Death Narration Turn (Narrator)
|
|
105
|
+
Turn: Narrator
|
|
106
|
+
Context:
|
|
107
|
+
- "Day X has begun"
|
|
108
|
+
- "Deaths: [player_name(s)] died" OR "No deaths occurred"
|
|
109
|
+
- "How they died: [mafia kill/execution/protected]"
|
|
110
|
+
- "Alive players: [list]"
|
|
111
|
+
Prompt: "Narrate the deaths that occurred last night. Be creative and atmospheric."
|
|
112
|
+
Response: Narrator crafts story (e.g., "The town awoke to find John's body in the square, a grim reminder...")
|
|
113
|
+
Broadcast: All agents receive the narration
|
|
114
|
+
|
|
115
|
+
## Discussion Phase (Narrator-moderated)
|
|
116
|
+
|
|
117
|
+
### Narrator Selection Turn (loop)
|
|
118
|
+
Turn: Narrator
|
|
119
|
+
Context:
|
|
120
|
+
- "Discussion phase - Day X"
|
|
121
|
+
- "Alive players: [list]"
|
|
122
|
+
- "Players who have spoken: [list]"
|
|
123
|
+
- "Recent discussion: [last few exchanges]"
|
|
124
|
+
Prompt: "Choose the next speaker, or type 'town_hall' to proceed to voting."
|
|
125
|
+
Response: "[player_name]" OR "town_hall"
|
|
126
|
+
Validation: Must be alive player or "town_hall"
|
|
127
|
+
|
|
128
|
+
### Agent Discussion Turn
|
|
129
|
+
Turn: Selected agent
|
|
130
|
+
Context:
|
|
131
|
+
- "You have been selected to speak by the narrator"
|
|
132
|
+
- "Alive players: [list]"
|
|
133
|
+
- "Previous discussion: [summary]"
|
|
134
|
+
Prompt: "Share your thoughts, suspicions, or information with the town."
|
|
135
|
+
Response: Agent speaks freely
|
|
136
|
+
Validation: None (can say anything)
|
|
137
|
+
Broadcast: All agents hear this speech
|
|
138
|
+
|
|
139
|
+
Note: Loop between narrator selection and agent speech until narrator calls "town_hall"
|
|
140
|
+
|
|
141
|
+
## Town Hall - Nomination Phase
|
|
142
|
+
|
|
143
|
+
### Nomination Turns (each agent, in order, can nominate once)
|
|
144
|
+
Context:
|
|
145
|
+
- "Town Hall - Nomination Phase"
|
|
146
|
+
- "Current nominees: [list]"
|
|
147
|
+
- "Nominations remaining: [3 - current count]"
|
|
148
|
+
- "Alive players: [list]"
|
|
149
|
+
Prompt: "Nominate one player for execution, or pass."
|
|
150
|
+
Response: "I nominate [player_name]" OR "I pass"
|
|
151
|
+
Validation: Must be alive player, can't nominate same person twice
|
|
152
|
+
|
|
153
|
+
### Seconding Turns (after each nomination, all other agents vote)
|
|
154
|
+
Context:
|
|
155
|
+
- "[Nominator] has nominated [nominee]"
|
|
156
|
+
- "Alive players: [list]"
|
|
157
|
+
Prompt: "Do you second this nomination? (yes/no)"
|
|
158
|
+
Response: "yes" OR "no"
|
|
159
|
+
Validation: Must be yes or no
|
|
160
|
+
System: If majority votes yes, add to nominees list
|
|
161
|
+
Note: Nomination phase ends when 3 nominees OR no more nominations
|
|
162
|
+
|
|
163
|
+
## Town Hall - Defense Phase (if >= 2 nominees)
|
|
164
|
+
|
|
165
|
+
### Narrator Introduction Turn (for each nominee)
|
|
166
|
+
Turn: Narrator
|
|
167
|
+
Context:
|
|
168
|
+
- "Defense Phase"
|
|
169
|
+
- "Current nominee: [name]"
|
|
170
|
+
- "Nominees: [list]"
|
|
171
|
+
Prompt: "Introduce [nominee] to the stand. Set the scene for their defense."
|
|
172
|
+
Response: Narrator sets atmosphere (e.g., "The accused steps forward, all eyes upon them...")
|
|
173
|
+
Broadcast: All agents hear introduction
|
|
174
|
+
|
|
175
|
+
### Defense Turn (nominee speaks)
|
|
176
|
+
Turn: Nominee
|
|
177
|
+
Context:
|
|
178
|
+
- "You are nominated for execution"
|
|
179
|
+
- "Nominees: [list]"
|
|
180
|
+
- "Narrator introduction: [text]"
|
|
181
|
+
Prompt: "Give your defense. Why should the town spare you?"
|
|
182
|
+
Response: Agent defends themselves
|
|
183
|
+
Validation: None
|
|
184
|
+
Broadcast: All agents hear defense
|
|
185
|
+
|
|
186
|
+
## Town Hall - Trial Phase (if >= 1 nominee)
|
|
187
|
+
|
|
188
|
+
### Trial Vote Turns (all agents vote)
|
|
189
|
+
Context:
|
|
190
|
+
- "Trial Phase"
|
|
191
|
+
- "Nominees: [list with their defenses]"
|
|
192
|
+
- "Alive players: [list]"
|
|
193
|
+
Prompt: "Vote for which nominee should go to the gallows."
|
|
194
|
+
Response: "I vote for [nominee_name]"
|
|
195
|
+
Validation: Must vote for one of the nominees
|
|
196
|
+
System: Tally votes, most votes goes to gallows (revote if tie)
|
|
197
|
+
|
|
198
|
+
## Town Hall - Gallows Phase
|
|
199
|
+
|
|
200
|
+
### Narrator Gallows Narration Turn
|
|
201
|
+
Turn: Narrator
|
|
202
|
+
Context:
|
|
203
|
+
- "[Condemned] has been chosen for the gallows"
|
|
204
|
+
- "Vote result: [vote breakdown]"
|
|
205
|
+
Prompt: "Narrate the walk to the gallows. Set a dramatic scene."
|
|
206
|
+
Response: Narrator creates atmosphere (e.g., "The crowd parts as [name] is led to the gallows...")
|
|
207
|
+
Broadcast: All agents hear narration
|
|
208
|
+
|
|
209
|
+
### Final Speech Turn (condemned agent)
|
|
210
|
+
Turn: Condemned agent
|
|
211
|
+
Context:
|
|
212
|
+
- "You have been sent to the gallows"
|
|
213
|
+
- "Alive players: [list]"
|
|
214
|
+
- "Narrator's scene: [text]"
|
|
215
|
+
Prompt: "Give your final speech before the execution vote."
|
|
216
|
+
Response: Agent's last words
|
|
217
|
+
Validation: None
|
|
218
|
+
Broadcast: All agents hear final words
|
|
219
|
+
|
|
220
|
+
### Execution Vote Turns (all agents except condemned)
|
|
221
|
+
Turn: Each agent
|
|
222
|
+
Context:
|
|
223
|
+
- "Execution Vote"
|
|
224
|
+
- "[Condemned]'s final speech: [text]"
|
|
225
|
+
- "Alive players: [list]"
|
|
226
|
+
Prompt: "Vote to execute or spare [condemned]? (execute/spare)"
|
|
227
|
+
Response: "execute" OR "spare"
|
|
228
|
+
Validation: Must be execute or spare
|
|
229
|
+
System: Tally votes, majority decides
|
|
230
|
+
|
|
231
|
+
### Death Reveal and Narration (if executed)
|
|
232
|
+
Turn: Narrator
|
|
233
|
+
Context:
|
|
234
|
+
- "[Condemned] was executed"
|
|
235
|
+
- "Their role: [role]"
|
|
236
|
+
- "Vote breakdown: [counts]"
|
|
237
|
+
Prompt: "Narrate the execution and role reveal. Be dramatic and vivid."
|
|
238
|
+
Response: Narrator crafts death scene (e.g., "As [name] takes their last breath, the truth emerges - they were a [role]...")
|
|
239
|
+
Broadcast: All agents hear narration and learn role
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
=== WIN CONDITION CHECK ===
|
|
243
|
+
After each day phase, check:
|
|
244
|
+
- If all mafia dead: Town wins
|
|
245
|
+
- If mafia >= non-mafia: Mafia wins
|
|
246
|
+
- If Jester was executed during town hall: Jester wins
|
|
247
|
+
Then loop to next night or end game
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
=== TECHNICAL NOTES ===
|
|
251
|
+
|
|
252
|
+
Message Flow:
|
|
253
|
+
- Each "turn" is triggered by send_message_to_user()
|
|
254
|
+
- Agent responds with text
|
|
255
|
+
- Game logic parses response for structured actions
|
|
256
|
+
- System validates and applies action
|
|
257
|
+
- Next turn begins
|
|
258
|
+
|
|
259
|
+
State Management:
|
|
260
|
+
- Game maintains: alive_players, dead_players, roles, vote_history
|
|
261
|
+
- Each agent's context is role-specific (mafia know each other, detective knows investigations)
|
|
262
|
+
- Narrator sees everything (omniscient view)
|
|
263
|
+
- Past turn history available for context
|
|
264
|
+
|
|
265
|
+
Error Handling:
|
|
266
|
+
- Invalid responses trigger reprompt with error message
|
|
267
|
+
- Timeout fallback: random valid action or pass
|
|
268
|
+
- Dead players cannot take actions (filter them from turns)
|
|
269
|
+
|
|
270
|
+
NARRATOR PERSPECTIVE - OMNISCIENT VIEW
|
|
271
|
+
|
|
272
|
+
The narrator is a special agent with full game knowledge and creative freedom.
|
|
273
|
+
They serve as storyteller, moderator, and atmosphere creator.
|
|
274
|
+
|
|
275
|
+
=== NARRATOR'S ROLE ===
|
|
276
|
+
|
|
277
|
+
Responsibilities:
|
|
278
|
+
1. Narrate all deaths with creative storytelling
|
|
279
|
+
2. Moderate discussion phase (choose speaking order)
|
|
280
|
+
3. Introduce nominees during defense phase
|
|
281
|
+
4. Set dramatic scenes for gallows phase
|
|
282
|
+
5. Reveal roles through narrative (not dry announcements)
|
|
283
|
+
|
|
284
|
+
Narrator Context (sees everything):
|
|
285
|
+
- All agent roles (complete role assignments)
|
|
286
|
+
- Night action results (who was targeted, protected, investigated)
|
|
287
|
+
- Vote tallies and motivations
|
|
288
|
+
- Complete conversation history
|
|
289
|
+
- Win condition status
|
|
290
|
+
- Agent strategies and deception attempts
|
|
291
|
+
|
|
292
|
+
Narrator Constraints:
|
|
293
|
+
- MUST NOT reveal hidden information to agents
|
|
294
|
+
- Can hint and create atmosphere but not spoil
|
|
295
|
+
- Narrations are broadcast to all agents
|
|
296
|
+
- Should maintain dramatic tension
|
|
297
|
+
- Balance creative freedom with game integrity
|
|
298
|
+
|
|
299
|
+
=== NARRATOR TURN EXAMPLES ===
|
|
300
|
+
|
|
301
|
+
## Death Narration Example
|
|
302
|
+
Context (narrator sees):
|
|
303
|
+
- John (Villager) was killed by mafia
|
|
304
|
+
- Sarah (Doctor) protected herself
|
|
305
|
+
- Tom (Detective) investigated Alice (found Mafia)
|
|
306
|
+
Narration (what narrator says to all):
|
|
307
|
+
"Dawn breaks over the sleepy town. A scream pierces the morning air—John's lifeless
|
|
308
|
+
body lies in the town square, eyes wide with terror. The townspeople gather in horror,
|
|
309
|
+
knowing the mafia struck again in the darkness. Who among you can be trusted?"
|
|
310
|
+
Note: Narrator doesn't reveal protection or investigation results
|
|
311
|
+
|
|
312
|
+
## Discussion Moderator Example
|
|
313
|
+
Context (narrator sees):
|
|
314
|
+
- Alive: Sarah, Tom, Alice, Bob, Carol
|
|
315
|
+
- Tom knows Alice is Mafia (from investigation)
|
|
316
|
+
- Alice is trying to deflect suspicion
|
|
317
|
+
Choice process (narrator's decision):
|
|
318
|
+
"Tom seems eager to speak after last night's events. Tom, you have the floor."
|
|
319
|
+
OR "Alice has been quiet. Alice, what are your thoughts?"
|
|
320
|
+
Strategy: Narrator can create drama by choosing order thoughtfully
|
|
321
|
+
|
|
322
|
+
## Gallows Narration Example
|
|
323
|
+
Context (narrator sees):
|
|
324
|
+
- Alice (Mafia) was executed 4-3 vote
|
|
325
|
+
Narration (what narrator says):
|
|
326
|
+
"The rope tightens. Alice's eyes dart across the crowd one last time. As her body
|
|
327
|
+
goes limp, a crumpled note falls from her pocket—a list of names, targets for the
|
|
328
|
+
mafia's dark work. She was one of THEM. The mafia's grip on this town weakens, but
|
|
329
|
+
victory is not yet assured."
|
|
330
|
+
|
|
331
|
+
=== NARRATOR GUIDELINES ===
|
|
332
|
+
|
|
333
|
+
Tone & Style:
|
|
334
|
+
- Gothic/mysterious atmosphere
|
|
335
|
+
- Vivid but not gratuitously violent
|
|
336
|
+
- Build tension and suspense
|
|
337
|
+
- Reward good storytelling from agents in narrations
|
|
338
|
+
- React to dramatic moments
|
|
339
|
+
|
|
340
|
+
Pacing:
|
|
341
|
+
- Don't rush through deaths
|
|
342
|
+
- Give each major moment weight
|
|
343
|
+
- Use narration to transition between phases
|
|
344
|
+
- Create breathing room between intense votes
|
|
345
|
+
|
|
346
|
+
Fairness:
|
|
347
|
+
- Don't favor any faction through narration
|
|
348
|
+
- Give equal dramatic treatment to all deaths
|
|
349
|
+
- Don't inadvertently hint at roles through word choice
|
|
350
|
+
- Maintain objectivity while being creative
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .actions import (
|
|
2
|
+
calculate_expression,
|
|
3
|
+
)
|
|
4
|
+
from .agent import (
|
|
5
|
+
LiteLLMMathFunction,
|
|
6
|
+
factory_math_dummy,
|
|
7
|
+
math_agent_params,
|
|
8
|
+
)
|
|
9
|
+
from .prompts import (
|
|
10
|
+
SYSPROMPT as MATH_SYSPROMPT,
|
|
11
|
+
)
|
|
12
|
+
from .types import (
|
|
13
|
+
action_calculate_expression,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"action_calculate_expression",
|
|
18
|
+
"calculate_expression",
|
|
19
|
+
"factory_math_dummy",
|
|
20
|
+
"LiteLLMMathFunction",
|
|
21
|
+
"MATH_SYSPROMPT",
|
|
22
|
+
"math_agent_params",
|
|
23
|
+
]
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
"""Utility actions exposed by the math example agent."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ast
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
from decimal import ROUND_HALF_EVEN, Decimal, InvalidOperation, localcontext
|
|
11
|
+
from typing import Any, Final, Union
|
|
12
|
+
|
|
13
|
+
from mail import action
|
|
14
|
+
|
|
15
|
+
Number = Union[int, Decimal]
|
|
16
|
+
|
|
17
|
+
# Extended-precision constants to keep deterministic values for math literals.
|
|
18
|
+
_CONSTANTS: Final[dict[str, Decimal]] = {
|
|
19
|
+
"pi": Decimal("3.14159265358979323846264338327950288419716939937510"),
|
|
20
|
+
"tau": Decimal("6.28318530717958647692528676655900576839433879875021"),
|
|
21
|
+
"e": Decimal("2.71828182845904523536028747135266249775724709369996"),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
_MIN_PRECISION: Final[int] = 64
|
|
25
|
+
_MAX_PRECISION: Final[int] = 4096
|
|
26
|
+
|
|
27
|
+
CALCULATE_EXPRESSION_PARAMETERS: Final[dict[str, Any]] = {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"properties": {
|
|
30
|
+
"expression": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "The arithmetic expression to evaluate.",
|
|
33
|
+
},
|
|
34
|
+
"precision": {
|
|
35
|
+
"type": "integer",
|
|
36
|
+
"minimum": 0,
|
|
37
|
+
"maximum": 12,
|
|
38
|
+
"description": (
|
|
39
|
+
"Optional number of decimal places for the formatted result."
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
"required": ["expression"],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _CalculatorError(ValueError):
|
|
48
|
+
"""Raised when the calculator cannot safely evaluate an expression."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _estimate_precision(expr: str) -> int:
|
|
52
|
+
digit_groups = re.findall(r"\d+", expr)
|
|
53
|
+
if not digit_groups:
|
|
54
|
+
return _MIN_PRECISION
|
|
55
|
+
total_digits = sum(len(group) for group in digit_groups)
|
|
56
|
+
return max(_MIN_PRECISION, min(_MAX_PRECISION, total_digits * 2))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _to_decimal(value: Number) -> Decimal:
|
|
60
|
+
if isinstance(value, Decimal):
|
|
61
|
+
return value
|
|
62
|
+
return Decimal(value)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _format_decimal(value: Decimal, precision: int | None) -> str:
|
|
66
|
+
dec = value
|
|
67
|
+
if precision is not None:
|
|
68
|
+
quant = Decimal(1).scaleb(-precision)
|
|
69
|
+
dec = dec.quantize(quant, rounding=ROUND_HALF_EVEN)
|
|
70
|
+
formatted = format(dec, "f")
|
|
71
|
+
return formatted
|
|
72
|
+
|
|
73
|
+
formatted = format(dec, "f")
|
|
74
|
+
if "." in formatted:
|
|
75
|
+
formatted = formatted.rstrip("0").rstrip(".")
|
|
76
|
+
if not formatted:
|
|
77
|
+
formatted = "0"
|
|
78
|
+
return formatted
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _format_result(value: Number, precision: int | None) -> tuple[str, str, bool]:
|
|
82
|
+
if isinstance(value, int):
|
|
83
|
+
dec_value = Decimal(value)
|
|
84
|
+
formatted = _format_decimal(dec_value, precision)
|
|
85
|
+
exact = str(value)
|
|
86
|
+
return exact, formatted, True
|
|
87
|
+
|
|
88
|
+
exact = _format_decimal(value, None)
|
|
89
|
+
formatted = _format_decimal(value, precision)
|
|
90
|
+
is_integer = value == value.to_integral_value()
|
|
91
|
+
return exact, formatted, is_integer
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _apply_unary(op: ast.unaryop, operand: Number) -> Number:
|
|
95
|
+
if isinstance(op, ast.UAdd):
|
|
96
|
+
return operand
|
|
97
|
+
if isinstance(op, ast.USub):
|
|
98
|
+
return -operand
|
|
99
|
+
raise _CalculatorError("Unsupported unary operator")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _apply_binop(op: ast.operator, left: Number, right: Number) -> Number:
|
|
103
|
+
if isinstance(left, int) and isinstance(right, int):
|
|
104
|
+
if isinstance(op, ast.Add):
|
|
105
|
+
return left + right
|
|
106
|
+
if isinstance(op, ast.Sub):
|
|
107
|
+
return left - right
|
|
108
|
+
if isinstance(op, ast.Mult):
|
|
109
|
+
return left * right
|
|
110
|
+
if isinstance(op, ast.Pow):
|
|
111
|
+
if right < 0:
|
|
112
|
+
left_dec = _to_decimal(left)
|
|
113
|
+
return left_dec**right
|
|
114
|
+
return left**right
|
|
115
|
+
if isinstance(op, ast.FloorDiv):
|
|
116
|
+
if right == 0:
|
|
117
|
+
raise ZeroDivisionError
|
|
118
|
+
return left // right
|
|
119
|
+
if isinstance(op, ast.Mod):
|
|
120
|
+
if right == 0:
|
|
121
|
+
raise ZeroDivisionError
|
|
122
|
+
return left % right
|
|
123
|
+
if isinstance(op, ast.Div):
|
|
124
|
+
left_dec = _to_decimal(left)
|
|
125
|
+
right_dec = _to_decimal(right)
|
|
126
|
+
return left_dec / right_dec
|
|
127
|
+
raise _CalculatorError("Unsupported operator")
|
|
128
|
+
|
|
129
|
+
left_dec = _to_decimal(left)
|
|
130
|
+
right_dec = _to_decimal(right)
|
|
131
|
+
|
|
132
|
+
if isinstance(op, ast.Add):
|
|
133
|
+
return left_dec + right_dec
|
|
134
|
+
if isinstance(op, ast.Sub):
|
|
135
|
+
return left_dec - right_dec
|
|
136
|
+
if isinstance(op, ast.Mult):
|
|
137
|
+
return left_dec * right_dec
|
|
138
|
+
if isinstance(op, ast.Div):
|
|
139
|
+
return left_dec / right_dec
|
|
140
|
+
if isinstance(op, ast.FloorDiv):
|
|
141
|
+
if right_dec == 0:
|
|
142
|
+
raise ZeroDivisionError
|
|
143
|
+
result = left_dec // right_dec
|
|
144
|
+
if result == result.to_integral_value():
|
|
145
|
+
return int(result)
|
|
146
|
+
return result
|
|
147
|
+
if isinstance(op, ast.Mod):
|
|
148
|
+
if right_dec == 0:
|
|
149
|
+
raise ZeroDivisionError
|
|
150
|
+
return left_dec % right_dec
|
|
151
|
+
if isinstance(op, ast.Pow):
|
|
152
|
+
if isinstance(right, int):
|
|
153
|
+
return left_dec**right
|
|
154
|
+
raise _CalculatorError("Exponent must be an integer")
|
|
155
|
+
raise _CalculatorError("Unsupported operator")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _eval_node(node: ast.AST, source: str) -> Number:
|
|
159
|
+
if isinstance(node, ast.Expression):
|
|
160
|
+
return _eval_node(node.body, source)
|
|
161
|
+
|
|
162
|
+
if isinstance(node, ast.Constant):
|
|
163
|
+
value = node.value
|
|
164
|
+
if isinstance(value, bool) or value is None:
|
|
165
|
+
raise _CalculatorError("Unsupported literal")
|
|
166
|
+
if isinstance(value, int):
|
|
167
|
+
return value
|
|
168
|
+
if isinstance(value, float):
|
|
169
|
+
# Reconstruct via source to avoid binary float artifacts if available.
|
|
170
|
+
segment = ast.get_source_segment(source, node)
|
|
171
|
+
if segment is not None:
|
|
172
|
+
return Decimal(segment.replace("_", ""))
|
|
173
|
+
return Decimal(str(value))
|
|
174
|
+
raise _CalculatorError("Only numeric literals are supported")
|
|
175
|
+
|
|
176
|
+
if isinstance(node, ast.Name):
|
|
177
|
+
if node.id in _CONSTANTS:
|
|
178
|
+
return _CONSTANTS[node.id]
|
|
179
|
+
raise _CalculatorError(f"Unknown constant '{node.id}'")
|
|
180
|
+
|
|
181
|
+
if isinstance(node, ast.BinOp):
|
|
182
|
+
left = _eval_node(node.left, source)
|
|
183
|
+
right = _eval_node(node.right, source)
|
|
184
|
+
return _apply_binop(node.op, left, right)
|
|
185
|
+
|
|
186
|
+
if isinstance(node, ast.UnaryOp):
|
|
187
|
+
operand = _eval_node(node.operand, source)
|
|
188
|
+
return _apply_unary(node.op, operand)
|
|
189
|
+
|
|
190
|
+
raise _CalculatorError("Unsupported syntax in expression")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@action(
|
|
194
|
+
name="calculate_expression",
|
|
195
|
+
description=(
|
|
196
|
+
"Evaluate a basic arithmetic expression with +, -, *, /, %, //, **, and "
|
|
197
|
+
"parentheses."
|
|
198
|
+
),
|
|
199
|
+
parameters=CALCULATE_EXPRESSION_PARAMETERS,
|
|
200
|
+
)
|
|
201
|
+
async def calculate_expression(args: dict[str, Any]) -> str:
|
|
202
|
+
"""Evaluate a basic arithmetic expression and return a structured JSON payload.
|
|
203
|
+
|
|
204
|
+
Supported grammar:
|
|
205
|
+
- numeric literals (integers or decimals)
|
|
206
|
+
- parentheses
|
|
207
|
+
- operators: +, -, *, /, //, %, **
|
|
208
|
+
- unary + and -
|
|
209
|
+
- constants: e, pi, tau
|
|
210
|
+
|
|
211
|
+
When provided, ``precision`` (0-12) controls rounding in ``formatted_result``.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
expression = args.get("expression")
|
|
215
|
+
if not isinstance(expression, str) or not expression.strip():
|
|
216
|
+
return "Error: 'expression' must be a non-empty string"
|
|
217
|
+
|
|
218
|
+
precision = args.get("precision")
|
|
219
|
+
if precision is not None:
|
|
220
|
+
if not isinstance(precision, int) or not (0 <= precision <= 12):
|
|
221
|
+
return "Error: 'precision' must be an integer between 0 and 12"
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
parsed = ast.parse(expression, mode="eval")
|
|
225
|
+
except SyntaxError:
|
|
226
|
+
return "Error: invalid syntax"
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
with localcontext() as ctx:
|
|
230
|
+
ctx.prec = _estimate_precision(expression)
|
|
231
|
+
result: Number = _eval_node(parsed, expression)
|
|
232
|
+
except _CalculatorError as exc:
|
|
233
|
+
return f"Error: {exc}"
|
|
234
|
+
except ZeroDivisionError:
|
|
235
|
+
return "Error: division by zero"
|
|
236
|
+
except (InvalidOperation, OverflowError):
|
|
237
|
+
return "Error: unable to evaluate expression"
|
|
238
|
+
|
|
239
|
+
if isinstance(result, Decimal) and not result.is_finite():
|
|
240
|
+
return "Error: result is not a finite number"
|
|
241
|
+
|
|
242
|
+
exact_result, formatted_result, is_integer = _format_result(result, precision)
|
|
243
|
+
|
|
244
|
+
payload = {
|
|
245
|
+
"expression": expression,
|
|
246
|
+
"result": exact_result,
|
|
247
|
+
"formatted_result": formatted_result,
|
|
248
|
+
"precision": precision,
|
|
249
|
+
"is_integer": is_integer,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return json.dumps(payload)
|