@llumiverse/core 1.3.0 → 1.4.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/lib/CompletionStream.d.ts +48 -0
- package/lib/CompletionStream.d.ts.map +1 -0
- package/lib/{esm/CompletionStream.js → CompletionStream.js} +142 -67
- package/lib/CompletionStream.js.map +1 -0
- package/lib/{types/Driver.d.ts → Driver.d.ts} +38 -5
- package/lib/Driver.d.ts.map +1 -0
- package/lib/{esm/Driver.js → Driver.js} +151 -106
- package/lib/Driver.js.map +1 -0
- package/lib/{types/async.d.ts → async.d.ts} +3 -3
- package/lib/async.d.ts.map +1 -0
- package/lib/{esm/async.js → async.js} +4 -4
- package/lib/async.js.map +1 -0
- package/lib/{types/conversation-utils.d.ts → conversation-utils.d.ts} +9 -0
- package/lib/conversation-utils.d.ts.map +1 -0
- package/lib/{esm/conversation-utils.js → conversation-utils.js} +78 -15
- package/lib/conversation-utils.js.map +1 -0
- package/lib/embeddings.d.ts +58 -0
- package/lib/embeddings.d.ts.map +1 -0
- package/lib/embeddings.js +131 -0
- package/lib/embeddings.js.map +1 -0
- package/lib/formatters/commons.d.ts +3 -0
- package/lib/formatters/commons.d.ts.map +1 -0
- package/lib/formatters/commons.js +4 -0
- package/lib/formatters/commons.js.map +1 -0
- package/lib/{types/formatters → formatters}/generic.d.ts +2 -2
- package/lib/formatters/generic.d.ts.map +1 -0
- package/lib/{esm/formatters → formatters}/generic.js +8 -8
- package/lib/formatters/generic.js.map +1 -0
- package/lib/formatters/index.d.ts +4 -0
- package/lib/formatters/index.d.ts.map +1 -0
- package/lib/formatters/index.js +4 -0
- package/lib/formatters/index.js.map +1 -0
- package/lib/{types/formatters → formatters}/nova.d.ts +4 -4
- package/lib/formatters/nova.d.ts.map +1 -0
- package/lib/{esm/formatters → formatters}/nova.js +19 -17
- package/lib/formatters/nova.js.map +1 -0
- package/lib/http-agent.d.ts +53 -0
- package/lib/http-agent.d.ts.map +1 -0
- package/lib/http-agent.js +141 -0
- package/lib/http-agent.js.map +1 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +7 -0
- package/lib/index.js.map +1 -0
- package/lib/{types/json.d.ts → json.d.ts} +1 -1
- package/lib/json.d.ts.map +1 -0
- package/lib/{esm/json.js → json.js} +4 -3
- package/lib/json.js.map +1 -0
- package/lib/logger.d.ts +3 -0
- package/lib/logger.d.ts.map +1 -0
- package/lib/logger.js +42 -0
- package/lib/logger.js.map +1 -0
- package/lib/resolver.d.ts +2 -0
- package/lib/resolver.d.ts.map +1 -0
- package/lib/{esm/resolver.js → resolver.js} +7 -4
- package/lib/resolver.js.map +1 -0
- package/lib/stream.d.ts.map +1 -0
- package/lib/stream.js.map +1 -0
- package/lib/{types/validation.d.ts → validation.d.ts} +1 -1
- package/lib/validation.d.ts.map +1 -0
- package/lib/validation.js +100 -0
- package/lib/validation.js.map +1 -0
- package/package.json +28 -38
- package/src/CompletionStream.ts +208 -115
- package/src/Driver.error.test.ts +101 -72
- package/src/Driver.ts +208 -152
- package/src/async.ts +30 -30
- package/src/conversation-utils.ts +92 -16
- package/src/embeddings.ts +157 -0
- package/src/formatters/commons.ts +2 -2
- package/src/formatters/generic.ts +20 -17
- package/src/formatters/index.ts +3 -3
- package/src/formatters/nova.ts +53 -51
- package/src/http-agent.test.ts +281 -0
- package/src/http-agent.ts +177 -0
- package/src/index.ts +7 -5
- package/src/json.ts +6 -5
- package/src/logger.ts +42 -0
- package/src/resolver.ts +12 -9
- package/src/stream.ts +4 -5
- package/src/validation.ts +42 -29
- package/lib/cjs/CompletionStream.js +0 -293
- package/lib/cjs/CompletionStream.js.map +0 -1
- package/lib/cjs/Driver.js +0 -300
- package/lib/cjs/Driver.js.map +0 -1
- package/lib/cjs/async.js +0 -131
- package/lib/cjs/async.js.map +0 -1
- package/lib/cjs/conversation-utils.js +0 -579
- package/lib/cjs/conversation-utils.js.map +0 -1
- package/lib/cjs/formatters/commons.js +0 -7
- package/lib/cjs/formatters/commons.js.map +0 -1
- package/lib/cjs/formatters/generic.js +0 -59
- package/lib/cjs/formatters/generic.js.map +0 -1
- package/lib/cjs/formatters/index.js +0 -20
- package/lib/cjs/formatters/index.js.map +0 -1
- package/lib/cjs/formatters/nova.js +0 -99
- package/lib/cjs/formatters/nova.js.map +0 -1
- package/lib/cjs/index.js +0 -22
- package/lib/cjs/index.js.map +0 -1
- package/lib/cjs/json.js +0 -29
- package/lib/cjs/json.js.map +0 -1
- package/lib/cjs/package.json +0 -3
- package/lib/cjs/resolver.js +0 -45
- package/lib/cjs/resolver.js.map +0 -1
- package/lib/cjs/stream.js +0 -30
- package/lib/cjs/stream.js.map +0 -1
- package/lib/cjs/validation.js +0 -96
- package/lib/cjs/validation.js.map +0 -1
- package/lib/esm/CompletionStream.js.map +0 -1
- package/lib/esm/Driver.js.map +0 -1
- package/lib/esm/async.js.map +0 -1
- package/lib/esm/conversation-utils.js.map +0 -1
- package/lib/esm/formatters/commons.js +0 -4
- package/lib/esm/formatters/commons.js.map +0 -1
- package/lib/esm/formatters/generic.js.map +0 -1
- package/lib/esm/formatters/index.js +0 -4
- package/lib/esm/formatters/index.js.map +0 -1
- package/lib/esm/formatters/nova.js.map +0 -1
- package/lib/esm/index.js +0 -6
- package/lib/esm/index.js.map +0 -1
- package/lib/esm/json.js.map +0 -1
- package/lib/esm/resolver.js.map +0 -1
- package/lib/esm/stream.js.map +0 -1
- package/lib/esm/validation.js +0 -88
- package/lib/esm/validation.js.map +0 -1
- package/lib/types/CompletionStream.d.ts +0 -20
- package/lib/types/CompletionStream.d.ts.map +0 -1
- package/lib/types/Driver.d.ts.map +0 -1
- package/lib/types/async.d.ts.map +0 -1
- package/lib/types/conversation-utils.d.ts.map +0 -1
- package/lib/types/formatters/commons.d.ts +0 -3
- package/lib/types/formatters/commons.d.ts.map +0 -1
- package/lib/types/formatters/generic.d.ts.map +0 -1
- package/lib/types/formatters/index.d.ts +0 -4
- package/lib/types/formatters/index.d.ts.map +0 -1
- package/lib/types/formatters/nova.d.ts.map +0 -1
- package/lib/types/index.d.ts +0 -6
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/json.d.ts.map +0 -1
- package/lib/types/resolver.d.ts +0 -2
- package/lib/types/resolver.d.ts.map +0 -1
- package/lib/types/stream.d.ts.map +0 -1
- package/lib/types/validation.d.ts.map +0 -1
- /package/lib/{types/stream.d.ts → stream.d.ts} +0 -0
- /package/lib/{esm/stream.js → stream.js} +0 -0
package/src/Driver.ts
CHANGED
|
@@ -5,74 +5,49 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
-
AIModel,
|
|
9
|
-
Completion,
|
|
10
|
-
CompletionChunkObject,
|
|
11
|
-
CompletionStream,
|
|
12
|
-
DataSource,
|
|
13
|
-
DriverOptions,
|
|
14
|
-
EmbeddingsOptions,
|
|
15
|
-
EmbeddingsResult,
|
|
16
|
-
ExecutionOptions,
|
|
17
|
-
ExecutionResponse,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} from
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}) as Logger['info'];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const ConsoleLogger: Logger = {
|
|
50
|
-
debug: createConsoleLoggerMethod(console.debug.bind(console)),
|
|
51
|
-
info: createConsoleLoggerMethod(console.info.bind(console)),
|
|
52
|
-
warn: createConsoleLoggerMethod(console.warn.bind(console)),
|
|
53
|
-
error: createConsoleLoggerMethod(console.error.bind(console)),
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const noop = () => void 0;
|
|
57
|
-
const NoopLogger: Logger = {
|
|
58
|
-
debug: noop as Logger['debug'],
|
|
59
|
-
info: noop as Logger['info'],
|
|
60
|
-
warn: noop as Logger['warn'],
|
|
61
|
-
error: noop as Logger['error'],
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function createLogger(logger: Logger | "console" | undefined) {
|
|
65
|
-
if (logger === "console") {
|
|
66
|
-
return ConsoleLogger;
|
|
67
|
-
} else if (logger) {
|
|
68
|
-
return logger;
|
|
69
|
-
} else {
|
|
70
|
-
return NoopLogger;
|
|
8
|
+
type AIModel,
|
|
9
|
+
type Completion,
|
|
10
|
+
type CompletionChunkObject,
|
|
11
|
+
type CompletionStream,
|
|
12
|
+
type DataSource,
|
|
13
|
+
type DriverOptions,
|
|
14
|
+
type EmbeddingsOptions,
|
|
15
|
+
type EmbeddingsResult,
|
|
16
|
+
type ExecutionOptions,
|
|
17
|
+
type ExecutionResponse,
|
|
18
|
+
LlumiverseError,
|
|
19
|
+
type LlumiverseErrorContext,
|
|
20
|
+
type Logger,
|
|
21
|
+
type ModelSearchPayload,
|
|
22
|
+
type PromptOptions,
|
|
23
|
+
type PromptSegment,
|
|
24
|
+
type Providers,
|
|
25
|
+
type TrainingJob,
|
|
26
|
+
type TrainingOptions,
|
|
27
|
+
type TrainingPromptOptions,
|
|
28
|
+
} from '@llumiverse/common';
|
|
29
|
+
import type { Agent } from 'undici';
|
|
30
|
+
import { DefaultCompletionStream, FallbackCompletionStream } from './CompletionStream.js';
|
|
31
|
+
import { formatTextPrompt } from './formatters/index.js';
|
|
32
|
+
import {
|
|
33
|
+
createAgentBackedFetch,
|
|
34
|
+
createDriverHttpAgent,
|
|
35
|
+
createDriverHttpAgentScope,
|
|
36
|
+
type DriverHttpAgentScope,
|
|
37
|
+
} from './http-agent.js';
|
|
38
|
+
import { createLogger } from './logger.js';
|
|
39
|
+
import { validateResult } from './validation.js';
|
|
40
|
+
|
|
41
|
+
export { createLogger } from './logger.js';
|
|
42
|
+
|
|
43
|
+
function getObjectProperty(value: unknown, key: string): unknown {
|
|
44
|
+
if (value && typeof value === 'object' && key in value) {
|
|
45
|
+
return (value as Record<string, unknown>)[key];
|
|
71
46
|
}
|
|
47
|
+
return undefined;
|
|
72
48
|
}
|
|
73
49
|
|
|
74
50
|
export interface Driver<PromptT = unknown> {
|
|
75
|
-
|
|
76
51
|
/**
|
|
77
52
|
*
|
|
78
53
|
* @param segments
|
|
@@ -104,7 +79,13 @@ export interface Driver<PromptT = unknown> {
|
|
|
104
79
|
//check that it is possible to connect to the environment
|
|
105
80
|
validateConnection(): Promise<boolean>;
|
|
106
81
|
|
|
107
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Generate embeddings for one or more inputs.
|
|
84
|
+
* Inputs may be text, image, video, or audio depending on the model and
|
|
85
|
+
* provider. Returns one result item per input, each with one or more
|
|
86
|
+
* output vectors (single-vector for text/image, multi-vector for
|
|
87
|
+
* segmented video/audio or joint-multimodal models).
|
|
88
|
+
*/
|
|
108
89
|
generateEmbeddings(options: EmbeddingsOptions): Promise<EmbeddingsResult>;
|
|
109
90
|
|
|
110
91
|
/**
|
|
@@ -117,56 +98,102 @@ export interface Driver<PromptT = unknown> {
|
|
|
117
98
|
/**
|
|
118
99
|
* To be implemented by each driver
|
|
119
100
|
*/
|
|
120
|
-
export abstract class AbstractDriver<OptionsT extends DriverOptions = DriverOptions, PromptT = unknown>
|
|
101
|
+
export abstract class AbstractDriver<OptionsT extends DriverOptions = DriverOptions, PromptT = unknown>
|
|
102
|
+
implements Driver<PromptT>
|
|
103
|
+
{
|
|
121
104
|
options: OptionsT;
|
|
122
105
|
logger: Logger;
|
|
123
106
|
|
|
124
107
|
abstract provider: Providers | string; // the provider name
|
|
125
108
|
|
|
109
|
+
private _httpAgent?: Agent;
|
|
110
|
+
private _driverFetch?: typeof fetch;
|
|
111
|
+
|
|
126
112
|
constructor(opts: OptionsT) {
|
|
127
113
|
this.options = opts;
|
|
128
114
|
this.logger = createLogger(opts.logger);
|
|
129
115
|
}
|
|
130
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Lazily-created undici `Agent` driven by `options.httpTimeout`.
|
|
119
|
+
* Pools sockets for the lifetime of the driver. Subclasses can
|
|
120
|
+
* either pass this directly to an SDK that accepts a `dispatcher`
|
|
121
|
+
* option (rare), or — much more commonly — use {@link getDriverFetch}
|
|
122
|
+
* to get a fetch implementation backed by it.
|
|
123
|
+
*
|
|
124
|
+
* Released via {@link destroy}.
|
|
125
|
+
*/
|
|
126
|
+
protected getHttpAgent(): Agent {
|
|
127
|
+
if (!this._httpAgent) {
|
|
128
|
+
this._httpAgent = createDriverHttpAgent(this.options.httpTimeout);
|
|
129
|
+
}
|
|
130
|
+
return this._httpAgent;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Fetch-compatible function backed by the driver's HTTP agent.
|
|
135
|
+
* Pass to any SDK that accepts a custom `fetch` option (OpenAI,
|
|
136
|
+
* Anthropic, `@google/genai`, Bedrock via Smithy, …) or use as a
|
|
137
|
+
* drop-in replacement for the global `fetch` in drivers that make
|
|
138
|
+
* raw HTTP calls.
|
|
139
|
+
*/
|
|
140
|
+
protected getDriverFetch(): typeof fetch {
|
|
141
|
+
if (!this._driverFetch) {
|
|
142
|
+
this._driverFetch = createAgentBackedFetch(this.getHttpAgent());
|
|
143
|
+
}
|
|
144
|
+
return this._driverFetch;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public createExecutionHttpAgentScope(options: Pick<ExecutionOptions, 'httpTimeout'>): DriverHttpAgentScope {
|
|
148
|
+
return createDriverHttpAgentScope(this.options.httpTimeout, options.httpTimeout);
|
|
149
|
+
}
|
|
150
|
+
|
|
131
151
|
async createTrainingPrompt(options: TrainingPromptOptions): Promise<string> {
|
|
132
|
-
const prompt = await this.createPrompt(options.segments, {
|
|
152
|
+
const prompt = await this.createPrompt(options.segments, {
|
|
153
|
+
result_schema: options.schema,
|
|
154
|
+
model: options.model,
|
|
155
|
+
});
|
|
133
156
|
return JSON.stringify({
|
|
134
157
|
prompt,
|
|
135
|
-
completion:
|
|
158
|
+
completion:
|
|
159
|
+
typeof options.completion === 'string' ? options.completion : JSON.stringify(options.completion),
|
|
136
160
|
});
|
|
137
161
|
}
|
|
138
162
|
|
|
139
163
|
startTraining(_dataset: DataSource, _options: TrainingOptions): Promise<TrainingJob> {
|
|
140
|
-
throw new Error(
|
|
164
|
+
throw new Error('Method not implemented.');
|
|
141
165
|
}
|
|
142
166
|
|
|
143
167
|
cancelTraining(_jobId: string): Promise<TrainingJob> {
|
|
144
|
-
throw new Error(
|
|
168
|
+
throw new Error('Method not implemented.');
|
|
145
169
|
}
|
|
146
170
|
|
|
147
171
|
getTrainingJob(_jobId: string): Promise<TrainingJob> {
|
|
148
|
-
throw new Error(
|
|
172
|
+
throw new Error('Method not implemented.');
|
|
149
173
|
}
|
|
150
174
|
|
|
151
175
|
validateResult(result: Completion, options: ExecutionOptions) {
|
|
152
176
|
if (!result.tool_use && !result.error && options.result_schema) {
|
|
153
177
|
try {
|
|
154
178
|
result.result = validateResult(result.result, options.result_schema);
|
|
155
|
-
} catch (error:
|
|
156
|
-
const
|
|
179
|
+
} catch (error: unknown) {
|
|
180
|
+
const validationError = error instanceof Error ? error : new Error(String(error));
|
|
181
|
+
const rawCode = getObjectProperty(error, 'code');
|
|
182
|
+
const code = rawCode === 'json_error' || rawCode === 'validation_error' ? rawCode : undefined;
|
|
183
|
+
const errorMessage = `[${this.provider}] [${options.model}] ${code ? `[${code}] ` : ''}Result validation error: ${validationError.message}`;
|
|
157
184
|
this.logger.error({ err: error, data: result.result }, errorMessage);
|
|
158
185
|
result.error = {
|
|
159
|
-
code:
|
|
160
|
-
message:
|
|
186
|
+
code: code || 'validation_error',
|
|
187
|
+
message: validationError.message,
|
|
161
188
|
data: result.result,
|
|
162
|
-
}
|
|
189
|
+
};
|
|
163
190
|
}
|
|
164
191
|
}
|
|
165
192
|
}
|
|
166
193
|
|
|
167
194
|
async execute(segments: PromptSegment[], options: ExecutionOptions): Promise<ExecutionResponse<PromptT>> {
|
|
168
195
|
const prompt = await this.createPrompt(segments, options);
|
|
169
|
-
return this._execute(prompt, options).catch((error:
|
|
196
|
+
return this._execute(prompt, options).catch((error: unknown) => {
|
|
170
197
|
// Don't wrap if already a LlumiverseError
|
|
171
198
|
if (LlumiverseError.isLlumiverseError(error)) {
|
|
172
199
|
throw error;
|
|
@@ -180,45 +207,63 @@ export abstract class AbstractDriver<OptionsT extends DriverOptions = DriverOpti
|
|
|
180
207
|
}
|
|
181
208
|
|
|
182
209
|
async _execute(prompt: PromptT, options: ExecutionOptions): Promise<ExecutionResponse<PromptT>> {
|
|
210
|
+
const httpScope = this.createExecutionHttpAgentScope(options);
|
|
183
211
|
try {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
return await httpScope.run(async () => {
|
|
213
|
+
try {
|
|
214
|
+
const start = Date.now();
|
|
215
|
+
let result: Completion;
|
|
216
|
+
|
|
217
|
+
if (this.isImageModel(options.model)) {
|
|
218
|
+
this.logger.debug(`[${this.provider}] Executing prompt on ${options.model}, image pathway.`);
|
|
219
|
+
result = await this.requestImageGeneration(prompt, options);
|
|
220
|
+
} else {
|
|
221
|
+
this.logger.debug(`[${this.provider}] Executing prompt on ${options.model}, text pathway.`);
|
|
222
|
+
result = await this.requestTextCompletion(prompt, options);
|
|
223
|
+
this.validateResult(result, options);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const execution_time = Date.now() - start;
|
|
227
|
+
return { ...result, prompt, execution_time };
|
|
228
|
+
} catch (error) {
|
|
229
|
+
// Don't wrap if already a LlumiverseError
|
|
230
|
+
if (LlumiverseError.isLlumiverseError(error)) {
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
// Log the original error for debugging
|
|
234
|
+
this.logger.error(
|
|
235
|
+
{
|
|
236
|
+
err: error,
|
|
237
|
+
data: { provider: this.provider, model: options.model, operation: 'execute', prompt },
|
|
238
|
+
},
|
|
239
|
+
`Error during execution in provider ${this.provider}:`,
|
|
240
|
+
);
|
|
241
|
+
throw this.formatLlumiverseError(error, {
|
|
242
|
+
provider: this.provider,
|
|
243
|
+
model: options.model,
|
|
244
|
+
operation: 'execute',
|
|
245
|
+
});
|
|
246
|
+
}
|
|
211
247
|
});
|
|
248
|
+
} finally {
|
|
249
|
+
await httpScope.close();
|
|
212
250
|
}
|
|
213
251
|
}
|
|
214
252
|
|
|
253
|
+
public formatDebugPrompt(prompt: PromptT): PromptT {
|
|
254
|
+
return prompt;
|
|
255
|
+
}
|
|
256
|
+
|
|
215
257
|
protected isImageModel(_model: string): boolean {
|
|
216
258
|
return false;
|
|
217
259
|
}
|
|
218
260
|
|
|
219
261
|
// by default no stream is supported. we block and we return all at once
|
|
220
262
|
async stream(segments: PromptSegment[], options: ExecutionOptions): Promise<CompletionStream<PromptT>> {
|
|
221
|
-
this.logger.debug(
|
|
263
|
+
this.logger.debug(
|
|
264
|
+
options,
|
|
265
|
+
`Executing prompt with provider ${this.provider} with options: ${JSON.stringify(options)}`,
|
|
266
|
+
);
|
|
222
267
|
const prompt = await this.createPrompt(segments, options);
|
|
223
268
|
const canStream = await this.canStream(options);
|
|
224
269
|
if (canStream) {
|
|
@@ -241,7 +286,9 @@ export abstract class AbstractDriver<OptionsT extends DriverOptions = DriverOpti
|
|
|
241
286
|
}
|
|
242
287
|
|
|
243
288
|
public async createPrompt(segments: PromptSegment[], opts: PromptOptions): Promise<PromptT> {
|
|
244
|
-
return await (opts.format
|
|
289
|
+
return await (opts.format
|
|
290
|
+
? (opts.format(segments, opts.result_schema) as PromptT)
|
|
291
|
+
: this.formatPrompt(segments, opts));
|
|
245
292
|
}
|
|
246
293
|
|
|
247
294
|
/**
|
|
@@ -280,7 +327,7 @@ export abstract class AbstractDriver<OptionsT extends DriverOptions = DriverOpti
|
|
|
280
327
|
_prompt: PromptT,
|
|
281
328
|
_result: unknown[],
|
|
282
329
|
_toolUse: unknown[] | undefined,
|
|
283
|
-
_options: ExecutionOptions
|
|
330
|
+
_options: ExecutionOptions,
|
|
284
331
|
): unknown | undefined {
|
|
285
332
|
// Default implementation returns undefined - drivers can override
|
|
286
333
|
return undefined;
|
|
@@ -289,90 +336,90 @@ export abstract class AbstractDriver<OptionsT extends DriverOptions = DriverOpti
|
|
|
289
336
|
/**
|
|
290
337
|
* Format an error into LlumiverseError. Override in driver implementations
|
|
291
338
|
* to provide provider-specific error parsing.
|
|
292
|
-
*
|
|
339
|
+
*
|
|
293
340
|
* The default implementation uses common patterns:
|
|
294
341
|
* - Status 429, 408: retryable (rate limit, timeout)
|
|
295
342
|
* - Status 529: retryable (overloaded)
|
|
296
343
|
* - Status 5xx: retryable (server errors)
|
|
297
|
-
* -
|
|
298
|
-
* -
|
|
299
|
-
*
|
|
344
|
+
* - High-confidence transient messages containing "rate limit", "timeout", etc.: retryable
|
|
345
|
+
* - Status 4xx (except above and transient provider quirks): not retryable (client errors)
|
|
346
|
+
*
|
|
300
347
|
* @param error - The error to format
|
|
301
348
|
* @param context - Context about where the error occurred
|
|
302
349
|
* @returns A standardized LlumiverseError
|
|
303
350
|
*/
|
|
304
|
-
public formatLlumiverseError(
|
|
305
|
-
error: unknown,
|
|
306
|
-
context: LlumiverseErrorContext
|
|
307
|
-
): LlumiverseError {
|
|
351
|
+
public formatLlumiverseError(error: unknown, context: LlumiverseErrorContext): LlumiverseError {
|
|
308
352
|
// Extract status code from common locations (only if numeric)
|
|
309
353
|
let code: number | undefined;
|
|
310
|
-
const rawCode =
|
|
311
|
-
|
|
312
|
-
|
|
354
|
+
const rawCode =
|
|
355
|
+
getObjectProperty(error, 'status') ||
|
|
356
|
+
getObjectProperty(error, 'statusCode') ||
|
|
357
|
+
getObjectProperty(error, 'code');
|
|
313
358
|
|
|
314
359
|
if (typeof rawCode === 'number') {
|
|
315
360
|
code = rawCode;
|
|
316
361
|
}
|
|
317
362
|
|
|
318
363
|
// Extract error name if available
|
|
319
|
-
const
|
|
364
|
+
const rawErrorName = getObjectProperty(error, 'name');
|
|
365
|
+
const errorName = typeof rawErrorName === 'string' ? rawErrorName : undefined;
|
|
320
366
|
|
|
321
367
|
// Extract message
|
|
322
|
-
const message = error instanceof Error
|
|
323
|
-
? error.message
|
|
324
|
-
: String(error);
|
|
368
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
325
369
|
|
|
326
370
|
// Determine retryability
|
|
327
371
|
const retryable = this.isRetryableError(code, message);
|
|
328
372
|
|
|
329
|
-
return new LlumiverseError(
|
|
330
|
-
`[${this.provider}] ${message}`,
|
|
331
|
-
retryable,
|
|
332
|
-
context,
|
|
333
|
-
error,
|
|
334
|
-
code,
|
|
335
|
-
errorName
|
|
336
|
-
);
|
|
373
|
+
return new LlumiverseError(`[${this.provider}] ${message}`, retryable, context, error, code, errorName);
|
|
337
374
|
}
|
|
338
375
|
|
|
339
376
|
/**
|
|
340
377
|
* Determine if an error is retryable based on status code and message.
|
|
341
378
|
* Can be overridden by drivers for provider-specific logic.
|
|
342
|
-
*
|
|
379
|
+
*
|
|
343
380
|
* @param statusCode - The HTTP status code (if available)
|
|
344
381
|
* @param message - The error message
|
|
345
382
|
* @returns True if retryable, false if not retryable, undefined if unknown
|
|
346
383
|
*/
|
|
347
384
|
protected isRetryableError(statusCode: number | undefined, message: string): boolean | undefined {
|
|
348
|
-
// Numeric status codes
|
|
349
|
-
if (statusCode !== undefined) {
|
|
350
|
-
if (statusCode === 429 || statusCode === 408) return true; // Rate limit, timeout
|
|
351
|
-
if (statusCode === 529) return true; // Overloaded
|
|
352
|
-
if (statusCode >= 500 && statusCode < 600) return true; // Server errors
|
|
353
|
-
return false; // 4xx client errors not retryable
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Message-based detection for non-HTTP errors
|
|
357
385
|
const lowerMessage = message.toLowerCase();
|
|
358
386
|
|
|
359
|
-
//
|
|
387
|
+
// Provider APIs sometimes surface transient failures under misleading
|
|
388
|
+
// client status codes, so high-confidence transient message signals
|
|
389
|
+
// must be honored before the generic 4xx classification below.
|
|
390
|
+
if (lowerMessage.includes('url_rejected-rejected_client_throttled')) return true;
|
|
391
|
+
if (lowerMessage.includes('url_rejected-rejected_rate_limited')) return true;
|
|
360
392
|
if (lowerMessage.includes('rate') && lowerMessage.includes('limit')) return true;
|
|
361
|
-
|
|
362
|
-
// Timeout variations (timeout, timed out, time out)
|
|
363
393
|
if (lowerMessage.includes('timeout')) return true;
|
|
364
394
|
if (lowerMessage.includes('timed') && lowerMessage.includes('out')) return true;
|
|
365
395
|
if (lowerMessage.includes('time') && lowerMessage.includes('out')) return true;
|
|
366
|
-
|
|
367
|
-
// Resource exhausted variations
|
|
368
396
|
if (lowerMessage.includes('resource') && lowerMessage.includes('exhaust')) return true;
|
|
369
|
-
|
|
370
|
-
// Other retryable patterns
|
|
371
|
-
if (lowerMessage.includes('retry')) return true;
|
|
372
397
|
if (lowerMessage.includes('overload')) return true;
|
|
373
398
|
if (lowerMessage.includes('throttl')) return true;
|
|
374
399
|
if (lowerMessage.includes('429')) return true;
|
|
375
400
|
if (lowerMessage.includes('529')) return true;
|
|
401
|
+
// A transport-level abort (request-timeout / dropped connection) or a
|
|
402
|
+
// deadline-exceeded is transient and should be retried — even when the provider
|
|
403
|
+
// surfaces it under a misleading 4xx status. A deliberate cancellation is raised
|
|
404
|
+
// as a Temporal CancelledFailure (not an LLM error), so it never reaches here.
|
|
405
|
+
if (lowerMessage.includes('aborted')) return true;
|
|
406
|
+
if (lowerMessage.includes('deadline')) return true;
|
|
407
|
+
|
|
408
|
+
// Explicit auth failures should never be retried even when they arrive without
|
|
409
|
+
// a numeric status code (for example, google-auth-library invalid_grant errors).
|
|
410
|
+
if (lowerMessage.includes('invalid_grant')) return false;
|
|
411
|
+
if (lowerMessage.includes("credential's issuer")) return false;
|
|
412
|
+
|
|
413
|
+
// Numeric status codes
|
|
414
|
+
if (statusCode !== undefined) {
|
|
415
|
+
if (statusCode === 429 || statusCode === 408) return true; // Rate limit, timeout
|
|
416
|
+
if (statusCode === 529) return true; // Overloaded
|
|
417
|
+
if (statusCode >= 500 && statusCode < 600) return true; // Server errors
|
|
418
|
+
return false; // 4xx client errors not retryable
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Message-based detection for non-HTTP errors
|
|
422
|
+
if (lowerMessage.includes('retry')) return true;
|
|
376
423
|
|
|
377
424
|
// Unknown errors - let consumer decide retry strategy
|
|
378
425
|
return undefined;
|
|
@@ -380,10 +427,13 @@ export abstract class AbstractDriver<OptionsT extends DriverOptions = DriverOpti
|
|
|
380
427
|
|
|
381
428
|
abstract requestTextCompletion(prompt: PromptT, options: ExecutionOptions): Promise<Completion>;
|
|
382
429
|
|
|
383
|
-
abstract requestTextCompletionStream(
|
|
430
|
+
abstract requestTextCompletionStream(
|
|
431
|
+
prompt: PromptT,
|
|
432
|
+
options: ExecutionOptions,
|
|
433
|
+
): Promise<AsyncIterable<CompletionChunkObject>>;
|
|
384
434
|
|
|
385
435
|
async requestImageGeneration(_prompt: PromptT, _options: ExecutionOptions): Promise<Completion> {
|
|
386
|
-
throw new Error(
|
|
436
|
+
throw new Error('Image generation not implemented.');
|
|
387
437
|
//Cannot be made abstract, as abstract methods are required in the derived class
|
|
388
438
|
}
|
|
389
439
|
|
|
@@ -398,9 +448,15 @@ export abstract class AbstractDriver<OptionsT extends DriverOptions = DriverOpti
|
|
|
398
448
|
|
|
399
449
|
/**
|
|
400
450
|
* Cleanup method called when the driver is evicted from the cache.
|
|
401
|
-
*
|
|
451
|
+
* Releases the lazily-created HTTP agent socket pool. Override this
|
|
452
|
+
* in driver implementations that need to release additional resources
|
|
453
|
+
* — MUST call `super.destroy()` to avoid leaking sockets.
|
|
402
454
|
*/
|
|
403
455
|
destroy(): void {
|
|
404
|
-
|
|
456
|
+
this._httpAgent?.close().catch(() => {
|
|
457
|
+
/* shutdown best-effort */
|
|
458
|
+
});
|
|
459
|
+
this._httpAgent = undefined;
|
|
460
|
+
this._driverFetch = undefined;
|
|
405
461
|
}
|
|
406
462
|
}
|
package/src/async.ts
CHANGED
|
@@ -1,50 +1,52 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { CompletionChunkObject } from '@llumiverse/common';
|
|
2
|
+
import type { ServerSentEvent } from '@vertesia/api-fetch-client';
|
|
3
3
|
|
|
4
4
|
export async function* asyncMap<T, R>(asyncIterable: AsyncIterable<T>, callback: (value: T, index: number) => R) {
|
|
5
5
|
let i = 0;
|
|
6
|
-
for await (const val of asyncIterable)
|
|
7
|
-
yield callback(val, i++);
|
|
6
|
+
for await (const val of asyncIterable) yield callback(val, i++);
|
|
8
7
|
}
|
|
9
8
|
|
|
10
9
|
export function oneAsyncIterator<T>(value: T): AsyncIterable<T> {
|
|
11
10
|
return {
|
|
12
11
|
async *[Symbol.asyncIterator]() {
|
|
13
|
-
yield value
|
|
14
|
-
}
|
|
15
|
-
}
|
|
12
|
+
yield value;
|
|
13
|
+
},
|
|
14
|
+
};
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* Given a ReadableStream of server sent events, tran
|
|
20
19
|
*/
|
|
21
|
-
export function transformSSEStream(
|
|
20
|
+
export function transformSSEStream(
|
|
21
|
+
stream: ReadableStream<ServerSentEvent>,
|
|
22
|
+
transform: (data: string) => CompletionChunkObject,
|
|
23
|
+
): ReadableStream<CompletionChunkObject> & AsyncIterable<CompletionChunkObject> {
|
|
22
24
|
// on node and bun the ReadableStream is an async iterable
|
|
23
|
-
return stream.pipeThrough(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
return stream.pipeThrough(
|
|
26
|
+
new TransformStream<ServerSentEvent, CompletionChunkObject>({
|
|
27
|
+
transform(event: ServerSentEvent, controller) {
|
|
28
|
+
if (event.type === 'event' && event.data && event.data !== '[DONE]') {
|
|
29
|
+
try {
|
|
30
|
+
const result = transform(event.data) ?? '';
|
|
31
|
+
controller.enqueue(result);
|
|
32
|
+
} catch {
|
|
33
|
+
// double check for the last event which is not a JSON - at this time togetherai and mistralai returns the string [DONE]
|
|
34
|
+
// do nothing - happens if data is not a JSON - the last event data is the [DONE] string
|
|
35
|
+
}
|
|
32
36
|
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
) satisfies ReadableStream<CompletionChunkObject> & AsyncIterable<CompletionChunkObject>;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
export class EventStream<T, ReturnT =
|
|
39
|
-
|
|
42
|
+
export class EventStream<T, ReturnT = unknown> implements AsyncIterable<T> {
|
|
40
43
|
private queue: T[] = [];
|
|
41
44
|
private pending?: {
|
|
42
|
-
resolve: (result: IteratorResult<T, ReturnT | undefined>) => void
|
|
43
|
-
reject: (err:
|
|
45
|
+
resolve: (result: IteratorResult<T, ReturnT | undefined>) => void;
|
|
46
|
+
reject: (err: unknown) => void;
|
|
44
47
|
};
|
|
45
48
|
private done = false;
|
|
46
49
|
|
|
47
|
-
|
|
48
50
|
push(event: T) {
|
|
49
51
|
if (this.done) {
|
|
50
52
|
throw new Error('Cannot push to a closed stream');
|
|
@@ -92,13 +94,11 @@ export class EventStream<T, ReturnT = any> implements AsyncIterable<T> {
|
|
|
92
94
|
}
|
|
93
95
|
const _value = await value;
|
|
94
96
|
return { done: true, value: _value };
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
+
},
|
|
98
|
+
};
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
102
|
/**
|
|
103
103
|
* Transform an async iterator by applying a function to each value.
|
|
104
104
|
* @param originalGenerator
|
|
@@ -107,7 +107,7 @@ export class EventStream<T, ReturnT = any> implements AsyncIterable<T> {
|
|
|
107
107
|
export async function* transformAsyncIterator<T, V>(
|
|
108
108
|
originalGenerator: AsyncIterable<T>,
|
|
109
109
|
transform: (value: T) => V | Promise<V>,
|
|
110
|
-
initCallback?: () => V | Promise<V
|
|
110
|
+
initCallback?: () => V | Promise<V>,
|
|
111
111
|
): AsyncIterable<V> {
|
|
112
112
|
if (initCallback) {
|
|
113
113
|
yield initCallback();
|