entroplain 0.1.0

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.
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "entroplain",
3
+ "version": "0.1.0",
4
+ "description": "Entropy-based early exit for efficient agent reasoning",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "jest",
10
+ "lint": "eslint src/",
11
+ "prepublishOnly": "npm run build"
12
+ },
13
+ "keywords": [
14
+ "llm",
15
+ "agent",
16
+ "entropy",
17
+ "early-exit",
18
+ "reasoning",
19
+ "efficiency"
20
+ ],
21
+ "author": "Entroplain Contributors",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/entroplain/entroplain.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/entroplain/entroplain/issues"
29
+ },
30
+ "homepage": "https://github.com/entroplain/entroplain#readme",
31
+ "devDependencies": {
32
+ "@types/jest": "^29.5.0",
33
+ "@types/node": "^20.0.0",
34
+ "jest": "^29.5.0",
35
+ "ts-jest": "^29.1.0",
36
+ "typescript": "^5.0.0"
37
+ },
38
+ "dependencies": {
39
+ "openai": "^4.0.0"
40
+ },
41
+ "engines": {
42
+ "node": ">=18.0.0"
43
+ }
44
+ }
package/pyproject.toml ADDED
@@ -0,0 +1,85 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "entroplain"
7
+ version = "0.1.0"
8
+ description = "Entropy-based early exit for efficient agent reasoning"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [
12
+ {name = "Entroplain Contributors"}
13
+ ]
14
+ keywords = ["llm", "agent", "entropy", "early-exit", "efficiency", "reasoning"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Intended Audience :: Science/Research",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ ]
28
+ requires-python = ">=3.8"
29
+ dependencies = [
30
+ "typing-extensions>=4.0.0;python_version<'3.10'",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ openai = ["openai>=1.0.0"]
35
+ anthropic = ["anthropic>=0.25.0"]
36
+ google = ["google-generativeai>=0.3.0"]
37
+ nvidia = ["requests>=2.28.0", "aiohttp>=3.8.0"]
38
+ ollama = ["requests>=2.28.0", "aiohttp>=3.8.0"]
39
+ llama-cpp = ["llama-cpp-python>=0.2.0"]
40
+ all = [
41
+ "openai>=1.0.0",
42
+ "anthropic>=0.25.0",
43
+ "google-generativeai>=0.3.0",
44
+ "requests>=2.28.0",
45
+ "aiohttp>=3.8.0",
46
+ "llama-cpp-python>=0.2.0",
47
+ ]
48
+ dev = [
49
+ "pytest>=7.0.0",
50
+ "pytest-asyncio>=0.21.0",
51
+ "black>=23.0.0",
52
+ "isort>=5.0.0",
53
+ "mypy>=1.0.0",
54
+ ]
55
+
56
+ [project.urls]
57
+ Homepage = "https://github.com/entroplain/entroplain"
58
+ Documentation = "https://github.com/entroplain/entroplain#readme"
59
+ Repository = "https://github.com/entroplain/entroplain.git"
60
+ Issues = "https://github.com/entroplain/entroplain/issues"
61
+
62
+ [project.scripts]
63
+ entroplain = "entroplain.cli:main"
64
+
65
+ [tool.setuptools.packages.find]
66
+ where = ["."]
67
+ include = ["entroplain*"]
68
+
69
+ [tool.black]
70
+ line-length = 100
71
+ target-version = ["py38", "py39", "py310", "py311", "py312"]
72
+
73
+ [tool.isort]
74
+ profile = "black"
75
+ line_length = 100
76
+
77
+ [tool.mypy]
78
+ python_version = "3.8"
79
+ warn_return_any = true
80
+ warn_unused_configs = true
81
+ disallow_untyped_defs = true
82
+
83
+ [tool.pytest.ini_options]
84
+ asyncio_mode = "auto"
85
+ testpaths = ["tests"]
package/src/hooks.ts ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Hooks for agent framework integration
3
+ */
4
+
5
+ import { EntropyMonitor } from './monitor';
6
+ import { EntropyPoint, MonitorStats } from './types';
7
+
8
+ // Global monitor instance
9
+ let monitor: EntropyMonitor | null = null;
10
+ let config: Record<string, unknown> = {};
11
+
12
+ /**
13
+ * Initialize entropy hooks
14
+ */
15
+ export function initHooks(userConfig: Record<string, unknown> = {}): void {
16
+ config = userConfig;
17
+ monitor = new EntropyMonitor({
18
+ entropyThreshold: (config.entropyThreshold as number) ?? 0.15,
19
+ minValleys: (config.minValleys as number) ?? 2,
20
+ velocityThreshold: (config.velocityThreshold as number) ?? 0.05,
21
+ minTokens: (config.minTokens as number) ?? 50,
22
+ });
23
+ }
24
+
25
+ /**
26
+ * Hook to track entropy for each token
27
+ */
28
+ export function trackEntropy(token: string, entropy: number): {
29
+ token: string;
30
+ entropy: number;
31
+ index: number;
32
+ isValley: boolean;
33
+ velocity: number;
34
+ shouldExit: boolean;
35
+ stats: MonitorStats;
36
+ } {
37
+ if (!monitor) {
38
+ initHooks();
39
+ }
40
+
41
+ const point = monitor!.track(token, entropy);
42
+
43
+ return {
44
+ token,
45
+ entropy,
46
+ index: point.index,
47
+ isValley: point.isValley,
48
+ velocity: point.velocity,
49
+ shouldExit: monitor!.shouldExit(),
50
+ stats: monitor!.getStats(),
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Hook to check if reasoning has converged
56
+ */
57
+ export function earlyExit(): boolean {
58
+ if (!monitor) {
59
+ return false;
60
+ }
61
+ return monitor.shouldExit();
62
+ }
63
+
64
+ /**
65
+ * Reset the global monitor state
66
+ */
67
+ export function resetHooks(): void {
68
+ if (monitor) {
69
+ monitor.reset();
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Get the current monitor instance
75
+ */
76
+ export function getMonitor(): EntropyMonitor | null {
77
+ return monitor;
78
+ }
79
+
80
+ /**
81
+ * Class-based hook for frameworks that prefer class instances
82
+ */
83
+ export class EntropyHook {
84
+ private monitor: EntropyMonitor;
85
+ private config: Record<string, unknown>;
86
+
87
+ constructor(userConfig: Record<string, unknown> = {}) {
88
+ this.config = userConfig;
89
+ this.monitor = new EntropyMonitor({
90
+ entropyThreshold: (this.config.entropyThreshold as number) ?? 0.15,
91
+ minValleys: (this.config.minValleys as number) ?? 2,
92
+ velocityThreshold: (this.config.velocityThreshold as number) ?? 0.05,
93
+ minTokens: (this.config.minTokens as number) ?? 50,
94
+ });
95
+ }
96
+
97
+ onToken(token: string, entropy: number): {
98
+ token: string;
99
+ entropy: number;
100
+ index: number;
101
+ isValley: boolean;
102
+ velocity: number;
103
+ shouldExit: boolean;
104
+ stats: MonitorStats;
105
+ } {
106
+ const point = this.monitor.track(token, entropy);
107
+
108
+ return {
109
+ token,
110
+ entropy,
111
+ index: point.index,
112
+ isValley: point.isValley,
113
+ velocity: point.velocity,
114
+ shouldExit: this.monitor.shouldExit(),
115
+ stats: this.monitor.getStats(),
116
+ };
117
+ }
118
+
119
+ shouldExit(): boolean {
120
+ return this.monitor.shouldExit();
121
+ }
122
+
123
+ reset(): void {
124
+ this.monitor.reset();
125
+ }
126
+
127
+ getStats(): MonitorStats {
128
+ return this.monitor.getStats();
129
+ }
130
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Entroplain — Entropy-based early exit for efficient agent reasoning
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ export { EntropyMonitor, calculateEntropy } from './monitor';
8
+ export { EntropyHook, trackEntropy, earlyExit } from './hooks';
9
+ export * from './types';
package/src/monitor.ts ADDED
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Entropy Monitor — Core entropy tracking and early exit logic
3
+ */
4
+
5
+ import { EntropyPoint, MonitorConfig, MonitorStats, ExitCondition } from './types';
6
+
7
+ export class EntropyMonitor {
8
+ private config: MonitorConfig;
9
+ private trajectory: EntropyPoint[] = [];
10
+ private valleys: EntropyPoint[] = [];
11
+ private index: number = 0;
12
+
13
+ constructor(options: Partial<MonitorConfig> = {}) {
14
+ this.config = {
15
+ entropyThreshold: options.entropyThreshold ?? 0.15,
16
+ minValleys: options.minValleys ?? 2,
17
+ velocityThreshold: options.velocityThreshold ?? 0.05,
18
+ minTokens: options.minTokens ?? 50,
19
+ valleyWindow: options.valleyWindow ?? 5,
20
+ plateauThreshold: options.plateauThreshold ?? 3,
21
+ exitCondition: options.exitCondition ?? 'combined',
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Calculate Shannon entropy from log probabilities
27
+ */
28
+ calculateEntropy(logprobs: number[], fromProbs: boolean = false): number {
29
+ if (!logprobs || logprobs.length === 0) {
30
+ return 0.0;
31
+ }
32
+
33
+ let entropy = 0.0;
34
+ for (const lp of logprobs) {
35
+ const prob = fromProbs ? lp : Math.exp(lp);
36
+ if (prob > 0) {
37
+ entropy -= prob * Math.log2(prob + 1e-10);
38
+ }
39
+ }
40
+
41
+ return entropy;
42
+ }
43
+
44
+ /**
45
+ * Track a token and its entropy value
46
+ */
47
+ track(token: string, entropy: number): EntropyPoint {
48
+ const point: EntropyPoint = {
49
+ index: this.index,
50
+ token,
51
+ entropy,
52
+ isValley: false,
53
+ velocity: 0.0,
54
+ };
55
+
56
+ // Calculate velocity
57
+ if (this.trajectory.length > 0) {
58
+ const prev = this.trajectory[this.trajectory.length - 1];
59
+ point.velocity = Math.abs(entropy - prev.entropy);
60
+ }
61
+
62
+ // Detect valley (local minimum)
63
+ if (this.trajectory.length >= 2) {
64
+ const prev2 = this.trajectory[this.trajectory.length - 2];
65
+ const prev1 = this.trajectory[this.trajectory.length - 1];
66
+ if (prev1.entropy < prev2.entropy && prev1.entropy < entropy) {
67
+ prev1.isValley = true;
68
+ this.valleys.push(prev1);
69
+ }
70
+ }
71
+
72
+ this.trajectory.push(point);
73
+ this.index++;
74
+
75
+ return point;
76
+ }
77
+
78
+ /**
79
+ * Get all entropy valleys (local minima)
80
+ */
81
+ getValleys(): Array<[number, number]> {
82
+ return this.valleys.map(v => [v.index, v.entropy]);
83
+ }
84
+
85
+ /**
86
+ * Get current entropy velocity
87
+ */
88
+ getVelocity(): number {
89
+ if (this.trajectory.length < 2) {
90
+ return 0.0;
91
+ }
92
+ return this.trajectory[this.trajectory.length - 1].velocity;
93
+ }
94
+
95
+ /**
96
+ * Get mean entropy over the trajectory
97
+ */
98
+ getMeanEntropy(): number {
99
+ if (this.trajectory.length === 0) {
100
+ return 0.0;
101
+ }
102
+ const sum = this.trajectory.reduce((acc, p) => acc + p.entropy, 0);
103
+ return sum / this.trajectory.length;
104
+ }
105
+
106
+ /**
107
+ * Get the number of detected valleys
108
+ */
109
+ getValleyCount(): number {
110
+ return this.valleys.length;
111
+ }
112
+
113
+ /**
114
+ * Check if valley count has plateaued
115
+ */
116
+ isValleysPlateau(): boolean {
117
+ if (this.valleys.length < this.config.minValleys) {
118
+ return false;
119
+ }
120
+
121
+ if (this.valleys.length < this.config.plateauThreshold) {
122
+ return false;
123
+ }
124
+
125
+ const recent = this.valleys.slice(-this.config.plateauThreshold);
126
+ const spacings: number[] = [];
127
+ for (let i = 0; i < recent.length - 1; i++) {
128
+ spacings.push(recent[i + 1].index - recent[i].index);
129
+ }
130
+
131
+ if (spacings.length === 0) return false;
132
+
133
+ const meanSpacing = spacings.reduce((a, b) => a + b, 0) / spacings.length;
134
+ const variance = spacings.reduce((acc, s) => acc + (s - meanSpacing) ** 2, 0) / spacings.length;
135
+
136
+ return variance < 10;
137
+ }
138
+
139
+ /**
140
+ * Check if current entropy is below threshold
141
+ */
142
+ isEntropyLow(): boolean {
143
+ if (this.trajectory.length === 0) {
144
+ return false;
145
+ }
146
+ return this.trajectory[this.trajectory.length - 1].entropy < this.config.entropyThreshold;
147
+ }
148
+
149
+ /**
150
+ * Check if velocity is below threshold
151
+ */
152
+ isVelocityStable(): boolean {
153
+ return this.getVelocity() < this.config.velocityThreshold;
154
+ }
155
+
156
+ /**
157
+ * Determine if reasoning has converged
158
+ */
159
+ shouldExit(): boolean {
160
+ // Always require minimum tokens
161
+ if (this.trajectory.length < this.config.minTokens) {
162
+ return false;
163
+ }
164
+
165
+ // Always require minimum valleys
166
+ if (this.valleys.length < this.config.minValleys) {
167
+ return false;
168
+ }
169
+
170
+ switch (this.config.exitCondition) {
171
+ case 'valleys_plateau':
172
+ return this.isValleysPlateau();
173
+ case 'entropy_drop':
174
+ return this.isEntropyLow();
175
+ case 'velocity_zero':
176
+ return this.isVelocityStable();
177
+ case 'combined':
178
+ return (this.isEntropyLow() || this.isValleysPlateau()) && this.isVelocityStable();
179
+ default:
180
+ return false;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Alias for shouldExit()
186
+ */
187
+ isConverged(): boolean {
188
+ return this.shouldExit();
189
+ }
190
+
191
+ /**
192
+ * Get full entropy trajectory
193
+ */
194
+ getTrajectory(): number[] {
195
+ return this.trajectory.map(p => p.entropy);
196
+ }
197
+
198
+ /**
199
+ * Get all tracked tokens
200
+ */
201
+ getTokens(): string[] {
202
+ return this.trajectory.map(p => p.token);
203
+ }
204
+
205
+ /**
206
+ * Get summary statistics
207
+ */
208
+ getStats(): MonitorStats {
209
+ if (this.trajectory.length === 0) {
210
+ return {
211
+ tokenCount: 0,
212
+ valleyCount: 0,
213
+ meanEntropy: 0,
214
+ minEntropy: 0,
215
+ maxEntropy: 0,
216
+ currentEntropy: 0,
217
+ currentVelocity: 0,
218
+ isConverged: false,
219
+ };
220
+ }
221
+
222
+ const entropies = this.trajectory.map(p => p.entropy);
223
+
224
+ return {
225
+ tokenCount: this.trajectory.length,
226
+ valleyCount: this.valleys.length,
227
+ meanEntropy: this.getMeanEntropy(),
228
+ minEntropy: Math.min(...entropies),
229
+ maxEntropy: Math.max(...entropies),
230
+ currentEntropy: entropies[entropies.length - 1],
231
+ currentVelocity: this.getVelocity(),
232
+ isConverged: this.shouldExit(),
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Clear all tracked data
238
+ */
239
+ reset(): void {
240
+ this.trajectory = [];
241
+ this.valleys = [];
242
+ this.index = 0;
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Standalone function to calculate Shannon entropy
248
+ */
249
+ export function calculateEntropy(logprobs: number[], fromProbs: boolean = false): number {
250
+ const monitor = new EntropyMonitor();
251
+ return monitor.calculateEntropy(logprobs, fromProbs);
252
+ }
package/src/types.ts ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Type definitions for Entroplain
3
+ */
4
+
5
+ export interface EntropyPoint {
6
+ index: number;
7
+ token: string;
8
+ entropy: number;
9
+ isValley: boolean;
10
+ velocity: number;
11
+ }
12
+
13
+ export interface MonitorConfig {
14
+ entropyThreshold: number;
15
+ minValleys: number;
16
+ velocityThreshold: number;
17
+ minTokens: number;
18
+ valleyWindow: number;
19
+ plateauThreshold: number;
20
+ exitCondition: ExitCondition;
21
+ }
22
+
23
+ export type ExitCondition =
24
+ | 'valleys_plateau'
25
+ | 'entropy_drop'
26
+ | 'velocity_zero'
27
+ | 'combined';
28
+
29
+ export interface MonitorStats {
30
+ tokenCount: number;
31
+ valleyCount: number;
32
+ meanEntropy: number;
33
+ minEntropy: number;
34
+ maxEntropy: number;
35
+ currentEntropy: number;
36
+ currentVelocity: number;
37
+ isConverged: boolean;
38
+ }
39
+
40
+ export interface TokenWithEntropy {
41
+ token: string;
42
+ entropy: number;
43
+ logprob: number;
44
+ topLogprobs: Array<{ token: string; logprob: number }>;
45
+ }
46
+
47
+ export interface ProviderConfig {
48
+ apiKey?: string;
49
+ baseUrl?: string;
50
+ model?: string;
51
+ }
52
+
53
+ export interface StreamOptions {
54
+ model?: string;
55
+ messages?: Array<{ role: string; content: string }>;
56
+ maxTokens?: number;
57
+ topLogprobs?: number;
58
+ }