目录

1. 功能分析

2. 接口设计

2.1 RESTful API

2.2 接口设计

获取会话列表

获取可用模型

创建新会话

获取历史消息

发送消息-全量返回

发送消息-流式响应

删除会话

3. 服务器开发

3.1 项目配置 && 服务器实现

3.2 main函数实现

3.3 前端页面

4. 项目扩展


1. 功能分析

在浏览器中输入服务器地址后,服务器会返回一个如下网页:

连上服务器之后,需要能获取到之前的会话历史记录。

点击新建对话,要能列出支持的模型,供用户进行选择:

当用户选择具体模型之后,需要创建一个新的会话,用来管理用户后续与该模型的聊天信息。假设用户选择deepseek-chat模型,点击确认创建对话按钮之后,在主页面会话列表位置会多一个会话:

会话列表中管理的会话是按照会话更新时间降序排列的。
用户选择具体的会话后,就可以和该大模型聊天了,比如选择最新的会话,点击之后就可以和大模型聊天了:

用户将鼠标悬停在某个会话上,在会话右下角会显示删除按钮,点击该按钮后可以删除该会话。

通过以上介绍,该聊天demo中涉及到以下功能点:

  • 获取会话列表
  • 获取支持的模型
  • 新建会话
  • 发送消息(默认以流式返回)
  • 获取会话历史
  • 删除会话

2. 接口设计

2.1 RESTful API

由于整个和大模型交互都是基于HTTP协议,聊天助手将来也是浏览器借助HTTP协议和服务器交互,因此本项目接口采用RESTfulAPI风格进行设计,它是基于HTTP协议的应用接口设计规划,提供了一种通过标准化操作和资源访问模式进行客户端和服务器通信的方式。

REST是Representational State Transfer的缩写,翻译过来就是表现层状态转移。

  • 资源(Resource)

RESTful API中的每一个对象、实体或数据都被抽象为一个资源。例如,用户、文章等都可以
作为资源。每个资源都通过一个唯一的URI(统一资源标识符)标识。

  • URI(统一资源标识)

URI是用于标识资源的地址。RESTfulAPI中,通常使用URL(统一资源定位符)作为URI。例如:
/users/123表示id为123的用户资源
/posts/456表示id为456的文章资源

  • HTTP动作(HTTP Methods)

RESTfuLAPI依赖于HTTP协议的常见方法来对资源进行操作,每个HTTP方法对应不同的操作:
GET:获取资源
POST:创建新的资源
PUT:更新资源
DELETE:删除资源

  • 无状态

每个请求都是独立的,服务器不会保存客户端任何会话状态。客户端发来的每一个请求,都必须包含服务器处理该请求所需的所有信息。

  • 表现层状态转移(Representational State Transfer)

资源的表现形式可以是JSON、XML、HTML等格式,通常RESTfuL API使用JSON作为数据交换格式,因为它轻量且易于解析。

请求URL: POST /api/user/id

请求参数:

字段名称 字段类型 字段说明 示例
requestld string 请求Id
{
    "requestId": "string",
    "sessionId": "string",
    "name": "string",
    "email": "string"
}

sessionId string 客户端会话Id
name string 用户名
email string 邮箱

返回响应: 200 OK

字段名称 字段类型 字段说明 示例
requestId string 请丢Id
{
    "requestId": "string",
    "errorCode": 0,
    "errorMsg": "",
    "result": {
        "userId": "string"
    }
}

errorCode integer 错误码:0成功
errorMsg string 错误信息
result object 响应结果
userId string 用户Id

2.2 接口设计

获取会话列表

请求URL:GET /api/sessions

返回响应:200 OK

字段名称 字段类型 字段说明 示例
success bool 是否成功
{
    "success": "bool",
    "message": "string",
    "data": "array"[
        {
            "id": "string",
            "model": "string",
            "created_at": "int64_t",
            "updated_at": "int64_t",
            "message_count": "int",
            "first_user_message": "string"
        }
    ]
}

message string 结果描述
data array 响应数据
id string 会话id
model string 模型名称
create_at int64_t 创建时间戳
update_at int64_t 更新时间戳
message_count int 对话次数
first_user_message string 第一条正文消息

获取可用模型

请求URL:GET /api/models

返回响应:200 OK

字段名称 字段类型 字段说明 示例
success bool 是否成功
{
    "success": "bool",
    "message": "string",
    "data": "array"[
        {
            "name": "string",
            "desc": "string"
        }
    ]
}

message string 结果描述
data array 响应数据
name string 模型名称
desc string 模型描述

创建新会话

请求URL: POST /api/session

请求参数:

字段名称 字段类型 字段说明 示例
model string 模型名称
{
    "model": "string"
}

返回响应: 200 OK

字段名称 字段类型 字段说明 示例
success bool 是否成功
{
    "success": "bool",
    "message": "string",
    "data": {
        "session_id": "string",
        "model": "string"
    }
}

message string 结果描述
data object 响应数据
session_id string 会话id
model string 模型名称

获取历史消息

请求URL:GET /api/session/$[session_id}/history

返回响应:200 OK

字段名 类型 说明 示例
success bool 是否成功
{
    "success": "bool",
    "message": "string",
    "data": "array"[
        {
            "id": "string",
            "role": "string",
            "content": "string",
            "timestamp": 0,
        }
    ]
}

message string 结果描述
data array 响应数据
id string 消息id
role string 消息类型
content string 消息正文
timestamp int64_t 消息时间戳(秒级)

发送消息-全量返回

请求URL:POST /api/message

请求参数:

字段名称 字段类型 字段说明 示例
session_id string 会话id
{
    "session_id": "string",
    "message": "string"
}

message string 消息内容

返回响应:200 OK

字段名称 字段类型 字段说明 示例
success bool 是否成功
{
    "success": "bool",
    "message": "string",
    "data": {
        "session_id": "string",
        "response": "string"
    }
}

message string 结果描述
data object 响应数据
session_id string 会话id
response string 模型返回

发送消息-流式响应

请求URL:POST /api/message/async

请求参数:

字段名称 字段类型 字段说明 示例
session_id string 会话id
{
    "session_id": "string",
    "message": "string"
}

message string 消息内容

返回响应:200 0K
流式响应,格式如下

data: 正⽂
data: 正⽂
data: 正⽂
data: [DONE]

删除会话

请求URL:DELETE /api/session/$[session_id}

返回响应:200 OK

字段名称 字段类型 字段说明 示例
success bool 是否成功
{
    "success": "bool",
    "message": "string"
}

message string 结果描述

3. 服务器开发

3.1 项目配置 && 服务器实现

服务器自身需要一些配置参数,比如服务器ip、端口等,在使用ChatSDK接入大模型时,也需要提供一些模型需要的参数,比如温度值、最大tokens数、apikey等。

/*************************ChatServer.h*************************/

#pragma once
#include <httplib.h>
#include <memory>
#include <ai_chat_sdk/ChatSDK.h>
#include <string>

namespace ai_chat_server
{
    struct ServerConfig
    {
        std::string host = "0.0.0.0";   // 服务器
        int port = 8080;                // 服务器绑定端口
        std::string logLevel = "INFO";  // 日志级别

        // 模型需要的配置信息
        double temperature = 0.7;   // 温度参数
        int maxTokens = 1024;       // 最大token数

        // API Key
        std::string deepseekAPIKey; // deepseek API Key
        std::string geminiAPIKey;   // gemini API Key
        std::string chatGPTAPIKey;  // chatGPT API Key

        // Ollama
        std::string ollamaModelName ;   //  Ollama模型名称
        std::string ollamaModelDesc;    // Ollama模型描述
        std::string ollamaEndpoint;     // ollama API地址
    };


    class ChatServer
    {
    public:
        ChatServer(const ServerConfig& config);

        bool start();   // 启动服务器
        void stop();    // 停止服务器
        bool isRunning() const;  // 是否正在运行
    
    private:
        // 构造错误响应
        std::string buildResponse(const std::string& message, bool success = false);
        // 处理创建会话请求
        void handleCreateSessionRequest(const httplib::Request& request,httplib::Response& response);
        // 处理获取会话列表请求
        void handleGetSessionListsRequest(const httplib::Request& request,httplib::Response& response);
        // 处理获取模型列表请求
        void handleGetModelListsRequest(const httplib::Request& request,httplib::Response& response);
        // 处理删除会话请求
        void handleDeleteSessionRequest(const httplib::Request& request,httplib::Response& response);
        // 处理获取历史消息请求
        void handleGetHistoryMessageRequest(const httplib::Request& request,httplib::Response& response);
        // 处理发送消息请求 - 全量返回
        void handleSendMessageRequest(const httplib::Request& request,httplib::Response& response);
        // 处理发送消息请求 - 增量返回
        void handleSendMessageStreamRequest(const httplib::Request& request,httplib::Response& response);

        // 设置HTTP路由规则
        void setHttpRoutes();
    private:
        ServerConfig _config;
        std::unique_ptr<httplib::Server> _chatServer = nullptr;    //HTTP服务器
        std::shared_ptr<ai_chat_sdk::ChatSDK> _chatSDK = nullptr; //聊天SDK
        std::atomic<bool> _isRunning = {false};    // 是否正在运行
    };
}
/*************************ChatServer.cpp*************************/

#include "ChatServer.h"
#include <ai_chat_sdk/util/mylog.h>
#include <httplib.h>
#include <jsoncpp/json/forwards.h>
#include <jsoncpp/json/value.h>
#include <jsoncpp/json/reader.h>
#include <jsoncpp/json/writer.h>



namespace ai_chat_server{

ChatServer::ChatServer(const ServerConfig& config){
    _chatSDK = std::make_shared<ai_chat_sdk::ChatSDK>();

    auto deepseekConfig = std::make_shared<ai_chat_sdk::APIConfig>();
    deepseekConfig->_modelName = "deepseek-chat";
    deepseekConfig->_apikey = config.deepseekAPIKey;
    deepseekConfig->_temperature = config.temperature;
    deepseekConfig->_maxTokens = config.maxTokens;

    // gpt-4o-mini
    auto chatGPTConfig = std::make_shared<ai_chat_sdk::APIConfig>();
    chatGPTConfig->_modelName = "gpt-4o-mini";
    chatGPTConfig->_apikey = config.chatGPTAPIKey;
    chatGPTConfig->_temperature = config.temperature;
    chatGPTConfig->_maxTokens = config.maxTokens;


    // gemini-2.0-flash
    auto geminiConfig = std::make_shared<ai_chat_sdk::APIConfig>();
    geminiConfig->_modelName = "gemini-2.0-flash";
    geminiConfig->_apikey = config.geminiAPIKey;
    geminiConfig->_temperature = config.temperature;
    geminiConfig->_maxTokens = config.maxTokens;

    // Ollama本地接入deepseek-r1:1.5b
    auto ollamaConfig = std::make_shared<ai_chat_sdk::OllamaConfig>();
    ollamaConfig->_modelName = config.ollamaModelName;
    ollamaConfig->_modelDesc = config.ollamaModelDesc;
    ollamaConfig->_endpoint = config.ollamaEndpoint;
    ollamaConfig->_temperature = config.temperature;
    ollamaConfig->_maxTokens = config.maxTokens;

    std::vector<std::shared_ptr<ai_chat_sdk::Config>> modelConfigs = {
        deepseekConfig, chatGPTConfig, geminiConfig, ollamaConfig
    };

    INFO("start init ChatSDK models...");
    if(!_chatSDK->initModels(modelConfigs)){
        ERR("ChatSDK init Failed!!!");
        return;
    }
    INFO("ChatSDK models init success!!!");

    // 创建http服务器
    _chatServer = std::make_unique<httplib::Server>();
    if(!_chatServer){
        ERR("ChatServer init Failed!!!");
        return;
    }
}

bool ChatServer::start(){
    if(_isRunning.load()){
        ERR("ChatServer is running!!!");
        return false;
    }

    // 设置路由规则
    setHttpRoutes();

    // 设置静态资源的路径
    // 前端页面相关的所有文件都放在www目录下  注意:将来前端页面名称命名为index.html
    // 当用户在浏览器中输入:http://ip:port/index.html    http://ip:port也能访问index.html页面
    // 在httplib中,默认情况下,如果请求路径中只有ip和端口,httplib默认会使用index.html文件
    _chatServer->set_mount_point("/", "./www");

    // 为了不卡服务器云不卡主线程,服务器在单独的线程中运行
    std::thread serverThread([this](){
        _chatServer->listen(_config.host, _config.port);
        INFO("ChatServer start on {} :{}", _config.host, _config.port);
    });

    serverThread.detach();
    _isRunning.store(true);
    INFO("ChatServer start success!!!");
    return true;
}

void ChatServer::stop(){
    if(!_isRunning.load()){
        ERR("ChatServer is not running!!!");
        return;
    }

    if(_chatServer){
        _chatServer->stop();
    }

    _isRunning.store(false);
    INFO("ChatServer stop success!!!");
}

bool ChatServer::isRunning() const{
    return _isRunning.load();
}

// 构造响应
std::string ChatServer::buildResponse(const std::string& message, bool success){
    Json::Value responseJson;
    responseJson["success"] = success;
    responseJson["message"] = message;

    // 序列化
    Json::StreamWriterBuilder writerBuilder;
    return Json::writeString(writerBuilder, responseJson);
}

// 处理创建会话请求
void ChatServer::handleCreateSessionRequest(const httplib::Request& request, httplib::Response& response)
{
    // 获取请求参数,请求参数在请求体
    // 通过反序列化拿到请求体的json格式
    Json::Value requestJson;
    Json::Reader reader;
    if(!reader.parse(request.body, requestJson)){
        std::string errorJsonStr = buildResponse("parse request body failed, json format error");
        response.status = 400; // 客户端发送的请求有语法错误,服务器无法理解或处理该请求
        response.set_content(errorJsonStr, "application/json");
        return;
    }

    // 获取请求参数
    std::string modelName = requestJson.get("model", "deepseek-chat").asString();

    // 创建会话
    std::string sessionID = _chatSDK->createSession(modelName);
    if(sessionID.empty()){
        std::string errorJsonStr = buildResponse("create session failed");
        response.status = 500; // 服务器内部错误,无法完成请求
        response.set_content(errorJsonStr, "application/json");
        return;
    }

    // 构建响应体
    Json::Value dataJson;
    dataJson["session_id"] = sessionID;
    dataJson["model"] = modelName;

    Json::Value responseJson;
    responseJson["success"] = true;
    responseJson["message"] = "create session success";
    responseJson["data"] = dataJson;

    // 序列化
    Json::StreamWriterBuilder writerBuilder;
    std::string responseJsonStr = Json::writeString(writerBuilder, responseJson);

    response.status = 200; // 成功
    response.set_content(responseJsonStr, "application/json");
}


// 处理获取会话列表请求
void ChatServer::handleGetSessionListsRequest(const httplib::Request& request, httplib::Response& response)
{
    // 获取会话列表
    std::vector<std::string> sessionIDs = _chatSDK->getSessionLists();

    // 构建session信息
    Json::Value dataArray(Json::arrayValue);
    for(const auto& sessionID : sessionIDs){
        auto session = _chatSDK->getSession(sessionID);
        if(session){
            Json::Value sessionJson;
            sessionJson["id"] = session->_sessionId;
            sessionJson["model"] = session->_modelName;
            sessionJson["created_at"] = static_cast<int64_t>(session->_createdAt);
            sessionJson["updated_at"] = static_cast<int64_t>(session->_updatedAt);
            sessionJson["message_count"] = session->_messages.size();
            if(!session->_messages.empty()){
                sessionJson["first_user_message"] = session->_messages.front()._content;
            }

            dataArray.append(sessionJson);
        }
    }

    // 构建响应体
    Json::Value responseJson;
    responseJson["success"] = true;
    responseJson["message"] = "get session lists success";
    responseJson["data"] = dataArray;

    // 序列化
    Json::StreamWriterBuilder writerBuilder;
    std::string responseJsonStr = Json::writeString(writerBuilder, responseJson);

    response.status = 200; // 成功
    response.set_content(responseJsonStr, "application/json");
}

// 处理获取模型列表请求
void ChatServer::handleGetModelListsRequest(const httplib::Request& request, httplib::Response& response)
{
    // 获取支持的模型列表
    auto modelLists = _chatSDK->getAvailableModels();

    // 构建响应体
    Json::Value dataArray(Json::arrayValue);
    for(const auto& modelInfo : modelLists){
        Json::Value modelJson;
        modelJson["name"] = modelInfo._modelName;
        modelJson["desc"] = modelInfo._modelDesc;
        dataArray.append(modelJson);
    }

    // 构建响应体
    Json::Value responseJson;
    responseJson["success"] = true;
    responseJson["message"] = "get model lists success";
    responseJson["data"] = dataArray;

    // 序列化
    Json::StreamWriterBuilder writerBuilder;
    std::string responseJsonStr = Json::writeString(writerBuilder, responseJson);

    response.status = 200; // 成功
    response.set_content(responseJsonStr, "application/json");
}
// 处理删除会话请求
void ChatServer::handleDeleteSessionRequest(const httplib::Request& request, httplib::Response& response)
{
    // 获取会话id,注意:会话id是一个路径参数
    std::string sessionId = request.matches[1];

    // 删除会话
    bool ret = _chatSDK->deleteSession(sessionId);
    if(ret){
        std::string errorJsonStr = buildResponse("delete session success", true);
        response.status = 200; 
        response.set_content(errorJsonStr, "application/json");
    }else{
        std::string errorJsonStr = buildResponse("delete session failed, session not found");
        response.status = 404;  // 会话不存在
        response.set_content(errorJsonStr, "application/json");
    }

}

// 处理获取历史消息请求
void ChatServer::handleGetHistoryMessageRequest(const httplib::Request& request, httplib::Response& response)
{
    // 获取会话id
    std::string sessionId = request.matches[1];
    // 获取会话
    auto session = _chatSDK->getSession(sessionId);
    if(!session){
        std::string errorJsonStr = buildResponse("session not found");
        response.status = 404;  // 会话不存在
        response.set_content(errorJsonStr, "application/json");
        return;
    }

    // 构建历史消息列表
    Json::Value dataArray(Json::arrayValue);
    for(const auto& message : session->_messages){
        Json::Value messageJson;
        messageJson["id"] = message._messageId;
        messageJson["role"] = message._role;
        messageJson["content"] = message._content;
        messageJson["timestamp"] = static_cast<int64_t>(message._timestamp);
        dataArray.append(messageJson);
    }

    // 构建响应体
    Json::Value responseJson;
    responseJson["success"] = true;
    responseJson["message"] = "get history messages success";
    responseJson["data"] = dataArray;

    // 序列化
    Json::StreamWriterBuilder writerBuilder;
    std::string responseJsonStr = Json::writeString(writerBuilder, responseJson);

    response.status = 200; // 成功
    response.set_content(responseJsonStr, "application/json");
}


// 处理发送消息请求-全量返回
void ChatServer::handleSendMessageRequest(const httplib::Request& request, httplib::Response& response)
{
    // 获取请求参数
    Json::Value requestJson;
    Json::Reader reader;
    if(!reader.parse(request.body, requestJson)){
        std::string errorJsonStr = buildResponse("parse request body failed, json format error");
        response.status = 400;  // 解析请求参数失败
        response.set_content(errorJsonStr, "application/json");
        return;
    }

    // 解析请求参数
    std::string sessionId = requestJson["session_id"].asString();
    std::string message = requestJson["message"].asString();
    if(sessionId.empty() || message.empty()){
        std::string errorJsonStr = buildResponse("session_id or message is empty");
        response.status = 400;  // 解析请求参数失败
        response.set_content(errorJsonStr, "application/json");
        return;
    }

    // 发送消息
    std::string assistantMessage = _chatSDK->sendMessage(sessionId, message);
    if(assistantMessage.empty()){
        std::string errorJsonStr = buildResponse("Failed to send AI response message");
        response.status = 500;  // 发送消息失败
        response.set_content(errorJsonStr, "application/json");
        return;
    }

    // 构造响应参数
    Json::Value dataJson;
    dataJson["session_id"] = sessionId;
    dataJson["response"] = assistantMessage;
    dataJson["data"]["assistant_message"] = assistantMessage;

    // 构建响应体
    Json::Value responseJson;
    responseJson["success"] = true;
    responseJson["message"] = "send message success";
    responseJson["data"] = dataJson;

    // 序列化
    Json::StreamWriterBuilder writerBuilder;
    std::string responseJsonStr = Json::writeString(writerBuilder, responseJson);

    response.status = 200; // 成功
    response.set_content(responseJsonStr, "application/json");
}



// 处理发送消息请求-增量返回
void ChatServer::handleSendMessageStreamRequest(const httplib::Request& request, httplib::Response& response)
{
    // 获取请求参数
    Json::Value requestJson;
    Json::Reader reader;
    if(!reader.parse(request.body, requestJson)){
        std::string errorJsonStr = buildResponse("parse request body failed, json format error");
        response.status = 400;  // 解析请求参数失败
        response.set_content(errorJsonStr, "application/json");
        return;
    }

    // 解析请求参数
    std::string sessionId = requestJson["session_id"].asString();
    std::string message = requestJson["message"].asString();
    if(sessionId.empty() || message.empty()){
        std::string errorJsonStr = buildResponse("session_id or message is empty");
        response.status = 400;  // 解析请求参数失败
        response.set_content(errorJsonStr, "application/json");
        return;
    }

    // 准备流式响应
    response.status = 200; // 成功
    response.set_header("Cache-Control", "no-cache");              // 不使用缓存,服务器立即将数据发送到网络
    response.set_header("Connection", "keep-alive");               // 保持连接,服务器不会关闭连接
    response.set_header("Access-Control-Allow-Origin", "*");        // 允许跨域请求
    response.set_header("Access-Control-Allow-Headers", "*");      // 允许所有请求头

    // set_chunked_content_provider:告诉服务器,响应内从不是一次性发送的,而是分多次逐步发送给客户端,一般用在实时生成响应内容 或者 流式数据传输场景
    // 
    response.set_chunked_content_provider("text/event-stream", [this, sessionId, message](size_t offset, httplib::DataSink& dataSink)->bool{

        auto writeChunk = [&](const std::string& chunk, bool last){ 
            // 将chunk转换为SSE数据格式
            // Json::valueToQuotedString: 对chunk进行Json转换,目的防止chunk中包含一些特殊字符来破坏数据格式,比如:在chunk中包含了两个连续的换行,就会影响SSE数据格式
            std::string sseData = "data: " + Json::valueToQuotedString(chunk.c_str()) + "\n\n";

            // 需要将模型返回的结果 chunk 发送给客户单
            dataSink.write(sseData.c_str(), sseData.size());  // 将数据写入响应流,即立即发送给客户单,该方法不会等待缓冲区满之后发送

            // 处理结束标记
            if(last){
                // 流向响应结束
                std::string doneData = "data: [DONE]\n\n";
                dataSink.write(doneData.c_str(), doneData.size());
                dataSink.done();    // 表示流式响应结束
                return false;       // 不再有后续数据
            }
            return true;
        };
        
        // 先给客户端发送一个空的数据块,避免客户端长时间的等待
        if (!writeChunk("", false)) {
            return false;
        }
        
        // 发送消息流
        _chatSDK->sendMessageStream(sessionId, message, writeChunk);

        return false;   // 不再有后续数据
    });
}

// 设置HTTP路由规则
void ChatServer::setHttpRoutes(){
    // 处理创建会话请求
    _chatServer->Post("/api/session", [this](const httplib::Request& request, httplib::Response& response){
        handleCreateSessionRequest(request, response);
    });

    // 处理获取会话列表请求
    _chatServer->Get("/api/sessions", [this](const httplib::Request& request, httplib::Response& response){
        handleGetSessionListsRequest(request, response);
    }); 

    // 处理获取模型列表请求
     _chatServer->Get("/api/models", [this](const httplib::Request& request, httplib::Response& response){
        handleGetModelListsRequest(request, response);
    });

    // 处理删除会话请求
     _chatServer->Delete("/api/session/(.*)", [this](const httplib::Request& request, httplib::Response& response){
        handleDeleteSessionRequest(request, response);
    });

    // 处理获取历史消息请求
     _chatServer->Get("/api/session/(.*)/history", [this](const httplib::Request& request, httplib::Response& response){
        handleGetHistoryMessageRequest(request, response);
    });

    // 处理发送消息请求-全量返回
    _chatServer->Post("/api/message", [this](const httplib::Request& request, httplib::Response& response){
        handleSendMessageRequest(request, response);
    });

    // 处理发送消息请求-增量返回
    _chatServer->Post("/api/message/async", [this](const httplib::Request& request, httplib::Response& response){
        handleSendMessageStreamRequest(request, response);
    });
}

}

3.2 main函数实现

1. 配置参数定义

/***************************main.cpp***************************

// 定义gflags参数
DEFINE_string(host, "0.0.0.0", "服务器绑定的地址");
DEFINE_int32(port, 8080, "服务器绑定的端口号");
DEFINE_bool(daemon, true, "是否以后台守护进程方式运行");
DEFINE_string(log_file, "", "日志输出文件路径(默认为stdout,守护进程模式下默认为server.log)");
DEFINE_string(log_level, "INFO", "日志级别");
DEFINE_double(temperature, 0.7, "温度值,影响生成文本的随机性");
DEFINE_int32(max_tokens, 2048, "最大token数");
DEFINE_string(config_file, "./ChatServer.conf", "配置文件路径");
// DEFINE_bool(version, false, "显示版本信息");
// Ollama配置参数
DEFINE_string(ollama_model_name, "", "Ollama模型名称");
DEFINE_string(ollama_model_desc, "", "Ollama模型描述");
DEFINE_string(ollama_endpoint, "", "Ollama API地址");

由于apikey比较隐私,避免在程序中暴露,可以提前将各个apikey配置在环境变量中。
在~/.bashrc中添加下列环境变量:

# api key
export deepseek_apikey="your deepseek api key"
export chatgpt_apikey="your chatgpt api key"
export gemini_apikey="your gemini api key"

添加完成之后,执行命令source~/.bashrc让添加的环境变量生效。

ubuntu@VM-12-14-ubuntu:~$ echo $deepseek_apikey
ubuntu@VM-12-14-ubuntu:~$ source ~/.bashrc 
ubuntu@VM-12-14-ubuntu:~$ echo $deepseek_apikey
sk-f72b9bf366a947555793488818cf7c433

这样在程序中就可以通过获取对应环境变量拿到apikey,保证apikey的隐私。

参数的值直接配置在代码中,如果需要修改参数时不是很方便,因此可以将服务器的参数配置在文件中,如果需要修改直接修改文件即可。在可执行程序所在目录添加配置信息文件chatServer.conf,参数配置在文件中。

# 服务器参数配置
-host=0.0.0.0
-port=8080
-log_level=DEBUG
# ⼤模型参数配置
-temperature=0.7
-max_tokens=2048
# Ollama接⼊模型参数配置
-ollama_endpoint=http://127.0.0.1:11434
-ollama_model_name=deepseek-r1:1.5b
-ollama_model_desc=本地部署的DeepSeek, DeepSeek 推出的旗舰级开源⼤模型(128K上下⽂),
性能强⼤, 专注于深度理解与推理
-config_file="chatServer.conf"

2. 使用方法

当运行程序时用户输入·/AIChatServer-h或./AIChatServer--help显示使用帮助说明

// 显示接口说明
void showAPIInfo()
{
    std::cout << "\nChatServer API接口说明:\n";
    std::cout << "  POST   /api/session              - 创建新会话\n";
    std::cout << "  GET    /api/sessions             - 获取所有会话列表\n";
    std::cout << "  GET    /api/models               - 获取可用模型列表\n";
    std::cout << "  DELETE /api/session/{session_id} - 删除指定会话\n";
    std::cout << "  GET    /api/session/{session_id}/history - 获取会话历史消息\n";
    std::cout << "  POST   /api/message              - 发送消息(全量返回)\n";
    std::cout << "  POST   /api/message/async        - 发送消息(流式返回)\n";
    std::cout << "\n使用示例:\n";
    std::cout << "  # 基本启动\n";
    std::cout << "  ./AIChatServer\n";
    std::cout << "\n  # 指定端口启动\n";
    std::cout << "  ./AIChatServer --port=9000\n";
    std::cout << "\n  # 使用指定配置文件\n";
    std::cout << "  ./AIChatServer --config_file=my_config.conf\n";
    std::cout << "\n  # 设置环境变量后启动\n";
    std::cout << "  export DEEPSEEK_API_KEY=your_api_key\n";
    std::cout << "  ./AIChatServer\n";
}

3. main函数实现

#include "ChatServer.h"
#include <gflags/gflags.h>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <string>
#include <stdexcept>
#include <chrono>
#include <thread>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <spdlog/common.h>
// 正确的日志头文件路径
#include <ai_chat_sdk/util/mylog.h>

// 定义gflags参数
DEFINE_string(host, "0.0.0.0", "服务器绑定的地址");
DEFINE_int32(port, 8080, "服务器绑定的端口号");
DEFINE_bool(daemon, true, "是否以后台守护进程方式运行");
DEFINE_string(log_file, "", "日志输出文件路径(默认为stdout,守护进程模式下默认为server.log)");
DEFINE_string(log_level, "INFO", "日志级别");
DEFINE_double(temperature, 0.7, "温度值,影响生成文本的随机性");
DEFINE_int32(max_tokens, 2048, "最大token数");
DEFINE_string(config_file, "./ChatServer.conf", "配置文件路径");
// DEFINE_bool(version, false, "显示版本信息");
// Ollama配置参数
DEFINE_string(ollama_model_name, "", "Ollama模型名称");
DEFINE_string(ollama_model_desc, "", "Ollama模型描述");
DEFINE_string(ollama_endpoint, "", "Ollama API地址");

// 版本号
const std::string VERSION = "1.0.0";

// 从环境变量获取API密钥
std::string getEnvVar(const std::string &key)
{
    char *value = std::getenv(key.c_str());
    if (value == nullptr) {
        return "";
    }
    return std::string(value);
}

// 注意:配置文件ChatServer.conf将由gflags库自动解析,不需要手动生成

// 验证配置参数
bool validateConfig(ai_chat_server::ServerConfig &config)
{
    // 验证温度值
    if (config.temperature < 0.0 || config.temperature > 2.0)
    {
        ERR("错误: 温度值必须在0.0到2.0之间,当前值: {}", config.temperature);
        return false;
    }

    // 验证最大token数
    if (config.maxTokens <= 0)
    {
        ERR("错误: 最大token数必须为正数,当前值: {}", config.maxTokens);
        return false;
    }

    // 验证至少有一个API密钥不为空
    if (config.deepseekAPIKey.empty() && config.chatGPTAPIKey.empty() && config.geminiAPIKey.empty())
    {
        ERR("错误: 至少需要提供一个有效的API密钥或Ollama模型配置");
        return false;
    }

    // 验证Ollama配置参数
    if (!config.ollamaModelName.empty())
    {
        if (config.ollamaModelDesc.empty() || config.ollamaEndpoint.empty())
        {
            ERR("错误: 如果提供了Ollama模型名称,则必须同时提供模型描述和端点");
            return false;
        }
    }

    return true;
}

// 守护进程化
void daemonize()
{
    // 1. Fork off the parent process
    pid_t pid = fork();
    if (pid < 0)
    {
        exit(EXIT_FAILURE);
    }
    if (pid > 0)
    {
        exit(EXIT_SUCCESS);
    }

    // 2. Create a new SID for the child process
    if (setsid() < 0)
    {
        exit(EXIT_FAILURE);
    }

    // 3. Signal handling (optional)
    // signal(SIGHUP, SIG_IGN);

    // 4. Fork again to prevent acquiring a controlling terminal
    pid = fork();
    if (pid < 0)
    {
        exit(EXIT_FAILURE);
    }
    if (pid > 0)
    {
        exit(EXIT_SUCCESS);
    }

    // 5. Change the current working directory (Optional)
    // chdir("/"); 

    // 6. Set file mode creation mask
    umask(0);

    // 7. Close standard file descriptors (will be handled by redirection in main)
}

// 显示接口说明
void showAPIInfo()
{
    std::cout << "\nChatServer API接口说明:\n";
    std::cout << "  POST   /api/session              - 创建新会话\n";
    std::cout << "  GET    /api/sessions             - 获取所有会话列表\n";
    std::cout << "  GET    /api/models               - 获取可用模型列表\n";
    std::cout << "  DELETE /api/session/{session_id} - 删除指定会话\n";
    std::cout << "  GET    /api/session/{session_id}/history - 获取会话历史消息\n";
    std::cout << "  POST   /api/message              - 发送消息(全量返回)\n";
    std::cout << "  POST   /api/message/async        - 发送消息(流式返回)\n";
    std::cout << "\n使用示例:\n";
    std::cout << "  # 基本启动\n";
    std::cout << "  ./AIChatServer\n";
    std::cout << "\n  # 指定端口启动\n";
    std::cout << "  ./AIChatServer --port=9000\n";
    std::cout << "\n  # 使用指定配置文件\n";
    std::cout << "  ./AIChatServer --config_file=my_config.conf\n";
    std::cout << "\n  # 设置环境变量后启动\n";
    std::cout << "  export DEEPSEEK_API_KEY=your_api_key\n";
    std::cout << "  ./AIChatServer\n";
}

int main(int argc, char **argv)
{
    try
    {
        // 显示帮助信息(当使用-h或--help时,gflags会自动显示帮助信息并退出)
        // 这里我们额外显示API接口说明
        if (argc == 2 && (std::string(argv[1]) == "-h" || std::string(argv[1]) == "--help"))
        {
            showAPIInfo();
            return 0;
        }

        // 解析命令行参数
        gflags::SetUsageMessage("AIChatServer - AI聊天服务器\n\n使用方法: ./AIChatServer [options]");
        gflags::ParseCommandLineFlags(&argc, &argv, true);
        gflags::SetVersionString(VERSION);
        // 使用gflags库自动解析配置文件
        // 如果配置文件存在,gflags会自动加载其中的参数
        // 注意:gflags会按照以下顺序解析参数:默认值 -> 配置文件 -> 命令行参数
        std::ifstream file(FLAGS_config_file);
        if (file)
        {
            gflags::SetCommandLineOption("flagfile", FLAGS_config_file.c_str());
        }
        // 构建ServerConfig
        ai_chat_server::ServerConfig config;
        config.host = FLAGS_host;
        config.port = FLAGS_port;
        config.logLevel = FLAGS_log_level;
        config.temperature = FLAGS_temperature;
        config.maxTokens = FLAGS_max_tokens;

        // 从环境变量获取API密钥
        config.deepseekAPIKey = getEnvVar("deepseek_apikey");
        config.chatGPTAPIKey = getEnvVar("chatgpt_apikey");
        config.geminiAPIKey = getEnvVar("gemini_apikey");
        // 从命令行参数获取Ollama配置
        config.ollamaModelName = FLAGS_ollama_model_name;
        config.ollamaModelDesc = FLAGS_ollama_model_desc;
        config.ollamaEndpoint = FLAGS_ollama_endpoint;

        // 验证配置参数
        if (!validateConfig(config))
        {
            ERR("配置验证失败,请检查参数设置");
            return 1;
        }

        // 守护进程处理
        if (FLAGS_daemon)
        {
            std::cout << "正在启动守护进程..." << std::endl;
            daemonize();
        }

        // 处理日志文件重定向
        std::string logFile = FLAGS_log_file;
        if (FLAGS_daemon && logFile.empty())
        {
            logFile = "server.log";
        }

        // 如果指定了日志文件,或者是守护进程模式(默认server.log),则重定向stdout/stderr
        if (!logFile.empty() && logFile != "stdout")
        {
            int fd = open(logFile.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
            if (fd >= 0)
            {
                dup2(fd, STDOUT_FILENO);
                dup2(fd, STDERR_FILENO);
                close(fd);
            }
            else
            {
                if (!FLAGS_daemon)
                {
                    std::cerr << "无法打开日志文件: " << logFile << std::endl;
                }
            }
        }

        // 设置日志级别
        spdlog::level::level_enum logLevel = spdlog::level::info; // 默认INFO级别
        if (config.logLevel == "TRACE")
            logLevel = spdlog::level::trace;
        else if (config.logLevel == "DEBUG")
            logLevel = spdlog::level::debug;
        else if (config.logLevel == "INFO")
            logLevel = spdlog::level::info;
        else if (config.logLevel == "WARN" || config.logLevel == "WARNING")
            logLevel = spdlog::level::warn;
        else if (config.logLevel == "ERROR")
            logLevel = spdlog::level::err;
        else if (config.logLevel == "CRITICAL")
            logLevel = spdlog::level::critical;

        // 初始化日志组件
        // 既然已经重定向了stdout,这里传递"stdout"给initLogger即可让它写入到我们的日志文件
        aurora::Logger::initLogger("ChatServer", "stdout", logLevel);
        // 显示当前配置
        INFO("AIChatServer 启动配置:");
        
        INFO("  版本: {}\n", VERSION);
        INFO("  主机: {}", config.host);
        INFO("  端口: {}", config.port);
        INFO("  日志级别: {}", config.logLevel);
        INFO("  温度值: {}", config.temperature);
        INFO("  最大Token: {}", config.maxTokens);
        INFO("  DeepSeek API Key: {}", (config.deepseekAPIKey.empty() ? "未设置" : "已设置"));
        INFO("  ChatGPT API Key: {}", (config.chatGPTAPIKey.empty() ? "未设置" : "已设置"));
        INFO("  Gemini API Key: {}", (config.geminiAPIKey.empty() ? "未设置" : "已设置"));
        INFO("  Ollama 模型: {}", (config.ollamaModelName.empty() ? "未设置" : config.ollamaModelName));
        INFO("  Ollama 模型描述: {}", (config.ollamaModelDesc.empty() ? "未设置" : config.ollamaModelDesc));
        INFO("  Ollama 端点: {}", (config.ollamaEndpoint.empty() ? "未设置" : config.ollamaEndpoint));

        // 创建并启动ChatServer
        ai_chat_server::ChatServer server(config);
        if (server.start())
        {
            INFO("ChatServer 启动成功!");
            INFO("服务器地址: http://{}:{}", config.host, config.port);

            // 主线程等待,让服务器在单独线程中运行
            while (server.isRunning())
            {
                std::this_thread::sleep_for(std::chrono::seconds(100));
            }
        }
        else
        {
            ERR("ChatServer 启动失败!");
            return 1;
        }

        return 0;
    }
    catch (const std::exception &e)
    {
        ERR("发生异常: {}", e.what());
        return 1;
    }
    catch (...)
    {
        ERR("发生未知异常");
        return 1;
    }
}

CMakeLists.txt

# 设置Cmake的最小版本
cmake_minimum_required(VERSION 3.10)

# 项目名称
project(AIChatServer)

# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 设置构建类型Debug
set(CMAKE_BUILD_TYPE Debug)

# 添加可执行文件
add_executable(AIChatServer main.cpp ChatServer.cpp)

# 设置输出目录
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})

# 查找依赖库
find_package(OpenSSL REQUIRED)

# 添加CPPHTTPLIB_OPENSSL_SUPPORT定义
target_compile_definitions(AIChatServer PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)

# 设置静态库的目录
link_directories(/usr/local/lib)

# 链接库
target_link_libraries(AIChatServer 
    ai_chat_sdk
    jsoncpp 
    fmt  
    spdlog 
    gflags
    OpenSSL::SSL 
    OpenSSL::Crypto 
    sqlite3
    gflags)

3.3 前端页面

前端页面可让trae生成,提示词在gitee上面。

https://gitee.com/Axurea/aichat-sdkhttps://gitee.com/Axurea/aichat-sdk

4. 项目扩展

  • 尝试接人其他大模型
  • ⽀持用户注册
  • 支持Tool Calling,⽐如发送邮件、查询数据库。让⼤模型能够⾃动调用⼯具完成具体的事宜
  • 搭建学习智能体
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐