227 lines
6.1 KiB
TypeScript
227 lines
6.1 KiB
TypeScript
/**
|
||
* NAC Knowledge Engine - AI智能体对话历史持久化
|
||
*
|
||
* MongoDB集合设计:
|
||
* agent_conversations: 会话元数据(id, userId, agentType, title, createdAt, updatedAt, messageCount)
|
||
* agent_messages: 消息记录(id, conversationId, role, content, confidence, sources, createdAt)
|
||
*
|
||
* 无Manus依赖,直接操作MongoDB
|
||
*/
|
||
|
||
import { ObjectId } from "mongodb";
|
||
import { getMongoDb, COLLECTIONS } from "./mongodb";
|
||
import type { AgentType } from "./aiAgents";
|
||
|
||
// ─── 类型定义 ─────────────────────────────────────────────────────
|
||
|
||
export interface AgentConversation {
|
||
_id?: ObjectId;
|
||
conversationId: string;
|
||
userId: number;
|
||
userEmail: string;
|
||
agentType: AgentType;
|
||
title: string; // 取自第一条用户消息的前50字
|
||
messageCount: number;
|
||
createdAt: Date;
|
||
updatedAt: Date;
|
||
}
|
||
|
||
export interface AgentMessageRecord {
|
||
_id?: ObjectId;
|
||
messageId: string;
|
||
conversationId: string;
|
||
role: "user" | "assistant";
|
||
content: string;
|
||
confidence?: number; // 仅assistant消息有
|
||
sources?: string[]; // 引用的知识库条目
|
||
suggestions?: string[]; // 后续建议
|
||
createdAt: Date;
|
||
}
|
||
|
||
// ─── 工具函数 ─────────────────────────────────────────────────────
|
||
|
||
function generateId(): string {
|
||
return new ObjectId().toHexString();
|
||
}
|
||
|
||
function extractTitle(message: string): string {
|
||
return message.slice(0, 50).replace(/\n/g, " ").trim() + (message.length > 50 ? "..." : "");
|
||
}
|
||
|
||
// ─── 会话管理 ─────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* 创建新会话
|
||
*/
|
||
export async function createConversation(
|
||
userId: number,
|
||
userEmail: string,
|
||
agentType: AgentType,
|
||
firstMessage: string
|
||
): Promise<string> {
|
||
const db = await getMongoDb();
|
||
if (!db) throw new Error("MongoDB不可用");
|
||
|
||
const conversationId = generateId();
|
||
const now = new Date();
|
||
|
||
const conversation: AgentConversation = {
|
||
conversationId,
|
||
userId,
|
||
userEmail,
|
||
agentType,
|
||
title: extractTitle(firstMessage),
|
||
messageCount: 0,
|
||
createdAt: now,
|
||
updatedAt: now,
|
||
};
|
||
|
||
await db.collection(COLLECTIONS.AGENT_CONVERSATIONS).insertOne(conversation);
|
||
return conversationId;
|
||
}
|
||
|
||
/**
|
||
* 获取用户的会话列表(按最近更新排序)
|
||
*/
|
||
export async function listConversations(
|
||
userId: number,
|
||
agentType?: AgentType,
|
||
limit = 20,
|
||
skip = 0
|
||
): Promise<{ conversations: AgentConversation[]; total: number }> {
|
||
const db = await getMongoDb();
|
||
if (!db) return { conversations: [], total: 0 };
|
||
|
||
const filter: Record<string, unknown> = { userId };
|
||
if (agentType) filter.agentType = agentType;
|
||
|
||
const [conversations, total] = await Promise.all([
|
||
db.collection(COLLECTIONS.AGENT_CONVERSATIONS)
|
||
.find(filter)
|
||
.sort({ updatedAt: -1 })
|
||
.skip(skip)
|
||
.limit(limit)
|
||
.toArray() as Promise<AgentConversation[]>,
|
||
db.collection(COLLECTIONS.AGENT_CONVERSATIONS).countDocuments(filter),
|
||
]);
|
||
|
||
return { conversations, total };
|
||
}
|
||
|
||
/**
|
||
* 获取单个会话信息
|
||
*/
|
||
export async function getConversation(
|
||
conversationId: string,
|
||
userId: number
|
||
): Promise<AgentConversation | null> {
|
||
const db = await getMongoDb();
|
||
if (!db) return null;
|
||
|
||
return db.collection(COLLECTIONS.AGENT_CONVERSATIONS)
|
||
.findOne({ conversationId, userId }) as Promise<AgentConversation | null>;
|
||
}
|
||
|
||
/**
|
||
* 删除会话及其所有消息
|
||
*/
|
||
export async function deleteConversation(
|
||
conversationId: string,
|
||
userId: number
|
||
): Promise<boolean> {
|
||
const db = await getMongoDb();
|
||
if (!db) return false;
|
||
|
||
const result = await db.collection(COLLECTIONS.AGENT_CONVERSATIONS)
|
||
.deleteOne({ conversationId, userId });
|
||
|
||
if (result.deletedCount > 0) {
|
||
await db.collection(COLLECTIONS.AGENT_MESSAGES)
|
||
.deleteMany({ conversationId });
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// ─── 消息管理 ─────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* 保存一对消息(用户消息 + AI回复)到数据库
|
||
*/
|
||
export async function saveMessagePair(
|
||
conversationId: string,
|
||
userMessage: string,
|
||
assistantMessage: string,
|
||
confidence: number,
|
||
sources?: string[],
|
||
suggestions?: string[]
|
||
): Promise<void> {
|
||
const db = await getMongoDb();
|
||
if (!db) return;
|
||
|
||
const now = new Date();
|
||
const userRecord: AgentMessageRecord = {
|
||
messageId: generateId(),
|
||
conversationId,
|
||
role: "user",
|
||
content: userMessage,
|
||
createdAt: now,
|
||
};
|
||
|
||
const assistantRecord: AgentMessageRecord = {
|
||
messageId: generateId(),
|
||
conversationId,
|
||
role: "assistant",
|
||
content: assistantMessage,
|
||
confidence,
|
||
sources,
|
||
suggestions,
|
||
createdAt: new Date(now.getTime() + 1), // 确保顺序
|
||
};
|
||
|
||
await db.collection(COLLECTIONS.AGENT_MESSAGES).insertMany([userRecord, assistantRecord]);
|
||
|
||
// 更新会话的消息计数和最后更新时间
|
||
await db.collection(COLLECTIONS.AGENT_CONVERSATIONS).updateOne(
|
||
{ conversationId },
|
||
{
|
||
$inc: { messageCount: 2 },
|
||
$set: { updatedAt: new Date() },
|
||
}
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 加载会话的消息历史(用于续接对话)
|
||
*/
|
||
export async function loadConversationMessages(
|
||
conversationId: string,
|
||
userId: number,
|
||
limit = 20
|
||
): Promise<AgentMessageRecord[]> {
|
||
const db = await getMongoDb();
|
||
if (!db) return [];
|
||
|
||
// 先验证会话属于该用户
|
||
const conv = await getConversation(conversationId, userId);
|
||
if (!conv) return [];
|
||
|
||
return db.collection(COLLECTIONS.AGENT_MESSAGES)
|
||
.find({ conversationId })
|
||
.sort({ createdAt: 1 })
|
||
.limit(limit)
|
||
.toArray() as Promise<AgentMessageRecord[]>;
|
||
}
|
||
|
||
/**
|
||
* 将消息历史转换为Agent可用的格式
|
||
*/
|
||
export function messagesToAgentHistory(
|
||
messages: AgentMessageRecord[]
|
||
): Array<{ role: "user" | "assistant"; content: string }> {
|
||
return messages.map(m => ({
|
||
role: m.role,
|
||
content: m.content,
|
||
}));
|
||
}
|