@astro-minimax/ai 0.9.0 → 0.9.3
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 +108 -18
- package/dist/cache/global-cache.d.ts +6 -2
- package/dist/cache/global-cache.d.ts.map +1 -1
- package/dist/cache/global-cache.js +24 -9
- package/dist/cache/index.d.ts +7 -6
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +12 -4
- package/dist/cache/injection-cache.d.ts +36 -0
- package/dist/cache/injection-cache.d.ts.map +1 -0
- package/dist/cache/injection-cache.js +90 -0
- package/dist/cache/kv-adapter.d.ts.map +1 -1
- package/dist/cache/kv-adapter.js +2 -1
- package/dist/cache/memory-adapter.d.ts.map +1 -1
- package/dist/cache/memory-adapter.js +2 -1
- package/dist/cache/response-cache.d.ts +10 -5
- package/dist/cache/response-cache.d.ts.map +1 -1
- package/dist/cache/response-cache.js +18 -6
- package/dist/components/AIChatContainer.d.ts +2 -2
- package/dist/components/AIChatContainer.d.ts.map +1 -1
- package/dist/components/AIChatContainer.js +8 -920
- package/dist/components/ChatInput.d.ts +15 -0
- package/dist/components/ChatInput.d.ts.map +1 -0
- package/dist/components/ChatInput.js +72 -0
- package/dist/components/ChatPanel.d.ts +1 -1
- package/dist/components/ChatPanel.d.ts.map +1 -1
- package/dist/components/ChatPanel.js +210 -672
- package/dist/components/CodeBlock.d.ts +31 -0
- package/dist/components/CodeBlock.d.ts.map +1 -0
- package/dist/components/CodeBlock.js +143 -0
- package/dist/components/MarkmapBlock.d.ts +4 -0
- package/dist/components/MarkmapBlock.d.ts.map +1 -0
- package/dist/components/MarkmapBlock.js +180 -0
- package/dist/components/MermaidBlock.d.ts +4 -0
- package/dist/components/MermaidBlock.d.ts.map +1 -0
- package/dist/components/MermaidBlock.js +193 -0
- package/dist/components/MessageBubble.d.ts +21 -0
- package/dist/components/MessageBubble.d.ts.map +1 -0
- package/dist/components/MessageBubble.js +233 -0
- package/dist/components/ReasoningBlock.d.ts +6 -0
- package/dist/components/ReasoningBlock.d.ts.map +1 -0
- package/dist/components/ReasoningBlock.js +11 -0
- package/dist/components/RichText.d.ts +41 -0
- package/dist/components/RichText.d.ts.map +1 -0
- package/dist/components/RichText.js +202 -0
- package/dist/components/VizShared.d.ts +57 -0
- package/dist/components/VizShared.d.ts.map +1 -0
- package/dist/components/VizShared.js +233 -0
- package/dist/components/tool-auto-continue.d.ts +5 -0
- package/dist/components/tool-auto-continue.d.ts.map +1 -0
- package/dist/components/tool-auto-continue.js +33 -0
- package/dist/constants.d.ts +61 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +72 -0
- package/dist/data/index.d.ts +4 -3
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +4 -10
- package/dist/data/knowledge-types.d.ts +8 -0
- package/dist/data/knowledge-types.d.ts.map +1 -0
- package/dist/data/knowledge-types.js +14 -0
- package/dist/data/metadata-loader.d.ts +4 -28
- package/dist/data/metadata-loader.d.ts.map +1 -1
- package/dist/data/metadata-loader.js +11 -34
- package/dist/data/types.d.ts +17 -2
- package/dist/data/types.d.ts.map +1 -1
- package/dist/extensions/index.d.ts +5 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +24 -0
- package/dist/extensions/injector.d.ts +14 -0
- package/dist/extensions/injector.d.ts.map +1 -0
- package/dist/extensions/injector.js +146 -0
- package/dist/extensions/loader.d.ts +5 -0
- package/dist/extensions/loader.d.ts.map +1 -0
- package/dist/extensions/loader.js +45 -0
- package/dist/extensions/registry.d.ts +4 -0
- package/dist/extensions/registry.d.ts.map +1 -0
- package/dist/extensions/registry.js +144 -0
- package/dist/extensions/types.d.ts +126 -0
- package/dist/extensions/types.d.ts.map +1 -0
- package/dist/extensions/types.js +0 -0
- package/dist/fact-registry/prompt-injector.d.ts +1 -1
- package/dist/fact-registry/prompt-injector.d.ts.map +1 -1
- package/dist/fact-registry/prompt-injector.js +2 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/intelligence/citation-guard.d.ts +2 -13
- package/dist/intelligence/citation-guard.d.ts.map +1 -1
- package/dist/intelligence/citation-guard.js +52 -23
- package/dist/intelligence/evidence-analysis.d.ts +24 -16
- package/dist/intelligence/evidence-analysis.d.ts.map +1 -1
- package/dist/intelligence/evidence-analysis.js +118 -20
- package/dist/intelligence/evidence-budget.d.ts +13 -0
- package/dist/intelligence/evidence-budget.d.ts.map +1 -0
- package/dist/intelligence/evidence-budget.js +49 -0
- package/dist/intelligence/index.d.ts +10 -4
- package/dist/intelligence/index.d.ts.map +1 -1
- package/dist/intelligence/index.js +27 -3
- package/dist/intelligence/keyword-extract.d.ts +1 -1
- package/dist/intelligence/keyword-extract.d.ts.map +1 -1
- package/dist/intelligence/keyword-extract.js +5 -9
- package/dist/intelligence/request-interpretation.d.ts +40 -0
- package/dist/intelligence/request-interpretation.d.ts.map +1 -0
- package/dist/intelligence/request-interpretation.js +71 -0
- package/dist/intelligence/response-templates.d.ts +1 -0
- package/dist/intelligence/response-templates.d.ts.map +1 -1
- package/dist/intelligence/response-templates.js +13 -0
- package/dist/prompt/dynamic-layer.d.ts +1 -5
- package/dist/prompt/dynamic-layer.d.ts.map +1 -1
- package/dist/prompt/dynamic-layer.js +145 -9
- package/dist/prompt/prompt-builder.d.ts +1 -1
- package/dist/prompt/prompt-builder.d.ts.map +1 -1
- package/dist/prompt/prompt-builder.js +5 -1
- package/dist/prompt/semi-static-layer.d.ts +1 -1
- package/dist/prompt/semi-static-layer.d.ts.map +1 -1
- package/dist/prompt/semi-static-layer.js +22 -12
- package/dist/prompt/static-layer.d.ts.map +1 -1
- package/dist/prompt/static-layer.js +37 -4
- package/dist/prompt/types.d.ts +9 -4
- package/dist/prompt/types.d.ts.map +1 -1
- package/dist/provider-manager/base.d.ts +5 -1
- package/dist/provider-manager/base.d.ts.map +1 -1
- package/dist/provider-manager/base.js +22 -2
- package/dist/provider-manager/config.d.ts.map +1 -1
- package/dist/provider-manager/config.js +3 -2
- package/dist/provider-manager/index.d.ts +1 -1
- package/dist/provider-manager/index.d.ts.map +1 -1
- package/dist/provider-manager/index.js +1 -2
- package/dist/provider-manager/manager.d.ts +10 -1
- package/dist/provider-manager/manager.d.ts.map +1 -1
- package/dist/provider-manager/manager.js +26 -10
- package/dist/provider-manager/openai.d.ts +2 -2
- package/dist/provider-manager/openai.d.ts.map +1 -1
- package/dist/provider-manager/openai.js +19 -4
- package/dist/provider-manager/types.d.ts +18 -38
- package/dist/provider-manager/types.d.ts.map +1 -1
- package/dist/provider-manager/workers.d.ts +2 -2
- package/dist/provider-manager/workers.d.ts.map +1 -1
- package/dist/provider-manager/workers.js +15 -4
- package/dist/query/followup.d.ts +7 -0
- package/dist/query/followup.d.ts.map +1 -0
- package/dist/query/followup.js +46 -0
- package/dist/query/intent.d.ts +6 -0
- package/dist/query/intent.d.ts.map +1 -0
- package/dist/query/intent.js +137 -0
- package/dist/query/types.d.ts +8 -0
- package/dist/query/types.d.ts.map +1 -0
- package/dist/query/types.js +0 -0
- package/dist/search/hybrid-search.d.ts +111 -0
- package/dist/search/hybrid-search.d.ts.map +1 -0
- package/dist/search/hybrid-search.js +326 -0
- package/dist/search/index.d.ts +11 -9
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +46 -10
- package/dist/search/scoring.d.ts +18 -0
- package/dist/search/scoring.d.ts.map +1 -0
- package/dist/search/{search-utils.js → scoring.js} +14 -27
- package/dist/search/search-api.d.ts +16 -1
- package/dist/search/search-api.d.ts.map +1 -1
- package/dist/search/search-api.js +118 -15
- package/dist/search/search-index.d.ts +2 -2
- package/dist/search/search-index.d.ts.map +1 -1
- package/dist/search/search-index.js +4 -2
- package/dist/search/session-cache.d.ts +4 -10
- package/dist/search/session-cache.d.ts.map +1 -1
- package/dist/search/session-cache.js +12 -45
- package/dist/search/types.d.ts +28 -0
- package/dist/search/types.d.ts.map +1 -1
- package/dist/search/vector-reranker.d.ts +3 -3
- package/dist/search/vector-reranker.d.ts.map +1 -1
- package/dist/search/vector-reranker.js +14 -2
- package/dist/server/chat-handler.d.ts +86 -1
- package/dist/server/chat-handler.d.ts.map +1 -1
- package/dist/server/chat-handler.js +835 -401
- package/dist/server/chat-message-utils.d.ts +6 -0
- package/dist/server/chat-message-utils.d.ts.map +1 -0
- package/dist/server/chat-message-utils.js +40 -0
- package/dist/server/chat-utils.d.ts +30 -0
- package/dist/server/chat-utils.d.ts.map +1 -0
- package/dist/server/chat-utils.js +88 -0
- package/dist/server/dev-server.js +238 -101
- package/dist/server/env-config.d.ts +22 -0
- package/dist/server/env-config.d.ts.map +1 -0
- package/dist/server/env-config.js +25 -0
- package/dist/server/errors.d.ts +1 -0
- package/dist/server/errors.d.ts.map +1 -1
- package/dist/server/errors.js +14 -7
- package/dist/server/index.d.ts +2 -4
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -25
- package/dist/server/metadata-init.d.ts +10 -5
- package/dist/server/metadata-init.d.ts.map +1 -1
- package/dist/server/metadata-init.js +78 -34
- package/dist/server/notify.d.ts +12 -11
- package/dist/server/notify.d.ts.map +1 -1
- package/dist/server/notify.js +46 -48
- package/dist/server/prompt-runtime.d.ts +60 -0
- package/dist/server/prompt-runtime.d.ts.map +1 -0
- package/dist/server/prompt-runtime.js +284 -0
- package/dist/server/stream-helpers.d.ts +30 -16
- package/dist/server/stream-helpers.d.ts.map +1 -1
- package/dist/server/stream-helpers.js +152 -15
- package/dist/server/types.d.ts +47 -12
- package/dist/server/types.d.ts.map +1 -1
- package/dist/structured-output/generator.d.ts +6 -0
- package/dist/structured-output/generator.d.ts.map +1 -0
- package/dist/structured-output/generator.js +164 -0
- package/dist/structured-output/index.d.ts +4 -0
- package/dist/structured-output/index.d.ts.map +1 -0
- package/dist/structured-output/index.js +6 -0
- package/dist/structured-output/schemas/evidence.d.ts +88 -0
- package/dist/structured-output/schemas/evidence.d.ts.map +1 -0
- package/dist/structured-output/schemas/evidence.js +65 -0
- package/dist/structured-output/types.d.ts +69 -0
- package/dist/structured-output/types.d.ts.map +1 -0
- package/dist/structured-output/types.js +0 -0
- package/dist/tools/action-tools.d.ts +63 -0
- package/dist/tools/action-tools.d.ts.map +1 -0
- package/dist/tools/action-tools.js +158 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +30 -0
- package/dist/utils/i18n.d.ts +1 -1
- package/dist/utils/i18n.d.ts.map +1 -1
- package/dist/utils/i18n.js +1 -1
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +36 -0
- package/dist/utils/text.d.ts +11 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +87 -0
- package/dist/utils/url.d.ts +19 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/url.js +13 -0
- package/package.json +46 -12
- package/dist/intelligence/intent-detect.d.ts +0 -40
- package/dist/intelligence/intent-detect.d.ts.map +0 -1
- package/dist/intelligence/intent-detect.js +0 -93
- package/dist/providers/index.d.ts +0 -2
- package/dist/providers/index.d.ts.map +0 -1
- package/dist/providers/index.js +0 -5
- package/dist/search/search-utils.d.ts +0 -47
- package/dist/search/search-utils.d.ts.map +0 -1
- package/dist/stream/index.d.ts +0 -3
- package/dist/stream/index.d.ts.map +0 -1
- package/dist/stream/index.js +0 -8
- package/dist/stream/mock-stream.d.ts +0 -12
- package/dist/stream/mock-stream.d.ts.map +0 -1
- package/dist/stream/mock-stream.js +0 -26
- package/dist/stream/response.d.ts +0 -10
- package/dist/stream/response.d.ts.map +0 -1
- package/dist/stream/response.js +0 -21
|
@@ -1,139 +1,97 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createUIMessageStream,
|
|
3
|
-
createUIMessageStreamResponse
|
|
4
|
-
streamText,
|
|
5
|
-
convertToModelMessages
|
|
3
|
+
createUIMessageStreamResponse
|
|
6
4
|
} from "ai";
|
|
7
5
|
import { t, getLang } from "../utils/i18n.js";
|
|
8
6
|
import {
|
|
9
|
-
getClientIP,
|
|
10
|
-
checkRateLimit,
|
|
11
|
-
rateLimitResponse,
|
|
12
|
-
searchArticles,
|
|
13
|
-
searchProjects,
|
|
14
|
-
getSessionCacheKey,
|
|
15
|
-
getCachedContext,
|
|
16
|
-
setCachedContext,
|
|
17
|
-
shouldReuseSearchContext,
|
|
18
7
|
buildLocalSearchQuery,
|
|
19
8
|
shouldRunKeywordExtraction,
|
|
20
9
|
extractSearchKeywords,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
getVoiceProfile,
|
|
10
|
+
rankArticlesByCategory,
|
|
11
|
+
applyBudgetToArticles,
|
|
12
|
+
shouldAppendCitations,
|
|
13
|
+
selectCitations,
|
|
14
|
+
formatCitationBlock,
|
|
15
|
+
resolveSearchInterpretation
|
|
16
|
+
} from "../intelligence/index.js";
|
|
17
|
+
import {
|
|
30
18
|
mergeResults,
|
|
31
|
-
|
|
19
|
+
searchArticles,
|
|
20
|
+
searchProjects,
|
|
21
|
+
getSessionCacheKey,
|
|
22
|
+
getCachedContext,
|
|
23
|
+
setCachedContext
|
|
24
|
+
} from "../search/index.js";
|
|
25
|
+
import {
|
|
26
|
+
extractCodeAnchors,
|
|
27
|
+
hasCodeAnchors,
|
|
28
|
+
normalizeText,
|
|
29
|
+
tokenize
|
|
30
|
+
} from "../utils/text.js";
|
|
31
|
+
import { getProviderManager } from "../provider-manager/index.js";
|
|
32
|
+
import {
|
|
32
33
|
createCacheAdapter,
|
|
33
|
-
detectPublicQuestion,
|
|
34
34
|
getGlobalSearchCache,
|
|
35
|
-
shouldAppendCitations,
|
|
36
|
-
formatCitationBlock,
|
|
37
|
-
selectCitations,
|
|
38
35
|
setGlobalSearchCache,
|
|
39
36
|
getGlobalCacheTTL,
|
|
40
37
|
getResponseCache,
|
|
41
38
|
setResponseCache,
|
|
42
39
|
getResponseCacheConfig,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
detectPublicQuestion,
|
|
41
|
+
normalizePublicCacheQuery
|
|
42
|
+
} from "../cache/index.js";
|
|
43
|
+
import {
|
|
44
|
+
getClientIP,
|
|
45
|
+
checkRateLimit,
|
|
46
|
+
rateLimitResponse
|
|
47
|
+
} from "../middleware/index.js";
|
|
48
|
+
import {
|
|
49
|
+
getExtensionRegistry,
|
|
50
|
+
getSemanticFallback,
|
|
51
|
+
mergeSearchDocuments
|
|
52
|
+
} from "../extensions/index.js";
|
|
53
|
+
import { initializeExtensions, areExtensionsLoaded } from "./metadata-init.js";
|
|
47
54
|
import { createChatStatusData } from "./types.js";
|
|
48
|
-
import { errors, corsPreflightResponse } from "./errors.js";
|
|
49
|
-
import { notifyAiChat } from "./notify.js";
|
|
55
|
+
import { errors, corsPreflightResponse, setCorsOrigin } from "./errors.js";
|
|
50
56
|
import {
|
|
51
57
|
writeSearchStatus,
|
|
52
58
|
writeGeneratingStatus,
|
|
53
59
|
writeSourceArticles,
|
|
54
|
-
|
|
60
|
+
writeSourceSnippets,
|
|
61
|
+
streamAnswerWithFallback,
|
|
55
62
|
streamMockFallback,
|
|
56
|
-
streamCachedResponse
|
|
63
|
+
streamCachedResponse,
|
|
64
|
+
writeTextChunk,
|
|
65
|
+
writeFinish
|
|
57
66
|
} from "./stream-helpers.js";
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
usage,
|
|
76
|
-
timing
|
|
77
|
-
});
|
|
78
|
-
if (waitUntil) {
|
|
79
|
-
waitUntil(notifyPromise);
|
|
80
|
-
} else {
|
|
81
|
-
void notifyPromise;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
function getMessageText(message) {
|
|
85
|
-
if (Array.isArray(message.parts)) {
|
|
86
|
-
return message.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
87
|
-
}
|
|
88
|
-
return "";
|
|
89
|
-
}
|
|
90
|
-
function hasContent(message) {
|
|
91
|
-
const text = getMessageText(message);
|
|
92
|
-
if (text.trim()) return true;
|
|
93
|
-
if (Array.isArray(message.parts)) {
|
|
94
|
-
return message.parts.some((p) => p.type !== "text");
|
|
95
|
-
}
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
function filterValidMessages(messages) {
|
|
99
|
-
const filtered = [];
|
|
100
|
-
let lastRole = null;
|
|
101
|
-
for (const msg of messages) {
|
|
102
|
-
if (!hasContent(msg)) continue;
|
|
103
|
-
if (msg.role === lastRole) continue;
|
|
104
|
-
filtered.push(msg);
|
|
105
|
-
lastRole = msg.role;
|
|
106
|
-
}
|
|
107
|
-
if (filtered.length > 0 && filtered[filtered.length - 1].role !== "user") {
|
|
108
|
-
filtered.pop();
|
|
109
|
-
}
|
|
110
|
-
return filtered;
|
|
111
|
-
}
|
|
112
|
-
function buildArticleContextPrompt(context) {
|
|
113
|
-
if (context.scope !== "article" || !context.article) return "";
|
|
114
|
-
const a = context.article;
|
|
115
|
-
const parts = [
|
|
116
|
-
"\n[\u5F53\u524D\u9605\u8BFB\u6587\u7AE0]",
|
|
117
|
-
`\u7528\u6237\u6B63\u5728\u9605\u8BFB\uFF1A\u300A${a.title}\u300B`
|
|
118
|
-
];
|
|
119
|
-
if (a.summary) parts.push(`\u6458\u8981\uFF1A${a.summary}`);
|
|
120
|
-
if (a.abstract) parts.push(`\u8BE6\u7EC6\u6982\u8981\uFF1A${a.abstract}`);
|
|
121
|
-
if (a.keyPoints?.length) parts.push(`\u6838\u5FC3\u8981\u70B9\uFF1A${a.keyPoints.join("\uFF1B")}`);
|
|
122
|
-
if (a.categories?.length) parts.push(`\u5206\u7C7B\uFF1A${a.categories.join("\u3001")}`);
|
|
123
|
-
parts.push(
|
|
124
|
-
"",
|
|
125
|
-
"\u4F60\u6B63\u5728\u966A\u7528\u6237\u9605\u8BFB\u8FD9\u7BC7\u6587\u7AE0\u3002\u4F18\u5148\u56F4\u7ED5\u8FD9\u7BC7\u6587\u7AE0\u7684\u5185\u5BB9\u56DE\u7B54\u95EE\u9898\u3002",
|
|
126
|
-
"\u5F53\u7528\u6237\u7684\u95EE\u9898\u4E0E\u5F53\u524D\u6587\u7AE0\u76F8\u5173\u65F6\uFF0C\u5F15\u7528\u6587\u7AE0\u4E2D\u7684\u5177\u4F53\u5185\u5BB9\u3002",
|
|
127
|
-
"\u5F53\u7528\u6237\u60F3\u8981\u5EF6\u4F38\u65F6\uFF0C\u63A8\u8350\u76F8\u5173\u7684\u535A\u5BA2\u6587\u7AE0\u3002"
|
|
128
|
-
);
|
|
129
|
-
return parts.join("\n");
|
|
130
|
-
}
|
|
67
|
+
import {
|
|
68
|
+
getMessageText,
|
|
69
|
+
filterValidMessages,
|
|
70
|
+
getLatestUserText
|
|
71
|
+
} from "./chat-message-utils.js";
|
|
72
|
+
import {
|
|
73
|
+
sendNotification,
|
|
74
|
+
getTimeoutConfig,
|
|
75
|
+
getHealthConfig
|
|
76
|
+
} from "./chat-utils.js";
|
|
77
|
+
import {
|
|
78
|
+
assemblePromptRuntime
|
|
79
|
+
} from "./prompt-runtime.js";
|
|
80
|
+
import { CHAT_HANDLER, RESPONSE } from "../constants.js";
|
|
81
|
+
import { createLogger, setLogLevel } from "../utils/logger.js";
|
|
82
|
+
import { getAllTools } from "../tools/index.js";
|
|
83
|
+
const log = createLogger("chat-handler");
|
|
131
84
|
async function handleChatRequest(options) {
|
|
132
85
|
const { env, request: req, waitUntil } = options;
|
|
86
|
+
if (env.AI_DEBUG) setLogLevel("debug");
|
|
87
|
+
if (env.CORS_ORIGIN) setCorsOrigin(env.CORS_ORIGIN);
|
|
133
88
|
if (req.method === "OPTIONS") return corsPreflightResponse();
|
|
134
89
|
if (req.method !== "POST") return errors.methodNotAllowed("zh");
|
|
135
90
|
const ip = getClientIP(req);
|
|
136
|
-
const rateCheck = checkRateLimit(
|
|
91
|
+
const rateCheck = checkRateLimit(
|
|
92
|
+
ip,
|
|
93
|
+
env
|
|
94
|
+
);
|
|
137
95
|
if (!rateCheck.allowed) return rateLimitResponse(rateCheck, "zh");
|
|
138
96
|
let body;
|
|
139
97
|
try {
|
|
@@ -143,116 +101,349 @@ async function handleChatRequest(options) {
|
|
|
143
101
|
}
|
|
144
102
|
const lang = getLang(body.lang ?? env.SITE_LANG);
|
|
145
103
|
const context = body.context ?? { scope: "global" };
|
|
146
|
-
const rawMessages = (body.messages ?? []).slice(
|
|
104
|
+
const rawMessages = (body.messages ?? []).slice(
|
|
105
|
+
-CHAT_HANDLER.MAX_HISTORY_MESSAGES
|
|
106
|
+
);
|
|
147
107
|
if (!rawMessages.length) return errors.emptyMessage(lang);
|
|
148
108
|
const messages = filterValidMessages(rawMessages);
|
|
149
109
|
if (!messages.length) return errors.emptyMessage(lang);
|
|
150
110
|
const latestMessage = messages[messages.length - 1];
|
|
151
|
-
const latestText = getMessageText(latestMessage);
|
|
111
|
+
const latestText = latestMessage.role === "user" ? getMessageText(latestMessage) : getLatestUserText(messages);
|
|
152
112
|
if (!latestText) return errors.emptyContent(lang);
|
|
153
|
-
if (latestText.length >
|
|
113
|
+
if (latestText.length > CHAT_HANDLER.MAX_INPUT_LENGTH)
|
|
114
|
+
return errors.inputTooLong(CHAT_HANDLER.MAX_INPUT_LENGTH, lang);
|
|
115
|
+
const timeouts = getTimeoutConfig(env);
|
|
154
116
|
const requestAbort = new AbortController();
|
|
155
|
-
const requestTimer = setTimeout(() => requestAbort.abort(),
|
|
117
|
+
const requestTimer = setTimeout(() => requestAbort.abort(), timeouts.request);
|
|
118
|
+
log.debug(
|
|
119
|
+
`Request: scope=${context.scope}, msg="${latestText.substring(0, 80)}", history=${messages.length}`
|
|
120
|
+
);
|
|
156
121
|
try {
|
|
157
|
-
return await runPipeline({
|
|
122
|
+
return await runPipeline({
|
|
123
|
+
env,
|
|
124
|
+
messages,
|
|
125
|
+
latestText,
|
|
126
|
+
context,
|
|
127
|
+
req,
|
|
128
|
+
requestAbort,
|
|
129
|
+
lang,
|
|
130
|
+
waitUntil,
|
|
131
|
+
timeouts
|
|
132
|
+
});
|
|
158
133
|
} catch (err) {
|
|
159
134
|
if (requestAbort.signal.aborted) return errors.timeout(lang);
|
|
160
|
-
|
|
135
|
+
log.error("Unexpected error:", err);
|
|
161
136
|
return errors.internal(void 0, lang);
|
|
162
137
|
} finally {
|
|
163
138
|
clearTimeout(requestTimer);
|
|
164
139
|
}
|
|
165
140
|
}
|
|
166
|
-
async function
|
|
167
|
-
const { env, messages, latestText, context, req, lang,
|
|
141
|
+
async function initializeContext(args) {
|
|
142
|
+
const { env, messages, latestText, context, req, lang, timeouts } = args;
|
|
168
143
|
const timing = { start: Date.now() };
|
|
169
144
|
const cache = createCacheAdapter(env);
|
|
170
145
|
const responseCacheConfig = getResponseCacheConfig(env);
|
|
146
|
+
const healthConfig = getHealthConfig(env);
|
|
171
147
|
const manager = getProviderManager(env, {
|
|
172
148
|
enableMockFallback: true,
|
|
173
|
-
unhealthyThreshold:
|
|
174
|
-
healthRecoveryTTL:
|
|
149
|
+
unhealthyThreshold: healthConfig.unhealthyThreshold,
|
|
150
|
+
healthRecoveryTTL: healthConfig.recoveryTtl
|
|
175
151
|
});
|
|
176
152
|
const hasRealProvider = manager.hasProviders();
|
|
177
|
-
const
|
|
153
|
+
const adapters = hasRealProvider ? await manager.getAvailableAdapters() : [];
|
|
154
|
+
const adapter = adapters[0] ?? null;
|
|
155
|
+
if (!areExtensionsLoaded()) {
|
|
156
|
+
await initializeExtensions();
|
|
157
|
+
}
|
|
158
|
+
const extensions = getExtensionRegistry().getLoadedExtensions();
|
|
178
159
|
const articleSlug = context.scope === "article" && context.article?.slug ? context.article.slug : void 0;
|
|
179
160
|
const publicQuestion = detectPublicQuestion(latestText);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
161
|
+
const cacheKey = getSessionCacheKey(req);
|
|
162
|
+
return {
|
|
163
|
+
env,
|
|
164
|
+
messages,
|
|
165
|
+
latestText,
|
|
166
|
+
context,
|
|
167
|
+
lang,
|
|
168
|
+
timeouts,
|
|
169
|
+
timing,
|
|
170
|
+
cache,
|
|
171
|
+
responseCacheConfig,
|
|
172
|
+
adapters,
|
|
173
|
+
adapter,
|
|
174
|
+
hasRealProvider,
|
|
175
|
+
extensions,
|
|
176
|
+
articleSlug,
|
|
177
|
+
publicQuestion,
|
|
178
|
+
cacheKey
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
async function analyzeAndBuildPrompt(ctx, search) {
|
|
182
|
+
return assemblePromptRuntime({
|
|
183
|
+
env: ctx.env,
|
|
184
|
+
latestText: ctx.latestText,
|
|
185
|
+
context: ctx.context,
|
|
186
|
+
lang: ctx.lang,
|
|
187
|
+
evidenceAnalysisTimeout: ctx.timeouts.evidenceAnalysis,
|
|
188
|
+
timing: {
|
|
189
|
+
total: Date.now() - ctx.timing.start,
|
|
190
|
+
keywordExtraction: ctx.timing.keywordExtraction,
|
|
191
|
+
search: ctx.timing.search,
|
|
192
|
+
evidenceAnalysis: ctx.timing.evidenceAnalysis,
|
|
193
|
+
generation: ctx.timing.generation
|
|
194
|
+
},
|
|
195
|
+
adapter: ctx.adapter,
|
|
196
|
+
hasRealProvider: ctx.hasRealProvider,
|
|
197
|
+
extensions: ctx.extensions,
|
|
198
|
+
cacheKey: ctx.cacheKey,
|
|
199
|
+
searchQuery: search.searchQuery,
|
|
200
|
+
relatedArticles: search.relatedArticles,
|
|
201
|
+
relatedProjects: search.relatedProjects,
|
|
202
|
+
budget: search.budget,
|
|
203
|
+
answerMode: search.interpretation.answer.contract
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function resolveSearchAnswerShaping(query) {
|
|
207
|
+
const { interpretation, budget } = resolveSearchInterpretation({
|
|
208
|
+
latestText: query
|
|
209
|
+
});
|
|
210
|
+
return { interpretation, budget };
|
|
211
|
+
}
|
|
212
|
+
function rankArticlesForQuery(query, articles) {
|
|
213
|
+
const { interpretation } = resolveSearchInterpretation({ latestText: query });
|
|
214
|
+
return rankArticlesByCategory(interpretation.topic.primary, articles);
|
|
215
|
+
}
|
|
216
|
+
const MIN_QUOTED_QUERY_LENGTH = 12;
|
|
217
|
+
const CURRENT_ARTICLE_SCORE_BOOST_RATIO = 0.12;
|
|
218
|
+
const CURRENT_ARTICLE_SCORE_CAP_RATIO = 1.08;
|
|
219
|
+
const CROSS_ARTICLE_INTENT_PATTERNS = [
|
|
220
|
+
/还有哪些/u,
|
|
221
|
+
/相关文章/u,
|
|
222
|
+
/类似/u,
|
|
223
|
+
/推荐/u,
|
|
224
|
+
/对比/u,
|
|
225
|
+
/比较/u,
|
|
226
|
+
/related/u,
|
|
227
|
+
/similar/u,
|
|
228
|
+
/compare/u,
|
|
229
|
+
/comparison/u,
|
|
230
|
+
/recommend/u,
|
|
231
|
+
/what else/u
|
|
232
|
+
];
|
|
233
|
+
function extractQuotedCandidate(text) {
|
|
234
|
+
const trimmed = text.trim();
|
|
235
|
+
if (!trimmed) return "";
|
|
236
|
+
const matches = [...trimmed.matchAll(/["“”'‘’「」『』《》](.+?)["“”'‘’「」『』《》]/g)].map((match) => match[1]?.trim() ?? "").filter(Boolean).sort((a, b) => b.length - a.length);
|
|
237
|
+
return matches[0] ?? "";
|
|
238
|
+
}
|
|
239
|
+
function isLikelyQuotedArticleQuery(text) {
|
|
240
|
+
const quoted = extractQuotedCandidate(text);
|
|
241
|
+
if (!quoted) return false;
|
|
242
|
+
return normalizeText(quoted).length >= MIN_QUOTED_QUERY_LENGTH;
|
|
243
|
+
}
|
|
244
|
+
function isCrossArticleIntent(text) {
|
|
245
|
+
if (isLikelyQuotedArticleQuery(text)) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
const normalized = normalizeText(text);
|
|
249
|
+
if (!normalized) return false;
|
|
250
|
+
return CROSS_ARTICLE_INTENT_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
251
|
+
}
|
|
252
|
+
function isCurrentArticle(article, articleSlug) {
|
|
253
|
+
return article.id === articleSlug || article.url?.includes(articleSlug);
|
|
254
|
+
}
|
|
255
|
+
function buildCurrentArticleFallback(context, articleSlug, tailScore) {
|
|
256
|
+
if (context?.scope !== "article" || !context.article) return null;
|
|
257
|
+
return {
|
|
258
|
+
id: articleSlug,
|
|
259
|
+
title: context.article.title,
|
|
260
|
+
url: `/posts/${articleSlug}`,
|
|
261
|
+
summary: context.article.summary ?? context.article.abstract,
|
|
262
|
+
keyPoints: context.article.keyPoints ?? [],
|
|
263
|
+
categories: context.article.categories ?? [],
|
|
264
|
+
dateTime: 0,
|
|
265
|
+
score: tailScore
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function rerankArticlesForCurrentArticleQuote(query, articles, options = {}) {
|
|
269
|
+
const { articleSlug, context } = options;
|
|
270
|
+
if (!articleSlug || context?.scope !== "article") return articles;
|
|
271
|
+
if (!isLikelyQuotedArticleQuery(query)) return articles;
|
|
272
|
+
if (isCrossArticleIntent(query)) return articles;
|
|
273
|
+
const cloned = [...articles];
|
|
274
|
+
const currentIndex = cloned.findIndex(
|
|
275
|
+
(article) => isCurrentArticle(article, articleSlug)
|
|
276
|
+
);
|
|
277
|
+
if (currentIndex >= 0) {
|
|
278
|
+
const topScore = cloned[0]?.score ?? 0;
|
|
279
|
+
if (topScore <= 0) return cloned;
|
|
280
|
+
const current = cloned[currentIndex];
|
|
281
|
+
const boostedScore = Math.min(
|
|
282
|
+
(current.score ?? 0) + topScore * CURRENT_ARTICLE_SCORE_BOOST_RATIO,
|
|
283
|
+
topScore * CURRENT_ARTICLE_SCORE_CAP_RATIO
|
|
284
|
+
);
|
|
285
|
+
cloned[currentIndex] = { ...current, score: boostedScore };
|
|
286
|
+
return cloned.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
287
|
+
}
|
|
288
|
+
const tailScore = Math.max((cloned[cloned.length - 1]?.score ?? 1) * 0.95, 0.01);
|
|
289
|
+
const fallback = buildCurrentArticleFallback(context, articleSlug, tailScore);
|
|
290
|
+
if (!fallback) return cloned;
|
|
291
|
+
return [...cloned, fallback];
|
|
292
|
+
}
|
|
293
|
+
function shapeArticlesForQuery(query, articles, options = {}) {
|
|
294
|
+
const { interpretation, budget } = resolveSearchInterpretation({
|
|
295
|
+
latestText: query
|
|
296
|
+
});
|
|
297
|
+
const boostedArticles = rerankArticlesForCurrentArticleQuote(
|
|
298
|
+
query,
|
|
299
|
+
articles,
|
|
300
|
+
options
|
|
301
|
+
);
|
|
302
|
+
const rankedArticles = hasCodeAnchors(query) ? rerankArticlesForCodeAnchors(query, boostedArticles) : rankArticlesByCategory(interpretation.topic.primary, boostedArticles);
|
|
303
|
+
return {
|
|
304
|
+
interpretation,
|
|
305
|
+
budget,
|
|
306
|
+
articles: applyBudgetToArticles(rankedArticles, budget)
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function rerankArticlesForCodeAnchors(query, articles) {
|
|
310
|
+
const rawAnchors = extractCodeAnchors(query);
|
|
311
|
+
if (rawAnchors.length === 0 || articles.length <= 1) return articles;
|
|
312
|
+
return [...articles].sort((a, b) => {
|
|
313
|
+
const aAnchorScore = scoreArticleForCodeAnchors(a, rawAnchors);
|
|
314
|
+
const bAnchorScore = scoreArticleForCodeAnchors(b, rawAnchors);
|
|
315
|
+
return bAnchorScore - aAnchorScore || (b.score ?? 0) - (a.score ?? 0) || a.title.localeCompare(b.title);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function scoreArticleForCodeAnchors(article, rawAnchors) {
|
|
319
|
+
const title = article.title;
|
|
320
|
+
const summary = article.summary ?? "";
|
|
321
|
+
const keyPoints = article.keyPoints ?? [];
|
|
322
|
+
const chunks = article.chunks ?? [];
|
|
323
|
+
let score = 0;
|
|
324
|
+
for (const anchor of rawAnchors) {
|
|
325
|
+
const normalizedAnchor = normalizeText(anchor);
|
|
326
|
+
if (!normalizedAnchor) continue;
|
|
327
|
+
if (title.includes(anchor)) {
|
|
328
|
+
score += 10;
|
|
329
|
+
continue;
|
|
198
330
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
response: llmResult.responseText,
|
|
229
|
-
articles: cachedSearch.articles,
|
|
230
|
-
projects: cachedSearch.projects,
|
|
231
|
-
lang,
|
|
232
|
-
model: adapter.model,
|
|
233
|
-
updatedAt: Date.now()
|
|
234
|
-
};
|
|
235
|
-
await setResponseCache(cache, publicQuestion.type, responseCacheData, globalTTL, globalCacheContext);
|
|
236
|
-
}
|
|
237
|
-
} else {
|
|
238
|
-
responseText = await streamMockFallback(w, latestText, lang);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
return createUIMessageStreamResponse({ stream: stream2, headers: { "Access-Control-Allow-Origin": "*", "Cache-Control": "no-cache" } });
|
|
331
|
+
if (summary.includes(anchor)) {
|
|
332
|
+
score += 6;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (keyPoints.some((point) => point.includes(anchor))) {
|
|
336
|
+
score += 5;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (chunks.some((chunk) => chunk.heading.includes(anchor))) {
|
|
340
|
+
score += 8;
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (chunks.some((chunk) => chunk.content.includes(anchor))) {
|
|
344
|
+
score += 9;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const normalizedTitle = normalizeText(title);
|
|
348
|
+
const normalizedSummary = normalizeText(summary);
|
|
349
|
+
const normalizedKeyPoints = keyPoints.map((point) => normalizeText(point));
|
|
350
|
+
if (normalizedTitle.includes(normalizedAnchor)) {
|
|
351
|
+
score += 6;
|
|
352
|
+
} else if (normalizedSummary.includes(normalizedAnchor)) {
|
|
353
|
+
score += 4;
|
|
354
|
+
} else if (normalizedKeyPoints.some((point) => point.includes(normalizedAnchor))) {
|
|
355
|
+
score += 3;
|
|
356
|
+
} else if (chunks.some(
|
|
357
|
+
(chunk) => normalizeText(chunk.heading).includes(normalizedAnchor) || normalizeText(chunk.content).includes(normalizedAnchor)
|
|
358
|
+
)) {
|
|
359
|
+
score += 5;
|
|
243
360
|
}
|
|
244
361
|
}
|
|
245
|
-
|
|
362
|
+
return score;
|
|
363
|
+
}
|
|
364
|
+
function shouldPersistAuthoritativeSources(sources) {
|
|
365
|
+
return sources.length > 0 && sources.every(
|
|
366
|
+
(source) => source.reason === "chunk" || source.reason === "evidence" || source.reason === "article-context"
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
function shapeCachedSearchForQuery(args) {
|
|
370
|
+
const shaped = shapeArticlesForQuery(args.query, args.articles);
|
|
371
|
+
return {
|
|
372
|
+
interpretation: shaped.interpretation,
|
|
373
|
+
budget: shaped.budget,
|
|
374
|
+
articles: shaped.articles,
|
|
375
|
+
projects: args.projects
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function shouldPersistResponseCacheEntry(args) {
|
|
379
|
+
return args.enabled && args.success && args.responseText.length > 0 && shouldPersistAuthoritativeSources(args.sources);
|
|
380
|
+
}
|
|
381
|
+
function shouldUsePublicQuestionCaches(args) {
|
|
382
|
+
return Boolean(args.publicQuestion && (!args.publicQuestion.needsContext || args.articleSlug));
|
|
383
|
+
}
|
|
384
|
+
function buildPublicCacheContext(args) {
|
|
385
|
+
return {
|
|
386
|
+
articleSlug: args.articleSlug,
|
|
387
|
+
lang: args.lang,
|
|
388
|
+
queryKey: normalizePublicCacheQuery(args.latestText)
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function shapePublicCacheBranch(args) {
|
|
392
|
+
return {
|
|
393
|
+
enabled: shouldUsePublicQuestionCaches({
|
|
394
|
+
publicQuestion: args.publicQuestion,
|
|
395
|
+
articleSlug: args.articleSlug
|
|
396
|
+
}),
|
|
397
|
+
context: buildPublicCacheContext({
|
|
398
|
+
articleSlug: args.articleSlug,
|
|
399
|
+
lang: args.lang,
|
|
400
|
+
latestText: args.latestText
|
|
401
|
+
})
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
async function retrieveContext(ctx, req) {
|
|
405
|
+
const {
|
|
406
|
+
messages,
|
|
407
|
+
latestText,
|
|
408
|
+
cache,
|
|
409
|
+
timeouts,
|
|
410
|
+
timing,
|
|
411
|
+
hasRealProvider,
|
|
412
|
+
adapter,
|
|
413
|
+
extensions,
|
|
414
|
+
publicQuestion,
|
|
415
|
+
articleSlug,
|
|
416
|
+
cacheKey,
|
|
417
|
+
lang
|
|
418
|
+
} = ctx;
|
|
246
419
|
const now = Date.now();
|
|
247
420
|
const cachedContext = cacheKey ? await getCachedContext(cacheKey, cache) : void 0;
|
|
248
|
-
const userTurnCount = messages.filter(
|
|
249
|
-
|
|
421
|
+
const userTurnCount = messages.filter(
|
|
422
|
+
(m) => m.role === "user"
|
|
423
|
+
).length;
|
|
424
|
+
const initialSearchInterpretation = resolveSearchInterpretation({
|
|
425
|
+
latestText,
|
|
426
|
+
cachedContext,
|
|
427
|
+
userTurnCount,
|
|
428
|
+
now
|
|
429
|
+
});
|
|
430
|
+
const reuseContext = initialSearchInterpretation.interpretation.conversation.shouldReuseContext;
|
|
250
431
|
let searchQuery = buildLocalSearchQuery(latestText) || latestText;
|
|
251
432
|
let relatedArticles = reuseContext && cachedContext ? cachedContext.articles : [];
|
|
252
433
|
let relatedProjects = reuseContext && cachedContext ? cachedContext.projects : [];
|
|
434
|
+
let { budget } = initialSearchInterpretation;
|
|
435
|
+
let { interpretation } = initialSearchInterpretation;
|
|
436
|
+
const semanticFallback = getSemanticFallback(latestText, extensions);
|
|
437
|
+
if (semanticFallback) {
|
|
438
|
+
searchQuery = semanticFallback.query;
|
|
439
|
+
}
|
|
253
440
|
if (reuseContext && cachedContext && cacheKey) {
|
|
254
441
|
searchQuery = cachedContext.query;
|
|
255
|
-
await setCachedContext(
|
|
442
|
+
await setCachedContext(
|
|
443
|
+
cacheKey,
|
|
444
|
+
{ ...cachedContext, updatedAt: now },
|
|
445
|
+
cache
|
|
446
|
+
);
|
|
256
447
|
} else {
|
|
257
448
|
if (hasRealProvider && adapter) {
|
|
258
449
|
const runKW = shouldRunKeywordExtraction({
|
|
@@ -263,7 +454,10 @@ async function runPipeline(args) {
|
|
|
263
454
|
if (runKW) {
|
|
264
455
|
const kwStart = Date.now();
|
|
265
456
|
const abortCtrl = new AbortController();
|
|
266
|
-
const timeoutId = setTimeout(
|
|
457
|
+
const timeoutId = setTimeout(
|
|
458
|
+
() => abortCtrl.abort(),
|
|
459
|
+
timeouts.keywordExtraction
|
|
460
|
+
);
|
|
267
461
|
try {
|
|
268
462
|
const provider = adapter.getProvider();
|
|
269
463
|
const kwResult = await extractSearchKeywords({
|
|
@@ -277,7 +471,9 @@ async function runPipeline(args) {
|
|
|
277
471
|
searchQuery = kwResult.query;
|
|
278
472
|
if (kwResult.primaryQuery && kwResult.primaryQuery !== searchQuery) {
|
|
279
473
|
const searchStart = Date.now();
|
|
280
|
-
const primary = searchArticles(kwResult.primaryQuery, {
|
|
474
|
+
const primary = searchArticles(kwResult.primaryQuery, {
|
|
475
|
+
enableDeepContent: false
|
|
476
|
+
});
|
|
281
477
|
relatedArticles = mergeResults(
|
|
282
478
|
searchArticles(searchQuery, { enableDeepContent: true }),
|
|
283
479
|
primary
|
|
@@ -286,8 +482,12 @@ async function runPipeline(args) {
|
|
|
286
482
|
timing.search = Date.now() - searchStart;
|
|
287
483
|
}
|
|
288
484
|
}
|
|
289
|
-
} catch {
|
|
485
|
+
} catch (err) {
|
|
290
486
|
timing.keywordExtraction = Date.now() - kwStart;
|
|
487
|
+
console.debug(
|
|
488
|
+
"[chat-handler] Keyword extraction failed, using local query:",
|
|
489
|
+
err.message
|
|
490
|
+
);
|
|
291
491
|
} finally {
|
|
292
492
|
clearTimeout(timeoutId);
|
|
293
493
|
}
|
|
@@ -295,20 +495,31 @@ async function runPipeline(args) {
|
|
|
295
495
|
}
|
|
296
496
|
if (!relatedArticles.length) {
|
|
297
497
|
const searchStart = Date.now();
|
|
298
|
-
relatedArticles = searchArticles(searchQuery, {
|
|
498
|
+
relatedArticles = searchArticles(searchQuery, {
|
|
499
|
+
enableDeepContent: true
|
|
500
|
+
});
|
|
299
501
|
relatedProjects = searchProjects(searchQuery);
|
|
300
502
|
timing.search = Date.now() - searchStart;
|
|
301
503
|
}
|
|
302
|
-
relatedArticles = rankArticlesByIntent(latestText, relatedArticles);
|
|
303
504
|
if (cacheKey) {
|
|
304
|
-
await setCachedContext(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
505
|
+
await setCachedContext(
|
|
506
|
+
cacheKey,
|
|
507
|
+
{
|
|
508
|
+
query: searchQuery,
|
|
509
|
+
articles: relatedArticles,
|
|
510
|
+
projects: relatedProjects,
|
|
511
|
+
updatedAt: now
|
|
512
|
+
},
|
|
513
|
+
cache
|
|
514
|
+
);
|
|
310
515
|
}
|
|
311
|
-
|
|
516
|
+
const publicCacheBranch = shapePublicCacheBranch({
|
|
517
|
+
publicQuestion,
|
|
518
|
+
articleSlug,
|
|
519
|
+
lang,
|
|
520
|
+
latestText
|
|
521
|
+
});
|
|
522
|
+
if (publicCacheBranch.enabled && publicQuestion) {
|
|
312
523
|
const globalTTL = getGlobalCacheTTL(publicQuestion.type);
|
|
313
524
|
await setGlobalSearchCache(
|
|
314
525
|
cache,
|
|
@@ -320,67 +531,223 @@ async function runPipeline(args) {
|
|
|
320
531
|
updatedAt: now
|
|
321
532
|
},
|
|
322
533
|
globalTTL,
|
|
323
|
-
|
|
534
|
+
publicCacheBranch.context
|
|
324
535
|
);
|
|
325
536
|
}
|
|
326
537
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
538
|
+
relatedArticles = mergeSearchDocuments(relatedArticles, extensions);
|
|
539
|
+
({ interpretation, budget, articles: relatedArticles } = shapeArticlesForQuery(
|
|
540
|
+
latestText,
|
|
541
|
+
relatedArticles,
|
|
542
|
+
{ articleSlug, context: ctx.context }
|
|
543
|
+
));
|
|
544
|
+
log.debug(
|
|
545
|
+
`Search: query="${searchQuery}", articles=${relatedArticles.length}, projects=${relatedProjects.length}, mode=${interpretation.answer.contract}`
|
|
546
|
+
);
|
|
547
|
+
if (relatedArticles.length > 0) {
|
|
548
|
+
log.debug(
|
|
549
|
+
`Top articles: ${relatedArticles.slice(0, 3).map(
|
|
550
|
+
(a) => `"${a.title}" (chunks: ${a.chunks?.length ?? 0})`
|
|
551
|
+
).join(", ")}`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
return { searchQuery, relatedArticles, relatedProjects, budget, interpretation };
|
|
555
|
+
}
|
|
556
|
+
async function runPipeline(args) {
|
|
557
|
+
const ctx = await initializeContext(args);
|
|
558
|
+
const {
|
|
559
|
+
env,
|
|
560
|
+
messages,
|
|
561
|
+
latestText,
|
|
562
|
+
context,
|
|
563
|
+
lang,
|
|
564
|
+
timeouts,
|
|
565
|
+
timing,
|
|
566
|
+
cache,
|
|
567
|
+
responseCacheConfig,
|
|
568
|
+
adapters,
|
|
569
|
+
adapter,
|
|
570
|
+
hasRealProvider,
|
|
571
|
+
extensions,
|
|
572
|
+
articleSlug,
|
|
573
|
+
publicQuestion,
|
|
574
|
+
cacheKey
|
|
575
|
+
} = ctx;
|
|
576
|
+
const { waitUntil } = args;
|
|
577
|
+
const tools = getAllTools();
|
|
578
|
+
const publicCacheBranch = shapePublicCacheBranch({
|
|
579
|
+
publicQuestion,
|
|
580
|
+
articleSlug,
|
|
581
|
+
lang,
|
|
582
|
+
latestText
|
|
583
|
+
});
|
|
584
|
+
if (publicCacheBranch.enabled && publicQuestion) {
|
|
585
|
+
const globalCacheContext = publicCacheBranch.context;
|
|
586
|
+
if (responseCacheConfig.enabled) {
|
|
587
|
+
const cachedResponse = await getResponseCache(
|
|
588
|
+
cache,
|
|
589
|
+
publicQuestion.type,
|
|
590
|
+
globalCacheContext
|
|
591
|
+
);
|
|
592
|
+
if (cachedResponse) {
|
|
593
|
+
const notifyTiming = { total: Date.now() - timing.start };
|
|
594
|
+
sendNotification({
|
|
595
|
+
env,
|
|
596
|
+
messages,
|
|
597
|
+
responseText: cachedResponse.response,
|
|
598
|
+
relatedArticles: cachedResponse.articles,
|
|
599
|
+
timing: notifyTiming,
|
|
600
|
+
waitUntil
|
|
601
|
+
});
|
|
602
|
+
const stream2 = createUIMessageStream({
|
|
603
|
+
execute: async ({ writer }) => {
|
|
604
|
+
await streamCachedResponse(
|
|
605
|
+
writer,
|
|
606
|
+
cachedResponse,
|
|
607
|
+
responseCacheConfig,
|
|
608
|
+
lang
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
return createUIMessageStreamResponse({
|
|
613
|
+
stream: stream2,
|
|
614
|
+
headers: {
|
|
615
|
+
"Access-Control-Allow-Origin": "*",
|
|
616
|
+
"Cache-Control": "no-cache"
|
|
617
|
+
}
|
|
343
618
|
});
|
|
344
|
-
if (evidenceResult.analysis) {
|
|
345
|
-
evidenceSection = buildEvidenceSection(evidenceResult.analysis);
|
|
346
|
-
}
|
|
347
|
-
timing.evidenceAnalysis = Date.now() - evidenceStart;
|
|
348
|
-
} catch {
|
|
349
|
-
timing.evidenceAnalysis = Date.now() - evidenceStart;
|
|
350
|
-
} finally {
|
|
351
|
-
clearTimeout(timeoutId);
|
|
352
619
|
}
|
|
353
620
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
621
|
+
const cachedSearch = await getGlobalSearchCache(
|
|
622
|
+
cache,
|
|
623
|
+
publicQuestion.type,
|
|
624
|
+
globalCacheContext
|
|
625
|
+
);
|
|
626
|
+
if (cachedSearch) {
|
|
627
|
+
const stream2 = createUIMessageStream({
|
|
628
|
+
execute: async ({ writer }) => {
|
|
629
|
+
const w = writer;
|
|
630
|
+
writeSearchStatus(
|
|
631
|
+
w,
|
|
632
|
+
cachedSearch.articles.length + cachedSearch.projects.length,
|
|
633
|
+
lang
|
|
634
|
+
);
|
|
635
|
+
if (cachedSearch.articles.length + cachedSearch.projects.length > 0) {
|
|
636
|
+
writeGeneratingStatus(w, lang);
|
|
637
|
+
}
|
|
638
|
+
let responseText = "";
|
|
639
|
+
if (adapter) {
|
|
640
|
+
const {
|
|
641
|
+
interpretation: cachedInterpretation,
|
|
642
|
+
budget: cachedBudget,
|
|
643
|
+
articles: shapedCachedArticles,
|
|
644
|
+
projects: shapedCachedProjects
|
|
645
|
+
} = shapeCachedSearchForQuery({
|
|
646
|
+
query: latestText,
|
|
647
|
+
articles: cachedSearch.articles,
|
|
648
|
+
projects: cachedSearch.projects
|
|
649
|
+
});
|
|
650
|
+
const promptRuntime = await assemblePromptRuntime({
|
|
651
|
+
env,
|
|
652
|
+
latestText,
|
|
653
|
+
context,
|
|
654
|
+
lang,
|
|
655
|
+
evidenceAnalysisTimeout: timeouts.evidenceAnalysis,
|
|
656
|
+
timing: {
|
|
657
|
+
total: Date.now() - timing.start,
|
|
658
|
+
keywordExtraction: timing.keywordExtraction,
|
|
659
|
+
search: timing.search,
|
|
660
|
+
evidenceAnalysis: timing.evidenceAnalysis,
|
|
661
|
+
generation: timing.generation
|
|
662
|
+
},
|
|
663
|
+
adapter,
|
|
664
|
+
hasRealProvider,
|
|
665
|
+
extensions,
|
|
666
|
+
cacheKey,
|
|
667
|
+
searchQuery: cachedSearch.query,
|
|
668
|
+
relatedArticles: shapedCachedArticles,
|
|
669
|
+
relatedProjects: shapedCachedProjects,
|
|
670
|
+
budget: cachedBudget,
|
|
671
|
+
answerMode: cachedInterpretation.answer.contract
|
|
672
|
+
});
|
|
673
|
+
const finalSources2 = buildFinalSources({
|
|
674
|
+
relatedArticles: shapedCachedArticles,
|
|
675
|
+
selectedSources: promptRuntime.selectedSources,
|
|
676
|
+
query: latestText,
|
|
677
|
+
lang,
|
|
678
|
+
max: RESPONSE.MAX_SOURCE_ARTICLES,
|
|
679
|
+
articleSlug,
|
|
680
|
+
context
|
|
681
|
+
});
|
|
682
|
+
writeSourceArticles(w, finalSources2);
|
|
683
|
+
writeSourceSnippets(w, finalSources2);
|
|
684
|
+
const llmResult = await streamAnswerWithFallback({
|
|
685
|
+
writer: w,
|
|
686
|
+
adapters: [adapter],
|
|
687
|
+
systemPrompt: promptRuntime.systemPrompt,
|
|
688
|
+
messages,
|
|
689
|
+
question: latestText,
|
|
690
|
+
lang,
|
|
691
|
+
tools
|
|
692
|
+
});
|
|
693
|
+
responseText = llmResult.responseText;
|
|
694
|
+
if (shouldPersistResponseCacheEntry({
|
|
695
|
+
enabled: responseCacheConfig.enabled,
|
|
696
|
+
success: llmResult.success,
|
|
697
|
+
responseText: llmResult.responseText,
|
|
698
|
+
sources: promptRuntime.selectedSources
|
|
699
|
+
})) {
|
|
700
|
+
const globalTTL = getGlobalCacheTTL(publicQuestion.type);
|
|
701
|
+
const responseCacheData = {
|
|
702
|
+
query: cachedSearch.query,
|
|
703
|
+
thinking: llmResult.reasoningText,
|
|
704
|
+
response: llmResult.responseText,
|
|
705
|
+
articles: shapedCachedArticles,
|
|
706
|
+
projects: shapedCachedProjects,
|
|
707
|
+
sources: promptRuntime.selectedSources,
|
|
708
|
+
lang,
|
|
709
|
+
model: adapter.model,
|
|
710
|
+
updatedAt: Date.now()
|
|
711
|
+
};
|
|
712
|
+
await setResponseCache(
|
|
713
|
+
cache,
|
|
714
|
+
publicQuestion.type,
|
|
715
|
+
responseCacheData,
|
|
716
|
+
globalTTL,
|
|
717
|
+
globalCacheContext
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
} else {
|
|
721
|
+
responseText = await streamMockFallback(w, latestText, lang);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
return createUIMessageStreamResponse({
|
|
726
|
+
stream: stream2,
|
|
727
|
+
headers: {
|
|
728
|
+
"Access-Control-Allow-Origin": "*",
|
|
729
|
+
"Cache-Control": "no-cache"
|
|
730
|
+
}
|
|
731
|
+
});
|
|
382
732
|
}
|
|
733
|
+
}
|
|
734
|
+
const search = await retrieveContext(ctx, args.req);
|
|
735
|
+
const { searchQuery, relatedArticles, relatedProjects, interpretation } = search;
|
|
736
|
+
const { systemPrompt, preflight, unknownRefusal, selectedSources } = await analyzeAndBuildPrompt(ctx, search);
|
|
737
|
+
const finalSources = buildFinalSources({
|
|
738
|
+
relatedArticles,
|
|
739
|
+
selectedSources,
|
|
740
|
+
query: latestText,
|
|
741
|
+
lang,
|
|
742
|
+
max: RESPONSE.MAX_SOURCE_ARTICLES,
|
|
743
|
+
articleSlug,
|
|
744
|
+
context
|
|
383
745
|
});
|
|
746
|
+
log.debug(
|
|
747
|
+
`Final sources: ${finalSources.map(
|
|
748
|
+
(source) => `${source.title}[${source.reason}]${source.lang ? `:${source.lang}` : ""}`
|
|
749
|
+
).join(", ")}`
|
|
750
|
+
);
|
|
384
751
|
const stream = createUIMessageStream({
|
|
385
752
|
execute: async ({ writer }) => {
|
|
386
753
|
const articleCount = relatedArticles.length + relatedProjects.length;
|
|
@@ -390,36 +757,38 @@ ${articlePrompt}` : evidenceSection,
|
|
|
390
757
|
messageMetadata: createChatStatusData({
|
|
391
758
|
stage: "search",
|
|
392
759
|
message: t("ai.status.found", lang, { count: articleCount }),
|
|
393
|
-
progress:
|
|
760
|
+
progress: RESPONSE.SEARCH_PROGRESS
|
|
394
761
|
})
|
|
395
762
|
});
|
|
396
763
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
writer.write({
|
|
400
|
-
type: "source-url",
|
|
401
|
-
sourceId: `source-${article.title}`,
|
|
402
|
-
url: article.url ?? "#",
|
|
403
|
-
title: article.title
|
|
404
|
-
});
|
|
405
|
-
} catch {
|
|
406
|
-
}
|
|
407
|
-
}
|
|
764
|
+
writeSourceArticles(writer, finalSources);
|
|
765
|
+
writeSourceSnippets(writer, finalSources);
|
|
408
766
|
if (preflight) {
|
|
409
767
|
writer.write({
|
|
410
768
|
type: "message-metadata",
|
|
411
769
|
messageMetadata: createChatStatusData({
|
|
412
770
|
stage: "answer",
|
|
413
771
|
message: t("ai.status.citation", lang),
|
|
414
|
-
progress:
|
|
772
|
+
progress: RESPONSE.COMPLETE_PROGRESS,
|
|
773
|
+
done: true
|
|
774
|
+
})
|
|
775
|
+
});
|
|
776
|
+
writeTextChunk(writer, preflight.text, "preflight");
|
|
777
|
+
writeFinish(writer);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
if (unknownRefusal) {
|
|
781
|
+
writer.write({
|
|
782
|
+
type: "message-metadata",
|
|
783
|
+
messageMetadata: createChatStatusData({
|
|
784
|
+
stage: "answer",
|
|
785
|
+
message: t("ai.status.generating", lang),
|
|
786
|
+
progress: RESPONSE.COMPLETE_PROGRESS,
|
|
415
787
|
done: true
|
|
416
788
|
})
|
|
417
789
|
});
|
|
418
|
-
|
|
419
|
-
writer
|
|
420
|
-
writer.write({ type: "text-delta", id: partId, delta: preflight.text });
|
|
421
|
-
writer.write({ type: "text-end", id: partId });
|
|
422
|
-
writer.write({ type: "finish", finishReason: "stop" });
|
|
790
|
+
writeTextChunk(writer, unknownRefusal.text, "unknown");
|
|
791
|
+
writeFinish(writer);
|
|
423
792
|
return;
|
|
424
793
|
}
|
|
425
794
|
writer.write({
|
|
@@ -427,141 +796,80 @@ ${articlePrompt}` : evidenceSection,
|
|
|
427
796
|
messageMetadata: createChatStatusData({
|
|
428
797
|
stage: "answer",
|
|
429
798
|
message: t("ai.status.generating", lang),
|
|
430
|
-
progress:
|
|
799
|
+
progress: RESPONSE.GENERATING_PROGRESS
|
|
431
800
|
})
|
|
432
801
|
});
|
|
433
|
-
let streamSuccess = false;
|
|
434
802
|
let responseText = "";
|
|
435
803
|
let reasoningText;
|
|
436
804
|
let tokenUsage;
|
|
437
805
|
const generationStart = Date.now();
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
try {
|
|
474
|
-
const usage = await Promise.resolve(usagePromise);
|
|
475
|
-
const inputTokens = usage.inputTokens ?? 0;
|
|
476
|
-
const outputTokens = usage.outputTokens ?? 0;
|
|
477
|
-
tokenUsage = {
|
|
478
|
-
total: usage.totalTokens ?? inputTokens + outputTokens,
|
|
479
|
-
input: inputTokens,
|
|
480
|
-
output: outputTokens
|
|
481
|
-
};
|
|
482
|
-
} catch {
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
timing.generation = Date.now() - generationStart;
|
|
486
|
-
responseText = text;
|
|
487
|
-
hasTextOutput = text.length > 0;
|
|
488
|
-
if (hasTextOutput && errors2.length === 0) {
|
|
489
|
-
adapter.recordSuccess();
|
|
490
|
-
if (shouldAppendCitations(responseText, relatedArticles, relatedProjects)) {
|
|
491
|
-
const citations = selectCitations(relatedArticles, relatedProjects, 3, 5);
|
|
492
|
-
if (citations.length > 0) {
|
|
493
|
-
const citationBlock = formatCitationBlock(citations, lang);
|
|
494
|
-
const citationId = `citation-${Date.now()}`;
|
|
495
|
-
writer.write({ type: "text-start", id: citationId });
|
|
496
|
-
writer.write({ type: "text-delta", id: citationId, delta: citationBlock });
|
|
497
|
-
writer.write({ type: "text-end", id: citationId });
|
|
498
|
-
responseText += citationBlock;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
writer.write({ type: "finish", finishReason: "stop" });
|
|
502
|
-
streamSuccess = true;
|
|
503
|
-
if (responseCacheConfig.enabled && publicQuestion && (!publicQuestion.needsContext || articleSlug)) {
|
|
504
|
-
const globalTTL = getGlobalCacheTTL(publicQuestion.type);
|
|
505
|
-
const responseCacheData = {
|
|
506
|
-
query: searchQuery,
|
|
507
|
-
thinking: reasoningText,
|
|
508
|
-
response: text,
|
|
509
|
-
articles: relatedArticles,
|
|
510
|
-
projects: relatedProjects,
|
|
511
|
-
lang,
|
|
512
|
-
model: adapter.model,
|
|
513
|
-
updatedAt: Date.now()
|
|
514
|
-
};
|
|
515
|
-
await setResponseCache(cache, publicQuestion.type, responseCacheData, globalTTL, { articleSlug, lang });
|
|
516
|
-
}
|
|
517
|
-
} else if (errors2.length > 0) {
|
|
518
|
-
adapter.recordFailure(errors2[0]);
|
|
519
|
-
console.error("[chat-handler] Stream error:", errors2[0].message);
|
|
520
|
-
const errorId = `error-${Date.now()}`;
|
|
521
|
-
writer.write({ type: "text-start", id: errorId });
|
|
522
|
-
writer.write({
|
|
523
|
-
type: "text-delta",
|
|
524
|
-
id: errorId,
|
|
525
|
-
delta: t("ai.error.generic", lang)
|
|
526
|
-
});
|
|
527
|
-
writer.write({ type: "text-end", id: errorId });
|
|
528
|
-
writer.write({ type: "finish", finishReason: "error" });
|
|
529
|
-
streamSuccess = true;
|
|
530
|
-
} else if (!hasTextOutput) {
|
|
531
|
-
const noOutputId = `no-output-${Date.now()}`;
|
|
532
|
-
writer.write({ type: "text-start", id: noOutputId });
|
|
533
|
-
writer.write({ type: "text-delta", id: noOutputId, delta: t("ai.error.noOutput", lang) });
|
|
534
|
-
writer.write({ type: "text-end", id: noOutputId });
|
|
535
|
-
writer.write({ type: "finish", finishReason: "stop" });
|
|
536
|
-
streamSuccess = true;
|
|
537
|
-
} else {
|
|
538
|
-
writer.write({ type: "finish", finishReason: "stop" });
|
|
539
|
-
streamSuccess = true;
|
|
806
|
+
const llmResult = await streamAnswerWithFallback({
|
|
807
|
+
writer,
|
|
808
|
+
adapters,
|
|
809
|
+
systemPrompt,
|
|
810
|
+
messages,
|
|
811
|
+
question: latestText,
|
|
812
|
+
lang,
|
|
813
|
+
temperature: CHAT_HANDLER.STREAMING_TEMPERATURE,
|
|
814
|
+
maxOutputTokens: CHAT_HANDLER.STREAMING_MAX_OUTPUT_TOKENS,
|
|
815
|
+
tools
|
|
816
|
+
});
|
|
817
|
+
timing.generation = llmResult.generationMs || Date.now() - generationStart;
|
|
818
|
+
responseText = llmResult.responseText;
|
|
819
|
+
reasoningText = llmResult.reasoningText;
|
|
820
|
+
tokenUsage = llmResult.tokenUsage ? {
|
|
821
|
+
total: llmResult.tokenUsage.total,
|
|
822
|
+
input: llmResult.tokenUsage.input,
|
|
823
|
+
output: llmResult.tokenUsage.output
|
|
824
|
+
} : void 0;
|
|
825
|
+
const usedAdapter = llmResult.adapter;
|
|
826
|
+
if (usedAdapter && llmResult.responseText.length > 0) {
|
|
827
|
+
log.debug(
|
|
828
|
+
`Provider success: adapter=${usedAdapter.id}, model=${usedAdapter.model}, responseLength=${responseText.length}, usage=${JSON.stringify(tokenUsage ?? null)}`
|
|
829
|
+
);
|
|
830
|
+
if (shouldAppendCitations(responseText, relatedArticles, relatedProjects)) {
|
|
831
|
+
const citations = selectCitations(
|
|
832
|
+
relatedArticles,
|
|
833
|
+
relatedProjects,
|
|
834
|
+
RESPONSE.MAX_SOURCE_ARTICLES,
|
|
835
|
+
RESPONSE.MAX_CITATIONS
|
|
836
|
+
);
|
|
837
|
+
if (citations.length > 0) {
|
|
838
|
+
const citationBlock = formatCitationBlock(citations, lang);
|
|
839
|
+
writeTextChunk(writer, citationBlock, "citation");
|
|
840
|
+
responseText += citationBlock;
|
|
540
841
|
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
842
|
+
}
|
|
843
|
+
if (publicCacheBranch.enabled && publicQuestion && shouldPersistResponseCacheEntry({
|
|
844
|
+
enabled: responseCacheConfig.enabled,
|
|
845
|
+
success: true,
|
|
846
|
+
responseText,
|
|
847
|
+
sources: selectedSources
|
|
848
|
+
})) {
|
|
849
|
+
const globalTTL = getGlobalCacheTTL(publicQuestion.type);
|
|
850
|
+
const responseCacheData = {
|
|
851
|
+
query: searchQuery,
|
|
852
|
+
thinking: reasoningText,
|
|
853
|
+
response: responseText,
|
|
854
|
+
articles: relatedArticles,
|
|
855
|
+
projects: relatedProjects,
|
|
856
|
+
sources: selectedSources,
|
|
857
|
+
lang,
|
|
858
|
+
model: usedAdapter.model,
|
|
859
|
+
updatedAt: Date.now()
|
|
860
|
+
};
|
|
861
|
+
await setResponseCache(
|
|
862
|
+
cache,
|
|
863
|
+
publicQuestion.type,
|
|
864
|
+
responseCacheData,
|
|
865
|
+
globalTTL,
|
|
866
|
+
publicCacheBranch.context
|
|
867
|
+
);
|
|
545
868
|
}
|
|
546
869
|
}
|
|
547
|
-
if (
|
|
548
|
-
const { getMockResponse } = await import("../providers/mock.js");
|
|
549
|
-
const mockText = getMockResponse(latestText, lang);
|
|
870
|
+
if (llmResult.usedMockFallback) {
|
|
550
871
|
timing.generation = Date.now() - generationStart;
|
|
551
|
-
responseText =
|
|
552
|
-
writer.write({
|
|
553
|
-
type: "message-metadata",
|
|
554
|
-
messageMetadata: createChatStatusData({
|
|
555
|
-
stage: "answer",
|
|
556
|
-
message: t("ai.status.fallback", lang),
|
|
557
|
-
progress: 80
|
|
558
|
-
})
|
|
559
|
-
});
|
|
560
|
-
const fallbackId = `fallback-${Date.now()}`;
|
|
561
|
-
writer.write({ type: "text-start", id: fallbackId });
|
|
562
|
-
writer.write({ type: "text-delta", id: fallbackId, delta: mockText });
|
|
563
|
-
writer.write({ type: "text-end", id: fallbackId });
|
|
564
|
-
writer.write({ type: "finish", finishReason: "stop" });
|
|
872
|
+
responseText = llmResult.responseText;
|
|
565
873
|
}
|
|
566
874
|
if (responseText) {
|
|
567
875
|
const notifyTiming = {
|
|
@@ -571,17 +879,16 @@ ${articlePrompt}` : evidenceSection,
|
|
|
571
879
|
evidenceAnalysis: timing.evidenceAnalysis,
|
|
572
880
|
generation: timing.generation
|
|
573
881
|
};
|
|
574
|
-
const notifyModel = adapter ? {
|
|
575
|
-
name: adapter.model,
|
|
576
|
-
provider: env.AI_PROVIDER || void 0,
|
|
577
|
-
apiHost: env.AI_BASE_URL || void 0
|
|
578
|
-
} : void 0;
|
|
579
882
|
sendNotification({
|
|
580
883
|
env,
|
|
581
884
|
messages,
|
|
582
885
|
responseText,
|
|
583
|
-
relatedArticles,
|
|
584
|
-
model:
|
|
886
|
+
relatedArticles: finalSources,
|
|
887
|
+
model: usedAdapter ? {
|
|
888
|
+
name: usedAdapter.model,
|
|
889
|
+
provider: env.AI_PROVIDER || void 0,
|
|
890
|
+
apiHost: env.AI_BASE_URL || void 0
|
|
891
|
+
} : void 0,
|
|
585
892
|
usage: tokenUsage,
|
|
586
893
|
timing: notifyTiming,
|
|
587
894
|
cacheKey,
|
|
@@ -598,6 +905,133 @@ ${articlePrompt}` : evidenceSection,
|
|
|
598
905
|
}
|
|
599
906
|
});
|
|
600
907
|
}
|
|
908
|
+
function buildFinalSources(args) {
|
|
909
|
+
const { relatedArticles, selectedSources, query, lang, max, articleSlug, context } = args;
|
|
910
|
+
const normalizedQuery = normalizeText(query);
|
|
911
|
+
const fallbackSources = relatedArticles.map((article) => ({
|
|
912
|
+
title: article.title,
|
|
913
|
+
url: article.url,
|
|
914
|
+
lang: article.lang,
|
|
915
|
+
reason: "retrieval-fallback",
|
|
916
|
+
score: article.score
|
|
917
|
+
}));
|
|
918
|
+
const preferredPool = selectedSources.length > 0 ? selectedSources : fallbackSources;
|
|
919
|
+
const isCurrentArticleSource = (source) => {
|
|
920
|
+
if (!articleSlug) return false;
|
|
921
|
+
const url = source.url ?? "";
|
|
922
|
+
if (url.includes(`/posts/${articleSlug}`) || url.includes(articleSlug)) {
|
|
923
|
+
return true;
|
|
924
|
+
}
|
|
925
|
+
if (context?.article?.title && source.title === context.article.title) {
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
return false;
|
|
929
|
+
};
|
|
930
|
+
const shouldPrioritizeCurrentArticle = !!articleSlug && context?.scope === "article" && isLikelyQuotedArticleQuery(query) && !isCrossArticleIntent(query);
|
|
931
|
+
if (shouldPrioritizeCurrentArticle) {
|
|
932
|
+
const orderedCurrent = preferredPool.filter(isCurrentArticleSource);
|
|
933
|
+
const orderedOther = preferredPool.filter((source) => !isCurrentArticleSource(source));
|
|
934
|
+
const currentChunkSources = orderedCurrent.filter((source) => source.reason === "chunk");
|
|
935
|
+
const currentNonChunkSources = orderedCurrent.filter((source) => source.reason !== "chunk");
|
|
936
|
+
const dedupedPrioritized = [];
|
|
937
|
+
const seenPrioritized = /* @__PURE__ */ new Set();
|
|
938
|
+
for (const source of [
|
|
939
|
+
...currentChunkSources,
|
|
940
|
+
...currentNonChunkSources,
|
|
941
|
+
...orderedOther
|
|
942
|
+
]) {
|
|
943
|
+
const key = `${source.title}::${source.url ?? ""}::${source.chunkId ?? ""}`;
|
|
944
|
+
if (seenPrioritized.has(key)) continue;
|
|
945
|
+
seenPrioritized.add(key);
|
|
946
|
+
dedupedPrioritized.push(source);
|
|
947
|
+
if (dedupedPrioritized.length >= max) break;
|
|
948
|
+
}
|
|
949
|
+
return dedupedPrioritized;
|
|
950
|
+
}
|
|
951
|
+
const sameLang = preferredPool.filter((source) => source.lang === lang);
|
|
952
|
+
const anchorTerms = tokenize(query).filter((token) => token.length >= 2).sort((a, b) => b.length - a.length).slice(0, 3);
|
|
953
|
+
const sourceMatchesAnchor = (source) => {
|
|
954
|
+
if (!anchorTerms.length) return true;
|
|
955
|
+
const title = normalizeText(source.title);
|
|
956
|
+
return anchorTerms.some((term) => title.includes(term));
|
|
957
|
+
};
|
|
958
|
+
const sameLangAnchored = sameLang.filter(sourceMatchesAnchor);
|
|
959
|
+
const crossLangAnchored = preferredPool.filter(
|
|
960
|
+
(source) => source.lang !== lang && sourceMatchesAnchor(source)
|
|
961
|
+
);
|
|
962
|
+
const articleSummaryQuery = isArticleSummaryQuery(normalizedQuery);
|
|
963
|
+
const rankByTitleCloseness = (sources) => {
|
|
964
|
+
return [...sources].sort((a, b) => {
|
|
965
|
+
const aScore = computeTitleCloseness(normalizedQuery, a.title);
|
|
966
|
+
const bScore = computeTitleCloseness(normalizedQuery, b.title);
|
|
967
|
+
return bScore - aScore || (b.score ?? 0) - (a.score ?? 0);
|
|
968
|
+
});
|
|
969
|
+
};
|
|
970
|
+
const orderedSameLang = rankByTitleCloseness(
|
|
971
|
+
sameLangAnchored.length > 0 ? sameLangAnchored : sameLang
|
|
972
|
+
);
|
|
973
|
+
const orderedCrossLang = rankByTitleCloseness(
|
|
974
|
+
crossLangAnchored.length > 0 ? crossLangAnchored : preferredPool.filter((source) => source.lang !== lang)
|
|
975
|
+
);
|
|
976
|
+
let ordered = articleSummaryQuery ? orderedSameLang.length >= max ? orderedSameLang : [...orderedSameLang, ...orderedCrossLang] : [...orderedSameLang, ...orderedCrossLang];
|
|
977
|
+
const deduped = [];
|
|
978
|
+
const seen = /* @__PURE__ */ new Set();
|
|
979
|
+
for (const source of ordered) {
|
|
980
|
+
const key = `${source.title}::${source.url ?? ""}`;
|
|
981
|
+
if (seen.has(key)) continue;
|
|
982
|
+
seen.add(key);
|
|
983
|
+
deduped.push(source);
|
|
984
|
+
if (deduped.length >= max) break;
|
|
985
|
+
}
|
|
986
|
+
return deduped;
|
|
987
|
+
}
|
|
988
|
+
function isArticleSummaryQuery(normalizedQuery) {
|
|
989
|
+
return [
|
|
990
|
+
"\u8FD9\u7BC7\u6587\u7AE0",
|
|
991
|
+
"\u4E3B\u8981\u8BB2\u4E86\u4EC0\u4E48",
|
|
992
|
+
"\u8BB2\u4E86\u4EC0\u4E48",
|
|
993
|
+
"\u6587\u7AE0\u4E3B\u8981",
|
|
994
|
+
"\u6587\u7AE0\u8BB2\u4E86\u4EC0\u4E48",
|
|
995
|
+
"article summary",
|
|
996
|
+
"what does this article",
|
|
997
|
+
"what is this article about"
|
|
998
|
+
].some((marker) => normalizedQuery.includes(marker));
|
|
999
|
+
}
|
|
1000
|
+
function computeTitleCloseness(normalizedQuery, title) {
|
|
1001
|
+
const normalizedTitle = normalizeText(title);
|
|
1002
|
+
const tokens = tokenize(normalizedQuery).filter((token) => token.length >= 2).sort((a, b) => b.length - a.length).slice(0, 5);
|
|
1003
|
+
let score = 0;
|
|
1004
|
+
for (const token of tokens) {
|
|
1005
|
+
if (normalizedTitle.includes(token)) {
|
|
1006
|
+
score += Math.max(1, Math.min(token.length, 6));
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (normalizedQuery.includes("\u6280\u672F\u67B6\u6784") && normalizedTitle.includes("\u6280\u672F\u67B6\u6784")) {
|
|
1010
|
+
score += 8;
|
|
1011
|
+
}
|
|
1012
|
+
if (normalizedQuery.includes("\u6A21\u5757") && normalizedTitle.includes("\u6A21\u5757")) {
|
|
1013
|
+
score += 4;
|
|
1014
|
+
}
|
|
1015
|
+
if (normalizedQuery.includes("\u6587\u7AE0") && normalizedTitle.includes("\u6587\u7AE0")) {
|
|
1016
|
+
score += 2;
|
|
1017
|
+
}
|
|
1018
|
+
return score;
|
|
1019
|
+
}
|
|
601
1020
|
export {
|
|
602
|
-
|
|
1021
|
+
buildFinalSources,
|
|
1022
|
+
buildPublicCacheContext,
|
|
1023
|
+
extractQuotedCandidate,
|
|
1024
|
+
handleChatRequest,
|
|
1025
|
+
isCrossArticleIntent,
|
|
1026
|
+
isLikelyQuotedArticleQuery,
|
|
1027
|
+
rankArticlesForQuery,
|
|
1028
|
+
rerankArticlesForCodeAnchors,
|
|
1029
|
+
rerankArticlesForCurrentArticleQuote,
|
|
1030
|
+
resolveSearchAnswerShaping,
|
|
1031
|
+
shapeArticlesForQuery,
|
|
1032
|
+
shapeCachedSearchForQuery,
|
|
1033
|
+
shapePublicCacheBranch,
|
|
1034
|
+
shouldPersistAuthoritativeSources,
|
|
1035
|
+
shouldPersistResponseCacheEntry,
|
|
1036
|
+
shouldUsePublicQuestionCaches
|
|
603
1037
|
};
|