@astro-minimax/ai 0.2.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/README.md +223 -0
- package/dist/cache/global-cache.d.ts +31 -0
- package/dist/cache/global-cache.d.ts.map +1 -0
- package/dist/cache/global-cache.js +141 -0
- package/dist/cache/index.d.ts +8 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +62 -0
- package/dist/cache/kv-adapter.d.ts +21 -0
- package/dist/cache/kv-adapter.d.ts.map +1 -0
- package/dist/cache/kv-adapter.js +102 -0
- package/dist/cache/memory-adapter.d.ts +24 -0
- package/dist/cache/memory-adapter.d.ts.map +1 -0
- package/dist/cache/memory-adapter.js +95 -0
- package/dist/cache/response-cache.d.ts +45 -0
- package/dist/cache/response-cache.d.ts.map +1 -0
- package/dist/cache/response-cache.js +85 -0
- package/dist/cache/types.d.ts +118 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +16 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +1 -0
- package/dist/data/metadata-loader.d.ts +37 -0
- package/dist/data/metadata-loader.d.ts.map +1 -0
- package/dist/data/metadata-loader.js +54 -0
- package/dist/data/types.d.ts +51 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/data/types.js +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/intelligence/citation-guard.d.ts +24 -0
- package/dist/intelligence/citation-guard.d.ts.map +1 -0
- package/dist/intelligence/citation-guard.js +82 -0
- package/dist/intelligence/evidence-analysis.d.ts +29 -0
- package/dist/intelligence/evidence-analysis.d.ts.map +1 -0
- package/dist/intelligence/evidence-analysis.js +88 -0
- package/dist/intelligence/index.d.ts +6 -0
- package/dist/intelligence/index.d.ts.map +1 -0
- package/dist/intelligence/index.js +4 -0
- package/dist/intelligence/intent-detect.d.ts +29 -0
- package/dist/intelligence/intent-detect.d.ts.map +1 -0
- package/dist/intelligence/intent-detect.js +64 -0
- package/dist/intelligence/keyword-extract.d.ts +31 -0
- package/dist/intelligence/keyword-extract.d.ts.map +1 -0
- package/dist/intelligence/keyword-extract.js +114 -0
- package/dist/intelligence/types.d.ts +27 -0
- package/dist/intelligence/types.d.ts.map +1 -0
- package/dist/intelligence/types.js +1 -0
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +1 -0
- package/dist/middleware/rate-limiter.d.ts +26 -0
- package/dist/middleware/rate-limiter.d.ts.map +1 -0
- package/dist/middleware/rate-limiter.js +129 -0
- package/dist/prompt/dynamic-layer.d.ts +7 -0
- package/dist/prompt/dynamic-layer.d.ts.map +1 -0
- package/dist/prompt/dynamic-layer.js +40 -0
- package/dist/prompt/index.d.ts +6 -0
- package/dist/prompt/index.d.ts.map +1 -0
- package/dist/prompt/index.js +4 -0
- package/dist/prompt/prompt-builder.d.ts +11 -0
- package/dist/prompt/prompt-builder.d.ts.map +1 -0
- package/dist/prompt/prompt-builder.js +19 -0
- package/dist/prompt/semi-static-layer.d.ts +7 -0
- package/dist/prompt/semi-static-layer.d.ts.map +1 -0
- package/dist/prompt/semi-static-layer.js +32 -0
- package/dist/prompt/static-layer.d.ts +3 -0
- package/dist/prompt/static-layer.d.ts.map +1 -0
- package/dist/prompt/static-layer.js +78 -0
- package/dist/prompt/types.d.ts +25 -0
- package/dist/prompt/types.d.ts.map +1 -0
- package/dist/prompt/types.js +1 -0
- package/dist/provider-manager/base.d.ts +26 -0
- package/dist/provider-manager/base.d.ts.map +1 -0
- package/dist/provider-manager/base.js +47 -0
- package/dist/provider-manager/config.d.ts +7 -0
- package/dist/provider-manager/config.d.ts.map +1 -0
- package/dist/provider-manager/config.js +134 -0
- package/dist/provider-manager/index.d.ts +8 -0
- package/dist/provider-manager/index.d.ts.map +1 -0
- package/dist/provider-manager/index.js +6 -0
- package/dist/provider-manager/manager.d.ts +18 -0
- package/dist/provider-manager/manager.d.ts.map +1 -0
- package/dist/provider-manager/manager.js +121 -0
- package/dist/provider-manager/mock.d.ts +18 -0
- package/dist/provider-manager/mock.d.ts.map +1 -0
- package/dist/provider-manager/mock.js +56 -0
- package/dist/provider-manager/openai.d.ts +20 -0
- package/dist/provider-manager/openai.d.ts.map +1 -0
- package/dist/provider-manager/openai.js +83 -0
- package/dist/provider-manager/types.d.ts +217 -0
- package/dist/provider-manager/types.d.ts.map +1 -0
- package/dist/provider-manager/types.js +6 -0
- package/dist/provider-manager/workers.d.ts +20 -0
- package/dist/provider-manager/workers.d.ts.map +1 -0
- package/dist/provider-manager/workers.js +74 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/mock.d.ts +14 -0
- package/dist/providers/mock.d.ts.map +1 -0
- package/dist/providers/mock.js +234 -0
- package/dist/search/index.d.ts +5 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +3 -0
- package/dist/search/search-api.d.ts +28 -0
- package/dist/search/search-api.d.ts.map +1 -0
- package/dist/search/search-api.js +110 -0
- package/dist/search/search-index.d.ts +6 -0
- package/dist/search/search-index.d.ts.map +1 -0
- package/dist/search/search-index.js +22 -0
- package/dist/search/search-utils.d.ts +43 -0
- package/dist/search/search-utils.d.ts.map +1 -0
- package/dist/search/search-utils.js +114 -0
- package/dist/search/session-cache.d.ts +19 -0
- package/dist/search/session-cache.d.ts.map +1 -0
- package/dist/search/session-cache.js +92 -0
- package/dist/search/types.d.ts +41 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/search/types.js +1 -0
- package/dist/server/chat-handler.d.ts +3 -0
- package/dist/server/chat-handler.d.ts.map +1 -0
- package/dist/server/chat-handler.js +750 -0
- package/dist/server/dev-server.d.ts +18 -0
- package/dist/server/dev-server.d.ts.map +1 -0
- package/dist/server/dev-server.js +294 -0
- package/dist/server/errors.d.ts +17 -0
- package/dist/server/errors.d.ts.map +1 -0
- package/dist/server/errors.js +41 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +5 -0
- package/dist/server/metadata-init.d.ts +11 -0
- package/dist/server/metadata-init.d.ts.map +1 -0
- package/dist/server/metadata-init.js +45 -0
- package/dist/server/notify.d.ts +25 -0
- package/dist/server/notify.d.ts.map +1 -0
- package/dist/server/notify.js +62 -0
- package/dist/server/types.d.ts +56 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +13 -0
- package/dist/stream/index.d.ts +3 -0
- package/dist/stream/index.d.ts.map +1 -0
- package/dist/stream/index.js +2 -0
- package/dist/stream/mock-stream.d.ts +12 -0
- package/dist/stream/mock-stream.d.ts.map +1 -0
- package/dist/stream/mock-stream.js +27 -0
- package/dist/stream/response.d.ts +10 -0
- package/dist/stream/response.d.ts.map +1 -0
- package/dist/stream/response.js +22 -0
- package/dist/utils/i18n.d.ts +18 -0
- package/dist/utils/i18n.d.ts.map +1 -0
- package/dist/utils/i18n.js +148 -0
- package/package.json +93 -0
- package/src/components/AIChatContainer.tsx +30 -0
- package/src/components/AIChatWidget.astro +30 -0
- package/src/components/ChatPanel.tsx +865 -0
- package/src/styles/source.css +2 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-flight check: if the user is asking about something that can be
|
|
3
|
+
* answered directly from the available context without an LLM, return it.
|
|
4
|
+
* This prevents hallucination for specific factual queries.
|
|
5
|
+
*/
|
|
6
|
+
export function getCitationGuardPreflight(params) {
|
|
7
|
+
const { userQuery, articles, projects } = params;
|
|
8
|
+
const q = userQuery.toLowerCase();
|
|
9
|
+
// Detect queries asking for article counts or lists
|
|
10
|
+
if (/有几篇|有多少篇|文章数量|总共.*文章/.test(q)) {
|
|
11
|
+
const total = articles.length;
|
|
12
|
+
if (total > 0) {
|
|
13
|
+
return {
|
|
14
|
+
text: `根据我检索到的信息,当前共找到 ${total} 篇相关文章。`,
|
|
15
|
+
actions: ['preflight_reject'],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Detect queries about specific article existence that we can verify
|
|
20
|
+
if (/有没有|是否有|有.*文章|写过.*吗/.test(q)) {
|
|
21
|
+
if (articles.length === 0 && projects.length === 0) {
|
|
22
|
+
return {
|
|
23
|
+
text: '根据博客内容搜索,目前没有找到与这个主题直接相关的文章。你可以尝试用其他关键词搜索,或者问我其他问题。',
|
|
24
|
+
actions: ['preflight_reject'],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates a transform stream that monitors the AI output for hallucinated references.
|
|
32
|
+
* Rewrites or suppresses fabricated article/project links.
|
|
33
|
+
*/
|
|
34
|
+
export function createCitationGuardTransform(params) {
|
|
35
|
+
const { articles, projects, onApplied } = params;
|
|
36
|
+
const validUrls = new Set([
|
|
37
|
+
...articles.map(a => a.url),
|
|
38
|
+
...projects.map(p => p.url),
|
|
39
|
+
]);
|
|
40
|
+
return (stream) => {
|
|
41
|
+
const actions = [];
|
|
42
|
+
let buffer = '';
|
|
43
|
+
const transform = new TransformStream({
|
|
44
|
+
transform(chunk, controller) {
|
|
45
|
+
buffer += chunk;
|
|
46
|
+
// Check for Markdown links: [text](url)
|
|
47
|
+
const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
48
|
+
let match;
|
|
49
|
+
let lastIndex = 0;
|
|
50
|
+
let output = '';
|
|
51
|
+
while ((match = linkPattern.exec(buffer)) !== null) {
|
|
52
|
+
const [fullMatch, text, url] = match;
|
|
53
|
+
output += buffer.slice(lastIndex, match.index);
|
|
54
|
+
if (url.startsWith('http') && !validUrls.has(url)) {
|
|
55
|
+
// Fabricated external URL — keep the text, remove the link
|
|
56
|
+
output += text;
|
|
57
|
+
actions.push('stream_rewrite');
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
output += fullMatch;
|
|
61
|
+
}
|
|
62
|
+
lastIndex = match.index + fullMatch.length;
|
|
63
|
+
}
|
|
64
|
+
// Keep unparsed remainder in buffer (may be mid-link)
|
|
65
|
+
buffer = buffer.slice(lastIndex);
|
|
66
|
+
if (output) {
|
|
67
|
+
controller.enqueue(output);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
flush(controller) {
|
|
71
|
+
if (buffer) {
|
|
72
|
+
controller.enqueue(buffer);
|
|
73
|
+
buffer = '';
|
|
74
|
+
}
|
|
75
|
+
if (actions.length > 0) {
|
|
76
|
+
onApplied?.({ actions });
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
return stream.pipeThrough(transform);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ArticleContext, ProjectContext } from '../search/types.js';
|
|
2
|
+
import type { EvidenceAnalysisResult, QueryComplexity } from './types.js';
|
|
3
|
+
export declare const EVIDENCE_ANALYSIS_TIMEOUT_MS = 8000;
|
|
4
|
+
export declare const EVIDENCE_ANALYSIS_MAX_TOKENS = 360;
|
|
5
|
+
/**
|
|
6
|
+
* Determines whether evidence analysis should be skipped.
|
|
7
|
+
* Skips for simple queries or when there's insufficient content to analyze.
|
|
8
|
+
*/
|
|
9
|
+
export declare function shouldSkipAnalysis(latestText: string, articleCount: number, complexity: QueryComplexity): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Uses an LLM to pre-analyze retrieved evidence and identify the most relevant pieces.
|
|
12
|
+
* This improves the quality of the final system prompt by pre-filtering noise.
|
|
13
|
+
*/
|
|
14
|
+
export declare function analyzeRetrievedEvidence(params: {
|
|
15
|
+
userQuery: string;
|
|
16
|
+
articles: ArticleContext[];
|
|
17
|
+
projects: ProjectContext[];
|
|
18
|
+
provider: {
|
|
19
|
+
chatModel: (model: string) => unknown;
|
|
20
|
+
};
|
|
21
|
+
model: string;
|
|
22
|
+
maxOutputTokens?: number;
|
|
23
|
+
abortSignal?: AbortSignal;
|
|
24
|
+
}): Promise<EvidenceAnalysisResult>;
|
|
25
|
+
/**
|
|
26
|
+
* Formats the evidence analysis for injection into the system prompt.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildEvidenceSection(analysis: string): string;
|
|
29
|
+
//# sourceMappingURL=evidence-analysis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidence-analysis.d.ts","sourceRoot":"","sources":["../../src/intelligence/evidence-analysis.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE1E,eAAO,MAAM,4BAA4B,OAAO,CAAC;AACjD,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,eAAe,GAC1B,OAAO,CAKT;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,QAAQ,EAAE;QAAE,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;KAAE,CAAC;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAiDlC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG7D"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { generateText } from 'ai';
|
|
2
|
+
export const EVIDENCE_ANALYSIS_TIMEOUT_MS = 8000;
|
|
3
|
+
export const EVIDENCE_ANALYSIS_MAX_TOKENS = 360;
|
|
4
|
+
/**
|
|
5
|
+
* Determines whether evidence analysis should be skipped.
|
|
6
|
+
* Skips for simple queries or when there's insufficient content to analyze.
|
|
7
|
+
*/
|
|
8
|
+
export function shouldSkipAnalysis(latestText, articleCount, complexity) {
|
|
9
|
+
if (articleCount < 2)
|
|
10
|
+
return true;
|
|
11
|
+
if (complexity === 'simple')
|
|
12
|
+
return true;
|
|
13
|
+
if (latestText.length < 15)
|
|
14
|
+
return true;
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Uses an LLM to pre-analyze retrieved evidence and identify the most relevant pieces.
|
|
19
|
+
* This improves the quality of the final system prompt by pre-filtering noise.
|
|
20
|
+
*/
|
|
21
|
+
export async function analyzeRetrievedEvidence(params) {
|
|
22
|
+
const { userQuery, articles, projects, provider, model, maxOutputTokens = EVIDENCE_ANALYSIS_MAX_TOKENS, abortSignal } = params;
|
|
23
|
+
const evidenceSummary = buildEvidenceSummary(articles, projects);
|
|
24
|
+
const prompt = `用户问题:${userQuery}
|
|
25
|
+
|
|
26
|
+
检索到的相关内容:
|
|
27
|
+
${evidenceSummary}
|
|
28
|
+
|
|
29
|
+
请分析这些内容,提取与用户问题最相关的2-3个关键信息点。格式:
|
|
30
|
+
<evidence>
|
|
31
|
+
[关键信息点1]
|
|
32
|
+
[关键信息点2]
|
|
33
|
+
</evidence>
|
|
34
|
+
|
|
35
|
+
只返回evidence标签内的内容,简洁准确。`;
|
|
36
|
+
try {
|
|
37
|
+
const result = await generateText({
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
model: provider.chatModel(model),
|
|
40
|
+
prompt,
|
|
41
|
+
maxOutputTokens,
|
|
42
|
+
temperature: 0.1,
|
|
43
|
+
abortSignal,
|
|
44
|
+
});
|
|
45
|
+
const rawText = result.text?.trim() ?? '';
|
|
46
|
+
const match = rawText.match(/<evidence>([\s\S]*?)<\/evidence>/);
|
|
47
|
+
const analysis = match?.[1]?.trim();
|
|
48
|
+
const u = result.usage;
|
|
49
|
+
return {
|
|
50
|
+
analysis,
|
|
51
|
+
parseStatus: analysis ? 'ok' : 'no_match',
|
|
52
|
+
rawText,
|
|
53
|
+
usage: u ? {
|
|
54
|
+
inputTokens: u.inputTokens ?? 0,
|
|
55
|
+
outputTokens: u.outputTokens ?? 0,
|
|
56
|
+
totalTokens: (u.inputTokens ?? 0) + (u.outputTokens ?? 0),
|
|
57
|
+
} : undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return {
|
|
62
|
+
parseStatus: 'error',
|
|
63
|
+
error: error instanceof Error ? error.message : String(error),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Formats the evidence analysis for injection into the system prompt.
|
|
69
|
+
*/
|
|
70
|
+
export function buildEvidenceSection(analysis) {
|
|
71
|
+
if (!analysis.trim())
|
|
72
|
+
return '';
|
|
73
|
+
return `\n## 关键证据分析\n${analysis}\n`;
|
|
74
|
+
}
|
|
75
|
+
function buildEvidenceSummary(articles, projects) {
|
|
76
|
+
const lines = [];
|
|
77
|
+
for (const article of articles.slice(0, 6)) {
|
|
78
|
+
lines.push(`文章: ${article.title}`);
|
|
79
|
+
if (article.summary)
|
|
80
|
+
lines.push(` 摘要: ${article.summary}`);
|
|
81
|
+
if (article.keyPoints.length)
|
|
82
|
+
lines.push(` 要点: ${article.keyPoints.slice(0, 3).join(', ')}`);
|
|
83
|
+
}
|
|
84
|
+
for (const project of projects.slice(0, 3)) {
|
|
85
|
+
lines.push(`项目: ${project.name} - ${project.description.slice(0, 100)}`);
|
|
86
|
+
}
|
|
87
|
+
return lines.join('\n');
|
|
88
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { isLikelyFollowUp, hasNewSignificantTokens, hasQueryOverlap, shouldReuseSearchContext, buildLocalSearchQuery, } from './intent-detect.js';
|
|
2
|
+
export { shouldRunKeywordExtraction, extractSearchKeywords, KEYWORD_EXTRACTION_TIMEOUT_MS, } from './keyword-extract.js';
|
|
3
|
+
export { shouldSkipAnalysis, analyzeRetrievedEvidence, buildEvidenceSection, EVIDENCE_ANALYSIS_TIMEOUT_MS, EVIDENCE_ANALYSIS_MAX_TOKENS, } from './evidence-analysis.js';
|
|
4
|
+
export { getCitationGuardPreflight, createCitationGuardTransform, } from './citation-guard.js';
|
|
5
|
+
export type { QueryComplexity, KeywordExtractionResult, TokenUsageStats, EvidenceAnalysisResult, CitationGuardPreflight, CitationGuardAction, } from './types.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/intelligence/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,eAAe,EACf,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,oBAAoB,EACpB,4BAA4B,EAC5B,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,eAAe,EACf,uBAAuB,EACvB,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { isLikelyFollowUp, hasNewSignificantTokens, hasQueryOverlap, shouldReuseSearchContext, buildLocalSearchQuery, } from './intent-detect.js';
|
|
2
|
+
export { shouldRunKeywordExtraction, extractSearchKeywords, KEYWORD_EXTRACTION_TIMEOUT_MS, } from './keyword-extract.js';
|
|
3
|
+
export { shouldSkipAnalysis, analyzeRetrievedEvidence, buildEvidenceSection, EVIDENCE_ANALYSIS_TIMEOUT_MS, EVIDENCE_ANALYSIS_MAX_TOKENS, } from './evidence-analysis.js';
|
|
4
|
+
export { getCitationGuardPreflight, createCitationGuardTransform, } from './citation-guard.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CachedSearchContext } from '../search/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Determines if the latest message is likely a follow-up to the previous context.
|
|
4
|
+
* Uses heuristics: message length, punctuation, word count.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isLikelyFollowUp(message: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Checks whether the current query contains significant new tokens
|
|
9
|
+
* that aren't present in the cached query.
|
|
10
|
+
*/
|
|
11
|
+
export declare function hasNewSignificantTokens(currentQuery: string, cachedQuery: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Checks whether the current query overlaps significantly with the cached query.
|
|
14
|
+
*/
|
|
15
|
+
export declare function hasQueryOverlap(currentQuery: string, cachedQuery: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Determines whether to reuse the cached search context for this request.
|
|
18
|
+
*/
|
|
19
|
+
export declare function shouldReuseSearchContext(params: {
|
|
20
|
+
latestText: string;
|
|
21
|
+
cachedContext: CachedSearchContext | undefined;
|
|
22
|
+
userTurnCount: number;
|
|
23
|
+
now: number;
|
|
24
|
+
}): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Builds a normalized local search query from the latest message.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildLocalSearchQuery(latestText: string): string;
|
|
29
|
+
//# sourceMappingURL=intent-detect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent-detect.d.ts","sourceRoot":"","sources":["../../src/intelligence/intent-detect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAK9D;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAUzD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAK1F;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAKlF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,OAAO,CASV;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEhE"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { tokenize, normalizeText } from '../search/search-utils.js';
|
|
2
|
+
import { SESSION_CACHE_TTL_MS } from '../search/session-cache.js';
|
|
3
|
+
const MAX_FOLLOW_UP_LENGTH = 48;
|
|
4
|
+
/**
|
|
5
|
+
* Determines if the latest message is likely a follow-up to the previous context.
|
|
6
|
+
* Uses heuristics: message length, punctuation, word count.
|
|
7
|
+
*/
|
|
8
|
+
export function isLikelyFollowUp(message) {
|
|
9
|
+
const text = message.trim();
|
|
10
|
+
if (!text || text.length > MAX_FOLLOW_UP_LENGTH)
|
|
11
|
+
return false;
|
|
12
|
+
const hasTerminalPunctuation = /[??!!。.…]$/.test(text);
|
|
13
|
+
const wordCount = text.split(/\s+/).filter(Boolean).length;
|
|
14
|
+
if (text.length <= 16)
|
|
15
|
+
return true;
|
|
16
|
+
if (!/\s/.test(text) && text.length <= 24)
|
|
17
|
+
return true;
|
|
18
|
+
return hasTerminalPunctuation && wordCount <= 6 && text.length <= 36;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Checks whether the current query contains significant new tokens
|
|
22
|
+
* that aren't present in the cached query.
|
|
23
|
+
*/
|
|
24
|
+
export function hasNewSignificantTokens(currentQuery, cachedQuery) {
|
|
25
|
+
const currentTokens = new Set(tokenize(currentQuery));
|
|
26
|
+
const cachedTokens = new Set(tokenize(cachedQuery));
|
|
27
|
+
const newTokens = [...currentTokens].filter(t => !cachedTokens.has(t) && t.length >= 2);
|
|
28
|
+
return newTokens.length > 0;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Checks whether the current query overlaps significantly with the cached query.
|
|
32
|
+
*/
|
|
33
|
+
export function hasQueryOverlap(currentQuery, cachedQuery) {
|
|
34
|
+
const currentTokens = tokenize(currentQuery);
|
|
35
|
+
const cachedNorm = normalizeText(cachedQuery);
|
|
36
|
+
if (!currentTokens.length || !cachedNorm)
|
|
37
|
+
return false;
|
|
38
|
+
return currentTokens.some(t => cachedNorm.includes(t));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Determines whether to reuse the cached search context for this request.
|
|
42
|
+
*/
|
|
43
|
+
export function shouldReuseSearchContext(params) {
|
|
44
|
+
const { latestText, cachedContext, userTurnCount, now } = params;
|
|
45
|
+
if (!cachedContext)
|
|
46
|
+
return false;
|
|
47
|
+
if (userTurnCount <= 1)
|
|
48
|
+
return false;
|
|
49
|
+
if (now - cachedContext.updatedAt > SESSION_CACHE_TTL_MS)
|
|
50
|
+
return false;
|
|
51
|
+
if (!isLikelyFollowUp(latestText))
|
|
52
|
+
return false;
|
|
53
|
+
if (!hasQueryOverlap(latestText, cachedContext.query))
|
|
54
|
+
return false;
|
|
55
|
+
if (hasNewSignificantTokens(latestText, cachedContext.query))
|
|
56
|
+
return false;
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Builds a normalized local search query from the latest message.
|
|
61
|
+
*/
|
|
62
|
+
export function buildLocalSearchQuery(latestText) {
|
|
63
|
+
return tokenize(latestText).join(' ');
|
|
64
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { KeywordExtractionResult } from './types.js';
|
|
2
|
+
export declare const KEYWORD_EXTRACTION_TIMEOUT_MS = 5000;
|
|
3
|
+
/**
|
|
4
|
+
* Determines whether to run LLM-based keyword extraction.
|
|
5
|
+
* Skips extraction for simple single-turn queries with a clear local query.
|
|
6
|
+
*/
|
|
7
|
+
export declare function shouldRunKeywordExtraction(params: {
|
|
8
|
+
messageCount: number;
|
|
9
|
+
localQuery: string;
|
|
10
|
+
latestText: string;
|
|
11
|
+
}): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Extracts optimized search keywords from the conversation using LLM.
|
|
14
|
+
* Falls back to local tokenization if LLM call fails or times out.
|
|
15
|
+
*/
|
|
16
|
+
export declare function extractSearchKeywords(params: {
|
|
17
|
+
messages: Array<{
|
|
18
|
+
role: string;
|
|
19
|
+
parts?: Array<{
|
|
20
|
+
type: string;
|
|
21
|
+
text?: string;
|
|
22
|
+
}>;
|
|
23
|
+
content?: string;
|
|
24
|
+
}>;
|
|
25
|
+
provider: {
|
|
26
|
+
chatModel: (model: string) => unknown;
|
|
27
|
+
};
|
|
28
|
+
model: string;
|
|
29
|
+
abortSignal?: AbortSignal;
|
|
30
|
+
}): Promise<KeywordExtractionResult>;
|
|
31
|
+
//# sourceMappingURL=keyword-extract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyword-extract.d.ts","sourceRoot":"","sources":["../../src/intelligence/keyword-extract.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAmB,MAAM,YAAY,CAAC;AAE3E,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAElD;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CASV;AAYD;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpG,QAAQ,EAAE;QAAE,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;KAAE,CAAC;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAmEnC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { generateText } from 'ai';
|
|
2
|
+
import { tokenize } from '../search/search-utils.js';
|
|
3
|
+
export const KEYWORD_EXTRACTION_TIMEOUT_MS = 5000;
|
|
4
|
+
/**
|
|
5
|
+
* Determines whether to run LLM-based keyword extraction.
|
|
6
|
+
* Skips extraction for simple single-turn queries with a clear local query.
|
|
7
|
+
*/
|
|
8
|
+
export function shouldRunKeywordExtraction(params) {
|
|
9
|
+
const { messageCount, localQuery, latestText } = params;
|
|
10
|
+
// Only extract for multi-turn conversations or ambiguous short messages
|
|
11
|
+
if (messageCount < 3)
|
|
12
|
+
return false;
|
|
13
|
+
if (latestText.length < 10)
|
|
14
|
+
return false;
|
|
15
|
+
// If the local query is already clear (multiple tokens), skip LLM
|
|
16
|
+
const tokens = tokenize(localQuery || latestText);
|
|
17
|
+
if (tokens.length >= 3)
|
|
18
|
+
return false;
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Classifies the complexity of the user's query.
|
|
23
|
+
*/
|
|
24
|
+
function classifyComplexity(text) {
|
|
25
|
+
const tokens = tokenize(text);
|
|
26
|
+
if (tokens.length <= 1 || text.length <= 10)
|
|
27
|
+
return 'simple';
|
|
28
|
+
if (tokens.length >= 5 || text.length > 80)
|
|
29
|
+
return 'complex';
|
|
30
|
+
return 'moderate';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extracts optimized search keywords from the conversation using LLM.
|
|
34
|
+
* Falls back to local tokenization if LLM call fails or times out.
|
|
35
|
+
*/
|
|
36
|
+
export async function extractSearchKeywords(params) {
|
|
37
|
+
const { messages, provider, model, abortSignal } = params;
|
|
38
|
+
const latestMessage = messages[messages.length - 1];
|
|
39
|
+
const latestText = getMessageText(latestMessage);
|
|
40
|
+
const complexity = classifyComplexity(latestText);
|
|
41
|
+
const conversationText = messages
|
|
42
|
+
.slice(-6) // Last 3 turns
|
|
43
|
+
.map(m => `${m.role}: ${getMessageText(m)}`)
|
|
44
|
+
.join('\n');
|
|
45
|
+
const prompt = `你是一个搜索关键词提取助手。分析以下对话,提取最佳搜索关键词。
|
|
46
|
+
|
|
47
|
+
对话:
|
|
48
|
+
${conversationText}
|
|
49
|
+
|
|
50
|
+
请提取:
|
|
51
|
+
1. 主查询词(最重要的1-2个关键词,用空格分隔)
|
|
52
|
+
2. 补充查询词(可选的辅助关键词)
|
|
53
|
+
|
|
54
|
+
仅返回JSON格式,不要其他内容:
|
|
55
|
+
{"query": "主查询词", "primaryQuery": "核心词"}`;
|
|
56
|
+
try {
|
|
57
|
+
const result = await generateText({
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
|
+
model: provider.chatModel(model),
|
|
60
|
+
prompt,
|
|
61
|
+
maxOutputTokens: 100,
|
|
62
|
+
temperature: 0,
|
|
63
|
+
abortSignal,
|
|
64
|
+
});
|
|
65
|
+
const rawText = result.text?.trim() ?? '';
|
|
66
|
+
// Try to parse JSON response
|
|
67
|
+
const jsonMatch = rawText.match(/\{[^}]+\}/);
|
|
68
|
+
if (jsonMatch) {
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
71
|
+
const query = (parsed.query ?? '').trim();
|
|
72
|
+
const primaryQuery = (parsed.primaryQuery ?? query).trim();
|
|
73
|
+
if (query) {
|
|
74
|
+
const u = result.usage;
|
|
75
|
+
return {
|
|
76
|
+
query,
|
|
77
|
+
primaryQuery,
|
|
78
|
+
complexity,
|
|
79
|
+
usedFallback: false,
|
|
80
|
+
usage: u ? {
|
|
81
|
+
inputTokens: u.inputTokens ?? 0,
|
|
82
|
+
outputTokens: u.outputTokens ?? 0,
|
|
83
|
+
totalTokens: (u.inputTokens ?? 0) + (u.outputTokens ?? 0),
|
|
84
|
+
} : undefined,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Parse failed — fall through to fallback
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return buildFallback(latestText, complexity, 'json_parse_failed');
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
96
|
+
return buildFallback(latestText, complexity, message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function buildFallback(latestText, complexity, error) {
|
|
100
|
+
const tokens = tokenize(latestText);
|
|
101
|
+
const query = tokens.slice(0, 3).join(' ') || latestText.slice(0, 30);
|
|
102
|
+
return { query, primaryQuery: query, complexity, usedFallback: true, error };
|
|
103
|
+
}
|
|
104
|
+
function getMessageText(message) {
|
|
105
|
+
if (message.content && typeof message.content === 'string')
|
|
106
|
+
return message.content;
|
|
107
|
+
if (Array.isArray(message.parts)) {
|
|
108
|
+
return message.parts
|
|
109
|
+
.filter((p) => p.type === 'text' && typeof p.text === 'string')
|
|
110
|
+
.map(p => p.text)
|
|
111
|
+
.join('');
|
|
112
|
+
}
|
|
113
|
+
return '';
|
|
114
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type QueryComplexity = 'simple' | 'moderate' | 'complex';
|
|
2
|
+
export interface KeywordExtractionResult {
|
|
3
|
+
query: string;
|
|
4
|
+
primaryQuery: string;
|
|
5
|
+
complexity: QueryComplexity;
|
|
6
|
+
usedFallback: boolean;
|
|
7
|
+
error?: string;
|
|
8
|
+
usage?: TokenUsageStats;
|
|
9
|
+
}
|
|
10
|
+
export interface TokenUsageStats {
|
|
11
|
+
inputTokens: number;
|
|
12
|
+
outputTokens: number;
|
|
13
|
+
totalTokens: number;
|
|
14
|
+
}
|
|
15
|
+
export interface EvidenceAnalysisResult {
|
|
16
|
+
analysis?: string;
|
|
17
|
+
parseStatus: string;
|
|
18
|
+
usage?: TokenUsageStats;
|
|
19
|
+
error?: string;
|
|
20
|
+
rawText?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface CitationGuardPreflight {
|
|
23
|
+
text: string;
|
|
24
|
+
actions: CitationGuardAction[];
|
|
25
|
+
}
|
|
26
|
+
export type CitationGuardAction = 'preflight_reject' | 'stream_rewrite' | 'stream_suppress';
|
|
27
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/intelligence/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAEhE,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,eAAe,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,mBAAmB,EAAE,CAAC;CAChC;AAED,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG,gBAAgB,GAAG,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,cAAc,EACd,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getClientIP, checkRateLimit, rateLimitResponse, } from './rate-limiter.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Three-tier IP-based rate limiter for chat API.
|
|
3
|
+
* Tiers: burst (short), sustained (medium), daily (long).
|
|
4
|
+
*/
|
|
5
|
+
export interface RateLimitResult {
|
|
6
|
+
allowed: boolean;
|
|
7
|
+
retryAfterMs: number;
|
|
8
|
+
limit: number;
|
|
9
|
+
remaining: number;
|
|
10
|
+
triggeredBy: 'burst' | 'sustained' | 'daily' | null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Extracts the real client IP from the request headers.
|
|
14
|
+
* Priority: cf-connecting-ip > x-forwarded-for > x-real-ip > 'unknown'
|
|
15
|
+
*/
|
|
16
|
+
export declare function getClientIP(req: Request): string;
|
|
17
|
+
/**
|
|
18
|
+
* Checks the rate limit for the given IP and env configuration.
|
|
19
|
+
* Records the request if allowed.
|
|
20
|
+
*/
|
|
21
|
+
export declare function checkRateLimit(ip: string, env?: Record<string, string | undefined>): RateLimitResult;
|
|
22
|
+
/**
|
|
23
|
+
* Builds a 429 response for a rejected rate limit check.
|
|
24
|
+
*/
|
|
25
|
+
export declare function rateLimitResponse(result: RateLimitResult, lang?: string): Response;
|
|
26
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,GAAG,WAAW,GAAG,OAAO,GAAG,IAAI,CAAC;CACrD;AA8CD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAOhD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,MAAM,EACV,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAM,GAC3C,eAAe,CAsDjB;AAWD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,CAalF"}
|