evolvingmachines-evolve 0.0.55.dev1355__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.
- bridge/__init__.py +5 -0
- bridge/dist/bridge.bundle.cjs +1275 -0
- evolve/__init__.py +819 -0
- evolve/_http.py +71 -0
- evolve/agent.py +896 -0
- evolve/bridge.py +509 -0
- evolve/browser_credentials.py +265 -0
- evolve/browser_profiles.py +95 -0
- evolve/config.py +600 -0
- evolve/hosted.py +8958 -0
- evolve/integrations.py +173 -0
- evolve/managed_secrets.py +175 -0
- evolve/pipeline/__init__.py +59 -0
- evolve/pipeline/pipeline.py +512 -0
- evolve/pipeline/types.py +286 -0
- evolve/prompts/__init__.py +132 -0
- evolve/prompts/agent_md/judge.md +30 -0
- evolve/prompts/agent_md/reduce.md +7 -0
- evolve/prompts/agent_md/verify.md +33 -0
- evolve/prompts/user/judge.md +1 -0
- evolve/prompts/user/retry_feedback.md +9 -0
- evolve/prompts/user/verify.md +1 -0
- evolve/py.typed +0 -0
- evolve/results.py +315 -0
- evolve/retry.py +133 -0
- evolve/schema.py +107 -0
- evolve/sessions_client.py +167 -0
- evolve/storage_client.py +178 -0
- evolve/swarm/__init__.py +75 -0
- evolve/swarm/results.py +140 -0
- evolve/swarm/swarm.py +2116 -0
- evolve/swarm/types.py +241 -0
- evolve/utils.py +227 -0
- evolvingmachines_evolve-0.0.55.dev1355.dist-info/METADATA +52 -0
- evolvingmachines_evolve-0.0.55.dev1355.dist-info/RECORD +38 -0
- evolvingmachines_evolve-0.0.55.dev1355.dist-info/WHEEL +5 -0
- evolvingmachines_evolve-0.0.55.dev1355.dist-info/licenses/LICENSE +201 -0
- evolvingmachines_evolve-0.0.55.dev1355.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Standalone sessions() client for historical traces and past sessions."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
5
|
+
|
|
6
|
+
from .results import BrowserReplay, SessionEvent, SessionInfo, SessionPage, SessionTranscript
|
|
7
|
+
from .utils import (
|
|
8
|
+
_filter_none,
|
|
9
|
+
_require_browser_replay,
|
|
10
|
+
_require_session_info,
|
|
11
|
+
_require_session_transcript,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SessionsClient:
|
|
16
|
+
"""Client for listing historical sessions and downloading traces.
|
|
17
|
+
|
|
18
|
+
Created via the standalone ``sessions()`` factory only.
|
|
19
|
+
Wraps bridge JSON-RPC calls to the TypeScript SDK's sessions() client.
|
|
20
|
+
|
|
21
|
+
Gateway-only: requires ``EVOLVE_API_KEY`` unless ``SessionsConfig(api_key=...)``
|
|
22
|
+
is provided.
|
|
23
|
+
|
|
24
|
+
Example::
|
|
25
|
+
|
|
26
|
+
from evolve import sessions
|
|
27
|
+
|
|
28
|
+
async with sessions() as client:
|
|
29
|
+
page = await client.list(limit=20, state='ended')
|
|
30
|
+
events = await client.events(page.items[0].id)
|
|
31
|
+
path = await client.download(page.items[0].id, to='./traces')
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
bridge: Any,
|
|
37
|
+
sessions_config: Any = None,
|
|
38
|
+
*,
|
|
39
|
+
_owns_bridge: bool = False,
|
|
40
|
+
):
|
|
41
|
+
self._bridge = bridge
|
|
42
|
+
self._config = sessions_config
|
|
43
|
+
self._owns_bridge = _owns_bridge
|
|
44
|
+
self._started = False
|
|
45
|
+
self._init_lock = asyncio.Lock()
|
|
46
|
+
|
|
47
|
+
async def _ensure_ready(self) -> None:
|
|
48
|
+
"""Ensure the bridge is started and ready to handle RPC calls."""
|
|
49
|
+
async with self._init_lock:
|
|
50
|
+
if self._started:
|
|
51
|
+
return
|
|
52
|
+
await self._bridge.start()
|
|
53
|
+
self._started = True
|
|
54
|
+
|
|
55
|
+
def _build_params(self, **kwargs: Any) -> Dict[str, Any]:
|
|
56
|
+
"""Build RPC params, including standalone sessions config."""
|
|
57
|
+
params: Dict[str, Any] = {}
|
|
58
|
+
if self._config is not None:
|
|
59
|
+
params['sessions'] = self._config.to_dict()
|
|
60
|
+
params.update(_filter_none(kwargs))
|
|
61
|
+
return params
|
|
62
|
+
|
|
63
|
+
async def list(
|
|
64
|
+
self,
|
|
65
|
+
limit: Optional[int] = None,
|
|
66
|
+
cursor: Optional[str] = None,
|
|
67
|
+
state: Optional[Literal['live', 'ended', 'all']] = None,
|
|
68
|
+
agent: Optional[str] = None,
|
|
69
|
+
tag_prefix: Optional[str] = None,
|
|
70
|
+
sort: Optional[Literal['newest', 'oldest', 'cost']] = None,
|
|
71
|
+
) -> SessionPage:
|
|
72
|
+
"""List historical sessions with optional filtering and pagination."""
|
|
73
|
+
await self._ensure_ready()
|
|
74
|
+
params = self._build_params(
|
|
75
|
+
limit=limit,
|
|
76
|
+
cursor=cursor,
|
|
77
|
+
state=state,
|
|
78
|
+
agent=agent,
|
|
79
|
+
tag_prefix=tag_prefix,
|
|
80
|
+
sort=sort,
|
|
81
|
+
)
|
|
82
|
+
response = await self._bridge.call('sessions_list', params)
|
|
83
|
+
return SessionPage(
|
|
84
|
+
items=[_require_session_info(item) for item in response.get('items', [])],
|
|
85
|
+
next_cursor=response.get('next_cursor'),
|
|
86
|
+
has_more=bool(response.get('has_more', False)),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
async def get(self, id: str) -> SessionInfo:
|
|
90
|
+
"""Get a single session's metadata by ID."""
|
|
91
|
+
await self._ensure_ready()
|
|
92
|
+
response = await self._bridge.call('sessions_get', self._build_params(id=id))
|
|
93
|
+
return _require_session_info(response)
|
|
94
|
+
|
|
95
|
+
async def events(
|
|
96
|
+
self,
|
|
97
|
+
id: str,
|
|
98
|
+
*,
|
|
99
|
+
since: Optional[int] = None,
|
|
100
|
+
) -> List[SessionEvent]:
|
|
101
|
+
"""Fetch parsed JSONL events for a historical session."""
|
|
102
|
+
await self._ensure_ready()
|
|
103
|
+
response = await self._bridge.call('sessions_events', self._build_params(id=id, since=since))
|
|
104
|
+
return list(response.get('events', []))
|
|
105
|
+
|
|
106
|
+
async def transcript(
|
|
107
|
+
self,
|
|
108
|
+
id: str,
|
|
109
|
+
*,
|
|
110
|
+
since: Optional[int] = None,
|
|
111
|
+
) -> SessionTranscript:
|
|
112
|
+
"""The transcript feed in one read: the ``session``, its ``events``
|
|
113
|
+
after ``since``, the ``total`` stored, and the gateway meter's per-call
|
|
114
|
+
``gateway_calls`` (see :class:`SessionTranscript`)."""
|
|
115
|
+
await self._ensure_ready()
|
|
116
|
+
response = await self._bridge.call('sessions_transcript', self._build_params(id=id, since=since))
|
|
117
|
+
return _require_session_transcript(response)
|
|
118
|
+
|
|
119
|
+
async def download(
|
|
120
|
+
self,
|
|
121
|
+
id: str,
|
|
122
|
+
*,
|
|
123
|
+
to: Optional[str] = None,
|
|
124
|
+
) -> str:
|
|
125
|
+
"""Download a session's raw JSONL trace file. Returns the local path."""
|
|
126
|
+
await self._ensure_ready()
|
|
127
|
+
response = await self._bridge.call('sessions_download', self._build_params(id=id, to=to))
|
|
128
|
+
return response['path']
|
|
129
|
+
|
|
130
|
+
async def browser_replay(
|
|
131
|
+
self,
|
|
132
|
+
id: str,
|
|
133
|
+
*,
|
|
134
|
+
timeout_ms: Optional[int] = None,
|
|
135
|
+
interval_ms: Optional[int] = None,
|
|
136
|
+
) -> BrowserReplay:
|
|
137
|
+
"""Wait for browser replay readiness and return replay/download URLs."""
|
|
138
|
+
await self._ensure_ready()
|
|
139
|
+
effective_timeout_ms = timeout_ms if timeout_ms is not None else 600_000
|
|
140
|
+
effective_interval_ms = interval_ms if interval_ms is not None else 5_000
|
|
141
|
+
if effective_timeout_ms <= 0:
|
|
142
|
+
raise ValueError('timeout_ms must be positive')
|
|
143
|
+
if effective_interval_ms <= 0:
|
|
144
|
+
raise ValueError('interval_ms must be positive')
|
|
145
|
+
rpc_timeout_s = max(1.0, (effective_timeout_ms / 1000) + 30)
|
|
146
|
+
response = await self._bridge.call(
|
|
147
|
+
'sessions_browser_replay',
|
|
148
|
+
self._build_params(id=id, timeout_ms=timeout_ms, interval_ms=interval_ms),
|
|
149
|
+
timeout_s=rpc_timeout_s,
|
|
150
|
+
)
|
|
151
|
+
return _require_browser_replay(response)
|
|
152
|
+
|
|
153
|
+
async def close(self) -> None:
|
|
154
|
+
"""Close the sessions client and release resources."""
|
|
155
|
+
if self._owns_bridge:
|
|
156
|
+
await self._bridge.stop()
|
|
157
|
+
|
|
158
|
+
async def __aenter__(self) -> 'SessionsClient':
|
|
159
|
+
try:
|
|
160
|
+
await self._ensure_ready()
|
|
161
|
+
return self
|
|
162
|
+
except Exception:
|
|
163
|
+
await self.close()
|
|
164
|
+
raise
|
|
165
|
+
|
|
166
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
167
|
+
await self.close()
|
evolve/storage_client.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""StorageClient for browsing and downloading checkpoints."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, Awaitable, Callable, Dict, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
from .results import CheckpointInfo
|
|
7
|
+
from .utils import _decode_files_from_transport, _filter_none, _parse_checkpoint, _require_checkpoint
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StorageClient:
|
|
11
|
+
"""Client for browsing and downloading checkpoints.
|
|
12
|
+
|
|
13
|
+
Created via the standalone ``storage()`` factory or ``Evolve.storage()`` accessor.
|
|
14
|
+
Wraps bridge JSON-RPC calls to the TypeScript SDK's StorageClient.
|
|
15
|
+
|
|
16
|
+
Two creation modes:
|
|
17
|
+
|
|
18
|
+
- **Standalone** (``storage(config)``): Owns its own bridge subprocess.
|
|
19
|
+
Use as an async context manager to ensure cleanup.
|
|
20
|
+
- **Bound** (``evolve.storage()``): Reuses the Evolve instance's bridge.
|
|
21
|
+
No cleanup needed — the Evolve instance manages the bridge.
|
|
22
|
+
|
|
23
|
+
Example (standalone)::
|
|
24
|
+
|
|
25
|
+
from evolve import storage, StorageConfig
|
|
26
|
+
|
|
27
|
+
async with storage(StorageConfig(url='s3://my-bucket/')) as store:
|
|
28
|
+
checkpoints = await store.list_checkpoints(limit=5)
|
|
29
|
+
files = await store.download_files(checkpoints[0].id)
|
|
30
|
+
|
|
31
|
+
Example (bound)::
|
|
32
|
+
|
|
33
|
+
async with Evolve(storage=StorageConfig(url='s3://bucket/')) as evolve:
|
|
34
|
+
store = evolve.storage()
|
|
35
|
+
info = await store.get_checkpoint('ckpt_abc123')
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
bridge: Any,
|
|
41
|
+
storage_config: Any = None,
|
|
42
|
+
*,
|
|
43
|
+
_owns_bridge: bool = False,
|
|
44
|
+
_init_fn: Optional[Callable[[], Awaitable[None]]] = None,
|
|
45
|
+
):
|
|
46
|
+
self._bridge = bridge
|
|
47
|
+
self._config = storage_config # None = use Evolve's initialized config
|
|
48
|
+
self._owns_bridge = _owns_bridge
|
|
49
|
+
# Readiness callback — in bound mode this triggers Evolve._ensure_initialized
|
|
50
|
+
# so the bridge adapter has a live Evolve instance to delegate to.
|
|
51
|
+
self._init_fn = _init_fn
|
|
52
|
+
self._started = False
|
|
53
|
+
self._init_lock = asyncio.Lock()
|
|
54
|
+
|
|
55
|
+
async def _ensure_ready(self) -> None:
|
|
56
|
+
"""Ensure the bridge is started and ready to handle RPC calls."""
|
|
57
|
+
async with self._init_lock:
|
|
58
|
+
if self._started:
|
|
59
|
+
return
|
|
60
|
+
if self._init_fn is not None:
|
|
61
|
+
await self._init_fn()
|
|
62
|
+
else:
|
|
63
|
+
await self._bridge.start()
|
|
64
|
+
self._started = True
|
|
65
|
+
|
|
66
|
+
def _build_params(self, **kwargs: Any) -> Dict[str, Any]:
|
|
67
|
+
"""Build RPC params, including storage config if in standalone mode."""
|
|
68
|
+
params: Dict[str, Any] = {}
|
|
69
|
+
if self._config is not None:
|
|
70
|
+
params['storage'] = self._config.to_dict()
|
|
71
|
+
params.update(_filter_none(kwargs))
|
|
72
|
+
return params
|
|
73
|
+
|
|
74
|
+
async def list_checkpoints(
|
|
75
|
+
self,
|
|
76
|
+
limit: Optional[int] = None,
|
|
77
|
+
tag: Optional[str] = None,
|
|
78
|
+
) -> List[CheckpointInfo]:
|
|
79
|
+
"""List checkpoints sorted by newest first.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
limit: Maximum number of checkpoints to return (default: 100, max: 500)
|
|
83
|
+
tag: Filter by session tag
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
List of CheckpointInfo sorted by newest first
|
|
87
|
+
"""
|
|
88
|
+
await self._ensure_ready()
|
|
89
|
+
params = self._build_params(limit=limit, tag=tag)
|
|
90
|
+
response = await self._bridge.call('storage_list_checkpoints', params)
|
|
91
|
+
return [_require_checkpoint(cp) for cp in response]
|
|
92
|
+
|
|
93
|
+
async def get_checkpoint(self, id: str) -> CheckpointInfo:
|
|
94
|
+
"""Get checkpoint metadata by ID.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
id: Checkpoint ID
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
CheckpointInfo with full metadata
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
Exception: If checkpoint not found
|
|
104
|
+
"""
|
|
105
|
+
await self._ensure_ready()
|
|
106
|
+
params = self._build_params(id=id)
|
|
107
|
+
response = await self._bridge.call('storage_get_checkpoint', params)
|
|
108
|
+
return _require_checkpoint(response)
|
|
109
|
+
|
|
110
|
+
async def download_checkpoint(
|
|
111
|
+
self,
|
|
112
|
+
id: str,
|
|
113
|
+
*,
|
|
114
|
+
to: Optional[str] = None,
|
|
115
|
+
extract: bool = True,
|
|
116
|
+
) -> str:
|
|
117
|
+
"""Download a checkpoint archive.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
id: Checkpoint ID or ``"latest"``
|
|
121
|
+
to: Target directory (default: current working directory)
|
|
122
|
+
extract: Extract the tar.gz archive (default: True).
|
|
123
|
+
If False, saves the raw .tar.gz file.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Path to the extracted directory or saved archive file
|
|
127
|
+
"""
|
|
128
|
+
await self._ensure_ready()
|
|
129
|
+
params = self._build_params(id=id, to=to, extract=extract if not extract else None)
|
|
130
|
+
response = await self._bridge.call('storage_download_checkpoint', params)
|
|
131
|
+
return response['path']
|
|
132
|
+
|
|
133
|
+
async def download_files(
|
|
134
|
+
self,
|
|
135
|
+
id: str,
|
|
136
|
+
*,
|
|
137
|
+
files: Optional[List[str]] = None,
|
|
138
|
+
glob: Optional[List[str]] = None,
|
|
139
|
+
to: Optional[str] = None,
|
|
140
|
+
) -> Dict[str, Union[str, bytes]]:
|
|
141
|
+
"""Download specific files from a checkpoint.
|
|
142
|
+
|
|
143
|
+
For large checkpoints, prefer :meth:`download_checkpoint` which streams
|
|
144
|
+
the archive to disk instead of loading all files into memory.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
id: Checkpoint ID or ``"latest"``
|
|
148
|
+
files: Exact file paths to extract (e.g., ``["workspace/data.txt"]``)
|
|
149
|
+
glob: Glob patterns to match (e.g., ``["workspace/*.txt"]``)
|
|
150
|
+
to: Write files to this directory instead of returning in-memory
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Dict mapping file path to content (str for text, bytes for binary)
|
|
154
|
+
"""
|
|
155
|
+
await self._ensure_ready()
|
|
156
|
+
params = self._build_params(id=id, files=files, glob=glob, to=to)
|
|
157
|
+
response = await self._bridge.call('storage_download_files', params)
|
|
158
|
+
return _decode_files_from_transport(response.get('files', {}))
|
|
159
|
+
|
|
160
|
+
async def close(self) -> None:
|
|
161
|
+
"""Close the storage client and release resources.
|
|
162
|
+
|
|
163
|
+
Only needed for standalone clients (created via ``storage()``).
|
|
164
|
+
Bound clients (from ``Evolve.storage()``) are managed by the Evolve instance.
|
|
165
|
+
"""
|
|
166
|
+
if self._owns_bridge:
|
|
167
|
+
await self._bridge.stop()
|
|
168
|
+
|
|
169
|
+
async def __aenter__(self) -> 'StorageClient':
|
|
170
|
+
try:
|
|
171
|
+
await self._ensure_ready()
|
|
172
|
+
return self
|
|
173
|
+
except Exception:
|
|
174
|
+
await self.close()
|
|
175
|
+
raise
|
|
176
|
+
|
|
177
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
178
|
+
await self.close()
|
evolve/swarm/__init__.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Swarm abstractions for parallel AI agent execution."""
|
|
2
|
+
|
|
3
|
+
from .swarm import Swarm
|
|
4
|
+
from .types import (
|
|
5
|
+
FileMap,
|
|
6
|
+
SwarmConfig,
|
|
7
|
+
BestOfConfig,
|
|
8
|
+
VerifyConfig,
|
|
9
|
+
IndexedMeta,
|
|
10
|
+
ReduceMeta,
|
|
11
|
+
JudgeMeta,
|
|
12
|
+
VerifyMeta,
|
|
13
|
+
BaseMeta,
|
|
14
|
+
OperationType,
|
|
15
|
+
Prompt,
|
|
16
|
+
PromptFn,
|
|
17
|
+
ItemInput,
|
|
18
|
+
SchemaType,
|
|
19
|
+
JudgeDecision,
|
|
20
|
+
VerifyDecision,
|
|
21
|
+
# Callback types
|
|
22
|
+
OnCandidateCompleteCallback,
|
|
23
|
+
OnJudgeCompleteCallback,
|
|
24
|
+
OnWorkerCompleteCallback,
|
|
25
|
+
OnVerifierCompleteCallback,
|
|
26
|
+
)
|
|
27
|
+
from .results import (
|
|
28
|
+
SwarmResult,
|
|
29
|
+
SwarmResultList,
|
|
30
|
+
ReduceResult,
|
|
31
|
+
BestOfResult,
|
|
32
|
+
BestOfInfo,
|
|
33
|
+
VerifyInfo,
|
|
34
|
+
is_swarm_result,
|
|
35
|
+
SWARM_RESULT_BRAND,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
# Main class
|
|
40
|
+
'Swarm',
|
|
41
|
+
# Config types
|
|
42
|
+
'SwarmConfig',
|
|
43
|
+
'BestOfConfig',
|
|
44
|
+
'VerifyConfig',
|
|
45
|
+
# Result types
|
|
46
|
+
'SwarmResult',
|
|
47
|
+
'SwarmResultList',
|
|
48
|
+
'ReduceResult',
|
|
49
|
+
'BestOfResult',
|
|
50
|
+
'BestOfInfo',
|
|
51
|
+
'VerifyInfo',
|
|
52
|
+
# Meta types
|
|
53
|
+
'IndexedMeta',
|
|
54
|
+
'ReduceMeta',
|
|
55
|
+
'JudgeMeta',
|
|
56
|
+
'VerifyMeta',
|
|
57
|
+
'BaseMeta',
|
|
58
|
+
'OperationType',
|
|
59
|
+
# Other types
|
|
60
|
+
'FileMap',
|
|
61
|
+
'Prompt',
|
|
62
|
+
'PromptFn',
|
|
63
|
+
'ItemInput',
|
|
64
|
+
'SchemaType',
|
|
65
|
+
'JudgeDecision',
|
|
66
|
+
'VerifyDecision',
|
|
67
|
+
# Callback types
|
|
68
|
+
'OnCandidateCompleteCallback',
|
|
69
|
+
'OnJudgeCompleteCallback',
|
|
70
|
+
'OnWorkerCompleteCallback',
|
|
71
|
+
'OnVerifierCompleteCallback',
|
|
72
|
+
# Helpers
|
|
73
|
+
'is_swarm_result',
|
|
74
|
+
'SWARM_RESULT_BRAND',
|
|
75
|
+
]
|
evolve/swarm/results.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Result types for Swarm abstractions."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Dict, Generic, List, Literal, Optional, TypeVar, Union
|
|
5
|
+
|
|
6
|
+
from .types import FileMap, IndexedMeta, ReduceMeta, JudgeMeta, VerifyMeta
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
T = TypeVar('T')
|
|
10
|
+
|
|
11
|
+
# Brand for runtime detection of SwarmResult (for chaining)
|
|
12
|
+
SWARM_RESULT_BRAND = "__swarm_result__"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# =============================================================================
|
|
16
|
+
# SWARM RESULT
|
|
17
|
+
# =============================================================================
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class SwarmResult(Generic[T]):
|
|
21
|
+
"""Result from a single worker (map, filter, bestof candidate).
|
|
22
|
+
|
|
23
|
+
Status meanings:
|
|
24
|
+
- "success": Positive outcome (agent succeeded / condition passed)
|
|
25
|
+
- "filtered": Neutral outcome (evaluated but didn't pass condition) - filter only
|
|
26
|
+
- "error": Negative outcome (agent error)
|
|
27
|
+
"""
|
|
28
|
+
status: Literal["success", "filtered", "error"]
|
|
29
|
+
data: Optional[T]
|
|
30
|
+
files: FileMap
|
|
31
|
+
meta: IndexedMeta
|
|
32
|
+
error: Optional[str] = None
|
|
33
|
+
raw_data: Optional[str] = None
|
|
34
|
+
best_of: Optional['BestOfInfo'] = None
|
|
35
|
+
verify: Optional['VerifyInfo'] = None
|
|
36
|
+
|
|
37
|
+
# Brand for runtime detection (used by is_swarm_result() function)
|
|
38
|
+
__swarm_result__: bool = field(default=True, repr=False)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class BestOfInfo(Generic[T]):
|
|
43
|
+
"""BestOf information attached to SwarmResult when map used bestOf option."""
|
|
44
|
+
winner_index: int
|
|
45
|
+
judge_reasoning: str
|
|
46
|
+
judge_meta: JudgeMeta
|
|
47
|
+
candidates: List[SwarmResult[T]]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class VerifyInfo:
|
|
52
|
+
"""Verification info attached to results when verify option used."""
|
|
53
|
+
passed: bool
|
|
54
|
+
reasoning: str
|
|
55
|
+
verify_meta: VerifyMeta
|
|
56
|
+
attempts: int
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# =============================================================================
|
|
60
|
+
# SWARM RESULT LIST
|
|
61
|
+
# =============================================================================
|
|
62
|
+
|
|
63
|
+
class SwarmResultList(List[SwarmResult[T]], Generic[T]):
|
|
64
|
+
"""List of SwarmResults with helper properties.
|
|
65
|
+
|
|
66
|
+
Extends list so all normal list operations work.
|
|
67
|
+
|
|
68
|
+
Getters:
|
|
69
|
+
- `.success` - items with positive outcome
|
|
70
|
+
- `.filtered` - items that didn't pass condition (filter only)
|
|
71
|
+
- `.error` - items that encountered errors
|
|
72
|
+
|
|
73
|
+
Chaining examples:
|
|
74
|
+
- `swarm.reduce(results.success, ...)` - forward only successful
|
|
75
|
+
- `swarm.reduce([*results.success, *results.filtered], ...)` - forward all evaluated
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def success(self) -> List[SwarmResult[T]]:
|
|
80
|
+
"""Returns items with status 'success'."""
|
|
81
|
+
return [r for r in self if r.status == "success"]
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def filtered(self) -> List[SwarmResult[T]]:
|
|
85
|
+
"""Returns items with status 'filtered' (didn't pass condition)."""
|
|
86
|
+
return [r for r in self if r.status == "filtered"]
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def error(self) -> List[SwarmResult[T]]:
|
|
90
|
+
"""Returns items with status 'error'."""
|
|
91
|
+
return [r for r in self if r.status == "error"]
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def from_results(cls, results: List[SwarmResult[T]]) -> 'SwarmResultList[T]':
|
|
95
|
+
"""Create SwarmResultList from a list of SwarmResults."""
|
|
96
|
+
result_list = cls()
|
|
97
|
+
result_list.extend(results)
|
|
98
|
+
return result_list
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# =============================================================================
|
|
102
|
+
# REDUCE RESULT
|
|
103
|
+
# =============================================================================
|
|
104
|
+
|
|
105
|
+
@dataclass
|
|
106
|
+
class ReduceResult(Generic[T]):
|
|
107
|
+
"""Result from reduce operation."""
|
|
108
|
+
status: Literal["success", "error"]
|
|
109
|
+
data: Optional[T]
|
|
110
|
+
files: FileMap
|
|
111
|
+
meta: ReduceMeta
|
|
112
|
+
error: Optional[str] = None
|
|
113
|
+
raw_data: Optional[str] = None
|
|
114
|
+
verify: Optional['VerifyInfo'] = None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# =============================================================================
|
|
118
|
+
# BESTOF RESULT
|
|
119
|
+
# =============================================================================
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class BestOfResult(Generic[T]):
|
|
123
|
+
"""Result from bestOf operation."""
|
|
124
|
+
winner: SwarmResult[T]
|
|
125
|
+
winner_index: int
|
|
126
|
+
judge_reasoning: str
|
|
127
|
+
judge_meta: JudgeMeta
|
|
128
|
+
candidates: List[SwarmResult[T]]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# =============================================================================
|
|
132
|
+
# HELPER FUNCTIONS
|
|
133
|
+
# =============================================================================
|
|
134
|
+
|
|
135
|
+
def is_swarm_result(obj: Any) -> bool:
|
|
136
|
+
"""Check if an object is a SwarmResult (for chaining detection)."""
|
|
137
|
+
return (
|
|
138
|
+
isinstance(obj, SwarmResult) or
|
|
139
|
+
(hasattr(obj, SWARM_RESULT_BRAND) and getattr(obj, SWARM_RESULT_BRAND) is True)
|
|
140
|
+
)
|