llm,doc

This commit is contained in:
2025-08-02 23:54:03 +08:00
parent 366ca479c4
commit e03259608e
17 changed files with 3836 additions and 212 deletions
+7
View File
@@ -0,0 +1,7 @@
package api
type ApiGroup struct {
LLMApi
}
var ApiGroupApp = new(ApiGroup)
+177
View File
@@ -0,0 +1,177 @@
package api
import (
"fmt"
"io"
"time"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/model/request"
volcengineResponse "github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/model/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/service"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
)
type LLMApi struct{}
// ChatCompletion
// @Tags Volcengine
// @Summary LLM聊天完成
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body request.ChatRequest true "聊天请求参数"
// @Success 200 {object} response.Response{data=volcengineResponse.ChatResponse} "聊天响应"
// @Router /volcengine/llm/chat [post]
func (l *LLMApi) ChatCompletion(c *gin.Context) {
var chatReq request.ChatRequest
err := c.ShouldBindJSON(&chatReq)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
// 生成请求ID
if chatReq.RequestID == "" {
chatReq.RequestID = uuid.New().String()
}
// 根据stream参数选择响应模式
if chatReq.Stream {
// 流式响应 (SSE)
l.handleStreamResponse(c, chatReq)
} else {
// 普通JSON响应
l.handleNormalResponse(c, chatReq)
}
}
// handleNormalResponse 处理普通JSON响应
func (l *LLMApi) handleNormalResponse(c *gin.Context, chatReq request.ChatRequest) {
resp, err := service.LLMServiceApp.ChatCompletion(chatReq)
if err != nil {
global.GVA_LOG.Error("聊天完成失败!", zap.Error(err), zap.String("requestID", chatReq.RequestID))
response.FailWithMessage("聊天完成失败: "+err.Error(), c)
return
}
global.GVA_LOG.Info("聊天完成成功", zap.String("requestID", chatReq.RequestID))
response.OkWithDetailed(resp, "聊天完成成功", c)
}
// handleStreamResponse 处理流式响应 (SSE)
func (l *LLMApi) handleStreamResponse(c *gin.Context, chatReq request.ChatRequest) {
// 设置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("Access-Control-Allow-Headers", "Cache-Control")
// 创建事件通道
eventChan := make(chan volcengineResponse.StreamEvent, 100)
defer close(eventChan)
// 启动流式处理
go func() {
err := service.LLMServiceApp.StreamChatCompletion(chatReq, eventChan)
if err != nil {
global.GVA_LOG.Error("流式聊天失败!", zap.Error(err), zap.String("requestID", chatReq.RequestID))
eventChan <- volcengineResponse.StreamEvent{
Event: "error",
Data: volcengineResponse.ChatResponse{
ID: chatReq.RequestID,
Error: &volcengineResponse.APIError{
Code: "stream_error",
Message: err.Error(),
Type: "internal_error",
},
},
}
}
}()
// 发送流式数据
c.Stream(func(w io.Writer) bool {
select {
case event, ok := <-eventChan:
if !ok {
return false
}
switch event.Event {
case "message":
// 发送消息事件
c.SSEvent("message", event.Data)
case "error":
// 发送错误事件
c.SSEvent("error", event.Data)
return false
case "done":
// 发送完成事件
c.SSEvent("done", event.Data)
return false
}
return true
case <-time.After(30 * time.Second):
// 超时处理
c.SSEvent("error", volcengineResponse.ChatResponse{
ID: chatReq.RequestID,
Error: &volcengineResponse.APIError{
Code: "timeout",
Message: "Stream timeout",
Type: "timeout_error",
},
})
return false
}
})
}
// StopGeneration
// @Tags Volcengine
// @Summary 停止LLM生成
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body request.StopRequest true "停止请求参数"
// @Success 200 {object} response.Response{data=volcengineResponse.StopResponse} "停止响应"
// @Router /volcengine/llm/stop [post]
func (l *LLMApi) StopGeneration(c *gin.Context) {
var stopReq request.StopRequest
err := c.ShouldBindJSON(&stopReq)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
resp, err := service.LLMServiceApp.StopGeneration(stopReq)
if err != nil {
global.GVA_LOG.Error("停止生成失败!", zap.Error(err), zap.String("requestID", stopReq.RequestID))
response.FailWithMessage("停止生成失败: "+err.Error(), c)
return
}
if resp.Success {
global.GVA_LOG.Info("停止生成成功", zap.String("requestID", stopReq.RequestID))
response.OkWithDetailed(resp, "停止生成成功", c)
} else {
global.GVA_LOG.Warn("停止生成失败", zap.String("requestID", stopReq.RequestID), zap.String("reason", resp.Message))
response.FailWithDetailed(resp, resp.Message, c)
}
}
// GetActiveSessionsCount
// @Tags Volcengine
// @Summary 获取活跃会话数量
// @Security ApiKeyAuth
// @Produce application/json
// @Success 200 {object} response.Response{data=int} "活跃会话数量"
// @Router /volcengine/llm/sessions [get]
func (l *LLMApi) GetActiveSessionsCount(c *gin.Context) {
count := service.LLMServiceApp.GetActiveSessionsCount()
response.OkWithDetailed(gin.H{"count": count}, fmt.Sprintf("当前活跃会话数量: %d", count), c)
}
+23
View File
@@ -0,0 +1,23 @@
package volcengine
import (
"github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/router"
"github.com/gin-gonic/gin"
)
type volcenginePlugin struct{}
// CreateVolcenginePlug 创建volcengine插件实例
func CreateVolcenginePlug() *volcenginePlugin {
return &volcenginePlugin{}
}
// Register 注册路由
func (*volcenginePlugin) Register(group *gin.RouterGroup) {
router.RouterGroupApp.InitLLMRouter(group)
}
// RouterPath 返回注册路由路径
func (*volcenginePlugin) RouterPath() string {
return "volcengine"
}
@@ -0,0 +1,23 @@
package request
// Message 聊天消息结构体
type Message struct {
Role string `json:"role" form:"role" binding:"required"` // 角色: system, user, assistant
Content string `json:"content" form:"content" binding:"required"` // 消息内容
}
// ChatRequest 聊天请求结构体
type ChatRequest struct {
Messages []Message `json:"messages" form:"messages" binding:"required"` // 对话消息列表
Model string `json:"model" form:"model"` // 模型名称,如果为空则使用默认模型
Stream bool `json:"stream" form:"stream"` // 是否流式响应,默认false
Temperature float64 `json:"temperature" form:"temperature"` // 温度参数,控制随机性,范围0-1
MaxTokens int `json:"max_tokens" form:"max_tokens"` // 最大生成token数
TopP float64 `json:"top_p" form:"top_p"` // 核采样参数
RequestID string `json:"request_id,omitempty" form:"request_id,omitempty"` // 请求ID,用于流式响应管理
}
// StopRequest 停止生成请求结构体
type StopRequest struct {
RequestID string `json:"request_id" form:"request_id" binding:"required"` // 要停止的请求ID
}
@@ -0,0 +1,66 @@
package response
import "time"
// Message 聊天消息结构体
type Message struct {
Role string `json:"role"` // 角色: system, user, assistant
Content string `json:"content"` // 消息内容
}
// Choice 响应选择结构体
type Choice struct {
Index int `json:"index"` // 选择索引
Message Message `json:"message,omitempty"` // 完整响应消息(非流式)
Delta Message `json:"delta,omitempty"` // 增量消息(流式)
FinishReason string `json:"finish_reason,omitempty"` // 结束原因: stop, length, content_filter
}
// Usage 使用量统计结构体
type Usage struct {
PromptTokens int `json:"prompt_tokens"` // 输入token数
CompletionTokens int `json:"completion_tokens"` // 输出token数
TotalTokens int `json:"total_tokens"` // 总token数
}
// APIError API错误结构体
type APIError struct {
Code string `json:"code"` // 错误代码
Message string `json:"message"` // 错误消息
Type string `json:"type"` // 错误类型
}
// ChatResponse 聊天响应结构体
type ChatResponse struct {
ID string `json:"id"` // 响应ID
Object string `json:"object"` // 对象类型: chat.completion 或 chat.completion.chunk
Created int64 `json:"created"` // 创建时间戳
Model string `json:"model"` // 使用的模型
Choices []Choice `json:"choices"` // 响应选择列表
Usage *Usage `json:"usage,omitempty"` // 使用量统计(非流式响应)
Error *APIError `json:"error,omitempty"` // 错误信息
}
// StopResponse 停止生成响应结构体
type StopResponse struct {
Success bool `json:"success"` // 是否成功停止
Message string `json:"message"` // 响应消息
RequestID string `json:"request_id"` // 请求ID
StoppedAt int64 `json:"stopped_at"` // 停止时间戳
}
// StreamEvent 流式事件结构体
type StreamEvent struct {
Event string `json:"event"` // 事件类型: message, error, done
Data ChatResponse `json:"data"` // 事件数据
}
// ChatSession 聊天会话管理结构体(用于内部管理)
type ChatSession struct {
RequestID string `json:"request_id"` // 请求ID
UserID uint `json:"user_id"` // 用户ID
Model string `json:"model"` // 使用的模型
StartTime time.Time `json:"start_time"` // 开始时间
IsStreaming bool `json:"is_streaming"` // 是否流式响应
Status string `json:"status"` // 状态: running, completed, stopped, error
}
+7
View File
@@ -0,0 +1,7 @@
package router
type RouterGroup struct {
LLMRouter
}
var RouterGroupApp = new(RouterGroup)
@@ -0,0 +1,30 @@
package router
import (
"github.com/flipped-aurora/gin-vue-admin/server/middleware"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/api"
"github.com/gin-gonic/gin"
)
type LLMRouter struct{}
// InitLLMRouter 初始化LLM路由
func (l *LLMRouter) InitLLMRouter(Router *gin.RouterGroup) {
// LLM相关路由组,使用操作记录中间件
llmRouter := Router.Group("llm").Use(middleware.OperationRecord())
// LLM查询路由组,不使用操作记录中间件(用于GET请求)
llmRouterWithoutRecord := Router.Group("llm")
// 获取API实例
llmApi := api.ApiGroupApp.LLMApi
{
// 需要记录操作的路由(POST请求)
llmRouter.POST("chat", llmApi.ChatCompletion) // LLM聊天完成
llmRouter.POST("stop", llmApi.StopGeneration) // 停止LLM生成
}
{
// 不需要记录操作的路由(GET请求)
llmRouterWithoutRecord.GET("sessions", llmApi.GetActiveSessionsCount) // 获取活跃会话数量
}
}
@@ -0,0 +1,316 @@
package service
import (
"context"
"fmt"
"sync"
"time"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/model/request"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/volcengine/model/response"
"github.com/google/uuid"
"github.com/volcengine/volcengine-go-sdk/service/arkruntime"
"github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
"go.uber.org/zap"
)
type LLMService struct {
client *arkruntime.Client
activeSessions sync.Map // requestID -> context.CancelFunc
}
var LLMServiceApp = new(LLMService)
// InitVolcengineClient 初始化火山引擎客户端
func (l *LLMService) InitVolcengineClient() error {
config := global.GVA_CONFIG.Volcengine
// 验证配置
if config.AccessKey == "" || config.SecretKey == "" {
return fmt.Errorf("volcengine access key or secret key is empty")
}
// 创建ARK Runtime客户端
l.client = arkruntime.NewClientWithAkSk(config.AccessKey, config.SecretKey)
global.GVA_LOG.Info("Volcengine client initialized successfully")
return nil
}
// ChatCompletion 非流式聊天完成
func (l *LLMService) ChatCompletion(req request.ChatRequest) (*response.ChatResponse, error) {
if l.client == nil {
if err := l.InitVolcengineClient(); err != nil {
return nil, err
}
}
// 生成请求ID
if req.RequestID == "" {
req.RequestID = uuid.New().String()
}
// 转换消息格式
messages := make([]*model.ChatCompletionMessage, len(req.Messages))
for i, msg := range req.Messages {
messages[i] = &model.ChatCompletionMessage{
Role: msg.Role,
Content: &model.ChatCompletionMessageContent{
StringValue: &msg.Content,
},
}
}
// 设置模型
modelName := req.Model
if modelName == "" {
modelName = global.GVA_CONFIG.Volcengine.DefaultModel
}
// 创建请求
chatReq := &model.ChatCompletionRequest{
Model: modelName,
Messages: messages,
}
// 设置可选参数
if req.Temperature > 0 {
chatReq.Temperature = float32(req.Temperature)
}
if req.MaxTokens > 0 {
chatReq.MaxTokens = req.MaxTokens
}
if req.TopP > 0 {
chatReq.TopP = float32(req.TopP)
}
// 创建上下文
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
// 存储取消函数
l.activeSessions.Store(req.RequestID, cancel)
defer l.activeSessions.Delete(req.RequestID)
// 调用API
resp, err := l.client.CreateChatCompletion(ctx, chatReq)
if err != nil {
global.GVA_LOG.Error("Chat completion failed", zap.Error(err), zap.String("requestID", req.RequestID))
return nil, fmt.Errorf("chat completion failed: %w", err)
}
// 转换响应格式
chatResp := &response.ChatResponse{
ID: req.RequestID,
Object: "chat.completion",
Created: time.Now().Unix(),
Model: modelName,
Choices: make([]response.Choice, len(resp.Choices)),
}
for i, choice := range resp.Choices {
messageContent := ""
if choice.Message.Content != nil && choice.Message.Content.StringValue != nil {
messageContent = *choice.Message.Content.StringValue
}
chatResp.Choices[i] = response.Choice{
Index: i,
Message: response.Message{
Role: choice.Message.Role,
Content: messageContent,
},
FinishReason: string(choice.FinishReason),
}
}
// 设置使用量统计
chatResp.Usage = &response.Usage{
PromptTokens: resp.Usage.PromptTokens,
CompletionTokens: resp.Usage.CompletionTokens,
TotalTokens: resp.Usage.TotalTokens,
}
global.GVA_LOG.Info("Chat completion successful", zap.String("requestID", req.RequestID))
return chatResp, nil
}
// StreamChatCompletion 流式聊天完成
func (l *LLMService) StreamChatCompletion(req request.ChatRequest, eventChan chan<- response.StreamEvent) error {
if l.client == nil {
if err := l.InitVolcengineClient(); err != nil {
return err
}
}
// 生成请求ID
if req.RequestID == "" {
req.RequestID = uuid.New().String()
}
// 转换消息格式
messages := make([]*model.ChatCompletionMessage, len(req.Messages))
for i, msg := range req.Messages {
messages[i] = &model.ChatCompletionMessage{
Role: msg.Role,
Content: &model.ChatCompletionMessageContent{
StringValue: &msg.Content,
},
}
}
// 设置模型
modelName := req.Model
if modelName == "" {
modelName = global.GVA_CONFIG.Volcengine.DefaultModel
}
// 创建流式请求
chatReq := &model.ChatCompletionRequest{
Model: modelName,
Messages: messages,
Stream: true,
}
// 设置可选参数
if req.Temperature > 0 {
chatReq.Temperature = float32(req.Temperature)
}
if req.MaxTokens > 0 {
chatReq.MaxTokens = req.MaxTokens
}
if req.TopP > 0 {
chatReq.TopP = float32(req.TopP)
}
// 创建上下文
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) // 流式响应需要更长时间
defer cancel()
// 存储取消函数
l.activeSessions.Store(req.RequestID, cancel)
defer l.activeSessions.Delete(req.RequestID)
// 调用流式API
stream_resp, err := l.client.CreateChatCompletionStream(ctx, chatReq)
if err != nil {
global.GVA_LOG.Error("Stream chat completion failed", zap.Error(err), zap.String("requestID", req.RequestID))
return fmt.Errorf("stream chat completion failed: %w", err)
}
defer stream_resp.Close()
// 处理流式响应
for {
select {
case <-ctx.Done():
// 上下文取消
eventChan <- response.StreamEvent{
Event: "error",
Data: response.ChatResponse{
ID: req.RequestID,
Error: &response.APIError{
Code: "context_cancelled",
Message: "Request was cancelled",
Type: "request_cancelled",
},
},
}
return ctx.Err()
default:
// 接收流式数据
recv, err := stream_resp.Recv()
if err != nil {
if err.Error() == "EOF" {
// 流结束
eventChan <- response.StreamEvent{
Event: "done",
Data: response.ChatResponse{
ID: req.RequestID,
Object: "chat.completion.chunk",
},
}
global.GVA_LOG.Info("Stream chat completion finished", zap.String("requestID", req.RequestID))
return nil
}
global.GVA_LOG.Error("Stream receive error", zap.Error(err), zap.String("requestID", req.RequestID))
eventChan <- response.StreamEvent{
Event: "error",
Data: response.ChatResponse{
ID: req.RequestID,
Error: &response.APIError{
Code: "stream_error",
Message: err.Error(),
Type: "stream_error",
},
},
}
return err
}
// 转换并发送响应
chatResp := response.ChatResponse{
ID: req.RequestID,
Object: "chat.completion.chunk",
Created: time.Now().Unix(),
Model: modelName,
Choices: make([]response.Choice, len(recv.Choices)),
}
for i, choice := range recv.Choices {
chatResp.Choices[i] = response.Choice{
Index: i,
Delta: response.Message{
Role: choice.Delta.Role,
Content: choice.Delta.Content,
},
}
if choice.FinishReason != "" {
chatResp.Choices[i].FinishReason = string(choice.FinishReason)
}
}
eventChan <- response.StreamEvent{
Event: "message",
Data: chatResp,
}
}
}
}
// StopGeneration 停止生成
func (l *LLMService) StopGeneration(req request.StopRequest) (*response.StopResponse, error) {
// 查找并取消对应的请求
if cancelFunc, ok := l.activeSessions.Load(req.RequestID); ok {
if cancel, ok := cancelFunc.(context.CancelFunc); ok {
cancel()
l.activeSessions.Delete(req.RequestID)
global.GVA_LOG.Info("Request stopped successfully", zap.String("requestID", req.RequestID))
return &response.StopResponse{
Success: true,
Message: "Request stopped successfully",
RequestID: req.RequestID,
StoppedAt: time.Now().Unix(),
}, nil
}
}
global.GVA_LOG.Warn("Request not found or already completed", zap.String("requestID", req.RequestID))
return &response.StopResponse{
Success: false,
Message: "Request not found or already completed",
RequestID: req.RequestID,
StoppedAt: time.Now().Unix(),
}, nil
}
// GetActiveSessionsCount 获取活跃会话数量
func (l *LLMService) GetActiveSessionsCount() int {
count := 0
l.activeSessions.Range(func(key, value interface{}) bool {
count++
return true
})
return count
}