@absolutejs/rag 0.18.1 → 0.20.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/CHANGELOG.md +16 -0
- package/RESEARCH.md +39 -0
- package/changelog.json +32 -0
- package/dist/research/index.js +113 -30
- package/dist/research/index.js.map +4 -4
- package/dist/src/research/evidence.d.ts +28 -2
- package/dist/src/research/types.d.ts +12 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@ This file is generated by `absolute-changelog` from the entries in
|
|
|
6
6
|
`changelog/`. Edit an entry, not this file — and add new ones under
|
|
7
7
|
`changelog/unreleased/`.
|
|
8
8
|
|
|
9
|
+
## 0.20.0 — 2026-09-24
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Republish the tested research quality improvements as 0.20.0 because npm has not made 0.19.0 installable.**
|
|
14
|
+
|
|
15
|
+
## 0.19.0 — 2026-09-24
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **Allow a separately configured review provider and model through the existing host accounting path.** (`ResearchConfig`)
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **Require relevance, entity, time and scope checks before supporting research fields; carry task criteria into review, bound default findings and share evidence space with follow-up sources. Resolve model-selected passage IDs to exact source text instead of asking models to transcribe quotes.** (`bindResearchReview`, `ResearchField`, `createResearch`)
|
|
24
|
+
|
|
9
25
|
## 0.18.1 — 2026-09-24
|
|
10
26
|
|
|
11
27
|
### Fixed
|
package/RESEARCH.md
CHANGED
|
@@ -266,3 +266,42 @@ const mcpTools = toMcpToolRegistry(researchManifest, {
|
|
|
266
266
|
Use `evaluateResearch` to run representative cases with optional independent human review and total measured cost. Unreviewed acceptance, unknown cost, and cost per accepted finding without accepted findings remain `null`. Publication freshness measures source publication dates; it is not event freshness.
|
|
267
267
|
|
|
268
268
|
Each result includes `groundingCase`, accepted by the existing `evaluateRAGAnswerGrounding({cases})` function from `@absolutejs/rag/quality`. Citation resolution metrics do not establish factual correctness or discovery recall. Keep approved ground truth and reviewer identity with comparisons, and hold task/model/budget policy constant when switching search providers.
|
|
269
|
+
|
|
270
|
+
### Research quality and reviewer configuration
|
|
271
|
+
|
|
272
|
+
Default research returns at most six concise findings. Every supported field must
|
|
273
|
+
have source-bound quotes and positive model checks for task relevance, entity,
|
|
274
|
+
time and scope. Missing or failed checks withhold support even when a quote
|
|
275
|
+
matches. `bindResearchReview` callers must now supply `checks` to promote a field.
|
|
276
|
+
These checks are fallible model judgments, not human or independent verification.
|
|
277
|
+
Task instructions and schema are supplied to both extraction and review.
|
|
278
|
+
Internally, the reviewer selects numbered source passages and the runtime copies
|
|
279
|
+
their text verbatim into citations. Invented or ambiguous references fail closed;
|
|
280
|
+
this removes quote transcription errors without establishing factual entailment.
|
|
281
|
+
|
|
282
|
+
The evidence budget is shared across sources instead of consumed entirely by
|
|
283
|
+
first results. Fetched pages replace their snippets and receive four times the
|
|
284
|
+
allocation weight of snippet-only sources; short sources donate unused space.
|
|
285
|
+
This preserves more page context while reserving room for follow-up retrieval.
|
|
286
|
+
A maximum of 64 unique sources bounds retained evidence. Truncation can still
|
|
287
|
+
omit necessary context; inspect limitations and field verdicts.
|
|
288
|
+
|
|
289
|
+
A separate reviewer can be configured without changing planning or extraction:
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
const research = createResearch({
|
|
293
|
+
search,
|
|
294
|
+
provider,
|
|
295
|
+
model: extractionModel,
|
|
296
|
+
reviewer: { provider: reviewProvider, model: reviewModel },
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Omitting `reviewer` uses the main provider/model. The host generation adapter and
|
|
301
|
+
admission/settlement path still apply to review calls. Price review reservations
|
|
302
|
+
and usage with the configured reviewer; do not apply the extraction model's rate
|
|
303
|
+
to a different model. A stronger reviewer does not repair missing retrieval.
|
|
304
|
+
|
|
305
|
+
An invalid planner schema no longer discards retrieved evidence: extraction and
|
|
306
|
+
review continue without retry, while the result remains partial and the failed
|
|
307
|
+
plan retains unknown settlement. Budget denial and cancellation still stop work.
|
package/changelog.json
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
"contract": 1,
|
|
3
3
|
"name": "@absolutejs/rag",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"changes": [
|
|
7
|
+
{
|
|
8
|
+
"kind": "fixed",
|
|
9
|
+
"summary": "Republish the tested research quality improvements as 0.20.0 because npm has not made 0.19.0 installable."
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"date": "2026-09-24",
|
|
13
|
+
"version": "0.20.0"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"changes": [
|
|
17
|
+
{
|
|
18
|
+
"kind": "added",
|
|
19
|
+
"summary": "Allow a separately configured review provider and model through the existing host accounting path.",
|
|
20
|
+
"symbols": [
|
|
21
|
+
"ResearchConfig"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"kind": "fixed",
|
|
26
|
+
"summary": "Require relevance, entity, time and scope checks before supporting research fields; carry task criteria into review, bound default findings and share evidence space with follow-up sources. Resolve model-selected passage IDs to exact source text instead of asking models to transcribe quotes.",
|
|
27
|
+
"symbols": [
|
|
28
|
+
"bindResearchReview",
|
|
29
|
+
"ResearchField",
|
|
30
|
+
"createResearch"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"date": "2026-09-24",
|
|
35
|
+
"version": "0.19.0"
|
|
36
|
+
},
|
|
5
37
|
{
|
|
6
38
|
"changes": [
|
|
7
39
|
{
|
package/dist/research/index.js
CHANGED
|
@@ -124141,8 +124141,16 @@ var ReviewSchema = Type2.Object({
|
|
|
124141
124141
|
fields: Type2.Array(Type2.Object({
|
|
124142
124142
|
path: Type2.String(),
|
|
124143
124143
|
verdict: Type2.Union(["supported", "unsupported", "conflicting", "unknown"].map((value) => Type2.Literal(value))),
|
|
124144
|
-
citations: Type2.Array(Type2.Object({
|
|
124145
|
-
|
|
124144
|
+
citations: Type2.Array(Type2.Object({ passageId: Type2.String() }), {
|
|
124145
|
+
maxItems: 8
|
|
124146
|
+
}),
|
|
124147
|
+
reason: Type2.String(),
|
|
124148
|
+
checks: Type2.Object({
|
|
124149
|
+
answersQuestion: Type2.Boolean(),
|
|
124150
|
+
correctEntity: Type2.Boolean(),
|
|
124151
|
+
correctTime: Type2.Boolean(),
|
|
124152
|
+
preservesScope: Type2.Boolean()
|
|
124153
|
+
})
|
|
124146
124154
|
}), { maxItems: 256 })
|
|
124147
124155
|
});
|
|
124148
124156
|
var researchLeaves = (value, maxFields = 128) => {
|
|
@@ -124172,18 +124180,53 @@ var bindResearchReview = (leaves, reviews, sources) => leaves.map((leaf) => {
|
|
|
124172
124180
|
return matching.length === 1 && sourceSupportsQuote(matching[0], citation.quote);
|
|
124173
124181
|
}) ?? [];
|
|
124174
124182
|
const valid = !!review && citations.length === review.citations.length;
|
|
124175
|
-
const
|
|
124183
|
+
const checksPass = review?.checks && [
|
|
124184
|
+
"answersQuestion",
|
|
124185
|
+
"correctEntity",
|
|
124186
|
+
"correctTime",
|
|
124187
|
+
"preservesScope"
|
|
124188
|
+
].every((key2) => review.checks?.[key2] === true);
|
|
124189
|
+
const verdict = !valid || leaf.value === null || !citations.length ? "unknown" : review.verdict === "supported" && !checksPass ? "unknown" : review.verdict;
|
|
124176
124190
|
return {
|
|
124177
124191
|
...leaf,
|
|
124178
124192
|
verdict,
|
|
124179
124193
|
citations,
|
|
124180
|
-
|
|
124194
|
+
checks: review?.checks,
|
|
124195
|
+
reason: valid && review.verdict === "supported" && !checksPass ? "Task relevance, entity, time, or scope was not established: " + review.reason : valid ? review.reason : "Missing, ambiguous, or invalid source-bound review"
|
|
124181
124196
|
};
|
|
124182
124197
|
});
|
|
124198
|
+
var researchPassages = (sources) => sources.map((source) => {
|
|
124199
|
+
const passages = [];
|
|
124200
|
+
for (const excerpt of source.excerpts) {
|
|
124201
|
+
let start = 0;
|
|
124202
|
+
while (start < excerpt.length) {
|
|
124203
|
+
let end = Math.min(start + 600, excerpt.length);
|
|
124204
|
+
if (end < excerpt.length) {
|
|
124205
|
+
const boundary = excerpt.lastIndexOf(`
|
|
124206
|
+
`, end);
|
|
124207
|
+
if (boundary > start + 300)
|
|
124208
|
+
end = boundary;
|
|
124209
|
+
if (excerpt.length - end < 12)
|
|
124210
|
+
end = excerpt.length;
|
|
124211
|
+
}
|
|
124212
|
+
passages.push({
|
|
124213
|
+
id: `${source.id}:${passages.length}`,
|
|
124214
|
+
text: excerpt.slice(start, end)
|
|
124215
|
+
});
|
|
124216
|
+
start = end;
|
|
124217
|
+
}
|
|
124218
|
+
}
|
|
124219
|
+
const { excerpts: _excerpts, ...metadata } = source;
|
|
124220
|
+
return { ...metadata, passages };
|
|
124221
|
+
});
|
|
124222
|
+
var resolveResearchPassages = (citations, sources) => citations.map((citation) => {
|
|
124223
|
+
const matches2 = sources.flatMap((source) => source.passages.filter((passage) => passage.id === citation.passageId).map((passage) => ({ sourceId: source.id, quote: passage.text })));
|
|
124224
|
+
return matches2.length === 1 ? matches2[0] : { sourceId: "", quote: "" };
|
|
124225
|
+
});
|
|
124183
124226
|
|
|
124184
124227
|
// src/research/runtime.ts
|
|
124185
124228
|
var DefaultSchema = Type2.Object({
|
|
124186
|
-
findings: Type2.Array(Type2.String(), { maxItems:
|
|
124229
|
+
findings: Type2.Array(Type2.String(), { maxItems: 6 })
|
|
124187
124230
|
});
|
|
124188
124231
|
var PlanSchema = Type2.Object({
|
|
124189
124232
|
queries: Type2.Array(Type2.String({ maxLength: 600 }), { maxItems: 8 }),
|
|
@@ -124270,8 +124313,8 @@ var createResearch = (config) => {
|
|
|
124270
124313
|
};
|
|
124271
124314
|
const generate2 = async (kind, schema, prompt, payload) => {
|
|
124272
124315
|
const generated = await operate(kind, () => (config.generateObject ?? generateObjectAI)({
|
|
124273
|
-
provider: config.provider,
|
|
124274
|
-
model: config.model,
|
|
124316
|
+
provider: kind === "review" ? config.reviewer?.provider ?? config.provider : config.provider,
|
|
124317
|
+
model: kind === "review" ? config.reviewer?.model ?? config.model : config.model,
|
|
124275
124318
|
schema,
|
|
124276
124319
|
signal,
|
|
124277
124320
|
contextPolicy: false,
|
|
@@ -124294,11 +124337,30 @@ ${prompt}`,
|
|
|
124294
124337
|
const urls = new Set;
|
|
124295
124338
|
const queries = new Set;
|
|
124296
124339
|
let evidenceChars = 0;
|
|
124340
|
+
const sourceText = new Map;
|
|
124341
|
+
const rebalanceEvidence = () => {
|
|
124342
|
+
let remaining = limits.evidenceChars;
|
|
124343
|
+
const weight = (source) => source.contentFetchedAt ? 4 : 1;
|
|
124344
|
+
const ordered = [...result.sources].sort((a, b) => sourceText.get(a.url).length / weight(a) - sourceText.get(b.url).length / weight(b));
|
|
124345
|
+
let remainingWeight = ordered.reduce((sum, source) => sum + weight(source), 0);
|
|
124346
|
+
for (const source of ordered) {
|
|
124347
|
+
const text = sourceText.get(source.url);
|
|
124348
|
+
const allowance = Math.floor(remaining * weight(source) / remainingWeight);
|
|
124349
|
+
remainingWeight -= weight(source);
|
|
124350
|
+
source.excerpts = [text.slice(0, allowance)];
|
|
124351
|
+
remaining -= source.excerpts[0].length;
|
|
124352
|
+
}
|
|
124353
|
+
evidenceChars = limits.evidenceChars - remaining;
|
|
124354
|
+
};
|
|
124297
124355
|
const addSource = (source) => {
|
|
124298
124356
|
if (!requiredPhrases.every((phrase) => normalize(`${source.title} ${source.excerpts.join(" ")}`).includes(normalize(phrase))))
|
|
124299
124357
|
return;
|
|
124300
|
-
if (result.sources.some((existing) => existing.url === source.url)
|
|
124358
|
+
if (result.sources.some((existing) => existing.url === source.url))
|
|
124301
124359
|
return;
|
|
124360
|
+
if (result.sources.length >= 64) {
|
|
124361
|
+
result.limitations.push("Evidence source limit reached");
|
|
124362
|
+
return;
|
|
124363
|
+
}
|
|
124302
124364
|
try {
|
|
124303
124365
|
const url = new URL(source.url);
|
|
124304
124366
|
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password)
|
|
@@ -124307,15 +124369,16 @@ ${prompt}`,
|
|
|
124307
124369
|
return;
|
|
124308
124370
|
}
|
|
124309
124371
|
const text = source.excerpts.join(`
|
|
124310
|
-
`).slice(0, limits.evidenceChars
|
|
124372
|
+
`).slice(0, limits.evidenceChars);
|
|
124311
124373
|
if (!text.trim())
|
|
124312
124374
|
return;
|
|
124313
|
-
|
|
124375
|
+
sourceText.set(source.url, text);
|
|
124314
124376
|
result.sources.push({
|
|
124315
124377
|
...source,
|
|
124316
124378
|
id: `s${result.sources.length + 1}`,
|
|
124317
124379
|
excerpts: [text]
|
|
124318
124380
|
});
|
|
124381
|
+
rebalanceEvidence();
|
|
124319
124382
|
};
|
|
124320
124383
|
const doSearch = async (query2) => {
|
|
124321
124384
|
for (const bounded of boundedQueries(query2)) {
|
|
@@ -124358,12 +124421,9 @@ ${prompt}`,
|
|
|
124358
124421
|
if (page.text && page.status !== "error") {
|
|
124359
124422
|
const prior = result.sources.find((source) => source.url === url);
|
|
124360
124423
|
if (prior) {
|
|
124361
|
-
|
|
124362
|
-
|
|
124363
|
-
|
|
124364
|
-
evidenceChars += extra.length;
|
|
124365
|
-
prior.contentFetchedAt = page.fetchedAt;
|
|
124366
|
-
}
|
|
124424
|
+
sourceText.set(prior.url, page.text.slice(0, limits.evidenceChars));
|
|
124425
|
+
prior.contentFetchedAt = page.fetchedAt;
|
|
124426
|
+
rebalanceEvidence();
|
|
124367
124427
|
} else
|
|
124368
124428
|
addSource({
|
|
124369
124429
|
id: "",
|
|
@@ -124381,16 +124441,24 @@ ${prompt}`,
|
|
|
124381
124441
|
for (let round = 0;round < limits.rounds; round++) {
|
|
124382
124442
|
if (!result.sources.length && queries.size >= limits.searches)
|
|
124383
124443
|
break;
|
|
124384
|
-
|
|
124385
|
-
|
|
124386
|
-
|
|
124387
|
-
|
|
124388
|
-
|
|
124389
|
-
|
|
124390
|
-
|
|
124391
|
-
|
|
124392
|
-
|
|
124393
|
-
|
|
124444
|
+
let plan;
|
|
124445
|
+
try {
|
|
124446
|
+
plan = await generate2("plan", PlanSchema, "Select follow-up searches for missing evidence and primary-source URLs to read. For current people or roles, seek the organization\u2019s own leadership/about/team pages; testimonials, package authors, and job listings are not staff identity evidence. For policies, read the full policy or agreement to preserve section scope and exceptions. URLs must come from the supplied search results. done means available evidence addresses the requested schema, not merely that some results exist. Return no actions if further work is not useful.", {
|
|
124447
|
+
query: input.query,
|
|
124448
|
+
schema: task.schema,
|
|
124449
|
+
instructions: task.instructions,
|
|
124450
|
+
sources: result.sources,
|
|
124451
|
+
searched: [...queries],
|
|
124452
|
+
read: [...urls],
|
|
124453
|
+
remainingSearches: limits.searches - queries.size,
|
|
124454
|
+
remainingReads: limits.reads - urls.size
|
|
124455
|
+
});
|
|
124456
|
+
} catch (error) {
|
|
124457
|
+
if (!(error instanceof Error) || error.message !== "Invalid plan output")
|
|
124458
|
+
throw error;
|
|
124459
|
+
result.limitations.push("Invalid planning output; continued with retrieved evidence");
|
|
124460
|
+
break;
|
|
124461
|
+
}
|
|
124394
124462
|
for (const url of plan.urls)
|
|
124395
124463
|
await doRead(url);
|
|
124396
124464
|
for (const query2 of plan.queries)
|
|
@@ -124406,15 +124474,30 @@ ${prompt}`,
|
|
|
124406
124474
|
}
|
|
124407
124475
|
if (evidenceChars >= limits.evidenceChars)
|
|
124408
124476
|
result.limitations.push("Evidence text limit reached");
|
|
124409
|
-
const data = await generate2("extract", task.schema, `Extract only
|
|
124477
|
+
const data = await generate2("extract", task.schema, `Extract only facts that directly answer the requested query and schema. Return concise, atomic answers; include only the minimum facts needed to answer the question. For a leadership query, report the named person and current title, not their biography, education, or acquisition history. Do not pad findings with background facts, excluded candidates, or statements that evidence is missing; return an empty findings array when no answer is established. Preserve all material qualifiers: program versus benefit/trial, customer versus partner, eligibility versus automatic approval, dates, exceptions, and attribution of promotional claims. Never generalize a section-specific condition to the whole organization or program. ${task.instructions ?? ""}`, {
|
|
124478
|
+
query: input.query,
|
|
124479
|
+
asOf: result.generatedAt,
|
|
124480
|
+
sources: result.sources
|
|
124481
|
+
});
|
|
124410
124482
|
const leaves = researchLeaves(data, limits.fields);
|
|
124411
124483
|
if (!leaves.length) {
|
|
124412
124484
|
result.data = data;
|
|
124413
124485
|
result.status = result.limitations.length ? "partial" : "empty";
|
|
124414
124486
|
return result;
|
|
124415
124487
|
}
|
|
124416
|
-
const
|
|
124417
|
-
|
|
124488
|
+
const reviewSources = researchPassages(result.sources);
|
|
124489
|
+
const reviewed = await generate2("review", ReviewSchema, "Independently review EVERY JSON pointer and its value. supported requires exact quotes establishing the entire fact for the correct entity and time. A name mention or publication date is insufficient to establish employment, event date, ownership, or buying intent. Check all sources for conflicts. Use conflicting when sources disagree, unsupported for contradicted claims, unknown for missing support. Null is unknown. Cite only supplied passage IDs; their text will be copied verbatim by the runtime. For each field, explicitly check answersQuestion (directly answers query/schema/instructions, not background or a non-answer), correctEntity, correctTime, and preservesScope. Read surrounding context and headings, not only the matching quote. A benefit/trial condition is not a program-wide rule; customer conditions are not partner conditions; eligible is not approved or automatic. Preserve exceptions, qualifiers and marketing attribution. Every check must pass for supported. A leader\u2019s education or career history does not answer who currently leads a team. Keep each reason under 25 words. Cite only the minimum passages needed to establish the entire field, at most four. Do not invent passage IDs. Read adjacent passages for conditions and exceptions, even when citing only one. Return each pointer once. Never treat this review as independent real-world verification.", {
|
|
124490
|
+
query: input.query,
|
|
124491
|
+
asOf: result.generatedAt,
|
|
124492
|
+
instructions: task.instructions,
|
|
124493
|
+
schema: task.schema,
|
|
124494
|
+
fields: leaves,
|
|
124495
|
+
sources: reviewSources
|
|
124496
|
+
});
|
|
124497
|
+
result.fields = bindResearchReview(leaves, reviewed.fields.map((field) => ({
|
|
124498
|
+
...field,
|
|
124499
|
+
citations: resolveResearchPassages(field.citations, reviewSources)
|
|
124500
|
+
})), result.sources);
|
|
124418
124501
|
const complete = result.fields.length > 0 && result.fields.every((field) => field.verdict === "supported");
|
|
124419
124502
|
result.data = complete ? data : null;
|
|
124420
124503
|
result.status = complete && !result.limitations.length ? "reviewed" : "partial";
|
|
@@ -125282,5 +125365,5 @@ export {
|
|
|
125282
125365
|
selectResearchMonitorEvents
|
|
125283
125366
|
};
|
|
125284
125367
|
|
|
125285
|
-
//# debugId=
|
|
125368
|
+
//# debugId=F9058798C65361FB64756E2164756E21
|
|
125286
125369
|
//# sourceMappingURL=index.js.map
|