NAC_Blockchain/ops/nac-admin/server/agentConversations.ts

227 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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,
}));
}