最近在为一个前端项目搭建 LangChain 聊天后端时,踩了不少坑:如何优雅地与 MongoDB 建立连接、怎么让聊天上下文持久化、怎样配合向量检索做知识增强......本文结合实际代码,完整梳理从零到一的实现流程,帮助你快速搭建可复用的消息管理与聊天历史存储方案。
项目结构与准备工作

仓库核心目录如下,本文重点聚焦 mongodb 中的基础设施:
- index.ts 提供 MongoDB 连接与 CRUD 能力
- chatHistory.ts 封装 LangChain 聊天历史工具
- vector.ts 集成向量检索能力
依赖与环境变量
js
pnpm install
pnpm add mongodb @langchain/core @langchain/mongodb @langchain/ollama
在 .env 中配置以下变量(示例):
yaml
MONGODB_URL=mongodb://localhost:27017
MONGODB_DB_NAME=langchain_demo
MONGODB_DB_CHAT_COLLECTION_NAME=chat_message_history
MONGODB_DB_VECTOR_COLLECTION_NAME=vector_collection
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_EMBED_MODEL=nomic-embed-text:latest
1. MongoDB 单例工具:稳定的底座
LangChain 只是调用入口,稳定的数据库层才是关键。MongoDBUtil 定义在 index.ts:1-140,采用单例模式管理连接与集合。
js
const mongoUtil = MongoDBUtil.getInstance();
await mongoUtil.connect(); // 默认读取 .env
const users = await mongoUtil.findMany('users', { role: 'admin' });
特性亮点:
- connect 挂靠 .env,本地/生产零改动
- 提供 insertOne、findMany、updateOne、deleteMany 等常用方法
- isConnected 可快速探测连接状态,便于中间件或健康检查
2. 聊天历史管理:LangChain + MongoDB
chatHistory.ts 基于 MongoDBChatMessageHistory 封装了 MongoChatHistoryTool
初始化与索引自愈
工具会在首次使用时检查集合是否存在;如果 listIndexes 返回 NamespaceNotFound(代码 26),会自动跳过索引检查,防止冷启动报错。
js
const historyTool = new MongoChatHistoryTool({
collectionName: 'demo_chat_history',
sessionTTLSeconds: 60 * 60, // 可选:自动过期
});
sessionTTLSeconds会在updatedAt上创建 TTL 索引,实现会话自动清理touchSession负责维护createdAt、updatedAt与消息数量,方便后台展示
完整的消息 API
js
await historyTool.appendSystemMessage(sessionId, '你是一个知识库助手');
await historyTool.appendHumanMessage(sessionId, '帮我解释 LangChain');
await historyTool.appendAIMessage(sessionId, 'LangChain 帮你连接 LLM 与外部数据源。');
const messages = await historyTool.getMessages(sessionId, { limit: 20 });
const transcript = await historyTool.getMessagesAsBuffer(sessionId, 'User', 'Assistant');
const sessions = await historyTool.listSessions({ limit: 10 });
await historyTool.deleteSession(sessionId);
常用场景总结:
appendMessages支持批量写入,可用于迁移历史记录getMessages默认返回时间顺序,可通过{ reverse: true }做反向分页listSessions自带搜索/分页/排序,易于接入前端会话列表
3. 向量检索:把聊天变成RAG
想让助手回答更贴合业务,需要把内部文档转成向量。MongodbVectorTool 位于 vector.ts,配合 LangChain 中的 MongoDBAtlasVectorSearch 实现 RAG。
初始化与索引
js
const vectorTool = new MongodbVectorTool({ collectionName: 'docs_vector' });
await vectorTool.initialize();
await MongodbVectorTool.initSearchIndex(vectorTool.getCollection());
initSearchIndex 会检查 vector_index 是否存在,自动创建 768 维余弦相似度索引。生产环境记得在 Atlas 中打开 Vector Search。
文档写入与检索
js
await vectorTool.addTexts(
['LangChain 让你把 LLM 接入数据库', 'MongoDB Atlas 原生支持向量检索'],
[{ source: 'blog' }, { source: 'docs' }]
);
const result = await vectorTool.similaritySearch('怎么把 LLM 连到数据库', 2);
如果你更喜欢自己准备 Document,可以使用静态 toDocuments 帮你打好 pageContent 和 metadata。
4. 构建一个可运行的 Demo
将以上工具结合起来,你可以在 index.ts 中快速跑通:
js
import MongoChatHistoryTool from './utils/mongodb/chatHistory.js';
import MongodbVectorTool from './utils/mongodb/vector.js';
import MongoDBUtil from './utils/mongodb/index.js';
async function bootstrap() {
const mongo = MongoDBUtil.getInstance();
await mongo.connect();
const sessionId = 'session-123';
const historyTool = new MongoChatHistoryTool();
await historyTool.appendSystemMessage(sessionId, '你是一位乐于助人的助手。');
await historyTool.appendHumanMessage(sessionId, '嗨,你能解释一下 LangChain 吗?');
await historyTool.appendAIMessage(sessionId, 'LangChain 能将语言模型与外部数据源相连接。');
console.log(await historyTool.getMessages(sessionId));
console.log(await historyTool.getMessagesAsBuffer(sessionId, 'User', '助手'));
const vectorTool = new MongodbVectorTool({});
await vectorTool.initialize();
await MongodbVectorTool.initSearchIndex(vectorTool.getCollection());
await vectorTool.addTexts(['LangChain 是一个用于构建具备情境感知功能的应用程序的框架。'], [{ tag: 'intro' }]);
console.log(await vectorTool.similaritySearch('什么是 LangChain?', 1));
await historyTool.deleteSession(sessionId);
await mongo.disconnect();
}
bootstrap().catch(console.error);
5.完整代码
index.ts
js
import { MongoClient, Db, Collection, ObjectId, UUID } from 'mongodb';
import type { DeleteOptions, DeleteResult, Document, Filter, OptionalUnlessRequiredId, WithId } from 'mongodb';
import 'dotenv/config';
import { Document as LangChainDocument } from '@langchain/core/documents';
/**
* MongoDB 工具类
* 提供基本的数据库连接和操作功能
*/
class MongoDBUtil {
private static instance: MongoDBUtil;
private client: MongoClient | null = null;
private db: Db | null = null;
private constructor() {}
/**
* 获取单例实例
* @returns MongoDBUtil 实例
*/
public static getInstance(): MongoDBUtil {
if (!MongoDBUtil.instance) {
MongoDBUtil.instance = new MongoDBUtil();
}
return MongoDBUtil.instance;
}
/**
* 连接到 MongoDB 数据库
* @param uri MongoDB 连接字符串
* @param dbName 数据库名称
* @returns Promise<void>
*/
public async connect(uri: string = process.env.MONGODB_URL || '', dbName: string = process.env.MONGODB_DB_NAME || ''): Promise<void> {
try {
this.client = new MongoClient(uri);
await this.client.connect();
this.db = this.client.db(dbName);
console.log('Connected to MongoDB');
} catch (error) {
console.error('Failed to connect to MongoDB:', error);
throw error;
}
}
/**
* 断开数据库连接
* @returns Promise<void>
*/
public async disconnect(): Promise<void> {
try {
if (this.client) {
await this.client.close();
this.client = null;
this.db = null;
console.log('Disconnected from MongoDB');
}
} catch (error) {
console.error('Failed to disconnect from MongoDB:', error);
throw error;
}
}
/**
* 获取集合引用
* @param collectionName 集合名称
* @returns Collection 对象
*/
public getCollection<T extends Document>(collectionName: string): Collection<T> {
if (!this.db) {
throw new Error('Database not connected. Please call connect() first.');
}
return this.db.collection<T>(collectionName);
}
/**
* @description: 将任意对象数组转换为 Document 数组
* @template T
* @param {T[]} items
* @param {(item: T, index: number) => Record<string, unknown>} metadataSelector
* @return {Document[]}
*/
static toDocuments<T extends Record<string, unknown>>(
items: T[],
metadataSelector?: (item: T, index: number) => Record<string, unknown>
): Document[] {
return items.map(
(item, index) =>
new LangChainDocument({
pageContent: JSON.stringify(item),
metadata: metadataSelector?.(item, index) ?? {},
id: new ObjectId().toString(),
})
);
}
/**
* 插入单个文档
* @param collectionName 集合名称
* @param document 要插入的文档
* @returns Promise<any>
*/
public async insertOne<T extends Document>(collectionName: string, document: OptionalUnlessRequiredId<T>): Promise<any> {
try {
const collection = this.getCollection<T>(collectionName);
const result = await collection.insertOne(document);
return result;
} catch (error) {
console.error(`Failed to insert document into ${collectionName}:`, error);
throw error;
}
}
/**
* 插入多个文档
* @param collectionName 集合名称
* @param documents 要插入的文档数组
* @returns Promise<any>
*/
public async insertMany<T extends Document>(collectionName: string, documents: OptionalUnlessRequiredId<T>[]): Promise<any> {
try {
const collection = this.getCollection<T>(collectionName);
const result = await collection.insertMany(documents);
return result;
} catch (error) {
console.error(`Failed to insert documents into ${collectionName}:`, error);
throw error;
}
}
/**
* 查找单个文档
* @param collectionName 集合名称
* @param query 查询条件
* @returns Promise<T | null>
*/
public async findOne<T extends Document>(collectionName: string, query: any = {}): Promise<WithId<T> | null> {
try {
const collection = this.getCollection<T>(collectionName);
const result = await collection.findOne(query);
return result;
} catch (error) {
console.error(`Failed to find document in ${collectionName}:`, error);
throw error;
}
}
/**
* 查找多个文档
* @param collectionName 集合名称
* @param query 查询条件
* @param options 查询选项(如限制数量、排序等)
* @returns Promise<T[]>
*/
public async findMany<T extends Document>(collectionName: string, query: any = {}, options?: any): Promise<WithId<T>[]> {
try {
const collection = this.getCollection<T>(collectionName);
const cursor = collection.find(query, options);
const results = await cursor.toArray();
return results;
} catch (error) {
console.error(`Failed to find documents in ${collectionName}:`, error);
throw error;
}
}
/**
* 更新单个文档
* @param collectionName 集合名称
* @param query 查询条件
* @param update 更新内容
* @returns Promise<any>
*/
public async updateOne<T extends Document>(collectionName: string, query: any, update: any): Promise<any> {
try {
const collection = this.getCollection<T>(collectionName);
const result = await collection.updateOne(query, update);
return result;
} catch (error) {
console.error(`Failed to update document in ${collectionName}:`, error);
throw error;
}
}
/**
* 更新多个文档
* @param collectionName 集合名称
* @param query 查询条件
* @param update 更新内容
* @returns Promise<any>
*/
public async updateMany<T extends Document>(collectionName: string, query: any, update: any): Promise<any> {
try {
const collection = this.getCollection<T>(collectionName);
const result = await collection.updateMany(query, update);
return result;
} catch (error) {
console.error(`Failed to update documents in ${collectionName}:`, error);
throw error;
}
}
/**
* 删除单个文档
* @param collectionName 集合名称
* @param query 查询条件
* @returns Promise<any>
*/
public async deleteOne<T extends Document>(collectionName: string, query?: Filter<T>): Promise<DeleteResult> {
try {
const collection = this.getCollection<T>(collectionName);
const result = await collection.deleteOne(query);
return result;
} catch (error) {
console.error(`Failed to delete document from ${collectionName}:`, error);
throw error;
}
}
/**
* 删除多个文档
* @param collectionName 集合名称
* @param query 查询条件
* @returns Promise<any>
*/
public async deleteMany<T extends Document>(collectionName: string, query?: Filter<T>): Promise<DeleteResult> {
try {
const collection = this.getCollection<T>(collectionName);
const result = await collection.deleteMany(query);
return result;
} catch (error) {
console.error(`Failed to delete documents from ${collectionName}:`, error);
throw error;
}
}
/**
* 检查数据库连接状态
* @returns boolean
*/
public isConnected(): boolean {
return !!this.client && !!this.db;
}
}
export default MongoDBUtil;
export { MongoChatHistoryTool } from './chatHistory.js';
vector.ts
js
import { OllamaEmbeddings } from '@langchain/ollama';
import MongoDBUtil from './index.js';
import 'dotenv/config';
import type { Collection, Filter } from 'mongodb';
import { MongoDBAtlasVectorSearch } from '@langchain/mongodb';
import { Document, type DocumentInterface } from '@langchain/core/documents';
export interface MongodbToolConfig {
/** 指定集合名称,不同 collection 互相隔离 */
collectionName?: string;
/** 自定义 Embeddings 实例,默认走 Ollama */
embeddings?: OllamaEmbeddings;
/** 默认检索条数 */
defaultK?: number;
/** Chroma 服务地址,优先使用 URL */
url?: string;
/** 本地持久化目录(与 url 二选一) */
persistDirectory?: string;
}
export class MongodbVectorTool {
private readonly config: MongodbToolConfig;
private readonly defaultK: number;
/** 实际使用的 Embeddings 实例 */
private readonly embeddings: OllamaEmbeddings;
/** mongodb 实例 */
private readonly mongoClient: MongoDBUtil;
/** 集合 */
private collection: Collection | null = null;
/** 向量存储 */
private vectorStore: MongoDBAtlasVectorSearch | null = null;
constructor(config: MongodbToolConfig) {
this.config = config;
this.defaultK = config.defaultK ?? 4;
this.embeddings =
config.embeddings ??
new OllamaEmbeddings({
model: process.env.OLLAMA_EMBED_MODEL ?? 'nomic-embed-text:latest',
baseUrl: process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434',
});
this.mongoClient = MongoDBUtil.getInstance();
}
/**
* 初始化向量工具
* @returns Promise<void>
*/
async initialize(): Promise<void> {
// 确保数据库连接
if (!this.mongoClient.isConnected()) {
await this.mongoClient.connect();
}
// 获取集合引用
this.collection = this.mongoClient.getCollection(
this.config.collectionName || process.env.MONGODB_DB_VECTOR_COLLECTION_NAME || 'vector_collection'
);
// 初始化向量存储
this.vectorStore = new MongoDBAtlasVectorSearch(this.embeddings, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
collection: this.collection as any,
indexName: 'vector_index',
textKey: 'text',
embeddingKey: 'embedding',
});
}
getCollection() {
return this.collection;
}
static async initSearchIndex(
collection: Collection | null,
definition = {
fields: [
{
type: 'vector',
numDimensions: 768,
path: 'embedding',
similarity: 'cosine',
},
],
}
) {
if (collection) {
const indexes = await collection.listSearchIndexes('vector_index').toArray();
if (indexes.length === 0) {
const index = {
name: 'vector_index',
type: 'vectorSearch',
definition: definition,
};
collection.createSearchIndex(index);
}
}
}
/**
* 确保向量工具已初始化
* @returns Promise<void>
*/
private async ensureInitialized(): Promise<void> {
if (!this.vectorStore || !this.collection) {
await this.initialize();
}
}
/**
* @description: 将任意对象数组转换为 Document 数组
* @template T
* @param {T[]} items
* @param {(item: T, index: number) => Record<string, unknown>} metadataSelector
* @return {Document[]}
*/
static toDocuments<T extends Record<string, unknown>>(
items: T[],
metadataSelector?: (item: T, index: number) => Record<string, unknown>
): Document[] {
return items.map(
(item, index) =>
new Document({
pageContent: JSON.stringify(item),
metadata: metadataSelector?.(item, index) ?? {},
})
);
}
/**
* @description: 添加文档
* @param {Document[]} documents
* @return {Promise<void>}
*/
async addDocuments(documents: Document[]): Promise<void> {
if (!documents.length) return;
await this.ensureInitialized();
if (!this.vectorStore) {
throw new Error('Vector store not initialized');
}
await this.vectorStore.addDocuments(documents);
}
/**
* @description: 添加文本
* @param {string[]} texts
* @param {Record<string, unknown>[]} metadatas
* @return {Promise<void>}
*/
async addTexts(texts: string[], metadatas?: Record<string, unknown>[]): Promise<void> {
const documents = texts.map(
(text, index) =>
new Document({
pageContent: text,
metadata: metadatas?.[index] ?? {},
})
);
await this.addDocuments(documents);
}
/**
* @description: 相似度检索
* @param {string} query
* @param {number} k
* @return {Promise<DocumentInterface[]>}
*/
async similaritySearch(query: string, k: number = this.defaultK): Promise<DocumentInterface[]> {
await this.ensureInitialized();
if (!this.vectorStore) {
throw new Error('Vector store not initialized');
}
return this.vectorStore.similaritySearch(query, k);
}
/**
* @description: 相似度检索并返回得分
* @param {string} query
* @param {number} k
* @return {Promise<[DocumentInterface, number][]>}
*/
async similaritySearchWithScore(query: string, k: number = this.defaultK): Promise<[DocumentInterface, number][]> {
await this.ensureInitialized();
if (!this.vectorStore) {
throw new Error('Vector store not initialized');
}
console.log(this.vectorStore);
return this.vectorStore.similaritySearchWithScore(query, k);
}
/**
* @description: 删除集合
* @return {Promise<void>}
*/
async deleteCollection(query: Filter<Document>): Promise<void> {
await this.ensureInitialized();
if (!this.mongoClient) {
throw new Error('MongoDBUtil not initialized');
}
try {
await this.mongoClient.deleteOne(this.config.collectionName || process.env.MONGODB_DB_VECTOR_COLLECTION_NAME || 'vector_collection', query);
} catch (error) {
console.error('Error deleting collection:', error);
throw error;
}
}
}
export default MongodbVectorTool;
chatHistory.ts
js
import { MongoDBChatMessageHistory } from '@langchain/mongodb';
import MongoDBUtil from './index.js';
import { MongoServerError, type Collection, type Document } from 'mongodb';
import {
AIMessage,
BaseMessage,
type BaseMessageLike,
HumanMessage,
type MessageContent,
SystemMessage,
coerceMessageLikeToMessage,
getBufferString,
} from '@langchain/core/messages';
// 定义排序顺序类型,用于指定升序或降序
type SortOrder = 'asc' | 'desc';
// MongoDB聊天历史选项接口
export interface MongoChatHistoryOptions {
// 可选的集合名称,默认为'chat_message_history'
collectionName?: string;
// 会话TTL(生存时间)秒数,用于设置会话过期时间
sessionTTLSeconds?: number;
}
// 列出会话的选项接口
export interface ListSessionsOptions {
// 限制返回的会话数量
limit?: number;
// 跳过的会话数量(用于分页)
skip?: number;
// 排序顺序(升序或降序)
order?: SortOrder;
// 特定会话ID列表,如果指定则只返回这些会话
sessionIds?: string[];
}
// 消息查询选项接口
export interface MessageQueryOptions {
// 限制返回的消息数量
limit?: number;
// 是否反转消息顺序
reverse?: boolean;
}
// 聊天会话摘要接口
export interface ChatSessionSummary {
// 会话ID
sessionId: string;
// 消息数量
messageCount: number;
// 会话创建时间
createdAt?: Date;
// 会话更新时间
updatedAt?: Date;
}
// 默认集合名称常量
const DEFAULT_COLLECTION_NAME = 'chat_message_history';
/**
* MongoDB聊天历史工具类
* 提供了对聊天历史的增删查改功能,支持会话管理和消息操作
*/
class MongoChatHistoryTool {
// 工具配置选项
private readonly options: MongoChatHistoryOptions;
// MongoDB工具实例
private readonly mongo: MongoDBUtil;
// MongoDB集合实例
private collection: Collection<Document> | null = null;
// 缓存的历史记录实例映射,用于提高性能
private readonly histories = new Map<string, MongoDBChatMessageHistory>();
// 索引是否已准备好的标志
private indexesReady = false;
/**
* 构造函数
* @param options 配置选项
*/
constructor(options: MongoChatHistoryOptions = {}) {
this.options = options;
this.mongo = MongoDBUtil.getInstance();
}
/**
* 获取集合名称
* 优先级:选项中指定 > 环境变量 > 默认值
*/
private get collectionName(): string {
return this.options.collectionName ?? process.env.MONGODB_DB_CHAT_COLLECTION_NAME ?? DEFAULT_COLLECTION_NAME;
}
/**
* 获取TTL(生存时间)设置
*/
private get ttl(): number | undefined {
return this.options.sessionTTLSeconds;
}
/**
* 确保初始化完成
* 连接数据库并设置集合和索引
*/
private async ensureInitialized(): Promise<void> {
if (!this.mongo.isConnected()) {
await this.mongo.connect();
}
if (!this.collection) {
this.collection = this.mongo.getCollection<Document>(this.collectionName);
}
if (!this.indexesReady && this.collection) {
await this.ensureIndexes();
this.indexesReady = true;
}
}
/**
* 确保必要的索引已创建
* 创建sessionId索引和TTL索引(如果设置了TTL)
*/
private async ensureIndexes(): Promise<void> {
if (!this.collection) {
return;
}
let indexInfos: Document[] = [];
try {
indexInfos = await this.collection.listIndexes().toArray();
} catch (error) {
if (error instanceof MongoServerError && error.code === 26) {
// NamespaceNotFound表示集合尚不存在,继续创建索引
indexInfos = [];
} else {
throw error;
}
}
const hasSessionIndex = indexInfos.some((idx) => idx.name === 'sessionId_1');
if (!hasSessionIndex) {
await this.collection.createIndex({ sessionId: 1 }, { unique: true });
}
if (this.ttl != null) {
const ttlIndexName = 'updatedAt_ttl';
const hasTTL = indexInfos.some((idx) => idx.name === ttlIndexName);
if (!hasTTL) {
await this.collection.createIndex({ updatedAt: 1 }, { name: ttlIndexName, expireAfterSeconds: this.ttl });
}
}
}
/**
* 获取指定会话的历史记录实例
* 如果不存在则创建新的实例并缓存
* @param sessionId 会话ID
*/
private async getHistory(sessionId: string): Promise<MongoDBChatMessageHistory> {
if (!sessionId) {
throw new Error('Session id required');
}
await this.ensureInitialized();
if (!this.collection) {
throw new Error('MongoDB collection unavailable');
}
let history = this.histories.get(sessionId);
if (!history) {
history = new MongoDBChatMessageHistory({ collection: this.collection as any, sessionId });
this.histories.set(sessionId, history);
}
return history;
}
/**
* 更新会话的时间戳和消息计数
* @param sessionId 会话ID
*/
private async touchSession(sessionId: string): Promise<void> {
if (!this.collection) {
return;
}
const now = new Date();
await this.collection.updateOne({ sessionId }, [
{
$set: {
updatedAt: now,
createdAt: { $ifNull: ['$createdAt', now] },
messageCount: { $size: '$messages' },
},
},
]);
}
/**
* 向指定会话追加消息
* @param sessionId 会话ID
* @param message 要添加的消息
*/
async appendMessage(sessionId: string, message: BaseMessageLike): Promise<void> {
const history = await this.getHistory(sessionId);
const normalized = coerceMessageLikeToMessage(message);
await history.addMessage(normalized);
await this.touchSession(sessionId);
}
/**
* 向指定会话追加人类消息
* @param sessionId 会话ID
* @param content 消息内容
*/
async appendHumanMessage(sessionId: string, content: string | MessageContent): Promise<void> {
await this.appendMessage(sessionId, new HumanMessage(content));
}
/**
* 向指定会话追加AI消息
* @param sessionId 会话ID
* @param content 消息内容
*/
async appendAIMessage(sessionId: string, content: string | MessageContent): Promise<void> {
await this.appendMessage(sessionId, new AIMessage(content));
}
/**
* 向指定会话追加系统消息
* @param sessionId 会话ID
* @param content 消息内容
*/
async appendSystemMessage(sessionId: string, content: string | MessageContent): Promise<void> {
await this.appendMessage(sessionId, new SystemMessage(content));
}
/**
* 向指定会话批量追加消息
* @param sessionId 会话ID
* @param messages 消息数组
*/
async appendMessages(sessionId: string, messages: BaseMessageLike[]): Promise<void> {
for (const message of messages) {
await this.appendMessage(sessionId, message);
}
}
/**
* 获取指定会话的消息
* @param sessionId 会话ID
* @param options 查询选项
*/
async getMessages(sessionId: string, options: MessageQueryOptions = {}): Promise<BaseMessage[]> {
const history = await this.getHistory(sessionId);
const messages = await history.getMessages();
const ordered = options.reverse ? [...messages].reverse() : messages;
if (options.limit == null || options.limit < 0) {
return ordered;
}
if (options.reverse) {
return ordered.slice(0, options.limit);
}
return ordered.slice(Math.max(0, ordered.length - options.limit));
}
/**
* 将指定会话的消息转换为缓冲字符串
* @param sessionId 会话ID
* @param humanPrefix 人类消息前缀
* @param aiPrefix AI消息前缀
*/
async getMessagesAsBuffer(sessionId: string, humanPrefix?: string, aiPrefix?: string): Promise<string> {
const messages = await this.getMessages(sessionId);
return getBufferString(messages, humanPrefix, aiPrefix);
}
/**
* 列出聊天会话
* @param options 列表选项
*/
async listSessions(options: ListSessionsOptions = {}): Promise<ChatSessionSummary[]> {
await this.ensureInitialized();
if (!this.collection) {
return [];
}
const { limit = 20, skip = 0, order = 'desc', sessionIds } = options;
const pipeline: Document[] = [];
if (sessionIds?.length) {
pipeline.push({ $match: { sessionId: { $in: sessionIds } } });
}
pipeline.push({
$addFields: {
messageCount: { $ifNull: ['$messageCount', { $size: '$messages' }] },
},
});
pipeline.push({ $sort: { updatedAt: order === 'asc' ? 1 : -1, sessionId: 1 } });
if (skip > 0) {
pipeline.push({ $skip: skip });
}
if (limit > 0) {
pipeline.push({ $limit: limit });
}
pipeline.push({
$project: {
_id: 0,
sessionId: 1,
messageCount: 1,
createdAt: 1,
updatedAt: 1,
},
});
const summaries = await this.collection.aggregate(pipeline).toArray();
return summaries.map((summary) => ({
sessionId: summary.sessionId as string,
messageCount: (summary.messageCount as number) ?? 0,
createdAt: summary.createdAt,
updatedAt: summary.updatedAt,
}));
}
/**
* 删除指定会话
* @param sessionId 会话ID
*/
async deleteSession(sessionId: string): Promise<void> {
await this.ensureInitialized();
if (!this.collection) {
return;
}
await this.collection.deleteOne({ sessionId });
this.histories.delete(sessionId);
}
/**
* 清空指定会话的消息
* @param sessionId 会话ID
*/
async clear(sessionId: string): Promise<void> {
const history = await this.getHistory(sessionId);
await history.clear();
this.histories.delete(sessionId);
}
}
export { MongoChatHistoryTool };
export default MongoChatHistoryTool;