@farukada/langchain-ts-rms 0.1.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/LICENSE +21 -0
- package/README.md +446 -0
- package/dist/app/freshness/evaluator.d.ts +23 -0
- package/dist/app/freshness/evaluator.d.ts.map +1 -0
- package/dist/app/freshness/evaluator.js +72 -0
- package/dist/app/freshness/evaluator.js.map +1 -0
- package/dist/app/governance/guardrails.d.ts +51 -0
- package/dist/app/governance/guardrails.d.ts.map +1 -0
- package/dist/app/governance/guardrails.js +68 -0
- package/dist/app/governance/guardrails.js.map +1 -0
- package/dist/app/graph/workflow.d.ts +1159 -0
- package/dist/app/graph/workflow.d.ts.map +1 -0
- package/dist/app/graph/workflow.js +468 -0
- package/dist/app/graph/workflow.js.map +1 -0
- package/dist/app/queryPlanning/planner.d.ts +18 -0
- package/dist/app/queryPlanning/planner.d.ts.map +1 -0
- package/dist/app/queryPlanning/planner.js +80 -0
- package/dist/app/queryPlanning/planner.js.map +1 -0
- package/dist/app/queryRewriting/rewriter.d.ts +32 -0
- package/dist/app/queryRewriting/rewriter.d.ts.map +1 -0
- package/dist/app/queryRewriting/rewriter.js +111 -0
- package/dist/app/queryRewriting/rewriter.js.map +1 -0
- package/dist/app/reranking/reranker.d.ts +27 -0
- package/dist/app/reranking/reranker.d.ts.map +1 -0
- package/dist/app/reranking/reranker.js +67 -0
- package/dist/app/reranking/reranker.js.map +1 -0
- package/dist/app/state/schema.d.ts +121 -0
- package/dist/app/state/schema.d.ts.map +1 -0
- package/dist/app/state/schema.js +88 -0
- package/dist/app/state/schema.js.map +1 -0
- package/dist/app/summarization/summarizationSchema.d.ts +34 -0
- package/dist/app/summarization/summarizationSchema.d.ts.map +1 -0
- package/dist/app/summarization/summarizationSchema.js +65 -0
- package/dist/app/summarization/summarizationSchema.js.map +1 -0
- package/dist/app/summarization/summarizer.d.ts +51 -0
- package/dist/app/summarization/summarizer.d.ts.map +1 -0
- package/dist/app/summarization/summarizer.js +181 -0
- package/dist/app/summarization/summarizer.js.map +1 -0
- package/dist/app/summarization/synthesisSchema.d.ts +16 -0
- package/dist/app/summarization/synthesisSchema.d.ts.map +1 -0
- package/dist/app/summarization/synthesisSchema.js +43 -0
- package/dist/app/summarization/synthesisSchema.js.map +1 -0
- package/dist/app/summarization/synthesizer.d.ts +21 -0
- package/dist/app/summarization/synthesizer.d.ts.map +1 -0
- package/dist/app/summarization/synthesizer.js +86 -0
- package/dist/app/summarization/synthesizer.js.map +1 -0
- package/dist/config/env.d.ts +49 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +54 -0
- package/dist/config/env.js.map +1 -0
- package/dist/domain/contracts.d.ts +59 -0
- package/dist/domain/contracts.d.ts.map +1 -0
- package/dist/domain/contracts.js +52 -0
- package/dist/domain/contracts.js.map +1 -0
- package/dist/domain/ports.d.ts +63 -0
- package/dist/domain/ports.d.ts.map +1 -0
- package/dist/domain/ports.js +2 -0
- package/dist/domain/ports.js.map +1 -0
- package/dist/domain/researchUtils.d.ts +51 -0
- package/dist/domain/researchUtils.d.ts.map +1 -0
- package/dist/domain/researchUtils.js +85 -0
- package/dist/domain/researchUtils.js.map +1 -0
- package/dist/infra/chat/chatModelProvider.d.ts +4 -0
- package/dist/infra/chat/chatModelProvider.d.ts.map +1 -0
- package/dist/infra/chat/chatModelProvider.js +13 -0
- package/dist/infra/chat/chatModelProvider.js.map +1 -0
- package/dist/infra/checkpoint/checkpointerFactory.d.ts +38 -0
- package/dist/infra/checkpoint/checkpointerFactory.d.ts.map +1 -0
- package/dist/infra/checkpoint/checkpointerFactory.js +54 -0
- package/dist/infra/checkpoint/checkpointerFactory.js.map +1 -0
- package/dist/infra/content/contentExtractor.d.ts +58 -0
- package/dist/infra/content/contentExtractor.d.ts.map +1 -0
- package/dist/infra/content/contentExtractor.js +296 -0
- package/dist/infra/content/contentExtractor.js.map +1 -0
- package/dist/infra/embeddings/embeddingProvider.d.ts +4 -0
- package/dist/infra/embeddings/embeddingProvider.d.ts.map +1 -0
- package/dist/infra/embeddings/embeddingProvider.js +11 -0
- package/dist/infra/embeddings/embeddingProvider.js.map +1 -0
- package/dist/infra/healthCheck.d.ts +23 -0
- package/dist/infra/healthCheck.d.ts.map +1 -0
- package/dist/infra/healthCheck.js +57 -0
- package/dist/infra/healthCheck.js.map +1 -0
- package/dist/infra/observability/tokenCounter.d.ts +30 -0
- package/dist/infra/observability/tokenCounter.d.ts.map +1 -0
- package/dist/infra/observability/tokenCounter.js +46 -0
- package/dist/infra/observability/tokenCounter.js.map +1 -0
- package/dist/infra/observability/tracing.d.ts +38 -0
- package/dist/infra/observability/tracing.d.ts.map +1 -0
- package/dist/infra/observability/tracing.js +92 -0
- package/dist/infra/observability/tracing.js.map +1 -0
- package/dist/infra/rateLimit/circuitBreaker.d.ts +54 -0
- package/dist/infra/rateLimit/circuitBreaker.d.ts.map +1 -0
- package/dist/infra/rateLimit/circuitBreaker.js +97 -0
- package/dist/infra/rateLimit/circuitBreaker.js.map +1 -0
- package/dist/infra/rateLimit/rateLimiter.d.ts +42 -0
- package/dist/infra/rateLimit/rateLimiter.d.ts.map +1 -0
- package/dist/infra/rateLimit/rateLimiter.js +89 -0
- package/dist/infra/rateLimit/rateLimiter.js.map +1 -0
- package/dist/infra/search/searxngClient.d.ts +29 -0
- package/dist/infra/search/searxngClient.d.ts.map +1 -0
- package/dist/infra/search/searxngClient.js +85 -0
- package/dist/infra/search/searxngClient.js.map +1 -0
- package/dist/infra/search/urlBlocklist.d.ts +28 -0
- package/dist/infra/search/urlBlocklist.d.ts.map +1 -0
- package/dist/infra/search/urlBlocklist.js +78 -0
- package/dist/infra/search/urlBlocklist.js.map +1 -0
- package/dist/infra/vector/qdrantClient.d.ts +18 -0
- package/dist/infra/vector/qdrantClient.d.ts.map +1 -0
- package/dist/infra/vector/qdrantClient.js +82 -0
- package/dist/infra/vector/qdrantClient.js.map +1 -0
- package/dist/infra/vector/researchRepository.d.ts +39 -0
- package/dist/infra/vector/researchRepository.d.ts.map +1 -0
- package/dist/infra/vector/researchRepository.js +294 -0
- package/dist/infra/vector/researchRepository.js.map +1 -0
- package/dist/lib/helpers.d.ts +50 -0
- package/dist/lib/helpers.d.ts.map +1 -0
- package/dist/lib/helpers.js +124 -0
- package/dist/lib/helpers.js.map +1 -0
- package/dist/lib/index.d.ts +65 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +61 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/rmsTool.d.ts +28 -0
- package/dist/lib/rmsTool.d.ts.map +1 -0
- package/dist/lib/rmsTool.js +79 -0
- package/dist/lib/rmsTool.js.map +1 -0
- package/dist/lib/schemas/lifecycleSchemas.d.ts +42 -0
- package/dist/lib/schemas/lifecycleSchemas.d.ts.map +1 -0
- package/dist/lib/schemas/lifecycleSchemas.js +176 -0
- package/dist/lib/schemas/lifecycleSchemas.js.map +1 -0
- package/dist/lib/schemas/researchSchemas.d.ts +23 -0
- package/dist/lib/schemas/researchSchemas.d.ts.map +1 -0
- package/dist/lib/schemas/researchSchemas.js +83 -0
- package/dist/lib/schemas/researchSchemas.js.map +1 -0
- package/dist/lib/tools/deleteResearch.d.ts +19 -0
- package/dist/lib/tools/deleteResearch.d.ts.map +1 -0
- package/dist/lib/tools/deleteResearch.js +37 -0
- package/dist/lib/tools/deleteResearch.js.map +1 -0
- package/dist/lib/tools/getDatetime.d.ts +7 -0
- package/dist/lib/tools/getDatetime.d.ts.map +1 -0
- package/dist/lib/tools/getDatetime.js +26 -0
- package/dist/lib/tools/getDatetime.js.map +1 -0
- package/dist/lib/tools/getResearch.d.ts +19 -0
- package/dist/lib/tools/getResearch.d.ts.map +1 -0
- package/dist/lib/tools/getResearch.js +32 -0
- package/dist/lib/tools/getResearch.js.map +1 -0
- package/dist/lib/tools/listResearch.d.ts +25 -0
- package/dist/lib/tools/listResearch.d.ts.map +1 -0
- package/dist/lib/tools/listResearch.js +41 -0
- package/dist/lib/tools/listResearch.js.map +1 -0
- package/dist/lib/tools/refreshResearch.d.ts +27 -0
- package/dist/lib/tools/refreshResearch.d.ts.map +1 -0
- package/dist/lib/tools/refreshResearch.js +81 -0
- package/dist/lib/tools/refreshResearch.js.map +1 -0
- package/dist/lib/tools/research.d.ts +108 -0
- package/dist/lib/tools/research.d.ts.map +1 -0
- package/dist/lib/tools/research.js +273 -0
- package/dist/lib/tools/research.js.map +1 -0
- package/dist/lib/tools/searchResearch.d.ts +25 -0
- package/dist/lib/tools/searchResearch.d.ts.map +1 -0
- package/dist/lib/tools/searchResearch.js +45 -0
- package/dist/lib/tools/searchResearch.js.map +1 -0
- package/dist/lib/types.d.ts +51 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/mcp/index.d.ts +12 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +12 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +45 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +440 -0
- package/dist/mcp/server.js.map +1 -0
- package/package.json +132 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
|
|
3
|
+
import { logInfo, logDebug, logWarn, withNodeTiming } from "../../infra/observability/tracing.js";
|
|
4
|
+
/** Maximum number of query rewrites before forcing summarization. */
|
|
5
|
+
export const MAX_REWRITES = 2;
|
|
6
|
+
/** Minimum relevance score below which a rewrite is triggered. */
|
|
7
|
+
export const MIN_RELEVANCE_SCORE = 0.4;
|
|
8
|
+
/**
|
|
9
|
+
* Schema for the LLM structured output when evaluating query relevance.
|
|
10
|
+
*/
|
|
11
|
+
export const QueryRewriteOutputSchema = z.object({
|
|
12
|
+
isRelevant: z.boolean().meta({
|
|
13
|
+
description: "Whether the search results are sufficiently relevant to the research subject. " +
|
|
14
|
+
"True if the results contain substantial information addressing the subject. " +
|
|
15
|
+
"False if the results are mostly irrelevant, off-topic, or too sparse.",
|
|
16
|
+
}),
|
|
17
|
+
relevanceScore: z
|
|
18
|
+
.number()
|
|
19
|
+
.min(0)
|
|
20
|
+
.max(1)
|
|
21
|
+
.default(0.5)
|
|
22
|
+
.meta({
|
|
23
|
+
description: "A score from 0.0 to 1.0 indicating how relevant the search results are to the subject. " +
|
|
24
|
+
"0.0 = completely irrelevant, 1.0 = perfectly relevant.",
|
|
25
|
+
examples: [0.2, 0.5, 0.8],
|
|
26
|
+
}),
|
|
27
|
+
rewrittenQuery: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.meta({
|
|
31
|
+
description: "If the results are not relevant, provide a rewritten version of the search query " +
|
|
32
|
+
"that might produce better results. Use more specific terms, add context, or rephrase. " +
|
|
33
|
+
"Only set this if isRelevant is false.",
|
|
34
|
+
}),
|
|
35
|
+
reasoning: z.string().meta({
|
|
36
|
+
description: "Brief explanation of why the results are or are not relevant, " +
|
|
37
|
+
"and what the rewritten query aims to improve.",
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
const SYSTEM_PROMPT = `You are a search quality evaluator in an automated research pipeline. Your assessment determines whether to proceed or retry with a better query.
|
|
41
|
+
|
|
42
|
+
Before scoring, consider: do results directly address the subject, and are the sources substantive?
|
|
43
|
+
|
|
44
|
+
Guidelines:
|
|
45
|
+
1. Assess whether results directly address the research subject.
|
|
46
|
+
2. Consider both coverage (breadth) and quality (source reliability, depth).
|
|
47
|
+
3. If insufficient, provide a rewritten query with more specific or clarifying terms.
|
|
48
|
+
4. Score based on actual content quality, not surface-level keyword matches.
|
|
49
|
+
|
|
50
|
+
Example — Subject: "Rust async runtime comparison", results mostly about Rust basics
|
|
51
|
+
→ relevanceScore: 0.15, rewrittenQuery: "tokio vs async-std vs smol Rust async runtime benchmark 2025"`;
|
|
52
|
+
/**
|
|
53
|
+
* Evaluates the relevance of search results to the subject and optionally
|
|
54
|
+
* rewrites the query for better results.
|
|
55
|
+
*
|
|
56
|
+
* Used as a feedback loop node in the LangGraph workflow:
|
|
57
|
+
* searcher → queryRewriter → (relevant? summarizer : searcher with rewritten query)
|
|
58
|
+
*/
|
|
59
|
+
export async function evaluateQueryRelevance(subject, searchResults, chatModel, traceId) {
|
|
60
|
+
return withNodeTiming("queryRewriter", traceId, subject, async () => {
|
|
61
|
+
logDebug("Evaluating search result relevance", {
|
|
62
|
+
node: "queryRewriter",
|
|
63
|
+
traceId,
|
|
64
|
+
researchId: subject,
|
|
65
|
+
});
|
|
66
|
+
const resultsPreview = searchResults
|
|
67
|
+
.slice(0, 5) // Only send top-5 to reduce tokens
|
|
68
|
+
.map((r, i) => `[${String(i + 1)}] ${r.title}\n${r.snippet}`)
|
|
69
|
+
.join("\n\n");
|
|
70
|
+
const humanMsg = `Research subject:\n<subject>\n${subject}\n</subject>\n\n` +
|
|
71
|
+
`Search results (${String(searchResults.length)} total, showing top 5):\n<search_results>\n${resultsPreview}\n</search_results>\n\n` +
|
|
72
|
+
`Evaluate the relevance of these results to the research subject.`;
|
|
73
|
+
try {
|
|
74
|
+
const structuredModel = chatModel.withStructuredOutput(QueryRewriteOutputSchema, {
|
|
75
|
+
method: "jsonSchema",
|
|
76
|
+
name: "query_relevance_evaluation",
|
|
77
|
+
});
|
|
78
|
+
const result = await structuredModel.invoke([
|
|
79
|
+
new SystemMessage(SYSTEM_PROMPT),
|
|
80
|
+
new HumanMessage(humanMsg),
|
|
81
|
+
]);
|
|
82
|
+
const relevanceScore = Math.min(1, Math.max(0, result.relevanceScore ?? 0.5));
|
|
83
|
+
const isRelevant = result.isRelevant && relevanceScore >= MIN_RELEVANCE_SCORE;
|
|
84
|
+
logInfo("Query relevance evaluated", {
|
|
85
|
+
node: "queryRewriter",
|
|
86
|
+
traceId,
|
|
87
|
+
researchId: subject,
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
isRelevant,
|
|
91
|
+
relevanceScore,
|
|
92
|
+
rewrittenQuery: isRelevant ? undefined : result.rewrittenQuery,
|
|
93
|
+
reasoning: result.reasoning,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
// On evaluation failure, assume results are relevant to avoid blocking
|
|
98
|
+
logWarn("Query relevance evaluation failed, assuming relevant", {
|
|
99
|
+
node: "queryRewriter",
|
|
100
|
+
traceId,
|
|
101
|
+
error: err instanceof Error ? err.message : String(err),
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
isRelevant: true,
|
|
105
|
+
relevanceScore: 0.5,
|
|
106
|
+
reasoning: "Evaluation failed; proceeding with current results.",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=rewriter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rewriter.js","sourceRoot":"","sources":["../../../src/app/queryRewriting/rewriter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { EmbeddingsInterface } from "@langchain/core/embeddings";
|
|
2
|
+
import type { SearxngSearchResult } from "../../infra/search/searxngClient.js";
|
|
3
|
+
export interface RerankOptions {
|
|
4
|
+
/** Minimum cosine similarity score to keep a result. Defaults to 0.3. */
|
|
5
|
+
minScore?: number;
|
|
6
|
+
/** Maximum number of results to return after re-ranking. Defaults to input length. */
|
|
7
|
+
topN?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface RankedResult {
|
|
10
|
+
result: SearxngSearchResult;
|
|
11
|
+
score: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Cosine similarity between two vectors.
|
|
15
|
+
*/
|
|
16
|
+
export declare function cosineSimilarity(a: number[], b: number[]): number;
|
|
17
|
+
/**
|
|
18
|
+
* Re-ranks search results by embedding-based semantic similarity to the subject.
|
|
19
|
+
*
|
|
20
|
+
* 1. Embeds the subject and each result snippet
|
|
21
|
+
* 2. Computes cosine similarity between subject and each snippet
|
|
22
|
+
* 3. Filters out results below `minScore`
|
|
23
|
+
* 4. Sorts by descending similarity
|
|
24
|
+
* 5. Returns top-N results
|
|
25
|
+
*/
|
|
26
|
+
export declare function rerankSearchResults(subject: string, results: SearxngSearchResult[], embeddings: EmbeddingsInterface, options?: RerankOptions, traceId?: string): Promise<RankedResult[]>;
|
|
27
|
+
//# sourceMappingURL=reranker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reranker.d.ts","sourceRoot":"","sources":["../../../src/app/reranking/reranker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAG/E,MAAM,WAAW,aAAa;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAWjE;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,mBAAmB,EAAE,EAC9B,UAAU,EAAE,mBAAmB,EAC/B,OAAO,CAAC,EAAE,aAAa,EACvB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,EAAE,CAAC,CA8CzB"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { logInfo, logDebug, withNodeTiming } from "../../infra/observability/tracing.js";
|
|
2
|
+
/**
|
|
3
|
+
* Cosine similarity between two vectors.
|
|
4
|
+
*/
|
|
5
|
+
export function cosineSimilarity(a, b) {
|
|
6
|
+
let dot = 0;
|
|
7
|
+
let normA = 0;
|
|
8
|
+
let normB = 0;
|
|
9
|
+
for (let i = 0; i < a.length; i++) {
|
|
10
|
+
dot += (a[i] ?? 0) * (b[i] ?? 0);
|
|
11
|
+
normA += (a[i] ?? 0) ** 2;
|
|
12
|
+
normB += (b[i] ?? 0) ** 2;
|
|
13
|
+
}
|
|
14
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
15
|
+
return denom === 0 ? 0 : dot / denom;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Re-ranks search results by embedding-based semantic similarity to the subject.
|
|
19
|
+
*
|
|
20
|
+
* 1. Embeds the subject and each result snippet
|
|
21
|
+
* 2. Computes cosine similarity between subject and each snippet
|
|
22
|
+
* 3. Filters out results below `minScore`
|
|
23
|
+
* 4. Sorts by descending similarity
|
|
24
|
+
* 5. Returns top-N results
|
|
25
|
+
*/
|
|
26
|
+
export async function rerankSearchResults(subject, results, embeddings, options, traceId) {
|
|
27
|
+
if (results.length === 0)
|
|
28
|
+
return [];
|
|
29
|
+
const minScore = options?.minScore ?? 0;
|
|
30
|
+
const topN = options?.topN ?? results.length;
|
|
31
|
+
return withNodeTiming("reranker", traceId, subject, async () => {
|
|
32
|
+
// Build texts for embedding: subject + each snippet
|
|
33
|
+
const snippets = results.map((r) => `${r.title}\n${r.snippet}`);
|
|
34
|
+
const allTexts = [subject, ...snippets];
|
|
35
|
+
logDebug("Embedding for re-ranking", {
|
|
36
|
+
node: "reranker",
|
|
37
|
+
traceId,
|
|
38
|
+
researchId: subject,
|
|
39
|
+
});
|
|
40
|
+
const vectors = await embeddings.embedDocuments(allTexts);
|
|
41
|
+
const subjectVector = vectors[0];
|
|
42
|
+
if (!subjectVector) {
|
|
43
|
+
return results.map((r) => ({ result: r, score: 0 }));
|
|
44
|
+
}
|
|
45
|
+
// Score each result
|
|
46
|
+
const scored = [];
|
|
47
|
+
for (let i = 0; i < results.length; i++) {
|
|
48
|
+
const resultVector = vectors[i + 1];
|
|
49
|
+
if (!resultVector)
|
|
50
|
+
continue;
|
|
51
|
+
const score = cosineSimilarity(subjectVector, resultVector);
|
|
52
|
+
if (score >= minScore) {
|
|
53
|
+
scored.push({ result: results[i], score });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Sort descending by score
|
|
57
|
+
scored.sort((a, b) => b.score - a.score);
|
|
58
|
+
const ranked = scored.slice(0, topN);
|
|
59
|
+
logInfo("Re-ranking complete", {
|
|
60
|
+
node: "reranker",
|
|
61
|
+
traceId,
|
|
62
|
+
researchId: subject,
|
|
63
|
+
});
|
|
64
|
+
return ranked;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=reranker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reranker.js","sourceRoot":"","sources":["../../../src/app/reranking/reranker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { SearxngSearchResult } from "../../infra/search/searxngClient.js";
|
|
2
|
+
import type { SummarizationResult } from "../summarization/summarizer.js";
|
|
3
|
+
export type RmsPhase = "freshness" | "queryPlanning" | "searching" | "queryRewriting" | "reranking" | "summarizing" | "persisting";
|
|
4
|
+
/**
|
|
5
|
+
* LangGraph state definition — shared across freshnessChecker, searcher,
|
|
6
|
+
* summarizer, and persister nodes.
|
|
7
|
+
*
|
|
8
|
+
* Uses `Annotation.Root` (same pattern as GMS) for typed graph state
|
|
9
|
+
* with reducers and defaults.
|
|
10
|
+
*
|
|
11
|
+
* @see GMS's `GmsStateAnnotation` in `src/app/state/schema.ts`
|
|
12
|
+
*/
|
|
13
|
+
export declare const RmsStateAnnotation: import("@langchain/langgraph").AnnotationRoot<{
|
|
14
|
+
/** The research subject/query being investigated. */
|
|
15
|
+
subject: import("@langchain/langgraph").LastValue<string>;
|
|
16
|
+
/** Optional tenant isolation ID. */
|
|
17
|
+
tenantId: import("@langchain/langgraph").LastValue<string | undefined>;
|
|
18
|
+
/** Whether to bypass cache and force a fresh search. */
|
|
19
|
+
forceRefresh: import("@langchain/langgraph").BaseChannel<boolean, boolean | import("@langchain/langgraph").OverwriteValue<boolean>, unknown>;
|
|
20
|
+
/** Maximum number of search results to retrieve. */
|
|
21
|
+
maxResults: import("@langchain/langgraph").BaseChannel<number, number | import("@langchain/langgraph").OverwriteValue<number>, unknown>;
|
|
22
|
+
/** Freshness threshold in days. */
|
|
23
|
+
freshnessDays: import("@langchain/langgraph").BaseChannel<number, number | import("@langchain/langgraph").OverwriteValue<number>, unknown>;
|
|
24
|
+
/** Cached research if found (populated by freshnessChecker). */
|
|
25
|
+
cachedResearch: import("@langchain/langgraph").LastValue<{
|
|
26
|
+
id: string;
|
|
27
|
+
subject: string;
|
|
28
|
+
summary: string;
|
|
29
|
+
sourceSummaries: {
|
|
30
|
+
url: string;
|
|
31
|
+
title: string;
|
|
32
|
+
keyTakeaways: string;
|
|
33
|
+
relevance: number;
|
|
34
|
+
tags: string[];
|
|
35
|
+
language: string;
|
|
36
|
+
}[];
|
|
37
|
+
sourceUrls: string[];
|
|
38
|
+
searchQueries: string[];
|
|
39
|
+
status: "active" | "stale" | "refreshing" | "archived" | "low_confidence";
|
|
40
|
+
confidenceScore: number;
|
|
41
|
+
sourceCount: number;
|
|
42
|
+
tags: string[];
|
|
43
|
+
language: string;
|
|
44
|
+
rawResultCount: number;
|
|
45
|
+
metadata: Record<string, unknown>;
|
|
46
|
+
createdAt?: string | undefined;
|
|
47
|
+
updatedAt?: string | undefined;
|
|
48
|
+
expiresAt?: string | undefined;
|
|
49
|
+
tenantId?: string | undefined;
|
|
50
|
+
keyFindings?: string[] | undefined;
|
|
51
|
+
limitations?: string[] | undefined;
|
|
52
|
+
} | undefined>;
|
|
53
|
+
/** Whether cached research is still fresh. */
|
|
54
|
+
isFresh: import("@langchain/langgraph").BaseChannel<boolean, boolean | import("@langchain/langgraph").OverwriteValue<boolean>, unknown>;
|
|
55
|
+
/** Raw search results from SearXNG. */
|
|
56
|
+
searchResults: import("@langchain/langgraph").BaseChannel<SearxngSearchResult[], SearxngSearchResult[] | import("@langchain/langgraph").OverwriteValue<SearxngSearchResult[]>, unknown>;
|
|
57
|
+
/** LLM summarization output. */
|
|
58
|
+
summarization: import("@langchain/langgraph").LastValue<SummarizationResult | undefined>;
|
|
59
|
+
/** Final persisted research entry. */
|
|
60
|
+
research: import("@langchain/langgraph").LastValue<{
|
|
61
|
+
id: string;
|
|
62
|
+
subject: string;
|
|
63
|
+
summary: string;
|
|
64
|
+
sourceSummaries: {
|
|
65
|
+
url: string;
|
|
66
|
+
title: string;
|
|
67
|
+
keyTakeaways: string;
|
|
68
|
+
relevance: number;
|
|
69
|
+
tags: string[];
|
|
70
|
+
language: string;
|
|
71
|
+
}[];
|
|
72
|
+
sourceUrls: string[];
|
|
73
|
+
searchQueries: string[];
|
|
74
|
+
status: "active" | "stale" | "refreshing" | "archived" | "low_confidence";
|
|
75
|
+
confidenceScore: number;
|
|
76
|
+
sourceCount: number;
|
|
77
|
+
tags: string[];
|
|
78
|
+
language: string;
|
|
79
|
+
rawResultCount: number;
|
|
80
|
+
metadata: Record<string, unknown>;
|
|
81
|
+
createdAt?: string | undefined;
|
|
82
|
+
updatedAt?: string | undefined;
|
|
83
|
+
expiresAt?: string | undefined;
|
|
84
|
+
tenantId?: string | undefined;
|
|
85
|
+
keyFindings?: string[] | undefined;
|
|
86
|
+
limitations?: string[] | undefined;
|
|
87
|
+
} | undefined>;
|
|
88
|
+
/** Source of the result: cache, web, or cache+web (refreshed). */
|
|
89
|
+
source: import("@langchain/langgraph").BaseChannel<"cache" | "web" | "cache+web", "cache" | "web" | "cache+web" | import("@langchain/langgraph").OverwriteValue<"cache" | "web" | "cache+web">, unknown>;
|
|
90
|
+
/** Current workflow phase. */
|
|
91
|
+
currentPhase: import("@langchain/langgraph").BaseChannel<RmsPhase, RmsPhase | import("@langchain/langgraph").OverwriteValue<RmsPhase>, unknown>;
|
|
92
|
+
/** Error message if any node fails. */
|
|
93
|
+
error: import("@langchain/langgraph").LastValue<string | undefined>;
|
|
94
|
+
/** Distributed trace ID for observability. */
|
|
95
|
+
traceId: import("@langchain/langgraph").LastValue<string | undefined>;
|
|
96
|
+
/** Optional metadata to attach to the research entry. */
|
|
97
|
+
metadata: import("@langchain/langgraph").LastValue<Record<string, unknown> | undefined>;
|
|
98
|
+
/** Number of query rewrites performed (agentic RAG loop). */
|
|
99
|
+
rewriteCount: import("@langchain/langgraph").BaseChannel<number, number | import("@langchain/langgraph").OverwriteValue<number>, unknown>;
|
|
100
|
+
/** The original subject before any query rewrites. */
|
|
101
|
+
originalSubject: import("@langchain/langgraph").LastValue<string | undefined>;
|
|
102
|
+
/** The rewritten query (set by queryRewriter when relevance is low). */
|
|
103
|
+
rewrittenQuery: import("@langchain/langgraph").LastValue<string | undefined>;
|
|
104
|
+
/** Search result relevance score from the query rewriter. */
|
|
105
|
+
relevanceScore: import("@langchain/langgraph").LastValue<number | undefined>;
|
|
106
|
+
/** Planned search queries generated by the query planner node. */
|
|
107
|
+
plannedQueries: import("@langchain/langgraph").BaseChannel<string[], string[] | import("@langchain/langgraph").OverwriteValue<string[]>, unknown>;
|
|
108
|
+
/** Accumulated token usage across all LLM calls (planner, rewriter, summarizer). */
|
|
109
|
+
tokenUsage: import("@langchain/langgraph").BaseChannel<{
|
|
110
|
+
promptTokens: number;
|
|
111
|
+
completionTokens: number;
|
|
112
|
+
}, {
|
|
113
|
+
promptTokens: number;
|
|
114
|
+
completionTokens: number;
|
|
115
|
+
} | import("@langchain/langgraph").OverwriteValue<{
|
|
116
|
+
promptTokens: number;
|
|
117
|
+
completionTokens: number;
|
|
118
|
+
}>, unknown>;
|
|
119
|
+
}>;
|
|
120
|
+
export type RmsState = typeof RmsStateAnnotation.State;
|
|
121
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/app/state/schema.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAE1E,MAAM,MAAM,QAAQ,GAChB,WAAW,GACX,eAAe,GACf,WAAW,GACX,gBAAgB,GAChB,WAAW,GACX,aAAa,GACb,YAAY,CAAC;AAEjB;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB;IAC7B,qDAAqD;;IAErD,oCAAoC;;IAEpC,wDAAwD;;IAKxD,oDAAoD;;IAKpD,mCAAmC;;IAKnC,gEAAgE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAEhE,8CAA8C;;IAK9C,uCAAuC;;IAKvC,gCAAgC;;IAEhC,sCAAsC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAEtC,kEAAkE;;IAKlE,8BAA8B;;IAK9B,uCAAuC;;IAEvC,8CAA8C;;IAE9C,yDAAyD;;IAEzD,6DAA6D;;IAK7D,sDAAsD;;IAEtD,wEAAwE;;IAExE,6DAA6D;;IAE7D,kEAAkE;;IAMlE,oFAAoF;;sBAC7C,MAAM;0BAAoB,MAAM;;sBAAhC,MAAM;0BAAoB,MAAM;;sBAAhC,MAAM;0BAAoB,MAAM;;EAOvE,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,kBAAkB,CAAC,KAAK,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Annotation } from "@langchain/langgraph";
|
|
2
|
+
/**
|
|
3
|
+
* LangGraph state definition — shared across freshnessChecker, searcher,
|
|
4
|
+
* summarizer, and persister nodes.
|
|
5
|
+
*
|
|
6
|
+
* Uses `Annotation.Root` (same pattern as GMS) for typed graph state
|
|
7
|
+
* with reducers and defaults.
|
|
8
|
+
*
|
|
9
|
+
* @see GMS's `GmsStateAnnotation` in `src/app/state/schema.ts`
|
|
10
|
+
*/
|
|
11
|
+
export const RmsStateAnnotation = Annotation.Root({
|
|
12
|
+
/** The research subject/query being investigated. */
|
|
13
|
+
subject: Annotation(),
|
|
14
|
+
/** Optional tenant isolation ID. */
|
|
15
|
+
tenantId: Annotation(),
|
|
16
|
+
/** Whether to bypass cache and force a fresh search. */
|
|
17
|
+
forceRefresh: Annotation({
|
|
18
|
+
reducer: (_prev, next) => next,
|
|
19
|
+
default: () => false,
|
|
20
|
+
}),
|
|
21
|
+
/** Maximum number of search results to retrieve. */
|
|
22
|
+
maxResults: Annotation({
|
|
23
|
+
reducer: (_prev, next) => next,
|
|
24
|
+
default: () => 10,
|
|
25
|
+
}),
|
|
26
|
+
/** Freshness threshold in days. */
|
|
27
|
+
freshnessDays: Annotation({
|
|
28
|
+
reducer: (_prev, next) => next,
|
|
29
|
+
default: () => 7,
|
|
30
|
+
}),
|
|
31
|
+
/** Cached research if found (populated by freshnessChecker). */
|
|
32
|
+
cachedResearch: Annotation(),
|
|
33
|
+
/** Whether cached research is still fresh. */
|
|
34
|
+
isFresh: Annotation({
|
|
35
|
+
reducer: (_prev, next) => next,
|
|
36
|
+
default: () => false,
|
|
37
|
+
}),
|
|
38
|
+
/** Raw search results from SearXNG. */
|
|
39
|
+
searchResults: Annotation({
|
|
40
|
+
reducer: (_prev, next) => next,
|
|
41
|
+
default: () => [],
|
|
42
|
+
}),
|
|
43
|
+
/** LLM summarization output. */
|
|
44
|
+
summarization: Annotation(),
|
|
45
|
+
/** Final persisted research entry. */
|
|
46
|
+
research: Annotation(),
|
|
47
|
+
/** Source of the result: cache, web, or cache+web (refreshed). */
|
|
48
|
+
source: Annotation({
|
|
49
|
+
reducer: (_prev, next) => next,
|
|
50
|
+
default: () => "web",
|
|
51
|
+
}),
|
|
52
|
+
/** Current workflow phase. */
|
|
53
|
+
currentPhase: Annotation({
|
|
54
|
+
reducer: (_prev, next) => next,
|
|
55
|
+
default: () => "freshness",
|
|
56
|
+
}),
|
|
57
|
+
/** Error message if any node fails. */
|
|
58
|
+
error: Annotation(),
|
|
59
|
+
/** Distributed trace ID for observability. */
|
|
60
|
+
traceId: Annotation(),
|
|
61
|
+
/** Optional metadata to attach to the research entry. */
|
|
62
|
+
metadata: Annotation(),
|
|
63
|
+
/** Number of query rewrites performed (agentic RAG loop). */
|
|
64
|
+
rewriteCount: Annotation({
|
|
65
|
+
reducer: (_prev, next) => next,
|
|
66
|
+
default: () => 0,
|
|
67
|
+
}),
|
|
68
|
+
/** The original subject before any query rewrites. */
|
|
69
|
+
originalSubject: Annotation(),
|
|
70
|
+
/** The rewritten query (set by queryRewriter when relevance is low). */
|
|
71
|
+
rewrittenQuery: Annotation(),
|
|
72
|
+
/** Search result relevance score from the query rewriter. */
|
|
73
|
+
relevanceScore: Annotation(),
|
|
74
|
+
/** Planned search queries generated by the query planner node. */
|
|
75
|
+
plannedQueries: Annotation({
|
|
76
|
+
reducer: (_prev, next) => next,
|
|
77
|
+
default: () => [],
|
|
78
|
+
}),
|
|
79
|
+
/** Accumulated token usage across all LLM calls (planner, rewriter, summarizer). */
|
|
80
|
+
tokenUsage: Annotation({
|
|
81
|
+
reducer: (prev, next) => ({
|
|
82
|
+
promptTokens: prev.promptTokens + next.promptTokens,
|
|
83
|
+
completionTokens: prev.completionTokens + next.completionTokens,
|
|
84
|
+
}),
|
|
85
|
+
default: () => ({ promptTokens: 0, completionTokens: 0 }),
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/app/state/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
/**
|
|
3
|
+
* Schema for structured LLM output when summarizing ONE search result.
|
|
4
|
+
* Used with `chatModel.withStructuredOutput(SourceSummaryOutputSchema)`.
|
|
5
|
+
*
|
|
6
|
+
* Each search result gets its own LLM call with this schema,
|
|
7
|
+
* keeping the task focused and the context small for reliable extraction.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SourceSummaryOutputSchema: z.ZodObject<{
|
|
10
|
+
keyTakeaways: z.ZodString;
|
|
11
|
+
relevance: z.ZodDefault<z.ZodNumber>;
|
|
12
|
+
tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
13
|
+
language: z.ZodDefault<z.ZodString>;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
export type SourceSummaryOutput = z.infer<typeof SourceSummaryOutputSchema>;
|
|
16
|
+
/**
|
|
17
|
+
* Schema for batched structured LLM output when summarizing ALL search results
|
|
18
|
+
* in a single call. Each element in the `sources` array corresponds to one
|
|
19
|
+
* search result, in order.
|
|
20
|
+
*
|
|
21
|
+
* @deprecated No longer used internally — the summarizer now processes
|
|
22
|
+
* one source per LLM call using {@link SourceSummaryOutputSchema}.
|
|
23
|
+
* Kept for backward compatibility of the public export surface.
|
|
24
|
+
*/
|
|
25
|
+
export declare const BatchSummaryOutputSchema: z.ZodObject<{
|
|
26
|
+
sources: z.ZodArray<z.ZodObject<{
|
|
27
|
+
keyTakeaways: z.ZodString;
|
|
28
|
+
relevance: z.ZodDefault<z.ZodNumber>;
|
|
29
|
+
tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
30
|
+
language: z.ZodDefault<z.ZodString>;
|
|
31
|
+
}, z.core.$strip>>;
|
|
32
|
+
}, z.core.$strip>;
|
|
33
|
+
export type BatchSummaryOutput = z.infer<typeof BatchSummaryOutputSchema>;
|
|
34
|
+
//# sourceMappingURL=summarizationSchema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarizationSchema.d.ts","sourceRoot":"","sources":["../../../src/app/summarization/summarizationSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAE3B;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB;;;;;iBA2CpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB;;;;;;;iBAMnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
/**
|
|
3
|
+
* Schema for structured LLM output when summarizing ONE search result.
|
|
4
|
+
* Used with `chatModel.withStructuredOutput(SourceSummaryOutputSchema)`.
|
|
5
|
+
*
|
|
6
|
+
* Each search result gets its own LLM call with this schema,
|
|
7
|
+
* keeping the task focused and the context small for reliable extraction.
|
|
8
|
+
*/
|
|
9
|
+
export const SourceSummaryOutputSchema = z.object({
|
|
10
|
+
keyTakeaways: z
|
|
11
|
+
.string()
|
|
12
|
+
.min(300)
|
|
13
|
+
.meta({
|
|
14
|
+
description: "8-15 detailed sentences extracting the most important facts, techniques, and insights from this specific source. " +
|
|
15
|
+
"Be concrete — include names, version numbers, code patterns, configuration values, and actionable details. " +
|
|
16
|
+
"Each sentence MUST convey a distinct fact. Cover all noteworthy information in the source.",
|
|
17
|
+
examples: [
|
|
18
|
+
"LangGraph v0.2 introduced the StateGraph API which replaces the legacy MessageGraph for stateful agent orchestration. " +
|
|
19
|
+
"The checkpointer interface supports pluggable backends including MemorySaver for development and PostgresSaver for production persistence.",
|
|
20
|
+
],
|
|
21
|
+
}),
|
|
22
|
+
relevance: z
|
|
23
|
+
.number()
|
|
24
|
+
.min(0)
|
|
25
|
+
.max(1)
|
|
26
|
+
.default(0.5)
|
|
27
|
+
.meta({
|
|
28
|
+
description: "How relevant this source is to the research subject. " +
|
|
29
|
+
"0.7-1.0: directly on topic. 0.4-0.7: partially relevant. 0.0-0.3: off-topic or irrelevant.",
|
|
30
|
+
examples: [0.3, 0.7, 0.95],
|
|
31
|
+
}),
|
|
32
|
+
tags: z
|
|
33
|
+
.array(z.string())
|
|
34
|
+
.default([])
|
|
35
|
+
.meta({
|
|
36
|
+
description: "1 to 3 topic tags extracted from this specific source. E.g. ['typescript', 'node-js'].",
|
|
37
|
+
examples: [
|
|
38
|
+
["typescript", "best-practices"],
|
|
39
|
+
["react", "hooks"],
|
|
40
|
+
],
|
|
41
|
+
}),
|
|
42
|
+
language: z
|
|
43
|
+
.string()
|
|
44
|
+
.default("en")
|
|
45
|
+
.meta({
|
|
46
|
+
description: "ISO 639-1 language code of this source. E.g. 'en', 'de', 'nl'.",
|
|
47
|
+
examples: ["en", "de", "nl", "fr"],
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* Schema for batched structured LLM output when summarizing ALL search results
|
|
52
|
+
* in a single call. Each element in the `sources` array corresponds to one
|
|
53
|
+
* search result, in order.
|
|
54
|
+
*
|
|
55
|
+
* @deprecated No longer used internally — the summarizer now processes
|
|
56
|
+
* one source per LLM call using {@link SourceSummaryOutputSchema}.
|
|
57
|
+
* Kept for backward compatibility of the public export surface.
|
|
58
|
+
*/
|
|
59
|
+
export const BatchSummaryOutputSchema = z.object({
|
|
60
|
+
sources: z.array(SourceSummaryOutputSchema).meta({
|
|
61
|
+
description: "An array of summaries, one per source in the order they were provided. " +
|
|
62
|
+
"Each element contains the key takeaways, relevance score, tags, and language for that source.",
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=summarizationSchema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarizationSchema.js","sourceRoot":"","sources":["../../../src/app/summarization/summarizationSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAE3B;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,YAAY,EAAE,CAAC;SACZ,MAAM,EAAE;SACR,GAAG,CAAC,GAAG,CAAC;SACR,IAAI,CAAC;QACJ,WAAW,EACT,mHAAmH;YACnH,6GAA6G;YAC7G,4FAA4F;QAC9F,QAAQ,EAAE;YACR,wHAAwH;gBACtH,4IAA4I;SAC/I;KACF,CAAC;IACJ,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,CAAC,CAAC;SACN,OAAO,CAAC,GAAG,CAAC;SACZ,IAAI,CAAC;QACJ,WAAW,EACT,uDAAuD;YACvD,4FAA4F;QAC9F,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC;KAC3B,CAAC;IACJ,IAAI,EAAE,CAAC;SACJ,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;SACjB,OAAO,CAAC,EAAE,CAAC;SACX,IAAI,CAAC;QACJ,WAAW,EACT,wFAAwF;QAC1F,QAAQ,EAAE;YACR,CAAC,YAAY,EAAE,gBAAgB,CAAC;YAChC,CAAC,OAAO,EAAE,OAAO,CAAC;SACnB;KACF,CAAC;IACJ,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,OAAO,CAAC,IAAI,CAAC;SACb,IAAI,CAAC;QACJ,WAAW,EAAE,gEAAgE;QAC7E,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;KACnC,CAAC;CACL,CAAC,CAAC;AAIH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC,IAAI,CAAC;QAC/C,WAAW,EACT,yEAAyE;YACzE,+FAA+F;KAClG,CAAC;CACH,CAAC,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
2
|
+
import type { SearxngSearchResult } from "../../infra/search/searxngClient.js";
|
|
3
|
+
import type { ExtractionMethod } from "../../infra/content/contentExtractor.js";
|
|
4
|
+
/** Per-source summary produced by the extraction step. */
|
|
5
|
+
export interface SourceSummary {
|
|
6
|
+
url: string;
|
|
7
|
+
title: string;
|
|
8
|
+
keyTakeaways: string;
|
|
9
|
+
relevance: number;
|
|
10
|
+
tags: string[];
|
|
11
|
+
language: string;
|
|
12
|
+
}
|
|
13
|
+
/** Per-URL extraction detail for debugging and verification. */
|
|
14
|
+
export interface ExtractionDetail {
|
|
15
|
+
url: string;
|
|
16
|
+
method: ExtractionMethod;
|
|
17
|
+
extractedLength: number;
|
|
18
|
+
}
|
|
19
|
+
/** Aggregate output from `summarizeSearchResults()`, optionally enriched with synthesis. */
|
|
20
|
+
export interface SummarizationResult {
|
|
21
|
+
sourceSummaries: SourceSummary[];
|
|
22
|
+
overallConfidence: number;
|
|
23
|
+
tags: string[];
|
|
24
|
+
language: string;
|
|
25
|
+
/** Per-URL extraction method breakdown for debugging. */
|
|
26
|
+
extractionBreakdown: ExtractionDetail[];
|
|
27
|
+
/** Synthesized multi-paragraph summary (produced by the synthesis step). */
|
|
28
|
+
synthesizedSummary?: string | undefined;
|
|
29
|
+
/** Key findings distilled from the research (produced by the synthesis step). */
|
|
30
|
+
keyFindings?: string[] | undefined;
|
|
31
|
+
/** Known gaps or limitations (produced by the synthesis step). */
|
|
32
|
+
limitations?: string[] | undefined;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Normalizes tags: lowercase, replace spaces with hyphens, deduplicate, sort.
|
|
36
|
+
* Ensures consistent tag format across all sources.
|
|
37
|
+
*/
|
|
38
|
+
export declare function normalizeTags(tags: string[]): string[];
|
|
39
|
+
/**
|
|
40
|
+
* Summarizes search results by making ONE focused LLM call per source.
|
|
41
|
+
*
|
|
42
|
+
* Each source gets full page content extracted (when available, via streaming
|
|
43
|
+
* with byte-limit protection) or falls back to SearXNG snippets. Content is
|
|
44
|
+
* truncated to {@link MAX_CHARS_PER_SOURCE} chars per source.
|
|
45
|
+
*
|
|
46
|
+
* Individual source failures are isolated — a failed LLM call for one source
|
|
47
|
+
* does not affect the others. Failed sources fall back to snippet-based
|
|
48
|
+
* degraded entries with `relevance: 0`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function summarizeSearchResults(subject: string, searchResults: SearxngSearchResult[], chatModel: BaseChatModel): Promise<SummarizationResult>;
|
|
51
|
+
//# sourceMappingURL=summarizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarizer.d.ts","sourceRoot":"","sources":["../../../src/app/summarization/summarizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AAEjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAI/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAIhF,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,gEAAgE;AAChE,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,gBAAgB,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,4FAA4F;AAC5F,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,aAAa,EAAE,CAAC;IACjC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,mBAAmB,EAAE,gBAAgB,EAAE,CAAC;IACxC,4EAA4E;IAC5E,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACnC,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CACpC;AAID;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAKtD;AA2HD;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,mBAAmB,EAAE,EACpC,SAAS,EAAE,aAAa,GACvB,OAAO,CAAC,mBAAmB,CAAC,CAuD9B"}
|