recursive-llm-ts 2.0.11 → 3.0.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.
- package/README.md +60 -43
- package/bin/rlm-go +0 -0
- package/dist/bridge-factory.d.ts +1 -1
- package/dist/bridge-factory.js +44 -14
- package/dist/bridge-interface.d.ts +1 -0
- package/dist/bunpy-bridge.d.ts +3 -4
- package/dist/bunpy-bridge.js +11 -143
- package/dist/go-bridge.d.ts +5 -0
- package/dist/go-bridge.js +136 -0
- package/dist/rlm-bridge.js +28 -5
- package/go/README.md +347 -0
- package/go/cmd/rlm/main.go +63 -0
- package/go/go.mod +12 -0
- package/go/go.sum +57 -0
- package/go/integration_test.sh +169 -0
- package/go/internal/rlm/benchmark_test.go +168 -0
- package/go/internal/rlm/errors.go +83 -0
- package/go/internal/rlm/openai.go +128 -0
- package/go/internal/rlm/parser.go +53 -0
- package/go/internal/rlm/parser_test.go +202 -0
- package/go/internal/rlm/prompt.go +68 -0
- package/go/internal/rlm/repl.go +260 -0
- package/go/internal/rlm/repl_test.go +291 -0
- package/go/internal/rlm/rlm.go +142 -0
- package/go/internal/rlm/types.go +108 -0
- package/go/test_mock.sh +90 -0
- package/go/test_rlm.sh +41 -0
- package/go/test_simple.sh +78 -0
- package/package.json +6 -9
- package/scripts/build-go-binary.js +41 -0
- package/recursive-llm/pyproject.toml +0 -70
- package/recursive-llm/src/rlm/__init__.py +0 -14
- package/recursive-llm/src/rlm/core.py +0 -322
- package/recursive-llm/src/rlm/parser.py +0 -93
- package/recursive-llm/src/rlm/prompts.py +0 -50
- package/recursive-llm/src/rlm/repl.py +0 -235
- package/recursive-llm/src/rlm/types.py +0 -37
- package/scripts/install-python-deps.js +0 -72
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"""System prompt templates for RLM."""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def build_system_prompt(context_size: int, depth: int = 0) -> str:
|
|
5
|
-
"""
|
|
6
|
-
Build system prompt for RLM.
|
|
7
|
-
|
|
8
|
-
Args:
|
|
9
|
-
context_size: Size of context in characters
|
|
10
|
-
depth: Current recursion depth
|
|
11
|
-
|
|
12
|
-
Returns:
|
|
13
|
-
System prompt string
|
|
14
|
-
"""
|
|
15
|
-
# Minimal prompt (paper-style)
|
|
16
|
-
prompt = f"""You are a Recursive Language Model. You interact with context through a Python REPL environment.
|
|
17
|
-
|
|
18
|
-
The context is stored in variable `context` (not in this prompt). Size: {context_size:,} characters.
|
|
19
|
-
|
|
20
|
-
Available in environment:
|
|
21
|
-
- context: str (the document to analyze)
|
|
22
|
-
- query: str (the question: "{"{"}query{"}"}")
|
|
23
|
-
- recursive_llm(sub_query, sub_context) -> str (recursively process sub-context)
|
|
24
|
-
- re: already imported regex module (use re.findall, re.search, etc.)
|
|
25
|
-
|
|
26
|
-
Write Python code to answer the query. The last expression or print() output will be shown to you.
|
|
27
|
-
|
|
28
|
-
Examples:
|
|
29
|
-
- print(context[:100]) # See first 100 chars
|
|
30
|
-
- errors = re.findall(r'ERROR', context) # Find all ERROR
|
|
31
|
-
- count = len(errors); print(count) # Count and show
|
|
32
|
-
|
|
33
|
-
When you have the answer, use FINAL("answer") - this is NOT a function, just write it as text.
|
|
34
|
-
|
|
35
|
-
Depth: {depth}"""
|
|
36
|
-
|
|
37
|
-
return prompt
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def build_user_prompt(query: str) -> str:
|
|
41
|
-
"""
|
|
42
|
-
Build user prompt.
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
query: User's question
|
|
46
|
-
|
|
47
|
-
Returns:
|
|
48
|
-
User prompt string
|
|
49
|
-
"""
|
|
50
|
-
return query
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
"""Safe REPL executor using RestrictedPython."""
|
|
2
|
-
|
|
3
|
-
import io
|
|
4
|
-
import sys
|
|
5
|
-
from typing import Dict, Any, Optional
|
|
6
|
-
from RestrictedPython import compile_restricted_exec, safe_globals, limited_builtins, utility_builtins
|
|
7
|
-
from RestrictedPython.Guards import guarded_iter_unpack_sequence, safer_getattr
|
|
8
|
-
from RestrictedPython.PrintCollector import PrintCollector
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class REPLError(Exception):
|
|
12
|
-
"""Error during REPL execution."""
|
|
13
|
-
pass
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class REPLExecutor:
|
|
17
|
-
"""Safe Python code executor."""
|
|
18
|
-
|
|
19
|
-
def __init__(self, timeout: int = 5, max_output_chars: int = 2000):
|
|
20
|
-
"""
|
|
21
|
-
Initialize REPL executor.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
timeout: Execution timeout in seconds (not currently enforced)
|
|
25
|
-
max_output_chars: Maximum characters to return (truncate if longer)
|
|
26
|
-
"""
|
|
27
|
-
self.timeout = timeout
|
|
28
|
-
self.max_output_chars = max_output_chars
|
|
29
|
-
|
|
30
|
-
def execute(self, code: str, env: Dict[str, Any]) -> str:
|
|
31
|
-
"""
|
|
32
|
-
Execute Python code in restricted environment.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
code: Python code to execute
|
|
36
|
-
env: Environment with context, query, recursive_llm, etc.
|
|
37
|
-
|
|
38
|
-
Returns:
|
|
39
|
-
String result of execution (stdout or last expression)
|
|
40
|
-
|
|
41
|
-
Raises:
|
|
42
|
-
REPLError: If code execution fails
|
|
43
|
-
"""
|
|
44
|
-
# Filter out code blocks if present (LLM might wrap code)
|
|
45
|
-
code = self._extract_code(code)
|
|
46
|
-
|
|
47
|
-
if not code.strip():
|
|
48
|
-
return "No code to execute"
|
|
49
|
-
|
|
50
|
-
# Build restricted globals
|
|
51
|
-
restricted_globals = self._build_globals(env)
|
|
52
|
-
|
|
53
|
-
# Capture stdout
|
|
54
|
-
old_stdout = sys.stdout
|
|
55
|
-
sys.stdout = captured_output = io.StringIO()
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
# Compile with RestrictedPython
|
|
59
|
-
byte_code = compile_restricted_exec(code)
|
|
60
|
-
|
|
61
|
-
if byte_code.errors:
|
|
62
|
-
raise REPLError(f"Compilation error: {', '.join(byte_code.errors)}")
|
|
63
|
-
|
|
64
|
-
# Execute
|
|
65
|
-
exec(byte_code.code, restricted_globals, env)
|
|
66
|
-
|
|
67
|
-
# Get output from stdout
|
|
68
|
-
output = captured_output.getvalue()
|
|
69
|
-
|
|
70
|
-
# Get output from PrintCollector if available
|
|
71
|
-
if '_print' in env and hasattr(env['_print'], '__call__'):
|
|
72
|
-
# PrintCollector stores prints in its txt attribute
|
|
73
|
-
print_collector = env['_print']
|
|
74
|
-
if hasattr(print_collector, 'txt'):
|
|
75
|
-
output += ''.join(print_collector.txt)
|
|
76
|
-
|
|
77
|
-
# Check if last line was an expression (try to get its value)
|
|
78
|
-
# This handles cases like: error_count (should return its value)
|
|
79
|
-
lines = code.strip().split('\n')
|
|
80
|
-
if lines:
|
|
81
|
-
last_line = lines[-1].strip()
|
|
82
|
-
# If last line is a simple expression (no assignment, no keyword)
|
|
83
|
-
if last_line and not any(kw in last_line for kw in ['=', 'import', 'def', 'class', 'if', 'for', 'while', 'with']):
|
|
84
|
-
try:
|
|
85
|
-
# Try to evaluate the last line as expression
|
|
86
|
-
result = eval(last_line, restricted_globals, env)
|
|
87
|
-
if result is not None:
|
|
88
|
-
output += str(result) + '\n'
|
|
89
|
-
except:
|
|
90
|
-
pass # Not an expression, ignore
|
|
91
|
-
|
|
92
|
-
if not output:
|
|
93
|
-
return "Code executed successfully (no output)"
|
|
94
|
-
|
|
95
|
-
# Truncate output if too long (as per paper: "truncated version of output")
|
|
96
|
-
if len(output) > self.max_output_chars:
|
|
97
|
-
truncated = output[:self.max_output_chars]
|
|
98
|
-
return f"{truncated}\n\n[Output truncated: {len(output)} chars total, showing first {self.max_output_chars}]"
|
|
99
|
-
|
|
100
|
-
return output.strip()
|
|
101
|
-
|
|
102
|
-
except Exception as e:
|
|
103
|
-
raise REPLError(f"Execution error: {str(e)}")
|
|
104
|
-
|
|
105
|
-
finally:
|
|
106
|
-
sys.stdout = old_stdout
|
|
107
|
-
|
|
108
|
-
def _extract_code(self, text: str) -> str:
|
|
109
|
-
"""
|
|
110
|
-
Extract code from markdown code blocks if present.
|
|
111
|
-
|
|
112
|
-
Args:
|
|
113
|
-
text: Raw text that might contain code
|
|
114
|
-
|
|
115
|
-
Returns:
|
|
116
|
-
Extracted code
|
|
117
|
-
"""
|
|
118
|
-
# Check for markdown code blocks
|
|
119
|
-
if '```python' in text:
|
|
120
|
-
start = text.find('```python') + len('```python')
|
|
121
|
-
end = text.find('```', start)
|
|
122
|
-
if end != -1:
|
|
123
|
-
return text[start:end].strip()
|
|
124
|
-
|
|
125
|
-
if '```' in text:
|
|
126
|
-
start = text.find('```') + 3
|
|
127
|
-
end = text.find('```', start)
|
|
128
|
-
if end != -1:
|
|
129
|
-
return text[start:end].strip()
|
|
130
|
-
|
|
131
|
-
return text
|
|
132
|
-
|
|
133
|
-
def _build_globals(self, env: Dict[str, Any]) -> Dict[str, Any]:
|
|
134
|
-
"""
|
|
135
|
-
Build restricted globals for safe execution.
|
|
136
|
-
|
|
137
|
-
Args:
|
|
138
|
-
env: User environment
|
|
139
|
-
|
|
140
|
-
Returns:
|
|
141
|
-
Safe globals dict
|
|
142
|
-
"""
|
|
143
|
-
restricted_globals = safe_globals.copy()
|
|
144
|
-
restricted_globals.update(limited_builtins)
|
|
145
|
-
restricted_globals.update(utility_builtins)
|
|
146
|
-
|
|
147
|
-
# Add guards
|
|
148
|
-
restricted_globals['_iter_unpack_sequence_'] = guarded_iter_unpack_sequence
|
|
149
|
-
restricted_globals['_getattr_'] = safer_getattr
|
|
150
|
-
restricted_globals['_getitem_'] = lambda obj, index: obj[index]
|
|
151
|
-
restricted_globals['_getiter_'] = iter
|
|
152
|
-
restricted_globals['_print_'] = PrintCollector
|
|
153
|
-
|
|
154
|
-
# Add additional safe builtins
|
|
155
|
-
restricted_globals.update({
|
|
156
|
-
# Types
|
|
157
|
-
'len': len,
|
|
158
|
-
'str': str,
|
|
159
|
-
'int': int,
|
|
160
|
-
'float': float,
|
|
161
|
-
'bool': bool,
|
|
162
|
-
'list': list,
|
|
163
|
-
'dict': dict,
|
|
164
|
-
'tuple': tuple,
|
|
165
|
-
'set': set,
|
|
166
|
-
'frozenset': frozenset,
|
|
167
|
-
'bytes': bytes,
|
|
168
|
-
'bytearray': bytearray,
|
|
169
|
-
|
|
170
|
-
# Iteration
|
|
171
|
-
'range': range,
|
|
172
|
-
'enumerate': enumerate,
|
|
173
|
-
'zip': zip,
|
|
174
|
-
'map': map,
|
|
175
|
-
'filter': filter,
|
|
176
|
-
'reversed': reversed,
|
|
177
|
-
'iter': iter,
|
|
178
|
-
'next': next,
|
|
179
|
-
|
|
180
|
-
# Aggregation
|
|
181
|
-
'sorted': sorted,
|
|
182
|
-
'sum': sum,
|
|
183
|
-
'min': min,
|
|
184
|
-
'max': max,
|
|
185
|
-
'any': any,
|
|
186
|
-
'all': all,
|
|
187
|
-
|
|
188
|
-
# Math
|
|
189
|
-
'abs': abs,
|
|
190
|
-
'round': round,
|
|
191
|
-
'pow': pow,
|
|
192
|
-
'divmod': divmod,
|
|
193
|
-
|
|
194
|
-
# String/repr
|
|
195
|
-
'chr': chr,
|
|
196
|
-
'ord': ord,
|
|
197
|
-
'hex': hex,
|
|
198
|
-
'oct': oct,
|
|
199
|
-
'bin': bin,
|
|
200
|
-
'repr': repr,
|
|
201
|
-
'ascii': ascii,
|
|
202
|
-
'format': format,
|
|
203
|
-
|
|
204
|
-
# Type checking
|
|
205
|
-
'isinstance': isinstance,
|
|
206
|
-
'issubclass': issubclass,
|
|
207
|
-
'callable': callable,
|
|
208
|
-
'type': type,
|
|
209
|
-
'hasattr': hasattr,
|
|
210
|
-
|
|
211
|
-
# Constants
|
|
212
|
-
'True': True,
|
|
213
|
-
'False': False,
|
|
214
|
-
'None': None,
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
# Add safe standard library modules
|
|
218
|
-
# These are read-only and don't allow file/network access
|
|
219
|
-
import re
|
|
220
|
-
import json
|
|
221
|
-
import math
|
|
222
|
-
from datetime import datetime, timedelta
|
|
223
|
-
from collections import Counter, defaultdict
|
|
224
|
-
|
|
225
|
-
restricted_globals.update({
|
|
226
|
-
're': re, # Regex (read-only)
|
|
227
|
-
'json': json, # JSON parsing (read-only)
|
|
228
|
-
'math': math, # Math functions
|
|
229
|
-
'datetime': datetime, # Date parsing
|
|
230
|
-
'timedelta': timedelta, # Time deltas
|
|
231
|
-
'Counter': Counter, # Counting helper
|
|
232
|
-
'defaultdict': defaultdict, # Dict with defaults
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
return restricted_globals
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
"""Type definitions for RLM."""
|
|
2
|
-
|
|
3
|
-
from typing import TypedDict, Optional, Any, Callable, Awaitable
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Message(TypedDict):
|
|
7
|
-
"""LLM message format."""
|
|
8
|
-
role: str
|
|
9
|
-
content: str
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class RLMConfig(TypedDict, total=False):
|
|
13
|
-
"""Configuration for RLM instance."""
|
|
14
|
-
model: str
|
|
15
|
-
recursive_model: Optional[str]
|
|
16
|
-
api_base: Optional[str]
|
|
17
|
-
api_key: Optional[str]
|
|
18
|
-
max_depth: int
|
|
19
|
-
max_iterations: int
|
|
20
|
-
temperature: float
|
|
21
|
-
timeout: int
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class REPLEnvironment(TypedDict, total=False):
|
|
25
|
-
"""REPL execution environment."""
|
|
26
|
-
context: str
|
|
27
|
-
query: str
|
|
28
|
-
recursive_llm: Callable[[str, str], Awaitable[str]]
|
|
29
|
-
re: Any # re module
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class CompletionResult(TypedDict):
|
|
33
|
-
"""Result from RLM completion."""
|
|
34
|
-
answer: str
|
|
35
|
-
iterations: int
|
|
36
|
-
depth: int
|
|
37
|
-
llm_calls: int
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const { execSync, execFileSync } = require('child_process');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
|
|
6
|
-
const pythonPackagePath = path.join(__dirname, '..', 'recursive-llm');
|
|
7
|
-
const pyprojectPath = path.join(pythonPackagePath, 'pyproject.toml');
|
|
8
|
-
|
|
9
|
-
// Check if pyproject.toml exists
|
|
10
|
-
if (!fs.existsSync(pyprojectPath)) {
|
|
11
|
-
console.warn('[recursive-llm-ts] Warning: pyproject.toml not found at', pyprojectPath);
|
|
12
|
-
console.warn('[recursive-llm-ts] Python dependencies will need to be installed manually');
|
|
13
|
-
process.exit(0); // Don't fail the install
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
console.log('[recursive-llm-ts] Installing Python dependencies...');
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const pythonCandidates = [];
|
|
20
|
-
if (process.env.PYTHON) {
|
|
21
|
-
pythonCandidates.push({ command: process.env.PYTHON, args: [] });
|
|
22
|
-
}
|
|
23
|
-
if (process.platform === 'win32') {
|
|
24
|
-
pythonCandidates.push({ command: 'py', args: ['-3'] });
|
|
25
|
-
pythonCandidates.push({ command: 'python', args: [] });
|
|
26
|
-
pythonCandidates.push({ command: 'python3', args: [] });
|
|
27
|
-
} else {
|
|
28
|
-
pythonCandidates.push({ command: 'python3', args: [] });
|
|
29
|
-
pythonCandidates.push({ command: 'python', args: [] });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
let pythonCmd = null;
|
|
33
|
-
for (const candidate of pythonCandidates) {
|
|
34
|
-
try {
|
|
35
|
-
execFileSync(candidate.command, [...candidate.args, '--version'], { stdio: 'pipe' });
|
|
36
|
-
pythonCmd = candidate;
|
|
37
|
-
break;
|
|
38
|
-
} catch {
|
|
39
|
-
// Try next candidate
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (pythonCmd) {
|
|
44
|
-
execFileSync(
|
|
45
|
-
pythonCmd.command,
|
|
46
|
-
[...pythonCmd.args, '-m', 'pip', 'install', '-e', pythonPackagePath],
|
|
47
|
-
{ stdio: 'inherit', cwd: pythonPackagePath }
|
|
48
|
-
);
|
|
49
|
-
} else {
|
|
50
|
-
// Fall back to pip/pip3 if a Python executable wasn't found
|
|
51
|
-
try {
|
|
52
|
-
execSync('pip --version', { stdio: 'pipe' });
|
|
53
|
-
} catch {
|
|
54
|
-
execSync('pip3 --version', { stdio: 'pipe' });
|
|
55
|
-
}
|
|
56
|
-
const pipCommand = process.platform === 'win32'
|
|
57
|
-
? `pip install -e "${pythonPackagePath}"`
|
|
58
|
-
: `pip install -e "${pythonPackagePath}" || pip3 install -e "${pythonPackagePath}"`;
|
|
59
|
-
execSync(pipCommand, {
|
|
60
|
-
stdio: 'inherit',
|
|
61
|
-
cwd: pythonPackagePath
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
console.log('[recursive-llm-ts] ✓ Python dependencies installed successfully');
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.warn('[recursive-llm-ts] Warning: Failed to auto-install Python dependencies');
|
|
67
|
-
console.warn('[recursive-llm-ts] This is not critical - you can install them manually:');
|
|
68
|
-
console.warn(`[recursive-llm-ts] cd node_modules/recursive-llm-ts/recursive-llm && python -m pip install -e .`);
|
|
69
|
-
console.warn('[recursive-llm-ts] Or ensure Python 3.9+ and pip are in your PATH');
|
|
70
|
-
// Don't fail the npm install - exit with 0
|
|
71
|
-
process.exit(0);
|
|
72
|
-
}
|