agenticml-lite 1.0.0__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.
Files changed (52) hide show
  1. agenticml/__init__.py +63 -0
  2. agenticml/__main__.py +5 -0
  3. agenticml/_frame_inspect.py +499 -0
  4. agenticml/_globals.py +308 -0
  5. agenticml/_import_hooks.py +127 -0
  6. agenticml/_source_inspect.py +253 -0
  7. agenticml/auth.py +314 -0
  8. agenticml/auto_metrics.py +160 -0
  9. agenticml/auto_tracking.py +998 -0
  10. agenticml/cli.py +611 -0
  11. agenticml/client.py +382 -0
  12. agenticml/code_tracking.py +614 -0
  13. agenticml/component_tracking.py +640 -0
  14. agenticml/distributed.py +35 -0
  15. agenticml/env.py +51 -0
  16. agenticml/ignore.py +118 -0
  17. agenticml/integrations/__init__.py +1 -0
  18. agenticml/integrations/hyperparams.py +265 -0
  19. agenticml/integrations/tensorboard.py +245 -0
  20. agenticml/launch.py +343 -0
  21. agenticml/lineage.py +522 -0
  22. agenticml/media.py +141 -0
  23. agenticml/model_tracking.py +245 -0
  24. agenticml/run.py +1386 -0
  25. agenticml/store/__init__.py +54 -0
  26. agenticml/store/gc.py +648 -0
  27. agenticml/store/layout.py +463 -0
  28. agenticml/store/notes.py +700 -0
  29. agenticml/store/objects.py +115 -0
  30. agenticml/store/overview.py +440 -0
  31. agenticml/store/reader.py +882 -0
  32. agenticml/store/writer.py +1203 -0
  33. agenticml/sync.py +1113 -0
  34. agenticml/system_metrics.py +151 -0
  35. agenticml/ui/__init__.py +7 -0
  36. agenticml/ui/server.py +1739 -0
  37. agenticml/ui/static/InstrumentSans.OFL.txt +93 -0
  38. agenticml/ui/static/InstrumentSans.woff2 +0 -0
  39. agenticml/ui/static/JetBrainsMono.OFL.txt +93 -0
  40. agenticml/ui/static/JetBrainsMono.woff2 +0 -0
  41. agenticml/ui/static/app.css +3109 -0
  42. agenticml/ui/static/app.js +3570 -0
  43. agenticml/ui/static/charts.js +427 -0
  44. agenticml/ui/static/code.js +777 -0
  45. agenticml/ui/static/codediff.js +471 -0
  46. agenticml/ui/static/index.html +29 -0
  47. agenticml_lite-1.0.0.dist-info/METADATA +283 -0
  48. agenticml_lite-1.0.0.dist-info/RECORD +52 -0
  49. agenticml_lite-1.0.0.dist-info/WHEEL +5 -0
  50. agenticml_lite-1.0.0.dist-info/entry_points.txt +2 -0
  51. agenticml_lite-1.0.0.dist-info/licenses/LICENSE +21 -0
  52. agenticml_lite-1.0.0.dist-info/top_level.txt +1 -0
agenticml/__init__.py ADDED
@@ -0,0 +1,63 @@
1
+ """agenticml — Python SDK for the AgenticML platform.
2
+
3
+ Install as ``agenticml-lite``, import as ``agenticml``.
4
+
5
+ Quickstart::
6
+
7
+ import agenticml
8
+
9
+ agenticml.init(project="demo", name="exp1", config={"lr": 0.01})
10
+ for step in range(10):
11
+ agenticml.log({"loss": 1 / (step + 1)})
12
+ agenticml.summary["best_loss"] = 0.1
13
+ agenticml.finish()
14
+
15
+ See https://agenticml.xyz for the full reference.
16
+ """
17
+
18
+ from ._globals import (
19
+ config,
20
+ finish,
21
+ get_active_run,
22
+ init,
23
+ log,
24
+ log_artifact,
25
+ summary,
26
+ track_dataloader,
27
+ track_files,
28
+ track_loss,
29
+ track_model,
30
+ track_optimizer,
31
+ )
32
+ from .distributed import get_rank, is_main_process
33
+ from .media import Image
34
+ from .run import Run
35
+
36
+ __version__ = "1.0.0"
37
+
38
+ __all__ = [
39
+ "Image",
40
+ "Run",
41
+ "__version__",
42
+ "config",
43
+ "finish",
44
+ "get_active_run",
45
+ "get_rank",
46
+ "init",
47
+ "is_main_process",
48
+ "log",
49
+ "log_artifact",
50
+ "summary",
51
+ "track_dataloader",
52
+ "track_files",
53
+ "track_loss",
54
+ "track_model",
55
+ "track_optimizer",
56
+ ]
57
+
58
+
59
+ def __getattr__(name):
60
+ if name == "Client":
61
+ from .client import Client
62
+ return Client
63
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
agenticml/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ """`python -m agenticml ...` — same as the `agenticml` command."""
2
+
3
+ from .cli import main
4
+
5
+ raise SystemExit(main())
@@ -0,0 +1,499 @@
1
+ """Resolve caller variable names and loss expressions without importing torch."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import functools
7
+ import inspect
8
+ import linecache
9
+ import sys
10
+ import textwrap
11
+ import types
12
+ from typing import Any
13
+
14
+ from .component_tracking import sanitize_component_name
15
+
16
+ # Longest first so "_optimizer" is removed before "_optim".
17
+ _TRAILING_SUFFIXES = (
18
+ "_optimizer",
19
+ "_criterion",
20
+ "_dataset",
21
+ "_loader",
22
+ "_model",
23
+ "_optim",
24
+ "_loss",
25
+ )
26
+ _SCOPE_NODES = (
27
+ ast.FunctionDef,
28
+ ast.AsyncFunctionDef,
29
+ ast.Lambda,
30
+ ast.ClassDef,
31
+ )
32
+ _MISSING = object()
33
+
34
+
35
+ def infer_variable_name(
36
+ obj: Any, start_frame: types.FrameType | None = None
37
+ ) -> str | None:
38
+ """Return the nearest non-agenticml variable name bound to ``obj``."""
39
+ try:
40
+ frame: Any = start_frame if start_frame is not None else sys._getframe(1)
41
+ except ValueError:
42
+ return None
43
+ seen: set[int] = set()
44
+ while frame is not None and id(frame) not in seen:
45
+ seen.add(id(frame))
46
+ try:
47
+ internal = _is_agenticml_frame(frame)
48
+ name = None if internal else _bound_name(frame, obj)
49
+ nxt = getattr(frame, "f_back", None)
50
+ except Exception:
51
+ break
52
+ if name is not None:
53
+ return name
54
+ frame = nxt
55
+ return None
56
+
57
+
58
+ def normalize_component_name(raw: str | None) -> str:
59
+ """Strip one trailing component suffix and sanitize the name."""
60
+ if not isinstance(raw, str):
61
+ return "default"
62
+ text = raw.strip()
63
+ for suffix in _TRAILING_SUFFIXES:
64
+ if text.endswith(suffix):
65
+ text = text[: -len(suffix)]
66
+ break
67
+ if not text.strip():
68
+ return "default"
69
+ return sanitize_component_name(text)
70
+
71
+
72
+ def resolve_loss_from_backward_frame(
73
+ frame: types.FrameType,
74
+ loss_tensor_name: str | None = None,
75
+ ) -> dict[str, Any]:
76
+ """Find the loss callable assigned in the frame that called ``backward()``."""
77
+ try:
78
+ return _resolve_loss(frame, loss_tensor_name)
79
+ except Exception:
80
+ return _failure_result(frame)
81
+
82
+
83
+ def _is_agenticml_frame(frame: Any) -> bool:
84
+ try:
85
+ module = frame.f_globals.get("__name__", "")
86
+ except Exception:
87
+ return False
88
+ return isinstance(module, str) and module.startswith("agenticml")
89
+
90
+
91
+ def _bound_name(frame: Any, obj: Any) -> str | None:
92
+ for attr in ("f_locals", "f_globals"):
93
+ name = _mapping_name(getattr(frame, attr, None), obj)
94
+ if name is not None:
95
+ return name
96
+ return None
97
+
98
+
99
+ def _mapping_name(mapping: Any, obj: Any) -> str | None:
100
+ if mapping is None:
101
+ return None
102
+ try:
103
+ items = list(mapping.items())
104
+ except Exception:
105
+ return None
106
+ for key, value in items:
107
+ if isinstance(key, str) and value is obj:
108
+ return key
109
+ return None
110
+
111
+
112
+ def _resolve_loss(frame: Any, loss_tensor_name: str | None) -> dict[str, Any]:
113
+ filename, lineno = _frame_file_line(frame)
114
+ parsed = _parse_frame(frame)
115
+ if parsed is None or lineno is None:
116
+ return _result(
117
+ "inline_expression",
118
+ None,
119
+ _line_text(filename, lineno, None, 0),
120
+ filename,
121
+ lineno,
122
+ )
123
+ source, tree, offset = parsed
124
+ found = _find_loss_expr(tree, lineno, offset, loss_tensor_name)
125
+ if found is None:
126
+ return _result(
127
+ "inline_expression",
128
+ None,
129
+ _line_text(filename, lineno, source, offset),
130
+ filename,
131
+ lineno,
132
+ )
133
+ expr, line = found
134
+ expression = _segment(source, expr)
135
+ if expression is None:
136
+ expression = _line_text(filename, line, source, offset)
137
+ resolved = _callable_from_expr(expr, frame)
138
+ if resolved is not None:
139
+ return _result("ast_assignment", resolved, expression, filename, line)
140
+ return _result("inline_expression", None, expression, filename, line)
141
+
142
+
143
+ def _failure_result(frame: Any) -> dict[str, Any]:
144
+ filename, lineno = _frame_file_line(frame)
145
+ return _result(
146
+ "inline_expression",
147
+ None,
148
+ _line_text(filename, lineno, None, 0),
149
+ filename,
150
+ lineno,
151
+ )
152
+
153
+
154
+ def _result(
155
+ evidence: str,
156
+ resolved: Any,
157
+ expression: str | None,
158
+ filename: str | None,
159
+ line: int | None,
160
+ ) -> dict[str, Any]:
161
+ return {
162
+ "evidence": evidence,
163
+ "callable": resolved,
164
+ "expression": expression,
165
+ "file": filename,
166
+ "line": line,
167
+ }
168
+
169
+
170
+ def _frame_file_line(frame: Any) -> tuple[str | None, int | None]:
171
+ filename = None
172
+ lineno = None
173
+ try:
174
+ raw_name = getattr(getattr(frame, "f_code", None), "co_filename", None)
175
+ if isinstance(raw_name, str) and raw_name:
176
+ filename = raw_name
177
+ except Exception:
178
+ filename = None
179
+ try:
180
+ raw_line = getattr(frame, "f_lineno", None)
181
+ if isinstance(raw_line, int) and raw_line >= 1:
182
+ lineno = raw_line
183
+ except Exception:
184
+ lineno = None
185
+ return filename, lineno
186
+
187
+
188
+ def _parse_frame(frame: Any) -> tuple[str, ast.AST, int] | None:
189
+ filename, _lineno = _frame_file_line(frame)
190
+ if filename:
191
+ source = _cached_source(filename)
192
+ if source:
193
+ try:
194
+ return source, ast.parse(source), 0
195
+ except (SyntaxError, ValueError):
196
+ pass
197
+ code = None
198
+ try:
199
+ code = getattr(frame, "f_code", None)
200
+ except Exception:
201
+ return None
202
+ if code is None:
203
+ return None
204
+ try:
205
+ lines, start = inspect.getsourcelines(code)
206
+ except (OSError, TypeError):
207
+ return None
208
+ source = textwrap.dedent("".join(lines))
209
+ try:
210
+ tree = ast.parse(source)
211
+ except (SyntaxError, ValueError):
212
+ return None
213
+ if not isinstance(start, int) or start < 1:
214
+ start = 1
215
+ return source, tree, start - 1
216
+
217
+
218
+ def _cached_source(filename: str) -> str | None:
219
+ try:
220
+ linecache.checkcache(filename)
221
+ lines = linecache.getlines(filename)
222
+ except Exception:
223
+ return None
224
+ if not lines:
225
+ return None
226
+ return "".join(lines)
227
+
228
+
229
+ def _find_loss_expr(
230
+ tree: ast.AST,
231
+ file_lineno: int,
232
+ offset: int,
233
+ loss_tensor_name: str | None,
234
+ ) -> tuple[ast.expr, int] | None:
235
+ scope = _enclosing_scope(tree, file_lineno, offset)
236
+ if loss_tensor_name is not None:
237
+ return _last_binding(scope, loss_tensor_name, file_lineno, offset)
238
+ call = _backward_call(scope, file_lineno, offset)
239
+ if call is None or not isinstance(call.func, ast.Attribute):
240
+ return None
241
+ receiver = call.func.value
242
+ if isinstance(receiver, ast.Name):
243
+ return _last_binding(scope, receiver.id, file_lineno, offset)
244
+ if isinstance(receiver, ast.NamedExpr):
245
+ line = _file_line(receiver, offset)
246
+ if line is None:
247
+ return None
248
+ return receiver.value, line
249
+ line = _file_line(receiver, offset)
250
+ if line is None:
251
+ return None
252
+ return receiver, line
253
+
254
+
255
+ def _enclosing_scope(tree: ast.AST, file_lineno: int, offset: int) -> ast.AST:
256
+ best: ast.AST = tree
257
+ best_span: int | None = None
258
+ for node in ast.walk(tree):
259
+ if not isinstance(node, _SCOPE_NODES):
260
+ continue
261
+ start = _file_line(node, offset)
262
+ end = _file_end(node, offset)
263
+ if start is None or end is None or not (start <= file_lineno <= end):
264
+ continue
265
+ span = end - start
266
+ if best_span is None or span <= best_span:
267
+ best = node
268
+ best_span = span
269
+ return best
270
+
271
+
272
+ def _backward_call(scope: ast.AST, file_lineno: int, offset: int) -> ast.Call | None:
273
+ best: ast.Call | None = None
274
+ best_span: int | None = None
275
+ for node in _iter_nodes(scope):
276
+ if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Attribute):
277
+ continue
278
+ if node.func.attr != "backward":
279
+ continue
280
+ start = _file_line(node, offset)
281
+ end = _file_end(node, offset)
282
+ if start is None or end is None or not (start <= file_lineno <= end):
283
+ continue
284
+ span = end - start
285
+ if best_span is None or span < best_span:
286
+ best = node
287
+ best_span = span
288
+ return best
289
+
290
+
291
+ def _last_binding(
292
+ scope: ast.AST, name: str, file_lineno: int, offset: int
293
+ ) -> tuple[ast.expr, int] | None:
294
+ found: tuple[ast.expr, int] | None = None
295
+ for node in _iter_nodes(scope):
296
+ value = _binding_value(node, name)
297
+ if value is None:
298
+ continue
299
+ line = _file_line(node, offset)
300
+ if line is None or line > file_lineno:
301
+ continue
302
+ if found is None or line >= found[1]:
303
+ found = (value, line)
304
+ return found
305
+
306
+
307
+ def _binding_value(node: ast.AST, name: str) -> ast.expr | None:
308
+ if isinstance(node, ast.Assign):
309
+ for target in node.targets:
310
+ if _target_binds(target, name):
311
+ return node.value
312
+ return None
313
+ if isinstance(node, ast.AnnAssign) and node.value is not None:
314
+ if _target_binds(node.target, name):
315
+ return node.value
316
+ return None
317
+ if (
318
+ isinstance(node, ast.NamedExpr)
319
+ and isinstance(node.target, ast.Name)
320
+ and node.target.id == name
321
+ ):
322
+ return node.value
323
+ return None
324
+
325
+
326
+ def _target_binds(target: ast.AST, name: str) -> bool:
327
+ if isinstance(target, ast.Name):
328
+ return target.id == name
329
+ if isinstance(target, (ast.Tuple, ast.List)):
330
+ return any(_target_binds(elt, name) for elt in target.elts)
331
+ if isinstance(target, ast.Starred):
332
+ return _target_binds(target.value, name)
333
+ return False
334
+
335
+
336
+ def _iter_nodes(root: ast.AST):
337
+ stack = [root]
338
+ while stack:
339
+ node = stack.pop()
340
+ yield node
341
+ children = [
342
+ child
343
+ for child in ast.iter_child_nodes(node)
344
+ if not isinstance(child, _SCOPE_NODES)
345
+ ]
346
+ stack.extend(reversed(children))
347
+
348
+
349
+ def _file_line(node: ast.AST, offset: int) -> int | None:
350
+ lineno = getattr(node, "lineno", None)
351
+ if not isinstance(lineno, int):
352
+ return None
353
+ return lineno + offset
354
+
355
+
356
+ def _file_end(node: ast.AST, offset: int) -> int | None:
357
+ end = getattr(node, "end_lineno", None)
358
+ if not isinstance(end, int):
359
+ return _file_line(node, offset)
360
+ return end + offset
361
+
362
+
363
+ def _segment(source: str, node: ast.AST) -> str | None:
364
+ try:
365
+ text = ast.get_source_segment(source, node)
366
+ except Exception:
367
+ return None
368
+ if not text:
369
+ return None
370
+ stripped = text.strip()
371
+ return stripped or None
372
+
373
+
374
+ def _line_text(
375
+ filename: str | None,
376
+ lineno: int | None,
377
+ source: str | None,
378
+ offset: int,
379
+ ) -> str | None:
380
+ if source is not None and lineno is not None:
381
+ index = lineno - offset - 1
382
+ lines = source.splitlines()
383
+ if 0 <= index < len(lines):
384
+ text = lines[index].strip()
385
+ if text:
386
+ return text
387
+ if not filename or lineno is None:
388
+ return None
389
+ try:
390
+ text = linecache.getline(filename, lineno).strip()
391
+ except Exception:
392
+ return None
393
+ return text or None
394
+
395
+
396
+ def _callable_from_expr(expr: ast.expr, frame: Any) -> Any:
397
+ try:
398
+ if not isinstance(expr, ast.Call):
399
+ return None
400
+ resolved = _callable_from_call(expr, frame)
401
+ except Exception:
402
+ return None
403
+ if _is_callable(resolved):
404
+ return resolved
405
+ return None
406
+
407
+
408
+ def _callable_from_call(call: ast.Call, frame: Any) -> Any:
409
+ func = call.func
410
+ if isinstance(func, ast.Call):
411
+ return _partial_from_call(func, frame)
412
+ return _resolve_callee(func, frame)
413
+
414
+
415
+ def _partial_from_call(call: ast.Call, frame: Any) -> Any:
416
+ if _resolve_callee(call.func, frame) is not functools.partial:
417
+ return None
418
+ if not call.args:
419
+ return None
420
+ wrapped = _static_value(call.args[0], frame)
421
+ if not _is_callable(wrapped):
422
+ return None
423
+ extra: list[Any] = []
424
+ for arg in call.args[1:]:
425
+ value = _static_value(arg, frame)
426
+ if value is _MISSING:
427
+ return wrapped
428
+ extra.append(value)
429
+ keywords: dict[str, Any] = {}
430
+ for keyword in call.keywords:
431
+ if keyword.arg is None:
432
+ return wrapped
433
+ value = _static_value(keyword.value, frame)
434
+ if value is _MISSING:
435
+ return wrapped
436
+ keywords[keyword.arg] = value
437
+ try:
438
+ return functools.partial(wrapped, *extra, **keywords)
439
+ except Exception:
440
+ return wrapped
441
+
442
+
443
+ def _resolve_callee(expr: ast.expr, frame: Any) -> Any:
444
+ if isinstance(expr, (ast.Name, ast.Attribute)):
445
+ return _eval_expr(expr, frame)
446
+ return _MISSING
447
+
448
+
449
+ def _eval_expr(expr: ast.expr, frame: Any) -> Any:
450
+ if isinstance(expr, ast.Name):
451
+ return _lookup(expr.id, frame)
452
+ if isinstance(expr, ast.Attribute):
453
+ base = _eval_expr(expr.value, frame)
454
+ if base is _MISSING:
455
+ return _MISSING
456
+ try:
457
+ return getattr(base, expr.attr)
458
+ except Exception:
459
+ return _MISSING
460
+ return _MISSING
461
+
462
+
463
+ def _static_value(expr: ast.expr, frame: Any) -> Any:
464
+ if isinstance(expr, ast.Constant):
465
+ return expr.value
466
+ if isinstance(expr, ast.UnaryOp) and isinstance(expr.operand, ast.Constant):
467
+ value = expr.operand.value
468
+ if isinstance(expr.op, ast.USub):
469
+ try:
470
+ return -value
471
+ except Exception:
472
+ return _MISSING
473
+ if isinstance(expr.op, ast.UAdd):
474
+ return value
475
+ if isinstance(expr, (ast.Name, ast.Attribute)):
476
+ return _eval_expr(expr, frame)
477
+ return _MISSING
478
+
479
+
480
+ def _lookup(name: str, frame: Any) -> Any:
481
+ for attr in ("f_locals", "f_globals"):
482
+ mapping = getattr(frame, attr, None)
483
+ if mapping is None:
484
+ continue
485
+ try:
486
+ if name in mapping:
487
+ return mapping[name]
488
+ except Exception:
489
+ continue
490
+ return _MISSING
491
+
492
+
493
+ def _is_callable(obj: Any) -> bool:
494
+ if obj is None or obj is _MISSING:
495
+ return False
496
+ try:
497
+ return callable(obj)
498
+ except Exception:
499
+ return False