一、项目介绍

GoNexus 项目是一个基于 Gin 框架和 Eino 框架搭建的 Go 语言 Web 应用服务平台。其中 Gin 框架拥有强大的 Web 服务能力,可以迅速的搭建 Web 应用,Eino 框架是字节跳动开源的 AI 应用开发框架,基于 Go 语言可构建 LLM 应用服务。GoNexus 通过集成 AI 技术实现 AI 聊天助手和图像识别等功能,致力于打造 Agent 应用开发。本项目依赖开源项目 GopherAI 做的二次打造。

项目采用全栈架构,后端使用 Gin 框架构建 RESTful API,前端采用 Vue.js 实现响应式界面,数据库层基于 MySQL 和 Redis 实现数据持久化和缓存,消息队列使用 RabbitMQ,图像识别模型通过 ONNX Runtime 运行 MobileNetV2,实现端到端的智能化应用开发。

开发过程基于 Claude Code 实现 Vibe Coding,深入体验新技术的使用。

二、技术架构图

三、关键模块

用户模块、AI 对话聊天模块、RAG 模块、MCP 模块、TTS 模块、图像识别模块

四、目录结构

GoNexus/
├── main.go # 程序入口:初始化 MySQL/Redis/RabbitMQ/AIHelperManager,启动 HTTP 服务
├── go.mod / go.sum # Go 模块依赖管理
├── config/
│ ├── config.go # 配置加载逻辑(读取 config.toml)
│ └── config.toml # 配置文件(主机、端口、数据库、AI模型等)

├── model/ # 数据模型层 (ORM 结构体定义)
│ ├── user.go # 用户模型 (User)
│ ├── seesion.go # 会话模型 (Session) + 会话列表信息 (SessionInfo)
│ └── message.go # 消息模型 (Message) + 聊天历史 (History)

├── dao/ # 数据访问层 (Database CRUD 操作)
│ ├── user/user.go # 用户数据访问(注册、登录、查询)
│ ├── session/session.go # 会话数据访问(创建、查询、删除)
│ └── message/message.go # 消息数据访问(创建、查询、批量获取)

├── service/ # 业务逻辑层
│ ├── user/user.go # 用户服务(注册、登录业务处理)
│ ├── session/session.go # AI 对话核心服务:
│ │ # CreateSessionAndSendMessage (同步对话)
│ │ # ChatSend (已有会话聊天)
│ │ # CreateStreamSessionOnly (流式会话创建)
│ │ # StreamMessageToCurrentSession(流式消息传输)
│ │ # GetUserSessionByUsername (获取用户会话列表)
│ │ # GetChatHistory (获取历史消息)
│ │ # DeleteSession (删除会话)
│ ├── tts/tts.go # TTS 语音合成服务
│ └── file/file.go # 文件上传服务

├── controller/ # 控制器层 (HTTP 请求处理)
│ ├── common.go # 公共方法
│ ├── user/user.go # 用户控制器(注册/登录 API 端点)
│ ├── session/session.go # 会话控制器(聊天/流式/SSE/会话管理/TTS API)
│ ├── tts/tts.go # TTS 控制器
│ └── file/file.go # 文件上传控制器

├── router/ # 路由层 (API 路由注册)
│ ├── router.go # 主路由初始化 & 中间件挂载
│ ├── ai.go # AI 聊天相关路由组 (/api/ai/chat/*)
│ ├── user.go # 用户相关路由组
│ └── file.go # 文件相关路由组

├── middleware/ # 中间件
│ └── jwt/jwt.go # JWT 认证中间件(Token 校验、用户身份解析)

├── common/ # 公共工具/基础设施组件
│ ├── aihelper/ # AI 核心引擎(多模型支持)
│ │ ├── aihelper.go # AIHelper 接口实现(DeepSeek/Qwen/QwenRag/QwenMCP)
│ │ ├── factory.go # 工厂模式 – 按类型创建 AI 实例
│ │ ├── manager.go # AIHelperManager 全局单例(管理用户→会话→AIHelper 映射)
│ │ └── model.go # AI 消息模型、Prompt 模板、工具定义
│ ├── code/code.go # 统一状态码与响应封装
│ ├── mysql/mysql.go # MySQL 连接池初始化 (GORM)
│ ├── redis/
│ │ ├── redis.go # Redis 连接池初始化
│ │ └── key.go # Redis Key 常量定义
│ ├── rabbitmq/
│ │ ├── rabbitmq.go # RabbitMQ 连接与管理
│ │ ├── init.go # 初始化配置
│ │ └── message.go # 消息队列操作封装
│ ├── rag/rag.go # RAG 增强检索生成(向量库集成)
│ ├── tts/tts.go # TTS 语音合成(腾讯云接口对接)
│ ├── email/email.go # 邮件发送功能
│ └── mcp/ # MCP (Model Context Protocol) 支持
│ ├── main.go # MCP 主入口
│ ├── client/ # MCP Client
│ └── server/ # MCP Server

├── utils/utils.go # 通用工具函数

├── doc/ # 项目文档
│ ├── docker-compose-software-aliyun.yml # Docker 编排文件
│ ├── testrag.md # RAG 测试文档
│ └── CreateSessionAndSendMessage时序图 # 时序图

├── vue-frontend/ # Vue.js 前端项目
│ ├── package.json / package-lock.json
│ ├── vue.config.js # Vite/Webpack 配置
│ ├── public/ # 静态资源
│ ├── dist/ # 构建产物
│ └── src/
│ ├── views/
│ │ └── AIChat.vue # AI 聊天主页面(会话列表 + 对话界面)
│ ├── utils/api.js # Axios API 封装
│ └── … (其他前端源码)

├── Dockerfile # 后端 Docker 构建文件
├── Dockerfile.mcp # MCP 服务 Docker 构建文件
├── CLAUDE.md # 项目说明文档
├── README.md # 项目 README
└── gonexus / gonexus-mcp # 编译后的可执行文件

五、AI 聊天对话系统

5.1 概述

AI 模块是 GoNexus 项目的核心组件之一,集成 LLM 提供智能对话功能。该组件采用模块化设计,支持多种 LLM 服务,实现统一的接口调用和管理。核心功能包括多会话隔离管理历史消息维护同步/流式响应输出,以及异步消息持久化存储

每个用户-会话-AIHelper映射关系都是彼此隔离的,为了支持和确保多个映射关系互不干扰和相互独立,通过工厂模式来创建和管理 AIHelper 实例。

其中 AIHelperManager 管理器只需要一个全局管理器就可以支持高效的 AIHelperManager 实例获取和生命周期管理,因此通过单例模式来创建 AIHelperManager 管理器。

同样 AIModelFactory 模型工厂也只需要一个全局工厂就可以支持高效的 AIModelFactory 实例获取和生命周期管理,因此通过单例模式来创建AIModelFactory 模型工厂。

消息响应支持同步和流式两种模式,同步模式是后端将 AI 回复一次性全部返回给前端,内容过长容易让用户等待感明显,适合短对话回复;流式模式是基于 SSE 实时将内容推送给前端,提升用户体验。

系统支持高并发和可扩展性,AIHelper 在追加消息的时候使用写锁保证多线程安全访问,在获取消息时使用读锁防止脏数据。

5.2 流程图

Controller 层:controller/session/session.go 中 CreateSessionAndSendMessage 方法解析参数,参数包括用户名、用户的问题和选取的模型类型。

Service 层:service/session/session.go 中 CreateSessionAndSendMessage 方法根据传入参数实现会话创建、AI回复生成。

DAO 层:dao/session/session.go 中 CreateSession 方法创建会话记录,然后存入数据库;dao/message/message.go 中 CreateMessage 方法创建消息记录,用于 RabbitMQ 实现异步存储消息数据。

Common 组件:AIHelper 生成回复、AIHelperManager 管理实例、AIModelFactory 创建模型、RabbitMQ 异步存储消息。

外部服务:Claude Code/Codex/DeepSeek/Qwen 提供的 LLM API 入口。

AI 聊天对话模块支持新会话创建和历史会话继续,每个会话都有自己的 sessionID,用户和 AI 的对话消息通过 RabbitMQ 异步持久化,避免阻塞。

5.3 功能拆分

5.3.1 AIHelperManager 管理器

全局单例:GetGlobalManager() *AIHelperManager 方法使用 sync.once 返回全局单例实例,参考代码如下:

var (
    globalManager *AIHelperManager
    once          sync.Once
)

func GetGlobalManager() *AIHelperManager {
    once.Do(func() {
        globalManager = &AIHelperManager{
            属性1: 初始化,
            属性2: 初始化,
            ...
        }
    })
    return globalManager
}

数据结构:为了支持多用户多会话隔离的需求,存储用户-会话-AIHelper的映射关系,使用 map[string]map[string]*AIHelper 来作为数据结构,其中外层 map 的键为 username,内层 map 的键为 sessionID,值为 AIHelper 指针。数据结构如下,在获取或创建映射关系的时候,使用 sync.RWMutex 读写锁来避免并发问题。

type AIHelperManager struct {
    helpers map[string]map[string]*AIHelper
    mu      sync.RWMutex
}

AIHelperManager 的成员方法如下

func (a *AIHelperManager) GetOrCreateAIHelper(username, sessionID, modelType string) (*AIHelper, error) {
    // 业务逻辑
}

获取或创建方法:GetOrCreateAIHelper(username, sessionID, modelType string) (*AIHelper, error) 方法根据参数先查询是否存在对应的 AIHelper 实例,若无则通过全局 GetGlobalFactory() *AIModelFactory 工厂方法创建,然后存储并返回。

5.3.2 AIModelFactory 模型工厂

全局单例:GetGlobalFactory() 方法与 AIHelperManager 的全局 GetGlobalManager() 一样,使用 sync.Once 返回全局单例实例,参考代码如下:

var (
    globalFactory *AIModelFactory
    factoryOnce   sync.Once
)

func GetGlobalFactory() *AIModelFactory {
    factoryOnce.Do(func() {
        globalFactory = &AIModelFactory{
            属性1: 初始化,
            ...
        }
        // 调用注册AI大模型创建方法
    })
    return globalFactory
}

数据结构:为了支持不同AI模型以及其对应的模型创建函数,使用 map[string]ModelCreator 作为数据结构,其中键为模型类型,如 “1” 表示 DeepSeek,”2″ 表示 Qwen 等等,值是一个方法 ModelCreator,go 语言的一大特点,方法可以作为类型使用,ModelCreator 定义为 func(ctx context.Context) (AIModel, error),代码如下:

type ModelCreator func(ctx context.Context) (AIModel, error)

type AIModelFactory struct {
    creators map[string]ModelCreator
    mu       sync.RWMutex
}

⚠️注意:与 AIHelperManager 不同的是 AIModelFactory 在初始化的时候要调用注册AI模型创建的方法,见下面代码:

func GetGlobalFactory() *AIModelFactory {
    factoryOnce.Do(func() {
        globalFactory = &AIModelFactory{
            属性1: 初始化,
            属性2: 初始化,
            ...
        }
        // 调用注册模型创建函数
        globalFactory.registerCreators()
    })
    return globalFactory
}

其中 registerCreators() 逻辑见下:

func (f *AIModelFactory) registerCreators() {
    // 注册DeepSeek    
    f.creators["1"] = func(ctx context.Context) (AIModel, error) {
        // 依托eino框架调用DeepSeekModel创建方法并返回
        return NewDeepSeekModel(ctx)
    }
    // 注册Qwen 
    f.creators["2"] = func(ctx context.Context) (AIModel, error) {
        // 依托eino框架调用QwenModel创建方法并返回
        return NewQwenModel(ctx)
    }
    // 其他需要注册的大模型
}

AIModelFactory 成员方法如下

func (f *AIModelFactory) registerCreators() {
    // 具体业务逻辑
}

func (f *AIModelFactory) CreateAIHelper(ctx context.Context, modelType, sessionID string) (*AIHelper, error) {
    // 具体业务逻辑
}

func (a *AIModelFactory) CreateAIModel(ctx context.Context, modelType, sessionID string) (AIModel, error) {
    // 具体业务逻辑
}

创建 AIHelper 方法:成员方法 CreateAIHelper(ctx context.Context, modelType, sessionID string) (*AIHelper, error) 方法内部调用 CreateAIModel(ctx context.Context, modelType string) (AIModel, error) 方法获取模型 aiModel,然后根据 aiModel 和 sessionID 调用 NewAIHelper(aiModel AIModel, sessionID string) *AIHelper 方法创建 AIHelper 实例并返回。

创建 AIModel 方法:成员方法 CreateAIModel(ctx context.Context, modelType, sessionID string) (AIModel, error) 方法根据 modelType 从 a.creators[modelType] 中获取对应的模型创建函数 ModelCreator,然后传入配置(如果有的话)获取对应的模型实例 AIModel 并返回。

5.3.3 AIHelper 实例结构

在调用 CreateAIHelper(ctx context.Context, modelType, sessionID string) (*AIHelper, error) 方法时最后会调用 NewAIHelper(aiModel AIModel, sessionID string) *AIHelper 方法创建 AIHelper 实例并返回,接下来介绍 AIHelper 实例结构及其成员方法和工作机制。

数据结构:AIHelper 结构体需要为每个 session 提供独立的 AI 交互环境。因此需要包含模型绑定、历史消息、会话ID,还需要将用户与 AI 的消息以异步的方式存入数据库。

type AIHelper struct {
    // AI模型接口,支持不同模型实现
    model     AIModel
    // 历史消息列表,存储用户与AI之间的对话记录               
    messages  []*model.Message
    // 读写锁,保护历史消息并发访问  
    mu        sync.RWMutex
    // 会话唯一标识,用于绑定消息和上下文      
    sessionID string            
    // 消息存储回调函数,异步发布到RabbitMQ
    saveFunc  func(*model.Message) (*model.Message, error) 
}

创建实例:每个用户的每次会话都对应一个独立 AIHelper 实例,需要多次创建,因此 AIHelper 实例就不需要再使用 sync.Once 创建全局单例变量了。NewAIHelper(aiModel AIModel, sessionID string) *AIHelper 方法如下:

func NewAIHelper(aiModel AIModel, sessionID string) *AIHelper {
    return &AIHelper{
        model:     aiModel,
        messages:  make([]*model.Message, 0),
        mu:        sync.RWMutex{},
        sessionID: sessionID,
        saveFunc:  func(msg *model.Message) (*model.Message, error) {
            // rabbitmq 异步消息入库逻辑
            // ...
            return msg, err
        },
    }
}

AIHelper 成员方法

func (a *AIHelper) AddMessage(username, content string, isUser, save bool) {
    userMsg := model.Message{
	SessionID: h.sessionID,
	Username:  username,
	Content:   content,
	IsUser:    isUser,
    }
    h.mu.Lock()   // 加写锁
    h.messages = append(h.messages, &userMsg)
    h.mu.Unlock() // 释放写锁
    if save {
        _, err := h.saveFunc(&userMsg)
	if err != nil {
	    log.Println("AddMessage save failed. err:", err)
	}
    }
}

func (a *AIHelper) GenerateResponse(ctx context.Context, username, userQuestion string) (*model.Message, error) {
    // 1. 存储用户消息model.Message
    h.AddMessage(userQuestion, username, true, true)
    // 2. 将model.Message转为schema.Message
    h.mu.RLock()   // 加读锁
    messages := utils.ConvertToSchemaMessages(h.messages)
    h.mu.RUnlock() // 释放读锁
    // 3. 调用大模型获得AI回复的消息schema.Message
    schemaMsg, err := h.model.GenerateResponse(ctx, messages)
    if err != nil {
	return nil, err
    }
    // 4. 将schema.Message转为model.Message
    modelMsg := utils.ConvertToModelMessages(h.sessionID, username, schemaMsg.Content)
    // 5. 存储AI消息schema.Message
    h.AddMessage(modelMsg.Content, username, false, true)
    return modelMsg, nil
}

工作机制

调用 NewAIHelper(aiModel AIModel, sessionID string) *AIHelper 方法会初始化 AIHelper 实例,异步存储 saveFunc 固定设置为 RabbitMQ。然后调用 GenerateResponse(ctx context.Context, username, userQuestion string) (*model.Message, error) 方法,其内部先后两次调用 AddMessage(username, content string, isUser, save bool) 方法,第一次是将用户的问题追加到 messages 消息数组中,然后自动调用 saveFunc 异步持久化,第二次是将 AI 的回复追加到 messages 消息数组中,然后同样自动调用 saveFunc 异步持久化。

⚠️注意:在AddMessage(username, content string, isUser, save bool) 方法中对 messages 进行追加的时候需要加入写锁(同一时刻只有一个 goroutine 能写),保护并发安全。在 GenerateResponse(ctx context.Context, username, userQuestion string) (*model.Message, error) 方法中因为要调用 eino 框架的 GenerateResponse() 方法,需要将消息转换成 eino 框架适配的格式,转换过程需要加入读锁(多个 goroutine 可以同时读,但写操作进来时会等待所有读完成),保护并发问题。

5.3.4 RabbitMQ 异步消息存储

RabbitMQ 采用生产者消费者模式来实现异步消息存储。在 5.3.3 中可以看到AddMessage(username, content string, isUser, save bool) 方法在内部调用了 h.saveFunc(&userMsg),接下来看一下 NewAIHelper(aiModel AIModel, sessionID string) *AIHelper 方法如何初始化的 RabbitMQ 异步入库的逻辑。

func NewAIHelper(aiModel AIModel, sessionID string) *AIHelper {
    return &AIHelper{
        model:     aiModel,
        messages:  make([]*model.Message, 0),
        mu:        sync.RWMutex{},
        sessionID: sessionID,
        saveFunc:  func(msg *model.Message) (*model.Message, error) {
            // rabbitmq 异步消息入库逻辑
            data := rabbitmq.GenerateMessageMQParam(msg.SessionID, msg.Username, msg.Content, msg.IsUser)
	    err := rabbitmq.RMQMessage.Publish(data)
            return msg, err
        },
    }
}

可以看到,saveFunc 内部调用了 GenerateMessageMQParam() 方法和 Publish() 方法,先生成 RabbitMQ 适配的消息结构 data,然后再由生产者发送数据。

Publish 的流程

AddMessage()
      └── saveFunc()
              └── rabbitmq.RMQMessage.Publish(data)
                      └── channel.Publish("GoNexus", "Message", ...)
                                  ↓ direct 路由匹配
                            Queue "Message"
                                  ↓
                        消费者 goroutine 接收 → 写 DB

在 main.go 中执行程序对 RabbitMQ 初始化时开启了一个消费者的 goroutine,专门负责消费数据,把数据写入数据库。接下来将从 main.go 文件中对 RabbitMQ 初始化到生产者生产消息,消费者消费消息的全流程进行解读,彻底剖析 RabbitMQ 的工作机制。

RabbitMQ 初始化到消费数据整体流程图

 main/startup
      └── InitRabbitMQ()                          [init.go]
              │
              ├── NewRabbitMQ(...)                [rabbitmq.go]
              │       ├── initConn()  → 建立全局 amqp.Connection
              │       ├── conn.Channel() → 创建 Channel
              │       └── ExchangeDeclare("GoNexus", "direct") → 声明交换机
              │
              └── go RMQMessage.Consume(MQMessage) → 启动消费者 goroutine

RabbitMQ 数据结构

type RabbitMQ struct {
    conn       *amqp.Connection
    channel    *amqp.Channel
    Exchange   string
    routingKey string
}

RabbitMQ 的成员方法

func (r *RabbitMQ) Publish(message []byte) error {
    // 业务逻辑
}

func (r *RabbitMQ) Consume(handle func(msg *amqp.Delivery) error) {
    // 业务逻辑
}

全局连接初始化

var conn *amqp.Connection   // 包级别全局连接,所有队列共用

func initConn() {
    mqUrl := fmt.Sprintf("amqp://%s:%s@%s:%d/%s", ...)
    conn, _ = amqp.Dial(mqUrl)   // TCP 长连接到 RabbitMQ
}

NewRabbitMQ 创建实例

func NewRabbitMQ(exchangeName, exchangeType, routingKey string) *RabbitMQ {
    // 1. 获取connection
    if conn == nil {
	initConn()
    }
    // 2. 创建channel
    ch, err := conn.Channel()
    if err != nil {
	log.Fatalf("create channel failed. err: %v", err)
    }
    // 3. 声明Exchange
    if err = ch.ExchangeDeclare(
	exchangeName,
	exchangeType,
	true,
	false,
	false,
	false,
	nil,
     ); err != nil {
	log.Fatalf("declare exchange failed. err: %v", err)
     }
    // 4. 返回实例
    return &RabbitMQ{
	conn:       conn,
	channel:    ch,
	Exchange:   exchangeName,
	routingKey: routingKey,
    }
}

conn 作为全局变量,NewRabbitMQ 方法中第一步通过懒加载判断,保证整个进程只建一条 TCP 连接,多个队列共享它。第二步会在此 TCP 连接中创建一个独立的 Channel,并在该 Channel 上声明 Exchange

InitRabbitMQ 启动消费者 goroutine

var RMQMessage *RabbitMQ // RabbitMQ全局实例

func InitRabbitMQ() {
    RMQMessage = NewRabbitMQ(ExchangeName, ExchangeType, RoutingKey) // 创建实例
    go RMQMessage.Consume(MQMessage) // 后台 goroutine 持续监听
}

Consume 的内部流程

① QueueDeclare("Message") → 声明持久化队列
② QueueBind(queue, "Message", "GoNexus") → 绑定到交换机
③ channel.Consume(queue, autoAck=true) → 返回 <-chan amqp.Delivery
④ for msg := range msgs → 阻塞等待,消息到来时触发
└── handle(&msg) → 调用 MQMessage() 写数据库

第 ④ 步的 for range 会永久阻塞这个 goroutine,直到 channel
被关闭(连接断开时),这就是消费者 goroutine 的生命周期。

消息消费处理

func MQMessage(msg *amqp.Delivery) error {
    json.Unmarshal(msg.Body, &param)    // 反序列化 JSON
    message.CreateMessage(newMsg)       // 写入数据库
}

5.3.5 AIModel 接口设计

type AIModel interface {
    // GenerateResponse 同步生成回复
    GenerateResponse(ctx context.Context, messages []*schema.Message) (*schema.Message, error)
    // StreamResponse 流式生成回复,通过回调函数实时输出
    StreamResponse(ctx context.Context, messages []*schema.Message, cb StreamCallback) (string, error)
    // GetModelType 返回模型类型
    GetModelType() string
}

根据不同的大模型调用 eino 框架来实现创建实例方法:

引入 “github.com/cloudwego/eino-ext/components/model/deepseek” 包创建 DeepSeek 模型实例

// DeepSeekModel DeepSeek模型实现
type DeepSeekModel struct {
	llm model.ToolCallingChatModel
}

// NewDeepSeekModel 获取DeepSeek模型实例
func NewDeepSeekModel(ctx context.Context) (*DeepSeekModel, error) {
    apiKey := os.Getenv("DEEPSEEK_API_KEY")
    modelName := os.Getenv("DEEPSEEK_MODEL_NAME")
    baseURL := os.Getenv("DEEPSEEK_BASE_URL")
    if apiKey == "" {
	return nil, errors.New("DEEPSEEK_API_KEY environment variable is not set")
    }
    llm, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{
	APIKey:  apiKey,
	BaseURL: baseURL,
	Model:   modelName,
    })
    if err != nil {
	return nil, fmt.Errorf("create deepseek model failed. err: %v", err)
    }
    return &DeepSeekModel{llm: llm}, nil
}

DeepSeekModel 结构体的成员方法

func (ds *DeepSeekModel) GenerateResponse(ctx context.Context, 
                                          messages []*schema.Message) (*schema.Message, error) {
    // 业务逻辑
}

func (ds *DeepSeekModel) StreamResponse(ctx context.Context, 
                                        messages []*schema.Message, cb StreamCallback) (string, error) {
    // 业务逻辑
}

func (ds *DeepSeekModel) GetModelType() string {
    // 业务逻辑
}

引入 “github.com/cloudwego/eino-ext/components/model/qwen” 包创建 Qwen 模型实例

// QwenModel  Qwen模型实现
type QwenModel struct {
	llm model.ToolCallingChatModel
}

// NewQwenModel 获取Qwen模型实例
func NewQwenModel(ctx context.Context) (*QwenModel, error) {
    apiKey := os.Getenv("QWEN_API_KEY")
    modelName := os.Getenv("QWEN_MODEL_NAME")
    baseURL := os.Getenv("QWEN_BASE_URL")
    if apiKey == "" {
	return nil, errors.New("QWEN_API_KEY environment variable is not set")
    }
    llm, err := qwen.NewChatModel(ctx, &qwen.ChatModelConfig{
	APIKey:  apiKey,
	BaseURL: baseURL,
	Model:   modelName,
    })
    if err != nil {
	return nil, fmt.Errorf("create qwen model failed. err: %v", err)
    }
    return &QwenModel{llm: llm}, nil
}

QwenModel 结构体的成员方法

func (q *QwenModel) GenerateResponse(ctx context.Context, 
                                     messages []*schema.Message) (*schema.Message, error) {
    // 业务逻辑
}

func (q *QwenModel) StreamResponse(ctx context.Context, 
                                   messages []*schema.Message, cb StreamCallback) (string, error) {
    // 业务逻辑
}

func (q *QwenModel) GetModelType() string {
    // 业务逻辑
}
5.4 验证结果

输入用户名和密码登录账户获取 token

输入问题“你知道openclaw吗”和选择模型“1-DeepSeek”,携带token访问同步回复接口,得到回复

5.5 SSE 流式响应

对于 SSE 头需要配置以下参数

c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Header("Access-Control-Allow-Origin", "*")
c.Header("X-Accel-Buffering", "no")

Content-Type 设置为 “text/event-stream” 表示告诉浏览器是 SSE 流。

Cache-Control 设置为 “no-cache” 表示禁用缓存。

Connection 设置为 “keep-alive” 表示保持连接。

Access-Control-Allow-Origin 设置为 “*” 表示支持跨域访问。

X-Accel-Buffering 设置为 “no” 表示关闭代理服务器缓冲,确保数据实时到达前端。

其中 X-Accel-Buffering 参数很重要,当时我一段实习的时候就是开发一个AI对话功能,在本地前后端联调的时候没有任何问题,但是部署到 Nginx 上 SSE 就失效了,最后排查问题原来是后端服务器在返回数据时没有让 Nginx 数据流实时返回前端。

解决办法有两种,一种是响应头中设置 X-Accel-Buffering: no,另一种是在 Nginx 配置文件将 proxy_buffering 设置为 off 从而关闭 Nginx 的代理缓冲。

这样一来,数据流就变成了:后端服务器 -> 实时转发 -> Nginx -> 实时转发 -> 客户端,数据可以以毫秒级的延迟到达客户端,保证了流畅的实时体验。

六、RAG 服务

6.1 概述

RAG 检索增强生成服务允许用户通过网站直接上传 RAG 文件,构建个性化知识库,提高 AI 的知识检索和生成能力,减少幻觉的可能。每个用户都可以上传数据自己的文件,打造独属于自己的 RAG,实现用户级别的隔离式 RAG 机制,确保不同用户之间的知识库相互独立、互不影响。

本服务将实现文件上传、向量化存储、检索功能,与AI 聊天对话系统相辅相成,支持用户通过上传文件构建个性化知识库,提高用户使用体验。

基于千问的 text-embedding-v4 模型对文件文档进行向量化。

6.2 向量化存储

使用 Redis 作为向量数据库统一管理文档向量与元数据,实现本地化存储方案,在平台服务内部实现文件解析与内容切分向量化处理向量入库与索引维护

6.3 检索筛选

通过 Top-K 相似度实现检索匹配,从向量库中筛选与当前问题最相关的文档片段。

基于 eino 框架的文本语义处理类组件进行开发,该组件包括四种抽象:获取和处理文本文档的组件抽象 Document.LoaderDocument.Transformer,文本文档语义化处理的组件抽象 Embedding,Embedding 之后将数据索引进行存储的组件抽象 Indexer,将语义相关文本文档进行索引和召回的组件抽象 Retriever。本项目采用后三种抽象。

Embedding 组件使用 ARK 平台的模型生成向量 Embedding – ARK,ARK Embedding 实现了 Embedder 接口,可无缝集成到 Eino 的 embedding 系统中,提供文本向量化能力。支持火山引擎 Ark(火山方舟)提供的文本嵌入和多模态嵌入 API。

Indexer 组件支持 Redis 索引器,实现了 Indexer 接口。该组件使用 Redis Hashes 存储带有向量嵌入的文档,支持向量相似度搜索功能。

Retriever 组件同样支持 Redis 索引器,实现了 Retriever 接口。该组件使用 Redis 向量搜索功能(FT.SEARCH)基于语义相似度检索文档。

七、MCP 服务

MCP 模块让 GoNexus 可以调用外部工具来增强 LLM 的功能,引入的工具包为 mcp-go ,一个 GitHub 上开源的工具包。在 GoNexus 项目中,MCP 主要实现天气查询功能,通过自建的 MCP Server 对接 wttr.in API ,并在 LLM 调用流程中作为工具集成。该项目的工作流程图如下:

八、TTS 服务

TTS 是 Text To Sound 的首字母缩写,本意是文本转语音功能。TTS 是模型生成语音输出的核心组件,它的设计目标是将用户输入的文本,通过标准化的 API 调用生成高质量的音频,前端通过轮询的方式去获取语音资源,任务完成后实现语音输出。本项目采用的是百度云的语音合成。登录百度智能云,搜索语音合成,点击立即使用,开通服务后可以申请应用获取 API Key 和 Secret Key,百度智能云提供一系列免费使用的 TTS 服务包,有一定额度,作为开发足够使用。

一共涉及两个方法,创建 TTS 对象方法 CreateTTS 和 查询 TTS 服务方法
QueryTTSFull。CreateTTS 方法根据 AI 生成的文本内容创建 TTS 任务,返回对应的 taskId,然后前端通过轮询的方式不断调用 QueryTTSFull 方法查询 taskId,确定这次 TTS 任务是否成功,直到返回 Success 后结束轮询输出语音。

Categories:

Tags:

No responses yet

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注