loopgain 0.1.8__tar.gz → 0.2.0__tar.gz
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.
- {loopgain-0.1.8 → loopgain-0.2.0}/PKG-INFO +130 -11
- {loopgain-0.1.8 → loopgain-0.2.0}/README.md +117 -9
- {loopgain-0.1.8 → loopgain-0.2.0}/loopgain/_version.py +1 -1
- {loopgain-0.1.8 → loopgain-0.2.0}/loopgain/core.py +21 -11
- {loopgain-0.1.8 → loopgain-0.2.0}/loopgain/integrations/__init__.py +31 -12
- loopgain-0.2.0/loopgain/integrations/claude_agent_sdk.py +206 -0
- loopgain-0.2.0/loopgain/integrations/langchain.py +187 -0
- loopgain-0.2.0/loopgain/integrations/openai_agents.py +197 -0
- {loopgain-0.1.8 → loopgain-0.2.0}/loopgain/telemetry.py +24 -6
- {loopgain-0.1.8 → loopgain-0.2.0}/loopgain.egg-info/PKG-INFO +130 -11
- {loopgain-0.1.8 → loopgain-0.2.0}/loopgain.egg-info/SOURCES.txt +3 -0
- loopgain-0.2.0/loopgain.egg-info/requires.txt +32 -0
- {loopgain-0.1.8 → loopgain-0.2.0}/pyproject.toml +21 -3
- {loopgain-0.1.8 → loopgain-0.2.0}/tests/test_core.py +16 -5
- {loopgain-0.1.8 → loopgain-0.2.0}/tests/test_stress.py +9 -7
- {loopgain-0.1.8 → loopgain-0.2.0}/tests/test_telemetry.py +26 -3
- loopgain-0.1.8/loopgain.egg-info/requires.txt +0 -17
- {loopgain-0.1.8 → loopgain-0.2.0}/LICENSE +0 -0
- {loopgain-0.1.8 → loopgain-0.2.0}/loopgain/__init__.py +0 -0
- {loopgain-0.1.8 → loopgain-0.2.0}/loopgain/integrations/autogen.py +0 -0
- {loopgain-0.1.8 → loopgain-0.2.0}/loopgain/integrations/crewai.py +0 -0
- {loopgain-0.1.8 → loopgain-0.2.0}/loopgain/integrations/langgraph.py +0 -0
- {loopgain-0.1.8 → loopgain-0.2.0}/loopgain.egg-info/dependency_links.txt +0 -0
- {loopgain-0.1.8 → loopgain-0.2.0}/loopgain.egg-info/top_level.txt +0 -0
- {loopgain-0.1.8 → loopgain-0.2.0}/setup.cfg +0 -0
- {loopgain-0.1.8 → loopgain-0.2.0}/tests/test_integrations.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loopgain
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Barkhausen stability monitor for AI agent loops. Real-time loop-gain (Aβ) monitoring with five named threshold bands, best-so-far rollback, and ETA prediction.
|
|
5
5
|
Author-email: Dave Fitzsimmons <hello@loopgain.ai>
|
|
6
6
|
License: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://loopgain.ai
|
|
8
8
|
Project-URL: Repository, https://github.com/loopgain-ai/loopgain
|
|
9
9
|
Project-URL: Issues, https://github.com/loopgain-ai/loopgain/issues
|
|
10
|
-
Keywords: ai,agent,ai-agent,ai-agents,agentic,agentic-ai,llm,llm-agent,llm-orchestration,agent-orchestration,agent-loop,verify-revise,verify-revise-loop,gvr,generator-verifier-reviser,convergence,divergence-detection,infinite-loop,infinite-loop-detection,loop-detection,loop-stability,stability-monitor,early-stopping,max-iterations,barkhausen,barkhausen-criterion,control-theory,feedback-loop,feedback-loop-stability,loop-gain,rollback,best-so-far,langgraph,crewai,autogen,claude,anthropic,openai
|
|
10
|
+
Keywords: ai,agent,ai-agent,ai-agents,agentic,agentic-ai,llm,llm-agent,llm-orchestration,agent-orchestration,agent-loop,verify-revise,verify-revise-loop,gvr,generator-verifier-reviser,convergence,divergence-detection,infinite-loop,infinite-loop-detection,loop-detection,loop-stability,stability-monitor,early-stopping,max-iterations,barkhausen,barkhausen-criterion,control-theory,feedback-loop,feedback-loop-stability,loop-gain,rollback,best-so-far,langgraph,crewai,autogen,langchain,openai-agents,openai-agents-sdk,claude-agent-sdk,claude,anthropic,openai
|
|
11
11
|
Classifier: Development Status :: 3 - Alpha
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
@@ -30,10 +30,21 @@ Provides-Extra: crewai
|
|
|
30
30
|
Requires-Dist: crewai>=0.30; extra == "crewai"
|
|
31
31
|
Provides-Extra: autogen
|
|
32
32
|
Requires-Dist: autogen-agentchat>=0.4; extra == "autogen"
|
|
33
|
+
Provides-Extra: langchain
|
|
34
|
+
Requires-Dist: langchain>=1.0; extra == "langchain"
|
|
35
|
+
Provides-Extra: openai-agents
|
|
36
|
+
Requires-Dist: openai-agents>=0.1; extra == "openai-agents"
|
|
37
|
+
Provides-Extra: claude-agent-sdk
|
|
38
|
+
Requires-Dist: claude-agent-sdk>=0.2; extra == "claude-agent-sdk"
|
|
33
39
|
Provides-Extra: all
|
|
34
40
|
Requires-Dist: langgraph>=0.2; extra == "all"
|
|
35
41
|
Requires-Dist: crewai>=0.30; extra == "all"
|
|
36
42
|
Requires-Dist: autogen-agentchat>=0.4; extra == "all"
|
|
43
|
+
Requires-Dist: langchain>=1.0; extra == "all"
|
|
44
|
+
Requires-Dist: openai-agents>=0.1; extra == "all"
|
|
45
|
+
Requires-Dist: claude-agent-sdk>=0.2; extra == "all"
|
|
46
|
+
Provides-Extra: examples
|
|
47
|
+
Requires-Dist: anthropic>=0.40.0; extra == "examples"
|
|
37
48
|
Dynamic: license-file
|
|
38
49
|
|
|
39
50
|
# LoopGain
|
|
@@ -49,7 +60,7 @@ Replace `max_iterations=5` with a real-time loop-gain (`Aβ`) monitor that knows
|
|
|
49
60
|
|
|
50
61
|
**Home:** [loopgain.ai](https://loopgain.ai)
|
|
51
62
|
|
|
52
|
-
Works for **any iterative AI workflow with a measurable error signal** — verify-revise loops, refinement passes, tool-use retry chains, RAG with self-correction, code-gen with linter feedback, multi-step reasoning loops. **Pre-built adapters for [LangGraph](#langgraph), [CrewAI](#crewai),
|
|
63
|
+
Works for **any iterative AI workflow with a measurable error signal** — verify-revise loops, refinement passes, tool-use retry chains, RAG with self-correction, code-gen with linter feedback, multi-step reasoning loops. **Pre-built adapters for [LangGraph](#langgraph), [CrewAI](#crewai), [AutoGen](#autogen-v04), [LangChain](#langchain), [OpenAI Agents SDK](#openai-agents-sdk), and [Claude Agent SDK](#claude-agent-sdk)**; drop-in via the raw API for any custom stack. Pure Python, no runtime dependencies.
|
|
53
64
|
|
|
54
65
|
**Keywords:** AI agent loops · agentic AI · infinite loop detection · divergence detection · early stopping · convergence · agent orchestration · LLM stability · generator-verifier-reviser · feedback-loop control.
|
|
55
66
|
|
|
@@ -116,7 +127,7 @@ It classifies `Aβ_smooth` into five named bands:
|
|
|
116
127
|
| `0.95 ≤ Aβ ≤ 1.05` | `OSCILLATING` | Break — return best-so-far |
|
|
117
128
|
| `> 1.05` | `DIVERGING` | Abort — roll back to best-so-far |
|
|
118
129
|
|
|
119
|
-
Plus a short-circuit: if observed error drops at or below `target_error`, the loop stops immediately with state `TARGET_MET`.
|
|
130
|
+
Plus a short-circuit: if observed error drops at or below `target_error`, the loop stops immediately with state `TARGET_MET`. The default `target_error=0.0` short-circuits on exactly zero error — the natural completion signal for verifier-driven loops. Pass `target_error=None` to disable the short-circuit and rely on stability detection alone.
|
|
120
131
|
|
|
121
132
|
The `±0.05` noise band around `Aβ=1` absorbs stochastic jitter from agent outputs without triggering false-positive aborts. The `0.85` `STALLING` boundary is an early warning — by the time `Aβ` crosses `1.0`, you've already wasted iterations.
|
|
122
133
|
|
|
@@ -156,7 +167,7 @@ This transforms divergence detection from "abort with garbage" into "abort with
|
|
|
156
167
|
|
|
157
168
|
Construct the monitor.
|
|
158
169
|
|
|
159
|
-
- `target_error` — Stop when an observed error drops at or below this. Default `0.0`
|
|
170
|
+
- `target_error` — Stop when an observed error drops at or below this. Default `0.0` short-circuits on exactly zero error (the natural completion signal for verifier-driven loops). Pass `None` to disable the short-circuit entirely.
|
|
160
171
|
- `max_iterations` — Hard safety cap. Default `None` (rely on stability detection). Recommended ~20–50 for production.
|
|
161
172
|
- `thresholds` — Custom `ThresholdBands` if defaults don't fit your domain.
|
|
162
173
|
- `smoothing_window` — EMA window for the smoothed Aβ. Default 3.
|
|
@@ -229,10 +240,13 @@ The hosted endpoint at `telemetry.loopgain.ai` is one acceptable destination. Th
|
|
|
229
240
|
Thin wrappers under `loopgain.integrations` drive each major agent framework's iteration with a `LoopGain` monitor and auto-stamp `framework="<name>"` on telemetry. The frameworks themselves are **optional dependencies** — install the extra you need:
|
|
230
241
|
|
|
231
242
|
```bash
|
|
232
|
-
pip install 'loopgain[langgraph]'
|
|
233
|
-
pip install 'loopgain[crewai]'
|
|
234
|
-
pip install 'loopgain[autogen]'
|
|
235
|
-
pip install 'loopgain[
|
|
243
|
+
pip install 'loopgain[langgraph]' # LangGraph
|
|
244
|
+
pip install 'loopgain[crewai]' # CrewAI
|
|
245
|
+
pip install 'loopgain[autogen]' # AutoGen v0.4+
|
|
246
|
+
pip install 'loopgain[langchain]' # LangChain (create_agent / AgentExecutor)
|
|
247
|
+
pip install 'loopgain[openai-agents]' # OpenAI Agents SDK
|
|
248
|
+
pip install 'loopgain[claude-agent-sdk]' # Anthropic Claude Agent SDK
|
|
249
|
+
pip install 'loopgain[all]' # all six
|
|
236
250
|
```
|
|
237
251
|
|
|
238
252
|
All adapters take a `LoopGain` instance plus an `error_fn` you provide — the framework doesn't know what your error signal is, so the adapter doesn't either. `error_fn` returns a non-negative number (or `None` to skip an iteration).
|
|
@@ -319,15 +333,120 @@ lg.send_telemetry(
|
|
|
319
333
|
|
|
320
334
|
Pass a `cancellation_token` to `adapter.run(...)` and the adapter will cancel it when LoopGain reaches a terminal state (target met, oscillation, divergence). The legacy v0.2 `ConversableAgent.initiate_chat` API is **not** supported — use the v0.4 event-driven runtime.
|
|
321
335
|
|
|
336
|
+
### LangChain
|
|
337
|
+
|
|
338
|
+
Duck-types against any LangChain agent that exposes `.stream(input, **kwargs)` / `.astream(input, **kwargs)` — both the current `langchain.agents.create_agent()` (v1+) and the legacy `AgentExecutor`. The adapter forwards `**stream_kwargs` verbatim, so the chunk shape your `error_fn` sees is the one your agent emits.
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
from langchain.agents import create_agent
|
|
342
|
+
from loopgain import LoopGain
|
|
343
|
+
from loopgain.integrations import LangChainAdapter
|
|
344
|
+
|
|
345
|
+
agent = create_agent(model="gpt-5-nano", tools=[get_weather])
|
|
346
|
+
lg = LoopGain(target_error=0.0, max_iterations=20)
|
|
347
|
+
|
|
348
|
+
def error_fn(chunk):
|
|
349
|
+
if chunk.get("type") != "updates":
|
|
350
|
+
return None
|
|
351
|
+
# Count unresolved tool calls; drops to 0 once the agent stops calling tools.
|
|
352
|
+
return sum(
|
|
353
|
+
1 for _, update in chunk["data"].items()
|
|
354
|
+
if getattr(update.get("messages", [None])[-1], "tool_calls", None)
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
adapter = LangChainAdapter(lg=lg, error_fn=error_fn)
|
|
358
|
+
final = adapter.run(
|
|
359
|
+
agent,
|
|
360
|
+
{"messages": [{"role": "user", "content": "What's the weather?"}]},
|
|
361
|
+
stream_mode="updates",
|
|
362
|
+
version="v2",
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
lg.send_telemetry(
|
|
366
|
+
endpoint=...,
|
|
367
|
+
token=...,
|
|
368
|
+
framework=adapter.framework_name, # "langchain"
|
|
369
|
+
)
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
For legacy `AgentExecutor`: just drop the `stream_mode` / `version` kwargs; each yielded chunk is an `AddableDict` per step (parse `intermediate_steps` or the terminal `output` key in your `error_fn`).
|
|
373
|
+
|
|
374
|
+
### OpenAI Agents SDK
|
|
375
|
+
|
|
376
|
+
Wraps `Runner.run_streamed(agent, input).stream_events()`. The SDK is async-first; the adapter mirrors that. A `run_sync` helper wraps the async path with `asyncio.run` for synchronous callers.
|
|
377
|
+
|
|
378
|
+
```python
|
|
379
|
+
from agents import Agent, function_tool
|
|
380
|
+
from loopgain import LoopGain
|
|
381
|
+
from loopgain.integrations import OpenAIAgentsAdapter
|
|
382
|
+
|
|
383
|
+
agent = Agent(name="Reviser", instructions="...", tools=[...])
|
|
384
|
+
|
|
385
|
+
lg = LoopGain(target_error=0.0, max_iterations=20)
|
|
386
|
+
|
|
387
|
+
def error_fn(event):
|
|
388
|
+
# Default observes only run_item_stream_event; pull the verifier's
|
|
389
|
+
# reported failure count off tool outputs.
|
|
390
|
+
if event.item.type == "tool_call_output_item":
|
|
391
|
+
return float(event.item.output.get("failures", 0))
|
|
392
|
+
return None
|
|
393
|
+
|
|
394
|
+
adapter = OpenAIAgentsAdapter(lg=lg, error_fn=error_fn)
|
|
395
|
+
result = await adapter.run(agent, input="Fix the bug.")
|
|
396
|
+
print(result.final_output)
|
|
397
|
+
|
|
398
|
+
lg.send_telemetry(
|
|
399
|
+
endpoint=...,
|
|
400
|
+
token=...,
|
|
401
|
+
framework=adapter.framework_name, # "openai-agents"
|
|
402
|
+
)
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
By default the adapter only forwards `run_item_stream_event` to `error_fn` — pass `observe_event_types=None` to see every event (including raw token deltas and agent-handoff notifications). When LoopGain reaches a terminal state, the adapter best-effort calls `.cancel()` on the underlying `RunResultStreaming`.
|
|
406
|
+
|
|
407
|
+
### Claude Agent SDK
|
|
408
|
+
|
|
409
|
+
Wraps Anthropic's `claude_agent_sdk.query(prompt=..., options=...)` async iterator. By default observes only `AssistantMessage` (skips `UserMessage` / `SystemMessage` / `ResultMessage`); override with `observe_message_types=None` or a custom tuple.
|
|
410
|
+
|
|
411
|
+
```python
|
|
412
|
+
from claude_agent_sdk import ClaudeAgentOptions, TextBlock
|
|
413
|
+
from loopgain import LoopGain
|
|
414
|
+
from loopgain.integrations import ClaudeAgentSDKAdapter
|
|
415
|
+
|
|
416
|
+
def error_fn(message):
|
|
417
|
+
# Count `FAIL:` markers a self-verifying persona emits.
|
|
418
|
+
for block in getattr(message, "content", []):
|
|
419
|
+
if isinstance(block, TextBlock):
|
|
420
|
+
return float(block.text.count("FAIL:"))
|
|
421
|
+
return None
|
|
422
|
+
|
|
423
|
+
lg = LoopGain(target_error=0.0, max_iterations=20)
|
|
424
|
+
adapter = ClaudeAgentSDKAdapter(lg=lg, error_fn=error_fn)
|
|
425
|
+
|
|
426
|
+
options = ClaudeAgentOptions(system_prompt="Self-verify each draft.")
|
|
427
|
+
result = await adapter.run(
|
|
428
|
+
prompt="Write a haiku about feedback loops.",
|
|
429
|
+
options=options,
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
lg.send_telemetry(
|
|
433
|
+
endpoint=...,
|
|
434
|
+
token=...,
|
|
435
|
+
framework=adapter.framework_name, # "claude-agent-sdk"
|
|
436
|
+
)
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
For the bidirectional `ClaudeSDKClient` use case, pass `message_iterator=client.receive_messages()` instead of `prompt=...`.
|
|
440
|
+
|
|
322
441
|
### Custom integrations
|
|
323
442
|
|
|
324
|
-
For frameworks without an adapter, the raw `LoopGain.observe()` API works against any iterable. The adapters are 100-200 lines each — copy one of `loopgain/integrations/{langgraph,crewai,autogen}.py` as a starting point.
|
|
443
|
+
For frameworks without an adapter, the raw `LoopGain.observe()` API works against any iterable. The adapters are 100-200 lines each — copy one of `loopgain/integrations/{langgraph,crewai,autogen,langchain,openai_agents,claude_agent_sdk}.py` as a starting point.
|
|
325
444
|
|
|
326
445
|
---
|
|
327
446
|
|
|
328
447
|
## Status
|
|
329
448
|
|
|
330
|
-
**Initial public release.** Core library shipped (current version: see the PyPI badge at the top). Framework adapters (LangGraph, CrewAI, AutoGen) are installable as optional extras. The cloud-aggregator [telemetry receiver](https://github.com/loopgain-ai/telemetry-receiver) and [dashboard](https://github.com/loopgain-ai/dashboard) are live as separate open-source repos. The math and the API surface are stable.
|
|
449
|
+
**Initial public release.** Core library shipped (current version: see the PyPI badge at the top). Framework adapters (LangGraph, CrewAI, AutoGen, LangChain, OpenAI Agents SDK, Claude Agent SDK) are installable as optional extras. The cloud-aggregator [telemetry receiver](https://github.com/loopgain-ai/telemetry-receiver) and [dashboard](https://github.com/loopgain-ai/dashboard) are live as separate open-source repos. The math and the API surface are stable.
|
|
331
450
|
|
|
332
451
|
This is alpha software. The API may break before 1.0 if production usage surfaces design issues; pin the version.
|
|
333
452
|
|
|
@@ -11,7 +11,7 @@ Replace `max_iterations=5` with a real-time loop-gain (`Aβ`) monitor that knows
|
|
|
11
11
|
|
|
12
12
|
**Home:** [loopgain.ai](https://loopgain.ai)
|
|
13
13
|
|
|
14
|
-
Works for **any iterative AI workflow with a measurable error signal** — verify-revise loops, refinement passes, tool-use retry chains, RAG with self-correction, code-gen with linter feedback, multi-step reasoning loops. **Pre-built adapters for [LangGraph](#langgraph), [CrewAI](#crewai),
|
|
14
|
+
Works for **any iterative AI workflow with a measurable error signal** — verify-revise loops, refinement passes, tool-use retry chains, RAG with self-correction, code-gen with linter feedback, multi-step reasoning loops. **Pre-built adapters for [LangGraph](#langgraph), [CrewAI](#crewai), [AutoGen](#autogen-v04), [LangChain](#langchain), [OpenAI Agents SDK](#openai-agents-sdk), and [Claude Agent SDK](#claude-agent-sdk)**; drop-in via the raw API for any custom stack. Pure Python, no runtime dependencies.
|
|
15
15
|
|
|
16
16
|
**Keywords:** AI agent loops · agentic AI · infinite loop detection · divergence detection · early stopping · convergence · agent orchestration · LLM stability · generator-verifier-reviser · feedback-loop control.
|
|
17
17
|
|
|
@@ -78,7 +78,7 @@ It classifies `Aβ_smooth` into five named bands:
|
|
|
78
78
|
| `0.95 ≤ Aβ ≤ 1.05` | `OSCILLATING` | Break — return best-so-far |
|
|
79
79
|
| `> 1.05` | `DIVERGING` | Abort — roll back to best-so-far |
|
|
80
80
|
|
|
81
|
-
Plus a short-circuit: if observed error drops at or below `target_error`, the loop stops immediately with state `TARGET_MET`.
|
|
81
|
+
Plus a short-circuit: if observed error drops at or below `target_error`, the loop stops immediately with state `TARGET_MET`. The default `target_error=0.0` short-circuits on exactly zero error — the natural completion signal for verifier-driven loops. Pass `target_error=None` to disable the short-circuit and rely on stability detection alone.
|
|
82
82
|
|
|
83
83
|
The `±0.05` noise band around `Aβ=1` absorbs stochastic jitter from agent outputs without triggering false-positive aborts. The `0.85` `STALLING` boundary is an early warning — by the time `Aβ` crosses `1.0`, you've already wasted iterations.
|
|
84
84
|
|
|
@@ -118,7 +118,7 @@ This transforms divergence detection from "abort with garbage" into "abort with
|
|
|
118
118
|
|
|
119
119
|
Construct the monitor.
|
|
120
120
|
|
|
121
|
-
- `target_error` — Stop when an observed error drops at or below this. Default `0.0`
|
|
121
|
+
- `target_error` — Stop when an observed error drops at or below this. Default `0.0` short-circuits on exactly zero error (the natural completion signal for verifier-driven loops). Pass `None` to disable the short-circuit entirely.
|
|
122
122
|
- `max_iterations` — Hard safety cap. Default `None` (rely on stability detection). Recommended ~20–50 for production.
|
|
123
123
|
- `thresholds` — Custom `ThresholdBands` if defaults don't fit your domain.
|
|
124
124
|
- `smoothing_window` — EMA window for the smoothed Aβ. Default 3.
|
|
@@ -191,10 +191,13 @@ The hosted endpoint at `telemetry.loopgain.ai` is one acceptable destination. Th
|
|
|
191
191
|
Thin wrappers under `loopgain.integrations` drive each major agent framework's iteration with a `LoopGain` monitor and auto-stamp `framework="<name>"` on telemetry. The frameworks themselves are **optional dependencies** — install the extra you need:
|
|
192
192
|
|
|
193
193
|
```bash
|
|
194
|
-
pip install 'loopgain[langgraph]'
|
|
195
|
-
pip install 'loopgain[crewai]'
|
|
196
|
-
pip install 'loopgain[autogen]'
|
|
197
|
-
pip install 'loopgain[
|
|
194
|
+
pip install 'loopgain[langgraph]' # LangGraph
|
|
195
|
+
pip install 'loopgain[crewai]' # CrewAI
|
|
196
|
+
pip install 'loopgain[autogen]' # AutoGen v0.4+
|
|
197
|
+
pip install 'loopgain[langchain]' # LangChain (create_agent / AgentExecutor)
|
|
198
|
+
pip install 'loopgain[openai-agents]' # OpenAI Agents SDK
|
|
199
|
+
pip install 'loopgain[claude-agent-sdk]' # Anthropic Claude Agent SDK
|
|
200
|
+
pip install 'loopgain[all]' # all six
|
|
198
201
|
```
|
|
199
202
|
|
|
200
203
|
All adapters take a `LoopGain` instance plus an `error_fn` you provide — the framework doesn't know what your error signal is, so the adapter doesn't either. `error_fn` returns a non-negative number (or `None` to skip an iteration).
|
|
@@ -281,15 +284,120 @@ lg.send_telemetry(
|
|
|
281
284
|
|
|
282
285
|
Pass a `cancellation_token` to `adapter.run(...)` and the adapter will cancel it when LoopGain reaches a terminal state (target met, oscillation, divergence). The legacy v0.2 `ConversableAgent.initiate_chat` API is **not** supported — use the v0.4 event-driven runtime.
|
|
283
286
|
|
|
287
|
+
### LangChain
|
|
288
|
+
|
|
289
|
+
Duck-types against any LangChain agent that exposes `.stream(input, **kwargs)` / `.astream(input, **kwargs)` — both the current `langchain.agents.create_agent()` (v1+) and the legacy `AgentExecutor`. The adapter forwards `**stream_kwargs` verbatim, so the chunk shape your `error_fn` sees is the one your agent emits.
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
from langchain.agents import create_agent
|
|
293
|
+
from loopgain import LoopGain
|
|
294
|
+
from loopgain.integrations import LangChainAdapter
|
|
295
|
+
|
|
296
|
+
agent = create_agent(model="gpt-5-nano", tools=[get_weather])
|
|
297
|
+
lg = LoopGain(target_error=0.0, max_iterations=20)
|
|
298
|
+
|
|
299
|
+
def error_fn(chunk):
|
|
300
|
+
if chunk.get("type") != "updates":
|
|
301
|
+
return None
|
|
302
|
+
# Count unresolved tool calls; drops to 0 once the agent stops calling tools.
|
|
303
|
+
return sum(
|
|
304
|
+
1 for _, update in chunk["data"].items()
|
|
305
|
+
if getattr(update.get("messages", [None])[-1], "tool_calls", None)
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
adapter = LangChainAdapter(lg=lg, error_fn=error_fn)
|
|
309
|
+
final = adapter.run(
|
|
310
|
+
agent,
|
|
311
|
+
{"messages": [{"role": "user", "content": "What's the weather?"}]},
|
|
312
|
+
stream_mode="updates",
|
|
313
|
+
version="v2",
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
lg.send_telemetry(
|
|
317
|
+
endpoint=...,
|
|
318
|
+
token=...,
|
|
319
|
+
framework=adapter.framework_name, # "langchain"
|
|
320
|
+
)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
For legacy `AgentExecutor`: just drop the `stream_mode` / `version` kwargs; each yielded chunk is an `AddableDict` per step (parse `intermediate_steps` or the terminal `output` key in your `error_fn`).
|
|
324
|
+
|
|
325
|
+
### OpenAI Agents SDK
|
|
326
|
+
|
|
327
|
+
Wraps `Runner.run_streamed(agent, input).stream_events()`. The SDK is async-first; the adapter mirrors that. A `run_sync` helper wraps the async path with `asyncio.run` for synchronous callers.
|
|
328
|
+
|
|
329
|
+
```python
|
|
330
|
+
from agents import Agent, function_tool
|
|
331
|
+
from loopgain import LoopGain
|
|
332
|
+
from loopgain.integrations import OpenAIAgentsAdapter
|
|
333
|
+
|
|
334
|
+
agent = Agent(name="Reviser", instructions="...", tools=[...])
|
|
335
|
+
|
|
336
|
+
lg = LoopGain(target_error=0.0, max_iterations=20)
|
|
337
|
+
|
|
338
|
+
def error_fn(event):
|
|
339
|
+
# Default observes only run_item_stream_event; pull the verifier's
|
|
340
|
+
# reported failure count off tool outputs.
|
|
341
|
+
if event.item.type == "tool_call_output_item":
|
|
342
|
+
return float(event.item.output.get("failures", 0))
|
|
343
|
+
return None
|
|
344
|
+
|
|
345
|
+
adapter = OpenAIAgentsAdapter(lg=lg, error_fn=error_fn)
|
|
346
|
+
result = await adapter.run(agent, input="Fix the bug.")
|
|
347
|
+
print(result.final_output)
|
|
348
|
+
|
|
349
|
+
lg.send_telemetry(
|
|
350
|
+
endpoint=...,
|
|
351
|
+
token=...,
|
|
352
|
+
framework=adapter.framework_name, # "openai-agents"
|
|
353
|
+
)
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
By default the adapter only forwards `run_item_stream_event` to `error_fn` — pass `observe_event_types=None` to see every event (including raw token deltas and agent-handoff notifications). When LoopGain reaches a terminal state, the adapter best-effort calls `.cancel()` on the underlying `RunResultStreaming`.
|
|
357
|
+
|
|
358
|
+
### Claude Agent SDK
|
|
359
|
+
|
|
360
|
+
Wraps Anthropic's `claude_agent_sdk.query(prompt=..., options=...)` async iterator. By default observes only `AssistantMessage` (skips `UserMessage` / `SystemMessage` / `ResultMessage`); override with `observe_message_types=None` or a custom tuple.
|
|
361
|
+
|
|
362
|
+
```python
|
|
363
|
+
from claude_agent_sdk import ClaudeAgentOptions, TextBlock
|
|
364
|
+
from loopgain import LoopGain
|
|
365
|
+
from loopgain.integrations import ClaudeAgentSDKAdapter
|
|
366
|
+
|
|
367
|
+
def error_fn(message):
|
|
368
|
+
# Count `FAIL:` markers a self-verifying persona emits.
|
|
369
|
+
for block in getattr(message, "content", []):
|
|
370
|
+
if isinstance(block, TextBlock):
|
|
371
|
+
return float(block.text.count("FAIL:"))
|
|
372
|
+
return None
|
|
373
|
+
|
|
374
|
+
lg = LoopGain(target_error=0.0, max_iterations=20)
|
|
375
|
+
adapter = ClaudeAgentSDKAdapter(lg=lg, error_fn=error_fn)
|
|
376
|
+
|
|
377
|
+
options = ClaudeAgentOptions(system_prompt="Self-verify each draft.")
|
|
378
|
+
result = await adapter.run(
|
|
379
|
+
prompt="Write a haiku about feedback loops.",
|
|
380
|
+
options=options,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
lg.send_telemetry(
|
|
384
|
+
endpoint=...,
|
|
385
|
+
token=...,
|
|
386
|
+
framework=adapter.framework_name, # "claude-agent-sdk"
|
|
387
|
+
)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
For the bidirectional `ClaudeSDKClient` use case, pass `message_iterator=client.receive_messages()` instead of `prompt=...`.
|
|
391
|
+
|
|
284
392
|
### Custom integrations
|
|
285
393
|
|
|
286
|
-
For frameworks without an adapter, the raw `LoopGain.observe()` API works against any iterable. The adapters are 100-200 lines each — copy one of `loopgain/integrations/{langgraph,crewai,autogen}.py` as a starting point.
|
|
394
|
+
For frameworks without an adapter, the raw `LoopGain.observe()` API works against any iterable. The adapters are 100-200 lines each — copy one of `loopgain/integrations/{langgraph,crewai,autogen,langchain,openai_agents,claude_agent_sdk}.py` as a starting point.
|
|
287
395
|
|
|
288
396
|
---
|
|
289
397
|
|
|
290
398
|
## Status
|
|
291
399
|
|
|
292
|
-
**Initial public release.** Core library shipped (current version: see the PyPI badge at the top). Framework adapters (LangGraph, CrewAI, AutoGen) are installable as optional extras. The cloud-aggregator [telemetry receiver](https://github.com/loopgain-ai/telemetry-receiver) and [dashboard](https://github.com/loopgain-ai/dashboard) are live as separate open-source repos. The math and the API surface are stable.
|
|
400
|
+
**Initial public release.** Core library shipped (current version: see the PyPI badge at the top). Framework adapters (LangGraph, CrewAI, AutoGen, LangChain, OpenAI Agents SDK, Claude Agent SDK) are installable as optional extras. The cloud-aggregator [telemetry receiver](https://github.com/loopgain-ai/telemetry-receiver) and [dashboard](https://github.com/loopgain-ai/dashboard) are live as separate open-source repos. The math and the API surface are stable.
|
|
293
401
|
|
|
294
402
|
This is alpha software. The API may break before 1.0 if production usage surfaces design issues; pin the version.
|
|
295
403
|
|
|
@@ -113,8 +113,9 @@ class LoopGainResult:
|
|
|
113
113
|
"""First non-None ``eta`` snapshot captured during the loop —
|
|
114
114
|
the predicted iterations-remaining at the moment the prediction
|
|
115
115
|
became computable. ``None`` if no prediction was ever made
|
|
116
|
-
(e.g., ``target_error
|
|
117
|
-
|
|
116
|
+
(e.g., ``target_error`` is ``None`` or ``0`` so the prediction
|
|
117
|
+
formula is undefined, loop never converged toward target, or the
|
|
118
|
+
loop terminated before two observations)."""
|
|
118
119
|
|
|
119
120
|
first_eta_at_iteration: Optional[int] = None
|
|
120
121
|
"""Iteration count when ``first_eta_prediction`` was captured.
|
|
@@ -149,7 +150,11 @@ class LoopGain:
|
|
|
149
150
|
|
|
150
151
|
Args:
|
|
151
152
|
target_error: Stop when an observed error drops at or below this.
|
|
152
|
-
Default ``0.0``
|
|
153
|
+
Default ``0.0`` short-circuits on exactly zero error — the
|
|
154
|
+
natural completion signal for most verifiers (no failing
|
|
155
|
+
tests, no validation errors, etc.). Pass ``None`` to disable
|
|
156
|
+
the short-circuit entirely and rely only on stability
|
|
157
|
+
detection and ``max_iterations``.
|
|
153
158
|
max_iterations: Hard safety cap. Default ``None`` (rely on
|
|
154
159
|
stability detection). Recommended ~20-50 for production.
|
|
155
160
|
thresholds: Custom ``ThresholdBands``. Default is the canonical
|
|
@@ -161,7 +166,7 @@ class LoopGain:
|
|
|
161
166
|
|
|
162
167
|
def __init__(
|
|
163
168
|
self,
|
|
164
|
-
target_error: float = 0.0,
|
|
169
|
+
target_error: Optional[float] = 0.0,
|
|
165
170
|
max_iterations: Optional[int] = None,
|
|
166
171
|
thresholds: Optional[ThresholdBands] = None,
|
|
167
172
|
smoothing_window: int = 3,
|
|
@@ -169,12 +174,14 @@ class LoopGain:
|
|
|
169
174
|
) -> None:
|
|
170
175
|
if smoothing_window < 1:
|
|
171
176
|
raise ValueError("smoothing_window must be >= 1")
|
|
172
|
-
if target_error < 0:
|
|
173
|
-
raise ValueError("target_error must be non-negative")
|
|
177
|
+
if target_error is not None and target_error < 0:
|
|
178
|
+
raise ValueError("target_error must be non-negative or None")
|
|
174
179
|
if max_iterations is not None and max_iterations < 1:
|
|
175
180
|
raise ValueError("max_iterations must be >= 1 or None")
|
|
176
181
|
|
|
177
|
-
self.target_error =
|
|
182
|
+
self.target_error: Optional[float] = (
|
|
183
|
+
float(target_error) if target_error is not None else None
|
|
184
|
+
)
|
|
178
185
|
self.max_iterations = max_iterations
|
|
179
186
|
self.thresholds = thresholds or ThresholdBands()
|
|
180
187
|
self.smoothing_window = smoothing_window
|
|
@@ -217,7 +224,9 @@ class LoopGain:
|
|
|
217
224
|
self._outputs.append(output)
|
|
218
225
|
|
|
219
226
|
# TARGET_MET short-circuit takes precedence over band classification.
|
|
220
|
-
|
|
227
|
+
# target_error=None disables the short-circuit entirely; any non-None
|
|
228
|
+
# value (including 0.0) fires TARGET_MET when magnitude <= target.
|
|
229
|
+
if self.target_error is not None and magnitude <= self.target_error:
|
|
221
230
|
self._state = TARGET_MET
|
|
222
231
|
self._terminal = True
|
|
223
232
|
return self._state
|
|
@@ -289,12 +298,13 @@ class LoopGain:
|
|
|
289
298
|
n_remaining = log(E_target / E_current) / log(Aβ_smooth)
|
|
290
299
|
|
|
291
300
|
Returns ``None`` when the prediction isn't well-defined:
|
|
292
|
-
no Aβ yet, ``target_error`` is
|
|
293
|
-
|
|
301
|
+
no Aβ yet, ``target_error`` is ``None`` (no target to predict
|
|
302
|
+
against) or ``0`` (the formula has a log-of-zero singularity),
|
|
303
|
+
target already met, or ``Aβ_smooth >= 1`` (non-converging gain).
|
|
294
304
|
"""
|
|
295
305
|
if not self._smoothed_history or not self._error_history:
|
|
296
306
|
return None
|
|
297
|
-
if self.target_error <= 0:
|
|
307
|
+
if self.target_error is None or self.target_error <= 0:
|
|
298
308
|
return None
|
|
299
309
|
e_current = self._error_history[-1]
|
|
300
310
|
if e_current <= self.target_error:
|
|
@@ -5,22 +5,26 @@ calls ``LoopGain.observe()`` on each step with an error magnitude derived
|
|
|
5
5
|
from a user-provided ``error_fn``, and (optionally) sends telemetry on
|
|
6
6
|
completion with ``framework="<name>"`` auto-stamped.
|
|
7
7
|
|
|
8
|
-
Adapters are isolated submodules so the host frameworks (langgraph,
|
|
9
|
-
autogen
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
Adapters are isolated submodules so the host frameworks (langgraph,
|
|
9
|
+
crewai, autogen, langchain, openai-agents, claude-agent-sdk) remain
|
|
10
|
+
*optional* dependencies. Importing this package does not import any
|
|
11
|
+
framework — each adapter only imports its framework when its class is
|
|
12
|
+
instantiated, and surfaces a clear ``ImportError`` if missing.
|
|
12
13
|
|
|
13
14
|
Install adapter extras::
|
|
14
15
|
|
|
15
|
-
pip install 'loopgain[langgraph]'
|
|
16
|
-
pip install 'loopgain[crewai]'
|
|
17
|
-
pip install 'loopgain[autogen]'
|
|
18
|
-
pip install 'loopgain[
|
|
16
|
+
pip install 'loopgain[langgraph]' # LangGraph
|
|
17
|
+
pip install 'loopgain[crewai]' # CrewAI
|
|
18
|
+
pip install 'loopgain[autogen]' # AutoGen v0.4+
|
|
19
|
+
pip install 'loopgain[langchain]' # LangChain (create_agent or AgentExecutor)
|
|
20
|
+
pip install 'loopgain[openai-agents]' # OpenAI Agents SDK
|
|
21
|
+
pip install 'loopgain[claude-agent-sdk]' # Anthropic Claude Agent SDK
|
|
22
|
+
pip install 'loopgain[all]' # all of the above
|
|
19
23
|
|
|
20
24
|
Common pattern::
|
|
21
25
|
|
|
22
26
|
from loopgain import LoopGain
|
|
23
|
-
from loopgain.integrations import LangGraphAdapter # or
|
|
27
|
+
from loopgain.integrations import LangGraphAdapter # or any other adapter
|
|
24
28
|
|
|
25
29
|
lg = LoopGain(target_error=0.1, max_iterations=20)
|
|
26
30
|
adapter = LangGraphAdapter(
|
|
@@ -40,13 +44,16 @@ Common pattern::
|
|
|
40
44
|
|
|
41
45
|
from __future__ import annotations
|
|
42
46
|
|
|
43
|
-
# Adapters are imported lazily so importing this package does NOT pull
|
|
44
|
-
#
|
|
45
|
-
#
|
|
47
|
+
# Adapters are imported lazily so importing this package does NOT pull
|
|
48
|
+
# in any framework. Each name resolves on first attribute access and
|
|
49
|
+
# surfaces a clear ImportError if its host framework isn't installed.
|
|
46
50
|
__all__ = [
|
|
47
51
|
"LangGraphAdapter",
|
|
48
52
|
"CrewAIAdapter",
|
|
49
53
|
"AutoGenAdapter",
|
|
54
|
+
"LangChainAdapter",
|
|
55
|
+
"OpenAIAgentsAdapter",
|
|
56
|
+
"ClaudeAgentSDKAdapter",
|
|
50
57
|
]
|
|
51
58
|
|
|
52
59
|
|
|
@@ -63,4 +70,16 @@ def __getattr__(name: str):
|
|
|
63
70
|
from loopgain.integrations.autogen import AutoGenAdapter
|
|
64
71
|
|
|
65
72
|
return AutoGenAdapter
|
|
73
|
+
if name == "LangChainAdapter":
|
|
74
|
+
from loopgain.integrations.langchain import LangChainAdapter
|
|
75
|
+
|
|
76
|
+
return LangChainAdapter
|
|
77
|
+
if name == "OpenAIAgentsAdapter":
|
|
78
|
+
from loopgain.integrations.openai_agents import OpenAIAgentsAdapter
|
|
79
|
+
|
|
80
|
+
return OpenAIAgentsAdapter
|
|
81
|
+
if name == "ClaudeAgentSDKAdapter":
|
|
82
|
+
from loopgain.integrations.claude_agent_sdk import ClaudeAgentSDKAdapter
|
|
83
|
+
|
|
84
|
+
return ClaudeAgentSDKAdapter
|
|
66
85
|
raise AttributeError(f"module 'loopgain.integrations' has no attribute {name!r}")
|