refactor(ai): replace SearXNG with Wikipedia adapter for analysis enrichment

- Add wikipediaClient.ts: native fetch to Wikipedia REST/Action APIs
  (search + summary), no extra npm dependency.
- Extract shared Redis cache into cacheStore.ts (decoupled from search).
- Term glossary now uses wikipediaSummary for direct article lookup.
- Remove searxngSearch.ts entirely; drop SEARXNG_BASE_URL config,
  add WIKIPEDIA_LANG / WIKIPEDIA_TIMEOUT_MS.
- Rename backend searxngCalls metric to webSearchCalls.
This commit is contained in:
asepharyana
2026-08-17 20:07:19 +07:00
parent 2825250804
commit 479f4719ba
10 changed files with 434 additions and 589 deletions
@@ -76,13 +76,13 @@ import {
buildStickerTextOnlyWarning,
buildStickerVisionPrompt,
} from "./moderationPrompt.js";
import { buildTermGlossaryBlock } from "./termGlossary.js";
import { extractUrlsFromText } from "./urlFetcher.js";
import {
extractSearchQueries,
formatSearchResults,
searchSearxng,
} from "./searxngSearch.js";
import { buildTermGlossaryBlock } from "./termGlossary.js";
import { extractUrlsFromText } from "./urlFetcher.js";
wikipediaSearch,
} from "./wikipediaClient.js";
// ---------------------------------------------------------------------------
// Types
@@ -354,12 +354,12 @@ export async function prepareMediaMessage(
),
);
// SearXNG
let searxngXml = "";
// Wikipedia web search (context enrichment)
let webSearchXml = "";
const queries = extractSearchQueries(content);
if (queries.length > 0) {
const results = await Promise.allSettled(
queries.map((q) => searchSearxng(q)),
queries.map((q) => wikipediaSearch(q)),
);
const parts: string[] = [];
for (let i = 0; i < results.length; i++) {
@@ -368,7 +368,7 @@ export async function prepareMediaMessage(
parts.push(formatSearchResults(r.value));
}
if (parts.length > 0)
searxngXml = `\n<web_searches>\n${parts.join("\n")}\n</web_searches>`;
webSearchXml = `\n<web_searches>\n${parts.join("\n")}\n</web_searches>`;
}
// Term glossary — cached per-word Wikipedia definitions for words the LLM
@@ -403,6 +403,6 @@ export async function prepareMediaMessage(
// still tracked in the DB for enforcement, just not shown to the LLM.
const isBot = resolveIsBot(target);
const isEdited = resolveIsEdited(target);
const messageBlock = `<message id="${escapeXml(target.id)}" user="${escapeXml(resolveDisplayName(target))}" time="${new Date(target.created_at).toISOString()}"${isBot ? ` bot="true"` : ""}${isEdited ? ` edited="true"` : ""}>\n ${refXml ? `\n ${refXml}` : ""}\n <content>${escapeXml(truncateForAi(content))}</content>${mediaContext ? ` ${escapeXml(mediaContext)}` : ""}${webContext}${mediaAnalysisContext}${searxngXml}${glossaryCtx}\n</message>`;
const messageBlock = `<message id="${escapeXml(target.id)}" user="${escapeXml(resolveDisplayName(target))}" time="${new Date(target.created_at).toISOString()}"${isBot ? ` bot="true"` : ""}${isEdited ? ` edited="true"` : ""}>\n ${refXml ? `\n ${refXml}` : ""}\n <content>${escapeXml(truncateForAi(content))}</content>${mediaContext ? ` ${escapeXml(mediaContext)}` : ""}${webContext}${mediaAnalysisContext}${webSearchXml}${glossaryCtx}\n</message>`;
return { targetId, messageBlock };
}