recursive-llm-ts 4.4.1 → 4.6.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/README.md +375 -12
- package/bin/rlm-go +0 -0
- package/dist/bridge-interface.d.ts +19 -2
- package/dist/cache.d.ts +78 -0
- package/dist/cache.js +246 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.js +162 -0
- package/dist/errors.d.ts +113 -0
- package/dist/errors.js +219 -0
- package/dist/events.d.ts +126 -0
- package/dist/events.js +77 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +38 -1
- package/dist/retry.d.ts +56 -0
- package/dist/retry.js +185 -0
- package/dist/rlm.d.ts +391 -13
- package/dist/rlm.js +815 -182
- package/dist/streaming.d.ts +96 -0
- package/dist/streaming.js +210 -0
- package/go/README.md +9 -1
- package/go/rlm/context_overflow.go +566 -0
- package/go/rlm/context_overflow_test.go +783 -0
- package/go/rlm/errors.go +161 -1
- package/go/rlm/rlm.go +10 -0
- package/go/rlm/structured.go +53 -0
- package/go/rlm/textrank.go +273 -0
- package/go/rlm/textrank_test.go +335 -0
- package/go/rlm/tfidf.go +225 -0
- package/go/rlm/tfidf_test.go +272 -0
- package/go/rlm/types.go +25 -2
- package/package.json +16 -4
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming support for recursive-llm-ts.
|
|
3
|
+
*
|
|
4
|
+
* Provides `AsyncIterable`-based streaming for both text completions
|
|
5
|
+
* and structured output, with AbortController support.
|
|
6
|
+
*/
|
|
7
|
+
export type StreamChunkType = 'text' | 'partial_object' | 'usage' | 'error' | 'done';
|
|
8
|
+
export interface StreamChunkBase {
|
|
9
|
+
type: StreamChunkType;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
}
|
|
12
|
+
export interface TextStreamChunk extends StreamChunkBase {
|
|
13
|
+
type: 'text';
|
|
14
|
+
text: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PartialObjectStreamChunk<T = unknown> extends StreamChunkBase {
|
|
17
|
+
type: 'partial_object';
|
|
18
|
+
object: Partial<T>;
|
|
19
|
+
/** JSON path of the field being populated */
|
|
20
|
+
path?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface UsageStreamChunk extends StreamChunkBase {
|
|
23
|
+
type: 'usage';
|
|
24
|
+
usage: {
|
|
25
|
+
promptTokens: number;
|
|
26
|
+
completionTokens: number;
|
|
27
|
+
totalTokens: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface ErrorStreamChunk extends StreamChunkBase {
|
|
31
|
+
type: 'error';
|
|
32
|
+
error: Error;
|
|
33
|
+
}
|
|
34
|
+
export interface DoneStreamChunk extends StreamChunkBase {
|
|
35
|
+
type: 'done';
|
|
36
|
+
stats: {
|
|
37
|
+
llm_calls: number;
|
|
38
|
+
iterations: number;
|
|
39
|
+
depth: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export type StreamChunk<T = unknown> = TextStreamChunk | PartialObjectStreamChunk<T> | UsageStreamChunk | ErrorStreamChunk | DoneStreamChunk;
|
|
43
|
+
export interface StreamOptions {
|
|
44
|
+
/** AbortController signal to cancel the stream */
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
/** Called on each chunk (alternative to async iteration) */
|
|
47
|
+
onChunk?: (chunk: StreamChunk) => void;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* An async iterable stream of completion chunks.
|
|
51
|
+
*
|
|
52
|
+
* Can be consumed with `for await...of` or by attaching an `onChunk` callback.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const stream = rlm.streamCompletion(query, context);
|
|
57
|
+
* let fullText = '';
|
|
58
|
+
* for await (const chunk of stream) {
|
|
59
|
+
* if (chunk.type === 'text') fullText += chunk.text;
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare class RLMStream<T = unknown> implements AsyncIterable<StreamChunk<T>> {
|
|
64
|
+
private chunks;
|
|
65
|
+
private resolvers;
|
|
66
|
+
private done;
|
|
67
|
+
private error;
|
|
68
|
+
private signal?;
|
|
69
|
+
private abortHandler?;
|
|
70
|
+
constructor(signal?: AbortSignal);
|
|
71
|
+
/** Push a chunk into the stream (called by the producer) */
|
|
72
|
+
push(chunk: StreamChunk<T>): void;
|
|
73
|
+
/** Signal an error on the stream */
|
|
74
|
+
pushError(err: Error): void;
|
|
75
|
+
/** Mark the stream as complete */
|
|
76
|
+
complete(stats: {
|
|
77
|
+
llm_calls: number;
|
|
78
|
+
iterations: number;
|
|
79
|
+
depth: number;
|
|
80
|
+
}): void;
|
|
81
|
+
private cleanup;
|
|
82
|
+
/** Collect all text chunks into a single string */
|
|
83
|
+
toText(): Promise<string>;
|
|
84
|
+
/** Collect the final structured object (for structured streaming) */
|
|
85
|
+
toObject(): Promise<T | undefined>;
|
|
86
|
+
[Symbol.asyncIterator](): AsyncIterator<StreamChunk<T>>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Creates a simulated stream from a non-streaming completion result.
|
|
90
|
+
* Useful as a compatibility bridge until full streaming is implemented in the Go binary.
|
|
91
|
+
*/
|
|
92
|
+
export declare function createSimulatedStream(text: string, stats: {
|
|
93
|
+
llm_calls: number;
|
|
94
|
+
iterations: number;
|
|
95
|
+
depth: number;
|
|
96
|
+
}, signal?: AbortSignal, chunkSize?: number): RLMStream;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Streaming support for recursive-llm-ts.
|
|
4
|
+
*
|
|
5
|
+
* Provides `AsyncIterable`-based streaming for both text completions
|
|
6
|
+
* and structured output, with AbortController support.
|
|
7
|
+
*/
|
|
8
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
9
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
10
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
11
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
12
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
13
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
14
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
18
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
19
|
+
var m = o[Symbol.asyncIterator], i;
|
|
20
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
21
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
22
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.RLMStream = void 0;
|
|
26
|
+
exports.createSimulatedStream = createSimulatedStream;
|
|
27
|
+
const errors_1 = require("./errors");
|
|
28
|
+
// ─── RLM Stream ──────────────────────────────────────────────────────────────
|
|
29
|
+
/**
|
|
30
|
+
* An async iterable stream of completion chunks.
|
|
31
|
+
*
|
|
32
|
+
* Can be consumed with `for await...of` or by attaching an `onChunk` callback.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const stream = rlm.streamCompletion(query, context);
|
|
37
|
+
* let fullText = '';
|
|
38
|
+
* for await (const chunk of stream) {
|
|
39
|
+
* if (chunk.type === 'text') fullText += chunk.text;
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
class RLMStream {
|
|
44
|
+
constructor(signal) {
|
|
45
|
+
this.chunks = [];
|
|
46
|
+
this.resolvers = [];
|
|
47
|
+
this.done = false;
|
|
48
|
+
this.error = null;
|
|
49
|
+
this.signal = signal;
|
|
50
|
+
if (signal) {
|
|
51
|
+
this.abortHandler = () => {
|
|
52
|
+
this.pushError(new errors_1.RLMAbortError());
|
|
53
|
+
};
|
|
54
|
+
signal.addEventListener('abort', this.abortHandler, { once: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Push a chunk into the stream (called by the producer) */
|
|
58
|
+
push(chunk) {
|
|
59
|
+
if (this.done)
|
|
60
|
+
return;
|
|
61
|
+
if (chunk.type === 'done') {
|
|
62
|
+
this.done = true;
|
|
63
|
+
}
|
|
64
|
+
if (this.resolvers.length > 0) {
|
|
65
|
+
const resolve = this.resolvers.shift();
|
|
66
|
+
resolve({ value: chunk, done: false });
|
|
67
|
+
if (chunk.type === 'done') {
|
|
68
|
+
// Resolve any remaining waiters
|
|
69
|
+
for (const r of this.resolvers) {
|
|
70
|
+
r({ value: undefined, done: true });
|
|
71
|
+
}
|
|
72
|
+
this.resolvers = [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
this.chunks.push(chunk);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Signal an error on the stream */
|
|
80
|
+
pushError(err) {
|
|
81
|
+
if (this.done)
|
|
82
|
+
return;
|
|
83
|
+
this.error = err;
|
|
84
|
+
this.done = true;
|
|
85
|
+
// Reject any waiting resolvers
|
|
86
|
+
for (const resolve of this.resolvers) {
|
|
87
|
+
resolve({ value: { type: 'error', error: err, timestamp: Date.now() }, done: false });
|
|
88
|
+
}
|
|
89
|
+
this.resolvers = [];
|
|
90
|
+
this.cleanup();
|
|
91
|
+
}
|
|
92
|
+
/** Mark the stream as complete */
|
|
93
|
+
complete(stats) {
|
|
94
|
+
this.push({ type: 'done', stats, timestamp: Date.now() });
|
|
95
|
+
this.cleanup();
|
|
96
|
+
}
|
|
97
|
+
cleanup() {
|
|
98
|
+
if (this.signal && this.abortHandler) {
|
|
99
|
+
this.signal.removeEventListener('abort', this.abortHandler);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Collect all text chunks into a single string */
|
|
103
|
+
toText() {
|
|
104
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
105
|
+
var _a, e_1, _b, _c;
|
|
106
|
+
let text = '';
|
|
107
|
+
try {
|
|
108
|
+
for (var _d = true, _e = __asyncValues(this), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
109
|
+
_c = _f.value;
|
|
110
|
+
_d = false;
|
|
111
|
+
const chunk = _c;
|
|
112
|
+
if (chunk.type === 'text') {
|
|
113
|
+
text += chunk.text;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
118
|
+
finally {
|
|
119
|
+
try {
|
|
120
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
121
|
+
}
|
|
122
|
+
finally { if (e_1) throw e_1.error; }
|
|
123
|
+
}
|
|
124
|
+
return text;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** Collect the final structured object (for structured streaming) */
|
|
128
|
+
toObject() {
|
|
129
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
130
|
+
var _a, e_2, _b, _c;
|
|
131
|
+
let latest;
|
|
132
|
+
try {
|
|
133
|
+
for (var _d = true, _e = __asyncValues(this), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
134
|
+
_c = _f.value;
|
|
135
|
+
_d = false;
|
|
136
|
+
const chunk = _c;
|
|
137
|
+
if (chunk.type === 'partial_object') {
|
|
138
|
+
latest = chunk.object;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
143
|
+
finally {
|
|
144
|
+
try {
|
|
145
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
146
|
+
}
|
|
147
|
+
finally { if (e_2) throw e_2.error; }
|
|
148
|
+
}
|
|
149
|
+
return latest;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
[Symbol.asyncIterator]() {
|
|
153
|
+
return {
|
|
154
|
+
next: () => {
|
|
155
|
+
var _a;
|
|
156
|
+
// Check for abort
|
|
157
|
+
if (((_a = this.signal) === null || _a === void 0 ? void 0 : _a.aborted) && !this.error) {
|
|
158
|
+
this.pushError(new errors_1.RLMAbortError());
|
|
159
|
+
}
|
|
160
|
+
// Return buffered chunks first
|
|
161
|
+
if (this.chunks.length > 0) {
|
|
162
|
+
const chunk = this.chunks.shift();
|
|
163
|
+
if (chunk.type === 'done' || chunk.type === 'error') {
|
|
164
|
+
return Promise.resolve({ value: chunk, done: false }).then(r => {
|
|
165
|
+
return { value: undefined, done: true };
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return Promise.resolve({ value: chunk, done: false });
|
|
169
|
+
}
|
|
170
|
+
// Stream is done
|
|
171
|
+
if (this.done) {
|
|
172
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
173
|
+
}
|
|
174
|
+
// Wait for next chunk
|
|
175
|
+
return new Promise((resolve) => {
|
|
176
|
+
this.resolvers.push(resolve);
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.RLMStream = RLMStream;
|
|
183
|
+
// ─── Stream Builder (for simulating streaming from non-streaming sources) ────
|
|
184
|
+
/**
|
|
185
|
+
* Creates a simulated stream from a non-streaming completion result.
|
|
186
|
+
* Useful as a compatibility bridge until full streaming is implemented in the Go binary.
|
|
187
|
+
*/
|
|
188
|
+
function createSimulatedStream(text, stats, signal, chunkSize = 20) {
|
|
189
|
+
const stream = new RLMStream(signal);
|
|
190
|
+
// Simulate streaming by splitting text into chunks
|
|
191
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
192
|
+
try {
|
|
193
|
+
for (let i = 0; i < text.length; i += chunkSize) {
|
|
194
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
195
|
+
stream.pushError(new errors_1.RLMAbortError());
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const chunk = text.slice(i, i + chunkSize);
|
|
199
|
+
stream.push({ type: 'text', text: chunk, timestamp: Date.now() });
|
|
200
|
+
// Small yield to allow consumer to process
|
|
201
|
+
yield new Promise(resolve => setImmediate(resolve));
|
|
202
|
+
}
|
|
203
|
+
stream.complete(stats);
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
stream.pushError(err instanceof Error ? err : new Error(String(err)));
|
|
207
|
+
}
|
|
208
|
+
}))();
|
|
209
|
+
return stream;
|
|
210
|
+
}
|
package/go/README.md
CHANGED
|
@@ -159,6 +159,11 @@ All fields in `config` are optional and have defaults:
|
|
|
159
159
|
| `max_iterations` | int | 30 | Maximum REPL iterations per call |
|
|
160
160
|
| `temperature` | float | 0.7 | LLM temperature (0-2) |
|
|
161
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 |
|
|
162
167
|
|
|
163
168
|
Any other fields in `config` are passed as extra parameters to the LLM API.
|
|
164
169
|
|
|
@@ -258,7 +263,10 @@ rlm/ # Public package (importable)
|
|
|
258
263
|
├── prompt.go # System prompt builder
|
|
259
264
|
├── repl.go # JavaScript REPL (goja)
|
|
260
265
|
├── openai.go # OpenAI API client
|
|
261
|
-
|
|
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
|
|
262
270
|
```
|
|
263
271
|
|
|
264
272
|
## Error Handling
|