ollmam+langchain.js实现本地大模型简单记忆对话-PostgreSQL版

前言

本文基于之前的本地大模型对话Demo进行优化,重点介绍如何将内存存储升级为PostgreSQL持久化存储,并增强对话安全性与检索准确性。仍需在Node.js 18+环境下运行,核心依赖工具(Ollama、PostgreSQL)的安装请参考官方文档。

为何升级为PostgreSQL存储?

内存存储虽适合快速原型验证,但存在重启后数据丢失的问题,无法满足实际应用场景。PostgreSQL作为成熟的关系型数据库,不仅能持久化存储对话记录,配合pgvector插件还能高效管理向量数据,为生产环境提供可靠支撑。

代码实现:从内存存储到PostgreSQL持久化

1. 数据库配置与实例初始化

修改内容:新增PostgreSQL配置,替代原内存存储的sessionStores设计

arduino 复制代码
// 替代原内存存储的全局配置
const pgConfig = {
    host: "127.0.0.1",
    port: 5432,
    user: "fanxiaosi",
    password: "f15130026310.",
    database: "One-AI-DB",
};

修改原因:内存存储无法持久化数据,服务重启后对话历史会丢失。

改进好处:使用PostgreSQL实现对话记录的永久存储,支持多实例共享数据,为分布式部署奠定基础。

2. 语言模型配置优化

修改内容:在原有基础上增加防复读配置

arduino 复制代码
const llm = new ChatOllama({
    model: 'deepseek-r1:1.5b',
    baseUrl: "http://127.0.0.1:11434",
    topP: 0.9,  // 从1.0调整为0.9
    topK: 40,   // 从20调整为40
    streaming: true,
    temperature: 0,  // 新增:固定输出温度,保证结果稳定性
    repeatPenalty: 1.2,  // 新增:防止多轮对话后复读系统指令
})

修改原因:原配置可能导致输出多样性不足或出现重复内容。

改进好处:通过repeatPenalty减少复读现象,调整topP/topK平衡输出多样性与相关性,固定temperature确保回复稳定性。

3. 提示词模板增强

修改内容:优化提示词模板结构,增加背景信息注入点

css 复制代码
const textPrompt = ChatPromptTemplate.fromMessages([    ["system", "你是一个专业的知识助手。只需回答用户问题,不要重复系统指令。如果无法从背景信息中找到答案,请基于已知对话回复。"],
    ["placeholder", "{chat_history}"],
    ["human", "背景信息:{relevant_history}\n\n当前问题:{user_input}"]  // 新增背景信息字段
]);

修改原因:原模板仅依赖完整历史对话,长对话场景下可能超出模型上下文窗口。

改进好处:通过{relevant_history}注入检索到的相关片段,既保证上下文相关性,又避免全量历史导致的窗口溢出问题。

4. 敏感词检查机制强化

修改内容:完善敏感词检查的错误处理流程

ini 复制代码
const profanityChecker = (input: { user_input: string }) => {
    const sensitiveWords = ["傻瓜", "白痴", "不良内容"];
    const userInput = input.user_input;

    if (sensitiveWords.some(word => userInput.includes(word))) {
        throw new Error("检测到敏感内容。请使用文明用语。");  // 明确的错误提示
    }

    return input;  // 检查通过则透传输入
};
const customCheckStep = RunnableLambda.from(profanityChecker);

修改原因:原实现未明确错误处理机制,可能导致异常传播不规范。

改进好处:通过抛出结构化错误,使前端能清晰捕获敏感词提示,同时中断后续处理流程,提升系统安全性。

5. 向量存储重构:单例模式+PGVector

修改内容:用单例模式管理PGVectorStore,替代原内存向量库

php 复制代码
// 替代原MemoryVectorStore的实现
let globalVectorStore: PGVectorStore | null = null;

export const getVectorStore = async (): Promise<PGVectorStore> => {
    if (!globalVectorStore) {
        globalVectorStore = await PGVectorStore.initialize(embeddingModel, {
            postgresConnectionOptions: pgConfig,
            tableName: "test_langchain_embeddings",
            columns: {
                idColumnName: "id",
                vectorColumnName: "embedding",
                contentColumnName: "text",
                metadataColumnName: "metadata",
            },
        });
    }
    return globalVectorStore;
};

修改原因:原内存向量库存在数据易失性和多实例同步问题。

改进好处:

  1. 单例模式避免重复创建数据库连接池,减少资源消耗
  2. PGVectorStore支持向量数据的持久化存储和高效检索
  3. 结构化的表设计便于后期数据维护和分析

6. 对话历史存储优化

修改内容:新增对话内容清洗逻辑,增强检索准确性

typescript 复制代码
export const addMessageToVectorStore = async (
    sessionId: string,
    humanInput: string,
    aiResponse?: string
) => {
    // 新增:清洗AI回复中的特殊标签
    const cleanAIResponse = aiResponse?.replace(/[\s\S]*?</think>/g, "").trim();

    const vectorStore = await getVectorStore();

    const messageText = cleanAIResponse
        ? `用户:${humanInput}\n助手:${cleanAIResponse}`
        : `用户:${humanInput}`;

    await vectorStore.addDocuments([{
        pageContent: messageText,
        metadata: {
            sessionId,
            timestamp: Date.now(),
            type: aiResponse ? "qa_pair" : "user_query"  // 新增类型标识
        }
    }]);
};

修改原因:原实现可能存储未经处理的模型输出(含思考过程标签),影响检索精度。

改进好处:

  1. 移除特殊标签确保存储内容纯净度
  2. 增加type字段区分消息类型,便于后续检索过滤
  3. 标准化存储格式提升语义匹配准确性

7. 检索逻辑优化

修改内容:增强检索器的会话隔离和结果去重能力

typescript 复制代码
export const retrieveRelevantHistory = async (
    userInput: string,
    sessionId: string
) => {
    const vectorStore = await getVectorStore();

    const retriever = vectorStore.asRetriever({
        searchType: "mmr",
        searchKwargs: {
            fetchK: 20,  // 扩大候选集
            lambda: 0.7,  // 平衡相关性和多样性
        },
        k: 3,  // 小模型适配的最优返回数量
        filter: { sessionId },  // 严格的会话隔离
    });

    const relevantDocs = await retriever.invoke(userInput);
    return relevantDocs.length > 0
        ? relevantDocs.map(doc => doc.pageContent).join("\n\n")
        : "";  // 空字符串替代原"无相关背景知识"
};

修改原因:原检索逻辑未严格隔离会话,且返回无关提示可能干扰模型。

改进好处:

  1. 通过filter确保只检索当前会话历史,避免跨会话信息泄露
  2. 调整k值适配1.5B小模型的上下文处理能力
  3. 返回空字符串替代提示文本,防止干扰模型判断

8. 链式调用流程重构

修改内容:优化调用链的参数传递逻辑

javascript 复制代码
const ragChain = RunnableSequence.from([
    customCheckStep,  // 优先执行敏感词检查
    {
        user_input: (input) => input.user_input,
        sessionId: (_input, config) => config.configurable?.sessionId,
        chat_history: (input) => input.chat_history,
    },
    RunnablePassthrough.assign({
        relevant_history: retrieveStep,  // 注入检索结果
    }),
    textPrompt,
    llm,
]);

修改原因:原链式调用参数传递不够清晰,可能导致上下文丢失。

改进好处:

  1. 明确参数映射关系,提升代码可读性
  2. 将敏感词检查置于流程最前端,提前拦截不良内容
  3. 分离参数提取与检索逻辑,便于单独调试

9. 会话历史管理升级

修改内容:使用PostgresChatMessageHistory替代内存存储

typescript 复制代码
const textChainWithHistory = new RunnableWithMessageHistory<any, any>({
    runnable: ragChain,
    getMessageHistory: (sessionId: string) => {
        return new PostgresChatMessageHistory({
            tableName: "chat_messages",
            sessionId,
            poolConfig: pgConfig,
        });
    },
    inputMessagesKey: "user_input",
    historyMessagesKey: "chat_history",
} as any);

修改原因:原InMemoryChatMessageHistory无法持久化存储对话记录。

改进好处:

  1. 实现对话历史的持久化存储,服务重启后不丢失
  2. 与PostgreSQL向量库形成数据联动,便于数据备份与迁移
  3. 支持跨进程共享会话历史,为集群部署提供可能

10. 服务器与请求处理增强

修改内容:完善HTTP服务器的错误处理和跨域支持

typescript 复制代码
const app = http.createServer(async (req, res) => {
    // 新增:过滤浏览器图标请求
    if (req.url === "/favicon.ico") {
        res.writeHead(404);
        res.end();
        return;
    }
    
    // 增强跨域处理
    if (req.method === "OPTIONS") {
        res.setHeader("Access-Control-Allow-Origin", "*");
        res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
        res.setHeader("Access-Control-Allow-Headers", "Content-Type");
        res.writeHead(204);
        res.end();
        return;
    }

    try {
        // 统一参数解析逻辑
        const params = await parseRequestParams(req);
        const sessionId = params.sessionId || "default-session-id";
        const user_input = params.user_input;
        
        // 输入验证
        if (!user_input) {
            res.writeHead(400).end("Missing user_input");
            return;
        }

        // 流式响应处理
        res.writeHead(200, {
            "Content-Type": "text/event-stream; charset=utf-8",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
        });

        const stream = await textChainWithHistory.stream(
            { user_input },
            { configurable: { sessionId } }
        );
        
        let fullSummary = "";
        for await (const chunk of stream) {
            const content = chunk.content || "";
            fullSummary += content;
            res.write(`data: ${JSON.stringify({ content })}\n\n`);
        }

        // 新增:回复完成后存入向量库
        if (fullSummary) {
            await addMessageToVectorStore(sessionId, user_input, fullSummary);
        }
        
        res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
        res.end();
    } catch (error: any) {
        // 错误信息通过SSE传递
        res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
    }
})

修改原因:原服务器实现缺乏完整的错误处理和请求过滤机制。

改进好处:

  1. 增加图标请求过滤,减少无效处理
  2. 完善的错误捕获与前端通知机制
  3. 标准化SSE响应格式,便于前端处理
  4. 确保AI回复在生成完成后才存入向量库,避免部分内容存储

结语

本次优化通过PostgreSQL实现了对话数据的持久化存储,增强了系统的稳定性和安全性,同时优化了检索精度和模型输出质量。相比内存存储方案,新版Demo更接近生产环境需求,可作为企业级本地大模型应用的基础框架。建议结合实际业务场景进一步扩展用户认证、权限控制等功能。

总结

  1. 核心升级方向:内存存储→PostgreSQL持久化,解决数据易失问题,适配生产场景;
  2. 关键优化点:敏感词前置检查、检索会话隔离、模型防复读配置,提升安全性与回复质量;
  3. 体验增强:标准化SSE流式响应、完善的错误处理,降低前端对接成本。
相关推荐
崔庆才丨静觅7 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
在校大学生0072 小时前
AI教我赚100万用1年的时间–4(水文)
aigc
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
心疼你的一切2 小时前
解密CANN仓库:AIGC的算力底座、关键应用与API实战解析
数据仓库·深度学习·aigc·cann