feat: 知识批量沉淀改为后台任务,避免 nginx 504
- /ingest/threads/batch 新增 background 参数(默认 true),立即返回 {queued:true}
- 抽取循环抽出 _run_batch_ingest,后台路径显式传入 created_by
- 前端知识库页与输入框、线程 hooks/types 配套调整
- config.yaml 上下文压缩测试值
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b380575f16
commit
342fa8265a
12 files changed
+170
-22
No files matched your search
@@ -10,6 +10,7 @@ import {
|
||||
LightbulbIcon,
|
||||
PaperclipIcon,
|
||||
PlusIcon,
|
||||
ShrinkIcon,
|
||||
SparklesIcon,
|
||||
RocketIcon,
|
||||
XIcon,
|
||||
@@ -329,6 +330,14 @@ export function InputBox({
|
||||
setLocalSettings("display", { ragModeEnabled: !ragModeEnabled });
|
||||
}, [ragModeEnabled, ragModeLocked, setLocalSettings]);
|
||||
|
||||
const contextCompressionEnabled =
|
||||
localSettings.display.contextCompressionEnabled ?? false;
|
||||
const handleContextCompressionToggle = useCallback(() => {
|
||||
setLocalSettings("display", {
|
||||
contextCompressionEnabled: !contextCompressionEnabled,
|
||||
});
|
||||
}, [contextCompressionEnabled, setLocalSettings]);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (message: PromptInputMessage) => {
|
||||
if (status === "streaming") {
|
||||
@@ -735,6 +744,35 @@ export function InputBox({
|
||||
<span className="text-xs font-normal">参考文献</span>
|
||||
</PromptInputButton>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={
|
||||
contextCompressionEnabled
|
||||
? "上下文压缩已开启:超长对话会自动压缩历史以节省 token"
|
||||
: "上下文压缩已关闭:点击开启"
|
||||
}
|
||||
>
|
||||
<PromptInputButton
|
||||
type="button"
|
||||
aria-pressed={contextCompressionEnabled}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"gap-1.5! rounded-full px-3! shadow-none",
|
||||
compact && "rounded-lg px-2.5! py-1! text-[11px] font-medium",
|
||||
contextCompressionEnabled
|
||||
? "border border-violet-300 bg-violet-50 text-violet-700 hover:bg-violet-100 dark:border-violet-700 dark:bg-violet-950/40 dark:text-violet-200"
|
||||
: compact
|
||||
? "border border-transparent bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||
: "border-0 bg-transparent text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100",
|
||||
)}
|
||||
onClick={handleContextCompressionToggle}
|
||||
>
|
||||
{contextCompressionEnabled && (
|
||||
<CheckIcon className="size-3 text-violet-700 dark:text-violet-200" />
|
||||
)}
|
||||
<ShrinkIcon className="size-3" />
|
||||
<span className="text-xs font-normal">上下文压缩</span>
|
||||
</PromptInputButton>
|
||||
</Tooltip>
|
||||
<PromptInputActionMenu>
|
||||
<ModeHoverGuide
|
||||
mode={
|
||||
|
||||
@@ -147,6 +147,13 @@ export interface BatchIngestPayload {
|
||||
include_sources?: boolean;
|
||||
skip_existing?: boolean;
|
||||
mode?: "summary" | "full";
|
||||
/**
|
||||
* When true (default), the backend runs the per-thread extraction loop in a
|
||||
* background task and returns immediately (`queued:true`) instead of blocking
|
||||
* the request until every thread is processed — which otherwise outlives the
|
||||
* nginx gateway timeout and surfaces as a generic "request failed".
|
||||
*/
|
||||
background?: boolean;
|
||||
/** Optional model override for knowledge extraction (null = configured default). */
|
||||
model_name?: string | null;
|
||||
/** Optional extraction-prompt template id (null = scope default). */
|
||||
@@ -157,6 +164,8 @@ export interface BatchIngestPayload {
|
||||
|
||||
export interface BatchIngestResult {
|
||||
ok: boolean;
|
||||
/** True when the backend queued the batch as a background task (see payload). */
|
||||
queued?: boolean;
|
||||
processed: number;
|
||||
created: number;
|
||||
skipped: number;
|
||||
|
||||
@@ -7,6 +7,7 @@ export const DEFAULT_LOCAL_SETTINGS: LocalSettings = {
|
||||
display: {
|
||||
streamSpeedMeterEnabled: true,
|
||||
ragModeEnabled: false,
|
||||
contextCompressionEnabled: false,
|
||||
citationCleanupWords: [],
|
||||
fontScale: 1,
|
||||
},
|
||||
@@ -43,6 +44,12 @@ export interface LocalSettings {
|
||||
* messages render tool results with the legacy generic search UI only.
|
||||
*/
|
||||
ragModeEnabled: boolean;
|
||||
/**
|
||||
* Per-user switch for context compression (上下文压缩). When enabled, long
|
||||
* conversations are summarized transiently before the model call to save
|
||||
* tokens. Default off; toggled from the chat input box (below 参考文献).
|
||||
*/
|
||||
contextCompressionEnabled: boolean;
|
||||
/**
|
||||
* Admin-maintained words or short phrases removed from reference snippets
|
||||
* and dialogs, one entry per line in the settings UI.
|
||||
|
||||
@@ -664,6 +664,9 @@ export function useThreadStream({
|
||||
lockThreadRagMode(threadId);
|
||||
}
|
||||
|
||||
const contextCompressionEnabled =
|
||||
localSettings.display.contextCompressionEnabled ?? false;
|
||||
|
||||
const mergedContext = {
|
||||
...context,
|
||||
...extraContext,
|
||||
@@ -715,6 +718,7 @@ export function useThreadStream({
|
||||
memory_injection_enabled:
|
||||
mergedContext.memory_injection_enabled ?? true,
|
||||
rag_mode_enabled: ragModeEnabled,
|
||||
summarization_enabled: contextCompressionEnabled,
|
||||
thread_id: threadId,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -20,6 +20,8 @@ export interface AgentThreadContext extends Record<string, unknown> {
|
||||
writing_artifact_path?: string;
|
||||
memory_injection_enabled?: boolean;
|
||||
rag_mode_enabled?: boolean;
|
||||
/** Per-request switch for context compression (上下文压缩). Default off. */
|
||||
summarization_enabled?: boolean;
|
||||
/** Per-conversation switch for knowledge-base RAG injection (phase 2). */
|
||||
knowledge_rag_enabled?: boolean;
|
||||
agent_id?: string;
|
||||
|
||||
@@ -176,10 +176,21 @@ function BatchImportDialog({
|
||||
skip_existing: skipExisting,
|
||||
status: "approved",
|
||||
include_sources: true,
|
||||
// Run in the background so the request returns immediately — a large
|
||||
// batch otherwise blocks past the gateway timeout and shows "request
|
||||
// failed". Threads stream into the list as they finish.
|
||||
background: true,
|
||||
model_name: model,
|
||||
template_id: template,
|
||||
folder,
|
||||
});
|
||||
if (result.queued) {
|
||||
toast.success(
|
||||
`已转入后台导入 ${result.processed} 个线程,完成后会陆续出现在左侧列表(可稍后刷新查看)`,
|
||||
);
|
||||
onOpenChange(false);
|
||||
return;
|
||||
}
|
||||
const base = `批量导入完成:新增 ${result.created},跳过 ${result.skipped},失败 ${result.failed}`;
|
||||
if (result.llm_fallback && result.llm_fallback > 0) {
|
||||
// The model failed on some threads — they were saved via rule-based
|
||||
@@ -549,7 +560,7 @@ export default function KnowledgeBasePage() {
|
||||
keyword: debouncedKeyword || undefined,
|
||||
source_type: sourceType === "all" ? undefined : sourceType,
|
||||
status: status === "all" ? undefined : status,
|
||||
limit: 500,
|
||||
limit: 1000,
|
||||
}),
|
||||
[debouncedKeyword, sourceType, status],
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user