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,175 @@
1
+ """Bounded mergeable descriptive statistics; no inferred performance findings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import bisect
6
+ import math
7
+ from dataclasses import dataclass
8
+
9
+
10
+ def number(value):
11
+ if type(value) not in {int, float} or value < 0:
12
+ raise ValueError("aggregate values must be finite and nonnegative")
13
+ try:
14
+ if not math.isfinite(value):
15
+ raise ValueError("aggregate values must be finite and nonnegative")
16
+ except OverflowError:
17
+ raise ValueError("aggregate numeric overflow") from None
18
+ return value
19
+
20
+
21
+ def total(left, right):
22
+ return number(left + right)
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class AggregateConfig:
27
+ latency_bounds_ms: tuple[float, ...] = (1, 5, 10, 50, 100, 500, 1000, 5000, 10000, 60000)
28
+ heavy_hitter_capacity: int = 32
29
+
30
+ def __post_init__(self):
31
+ if (
32
+ not isinstance(self.latency_bounds_ms, tuple)
33
+ or not 1 <= len(self.latency_bounds_ms) <= 256
34
+ ):
35
+ raise ValueError("latency bounds must be a tuple of 1..256 increasing positive values")
36
+ previous = 0
37
+ for bound in self.latency_bounds_ms:
38
+ if number(bound) <= previous:
39
+ raise ValueError("latency bounds must be strictly increasing and positive")
40
+ previous = bound
41
+ if (
42
+ type(self.heavy_hitter_capacity) is not int
43
+ or not 1 <= self.heavy_hitter_capacity <= 1024
44
+ ):
45
+ raise ValueError("heavy_hitter_capacity must be 1..1024")
46
+
47
+ def to_dict(self):
48
+ return {
49
+ "latency_bounds_ms": list(self.latency_bounds_ms),
50
+ "heavy_hitter_capacity": self.heavy_hitter_capacity,
51
+ }
52
+
53
+
54
+ class Histogram:
55
+ def __init__(self, bounds):
56
+ self.bounds = tuple(bounds)
57
+ self.counts = [0] * (len(bounds) + 1)
58
+ self.count = 0
59
+ self.sum = 0.0
60
+ self.minimum = self.maximum = None
61
+
62
+ def add(self, value):
63
+ number(value)
64
+ updated = total(self.sum, value)
65
+ self.counts[bisect.bisect_left(self.bounds, value)] += 1
66
+ self.count += 1
67
+ self.sum = updated
68
+ self.minimum = value if self.minimum is None else min(self.minimum, value)
69
+ self.maximum = value if self.maximum is None else max(self.maximum, value)
70
+
71
+ def merge(self, other):
72
+ if self.bounds != other.bounds:
73
+ raise ValueError("incompatible histogram bounds")
74
+ updated = total(self.sum, other.sum)
75
+ self.counts = [a + b for a, b in zip(self.counts, other.counts)]
76
+ self.count += other.count
77
+ self.sum = updated
78
+ if other.count:
79
+ self.minimum = (
80
+ other.minimum if self.minimum is None else min(self.minimum, other.minimum)
81
+ )
82
+ self.maximum = (
83
+ other.maximum if self.maximum is None else max(self.maximum, other.maximum)
84
+ )
85
+
86
+ def quantile(self, fraction):
87
+ if not self.count:
88
+ return None
89
+ rank = max(1, math.ceil(self.count * fraction))
90
+ cumulative = 0
91
+ for index, count in enumerate(self.counts):
92
+ cumulative += count
93
+ if cumulative >= rank:
94
+ return {
95
+ "lower_ms": max(self.minimum, 0 if index == 0 else self.bounds[index - 1]),
96
+ "upper_ms": min(
97
+ self.maximum,
98
+ self.bounds[index] if index < len(self.bounds) else self.maximum,
99
+ ),
100
+ "rank": rank,
101
+ }
102
+ raise ValueError("inconsistent histogram counts")
103
+
104
+ def to_dict(self):
105
+ return {
106
+ "bounds_ms": list(self.bounds),
107
+ "counts": self.counts[:],
108
+ "count": self.count,
109
+ "sum_ms": self.sum,
110
+ "min_ms": self.minimum,
111
+ "max_ms": self.maximum,
112
+ "mean_ms": self.sum / self.count if self.count else None,
113
+ "quantile_intervals": {str(q): self.quantile(q) for q in (0.5, 0.9, 0.95, 0.99)},
114
+ "algorithm": "fixed_upper_inclusive_bins_v1",
115
+ }
116
+
117
+
118
+ class WeightedCandidates:
119
+ """At most K candidate keys, each with a lower bound and common additive error.
120
+
121
+ When full, subtract min(incoming weight, smallest counter) from every counter
122
+ and the incoming weight. The accumulated subtraction bounds any key's loss.
123
+ Merge adds both losses and then feeds surviving counters through the same rule.
124
+ """
125
+
126
+ def __init__(self, capacity):
127
+ self.capacity = capacity
128
+ self.weights = {}
129
+ self.error = 0.0
130
+ self.total = 0.0
131
+
132
+ def add(self, key, weight):
133
+ number(weight)
134
+ self.total = total(self.total, weight)
135
+ self._add(key, weight)
136
+
137
+ def _add(self, key, weight):
138
+ if not weight:
139
+ return
140
+ if key in self.weights:
141
+ self.weights[key] = total(self.weights[key], weight)
142
+ elif len(self.weights) < self.capacity:
143
+ self.weights[key] = weight
144
+ else:
145
+ decrement = min(weight, min(self.weights.values()))
146
+ self.weights = {
147
+ key: value - decrement for key, value in self.weights.items() if value > decrement
148
+ }
149
+ self.error = total(self.error, decrement)
150
+ if weight > decrement:
151
+ self.weights[key] = weight - decrement
152
+
153
+ def merge(self, other):
154
+ if self.capacity != other.capacity:
155
+ raise ValueError("incompatible candidate capacities")
156
+ self.total = total(self.total, other.total)
157
+ self.error = total(self.error, other.error)
158
+ for key, weight in sorted(other.weights.items()):
159
+ self._add(key, weight)
160
+
161
+ def to_dict(self):
162
+ return {
163
+ "capacity": self.capacity,
164
+ "total_weight": self.total,
165
+ "unlisted_key_upper_bound": self.error,
166
+ "algorithm": "bounded_weighted_decrement_v1",
167
+ "candidates": [
168
+ {
169
+ "key_sha256": key,
170
+ "lower_bound": value,
171
+ "upper_bound": min(self.total, total(value, self.error)),
172
+ }
173
+ for key, value in sorted(self.weights.items(), key=lambda item: (-item[1], item[0]))
174
+ ],
175
+ }