NO.4|模型管理|会话管理|数据管理|Sqlite

模型管理

现在deepseek-chat、gpt-4o-mini、gemini-2.0-flash模型已经接⼊成功,每个模型都对应单独的Provider类,为了后续使⽤简单,再封装⼀个LLMManager类将模型管理起来,后续通过多态的⽅式实现模型路由

LLMManager.h

LLMManager.cpp




会话管理

假设,现在使用 LLMManager 封装了一个智能聊天助手应用,当用户和模型聊天的过程中,会存在以下问题:

  1. 在某次聊天中,我们和模型进行了多轮的消息发送,这些消息在程序中如何存储?
  2. 模型没有记忆,每次给模型发送消息时需要带上历史消息,如果没有保存历史消息,在给模型发送消息时就无法带上历史消息,模型在回复的时候也没有办法参考往期的消息内容
  3. 假设和模型有多轮聊天,每轮聊天中都包含有多条消息,如何区分某条消息属于那一次聊天中的消息呢?
  4. 在给模型发送消息时,需要带上历史消息,假设所有的消息都保存在一起,如何获取某次聊天中的所有消息呢?
    要解决上述问题,需要引入会话机制。每次和模型开启新一轮聊天,都可以看成是一次会话。

在本次会话中,会和模型进行多次聊天,这些聊天信息可以保存在本次会话中了,除了保存会话消息外,还需要保存模型名称、会话创建时间等都需要保存 ------ 需要设计数据结构,该数据结构就是会话结构

会话介绍

会话是⽤⼾与⼤语⾔模型之间的⼀系列连续交互,它通过维护上下⽂和状态信息,确保对话的连贯性和⼀致性。由于⼤模型不会为⽤⼾管理会话信息,因此需要程序员⼿动完成会话管理。

会话管理涉及以下内容:

  1. 保存会话数据。由于存在多组会话,每组会话可能有多条聊天消息,在保存会话数据时可以将会话id和具体的会话简历映射关系,⽅便后续查询。<session_id,session>
  2. 创建会话。在和⼤模型建⽴连接前,需要先创建好会话,将后续每次和⼤模型聊天的消息信息要存在该会话中。
  3. 通过会话id获取指定会话。
  4. 向某会话中添加消息。在和⼤模型聊天时,需要将⽤⼾提问、模型恢复的消息保存到会话中。
  5. 获某会话中所有消息。当⽤⼾点击某个会话时,需要将该会话中的历史消息显⽰到界⾯。
  6. 索取所有会话列表。获取所有的会话列表,显⽰在界⾯中。
  7. 删除会话。⽤⼾可以在⻚⾯中删除具体的会话。
  8. 更新会话时间戳。继上次聊完之后,⽤⼾再次打开该会话继续和模型聊天时,需更新会话时间戳。
  9. 清空所有会话
  10. 获取会话总数
  11. ⽣成会话id。需要⽣成唯⼀的会话id来标记具体某条会话。
  12. ⽣成消息id。需要使⽤唯⼀的消息id来标记具体某条消息。
    注意:会话管理模块会保存所有的会话,在同⼀时刻,可能会对多个会话进⾏操作,因此创建会话、更新会话、删除会话等时需要考虑线程安全问题。
会话管理数据结构设计
会话实现

SessionManager.h

SessionManager.cpp





getSessionLists()实际返回的是会话的会话id

能否直接返回会话对象?

答案是可以的,但不够友好,具体的原因如下:

  1. 直接返回会话对象,在返回时候会增加内存拷贝的开销,影响程序的运行效率
  2. SessionManager 中基本都是通过会话 id 来操作对应的回话对象的,如果返回会话对象,其他模块就要对会话对象进行操作,SessionManager 和其他模块的耦合度增高,不便于后期的维护
    所以返回会话对象不是很友好
    而且,会话信息存储在 sqlite 中,获取会话历史消息、删除会话等,都是通过会话 id 来进行的,因此返回会话 id 更有利于 CURD 的操作
    因此返回会话 id 会更好一点
    注意:在返回的这些会话 id 中,最好将会话按照更新的时间戳进行降序排列

数据管理

持久化存储

⽬前在程序中创建的会话以及和模型交互的历史数据都存储在内存中,⽽内存是⼀种带电存储设备,⼀旦断电或者程序重启之后数据就丢失了,想要拿到之前的会话数据就回天乏术了。Deepseek、ChatGPT提供的⽹⻚聊天机器⼈都是⽀持会话存储的,⽅便⽤⼾查看以往的会话记录。因此,ChatSDK中也需要添加数据持久化存储的功能,即将数据永久的保存下来,不会因为断电或关闭程序⽽引起数据丢失。

持久化存储最常⻅的⽅案就是将数据存储到数据库,由于ChatSDK实现好之后,需要共享给其他需要的⽤⼾使⽤,为了减少⽤⼾使⽤成本(使⽤时不需要安装过多依赖或三⽅⼯具),本项⽬采⽤SQLite实现数据存储。

SQLite

SQLite介绍

SQLite Home Page
SQLite 数据类型 | 菜鸟教程

SQLite是⼀个开源、轻量级、零配置、⽆服务器、⾃包含、事务性、⽀持标准SQL、跨平台的数据库管理系统。

  • 开源:免费使⽤,源码透明
  • 轻量级:整个库⾮常⼩,编译之后仅需⼏百KB
  • 零配置:⽆需安装、启动、停⽌或配置任何数据库服务器进程
  • ⽆服务器:SQLite没有独⽴的服务器进程,应⽤程序直接通过函数调⽤读写磁盘上的数据库⽂件
  • ⾃包含:整个数据库就是⼀个独⽴的、跨平台的单个⽂件(通常是.db后缀),⽂件中包含所有表、索引、数据和元信息
  • 事务性:完全⽀持ACID(原⼦性、⼀致性、隔离性、持久性)事务,即使系统崩溃或断电也嗯呢保证数据完整性
  • ⽀持标准SQL:它使⽤SQL语句来管理和查询数据,最多⽀持128TB
  • 跨平台:数据库⽂件跨平台
  • 简单易⽤:只需包含⼀个sqlite3.h和⼀个库⽂件即可
    ⼀般常⽤于嵌⼊式系统、移动开发中。
ubuntu下SQLite命令⾏和开发库安装
c 复制代码
> sudo apt install sqlite3 # 安装 sqlite3 命令⾏⼯具  
> sudo apt install libsqlite3-dev # 安装 sqlite 开发包  
> dpkg -L libsqlite3-dev  # 列出已安装的 libsqlite3-dev 软件包所包含的所有⽂件及其安装路径
/.  
/usr  
/usr/include  
/usr/include/sqlite3.h  
/usr/include/sqlite3ext.h  
/usr/lib  
/usr/lib/x86_64-linux-gnu  
/usr/lib/x86_64-linux-gnu/libsqlite3.a  
/usr/lib/x86_64-linux-gnu/pkgconfig  
/usr/lib/x86_64-linux-gnu/pkgconfig/sqlite3.pc  
/usr/share  
/usr/share/doc  
/usr/share/doc/libsqlite3-dev  
/usr/share/doc/libsqlite3-dev/copyright
/usr/lib/x86_64-linux-gnu/libsqlite3.so  
/usr/share/doc/libsqlite3-dev/changelog.Debian.gz

编译时链接 sqlite3 库

c 复制代码
makefile⽂件:  
g++ main.cpp -o my_program -lsqlite3  

CMakelists.txt:  
target_link_libraries(${SDK_NAME} sqlite3)
终端使⽤介绍

常⽤命令

数据库操作命令

命令 说明 示例
.open 文件名 打开或创建数据库文件 .open student.db
.databases 显示所有连接的数据库 .databases
.attach 文件 as 别名 附加另一个数据库 .attach archive.db as archive
.backup 文件名 备份当前数据库 .backup backup.db
.restore 文件名 从备份恢复数据库 .restore recovery.db
.quit 退出 SQLite .quit
.exit 退出 SQLite .exit
.help 显示帮助信息 .help
表和结构命令
命令 说明 示例
.tables 显示所有表 .tables
.schema [表名] 显示表结构 .schema students
.indexes [表名] 显示索引 .indexes students
注意:sqlite3⽀持标准sql语句。
使⽤演⽰

创建好表之后,sqlite 会根据每个列的数据类型自动生成亲和类型

sql 复制代码
create table test{
 a INT, -- 亲和类型:
 INTEGER b TEXT -- 亲和类型:TEXT 
};

在插入数据的时候,sqlite 会根据实际插入的数据,结合亲和类型,来确定数据实际在磁盘上的存储类

sql 复制代码
insert into test values ('123', 456);
  • a 列 :INT(亲和类型 INTEGER),实际插入的数据'123'字符串,会被转换为整形数据123
  • b 列 :TEXT(亲和类型 TEXT),实际插入的数据456整形数据,会被转化为字符串'456'
    实际在定义表的时候,用户根据列的属性选择合适的数据类型即可,不用关心亲和类型和存储类
    sqlite 会根据偏好设置亲和类型,实际的存储类型取决于偏好类型转化规则和实际插入的数值

查看版本

创建数据库

创建表

查看结构

插入数据,查询数据

删除数据

复制代码
.quit

退出sqlite

常⽤api介绍

包含头文件

c++ 复制代码
#include <sqlite3.h>
c 复制代码
数据库连接管理:  
int sqlite3_open(const char *filename, sqlite3 **ppDb);
功能:打开⼀个数据库连接。如果数据库⽂件不存在,则创建它  
参数:filename - 数据库⽂件的路径,  
	  ppDb:⼀个指向sqlite3*的指针。函数成功后会在这个指针⾥填⼊数据库连接句柄  
返回值:SQLITE_OK 表⽰创建成功,否则为错误码  

int sqlite3_close(sqlite3* db);  
功能:关闭打开的数据库连接,并释放所有资源  
参数:要关闭的数据库连接句柄  
返回值:SQLITE_OK表⽰成功,如果还有未完成的预处理语句 
 
const char *sqlite3_errmsg(sqlite3* db);  
功能:获取与数据库连接相关的最后⼀次错误操作的英⽂描述字符串。在操作失败后调⽤它来获取错误原因  
参数:db数据库连接句柄  
返回值:只想错误信息字符串的指针。如果还有查询没有完成,将返回SQLITE_BUSY禁⽌关闭的错误消息 

执⾏SQL语句  
int sqlite3_prepare_v2( sqlite3 *db,const char *zSql,int nByte,  
						sqlite3_stmt **ppStmt,const char **pzTail);  
功能:将⼀条SQL语句编译成⼀个预处理语句对象,为执⾏和绑定参数做准备  
参数:db - 数据库连接句柄  
	  zSql:要编译的SQL语句⽂本  
	  nByte:SQL⽂本的⻓度(字节),-1表⽰⾃动计算到\0结束  
	  ppStmt:指向sqlite3_stmt*的指针,表⽰输出预处理结果语句的句柄  
	  pzTail:指向未使⽤的SQL部分指针,通常置为NULL  
返回值:编译成功返回SQLITE_K,否则返回错误码  
int sqlite3_bind_int64(sqlite3_stmt*, int index, sqlite3_int64 value);  
功能:向预处理语句中的指定参数位置绑定⼀个64位的整数值  
参数:index - 参数的序号,从1开始  
value - 要绑定的整数值  
返回值:SQLITE_OK表⽰绑定成功,否则为错误码  

int sqlite3_bind_text(sqlite3_stmt*, int index, const char *value, 
								     int len, void(*destructor)(void*));  
功能:向预处理语句的指定参数位置绑定⼀个⽂本字符串  
参数: index - 参数的序号,从1开始  
	  value - 要绑定的⽂本字符串  
	  len - 字符串的⻓度(字节),-1表⽰⾃动计算到\0结束  
	  destructor:⽤于指定SQLite不会尝试释放该字符串  
	  SQLITE_STATIC:SQLite不会尝试释放该字符串  
	  SQLITE_TRANSIENT:SQLite会制作该字符串的副本  
返回值:SQLITE_OK表⽰绑定成功,否则为错误码  

int sqlite3_exec(sqlite3* db, const char *sql,
				int (*callback)(void*, int, char**, char**),void *arg,char **errmsg);  
功能:执⾏⼀条或多条SQL语句,适合不返回数据的语句,如CREATE、INSERT  
参数:db - 数据库连接句柄  
	 sql - 要执⾏的SQL语句字符串  
	 callback:回调函数,每有⼀条结果记录就产⽣⼀次调⽤,可以设置为NULL  
	 arg:传递给回调函数的第⼀个参数  
	 errmsg:只想错误信息的指针。必须⽤sqlite3_free释放  
返回值:执⾏成功返回SQLITE_OK,否则返回错误码  

int sqlite3_step(sqlite3_stmt *pStmt);  
功能:⽤于逐步执⾏预处理语句。⼀般⽤于执⾏查询和更新操作,并返回执⾏结果。对于查询操作,sqlite3_step 需要多次调⽤,直到返回 SQLITE_DONE  
参数:pstmt - 预处理语句句柄  
返回值: SQLITE_ROW - 表⽰有⼀⾏结果数据就绪  
		SQLITE_DONE - 表⽰语句执⾏完成  
		否则表⽰有其他错误  

const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);  
功能:从当前结果⾏中,以const unsigned cht*的形式获取制定⽂本列的值  
参数:iCol表⽰列号,从0开始  
返回值:指向⽂本数据的指针。  

int sqlite3_finalize(sqlite3_stmt *pStmt);  
功能:销毁预处理语句对象,释放所有关联资源  
参数:pstmt - 要销毁的预处理语句句柄  
返回值:成功返回SQLITE_OK,否则为错误码

sqlite3_prepare_v2

c++ 复制代码
insert into student(name, gender, age, gap) values('张三', '男', 18, 3.5) insert into student(name, gender, age, gap) values('李四', '男', 18, 3.5) insert into student(name, gender, age, gap) values(?, ?, ?, ?) 
insert into student(name, gender, age, gap) values(:name, :gender, :age, :gap)

sqlite3_prepare_v2: 函数会对输入的 SQL 语句进行语法检测,如果语法有问题会立马返回错误码

如果语法正确,会将 SQL 语句编译成字节码,这个字节码给 SQLite 的虚拟机执行的形式

编译完成之后,该函数会创建一个sqlite3_stmt的结构,将编译的结果存储在该对象中

使用案例

testSqlite3.cpp









编译

数据管理模块设计

DataManager.h


数据管理模块实现

DataManager.cpp


















会话数据同步到数据库

每次创建新会话、删除会话、更新会话、以及⽣成消息记录等都需要同步到SQLite中,因此让SessionManager类持有⼀个DataManager的对象,当发⽣上述操作时,通过DataManager的对象持久化会话数据。

SessionManager.h

SessionManager.cpp







DataManager.h

DataManager.cpp

相关推荐
SPC的存折1 小时前
在Alpine 搭建 WordPress
linux·运维·服务器·数据库·php
m0_640309301 小时前
如何大幅提升 Google Sheets 数据库更新脚本的执行效率
jvm·数据库·python
杨浦老苏1 小时前
开源数据库备份工具Databasus
数据库·docker·备份·群晖
Greyson11 小时前
CSS如何实现单选按钮自定义样式_利用伪元素隐藏默认UI
jvm·数据库·python
2401_835956812 小时前
Go语言怎么防SQL注入_Go语言SQL注入防护教程【深入】
jvm·数据库·python
杨云龙UP2 小时前
CentOS7.9及以上环境部署TDengine TSDB-OSS实战指南:安装、配置、建库、建超级表与验证_20250418
大数据·linux·运维·数据库·centos·时序数据库·tdengine
m0_514520572 小时前
宝塔面板怎样实现数据库的多地异地自动备份_结合阿里云OSS与定时任务插件
jvm·数据库·python
北漂Zachary2 小时前
四大编程语言终极对决
java·linux·数据库
qq_334563552 小时前
golang如何优化磁盘IO性能_golang磁盘IO性能优化思路
jvm·数据库·python