首页
/ Langchain-Chatchat 数据库会话管理深度解析:session_scope、with_session 与 get_db 的源码剖析与工程实践

Langchain-Chatchat 数据库会话管理深度解析:session_scope、with_session 与 get_db 的源码剖析与工程实践

2026-09-08 13:02:17作者:贡沫苏Truman

在 Langchain-Chatchat 服务端架构中,对话记录、知识库元数据、会话(Conversation)与消息(Message)等结构化数据统一由 SQLAlchemy ORM 持久化,而这一切都建立在一个薄薄的文件 chatchat/server/db/session.py 之上。本文以 markdown_docs/server/db/session.md 所讲解的 session_scopewith_sessionget_dbget_db0 四个会话管理设施为主体,结合仓库中 db/base.py、各 Repository 与迁移脚本的真实实现,深入剖析 Langchain-Chatchat 如何在重复的"开会话—提交—回滚—关闭"样板代码中抽取出统一方案,并给出可直接落地复用的工程范式。读完本文,你将掌握如何为新增的数据表编写带自动事务管理的数据访问函数,以及在 FastAPI 路由中正确注入与释放数据库会话。

一、会话(Session)从哪来:engine 与 SessionLocal 的初始化

要理解四个管理函数,必须先看它们的共同基座——SessionLocal 会话工厂。它定义在 libs/chatchat-server/chatchat/server/db/base.py

import json

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from sqlalchemy.orm import sessionmaker

from chatchat.settings import Settings


engine = create_engine(
    Settings.basic_settings.SQLALCHEMY_DATABASE_URI,
    json_serializer=lambda obj: json.dumps(obj, ensure_ascii=False),
)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base: DeclarativeMeta = declarative_base()

这段代码做了三件关键的事:

  1. 通过 create_engine 建立数据库引擎,连接的 URI 来自全局设置 Settings.basic_settings.SQLALCHEMY_DATABASE_URI。在 libs/chatchat-server/chatchat/settings.py 中,其默认值是 SQLite 文件:
    """数据库默认存储路径。如果使用sqlite,可以直接修改DB_ROOT_PATH;
    如果使用其它数据库,请直接修改SQLALCHEMY_DATABASE_URI。"""
    SQLALCHEMY_DATABASE_URI: str = "sqlite:///" + str(CHATCHAT_ROOT / "data/knowledge_base/info.db")
    
    也就是说,默认情况下对话、知识库等结构化数据落在 chatchat 数据目录下的 info.db。若需要切换 MySQL / PostgreSQL 等数据库,只需修改该 URI,其余上层代码无需变动——这正是会话工厂统一管理的价值所在。
  2. 引擎自定义了 json_serializer,使用 ensure_ascii=False 序列化 JSON 字段,避免中文字符被转义为 \uXXXX,保证中文内容(如对话消息中的 query、response)可读、可检索。
  3. sessionmaker 配置了 autocommit=False, autoflush=False:事务必须显式 commit 才会落库;查询前也不会隐式 flush 未保存的改动。这个语义是理解后文所有提交/回滚逻辑的前提。

session.py 中的每个管理设施本质上都是在围绕 SessionLocal() 这一工厂做"生命周期"收口。

二、session_scope:用上下文管理器兜底事务三件事

原文档将 session_scope 定位为"自动管理数据库会话生命周期的上下文管理器",其源码位于 libs/chatchat-server/chatchat/server/db/session.py

from contextlib import contextmanager


@contextmanager
def session_scope() -> Session:
    """上下文管理器用于自动获取 Session, 避免错误"""
    session = SessionLocal()
    try:
        yield session
        session.commit()
    except:
        session.rollback()
        raise
    finally:
        session.close()

2.1 执行流程拆解

该函数不接受任何参数,使用 @contextmanager 将普通生成器包装为上下文管理器,with session_scope() as session: 的语义逐段展开为:

阶段 触发条件 执行动作 效果
进入 with 调用 session_scope() SessionLocal() 创建会话 拿到新会话
用户代码块 正常运行结束 yield 之后的 session.commit() 执行 所有改动一次性提交落库
用户代码抛异常 任意 except 分支 session.rollback() + raise 撤销全部未提交改动,并把异常原样抛给调用方
finally 无论成功或异常 session.close() 归还/释放数据库连接,杜绝泄漏

2.2 仓库中的真实调用场景

session_scope 直接以 with 语法被使用在数据迁移入口 libs/chatchat-server/chatchat/server/knowledge_base/migrate.py 中:当从旧版 SQLite 备份库导入历史数据时(import_from_db),函数遍历 sqlite_master 中的每张表,逐行构造 ORM 模型对象并 session.add(model.class_(**data))。这里的迁移逻辑被整体包裹在 with session_scope() as session: 之中(见该文件第 23 行导入与第 81 行使用),意味着任何一个数据行转换失败都会触发整体回滚,不会留下"导了一半"的脏数据;导入成功后统一提交。这是"多步写操作必须原子化"的典型场景,也是 session_scope 相对裸用 SessionLocal 的核心优势。

2.3 使用注意

原文档强调:yield 之后的代码块中产生的数据库修改,在无异常时会自动提交;异常时自动回滚,因此开发者在 with 块内不需要手动 commit/rollback。同时也要注意两点工程细节:

  • session_scope 捕获的是裸 except(即捕获所有异常),这意味着包括 KeyboardInterrupt 在内的异常也会先回滚再上抛——语义上偏"保守安全",实际使用中更推荐结合具体业务在 with 块外做精细的异常分类处理;
  • 提交/回滚均发生在用户代码块结束后,因此不要在 with 块内提前依赖"已持久化"的数据做跨会话读取,如需立即读取请自行 commit 或改用细粒度事务。

三、with_session:把事务管理收敛成一行注解

如果说 session_scope 服务的是"用 with 包裹一段逻辑",那么 with_session 就是把这种管理下沉为装饰器,让"以 session 为首参"的仓库函数彻底摆脱事务样板代码。其实现为 libs/chatchat-server/chatchat/server/db/session.py

from functools import wraps


def with_session(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        with session_scope() as session:
            try:
                result = f(session, *args, **kwargs)
                session.commit()
                return result
            except:
                session.rollback()
                raise

    return wrapper

3.1 wrapper 内部的双保险

wrapper 的逻辑可以看作 session_scope 的"装饰器化封装":

  • 首先进入 with session_scope() as session: 创建会话上下文;
  • 将被装饰函数 f 调用,自动把会话对象作为第一个位置参数传入,即调用形态固定为 f(session, *args, **kwargs)
  • f 返回后再次显式 session.commit()(此处与 session_scope 中的 commit 构成双保险,语义一致);
  • 任意异常路径先 session.rollback()raise,保证事务原子性与异常可见性;
  • 由于使用了 functools.wraps(f),被装饰函数的 __name____doc__ 等元信息得以保留,便于日志与调试。

3.2 对被装饰函数的硬性约定

原文档明确了两条约束,源码层面亦可验证:

  1. 第一个形参必须是 session。例如 conversation_repository.py 中新增对话的函数:

    # libs/chatchat-server/chatchat/server/db/repository/conversation_repository.py
    from chatchat.server.db.session import with_session
    
    
    @with_session
    def add_conversation_to_db(session, chat_type, name="", conversation_id=None):
        """新增聊天记录"""
        if not conversation_id:
            conversation_id = uuid.uuid4().hex
        c = ConversationModel(id=conversation_id, chat_type=chat_type, name=name)
        session.add(c)
        return c.id
    

    调用方传入 chat_typename 即可,session 由装饰器自动注入,session.add(c) 在函数返回后随 with 块退出自动提交。

  2. 异常必须由调用方妥善处理:因为装饰器只负责回滚并 raise,是否向上层用户提示、如何记录日志仍取决于业务代码。

3.3 仓库中的广泛覆盖

从源码搜索可见,with_session 目前被 Langchain-Chatchat 全部 7 个 Repository 模块导入并大量使用,是数据库访问层的事实标准:

仓库模块 文件路径 用途
会话仓库 repository/conversation_repository.py 增删查对话(Conversation)
消息仓库 repository/message_repository.py 增改消息与元数据
知识库仓库 repository/knowledge_base_repository.py 知识库信息持久化
知识文件仓库 repository/knowledge_file_repository.py 文件与向量库同步状态
知识元数据仓库 repository/knowledge_metadata_repository.py 自定义元数据读写
消息事件仓库 repository/human_message_event_repository.py Agent 人机交互事件
MCP 连接仓库 repository/mcp_connection_repository.py MCP 服务连接配置

message_repository.py 中的 add_message_to_db 为例,它在 session.add(m) 后甚至又显式调用了一次 session.commit()(第 32 行),与装饰器末尾的 commit 叠加——说明仓库层作者有意在写操作密集的函数内保证"即时可查"的语义。这也提示读者:with_session 允许函数体内自行控制提交粒度,装饰器提供的是最低限度的兜底而非唯一提交点。

一个值得注意的细节是 with_session 装饰器本身不接收额外参数(例如不支持自定义异常处理函数),属于"固定策略"装饰器;对会话生命周期有更强定制需求的场景,仓库代码给出的答案往往是退回显式 with session_scope() as session:(如迁移脚本),而不是对装饰器做复杂化扩展。

四、get_db 与 get_db0:面向依赖注入的两种会话供给

除上下文管理器与装饰器外,session.py 还导出了两个"纯创建会话"的函数,服务 FastAPI 风格的依赖注入场景。源码同样位于 libs/chatchat-server/chatchat/server/db/session.py

def get_db() -> SessionLocal:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


def get_db0() -> SessionLocal:
    db = SessionLocal()
    return db

4.1 get_db:生成器式依赖,请求结束自动关闭

get_db 是一个生成器函数(函数体内含 yield),它的设计目标与 FastAPI 的依赖注入系统天然契合:

  • 每次依赖被解析时创建一个全新的 SessionLocal() 实例,保证"一个请求一个会话",避免多请求共享会话带来的状态串扰;
  • yield db 将会话交给路由处理器执行增删改查;
  • 无论处理器是否抛异常,控制权最终都会回到 finally: db.close(),确保连接资源被及时释放。

在 FastAPI 中的标准接线方式如下(以仓库内定义的 get_db 为例):

from fastapi import Depends
from sqlalchemy.orm import Session
from chatchat.server.db.session import get_db


@app.get("/conversations/{conversation_id}")
def list_messages(conversation_id: str, db: Session = Depends(get_db)):
    # 直接使用 db 查询,请求结束后由框架负责 close
    return db.query(MessageModel).filter(...).all()

原文档特别提醒:因为使用了 yieldget_db() 返回的是生成器而非 SessionLocal 实例,不能直接当作会话对象用;它必须配合框架(FastAPI 的 Depends 或类似机制)才能发挥"自动开、自动关"的作用。

4.2 get_db0:手工管理生命周期的简化工厂

get_db0 则是"什么都不管"的极简版本——创建并立即返回一个 SessionLocal 实例,不做提交、回滚与关闭的兜底

  • 适用前提:SessionLocal 已被正确配置(数据库连接参数、绑定引擎等均已在 db/base.py 完成),这一点由模块导入顺序天然保证;
  • 使用代价:调用方必须自己对会话负责——用完记得 close(),写操作要自行 commit() / rollback(),否则会出现连接泄漏或脏数据;
  • 典型用途:同步脚本、后台任务、WebUI 等不方便走 Depends 链路的场景中,需要"先拿一个 session,再手动管理"时。

从源码搜索结果看,get_db / get_db0session.py 中被定义并在依赖注入体系中作为可复用的会话供给点;具体路由在接入时通常经由 Depends(get_db) 这样的形式解耦。编写新数据访问代码时,应优先选择有兜底的方案。

五、四条 API 的选型对照与实践建议

将上述四种会话管理设施放在同一张表中横向对比,可以更清晰地看到各自的定位:

API 类型 自动 commit 自动 rollback 自动 close 典型使用场景
session_scope() @contextmanager 上下文管理器 ✅(无异常时) ✅(异常时) 多步写操作需要原子化:如 migrate.py 的整表数据导入
@with_session 函数装饰器 ✅(含二次兜底) 仓库层数据访问函数:Conversation / Message / KB / MCP 等 7 个 Repository
get_db() 生成器函数(FastAPI 依赖) ❌(需业务内 commit) ✅(finally Web API 层依赖注入:每请求独立会话
get_db0() 普通工厂函数 ❌(调用方负责) 脚本 / 手动管理场景,需自行 close

工程选型可归纳为一条原则:能声明式就不要手写。编写独立的 CRUD 函数时首选 @with_session,编写需要跨多表保证一致性的批量逻辑时用 with session_scope() as session:,编写 Web 处理器时通过 Depends(get_db) 注入,仅在确有特殊生命周期需求时才回退到 get_db0 并显式管理。

六、理解 Langchain-Chatchat 数据层的整体脉络

session.py 之所以是数据层的关键枢纽,是因为它处于三层架构的中间位置:

FastAPI 路由 / WebUI / Agent 回调
            │  Depends(get_db) / 直接调用
            ▼
   Repository 仓库层(7 个 *Repository)
            │  @with_session
            ▼
      chatchat/server/db/session.py
      (session_scope / with_session / get_db / get_db0)
            │  操作 SessionLocal()
            ▼
 db/base.py:engine + sessionmaker + Base
            │  Settings.basic_settings.SQLALCHEMY_DATABASE_URI
            ▼
     SQLite(info.db) / MySQL / PostgreSQL

沿此链路可以迅速定位任何一条数据的来龙去脉:例如对话历史之所以能持久化到 info.db,是因为聊天回调链最终调用 message_repository 中带 @with_sessionadd_message_to_db;该函数收到由装饰器自动注入的会话后 session.add(m),随后在 session_scope 的 commit 阶段真正落库。新增一张业务表时,只需仿照现有 Repository 定义 @with_session 函数并确保模型继承 db/models/base.py 中的 Base,即可无缝获得一致的会话治理能力,无需关心底层连接与事务细节。

附注:本文所有代码片段均可回溯至仓库内对应文件——session.py(四个管理设施本体)、base.py(engine / SessionLocal / Base)、settings.py(默认 SQLite URI)、migrate.pysession_scope 的批量导入实战)以及各 Repository 模块(with_session 的大规模落地)。研读这些文件,即可完整复现 Langchain-Chatchat 的会话治理设计。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391