@hue-run/sdk 0.5.1 → 0.7.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/CLI.md +17 -14
- package/ENVIRONMENTS.md +1 -1
- package/EVALUATIONS.md +19 -0
- package/README.md +35 -4
- package/dist/cli/eval.js +11 -8
- package/dist/cli/login.js +1 -1
- package/dist/client.js +3 -2
- package/dist/config.d.ts +3 -1
- package/dist/config.js +8 -1
- package/dist/evals/client.d.ts +98 -1
- package/dist/evals/client.js +195 -0
- package/dist/evals/types.d.ts +77 -0
- package/dist/live-spans.d.ts +25 -0
- package/dist/live-spans.js +115 -0
- package/dist/transport.d.ts +17 -0
- package/dist/transport.js +390 -61
- package/dist/types.d.ts +7 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/evals/client.js
CHANGED
|
@@ -12,6 +12,85 @@ export class HueApiError extends Error {
|
|
|
12
12
|
this.name = "HueApiError";
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
const registryFieldAliases = [
|
|
16
|
+
["datasetId", "evalSetId"],
|
|
17
|
+
["datasetVersionId", "evalSetVersionId"],
|
|
18
|
+
["scorerId", "evaluatorId"],
|
|
19
|
+
["scorerVersionId", "evaluatorVersionId"],
|
|
20
|
+
];
|
|
21
|
+
const registryEnvelopes = new Set(["items", "item", "versions", "version"]);
|
|
22
|
+
// Only Hue response envelopes are traversed. Case inputs, metadata, and evaluator
|
|
23
|
+
// definitions are customer JSON and must retain their original field names.
|
|
24
|
+
function productRegistryFields(value) {
|
|
25
|
+
if (Array.isArray(value))
|
|
26
|
+
return value.map((item) => productRegistryFields(item));
|
|
27
|
+
if (value === null || typeof value !== "object")
|
|
28
|
+
return value;
|
|
29
|
+
const result = { ...value };
|
|
30
|
+
for (const key of registryEnvelopes) {
|
|
31
|
+
if (Object.hasOwn(result, key))
|
|
32
|
+
result[key] = productRegistryFields(result[key]);
|
|
33
|
+
}
|
|
34
|
+
for (const [legacy, product] of registryFieldAliases) {
|
|
35
|
+
if (Object.hasOwn(result, legacy)) {
|
|
36
|
+
if (Object.hasOwn(result, product) && result[legacy] !== result[product])
|
|
37
|
+
throw new HueApiError();
|
|
38
|
+
result[product] = result[legacy];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
const runResponseAliases = [
|
|
44
|
+
["datasetId", "evalSetId"],
|
|
45
|
+
["datasetName", "evalSetName"],
|
|
46
|
+
["datasetDisplayName", "evalSetDisplayName"],
|
|
47
|
+
["datasetVersion", "evalSetVersion"],
|
|
48
|
+
["datasetVersionId", "evalSetVersionId"],
|
|
49
|
+
["datasetVersionIds", "evalSetVersionIds"],
|
|
50
|
+
["scorerId", "evaluatorId"],
|
|
51
|
+
["scorerName", "evaluatorName"],
|
|
52
|
+
["scorerVersion", "evaluatorVersion"],
|
|
53
|
+
["scorerVersionId", "evaluatorVersionId"],
|
|
54
|
+
["scorerVersionIds", "evaluatorVersionIds"],
|
|
55
|
+
["scorerVersions", "evaluatorVersions"],
|
|
56
|
+
["evaluationRunId", "scoringId"],
|
|
57
|
+
];
|
|
58
|
+
const runResponseEnvelopes = new Set([
|
|
59
|
+
"items",
|
|
60
|
+
"item",
|
|
61
|
+
"versions",
|
|
62
|
+
"version",
|
|
63
|
+
"scorerVersions",
|
|
64
|
+
"evaluatorVersions",
|
|
65
|
+
]);
|
|
66
|
+
function productRunFields(value, kind) {
|
|
67
|
+
if (Array.isArray(value))
|
|
68
|
+
return value.map((item) => productRunFields(item, kind));
|
|
69
|
+
if (value === null || typeof value !== "object")
|
|
70
|
+
return value;
|
|
71
|
+
const result = { ...value };
|
|
72
|
+
for (const key of runResponseEnvelopes) {
|
|
73
|
+
if (Object.hasOwn(result, key))
|
|
74
|
+
result[key] = productRunFields(result[key], kind);
|
|
75
|
+
}
|
|
76
|
+
for (const key of ["evaluation", "scoring"]) {
|
|
77
|
+
if (Object.hasOwn(result, key))
|
|
78
|
+
result[key] = productRunFields(result[key], "scoring");
|
|
79
|
+
}
|
|
80
|
+
const identityAlias = kind === "result" ? ["runId", "scoringId"] : ["experimentId", "runId"];
|
|
81
|
+
const aliases = [...runResponseAliases, identityAlias];
|
|
82
|
+
for (const [legacy, product] of aliases) {
|
|
83
|
+
if (Object.hasOwn(result, legacy)) {
|
|
84
|
+
if (Object.hasOwn(result, product) &&
|
|
85
|
+
JSON.stringify(result[legacy]) !== JSON.stringify(result[product]))
|
|
86
|
+
throw new HueApiError();
|
|
87
|
+
result[product] = result[legacy];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (Object.hasOwn(result, "evaluation") && !Object.hasOwn(result, "scoring"))
|
|
91
|
+
result.scoring = result.evaluation;
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
15
94
|
/**
|
|
16
95
|
* Typed client for Hue's evaluation REST API: datasets, scorers, experiments, executions, runs,
|
|
17
96
|
* results and hosted judge jobs. No implicit mutation retry: callers retain stable idempotency keys
|
|
@@ -225,6 +304,58 @@ export class EvaluationClient {
|
|
|
225
304
|
getScorerVersion(id) {
|
|
226
305
|
return this.request("GET", `/scorer-versions/${uuid(id)}`);
|
|
227
306
|
}
|
|
307
|
+
/** Creates an eval set using the existing v1 registry path. */
|
|
308
|
+
async createEvalSet(input) {
|
|
309
|
+
return productRegistryFields(await this.createDataset(input));
|
|
310
|
+
}
|
|
311
|
+
/** Reads an eval set and its versions. */
|
|
312
|
+
async getEvalSet(id) {
|
|
313
|
+
return productRegistryFields(await this.getDataset(id));
|
|
314
|
+
}
|
|
315
|
+
/** Lists eval sets. */
|
|
316
|
+
async listEvalSets(page) {
|
|
317
|
+
return productRegistryFields(await this.listDatasets(page));
|
|
318
|
+
}
|
|
319
|
+
/** Creates a draft eval set version, optionally copying cases from another version. */
|
|
320
|
+
async createEvalSetVersion(id, input = {}) {
|
|
321
|
+
return productRegistryFields(await this.createDatasetVersion(id, input));
|
|
322
|
+
}
|
|
323
|
+
/** Reads an eval set version. */
|
|
324
|
+
async getEvalSetVersion(id) {
|
|
325
|
+
return productRegistryFields(await this.getDatasetVersion(id));
|
|
326
|
+
}
|
|
327
|
+
/** Lists cases in an eval set version. */
|
|
328
|
+
async listEvalSetCases(id, page) {
|
|
329
|
+
return productRegistryFields(await this.listCases(id, page));
|
|
330
|
+
}
|
|
331
|
+
/** Adds a case to a draft eval set version at its expected revision. */
|
|
332
|
+
async addEvalSetCase(id, input) {
|
|
333
|
+
return productRegistryFields(await this.addCase(id, input));
|
|
334
|
+
}
|
|
335
|
+
/** Freezes a draft eval set version at its expected revision. */
|
|
336
|
+
async freezeEvalSetVersion(id, expectedRevision) {
|
|
337
|
+
return productRegistryFields(await this.freezeDatasetVersion(id, expectedRevision));
|
|
338
|
+
}
|
|
339
|
+
/** Creates an evaluator identity. */
|
|
340
|
+
async createEvaluator(input) {
|
|
341
|
+
return productRegistryFields(await this.createScorer(input));
|
|
342
|
+
}
|
|
343
|
+
/** Reads an evaluator and its published versions. */
|
|
344
|
+
async getEvaluator(id) {
|
|
345
|
+
return productRegistryFields(await this.getScorer(id));
|
|
346
|
+
}
|
|
347
|
+
/** Lists evaluators. */
|
|
348
|
+
async listEvaluators(page) {
|
|
349
|
+
return productRegistryFields(await this.listScorers(page));
|
|
350
|
+
}
|
|
351
|
+
/** Publishes an immutable evaluator version. */
|
|
352
|
+
async publishEvaluatorVersion(id, definition) {
|
|
353
|
+
return productRegistryFields(await this.publishScorerVersion(id, definition));
|
|
354
|
+
}
|
|
355
|
+
/** Reads a published evaluator version. */
|
|
356
|
+
async getEvaluatorVersion(id) {
|
|
357
|
+
return productRegistryFields(await this.getScorerVersion(id));
|
|
358
|
+
}
|
|
228
359
|
/** Creates an experiment over a frozen dataset version with pinned scorer versions and a configuration. */
|
|
229
360
|
createExperiment(input) {
|
|
230
361
|
return this.request("POST", "/experiments", input);
|
|
@@ -376,6 +507,70 @@ export class EvaluationClient {
|
|
|
376
507
|
getResult(id) {
|
|
377
508
|
return this.request("GET", `/evaluation-results/${uuid(id)}`);
|
|
378
509
|
}
|
|
510
|
+
/** Creates a run using product request fields on the existing v1 path. */
|
|
511
|
+
async createRun(input) {
|
|
512
|
+
return productRunFields(await this.request("POST", "/experiments", input), "run");
|
|
513
|
+
}
|
|
514
|
+
/** Reads a run with its scoring progress. */
|
|
515
|
+
async getRun(id) {
|
|
516
|
+
return productRunFields(await this.getExperiment(id), "run");
|
|
517
|
+
}
|
|
518
|
+
/** Lists a run's cases with their latest executions. */
|
|
519
|
+
async listRunItems(id, page) {
|
|
520
|
+
return productRunFields(await this.listExperimentItems(id, page), "run");
|
|
521
|
+
}
|
|
522
|
+
/** Reads one frozen case of a run. */
|
|
523
|
+
async getRunCase(id, caseId) {
|
|
524
|
+
return productRunFields(await this.getExperimentCase(id, caseId), "run");
|
|
525
|
+
}
|
|
526
|
+
/** Starts or replays a target execution for a run case. */
|
|
527
|
+
async startRunExecution(id, caseId, input) {
|
|
528
|
+
return productRunFields(await this.startExecution(id, caseId, input), "run");
|
|
529
|
+
}
|
|
530
|
+
/** Reads one run execution. */
|
|
531
|
+
async getRunExecution(id) {
|
|
532
|
+
return productRunFields(await this.getExecution(id), "run");
|
|
533
|
+
}
|
|
534
|
+
/** Saves a run execution's outcome. */
|
|
535
|
+
async completeRunExecution(id, input) {
|
|
536
|
+
return productRunFields(await this.completeExecution(id, input), "run");
|
|
537
|
+
}
|
|
538
|
+
/** Marks a run finished. */
|
|
539
|
+
async finishRun(id, idempotencyKey) {
|
|
540
|
+
return productRunFields(await this.finishExperiment(id, idempotencyKey), "run");
|
|
541
|
+
}
|
|
542
|
+
/** Creates a standalone scoring pass over saved subjects. */
|
|
543
|
+
async createScoring(input) {
|
|
544
|
+
return productRunFields(await this.request("POST", "/evaluation-runs", input), "scoring");
|
|
545
|
+
}
|
|
546
|
+
/** Reads a scoring pass and its pinned evaluators. */
|
|
547
|
+
async getScoring(id) {
|
|
548
|
+
return productRunFields(await this.getEvaluationRun(id), "scoring");
|
|
549
|
+
}
|
|
550
|
+
/** Lists scoring passes in the project. */
|
|
551
|
+
async listScorings(page) {
|
|
552
|
+
return productRunFields(await this.listEvaluationRuns(page), "scoring");
|
|
553
|
+
}
|
|
554
|
+
/** Lists the subjects of a scoring pass. */
|
|
555
|
+
async listScoringItems(id, page) {
|
|
556
|
+
return productRunFields(await this.listEvaluationItems(id, page), "scoring");
|
|
557
|
+
}
|
|
558
|
+
/** Reads a saved subject with product-named source fields. */
|
|
559
|
+
async getScoringSubject(id) {
|
|
560
|
+
return productRunFields(await this.getSubject(id), "run");
|
|
561
|
+
}
|
|
562
|
+
/** Uploads evaluator results for a scoring pass using product request fields. */
|
|
563
|
+
async submitScoringResults(id, input) {
|
|
564
|
+
return productRunFields(await this.request("POST", `/evaluation-runs/${uuid(id)}/results`, input), "result");
|
|
565
|
+
}
|
|
566
|
+
/** Lists result summaries for a scoring pass. */
|
|
567
|
+
async listScoringResults(id, page) {
|
|
568
|
+
return productRunFields(await this.listResults(id, page), "result");
|
|
569
|
+
}
|
|
570
|
+
/** Reads a stored evaluator result. */
|
|
571
|
+
async getScoringResult(id) {
|
|
572
|
+
return productRunFields(await this.getResult(id), "result");
|
|
573
|
+
}
|
|
379
574
|
/** Dispatches hosted judge jobs for `llm_judge` pins; check {@link getJudgeBudget} first. */
|
|
380
575
|
createJudgeJobs(id, input) {
|
|
381
576
|
return this.request("POST", `/evaluation-runs/${uuid(id)}/judge-jobs`, input);
|
package/dist/evals/types.d.ts
CHANGED
|
@@ -76,6 +76,21 @@ export interface DatasetCase {
|
|
|
76
76
|
/** Immutable input-file manifest identity, when files are attached. */
|
|
77
77
|
artifactManifestId?: string | null;
|
|
78
78
|
}
|
|
79
|
+
/** An eval set and its versions. Both field names remain available during v1 compatibility. */
|
|
80
|
+
export type EvalSet = Omit<Dataset, "versions"> & {
|
|
81
|
+
/** Versions of this eval set. */
|
|
82
|
+
versions: EvalSetVersion[];
|
|
83
|
+
};
|
|
84
|
+
/** A version of an eval set, with its product field name. */
|
|
85
|
+
export type EvalSetVersion = DatasetVersion & {
|
|
86
|
+
/** Owning eval set ID. */
|
|
87
|
+
evalSetId: string;
|
|
88
|
+
};
|
|
89
|
+
/** A stored case in an eval set version. */
|
|
90
|
+
export type EvalSetCase = DatasetCase & {
|
|
91
|
+
/** Eval set version containing this case. */
|
|
92
|
+
evalSetVersionId: string;
|
|
93
|
+
};
|
|
79
94
|
/** One pinned input file of a case or subject, as recorded in Hue's immutable manifest. */
|
|
80
95
|
export interface CaseFile {
|
|
81
96
|
/** Hue artifact identity of the pinned bytes. */
|
|
@@ -346,6 +361,16 @@ export interface ScorerVersion {
|
|
|
346
361
|
/** The pinned definition. */
|
|
347
362
|
definition: ScorerDefinition;
|
|
348
363
|
}
|
|
364
|
+
/** An evaluator and its published versions. */
|
|
365
|
+
export type Evaluator = Omit<Scorer, "versions"> & {
|
|
366
|
+
/** Published versions, when included in the response. */
|
|
367
|
+
versions?: EvaluatorVersion[];
|
|
368
|
+
};
|
|
369
|
+
/** An immutable published evaluator definition. */
|
|
370
|
+
export type EvaluatorVersion = ScorerVersion & {
|
|
371
|
+
/** Owning evaluator ID when the server supplies it; older v1 responses may omit it. */
|
|
372
|
+
evaluatorId?: string;
|
|
373
|
+
};
|
|
349
374
|
/** Final state of a target execution. */
|
|
350
375
|
export type TerminalState = "succeeded" | "error" | "cancelled";
|
|
351
376
|
/** One attempt to run the target for a case. */
|
|
@@ -439,6 +464,30 @@ export interface Experiment {
|
|
|
439
464
|
cancelled: number;
|
|
440
465
|
};
|
|
441
466
|
}
|
|
467
|
+
/** A run over a frozen eval set version and configuration. */
|
|
468
|
+
export type Run = Experiment & {
|
|
469
|
+
/** Frozen eval set version under test. */
|
|
470
|
+
evalSetVersionId: string;
|
|
471
|
+
/** Scoring pass created for this run. */
|
|
472
|
+
scoring: Scoring;
|
|
473
|
+
};
|
|
474
|
+
/** One frozen case in a run. */
|
|
475
|
+
export type RunCase = ExperimentCase & {
|
|
476
|
+
/** Source eval set version ID. */
|
|
477
|
+
evalSetVersionId: string;
|
|
478
|
+
};
|
|
479
|
+
/** A scoring pass over saved subjects with pinned evaluators. */
|
|
480
|
+
export type Scoring = EvaluationRun & {
|
|
481
|
+
/** Evaluator versions pinned to this scoring pass. */
|
|
482
|
+
evaluatorVersions: EvaluatorVersion[];
|
|
483
|
+
/** Linked run ID, or null for standalone scoring. */
|
|
484
|
+
runId?: string | null;
|
|
485
|
+
};
|
|
486
|
+
/** One row in the project's scoring list. */
|
|
487
|
+
export type ScoringSummary = EvaluationRunSummary & {
|
|
488
|
+
/** Linked run ID, or null for standalone scoring. */
|
|
489
|
+
runId: string | null;
|
|
490
|
+
};
|
|
442
491
|
/** A sanitized error type with an optional bounded message. */
|
|
443
492
|
export interface TypedError {
|
|
444
493
|
/** Stable error type. */
|
|
@@ -549,6 +598,13 @@ export interface Subject {
|
|
|
549
598
|
/** The target's declared primary generated artifact, or `null`. */
|
|
550
599
|
primaryArtifactId?: string | null;
|
|
551
600
|
}
|
|
601
|
+
/** An immutable saved subject with product-named source fields. */
|
|
602
|
+
export type ScoringSubject = Subject & {
|
|
603
|
+
/** Source eval set version ID. */
|
|
604
|
+
evalSetVersionId: string;
|
|
605
|
+
/** Source run ID. */
|
|
606
|
+
runId: string;
|
|
607
|
+
};
|
|
552
608
|
/** A reported metric value. */
|
|
553
609
|
export interface Metric {
|
|
554
610
|
/** Declared metric name. */
|
|
@@ -588,6 +644,27 @@ export type Result = Score & {
|
|
|
588
644
|
/** Source digest of the local scorer, for `local_code` pins. */
|
|
589
645
|
sourceDigest?: string;
|
|
590
646
|
};
|
|
647
|
+
/** A score uploaded through the product-named scoring method. */
|
|
648
|
+
export type ScoringResultInput = Score & {
|
|
649
|
+
/** Scoring item this result belongs to. */
|
|
650
|
+
evaluationItemId: string;
|
|
651
|
+
/** Evaluator version that produced the score. */
|
|
652
|
+
evaluatorVersionId: string;
|
|
653
|
+
/** Source digest for a local code evaluator. */
|
|
654
|
+
sourceDigest?: string;
|
|
655
|
+
};
|
|
656
|
+
/** A result as listed by {@link EvaluationClient.listScoringResults}. */
|
|
657
|
+
export type ScoringResultSummary = ResultSummary & {
|
|
658
|
+
/** Evaluator version that produced this result. */
|
|
659
|
+
evaluatorVersionId: string;
|
|
660
|
+
};
|
|
661
|
+
/** A full stored result from {@link EvaluationClient.getScoringResult}. */
|
|
662
|
+
export type StoredScoringResult = StoredResult & {
|
|
663
|
+
/** Scoring pass containing this result. */
|
|
664
|
+
scoringId: string;
|
|
665
|
+
/** Evaluator version that produced this result. */
|
|
666
|
+
evaluatorVersionId: string;
|
|
667
|
+
};
|
|
591
668
|
/** What a local scorer callback receives. */
|
|
592
669
|
export interface ScoreContext {
|
|
593
670
|
/** Case inputs. */
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Attributes } from "@opentelemetry/api";
|
|
2
|
+
import { type ReadableSpan } from "@opentelemetry/sdk-trace";
|
|
3
|
+
/**
|
|
4
|
+
* A finished span never carries the placeholder markers, whatever an application set: Hue would
|
|
5
|
+
* read such a span as a malformed placeholder and reject it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function withoutPlaceholderMarkers(attributes: Attributes): Attributes;
|
|
8
|
+
/** Open spans tracked for announcement at once; later starts are not announced. */
|
|
9
|
+
export declare const MAX_LIVE_SPANS = 1024;
|
|
10
|
+
export declare const LIVE_SPAN_INTERVAL_MILLIS = 500;
|
|
11
|
+
/**
|
|
12
|
+
* Response header on every trace acknowledgement from a Hue that accepts placeholders. Without
|
|
13
|
+
* it the receiver predates them and rejects each one by its zero end time.
|
|
14
|
+
*/
|
|
15
|
+
export declare const PLACEHOLDERS_HEADER = "hue-pending-spans";
|
|
16
|
+
/** Hue's own spans and recognizable AI spans, judged from what is known when the span starts. */
|
|
17
|
+
export declare function announcesLiveSpan(span: ReadableSpan): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* A normal OTLP span announcing `span` while it runs: a new identity whose parent is the real
|
|
20
|
+
* span, the real start time and an end time of 0. The markers are added after redaction.
|
|
21
|
+
*/
|
|
22
|
+
export declare function pendingPlaceholder(span: ReadableSpan): {
|
|
23
|
+
record: ReadableSpan;
|
|
24
|
+
markers: Attributes;
|
|
25
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { SpanStatusCode } from "@opentelemetry/api";
|
|
2
|
+
import { RandomIdGenerator } from "@opentelemetry/sdk-trace";
|
|
3
|
+
import { HUE_SCOPE } from "./config.js";
|
|
4
|
+
/** Marks a placeholder for a span that has started but not ended. The value versions the shape. */
|
|
5
|
+
const PENDING_SPAN_TYPE_KEY = "hue.span_type";
|
|
6
|
+
const PENDING_SPAN_TYPE = "pending_span";
|
|
7
|
+
/** The real span's own parent. Absent when the real span is a root. */
|
|
8
|
+
const PENDING_PARENT_KEY = "hue.pending_parent_id";
|
|
9
|
+
const markerKeys = [PENDING_SPAN_TYPE_KEY, PENDING_PARENT_KEY];
|
|
10
|
+
/**
|
|
11
|
+
* A finished span never carries the placeholder markers, whatever an application set: Hue would
|
|
12
|
+
* read such a span as a malformed placeholder and reject it.
|
|
13
|
+
*/
|
|
14
|
+
export function withoutPlaceholderMarkers(attributes) {
|
|
15
|
+
if (!markerKeys.some((key) => Object.hasOwn(attributes, key)))
|
|
16
|
+
return attributes;
|
|
17
|
+
const kept = { ...attributes };
|
|
18
|
+
for (const key of markerKeys)
|
|
19
|
+
delete kept[key];
|
|
20
|
+
return kept;
|
|
21
|
+
}
|
|
22
|
+
/** Open spans tracked for announcement at once; later starts are not announced. */
|
|
23
|
+
export const MAX_LIVE_SPANS = 1024;
|
|
24
|
+
export const LIVE_SPAN_INTERVAL_MILLIS = 500;
|
|
25
|
+
/**
|
|
26
|
+
* Response header on every trace acknowledgement from a Hue that accepts placeholders. Without
|
|
27
|
+
* it the receiver predates them and rejects each one by its zero end time.
|
|
28
|
+
*/
|
|
29
|
+
export const PLACEHOLDERS_HEADER = "hue-pending-spans";
|
|
30
|
+
const MAX_PLACEHOLDER_VALUE_BYTES = 64 * 1024;
|
|
31
|
+
// Kept narrow on purpose: common application processors export only these spans, and a
|
|
32
|
+
// placeholder whose real span is filtered later would read as running until the trace stalls.
|
|
33
|
+
const livePrefixes = ["gen_ai.", "ai.", "llm.", "traceloop."];
|
|
34
|
+
// Definitions and instructions are large and rarely useful while a span runs; the real span
|
|
35
|
+
// still carries them. Copied markers would misplace the placeholder.
|
|
36
|
+
const omittedKeys = [
|
|
37
|
+
"gen_ai.tool.definitions",
|
|
38
|
+
"gen_ai.system_instructions",
|
|
39
|
+
PENDING_SPAN_TYPE_KEY,
|
|
40
|
+
PENDING_PARENT_KEY,
|
|
41
|
+
];
|
|
42
|
+
const ids = new RandomIdGenerator();
|
|
43
|
+
/** Hue's own spans and recognizable AI spans, judged from what is known when the span starts. */
|
|
44
|
+
export function announcesLiveSpan(span) {
|
|
45
|
+
if (span.instrumentationScope.name === HUE_SCOPE || span.name.startsWith("ai."))
|
|
46
|
+
return true;
|
|
47
|
+
for (const key in span.attributes)
|
|
48
|
+
if (livePrefixes.some((prefix) => key.startsWith(prefix)))
|
|
49
|
+
return true;
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
function valueBytes(value) {
|
|
53
|
+
if (typeof value === "string")
|
|
54
|
+
return Buffer.byteLength(value);
|
|
55
|
+
if (!Array.isArray(value))
|
|
56
|
+
return 8;
|
|
57
|
+
let bytes = 0;
|
|
58
|
+
for (const item of value)
|
|
59
|
+
bytes += typeof item === "string" ? Buffer.byteLength(item) : 8;
|
|
60
|
+
return bytes;
|
|
61
|
+
}
|
|
62
|
+
function placeholderAttributes(source) {
|
|
63
|
+
const attributes = {};
|
|
64
|
+
for (const key in source) {
|
|
65
|
+
if (!Object.hasOwn(source, key))
|
|
66
|
+
continue;
|
|
67
|
+
if (omittedKeys.some((omitted) => key === omitted || key.startsWith(`${omitted}.`)))
|
|
68
|
+
continue;
|
|
69
|
+
const value = source[key];
|
|
70
|
+
if (valueBytes(value) <= MAX_PLACEHOLDER_VALUE_BYTES)
|
|
71
|
+
attributes[key] = value;
|
|
72
|
+
}
|
|
73
|
+
return attributes;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A normal OTLP span announcing `span` while it runs: a new identity whose parent is the real
|
|
77
|
+
* span, the real start time and an end time of 0. The markers are added after redaction.
|
|
78
|
+
*/
|
|
79
|
+
export function pendingPlaceholder(span) {
|
|
80
|
+
const real = span.spanContext();
|
|
81
|
+
const own = {
|
|
82
|
+
traceId: real.traceId,
|
|
83
|
+
spanId: ids.generateSpanId(),
|
|
84
|
+
traceFlags: real.traceFlags,
|
|
85
|
+
...(real.traceState ? { traceState: real.traceState } : {}),
|
|
86
|
+
};
|
|
87
|
+
const parent = span.parentSpanContext?.spanId;
|
|
88
|
+
const record = {
|
|
89
|
+
name: span.name,
|
|
90
|
+
kind: span.kind,
|
|
91
|
+
spanContext: () => own,
|
|
92
|
+
// Hue re-keys a placeholder as its real span, so the flags describe the real span's parent.
|
|
93
|
+
parentSpanContext: { ...real, isRemote: span.parentSpanContext?.isRemote === true },
|
|
94
|
+
startTime: span.startTime,
|
|
95
|
+
endTime: [0, 0],
|
|
96
|
+
duration: [0, 0],
|
|
97
|
+
ended: true,
|
|
98
|
+
status: { code: SpanStatusCode.UNSET },
|
|
99
|
+
attributes: placeholderAttributes(span.attributes),
|
|
100
|
+
links: [],
|
|
101
|
+
events: [],
|
|
102
|
+
resource: span.resource,
|
|
103
|
+
instrumentationScope: span.instrumentationScope,
|
|
104
|
+
droppedAttributesCount: 0,
|
|
105
|
+
droppedEventsCount: 0,
|
|
106
|
+
droppedLinksCount: 0,
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
record,
|
|
110
|
+
markers: {
|
|
111
|
+
[PENDING_SPAN_TYPE_KEY]: PENDING_SPAN_TYPE,
|
|
112
|
+
...(parent ? { [PENDING_PARENT_KEY]: parent } : {}),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
package/dist/transport.d.ts
CHANGED
|
@@ -50,8 +50,25 @@ export declare class HueTransport {
|
|
|
50
50
|
private closed;
|
|
51
51
|
private shutdownPromise?;
|
|
52
52
|
private flushPromise?;
|
|
53
|
+
private live;
|
|
54
|
+
private liveTimer?;
|
|
55
|
+
private liveSpans;
|
|
56
|
+
private placeholdersRejected;
|
|
57
|
+
private placeholders;
|
|
58
|
+
private placeholderSources;
|
|
59
|
+
private batchSpans?;
|
|
53
60
|
constructor(options: HueOptions);
|
|
61
|
+
/**
|
|
62
|
+
* Advisory records (placeholders) are admitted only while the queue is under a quarter of its
|
|
63
|
+
* record and byte budgets, so they never take more than a quarter from real records. They are
|
|
64
|
+
* skipped silently.
|
|
65
|
+
*/
|
|
54
66
|
private enqueue;
|
|
67
|
+
private track;
|
|
68
|
+
/** Placeholders are built lazily, so input set right after a span starts is included. */
|
|
69
|
+
private announceLiveSpans;
|
|
70
|
+
private stopLiveTimer;
|
|
71
|
+
private stopLiveSpans;
|
|
55
72
|
/** Cumulative counters and current queue gauges. */
|
|
56
73
|
getReport(): ExportReport;
|
|
57
74
|
/** Copies of the latest 128 sanitized issues, oldest first. */
|