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,466 @@
1
+ """Explicit LangGraph dispatch controls; the SDK remains an optional dependency."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from collections.abc import Callable, Iterator
7
+ from contextlib import contextmanager, nullcontext
8
+ from contextvars import ContextVar
9
+ from dataclasses import dataclass, replace
10
+ from functools import lru_cache, wraps
11
+ from importlib.metadata import PackageNotFoundError, version
12
+ from weakref import ReferenceType, ref
13
+
14
+ from agentloop.budget_types import DispatchOptions
15
+ from agentloop.harness import (
16
+ ACTIONS,
17
+ BOUNDARIES,
18
+ EXECUTION_KINDS,
19
+ AdapterCapabilities,
20
+ Harness,
21
+ HarnessConfig,
22
+ HarnessControlError,
23
+ HarnessRun,
24
+ Hook,
25
+ )
26
+ from agentloop.harness_evidence import HarnessEvidenceError
27
+ from agentloop.loop_types import StepInfo
28
+ from agentloop.tracer import AgentTrace, bind_trace_context, current_event_id, current_trace
29
+
30
+ SUPPORTED_VERSION = "1.2.11"
31
+
32
+
33
+ def _check_version() -> None:
34
+ try:
35
+ installed = version("langgraph")
36
+ except PackageNotFoundError:
37
+ installed = None
38
+ if installed != SUPPORTED_VERSION:
39
+ raise RuntimeError(f"LangGraph harness requires langgraph=={SUPPORTED_VERSION}")
40
+
41
+
42
+ @lru_cache(maxsize=1)
43
+ def _signal_type():
44
+ # Public control-flow base: the pinned runtime bypasses retries and handlers.
45
+ from langgraph.errors import GraphBubbleUp
46
+
47
+ class HarnessSignal(GraphBubbleUp):
48
+ def __init__(self, original):
49
+ self.original = original
50
+ super().__init__("AgentLoop harness control")
51
+
52
+ return HarnessSignal
53
+
54
+
55
+ def _runtime_identity() -> tuple[str, int]:
56
+ from langgraph.runtime import get_runtime
57
+
58
+ info = get_runtime().execution_info
59
+ task_id, attempt = info.task_id, info.node_attempt
60
+ if not isinstance(task_id, str) or not task_id or type(attempt) is not int or attempt < 1:
61
+ raise RuntimeError("LangGraph did not supply a valid task identity and attempt")
62
+ return task_id, attempt
63
+
64
+
65
+ def _kind(function):
66
+ for kind, check in (
67
+ ("async_generator", inspect.isasyncgenfunction),
68
+ ("generator", inspect.isgeneratorfunction),
69
+ ("async", inspect.iscoroutinefunction),
70
+ ):
71
+ if check(function) or check(getattr(function, "__call__", None)):
72
+ return kind
73
+ return "sync"
74
+
75
+
76
+ @dataclass(frozen=True)
77
+ class _Session:
78
+ run: HarnessRun
79
+ trace: AgentTrace | None
80
+ parent: str | None
81
+
82
+
83
+ class _ScopedIterator:
84
+ """Forward the generator protocol with no context binding across yields."""
85
+
86
+ def __init__(self, source, scope):
87
+ self.source, self.scope = source, scope
88
+
89
+ def __iter__(self):
90
+ return self
91
+
92
+ def __next__(self):
93
+ with self.scope():
94
+ return next(self.source)
95
+
96
+ def send(self, value):
97
+ with self.scope():
98
+ return self.source.send(value)
99
+
100
+ def throw(self, *args):
101
+ with self.scope():
102
+ return self.source.throw(*args)
103
+
104
+ def close(self):
105
+ with self.scope():
106
+ close = getattr(self.source, "close", None)
107
+ if close is not None:
108
+ return close()
109
+
110
+
111
+ class _ScopedAsyncIterator:
112
+ def __init__(self, source, scope):
113
+ self.source, self.scope = source, scope
114
+
115
+ async def __anext__(self):
116
+ with self.scope():
117
+ return await self.source.__anext__()
118
+
119
+ async def asend(self, value):
120
+ with self.scope():
121
+ return await self.source.asend(value)
122
+
123
+ async def athrow(self, error):
124
+ with self.scope():
125
+ return await self.source.athrow(error)
126
+
127
+ async def aclose(self):
128
+ with self.scope():
129
+ return await self.source.aclose()
130
+
131
+
132
+ def _identity(value):
133
+ return value
134
+
135
+
136
+ class LangGraphHarness:
137
+ """Register explicit boundaries before compiling, then wrap the compiled app.
138
+
139
+ ``boundaries`` declares coverage, not discovery: only decorated callables
140
+ are controlled. Node admission uses the ``iteration`` boundary. No builder
141
+ registry, provider client, or existing tracing adapter is modified.
142
+ """
143
+
144
+ def __init__(
145
+ self,
146
+ config: HarnessConfig = HarnessConfig(),
147
+ *,
148
+ boundaries=frozenset({"iteration", "completion"}),
149
+ ):
150
+ boundaries = frozenset(boundaries)
151
+ if not boundaries <= BOUNDARIES:
152
+ raise ValueError("unsupported LangGraph harness boundary")
153
+ self._harness = Harness(
154
+ config,
155
+ AdapterCapabilities(
156
+ "langgraph",
157
+ frozenset(
158
+ Hook(boundary, phase)
159
+ for boundary in boundaries
160
+ for phase in ("before", "after")
161
+ ),
162
+ ACTIONS,
163
+ EXECUTION_KINDS,
164
+ ),
165
+ )
166
+ self._session: ContextVar[_Session | None] = ContextVar(
167
+ "langgraph_harness_session", default=None
168
+ )
169
+ self._branch: ContextVar[str] = ContextVar("langgraph_harness_branch", default="main")
170
+ self._last_run: ContextVar[HarnessRun | None] = ContextVar(
171
+ "langgraph_harness_last_run", default=None
172
+ )
173
+
174
+ @property
175
+ def capabilities(self) -> AdapterCapabilities:
176
+ return self._harness.capabilities
177
+
178
+ @property
179
+ def last_run(self) -> HarnessRun | None:
180
+ """Most recently started run in this caller's context, including failures."""
181
+ return self._last_run.get()
182
+
183
+ @contextmanager
184
+ def _scope(self, session, branch=None, *, root=False) -> Iterator[None]:
185
+ session_token = self._session.set(session)
186
+ branch_token = self._branch.set(branch if branch is not None else self._branch.get())
187
+ trace_scope = bind_trace_context(session.trace, session.parent) if root else nullcontext()
188
+ try:
189
+ with trace_scope:
190
+ try:
191
+ yield
192
+ except (HarnessControlError, HarnessEvidenceError) as exc:
193
+ if root:
194
+ raise
195
+ raise _signal_type()(exc) from None
196
+ except _signal_type() as exc:
197
+ if root:
198
+ raise exc.original from None
199
+ raise
200
+ finally:
201
+ self._branch.reset(branch_token)
202
+ self._session.reset(session_token)
203
+
204
+ def _start(self, run=None):
205
+ if run is not None and (
206
+ not isinstance(run, HarnessRun) or run.harness is not self._harness
207
+ ):
208
+ raise ValueError("recovered run must belong to this adapter")
209
+ session = _Session(
210
+ self._harness.start_run() if run is None else run, current_trace(), current_event_id()
211
+ )
212
+ self._last_run.set(session.run)
213
+ return session
214
+
215
+ def node(self, name: str, *, branch_id: str | None = None, step_reader: Callable | None = None):
216
+ """Decorate a sync/async node before add_node; arguments are passed unchanged.
217
+
218
+ By default each framework task is a separate branch. Supply a stable
219
+ branch_id to compare sequential graph iterations. An optional trusted
220
+ step_reader accepts the same arguments and returns bounded StepInfo.
221
+ """
222
+ StepInfo(name)
223
+ if branch_id is not None and (not isinstance(branch_id, str) or not branch_id):
224
+ raise ValueError("branch_id must be a nonempty string")
225
+ if step_reader is not None and (not callable(step_reader) or _kind(step_reader) != "sync"):
226
+ raise ValueError("step_reader must be a synchronous callable")
227
+ return lambda function: self._wrap(
228
+ function,
229
+ "iteration",
230
+ DispatchOptions(step=StepInfo(name)),
231
+ None,
232
+ node=True,
233
+ branch_id=branch_id,
234
+ step_reader=step_reader,
235
+ )
236
+
237
+ def model(self, function, *, dispatch: DispatchOptions | None = None, usage_reader=None):
238
+ """Wrap one explicit model callable, including a lazy stream callable."""
239
+ return self._wrap(
240
+ function, "model", DispatchOptions() if dispatch is None else dispatch, usage_reader
241
+ )
242
+
243
+ def tool(self, function, *, dispatch: DispatchOptions | None = None, usage_reader=None):
244
+ """Wrap one explicit tool callable; hidden work is outside this boundary."""
245
+ return self._wrap(
246
+ function, "tool", DispatchOptions() if dispatch is None else dispatch, usage_reader
247
+ )
248
+
249
+ def _wrap(
250
+ self,
251
+ function,
252
+ boundary,
253
+ options,
254
+ usage_reader,
255
+ *,
256
+ node=False,
257
+ branch_id=None,
258
+ step_reader=None,
259
+ ):
260
+ if not callable(function):
261
+ raise ValueError("protected work must be callable")
262
+ if self._harness.config.mode == "disabled":
263
+ return function
264
+ if Hook(boundary) not in self.capabilities.hooks:
265
+ raise ValueError("boundary must be declared in LangGraphHarness(boundaries=...)")
266
+ if type(options) is not DispatchOptions:
267
+ raise ValueError("dispatch must be DispatchOptions")
268
+ if usage_reader is not None and (
269
+ not callable(usage_reader) or _kind(usage_reader) != "sync"
270
+ ):
271
+ raise ValueError("usage_reader must be a synchronous callable")
272
+ kind = _kind(function)
273
+ if node and kind in {"generator", "async_generator"}:
274
+ raise ValueError(
275
+ "generator nodes are unsupported; compiled graph streams are supported"
276
+ )
277
+ settings = (self, boundary, options, usage_reader, node, branch_id, step_reader)
278
+ marker = getattr(function, "__agentloop_langgraph_harness__", None)
279
+ if (
280
+ isinstance(marker, tuple)
281
+ and len(marker) == 2
282
+ and isinstance(marker[0], ReferenceType)
283
+ and marker[0]() is function
284
+ ):
285
+ if marker[1] == settings:
286
+ return function
287
+ raise ValueError("wrap the original callable to change harness settings")
288
+
289
+ def prepare(args, kwargs):
290
+ session = self._session.get()
291
+ if session is None:
292
+ raise RuntimeError("active LangGraph controls require adapter.runnable(app)")
293
+ branch, dispatch = self._branch.get(), options
294
+ if node:
295
+ task_id, attempt = _runtime_identity()
296
+ branch = branch_id or task_id
297
+ step = step_reader(*args, **kwargs) if step_reader is not None else options.step
298
+ if type(step) is not StepInfo:
299
+ raise ValueError("step_reader must return StepInfo")
300
+ dispatch = replace(
301
+ options, step=step, retry_source="framework" if attempt > 1 else None
302
+ )
303
+ protected = session.run.wrap(
304
+ function,
305
+ boundary=boundary,
306
+ branch_id=branch,
307
+ dispatch=dispatch,
308
+ usage_reader=usage_reader,
309
+ )
310
+ return protected, lambda: self._scope(session, branch)
311
+
312
+ if kind == "async":
313
+
314
+ @wraps(function)
315
+ async def wrapped(*args, **kwargs):
316
+ protected, scope = prepare(args, kwargs)
317
+ with scope():
318
+ return await protected(*args, **kwargs)
319
+ elif kind == "generator":
320
+
321
+ @wraps(function)
322
+ def wrapped(*args, **kwargs):
323
+ protected, scope = prepare(args, kwargs)
324
+ with scope():
325
+ source = protected(*args, **kwargs)
326
+ return (yield from _ScopedIterator(source, scope))
327
+ elif kind == "async_generator":
328
+
329
+ @wraps(function)
330
+ async def wrapped(*args, **kwargs):
331
+ protected, scope = prepare(args, kwargs)
332
+ source = _ScopedAsyncIterator(protected(*args, **kwargs), scope)
333
+ # Forward sends, throws and close instead of reducing to async-for.
334
+ try:
335
+ item = await source.__anext__()
336
+ while True:
337
+ try:
338
+ sent = yield item
339
+ except GeneratorExit:
340
+ await source.aclose()
341
+ raise
342
+ except BaseException as exc:
343
+ item = await source.athrow(exc)
344
+ else:
345
+ item = await source.asend(sent)
346
+ except StopAsyncIteration:
347
+ return
348
+ else:
349
+
350
+ @wraps(function)
351
+ def wrapped(*args, **kwargs):
352
+ protected, scope = prepare(args, kwargs)
353
+ with scope():
354
+ return protected(*args, **kwargs)
355
+
356
+ wrapped.__agentloop_langgraph_harness__ = (ref(wrapped), settings)
357
+ return wrapped
358
+
359
+ def runnable(self, app):
360
+ """Wrap invoke/ainvoke/stream/astream; disabled mode returns app itself."""
361
+ if self._harness.config.mode == "disabled":
362
+ return app
363
+ if isinstance(app, ControlledRunnable):
364
+ if app._adapter is self:
365
+ return app
366
+ raise ValueError("runnable is already controlled by another adapter")
367
+ _check_version()
368
+ for method in ("invoke", "ainvoke", "stream", "astream"):
369
+ if not callable(getattr(app, method, None)):
370
+ raise TypeError(f"compiled app must provide {method}")
371
+ return ControlledRunnable(self, app)
372
+
373
+ def _complete(self, session, value):
374
+ if Hook("completion") not in self.capabilities.hooks:
375
+ return value
376
+ return session.run.wrap(_identity, boundary="completion")(value)
377
+
378
+
379
+ class ControlledRunnable:
380
+ """The four controlled entrypoints; access the original app for other APIs.
381
+
382
+ This intentionally does not proxy batch or configuration methods that could
383
+ return an unprotected runnable. Rewrap such a runnable explicitly.
384
+ """
385
+
386
+ def __init__(self, adapter, app):
387
+ self._adapter, self.app = adapter, app
388
+ self._last_invocation = ContextVar("langgraph_root_invocation", default=None)
389
+ self._recovery_bound = False
390
+
391
+ def _ordinary_entry(self):
392
+ if self._recovery_bound:
393
+ raise ValueError("checkpoint-owned runnable requires LangGraphRecovery.resume")
394
+
395
+ def invoke(self, *args, **kwargs):
396
+ self._ordinary_entry()
397
+ adapter = self._adapter
398
+ session = adapter._start()
399
+ config = kwargs.get("config", args[1] if len(args) > 1 else None)
400
+ selected = config.get("configurable", {}) if isinstance(config, dict) else {}
401
+ selected = selected if isinstance(selected, dict) else {}
402
+ identity = {
403
+ "run_id": session.run.run_id,
404
+ "thread_id": selected.get("thread_id"),
405
+ "checkpoint_ns": selected.get("checkpoint_ns", ""),
406
+ "trace_id": None if session.trace is None else session.trace.run_id,
407
+ "durability": kwargs.get("durability"),
408
+ "finished": False,
409
+ }
410
+ self._last_invocation.set(identity)
411
+ try:
412
+ with adapter._scope(session, "main", root=True):
413
+ return adapter._complete(session, self.app.invoke(*args, **kwargs))
414
+ finally:
415
+ self._last_invocation.set({**identity, "finished": True})
416
+
417
+ async def ainvoke(self, *args, **kwargs):
418
+ self._ordinary_entry()
419
+ self._last_invocation.set(None)
420
+ adapter = self._adapter
421
+ session = adapter._start()
422
+ with adapter._scope(session, "main", root=True):
423
+ return adapter._complete(session, await self.app.ainvoke(*args, **kwargs))
424
+
425
+ def stream(self, *args, **kwargs):
426
+ self._ordinary_entry()
427
+ self._last_invocation.set(None)
428
+ adapter = self._adapter
429
+ session = adapter._start()
430
+
431
+ def scope():
432
+ return adapter._scope(session, "main", root=True)
433
+
434
+ with scope():
435
+ source = self.app.stream(*args, **kwargs)
436
+ result = yield from _ScopedIterator(source, scope)
437
+ with scope():
438
+ return adapter._complete(session, result)
439
+
440
+ async def astream(self, *args, **kwargs):
441
+ self._ordinary_entry()
442
+ self._last_invocation.set(None)
443
+ adapter = self._adapter
444
+ session = adapter._start()
445
+
446
+ def scope():
447
+ return adapter._scope(session, "main", root=True)
448
+
449
+ with scope():
450
+ source = self.app.astream(*args, **kwargs)
451
+ source = _ScopedAsyncIterator(source, scope)
452
+ try:
453
+ item = await source.__anext__()
454
+ while True:
455
+ try:
456
+ sent = yield item
457
+ except GeneratorExit:
458
+ await source.aclose()
459
+ raise
460
+ except BaseException as exc:
461
+ item = await source.athrow(exc)
462
+ else:
463
+ item = await source.asend(sent)
464
+ except StopAsyncIteration:
465
+ with scope():
466
+ adapter._complete(session, None)