@kweaver-ai/kweaver-sdk 0.7.4 → 0.8.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 +20 -0
- package/README.zh.md +18 -0
- package/dist/api/agent-observability.d.ts +51 -0
- package/dist/api/agent-observability.js +108 -0
- package/dist/api/conversations.d.ts +4 -8
- package/dist/api/conversations.js +16 -58
- package/dist/api/datasources.d.ts +2 -20
- package/dist/api/datasources.js +7 -123
- package/dist/api/trace.d.ts +44 -0
- package/dist/api/trace.js +81 -0
- package/dist/api/vega.d.ts +53 -0
- package/dist/api/vega.js +144 -0
- package/dist/cli.js +5 -0
- package/dist/commands/bkn-ops.js +12 -6
- package/dist/commands/bkn-utils.d.ts +9 -0
- package/dist/commands/bkn-utils.js +17 -0
- package/dist/commands/ds.js +7 -2
- package/dist/commands/trace.d.ts +14 -0
- package/dist/commands/trace.js +168 -0
- package/dist/resources/datasources.js +2 -1
- package/dist/trace-core/diagnose/builtin-rules/excessive-tool-calls-per-turn.d.ts +2 -0
- package/dist/trace-core/diagnose/builtin-rules/excessive-tool-calls-per-turn.js +15 -0
- package/dist/trace-core/diagnose/builtin-rules/excessive-tool-calls-per-turn.yaml +16 -0
- package/dist/trace-core/diagnose/builtin-rules/llm-response-truncated-no-continue.d.ts +2 -0
- package/dist/trace-core/diagnose/builtin-rules/llm-response-truncated-no-continue.js +44 -0
- package/dist/trace-core/diagnose/builtin-rules/llm-response-truncated-no-continue.yaml +15 -0
- package/dist/trace-core/diagnose/builtin-rules/register.d.ts +1 -0
- package/dist/trace-core/diagnose/builtin-rules/register.js +11 -0
- package/dist/trace-core/diagnose/builtin-rules/retrieval-empty-no-fallback.d.ts +2 -0
- package/dist/trace-core/diagnose/builtin-rules/retrieval-empty-no-fallback.js +29 -0
- package/dist/trace-core/diagnose/builtin-rules/retrieval-empty-no-fallback.yaml +15 -0
- package/dist/trace-core/diagnose/builtin-rules/tool-error-swallowed.d.ts +2 -0
- package/dist/trace-core/diagnose/builtin-rules/tool-error-swallowed.js +45 -0
- package/dist/trace-core/diagnose/builtin-rules/tool-error-swallowed.yaml +15 -0
- package/dist/trace-core/diagnose/builtin-rules/tool-loop-no-state-change.d.ts +2 -0
- package/dist/trace-core/diagnose/builtin-rules/tool-loop-no-state-change.js +38 -0
- package/dist/trace-core/diagnose/builtin-rules/tool-loop-no-state-change.yaml +16 -0
- package/dist/trace-core/diagnose/index.d.ts +9 -0
- package/dist/trace-core/diagnose/index.js +104 -0
- package/dist/trace-core/diagnose/predicate-registry.d.ts +7 -0
- package/dist/trace-core/diagnose/predicate-registry.js +30 -0
- package/dist/trace-core/diagnose/report-assembler.d.ts +12 -0
- package/dist/trace-core/diagnose/report-assembler.js +90 -0
- package/dist/trace-core/diagnose/rule-loader.d.ts +11 -0
- package/dist/trace-core/diagnose/rule-loader.js +86 -0
- package/dist/trace-core/diagnose/schemas.d.ts +109 -0
- package/dist/trace-core/diagnose/schemas.js +94 -0
- package/dist/trace-core/diagnose/signal-probe.d.ts +5 -0
- package/dist/trace-core/diagnose/signal-probe.js +21 -0
- package/dist/trace-core/diagnose/synthesizer-template.d.ts +2 -0
- package/dist/trace-core/diagnose/synthesizer-template.js +49 -0
- package/dist/trace-core/diagnose/trace-shaper.d.ts +3 -0
- package/dist/trace-core/diagnose/trace-shaper.js +72 -0
- package/dist/trace-core/diagnose/types.d.ts +124 -0
- package/dist/trace-core/diagnose/types.js +1 -0
- package/package.json +14 -4
package/dist/api/vega.d.ts
CHANGED
|
@@ -238,3 +238,56 @@ export interface ListAllVegaResourcesOptions {
|
|
|
238
238
|
/** List all Vega resources (no catalog filter). Uses GET /resources — not /resources/list, which
|
|
239
239
|
* conflicts with GET /resources/{id} on some gateways (path segment "list" is treated as an id). */
|
|
240
240
|
export declare function listAllVegaResources(options: ListAllVegaResourcesOptions): Promise<string>;
|
|
241
|
+
export interface ListTablesWithColumnsOptions {
|
|
242
|
+
baseUrl: string;
|
|
243
|
+
accessToken: string;
|
|
244
|
+
/** A vega catalog id, not a legacy data-connection datasource UUID. */
|
|
245
|
+
id: string;
|
|
246
|
+
keyword?: string;
|
|
247
|
+
limit?: number;
|
|
248
|
+
offset?: number;
|
|
249
|
+
businessDomain?: string;
|
|
250
|
+
autoScan?: boolean;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* List tables with column details from a vega catalog.
|
|
254
|
+
*
|
|
255
|
+
* Two-stage fetch:
|
|
256
|
+
* 1. GET /api/vega-backend/v1/catalogs/{id}/resources?category=table — list summaries
|
|
257
|
+
* 2. For each resource: GET /api/vega-backend/v1/resources/{rid} — pull source_metadata.columns
|
|
258
|
+
*
|
|
259
|
+
* If the catalog has no resources and `autoScan=true`, triggers a discover and
|
|
260
|
+
* retries the list once. The optional `keyword` filters summaries client-side
|
|
261
|
+
* before the per-resource detail fetches — useful to keep N+1 down to k+1.
|
|
262
|
+
*
|
|
263
|
+
* `id` is a vega catalog id.
|
|
264
|
+
*/
|
|
265
|
+
export declare function listTablesWithColumns(options: ListTablesWithColumnsOptions): Promise<string>;
|
|
266
|
+
export interface ScanMetadataOptions {
|
|
267
|
+
baseUrl: string;
|
|
268
|
+
accessToken: string;
|
|
269
|
+
id: string;
|
|
270
|
+
/** Retained for signature compatibility; ignored — vega catalog already knows its connector_type. */
|
|
271
|
+
dsType?: string;
|
|
272
|
+
businessDomain?: string;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Trigger a metadata scan for a vega catalog and wait for completion.
|
|
276
|
+
* `id` is a vega catalog id (e.g. `d7nicrcjto2s73d9g67g`), not a legacy
|
|
277
|
+
* data-connection datasource UUID.
|
|
278
|
+
*/
|
|
279
|
+
export declare function scanMetadata(options: ScanMetadataOptions): Promise<string>;
|
|
280
|
+
export interface ScanDatasourceMetadataOptions {
|
|
281
|
+
baseUrl: string;
|
|
282
|
+
accessToken: string;
|
|
283
|
+
id: string;
|
|
284
|
+
businessDomain?: string;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Trigger a metadata scan and wait for completion. `id` is a vega catalog id.
|
|
288
|
+
*
|
|
289
|
+
* @deprecated Use {@link scanMetadata} directly. This wrapper exists only for
|
|
290
|
+
* backward compatibility with callers that used the old data-connection-based
|
|
291
|
+
* `scanDatasourceMetadata` signature.
|
|
292
|
+
*/
|
|
293
|
+
export declare function scanDatasourceMetadata(options: ScanDatasourceMetadataOptions): Promise<string>;
|
package/dist/api/vega.js
CHANGED
|
@@ -488,3 +488,147 @@ export async function listAllVegaResources(options) {
|
|
|
488
488
|
businessDomain,
|
|
489
489
|
});
|
|
490
490
|
}
|
|
491
|
+
/**
|
|
492
|
+
* List tables with column details from a vega catalog.
|
|
493
|
+
*
|
|
494
|
+
* Two-stage fetch:
|
|
495
|
+
* 1. GET /api/vega-backend/v1/catalogs/{id}/resources?category=table — list summaries
|
|
496
|
+
* 2. For each resource: GET /api/vega-backend/v1/resources/{rid} — pull source_metadata.columns
|
|
497
|
+
*
|
|
498
|
+
* If the catalog has no resources and `autoScan=true`, triggers a discover and
|
|
499
|
+
* retries the list once. The optional `keyword` filters summaries client-side
|
|
500
|
+
* before the per-resource detail fetches — useful to keep N+1 down to k+1.
|
|
501
|
+
*
|
|
502
|
+
* `id` is a vega catalog id.
|
|
503
|
+
*/
|
|
504
|
+
export async function listTablesWithColumns(options) {
|
|
505
|
+
const { baseUrl, accessToken, id, keyword, limit, offset, businessDomain = "bd_public", autoScan = true, } = options;
|
|
506
|
+
async function listResourceSummaries() {
|
|
507
|
+
const body = await listVegaCatalogResources({
|
|
508
|
+
baseUrl,
|
|
509
|
+
accessToken,
|
|
510
|
+
id,
|
|
511
|
+
category: "table",
|
|
512
|
+
limit,
|
|
513
|
+
offset,
|
|
514
|
+
businessDomain,
|
|
515
|
+
});
|
|
516
|
+
const parsed = JSON.parse(body);
|
|
517
|
+
return Array.isArray(parsed) ? parsed : (parsed.entries ?? parsed.data ?? []);
|
|
518
|
+
}
|
|
519
|
+
let summaries = await listResourceSummaries();
|
|
520
|
+
if (summaries.length === 0 && autoScan) {
|
|
521
|
+
await scanMetadata({ baseUrl, accessToken, id, businessDomain });
|
|
522
|
+
summaries = await listResourceSummaries();
|
|
523
|
+
}
|
|
524
|
+
// Keyword filter applied after autoScan guard: if the catalog has tables but
|
|
525
|
+
// keyword matches none, we must NOT trigger a redundant discover.
|
|
526
|
+
if (keyword) {
|
|
527
|
+
const k = keyword.toLowerCase();
|
|
528
|
+
summaries = summaries.filter((it) => it.name.toLowerCase().includes(k));
|
|
529
|
+
}
|
|
530
|
+
const details = await Promise.all(summaries.map(async (s) => {
|
|
531
|
+
let body;
|
|
532
|
+
try {
|
|
533
|
+
body = await getVegaResource({
|
|
534
|
+
baseUrl,
|
|
535
|
+
accessToken,
|
|
536
|
+
id: s.id,
|
|
537
|
+
businessDomain,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
catch (err) {
|
|
541
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
542
|
+
throw new Error(`vega resource ${s.id} fetch failed: ${reason}`);
|
|
543
|
+
}
|
|
544
|
+
const parsed = JSON.parse(body);
|
|
545
|
+
if (Array.isArray(parsed.entries)) {
|
|
546
|
+
const arr = parsed.entries;
|
|
547
|
+
if (arr.length === 0) {
|
|
548
|
+
throw new Error(`vega resource ${s.id} returned empty entries`);
|
|
549
|
+
}
|
|
550
|
+
return arr[0];
|
|
551
|
+
}
|
|
552
|
+
if (Array.isArray(parsed.data)) {
|
|
553
|
+
const arr = parsed.data;
|
|
554
|
+
if (arr.length === 0) {
|
|
555
|
+
throw new Error(`vega resource ${s.id} returned empty data`);
|
|
556
|
+
}
|
|
557
|
+
return arr[0];
|
|
558
|
+
}
|
|
559
|
+
return parsed;
|
|
560
|
+
}));
|
|
561
|
+
const tables = [];
|
|
562
|
+
for (const d of details) {
|
|
563
|
+
const columnsRaw = (d.source_metadata?.columns ?? []);
|
|
564
|
+
const tablePkArray = extractPrimaryKeys(d);
|
|
565
|
+
const columns = columnsRaw.map((c) => {
|
|
566
|
+
const name = String(c.name ?? c.field_name ?? "");
|
|
567
|
+
const flagged = isColumnPrimaryKey(c) || tablePkArray.includes(name);
|
|
568
|
+
return {
|
|
569
|
+
name,
|
|
570
|
+
type: String(c.type ?? c.field_type ?? "varchar"),
|
|
571
|
+
comment: typeof c.description === "string"
|
|
572
|
+
? c.description
|
|
573
|
+
: (typeof c.comment === "string" ? c.comment : undefined),
|
|
574
|
+
...(flagged ? { isPrimaryKey: true } : {}),
|
|
575
|
+
};
|
|
576
|
+
});
|
|
577
|
+
const synthesizedPks = tablePkArray.length > 0
|
|
578
|
+
? tablePkArray
|
|
579
|
+
: columns.filter((c) => c.isPrimaryKey).map((c) => c.name);
|
|
580
|
+
tables.push({
|
|
581
|
+
name: d.name,
|
|
582
|
+
columns,
|
|
583
|
+
...(synthesizedPks.length > 0 ? { primaryKeys: synthesizedPks } : {}),
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
return JSON.stringify(tables);
|
|
587
|
+
}
|
|
588
|
+
// Two PK metadata shapes are recognized — both confirmed conventions:
|
|
589
|
+
// - per-column `is_primary_key: true` (data-connection metadata standard)
|
|
590
|
+
// - per-column `column_key === "PRI"` (MySQL INFORMATION_SCHEMA pass-through)
|
|
591
|
+
// - table-level `primary_keys: string[]` (composite-PK carrier)
|
|
592
|
+
// Other plausible spellings (camelCase, singular keys, SQLite `pk` integer) are
|
|
593
|
+
// intentionally NOT recognized here — adding them speculatively risks false
|
|
594
|
+
// matches and creates code paths the test suite can't pin down. Extend only when
|
|
595
|
+
// a real backend response demonstrates the need.
|
|
596
|
+
function isColumnPrimaryKey(col) {
|
|
597
|
+
if (col.is_primary_key === true)
|
|
598
|
+
return true;
|
|
599
|
+
if (typeof col.column_key === "string" && col.column_key.toUpperCase() === "PRI")
|
|
600
|
+
return true;
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
function extractPrimaryKeys(table) {
|
|
604
|
+
const arr = table.primary_keys;
|
|
605
|
+
if (Array.isArray(arr)) {
|
|
606
|
+
return arr.filter((x) => typeof x === "string");
|
|
607
|
+
}
|
|
608
|
+
return [];
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Trigger a metadata scan for a vega catalog and wait for completion.
|
|
612
|
+
* `id` is a vega catalog id (e.g. `d7nicrcjto2s73d9g67g`), not a legacy
|
|
613
|
+
* data-connection datasource UUID.
|
|
614
|
+
*/
|
|
615
|
+
export async function scanMetadata(options) {
|
|
616
|
+
const { baseUrl, accessToken, id, businessDomain = "bd_public" } = options;
|
|
617
|
+
return discoverVegaCatalog({
|
|
618
|
+
baseUrl,
|
|
619
|
+
accessToken,
|
|
620
|
+
id,
|
|
621
|
+
wait: true,
|
|
622
|
+
businessDomain,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Trigger a metadata scan and wait for completion. `id` is a vega catalog id.
|
|
627
|
+
*
|
|
628
|
+
* @deprecated Use {@link scanMetadata} directly. This wrapper exists only for
|
|
629
|
+
* backward compatibility with callers that used the old data-connection-based
|
|
630
|
+
* `scanDatasourceMetadata` signature.
|
|
631
|
+
*/
|
|
632
|
+
export async function scanDatasourceMetadata(options) {
|
|
633
|
+
return scanMetadata(options);
|
|
634
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -183,6 +183,7 @@ Commands:
|
|
|
183
183
|
tool Tools inside a toolbox (upload OpenAPI spec, list, enable/disable)
|
|
184
184
|
vega Vega observability (catalog, resource, query/sql, connector-type, health/stats/inspect)
|
|
185
185
|
context-loader Context-loader MCP/HTTP (config, tools, resources, search-schema, tool-call, query-*, etc.)
|
|
186
|
+
trace Diagnose a single trace with rule-based analysis
|
|
186
187
|
help Show this message`);
|
|
187
188
|
}
|
|
188
189
|
export async function run(argv) {
|
|
@@ -287,6 +288,10 @@ export async function run(argv) {
|
|
|
287
288
|
if (command === "context-loader" || command === "context") {
|
|
288
289
|
return runContextLoaderCommand(rest);
|
|
289
290
|
}
|
|
291
|
+
if (command === "trace") {
|
|
292
|
+
const { runTraceCommand } = await import("./commands/trace.js");
|
|
293
|
+
return await runTraceCommand(rest);
|
|
294
|
+
}
|
|
290
295
|
console.error(`Unknown command: ${command}`);
|
|
291
296
|
printHelp();
|
|
292
297
|
return 1;
|
package/dist/commands/bkn-ops.js
CHANGED
|
@@ -5,7 +5,7 @@ import { loadNetwork, allObjects, allRelations, allActions, generateChecksum, va
|
|
|
5
5
|
import { prepareBknDirectoryForImport, stripBknEncodingCliArgs, } from "../utils/bkn-encoding.js";
|
|
6
6
|
import { ensureValidToken, formatHttpError } from "../auth/oauth.js";
|
|
7
7
|
import { createKnowledgeNetwork, createObjectTypes, deleteKnowledgeNetwork, buildKnowledgeNetwork, getBuildStatus, } from "../api/knowledge-networks.js";
|
|
8
|
-
import { listTablesWithColumns, scanDatasourceMetadata } from "../api/
|
|
8
|
+
import { listTablesWithColumns, scanDatasourceMetadata } from "../api/vega.js";
|
|
9
9
|
import { createDataView, findDataView } from "../api/dataviews.js";
|
|
10
10
|
import { resolveFiles } from "./ds.js";
|
|
11
11
|
import { buildTableName } from "./import-csv.js";
|
|
@@ -13,7 +13,7 @@ import { downloadBkn, uploadBkn, listActionSchedules, getActionSchedule, createA
|
|
|
13
13
|
import { formatCallOutput } from "./call.js";
|
|
14
14
|
import { resolveBusinessDomain } from "../config/store.js";
|
|
15
15
|
import { runDsImportCsv } from "./ds.js";
|
|
16
|
-
import { pollWithBackoff, detectDisplayKey, formatPkDetectionError, parsePkMap, resolvePrimaryKey, confirmYes, } from "./bkn-utils.js";
|
|
16
|
+
import { pollWithBackoff, detectDisplayKey, formatPkDetectionError, parsePkMap, resolvePrimaryKey, confirmYes, assertVegaCatalogId, } from "./bkn-utils.js";
|
|
17
17
|
// ── BKN object name validation ──────────────────────────────────────────────
|
|
18
18
|
// Mirrors bkn-backend OBJECT_NAME_MAX_LENGTH (interfaces/common.go:28) and
|
|
19
19
|
// validateObjectName (driveradapters/validate.go:85). 40 utf-8 codepoints,
|
|
@@ -473,9 +473,11 @@ export async function runKnPullCommand(args) {
|
|
|
473
473
|
}
|
|
474
474
|
}
|
|
475
475
|
// ── Create from datasource ──────────────────────────────────────────────────
|
|
476
|
-
const KN_CREATE_FROM_DS_HELP = `kweaver bkn create-from-ds <
|
|
476
|
+
const KN_CREATE_FROM_DS_HELP = `kweaver bkn create-from-ds <vega-catalog-id> --name X [options]
|
|
477
477
|
|
|
478
|
-
Create a knowledge network from a datasource (dataviews + object types + optional build).
|
|
478
|
+
Create a knowledge network from a vega catalog datasource (dataviews + object types + optional build).
|
|
479
|
+
<vega-catalog-id> is a vega catalog id (use \`kweaver vega catalog list\` to find one;
|
|
480
|
+
legacy data-connection datasource UUIDs are no longer accepted).
|
|
479
481
|
|
|
480
482
|
Options:
|
|
481
483
|
--name <s> Knowledge network name (required)
|
|
@@ -548,6 +550,7 @@ export function parseKnCreateFromDsArgs(args) {
|
|
|
548
550
|
if (!dsId || !name) {
|
|
549
551
|
throw new Error("Usage: kweaver bkn create-from-ds <ds-id> --name X [options]");
|
|
550
552
|
}
|
|
553
|
+
assertVegaCatalogId(dsId);
|
|
551
554
|
const pkMap = pkMapStr ? parsePkMap(pkMapStr) : {};
|
|
552
555
|
if (!businessDomain)
|
|
553
556
|
businessDomain = resolveBusinessDomain();
|
|
@@ -803,9 +806,11 @@ export async function runKnCreateFromDsCommand(args, sampleRows) {
|
|
|
803
806
|
}
|
|
804
807
|
}
|
|
805
808
|
// ── Create from CSV ─────────────────────────────────────────────────────────
|
|
806
|
-
const KN_CREATE_FROM_CSV_HELP = `kweaver bkn create-from-csv <
|
|
809
|
+
const KN_CREATE_FROM_CSV_HELP = `kweaver bkn create-from-csv <vega-catalog-id> --files <glob> --name X [options]
|
|
807
810
|
|
|
808
|
-
Import CSV files into datasource, then create a knowledge network.
|
|
811
|
+
Import CSV files into a vega catalog datasource, then create a knowledge network.
|
|
812
|
+
<vega-catalog-id> is a vega catalog id (use \`kweaver vega catalog list\` to find one;
|
|
813
|
+
legacy data-connection datasource UUIDs are no longer accepted).
|
|
809
814
|
|
|
810
815
|
Options:
|
|
811
816
|
--files <s> CSV file paths (comma-separated or glob, required)
|
|
@@ -891,6 +896,7 @@ export function parseKnCreateFromCsvArgs(args) {
|
|
|
891
896
|
if (!dsId || !files || !name) {
|
|
892
897
|
throw new Error("Usage: kweaver bkn create-from-csv <ds-id> --files <glob> --name X [options]");
|
|
893
898
|
}
|
|
899
|
+
assertVegaCatalogId(dsId);
|
|
894
900
|
const pkMap = pkMapStr ? parsePkMap(pkMapStr) : {};
|
|
895
901
|
if (!businessDomain)
|
|
896
902
|
businessDomain = resolveBusinessDomain();
|
|
@@ -86,4 +86,13 @@ export declare function detectDisplayKey(table: {
|
|
|
86
86
|
type: string;
|
|
87
87
|
}>;
|
|
88
88
|
}, primaryKey: string): string;
|
|
89
|
+
/**
|
|
90
|
+
* Reject legacy data-connection datasource UUIDs.
|
|
91
|
+
*
|
|
92
|
+
* Since the SDK migration to vega-backend (#114), commands that call
|
|
93
|
+
* `listTablesWithColumns` / `scanMetadata` expect a vega catalog id (a short
|
|
94
|
+
* slug like `d7nicrcjto2s73d9g67g`), not the UUID-shaped id stored in
|
|
95
|
+
* data-connection.
|
|
96
|
+
*/
|
|
97
|
+
export declare function assertVegaCatalogId(id: string): void;
|
|
89
98
|
export declare function confirmYes(prompt: string): Promise<boolean>;
|
|
@@ -183,6 +183,23 @@ export function detectDisplayKey(table, primaryKey) {
|
|
|
183
183
|
}
|
|
184
184
|
return primaryKey;
|
|
185
185
|
}
|
|
186
|
+
// ── Vega catalog id guard ────────────────────────────────────────────────────
|
|
187
|
+
const UUID_V4_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
188
|
+
/**
|
|
189
|
+
* Reject legacy data-connection datasource UUIDs.
|
|
190
|
+
*
|
|
191
|
+
* Since the SDK migration to vega-backend (#114), commands that call
|
|
192
|
+
* `listTablesWithColumns` / `scanMetadata` expect a vega catalog id (a short
|
|
193
|
+
* slug like `d7nicrcjto2s73d9g67g`), not the UUID-shaped id stored in
|
|
194
|
+
* data-connection.
|
|
195
|
+
*/
|
|
196
|
+
export function assertVegaCatalogId(id) {
|
|
197
|
+
if (UUID_V4_RE.test(id)) {
|
|
198
|
+
throw new Error(`expected a vega catalog id, got UUID '${id}'. ` +
|
|
199
|
+
`This looks like a legacy data-connection datasource UUID. ` +
|
|
200
|
+
`Run \`kweaver vega catalog list --keyword <name>\` to find the corresponding catalog id.`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
186
203
|
// ── Interactive confirmation ─────────────────────────────────────────────────
|
|
187
204
|
export function confirmYes(prompt) {
|
|
188
205
|
return new Promise((resolve) => {
|
package/dist/commands/ds.js
CHANGED
|
@@ -3,9 +3,11 @@ import { statSync } from "node:fs";
|
|
|
3
3
|
import { glob } from "node:fs/promises";
|
|
4
4
|
import { resolve as resolvePath } from "node:path";
|
|
5
5
|
import { ensureValidToken, formatHttpError, with401RefreshRetry } from "../auth/oauth.js";
|
|
6
|
-
import { testDatasource, createDatasource, listDatasources, getDatasource, deleteDatasource,
|
|
6
|
+
import { testDatasource, createDatasource, listDatasources, getDatasource, deleteDatasource, } from "../api/datasources.js";
|
|
7
|
+
import { listTablesWithColumns, scanMetadata } from "../api/vega.js";
|
|
7
8
|
import { formatCallOutput } from "./call.js";
|
|
8
9
|
import { resolveBusinessDomain } from "../config/store.js";
|
|
10
|
+
import { assertVegaCatalogId } from "./bkn-utils.js";
|
|
9
11
|
import { parseCsvFile, buildTableName, splitBatches, buildFieldMappings, buildDagBody, } from "./import-csv.js";
|
|
10
12
|
import { executeDataflow } from "../api/dataflow.js";
|
|
11
13
|
function confirmYes(prompt) {
|
|
@@ -196,9 +198,12 @@ async function runDsTablesCommand(args) {
|
|
|
196
198
|
id = arg;
|
|
197
199
|
}
|
|
198
200
|
if (!id) {
|
|
199
|
-
console.error("Usage: kweaver ds tables <id> [--keyword X]"
|
|
201
|
+
console.error("Usage: kweaver ds tables <vega-catalog-id> [--keyword X]\n" +
|
|
202
|
+
" <vega-catalog-id> is a vega catalog id (use `kweaver vega catalog list` to find one;\n" +
|
|
203
|
+
" legacy data-connection datasource UUIDs are no longer accepted).");
|
|
200
204
|
return 1;
|
|
201
205
|
}
|
|
206
|
+
assertVegaCatalogId(id);
|
|
202
207
|
const token = await ensureValidToken();
|
|
203
208
|
const body = await listTablesWithColumns({
|
|
204
209
|
baseUrl: token.baseUrl,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ParsedTraceArgs {
|
|
2
|
+
subcommand: "diagnose" | "rules-validate" | "help";
|
|
3
|
+
conversationId?: string;
|
|
4
|
+
rulePath?: string;
|
|
5
|
+
out: string | null;
|
|
6
|
+
rulesDir: string | null;
|
|
7
|
+
noBuiltin: boolean;
|
|
8
|
+
noLlm: boolean;
|
|
9
|
+
baseUrl: string | null;
|
|
10
|
+
token: string | null;
|
|
11
|
+
businessDomain: string | null;
|
|
12
|
+
}
|
|
13
|
+
export declare function parseTraceArgs(argv: string[]): ParsedTraceArgs;
|
|
14
|
+
export declare function runTraceCommand(rest: string[]): Promise<number>;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import yargs from "yargs";
|
|
2
|
+
import { diagnose, TraceNotFoundError } from "../trace-core/diagnose/index.js";
|
|
3
|
+
import { RuleLoadError } from "../trace-core/diagnose/rule-loader.js";
|
|
4
|
+
import { RuleProbeError } from "../trace-core/diagnose/signal-probe.js";
|
|
5
|
+
import { RuleSchema } from "../trace-core/diagnose/schemas.js";
|
|
6
|
+
import { ensureValidToken } from "../auth/oauth.js";
|
|
7
|
+
import yaml from "js-yaml";
|
|
8
|
+
import fs from "node:fs/promises";
|
|
9
|
+
export function parseTraceArgs(argv) {
|
|
10
|
+
if (argv.length === 0) {
|
|
11
|
+
return defaults("help");
|
|
12
|
+
}
|
|
13
|
+
const head = argv[0];
|
|
14
|
+
if (head !== "diagnose") {
|
|
15
|
+
return defaults("help");
|
|
16
|
+
}
|
|
17
|
+
if (argv[1] === "rules" && argv[2] === "validate") {
|
|
18
|
+
return { ...defaults("rules-validate"), rulePath: argv[3] };
|
|
19
|
+
}
|
|
20
|
+
// diagnose <traceId> [flags...]
|
|
21
|
+
const parsed = yargs(argv.slice(1))
|
|
22
|
+
.option("out", { type: "string", default: undefined })
|
|
23
|
+
.option("rules", { type: "string", default: undefined })
|
|
24
|
+
.option("builtin", { type: "boolean", default: true }) // --no-builtin sets this to false
|
|
25
|
+
.option("llm", { type: "boolean", default: false }) // PR-A: forced false (--no-llm)
|
|
26
|
+
.option("token", { type: "string" })
|
|
27
|
+
.option("base-url", { type: "string" })
|
|
28
|
+
.option("business-domain", { alias: "bd", type: "string" })
|
|
29
|
+
.help(false)
|
|
30
|
+
.parseSync();
|
|
31
|
+
return {
|
|
32
|
+
subcommand: "diagnose",
|
|
33
|
+
conversationId: String(parsed._[0] ?? ""),
|
|
34
|
+
out: parsed.out ?? null,
|
|
35
|
+
rulesDir: parsed.rules ?? null,
|
|
36
|
+
noBuiltin: !parsed.builtin,
|
|
37
|
+
noLlm: !parsed.llm,
|
|
38
|
+
baseUrl: parsed.baseUrl ?? null,
|
|
39
|
+
token: parsed.token ?? null,
|
|
40
|
+
businessDomain: parsed.businessDomain ?? null,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function defaults(sub) {
|
|
44
|
+
return {
|
|
45
|
+
subcommand: sub,
|
|
46
|
+
out: null,
|
|
47
|
+
rulesDir: null,
|
|
48
|
+
noBuiltin: false,
|
|
49
|
+
noLlm: true,
|
|
50
|
+
baseUrl: null,
|
|
51
|
+
token: null,
|
|
52
|
+
businessDomain: null,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function printHelp() {
|
|
56
|
+
process.stdout.write(`kweaver trace — trace diagnosis commands
|
|
57
|
+
|
|
58
|
+
Subcommands:
|
|
59
|
+
trace diagnose <conversation_id> Diagnose the trace produced by a conversation; emit YAML report
|
|
60
|
+
(the id is the conversation_id returned by 'agent chat' /
|
|
61
|
+
'agent sessions'; spans are fetched from agent-observability)
|
|
62
|
+
--out <file> Write report to file (default: stdout)
|
|
63
|
+
--rules <dir> Override <cwd>/diagnosis-rules/
|
|
64
|
+
--no-builtin Disable the 5 builtin baseline rules
|
|
65
|
+
--no-llm PR-A: always on; PR-B will allow disabling
|
|
66
|
+
|
|
67
|
+
trace diagnose rules validate <rule.yaml> Validate a rule yaml file (exit 0 ok, 6 fail)
|
|
68
|
+
|
|
69
|
+
Auth flags (any subcommand): --token, --base-url, --business-domain (-bd).
|
|
70
|
+
`);
|
|
71
|
+
}
|
|
72
|
+
export async function runTraceCommand(rest) {
|
|
73
|
+
const args = parseTraceArgs(rest);
|
|
74
|
+
if (args.subcommand === "help") {
|
|
75
|
+
printHelp();
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
if (args.subcommand === "rules-validate") {
|
|
79
|
+
return await runRulesValidate(args.rulePath ?? "");
|
|
80
|
+
}
|
|
81
|
+
// diagnose
|
|
82
|
+
if (!args.conversationId) {
|
|
83
|
+
process.stderr.write("error: missing <conversation_id>\n");
|
|
84
|
+
return 2;
|
|
85
|
+
}
|
|
86
|
+
let baseUrl = args.baseUrl ?? process.env.KWEAVER_BASE_URL ?? "";
|
|
87
|
+
let token = args.token ?? process.env.KWEAVER_TOKEN ?? "";
|
|
88
|
+
const bd = args.businessDomain ?? process.env.KWEAVER_BUSINESS_DOMAIN ?? "bd_public";
|
|
89
|
+
// Fall back to the active platform from `~/.kweaver/` (same as agent trace),
|
|
90
|
+
// so users don't need to pass --base-url / --token explicitly. Tokens are
|
|
91
|
+
// auto-refreshed for OAuth platforms; "__NO_AUTH__" is returned for no-auth.
|
|
92
|
+
if (!baseUrl || !token) {
|
|
93
|
+
try {
|
|
94
|
+
const t = await ensureValidToken();
|
|
95
|
+
if (!baseUrl)
|
|
96
|
+
baseUrl = t.baseUrl;
|
|
97
|
+
if (!token)
|
|
98
|
+
token = t.accessToken;
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
process.stderr.write(`error: missing --base-url / --token, and no active platform in ~/.kweaver/ — ${e.message}\n`);
|
|
102
|
+
return 5;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!baseUrl || !token) {
|
|
106
|
+
process.stderr.write("error: missing --base-url / --token (or KWEAVER_BASE_URL / KWEAVER_TOKEN env)\n");
|
|
107
|
+
return 5;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
await diagnose(args.conversationId, {
|
|
111
|
+
out: args.out,
|
|
112
|
+
rulesDir: args.rulesDir,
|
|
113
|
+
noBuiltin: args.noBuiltin,
|
|
114
|
+
noLlm: true,
|
|
115
|
+
agentProvider: null,
|
|
116
|
+
timeoutMs: 60000,
|
|
117
|
+
baseUrl,
|
|
118
|
+
token,
|
|
119
|
+
businessDomain: bd,
|
|
120
|
+
});
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
if (e instanceof TraceNotFoundError) {
|
|
125
|
+
process.stderr.write(`error: ${e.message}; check time window / tenant\n`);
|
|
126
|
+
return 4;
|
|
127
|
+
}
|
|
128
|
+
if (e instanceof RuleLoadError) {
|
|
129
|
+
process.stderr.write(`error: ${e.message}\n`);
|
|
130
|
+
return 6;
|
|
131
|
+
}
|
|
132
|
+
if (e instanceof RuleProbeError) {
|
|
133
|
+
process.stderr.write(`error: ${e.message}\n`);
|
|
134
|
+
return 6;
|
|
135
|
+
}
|
|
136
|
+
process.stderr.write(`error: ${e.message}\n`);
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function runRulesValidate(rulePath) {
|
|
141
|
+
if (!rulePath) {
|
|
142
|
+
process.stderr.write("error: missing <rule.yaml> path\n");
|
|
143
|
+
return 2;
|
|
144
|
+
}
|
|
145
|
+
let raw;
|
|
146
|
+
try {
|
|
147
|
+
raw = await fs.readFile(rulePath, "utf8");
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
process.stderr.write(`error: cannot read ${rulePath}: ${e.message}\n`);
|
|
151
|
+
return 6;
|
|
152
|
+
}
|
|
153
|
+
let parsed;
|
|
154
|
+
try {
|
|
155
|
+
parsed = yaml.load(raw);
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
process.stderr.write(`error: yaml parse error: ${e.message}\n`);
|
|
159
|
+
return 6;
|
|
160
|
+
}
|
|
161
|
+
const result = RuleSchema.safeParse(parsed);
|
|
162
|
+
if (!result.success) {
|
|
163
|
+
process.stderr.write(`error: schema validation failed:\n${result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}\n`);
|
|
164
|
+
return 6;
|
|
165
|
+
}
|
|
166
|
+
process.stdout.write(`ok: ${rulePath} validates against diagnosis-rule/v1\n`);
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { testDatasource, createDatasource, listDatasources, getDatasource, deleteDatasource, listTables,
|
|
1
|
+
import { testDatasource, createDatasource, listDatasources, getDatasource, deleteDatasource, listTables, } from "../api/datasources.js";
|
|
2
|
+
import { listTablesWithColumns, scanMetadata } from "../api/vega.js";
|
|
2
3
|
export class DataSourcesResource {
|
|
3
4
|
ctx;
|
|
4
5
|
constructor(ctx) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// PR-A approximation: counts tool calls across the entire trace, not per user turn.
|
|
2
|
+
// Real per-turn scoping requires turn segmentation by gen_ai.conversation.id round trips,
|
|
3
|
+
// which is deferred to PR-B (where the synthesizer can also use turn boundaries for narratives).
|
|
4
|
+
// For single-turn traces (the common case in PR-A) this approximation matches the rule semantics.
|
|
5
|
+
export const predicate = (trace, params) => {
|
|
6
|
+
const max = params.max_tool_calls_per_turn ?? 10;
|
|
7
|
+
const tools = trace.byKind.get("tool") ?? [];
|
|
8
|
+
if (tools.length <= max)
|
|
9
|
+
return [];
|
|
10
|
+
return [{
|
|
11
|
+
evidenceSpans: tools.map((t) => t.spanId),
|
|
12
|
+
excerpt: `tool calls per turn exceeded threshold: ${tools.length} > ${max}`,
|
|
13
|
+
bindings: { count: tools.length, max_calls: max },
|
|
14
|
+
}];
|
|
15
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
schema_version: diagnosis-rule/v1
|
|
2
|
+
id: excessive_tool_calls_per_turn
|
|
3
|
+
severity: medium
|
|
4
|
+
symptom: excessive_tool_calls_per_user_turn
|
|
5
|
+
taxonomy:
|
|
6
|
+
signals_axis: execution
|
|
7
|
+
ms_class: tool_misuse
|
|
8
|
+
suggested_fix:
|
|
9
|
+
target: decision_agent.prompt
|
|
10
|
+
change_template: "constrain plan to at most {{max_calls}} tool calls per user turn; observed {{count}}"
|
|
11
|
+
verify_with:
|
|
12
|
+
assertion_templates:
|
|
13
|
+
- "tool_call_count_per_turn <= {{max_calls}}"
|
|
14
|
+
predicate: builtin:excessive_tool_calls_per_turn
|
|
15
|
+
params:
|
|
16
|
+
max_tool_calls_per_turn: 10
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function finishReason(s) {
|
|
2
|
+
// OTel GenAI 1.x emits an array (`finish_reasons`); older spans / fixtures
|
|
3
|
+
// use the singular string form. Accept both; first non-empty entry wins.
|
|
4
|
+
const arr = s.attributes["gen_ai.response.finish_reasons"];
|
|
5
|
+
if (Array.isArray(arr)) {
|
|
6
|
+
for (const r of arr) {
|
|
7
|
+
if (typeof r === "string" && r.length > 0)
|
|
8
|
+
return r;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const a = s.attributes["gen_ai.response.finish_reason"] ?? s.attributes["llm.finish_reason"];
|
|
12
|
+
return typeof a === "string" ? a : "";
|
|
13
|
+
}
|
|
14
|
+
function conversationId(s) {
|
|
15
|
+
const v = s.attributes["gen_ai.conversation.id"];
|
|
16
|
+
return typeof v === "string" ? v : "";
|
|
17
|
+
}
|
|
18
|
+
export const predicate = (trace) => {
|
|
19
|
+
const llms = (trace.byKind.get("llm") ?? [])
|
|
20
|
+
.slice()
|
|
21
|
+
.sort((a, b) => Number(BigInt(a.startTimeUnixNano) - BigInt(b.startTimeUnixNano)));
|
|
22
|
+
const hits = [];
|
|
23
|
+
for (let i = 0; i < llms.length; i++) {
|
|
24
|
+
const s = llms[i];
|
|
25
|
+
if (finishReason(s) !== "length")
|
|
26
|
+
continue;
|
|
27
|
+
const convId = conversationId(s);
|
|
28
|
+
let hasContinuation = false;
|
|
29
|
+
for (let j = i + 1; j < llms.length; j++) {
|
|
30
|
+
if (conversationId(llms[j]) === convId) {
|
|
31
|
+
hasContinuation = true;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!hasContinuation) {
|
|
36
|
+
hits.push({
|
|
37
|
+
evidenceSpans: [s.spanId],
|
|
38
|
+
excerpt: `LLM response truncated (finish_reason=length) with no continuation span in conversation '${convId}'`,
|
|
39
|
+
bindings: { conversation_id: convId },
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return hits;
|
|
44
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
schema_version: diagnosis-rule/v1
|
|
2
|
+
id: llm_response_truncated_no_continue
|
|
3
|
+
severity: medium
|
|
4
|
+
symptom: llm_output_truncated_with_no_continuation
|
|
5
|
+
taxonomy:
|
|
6
|
+
signals_axis: execution
|
|
7
|
+
ms_class: context_loss
|
|
8
|
+
suggested_fix:
|
|
9
|
+
target: decision_agent.prompt
|
|
10
|
+
change_template: "after finish_reason=length, send a continuation request or split the task earlier"
|
|
11
|
+
verify_with:
|
|
12
|
+
assertion_templates:
|
|
13
|
+
- "if(llm.finish_reason == 'length'): next_step in [continuation, split_task]"
|
|
14
|
+
predicate: builtin:llm_response_truncated_no_continue
|
|
15
|
+
params: {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { registerPredicate } from "../predicate-registry.js";
|
|
2
|
+
import { predicate as toolLoopNoStateChange } from "./tool-loop-no-state-change.js";
|
|
3
|
+
import { predicate as toolErrorSwallowed } from "./tool-error-swallowed.js";
|
|
4
|
+
import { predicate as retrievalEmptyNoFallback } from "./retrieval-empty-no-fallback.js";
|
|
5
|
+
import { predicate as llmResponseTruncatedNoContinue } from "./llm-response-truncated-no-continue.js";
|
|
6
|
+
import { predicate as excessiveToolCallsPerTurn } from "./excessive-tool-calls-per-turn.js";
|
|
7
|
+
registerPredicate("tool_loop_no_state_change", toolLoopNoStateChange);
|
|
8
|
+
registerPredicate("tool_error_swallowed", toolErrorSwallowed);
|
|
9
|
+
registerPredicate("retrieval_empty_no_fallback", retrievalEmptyNoFallback);
|
|
10
|
+
registerPredicate("llm_response_truncated_no_continue", llmResponseTruncatedNoContinue);
|
|
11
|
+
registerPredicate("excessive_tool_calls_per_turn", excessiveToolCallsPerTurn);
|