Files
chatpapers/src/modules/llm/history.ts
T

77 lines
2.4 KiB
TypeScript

import type { ChatMessage } from "../llm/types";
/** Detect messages that already embed full paper dumps (should not be in UI history). */
export function isPaperDump(content: string): boolean {
return (
content.includes("【论文正文】") ||
content.includes("【论文 1】") ||
content.startsWith("【论文元数据】") ||
content.startsWith("已收到论文内容") ||
content.includes("【说明】以上为本次会话的论文材料")
);
}
/**
* Keep short Q&A turns for the model. Truncate very long assistant replies
* so paper context + latest question are less likely to be dropped.
*/
export function sanitizeChatHistory(
history: ChatMessage[],
options?: { maxMessages?: number; maxAssistantChars?: number },
): ChatMessage[] {
const maxMessages = options?.maxMessages ?? 10;
const maxAssistantChars = options?.maxAssistantChars ?? 5000;
const cleaned = history
.filter((m) => m.role === "user" || m.role === "assistant")
.filter((m) => !isPaperDump(m.content))
.map((m) => {
if (
m.role === "assistant" &&
m.content &&
m.content.length > maxAssistantChars
) {
return {
...m,
content:
m.content.slice(0, maxAssistantChars) +
"\n\n…(为保留论文上下文,历史回复已截断)",
};
}
return m;
});
return cleaned.slice(-maxMessages);
}
/**
* Pin paper corpus near the start of the request, then prior Q&A, then the
* current task. Avoids stuffing full papers only into the latest message
* (which providers often truncate under context pressure on turn 2+).
*/
export function assemblePaperChatMessages(options: {
system: string;
paperBlock: string;
taskBlock: string;
history: ChatMessage[];
}): ChatMessage[] {
const messages: ChatMessage[] = [
{ role: "system", content: options.system },
{
role: "user",
content: `${options.paperBlock}
【说明】以上为本次会话的论文材料。后续所有问题与分析任务均基于这些论文;无需用户再次提供全文或文本。`,
},
{
role: "assistant",
content:
"已收到并理解以上论文材料。请继续提问或下达分析任务,我将基于这些论文作答,不会要求你再次提供全文。",
},
];
messages.push(...sanitizeChatHistory(options.history));
messages.push({ role: "user", content: options.taskBlock });
return messages;
}