目录前言一、我们要做的不是“大模型”而是“大模型应用”二、项目最终能做到什么三、项目整体架构3.1 Web UI用户真正操作的界面3.2 ChatServer把 SDK 变成 HTTP 服务四、ChatSDK整个项目真正的核心五、SDK 内部为什么还要继续拆模块5.1 LLMManager统一管理不同模型5.2 SessionManager管理聊天上下文5.3 DataManager把会话保存到 SQLite六、一条消息到底是怎么走的七、项目使用了哪些技术八、当前源码目录先有个印象九、为什么这个项目值得从 SDK 开始做写到最后前言系列从零实现 C AI 大模型接入 SDK第一篇项目源码AI-Chat-SDKhttps://gitee.com/kuang-zhenting/my_ai_cpp_project现在我们已经可以直接在网页或客户端中使用各种大模型但对于开发人员来说只会“打开网页聊天”还不够。真正把大模型接入自己的程序时我们还需要处理 HTTP 请求、JSON 数据、不同厂商的接口差异、流式响应、上下文管理以及聊天记录持久化等问题。这个系列要做的就是从 C 项目的角度把这些问题一步一步解决。最终我们会实现一套自己的ChatSDK上层只需要面对统一接口就可以接入 DeepSeek、ChatGPT、Gemini 以及 Ollama 本地模型。在 SDK 之上再实现一个带网页前端的 AI 聊天助手把模型接入、会话管理、流式输出和历史记录真正串成一个完整项目。这篇作为整个系列的开篇我们先不急着进入具体代码而是先把三个问题搞清楚这个项目最终要做成什么样为什么需要自己封装一层 SDK整个项目由哪些模块组成它们之间是什么关系一、我们要做的不是“大模型”而是“大模型应用”首先要明确一个边界这个项目并不是让我们自己训练一个大语言模型。DeepSeek、ChatGPT、Gemini 这类模型本身已经由厂商训练完成我们要解决的是如何在 C 程序中使用这些模型提供的能力。最直接的方法当然是针对某一家模型写一段 HTTP 请求代码把用户输入发送过去再解析返回结果这样确实能跑。但当项目继续往下做很快就会遇到新的问题不同模型的请求地址不同请求 JSON 格式不同返回 JSON 结构不同流式响应格式和解析方式也可能不同有的模型走云端 API有的模型通过 Ollama 在本地运行聊天不能只发送当前一句话还要维护之前的上下文程序重启后我们还希望历史会话能够继续存在。如果所有这些逻辑都堆在业务代码里后面每增加一种模型聊天业务都会跟着一起改。因此这个项目真正要解决的问题不是“怎么调用一次 API”而是怎么把不同模型的接入细节封装起来对上层提供一套统一、稳定的 C 接口。这就是 ChatSDK 存在的意义。二、项目最终能做到什么按照当前项目源码我们最后得到的是一个完整的网页 AI 聊天助手而不只是几段 API 测试代码。目前项目的主要能力包括接入 DeepSeek、ChatGPT、Gemini 等云端模型通过 Ollama 接入本地模型创建并管理多个聊天会话每个会话绑定自己的模型支持普通完整回复支持基于 SSE 的流式回复使用 SQLite 保存会话和历史消息支持会话重命名支持会话置顶和取消置顶提供 HTTP 接口供前端调用提供网页聊天界面AI 回复支持 Markdown 渲染、代码高亮和代码复制。从使用者的角度看它就是一个聊天应用但从项目实现的角度看真正值得学习的是背后的分层设计。三、项目整体架构先来看最终项目的大致结构整个项目可以从上到下理解成几层。3.1 Web UI用户真正操作的界面最上面是网页前端。用户可以在这里查看历史会话创建新会话并选择模型切换不同会话发送消息查看流式回复重命名、置顶或删除会话。前端本身并不直接访问 DeepSeek、ChatGPT 或 Gemini而是统一请求我们自己的ChatServer。这样做以后浏览器不需要知道不同模型 API 的具体差异也不需要保存云端模型的 API Key。3.2 ChatServer把 SDK 变成 HTTP 服务ChatServer位于应用层它基于cpp-httplib提供 HTTP 接口例如POST /api/sessions GET /api/sessions GET /api/models DELETE /api/sessions/{session_id} PATCH /api/sessions/{session_id}/rename PATCH /api/sessions/{session_id}/pin GET /api/sessions/{session_id}/history POST /api/message POST /api/message/async这里最重要的一点是ChatServer 负责 HTTPChatSDK 负责 AI 聊天业务。也就是说服务器知道怎么接收浏览器请求、怎么返回 JSON、怎么通过 SSE 往前端持续推送数据但它不需要自己实现某个大模型的请求协议。真正的大模型调用会继续交给下面的 ChatSDK。四、ChatSDK整个项目真正的核心ChatSDK是 SDK 对外的统一入口。从当前源码来看上层主要通过它完成模型初始化、会话管理以及消息发送class ChatSDK { public: bool initModels(const std::vectorstd::shared_ptrConfig configs); std::string createSession(const std::string modelName); std::shared_ptrSession getSession(const std::string sessionId); std::vectorstd::string getSessionList() const; bool deleteSession(const std::string sessionId); bool renameSession(const std::string sessionId, const std::string title); bool setSessionPinned(const std::string sessionId, bool pinned); std::vectorModelInfo getAvailableModels() const; std::string sendMessage( const std::string sessionId, const std::string message); std::string sendMessageStream( const std::string sessionId, const std::string message, std::functionvoid(const std::string , bool) callback); };这里暂时不用研究每个函数内部是怎么实现的。我们现在只需要建立一个认识对于 SDK 的使用者来说不需要直接操作某个具体 Provider也不需要关心消息最后发给了哪一家模型。上层只面对ChatSDK底下的模型选择、会话历史、请求转发和数据保存由 SDK 内部继续分工。这也是整个项目后面所有设计的主线。五、SDK 内部为什么还要继续拆模块如果把所有事情都塞进ChatSDK这个类很快也会变得非常庞大。所以当前项目继续把职责拆成了几个核心模块。5.1 LLMManager统一管理不同模型LLMManager负责管理所有 Provider。项目定义了统一的ILLMProvider接口然后让不同模型分别实现自己的 ProviderILLMProvider ├── DeepSeekProvider ├── ChatGPTProvider ├── GeminiProvider └── OllamaLLMProvider这样LLMManager在发送消息时只需要根据模型名称找到对应 Provider再调用统一接口即可。至于某个 Provider 内部使用什么 URL、请求体怎么组织、响应怎么解析都由它自己负责。这解决的是多模型之间的差异问题。5.2 SessionManager管理聊天上下文大模型聊天和普通的一次 HTTP 请求不同。如果用户连续问我推荐一部科幻电影 AI…… 我为什么推荐它第二个问题要想回答正确就必须把前面的聊天内容一起提供给模型。因此项目中使用SessionManager管理会话。每个 Session 会记录会话 ID当前使用的模型历史消息创建和更新时间会话标题置顶状态等信息。这解决的是一次次独立请求如何组成连续聊天的问题。5.3 DataManager把会话保存到 SQLite如果历史消息只存在内存中一旦服务器退出之前的聊天记录就全部丢失了。所以SessionManager后面还有DataManager负责使用 SQLite 保存会话信息消息记录标题置顶状态时间信息。这样服务器重新启动以后历史会话仍然可以恢复。六、一条消息到底是怎么走的有了上面的模块划分以后一条消息从网页发送出去大致会经过下面这条链路以一次流式聊天为例用户在网页输入消息前端调用ChatServer的流式消息接口ChatServer把session_id和用户消息交给ChatSDKChatSDK根据 Session 找到当前会话绑定的模型用户消息先加入会话历史LLMManager找到对应的 ProviderProvider 按当前模型的协议发起请求模型返回的数据通过回调逐段向上交付ChatServer使用 SSE 把片段持续推送给网页本轮结束后完整的助手回复再写入会话并持久化到 SQLite。这一条数据流其实就是整个项目最核心的主线。后面的很多代码看起来模块很多但只要始终记住Web UI ↓ ChatServer ↓ ChatSDK ├── LLMManager → Provider → LLM └── SessionManager → DataManager → SQLite再去看具体类时就不会容易迷路。七、项目使用了哪些技术这个项目主体使用 C17整体依赖并不算特别夸张但基本覆盖了一个完整网络应用会碰到的几个方向。技术 / 库在项目中的作用C17SDK 和 ChatServer 的主要开发语言CMakeSDK 与服务器的构建、安装cpp-httplibHTTP 客户端和 HTTP ServerOpenSSLHTTPS 请求支持jsoncppJSON 请求与响应解析SQLite3会话和消息持久化spdlog fmt日志输出与格式化gflagsChatServer 命令行参数与配置HTML / CSS / JavaScript网页聊天界面SSE将模型生成内容流式推送到浏览器前端还使用了marked、highlight.js和DOMPurify分别用于 Markdown 解析、代码高亮以及 HTML 内容清理。大家不需要在第一篇就把这些库全部学会。它们会随着项目推进逐渐出现到真正需要时再理解反而更容易建立联系。八、当前源码目录先有个印象当前项目的主要目录可以先简化理解成这样my_ai_cpp_project ├── SDK/ │ ├── include/ │ └── src/ │ ├── ChatServer/ │ ├── www/ │ ├── ChatServer.h │ ├── ChatServer.cpp │ └── main.cpp │ ├── TEST/ ├── SQL_TEST/ ├── create_ollama_service.sh └── README.md其中SDK/是整个系列最核心的部分ChatServer/把 SDK 封装成可以供网页调用的 HTTP 服务ChatServer/www/保存前端页面TEST/、SQL_TEST/用于项目开发过程中的测试README.md记录最终项目的构建、配置和使用方式。第一眼看到这些目录时不用急着逐个研究。我们后面会按照项目真正的依赖顺序一层一层把它们搭起来。九、为什么这个项目值得从 SDK 开始做如果我们的目标只是“让 C 调一次 DeepSeek”几十行代码就可能完成。但这种代码很难继续扩展。这个项目真正有价值的地方是在一次次功能增加的过程中逐渐建立出统一接口 ↓ 不同 Provider ↓ 模型统一管理 ↓ 会话与上下文管理 ↓ SQLite 持久化 ↓ ChatSDK 对外封装 ↓ HTTP Server ↓ 网页聊天应用也就是说我们最后得到的不只是“会调用大模型 API”而是能够理解如何把一个外部 AI 能力逐渐封装成自己项目里可以长期使用的模块。这也是我整理这个系列时最想保留下来的主线。写到最后到这里我们已经知道这个项目最终要做什么也知道了各个核心模块之间的大致关系。接下来不需要立刻钻进 Provider 的 HTTP 代码。在真正开始接入模型之前我们还需要先补齐少量和大模型调用直接相关的基础概念并把开发环境准备好。等这些准备完成以后再正式进入 ChatSDK 的实现。后面的文章里我们会从最底层开始一步一步把上面的架构真正写出来。