plato-sdk-v2 2.1.11__py3-none-any.whl → 2.2.4__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.
plato/agents/callback.py DELETED
@@ -1,246 +0,0 @@
1
- """Chronos callback utilities for agents.
2
-
3
- This module provides utilities for agents to communicate with Chronos,
4
- including pushing logs, uploading trajectories, and uploading zipped logs.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import base64
10
- import io
11
- import json
12
- import logging
13
- import zipfile
14
- from pathlib import Path
15
- from typing import Any
16
-
17
- import httpx
18
-
19
- logger = logging.getLogger(__name__)
20
-
21
-
22
- class ChronosCallback:
23
- """Utility class for communicating with Chronos server.
24
-
25
- Handles pushing logs, updating status, and uploading artifacts
26
- (trajectory and zipped logs) to the Chronos callback endpoints.
27
-
28
- Example:
29
- callback = ChronosCallback(
30
- callback_url="http://chronos.example.com/api/callback",
31
- session_id="abc123",
32
- )
33
-
34
- # Push logs during execution
35
- await callback.push_logs([
36
- {"level": "info", "message": "Starting agent..."},
37
- ])
38
-
39
- # After agent completes, upload artifacts
40
- await callback.upload_artifacts(logs_dir="/path/to/logs")
41
- """
42
-
43
- def __init__(self, callback_url: str, session_id: str):
44
- """Initialize the callback client.
45
-
46
- Args:
47
- callback_url: Full callback base URL (e.g., http://server/api/callback)
48
- session_id: The Chronos session ID for this run
49
- """
50
- self.callback_url = callback_url.rstrip("/")
51
- self.session_id = session_id
52
- self._enabled = bool(callback_url and session_id)
53
-
54
- @property
55
- def enabled(self) -> bool:
56
- """Check if callbacks are enabled (both server and session_id set)."""
57
- return self._enabled
58
-
59
- async def push_logs(self, logs: list[dict[str, Any]]) -> bool:
60
- """Push log entries to Chronos.
61
-
62
- Args:
63
- logs: List of log entries, each with 'level' and 'message' keys.
64
- Optional keys: 'timestamp', 'extra'
65
-
66
- Returns:
67
- True if logs were pushed successfully, False otherwise.
68
- """
69
- if not self._enabled:
70
- return False
71
-
72
- try:
73
- async with httpx.AsyncClient(timeout=10.0) as client:
74
- response = await client.post(
75
- f"{self.callback_url}/logs",
76
- json={"session_id": self.session_id, "logs": logs},
77
- )
78
- return response.status_code == 200
79
- except Exception as e:
80
- logger.warning(f"Failed to push logs to Chronos: {e}")
81
- return False
82
-
83
- async def push_log(
84
- self,
85
- message: str,
86
- level: str = "info",
87
- extra: dict[str, Any] | None = None,
88
- ) -> bool:
89
- """Push a single log entry to Chronos.
90
-
91
- Args:
92
- message: Log message
93
- level: Log level (debug, info, warning, error)
94
- extra: Optional additional structured data
95
-
96
- Returns:
97
- True if log was pushed successfully, False otherwise.
98
- """
99
- log_entry: dict[str, Any] = {"level": level, "message": message}
100
- if extra:
101
- log_entry["extra"] = extra
102
- return await self.push_logs([log_entry])
103
-
104
- async def update_status(
105
- self,
106
- status: str,
107
- message: str | None = None,
108
- extra: dict[str, Any] | None = None,
109
- ) -> bool:
110
- """Update the session status in Chronos.
111
-
112
- Args:
113
- status: New status (running, completed, failed)
114
- message: Optional status message
115
- extra: Optional additional data
116
-
117
- Returns:
118
- True if status was updated successfully, False otherwise.
119
- """
120
- if not self._enabled:
121
- return False
122
-
123
- try:
124
- async with httpx.AsyncClient(timeout=10.0) as client:
125
- response = await client.post(
126
- f"{self.callback_url}/status",
127
- json={
128
- "session_id": self.session_id,
129
- "status": status,
130
- "message": message,
131
- "extra": extra,
132
- },
133
- )
134
- return response.status_code == 200
135
- except Exception as e:
136
- logger.warning(f"Failed to update status in Chronos: {e}")
137
- return False
138
-
139
- def find_trajectory(self, logs_dir: str) -> dict[str, Any] | None:
140
- """Find and load ATIF trajectory from logs directory.
141
-
142
- Agents write ATIF trajectory to /logs/agent/trajectory.json after
143
- completing their run. This method finds and loads that file.
144
-
145
- Args:
146
- logs_dir: Path to the logs directory
147
-
148
- Returns:
149
- Parsed ATIF trajectory dict if found, None otherwise.
150
- """
151
- trajectory_path = Path(logs_dir) / "agent" / "trajectory.json"
152
-
153
- if not trajectory_path.exists():
154
- return None
155
-
156
- try:
157
- with open(trajectory_path) as f:
158
- data = json.load(f)
159
- if isinstance(data, dict) and "schema_version" in data:
160
- logger.info(f"Found ATIF trajectory: {trajectory_path}")
161
- return data
162
- return None
163
- except Exception as e:
164
- logger.warning(f"Failed to load trajectory from {trajectory_path}: {e}")
165
- return None
166
-
167
- def zip_logs_dir(self, logs_dir: str) -> bytes:
168
- """Zip the entire logs directory.
169
-
170
- Args:
171
- logs_dir: Path to the logs directory
172
-
173
- Returns:
174
- Zip file contents as bytes.
175
- """
176
- logs_path = Path(logs_dir)
177
- buffer = io.BytesIO()
178
-
179
- with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as zf:
180
- for file_path in logs_path.rglob("*"):
181
- if file_path.is_file():
182
- arcname = file_path.relative_to(logs_path)
183
- zf.write(file_path, arcname)
184
-
185
- buffer.seek(0)
186
- return buffer.read()
187
-
188
- async def upload_artifacts(
189
- self,
190
- logs_dir: str,
191
- trajectory: dict[str, Any] | None = None,
192
- ) -> bool:
193
- """Upload trajectory and zipped logs to Chronos.
194
-
195
- If trajectory is not provided, attempts to find it in the logs directory.
196
- The logs directory is zipped and uploaded to S3 via Chronos.
197
-
198
- Args:
199
- logs_dir: Path to the logs directory
200
- trajectory: Optional pre-loaded trajectory dict. If None, will
201
- attempt to find trajectory.json in logs_dir.
202
-
203
- Returns:
204
- True if artifacts were uploaded successfully, False otherwise.
205
- """
206
- if not self._enabled:
207
- return False
208
-
209
- # Find trajectory if not provided
210
- if trajectory is None:
211
- trajectory = self.find_trajectory(logs_dir)
212
- if trajectory:
213
- logger.info("Found ATIF trajectory in logs directory")
214
- else:
215
- logger.info("No ATIF trajectory found in logs directory")
216
-
217
- # Zip logs directory
218
- logs_base64: str | None = None
219
- try:
220
- logs_zip = self.zip_logs_dir(logs_dir)
221
- logs_base64 = base64.b64encode(logs_zip).decode("utf-8")
222
- logger.info(f"Zipped logs: {len(logs_zip)} bytes")
223
- except Exception as e:
224
- logger.warning(f"Failed to zip logs: {e}")
225
-
226
- # Upload to Chronos
227
- try:
228
- async with httpx.AsyncClient(timeout=60.0) as client:
229
- response = await client.post(
230
- f"{self.callback_url}/artifacts",
231
- json={
232
- "session_id": self.session_id,
233
- "trajectory": trajectory,
234
- "logs_base64": logs_base64,
235
- },
236
- )
237
- if response.status_code == 200:
238
- result = response.json()
239
- logger.info(f"Uploaded artifacts to Chronos: {result}")
240
- return True
241
- else:
242
- logger.warning(f"Failed to upload artifacts: {response.status_code} {response.text}")
243
- return False
244
- except Exception as e:
245
- logger.warning(f"Failed to upload artifacts to Chronos: {e}")
246
- return False