@evolvingmachines/evolve 0.0.55-project-sable.20260916.db0bc0bb → 0.0.56
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.
- package/dist/cli/index.cjs +32 -32
- package/dist/cli/index.js +27 -27
- package/package.json +4 -6
- package/skills/evolve/SKILL.md +4 -3
- package/skills/evolve-evals/SKILL.md +112 -0
- package/{docs-evals → skills/evolve-evals/references}/cli-reference/run.mdx +1 -1
- package/{docs-evals → skills/evolve-evals/references}/cli-reference/skills.mdx +4 -4
- package/{docs-evals → skills/evolve-evals/references}/core-concepts/agents.mdx +2 -2
- package/{docs-evals → skills/evolve-evals/references}/core-concepts/analyze.mdx +1 -1
- package/{docs-evals → skills/evolve-evals/references}/core-concepts/jobs.mdx +3 -3
- package/{docs-evals → skills/evolve-evals/references}/core-concepts/models.mdx +1 -1
- package/{docs-evals → skills/evolve-evals/references}/core-concepts/sandboxes.mdx +2 -2
- package/{docs-evals → skills/evolve-evals/references}/core-concepts/secrets.mdx +1 -1
- package/{docs-evals → skills/evolve-evals/references}/core-concepts/trial-outputs.mdx +1 -1
- package/{docs-evals → skills/evolve-evals/references}/getting-started/quick-start.mdx +1 -1
- package/{docs-evals → skills/evolve-evals/references}/index.mdx +1 -1
- package/{docs-evals → skills/evolve-evals/references}/sdk/python.mdx +1 -1
- package/{docs-evals → skills/evolve-evals/references}/sdk/typescript.mdx +1 -1
- package/{docs-evals → skills/evolve-evals/references}/sdk-reference/errors.mdx +2 -2
- package/{docs-evals → skills/evolve-evals/references}/sdk-reference/jobs.mdx +2 -2
- package/docs-agents/SKILL.md +0 -138
- package/docs-agents/index.md +0 -81
- package/docs-agents/python/01-getting-started.md +0 -511
- package/docs-agents/python/02-configuration.md +0 -855
- package/docs-agents/python/03-runtime.md +0 -899
- package/docs-agents/python/04-streaming.md +0 -451
- package/docs-agents/python/05-swarm-pipeline.md +0 -953
- package/docs-agents/python/index.md +0 -122
- package/docs-agents/typescript/01-getting-started.md +0 -494
- package/docs-agents/typescript/02-configuration.md +0 -841
- package/docs-agents/typescript/03-runtime.md +0 -864
- package/docs-agents/typescript/04-streaming.md +0 -478
- package/docs-agents/typescript/05-swarm-pipeline.md +0 -957
- package/docs-agents/typescript/index.md +0 -121
- package/docs-evals/SKILL.md +0 -112
- package/docs-evals/docs.json +0 -180
- /package/{docs-evals → skills/evolve-evals/references}/cli-reference/agent.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/cli-reference/analysis.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/cli-reference/analyze.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/cli-reference/auth.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/cli-reference/check.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/cli-reference/dataset.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/cli-reference/index.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/cli-reference/job.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/cli-reference/secrets.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/cli-reference/skill.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/cli-reference/trial.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/cli-reference/upload.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/core-concepts/check.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/core-concepts/datasets.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/core-concepts/index.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/core-concepts/skills.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/core-concepts/tasks.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/core-concepts/trials.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/core-concepts/upload.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/dashboard/checks.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/dashboard/datasets.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/dashboard/jobs.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/dashboard/traces.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/dashboard/trial-viewer.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/getting-started/installation.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/agents.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/analyses.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/auth.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/checks.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/datasets.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/index.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/meta.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/skills.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/trials.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/types.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/snippets/global-options.mdx +0 -0
- /package/{docs-evals → skills/evolve-evals/references}/snippets/list-flags.mdx +0 -0
|
@@ -1,953 +0,0 @@
|
|
|
1
|
-
# Swarm & Pipeline
|
|
2
|
-
|
|
3
|
-
Functional programming for AI agents: `map`, `filter`, `reduce`, `best_of`, `verify`.
|
|
4
|
-
|
|
5
|
-
```python
|
|
6
|
-
from evolve import Swarm, SwarmConfig, AgentConfig, IntegrationsSetup
|
|
7
|
-
from pydantic import BaseModel # Or use plain JSON Schema dicts instead
|
|
8
|
-
|
|
9
|
-
agent = AgentConfig(type='claude')
|
|
10
|
-
|
|
11
|
-
swarm = Swarm(SwarmConfig(
|
|
12
|
-
agent=agent, # Default agent for all operations
|
|
13
|
-
concurrency=4, # Max parallel sandboxes (default: 4)
|
|
14
|
-
timeout_ms=3_600_000, # Default timeout per worker (default: 1 hour)
|
|
15
|
-
tag='my-pipeline', # Tag prefix for observability
|
|
16
|
-
skills=['anthropics/skills'], # Default skills (skills.sh / git / local references)
|
|
17
|
-
integrations=IntegrationsSetup( # Default Integrations config for all workers
|
|
18
|
-
user_id='root',
|
|
19
|
-
apps=['gmail', 'notion'],
|
|
20
|
-
),
|
|
21
|
-
mcp_servers={...}, # Default MCP servers for all workers
|
|
22
|
-
retry=RetryConfig( # Default retry config for all operations
|
|
23
|
-
max_attempts=3,
|
|
24
|
-
backoff_ms=1000,
|
|
25
|
-
backoff_multiplier=2,
|
|
26
|
-
),
|
|
27
|
-
))
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
> **Defaults**: `agent`, `skills`, `integrations`, `mcp_servers`, `timeout_ms`, and `retry` set here are inherited by all operations (`map`, `filter`, `reduce`, `best_of`). Pass these options to individual operations to override.
|
|
31
|
-
|
|
32
|
-
**SwarmConfig** — configuration for Swarm instance:
|
|
33
|
-
```python
|
|
34
|
-
SwarmConfig(
|
|
35
|
-
agent=AgentConfig,
|
|
36
|
-
sandbox=SandboxProvider,
|
|
37
|
-
skills=list[str],
|
|
38
|
-
integrations=IntegrationsSetup,
|
|
39
|
-
mcp_servers=dict[str, McpServerConfig],
|
|
40
|
-
concurrency=int,
|
|
41
|
-
timeout_ms=int,
|
|
42
|
-
workspace_mode=str,
|
|
43
|
-
tag=str,
|
|
44
|
-
retry=RetryConfig,
|
|
45
|
-
)
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
| Option | Default | Notes |
|
|
49
|
-
|--------|---------|-------|
|
|
50
|
-
| `agent.type` | `'claude'` | Auto-resolved from env |
|
|
51
|
-
| `agent.model` | per type | `'opus'` (claude), `'gpt-5.6-sol'` (codex), etc. |
|
|
52
|
-
| `sandbox` | auto-resolved | Provider for every worker; falls back to env (`E2B_API_KEY`, `DAYTONA_API_KEY`, `MODAL_TOKEN_*`, `EVOLVE_API_KEY`) |
|
|
53
|
-
| `skills` | `None` | Set here or per-operation |
|
|
54
|
-
| `integrations` | `None` | Set here or per-operation |
|
|
55
|
-
| `mcp_servers` | `None` | Set here or per-operation |
|
|
56
|
-
| `concurrency` | `4` | Max parallel sandboxes |
|
|
57
|
-
| `timeout_ms` | `3_600_000` | 1 hour per worker |
|
|
58
|
-
| `workspace_mode` | `'knowledge'` | `'knowledge'` or `'swe'`; `'task'` is not a Swarm mode (the type does not stop you writing it — do not) |
|
|
59
|
-
| `tag` | `'swarm'` | Observability prefix |
|
|
60
|
-
| `retry` | `None` | Set here or per-operation |
|
|
61
|
-
|
|
62
|
-
The `agent` here is the full `AgentConfig` — the same shape `Evolve(config=...)` takes, so `api_key`, `provider_api_key`, `oauth_token` and `max_context_size` all belong on it. Individual operations override this default with the same `AgentConfig` class, not a narrower one: `map(..., agent=AgentConfig(...))`, and `BestOfConfig(task_agents=[...], judge_agent=...)` take exactly what you pass here. (The TypeScript SDK narrows the per-operation form to an `AgentOverride`; Python does not.)
|
|
63
|
-
|
|
64
|
-
**Minimal setup** — with `EVOLVE_API_KEY` set (see [Authentication](./01-getting-started.md#authentication)):
|
|
65
|
-
|
|
66
|
-
```python
|
|
67
|
-
from dotenv import load_dotenv
|
|
68
|
-
load_dotenv() # If using .env file
|
|
69
|
-
|
|
70
|
-
from evolve import Swarm
|
|
71
|
-
|
|
72
|
-
swarm = Swarm() # Auto-resolves agent (claude) and sandbox from env
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
**RetryConfig** — auto-retry on error with exponential backoff:
|
|
76
|
-
```python
|
|
77
|
-
RetryConfig(
|
|
78
|
-
max_attempts=3,
|
|
79
|
-
backoff_ms=1000,
|
|
80
|
-
backoff_multiplier=2,
|
|
81
|
-
retry_on=lambda r: r.status == 'error', # Custom condition
|
|
82
|
-
on_item_retry=lambda idx, attempt, error: ..., # Callback
|
|
83
|
-
)
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## Input Types
|
|
87
|
-
|
|
88
|
-
Swarm runs in **knowledge mode** by default—files are uploaded to `context/` in the sandbox.
|
|
89
|
-
|
|
90
|
-
**FileMap structure:**
|
|
91
|
-
|
|
92
|
-
```python
|
|
93
|
-
# FileMap: dict[path, content]
|
|
94
|
-
# - path: str → file path in context/ folder
|
|
95
|
-
# - content: str | bytes → file content
|
|
96
|
-
|
|
97
|
-
FileMap = dict[str, str | bytes]
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
**Case 1: One file per worker**
|
|
103
|
-
|
|
104
|
-
```python
|
|
105
|
-
# 3 workers, each gets 1 file
|
|
106
|
-
items: list[FileMap] = [
|
|
107
|
-
{'report.txt': 'Q1 revenue...'}, # → Worker 0: context/report.txt
|
|
108
|
-
{'report.txt': 'Q2 revenue...'}, # → Worker 1: context/report.txt
|
|
109
|
-
{'report.txt': 'Q3 revenue...'}, # → Worker 2: context/report.txt
|
|
110
|
-
]
|
|
111
|
-
|
|
112
|
-
results = await swarm.map(
|
|
113
|
-
items=items,
|
|
114
|
-
prompt='Summarize this report',
|
|
115
|
-
)
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
**Case 2: Multiple files per worker**
|
|
121
|
-
|
|
122
|
-
```python
|
|
123
|
-
# 3 workers, each gets 2 files
|
|
124
|
-
items: list[FileMap] = [
|
|
125
|
-
{ # → Worker 0:
|
|
126
|
-
'doc1.pdf': open('./doc1.pdf', 'rb').read(), # context/doc1.pdf
|
|
127
|
-
'doc2.pdf': open('./doc2.pdf', 'rb').read(), # context/doc2.pdf
|
|
128
|
-
},
|
|
129
|
-
{ # → Worker 1:
|
|
130
|
-
'doc3.pdf': open('./doc3.pdf', 'rb').read(), # context/doc3.pdf
|
|
131
|
-
'doc4.pdf': open('./doc4.pdf', 'rb').read(), # context/doc4.pdf
|
|
132
|
-
},
|
|
133
|
-
{ # → Worker 2:
|
|
134
|
-
'doc5.pdf': open('./doc5.pdf', 'rb').read(), # context/doc5.pdf
|
|
135
|
-
'doc6.pdf': open('./doc6.pdf', 'rb').read(), # context/doc6.pdf
|
|
136
|
-
},
|
|
137
|
-
]
|
|
138
|
-
|
|
139
|
-
results = await swarm.map(
|
|
140
|
-
items=items,
|
|
141
|
-
prompt='Compare these two documents',
|
|
142
|
-
)
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
**Case 3: Entire folder per worker**
|
|
148
|
-
|
|
149
|
-
```python
|
|
150
|
-
from evolve import read_local_dir
|
|
151
|
-
|
|
152
|
-
# read_local_dir(path, recursive) → returns FileMap with all files
|
|
153
|
-
items: list[FileMap] = [
|
|
154
|
-
read_local_dir('./project-a', recursive=True), # → Worker 0: all files from project-a
|
|
155
|
-
read_local_dir('./project-b', recursive=True), # → Worker 1: all files from project-b
|
|
156
|
-
read_local_dir('./project-c', recursive=True), # → Worker 2: all files from project-c
|
|
157
|
-
]
|
|
158
|
-
|
|
159
|
-
results = await swarm.map(
|
|
160
|
-
items=items,
|
|
161
|
-
prompt='Review this codebase',
|
|
162
|
-
)
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
## Abstractions
|
|
166
|
-
|
|
167
|
-
Two types of operations:
|
|
168
|
-
|
|
169
|
-
| Operation | Type | Description | Passes On |
|
|
170
|
-
|-----------|------|-------------|-----------|
|
|
171
|
-
| `best_of` | transform + select | `input` → `output` (best of N candidates) | winner output |
|
|
172
|
-
| `map` | transform | `input` → `output` (agent produces new data) | agent output |
|
|
173
|
-
| `filter` | gate | `input` → `input` (agent evaluates, condition decides) | original input + status (`success` \| `filtered`) |
|
|
174
|
-
| `reduce` | transform | `inputs` → `output` (agent synthesizes) | agent output |
|
|
175
|
-
|
|
176
|
-
**Transforms** produce new output files. **Filter** passes through original input files unchanged.
|
|
177
|
-
|
|
178
|
-
**BestOfConfig** — run N candidates in parallel, judge picks the best:
|
|
179
|
-
```python
|
|
180
|
-
BestOfConfig(
|
|
181
|
-
n=int,
|
|
182
|
-
judge_criteria=str,
|
|
183
|
-
task_agents=list[AgentConfig],
|
|
184
|
-
judge_agent=AgentConfig,
|
|
185
|
-
skills=list[str],
|
|
186
|
-
judge_skills=list[str],
|
|
187
|
-
integrations=IntegrationsSetup,
|
|
188
|
-
judge_integrations=IntegrationsSetup,
|
|
189
|
-
mcp_servers=dict[str, McpServerConfig],
|
|
190
|
-
judge_mcp_servers=dict[str, McpServerConfig],
|
|
191
|
-
on_candidate_complete=Callable[[int, int, str], None],
|
|
192
|
-
on_judge_complete=Callable[[int, int, str], None],
|
|
193
|
-
)
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
**VerifyConfig** — LLM-as-judge verifies output, retries with feedback if failed:
|
|
197
|
-
```python
|
|
198
|
-
VerifyConfig(
|
|
199
|
-
criteria=str,
|
|
200
|
-
max_attempts=int,
|
|
201
|
-
verifier_agent=AgentConfig,
|
|
202
|
-
verifier_skills=list[str],
|
|
203
|
-
verifier_integrations=IntegrationsSetup,
|
|
204
|
-
verifier_mcp_servers=dict[str, McpServerConfig],
|
|
205
|
-
on_worker_complete=Callable[[int, int, str], None],
|
|
206
|
-
on_verifier_complete=Callable[[int, int, bool, str | None], None],
|
|
207
|
-
)
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### best_of
|
|
211
|
-
|
|
212
|
-
Run N agents on the same `item` in parallel, then a judge picks the best. `Agent[i]` outputs `candidates[i]`, judge selects `winner`.
|
|
213
|
-
|
|
214
|
-
```
|
|
215
|
-
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
216
|
-
│ Sandbox 0 │ │ Sandbox 1 │ │ Sandbox 2 │
|
|
217
|
-
│ Agent 0 │ │ Agent 1 │ │ Agent 2 │
|
|
218
|
-
│ │ │ │ │ │
|
|
219
|
-
│ context/ │ │ context/ │ │ context/ │
|
|
220
|
-
│ item │ │ item │ │ item │
|
|
221
|
-
│ output/ │ │ output/ │ │ output/ │
|
|
222
|
-
│ candidates[0]│ │ candidates[1]│ │ candidates[2]│
|
|
223
|
-
└───────┬─────────┘ └───────┬─────────┘ └───────┬─────────┘
|
|
224
|
-
│ │ │
|
|
225
|
-
└───────────────────┼───────────────────┘
|
|
226
|
-
▼
|
|
227
|
-
┌───────────────┐
|
|
228
|
-
│ Judge │
|
|
229
|
-
└───────┬───────┘
|
|
230
|
-
│
|
|
231
|
-
▼
|
|
232
|
-
winner
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
```python
|
|
236
|
-
# Signature
|
|
237
|
-
await swarm.best_of(
|
|
238
|
-
item=FileMap | SwarmResult,
|
|
239
|
-
prompt=str,
|
|
240
|
-
config=BestOfConfig( # n?, judge_criteria, task_agents?, judge_agent?, callbacks
|
|
241
|
-
judge_criteria='...',
|
|
242
|
-
n=3,
|
|
243
|
-
on_candidate_complete=lambda idx, cand_idx, status: ...,
|
|
244
|
-
on_judge_complete=lambda idx, winner_idx, reasoning: ...,
|
|
245
|
-
),
|
|
246
|
-
name=str, # Operation name for observability (appears in meta.operation_name)
|
|
247
|
-
schema=PydanticModel | dict, # Optional
|
|
248
|
-
system_prompt=str, # Optional
|
|
249
|
-
retry=RetryConfig(...), # Per-candidate retry (judge uses default)
|
|
250
|
-
timeout_ms=int, # Optional
|
|
251
|
-
) -> BestOfResult
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
```python
|
|
255
|
-
input_item = {'task.txt': 'Complex problem...'}
|
|
256
|
-
|
|
257
|
-
result = await swarm.best_of(
|
|
258
|
-
item=input_item,
|
|
259
|
-
prompt='Solve this problem',
|
|
260
|
-
config=BestOfConfig(
|
|
261
|
-
n=3,
|
|
262
|
-
judge_criteria='Most accurate and well-explained solution',
|
|
263
|
-
on_candidate_complete=lambda idx, cand_idx, status: print(f'Candidate {cand_idx}: {status}'),
|
|
264
|
-
on_judge_complete=lambda idx, winner_idx, reasoning: print(f'Winner: {winner_idx}'),
|
|
265
|
-
),
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
print(result.winner) # Best SwarmResult
|
|
269
|
-
print(result.winner_index) # 0, 1, or 2
|
|
270
|
-
print(result.judge_reasoning) # Why this was chosen
|
|
271
|
-
print(result.candidates) # All candidate results
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
Use different agents per candidate:
|
|
275
|
-
|
|
276
|
-
```python
|
|
277
|
-
claude_agent = AgentConfig(type='claude', model='opus')
|
|
278
|
-
codex_agent = AgentConfig(type='codex', model='gpt-5.3-codex')
|
|
279
|
-
gemini_agent = AgentConfig(type='gemini', model='gemini-3.5-flash')
|
|
280
|
-
|
|
281
|
-
result = await swarm.best_of(
|
|
282
|
-
item=input_item,
|
|
283
|
-
prompt='Solve this',
|
|
284
|
-
config=BestOfConfig(
|
|
285
|
-
task_agents=[claude_agent, codex_agent, gemini_agent],
|
|
286
|
-
judge_criteria='Best solution quality',
|
|
287
|
-
judge_agent=claude_agent,
|
|
288
|
-
mcp_servers={...}, # (optional) MCP servers for candidates
|
|
289
|
-
judge_mcp_servers={...}, # (optional) MCP servers for judge
|
|
290
|
-
skills=['anthropics/skills'], # (optional) Skills for candidates
|
|
291
|
-
judge_skills=['anthropics/skills'], # (optional) Skills for judge
|
|
292
|
-
integrations=IntegrationsSetup(...), # (optional) Integrations config for candidates
|
|
293
|
-
judge_integrations=IntegrationsSetup(...), # (optional) Integrations config for judge
|
|
294
|
-
),
|
|
295
|
-
)
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
### map
|
|
299
|
-
|
|
300
|
-
Process items in parallel. `Agent[i]` sees `items[i]` and outputs `results[i]` (which includes `result.json` if `schema` provided).
|
|
301
|
-
|
|
302
|
-
```
|
|
303
|
-
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
304
|
-
│ Sandbox 0 │ │ Sandbox 1 │ │ Sandbox 2 │
|
|
305
|
-
│ Agent 0 │ │ Agent 1 │ │ Agent 2 │
|
|
306
|
-
│ │ │ │ │ │
|
|
307
|
-
│ context/ │ │ context/ │ │ context/ │
|
|
308
|
-
│ items[0] │ │ items[1] │ │ items[2] │
|
|
309
|
-
│ output/ │ │ output/ │ │ output/ │
|
|
310
|
-
│ results[0] │ │ results[1] │ │ results[2] │
|
|
311
|
-
└───────┬─────────┘ └───────┬─────────┘ └───────┬─────────┘
|
|
312
|
-
│ │ │
|
|
313
|
-
└───────────────────┼───────────────────┘
|
|
314
|
-
▼
|
|
315
|
-
[results[0], results[1], results[2]]
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
```python
|
|
319
|
-
# Signature (schema accepts Pydantic model or JSON Schema dict)
|
|
320
|
-
await swarm.map(
|
|
321
|
-
items=list[FileMap] | list[SwarmResult],
|
|
322
|
-
prompt=str | Callable[[FileMap, int], str],
|
|
323
|
-
name=str, # Operation name for observability (appears in meta.operation_name)
|
|
324
|
-
schema=PydanticModel | dict, # Optional
|
|
325
|
-
system_prompt=str, # Optional
|
|
326
|
-
agent=AgentConfig, # Optional override
|
|
327
|
-
best_of=BestOfConfig, # N candidates + judge (mutually exclusive with verify)
|
|
328
|
-
verify=VerifyConfig, # LLM-as-judge quality check with retry loop
|
|
329
|
-
retry=RetryConfig, # Auto-retry on error with backoff
|
|
330
|
-
mcp_servers=dict[str, McpServerConfig], # Optional
|
|
331
|
-
skills=list[str], # Optional - e.g. ['anthropics/skills']
|
|
332
|
-
integrations=IntegrationsSetup, # managed integrations config
|
|
333
|
-
timeout_ms=int, # Optional
|
|
334
|
-
) -> SwarmResultList
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
```python
|
|
338
|
-
# Basic
|
|
339
|
-
results = await swarm.map(
|
|
340
|
-
items=documents,
|
|
341
|
-
prompt='Summarize this document',
|
|
342
|
-
)
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
When `schema` is provided, a structured output prompt is automatically embedded—instructing the agent to write `output/result.json` matching the schema.
|
|
346
|
-
|
|
347
|
-
```python
|
|
348
|
-
# With Pydantic schema
|
|
349
|
-
class SummarySchema(BaseModel):
|
|
350
|
-
title: str
|
|
351
|
-
key_points: list[str]
|
|
352
|
-
|
|
353
|
-
results = await swarm.map(
|
|
354
|
-
items=documents,
|
|
355
|
-
prompt='Extract summary',
|
|
356
|
-
schema=SummarySchema,
|
|
357
|
-
)
|
|
358
|
-
|
|
359
|
-
# Or with JSON Schema
|
|
360
|
-
summary_json_schema = {
|
|
361
|
-
'type': 'object',
|
|
362
|
-
'properties': {
|
|
363
|
-
'title': {'type': 'string'},
|
|
364
|
-
'key_points': {'type': 'array', 'items': {'type': 'string'}},
|
|
365
|
-
},
|
|
366
|
-
'required': ['title', 'key_points'],
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
results = await swarm.map(
|
|
370
|
-
items=documents,
|
|
371
|
-
prompt='Extract summary',
|
|
372
|
-
schema=summary_json_schema,
|
|
373
|
-
)
|
|
374
|
-
|
|
375
|
-
# With dynamic prompt
|
|
376
|
-
results = await swarm.map(
|
|
377
|
-
items=documents,
|
|
378
|
-
prompt=lambda files, index: f'Analyze document {index + 1}: focus on revenue',
|
|
379
|
-
)
|
|
380
|
-
|
|
381
|
-
# Access results
|
|
382
|
-
for r in results:
|
|
383
|
-
if r.status == 'success':
|
|
384
|
-
print(r.data) # Parsed schema instance or FileMap
|
|
385
|
-
print(r.files) # Output files from agent
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### map + best_of
|
|
389
|
-
|
|
390
|
-
Combine map parallelism with best_of quality:
|
|
391
|
-
|
|
392
|
-
```python
|
|
393
|
-
class AnalysisSchema(BaseModel):
|
|
394
|
-
findings: list[str]
|
|
395
|
-
confidence: float
|
|
396
|
-
|
|
397
|
-
# Each item gets N candidates, judge picks best per item
|
|
398
|
-
results = await swarm.map(
|
|
399
|
-
items=documents,
|
|
400
|
-
prompt='Analyze thoroughly',
|
|
401
|
-
schema=AnalysisSchema,
|
|
402
|
-
best_of=BestOfConfig(
|
|
403
|
-
n=3,
|
|
404
|
-
judge_criteria='Most comprehensive analysis',
|
|
405
|
-
# task_agents=[...], # Different agent per candidate
|
|
406
|
-
# judge_agent=..., # Override judge agent
|
|
407
|
-
# mcp_servers={...}, # MCP servers for candidates
|
|
408
|
-
# judge_mcp_servers={...}, # MCP servers for judge
|
|
409
|
-
# skills=[...], # Skills for candidates
|
|
410
|
-
# judge_skills=[...], # Skills for judge
|
|
411
|
-
),
|
|
412
|
-
)
|
|
413
|
-
|
|
414
|
-
# Results contain only winners (one per input item)
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
### filter
|
|
418
|
-
|
|
419
|
-
Two-step evaluation (`schema` and `condition` are required):
|
|
420
|
-
1. `Agent[i]` sees `items[i]`, assesses it, outputs `result.json` matching `schema`
|
|
421
|
-
2. SDK parses `result.json` → `data`, your `condition(data)` applies the threshold
|
|
422
|
-
3. Passing items forward their original input files, not agent output
|
|
423
|
-
|
|
424
|
-
```
|
|
425
|
-
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
426
|
-
│ Sandbox 0 │ │ Sandbox 1 │ │ Sandbox 2 │
|
|
427
|
-
│ Agent 0 │ │ Agent 1 │ │ Agent 2 │
|
|
428
|
-
│ │ │ │ │ │
|
|
429
|
-
│ context/ │ │ context/ │ │ context/ │
|
|
430
|
-
│ items[0] │ │ items[1] │ │ items[2] │
|
|
431
|
-
│ output/ │ │ output/ │ │ output/ │
|
|
432
|
-
│ result.json │ │ result.json │ │ result.json │
|
|
433
|
-
└───────┬─────────┘ └───────┬─────────┘ └───────┬─────────┘
|
|
434
|
-
│ │ │
|
|
435
|
-
└───────────────────┼───────────────────┘
|
|
436
|
-
▼
|
|
437
|
-
condition(data)
|
|
438
|
-
✓ ✗ ✓
|
|
439
|
-
│ │
|
|
440
|
-
▼ ▼
|
|
441
|
-
[items[0], items[2]]
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
```python
|
|
445
|
-
# Signature (schema accepts Pydantic model or JSON Schema dict)
|
|
446
|
-
await swarm.filter(
|
|
447
|
-
items=list[FileMap] | list[SwarmResult],
|
|
448
|
-
prompt=str, # Describe what to assess (agent outputs result.json)
|
|
449
|
-
name=str, # Operation name for observability (appears in meta.operation_name)
|
|
450
|
-
schema=PydanticModel | dict, # Required - defines evaluation output structure
|
|
451
|
-
condition=Callable[[Any], bool], # Local function applies threshold
|
|
452
|
-
system_prompt=str, # Optional
|
|
453
|
-
agent=AgentConfig, # Optional override
|
|
454
|
-
verify=VerifyConfig, # LLM-as-judge quality check with retry loop
|
|
455
|
-
retry=RetryConfig, # Auto-retry on error with backoff
|
|
456
|
-
mcp_servers=dict[str, McpServerConfig], # Optional
|
|
457
|
-
skills=list[str], # Optional - e.g. ['anthropics/skills']
|
|
458
|
-
integrations=IntegrationsSetup, # managed integrations config
|
|
459
|
-
timeout_ms=int, # Optional
|
|
460
|
-
) -> SwarmResultList
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
```python
|
|
464
|
-
class EvalSchema(BaseModel):
|
|
465
|
-
severity: Literal['critical', 'warning', 'info']
|
|
466
|
-
score: float
|
|
467
|
-
|
|
468
|
-
results = await swarm.filter(
|
|
469
|
-
items=documents,
|
|
470
|
-
prompt='Assess the severity of issues in this document', # Agent evaluates
|
|
471
|
-
schema=EvalSchema,
|
|
472
|
-
condition=lambda data: data.severity == 'critical', # Code applies threshold
|
|
473
|
-
)
|
|
474
|
-
|
|
475
|
-
# Three possible statuses:
|
|
476
|
-
results.success # Passed condition
|
|
477
|
-
results.filtered # Evaluated but didn't pass
|
|
478
|
-
results.error # Agent error
|
|
479
|
-
|
|
480
|
-
# Chain to next step
|
|
481
|
-
await swarm.reduce(
|
|
482
|
-
items=results.success,
|
|
483
|
-
prompt='Summarize critical issues',
|
|
484
|
-
)
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
### reduce
|
|
488
|
-
|
|
489
|
-
Synthesize many items into one. A single agent sees all `items` as `item_0/`, `item_1/`, etc. and outputs a unified `result` (which includes `result.json` if `schema` provided).
|
|
490
|
-
|
|
491
|
-
```
|
|
492
|
-
┌─────────────────────────┐
|
|
493
|
-
│ Sandbox │
|
|
494
|
-
│ Agent │
|
|
495
|
-
│ │
|
|
496
|
-
│ context/ │
|
|
497
|
-
│ item_0/items[0] │
|
|
498
|
-
│ item_1/items[1] │
|
|
499
|
-
│ item_2/items[2] │
|
|
500
|
-
│ output/ │
|
|
501
|
-
│ result │
|
|
502
|
-
└────────────┬────────────┘
|
|
503
|
-
│
|
|
504
|
-
▼
|
|
505
|
-
result
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
```python
|
|
509
|
-
# Signature (schema accepts Pydantic model or JSON Schema dict)
|
|
510
|
-
await swarm.reduce(
|
|
511
|
-
items=list[FileMap] | list[SwarmResult],
|
|
512
|
-
prompt=str,
|
|
513
|
-
name=str, # Operation name for observability (appears in meta.operation_name)
|
|
514
|
-
schema=PydanticModel | dict, # Optional
|
|
515
|
-
system_prompt=str, # Optional
|
|
516
|
-
agent=AgentConfig, # Optional override
|
|
517
|
-
verify=VerifyConfig, # LLM-as-judge quality check with retry loop
|
|
518
|
-
retry=RetryConfig, # Auto-retry on error with backoff
|
|
519
|
-
mcp_servers=dict[str, McpServerConfig], # Optional
|
|
520
|
-
skills=list[str], # Optional - e.g. ['anthropics/skills']
|
|
521
|
-
integrations=IntegrationsSetup, # managed integrations config
|
|
522
|
-
timeout_ms=int, # Optional
|
|
523
|
-
) -> ReduceResult
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
```python
|
|
527
|
-
# Agent sees: item_0/, item_1/, item_2/, etc.
|
|
528
|
-
report = await swarm.reduce(
|
|
529
|
-
items=results.success,
|
|
530
|
-
prompt='Create a unified report from all analyses',
|
|
531
|
-
)
|
|
532
|
-
|
|
533
|
-
if report.status == 'success':
|
|
534
|
-
print(report.files) # Final output files
|
|
535
|
-
print(report.data) # Parsed schema if provided
|
|
536
|
-
|
|
537
|
-
# With schema
|
|
538
|
-
class ReportSchema(BaseModel):
|
|
539
|
-
summary: str
|
|
540
|
-
recommendations: list[str]
|
|
541
|
-
|
|
542
|
-
report = await swarm.reduce(
|
|
543
|
-
items=items,
|
|
544
|
-
prompt='Create report',
|
|
545
|
-
schema=ReportSchema,
|
|
546
|
-
)
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
### verify (quality gate)
|
|
550
|
-
|
|
551
|
-
Add a verification loop to any operation (`map`, `filter`, `reduce`). A separate verifier agent checks if the worker's output meets your criteria. On failure, the worker retries with the verifier's feedback — up to `max_attempts` times.
|
|
552
|
-
|
|
553
|
-
```
|
|
554
|
-
┌───────────────┐ ┌──────────────┐
|
|
555
|
-
│ Worker │─────▶│ Verifier │
|
|
556
|
-
│ (attempt 1) │ │ │
|
|
557
|
-
└───────────────┘ └──────┬───────┘
|
|
558
|
-
│
|
|
559
|
-
pass? │ fail + feedback
|
|
560
|
-
│ │ │
|
|
561
|
-
▼ │ ▼
|
|
562
|
-
output │ ┌───────────────┐
|
|
563
|
-
└─▶│ Worker │──▶ Verifier ──▶ ...
|
|
564
|
-
│ (attempt 2) │ (up to max_attempts)
|
|
565
|
-
└───────────────┘
|
|
566
|
-
```
|
|
567
|
-
|
|
568
|
-
```python
|
|
569
|
-
results = await swarm.map(
|
|
570
|
-
items=documents,
|
|
571
|
-
prompt='Write a detailed analysis',
|
|
572
|
-
schema=AnalysisSchema,
|
|
573
|
-
verify=VerifyConfig(
|
|
574
|
-
criteria='Analysis must include specific data points and cite sources',
|
|
575
|
-
max_attempts=3, # Default: 3
|
|
576
|
-
# verifier_agent=AgentConfig(type='claude', model='opus'), # Override verifier
|
|
577
|
-
# verifier_skills=['anthropics/skills'], # Skills for verifier
|
|
578
|
-
on_worker_complete=lambda idx, attempt, status: print(f'Item {idx}, attempt {attempt}: {status}'),
|
|
579
|
-
on_verifier_complete=lambda idx, attempt, passed, feedback: print(f'Verify item {idx}: {"PASS" if passed else feedback}'),
|
|
580
|
-
),
|
|
581
|
-
)
|
|
582
|
-
|
|
583
|
-
# Check verification outcome
|
|
584
|
-
for r in results:
|
|
585
|
-
if r.verify:
|
|
586
|
-
print(f'Passed: {r.verify.passed}, attempts: {r.verify.attempts}')
|
|
587
|
-
print(f'Reasoning: {r.verify.reasoning}')
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
On each failed attempt, the verifier's feedback is appended to the worker's prompt so it can address specific issues. The final result includes `verify: VerifyInfo` with the outcome.
|
|
591
|
-
|
|
592
|
-
> **Note:** `verify` and `best_of` are mutually exclusive on `map`. Use one or the other per operation.
|
|
593
|
-
|
|
594
|
-
---
|
|
595
|
-
|
|
596
|
-
## Result Types
|
|
597
|
-
|
|
598
|
-
```python
|
|
599
|
-
@dataclass
|
|
600
|
-
class SwarmResult:
|
|
601
|
-
"""Result from map, filter, best_of candidates."""
|
|
602
|
-
status: Literal['success', 'filtered', 'error']
|
|
603
|
-
data: Any | None # Parsed schema, or None on error
|
|
604
|
-
files: FileMap # Output files (map/best_of) or input files (filter)
|
|
605
|
-
meta: IndexedMeta # operation_id, operation, tag, sandbox_id, item_index
|
|
606
|
-
error: str | None # Error message if status == 'error'
|
|
607
|
-
raw_data: str | None # Raw result.json when parse/validation failed
|
|
608
|
-
best_of: BestOfInfo | None # Present when map used best_of option
|
|
609
|
-
verify: VerifyInfo | None # Present when verify option was used
|
|
610
|
-
|
|
611
|
-
# SwarmResultList - from map, filter (extends list)
|
|
612
|
-
results.success # list[SwarmResult] with status 'success'
|
|
613
|
-
results.filtered # list[SwarmResult] with status 'filtered'
|
|
614
|
-
results.error # list[SwarmResult] with status 'error'
|
|
615
|
-
|
|
616
|
-
@dataclass
|
|
617
|
-
class ReduceResult:
|
|
618
|
-
"""Result from reduce."""
|
|
619
|
-
status: Literal['success', 'error']
|
|
620
|
-
data: Any | None
|
|
621
|
-
files: FileMap
|
|
622
|
-
meta: ReduceMeta # operation_id, operation, tag, sandbox_id, input_count, input_indices
|
|
623
|
-
error: str | None
|
|
624
|
-
raw_data: str | None
|
|
625
|
-
verify: VerifyInfo | None
|
|
626
|
-
|
|
627
|
-
@dataclass
|
|
628
|
-
class VerifyInfo:
|
|
629
|
-
"""Verification outcome."""
|
|
630
|
-
passed: bool # Final verification status
|
|
631
|
-
reasoning: str # Verifier's reasoning
|
|
632
|
-
verify_meta: VerifyMeta # operation_id, operation, tag, sandbox_id, attempts
|
|
633
|
-
attempts: int # Total attempts made
|
|
634
|
-
|
|
635
|
-
@dataclass
|
|
636
|
-
class BestOfInfo:
|
|
637
|
-
"""Present when map used best_of option."""
|
|
638
|
-
winner_index: int
|
|
639
|
-
judge_reasoning: str
|
|
640
|
-
judge_meta: JudgeMeta # operation_id, operation, tag, sandbox_id, candidate_count
|
|
641
|
-
candidates: list[SwarmResult]
|
|
642
|
-
|
|
643
|
-
@dataclass
|
|
644
|
-
class BestOfResult:
|
|
645
|
-
"""Result from best_of."""
|
|
646
|
-
winner: SwarmResult
|
|
647
|
-
winner_index: int
|
|
648
|
-
judge_reasoning: str
|
|
649
|
-
judge_meta: JudgeMeta # operation_id, operation, tag, sandbox_id, candidate_count
|
|
650
|
-
candidates: list[SwarmResult]
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
## Chaining Operations
|
|
654
|
-
|
|
655
|
-
When chaining Swarm operations, `result.json` from a previous step is automatically renamed to `data.json`. This avoids confusion when the downstream agent writes its own `result.json`. This also applies to [Pipeline](#pipeline).
|
|
656
|
-
|
|
657
|
-
**Example: map → reduce chain**
|
|
658
|
-
|
|
659
|
-
```
|
|
660
|
-
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
661
|
-
│ MAP (parallel) │
|
|
662
|
-
│ │
|
|
663
|
-
│ item_0 agent writes: item_1 agent writes: │
|
|
664
|
-
│ output/ output/ │
|
|
665
|
-
│ result.json ← schema result.json ← schema │
|
|
666
|
-
│ │
|
|
667
|
-
└─────────────────────────────────────────────────────────────────────────────┘
|
|
668
|
-
│
|
|
669
|
-
▼
|
|
670
|
-
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
671
|
-
│ REDUCE (single agent) │
|
|
672
|
-
│ │
|
|
673
|
-
│ context/ │
|
|
674
|
-
│ item_0/ │
|
|
675
|
-
│ data.json ← renamed from result.json │
|
|
676
|
-
│ item_1/ │
|
|
677
|
-
│ data.json ← renamed from result.json │
|
|
678
|
-
│ output/ │
|
|
679
|
-
│ result.json ← reduce agent writes its own │
|
|
680
|
-
│ │
|
|
681
|
-
└─────────────────────────────────────────────────────────────────────────────┘
|
|
682
|
-
```
|
|
683
|
-
|
|
684
|
-
```python
|
|
685
|
-
class AnalysisSchema(BaseModel):
|
|
686
|
-
summary: str
|
|
687
|
-
|
|
688
|
-
class SeveritySchema(BaseModel):
|
|
689
|
-
severity: Literal['critical', 'warning', 'info']
|
|
690
|
-
|
|
691
|
-
# Full pipeline: map → filter → reduce
|
|
692
|
-
analyzed = await swarm.map(
|
|
693
|
-
items=documents,
|
|
694
|
-
prompt='Analyze',
|
|
695
|
-
schema=AnalysisSchema,
|
|
696
|
-
)
|
|
697
|
-
|
|
698
|
-
critical = await swarm.filter(
|
|
699
|
-
items=analyzed.success,
|
|
700
|
-
prompt='Evaluate severity',
|
|
701
|
-
schema=SeveritySchema,
|
|
702
|
-
condition=lambda d: d.severity == 'critical',
|
|
703
|
-
)
|
|
704
|
-
|
|
705
|
-
report = await swarm.reduce(
|
|
706
|
-
items=critical.success,
|
|
707
|
-
prompt='Create summary report',
|
|
708
|
-
)
|
|
709
|
-
|
|
710
|
-
# Combine success and filtered
|
|
711
|
-
all_evaluated = [*critical.success, *critical.filtered]
|
|
712
|
-
await swarm.reduce(
|
|
713
|
-
items=all_evaluated,
|
|
714
|
-
prompt='Summarize all evaluated items',
|
|
715
|
-
)
|
|
716
|
-
```
|
|
717
|
-
|
|
718
|
-
## AgentOverride
|
|
719
|
-
|
|
720
|
-
Override the default agent for any operation. There is no separate override type in Python — you pass the same `AgentConfig` the Swarm itself takes, and `api_key` is inherited from the Swarm config when you leave it out. Only the fields worth overriding per operation are shown:
|
|
721
|
-
|
|
722
|
-
```python
|
|
723
|
-
@dataclass
|
|
724
|
-
class AgentConfig:
|
|
725
|
-
type: Literal['claude', 'codex', 'gemini', 'qwen', 'kimi', 'opencode', 'droid']
|
|
726
|
-
api_key: str | None = None
|
|
727
|
-
model: str | None = None
|
|
728
|
-
reasoning_effort: Literal['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'thinking', 'no-thinking'] | None = None
|
|
729
|
-
```
|
|
730
|
-
|
|
731
|
-
```python
|
|
732
|
-
codex_agent = AgentConfig(
|
|
733
|
-
type='codex',
|
|
734
|
-
reasoning_effort='high',
|
|
735
|
-
)
|
|
736
|
-
|
|
737
|
-
results = await swarm.map(
|
|
738
|
-
items=items,
|
|
739
|
-
prompt='Analyze',
|
|
740
|
-
agent=codex_agent,
|
|
741
|
-
)
|
|
742
|
-
```
|
|
743
|
-
|
|
744
|
-
## Concurrency
|
|
745
|
-
|
|
746
|
-
Global semaphore limits parallel sandboxes across all operations.
|
|
747
|
-
|
|
748
|
-
```python
|
|
749
|
-
swarm = Swarm(SwarmConfig(
|
|
750
|
-
agent=agent,
|
|
751
|
-
concurrency=4, # Max 4 sandboxes at once (default: 4)
|
|
752
|
-
))
|
|
753
|
-
|
|
754
|
-
# map(10) with best_of(5) = 60 agent calls, but only 4 run at any time
|
|
755
|
-
```
|
|
756
|
-
|
|
757
|
-
**Ordering guarantees:**
|
|
758
|
-
- `best_of`: Judge runs only after all candidates complete
|
|
759
|
-
- `map` → `filter` → `reduce`: Each phase completes before next starts
|
|
760
|
-
- Within a phase: Items run in parallel (up to concurrency limit)
|
|
761
|
-
|
|
762
|
-
---
|
|
763
|
-
|
|
764
|
-
## Pipeline
|
|
765
|
-
|
|
766
|
-
Fluent wrapper over Swarm for chaining operations. **All Swarm features work in Pipeline steps** — `schema`, `best_of`, `verify`, `retry`, `agent`, `mcp_servers`, `skills`, `integrations`, dynamic prompts.
|
|
767
|
-
|
|
768
|
-
```python
|
|
769
|
-
from dotenv import load_dotenv
|
|
770
|
-
load_dotenv()
|
|
771
|
-
|
|
772
|
-
from evolve import Swarm, Pipeline
|
|
773
|
-
|
|
774
|
-
swarm = Swarm() # See Swarm Abstractions for full config
|
|
775
|
-
|
|
776
|
-
pipeline = (
|
|
777
|
-
Pipeline(swarm)
|
|
778
|
-
.map(MapConfig(
|
|
779
|
-
name='analyze',
|
|
780
|
-
prompt='Analyze...',
|
|
781
|
-
schema=AnalysisSchema,
|
|
782
|
-
))
|
|
783
|
-
.filter(FilterConfig(
|
|
784
|
-
name='critical',
|
|
785
|
-
prompt='Rate...',
|
|
786
|
-
schema=SeveritySchema,
|
|
787
|
-
condition=lambda d: d.severity == 'critical',
|
|
788
|
-
))
|
|
789
|
-
.reduce(ReduceConfig(
|
|
790
|
-
name='report',
|
|
791
|
-
prompt='Summarize...',
|
|
792
|
-
))
|
|
793
|
-
)
|
|
794
|
-
|
|
795
|
-
# Reusable — run with different data
|
|
796
|
-
result1 = await pipeline.run(batch1)
|
|
797
|
-
result2 = await pipeline.run(batch2)
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
### Step Configurations
|
|
801
|
-
|
|
802
|
-
Each step accepts the same options as the corresponding Swarm method, plus `name` for observability:
|
|
803
|
-
|
|
804
|
-
```python
|
|
805
|
-
# Map step — same as swarm.map() + name
|
|
806
|
-
MapConfig(
|
|
807
|
-
name=str, # Step name (appears in events)
|
|
808
|
-
prompt=str | Callable[[FileMap, int], str],
|
|
809
|
-
schema=PydanticModel | dict, # Optional
|
|
810
|
-
best_of=BestOfConfig, # N candidates + judge
|
|
811
|
-
verify=VerifyConfig, # LLM-as-judge quality check
|
|
812
|
-
retry=RetryConfig, # Auto-retry on error
|
|
813
|
-
agent=AgentConfig,
|
|
814
|
-
mcp_servers=dict[str, McpServerConfig],
|
|
815
|
-
skills=list[str], # Skills for workers
|
|
816
|
-
integrations=IntegrationsSetup, # managed integrations config
|
|
817
|
-
system_prompt=str,
|
|
818
|
-
timeout_ms=int,
|
|
819
|
-
)
|
|
820
|
-
|
|
821
|
-
# Filter step — same as swarm.filter() + name + emit
|
|
822
|
-
FilterConfig(
|
|
823
|
-
name=str,
|
|
824
|
-
prompt=str,
|
|
825
|
-
schema=PydanticModel | dict, # Required
|
|
826
|
-
condition=Callable[[Any], bool], # Required
|
|
827
|
-
emit='success' | 'filtered' | 'all', # What passes to next step: 'success' (default), 'filtered', or 'all'
|
|
828
|
-
verify=VerifyConfig,
|
|
829
|
-
retry=RetryConfig,
|
|
830
|
-
agent=AgentConfig,
|
|
831
|
-
mcp_servers=dict[str, McpServerConfig],
|
|
832
|
-
skills=list[str], # Skills for workers
|
|
833
|
-
integrations=IntegrationsSetup, # managed integrations config
|
|
834
|
-
system_prompt=str,
|
|
835
|
-
timeout_ms=int,
|
|
836
|
-
)
|
|
837
|
-
|
|
838
|
-
# Reduce step — same as swarm.reduce() + name (terminal: no steps after)
|
|
839
|
-
ReduceConfig(
|
|
840
|
-
name=str,
|
|
841
|
-
prompt=str,
|
|
842
|
-
schema=PydanticModel | dict, # Optional
|
|
843
|
-
verify=VerifyConfig,
|
|
844
|
-
retry=RetryConfig,
|
|
845
|
-
agent=AgentConfig,
|
|
846
|
-
mcp_servers=dict[str, McpServerConfig],
|
|
847
|
-
skills=list[str], # Skills for workers
|
|
848
|
-
integrations=IntegrationsSetup, # managed integrations config
|
|
849
|
-
system_prompt=str,
|
|
850
|
-
timeout_ms=int,
|
|
851
|
-
)
|
|
852
|
-
```
|
|
853
|
-
|
|
854
|
-
### Full Example
|
|
855
|
-
|
|
856
|
-
```python
|
|
857
|
-
pipeline = (
|
|
858
|
-
Pipeline(swarm)
|
|
859
|
-
|
|
860
|
-
.map(MapConfig(
|
|
861
|
-
name='analyze',
|
|
862
|
-
prompt=lambda files, idx: f'Analyze document {idx + 1}',
|
|
863
|
-
schema=AnalysisSchema,
|
|
864
|
-
best_of=BestOfConfig(
|
|
865
|
-
n=3,
|
|
866
|
-
judge_criteria='Most thorough analysis',
|
|
867
|
-
),
|
|
868
|
-
retry=RetryConfig(max_attempts=2),
|
|
869
|
-
agent=AgentConfig(type='claude', model='opus'),
|
|
870
|
-
))
|
|
871
|
-
|
|
872
|
-
.filter(FilterConfig(
|
|
873
|
-
name='quality-gate',
|
|
874
|
-
prompt='Rate the analysis quality',
|
|
875
|
-
schema=QualitySchema, # Has score: float, reasoning: str
|
|
876
|
-
condition=lambda d: d.score >= 8,
|
|
877
|
-
emit='success', # Only high-quality pass through
|
|
878
|
-
verify=VerifyConfig(
|
|
879
|
-
criteria='Rating must be justified with specific examples',
|
|
880
|
-
),
|
|
881
|
-
))
|
|
882
|
-
|
|
883
|
-
.reduce(ReduceConfig(
|
|
884
|
-
name='synthesize',
|
|
885
|
-
prompt='Create executive summary from all analyses',
|
|
886
|
-
schema=ReportSchema,
|
|
887
|
-
verify=VerifyConfig(
|
|
888
|
-
criteria='Summary must cover all key findings',
|
|
889
|
-
),
|
|
890
|
-
))
|
|
891
|
-
|
|
892
|
-
.on('step_complete', lambda e: print(f'{e.name}: {e.success_count}/{e.success_count + e.error_count}'))
|
|
893
|
-
)
|
|
894
|
-
|
|
895
|
-
result = await pipeline.run(documents)
|
|
896
|
-
```
|
|
897
|
-
|
|
898
|
-
### Events
|
|
899
|
-
|
|
900
|
-
Pipeline unifies all Swarm callbacks at the pipeline level, adding `step_index` and `step_name`:
|
|
901
|
-
|
|
902
|
-
```python
|
|
903
|
-
(
|
|
904
|
-
pipeline
|
|
905
|
-
.on('step_start', lambda e: print(f'Step {e.index} started with {e.item_count} items'))
|
|
906
|
-
.on('step_complete', lambda e: print(f'Step {e.index} done in {e.duration_ms}ms'))
|
|
907
|
-
.on('step_error', lambda e: print(f'Step {e.index} failed: {e.error}'))
|
|
908
|
-
)
|
|
909
|
-
|
|
910
|
-
# Or object style
|
|
911
|
-
pipeline.on(PipelineEvents(
|
|
912
|
-
on_step_complete=lambda e: print(f'{e.name}: {e.success_count} success'),
|
|
913
|
-
on_item_retry=lambda e: print(f'Retry: step {e.step_index}, item {e.item_index}'),
|
|
914
|
-
on_verifier_complete=lambda e: print(f"Verify: {'PASS' if e.passed else e.feedback}"),
|
|
915
|
-
))
|
|
916
|
-
```
|
|
917
|
-
|
|
918
|
-
| Event | Fields |
|
|
919
|
-
|-------|--------|
|
|
920
|
-
| `step_start` | `type`, `index`, `name?`, `item_count` |
|
|
921
|
-
| `step_complete` | `type`, `index`, `name?`, `duration_ms`, `success_count`, `error_count`, `filtered_count` |
|
|
922
|
-
| `step_error` | `type`, `index`, `name?`, `error` |
|
|
923
|
-
| `item_retry` | `step_index`, `step_name?`, `item_index`, `attempt`, `error` |
|
|
924
|
-
| `worker_complete` | `step_index`, `step_name?`, `item_index`, `attempt`, `status` |
|
|
925
|
-
| `verifier_complete` | `step_index`, `step_name?`, `item_index`, `attempt`, `passed`, `feedback?` |
|
|
926
|
-
| `candidate_complete` | `step_index`, `step_name?`, `item_index`, `candidate_index`, `status` |
|
|
927
|
-
| `judge_complete` | `step_index`, `step_name?`, `item_index`, `winner_index`, `reasoning` |
|
|
928
|
-
|
|
929
|
-
### Result
|
|
930
|
-
|
|
931
|
-
```python
|
|
932
|
-
@dataclass
|
|
933
|
-
class PipelineResult:
|
|
934
|
-
pipeline_run_id: str
|
|
935
|
-
steps: list[StepResult] # type, index, duration_ms, results
|
|
936
|
-
output: list[SwarmResult] | ReduceResult
|
|
937
|
-
total_duration_ms: int
|
|
938
|
-
|
|
939
|
-
# Access step results
|
|
940
|
-
for step in result.steps:
|
|
941
|
-
print(f'{step.type} took {step.duration_ms}ms')
|
|
942
|
-
```
|
|
943
|
-
|
|
944
|
-
### Terminal Pipeline
|
|
945
|
-
|
|
946
|
-
After `.reduce()`, no more steps can be added (returns `TerminalPipeline`):
|
|
947
|
-
|
|
948
|
-
```python
|
|
949
|
-
terminal = pipeline.reduce(ReduceConfig(prompt='...'))
|
|
950
|
-
terminal.map(MapConfig(prompt='...')) # Raises: "Cannot add steps after reduce"
|
|
951
|
-
```
|
|
952
|
-
|
|
953
|
-
---
|