@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
package/CLI.md
CHANGED
|
@@ -42,7 +42,7 @@ project manifest are refused because managers can update ancestor locks; Python
|
|
|
42
42
|
The generated `hue.setup.mjs` or `hue_setup.py` always selects `captureContent: false` /
|
|
43
43
|
`capture_content=False`. For a supported application, setup installs the dependency and adds the
|
|
44
44
|
managed import and middleware registration to the existing entrypoint; an unreferenced helper is
|
|
45
|
-
not a completed integration. TypeScript uses `@hue-run/sdk@0.18.
|
|
45
|
+
not a completed integration. TypeScript uses `@hue-run/sdk@0.18.1`, `@opentelemetry/api@1.9.1` and
|
|
46
46
|
`@opentelemetry/context-async-hooks@2.11.0`; Python setup uses its separately tested package pin.
|
|
47
47
|
Content capture requires an ordinary account-managed key and a later explicit application decision.
|
|
48
48
|
|
|
@@ -114,6 +114,7 @@ hue setup --origin http://127.0.0.1:PORT # isolated loopback tests only
|
|
|
114
114
|
hue login # validate keys created in Hue and store them in ./.env.hue
|
|
115
115
|
hue mcp install --client claude-code # write the Hue MCP configuration for a coding agent
|
|
116
116
|
hue listen --subscription <id> --forward-to http://localhost:3000/slack/events # deliver events to a local bot
|
|
117
|
+
hue import langfuse --from 2026-01-01 --dry-run # count a Langfuse project's traces, then import them
|
|
117
118
|
```
|
|
118
119
|
|
|
119
120
|
Hosted setup accepts HTTPS origins only. HTTP is accepted solely for `localhost`, `127.0.0.1` and
|
|
@@ -365,6 +366,127 @@ codes: `0` stopped, `1` Hue refused the credential, the subscription (unknown, r
|
|
|
365
366
|
delivers to a request URL) or the pull itself (a redirect, another refusal or an answer without
|
|
366
367
|
deliveries), `2` usage error, `130` interrupted twice.
|
|
367
368
|
|
|
369
|
+
## Import traces from Langfuse
|
|
370
|
+
|
|
371
|
+
`hue import langfuse` copies a Langfuse project's traces into Hue, for a team moving from Langfuse.
|
|
372
|
+
It runs on your machine: it reads Langfuse's public API with the project's own keys and sends the
|
|
373
|
+
spans with your Hue project key through the SDK's exporter, so the Langfuse keys go only to Langfuse
|
|
374
|
+
and Hue never receives or stores them. The command is part of the `hue` executable and runs with
|
|
375
|
+
`npx` whatever language the application is written in; the Python SDK has no counterpart.
|
|
376
|
+
|
|
377
|
+
```sh
|
|
378
|
+
# .env.langfuse, kept out of version control:
|
|
379
|
+
# LANGFUSE_PUBLIC_KEY=pk-lf-...
|
|
380
|
+
# LANGFUSE_SECRET_KEY=sk-lf-...
|
|
381
|
+
# LANGFUSE_BASE_URL=https://cloud.langfuse.com
|
|
382
|
+
npx -y @hue-run/sdk@0.18.1 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
|
|
383
|
+
npx -y @hue-run/sdk@0.18.1 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
`LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` come from the environment or `--env-path`
|
|
387
|
+
(`--env-file` also works when the file exists), loaded first; a variable already set keeps its
|
|
388
|
+
value. `--langfuse-public-key` replaces the public key on the command line, but the secret key is
|
|
389
|
+
never taken from it, where other local processes and shell history can read it. The host is
|
|
390
|
+
`--langfuse-host`, then `LANGFUSE_BASE_URL`, then `LANGFUSE_HOST`, then `https://cloud.langfuse.com`:
|
|
391
|
+
use your region's host or your self-hosted URL, over HTTPS (plain HTTP only on this machine). A
|
|
392
|
+
request carrying the keys follows no redirect. `HUE_API_KEY` is the Hue project key the spans are
|
|
393
|
+
sent with, from the environment, the env file, or `./.env.hue` (where `hue login` stores it) when
|
|
394
|
+
neither sets it (a dry run, which needs no Hue key, reads no `./.env.hue` beside a named file); `--origin` (default `HUE_BASE_URL`, then `https://app.hue.run`) is checked with
|
|
395
|
+
`GET /api/v1/projects/current` before Langfuse is read. Keys are never printed.
|
|
396
|
+
|
|
397
|
+
**What is read.** The command first asks for `GET /api/public/v2/observations`, which Langfuse Cloud
|
|
398
|
+
and self-hosted Langfuse v4 answer. The traces are then the root observations
|
|
399
|
+
(`isRootObservation=true`) that started in the window, listed newest first in cursor pages, and each
|
|
400
|
+
trace's observations are read 100 a page with the field groups
|
|
401
|
+
`core,basic,time,io,metadata,model,usage,prompt,trace_context`; a page whose metadata values Langfuse
|
|
402
|
+
cut at 200 characters is read again with those keys expanded. A deployment that answers `404` there
|
|
403
|
+
(self-hosted Langfuse v3) is read through `GET /api/public/traces` (`fromTimestamp`, `toTimestamp`,
|
|
404
|
+
`orderBy=timestamp.desc`, `fields=core,io`) and `GET /api/public/observations?traceId=…`, in numbered
|
|
405
|
+
pages. An answer over 32 MiB, or a `413`, is asked for again in smaller pages.
|
|
406
|
+
|
|
407
|
+
**Selection.** `--from` and `--to` take an ISO 8601 date (midnight UTC) or a date and time with a time
|
|
408
|
+
zone. A trace is selected by when it started, its root observation's start time or a v1 trace's
|
|
409
|
+
timestamp, `--from` inclusive and `--to` exclusive. Without `--to` the window ends an hour before the
|
|
410
|
+
command starts, so a trace still running, or still arriving in Langfuse, is left for a later run
|
|
411
|
+
rather than sent half recorded. `--limit N` imports the N
|
|
412
|
+
newest traces; `--trace-id ID` (repeatable, up to 1,000) imports those traces only and is not combined
|
|
413
|
+
with the window or the limit.
|
|
414
|
+
|
|
415
|
+
**What Hue receives.** Each observation becomes a completed span as a Langfuse OpenTelemetry SDK
|
|
416
|
+
records it: its name, start, end (an observation without an end lasts no time) and parent, with
|
|
417
|
+
`langfuse.observation.type` (lower case), `langfuse.observation.input` and `.output` (a string as
|
|
418
|
+
recorded, any other value as its JSON), one `langfuse.observation.metadata.<key>` per metadata key,
|
|
419
|
+
`.level`, `.status_message`, `.model.name`, `.model.parameters`, `.usage_details`, `.cost_details`,
|
|
420
|
+
`.completion_start_time`, `.prompt.name` and `.prompt.version`, and the trace's
|
|
421
|
+
`langfuse.trace.name`, `langfuse.trace.tags`, `langfuse.user.id`, `langfuse.session.id`,
|
|
422
|
+
`langfuse.release`, `langfuse.version` and `langfuse.environment`. The model is also
|
|
423
|
+
`gen_ai.request.model`, and Langfuse's `input` and `output` usage counts are
|
|
424
|
+
`gen_ai.usage.input_tokens` and `gen_ai.usage.output_tokens`. An observation at level `ERROR` is an
|
|
425
|
+
error span with its status message. A v1 trace is also a span of its own, named as the trace and
|
|
426
|
+
carrying `langfuse.trace.input`, `.output`, `.metadata.<key>`, `.tags` and `.public`; its observations
|
|
427
|
+
without a parent are its children, and it lasts from the earliest of its timestamp and their starts
|
|
428
|
+
to their last end. Spans carry `service.name` `langfuse` (`--service-name` sets another) and the
|
|
429
|
+
instrumentation scope `@hue-run/sdk/import/langfuse`.
|
|
430
|
+
|
|
431
|
+
**Ids and resuming.** A Langfuse id that already is an OpenTelemetry id (32 lowercase hex digits for
|
|
432
|
+
a trace, 16 for an observation, as Langfuse's OpenTelemetry SDKs write them) is kept; any other id,
|
|
433
|
+
such as an older SDK's UUID, becomes one derived from it with SHA-256. The original ids are kept as
|
|
434
|
+
`langfuse.trace.id` and `langfuse.observation.id`. Running the same command again sends the same
|
|
435
|
+
spans, and Hue keeps one copy of an exact replay of a completed span, so an interrupted or partly
|
|
436
|
+
failed import is resumed by running it again. Hue refuses a different payload for a span it already
|
|
437
|
+
holds: keep `--media`, `--no-content` and `--service-name` the same when resuming, and expect a span
|
|
438
|
+
whose Langfuse data changed after it was imported to be refused. For the same reason the import
|
|
439
|
+
sends nothing that depends on the run: a trace's root never carries `hue.sdk.dropped_records`, and
|
|
440
|
+
files and large values are stored before their span is sent. Leave out of the window any traces that
|
|
441
|
+
already reached Hue through OpenTelemetry.
|
|
442
|
+
|
|
443
|
+
**Export and content.** Spans go through the SDK's own exporter, as `createHue` sends them: batched,
|
|
444
|
+
compressed, retried and within the limits Hue advertises. Before a span is sent, its content is
|
|
445
|
+
stored as that exporter stores it, with the exporter's own placement: a file inline in an attribute
|
|
446
|
+
Hue reads files from (a `data:` URL, or the bytes of a provider part, that Langfuse kept in place) is
|
|
447
|
+
uploaded and placed in its digest form, and a text value still over Hue's inline limit (1 MiB) is
|
|
448
|
+
uploaded whole and replaced by its first 16 KiB, each listed in `hue.blobs` (a whole value also in
|
|
449
|
+
`hue.truncated`). Doing this first means a file or value that cannot be stored right now holds its
|
|
450
|
+
span back for the next run instead of being kept inline or cut; one over a limit (Hue's size limit,
|
|
451
|
+
or a span's 64 entries) is placed the same way on every run and counted in the summary. Content is
|
|
452
|
+
captured, the SDK's default; `--no-content` sends metadata only, as `captureContent: false` does (no
|
|
453
|
+
inputs, outputs, status messages or model parameters; metadata, model names and usage stay, and
|
|
454
|
+
nothing is uploaded). The import waits for its queue to be sent every 1,024 spans or 32 MiB, so its
|
|
455
|
+
memory stays bounded whatever the project's size.
|
|
456
|
+
|
|
457
|
+
**Media.** Langfuse's SDKs replace a file in an input, output or metadata value with a reference,
|
|
458
|
+
`@@@langfuseMedia:type=<media type>|id=<id>|source=<source>@@@`. Without `--media`, references are
|
|
459
|
+
sent as recorded, and Hue shows them as files held in Langfuse. With `--media`, each reference where
|
|
460
|
+
Hue reads a file (a whole string, or the content of a provider part such as an OpenAI `image_url` or
|
|
461
|
+
`input_audio`, an Anthropic `source` or a GenAI `blob`, no deeper than ten levels) is looked up with
|
|
462
|
+
`GET /api/public/media/{id}`. The file is downloaded from the link Langfuse answers (following its
|
|
463
|
+
store's redirects, without the Langfuse keys), uploaded under the span's trace through Hue's upload route (reservation, presigned
|
|
464
|
+
`PUT`, completion), and placed in the digest form the SDK writes for media it records: a whole string
|
|
465
|
+
becomes `{"type":"blob","mime_type":…,"modality":…,"sha256":…,"size":…}` and a provider part loses its
|
|
466
|
+
bytes and gains `sha256` and `size`, every other byte of the value's text kept, each file listed in
|
|
467
|
+
`hue.blobs` under `<attribute>#<JSON pointer>`. Each file is downloaded once per trace. A reference
|
|
468
|
+
stays as recorded when Langfuse holds no bytes for it, its file is over 25 MiB, it sits inside longer
|
|
469
|
+
text, or its span already lists 64 files. A file that cannot be downloaded or stored right now holds
|
|
470
|
+
its span back: the trace is reported, and the next run sends it.
|
|
471
|
+
|
|
472
|
+
**Rate limits and failures.** A `429` from Langfuse is waited out for as long as its `Retry-After`
|
|
473
|
+
asks, up to 15 minutes and ten times in a row; connection failures, timeouts and `5xx` answers are
|
|
474
|
+
retried after 1, 2, 4, 8 and 16 seconds. Langfuse Cloud limits each organization's requests per
|
|
475
|
+
minute by plan, so a large project imports at that pace: a request per page of traces and per page
|
|
476
|
+
of each trace's observations, and one per media file. A refusal of the Langfuse keys (`401` or `403`)
|
|
477
|
+
or of `HUE_API_KEY` stops the import before anything is sent; a trace Langfuse cannot answer for is
|
|
478
|
+
reported and the import goes on.
|
|
479
|
+
|
|
480
|
+
**Output and exit codes.** `--dry-run` reads and counts traces, spans and media references, needs no
|
|
481
|
+
Hue key and sends nothing. Without `--yes` the command asks for confirmation in a terminal (declining
|
|
482
|
+
exits `1`) and refuses to start without one. Progress is printed every 100 traces or 10 seconds, then
|
|
483
|
+
a summary of the traces imported, the spans sent and accepted, and the media files uploaded and left
|
|
484
|
+
in Langfuse; each trace not fully imported is named with the reason. Ctrl+C or `SIGTERM` stops
|
|
485
|
+
reading, sends what was read and exits `130`; a second Ctrl+C a second or more later exits `130` at
|
|
486
|
+
once, abandoning exports still in flight for the next run to send. Exit codes: `0` every selected
|
|
487
|
+
trace was imported, `1` a trace, span or file was not (or a key was refused), `2` usage error, `130`
|
|
488
|
+
stopped.
|
|
489
|
+
|
|
368
490
|
## Local state and conflicts
|
|
369
491
|
|
|
370
492
|
Before its first network write, setup creates a lowercase UUIDv4 and a 32-byte random installation
|
|
@@ -469,7 +591,7 @@ node packages/sdk-typescript/scripts/verify-package.mjs --artifacts-dir .artifac
|
|
|
469
591
|
# Set project to an existing supported fixture; use the same directory on resume.
|
|
470
592
|
project=/absolute/path/to/supported-fixture
|
|
471
593
|
node packages/sdk-typescript/scripts/verify-setup-live.mjs \
|
|
472
|
-
--archive .artifacts/typescript/hue-run-sdk-0.18.
|
|
594
|
+
--archive .artifacts/typescript/hue-run-sdk-0.18.1.tgz \
|
|
473
595
|
--origin https://STAGING_ORIGIN \
|
|
474
596
|
--project "$project" --command setup \
|
|
475
597
|
--evidence .context/setup-staging-before-claim.json
|
|
@@ -480,7 +602,7 @@ the private local handoff and finish the real browser claim, then reconcile the
|
|
|
480
602
|
|
|
481
603
|
```sh
|
|
482
604
|
node packages/sdk-typescript/scripts/verify-setup-live.mjs \
|
|
483
|
-
--archive .artifacts/typescript/hue-run-sdk-0.18.
|
|
605
|
+
--archive .artifacts/typescript/hue-run-sdk-0.18.1.tgz \
|
|
484
606
|
--origin https://STAGING_ORIGIN \
|
|
485
607
|
--project "$project" --command claim \
|
|
486
608
|
--evidence .context/setup-staging-after-claim.json
|
|
@@ -522,10 +644,10 @@ otherwise. Never hand the agent Hue-native tools (`context.tools`, `context.mcp`
|
|
|
522
644
|
production agent lacks. Pin the SDK, so the agent's repository gains no dependency:
|
|
523
645
|
|
|
524
646
|
```sh
|
|
525
|
-
npx -y @hue-run/sdk@0.18.
|
|
526
|
-
npx -y @hue-run/sdk@0.18.
|
|
527
|
-
npx -y @hue-run/sdk@0.18.
|
|
528
|
-
npx -y @hue-run/sdk@0.18.
|
|
647
|
+
npx -y @hue-run/sdk@0.18.1 eval --case "Refund an eligible charge" --command "node agent.js"
|
|
648
|
+
npx -y @hue-run/sdk@0.18.1 eval --case https://app.hue.run/projects/demo/scenarios/<id> --command "python agent.py" --timeout 120 --baseline <run id>
|
|
649
|
+
npx -y @hue-run/sdk@0.18.1 eval --set "Billing regressions" --scorer-version <id> --command "node agent.js" --save-version
|
|
650
|
+
npx -y @hue-run/sdk@0.18.1 eval --worker --command "node agent.js" --agent-key support-agent --revision <new-agent-revision>
|
|
529
651
|
```
|
|
530
652
|
|
|
531
653
|
One invocation creates one run holding every selected case; it prints the run URL
|
|
@@ -563,7 +685,7 @@ export default function runMyAgent(inputs: JsonValue, context: SimulationTargetC
|
|
|
563
685
|
```
|
|
564
686
|
|
|
565
687
|
```sh
|
|
566
|
-
npx -y @hue-run/sdk@0.18.
|
|
688
|
+
npx -y @hue-run/sdk@0.18.1 eval --case "Refund an eligible charge" ./hue-agent.ts --content
|
|
567
689
|
```
|
|
568
690
|
|
|
569
691
|
`--scenario` remains an alias for `--case` for existing scripts. Pass one selection flag.
|
package/EVALUATIONS.md
CHANGED
|
@@ -317,10 +317,13 @@ An uploaded file needs one of `outputContentTypes`, a well-formed name of 1–25
|
|
|
317
317
|
(as JavaScript counts a string's `length`) without path separators or control characters, and 1
|
|
318
318
|
byte to 25 MiB. Every file, and the case itself as it will be sent (its revision, key, values and
|
|
319
319
|
size), is checked before the first upload; a file outside that policy, a `path` that cannot be
|
|
320
|
-
read, or a case Hue would refuse throws `TypeError` or `RangeError` without a request.
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
320
|
+
read, or a case Hue would refuse throws `TypeError` or `RangeError` without a request. A `path`
|
|
321
|
+
is hashed a slice at a time when it is checked and read again when it is uploaded, one file at a
|
|
322
|
+
time, so the write holds at most one file in memory and sends only the bytes its check hashed. A
|
|
323
|
+
file that changed in between, or can no longer be read, sends nothing: the write throws `TypeError`
|
|
324
|
+
and the case is not written. The reservation key names the version, the case's
|
|
325
|
+
`externalKey`, the role, the bytes, the name and the content type, so sending the same case again
|
|
326
|
+
reuses its artifacts and a corrected name or type reserves a new one; a reference to the artifact a file uploads as pins it once. `source`, `attached_template`, `attached_reference` and `original` files reach
|
|
324
327
|
the target as `context.files`; `org_template` and `evaluator_reference` files reach only scorers.
|
|
325
328
|
|
|
326
329
|
Recorded messages copied into a case's inputs keep their large files as digest parts. Pin each file
|
package/README.md
CHANGED
|
@@ -274,7 +274,8 @@ Each uploaded file is listed in the record's `hue.blobs`, keyed `<attribute>#<JS
|
|
|
274
274
|
through the string, which no JSON pointer can address, so Hue matches it by its digest.
|
|
275
275
|
- Text media over 64 KiB of decoded bytes in a provider part keeps a UTF-8-safe 16 KiB prefix in
|
|
276
276
|
its content field instead, and its key names that field
|
|
277
|
-
(`gen_ai.input.messages#/0/parts/1/content`). Smaller text and empty fields stay inline.
|
|
277
|
+
(`gen_ai.input.messages#/0/parts/1/content`). Smaller text and empty fields stay inline. A text
|
|
278
|
+
file a GenAI `blob`, Vertex `media` or AI SDK part holds as bare base64 takes the digest form.
|
|
278
279
|
|
|
279
280
|
Only the file's own value, or the members of the part that held it, change: every other byte of the
|
|
280
281
|
recorded text, its key order, number spellings, escapes and spacing included, is kept, in an encoded
|
|
@@ -286,9 +287,11 @@ Entries use the recorded media type and, when valid, the filename, name or title
|
|
|
286
287
|
is nonblank after trimming, at most 256 UTF-16 code units and well-formed Unicode, with no C0
|
|
287
288
|
characters (U+0000–U+001F) or DEL (U+007F). Invalid names are omitted from the entry without
|
|
288
289
|
rewriting the original field. Data URLs and fields explicitly encoded as base64 by the provider
|
|
289
|
-
contract are decoded before hashing. Other
|
|
290
|
-
type
|
|
291
|
-
`
|
|
290
|
+
contract are decoded before hashing. Other content is read as Hue's reader reads it, whatever its
|
|
291
|
+
media type: content that reads as base64 (a length divisible by four, base64 characters only, as
|
|
292
|
+
the GenAI conventions and `client.media.resolve` write a `blob` or `media` part's content) is the
|
|
293
|
+
file's base64 and is decoded, so `YWJj` hashes as `abc`; anything else is the file's own UTF-8
|
|
294
|
+
text. The digest matches `hue.recordFile`'s digest for the same bytes.
|
|
292
295
|
|
|
293
296
|
A file that is not uploaded (the upload failed, the receiver has no upload route, or a budget ran
|
|
294
297
|
out) keeps its digest without an entry in the recorded messages (`gen_ai.input.messages`,
|
|
@@ -362,7 +365,11 @@ With `captureContent: false` or a setup credential no bytes leave the process: a
|
|
|
362
365
|
attribute still records the part, without its name, and content values are not recorded at all. A
|
|
363
366
|
part the redactor removes or rewrites is not uploaded. A part that cannot be uploaded keeps its
|
|
364
367
|
digest and is counted in `report.uploadFallbacks`, as is one nested more than 10 levels deep in its
|
|
365
|
-
value, where Hue does not read parts, or one in a value cut to the value limit.
|
|
368
|
+
value, where Hue does not read parts, or one in a value cut to the value limit. An attachment in a
|
|
369
|
+
value that is still over the value limit with its files as digest parts is uploaded and listed as
|
|
370
|
+
usual, and `client.media.fetch({ traceId, sha256 })` and Hue's span read reach it by digest; the
|
|
371
|
+
value itself uploads whole and keeps only its first 16 KiB inline, so readers do not place the file
|
|
372
|
+
at its position in the value. Creating an
|
|
366
373
|
attachment never throws: one without exactly one of `data` and `path`, with unreadable or larger
|
|
367
374
|
data or an invalid content type is recorded as `null` and counted as an instrumentation failure;
|
|
368
375
|
an invalid name alone is omitted and counted.
|
|
@@ -480,7 +487,37 @@ the recorded model call again with the real image, document or audio. See
|
|
|
480
487
|
| `LangfuseMedia` | `Attachment` | [`Attachment`](#attach-a-file-anywhere), at the same position |
|
|
481
488
|
| `resolveMediaReferences({ obj, resolveWith: "base64DataUri" })` | | `client.media.resolve(obj, { traceId })` |
|
|
482
489
|
| `GET /api/public/media/{mediaId}` | `ReadonlyAttachment.data()` | `client.media.fetch({ traceId, sha256 })` |
|
|
483
|
-
| Media tokens (`@@@langfuseMedia:…@@@`) | `ExternalAttachment` | Left as recorded: Hue never fetches a remote URL or a file another service holds |
|
|
490
|
+
| Media tokens (`@@@langfuseMedia:…@@@`) | `ExternalAttachment` | Left as recorded: Hue never fetches a remote URL or a file another service holds. [`hue import langfuse --media`](#import-from-langfuse) copies Langfuse's files with your keys |
|
|
491
|
+
|
|
492
|
+
## Import from Langfuse
|
|
493
|
+
|
|
494
|
+
`hue import langfuse` copies a Langfuse project's history into Hue. It runs on your machine with
|
|
495
|
+
the Langfuse project's own keys (`LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, and
|
|
496
|
+
`LANGFUSE_BASE_URL` or `LANGFUSE_HOST`), which go only to Langfuse, and sends the spans with your
|
|
497
|
+
`HUE_API_KEY`:
|
|
498
|
+
|
|
499
|
+
```sh
|
|
500
|
+
npx -y @hue-run/sdk@0.18.1 import langfuse --env-path .env.langfuse --from 2026-01-01 --dry-run
|
|
501
|
+
npx -y @hue-run/sdk@0.18.1 import langfuse --env-path .env.langfuse --from 2026-01-01 --media
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
- It reads Langfuse Cloud and self-hosted v4 through the Observations API v2, and self-hosted v3
|
|
505
|
+
through the v1 traces and observations API, newest first; `--from`, `--to`, `--limit` and
|
|
506
|
+
`--trace-id` select traces, and `--dry-run` counts them without sending anything.
|
|
507
|
+
- Each observation becomes the span a Langfuse OpenTelemetry SDK records, with the
|
|
508
|
+
`langfuse.observation.*` and `langfuse.trace.*` attributes Hue reads, the user, session, release
|
|
509
|
+
and environment, and the model and token counts as GenAI attributes. Hex ids are kept and other
|
|
510
|
+
ids derived from them, so running the same command again resumes an import without duplicates.
|
|
511
|
+
- Spans are exported by the SDK's transport with its content-capture default; `--no-content`
|
|
512
|
+
sends metadata only.
|
|
513
|
+
- With `--media`, files that Langfuse media references point to are downloaded and uploaded under
|
|
514
|
+
the trace in the [digest form](#automatically-captured-media) the SDK writes, listed in
|
|
515
|
+
`hue.blobs`; a reference Langfuse holds no bytes for stays as recorded.
|
|
516
|
+
- Langfuse's rate limits are waited out for as long as `Retry-After` asks.
|
|
517
|
+
|
|
518
|
+
The command has no Python counterpart; it runs with `npx` for an application in any language. See
|
|
519
|
+
[Import traces from Langfuse](CLI.md#import-traces-from-langfuse) for every option, the attribute
|
|
520
|
+
mapping, media handling, limits and exit codes.
|
|
484
521
|
|
|
485
522
|
## Vercel AI SDK 6
|
|
486
523
|
|
package/dist/blobs.js
CHANGED
|
@@ -626,7 +626,7 @@ export async function offloadRecord(attributes, collector, options) {
|
|
|
626
626
|
const textual = isTextMediaType(contentType);
|
|
627
627
|
if (textual && decoded.bytes.byteLength <= INLINE_FILE_LIMIT)
|
|
628
628
|
return Promise.resolve();
|
|
629
|
-
const prefix = keepsPrefix(file, textual);
|
|
629
|
+
const prefix = keepsPrefix(file, textual, decoded);
|
|
630
630
|
const partKey = `${key}#${prefix ? file.pointer : file.partPointer}`;
|
|
631
631
|
const slot = slots > 0 && isBlobKey(partKey) ? (slots--, true) : false;
|
|
632
632
|
return (async () => {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hue import langfuse`: copies a Langfuse project's traces into Hue from this machine. Langfuse
|
|
3
|
+
* is read with the project's own keys through its public API; each observation becomes the span
|
|
4
|
+
* the Langfuse OpenTelemetry SDKs would have recorded and is exported through the SDK's own
|
|
5
|
+
* transport, so batching, retries, limits, content capture and large-value uploads are the SDK's.
|
|
6
|
+
* Ids are kept or derived from Langfuse's, so a second run sends the same spans again and Hue
|
|
7
|
+
* keeps one copy of each: an interrupted or partly failed import is resumed by running it again.
|
|
8
|
+
*/
|
|
9
|
+
/** Streams, environment and timing seams for {@link runImportCommand}; tests inject these. */
|
|
10
|
+
export interface ImportCommandIo {
|
|
11
|
+
stdout?: NodeJS.WritableStream;
|
|
12
|
+
stderr?: NodeJS.WritableStream;
|
|
13
|
+
stdin?: NodeJS.ReadableStream & {
|
|
14
|
+
isTTY?: boolean;
|
|
15
|
+
};
|
|
16
|
+
env?: NodeJS.ProcessEnv;
|
|
17
|
+
cwd?: string;
|
|
18
|
+
fetch?: typeof fetch;
|
|
19
|
+
/** Waits `ms`, resolving early when the import is stopped. */
|
|
20
|
+
sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
21
|
+
/** Registers the stop request (SIGINT and SIGTERM by default) and returns its removal. */
|
|
22
|
+
onInterrupt?: (listener: () => void) => () => void;
|
|
23
|
+
/** The current time, for the default end of the window. */
|
|
24
|
+
now?: () => Date;
|
|
25
|
+
/** Ends the process after a second stop request (`process.exit` by default), so exports still
|
|
26
|
+
* in flight do not hold it until their requests time out. */
|
|
27
|
+
exit?: (code: number) => void;
|
|
28
|
+
}
|
|
29
|
+
export declare const IMPORT_USAGE = "Usage: hue import langfuse [options]\n\nCopy traces from a Langfuse project into Hue. The import runs on this machine with your own\nLangfuse keys, which are sent only to Langfuse. Each observation becomes a span with the\nlangfuse.* attributes Hue reads; ids are kept, so running the same command again resumes an\nimport without duplicating what was already sent.\n\nLangfuse (keys from the environment or --env-path; the secret key never from the command line):\n LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY The Langfuse project's API keys\n LANGFUSE_BASE_URL or LANGFUSE_HOST Langfuse host (default https://cloud.langfuse.com)\n --langfuse-host URL Langfuse host, instead of the variables\n --langfuse-public-key KEY Public key, instead of LANGFUSE_PUBLIC_KEY\n\nHue:\n HUE_API_KEY The Hue project key the traces are sent with (environment, --env-path\n or ./.env.hue); not needed with --dry-run\n --origin URL Hue origin (default HUE_BASE_URL, then https://app.hue.run)\n --service-name NAME service.name of the imported spans (default langfuse)\n\nSelection (newest first):\n --from TIME Traces that started at or after TIME: an ISO 8601 date (midnight UTC)\n or a date and time with a zone, such as 2026-01-01T09:00:00Z\n --to TIME Traces that started before TIME (default: an hour before the command\n starts, so traces still running are left for a later run)\n --limit N At most N traces\n --trace-id ID This Langfuse trace (repeatable); not with --from, --to or --limit\n\nContent:\n --media Download the files Langfuse media references (@@@langfuseMedia:\u2026@@@)\n point to and upload them to Hue; without it, references stay as text\n --no-content Send metadata only: no inputs, outputs, status messages or model\n parameters (content is sent by default, as the SDK records it)\n\nRun:\n --dry-run Read Langfuse and count what would be imported; send nothing to Hue\n --yes Import without asking for confirmation (required without a terminal)\n --env-path PATH Load a dotenv file first (--env-file also works when it exists);\n ./.env.hue loads when HUE_API_KEY is not set. Variables already set win\n -h, --help Show this help\n\nExit codes: 0 every trace was imported, 1 a trace, span or file was not, 2 usage error,\n130 stopped (run the same command again to resume).";
|
|
30
|
+
/** An instant of the window: an ISO 8601 date (midnight UTC) or a date and time with a zone. */
|
|
31
|
+
export declare function parseInstant(option: string, value: string): string;
|
|
32
|
+
/** The Langfuse host as a base URL without a trailing slash, or why it is refused. */
|
|
33
|
+
export declare function parseLangfuseHost(value: string): string;
|
|
34
|
+
/** Runs `hue import langfuse`; resolves to the exit code. */
|
|
35
|
+
export declare function runImportCommand(argv: string[], io?: ImportCommandIo): Promise<number>;
|