entroplain 0.1.0 → 0.1.1

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.
Binary file
@@ -0,0 +1,178 @@
1
+ # Entroplain Usage Guide for Agents
2
+
3
+ ## Quick Setup
4
+
5
+ ### For OpenClaw/Claude Code (Proxy Method)
6
+
7
+ Run the entropy proxy and point your agent to it:
8
+
9
+ ```bash
10
+ # Start the proxy (monitors entropy, enables early exit)
11
+ python -m entroplain.proxy --port 8765 --log-entropy
12
+
13
+ # Set environment to use proxy
14
+ export OPENAI_BASE_URL=http://localhost:8765/v1
15
+ # or for NVIDIA:
16
+ export NVIDIA_BASE_URL=http://localhost:8765/v1
17
+ ```
18
+
19
+ Now OpenClaw/Claude Code will automatically have entropy monitoring!
20
+
21
+ ### How the Proxy Works
22
+
23
+ ```
24
+ Agent -> Proxy (localhost:8765) -> Real API
25
+ |
26
+ v
27
+ Entropy Monitor
28
+ |
29
+ v
30
+ Early Exit Check
31
+ ```
32
+
33
+ The proxy:
34
+ 1. Intercepts all chat completion requests
35
+ 2. Enables logprobs automatically
36
+ 3. Calculates entropy for each token
37
+ 4. Terminates stream when reasoning converges
38
+ 5. Passes everything through unchanged to the agent
39
+
40
+ ---
41
+
42
+ ## Direct Usage (Python)
43
+
44
+ ```python
45
+ from entroplain import EntropyMonitor, NVIDIAProvider
46
+
47
+ monitor = EntropyMonitor()
48
+ provider = NVIDIAProvider()
49
+
50
+ for token in provider.stream_with_entropy(
51
+ model="meta/llama-3.1-70b-instruct",
52
+ messages=[{"role": "user", "content": "Solve: x^2 = 16"}]
53
+ ):
54
+ monitor.track(token.token, token.entropy)
55
+ print(token.token, end="")
56
+
57
+ if monitor.should_exit():
58
+ print("\n[Early exit - reasoning converged]")
59
+ break
60
+
61
+ print(f"\nStats: {monitor.get_stats()}")
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Supported Providers
67
+
68
+ | Provider | Works? | How |
69
+ |----------|--------|-----|
70
+ | OpenAI | YES | `logprobs: true` |
71
+ | NVIDIA NIM | YES | OpenAI-compatible |
72
+ | Anthropic Claude 4 | YES | `logprobs: True` |
73
+ | Google Gemini | YES | `response_logprobs=True` |
74
+ | Ollama (local) | YES | Built-in logit access |
75
+ | llama.cpp | YES | Built-in logit access |
76
+
77
+ ---
78
+
79
+ ## Configuration
80
+
81
+ ### Exit Conditions
82
+
83
+ ```python
84
+ monitor = EntropyMonitor(
85
+ entropy_threshold=0.15, # Exit when entropy drops below this
86
+ min_valleys=2, # Require N reasoning milestones
87
+ min_tokens=50, # Don't exit before this many tokens
88
+ velocity_threshold=0.05, # Exit when change rate stabilizes
89
+ exit_condition="combined" # or: "valleys_plateau", "entropy_drop", "velocity_zero"
90
+ )
91
+ ```
92
+
93
+ ### Environment Variables
94
+
95
+ ```bash
96
+ # API keys (used by providers)
97
+ export OPENAI_API_KEY=sk-...
98
+ export ANTHROPIC_API_KEY=sk-ant-...
99
+ export NVIDIA_API_KEY=nvapi-...
100
+ export GOOGLE_API_KEY=...
101
+
102
+ # For proxy
103
+ export ENTROPPLAIN_PORT=8765
104
+ export ENTROPPLAIN_LOG_ENTROPY=true
105
+ ```
106
+
107
+ ---
108
+
109
+ ## CLI
110
+
111
+ ```bash
112
+ # Analyze a prompt
113
+ entroplain analyze "What is 2+2?" --model gpt-4o
114
+
115
+ # Stream with early exit
116
+ entroplain stream "Explain quantum computing" --exit-on-converge
117
+
118
+ # Run proxy
119
+ entroplain proxy --port 8765 --log-entropy
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Agent Integration Examples
125
+
126
+ ### OpenClaw with Proxy
127
+
128
+ ```yaml
129
+ # In config.yaml
130
+ llm:
131
+ provider: openai-compatible
132
+ base_url: http://localhost:8765/v1 # Point to proxy
133
+ primary_model: meta/llama-3.1-70b-instruct
134
+ ```
135
+
136
+ ### Claude Code with Proxy
137
+
138
+ Set environment before running:
139
+ ```bash
140
+ export ANTHROPIC_BASE_URL=http://localhost:8765/v1
141
+ claude
142
+ ```
143
+
144
+ ### Custom Agent
145
+
146
+ ```python
147
+ from entroplain.hooks import EntropyHook
148
+
149
+ hook = EntropyHook(config={"entropy_threshold": 0.15})
150
+
151
+ for token in your_agent.generate_stream():
152
+ result = hook.on_token(token.text, token.entropy)
153
+
154
+ if result["should_exit"]:
155
+ print(f"Early exit at token {result['index']}")
156
+ break
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Troubleshooting
162
+
163
+ ### "No logprobs returned"
164
+ Some models don't support logprobs. Try a different model or check provider docs.
165
+
166
+ ### "Entropy is always 0"
167
+ Make sure `logprobs: true` and `top_logprobs: 5` are set in your API request.
168
+
169
+ ### "Proxy won't start"
170
+ Install dependencies: `pip install entroplain[all] fastapi uvicorn httpx`
171
+
172
+ ---
173
+
174
+ ## Learn More
175
+
176
+ - GitHub: https://github.com/entroplain/entroplain
177
+ - PyPI: https://pypi.org/project/entroplain/
178
+ - npm: https://www.npmjs.com/package/entroplain
@@ -2,7 +2,7 @@
2
2
  Entroplain — Entropy-based early exit for efficient agent reasoning.
3
3
  """
4
4
 
5
- __version__ = "0.1.0"
5
+ __version__ = "0.1.1"
6
6
  __author__ = "Entroplain Contributors"
7
7
 
8
8
  from .monitor import EntropyMonitor, calculate_entropy
@@ -15,6 +15,7 @@ from .providers import (
15
15
  LlamaCppProvider,
16
16
  )
17
17
  from .hooks import track_entropy, early_exit
18
+ from .proxy import EntropyProxy, ProxyConfig
18
19
 
19
20
  __all__ = [
20
21
  "EntropyMonitor",
@@ -27,4 +28,6 @@ __all__ = [
27
28
  "LlamaCppProvider",
28
29
  "track_entropy",
29
30
  "early_exit",
31
+ "EntropyProxy",
32
+ "ProxyConfig",
30
33
  ]
@@ -0,0 +1,278 @@
1
+ """
2
+ Entropy Monitoring Proxy for OpenClaw/Claude Code.
3
+
4
+ This proxy intercepts LLM API calls and adds entropy monitoring,
5
+ enabling early exit without modifying the agent framework itself.
6
+
7
+ Usage:
8
+ # Set as your API endpoint
9
+ export OPENAI_BASE_URL=http://localhost:8765
10
+
11
+ # Run the proxy
12
+ python -m entroplain.proxy --port 8765 --provider openai
13
+ """
14
+
15
+ import json
16
+ import asyncio
17
+ import logging
18
+ from typing import Optional, Dict, Any, AsyncIterator
19
+ from dataclasses import dataclass
20
+ import httpx
21
+ from fastapi import FastAPI, Request, Response
22
+ from fastapi.responses import StreamingResponse
23
+ import uvicorn
24
+
25
+ from .monitor import EntropyMonitor
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ @dataclass
31
+ class ProxyConfig:
32
+ """Configuration for the entropy proxy."""
33
+ port: int = 8765
34
+ provider: str = "openai" # openai, anthropic, nvidia
35
+ api_base: str = "https://api.openai.com/v1"
36
+ entropy_threshold: float = 0.15
37
+ min_valleys: int = 2
38
+ min_tokens: int = 50
39
+ velocity_threshold: float = 0.05
40
+ enable_early_exit: bool = True
41
+ log_entropy: bool = True
42
+
43
+
44
+ class EntropyProxy:
45
+ """
46
+ Proxy that adds entropy monitoring to LLM API calls.
47
+
48
+ Intercepts streaming responses, calculates entropy, and can
49
+ terminate early when reasoning has converged.
50
+ """
51
+
52
+ def __init__(self, config: ProxyConfig):
53
+ self.config = config
54
+ self.monitor = EntropyMonitor(
55
+ entropy_threshold=config.entropy_threshold,
56
+ min_valleys=config.min_valleys,
57
+ min_tokens=config.min_tokens,
58
+ velocity_threshold=config.velocity_threshold
59
+ )
60
+ self.app = FastAPI(title="Entroplain Proxy")
61
+ self._setup_routes()
62
+
63
+ def _setup_routes(self):
64
+ @self.app.post("/v1/chat/completions")
65
+ async def chat_completions(request: Request):
66
+ return await self._handle_chat(request)
67
+
68
+ @self.app.get("/health")
69
+ async def health():
70
+ return {"status": "ok", "monitor": self.monitor.get_stats()}
71
+
72
+ @self.app.post("/reset")
73
+ async def reset():
74
+ self.monitor.reset()
75
+ return {"status": "reset"}
76
+
77
+ async def _handle_chat(self, request: Request):
78
+ """Handle chat completion requests with entropy monitoring."""
79
+ body = await request.json()
80
+
81
+ # Ensure logprobs are enabled for entropy calculation
82
+ if "logprobs" not in body:
83
+ body["logprobs"] = True
84
+ if "top_logprobs" not in body:
85
+ body["top_logprobs"] = 5
86
+
87
+ # Reset monitor for new request
88
+ self.monitor.reset()
89
+
90
+ # Forward request to actual API
91
+ async with httpx.AsyncClient() as client:
92
+ response = await client.post(
93
+ f"{self.config.api_base}/chat/completions",
94
+ json=body,
95
+ headers={
96
+ "Content-Type": "application/json",
97
+ "Authorization": request.headers.get("Authorization", "")
98
+ },
99
+ timeout=120.0
100
+ )
101
+
102
+ if not body.get("stream", False):
103
+ # Non-streaming: just return response
104
+ return Response(
105
+ content=response.content,
106
+ status_code=response.status_code,
107
+ headers=dict(response.headers)
108
+ )
109
+
110
+ # Streaming: monitor entropy and potentially exit early
111
+ return StreamingResponse(
112
+ self._stream_with_entropy(response),
113
+ media_type="text/event-stream"
114
+ )
115
+
116
+ async def _stream_with_entropy(
117
+ self, response: httpx.Response
118
+ ) -> AsyncIterator[str]:
119
+ """Stream response with entropy monitoring."""
120
+ exited_early = False
121
+ full_content = ""
122
+
123
+ async for line in response.aiter_lines():
124
+ if not line.startswith("data: "):
125
+ yield line + "\n"
126
+ continue
127
+
128
+ data = line[6:] # Remove "data: " prefix
129
+ if data == "[DONE]":
130
+ yield line + "\n"
131
+ break
132
+
133
+ try:
134
+ chunk = json.loads(data)
135
+ except json.JSONDecodeError:
136
+ yield line + "\n"
137
+ continue
138
+
139
+ # Extract token and logprobs
140
+ if chunk.get("choices"):
141
+ choice = chunk["choices"][0]
142
+
143
+ # Get token content
144
+ if choice.get("delta", {}).get("content"):
145
+ token = choice["delta"]["content"]
146
+ full_content += token
147
+
148
+ # Calculate entropy from logprobs
149
+ if choice.get("logprobs", {}).get("content"):
150
+ logprobs_data = choice["logprobs"]["content"]
151
+ if logprobs_data:
152
+ entropy = self._calculate_entropy(logprobs_data[0])
153
+ self.monitor.track(token, entropy)
154
+
155
+ if self.config.log_entropy:
156
+ logger.info(
157
+ f"Token: {repr(token)}, Entropy: {entropy:.4f}, "
158
+ f"Valleys: {len(self.monitor.get_valleys())}"
159
+ )
160
+
161
+ # Check for early exit
162
+ if (
163
+ self.config.enable_early_exit
164
+ and self.monitor.should_exit()
165
+ ):
166
+ logger.info(
167
+ f"Early exit triggered! "
168
+ f"Tokens: {len(full_content)}, "
169
+ f"Valleys: {len(self.monitor.get_valleys())}"
170
+ )
171
+ exited_early = True
172
+ yield "data: [DONE]\n\n"
173
+ break
174
+
175
+ yield line + "\n"
176
+
177
+ if not exited_early:
178
+ logger.info(
179
+ f"Stream completed. "
180
+ f"Tokens: {self.monitor.get_stats()['token_count']}, "
181
+ f"Valleys: {len(self.monitor.get_valleys())}"
182
+ )
183
+
184
+ def _calculate_entropy(self, logprobs_data: Dict) -> float:
185
+ """Calculate Shannon entropy from logprobs."""
186
+ import math
187
+
188
+ if not logprobs_data or "top_logprobs" not in logprobs_data:
189
+ return 0.0
190
+
191
+ entropy = 0.0
192
+ for lp in logprobs_data["top_logprobs"]:
193
+ prob = math.exp(lp["logprob"])
194
+ if prob > 0:
195
+ entropy -= prob * math.log2(prob + 1e-10)
196
+
197
+ return entropy
198
+
199
+ def run(self):
200
+ """Start the proxy server."""
201
+ uvicorn.run(self.app, host="0.0.0.0", port=self.config.port)
202
+
203
+
204
+ def main():
205
+ """CLI entry point for running the proxy."""
206
+ import argparse
207
+
208
+ parser = argparse.ArgumentParser(description="Entropy Monitoring Proxy")
209
+ parser.add_argument("--port", type=int, default=8765, help="Proxy port")
210
+ parser.add_argument(
211
+ "--provider",
212
+ default="openai",
213
+ choices=["openai", "anthropic", "nvidia"],
214
+ help="LLM provider"
215
+ )
216
+ parser.add_argument(
217
+ "--api-base",
218
+ default="https://api.openai.com/v1",
219
+ help="API base URL"
220
+ )
221
+ parser.add_argument(
222
+ "--entropy-threshold",
223
+ type=float,
224
+ default=0.15,
225
+ help="Entropy threshold for early exit"
226
+ )
227
+ parser.add_argument(
228
+ "--min-valleys",
229
+ type=int,
230
+ default=2,
231
+ help="Minimum valleys before early exit"
232
+ )
233
+ parser.add_argument(
234
+ "--no-early-exit",
235
+ action="store_true",
236
+ help="Disable early exit (monitor only)"
237
+ )
238
+ parser.add_argument(
239
+ "--log-entropy",
240
+ action="store_true",
241
+ help="Log entropy values to console"
242
+ )
243
+
244
+ args = parser.parse_args()
245
+
246
+ config = ProxyConfig(
247
+ port=args.port,
248
+ provider=args.provider,
249
+ api_base=args.api_base,
250
+ entropy_threshold=args.entropy_threshold,
251
+ min_valleys=args.min_valleys,
252
+ enable_early_exit=not args.no_early_exit,
253
+ log_entropy=args.log_entropy
254
+ )
255
+
256
+ proxy = EntropyProxy(config)
257
+
258
+ print(f"""
259
+ ╔═══════════════════════════════════════════════════════════╗
260
+ ║ ENTROPPLAIN ENTROPY MONITORING PROXY ║
261
+ ╠═══════════════════════════════════════════════════════════╣
262
+ ║ Proxy running on: http://localhost:{args.port} ║
263
+ ║ Provider: {args.provider:<10} ║
264
+ ║ API Base: {args.api_base:<30} ║
265
+ ║ Early Exit: {'DISABLED' if args.no_early_exit else 'ENABLED'} ║
266
+ ╠═══════════════════════════════════════════════════════════╣
267
+ ║ Set your agent's API endpoint to: ║
268
+ ║ export OPENAI_BASE_URL=http://localhost:{args.port} ║
269
+ ║ # or for NVIDIA: ║
270
+ ║ export NVIDIA_BASE_URL=http://localhost:{args.port} ║
271
+ ╚═══════════════════════════════════════════════════════════╝
272
+ """)
273
+
274
+ proxy.run()
275
+
276
+
277
+ if __name__ == "__main__":
278
+ main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "entroplain",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Entropy-based early exit for efficient agent reasoning",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "entroplain"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Entropy-based early exit for efficient agent reasoning"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -44,6 +44,9 @@ all = [
44
44
  "requests>=2.28.0",
45
45
  "aiohttp>=3.8.0",
46
46
  "llama-cpp-python>=0.2.0",
47
+ "fastapi>=0.100.0",
48
+ "uvicorn>=0.23.0",
49
+ "httpx>=0.24.0",
47
50
  ]
48
51
  dev = [
49
52
  "pytest>=7.0.0",
@@ -61,6 +64,7 @@ Issues = "https://github.com/entroplain/entroplain/issues"
61
64
 
62
65
  [project.scripts]
63
66
  entroplain = "entroplain.cli:main"
67
+ entroplain-proxy = "entroplain.proxy:main"
64
68
 
65
69
  [tool.setuptools.packages.find]
66
70
  where = ["."]
Binary file