ChatGPT 模型接入 SDK 的 C++ 实现细节

0 阅读

多模型接入架构与 ChatGPT 接入概述

这套 SDK 采用抽象提供者模式来统一不同大模型的接入方式。核心是一个叫 ILLMProvider 的抽象基类,所有具体的模型实现——比如 DeepSeek、Gemini 或 ChatGPT——都得继承它并实现标准方法。

文章配图

之前已经完整接入了 DeepSeek,包括初始化、全量响应和流式响应。这次做 ChatGPT 接入时,直接复制了 DeepSeekProvider 的头文件和源文件,只改了几处关键地方:类名、API 根地址、请求参数格式,还有怎么从返回的 JSON 里抠出真正的回答。这样省了不少事,大概八成代码都能直接复用。

文章配图

要跑起来,你得先去 OpenAI 官网申请一个 API Key,还得确保你的机器能正常访问 api.openai.com

这里得澄清一个常见误解:我们接的不是“ChatGPT”这个产品,而是它背后的具体模型。这次选的是 gpt-4o-mini,这是 GPT-4 系列里的轻量版,便宜又快,适合大多数常规任务。

在这里插入图片描述

ChatGPTProvider 头文件设计

在这里插入图片描述

头文件放在 sdk/include/ChatGPTProvider.h,对应的实现是 sdk/src/ChatGPTProvider.cpp

在这里插入图片描述

项目依赖两个第三方库:jsoncpp 用来处理 JSON 数据,httplib 负责发 HTTP 请求。内部还用到了自定义的日志工具 myLog.h、抽象基类 ILLMProvider.h,以及一个通用的 Message 结构体来表示聊天历史。

头文件里声明了 ChatGPTProvider 类,它继承自 ILLMProvider,主要实现这几个方法:

  • initModel:读取配置,主要是 API Key 和可选的 endpoint。
  • isAvailable:返回模型是否已成功初始化。
  • getModelNamegetModelDesc:提供模型的标识和简短描述。
  • sendMessageFull:同步调用,一次性拿回完整回答。
  • sendMessageStream:异步流式调用,通过回调函数一块一块地接收数据。

私有成员很简单,就三个:_api_key 存密钥,_endpoint 存 API 地址,默认是 https://api.openai.com,还有一个 _isAvailable 布尔值标记状态。

ChatGPTProvider 源文件核心实现

初始化与基础配置

源文件开头就是常规操作,引入头文件和命名空间。initModel 方法是入口,逻辑很直接:

首先,它在传进来的 model_config map 里找 "api_key"。如果找不到,就记一条错误日志,直接返回 false。这一步不能含糊,没密钥什么都干不了。

接着找 "endpoint"。如果用户没配,就用 OpenAI 的官方默认地址。配好了之后,把 _isAvailable 设成 true,再打一条 info 日志,把密钥(脱敏显示)和 endpoint 打出来方便调试。整个过程没有多余的花哨操作,就是最朴素的配置加载。

状态与元信息

isAvailable 方法直接返回那个私有布尔值,简单到不用解释。

getModelName 返回的是 "gpt-4o-mini",而不是笼统的 "ChatGPT"。这样做是为了在 SDK 内部能精确区分不同的模型实例,毕竟同一个厂商也可能有多个模型。

getModelDesc 返回一句人话描述:"OpenAI推出的轻量级、高性价比模型,核心能力接近GPT-4 Turbo但成本更低"。这个描述会出现在日志或者调试信息里,让开发者一眼看懂这个模型是干嘛的。

消息发送接口

sendMessageFullsendMessageStream 是两个核心功能,但它们的骨架和 DeepSeek 的实现几乎一样。

对于 sendMessageFull,流程是这样的:

  1. 构造一个符合 OpenAI 格式的 JSON 请求体,里面包含 model(固定为 gpt-4o-mini)、messages 数组,以及其他可选参数。
  2. 设置 HTTP 请求头,最关键的是 Authorization: Bearer <your-api-key>
  3. https://api.openai.com/v1/chat/completions 发一个 POST 请求。
  4. 收到响应后,用 jsoncpp 解析。如果状态码不是 200,就把错误信息塞给 reason 参数并返回空字符串。
  5. 如果成功,就从 choices[0].message.content 路径下取出文本,作为结果返回。

sendMessageStream 的逻辑更复杂一点,因为它要处理流式数据。它同样构造请求,但会额外加上 "stream": true 字段。然后使用 httplib 的流式客户端功能,一边接收服务器发来的 SSE(Server-Sent Events)数据块,一边实时解析。

每个数据块也是一个 JSON 对象,有效内容在 choices[0].delta.content 里。SDK 会把这些增量内容拼起来,并通过传入的 callback 函数通知上层。callback 的第二个参数是个布尔值,当收到 [DONE] 标记时,就把它设为 true,告诉调用方“最后一块了”。

这两个接口的差异点其实就集中在请求 URL、认证头和 JSON 解析路径上。正因为底层网络和 JSON 库是通用的,所以复用性才这么高。这也是抽象架构带来的好处——换一个模型,基本上就是改个配置和几行解析代码的事。

总的来说,这次接入没有发明新轮子,而是在已有框架下做了一次干净利落的适配。重点在于理解 OpenAI API 的具体要求,并将其精准地映射到 SDK 的统一接口上。