tans-agents-ui / lib /compactor.ts
Đào Minh Tú
fix(tools+models): repair UTF-8 mojibake in tool descriptions; trim model list to verified-working set; filter non-chat models from Google live discovery
671888a
Raw
History Blame Contribute Delete
3.03 kB
import { generateText, type LanguageModel } from "ai"
import { countTokens } from "@/lib/tokens"
export type CompactableMessage = {
role: string
content: unknown
}
const CONTEXT_LIMITS: Record<string, number> = {
"gpt-4o": 128_000,
"gpt-4o-mini": 128_000,
"gemini-2.5-flash": 1_000_000,
"gemini-2.5-flash-lite": 1_000_000,
"gemma-4-26b-a4b-it": 32_000,
"gemma-4-31b-it": 32_000,
"llama-3.1-8b-instant": 131_000,
"llama-3.3-70b-versatile": 131_000,
"openai/gpt-oss-120b": 131_000,
"openai/gpt-oss-20b": 131_000,
"qwen/qwen3-32b": 131_000,
"meta-llama/llama-4-scout-17b-16e-instruct": 131_000,
default: 128_000,
}
const COMPACTION_PROMPT =
"Tóm tắt cuộc trò chuyện sau thành 1 system note ngắn gọn (bullet points). Giữ tên, fact quan trọng, decisions:"
function contentToText(content: unknown): string {
if (typeof content === "string") return content
if (Array.isArray(content)) {
return content
.map((part) => {
if (typeof part === "string") return part
if (part && typeof part === "object" && "text" in part) return String(part.text ?? "")
return ""
})
.filter(Boolean)
.join(" ")
}
if (content == null) return ""
try {
return JSON.stringify(content)
} catch {
return String(content)
}
}
function serializeMessages(messages: CompactableMessage[]) {
return messages
.map((message) => `${message.role}: ${contentToText(message.content)}`.trim())
.join("\n\n")
}
export function getContextLimit(modelId: string) {
return CONTEXT_LIMITS[modelId] ?? CONTEXT_LIMITS.default
}
export function countConversationTokens(messages: CompactableMessage[]) {
return countTokens(serializeMessages(messages))
}
export async function compactMessagesIfNeeded({
messages,
modelId,
compactModel,
}: {
messages: CompactableMessage[]
modelId: string
compactModel?: LanguageModel
}): Promise<{ messages: CompactableMessage[]; compacted: boolean; totalTokens: number; limit: number }> {
const safeMessages = Array.isArray(messages) ? messages : []
const limit = getContextLimit(modelId)
const totalTokens = countConversationTokens(safeMessages)
if (!compactModel || totalTokens <= limit * 0.8 || safeMessages.length <= 4) {
return { messages: safeMessages, compacted: false, totalTokens, limit }
}
const olderMessages = safeMessages.slice(0, -4)
const recentMessages = safeMessages.slice(-4)
const transcript = serializeMessages(olderMessages)
if (!transcript.trim()) return { messages: safeMessages, compacted: false, totalTokens, limit }
const { text } = await generateText({
model: compactModel,
prompt: `${COMPACTION_PROMPT}\n\n${transcript}`,
maxTokens: 700,
})
const summary = text.trim()
if (!summary) return { messages: safeMessages, compacted: false, totalTokens, limit }
return {
messages: [{ role: "system", content: `Tóm tắt context cũ:\n${summary}` }, ...recentMessages],
compacted: true,
totalTokens,
limit,
}
}