@littlebigbrain/client 0.4.3 → 0.5.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/README.md CHANGED
@@ -54,6 +54,17 @@ feedback, and model runs in little big brain:
54
54
  5. Grade cited results, reconnect to durable trainer jobs, and require a
55
55
  held-out quality plus latency gate before promotion.
56
56
 
57
+ `suggestionShown`, `suggestionAdopted`, `externalPlannerTrace`, and
58
+ `askFeedback` use generated versioned payload types and idempotency keys. Their
59
+ acknowledgements expose stable receipt/event identity, replay state, and why an
60
+ event is or is not trainable.
61
+
62
+ For an LLM query planner, call `lbb.context.suggest(...)` to fill grounded
63
+ schema/value prefixes, then `lbb.context.resolve(...)` to snap free-text guesses
64
+ onto real vocabulary. `resolve` uses managed embeddings when configured. Record
65
+ adopted suggestions and accepted/rejected/corrected plans so a smaller planner
66
+ and suggest ranker can be trained on the product's actual workload.
67
+
57
68
  The [enterprise-search integration guide](https://docs.littlebigbrain.com/guides/enterprise-search/)
58
69
  contains the graph model, migration sequence, and acceptance tests.
59
70
 
package/dist/client.d.ts CHANGED
@@ -7,6 +7,15 @@ export { LbbError } from "./transport.js";
7
7
  export type { CallOptions, Query, QueryValue, RequestOptions, } from "./transport.js";
8
8
  export type { EntityListOptions, HybridSearchOptions } from "./namespaces.js";
9
9
  export { ContextNamespace, EntityNamespace, FactsNamespace, GraphNamespace, IndexNamespace, OntologyNamespace, QueryNamespace, SchemaNamespace, SearchNamespace, } from "./namespaces.js";
10
+ export interface IndexLineageObservation {
11
+ metadata: Schemas["GraphMetadataResponse"];
12
+ lineage: Schemas["IndexLineage"];
13
+ buildCommit?: string;
14
+ replica?: string;
15
+ requestId?: string;
16
+ attempts: number;
17
+ elapsedMs: number;
18
+ }
10
19
  /**
11
20
  * A typed HTTP client for a little big brain graph server. One instance is scoped to a
12
21
  * single graph/branch; construct another for a different scope. All methods
@@ -133,10 +142,21 @@ export declare class LbbClient {
133
142
  }): Promise<unknown>;
134
143
  embeddingConfig(): Promise<Schemas["ManagedEmbeddingConfigResponse"]>;
135
144
  setEmbeddingConfig(body: Schemas["ManagedEmbeddingConfigRequest"]): Promise<Schemas["ManagedEmbeddingConfigResponse"]>;
145
+ submitEmbeddingBackfill(opts?: {
146
+ batchSize?: number;
147
+ limit?: number;
148
+ full?: boolean;
149
+ idempotencyKey?: string;
150
+ }): Promise<Schemas["ManagedEmbeddingBackfillJobStatusResponse"]>;
151
+ embeddingBackfillJob(jobId: string): Promise<Schemas["ManagedEmbeddingBackfillJobStatusResponse"]>;
152
+ cancelEmbeddingBackfill(jobId: string): Promise<Schemas["ManagedEmbeddingBackfillJobStatusResponse"]>;
136
153
  backfillEmbeddings(opts?: {
137
154
  batchSize?: number;
138
155
  limit?: number;
139
156
  full?: boolean;
157
+ idempotencyKey?: string;
158
+ timeoutMs?: number;
159
+ pollIntervalMs?: number;
140
160
  }): Promise<Schemas["ManagedEmbeddingBackfillResponse"]>;
141
161
  promoteEmbedding(opts: {
142
162
  runId: string;
@@ -224,7 +244,21 @@ export declare class LbbClient {
224
244
  * explicit feedback capture. `accepted: false` in the response means
225
245
  * signal capture is off on this deployment (the contract is identical).
226
246
  */
227
- askFeedback(body: Schemas["AskFeedbackRequest"]): Promise<Schemas["AskFeedbackResponse"]>;
247
+ askFeedback(body: Schemas["AskFeedbackRequest"], opts?: {
248
+ idempotencyKey?: string;
249
+ }): Promise<Schemas["AskFeedbackResponse"]>;
250
+ ingestSignals(body: Schemas["SignalIngestRequest"], opts?: {
251
+ idempotencyKey?: string;
252
+ }): Promise<Schemas["SignalIngestResponse"]>;
253
+ suggestionShown(payload: Schemas["SuggestionShownV1"], opts?: {
254
+ idempotencyKey?: string;
255
+ }): Promise<Schemas["SignalIngestResponse"]>;
256
+ suggestionAdopted(payload: Schemas["SuggestionAdoptedV1"], opts?: {
257
+ idempotencyKey?: string;
258
+ }): Promise<Schemas["SignalIngestResponse"]>;
259
+ externalPlannerTrace(payload: Schemas["ExternalPlannerTraceV1"], opts?: {
260
+ idempotencyKey?: string;
261
+ }): Promise<Schemas["SignalIngestResponse"]>;
228
262
  /**
229
263
  * The planner fine-tune's training feed: accepted/corrected feedback
230
264
  * joined to its traces (signals ≤ the split pin), topped up with
@@ -521,6 +555,10 @@ export declare class LbbClient {
521
555
  status(): Promise<unknown>;
522
556
  /** Graph footprint, WAL tail, and index coverage. */
523
557
  metadata(): Promise<Schemas["GraphMetadataResponse"]>;
558
+ waitForIndexLineage(targetSeq: number, opts?: {
559
+ timeoutMs?: number;
560
+ pollIntervalMs?: number;
561
+ }): Promise<IndexLineageObservation>;
524
562
  /** Graph counts and type/relation buckets. */
525
563
  summary(): Promise<Schemas["GraphSummaryResponse"]>;
526
564
  /** List the graphs (and branches) under the scoped tenant. */
package/dist/client.js CHANGED
@@ -395,11 +395,40 @@ export class LbbClient {
395
395
  setEmbeddingConfig(body) {
396
396
  return this.request("POST", "/v1/graph/embedding", { body });
397
397
  }
398
- backfillEmbeddings(opts = {}) {
399
- return this.request("POST", "/v1/graph/embedding/backfill", {
400
- query: { batch_size: opts.batchSize, limit: opts.limit, full: opts.full },
398
+ submitEmbeddingBackfill(opts = {}) {
399
+ return this.request("POST", "/v1/graph/embedding/backfill-jobs", {
400
+ body: {
401
+ batch_size: opts.batchSize,
402
+ limit: opts.limit,
403
+ full: opts.full ?? false,
404
+ },
405
+ idempotencyKey: opts.idempotencyKey ?? this.idempotencyKey("embedding-backfill"),
406
+ });
407
+ }
408
+ embeddingBackfillJob(jobId) {
409
+ return this.request("GET", "/v1/graph/embedding/backfill-jobs", {
410
+ query: { job_id: jobId },
411
+ });
412
+ }
413
+ cancelEmbeddingBackfill(jobId) {
414
+ return this.request("DELETE", "/v1/graph/embedding/backfill-jobs", {
415
+ query: { job_id: jobId },
401
416
  });
402
417
  }
418
+ async backfillEmbeddings(opts = {}) {
419
+ let status = await this.submitEmbeddingBackfill(opts);
420
+ const deadline = Date.now() + (opts.timeoutMs ?? 30 * 60_000);
421
+ while (status.status === "pending" || status.status === "running") {
422
+ if (Date.now() >= deadline)
423
+ throw new Error(`embedding backfill ${status.job_id} did not finish before timeout`);
424
+ await sleep(opts.pollIntervalMs ?? 2_000);
425
+ status = await this.embeddingBackfillJob(status.job_id);
426
+ }
427
+ if (status.status !== "succeeded" || status.result == null)
428
+ throw new Error(status.terminal_error ??
429
+ `embedding backfill ${status.job_id} ended ${status.status}`);
430
+ return status.result;
431
+ }
403
432
  promoteEmbedding(opts) {
404
433
  return this.request("POST", "/v1/graph/embedding/promote", {
405
434
  query: { run_id: opts.runId, allow_regression: opts.allowRegression },
@@ -506,8 +535,35 @@ export class LbbClient {
506
535
  * explicit feedback capture. `accepted: false` in the response means
507
536
  * signal capture is off on this deployment (the contract is identical).
508
537
  */
509
- askFeedback(body) {
510
- return this.request("POST", "/v1/ask/feedback", { body });
538
+ askFeedback(body, opts = {}) {
539
+ return this.request("POST", "/v1/ask/feedback", {
540
+ body,
541
+ idempotencyKey: opts.idempotencyKey ?? this.idempotencyKey("ask-feedback"),
542
+ });
543
+ }
544
+ ingestSignals(body, opts = {}) {
545
+ return this.request("POST", "/v1/signals", {
546
+ body,
547
+ idempotencyKey: opts.idempotencyKey ?? this.idempotencyKey("signals"),
548
+ });
549
+ }
550
+ suggestionShown(payload, opts = {}) {
551
+ return this.ingestSignals({ signals: [{ kind: "suggestion_shown", payload }] }, opts);
552
+ }
553
+ suggestionAdopted(payload, opts = {}) {
554
+ return this.ingestSignals({ signals: [{ kind: "suggestion_adopted", payload }] }, opts);
555
+ }
556
+ externalPlannerTrace(payload, opts = {}) {
557
+ return this.ingestSignals({
558
+ signals: [
559
+ {
560
+ kind: "external_planner_trace",
561
+ request_id: payload.ask_id,
562
+ snapshot_token: payload.snapshot_token,
563
+ payload,
564
+ },
565
+ ],
566
+ }, opts);
511
567
  }
512
568
  /**
513
569
  * The planner fine-tune's training feed: accepted/corrected feedback
@@ -923,6 +979,37 @@ export class LbbClient {
923
979
  metadata() {
924
980
  return this.request("GET", "/v1/graph/metadata");
925
981
  }
982
+ async waitForIndexLineage(targetSeq, opts = {}) {
983
+ const deadline = Date.now() + (opts.timeoutMs ?? 30_000);
984
+ let last;
985
+ while (true) {
986
+ last = await this.rawRequest("GET", "/v1/graph/metadata");
987
+ const lineage = last.data.index_lineage;
988
+ if (lineage != null &&
989
+ lineage.bm25_indexed_commit_seq != null &&
990
+ lineage.bm25_indexed_commit_seq >= targetSeq &&
991
+ lineage.ann_indexed_commit_seq != null &&
992
+ lineage.ann_indexed_commit_seq >= targetSeq &&
993
+ lineage.adjacency_indexed_commit_seq != null &&
994
+ lineage.adjacency_indexed_commit_seq >= targetSeq) {
995
+ return {
996
+ metadata: last.data,
997
+ lineage,
998
+ buildCommit: last.headers?.get("lbb-build-commit") ?? undefined,
999
+ replica: last.headers?.get("lbb-replica") ?? undefined,
1000
+ requestId: last.requestId,
1001
+ attempts: last.attempts,
1002
+ elapsedMs: last.elapsedMs,
1003
+ };
1004
+ }
1005
+ if (Date.now() >= deadline) {
1006
+ const build = last.headers?.get("lbb-build-commit") ?? "unknown";
1007
+ const replica = last.headers?.get("lbb-replica") ?? "unknown";
1008
+ throw new Error(`index lineage did not reach ${targetSeq} before timeout (build=${build}, replica=${replica}, last=${JSON.stringify(last.data.index_lineage)})`);
1009
+ }
1010
+ await sleep(opts.pollIntervalMs ?? 250);
1011
+ }
1012
+ }
926
1013
  /** Graph counts and type/relation buckets. */
927
1014
  summary() {
928
1015
  return this.request("GET", "/v1/graph/summary");
@@ -39,7 +39,15 @@ export declare class GraphNamespace {
39
39
  batchSize?: number;
40
40
  limit?: number;
41
41
  full?: boolean;
42
+ pollIntervalMs?: number;
42
43
  }): Promise<Schemas["ManagedEmbeddingBackfillResponse"]>;
44
+ submitEmbeddingBackfill(options?: CallOptions & {
45
+ batchSize?: number;
46
+ limit?: number;
47
+ full?: boolean;
48
+ }): Promise<Schemas["ManagedEmbeddingBackfillJobStatusResponse"]>;
49
+ embeddingBackfillJob(jobId: string): Promise<Schemas["ManagedEmbeddingBackfillJobStatusResponse"]>;
50
+ cancelEmbeddingBackfill(jobId: string): Promise<Schemas["ManagedEmbeddingBackfillJobStatusResponse"]>;
43
51
  promoteEmbedding(options: CallOptions & {
44
52
  runId: string;
45
53
  allowRegression?: boolean;
@@ -53,12 +53,29 @@ export class GraphNamespace {
53
53
  });
54
54
  }
55
55
  backfillEmbeddings(options = {}) {
56
- const { batchSize, limit, full, ...request } = options;
57
- return this.client.request("POST", "/v1/graph/embedding/backfill", {
58
- ...request,
59
- query: { batch_size: batchSize, limit, full },
56
+ return this.client.backfillEmbeddings({
57
+ batchSize: options.batchSize,
58
+ limit: options.limit,
59
+ full: options.full,
60
+ idempotencyKey: options.idempotencyKey,
61
+ timeoutMs: options.timeoutMs,
62
+ pollIntervalMs: options.pollIntervalMs,
63
+ });
64
+ }
65
+ submitEmbeddingBackfill(options = {}) {
66
+ return this.client.submitEmbeddingBackfill({
67
+ batchSize: options.batchSize,
68
+ limit: options.limit,
69
+ full: options.full,
70
+ idempotencyKey: options.idempotencyKey,
60
71
  });
61
72
  }
73
+ embeddingBackfillJob(jobId) {
74
+ return this.client.embeddingBackfillJob(jobId);
75
+ }
76
+ cancelEmbeddingBackfill(jobId) {
77
+ return this.client.cancelEmbeddingBackfill(jobId);
78
+ }
62
79
  promoteEmbedding(options) {
63
80
  const { runId, allowRegression, ...request } = options;
64
81
  return this.client.request("POST", "/v1/graph/embedding/promote", {