You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
84 lines
2.8 KiB
84 lines
2.8 KiB
import { generateText } from "ai";
|
|
import { type LanguageModel } from "ai";
|
|
import { resolveModelForUser, resolveModelAny, toLanguageModel } from "#server/service/llm/model-resolver";
|
|
import { updateSession } from "./session";
|
|
import type { AgentRow } from "./agent";
|
|
import log4js from "logger";
|
|
|
|
const logger = log4js.getLogger("APP");
|
|
|
|
const FIRST_LINE_MAX_LENGTH = 50;
|
|
|
|
export type TitleStrategy = "llm" | "first-line" | "none";
|
|
|
|
export function truncateFirstLine(text: string): string {
|
|
const firstLine = text.split("\n")[0]?.trim() ?? "";
|
|
if (firstLine.length <= FIRST_LINE_MAX_LENGTH) return firstLine;
|
|
return firstLine.slice(0, FIRST_LINE_MAX_LENGTH) + "…";
|
|
}
|
|
|
|
export async function generateSessionTitle(params: {
|
|
sessionId: string;
|
|
agent: AgentRow;
|
|
firstUserMessage: string;
|
|
firstAssistantMessage: string;
|
|
userId: number | null;
|
|
}): Promise<void> {
|
|
const { sessionId, agent, firstUserMessage, firstAssistantMessage, userId } = params;
|
|
const strategy: TitleStrategy = (agent.titleStrategy as TitleStrategy) ?? "llm";
|
|
|
|
if (strategy === "none") {
|
|
return;
|
|
}
|
|
|
|
if (strategy === "first-line") {
|
|
const title = truncateFirstLine(firstUserMessage);
|
|
if (title) {
|
|
await updateSession(sessionId, { title });
|
|
logger.info("[AGENT-TITLE] first-line sessionId=%s title=%s", sessionId, title);
|
|
}
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const titleModelId = agent.titleModelId ?? agent.defaultModelId;
|
|
if (!titleModelId) {
|
|
logger.warn("[AGENT-TITLE] no titleModelId/defaultModelId for agent=%s, fallback to first-line", agent.slug);
|
|
const title = truncateFirstLine(firstUserMessage);
|
|
if (title) await updateSession(sessionId, { title });
|
|
return;
|
|
}
|
|
|
|
const resolved = await resolveModelForUser(titleModelId, userId);
|
|
if (!resolved) {
|
|
logger.warn("[AGENT-TITLE] titleModelId=%d not resolvable, fallback to first-line", titleModelId);
|
|
const title = truncateFirstLine(firstUserMessage);
|
|
if (title) await updateSession(sessionId, { title });
|
|
return;
|
|
}
|
|
|
|
const languageModel: LanguageModel = toLanguageModel(resolved);
|
|
|
|
const prompt = `请根据以下对话生成一个简短的标题(不超过20个字,不要加引号、不要加书名号):
|
|
|
|
用户: ${firstUserMessage.slice(0, 500)}
|
|
助手: ${firstAssistantMessage.slice(0, 500)}
|
|
|
|
标题:`;
|
|
|
|
const result = await generateText({
|
|
model: languageModel,
|
|
prompt,
|
|
maxOutputTokens: 200,
|
|
});
|
|
|
|
const title = result.text.trim().slice(0, 100);
|
|
|
|
if (title) {
|
|
await updateSession(sessionId, { title });
|
|
logger.info("[AGENT-TITLE] llm sessionId=%s title=%s", sessionId, title);
|
|
}
|
|
} catch (err) {
|
|
logger.error("[AGENT-TITLE] sessionId=%s error: %s", sessionId, err instanceof Error ? err.message : String(err));
|
|
}
|
|
}
|
|
|