@littlebigbrain/mcp 0.16.0 → 0.17.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/README.md +71 -5
- package/dist/files-tool.d.ts +2 -0
- package/dist/files-tool.js +7 -2
- package/dist/records-write.d.ts +101 -0
- package/dist/records-write.js +150 -0
- package/dist/reviews-tool.js +2 -2
- package/dist/tool-contracts.js +36 -0
- package/dist/tool-runtime.js +26 -14
- package/dist/tools.js +139 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -164,7 +164,7 @@ its original identifiers; inspect it with SPARQL when choosing query predicates.
|
|
|
164
164
|
| `lbb_workflows_manage` | Create a workflow from a starter workflow on a class (a developer workflow with its workflow type, owned outputs and batch), ask it to run now, or pause and resume a developer workflow. Define a workflow with triggers of your worker (shards, routing field, window) and run it now. |
|
|
165
165
|
| `lbb_workflows_delete` | Delete a triggered workflow, or a workflow with triggers by its name. What it wrote stays. |
|
|
166
166
|
| `lbb_files` | List the files of a graph, all or by parse status or a part of the name, and read the parsed pages of one: each line with its id, its box on the page and its text. |
|
|
167
|
-
| `lbb_commit` | Write
|
|
167
|
+
| `lbb_commit` | Write legacy JSON facts, explicitly commit versioned records, retract facts, or record search feedback. |
|
|
168
168
|
| `lbb_configure` | Define record types and relationships, list and apply ontology starters (CRM, documents, work), publish validation rules, or keep the graph's notes and worked examples for questions in plain words (`get_rewrite_profile`, `set_rewrite_profile`). |
|
|
169
169
|
| `lbb_evals` | Label query results and check whether later queries return the expected answers. Read the model checks of a graph, and agree with or correct a check that the user confirmed. |
|
|
170
170
|
| `lbb_model_choice` | Read which model each use runs (question answers, query routing, search rerank, ontology fit, eval labels), and the trials that test other models against the graph's ground truth. |
|
|
@@ -175,13 +175,79 @@ See [search by meaning](https://docs.littlebigbrain.com/guides/search-by-meaning
|
|
|
175
175
|
for embedding setup. To define constraints with the Shapes Constraint Language
|
|
176
176
|
(SHACL), see [query and validation](https://docs.littlebigbrain.com/guides/sparql-and-shacl/).
|
|
177
177
|
|
|
178
|
-
|
|
179
|
-
through `lbb_rdf`
|
|
180
|
-
the
|
|
181
|
-
support additive `INSERT DATA`;
|
|
178
|
+
Legacy `mode=facts` follows the graph's existing first-write restrictions; a
|
|
179
|
+
graph first written through `lbb_rdf` refuses that legacy JSON path. Explicit
|
|
180
|
+
`mode=records` uses the separate versioned contract described below and does not
|
|
181
|
+
activate or convert an existing graph. RDF updates support additive `INSERT DATA`;
|
|
182
182
|
see the [RDF guide](https://docs.littlebigbrain.com/guides/load-rdf/) for format and
|
|
183
183
|
update limits.
|
|
184
184
|
|
|
185
|
+
|
|
186
|
+
## Versioned records commits
|
|
187
|
+
|
|
188
|
+
Use explicit `lbb_commit` `mode: "records"` with a nested `records` body whose
|
|
189
|
+
`version` is `records/1`. The server must recognize `records_commit_v1` and the
|
|
190
|
+
graph must already have qualified support; protocol recognition does not
|
|
191
|
+
activate a graph or offer a graph-type choice. Ordinary/missing/unactivated
|
|
192
|
+
graphs keep their typed refusal. There is no creation, conversion or activation
|
|
193
|
+
option here. This requires the matching client release containing
|
|
194
|
+
`records.commit`; publish that client before releasing this MCP update.
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"mode": "records",
|
|
199
|
+
"records": {
|
|
200
|
+
"version": "records/1",
|
|
201
|
+
"entities": [{
|
|
202
|
+
"id": "https://example.org/service/auth",
|
|
203
|
+
"add": {"https://example.org/title": {
|
|
204
|
+
"@value": "Auth Service",
|
|
205
|
+
"@type": "http://www.w3.org/2001/XMLSchema#string"
|
|
206
|
+
}}
|
|
207
|
+
}]
|
|
208
|
+
},
|
|
209
|
+
"idempotency_key": "auth-title:v1"
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The complete object is forwarded without dropping unknown fields, lowercasing
|
|
214
|
+
IRIs, coercing literals or resolving schema keys locally. The server validates
|
|
215
|
+
it strictly. Do not mix legacy facts/embeddings/properties/retract/feedback or
|
|
216
|
+
`dry_run`/backfill arguments into records mode, even with empty or false values.
|
|
217
|
+
`expected_graph_epoch` adds an exact decimal incarnation guard.
|
|
218
|
+
|
|
219
|
+
An explicit idempotency key wins. When omitted, MCP derives a stable content key
|
|
220
|
+
from the original body, graph and epoch guard. The snapshot and key are fixed
|
|
221
|
+
before approval and transport retries. Keep the same original body/key after a
|
|
222
|
+
lost reply; do not use a new key to investigate an unknown result. An exhausted
|
|
223
|
+
POST transport failure or malformed success returns `record_outcome_unknown`,
|
|
224
|
+
`outcome: "unknown"` and the original `idempotency_key`, without legacy fallback.
|
|
225
|
+
A capability failure sends no POST and cannot become a write acceptance.
|
|
226
|
+
Typed server errors keep their original code/status and also retain the recovery
|
|
227
|
+
key; the tool does not invent an acceptance or refusal verdict for them.
|
|
228
|
+
|
|
229
|
+
Edits that may remove support use the existing destructive approval policy:
|
|
230
|
+
`set`, `unset`, `remove`, `delete`, `replace_scope`, or a relationship with
|
|
231
|
+
`asserted: false`. A set may replace an existing field. The question names the
|
|
232
|
+
graph and source owner/scope; refusal sends no API request. Add-only work needs
|
|
233
|
+
no destructive approval. The existing person-controlled
|
|
234
|
+
`LBB_MCP_DESTRUCTIVE=allow` setting applies; no body field can bypass approval.
|
|
235
|
+
The server remains responsible for current authority and reserved source owners.
|
|
236
|
+
|
|
237
|
+
Success returns the complete original receipt as compact JSON and structured
|
|
238
|
+
content. Resolved IDs, schema, projection counts, no-op and replay are never
|
|
239
|
+
compacted. Support can change with zero visible additions/removals. Acceptance,
|
|
240
|
+
publication and class embedding readiness are separate observations. MCP keeps
|
|
241
|
+
its 80,000 UTF-8 byte output ceiling: an unexpected oversized valid receipt reports
|
|
242
|
+
`record_accepted_output_limit` with `outcome: "accepted"`, sequence and key so the
|
|
243
|
+
full receipt can be recovered through the SDK/HTTP API. It never calls that
|
|
244
|
+
known acceptance a failed mutation or unknown outcome. Oversized error messages
|
|
245
|
+
are explicitly flagged while preserving ordinary typed codes/status.
|
|
246
|
+
|
|
247
|
+
The records body is bounded to one MiB and its escaped recovery key to 8 KiB at
|
|
248
|
+
the MCP boundary; the server's smaller total-request/idempotency limits still
|
|
249
|
+
apply. These limits do not expand any graph/source budget or enable admin erasure.
|
|
250
|
+
|
|
185
251
|
## Pages and saved queries
|
|
186
252
|
|
|
187
253
|
When a result includes `next`, pass those arguments to the same tool to read the
|
package/dist/files-tool.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export interface PageLinesAnswer {
|
|
|
33
33
|
height: number;
|
|
34
34
|
lines_total: number;
|
|
35
35
|
lines_matching: number;
|
|
36
|
+
/** The page is mostly image and has fewer than 16 characters of text: the parse read almost none, and OCR is not built. */
|
|
37
|
+
ocr_required?: true;
|
|
36
38
|
lines: string[];
|
|
37
39
|
}>;
|
|
38
40
|
shown: number;
|
package/dist/files-tool.js
CHANGED
|
@@ -66,6 +66,7 @@ export function pageLines(pages, window) {
|
|
|
66
66
|
height: page.height,
|
|
67
67
|
lines_total: page.lines.length,
|
|
68
68
|
lines_matching: matching,
|
|
69
|
+
...(page.ocr_required ? { ocr_required: true } : {}),
|
|
69
70
|
lines: shown,
|
|
70
71
|
});
|
|
71
72
|
if (stoppedAt !== undefined) {
|
|
@@ -114,7 +115,7 @@ async function fileSha(target, args) {
|
|
|
114
115
|
}
|
|
115
116
|
export function registerFilesTool(host, client) {
|
|
116
117
|
host.register("lbb_files", {
|
|
117
|
-
description: "Files of the graph, read only. A file is a document the graph holds (its bytes and a Document entity)
|
|
118
|
+
description: "Files of the graph, read only. A file is a document the graph holds (its bytes and a Document entity). A file a workflow run wrote (a generated file, with an origin and an expires_at) has no Document and no Document IRI. It expires unless a person keeps it, so the list shows it only after that, and it is never evidence for a fact. The documents.parse workflow (lbb_workflows) reads the text layer of each PDF into pages with every line and its box. list shows 50 files a call, newest upload first, with their sha256, name, size, Document IRI and parse (status parsed, unsupported or failed, page_count); when more follow, next names the after of the next call (with the same filters). parse_status keeps the files of one parse status; waiting is a file not parsed yet. name keeps the files whose name holds a text, in any case. A filtered list reads the Documents of the published generation and names its commit (as_of_commit_seq). A file uploaded, renamed or parsed a moment ago can be missing from it until publication. pages reads parsed pages of one file (sha256, or name for an exact file name): from is the first 0-based page index (the page of a region and of a DocEntry or DocMention), to the page after the last one (default from+1). Each line comes as `<line id> [x0,y0,x1,y1] <text>`: the id is p<page number>-l<line number>, both from 1, and the box is normalized to the page (0 to 1, origin top left). contains keeps the lines that hold a text; max_lines caps the lines (default 200). When lines were left out, next names the from and line_offset of the next call. A file not parsed yet answers file_not_parsed.",
|
|
118
119
|
inputSchema: {
|
|
119
120
|
action: z.enum(["list", "pages"]),
|
|
120
121
|
after: z
|
|
@@ -212,8 +213,12 @@ export function registerFilesTool(host, client) {
|
|
|
212
213
|
const range = answer.to - answer.from > 1
|
|
213
214
|
? `pages ${answer.from} to ${answer.to - 1}`
|
|
214
215
|
: `page ${answer.from}`;
|
|
216
|
+
const scanned = lines.pages.filter((entry) => entry.ocr_required).length;
|
|
217
|
+
const needsOcr = scanned
|
|
218
|
+
? `; ${scanned === 1 ? "1 page needs" : `${scanned} pages need`} OCR, almost no text was read`
|
|
219
|
+
: "";
|
|
215
220
|
return toolResult({
|
|
216
|
-
summary: `${label}, ${range} of ${answer.page_count} (0-based): ${lines.shown} lines${lines.next ? ", more after next" : ""}`,
|
|
221
|
+
summary: `${label}, ${range} of ${answer.page_count} (0-based): ${lines.shown} lines${needsOcr}${lines.next ? ", more after next" : ""}`,
|
|
217
222
|
data: {
|
|
218
223
|
sha256: answer.sha256,
|
|
219
224
|
parser: answer.parser,
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { type LbbClient } from "@littlebigbrain/client";
|
|
2
|
+
import { type ToolExtra, type ToolHost } from "./approval.js";
|
|
3
|
+
type Args = {
|
|
4
|
+
records?: Record<string, unknown>;
|
|
5
|
+
idempotencyKey?: string;
|
|
6
|
+
expectedGraphEpoch?: string;
|
|
7
|
+
graph?: string;
|
|
8
|
+
legacyInputs: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function commitRecordsTool(host: ToolHost, client: LbbClient, extra: ToolExtra, args: Args): Promise<{
|
|
11
|
+
structuredContent: {
|
|
12
|
+
error: {
|
|
13
|
+
idempotency_key: string;
|
|
14
|
+
type: string;
|
|
15
|
+
code: string;
|
|
16
|
+
message: string;
|
|
17
|
+
outcome: string;
|
|
18
|
+
param: null;
|
|
19
|
+
request_id: null;
|
|
20
|
+
doc_url: null;
|
|
21
|
+
status: null;
|
|
22
|
+
} | {
|
|
23
|
+
idempotency_key?: string | undefined;
|
|
24
|
+
type: string;
|
|
25
|
+
code: string;
|
|
26
|
+
message: string;
|
|
27
|
+
param: string | null;
|
|
28
|
+
request_id: string | null;
|
|
29
|
+
doc_url: string | null;
|
|
30
|
+
status: number;
|
|
31
|
+
outcome?: undefined;
|
|
32
|
+
} | {
|
|
33
|
+
idempotency_key?: string | undefined;
|
|
34
|
+
type: string;
|
|
35
|
+
code: string;
|
|
36
|
+
message: string;
|
|
37
|
+
param: null;
|
|
38
|
+
request_id: null;
|
|
39
|
+
doc_url: null;
|
|
40
|
+
status: null;
|
|
41
|
+
outcome?: undefined;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
content: {
|
|
45
|
+
type: "text";
|
|
46
|
+
text: string;
|
|
47
|
+
}[];
|
|
48
|
+
isError: true;
|
|
49
|
+
} | {
|
|
50
|
+
isError: true;
|
|
51
|
+
content: {
|
|
52
|
+
type: "text";
|
|
53
|
+
text: string;
|
|
54
|
+
}[];
|
|
55
|
+
structuredContent: {
|
|
56
|
+
error: {
|
|
57
|
+
idempotency_key?: string | undefined;
|
|
58
|
+
type: string;
|
|
59
|
+
code: string;
|
|
60
|
+
message: string;
|
|
61
|
+
message_truncated: boolean;
|
|
62
|
+
status: number | null;
|
|
63
|
+
param: string | null;
|
|
64
|
+
request_id: string | null;
|
|
65
|
+
doc_url: string | null;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
} | {
|
|
69
|
+
isError: true;
|
|
70
|
+
content: {
|
|
71
|
+
type: "text";
|
|
72
|
+
text: string;
|
|
73
|
+
}[];
|
|
74
|
+
structuredContent: {
|
|
75
|
+
error: {
|
|
76
|
+
type: string;
|
|
77
|
+
code: string;
|
|
78
|
+
message: string;
|
|
79
|
+
outcome: string;
|
|
80
|
+
idempotency_key: string;
|
|
81
|
+
epoch: string;
|
|
82
|
+
commit_seq: number;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
} | {
|
|
86
|
+
content: {
|
|
87
|
+
type: "text";
|
|
88
|
+
text: string;
|
|
89
|
+
}[];
|
|
90
|
+
structuredContent: {
|
|
91
|
+
summary: string;
|
|
92
|
+
data: {
|
|
93
|
+
idempotent_replay: boolean;
|
|
94
|
+
object: import("@littlebigbrain/client").components["schemas"]["RecordCommitObject"];
|
|
95
|
+
receipt: import("@littlebigbrain/client").components["schemas"]["RecordCommitReceipt"];
|
|
96
|
+
version: import("@littlebigbrain/client").components["schemas"]["RecordsVersion"];
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
isError?: undefined;
|
|
100
|
+
}>;
|
|
101
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/** Records-specific MCP framing; semantic admission belongs to the server. */
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { LbbCapabilityError, } from "@littlebigbrain/client";
|
|
4
|
+
import { graphLabel, oneLine, } from "./approval.js";
|
|
5
|
+
import { HARD_OUTPUT_CHARS } from "./tool-contracts.js";
|
|
6
|
+
import { contentHashKey, enrichError, errorResult, scoped, ToolError, } from "./tool-runtime.js";
|
|
7
|
+
const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8
|
+
const nonempty = (value) => isObject(value)
|
|
9
|
+
? Object.keys(value).length > 0
|
|
10
|
+
: Array.isArray(value) && value.length > 0;
|
|
11
|
+
function mayRemove(body) {
|
|
12
|
+
if (body.replace_scope === true)
|
|
13
|
+
return true;
|
|
14
|
+
for (const rows of [body.entities, body.relationships]) {
|
|
15
|
+
if (!Array.isArray(rows))
|
|
16
|
+
continue;
|
|
17
|
+
for (const row of rows) {
|
|
18
|
+
if (isObject(row) &&
|
|
19
|
+
(row.delete === true ||
|
|
20
|
+
row.asserted === false ||
|
|
21
|
+
nonempty(row.set) ||
|
|
22
|
+
nonempty(row.unset) ||
|
|
23
|
+
nonempty(row.remove)))
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
function approvalMessage(client, graph, body) {
|
|
30
|
+
const entities = Array.isArray(body.entities) ? body.entities.length : 0;
|
|
31
|
+
const relationships = Array.isArray(body.relationships)
|
|
32
|
+
? body.relationships.length
|
|
33
|
+
: 0;
|
|
34
|
+
const action = body.replace_scope === true
|
|
35
|
+
? "Replace the complete desired support"
|
|
36
|
+
: "Apply record edits that may replace or remove support";
|
|
37
|
+
return `${action} in ${graphLabel(client, graph)}, owner "${oneLine(body.owner ?? "application")}", scope "${oneLine(body.scope ?? "default")}" (${entities} entities, ${relationships} relationships)? Other owners' support is retained. This does not erase retained history.`;
|
|
38
|
+
}
|
|
39
|
+
function boundedError(value, key) {
|
|
40
|
+
const original = errorResult(value);
|
|
41
|
+
// A typed server error can also need receipt recovery. Keep its code/status
|
|
42
|
+
// unchanged, but retain the derived key rather than making callers guess it.
|
|
43
|
+
const identity = {
|
|
44
|
+
...original.structuredContent.error,
|
|
45
|
+
...(key !== undefined ? { idempotency_key: key } : {}),
|
|
46
|
+
};
|
|
47
|
+
const result = {
|
|
48
|
+
...original,
|
|
49
|
+
structuredContent: { error: identity },
|
|
50
|
+
content: [
|
|
51
|
+
{
|
|
52
|
+
type: "text",
|
|
53
|
+
text: JSON.stringify({ error: identity }, null, 2),
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
if (Buffer.byteLength(result.content[0].text, "utf8") <= HARD_OUTPUT_CHARS)
|
|
58
|
+
return result;
|
|
59
|
+
const source = result.structuredContent.error;
|
|
60
|
+
const short = (value, max = 512) => typeof value === "string" &&
|
|
61
|
+
Buffer.byteLength(JSON.stringify(value), "utf8") <= max
|
|
62
|
+
? value
|
|
63
|
+
: null;
|
|
64
|
+
const error = {
|
|
65
|
+
type: short(source.type) ?? "tool_error",
|
|
66
|
+
code: short(source.code) ?? "record_error_output_limit",
|
|
67
|
+
message: "The complete server error exceeds the MCP output budget. Its code/status are retained; inspect the SDK or HTTP response for the full message. No complete commit receipt was returned.",
|
|
68
|
+
message_truncated: true,
|
|
69
|
+
status: typeof source.status === "number" && Number.isSafeInteger(source.status)
|
|
70
|
+
? source.status
|
|
71
|
+
: null,
|
|
72
|
+
param: short(source.param),
|
|
73
|
+
request_id: short(source.request_id),
|
|
74
|
+
doc_url: short(source.doc_url),
|
|
75
|
+
...(key !== undefined ? { idempotency_key: key } : {}),
|
|
76
|
+
};
|
|
77
|
+
const payload = { error };
|
|
78
|
+
return {
|
|
79
|
+
isError: true,
|
|
80
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
81
|
+
structuredContent: payload,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export async function commitRecordsTool(host, client, extra, args) {
|
|
85
|
+
let requestKey;
|
|
86
|
+
try {
|
|
87
|
+
if (args.legacyInputs)
|
|
88
|
+
throw new ToolError("record_payload_ambiguous", "mode=records cannot include legacy facts, feedback, retract, dry_run, observed_at or edge_idempotency arguments, even when empty or false");
|
|
89
|
+
if (!isObject(args.records) || args.records.version !== "records/1") {
|
|
90
|
+
throw new ToolError("record_version_unsupported", "mode=records requires a records object with explicit version records/1");
|
|
91
|
+
}
|
|
92
|
+
// Capture before approval/capability I/O. Do not strip unknown fields,
|
|
93
|
+
// normalize IRIs, bind friendly keys or copy the server's source policy.
|
|
94
|
+
const raw = JSON.stringify(args.records);
|
|
95
|
+
if (Buffer.byteLength(raw, "utf8") > 1024 * 1024)
|
|
96
|
+
throw new ToolError("record_payload_budget", "The records body exceeds the MCP one MiB request budget; use a bounded SDK request");
|
|
97
|
+
const body = JSON.parse(raw);
|
|
98
|
+
if (!isObject(body) || body.version !== "records/1")
|
|
99
|
+
throw new ToolError("record_version_unsupported", "the encoded records object must retain version records/1");
|
|
100
|
+
const graph = args.graph;
|
|
101
|
+
const epoch = args.expectedGraphEpoch;
|
|
102
|
+
const key = args.idempotencyKey ??
|
|
103
|
+
contentHashKey({ graph: graph ?? client.graphName }, { mode: "records", expected_graph_epoch: epoch, body });
|
|
104
|
+
if (key.trim().length === 0)
|
|
105
|
+
throw new ToolError("record_payload_invalid", "records idempotency_key must not be empty");
|
|
106
|
+
// This is an output-identity allowance, not a duplicate of HTTP key syntax.
|
|
107
|
+
// It leaves room to return the FULL original key if receipt recovery fails.
|
|
108
|
+
if (Buffer.byteLength(JSON.stringify(key), "utf8") > 8192)
|
|
109
|
+
throw new ToolError("record_payload_budget", "The idempotency key exceeds the MCP recovery-identity budget");
|
|
110
|
+
requestKey = key;
|
|
111
|
+
if (mayRemove(body)) {
|
|
112
|
+
const refused = await host.approve(extra, {
|
|
113
|
+
message: approvalMessage(client, graph, body),
|
|
114
|
+
});
|
|
115
|
+
if (refused)
|
|
116
|
+
return refused;
|
|
117
|
+
}
|
|
118
|
+
const result = await scoped(client, graph).records.commit(body, { idempotencyKey: key, expectedGraphEpoch: epoch });
|
|
119
|
+
const envelope = { summary: "lbb_commit.records", data: result };
|
|
120
|
+
const text = JSON.stringify(envelope);
|
|
121
|
+
if (Buffer.byteLength(text, "utf8") > HARD_OUTPUT_CHARS) {
|
|
122
|
+
// Acceptance is already known; do not relabel it as a failed mutation or
|
|
123
|
+
// an unknown outcome just because the full result cannot fit this tool.
|
|
124
|
+
const error = {
|
|
125
|
+
type: "tool_error",
|
|
126
|
+
code: "record_accepted_output_limit",
|
|
127
|
+
message: "The record commit was accepted, but its complete receipt exceeds the MCP output budget. Recover the original receipt with the same body/key through the SDK or HTTP API.",
|
|
128
|
+
outcome: "accepted",
|
|
129
|
+
idempotency_key: key,
|
|
130
|
+
epoch: result.receipt.epoch,
|
|
131
|
+
commit_seq: result.receipt.commit_seq,
|
|
132
|
+
};
|
|
133
|
+
const payload = { error };
|
|
134
|
+
return {
|
|
135
|
+
isError: true,
|
|
136
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
137
|
+
structuredContent: payload,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
content: [{ type: "text", text }],
|
|
142
|
+
structuredContent: envelope,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
return boundedError(error instanceof LbbCapabilityError
|
|
147
|
+
? new ToolError("record_capability_unavailable", error.message)
|
|
148
|
+
: await enrichError(client, error), requestKey);
|
|
149
|
+
}
|
|
150
|
+
}
|
package/dist/reviews-tool.js
CHANGED
|
@@ -73,7 +73,7 @@ const questionSchema = z
|
|
|
73
73
|
.strict();
|
|
74
74
|
export function registerReviewsTools(host, client) {
|
|
75
75
|
host.register("lbb_reviews", {
|
|
76
|
-
description: `Review items, read only: questions for a person (human in the loop) that workflows, integrations, agents and people file. ${KINDS_TEXT} list returns one page as the server sends it ({object, data, has_more, next_cursor}), the latest update first: pass next_cursor as cursor, with the same filters, for the next page. get reads one item (rev_…) with its question, evidence, answer and effects. patterns groups the live items that ask one decision, the largest first. Evals files the questions of model checks (origin_id evals:checks), calibration sessions (evals:calibration)
|
|
76
|
+
description: `Review items, read only: questions for a person (human in the loop) that workflows, integrations, agents and people file. ${KINDS_TEXT} list returns one page as the server sends it ({object, data, has_more, next_cursor}), the latest update first: pass next_cursor as cursor, with the same filters, for the next page. get reads one item (rev_…) with its question, evidence, answer and effects. patterns groups the live items that ask one decision, the largest first. Evals files the questions of model checks (origin_id evals:checks), calibration sessions (evals:calibration), golden queries (evals:golden) and the values the two arms of an experiment differ on (evals:datasets) as grade and choice items; eval_id keeps the items of one record (for evals:datasets, the dataset), and their answer is the record's label. An item of evals:datasets asks whether one value is right or wrong, and the origin names the dataset item, the property and the value. File, answer, comment on or withdraw items with lbb_reviews_manage.`,
|
|
77
77
|
inputSchema: {
|
|
78
78
|
action: z.enum(["list", "get", "patterns"]),
|
|
79
79
|
status: z
|
|
@@ -95,7 +95,7 @@ export function registerReviewsTools(host, client) {
|
|
|
95
95
|
eval_id: z
|
|
96
96
|
.string()
|
|
97
97
|
.optional()
|
|
98
|
-
.describe("list, patterns: the items Evals filed for one record (eval.id): a calibration session, a model call's check, a golden."),
|
|
98
|
+
.describe("list, patterns: the items Evals filed for one record (eval.id): a calibration session, a model call's check, a golden, a dataset."),
|
|
99
99
|
workflow: z
|
|
100
100
|
.string()
|
|
101
101
|
.optional()
|
package/dist/tool-contracts.js
CHANGED
|
@@ -551,6 +551,15 @@ const evalExpectedValueSchema = z
|
|
|
551
551
|
.optional()
|
|
552
552
|
.describe("Words of the text that state the value"),
|
|
553
553
|
reason: z.string().optional().describe("Why a value is left out"),
|
|
554
|
+
source: z
|
|
555
|
+
.object({
|
|
556
|
+
kind: z.enum(["test", "review"]),
|
|
557
|
+
id: z.string(),
|
|
558
|
+
at: z.string(),
|
|
559
|
+
})
|
|
560
|
+
.strict()
|
|
561
|
+
.optional()
|
|
562
|
+
.describe("Who gave the label and when: a person's answer on a test (wtest_…) or a review item (rev_…). Send it back as read, or leave it out"),
|
|
554
563
|
})
|
|
555
564
|
.strict();
|
|
556
565
|
/** One example of a values dataset. */
|
|
@@ -580,6 +589,33 @@ export const evalDatasetItemSchema = z
|
|
|
580
589
|
.optional(),
|
|
581
590
|
})
|
|
582
591
|
.strict();
|
|
592
|
+
/** What B changes in A in an experiment: the model of one use, or a revision. */
|
|
593
|
+
export const experimentArmSchema = z
|
|
594
|
+
.object({
|
|
595
|
+
model: z
|
|
596
|
+
.object({
|
|
597
|
+
use: z
|
|
598
|
+
.enum(["extract", "decide"])
|
|
599
|
+
.describe("The use whose model B changes"),
|
|
600
|
+
model: z
|
|
601
|
+
.object({
|
|
602
|
+
provider: z.string().describe("anthropic, typesafe or mock"),
|
|
603
|
+
model: z.string().describe("A model of the use's options"),
|
|
604
|
+
effort: z
|
|
605
|
+
.string()
|
|
606
|
+
.optional()
|
|
607
|
+
.describe("The effort of a Claude model"),
|
|
608
|
+
})
|
|
609
|
+
.strict(),
|
|
610
|
+
})
|
|
611
|
+
.strict()
|
|
612
|
+
.optional(),
|
|
613
|
+
revision: z
|
|
614
|
+
.string()
|
|
615
|
+
.optional()
|
|
616
|
+
.describe("A kept settings revision of the workflow (wrev_…)"),
|
|
617
|
+
})
|
|
618
|
+
.strict();
|
|
583
619
|
export const inspectInputSchema = z.discriminatedUnion("action", [
|
|
584
620
|
z.object({ action: z.literal("guide"), ...readScope }).strict(),
|
|
585
621
|
z
|
package/dist/tool-runtime.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { LbbError } from "@littlebigbrain/client";
|
|
3
|
+
import { LbbError, LbbRecordCommitOutcomeError, } from "@littlebigbrain/client";
|
|
4
4
|
import { DEFAULT_DETAIL, HARD_OUTPUT_CHARS, MAX_QUERY_ROW_LIMIT, } from "./tool-contracts.js";
|
|
5
5
|
export const scoped = (client, graph) => graph !== undefined ? client.withScope({ graph }) : client;
|
|
6
6
|
export function normalizeDetail(detail) {
|
|
@@ -490,25 +490,37 @@ export class ToolError extends Error {
|
|
|
490
490
|
}
|
|
491
491
|
}
|
|
492
492
|
export function errorResult(error) {
|
|
493
|
-
const payload = error instanceof
|
|
493
|
+
const payload = error instanceof LbbRecordCommitOutcomeError
|
|
494
494
|
? {
|
|
495
|
-
type: error.type ?? "api_error",
|
|
496
|
-
code: error.code ?? "unstructured_error",
|
|
497
|
-
message: error.message,
|
|
498
|
-
param: error.param ?? null,
|
|
499
|
-
request_id: error.requestId ?? null,
|
|
500
|
-
doc_url: error.docUrl ?? null,
|
|
501
|
-
status: error.status,
|
|
502
|
-
}
|
|
503
|
-
: {
|
|
504
495
|
type: "tool_error",
|
|
505
|
-
code:
|
|
506
|
-
message: error
|
|
496
|
+
code: "record_outcome_unknown",
|
|
497
|
+
message: error.message,
|
|
498
|
+
outcome: error.outcome,
|
|
499
|
+
idempotency_key: error.idempotencyKey,
|
|
507
500
|
param: null,
|
|
508
501
|
request_id: null,
|
|
509
502
|
doc_url: null,
|
|
510
503
|
status: null,
|
|
511
|
-
}
|
|
504
|
+
}
|
|
505
|
+
: error instanceof LbbError
|
|
506
|
+
? {
|
|
507
|
+
type: error.type ?? "api_error",
|
|
508
|
+
code: error.code ?? "unstructured_error",
|
|
509
|
+
message: error.message,
|
|
510
|
+
param: error.param ?? null,
|
|
511
|
+
request_id: error.requestId ?? null,
|
|
512
|
+
doc_url: error.docUrl ?? null,
|
|
513
|
+
status: error.status,
|
|
514
|
+
}
|
|
515
|
+
: {
|
|
516
|
+
type: "tool_error",
|
|
517
|
+
code: error instanceof ToolError ? error.code : "tool_error",
|
|
518
|
+
message: error instanceof Error ? error.message : String(error),
|
|
519
|
+
param: null,
|
|
520
|
+
request_id: null,
|
|
521
|
+
doc_url: null,
|
|
522
|
+
status: null,
|
|
523
|
+
};
|
|
512
524
|
const structuredContent = { error: payload };
|
|
513
525
|
return {
|
|
514
526
|
content: [
|
package/dist/tools.js
CHANGED
|
@@ -3,11 +3,12 @@ import { metadataPage } from "./metadata-pages.js";
|
|
|
3
3
|
import { answerQuestion, } from "./question.js";
|
|
4
4
|
import { commitAt, compareQuery, describeGraph, findNames, } from "./query-tools.js";
|
|
5
5
|
import { registerRdfTool } from "./rdf-tool.js";
|
|
6
|
+
import { commitRecordsTool } from "./records-write.js";
|
|
6
7
|
import { registerFilesTool } from "./files-tool.js";
|
|
7
8
|
import { registerReviewsTools } from "./reviews-tool.js";
|
|
8
9
|
import { APPROVAL_TEXT, graphLabel, oneLine, ontologyRemovalMessage, retractMessage, shapesRejectMessage, toolHost, } from "./approval.js";
|
|
9
10
|
import { queryTiming } from "./query-observer.js";
|
|
10
|
-
import { DESTRUCTIVE, HARD_OUTPUT_CHARS, MUTATING, READ_ONLY, configureInputSchema, configureWireSchema, detailSchema, entityEmbeddingSchema, evalDatasetItemSchema, evalLabelItemSchema, evidenceSchema, graphScope, inspectInputSchema, inspectWireSchema, jsonObjectSchema, queryInputSchema, queryWireSchema, reviewAnswerSchema, searchFeedbackSchema, suggestedFactsBody, testInstallSchema, yearMonthSchema, } from "./tool-contracts.js";
|
|
11
|
+
import { DESTRUCTIVE, HARD_OUTPUT_CHARS, MUTATING, READ_ONLY, configureInputSchema, configureWireSchema, detailSchema, entityEmbeddingSchema, evalDatasetItemSchema, experimentArmSchema, evalLabelItemSchema, evidenceSchema, graphScope, inspectInputSchema, inspectWireSchema, jsonObjectSchema, queryInputSchema, queryWireSchema, reviewAnswerSchema, searchFeedbackSchema, suggestedFactsBody, testInstallSchema, yearMonthSchema, } from "./tool-contracts.js";
|
|
11
12
|
import { analyze, assertCursorScope, contentHashKey, decodeQueryCursor, effectiveRowLimit, enrichError, errorResult, guide, normalizeDetail, normalizeLbbIris, ontologyDefineBody, pageLimit, queryCommitPin, queryEnvelope, queryToolResult, requireString, rowPageFrom, rowPageNext, run, runPage, scoped, stableJson, toolResult, ToolError, } from "./tool-runtime.js";
|
|
12
13
|
/** The most tests `GET /v1/workflows/named/tests` returns in one page. */
|
|
13
14
|
const WORKFLOW_TESTS_PAGE_MAX = 50;
|
|
@@ -17,6 +18,7 @@ const WORKFLOW_QUEUE_PAGE_MAX = 100;
|
|
|
17
18
|
const WORKFLOW_RUN_RESULTS_PAGE_MAX = 200;
|
|
18
19
|
/** The most datasets `GET /v1/evals/datasets` returns in one page. */
|
|
19
20
|
const EVAL_DATASETS_PAGE_MAX = 50;
|
|
21
|
+
const EVAL_EXPERIMENTS_PAGE_MAX = 50;
|
|
20
22
|
export function registerLbbTools(server, client, options = {}) {
|
|
21
23
|
const host = toolHost(server, options);
|
|
22
24
|
registerRdfTool(host, client);
|
|
@@ -904,7 +906,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
904
906
|
return { name: args.name, ...inputs };
|
|
905
907
|
};
|
|
906
908
|
host.register("lbb_workflows", {
|
|
907
|
-
description: "Starter workflows and triggered workflows, read only. A starter workflow is a template; a triggered workflow is a workflow created from one on the graph. It watches a class: when a published commit adds or changes instances of the class (or changes the watched fields), the workflow reads them and does its work. search.embed keeps an embedding of the class (the same as lbb_embeddings); ontology.fit reads text fields and files ontology suggestions (the same as lbb_fit_sources); workflow (a developer workflow) sends the new and changed instances to a workflow of the developer's worker as trigger messages, and that code writes the facts the workflow owns. documents.parse reads uploaded PDF files into pages with line boxes, documents.link connects mentions of the same thing across the documents (it writes DocEntry and DocMention instances and links each mention to the entry that defines or describes it), documents.map proposes each link as a FactProposal in the graph's own ontology (fact_subject, fact_predicate, fact_object, fact_confidence, fact_evidence) for a review workflow, and values.extract fills chosen properties from text, each value with a quote; read the files and their pages with lbb_files. starters lists the starter workflows this server offers with their group, settings and whether each can run here. list shows every triggered workflow of the graph with its state (backfilling, ready, paused, failed, unavailable), lag, counters, this month's spend and last error; get shows one (starter and name) with its own status in details (for workflow: the messages sent, the instances waiting, the message in flight and whether it is paused). preview shows what creating one would do (the resolved watch, the instance count and the workflow's own preview; for workflow, the first messages it would send), calls no model and stores nothing. workflows lists the same workflows as workflows with triggers (/v1/workflows/named): each one's target workflow type (LBB's own are lbb.* types), its shards (engine identities starter.<name> and starter.<name>.<k>), its routing field, its window, its one watch trigger, its status and each shard's inbox, one page in data (has_more and next_cursor page them with cursor and limit); name for one. queue (name) shows what a workflow's watch has not sent yet: each waiting instance with its shard and its origin (a backfill, a commit range with the watched fields and the requests that changed it, a run now, a schema change), the pass in progress, and per shard its message in flight and its first unfinished turns with their origins; the items are in data, and has_more and next_cursor page them with cursor and limit. run_results (name) shows what the workflow's runs did with each input, newest run first: the input's IRI and label, its outcome (written, review, no_value, failed, duplicate) and its values (the value, the quote that states it, its support, the conversion, and the review item it went to); run keeps one run (with run_counts, every input by outcome), outcome keeps some outcomes, iri keeps one input; data, has_more and next_cursor page them. A run records its results when it ends; they are kept 30 days. revisions (name) shows the history of a workflow's settings, newest change first: each item is a revision (settings, the request that makes it, and an id wrev_… that is the hash of the settings) with who changed it, when, how (create, change, activate, existing) and bound (the one in use); a row whose revision was deleted has available false and no settings; has_more and next_cursor page it with cursor and limit (1 to 100, default 20). revision (name, revision) reads one revision; compare two by their settings to say what changed. Each run summary names the revision it ran on. Message workflows of the workflow engine are not listed here. tests lists the graph's workflow tests, newest first, without texts and results (name: one workflow's tests), one page with next_cursor; tests_get reads one test (id) with its inputs' texts and, per input, each value read with its quote, its support and what the live workflow does with it (writes it, gives it to review, or drops it). A test goes queued, running, then done, failed or cancelled. Create, refresh, pause or resume with lbb_workflows_manage, which also starts, answers and cancels tests and goes back to an earlier revision with its action activate; delete with lbb_workflows_delete.",
|
|
909
|
+
description: "Starter workflows and triggered workflows, read only. A starter workflow is a template; a triggered workflow is a workflow created from one on the graph. It watches a class: when a published commit adds or changes instances of the class (or changes the watched fields), the workflow reads them and does its work. search.embed keeps an embedding of the class (the same as lbb_embeddings); ontology.fit reads text fields and files ontology suggestions (the same as lbb_fit_sources); workflow (a developer workflow) sends the new and changed instances to a workflow of the developer's worker as trigger messages, and that code writes the facts the workflow owns. documents.parse reads uploaded PDF files into pages with line boxes, documents.link connects mentions of the same thing across the documents (it writes DocEntry and DocMention instances and links each mention to the entry that defines or describes it), documents.map proposes each link as a FactProposal in the graph's own ontology (fact_subject, fact_predicate, fact_object, fact_confidence, fact_evidence) for a review workflow, and values.extract fills chosen properties from text, each value with a quote; read the files and their pages with lbb_files. starters lists the starter workflows this server offers with their group, settings and whether each can run here. list shows every triggered workflow of the graph with its state (backfilling, ready, paused, failed, unavailable), lag, counters, this month's spend and last error; get shows one (starter and name) with its own status in details (for workflow: the messages sent, the instances waiting, the message in flight and whether it is paused). preview shows what creating one would do (the resolved watch, the instance count and the workflow's own preview; for workflow, the first messages it would send), calls no model and stores nothing. workflows lists the same workflows as workflows with triggers (/v1/workflows/named): each one's target workflow type (LBB's own are lbb.* types), its shards (engine identities starter.<name> and starter.<name>.<k>), its routing field, its window, its one watch trigger, its status and each shard's inbox, one page in data (has_more and next_cursor page them with cursor and limit); name for one. queue (name) shows what a workflow's watch has not sent yet: each waiting instance with its shard and its origin (a backfill, a commit range with the watched fields and the requests that changed it, a run now, a schema change), the pass in progress, and per shard its message in flight and its first unfinished turns with their origins; the items are in data, and has_more and next_cursor page them with cursor and limit. run_results (name) shows what the workflow's runs did with each input, newest run first: the input's IRI and label, its outcome (written, review, no_value, failed, duplicate) and its values (the value, the quote that states it, its support, the conversion, and the review item it went to); run keeps one run (with run_counts, every input by outcome), outcome keeps some outcomes, iri keeps one input; data, has_more and next_cursor page them. A run records its results when it ends; they are kept 30 days. revisions (name) shows the history of a workflow's settings, newest change first: each item is a revision (settings, the request that makes it, and an id wrev_… that is the hash of the settings) with who changed it, when, how (create, change, activate, existing) and bound (the one in use); a row whose revision was deleted has available false and no settings; has_more and next_cursor page it with cursor and limit (1 to 100, default 20). last_tested on a revision of a workflow that has tests (values.extract) says what the workflow's tests read with those exact settings: an object (test, finished, counts, and experiment when the test is an arm of an experiment) names the newest test that read inputs with them, null means no test is recorded for those settings, and no key means the workflow has no tests or the server does not say; it shows what happened and gives no verdict. revision (name, revision) reads one revision; compare two by their settings to say what changed. Each run summary names the revision it ran on. Message workflows of the workflow engine are not listed here. tests lists the graph's workflow tests, newest first, without texts and results (name: one workflow's tests), one page with next_cursor; tests_get reads one test (id) with its inputs' texts and, per input, each value read with its quote, its support and what the live workflow does with it (writes it, gives it to review, or drops it). A test goes queued, running, then done, failed or cancelled. Create, refresh, pause or resume with lbb_workflows_manage, which also starts, answers and cancels tests and goes back to an earlier revision with its action activate; delete with lbb_workflows_delete.",
|
|
908
910
|
inputSchema: {
|
|
909
911
|
action: z.enum([
|
|
910
912
|
"starters",
|
|
@@ -1030,7 +1032,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
1030
1032
|
});
|
|
1031
1033
|
});
|
|
1032
1034
|
host.register("lbb_workflows_manage", {
|
|
1033
|
-
description: "Create a workflow from a starter workflow, or refresh, pause or resume a triggered workflow. Run lbb_workflows action=preview first and show the user the watch and the instance count. use creates a workflow from a starter workflow of action=starters, under a name (default: the class's local name), with its watch (class, fields) and params; the workflow then reads every instance of the class and follows each published commit. A developer workflow (starter=workflow) also takes workflow (workflow_type and version of the developer's worker), owns and batch; it needs a server with the workflow engine. use again with the same starter and name changes only what you name, and the same request again changes nothing. A workflow that calls models (ontology.fit, documents.link, documents.map, values.extract) spends model budget as it reads. refresh asks the workflow to run now and returns at once with queued; poll lbb_workflows action=get for progress. pause stops a workflow (a developer workflow, documents.parse, documents.link, documents.map or values.extract) from reading and sending, and stops the claims of a developer workflow's shards; resume lets it catch up. Without starter, pause and resume name a workflow with triggers. search.embed and ontology.fit cannot be paused. define creates or changes a workflow with triggers of the developer's worker (/v1/workflows/named): name, workflow, one trigger (class and fields for a watch, media_types for a content trigger on uploads, schedule for one run per slot of a timer, or stream for the events a client sends, in batches), owns, batch, and shards (engine identities, 1 to 16), route_by (a field that keeps work in order) and window (messages queued per shard, default 4), and optionally budget_usd_per_month (the workflow's own monthly model budget), on_exhausted (block or skip) and fan_in (the id of a fan-in identity the shards send to); it needs a server with the workflow engine, and a change of shards waits for empty inboxes. run (name) runs a workflow with triggers now: its watch sends every instance again, a content trigger reads its log again, a schedule sends one run outside its slots, a stream sends its partial batches. activate (name, revision) makes an earlier revision of a workflow's settings (from lbb_workflows action=revisions) the one in use: it goes through the same checks as a change, and answers the workflow with its active_revision. Show the user what changes first (compare the settings of the two revisions), and pass expected_revision, the revision you read as bound, so a change by someone else since fails with workflow_revision_conflict. A revision whose settings the graph now reads differently fails with workflow_revision_stale; then change the workflow instead. Activating changes what the workflow does from then on and deletes no data, so it is not a destructive write; a change of the watch, the parameters or the gate makes the workflow read its class again, and work that waits or runs keeps the revision it started with. tests_create tests a values.extract workflow on chosen inputs before it writes: name (a workflow of the graph; starter defaults to values.extract) or install (a workflow not created yet, as use takes it: starter, name, watch, params, gate), and iris (at most 20) or sample (1 to 20, default 6), and models to try other models before choosing one. A test reads each input as the workflow would and calls its models (the calls count in the monthly workflow budget); it writes nothing to the graph. Poll lbb_workflows action=tests_get. tests_answer records the person's answer to one value of a read input (id, iri, property, answer: accept, reject, correct with value, or cant_tell; null removes it); it writes nothing to the graph. Answer only what the user decided. tests_cancel stops a queued or running test (id). Save a test's answers as a dataset with lbb_evals action=datasets_create.",
|
|
1035
|
+
description: "Create a workflow from a starter workflow, or refresh, pause or resume a triggered workflow. Run lbb_workflows action=preview first and show the user the watch and the instance count. use creates a workflow from a starter workflow of action=starters, under a name (default: the class's local name), with its watch (class, fields) and params; the workflow then reads every instance of the class and follows each published commit. A developer workflow (starter=workflow) also takes workflow (workflow_type and version of the developer's worker), owns and batch; it needs a server with the workflow engine. use again with the same starter and name changes only what you name, and the same request again changes nothing. A workflow that calls models (ontology.fit, documents.link, documents.map, values.extract) spends model budget as it reads. refresh asks the workflow to run now and returns at once with queued; poll lbb_workflows action=get for progress. pause stops a workflow (a developer workflow, documents.parse, documents.link, documents.map or values.extract) from reading and sending, and stops the claims of a developer workflow's shards; resume lets it catch up. Without starter, pause and resume name a workflow with triggers. search.embed and ontology.fit cannot be paused. define creates or changes a workflow with triggers of the developer's worker (/v1/workflows/named): name, workflow, one trigger (class and fields for a watch, media_types for a content trigger on uploads, schedule for one run per slot of a timer, or stream for the events a client sends, in batches), owns, batch, and shards (engine identities, 1 to 16), route_by (a field that keeps work in order) and window (messages queued per shard, default 4), and optionally budget_usd_per_month (the workflow's own monthly model budget), on_exhausted (block or skip) and fan_in (the id of a fan-in identity the shards send to); it needs a server with the workflow engine, and a change of shards waits for empty inboxes. run (name) runs a workflow with triggers now: its watch sends every instance again, a content trigger reads its log again, a schedule sends one run outside its slots, a stream sends its partial batches. activate (name, revision) makes an earlier revision of a workflow's settings (from lbb_workflows action=revisions) the one in use: it goes through the same checks as a change, and answers the workflow with its active_revision. Show the user what changes first (compare the settings of the two revisions), and when the revision's last_tested is null say that no test is recorded for those settings and ask before you activate (activation stays allowed), and pass expected_revision, the revision you read as bound, so a change by someone else since fails with workflow_revision_conflict. A revision whose settings the graph now reads differently fails with workflow_revision_stale; then change the workflow instead. Activating changes what the workflow does from then on and deletes no data, so it is not a destructive write; a change of the watch, the parameters or the gate makes the workflow read its class again, and work that waits or runs keeps the revision it started with. tests_create tests a values.extract workflow on chosen inputs before it writes: name (a workflow of the graph; starter defaults to values.extract) or install (a workflow not created yet, as use takes it: starter, name, watch, params, gate), and iris (at most 20) or sample (1 to 20, default 6), and models to try other models before choosing one. A test reads each input as the workflow would and calls its models (the calls count in the monthly workflow budget); it writes nothing to the graph. Poll lbb_workflows action=tests_get. tests_answer records the person's answer to one value of a read input (id, iri, property, answer: accept, reject, correct with value, or cant_tell; null removes it); it writes nothing to the graph. Answer only what the user decided. tests_cancel stops a queued or running test (id). Save a test's answers as a dataset with lbb_evals action=datasets_create.",
|
|
1034
1036
|
inputSchema: {
|
|
1035
1037
|
action: z.enum([
|
|
1036
1038
|
"use",
|
|
@@ -1069,6 +1071,10 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
1069
1071
|
.string()
|
|
1070
1072
|
.optional()
|
|
1071
1073
|
.describe("activate: the revision the workflow had bound when you read it; another one fails with workflow_revision_conflict."),
|
|
1074
|
+
experiment: z
|
|
1075
|
+
.string()
|
|
1076
|
+
.optional()
|
|
1077
|
+
.describe("activate: an experiment (exp_…) whose B this activation applies; revision is B's revision. The workflow must still run the experiment's A, else experiment_outdated; when B's revision is active already, nothing changes."),
|
|
1072
1078
|
install: testInstallSchema
|
|
1073
1079
|
.optional()
|
|
1074
1080
|
.describe("tests_create: a workflow not created yet, as use takes it: starter (values.extract), name, watch ({class, fields, exclude}), params and gate ({below})."),
|
|
@@ -1131,9 +1137,12 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
1131
1137
|
case "run":
|
|
1132
1138
|
return workflows.named.run(workflowName(args));
|
|
1133
1139
|
case "activate":
|
|
1134
|
-
return workflows.named.activateRevision(workflowName(args), revisionId(args.revision),
|
|
1135
|
-
|
|
1136
|
-
|
|
1140
|
+
return workflows.named.activateRevision(workflowName(args), revisionId(args.revision), {
|
|
1141
|
+
...(args.expected_revision
|
|
1142
|
+
? { expectedRevision: args.expected_revision }
|
|
1143
|
+
: {}),
|
|
1144
|
+
...(args.experiment ? { experiment: args.experiment } : {}),
|
|
1145
|
+
});
|
|
1137
1146
|
case "tests_create":
|
|
1138
1147
|
return workflows.tests.create(testRequest(args));
|
|
1139
1148
|
case "tests_answer":
|
|
@@ -1310,6 +1319,10 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
1310
1319
|
.string()
|
|
1311
1320
|
.optional()
|
|
1312
1321
|
.describe("put_choice / delete_choice: a named workflow of the graph; absent for the environment."),
|
|
1322
|
+
experiment: z
|
|
1323
|
+
.string()
|
|
1324
|
+
.optional()
|
|
1325
|
+
.describe("put_choice: an experiment (exp_…) whose B this choice applies, with workflow; use and the model are B's change. The workflow must still run the experiment's A, else experiment_outdated; when it runs B already, nothing changes."),
|
|
1313
1326
|
detail: detailSchema,
|
|
1314
1327
|
...graphScope,
|
|
1315
1328
|
},
|
|
@@ -1355,6 +1368,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
1355
1368
|
...(args.effort ? { effort: args.effort } : {}),
|
|
1356
1369
|
},
|
|
1357
1370
|
...(args.workflow ? { workflow: args.workflow } : {}),
|
|
1371
|
+
...(args.experiment ? { experiment: args.experiment } : {}),
|
|
1358
1372
|
});
|
|
1359
1373
|
case "delete_choice":
|
|
1360
1374
|
if (!args.use)
|
|
@@ -1370,8 +1384,24 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
1370
1384
|
throw new Error("this action requires id");
|
|
1371
1385
|
return id;
|
|
1372
1386
|
};
|
|
1387
|
+
const experimentId = (id) => {
|
|
1388
|
+
if (!id)
|
|
1389
|
+
throw new Error("this action requires id (the experiment's)");
|
|
1390
|
+
return id;
|
|
1391
|
+
};
|
|
1392
|
+
const experimentRequest = (args) => {
|
|
1393
|
+
if (!args.workflow || !args.dataset || !args.b)
|
|
1394
|
+
throw new Error("this action requires workflow, dataset and b");
|
|
1395
|
+
return {
|
|
1396
|
+
workflow: args.workflow,
|
|
1397
|
+
dataset: args.dataset,
|
|
1398
|
+
b: args.b,
|
|
1399
|
+
...(args.max_items ? { max_items: args.max_items } : {}),
|
|
1400
|
+
...(args.max_cost_usd ? { max_cost_usd: args.max_cost_usd } : {}),
|
|
1401
|
+
};
|
|
1402
|
+
};
|
|
1373
1403
|
host.register("lbb_evals", {
|
|
1374
|
-
description: "Managed evals: ground truth for the questions an app asks and the searches it runs. A query run with `request` (lbb_query, also mode=question) records a trace: the question, the query that answered it, the type of that query (`query_type`: sparql, hybrid = search by meaning with conditions, search = search by meaning alone) and one item per result (hit or row); trace reads it back with the item ids. label has two forms. On a question's trace, valid alone judges the whole answer: valid=true makes the trace's query the golden query of the question; valid=false with sparql gives the right query, which becomes the golden query; valid=false alone marks the answer wrong; route with valid=true (or valid=false with sparql) names the route the question expects, and valid=true alone takes the trace's route. item + valid (or items) judges one result, a citation of the answer: the ground truth of the hits of a search by meaning, and a result marked wrong must not come back for any type. golden freezes a stored query or a search (every result it returns now is relevant). run checks every golden at the current commit: a question is asked again through the query rewriter, then the type of the query it wrote is compared with the expected type, its rows with the rows of the golden query at the same commit (`query_check`), and its results with the judged results, and the router's pick with the golden's route (route_check, which does not change the verdict); a search is done again; new results open a review trace. run_answers checks your own app's answers instead: ask each question golden's request with your own loop, then send answers=[{golden, sparql, route}] (route = the route of lbb_query mode=question with route mode, sparql absent for an unanswerable question, error for a failure); the server runs each query and checks it the same way, and the runs form the app's own series (runner; results with runner reads them). judge lets the platform's judge model (the hosted frontier model) label unlabeled results. summary, traces, goldens, results, and settings read state. Model checks: a judge model checks a sample of the model calls LBB makes for the graph (rerank, route, ask = the answer of a question, rewrite, fit, propose, label). checks_summary reads a month per job and model (checks, score, right, partly, wrong, reviews) and the judge's agreement with people; checks lists the month's checks, newest first, with the judge's verdict, score and reason and the ground truth (`truth`). review_check records a person's review of one check (call_id): agree=true keeps the judge's verdict; agree=false with verdict (and an optional score, reference and note) corrects it. The review becomes the call's ground truth, so show the check to the user and review only what the user confirmed. Datasets: named sets of examples with the outputs a correct run gives, which workflow tests and template evals share. The kind values holds per item an input text (name, text, iri), the property values a correct run writes (values) and those it must leave out (leave_out). datasets lists the graph's datasets, newest first, without their items (one page with next_cursor); datasets_get reads one (id) with its items; datasets_export returns it as a template's samples, records (records.json) and expected (expected.json). datasets_create makes one with name and items (at most 500), install (the settings it is graded with: each property's value type, units, context) and about, or from a workflow test's answers with source={kind: test, id}. datasets_items adds items to a dataset (id); an item with the name of one it holds replaces it. Delete a dataset with lbb_evals_delete, which asks the person first. A dataset ends with its graph.",
|
|
1404
|
+
description: "Managed evals: ground truth for the questions an app asks and the searches it runs. A query run with `request` (lbb_query, also mode=question) records a trace: the question, the query that answered it, the type of that query (`query_type`: sparql, hybrid = search by meaning with conditions, search = search by meaning alone) and one item per result (hit or row); trace reads it back with the item ids. label has two forms. On a question's trace, valid alone judges the whole answer: valid=true makes the trace's query the golden query of the question; valid=false with sparql gives the right query, which becomes the golden query; valid=false alone marks the answer wrong; route with valid=true (or valid=false with sparql) names the route the question expects, and valid=true alone takes the trace's route. item + valid (or items) judges one result, a citation of the answer: the ground truth of the hits of a search by meaning, and a result marked wrong must not come back for any type. golden freezes a stored query or a search (every result it returns now is relevant). run checks every golden at the current commit: a question is asked again through the query rewriter, then the type of the query it wrote is compared with the expected type, its rows with the rows of the golden query at the same commit (`query_check`), and its results with the judged results, and the router's pick with the golden's route (route_check, which does not change the verdict); a search is done again; new results open a review trace. run_answers checks your own app's answers instead: ask each question golden's request with your own loop, then send answers=[{golden, sparql, route}] (route = the route of lbb_query mode=question with route mode, sparql absent for an unanswerable question, error for a failure); the server runs each query and checks it the same way, and the runs form the app's own series (runner; results with runner reads them). judge lets the platform's judge model (the hosted frontier model) label unlabeled results. summary, traces, goldens, results, and settings read state. Model checks: a judge model checks a sample of the model calls LBB makes for the graph (rerank, route, ask = the answer of a question, rewrite, fit, propose, label). checks_summary reads a month per job and model (checks, score, right, partly, wrong, reviews) and the judge's agreement with people; checks lists the month's checks, newest first, with the judge's verdict, score and reason and the ground truth (`truth`). review_check records a person's review of one check (call_id): agree=true keeps the judge's verdict; agree=false with verdict (and an optional score, reference and note) corrects it. The review becomes the call's ground truth, so show the check to the user and review only what the user confirmed. Datasets: named sets of examples with the outputs a correct run gives, which workflow tests and template evals share. The kind values holds per item an input text (name, text, iri), the property values a correct run writes (values) and those it must leave out (leave_out). datasets lists the graph's datasets, newest first, without their items (one page with next_cursor); datasets_get reads one (id) with its items; datasets_export returns it as a template's samples, records (records.json) and expected (expected.json). datasets_create makes one with name and items (at most 500), install (the settings it is graded with: each property's value type, units, context) and about, or from a workflow test's answers with source={kind: test, id}. datasets_items adds items to a dataset (id); an item with the name of one it holds replaces it. Delete a dataset with lbb_evals_delete, which asks the person first. A dataset ends with its graph. Experiments: two configurations of a workflow, A and B, on the items of a dataset, with the difference of B against A and its 95% interval. A is the workflow as it runs now; b changes one part: {model: {use: extract or decide, model: {provider, model, effort}}} (a model from lbb_model_choice action=choices options) or {revision: wrev_…} (a kept settings revision), not both. Each arm runs as a workflow test that writes nothing to the graph and spends model budget; each item is graded against the dataset's labels. experiments_estimate shows the cost first (items, the cost of both arms, the limit, the budget left): call it, show the user the figures, and start only what the user confirmed. experiments_create starts one (workflow, dataset, b, optionally max_items up to 200 and max_cost_usd up to 100, default 5); poll experiment (id) until its status is done, failed or cancelled, and read report.delta with ci_low and ci_high (outcome too_few under 10 items, else better, worse or same) and the item rows. experiments lists them (workflow, dataset, status, one page with next_cursor); experiments_cancel stops one, keeping what it compared. None of them changes the workflow or the graph. To use B, make the change with the experiment's id: lbb_model_choice_manage action=put_choice (or lbb_workflows_manage action=activate) with experiment, after the user confirmed; it fails with experiment_outdated when the workflow moved since.",
|
|
1375
1405
|
inputSchema: {
|
|
1376
1406
|
action: z.enum([
|
|
1377
1407
|
"summary",
|
|
@@ -1395,6 +1425,11 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
1395
1425
|
"datasets_export",
|
|
1396
1426
|
"datasets_create",
|
|
1397
1427
|
"datasets_items",
|
|
1428
|
+
"experiments",
|
|
1429
|
+
"experiment",
|
|
1430
|
+
"experiments_estimate",
|
|
1431
|
+
"experiments_create",
|
|
1432
|
+
"experiments_cancel",
|
|
1398
1433
|
]),
|
|
1399
1434
|
trace_id: z
|
|
1400
1435
|
.string()
|
|
@@ -1411,7 +1446,7 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
1411
1446
|
items: z
|
|
1412
1447
|
.array(z.union([evalLabelItemSchema, evalDatasetItemSchema]))
|
|
1413
1448
|
.optional()
|
|
1414
|
-
.describe("label: several results at once, each {id, valid, note}. datasets_create, datasets_items: the examples, at most 500, each {name, text, values, leave_out, iri, case}; values and leave_out hold {property, value, accept, stated, quote, reason}."),
|
|
1449
|
+
.describe("label: several results at once, each {id, valid, note}. datasets_create, datasets_items: the examples, at most 500, each {name, text, values, leave_out, iri, case}; values and leave_out hold {property, value, accept, stated, quote, reason, source}; source says which person's answer gave a label (kind test or review, id, at): send it back as read, or leave it out."),
|
|
1415
1450
|
by: z
|
|
1416
1451
|
.string()
|
|
1417
1452
|
.optional()
|
|
@@ -1555,11 +1590,39 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
1555
1590
|
id: z
|
|
1556
1591
|
.string()
|
|
1557
1592
|
.optional()
|
|
1558
|
-
.describe("datasets_get, datasets_export, datasets_items: the dataset's id (ds_…)."),
|
|
1593
|
+
.describe("datasets_get, datasets_export, datasets_items: the dataset's id (ds_…). experiment, experiments_cancel: the experiment's id (exp_…)."),
|
|
1559
1594
|
cursor: z
|
|
1560
1595
|
.string()
|
|
1561
1596
|
.optional()
|
|
1562
|
-
.describe("datasets: the next_cursor of the page before."),
|
|
1597
|
+
.describe("datasets, experiments: the next_cursor of the page before."),
|
|
1598
|
+
workflow: z
|
|
1599
|
+
.string()
|
|
1600
|
+
.optional()
|
|
1601
|
+
.describe("experiments_estimate, experiments_create: the workflow to compare. experiments: only the experiments of this workflow."),
|
|
1602
|
+
dataset: z
|
|
1603
|
+
.string()
|
|
1604
|
+
.optional()
|
|
1605
|
+
.describe("experiments_estimate, experiments_create: the dataset (ds_…) whose items both arms run. experiments: only the experiments on this dataset."),
|
|
1606
|
+
b: experimentArmSchema
|
|
1607
|
+
.optional()
|
|
1608
|
+
.describe("experiments_estimate, experiments_create: what B changes in A: {model: {use, model: {provider, model, effort}}} or {revision}, not both."),
|
|
1609
|
+
max_items: z
|
|
1610
|
+
.number()
|
|
1611
|
+
.int()
|
|
1612
|
+
.min(1)
|
|
1613
|
+
.max(200)
|
|
1614
|
+
.optional()
|
|
1615
|
+
.describe("experiments_estimate, experiments_create: run the dataset's first items only (default 200)."),
|
|
1616
|
+
max_cost_usd: z
|
|
1617
|
+
.number()
|
|
1618
|
+
.positive()
|
|
1619
|
+
.max(100)
|
|
1620
|
+
.optional()
|
|
1621
|
+
.describe("experiments_estimate, experiments_create: the cost limit of both arms in US dollars (default 5)."),
|
|
1622
|
+
status: z
|
|
1623
|
+
.enum(["queued", "running", "done", "failed", "cancelled"])
|
|
1624
|
+
.optional()
|
|
1625
|
+
.describe("experiments: only the experiments with this status."),
|
|
1563
1626
|
name: z
|
|
1564
1627
|
.string()
|
|
1565
1628
|
.optional()
|
|
@@ -1596,7 +1659,17 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
1596
1659
|
...graphScope,
|
|
1597
1660
|
},
|
|
1598
1661
|
annotations: MUTATING,
|
|
1599
|
-
}, ({ action, trace_id, item, valid, items, by, note, sparql, route, answers, runner, surface, embedding, top_k, request, golden_id, limit, unlabeled, consistency, month, job, verdict, reviewed, after, call_id, agree, score, reference, id, cursor, name, kind, install, about, source, detail, graph, }) => {
|
|
1662
|
+
}, ({ action, trace_id, item, valid, items, by, note, sparql, route, answers, runner, surface, embedding, top_k, request, golden_id, limit, unlabeled, consistency, month, job, verdict, reviewed, after, call_id, agree, score, reference, id, cursor, workflow, dataset, b, max_items, max_cost_usd, status, name, kind, install, about, source, detail, graph, }) => {
|
|
1663
|
+
if (action === "experiments") {
|
|
1664
|
+
const experiments = scoped(client, graph).evals.experiments;
|
|
1665
|
+
return runPage(client, "lbb_evals.experiments", detail, pageLimit(detail, limit, EVAL_EXPERIMENTS_PAGE_MAX), (pageSize) => experiments.list({
|
|
1666
|
+
workflow,
|
|
1667
|
+
dataset,
|
|
1668
|
+
status,
|
|
1669
|
+
limit: pageSize,
|
|
1670
|
+
cursor,
|
|
1671
|
+
}));
|
|
1672
|
+
}
|
|
1600
1673
|
if (action === "datasets") {
|
|
1601
1674
|
const datasets = scoped(client, graph).evals.datasets;
|
|
1602
1675
|
return runPage(client, "lbb_evals.datasets", detail, pageLimit(detail, limit, EVAL_DATASETS_PAGE_MAX), (pageSize) => datasets.list({ limit: pageSize, cursor }));
|
|
@@ -1728,6 +1801,26 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
1728
1801
|
throw new Error("datasets_items requires items");
|
|
1729
1802
|
return target.evals.datasets.addItems(datasetId(id), examples);
|
|
1730
1803
|
}
|
|
1804
|
+
case "experiment":
|
|
1805
|
+
return target.evals.experiments.get(experimentId(id));
|
|
1806
|
+
case "experiments_cancel":
|
|
1807
|
+
return target.evals.experiments.cancel(experimentId(id));
|
|
1808
|
+
case "experiments_estimate":
|
|
1809
|
+
return target.evals.experiments.estimate(experimentRequest({
|
|
1810
|
+
workflow,
|
|
1811
|
+
dataset,
|
|
1812
|
+
b,
|
|
1813
|
+
max_items,
|
|
1814
|
+
max_cost_usd,
|
|
1815
|
+
}));
|
|
1816
|
+
case "experiments_create":
|
|
1817
|
+
return target.evals.experiments.create(experimentRequest({
|
|
1818
|
+
workflow,
|
|
1819
|
+
dataset,
|
|
1820
|
+
b,
|
|
1821
|
+
max_items,
|
|
1822
|
+
max_cost_usd,
|
|
1823
|
+
}));
|
|
1731
1824
|
}
|
|
1732
1825
|
});
|
|
1733
1826
|
});
|
|
@@ -1757,10 +1850,19 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
1757
1850
|
return run(client, "lbb_evals_delete", detail, () => datasets.delete(id));
|
|
1758
1851
|
});
|
|
1759
1852
|
host.register("lbb_commit", {
|
|
1760
|
-
description: `Write graph facts, retract them, or label ranked results. mode=facts writes triplets/embeddings/properties; mode=retract removes a wrongly-added fact (by edge or by entity) without a full reset; mode=search_feedback stores query/result relevance labels (Feedback grades: 3=ideal/good, 1=partial, 0=bad; include query, search_id when available, target, rank, score). Explicit idempotency_key wins; when omitted, MCP derives a stable content hash so content-identical retries dedupe. Facts mode defaults edge_idempotency to append; pass skip_unchanged for re-runnable backfills. A retract asks the person to approve the links and records it removes first. ${APPROVAL_TEXT}`,
|
|
1853
|
+
description: `Write graph facts, retract them, or label ranked results. Explicit mode=records forwards a strict records/1 body, with exact IRIs/terms and the original receipt. It requires an existing support-enabled graph; protocol recognition never activates or converts a graph. Record edits that may remove support (set/unset/remove/delete/replace_scope or asserted=false) ask for approval. mode=facts writes triplets/embeddings/properties; mode=retract removes a wrongly-added fact (by edge or by entity) without a full reset; mode=search_feedback stores query/result relevance labels (Feedback grades: 3=ideal/good, 1=partial, 0=bad; include query, search_id when available, target, rank, score). Explicit idempotency_key wins; when omitted, MCP derives a stable content hash so content-identical retries dedupe. Facts mode defaults edge_idempotency to append; pass skip_unchanged for re-runnable backfills. A retract asks the person to approve the links and records it removes first. ${APPROVAL_TEXT}`,
|
|
1761
1854
|
inputSchema: {
|
|
1762
1855
|
idempotency_key: z.string().optional(),
|
|
1763
|
-
mode: z
|
|
1856
|
+
mode: z
|
|
1857
|
+
.enum(["facts", "retract", "search_feedback", "records"])
|
|
1858
|
+
.optional(),
|
|
1859
|
+
records: jsonObjectSchema
|
|
1860
|
+
.optional()
|
|
1861
|
+
.describe("Explicit mode=records only: the unchanged RecordCommitRequest object with version=records/1. Unknown fields are preserved for strict server validation; no native IRI normalization or scalar coercion. Cannot mix legacy write arguments."),
|
|
1862
|
+
expected_graph_epoch: z
|
|
1863
|
+
.string()
|
|
1864
|
+
.optional()
|
|
1865
|
+
.describe("mode=records only: exact decimal graph incarnation guard. Protocol support does not activate this graph."),
|
|
1764
1866
|
triplets: z
|
|
1765
1867
|
.array(z.object({
|
|
1766
1868
|
source: z.object({ type: z.string(), name: z.string() }),
|
|
@@ -1821,7 +1923,30 @@ export function registerLbbTools(server, client, options = {}) {
|
|
|
1821
1923
|
},
|
|
1822
1924
|
// A retract removes facts, so the tool may destroy.
|
|
1823
1925
|
annotations: DESTRUCTIVE,
|
|
1824
|
-
}, async ({ idempotency_key, mode, triplets, entity_embeddings, entity_properties, search_feedback, dry_run, observed_at, edge_idempotency, retract_edges, retract_entities, graph, }, extra) => {
|
|
1926
|
+
}, async ({ idempotency_key, mode, records, expected_graph_epoch, triplets, entity_embeddings, entity_properties, search_feedback, dry_run, observed_at, edge_idempotency, retract_edges, retract_entities, graph, }, extra) => {
|
|
1927
|
+
if (mode === "records" ||
|
|
1928
|
+
records !== undefined ||
|
|
1929
|
+
expected_graph_epoch !== undefined) {
|
|
1930
|
+
if (mode !== "records")
|
|
1931
|
+
return errorResult(new ToolError("record_mode_required", "records and expected_graph_epoch require explicit mode=records; no implicit legacy write was sent"));
|
|
1932
|
+
return commitRecordsTool(host, client, extra, {
|
|
1933
|
+
records,
|
|
1934
|
+
idempotencyKey: idempotency_key,
|
|
1935
|
+
expectedGraphEpoch: expected_graph_epoch,
|
|
1936
|
+
graph,
|
|
1937
|
+
legacyInputs: [
|
|
1938
|
+
triplets,
|
|
1939
|
+
entity_embeddings,
|
|
1940
|
+
entity_properties,
|
|
1941
|
+
search_feedback,
|
|
1942
|
+
dry_run,
|
|
1943
|
+
observed_at,
|
|
1944
|
+
edge_idempotency,
|
|
1945
|
+
retract_edges,
|
|
1946
|
+
retract_entities,
|
|
1947
|
+
].some((value) => value !== undefined),
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1825
1950
|
const commitMode = mode ??
|
|
1826
1951
|
(search_feedback
|
|
1827
1952
|
? "search_feedback"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@littlebigbrain/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "MCP server for little big brain: search, graph queries, and data import",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"test:coverage": "npm run clean:test && tsc -p tsconfig.test.json && node --test --experimental-test-coverage --test-coverage-include=test-dist/*.js --test-coverage-exclude=test-dist/*.test.js --test-coverage-exclude=test-dist/stdio.js --test-coverage-exclude=test-dist/test-support.js --test-coverage-lines=90 --test-coverage-branches=70 --test-coverage-functions=85 \"test-dist/**/*.test.js\""
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@littlebigbrain/client": "^0.
|
|
55
|
+
"@littlebigbrain/client": "^0.32.0",
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1",
|
|
57
57
|
"zod": "^4.6.6"
|
|
58
58
|
},
|