agentloop-profiler 0.1.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 (166) hide show
  1. agentloop/__init__.py +136 -0
  2. agentloop/ablation_protocol.py +229 -0
  3. agentloop/ablations.py +564 -0
  4. agentloop/aggregate_cli.py +88 -0
  5. agentloop/aggregate_stats.py +175 -0
  6. agentloop/aggregates.py +532 -0
  7. agentloop/audit.py +39 -0
  8. agentloop/autoinstrument.py +145 -0
  9. agentloop/batch_experiments.py +295 -0
  10. agentloop/batching.py +95 -0
  11. agentloop/budget_types.py +335 -0
  12. agentloop/budgets.py +539 -0
  13. agentloop/calibration.py +872 -0
  14. agentloop/checkpoint_state.py +306 -0
  15. agentloop/checkpoint_store.py +86 -0
  16. agentloop/ci.py +202 -0
  17. agentloop/cli.py +1314 -0
  18. agentloop/client.py +228 -0
  19. agentloop/completion.py +345 -0
  20. agentloop/completion_types.py +199 -0
  21. agentloop/config.py +50 -0
  22. agentloop/context_controls.py +443 -0
  23. agentloop/context_experiments.py +337 -0
  24. agentloop/context_types.py +216 -0
  25. agentloop/costs.py +667 -0
  26. agentloop/cursor_validation.py +48 -0
  27. agentloop/decorators.py +445 -0
  28. agentloop/demo.py +300 -0
  29. agentloop/doctor.py +341 -0
  30. agentloop/drift.py +361 -0
  31. agentloop/drift_cli.py +147 -0
  32. agentloop/drift_types.py +110 -0
  33. agentloop/drift_windows.py +340 -0
  34. agentloop/entrypoint.py +201 -0
  35. agentloop/estimates.py +133 -0
  36. agentloop/events.py +75 -0
  37. agentloop/experiment_artifacts.py +80 -0
  38. agentloop/experiment_observations.py +169 -0
  39. agentloop/experiment_reports.py +545 -0
  40. agentloop/experiment_types.py +355 -0
  41. agentloop/experiments.py +392 -0
  42. agentloop/exporters.py +117 -0
  43. agentloop/finding_benchmark_types.py +131 -0
  44. agentloop/finding_benchmarks.py +424 -0
  45. agentloop/findings.py +419 -0
  46. agentloop/graph.py +464 -0
  47. agentloop/harness.py +825 -0
  48. agentloop/harness_bench/__init__.py +1 -0
  49. agentloop/harness_bench/cli.py +77 -0
  50. agentloop/harness_bench/drivers.py +96 -0
  51. agentloop/harness_bench/offline.py +507 -0
  52. agentloop/harness_evidence.py +585 -0
  53. agentloop/harness_usage.py +91 -0
  54. agentloop/html_report.py +860 -0
  55. agentloop/integrations/__init__.py +1 -0
  56. agentloop/integrations/crewai.py +144 -0
  57. agentloop/integrations/harbor/__init__.py +1 -0
  58. agentloop/integrations/harbor/atif.py +982 -0
  59. agentloop/integrations/harbor/atif_validation.py +329 -0
  60. agentloop/integrations/harbor/cli.py +207 -0
  61. agentloop/integrations/harbor/manifest.py +86 -0
  62. agentloop/integrations/harbor/trial_evidence.py +196 -0
  63. agentloop/integrations/harbor/trials.py +1109 -0
  64. agentloop/integrations/harbor/verifier.py +118 -0
  65. agentloop/integrations/langgraph.py +150 -0
  66. agentloop/integrations/langgraph_harness.py +466 -0
  67. agentloop/integrations/langgraph_recovery.py +366 -0
  68. agentloop/integrations/omnigent/__init__.py +1 -0
  69. agentloop/integrations/omnigent/cli.py +78 -0
  70. agentloop/integrations/omnigent/evidence.py +93 -0
  71. agentloop/integrations/omnigent/telemetry.py +422 -0
  72. agentloop/integrations/openai.py +415 -0
  73. agentloop/integrations/openai_agents.py +291 -0
  74. agentloop/integrations/sdk.py +82 -0
  75. agentloop/integrations/vercel_ai.py +83 -0
  76. agentloop/interoperability/__init__.py +5 -0
  77. agentloop/interoperability/artifacts.py +112 -0
  78. agentloop/interoperability/cli.py +59 -0
  79. agentloop/interoperability/cohort_evidence.py +175 -0
  80. agentloop/interoperability/cohorts.py +681 -0
  81. agentloop/interoperability/contracts.py +427 -0
  82. agentloop/interoperability/coordination.py +554 -0
  83. agentloop/interoperability/evidence.py +215 -0
  84. agentloop/interoperability/otlp_jsonl.py +775 -0
  85. agentloop/interoperability/otlp_validation.py +496 -0
  86. agentloop/interoperability/registry.py +561 -0
  87. agentloop/interoperability/validation.py +333 -0
  88. agentloop/intervention_service.py +110 -0
  89. agentloop/intervention_store.py +118 -0
  90. agentloop/interventions.py +190 -0
  91. agentloop/issues.py +90 -0
  92. agentloop/judgment_adapters.py +125 -0
  93. agentloop/judgment_benchmark_types.py +98 -0
  94. agentloop/judgment_benchmarks.py +606 -0
  95. agentloop/judgment_types.py +241 -0
  96. agentloop/judgment_views.py +38 -0
  97. agentloop/judgments.py +342 -0
  98. agentloop/loop_guards.py +274 -0
  99. agentloop/loop_types.py +66 -0
  100. agentloop/markdown.py +102 -0
  101. agentloop/metrics.py +342 -0
  102. agentloop/migrations.py +303 -0
  103. agentloop/model_routing.py +470 -0
  104. agentloop/onboarding.py +315 -0
  105. agentloop/operations.py +44 -0
  106. agentloop/optimizer.py +199 -0
  107. agentloop/otel.py +715 -0
  108. agentloop/otel_ids.py +81 -0
  109. agentloop/otel_semantics.py +134 -0
  110. agentloop/parallelism.py +141 -0
  111. agentloop/patches.py +603 -0
  112. agentloop/plan_export.py +126 -0
  113. agentloop/policy_promotion.py +325 -0
  114. agentloop/promotion_evidence.py +304 -0
  115. agentloop/promotion_types.py +209 -0
  116. agentloop/quality.py +446 -0
  117. agentloop/ranking.py +529 -0
  118. agentloop/recovery_evidence.py +96 -0
  119. agentloop/replay.py +753 -0
  120. agentloop/retention.py +375 -0
  121. agentloop/retention_types.py +113 -0
  122. agentloop/reuse_experiments.py +247 -0
  123. agentloop/routing_checks.py +116 -0
  124. agentloop/routing_types.py +241 -0
  125. agentloop/rules.py +396 -0
  126. agentloop/runtime.py +286 -0
  127. agentloop/savings.py +158 -0
  128. agentloop/scheduling_plan.py +99 -0
  129. agentloop/scheduling_types.py +169 -0
  130. agentloop/schema.py +257 -0
  131. agentloop/semantic_rules.py +200 -0
  132. agentloop/semantic_waste.py +345 -0
  133. agentloop/semantic_waste_types.py +88 -0
  134. agentloop/server.py +494 -0
  135. agentloop/store.py +1429 -0
  136. agentloop/structural_experiments.py +431 -0
  137. agentloop/structural_types.py +113 -0
  138. agentloop/structured_quality.py +1020 -0
  139. agentloop/studies.py +629 -0
  140. agentloop/study_statistics.py +58 -0
  141. agentloop/substitution_evidence.py +199 -0
  142. agentloop/substitution_export.py +190 -0
  143. agentloop/substitution_reports.py +407 -0
  144. agentloop/substitution_types.py +284 -0
  145. agentloop/substitutions.py +267 -0
  146. agentloop/timing.py +142 -0
  147. agentloop/tokens.py +188 -0
  148. agentloop/tool_scheduling.py +410 -0
  149. agentloop/trace_sources.py +31 -0
  150. agentloop/tracer.py +494 -0
  151. agentloop/value.py +306 -0
  152. agentloop/version.py +3 -0
  153. agentloop/workflow_types.py +264 -0
  154. agentloop/workflows.py +284 -0
  155. agentloop_profiler-0.1.0.dist-info/METADATA +513 -0
  156. agentloop_profiler-0.1.0.dist-info/RECORD +166 -0
  157. agentloop_profiler-0.1.0.dist-info/WHEEL +5 -0
  158. agentloop_profiler-0.1.0.dist-info/entry_points.txt +2 -0
  159. agentloop_profiler-0.1.0.dist-info/licenses/LICENSE +201 -0
  160. agentloop_profiler-0.1.0.dist-info/licenses/THIRD_PARTY_LICENSES.md +101 -0
  161. agentloop_profiler-0.1.0.dist-info/top_level.txt +2 -0
  162. dashboard/__init__.py +5 -0
  163. dashboard/app.py +922 -0
  164. dashboard/streamlit_compat.py +63 -0
  165. dashboard/trace_upload.py +57 -0
  166. dashboard/value_view.py +105 -0
@@ -0,0 +1,118 @@
1
+ """External reward preservation and explicit, hashed acceptance criteria."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import math
7
+ from dataclasses import dataclass
8
+ from hashlib import sha256
9
+ from typing import Any
10
+
11
+ from agentloop.interoperability.contracts import _validate_quality
12
+ from agentloop.interoperability.validation import ImportValidationError, validate_json_tree
13
+ from agentloop.interventions import canonical_json
14
+
15
+
16
+ def _fail(field: str, reason: str) -> None:
17
+ raise ImportValidationError("invalid_verifier", field, reason)
18
+
19
+
20
+ def reward_dimensions(value: Any) -> dict[str, int | float | None] | None:
21
+ if value is None:
22
+ return None
23
+ validate_json_tree(value)
24
+ if not isinstance(value, dict):
25
+ _fail("rewards", "reward dimensions must be an object or null")
26
+ for key, number in value.items():
27
+ if not isinstance(key, str) or not key.strip() or len(key.encode()) > 512:
28
+ _fail("rewards", "reward names must be bounded strings")
29
+ if number is not None and (type(number) not in {int, float} or not math.isfinite(number)):
30
+ _fail("rewards", "rewards must be finite numbers or null")
31
+ return dict(value)
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class ScoringContract:
36
+ _json: str
37
+
38
+ def to_dict(self) -> dict:
39
+ return json.loads(self._json)
40
+
41
+ @classmethod
42
+ def from_dict(cls, value: dict) -> ScoringContract:
43
+ validate_json_tree(value)
44
+ if not isinstance(value, dict) or set(value) - {
45
+ "schema_version",
46
+ "scorer_id",
47
+ "rule",
48
+ "thresholds",
49
+ "config_sha256",
50
+ }:
51
+ _fail("scoring", "unsupported scoring fields")
52
+ required = {"schema_version", "scorer_id", "rule", "thresholds"}
53
+ if not required <= set(value):
54
+ _fail("scoring", "versioned scorer identity, rule and thresholds are required")
55
+ config = {key: value[key] for key in required}
56
+ digest = sha256(canonical_json(config).encode()).hexdigest()
57
+ if value.get("config_sha256", digest) != digest:
58
+ _fail("scoring.config_sha256", "scoring hash does not match its definition")
59
+ config["config_sha256"] = digest
60
+ # Reuse the frozen receipt contract, including boolean/nonfinite rejection.
61
+ _validate_quality(
62
+ {
63
+ "execution_status": "unknown",
64
+ "verifier_status": "missing",
65
+ "verifier_dimensions": {},
66
+ "quality_pass": None, # nosec B105
67
+ "quality_basis": "configured_thresholds",
68
+ "scoring_contract": config,
69
+ "verifier_isolation": "unknown",
70
+ }
71
+ )
72
+ return cls(canonical_json(config))
73
+
74
+ @classmethod
75
+ def all_gte(cls, scorer_id: str, thresholds: dict[str, float]) -> ScoringContract:
76
+ return cls.from_dict(
77
+ {
78
+ "schema_version": "1.0",
79
+ "scorer_id": scorer_id,
80
+ "rule": "all_gte",
81
+ "thresholds": thresholds,
82
+ }
83
+ )
84
+
85
+
86
+ def external_outcome(
87
+ *,
88
+ execution_status: str,
89
+ verifier_status: str,
90
+ dimensions: dict | None,
91
+ isolation: str = "unknown",
92
+ scoring: ScoringContract | None = None,
93
+ ) -> dict:
94
+ rewards = reward_dimensions(dimensions)
95
+ config = scoring.to_dict() if scoring is not None else None
96
+ passed = None
97
+ if (
98
+ config is not None
99
+ and verifier_status == "available"
100
+ and rewards is not None
101
+ and all(rewards.get(key) is not None for key in config["thresholds"])
102
+ ):
103
+ passed = all(rewards[key] >= threshold for key, threshold in config["thresholds"].items())
104
+ result = {
105
+ "execution_status": execution_status,
106
+ "verifier_status": verifier_status,
107
+ "verifier_dimensions": rewards if rewards is not None else {},
108
+ "quality_pass": passed,
109
+ "quality_basis": "configured_thresholds"
110
+ if config is not None
111
+ else "external_uninterpreted_reward"
112
+ if verifier_status == "available" and rewards
113
+ else "unavailable",
114
+ "scoring_contract": config,
115
+ "verifier_isolation": isolation,
116
+ }
117
+ _validate_quality(result)
118
+ return result
@@ -0,0 +1,150 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ from collections.abc import Callable, Mapping, Sequence
5
+ from functools import wraps
6
+ from typing import Any, TypeVar
7
+
8
+ from agentloop import trace_agent, trace_tool_call
9
+
10
+ F = TypeVar("F", bound=Callable[..., Any])
11
+
12
+
13
+ def _merge_metadata(
14
+ metadata: dict[str, Any] | None, extra: dict[str, Any] | None = None
15
+ ) -> dict[str, Any]:
16
+ merged = {"integration": "langgraph"}
17
+ if metadata:
18
+ merged.update(metadata)
19
+ if extra:
20
+ merged.update(extra)
21
+ return merged
22
+
23
+
24
+ def trace_node(name: str | None = None, metadata: dict[str, Any] | None = None) -> Callable[[F], F]:
25
+ """Wrap a LangGraph node function as an AgentLoop node span.
26
+
27
+ This is dependency-free: it works for normal callables, async callables, or
28
+ LangGraph node functions without importing LangGraph inside AgentLoop.
29
+ """
30
+
31
+ def decorator(fn: F) -> F:
32
+ step_name = name or getattr(fn, "__name__", "langgraph_node")
33
+ step_metadata = _merge_metadata(metadata, {"node_name": step_name})
34
+
35
+ if inspect.iscoroutinefunction(fn):
36
+
37
+ @wraps(fn)
38
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
39
+ with trace_tool_call(step_name, metadata=step_metadata):
40
+ return await fn(*args, **kwargs)
41
+
42
+ return async_wrapper # type: ignore[return-value]
43
+
44
+ @wraps(fn)
45
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
46
+ with trace_tool_call(step_name, metadata=step_metadata):
47
+ return fn(*args, **kwargs)
48
+
49
+ return wrapper # type: ignore[return-value]
50
+
51
+ return decorator
52
+
53
+
54
+ def instrument_state_graph(graph: Any, metadata: dict[str, Any] | None = None) -> Any:
55
+ """Monkey-patch a LangGraph StateGraph-like builder so add_node auto-wraps nodes.
56
+
57
+ Usage:
58
+ builder = StateGraph(State)
59
+ instrument_state_graph(builder)
60
+ builder.add_node("retrieve", retrieve_fn) # automatically traced
61
+
62
+ The function intentionally accepts `Any` so AgentLoop does not need LangGraph
63
+ as a dependency. It expects the object to expose an `add_node(name, action, ...)`
64
+ method and an empty `nodes` mapping or sequence. Call this before adding any
65
+ nodes; late instrumentation raises instead of silently producing partial traces.
66
+ """
67
+
68
+ if getattr(graph, "_agentloop_instrumented", False):
69
+ return graph
70
+ original_add_node = getattr(graph, "add_node", None)
71
+ if original_add_node is None or not callable(original_add_node):
72
+ raise TypeError("Expected a LangGraph-like object with callable add_node(...).")
73
+ nodes = getattr(graph, "nodes", None)
74
+ if not isinstance(nodes, Mapping | Sequence) or isinstance(nodes, str | bytes | bytearray):
75
+ raise TypeError("Expected a LangGraph-like builder with a nodes mapping or sequence.")
76
+ if nodes:
77
+ raise ValueError(
78
+ "Call instrument_state_graph() before add_node(); this builder already contains "
79
+ "nodes. Create a new builder and instrument it before adding nodes, or wrap node "
80
+ "functions with trace_node() explicitly before registering them."
81
+ )
82
+
83
+ @wraps(original_add_node)
84
+ def add_node(name: str, action: Any = None, *args: Any, **kwargs: Any) -> Any:
85
+ wrapped_action = action
86
+ if callable(action):
87
+ wrapped_action = trace_node(
88
+ str(name), _merge_metadata(metadata, {"auto_instrumented": True})
89
+ )(action)
90
+ return original_add_node(name, wrapped_action, *args, **kwargs)
91
+
92
+ setattr(graph, "add_node", add_node)
93
+ setattr(graph, "_agentloop_instrumented", True)
94
+ return graph
95
+
96
+
97
+ class TracedRunnable:
98
+ """Small wrapper for compiled LangGraph apps/runnables.
99
+
100
+ It creates an AgentLoop trace around `.invoke(...)`, `.ainvoke(...)`, `.stream(...)`,
101
+ and `.astream(...)` while delegating every other attribute to the wrapped app.
102
+ """
103
+
104
+ def __init__(
105
+ self, app: Any, name: str = "langgraph_app", metadata: dict[str, Any] | None = None
106
+ ):
107
+ self.app = app
108
+ self.name = name
109
+ self.metadata = _merge_metadata(metadata, {"runnable": True})
110
+ self.last_trace = None
111
+
112
+ def __getattr__(self, item: str) -> Any:
113
+ return getattr(self.app, item)
114
+
115
+ def invoke(self, *args: Any, **kwargs: Any) -> Any:
116
+ with trace_agent(self.name, metadata=self.metadata) as trace:
117
+ result = self.app.invoke(*args, **kwargs)
118
+ self.last_trace = trace
119
+ return result
120
+
121
+ async def ainvoke(self, *args: Any, **kwargs: Any) -> Any:
122
+ with trace_agent(self.name, metadata=self.metadata) as trace:
123
+ result = await self.app.ainvoke(*args, **kwargs)
124
+ self.last_trace = trace
125
+ return result
126
+
127
+ def stream(self, *args: Any, **kwargs: Any) -> Any:
128
+ with trace_agent(self.name, metadata=self.metadata) as trace:
129
+ for item in self.app.stream(*args, **kwargs):
130
+ yield item
131
+ self.last_trace = trace
132
+
133
+ async def astream(self, *args: Any, **kwargs: Any) -> Any:
134
+ with trace_agent(self.name, metadata=self.metadata) as trace:
135
+ async for item in self.app.astream(*args, **kwargs):
136
+ yield item
137
+ self.last_trace = trace
138
+
139
+ def export_last_trace(self, path: str) -> None:
140
+ if self.last_trace is None:
141
+ raise RuntimeError("No trace recorded yet. Call invoke/ainvoke/stream/astream first.")
142
+ self.last_trace.export_json(path)
143
+
144
+
145
+ def trace_runnable(
146
+ app: Any, name: str = "langgraph_app", metadata: dict[str, Any] | None = None
147
+ ) -> TracedRunnable:
148
+ """Wrap a compiled LangGraph app/runnable with AgentLoop tracing."""
149
+
150
+ return TracedRunnable(app=app, name=name, metadata=metadata)