recursive-llm-ts 5.0.1 → 5.0.2

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 CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "recursive-llm-ts",
3
- "version": "5.0.1",
3
+ "version": "5.0.2",
4
4
  "description": "TypeScript bridge for recursive-llm: Recursive Language Models for unbounded context processing with structured outputs",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [
8
8
  "dist",
9
- "bin",
10
- "go",
9
+ "go/cmd",
10
+ "go/go.mod",
11
+ "go/go.sum",
12
+ "go/rlm/*.go",
13
+ "!go/rlm/*_test.go",
11
14
  "scripts/build-go-binary.js"
12
15
  ],
13
16
  "scripts": {
package/bin/rlm-go DELETED
Binary file
package/go/README.md DELETED
@@ -1,426 +0,0 @@
1
- # RLM Go Module
2
-
3
- Go implementation of Recursive Language Models (RLM) based on the [original Python implementation](https://github.com/alexzhang13/rlm).
4
-
5
- ## Overview
6
-
7
- This is both a standalone Go library and CLI binary that implements the RLM algorithm, allowing language models to process extremely long contexts (100k+ tokens) by storing context as a variable and allowing recursive exploration.
8
-
9
- **Key Difference from Python**: Uses JavaScript REPL instead of Python REPL for code execution.
10
-
11
- ## Installation
12
-
13
- ### As a Go Library
14
-
15
- ```bash
16
- go get github.com/howlerops/recursive-llm-ts/go
17
- ```
18
-
19
- ### Usage as Library
20
-
21
- ```go
22
- package main
23
-
24
- import (
25
- "fmt"
26
- "os"
27
-
28
- "github.com/howlerops/recursive-llm-ts/go/rlm"
29
- )
30
-
31
- func main() {
32
- config := rlm.Config{
33
- MaxDepth: 5,
34
- MaxIterations: 30,
35
- APIKey: os.Getenv("OPENAI_API_KEY"),
36
- }
37
-
38
- engine := rlm.New("gpt-4o-mini", config)
39
-
40
- answer, stats, err := engine.Completion(
41
- "What are the key themes?",
42
- "Your long document here...",
43
- )
44
- if err != nil {
45
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
46
- os.Exit(1)
47
- }
48
-
49
- fmt.Printf("Answer: %s\n", answer)
50
- fmt.Printf("Stats: %d LLM calls, %d iterations\n",
51
- stats.LlmCalls, stats.Iterations)
52
- }
53
- ```
54
-
55
- ### Structured Output
56
-
57
- ```go
58
- schema := &rlm.JSONSchema{
59
- Type: "object",
60
- Properties: map[string]*rlm.JSONSchema{
61
- "summary": {Type: "string"},
62
- "score": {Type: "number"},
63
- },
64
- Required: []string{"summary", "score"},
65
- }
66
-
67
- config := &rlm.StructuredConfig{
68
- Schema: schema,
69
- MaxRetries: 3,
70
- }
71
-
72
- result, stats, err := engine.StructuredCompletion(
73
- "Summarize and score",
74
- document,
75
- config,
76
- )
77
- ```
78
-
79
- ## Building the CLI Binary
80
-
81
- ```bash
82
- # Build the binary
83
- go build -o rlm-go ./cmd/rlm
84
-
85
- # Run tests
86
- go test ./rlm/... -v
87
-
88
- # Build with optimization
89
- go build -ldflags="-s -w" -o rlm-go ./cmd/rlm
90
- ```
91
-
92
- ## Usage
93
-
94
- The binary accepts JSON input on stdin and returns JSON output on stdout.
95
-
96
- ### Input Format
97
-
98
- ```json
99
- {
100
- "model": "gpt-4o-mini",
101
- "query": "What are the main themes?",
102
- "context": "Your long document here...",
103
- "config": {
104
- "recursive_model": "gpt-4o-mini",
105
- "api_base": "https://api.openai.com/v1",
106
- "api_key": "sk-...",
107
- "max_depth": 5,
108
- "max_iterations": 30,
109
- "temperature": 0.7
110
- }
111
- }
112
- ```
113
-
114
- ### Output Format
115
-
116
- ```json
117
- {
118
- "result": "The main themes are...",
119
- "stats": {
120
- "llm_calls": 3,
121
- "iterations": 2,
122
- "depth": 0
123
- }
124
- }
125
- ```
126
-
127
- ### Example
128
-
129
- ```bash
130
- # Basic usage
131
- echo '{
132
- "model": "gpt-4o-mini",
133
- "query": "Summarize this",
134
- "context": "Long document...",
135
- "config": {
136
- "api_key": "sk-..."
137
- }
138
- }' | ./rlm
139
-
140
- # With environment variable for API key
141
- export OPENAI_API_KEY="sk-..."
142
- echo '{
143
- "model": "gpt-4o-mini",
144
- "query": "What is this about?",
145
- "context": "Document text..."
146
- }' | ./rlm
147
- ```
148
-
149
- ## Configuration Options
150
-
151
- All fields in `config` are optional and have defaults:
152
-
153
- | Field | Type | Default | Description |
154
- |-------|------|---------|-------------|
155
- | `recursive_model` | string | Same as `model` | Cheaper model for recursive calls |
156
- | `api_base` | string | `https://api.openai.com/v1` | API endpoint URL |
157
- | `api_key` | string | From `OPENAI_API_KEY` env | API key for authentication |
158
- | `max_depth` | int | 5 | Maximum recursion depth |
159
- | `max_iterations` | int | 30 | Maximum REPL iterations per call |
160
- | `temperature` | float | 0.7 | LLM temperature (0-2) |
161
- | `timeout` | int | 60 | HTTP timeout in seconds |
162
- | `context_overflow.enabled` | bool | true | Enable context overflow recovery |
163
- | `context_overflow.strategy` | string | `mapreduce` | Reduction strategy: mapreduce, truncate, chunked, tfidf, textrank, refine |
164
- | `context_overflow.max_model_tokens` | int | 0 (auto) | Override detected model token limit |
165
- | `context_overflow.safety_margin` | float | 0.15 | Fraction reserved for prompt overhead |
166
- | `context_overflow.max_reduction_attempts` | int | 3 | Max retry attempts |
167
-
168
- Any other fields in `config` are passed as extra parameters to the LLM API.
169
-
170
- ## JavaScript REPL Environment
171
-
172
- The LLM can write JavaScript code to explore the context. Available globals:
173
-
174
- ### Core Variables
175
- - `context` - The document to analyze (string)
176
- - `query` - The user's question (string)
177
- - `recursive_llm(sub_query, sub_context)` - Recursively process sub-context
178
-
179
- ### String Operations
180
- ```javascript
181
- context.slice(0, 100) // First 100 chars
182
- context.split('\n') // Split by newline
183
- context.length // String length
184
- ```
185
-
186
- ### Regex (Python-style API)
187
- ```javascript
188
- re.findall("ERROR", context) // Find all matches
189
- re.search("ERROR", context) // Find first match
190
- ```
191
-
192
- ### Built-in Functions
193
- ```javascript
194
- len(context) // Length of string/array
195
- print("hello") // Print output
196
- console.log("hello") // Same as print
197
- ```
198
-
199
- ### JSON
200
- ```javascript
201
- json.loads('{"key":"value"}') // Parse JSON
202
- json.dumps({key: "value"}) // Stringify JSON
203
- ```
204
-
205
- ### Array Operations
206
- ```javascript
207
- range(5) // [0, 1, 2, 3, 4]
208
- range(2, 5) // [2, 3, 4]
209
- sorted([3, 1, 2]) // [1, 2, 3]
210
- sum([1, 2, 3]) // 6
211
- min([1, 2, 3]) // 1
212
- max([1, 2, 3]) // 3
213
- enumerate(['a', 'b']) // [[0,'a'], [1,'b']]
214
- zip([1, 2], ['a', 'b']) // [[1,'a'], [2,'b']]
215
- any([false, true]) // true
216
- all([true, true]) // true
217
- ```
218
-
219
- ### Counting & Grouping
220
- ```javascript
221
- Counter("hello") // {h:1, e:1, l:2, o:1}
222
- defaultdict(() => 0) // Dict with default values
223
- ```
224
-
225
- ### Math
226
- ```javascript
227
- Math.floor(3.7) // 3
228
- Math.ceil(3.2) // 4
229
- Math.max(1, 2, 3) // 3
230
- ```
231
-
232
- ### Returning Results
233
- ```javascript
234
- // Option 1: Direct answer (write as text, not code)
235
- FINAL("The answer is 42")
236
-
237
- // Option 2: Return a variable
238
- const answer = "The answer is 42"
239
- FINAL_VAR(answer)
240
- ```
241
-
242
- ## Supported LLM Providers
243
-
244
- Works with any OpenAI-compatible API:
245
-
246
- - **OpenAI**: `model: "gpt-4o"`, `model: "gpt-4o-mini"`
247
- - **Azure OpenAI**: Set custom `api_base`
248
- - **Ollama**: `api_base: "http://localhost:11434/v1"`, `model: "llama3.2"`
249
- - **llama.cpp**: `api_base: "http://localhost:8000/v1"`
250
- - **vLLM**: `api_base: "http://localhost:8000/v1"`
251
- - Any other OpenAI-compatible endpoint
252
-
253
- ## Architecture
254
-
255
- ```
256
- cmd/rlm/main.go # CLI entry point (JSON I/O)
257
- rlm/ # Public package (importable)
258
- ├── doc.go # Package documentation
259
- ├── rlm.go # Core RLM logic
260
- ├── types.go # Config and stats types
261
- ├── structured.go # Structured completion with schema validation
262
- ├── parser.go # FINAL() extraction
263
- ├── prompt.go # System prompt builder
264
- ├── repl.go # JavaScript REPL (goja)
265
- ├── openai.go # OpenAI API client
266
- ├── errors.go # Error types
267
- ├── context_overflow.go # Context overflow detection + 6 reduction strategies
268
- ├── tfidf.go # TF-IDF extractive compression (pure Go)
269
- └── textrank.go # TextRank graph-based ranking with PageRank
270
- ```
271
-
272
- ## Error Handling
273
-
274
- Errors are written to stderr with exit code 1:
275
-
276
- ```bash
277
- # Missing model
278
- echo '{"query":"test"}' | ./rlm
279
- # stderr: Missing model in request payload
280
-
281
- # API error
282
- echo '{
283
- "model": "invalid",
284
- "query": "test",
285
- "context": "test"
286
- }' | ./rlm 2>&1
287
- # stderr: LLM request failed (401): ...
288
- ```
289
-
290
- ## Testing
291
-
292
- ```bash
293
- # Run all tests
294
- go test ./rlm/... -v
295
-
296
- # Run specific test
297
- go test ./rlm -run TestParser -v
298
-
299
- # With coverage
300
- go test ./rlm/... -cover
301
-
302
- # Benchmark
303
- go test ./rlm/... -bench=. -benchmem
304
- ```
305
-
306
- ## Performance
307
-
308
- - **Binary size**: ~15MB (uncompressed), ~5MB (compressed with UPX)
309
- - **Memory**: ~50MB baseline + context size
310
- - **Startup**: <10ms
311
- - **REPL overhead**: ~1-2ms per iteration
312
-
313
- ## Comparison with Python Implementation
314
-
315
- | Feature | Python | Go |
316
- |---------|--------|-----|
317
- | **REPL Language** | Python (RestrictedPython) | JavaScript (goja) |
318
- | **LLM Providers** | 100+ via LiteLLM | OpenAI-compatible only |
319
- | **Async Support** | ✅ Full async/await | ❌ Synchronous only |
320
- | **Distribution** | Requires Python runtime | ✅ Single binary |
321
- | **Startup Time** | ~500ms | ~10ms |
322
- | **Memory Usage** | ~150MB | ~50MB |
323
-
324
- ## Known Limitations
325
-
326
- 1. **JavaScript vs Python**: LLMs are more familiar with Python, may need more iterations
327
- 2. **No async**: Recursive calls are sequential, not parallel
328
- 3. **OpenAI API only**: Doesn't support all LiteLLM providers
329
- 4. **No streaming**: Full response only
330
-
331
- ## Integration with TypeScript
332
-
333
- From Node.js/TypeScript:
334
-
335
- ```typescript
336
- import { spawn } from 'child_process';
337
-
338
- interface RLMRequest {
339
- model: string;
340
- query: string;
341
- context: string;
342
- config?: {
343
- api_key?: string;
344
- max_depth?: number;
345
- max_iterations?: number;
346
- };
347
- }
348
-
349
- interface RLMResponse {
350
- result: string;
351
- stats: {
352
- llm_calls: number;
353
- iterations: number;
354
- depth: number;
355
- };
356
- }
357
-
358
- async function callRLM(request: RLMRequest): Promise<RLMResponse> {
359
- return new Promise((resolve, reject) => {
360
- const proc = spawn('./rlm');
361
- let stdout = '';
362
- let stderr = '';
363
-
364
- proc.stdout.on('data', (data) => { stdout += data; });
365
- proc.stderr.on('data', (data) => { stderr += data; });
366
-
367
- proc.on('close', (code) => {
368
- if (code !== 0) {
369
- reject(new Error(stderr || `Exit code ${code}`));
370
- } else {
371
- resolve(JSON.parse(stdout));
372
- }
373
- });
374
-
375
- proc.stdin.write(JSON.stringify(request));
376
- proc.stdin.end();
377
- });
378
- }
379
-
380
- // Usage
381
- const result = await callRLM({
382
- model: 'gpt-4o-mini',
383
- query: 'What is this about?',
384
- context: longDocument,
385
- config: {
386
- api_key: process.env.OPENAI_API_KEY,
387
- },
388
- });
389
-
390
- console.log(result.result);
391
- console.log(`Stats: ${result.stats.llm_calls} LLM calls`);
392
- ```
393
-
394
- ## Troubleshooting
395
-
396
- ### "Missing model in request payload"
397
- Include the `model` field in your JSON input.
398
-
399
- ### "LLM request failed (401)"
400
- Check your API key is correct and has sufficient credits.
401
-
402
- ### "max iterations exceeded"
403
- Increase `max_iterations` in config, or simplify your query.
404
-
405
- ### "max recursion depth exceeded"
406
- Increase `max_depth` in config.
407
-
408
- ### "Execution error: ReferenceError: xyz is not defined"
409
- Check the JavaScript syntax. Use `console.log()` not `print()`, or ensure `print()` is available.
410
-
411
- ## Contributing
412
-
413
- 1. Write tests for new features
414
- 2. Ensure all tests pass: `go test ./rlm/... -v`
415
- 3. Format code: `go fmt ./...`
416
- 4. Update documentation
417
-
418
- ## License
419
-
420
- MIT License - Same as the original Python implementation
421
-
422
- ## Acknowledgments
423
-
424
- - Based on [Recursive Language Models paper](https://alexzhang13.github.io/blog/2025/rlm/) by Alex Zhang and Omar Khattab (MIT)
425
- - Original Python implementation: https://github.com/alexzhang13/rlm
426
- - JavaScript engine: [goja](https://github.com/dop251/goja)
@@ -1,169 +0,0 @@
1
- #!/bin/bash
2
- # Integration test with real LLM API
3
- # Set OPENAI_API_KEY environment variable before running
4
-
5
- set -e
6
-
7
- echo "🧪 RLM Go Integration Tests"
8
- echo "================================"
9
- echo ""
10
-
11
- # Check if binary exists
12
- if [ ! -f "./rlm" ]; then
13
- echo "❌ Binary not found. Building..."
14
- go build -o rlm ./cmd/rlm
15
- echo "✅ Built binary"
16
- fi
17
-
18
- # Check for API key
19
- if [ -z "$OPENAI_API_KEY" ]; then
20
- echo "❌ OPENAI_API_KEY environment variable not set"
21
- echo ""
22
- echo "Usage:"
23
- echo " export OPENAI_API_KEY='sk-...'"
24
- echo " ./integration_test.sh"
25
- exit 1
26
- fi
27
-
28
- echo "✅ API key found"
29
- echo ""
30
-
31
- # Test 1: Simple query
32
- echo "📝 Test 1: Simple context analysis"
33
- echo "-----------------------------------"
34
- RESULT=$(cat <<EOF | ./rlm
35
- {
36
- "model": "gpt-4o-mini",
37
- "query": "How many times does the word 'test' appear?",
38
- "context": "This is a test. Another test here. Final test.",
39
- "config": {
40
- "api_key": "$OPENAI_API_KEY",
41
- "max_iterations": 10
42
- }
43
- }
44
- EOF
45
- )
46
-
47
- if [ $? -eq 0 ]; then
48
- echo "✅ Test 1 passed"
49
- echo "Result: $(echo $RESULT | jq -r '.result')"
50
- echo "Stats: $(echo $RESULT | jq '.stats')"
51
- else
52
- echo "❌ Test 1 failed"
53
- exit 1
54
- fi
55
- echo ""
56
-
57
- # Test 2: Count/aggregation
58
- echo "📝 Test 2: Counting errors in logs"
59
- echo "-----------------------------------"
60
- LOG_CONTEXT='2024-01-01 INFO: System started
61
- 2024-01-01 ERROR: Connection failed
62
- 2024-01-01 INFO: Retrying
63
- 2024-01-01 ERROR: Timeout
64
- 2024-01-01 ERROR: Failed again
65
- 2024-01-01 INFO: Success'
66
-
67
- RESULT=$(./rlm <<EOF
68
- {
69
- "model": "gpt-4o-mini",
70
- "query": "Count how many ERROR entries are in the logs",
71
- "context": "$LOG_CONTEXT",
72
- "config": {
73
- "api_key": "$OPENAI_API_KEY",
74
- "max_iterations": 10
75
- }
76
- }
77
- EOF
78
- )
79
-
80
- if [ $? -eq 0 ]; then
81
- echo "✅ Test 2 passed"
82
- echo "Result: $(echo $RESULT | jq -r '.result')"
83
- ITERATIONS=$(echo $RESULT | jq '.stats.iterations')
84
- echo "Iterations: $ITERATIONS"
85
- else
86
- echo "❌ Test 2 failed"
87
- exit 1
88
- fi
89
- echo ""
90
-
91
- # Test 3: Long context
92
- echo "📝 Test 3: Long context processing"
93
- echo "-----------------------------------"
94
- LONG_CONTEXT=$(cat <<EOF
95
- Chapter 1: The Beginning
96
-
97
- It was a dark and stormy night. The hero embarked on a journey.
98
- $(for i in {1..100}; do echo "Line $i of the story continues here with more content."; done)
99
-
100
- Chapter 2: The Middle
101
-
102
- The hero faced many challenges.
103
- $(for i in {1..100}; do echo "Line $i describes the adventure."; done)
104
-
105
- Chapter 3: The End
106
-
107
- Finally, the hero succeeded and returned home triumphant.
108
- EOF
109
- )
110
-
111
- RESULT=$(cat <<EOF | ./rlm
112
- {
113
- "model": "gpt-4o-mini",
114
- "query": "How many chapters are in this document?",
115
- "context": "$LONG_CONTEXT",
116
- "config": {
117
- "api_key": "$OPENAI_API_KEY",
118
- "max_iterations": 15
119
- }
120
- }
121
- EOF
122
- )
123
-
124
- if [ $? -eq 0 ]; then
125
- echo "✅ Test 3 passed"
126
- echo "Result: $(echo $RESULT | jq -r '.result')"
127
- LLM_CALLS=$(echo $RESULT | jq '.stats.llm_calls')
128
- echo "LLM calls: $LLM_CALLS"
129
- else
130
- echo "❌ Test 3 failed"
131
- exit 1
132
- fi
133
- echo ""
134
-
135
- # Test 4: Different model configurations
136
- echo "📝 Test 4: Two-model configuration"
137
- echo "-----------------------------------"
138
- RESULT=$(cat <<EOF | ./rlm
139
- {
140
- "model": "gpt-4o",
141
- "query": "What is this text about?",
142
- "context": "Artificial intelligence and machine learning are transforming technology.",
143
- "config": {
144
- "recursive_model": "gpt-4o-mini",
145
- "api_key": "$OPENAI_API_KEY",
146
- "max_iterations": 10,
147
- "temperature": 0.3
148
- }
149
- }
150
- EOF
151
- )
152
-
153
- if [ $? -eq 0 ]; then
154
- echo "✅ Test 4 passed"
155
- echo "Result: $(echo $RESULT | jq -r '.result')"
156
- else
157
- echo "❌ Test 4 failed"
158
- exit 1
159
- fi
160
- echo ""
161
-
162
- echo "================================"
163
- echo "✅ All integration tests passed!"
164
- echo ""
165
- echo "Summary:"
166
- echo " - Simple queries work"
167
- echo " - Counting/aggregation works"
168
- echo " - Long context works"
169
- echo " - Model configuration works"