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流式响应、完善的错误处理,降低前端对接成本。
相关推荐
风送雨2 小时前
多模态RAG工程开发教程(上)
python·langchain
renke33642 小时前
Flutter 2025 模块化与微前端工程体系:从单体到可插拔架构,实现高效协作、独立交付与动态加载的下一代应用结构
前端·flutter·架构
wordbaby2 小时前
配置 Git Hooks:使用 Husky + lint-staged 自动代码检查
前端
得物技术2 小时前
Ant Design 6.0 尝鲜:上手现代化组件开发|得物技术
前端
孟祥_成都2 小时前
前端和小白都能看懂的 LangChain Model 模块核心实战指南
前端·人工智能
wordbaby2 小时前
配置 VS Code / Cursor 保存时自动格式化代码
前端
LYFlied2 小时前
Spec Coding:AI时代前端开发的范式革新
前端·人工智能·工程化·spec coding
古蓬莱掌管玉米的神2 小时前
day1
前端
多看书少吃饭3 小时前
从 ScriptProcessor 到 AudioWorklet:Electron 桌面端录音实践总结
前端·javascript·electron