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.
- agentloop/__init__.py +136 -0
- agentloop/ablation_protocol.py +229 -0
- agentloop/ablations.py +564 -0
- agentloop/aggregate_cli.py +88 -0
- agentloop/aggregate_stats.py +175 -0
- agentloop/aggregates.py +532 -0
- agentloop/audit.py +39 -0
- agentloop/autoinstrument.py +145 -0
- agentloop/batch_experiments.py +295 -0
- agentloop/batching.py +95 -0
- agentloop/budget_types.py +335 -0
- agentloop/budgets.py +539 -0
- agentloop/calibration.py +872 -0
- agentloop/checkpoint_state.py +306 -0
- agentloop/checkpoint_store.py +86 -0
- agentloop/ci.py +202 -0
- agentloop/cli.py +1314 -0
- agentloop/client.py +228 -0
- agentloop/completion.py +345 -0
- agentloop/completion_types.py +199 -0
- agentloop/config.py +50 -0
- agentloop/context_controls.py +443 -0
- agentloop/context_experiments.py +337 -0
- agentloop/context_types.py +216 -0
- agentloop/costs.py +667 -0
- agentloop/cursor_validation.py +48 -0
- agentloop/decorators.py +445 -0
- agentloop/demo.py +300 -0
- agentloop/doctor.py +341 -0
- agentloop/drift.py +361 -0
- agentloop/drift_cli.py +147 -0
- agentloop/drift_types.py +110 -0
- agentloop/drift_windows.py +340 -0
- agentloop/entrypoint.py +201 -0
- agentloop/estimates.py +133 -0
- agentloop/events.py +75 -0
- agentloop/experiment_artifacts.py +80 -0
- agentloop/experiment_observations.py +169 -0
- agentloop/experiment_reports.py +545 -0
- agentloop/experiment_types.py +355 -0
- agentloop/experiments.py +392 -0
- agentloop/exporters.py +117 -0
- agentloop/finding_benchmark_types.py +131 -0
- agentloop/finding_benchmarks.py +424 -0
- agentloop/findings.py +419 -0
- agentloop/graph.py +464 -0
- agentloop/harness.py +825 -0
- agentloop/harness_bench/__init__.py +1 -0
- agentloop/harness_bench/cli.py +77 -0
- agentloop/harness_bench/drivers.py +96 -0
- agentloop/harness_bench/offline.py +507 -0
- agentloop/harness_evidence.py +585 -0
- agentloop/harness_usage.py +91 -0
- agentloop/html_report.py +860 -0
- agentloop/integrations/__init__.py +1 -0
- agentloop/integrations/crewai.py +144 -0
- agentloop/integrations/harbor/__init__.py +1 -0
- agentloop/integrations/harbor/atif.py +982 -0
- agentloop/integrations/harbor/atif_validation.py +329 -0
- agentloop/integrations/harbor/cli.py +207 -0
- agentloop/integrations/harbor/manifest.py +86 -0
- agentloop/integrations/harbor/trial_evidence.py +196 -0
- agentloop/integrations/harbor/trials.py +1109 -0
- agentloop/integrations/harbor/verifier.py +118 -0
- agentloop/integrations/langgraph.py +150 -0
- agentloop/integrations/langgraph_harness.py +466 -0
- agentloop/integrations/langgraph_recovery.py +366 -0
- agentloop/integrations/omnigent/__init__.py +1 -0
- agentloop/integrations/omnigent/cli.py +78 -0
- agentloop/integrations/omnigent/evidence.py +93 -0
- agentloop/integrations/omnigent/telemetry.py +422 -0
- agentloop/integrations/openai.py +415 -0
- agentloop/integrations/openai_agents.py +291 -0
- agentloop/integrations/sdk.py +82 -0
- agentloop/integrations/vercel_ai.py +83 -0
- agentloop/interoperability/__init__.py +5 -0
- agentloop/interoperability/artifacts.py +112 -0
- agentloop/interoperability/cli.py +59 -0
- agentloop/interoperability/cohort_evidence.py +175 -0
- agentloop/interoperability/cohorts.py +681 -0
- agentloop/interoperability/contracts.py +427 -0
- agentloop/interoperability/coordination.py +554 -0
- agentloop/interoperability/evidence.py +215 -0
- agentloop/interoperability/otlp_jsonl.py +775 -0
- agentloop/interoperability/otlp_validation.py +496 -0
- agentloop/interoperability/registry.py +561 -0
- agentloop/interoperability/validation.py +333 -0
- agentloop/intervention_service.py +110 -0
- agentloop/intervention_store.py +118 -0
- agentloop/interventions.py +190 -0
- agentloop/issues.py +90 -0
- agentloop/judgment_adapters.py +125 -0
- agentloop/judgment_benchmark_types.py +98 -0
- agentloop/judgment_benchmarks.py +606 -0
- agentloop/judgment_types.py +241 -0
- agentloop/judgment_views.py +38 -0
- agentloop/judgments.py +342 -0
- agentloop/loop_guards.py +274 -0
- agentloop/loop_types.py +66 -0
- agentloop/markdown.py +102 -0
- agentloop/metrics.py +342 -0
- agentloop/migrations.py +303 -0
- agentloop/model_routing.py +470 -0
- agentloop/onboarding.py +315 -0
- agentloop/operations.py +44 -0
- agentloop/optimizer.py +199 -0
- agentloop/otel.py +715 -0
- agentloop/otel_ids.py +81 -0
- agentloop/otel_semantics.py +134 -0
- agentloop/parallelism.py +141 -0
- agentloop/patches.py +603 -0
- agentloop/plan_export.py +126 -0
- agentloop/policy_promotion.py +325 -0
- agentloop/promotion_evidence.py +304 -0
- agentloop/promotion_types.py +209 -0
- agentloop/quality.py +446 -0
- agentloop/ranking.py +529 -0
- agentloop/recovery_evidence.py +96 -0
- agentloop/replay.py +753 -0
- agentloop/retention.py +375 -0
- agentloop/retention_types.py +113 -0
- agentloop/reuse_experiments.py +247 -0
- agentloop/routing_checks.py +116 -0
- agentloop/routing_types.py +241 -0
- agentloop/rules.py +396 -0
- agentloop/runtime.py +286 -0
- agentloop/savings.py +158 -0
- agentloop/scheduling_plan.py +99 -0
- agentloop/scheduling_types.py +169 -0
- agentloop/schema.py +257 -0
- agentloop/semantic_rules.py +200 -0
- agentloop/semantic_waste.py +345 -0
- agentloop/semantic_waste_types.py +88 -0
- agentloop/server.py +494 -0
- agentloop/store.py +1429 -0
- agentloop/structural_experiments.py +431 -0
- agentloop/structural_types.py +113 -0
- agentloop/structured_quality.py +1020 -0
- agentloop/studies.py +629 -0
- agentloop/study_statistics.py +58 -0
- agentloop/substitution_evidence.py +199 -0
- agentloop/substitution_export.py +190 -0
- agentloop/substitution_reports.py +407 -0
- agentloop/substitution_types.py +284 -0
- agentloop/substitutions.py +267 -0
- agentloop/timing.py +142 -0
- agentloop/tokens.py +188 -0
- agentloop/tool_scheduling.py +410 -0
- agentloop/trace_sources.py +31 -0
- agentloop/tracer.py +494 -0
- agentloop/value.py +306 -0
- agentloop/version.py +3 -0
- agentloop/workflow_types.py +264 -0
- agentloop/workflows.py +284 -0
- agentloop_profiler-0.1.0.dist-info/METADATA +513 -0
- agentloop_profiler-0.1.0.dist-info/RECORD +166 -0
- agentloop_profiler-0.1.0.dist-info/WHEEL +5 -0
- agentloop_profiler-0.1.0.dist-info/entry_points.txt +2 -0
- agentloop_profiler-0.1.0.dist-info/licenses/LICENSE +201 -0
- agentloop_profiler-0.1.0.dist-info/licenses/THIRD_PARTY_LICENSES.md +101 -0
- agentloop_profiler-0.1.0.dist-info/top_level.txt +2 -0
- dashboard/__init__.py +5 -0
- dashboard/app.py +922 -0
- dashboard/streamlit_compat.py +63 -0
- dashboard/trace_upload.py +57 -0
- 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)
|