@hue-run/sdk 0.18.0 → 0.18.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/CLI.md +130 -8
- package/EVALUATIONS.md +7 -4
- package/README.md +43 -6
- package/dist/blobs.js +1 -1
- package/dist/cli/import.d.ts +35 -0
- package/dist/cli/import.js +692 -0
- package/dist/cli/langfuse.d.ts +309 -0
- package/dist/cli/langfuse.js +1075 -0
- package/dist/evals/client.d.ts +7 -2
- package/dist/evals/client.js +20 -4
- package/dist/evals/files.js +100 -12
- package/dist/evals/media.d.ts +5 -1
- package/dist/evals/media.js +15 -3
- package/dist/evals/types.d.ts +4 -3
- package/dist/inline-files.d.ts +12 -3
- package/dist/inline-files.js +12 -9
- package/dist/setup/cli.js +7 -3
- package/dist/transport.d.ts +3 -0
- package/dist/transport.js +11 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { type AttributeValue, type HrTime } from "@opentelemetry/api";
|
|
2
|
+
import type { Resource } from "@opentelemetry/resources";
|
|
3
|
+
import type { ReadableSpan } from "@opentelemetry/sdk-trace";
|
|
4
|
+
import { type BlobUploader, type UploadContext } from "../blobs.js";
|
|
5
|
+
import { type InlineFilePart, type MediaValue } from "../inline-files.js";
|
|
6
|
+
/** The Langfuse project an import reads and how it waits. */
|
|
7
|
+
export interface LangfuseConnection {
|
|
8
|
+
/** The API's base URL, `https://cloud.langfuse.com` or a self-hosted origin, without a trailing
|
|
9
|
+
* slash. */
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
publicKey: string;
|
|
12
|
+
secretKey: string;
|
|
13
|
+
fetch?: typeof fetch;
|
|
14
|
+
/** Waits `ms`; resolves early when the import is stopped. */
|
|
15
|
+
sleep: (ms: number) => Promise<void>;
|
|
16
|
+
signal: AbortSignal;
|
|
17
|
+
/** Told about each wait for a rate limit or a failed request, in whole seconds. */
|
|
18
|
+
onWait?: (seconds: number, reason: string) => void;
|
|
19
|
+
}
|
|
20
|
+
/** A request Langfuse refused or did not answer; `fatal` stops the whole import. */
|
|
21
|
+
export declare class LangfuseError extends Error {
|
|
22
|
+
readonly status?: number | undefined;
|
|
23
|
+
readonly fatal: boolean;
|
|
24
|
+
constructor(message: string, status?: number | undefined, fatal?: boolean);
|
|
25
|
+
}
|
|
26
|
+
/** The import was stopped while a request was waiting. */
|
|
27
|
+
export declare class ImportStopped extends Error {
|
|
28
|
+
constructor();
|
|
29
|
+
}
|
|
30
|
+
/** A trace's time window, as ISO 8601 instants. */
|
|
31
|
+
export interface ImportWindow {
|
|
32
|
+
from?: string;
|
|
33
|
+
to?: string;
|
|
34
|
+
}
|
|
35
|
+
/** The API a Langfuse deployment reads traces through: `v2` (Langfuse Cloud and self-hosted v4
|
|
36
|
+
* and later, observations with cursor pages) or `v1` (self-hosted v3, traces and observations
|
|
37
|
+
* with numbered pages). */
|
|
38
|
+
export type LangfuseApiVersion = "v1" | "v2";
|
|
39
|
+
/** A Langfuse observation as the v1 or v2 API returns it, only the fields the import reads. */
|
|
40
|
+
export interface LangfuseObservation {
|
|
41
|
+
id: string;
|
|
42
|
+
traceId: string;
|
|
43
|
+
type?: string;
|
|
44
|
+
name?: string;
|
|
45
|
+
startTime: string;
|
|
46
|
+
endTime?: string;
|
|
47
|
+
completionStartTime?: string;
|
|
48
|
+
parentObservationId?: string;
|
|
49
|
+
level?: string;
|
|
50
|
+
statusMessage?: string;
|
|
51
|
+
version?: string;
|
|
52
|
+
environment?: string;
|
|
53
|
+
input?: unknown;
|
|
54
|
+
output?: unknown;
|
|
55
|
+
metadata?: unknown;
|
|
56
|
+
model?: string;
|
|
57
|
+
modelParameters?: unknown;
|
|
58
|
+
usageDetails?: Record<string, unknown>;
|
|
59
|
+
costDetails?: Record<string, unknown>;
|
|
60
|
+
promptName?: string;
|
|
61
|
+
promptVersion?: number;
|
|
62
|
+
userId?: string;
|
|
63
|
+
sessionId?: string;
|
|
64
|
+
traceName?: string;
|
|
65
|
+
tags?: string[];
|
|
66
|
+
release?: string;
|
|
67
|
+
}
|
|
68
|
+
/** A Langfuse v1 trace, the fields the import reads. */
|
|
69
|
+
export interface LangfuseTrace {
|
|
70
|
+
id: string;
|
|
71
|
+
timestamp: string;
|
|
72
|
+
name?: string;
|
|
73
|
+
input?: unknown;
|
|
74
|
+
output?: unknown;
|
|
75
|
+
metadata?: unknown;
|
|
76
|
+
sessionId?: string;
|
|
77
|
+
userId?: string;
|
|
78
|
+
release?: string;
|
|
79
|
+
version?: string;
|
|
80
|
+
tags?: string[];
|
|
81
|
+
public?: boolean;
|
|
82
|
+
environment?: string;
|
|
83
|
+
}
|
|
84
|
+
/** An observation from an API answer, or undefined when it lacks the fields every span needs. */
|
|
85
|
+
export declare function readObservation(value: unknown): LangfuseObservation | undefined;
|
|
86
|
+
/** A v1 trace from an API answer, or undefined when it lacks an id or timestamp. */
|
|
87
|
+
export declare function readTrace(value: unknown): LangfuseTrace | undefined;
|
|
88
|
+
/** An answer read whole: its status and body, or `tooLarge` for a body past the bound. */
|
|
89
|
+
type Answer = {
|
|
90
|
+
status: number;
|
|
91
|
+
body: Buffer;
|
|
92
|
+
} | {
|
|
93
|
+
status: number;
|
|
94
|
+
tooLarge: true;
|
|
95
|
+
};
|
|
96
|
+
/** Reads one Langfuse project through its public API with Basic authentication. */
|
|
97
|
+
export declare class LangfuseApi {
|
|
98
|
+
private readonly connection;
|
|
99
|
+
private readonly fetchImpl;
|
|
100
|
+
private readonly authorization;
|
|
101
|
+
private readonly seen;
|
|
102
|
+
constructor(connection: LangfuseConnection);
|
|
103
|
+
private stopped;
|
|
104
|
+
private wait;
|
|
105
|
+
/**
|
|
106
|
+
* One GET with retries: a rate limit (HTTP 429) is waited out for as long as its `Retry-After`
|
|
107
|
+
* asks (up to 15 minutes, ten times in a row), and a connection failure, a timeout or a 5xx is
|
|
108
|
+
* tried again after 1, 2, 4, 8 and 16 seconds (or its `Retry-After`, up to a minute). An
|
|
109
|
+
* authenticated request never follows a redirect, so the keys go only to the configured host;
|
|
110
|
+
* `authenticated` is false for a presigned media download, which carries no Langfuse keys.
|
|
111
|
+
*/
|
|
112
|
+
get(url: URL, authenticated?: boolean, maxBytes?: number, timeoutMs?: number): Promise<Answer>;
|
|
113
|
+
/** A GET of `path` under the API, answered with JSON; refusals of the keys are fatal. */
|
|
114
|
+
private json;
|
|
115
|
+
/** Which API the deployment reads traces through: v2 unless it answers that it has none. */
|
|
116
|
+
version(): Promise<LangfuseApiVersion>;
|
|
117
|
+
/** Whether a trace id has not been listed before in this import. */
|
|
118
|
+
private first;
|
|
119
|
+
/** The ids of the traces whose root observations started in the window, newest first. */
|
|
120
|
+
rootTraceIds(window: ImportWindow): AsyncGenerator<string>;
|
|
121
|
+
/** A trace's observations through the v2 API, a page at a time. */
|
|
122
|
+
v2Observations(traceId: string): AsyncGenerator<LangfuseObservation[]>;
|
|
123
|
+
/** The v1 traces whose timestamp falls in the window, newest first. */
|
|
124
|
+
v1Traces(window: ImportWindow): AsyncGenerator<LangfuseTrace>;
|
|
125
|
+
/** One v1 trace by id, or undefined when Langfuse holds none. */
|
|
126
|
+
v1Trace(traceId: string): Promise<LangfuseTrace | undefined>;
|
|
127
|
+
/** A trace's observations through the v1 API, a page at a time. */
|
|
128
|
+
v1Observations(traceId: string): AsyncGenerator<LangfuseObservation[]>;
|
|
129
|
+
private page;
|
|
130
|
+
private observations;
|
|
131
|
+
/**
|
|
132
|
+
* A media file's bytes through `GET /api/public/media/{id}` and the download link it answers
|
|
133
|
+
* (fetched without the Langfuse keys): `missing` when Langfuse holds no bytes for it (no record,
|
|
134
|
+
* no link, or a link whose object is gone), `tooLarge` past {@link MAX_FILE_DATA_BYTES}.
|
|
135
|
+
*/
|
|
136
|
+
media(id: string): Promise<Uint8Array | "missing" | "tooLarge">;
|
|
137
|
+
}
|
|
138
|
+
/** The OpenTelemetry trace id of a Langfuse trace: its own id when it is one (32 lowercase hex
|
|
139
|
+
* digits, as the Langfuse OpenTelemetry SDKs write), otherwise one derived from it by SHA-256, so
|
|
140
|
+
* the same trace always takes the same id. */
|
|
141
|
+
export declare function hueTraceId(langfuseTraceId: string): string;
|
|
142
|
+
/** The OpenTelemetry span id of a Langfuse observation: its own id when it is one (16 lowercase
|
|
143
|
+
* hex digits), otherwise one derived from the trace's and its own id. */
|
|
144
|
+
export declare function hueSpanId(langfuseTraceId: string, observationId: string): string;
|
|
145
|
+
/** The span id of the span that stands for a v1 trace itself. */
|
|
146
|
+
export declare function traceSpanId(langfuseTraceId: string): string;
|
|
147
|
+
/** An ISO 8601 instant as OpenTelemetry time, keeping up to nanosecond digits; undefined for
|
|
148
|
+
* anything else. */
|
|
149
|
+
export declare function hrTimeOf(value: string | undefined): HrTime | undefined;
|
|
150
|
+
/** The time a set of spans covers, from the earliest start to the latest end. */
|
|
151
|
+
export interface TimeBounds {
|
|
152
|
+
start: HrTime;
|
|
153
|
+
end: HrTime;
|
|
154
|
+
}
|
|
155
|
+
/** `bounds` widened to cover `span`. */
|
|
156
|
+
export declare function widen(bounds: TimeBounds | undefined, span: TimeBounds): TimeBounds;
|
|
157
|
+
/** A span before it is handed to the exporter. */
|
|
158
|
+
export interface ImportSpan {
|
|
159
|
+
traceId: string;
|
|
160
|
+
spanId: string;
|
|
161
|
+
parentSpanId?: string;
|
|
162
|
+
name: string;
|
|
163
|
+
start: HrTime;
|
|
164
|
+
end: HrTime;
|
|
165
|
+
attributes: Record<string, AttributeValue>;
|
|
166
|
+
/** Set for an observation at level `ERROR`: the span's status is an error with this message. */
|
|
167
|
+
error?: string;
|
|
168
|
+
}
|
|
169
|
+
/** What a v1 trace gives each of its observations, as the Langfuse SDKs propagate it. */
|
|
170
|
+
export interface TraceContext {
|
|
171
|
+
langfuseTraceId: string;
|
|
172
|
+
/** The v1 trace's own span, the parent of its observations without one. */
|
|
173
|
+
rootSpanId?: string;
|
|
174
|
+
name?: string;
|
|
175
|
+
userId?: string;
|
|
176
|
+
sessionId?: string;
|
|
177
|
+
tags?: string[];
|
|
178
|
+
release?: string;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* A Langfuse observation as the span the Langfuse OpenTelemetry SDKs would have recorded:
|
|
182
|
+
* `langfuse.observation.*` (type in lower case, input, output, one `metadata.<key>` attribute per
|
|
183
|
+
* key, level, status message, model, model parameters, usage and cost details, completion start,
|
|
184
|
+
* prompt), `langfuse.trace.name`, `langfuse.trace.tags`, `langfuse.user.id`,
|
|
185
|
+
* `langfuse.session.id`, `langfuse.release`, `langfuse.version` and `langfuse.environment`, with
|
|
186
|
+
* `gen_ai.request.model` and the input and output token counts as GenAI attributes. The
|
|
187
|
+
* observation's and trace's own ids are kept as `langfuse.observation.id` and
|
|
188
|
+
* `langfuse.trace.id`. Throws for a start time that is no ISO 8601 instant.
|
|
189
|
+
*/
|
|
190
|
+
export declare function observationSpan(observation: LangfuseObservation, context: TraceContext): ImportSpan;
|
|
191
|
+
/** The context a v1 trace gives its observations. */
|
|
192
|
+
export declare function traceContext(trace: LangfuseTrace): TraceContext;
|
|
193
|
+
/**
|
|
194
|
+
* The span that stands for a v1 trace, the parent of its observations without one, as the trace
|
|
195
|
+
* Langfuse shows above them: `langfuse.trace.*` (name, input, output, one `metadata.<key>`
|
|
196
|
+
* attribute per key, tags, public), the user, session, release, version and environment. It
|
|
197
|
+
* covers the trace's timestamp and its observations, `observed`. Throws for a timestamp that is
|
|
198
|
+
* no ISO 8601 instant.
|
|
199
|
+
*/
|
|
200
|
+
export declare function traceSpan(trace: LangfuseTrace, observed: TimeBounds | undefined): ImportSpan;
|
|
201
|
+
/** A converted span as the OpenTelemetry SDK hands a finished span to a processor. */
|
|
202
|
+
export declare function readableSpan(span: ImportSpan, resource: Resource, scope: {
|
|
203
|
+
name: string;
|
|
204
|
+
}): ReadableSpan;
|
|
205
|
+
/** A Langfuse media reference, as the Langfuse SDKs write it in place of a file they uploaded. */
|
|
206
|
+
export interface MediaToken {
|
|
207
|
+
type: string;
|
|
208
|
+
id: string;
|
|
209
|
+
source: string;
|
|
210
|
+
}
|
|
211
|
+
/** The token a whole string is (`@@@langfuseMedia:type=<media type>|id=<id>|source=<source>@@@`),
|
|
212
|
+
* or undefined when it is not exactly one. */
|
|
213
|
+
export declare function parseMediaToken(value: string): MediaToken | undefined;
|
|
214
|
+
/** The span attributes whose text Hue reads files from and the import rewrites tokens in. */
|
|
215
|
+
export declare function isMediaAttribute(key: string): boolean;
|
|
216
|
+
/** One token found where Hue reads a file, and the place it takes once its bytes are uploaded. */
|
|
217
|
+
export interface MediaUse {
|
|
218
|
+
attribute: string;
|
|
219
|
+
token: MediaToken;
|
|
220
|
+
/** The token's JSON pointer in the attribute's value. */
|
|
221
|
+
pointer: string;
|
|
222
|
+
/** The `hue.blobs` key the file is listed under: `<attribute>#<pointer of its part>`. */
|
|
223
|
+
key: string;
|
|
224
|
+
}
|
|
225
|
+
/** The tokens of one attribute and the SDK's reading of its value with each token's place. */
|
|
226
|
+
interface AttributeMedia {
|
|
227
|
+
attribute: string;
|
|
228
|
+
uses: MediaUse[];
|
|
229
|
+
media: MediaValue;
|
|
230
|
+
/** The files of `media` at each use's pointer. */
|
|
231
|
+
files: Map<string, InlineFilePart>;
|
|
232
|
+
}
|
|
233
|
+
/** The tokens a span carries: where each takes a file's place, and how many stay as they are
|
|
234
|
+
* (inside longer text, deeper than Hue reads, or in a place Hue reads no file from). */
|
|
235
|
+
export interface SpanMedia {
|
|
236
|
+
attributes: AttributeMedia[];
|
|
237
|
+
kept: number;
|
|
238
|
+
}
|
|
239
|
+
/** The tokens of a span's media attributes. */
|
|
240
|
+
export declare function spanMedia(span: ImportSpan): SpanMedia;
|
|
241
|
+
/** A token's file once its bytes were read and uploaded under the span's trace. */
|
|
242
|
+
export interface UploadedMedia {
|
|
243
|
+
sha256: string;
|
|
244
|
+
size: number;
|
|
245
|
+
/** The media type Hue stored the file under. */
|
|
246
|
+
contentType: string;
|
|
247
|
+
}
|
|
248
|
+
/** What became of one token's file. */
|
|
249
|
+
export type MediaOutcome = {
|
|
250
|
+
kind: "uploaded";
|
|
251
|
+
file: UploadedMedia;
|
|
252
|
+
}
|
|
253
|
+
/** Langfuse holds no bytes for it, or more than an import reads: it stays a token. */
|
|
254
|
+
| {
|
|
255
|
+
kind: "kept";
|
|
256
|
+
}
|
|
257
|
+
/** Its bytes could not be read or stored now: the span is not sent, so a later run can. */
|
|
258
|
+
| {
|
|
259
|
+
kind: "failed";
|
|
260
|
+
reason: string;
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* The span's attributes with each token whose file was uploaded in the digest form the SDK
|
|
264
|
+
* writes for a recorded file, `{"type":"blob","mime_type","modality","sha256","size"}` for a
|
|
265
|
+
* whole string or the provider part with its bytes removed and `sha256` and `size` set, every
|
|
266
|
+
* other byte of the attribute's text kept, and each file listed under `hue.blobs` as
|
|
267
|
+
* `<attribute>#<pointer>`. At most {@link MAX_BLOB_ENTRIES} files per span are placed; the rest
|
|
268
|
+
* stay tokens. Returns how many were placed.
|
|
269
|
+
*/
|
|
270
|
+
export declare function placeMedia(span: ImportSpan, media: SpanMedia, outcomes: ReadonlyMap<MediaUse, UploadedMedia>): number;
|
|
271
|
+
/** The uses of a span that may be uploaded: the first {@link MAX_BLOB_ENTRIES} with a key Hue
|
|
272
|
+
* accepts. */
|
|
273
|
+
export declare function uploadableUses(media: SpanMedia): {
|
|
274
|
+
uses: MediaUse[];
|
|
275
|
+
kept: number;
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* Reads each file of a trace's tokens from Langfuse once and uploads it under the trace with the
|
|
279
|
+
* SDK's upload route (reservation, presigned PUT, completion); another reference to the same
|
|
280
|
+
* file in the trace reuses the outcome. Bytes are held only while their file uploads.
|
|
281
|
+
*/
|
|
282
|
+
export declare class TraceMedia {
|
|
283
|
+
private readonly api;
|
|
284
|
+
private readonly uploader;
|
|
285
|
+
private readonly traceId;
|
|
286
|
+
private readonly upload;
|
|
287
|
+
private readonly outcomes;
|
|
288
|
+
constructor(api: LangfuseApi, uploader: BlobUploader, traceId: string, upload: () => UploadContext);
|
|
289
|
+
/** The outcome for a token's file, read and uploaded on its first use in the trace. */
|
|
290
|
+
file(use: MediaUse): Promise<MediaOutcome>;
|
|
291
|
+
private fresh;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* The rest of a span's content stored as the SDK's exporter stores it, but before the span is
|
|
295
|
+
* sent: files inline in the attributes Hue reads files from (a `data:` URL or base64 a provider
|
|
296
|
+
* part holds) uploaded and placed in their digest form, and text values still over Hue's inline
|
|
297
|
+
* limit (1 MiB of UTF-8, the limit Hue advertises) uploaded whole and replaced by their first
|
|
298
|
+
* 16 KiB, each listed in `hue.blobs` (and a whole value in `hue.truncated`). This is the
|
|
299
|
+
* exporter's own placement (`offloadAttributes`), run here so that a file or value that cannot be
|
|
300
|
+
* stored now holds its span back rather than being kept inline or cut, and every run sends the
|
|
301
|
+
* same span. A file or value over a limit (Hue's size limit, the span's 64 entries) is placed
|
|
302
|
+
* alike on every run and only counted. Resolves to the reason the span is held back, if it is.
|
|
303
|
+
*/
|
|
304
|
+
export declare function storeSpanContent(span: ImportSpan, uploader: BlobUploader, upload: UploadContext): Promise<{
|
|
305
|
+
heldBack?: string;
|
|
306
|
+
stored: number;
|
|
307
|
+
overLimits: number;
|
|
308
|
+
}>;
|
|
309
|
+
export {};
|