@kweaver-ai/kweaver-sdk 0.7.3 → 0.7.4
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 +29 -0
- package/README.zh.md +26 -0
- package/bin/kweaver.js +12 -11
- package/dist/api/bkn-backend.d.ts +1 -0
- package/dist/api/bkn-backend.js +1 -1
- package/dist/api/bkn-metrics.d.ts +59 -0
- package/dist/api/bkn-metrics.js +129 -0
- package/dist/api/conversations.d.ts +47 -2
- package/dist/api/conversations.js +113 -17
- package/dist/api/datasources.js +43 -6
- package/dist/api/model-invocation.d.ts +58 -0
- package/dist/api/model-invocation.js +203 -0
- package/dist/api/models.d.ts +79 -0
- package/dist/api/models.js +183 -0
- package/dist/api/ontology-query-metrics.d.ts +14 -0
- package/dist/api/ontology-query-metrics.js +30 -0
- package/dist/bundled-model-templates.d.ts +17 -0
- package/dist/bundled-model-templates.js +24 -0
- package/dist/cli.js +10 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.js +5 -0
- package/dist/commands/agent.d.ts +7 -1
- package/dist/commands/agent.js +75 -21
- package/dist/commands/bkn-metric.d.ts +1 -0
- package/dist/commands/bkn-metric.js +406 -0
- package/dist/commands/bkn-ops.js +17 -11
- package/dist/commands/bkn-utils.d.ts +29 -0
- package/dist/commands/bkn-utils.js +37 -0
- package/dist/commands/bkn.js +4 -0
- package/dist/commands/ds.js +7 -1
- package/dist/commands/explore-chat.js +2 -2
- package/dist/commands/model.d.ts +72 -0
- package/dist/commands/model.js +1315 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +5 -0
- package/dist/resources/models.d.ts +40 -0
- package/dist/resources/models.js +88 -0
- package/dist/templates/model/llm-basic.json +13 -0
- package/dist/templates/model/manifest.json +16 -0
- package/dist/templates/model/small-basic.json +6 -0
- package/dist/utils/trace-views.d.ts +44 -0
- package/dist/utils/trace-views.js +425 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -174,12 +174,15 @@ kweaver token
|
|
|
174
174
|
kweaver ds list/get/delete/tables/connect
|
|
175
175
|
kweaver ds import-csv <ds_id> --files <glob> [--table-prefix <p>] [--batch-size 500] [--recreate]
|
|
176
176
|
kweaver dataflow list/run/runs/logs
|
|
177
|
+
kweaver model llm list/get/add/edit/delete/test/chat/--template
|
|
178
|
+
kweaver model small list/get/add/edit/delete/test/embeddings/rerank/--template
|
|
177
179
|
kweaver dataview list/find/get/query/delete
|
|
178
180
|
kweaver bkn list/get/stats/export/create/update/delete
|
|
179
181
|
kweaver bkn create-from-ds <ds_id> --name <name> [--tables t1,t2] [--build]
|
|
180
182
|
kweaver bkn create-from-csv <ds_id> --files <glob> --name <name> [--build]
|
|
181
183
|
kweaver bkn validate/push/pull
|
|
182
184
|
kweaver bkn object-type list/get/create/update/delete/query/properties
|
|
185
|
+
kweaver bkn metric list/get/create/search/validate/update/delete/query/dry-run
|
|
183
186
|
kweaver bkn relation-type list/get/create/update/delete
|
|
184
187
|
kweaver bkn action-type list/query/execute
|
|
185
188
|
kweaver bkn subgraph / search
|
|
@@ -211,6 +214,30 @@ kweaver dataflow logs <dagId> <instanceId> --detail
|
|
|
211
214
|
|
|
212
215
|
`kweaver dataflow runs --since` filters one local natural day. If the value cannot be parsed by `new Date(...)`, the CLI falls back to the most recent 20 runs. `kweaver dataflow logs` defaults to summary output; add `--detail` to print indented `input` and `output` payloads.
|
|
213
216
|
|
|
217
|
+
### Model factory CLI examples
|
|
218
|
+
|
|
219
|
+
`model` talks to **mf-model-manager** (`/api/mf-model-manager/v1`) for CRUD and **mf-model-api** (`/api/mf-model-api/v1`) for OpenAI-compatible **`chat`**, **`small embeddings`**, and **`small rerank`**. Override origins with `--mf-base-url` / `--mf-api-base-url` or `KWEAVER_MF_MODEL_MANAGER_URL` / `KWEAVER_MF_MODEL_API_URL`. `model llm --template` / `model small --template` prints one offline **`basic`** JSON stub per branch (no API calls). **LLM** `model_type` on the platform is one of **`llm`** (text), **`rlm`** (reasoning), or **`vu`** (vision / multimodal); filter with `kweaver model llm list --type …`. See [`skills/kweaver-core/references/model.md`](../../skills/kweaver-core/references/model.md#llm-model-types).
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
kweaver model llm list --limit 30
|
|
223
|
+
kweaver model llm list --type rlm
|
|
224
|
+
kweaver model llm get <model_id>
|
|
225
|
+
kweaver model llm add --body-file ./llm.json --upstream-url https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions --api-model qwen-plus --api-key-file ~/.dashscope_key
|
|
226
|
+
kweaver model llm chat <model_id> -m "Hello" --no-stream
|
|
227
|
+
kweaver model llm --template > ./llm.json
|
|
228
|
+
kweaver model small list
|
|
229
|
+
kweaver model small add --name my-emb --type embedding --batch-size 8 --max-tokens 512 --embedding-dim 1536 \
|
|
230
|
+
--model-config-file ./cfg.json
|
|
231
|
+
kweaver model small add --name my-emb --type embedding --batch-size 8 --max-tokens 8192 --embedding-dim 1024 \
|
|
232
|
+
--upstream-url https://dashscope-intl.aliyuncs.com/compatible-mode/v1/embeddings --api-model text-embedding-v4 --api-key-file ~/.dashscope_key
|
|
233
|
+
kweaver model small test <model_id>
|
|
234
|
+
kweaver model small embeddings <model_id> -i "hello" -i "world"
|
|
235
|
+
kweaver model small rerank <model_id> -q "query" -d "doc a" -d "doc b"
|
|
236
|
+
kweaver model small --template > ./embedding-model-config.json
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Full flags: `kweaver model --help` and [`skills/kweaver-core/references/model.md`](../../skills/kweaver-core/references/model.md).
|
|
240
|
+
|
|
214
241
|
### Vega `sql` CLI examples
|
|
215
242
|
|
|
216
243
|
Direct SQL against catalog-backed resources (`POST /api/vega-backend/v1/resources/query`). In SQL, use **`{{<resource_id>}}`** or **`{{.<resource_id>}}`** (Vega resource id from `vega resource list` / `get`) so the backend resolves the physical table and connector. `--resource-type` accepts the connector type of the target data source (run `kweaver vega connector-type list` to see available types). In simple mode, **quote the entire `--query` value** so the shell does not treat `{` / `}` specially.
|
|
@@ -256,6 +283,8 @@ kweaver tool debug --toolbox <BOX_ID> <TOOL_ID> \
|
|
|
256
283
|
| Variable | Description |
|
|
257
284
|
|---|---|
|
|
258
285
|
| `KWEAVER_BASE_URL` | KWeaver instance URL |
|
|
286
|
+
| `KWEAVER_MF_MODEL_MANAGER_URL` | Optional override for mf-model-manager API (defaults from `KWEAVER_BASE_URL` + `/api/mf-model-manager/v1`) |
|
|
287
|
+
| `KWEAVER_MF_MODEL_API_URL` | Optional override for mf-model-api (defaults from `KWEAVER_BASE_URL` + `/api/mf-model-api/v1`) |
|
|
259
288
|
| `KWEAVER_BUSINESS_DOMAIN` | Business domain identifier |
|
|
260
289
|
| `KWEAVER_TOKEN` | Access token |
|
|
261
290
|
| `KWEAVER_TOKEN_SOURCE` | Internal sentinel set by the CLI when `--token` is passed; do not set manually |
|
package/README.zh.md
CHANGED
|
@@ -165,9 +165,12 @@ kweaver config show / list-bd / set-bd <value> # 业务域;show/list-bd 在
|
|
|
165
165
|
kweaver token
|
|
166
166
|
kweaver ds list/get/delete/tables/connect
|
|
167
167
|
kweaver dataflow list/run/runs/logs
|
|
168
|
+
kweaver model llm list/get/add/edit/delete/test/chat/--template
|
|
169
|
+
kweaver model small list/get/add/edit/delete/test/embeddings/rerank/--template
|
|
168
170
|
kweaver dataview list/find/get/query/delete
|
|
169
171
|
kweaver bkn list/get/stats/export/create/update/delete
|
|
170
172
|
kweaver bkn object-type list/get/create/update/delete/query/properties
|
|
173
|
+
kweaver bkn metric list/get/create/search/validate/update/delete/query/dry-run
|
|
171
174
|
kweaver bkn relation-type list/get/create/update/delete
|
|
172
175
|
kweaver bkn action-type list/query/execute
|
|
173
176
|
kweaver bkn subgraph
|
|
@@ -197,6 +200,27 @@ kweaver dataflow logs <dagId> <instanceId> --detail
|
|
|
197
200
|
|
|
198
201
|
`kweaver dataflow runs --since` 会按本地自然日过滤;如果参数无法被 `new Date(...)` 解析,CLI 会回退到最近 20 条运行记录。`kweaver dataflow logs` 默认输出摘要;加上 `--detail` 会打印带缩进的 `input` 和 `output` 载荷。
|
|
199
202
|
|
|
203
|
+
### 模型工厂 CLI 示例
|
|
204
|
+
|
|
205
|
+
- **mf-model-manager**:`/api/mf-model-manager/v1`(配置 CRUD、`test`)
|
|
206
|
+
- **mf-model-api**:`/api/mf-model-api/v1`(OpenAI 兼容 **`chat`**,以及小模型 **`embeddings` / `rerank`**)
|
|
207
|
+
|
|
208
|
+
离线模版:`model llm --template` / `model small --template` 输出内置 **`basic`** JSON(不调后端)。大模型 **`model_type`** 在后端仅为 **`llm`**(文本)、**`rlm`**(推理)、**`vu`**(视觉/多模态,接口缩写不是 `vlm`);列表过滤:`kweaver model llm list --type …`。详见 [`skills/kweaver-core/references/model.md`](../../skills/kweaver-core/references/model.md#llm-model-types)。
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
kweaver model llm list --limit 30
|
|
212
|
+
kweaver model llm list --type vu
|
|
213
|
+
kweaver model llm chat <model_id> -m "你好" --no-stream
|
|
214
|
+
kweaver model llm --template > ./llm.json
|
|
215
|
+
kweaver model small add --name my-emb --type embedding --batch-size 8 --max-tokens 512 --embedding-dim 1536 \
|
|
216
|
+
--model-config-file ./cfg.json
|
|
217
|
+
kweaver model small embeddings <model_id> -i "hello" -i "world"
|
|
218
|
+
kweaver model small rerank <model_id> -q "查询" -d "文档a" -d "文档b"
|
|
219
|
+
kweaver model small --template > ./embedding-model-config.json
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
完整说明见 [`skills/kweaver-core/references/model.md`](../../skills/kweaver-core/references/model.md) 与 `kweaver model --help`。
|
|
223
|
+
|
|
200
224
|
### Vega `sql` CLI 示例
|
|
201
225
|
|
|
202
226
|
对 Catalog 资源执行直连 SQL(`POST /api/vega-backend/v1/resources/query`)。SQL 中使用 **`{{<resource_id>}}`** 或 **`{{.<resource_id>}}`**(资源 id 来自 `vega resource list` / `get`),后端据此解析物理表与 connector。`--resource-type` 为目标数据源的连接器类型,可通过 `kweaver vega connector-type list` 查看。简单模式下请**用引号包住整个 `--query` 参数**,避免 shell 对花括号做特殊处理。
|
|
@@ -218,6 +242,8 @@ kweaver vega sql -d '{"resource_type":"mysql","query":"SELECT * FROM {{res-1}} L
|
|
|
218
242
|
| 变量 | 说明 |
|
|
219
243
|
|---|---|
|
|
220
244
|
| `KWEAVER_BASE_URL` | KWeaver 实例地址 |
|
|
245
|
+
| `KWEAVER_MF_MODEL_MANAGER_URL` | 可选:mf-model-manager API 根地址(默认 `KWEAVER_BASE_URL` + `/api/mf-model-manager/v1`) |
|
|
246
|
+
| `KWEAVER_MF_MODEL_API_URL` | 可选:mf-model-api 根地址(默认 `KWEAVER_BASE_URL` + `/api/mf-model-api/v1`) |
|
|
221
247
|
| `KWEAVER_BUSINESS_DOMAIN` | 业务域标识 |
|
|
222
248
|
| `KWEAVER_TOKEN` | 访问令牌 |
|
|
223
249
|
| `KWEAVER_TOKEN_SOURCE` | CLI 传入 `--token` 时由程序设置的内部标记;请勿手动设置 |
|
package/bin/kweaver.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// Wait for stdout and stderr to fully flush before terminating. Checking
|
|
4
|
+
// writableNeedDrain alone is not enough — a single console.log under the
|
|
5
|
+
// highWaterMark returns synchronously while bytes are still queued, so
|
|
6
|
+
// process.exit() can truncate piped output (~7-8KB) under spawn capture.
|
|
7
|
+
// Empty write + callback fires only after all preceding writes drain.
|
|
3
8
|
function exit(code) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
process.stderr.once("drain", done);
|
|
12
|
-
} else {
|
|
13
|
-
process.exit(code);
|
|
14
|
-
}
|
|
9
|
+
let pending = 2;
|
|
10
|
+
const done = () => {
|
|
11
|
+
pending -= 1;
|
|
12
|
+
if (pending === 0) process.exit(code);
|
|
13
|
+
};
|
|
14
|
+
process.stdout.write("", done);
|
|
15
|
+
process.stderr.write("", done);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
import("../dist/cli.js").then(({ run }) => {
|
|
@@ -14,6 +14,7 @@ export interface BknBackendBaseOptions {
|
|
|
14
14
|
export interface BknBackendKnOptions extends BknBackendBaseOptions {
|
|
15
15
|
knId: string;
|
|
16
16
|
}
|
|
17
|
+
export declare function knUrl(baseUrl: string, knId: string, path: string): string;
|
|
17
18
|
export interface ConceptGroupOptions extends BknBackendKnOptions {
|
|
18
19
|
cgId: string;
|
|
19
20
|
}
|
package/dist/api/bkn-backend.js
CHANGED
|
@@ -20,7 +20,7 @@ export async function uploadBkn(options) {
|
|
|
20
20
|
return body;
|
|
21
21
|
}
|
|
22
22
|
const BKN_BASE = "/api/bkn-backend/v1";
|
|
23
|
-
function knUrl(baseUrl, knId, path) {
|
|
23
|
+
export function knUrl(baseUrl, knId, path) {
|
|
24
24
|
const base = baseUrl.replace(/\/+$/, "");
|
|
25
25
|
return `${base}${BKN_BASE}/knowledge-networks/${encodeURIComponent(knId)}/${path}`;
|
|
26
26
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { BknBackendKnOptions } from "./bkn-backend.js";
|
|
2
|
+
export interface ListMetricsOptions extends BknBackendKnOptions {
|
|
3
|
+
namePattern?: string;
|
|
4
|
+
sort?: "update_time" | "name";
|
|
5
|
+
direction?: "asc" | "desc";
|
|
6
|
+
offset?: number;
|
|
7
|
+
limit?: number;
|
|
8
|
+
tag?: string;
|
|
9
|
+
groupId?: string;
|
|
10
|
+
branch?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function listMetrics(options: ListMetricsOptions): Promise<string>;
|
|
13
|
+
export interface CreateMetricsOptions extends BknBackendKnOptions {
|
|
14
|
+
body: string;
|
|
15
|
+
branch?: string;
|
|
16
|
+
strictMode?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function createMetrics(options: CreateMetricsOptions): Promise<string>;
|
|
19
|
+
export interface SearchMetricsOptions extends BknBackendKnOptions {
|
|
20
|
+
body: string;
|
|
21
|
+
branch?: string;
|
|
22
|
+
strictMode?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function searchMetrics(options: SearchMetricsOptions): Promise<string>;
|
|
25
|
+
export interface ValidateMetricsOptions extends BknBackendKnOptions {
|
|
26
|
+
body: string;
|
|
27
|
+
branch?: string;
|
|
28
|
+
strictMode?: boolean;
|
|
29
|
+
importMode?: "normal" | "ignore" | "overwrite";
|
|
30
|
+
}
|
|
31
|
+
export declare function validateMetrics(options: ValidateMetricsOptions): Promise<string>;
|
|
32
|
+
export interface GetMetricOptions extends BknBackendKnOptions {
|
|
33
|
+
metricId: string;
|
|
34
|
+
branch?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function getMetric(options: GetMetricOptions): Promise<string>;
|
|
37
|
+
export interface UpdateMetricOptions extends BknBackendKnOptions {
|
|
38
|
+
metricId: string;
|
|
39
|
+
body: string;
|
|
40
|
+
branch?: string;
|
|
41
|
+
strictMode?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare function updateMetric(options: UpdateMetricOptions): Promise<string>;
|
|
44
|
+
export interface DeleteMetricOptions extends BknBackendKnOptions {
|
|
45
|
+
metricId: string;
|
|
46
|
+
branch?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function deleteMetric(options: DeleteMetricOptions): Promise<string>;
|
|
49
|
+
export interface GetMetricsByIdsOptions extends BknBackendKnOptions {
|
|
50
|
+
/** Comma-separated metric IDs in a single path segment. */
|
|
51
|
+
metricIds: string;
|
|
52
|
+
branch?: string;
|
|
53
|
+
}
|
|
54
|
+
export declare function getMetrics(options: GetMetricsByIdsOptions): Promise<string>;
|
|
55
|
+
export interface DeleteMetricsByIdsOptions extends BknBackendKnOptions {
|
|
56
|
+
metricIds: string;
|
|
57
|
+
branch?: string;
|
|
58
|
+
}
|
|
59
|
+
export declare function deleteMetrics(options: DeleteMetricsByIdsOptions): Promise<string>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { HttpError } from "../utils/http.js";
|
|
2
|
+
import { buildHeaders } from "./headers.js";
|
|
3
|
+
import { knUrl } from "./bkn-backend.js";
|
|
4
|
+
async function readTextOrThrow(response) {
|
|
5
|
+
const text = await response.text();
|
|
6
|
+
if (!response.ok) {
|
|
7
|
+
throw new HttpError(response.status, response.statusText, text);
|
|
8
|
+
}
|
|
9
|
+
return text;
|
|
10
|
+
}
|
|
11
|
+
function appendSearchParams(url, entries) {
|
|
12
|
+
for (const [k, v] of entries) {
|
|
13
|
+
if (v === undefined)
|
|
14
|
+
continue;
|
|
15
|
+
url.searchParams.set(k, String(v));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function listMetrics(options) {
|
|
19
|
+
const { baseUrl, accessToken, knId, businessDomain = "bd_public", namePattern, sort, direction, offset, limit, tag, groupId, branch, } = options;
|
|
20
|
+
const url = new URL(knUrl(baseUrl, knId, "metrics"));
|
|
21
|
+
appendSearchParams(url, [
|
|
22
|
+
["name_pattern", namePattern],
|
|
23
|
+
["sort", sort],
|
|
24
|
+
["direction", direction],
|
|
25
|
+
["offset", offset],
|
|
26
|
+
["limit", limit],
|
|
27
|
+
["tag", tag],
|
|
28
|
+
["group_id", groupId],
|
|
29
|
+
["branch", branch],
|
|
30
|
+
]);
|
|
31
|
+
const response = await fetch(url.toString(), {
|
|
32
|
+
method: "GET",
|
|
33
|
+
headers: buildHeaders(accessToken, businessDomain),
|
|
34
|
+
});
|
|
35
|
+
return readTextOrThrow(response);
|
|
36
|
+
}
|
|
37
|
+
export async function createMetrics(options) {
|
|
38
|
+
return postWithMethodOverride(options, "POST");
|
|
39
|
+
}
|
|
40
|
+
export async function searchMetrics(options) {
|
|
41
|
+
return postWithMethodOverride(options, "GET");
|
|
42
|
+
}
|
|
43
|
+
async function postWithMethodOverride(options, methodOverride) {
|
|
44
|
+
const { baseUrl, accessToken, knId, body, businessDomain = "bd_public", branch, strictMode } = options;
|
|
45
|
+
const url = new URL(knUrl(baseUrl, knId, "metrics"));
|
|
46
|
+
appendSearchParams(url, [
|
|
47
|
+
["branch", branch],
|
|
48
|
+
["strict_mode", strictMode],
|
|
49
|
+
]);
|
|
50
|
+
const response = await fetch(url.toString(), {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
...buildHeaders(accessToken, businessDomain),
|
|
54
|
+
"content-type": "application/json",
|
|
55
|
+
"X-HTTP-Method-Override": methodOverride,
|
|
56
|
+
},
|
|
57
|
+
body,
|
|
58
|
+
});
|
|
59
|
+
return readTextOrThrow(response);
|
|
60
|
+
}
|
|
61
|
+
export async function validateMetrics(options) {
|
|
62
|
+
const { baseUrl, accessToken, knId, body, businessDomain = "bd_public", branch, strictMode, importMode } = options;
|
|
63
|
+
const url = new URL(knUrl(baseUrl, knId, "metrics/validation"));
|
|
64
|
+
appendSearchParams(url, [
|
|
65
|
+
["branch", branch],
|
|
66
|
+
["strict_mode", strictMode],
|
|
67
|
+
["import_mode", importMode],
|
|
68
|
+
]);
|
|
69
|
+
const response = await fetch(url.toString(), {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: { ...buildHeaders(accessToken, businessDomain), "content-type": "application/json" },
|
|
72
|
+
body,
|
|
73
|
+
});
|
|
74
|
+
return readTextOrThrow(response);
|
|
75
|
+
}
|
|
76
|
+
export async function getMetric(options) {
|
|
77
|
+
const { baseUrl, accessToken, knId, metricId, businessDomain = "bd_public", branch } = options;
|
|
78
|
+
const url = new URL(knUrl(baseUrl, knId, `metrics/${encodeURIComponent(metricId)}`));
|
|
79
|
+
appendSearchParams(url, [["branch", branch]]);
|
|
80
|
+
const response = await fetch(url.toString(), {
|
|
81
|
+
method: "GET",
|
|
82
|
+
headers: buildHeaders(accessToken, businessDomain),
|
|
83
|
+
});
|
|
84
|
+
return readTextOrThrow(response);
|
|
85
|
+
}
|
|
86
|
+
export async function updateMetric(options) {
|
|
87
|
+
const { baseUrl, accessToken, knId, metricId, body, businessDomain = "bd_public", branch, strictMode } = options;
|
|
88
|
+
const url = new URL(knUrl(baseUrl, knId, `metrics/${encodeURIComponent(metricId)}`));
|
|
89
|
+
appendSearchParams(url, [
|
|
90
|
+
["branch", branch],
|
|
91
|
+
["strict_mode", strictMode],
|
|
92
|
+
]);
|
|
93
|
+
const response = await fetch(url.toString(), {
|
|
94
|
+
method: "PUT",
|
|
95
|
+
headers: { ...buildHeaders(accessToken, businessDomain), "content-type": "application/json" },
|
|
96
|
+
body,
|
|
97
|
+
});
|
|
98
|
+
return readTextOrThrow(response);
|
|
99
|
+
}
|
|
100
|
+
export async function deleteMetric(options) {
|
|
101
|
+
const { baseUrl, accessToken, knId, metricId, businessDomain = "bd_public", branch } = options;
|
|
102
|
+
const url = new URL(knUrl(baseUrl, knId, `metrics/${encodeURIComponent(metricId)}`));
|
|
103
|
+
appendSearchParams(url, [["branch", branch]]);
|
|
104
|
+
const response = await fetch(url.toString(), {
|
|
105
|
+
method: "DELETE",
|
|
106
|
+
headers: buildHeaders(accessToken, businessDomain),
|
|
107
|
+
});
|
|
108
|
+
return readTextOrThrow(response);
|
|
109
|
+
}
|
|
110
|
+
export async function getMetrics(options) {
|
|
111
|
+
const { baseUrl, accessToken, knId, metricIds, businessDomain = "bd_public", branch } = options;
|
|
112
|
+
const url = new URL(knUrl(baseUrl, knId, `metrics/${metricIds}`));
|
|
113
|
+
appendSearchParams(url, [["branch", branch]]);
|
|
114
|
+
const response = await fetch(url.toString(), {
|
|
115
|
+
method: "GET",
|
|
116
|
+
headers: buildHeaders(accessToken, businessDomain),
|
|
117
|
+
});
|
|
118
|
+
return readTextOrThrow(response);
|
|
119
|
+
}
|
|
120
|
+
export async function deleteMetrics(options) {
|
|
121
|
+
const { baseUrl, accessToken, knId, metricIds, businessDomain = "bd_public", branch } = options;
|
|
122
|
+
const url = new URL(knUrl(baseUrl, knId, `metrics/${metricIds}`));
|
|
123
|
+
appendSearchParams(url, [["branch", branch]]);
|
|
124
|
+
const response = await fetch(url.toString(), {
|
|
125
|
+
method: "DELETE",
|
|
126
|
+
headers: buildHeaders(accessToken, businessDomain),
|
|
127
|
+
});
|
|
128
|
+
return readTextOrThrow(response);
|
|
129
|
+
}
|
|
@@ -16,16 +16,61 @@ export interface ListMessagesOptions {
|
|
|
16
16
|
export interface GetTracesOptions {
|
|
17
17
|
baseUrl: string;
|
|
18
18
|
accessToken: string;
|
|
19
|
-
agentId: string;
|
|
20
19
|
conversationId: string;
|
|
20
|
+
/** Deprecated. trace-ai keys spans by conversation_id only; kept for CLI compatibility. */
|
|
21
|
+
agentId?: string;
|
|
21
22
|
businessDomain?: string;
|
|
23
|
+
/** Max distinct traceIds to fetch for one conversation. Default 100. */
|
|
24
|
+
maxTraceIds?: number;
|
|
25
|
+
/** Max spans returned by the second hop. Default 2000. */
|
|
26
|
+
maxSpans?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface TraceSpan {
|
|
29
|
+
traceId: string;
|
|
30
|
+
spanId: string;
|
|
31
|
+
parentSpanId?: string;
|
|
32
|
+
name: string;
|
|
33
|
+
kind?: string;
|
|
34
|
+
startTime: string;
|
|
35
|
+
endTime?: string;
|
|
36
|
+
durationInNanos?: number;
|
|
37
|
+
status?: {
|
|
38
|
+
code?: string | number;
|
|
39
|
+
message?: string;
|
|
40
|
+
};
|
|
41
|
+
serviceName?: string;
|
|
42
|
+
attributes?: Record<string, unknown>;
|
|
43
|
+
events?: Array<{
|
|
44
|
+
name?: string;
|
|
45
|
+
time?: string;
|
|
46
|
+
attributes?: Record<string, unknown>;
|
|
47
|
+
}>;
|
|
48
|
+
/** Raw _source object from the trace store, kept verbatim for formatters that need extra fields. */
|
|
49
|
+
raw?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
export interface TracesByConversationResult {
|
|
52
|
+
conversationId: string;
|
|
53
|
+
traceIds: string[];
|
|
54
|
+
spans: TraceSpan[];
|
|
55
|
+
/** True if the traceId aggregation hit its bucket cap and may be incomplete. */
|
|
56
|
+
truncated: boolean;
|
|
22
57
|
}
|
|
23
58
|
/**
|
|
24
59
|
* List conversations for an agent.
|
|
25
60
|
* Returns empty array on 404 (endpoint may not be available in all deployments).
|
|
26
61
|
*/
|
|
27
62
|
export declare function listConversations(opts: ListConversationsOptions): Promise<string>;
|
|
28
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Fetch all spans belonging to a conversation via trace-ai's OpenSearch-style _search.
|
|
65
|
+
*
|
|
66
|
+
* Two-hop strategy (see kweaver-sdk#115):
|
|
67
|
+
* 1. Aggregate traceIds for spans tagged with gen_ai.conversation.id == conversationId.
|
|
68
|
+
* 2. Fetch every span sharing those traceIds — this recovers pipeline spans
|
|
69
|
+
* (HTTP entry, internal RPCs, prompt-build) that are not tagged with conversation_id.
|
|
70
|
+
*
|
|
71
|
+
* Returns a structured result; callers can format as tree/perf/evidence views or stringify.
|
|
72
|
+
*/
|
|
73
|
+
export declare function getTracesByConversation(opts: GetTracesOptions): Promise<TracesByConversationResult>;
|
|
29
74
|
/**
|
|
30
75
|
* List messages for a conversation.
|
|
31
76
|
* Returns empty array on 404 (endpoint may not be available in all deployments).
|
|
@@ -29,33 +29,129 @@ export async function listConversations(opts) {
|
|
|
29
29
|
}
|
|
30
30
|
return body || "[]";
|
|
31
31
|
}
|
|
32
|
-
function
|
|
32
|
+
function buildTraceSearchUrl(baseUrl) {
|
|
33
33
|
const base = baseUrl.replace(/\/+$/, "");
|
|
34
|
-
return `${base}/api/agent-
|
|
34
|
+
return `${base}/api/agent-observability/v1/traces/_search`;
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
const url = buildTracesUrl(baseUrl, agentId, conversationId);
|
|
39
|
-
const response = await fetch(url, {
|
|
36
|
+
async function postTraceSearch(baseUrl, accessToken, businessDomain, body) {
|
|
37
|
+
const response = await fetch(buildTraceSearchUrl(baseUrl), {
|
|
40
38
|
method: "POST",
|
|
41
39
|
headers: {
|
|
42
40
|
"Content-Type": "application/json",
|
|
43
|
-
accept: "application/json",
|
|
44
41
|
...buildHeaders(accessToken, businessDomain),
|
|
45
42
|
},
|
|
46
|
-
body: JSON.stringify(
|
|
47
|
-
agent_id: agentId,
|
|
48
|
-
start_time: 1,
|
|
49
|
-
end_time: Date.now() + 86400000,
|
|
50
|
-
page: 1,
|
|
51
|
-
size: 50,
|
|
52
|
-
}),
|
|
43
|
+
body: JSON.stringify(body),
|
|
53
44
|
});
|
|
54
|
-
const
|
|
45
|
+
const text = await response.text();
|
|
55
46
|
if (!response.ok) {
|
|
56
|
-
throw new Error(`getTracesByConversation failed: HTTP ${response.status} ${response.statusText} — ${
|
|
47
|
+
throw new Error(`getTracesByConversation failed: HTTP ${response.status} ${response.statusText} — ${text.slice(0, 200)}`);
|
|
57
48
|
}
|
|
58
|
-
|
|
49
|
+
if (!text)
|
|
50
|
+
return {};
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(text);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
throw new Error(`getTracesByConversation: invalid JSON response — ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function computeDurationNanos(source) {
|
|
59
|
+
if (typeof source.durationInNanos === "number")
|
|
60
|
+
return source.durationInNanos;
|
|
61
|
+
if (typeof source.duration === "number")
|
|
62
|
+
return source.duration;
|
|
63
|
+
const startTime = source.startTime ?? source.start_time;
|
|
64
|
+
const endTime = source.endTime ?? source.end_time;
|
|
65
|
+
if (typeof startTime !== "string" || typeof endTime !== "string")
|
|
66
|
+
return undefined;
|
|
67
|
+
const startMs = Date.parse(startTime);
|
|
68
|
+
const endMs = Date.parse(endTime);
|
|
69
|
+
if (Number.isNaN(startMs) || Number.isNaN(endMs))
|
|
70
|
+
return undefined;
|
|
71
|
+
return Math.max(0, (endMs - startMs) * 1e6);
|
|
72
|
+
}
|
|
73
|
+
function extractServiceName(source) {
|
|
74
|
+
if (typeof source.serviceName === "string")
|
|
75
|
+
return source.serviceName;
|
|
76
|
+
if (typeof source.service_name === "string")
|
|
77
|
+
return source.service_name;
|
|
78
|
+
const attributes = source.attributes;
|
|
79
|
+
if (typeof attributes?.["service.name"] === "string")
|
|
80
|
+
return attributes["service.name"];
|
|
81
|
+
const resource = source.resource;
|
|
82
|
+
if (typeof resource?.["service.name"] === "string")
|
|
83
|
+
return resource["service.name"];
|
|
84
|
+
const resourceService = resource?.service?.name;
|
|
85
|
+
if (typeof resourceService === "string")
|
|
86
|
+
return resourceService;
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
function normalizeSpan(source) {
|
|
90
|
+
const traceId = String(source.traceId ?? source.trace_id ?? "");
|
|
91
|
+
const spanId = String(source.spanId ?? source.span_id ?? "");
|
|
92
|
+
if (!traceId || !spanId)
|
|
93
|
+
return null;
|
|
94
|
+
const parentRaw = source.parentSpanId ?? source.parent_span_id ?? source.parentSpanID;
|
|
95
|
+
const parentSpanId = parentRaw ? String(parentRaw) : undefined;
|
|
96
|
+
const status = source.status;
|
|
97
|
+
const attributes = source.attributes ?? undefined;
|
|
98
|
+
const events = source.events;
|
|
99
|
+
return {
|
|
100
|
+
traceId,
|
|
101
|
+
spanId,
|
|
102
|
+
parentSpanId: parentSpanId && parentSpanId !== "" && parentSpanId !== "0" ? parentSpanId : undefined,
|
|
103
|
+
name: String(source.name ?? ""),
|
|
104
|
+
kind: source.kind,
|
|
105
|
+
startTime: String(source.startTime ?? source.start_time ?? ""),
|
|
106
|
+
endTime: source.endTime ? String(source.endTime) : undefined,
|
|
107
|
+
durationInNanos: computeDurationNanos(source),
|
|
108
|
+
status,
|
|
109
|
+
serviceName: extractServiceName(source),
|
|
110
|
+
attributes,
|
|
111
|
+
events,
|
|
112
|
+
raw: source,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Fetch all spans belonging to a conversation via trace-ai's OpenSearch-style _search.
|
|
117
|
+
*
|
|
118
|
+
* Two-hop strategy (see kweaver-sdk#115):
|
|
119
|
+
* 1. Aggregate traceIds for spans tagged with gen_ai.conversation.id == conversationId.
|
|
120
|
+
* 2. Fetch every span sharing those traceIds — this recovers pipeline spans
|
|
121
|
+
* (HTTP entry, internal RPCs, prompt-build) that are not tagged with conversation_id.
|
|
122
|
+
*
|
|
123
|
+
* Returns a structured result; callers can format as tree/perf/evidence views or stringify.
|
|
124
|
+
*/
|
|
125
|
+
export async function getTracesByConversation(opts) {
|
|
126
|
+
const { baseUrl, accessToken, conversationId, businessDomain = "bd_public", maxTraceIds = 100, maxSpans = 2000, } = opts;
|
|
127
|
+
const aggResult = await postTraceSearch(baseUrl, accessToken, businessDomain, {
|
|
128
|
+
size: 0,
|
|
129
|
+
query: { term: { "attributes.gen_ai.conversation.id.keyword": conversationId } },
|
|
130
|
+
aggs: { tids: { terms: { field: "traceId.keyword", size: maxTraceIds } } },
|
|
131
|
+
});
|
|
132
|
+
const aggregations = aggResult.aggregations;
|
|
133
|
+
const tids = aggregations?.tids;
|
|
134
|
+
const buckets = tids?.buckets ?? [];
|
|
135
|
+
const truncated = (tids?.sum_other_doc_count ?? 0) > 0;
|
|
136
|
+
const traceIds = buckets.map((b) => b.key).filter((k) => typeof k === "string" && k.length > 0);
|
|
137
|
+
if (traceIds.length === 0) {
|
|
138
|
+
return { conversationId, traceIds: [], spans: [], truncated: false };
|
|
139
|
+
}
|
|
140
|
+
const spansResult = await postTraceSearch(baseUrl, accessToken, businessDomain, {
|
|
141
|
+
size: maxSpans,
|
|
142
|
+
query: { terms: { "traceId.keyword": traceIds } },
|
|
143
|
+
sort: [{ startTime: "asc" }],
|
|
144
|
+
});
|
|
145
|
+
const hits = spansResult.hits?.hits ?? [];
|
|
146
|
+
const spans = [];
|
|
147
|
+
for (const hit of hits) {
|
|
148
|
+
if (!hit._source)
|
|
149
|
+
continue;
|
|
150
|
+
const span = normalizeSpan(hit._source);
|
|
151
|
+
if (span)
|
|
152
|
+
spans.push(span);
|
|
153
|
+
}
|
|
154
|
+
return { conversationId, traceIds, spans, truncated };
|
|
59
155
|
}
|
|
60
156
|
/**
|
|
61
157
|
* List messages for a conversation.
|
package/dist/api/datasources.js
CHANGED
|
@@ -162,15 +162,52 @@ export async function listTablesWithColumns(options) {
|
|
|
162
162
|
const colData = (await colResponse.json());
|
|
163
163
|
columnsRaw = Array.isArray(colData) ? colData : (colData.entries ?? colData.data ?? []);
|
|
164
164
|
}
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
165
|
+
const tablePkArray = extractPrimaryKeys(t);
|
|
166
|
+
const columns = columnsRaw.map((c) => {
|
|
167
|
+
const name = String(c.name ?? c.field_name ?? "");
|
|
168
|
+
const flagged = isColumnPrimaryKey(c) || tablePkArray.includes(name);
|
|
169
|
+
return {
|
|
170
|
+
name,
|
|
171
|
+
type: String(c.type ?? c.field_type ?? "varchar"),
|
|
172
|
+
comment: typeof c.comment === "string" ? c.comment : undefined,
|
|
173
|
+
...(flagged ? { isPrimaryKey: true } : {}),
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
// Reconcile: if backend gave per-column flags but no table-level array,
|
|
177
|
+
// synthesize one so downstream callers have a single PK source of truth.
|
|
178
|
+
const synthesizedPks = tablePkArray.length > 0
|
|
179
|
+
? tablePkArray
|
|
180
|
+
: columns.filter((c) => c.isPrimaryKey).map((c) => c.name);
|
|
181
|
+
tables.push({
|
|
182
|
+
name: tableName,
|
|
183
|
+
columns,
|
|
184
|
+
...(synthesizedPks.length > 0 ? { primaryKeys: synthesizedPks } : {}),
|
|
185
|
+
});
|
|
171
186
|
}
|
|
172
187
|
return JSON.stringify(tables);
|
|
173
188
|
}
|
|
189
|
+
// Two PK metadata shapes are recognized — both confirmed conventions:
|
|
190
|
+
// - per-column `is_primary_key: true` (data-connection metadata standard)
|
|
191
|
+
// - per-column `column_key === "PRI"` (MySQL INFORMATION_SCHEMA pass-through)
|
|
192
|
+
// - table-level `primary_keys: string[]` (composite-PK carrier)
|
|
193
|
+
// Other plausible spellings (camelCase, singular keys, SQLite `pk` integer) are
|
|
194
|
+
// intentionally NOT recognized here — adding them speculatively risks false
|
|
195
|
+
// matches and creates code paths the test suite can't pin down. Extend only when
|
|
196
|
+
// a real backend response demonstrates the need.
|
|
197
|
+
function isColumnPrimaryKey(col) {
|
|
198
|
+
if (col.is_primary_key === true)
|
|
199
|
+
return true;
|
|
200
|
+
if (typeof col.column_key === "string" && col.column_key.toUpperCase() === "PRI")
|
|
201
|
+
return true;
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
function extractPrimaryKeys(table) {
|
|
205
|
+
const arr = table.primary_keys;
|
|
206
|
+
if (Array.isArray(arr)) {
|
|
207
|
+
return arr.filter((x) => typeof x === "string");
|
|
208
|
+
}
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
174
211
|
export async function scanMetadata(options) {
|
|
175
212
|
const { baseUrl, accessToken, id, dsType = "mysql", businessDomain = "bd_public", } = options;
|
|
176
213
|
const base = baseUrl.replace(/\/+$/, "");
|