@absolutejs/ai 0.2.6 → 0.2.7
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 +6 -0
- package/README.md +2 -0
- package/changelog.json +276 -261
- package/dist/ai/index.js +4 -2
- package/dist/ai/index.js.map +4 -4
- package/dist/ai/providers/anthropic.js +2 -2
- package/dist/ai/providers/anthropic.js.map +3 -3
- package/dist/ai/providers/gemini.js +2 -2
- package/dist/ai/providers/gemini.js.map +3 -3
- package/dist/ai/providers/ollama.js +2 -2
- package/dist/ai/providers/ollama.js.map +3 -3
- package/dist/ai/providers/openai.js +2 -2
- package/dist/ai/providers/openai.js.map +3 -3
- package/dist/ai/providers/openaiCompatible.js +2 -2
- package/dist/ai/providers/openaiCompatible.js.map +3 -3
- package/dist/ai/providers/openaiResponses.js +2 -2
- package/dist/ai/providers/openaiResponses.js.map +3 -3
- package/dist/ai/providers/openrouter.js +2 -2
- package/dist/ai/providers/openrouter.js.map +3 -3
- package/dist/ai/research.js +84 -0
- package/dist/ai/research.js.map +10 -0
- package/dist/src/ai/generateAI.d.ts +2 -0
- package/dist/src/ai/research.d.ts +33 -0
- package/dist/src/ai/research.test.d.ts +1 -0
- package/dist/types/ai.d.ts +2 -0
- package/package.json +7 -2
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
4
|
+
// src/ai/research.ts
|
|
5
|
+
import {
|
|
6
|
+
boundedQueries,
|
|
7
|
+
evidenceText,
|
|
8
|
+
sourceSupportsQuote
|
|
9
|
+
} from "@absolutejs/search";
|
|
10
|
+
var researchWithEvidence = async (input) => {
|
|
11
|
+
const requested = input.queries ?? boundedQueries(input.query);
|
|
12
|
+
const maxQueries = input.maxQueries ?? 2;
|
|
13
|
+
if (!Number.isInteger(maxQueries) || maxQueries < 1)
|
|
14
|
+
throw new Error("Research requires a positive query budget");
|
|
15
|
+
const searches = [];
|
|
16
|
+
const limitations = [];
|
|
17
|
+
const generatedAt = new Date().toISOString();
|
|
18
|
+
if (requested.length > maxQueries)
|
|
19
|
+
limitations.push("Query budget reached; some requested research was not performed");
|
|
20
|
+
for (const query of requested.slice(0, maxQueries)) {
|
|
21
|
+
input.signal?.throwIfAborted();
|
|
22
|
+
searches.push(await input.provider.search({
|
|
23
|
+
query,
|
|
24
|
+
mode: "context",
|
|
25
|
+
signal: input.signal,
|
|
26
|
+
maxTokens: input.maxTokens ?? 4096,
|
|
27
|
+
freshness: input.freshness
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
input.signal?.throwIfAborted();
|
|
31
|
+
const byUrl = new Map;
|
|
32
|
+
for (const search of searches) {
|
|
33
|
+
limitations.push(...search.limitations);
|
|
34
|
+
for (const source of search.sources) {
|
|
35
|
+
const previous = byUrl.get(source.url);
|
|
36
|
+
byUrl.set(source.url, previous ? {
|
|
37
|
+
...previous,
|
|
38
|
+
excerpts: [
|
|
39
|
+
...new Set([...previous.excerpts, ...source.excerpts])
|
|
40
|
+
]
|
|
41
|
+
} : source);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const normalize = (value) => value.normalize("NFKC").toLowerCase().replace(/\s+/gu, " ").trim();
|
|
45
|
+
const requiredPhrases = input.requiredPhrases ?? [...input.query.matchAll(/"([^"\n]+)"/gu)].map((match) => match[1]);
|
|
46
|
+
const sources = [...byUrl.values()].filter((source) => requiredPhrases.every((phrase) => normalize(`${source.title} ${source.excerpts.join(" ")}`).includes(normalize(phrase))));
|
|
47
|
+
if (sources.length < byUrl.size)
|
|
48
|
+
limitations.push("Sources that did not establish the required literal entity were excluded; similarly named entities are not substitutes.");
|
|
49
|
+
const complete = searches.length > 0 && searches.every((search) => search.status === "ok" || search.status === "empty") && requested.length <= maxQueries;
|
|
50
|
+
const base = { searches, sources, limitations, generatedAt };
|
|
51
|
+
if (!sources.length)
|
|
52
|
+
return {
|
|
53
|
+
...base,
|
|
54
|
+
status: complete ? "empty" : "unavailable",
|
|
55
|
+
text: "",
|
|
56
|
+
findings: []
|
|
57
|
+
};
|
|
58
|
+
input.signal?.throwIfAborted();
|
|
59
|
+
const synthesis = await input.synthesize(evidenceText(sources), input.signal);
|
|
60
|
+
const findings = synthesis.findings.filter((finding) => {
|
|
61
|
+
const source = sources.find((source2) => source2.id === finding.sourceId);
|
|
62
|
+
return typeof finding.claim === "string" && Boolean(finding.claim.trim()) && typeof finding.quote === "string" && source && sourceSupportsQuote(source, finding.quote);
|
|
63
|
+
});
|
|
64
|
+
if (findings.length !== synthesis.findings.length)
|
|
65
|
+
limitations.push("Unsupported source references or quotations were excluded");
|
|
66
|
+
limitations.push(...synthesis.limitations ?? []);
|
|
67
|
+
const text = findings.map((finding) => `${finding.claim}
|
|
68
|
+
Evidence: ${finding.quote}
|
|
69
|
+
Source: ${sources.find((source) => source.id === finding.sourceId).url}`).join(`
|
|
70
|
+
|
|
71
|
+
`);
|
|
72
|
+
return {
|
|
73
|
+
...base,
|
|
74
|
+
status: complete && findings.length > 0 && !limitations.length ? "grounded" : "partial",
|
|
75
|
+
text,
|
|
76
|
+
findings
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
export {
|
|
80
|
+
researchWithEvidence
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
//# debugId=0CBE93DA058E304664756E2164756E21
|
|
84
|
+
//# sourceMappingURL=research.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/ai/research.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import {\n boundedQueries,\n evidenceText,\n sourceSupportsQuote,\n type SearchProvider,\n type SearchResult,\n type SearchSource,\n} from \"@absolutejs/search\";\nexport type ResearchFinding = {\n claim: string;\n sourceId: string;\n quote: string;\n eventDate?: string;\n};\nexport type ResearchEvidence = {\n status: \"grounded\" | \"partial\" | \"empty\" | \"unavailable\";\n text: string;\n findings: ResearchFinding[];\n sources: SearchSource[];\n searches: SearchResult[];\n limitations: string[];\n generatedAt: string;\n};\nexport type ResearchSynthesis = {\n findings: ResearchFinding[];\n limitations?: string[];\n};\n/** Retrieval and synthesis are separate injected capabilities. No hosted search is enabled here. */\nexport const researchWithEvidence = async (input: {\n query: string;\n queries?: string[];\n provider: SearchProvider;\n signal?: AbortSignal;\n maxQueries?: number;\n maxTokens?: number;\n freshness?: string;\n /** Required literal entity phrases; defaults to quoted phrases in the question. */\n requiredPhrases?: string[];\n synthesize: (\n evidence: string,\n signal?: AbortSignal,\n ) => Promise<ResearchSynthesis>;\n}): Promise<ResearchEvidence> => {\n const requested = input.queries ?? boundedQueries(input.query);\n const maxQueries = input.maxQueries ?? 2;\n if (!Number.isInteger(maxQueries) || maxQueries < 1)\n throw new Error(\"Research requires a positive query budget\");\n const searches: SearchResult[] = [];\n const limitations: string[] = [];\n const generatedAt = new Date().toISOString();\n if (requested.length > maxQueries)\n limitations.push(\n \"Query budget reached; some requested research was not performed\",\n );\n for (const query of requested.slice(0, maxQueries)) {\n input.signal?.throwIfAborted();\n searches.push(\n await input.provider.search({\n query,\n mode: \"context\",\n signal: input.signal,\n maxTokens: input.maxTokens ?? 4096,\n freshness: input.freshness,\n }),\n );\n }\n input.signal?.throwIfAborted();\n const byUrl = new Map<string, SearchSource>();\n for (const search of searches) {\n limitations.push(...search.limitations);\n for (const source of search.sources) {\n const previous = byUrl.get(source.url);\n byUrl.set(\n source.url,\n previous\n ? {\n ...previous,\n excerpts: [\n ...new Set([...previous.excerpts, ...source.excerpts]),\n ],\n }\n : source,\n );\n }\n }\n const normalize = (value:string) => value.normalize(\"NFKC\").toLowerCase().replace(/\\s+/gu,\" \").trim();\n const requiredPhrases = input.requiredPhrases ?? [...input.query.matchAll(/\"([^\"\\n]+)\"/gu)].map(match=>match[1]!);\n const sources = [...byUrl.values()].filter(source=>requiredPhrases.every(phrase=>normalize(`${source.title} ${source.excerpts.join(\" \")}`).includes(normalize(phrase))));\n if (sources.length < byUrl.size) limitations.push(\"Sources that did not establish the required literal entity were excluded; similarly named entities are not substitutes.\");\n const complete =\n searches.length > 0 &&\n searches.every(\n (search) => search.status === \"ok\" || search.status === \"empty\",\n ) &&\n requested.length <= maxQueries;\n const base = { searches, sources, limitations, generatedAt };\n if (!sources.length)\n return {\n ...base,\n status: complete ? \"empty\" : \"unavailable\",\n text: \"\",\n findings: [],\n };\n input.signal?.throwIfAborted();\n const synthesis = await input.synthesize(evidenceText(sources), input.signal);\n const findings = synthesis.findings.filter((finding) => {\n const source = sources.find((source) => source.id === finding.sourceId);\n return (\n typeof finding.claim === \"string\" &&\n Boolean(finding.claim.trim()) &&\n typeof finding.quote === \"string\" &&\n source &&\n sourceSupportsQuote(source, finding.quote)\n );\n });\n if (findings.length !== synthesis.findings.length)\n limitations.push(\n \"Unsupported source references or quotations were excluded\",\n );\n limitations.push(...(synthesis.limitations ?? []));\n const text = findings\n .map(\n (finding) =>\n `${finding.claim}\\nEvidence: ${finding.quote}\\nSource: ${sources.find((source) => source.id === finding.sourceId)!.url}`,\n )\n .join(\"\\n\\n\");\n // Quotation/source checks establish provenance, not semantic entailment. Host review remains necessary.\n return {\n ...base,\n status:\n complete && findings.length > 0 && !limitations.length\n ? \"grounded\"\n : \"partial\",\n text,\n findings,\n };\n};\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "0CBE93DA058E304664756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -6,6 +6,7 @@ export type GenerateAIToolCall = {
|
|
|
6
6
|
input: unknown;
|
|
7
7
|
};
|
|
8
8
|
export type GenerateAIOptions = {
|
|
9
|
+
maxRetries?: number;
|
|
9
10
|
contextPolicy?: AIContextPolicy | false;
|
|
10
11
|
provider: AIProviderConfig;
|
|
11
12
|
model: string;
|
|
@@ -73,6 +74,7 @@ export declare const toProviderTools: (tools: AIToolMap) => AIProviderToolDefini
|
|
|
73
74
|
*/
|
|
74
75
|
export declare const generateAIWithTools: (options: GenerateAIWithToolsOptions) => Promise<GenerateAIWithToolsResult>;
|
|
75
76
|
export type GenerateObjectAIOptions<T> = {
|
|
77
|
+
maxRetries?: number;
|
|
76
78
|
contextPolicy?: AIContextPolicy | false;
|
|
77
79
|
provider: AIProviderConfig;
|
|
78
80
|
model: string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type SearchProvider, type SearchResult, type SearchSource } from "@absolutejs/search";
|
|
2
|
+
export type ResearchFinding = {
|
|
3
|
+
claim: string;
|
|
4
|
+
sourceId: string;
|
|
5
|
+
quote: string;
|
|
6
|
+
eventDate?: string;
|
|
7
|
+
};
|
|
8
|
+
export type ResearchEvidence = {
|
|
9
|
+
status: "grounded" | "partial" | "empty" | "unavailable";
|
|
10
|
+
text: string;
|
|
11
|
+
findings: ResearchFinding[];
|
|
12
|
+
sources: SearchSource[];
|
|
13
|
+
searches: SearchResult[];
|
|
14
|
+
limitations: string[];
|
|
15
|
+
generatedAt: string;
|
|
16
|
+
};
|
|
17
|
+
export type ResearchSynthesis = {
|
|
18
|
+
findings: ResearchFinding[];
|
|
19
|
+
limitations?: string[];
|
|
20
|
+
};
|
|
21
|
+
/** Retrieval and synthesis are separate injected capabilities. No hosted search is enabled here. */
|
|
22
|
+
export declare const researchWithEvidence: (input: {
|
|
23
|
+
query: string;
|
|
24
|
+
queries?: string[];
|
|
25
|
+
provider: SearchProvider;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
maxQueries?: number;
|
|
28
|
+
maxTokens?: number;
|
|
29
|
+
freshness?: string;
|
|
30
|
+
/** Required literal entity phrases; defaults to quoted phrases in the question. */
|
|
31
|
+
requiredPhrases?: string[];
|
|
32
|
+
synthesize: (evidence: string, signal?: AbortSignal) => Promise<ResearchSynthesis>;
|
|
33
|
+
}) => Promise<ResearchEvidence>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/ai.d.ts
CHANGED
|
@@ -204,6 +204,8 @@ export type ReasoningConfig = {
|
|
|
204
204
|
effort?: ReasoningEffort;
|
|
205
205
|
};
|
|
206
206
|
export type AIProviderStreamParams = {
|
|
207
|
+
/** Override transport retries for operations whose host owns attempt accounting. */
|
|
208
|
+
maxRetries?: number;
|
|
207
209
|
/**
|
|
208
210
|
* Mark the system prompt as cacheable (Anthropic prompt caching). The system
|
|
209
211
|
* block is sent with `cache_control: ephemeral`, so repeated calls reusing the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@absolutejs/ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"homepage": "https://github.com/absolutejs/ai",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/absolutejs/ai/issues"
|
|
@@ -92,11 +92,16 @@
|
|
|
92
92
|
"./models": {
|
|
93
93
|
"import": "./dist/ai/models/index.js",
|
|
94
94
|
"types": "./dist/src/ai/models/index.d.ts"
|
|
95
|
+
},
|
|
96
|
+
"./research": {
|
|
97
|
+
"types": "./dist/src/ai/research.d.ts",
|
|
98
|
+
"import": "./dist/ai/research.js"
|
|
95
99
|
}
|
|
96
100
|
},
|
|
97
101
|
"dependencies": {
|
|
98
102
|
"@absolutejs/linked-providers": "0.0.2",
|
|
99
|
-
"@openrouter/sdk": "^1.2.47"
|
|
103
|
+
"@openrouter/sdk": "^1.2.47",
|
|
104
|
+
"@absolutejs/search": "^0.1.0"
|
|
100
105
|
},
|
|
101
106
|
"peerDependencies": {
|
|
102
107
|
"@absolutejs/isolated-jsc": ">= 0.4.0",
|