@factbag/clef 0.0.0-alpha.5 → 0.0.0-alpha.6
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/CHANGELOG.md +21 -0
- package/dist/public/index.d.ts +25 -2
- package/dist/public/index.js +154 -48
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @factbag/clef
|
|
2
2
|
|
|
3
|
+
## 0.0.0-alpha.6
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6433c6b: Classify open-ended `String`, `Number`, and `Date` fields, and entity fields with no registered entities, over the values each passage offers. Pass `createChunkSymbolExtractor` to `classifier(...)` from `@factbag/jev` or `@factbag/clef` to build an extractor per chunk whose `query` answers candidate values for the open fields of the predicates it classifies. Both packages export the `ChunkSymbolExtractor`, `CreateChunkSymbolExtractor`, and `SymbolAnswer` types.
|
|
8
|
+
|
|
9
|
+
Extracted values are candidates only. An extracted entity is registered in `run.kb.entities` when asserted or denied evidence mentions it; entities the model does not admit evidence for are discarded.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [7225a51]
|
|
14
|
+
- Updated dependencies [7f26b45]
|
|
15
|
+
- Updated dependencies [b7c85ff]
|
|
16
|
+
- Updated dependencies [ed6ec84]
|
|
17
|
+
- Updated dependencies [5114b2f]
|
|
18
|
+
- Updated dependencies [15391f2]
|
|
19
|
+
- Updated dependencies [eab3679]
|
|
20
|
+
- Updated dependencies [ecbd8d9]
|
|
21
|
+
- Updated dependencies [e4b343e]
|
|
22
|
+
- factbag@0.0.0-alpha.6
|
|
23
|
+
|
|
3
24
|
## 0.0.0-alpha.5
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
package/dist/public/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GuardedKB, KnowledgeBase } from "factbag/kb";
|
|
2
|
-
import { Corpus, Metadata } from "factbag/corpus";
|
|
2
|
+
import { Chunk, Corpus, Metadata } from "factbag/corpus";
|
|
3
|
+
import { Entity, PredicateDeclaration, Scalar } from "factbag/dsl";
|
|
3
4
|
/** Default minimum probability for admitting signed evidence. @beta */
|
|
4
5
|
export declare const DEFAULT_MINIMUM_PROBABILITY = .4;
|
|
5
6
|
/** Default maximum probability gap from the strongest signed outcome. @beta */
|
|
@@ -31,6 +32,25 @@ type Settings = {
|
|
|
31
32
|
};
|
|
32
33
|
/** Settings with every default applied. @beta */
|
|
33
34
|
type Normalized = Readonly<Required<Settings>>;
|
|
35
|
+
/**
|
|
36
|
+
* Candidate values for each requested field, by predicate and then by field name. Open
|
|
37
|
+
* fields are `String`, `Number`, `Date`, and entity fields. The same value may be offered
|
|
38
|
+
* to any number of fields.
|
|
39
|
+
* @beta
|
|
40
|
+
*/
|
|
41
|
+
type SymbolAnswer = ReadonlyMap<PredicateDeclaration, Readonly<Record<string, readonly (Scalar | Entity)[]>>>;
|
|
42
|
+
/**
|
|
43
|
+
* Candidate symbols found in a fixed set of chunks. How and when it reads them, and what
|
|
44
|
+
* it caches between queries, is up to the implementation. An entity's type and id are its
|
|
45
|
+
* identity, so the ids it mints decide which mentions are the same entity.
|
|
46
|
+
* @beta
|
|
47
|
+
*/
|
|
48
|
+
type ChunkSymbolExtractor = {
|
|
49
|
+
/** Candidate values for exactly the requested fields of each requested predicate. */
|
|
50
|
+
readonly query: (requests: ReadonlyMap<PredicateDeclaration, readonly string[]>) => SymbolAnswer | Promise<SymbolAnswer>;
|
|
51
|
+
};
|
|
52
|
+
/** Build an extractor over the chunks it will be queried about. @beta */
|
|
53
|
+
type CreateChunkSymbolExtractor = (chunks: readonly Chunk[]) => ChunkSymbolExtractor;
|
|
34
54
|
/** One opaque unit of work. Its effects are written to the run’s knowledge base. @beta */
|
|
35
55
|
type Job = {
|
|
36
56
|
readonly execute: () => Promise<void>;
|
|
@@ -66,12 +86,15 @@ export declare function createConfig(config?: Config): Normalized & Config;
|
|
|
66
86
|
* Validate available predicates eagerly and return a lazy iterator of executable jobs.
|
|
67
87
|
* Bare KBs expose all known predicates; explicit guards retain their permission policy.
|
|
68
88
|
* Only job.execute() makes a remote call. The consumer owns pacing and concurrency.
|
|
89
|
+
* With `createChunkSymbolExtractor`, open-ended and entity fields also take the values
|
|
90
|
+
* each chunk offers, so they need no finite domain up front.
|
|
69
91
|
* @beta
|
|
70
92
|
*/
|
|
71
93
|
export declare function classifier<M extends Metadata = Metadata>(input: {
|
|
72
94
|
corpus: Corpus<M>;
|
|
73
95
|
input: KnowledgeBase | GuardedKB;
|
|
74
96
|
config?: Config;
|
|
97
|
+
createChunkSymbolExtractor?: CreateChunkSymbolExtractor;
|
|
75
98
|
}): Run;
|
|
76
99
|
/**
|
|
77
100
|
* Settle contradictions in a knowledge base by reading each one's evidence together.
|
|
@@ -87,4 +110,4 @@ export declare function classifier<M extends Metadata = Metadata>(input: {
|
|
|
87
110
|
* @beta
|
|
88
111
|
*/
|
|
89
112
|
export declare function resolver<M extends Metadata = Metadata>(corpus: Corpus<M>, kb: KnowledgeBase, config?: Config): Run;
|
|
90
|
-
export type { Config, Job, Normalized, Run, Settings };
|
|
113
|
+
export type { ChunkSymbolExtractor, Config, CreateChunkSymbolExtractor, Job, Normalized, Run, Settings, SymbolAnswer };
|
package/dist/public/index.js
CHANGED
|
@@ -10,7 +10,9 @@ import { fieldEquals } from "factbag/dsl";
|
|
|
10
10
|
* Query-only predicates are excluded.
|
|
11
11
|
*
|
|
12
12
|
* Called once when constructing a run, so unsupported predicates fail before reading
|
|
13
|
-
* the corpus or calling Jev.
|
|
13
|
+
* the corpus or calling Jev. Each field starts with the values known before any passage
|
|
14
|
+
* is read: open-ended fields, and entity fields with no registered entities, start empty.
|
|
15
|
+
* Candidate combinations are built later, after filtering.
|
|
14
16
|
*/
|
|
15
17
|
function classifiablePredicates(access) {
|
|
16
18
|
const actions = /* @__PURE__ */ new Map();
|
|
@@ -29,10 +31,12 @@ function classifiablePredicates(access) {
|
|
|
29
31
|
};
|
|
30
32
|
});
|
|
31
33
|
}
|
|
32
|
-
/** Validate a
|
|
34
|
+
/** Validate a field's type and collect the values known before reading any passage. */
|
|
33
35
|
function fieldValues({ predicate, name, kb }) {
|
|
34
36
|
const type = predicate.params[name];
|
|
35
|
-
if (type ===
|
|
37
|
+
if (type === void 0) throw new UnsupportedPredicateError(`Invalid schema for ${predicate.name}: field "${name}" has no type`, predicate, name);
|
|
38
|
+
/** Open-ended fields take values only from passages. */
|
|
39
|
+
if (type === String || type === Number || type === Date) return [];
|
|
36
40
|
if (type === Boolean) return [false, true];
|
|
37
41
|
if (typeof type === "object") {
|
|
38
42
|
const kind = type.values[0] instanceof Date ? "date" : typeof type.values[0];
|
|
@@ -40,14 +44,57 @@ function fieldValues({ predicate, name, kb }) {
|
|
|
40
44
|
if (type.values.length === 0 || kind !== "string" && kind !== "number" && kind !== "date" || type.values.some((value) => (value instanceof Date ? "date" : typeof value) !== kind) || keys.some((key) => typeof key === "number" && Number.isNaN(key)) || new Set(keys).size !== type.values.length) throw new UnsupportedPredicateError(`Invalid enum domain for ${predicate.name}.${name}`, predicate, name);
|
|
41
45
|
return [...type.values];
|
|
42
46
|
}
|
|
43
|
-
|
|
44
|
-
if (ids.length === 0) throw new UnsupportedPredicateError(`No registered entities for ${predicate.name}.${name}`, predicate, name);
|
|
45
|
-
return ids.map((id) => {
|
|
47
|
+
return kb.entities.ids(type).map((id) => {
|
|
46
48
|
const entity = kb.entities.get(type, id);
|
|
47
49
|
if (entity === void 0) throw new Error("A registered entity domain was modified");
|
|
48
50
|
return entity;
|
|
49
51
|
});
|
|
50
52
|
}
|
|
53
|
+
// ../../packages/system-one/src/utils.ts
|
|
54
|
+
/** Snapshot JSON data, including nested dates and entities identified by a string id. */
|
|
55
|
+
function encode(value) {
|
|
56
|
+
const entities = /* @__PURE__ */ new WeakMap();
|
|
57
|
+
const json = JSON.stringify(value, (_, item) => {
|
|
58
|
+
if (!isRecord$1(item) || typeof item["id"] !== "string") return item;
|
|
59
|
+
let entity = entities.get(item);
|
|
60
|
+
if (entity === void 0) {
|
|
61
|
+
entity = {
|
|
62
|
+
...item,
|
|
63
|
+
type: typeof item.constructor === "function" ? item.constructor.name : "Object"
|
|
64
|
+
};
|
|
65
|
+
entities.set(item, entity);
|
|
66
|
+
}
|
|
67
|
+
return entity;
|
|
68
|
+
});
|
|
69
|
+
if (typeof json !== "string") throw new TypeError("Cannot encode a value without a JSON representation");
|
|
70
|
+
return JSON.parse(json);
|
|
71
|
+
}
|
|
72
|
+
/** A compact diagnostic label, using entity ids and the predicate's declared field order. */
|
|
73
|
+
function formatAtom({ predicate, fields }) {
|
|
74
|
+
return `${predicate.name}(${predicate.paramNames.map((name) => format(fields[name])).join(", ")})`;
|
|
75
|
+
}
|
|
76
|
+
function format(value) {
|
|
77
|
+
if (value instanceof Date) return value.toISOString();
|
|
78
|
+
return isRecord$1(value) && "id" in value ? String(value["id"]) : String(value);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Fail fast when an invariant breaks: a bug in Factbag, never bad input from the caller,
|
|
82
|
+
* which gets a descriptive error of its own.
|
|
83
|
+
*/
|
|
84
|
+
function assert(condition, message) {
|
|
85
|
+
if (!condition) throw new Error(`Assertion failed: ${message}`);
|
|
86
|
+
}
|
|
87
|
+
function isRecord$1(value) {
|
|
88
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
89
|
+
}
|
|
90
|
+
/** An entity value: an object with a string id, other than a Date. */
|
|
91
|
+
function isEntity(value) {
|
|
92
|
+
return typeof value === "object" && value !== null && !(value instanceof Date) && "id" in value && typeof value.id === "string";
|
|
93
|
+
}
|
|
94
|
+
/** A finite probability, including both endpoints. */
|
|
95
|
+
function isProbability(value) {
|
|
96
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
97
|
+
}
|
|
51
98
|
// ../../packages/system-one/src/classifier/extract-evidence.ts
|
|
52
99
|
/**
|
|
53
100
|
* Assess the retained propositions against one passage and add admitted evidence to
|
|
@@ -67,6 +114,12 @@ async function extractEvidence({ chunk, predicates, symbols, config, kb }) {
|
|
|
67
114
|
})))).flat();
|
|
68
115
|
for (const { sign, atom, metadata } of evidence) if (sign === "+") kb.asserts(atom, metadata);
|
|
69
116
|
else kb.denies(atom, metadata);
|
|
117
|
+
/**
|
|
118
|
+
* Register the entities that written evidence mentions, so later readers of the KB can
|
|
119
|
+
* enumerate them. Extracted candidates that no evidence mentions stay unregistered.
|
|
120
|
+
* Identity is type and id, so an entity from several chunks is registered once.
|
|
121
|
+
*/
|
|
122
|
+
kb.entities.add(...evidence.flatMap(({ atom }) => Object.values(atom.fields)).filter((value) => isEntity(value)));
|
|
70
123
|
}
|
|
71
124
|
/**
|
|
72
125
|
* Enumerate one predicate's candidate facts, have the model judge each against the passage,
|
|
@@ -123,6 +176,59 @@ function enumerateCandidateFacts({ predicate, symbols }) {
|
|
|
123
176
|
fields: values
|
|
124
177
|
}));
|
|
125
178
|
}
|
|
179
|
+
// ../../packages/system-one/src/classifier/extract-symbols.ts
|
|
180
|
+
/**
|
|
181
|
+
* Widen each retained predicate's open fields with the values this chunk offers. This is
|
|
182
|
+
* the second pipeline step, after predicate filtering, so only fields of predicates the
|
|
183
|
+
* passage may bear on are queried. Boolean and enumeration fields keep their declared
|
|
184
|
+
* domains; entity fields keep their registered entities. Extracted entities are only
|
|
185
|
+
* candidates: nothing is registered here. Fields may still be empty afterwards.
|
|
186
|
+
*/
|
|
187
|
+
async function extractSymbols({ chunk, predicates, createExtractor }) {
|
|
188
|
+
const requests = new Map(predicates.map(({ declaration }) => [declaration, openFields(declaration)]).filter(([, fields]) => fields.length > 0));
|
|
189
|
+
/** Nothing to ask builds no extractor. */
|
|
190
|
+
if (createExtractor === void 0 || requests.size === 0) return predicates;
|
|
191
|
+
const answer = await createExtractor([chunk]).query(requests);
|
|
192
|
+
assertAnswers(requests, answer);
|
|
193
|
+
return predicates.map((predicate) => ({
|
|
194
|
+
...predicate,
|
|
195
|
+
roles: Object.fromEntries(Object.entries(predicate.roles).map(([name, domain]) => [name, unique([...domain, ...answer.get(predicate.declaration)?.[name] ?? []])]))
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
/** The fields whose values come from the passage: every field but Boolean and enumeration ones. */
|
|
199
|
+
function openFields(predicate) {
|
|
200
|
+
return predicate.paramNames.filter((name) => {
|
|
201
|
+
const type = predicate.params[name];
|
|
202
|
+
return typeof type === "function" && type !== Boolean;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* The answer must cover exactly the requested predicates and fields, with values each
|
|
207
|
+
* field can hold, so an extractor bug fails the job before anything is asked.
|
|
208
|
+
*/
|
|
209
|
+
function assertAnswers(requests, answer) {
|
|
210
|
+
for (const predicate of answer.keys()) if (!requests.has(predicate)) throw new TypeError(`Extracted symbols answer unrequested predicate "${predicate.name}"`);
|
|
211
|
+
for (const [predicate, fields] of requests) {
|
|
212
|
+
const values = answer.get(predicate);
|
|
213
|
+
if (values === void 0) throw new TypeError(`Extracted symbols omit requested predicate "${predicate.name}"`);
|
|
214
|
+
if (Object.keys(values).length !== fields.length || !fields.every((name) => Object.hasOwn(values, name))) throw new TypeError(`Extracted symbols for "${predicate.name}" must answer exactly its fields: ${fields.join(", ")}`);
|
|
215
|
+
for (const name of fields) {
|
|
216
|
+
const type = predicate.params[name];
|
|
217
|
+
if (!(values[name] ?? []).every((value) => holds(type, value))) throw new TypeError(`Extracted symbols for ${predicate.name}.${name} must be valid values of its type`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** Whether a field of this type can hold the value: a finite number, valid date, or own entity. */
|
|
222
|
+
function holds(type, value) {
|
|
223
|
+
if (type === String) return typeof value === "string";
|
|
224
|
+
if (type === Number) return typeof value === "number" && Number.isFinite(value);
|
|
225
|
+
if (type === Date) return value instanceof Date && !Number.isNaN(value.getTime());
|
|
226
|
+
return isEntity(value) && value.constructor === type;
|
|
227
|
+
}
|
|
228
|
+
/** Drop structural duplicates, keeping the first occurrence. */
|
|
229
|
+
function unique(values) {
|
|
230
|
+
return values.filter((value, index) => values.findIndex((other) => fieldEquals(value, other)) === index);
|
|
231
|
+
}
|
|
126
232
|
// ../../packages/system-one/src/classifier/filter-predicates.ts
|
|
127
233
|
/** Retain the predicates relevant to this passage. This is the first pipeline step. */
|
|
128
234
|
async function filterPredicates({ chunk, predicates, config }) {
|
|
@@ -161,11 +267,15 @@ function needsFiltering(predicates, config) {
|
|
|
161
267
|
* Validate available predicates eagerly and return a lazy iterator of executable jobs.
|
|
162
268
|
* Bare KBs expose all known predicates; explicit guards retain their permission policy.
|
|
163
269
|
* Only job.execute() makes a remote call. The consumer owns pacing and concurrency.
|
|
270
|
+
* With `createChunkSymbolExtractor`, open-ended and entity fields also take the values
|
|
271
|
+
* each chunk offers, so they need no finite domain up front. Without one, a predicate
|
|
272
|
+
* with a field that has no values fails here.
|
|
164
273
|
*/
|
|
165
|
-
function classifier$1({ corpus, input, config }) {
|
|
274
|
+
function classifier$1({ corpus, input, config, createChunkSymbolExtractor }) {
|
|
166
275
|
const baselineKB = (input instanceof GuardedKB ? input : guard(input).permit("*")).fork();
|
|
167
276
|
const outputKB = baselineKB.kb.fork();
|
|
168
277
|
const predicates = classifiablePredicates(baselineKB);
|
|
278
|
+
if (createChunkSymbolExtractor === void 0) assertAskable(predicates);
|
|
169
279
|
const chunker = chunkers.recursive({
|
|
170
280
|
size: 8e3,
|
|
171
281
|
overlap: 200
|
|
@@ -173,16 +283,21 @@ function classifier$1({ corpus, input, config }) {
|
|
|
173
283
|
const jobs = corpus.documents().values().filter(() => predicates.length > 0).flatMap((document) => corpus.chunk(document, chunker)).map((chunk) => ({ execute: async () => await processChunk({
|
|
174
284
|
chunk,
|
|
175
285
|
predicates,
|
|
286
|
+
createExtractor: createChunkSymbolExtractor,
|
|
176
287
|
config,
|
|
177
288
|
kb: outputKB
|
|
178
289
|
}) }));
|
|
179
290
|
return Object.assign(jobs, { kb: outputKB });
|
|
180
291
|
}
|
|
181
|
-
async function processChunk({ chunk, predicates: inputPredicates, config, kb }) {
|
|
182
|
-
const predicates = await
|
|
292
|
+
async function processChunk({ chunk, predicates: inputPredicates, createExtractor, config, kb }) {
|
|
293
|
+
const predicates = await extractSymbols({
|
|
183
294
|
chunk,
|
|
184
|
-
predicates:
|
|
185
|
-
|
|
295
|
+
predicates: await filterPredicates({
|
|
296
|
+
chunk,
|
|
297
|
+
predicates: inputPredicates,
|
|
298
|
+
config
|
|
299
|
+
}),
|
|
300
|
+
createExtractor
|
|
186
301
|
});
|
|
187
302
|
await extractEvidence({
|
|
188
303
|
chunk,
|
|
@@ -196,39 +311,25 @@ async function processChunk({ chunk, predicates: inputPredicates, config, kb })
|
|
|
196
311
|
kb
|
|
197
312
|
});
|
|
198
313
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
});
|
|
215
|
-
if (typeof json !== "string") throw new TypeError("Cannot encode a value without a JSON representation");
|
|
216
|
-
return JSON.parse(json);
|
|
217
|
-
}
|
|
218
|
-
/** A compact diagnostic label, using entity ids and the predicate's declared field order. */
|
|
219
|
-
function formatAtom({ predicate, fields }) {
|
|
220
|
-
return `${predicate.name}(${predicate.paramNames.map((name) => format(fields[name])).join(", ")})`;
|
|
221
|
-
}
|
|
222
|
-
function format(value) {
|
|
223
|
-
if (value instanceof Date) return value.toISOString();
|
|
224
|
-
return isRecord$1(value) && "id" in value ? String(value["id"]) : String(value);
|
|
225
|
-
}
|
|
226
|
-
function isRecord$1(value) {
|
|
227
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
314
|
+
/**
|
|
315
|
+
* Throw for the first predicate with a field that has no values. Without a symbol
|
|
316
|
+
* extractor, fields hold only the values known up front, so such a predicate could never
|
|
317
|
+
* be asked about.
|
|
318
|
+
*/
|
|
319
|
+
function assertAskable(predicates) {
|
|
320
|
+
for (const { declaration, roles } of predicates) {
|
|
321
|
+
const name = emptyField({
|
|
322
|
+
declaration,
|
|
323
|
+
roles
|
|
324
|
+
});
|
|
325
|
+
if (name === void 0) continue;
|
|
326
|
+
const type = declaration.params[name];
|
|
327
|
+
throw new UnsupportedPredicateError(type === String || type === Number || type === Date ? `Cannot classify predicate "${declaration.name}": field "${name}" has an unsupported open-ended domain. Use Boolean, enumeration(...), registered entities, or a symbol extractor.` : `No registered entities for ${declaration.name}.${name}`, declaration, name);
|
|
328
|
+
}
|
|
228
329
|
}
|
|
229
|
-
/**
|
|
230
|
-
function
|
|
231
|
-
return
|
|
330
|
+
/** The first field, in declared order, with no values. */
|
|
331
|
+
function emptyField({ declaration, roles }) {
|
|
332
|
+
return declaration.paramNames.find((name) => (roles[name] ?? []).length === 0);
|
|
232
333
|
}
|
|
233
334
|
// ../../packages/system-one/src/config.ts
|
|
234
335
|
/** Default minimum probability for admitting signed evidence. @beta */
|
|
@@ -411,7 +512,7 @@ function systemOneModel(endpoint) {
|
|
|
411
512
|
judgement: choice(response, OUTCOMES, id, name)
|
|
412
513
|
})
|
|
413
514
|
})),
|
|
414
|
-
adjudicate: async ({ atom, passages }) => await ask({
|
|
515
|
+
adjudicate: async ({ atom, passages }) => passages.length === 0 ? [] : await ask({
|
|
415
516
|
endpoint,
|
|
416
517
|
prefix: "passage",
|
|
417
518
|
items: passages,
|
|
@@ -433,10 +534,12 @@ async function inBatches(items, size, request) {
|
|
|
433
534
|
* Ask one question per item in a single request and turn each answer into a result.
|
|
434
535
|
* Question keys are positional, so they are minted and matched only here. Validation
|
|
435
536
|
* happens while building results, so one bad answer rejects the whole request.
|
|
436
|
-
*
|
|
537
|
+
* Every request holds at least one question: callers send nothing when there is nothing
|
|
538
|
+
* to ask, so an empty request is a bug. More than the provider allows sends nothing and
|
|
539
|
+
* throws.
|
|
437
540
|
*/
|
|
438
541
|
async function ask({ endpoint: { name, maxBatchSize, transport }, prefix, items, state, question, answer }) {
|
|
439
|
-
|
|
542
|
+
assert(items.length > 0, `Asked ${name} an empty ${prefix} request`);
|
|
440
543
|
if (items.length > maxBatchSize) throw new RangeError(`Cannot ask ${items.length} ${prefix} questions in one request: ${name} accepts at most ${maxBatchSize}`);
|
|
441
544
|
const keyed = items.map((item, index) => ({
|
|
442
545
|
id: `${prefix}_${index}`,
|
|
@@ -583,10 +686,11 @@ function systemOne(provider) {
|
|
|
583
686
|
pipelines.set(normalized, resolved);
|
|
584
687
|
return normalized;
|
|
585
688
|
},
|
|
586
|
-
classifier: ({ corpus, input, config }) => classifier$1({
|
|
689
|
+
classifier: ({ corpus, input, config, createChunkSymbolExtractor }) => classifier$1({
|
|
587
690
|
corpus,
|
|
588
691
|
input,
|
|
589
|
-
config: pipeline(config)
|
|
692
|
+
config: pipeline(config),
|
|
693
|
+
createChunkSymbolExtractor
|
|
590
694
|
}),
|
|
591
695
|
resolver: (corpus, kb, config) => resolver$1(corpus, kb, pipeline(config))
|
|
592
696
|
};
|
|
@@ -657,6 +761,8 @@ function createConfig(config) {
|
|
|
657
761
|
* Validate available predicates eagerly and return a lazy iterator of executable jobs.
|
|
658
762
|
* Bare KBs expose all known predicates; explicit guards retain their permission policy.
|
|
659
763
|
* Only job.execute() makes a remote call. The consumer owns pacing and concurrency.
|
|
764
|
+
* With `createChunkSymbolExtractor`, open-ended and entity fields also take the values
|
|
765
|
+
* each chunk offers, so they need no finite domain up front.
|
|
660
766
|
* @beta
|
|
661
767
|
*/
|
|
662
768
|
function classifier(input) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@factbag/clef",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.6",
|
|
4
4
|
"description": "Lazy AI-assisted classification and evidence resolution for Factbag, using Cloudflare's Clef models on Workers AI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"classification",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"vitest": "5.0.5"
|
|
88
88
|
},
|
|
89
89
|
"peerDependencies": {
|
|
90
|
-
"factbag": "0.0.0-alpha.
|
|
90
|
+
"factbag": "0.0.0-alpha.6"
|
|
91
91
|
},
|
|
92
92
|
"scripts": {
|
|
93
93
|
"api-report": "api-extractor run --local",
|