From 4339b6eee80e5bacaa33061c7b4e5b0a7e70a3bd Mon Sep 17 00:00:00 2001 From: renzhiyuan <465386466@qq.com> Date: Thu, 4 Dec 2025 10:14:57 +0800 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=A8=A1=E5=9E=8B=E4=B8=8E=E6=9D=83=E9=99=90=E6=8E=A7?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config_test.yaml | 4 +- internal/biz/llm_service/ollama.go | 1 + internal/config/config.go | 9 ++-- internal/gateway/client.go | 74 ++++++++++++++++++++++-------- internal/gateway/gateway.go | 25 ++++++++-- internal/pkg/func.go | 10 ++++ internal/server/router/router.go | 5 +- internal/services/chat.go | 48 ++++++++----------- 8 files changed, 114 insertions(+), 62 deletions(-) diff --git a/config/config_test.yaml b/config/config_test.yaml index 8275102..2929dd7 100644 --- a/config/config_test.yaml +++ b/config/config_test.yaml @@ -3,11 +3,12 @@ server: port: 8090 host: "0.0.0.0" + ollama: base_url: "http://127.0.0.1:11434" model: "qwen3-coder:480b-cloud" generate_model: "qwen3-coder:480b-cloud" - vl_model: "qwen2.5vl:7b" + vl_model: "gemini-3-pro-preview" timeout: "120s" level: "info" format: "json" @@ -19,6 +20,7 @@ sys: channel_pool_len: 100 channel_pool_size: 32 llm_pool_len: 5 + heartbeat_interval: 30 redis: host: 47.97.27.195:6379 type: node diff --git a/internal/biz/llm_service/ollama.go b/internal/biz/llm_service/ollama.go index 60d9c78..6527a3b 100644 --- a/internal/biz/llm_service/ollama.go +++ b/internal/biz/llm_service/ollama.go @@ -139,6 +139,7 @@ func (r *OllamaService) RecognizeWithImg(ctx context.Context, requireData *entit Prompt: r.config.DefaultPrompt.ImgRecognize.UserPrompt, Images: requireData.ImgByte, KeepAlive: &api.Duration{Duration: 3600 * time.Second}, + //Think: &api.ThinkValue{Value: false}, }) if err != nil { return diff --git a/internal/config/config.go b/internal/config/config.go index 5c9fa21..da25c39 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -36,10 +36,11 @@ type LLM struct { // SysConfig 系统配置 type SysConfig struct { - SessionLen int `mapstructure:"session_len"` - ChannelPoolLen int `mapstructure:"channel_pool_len"` - ChannelPoolSize int `mapstructure:"channel_pool_size"` - LlmPoolLen int `mapstructure:"llm_pool_len"` + SessionLen int `mapstructure:"session_len"` + ChannelPoolLen int `mapstructure:"channel_pool_len"` + ChannelPoolSize int `mapstructure:"channel_pool_size"` + LlmPoolLen int `mapstructure:"llm_pool_len"` + HeartbeatInterval int `mapstructure:"heartbeat_interval"` } // ServerConfig 服务器配置 diff --git a/internal/gateway/client.go b/internal/gateway/client.go index b49bf45..f90678c 100644 --- a/internal/gateway/client.go +++ b/internal/gateway/client.go @@ -3,33 +3,44 @@ package gateway import ( errors "ai_scheduler/internal/data/error" "ai_scheduler/internal/data/model" - "encoding/hex" - "fmt" - "github.com/gofiber/websocket/v2" + "ai_scheduler/internal/pkg" + "context" + "encoding/binary" + "log" "math/rand" "time" + + "github.com/gofiber/websocket/v2" ) var ( ErrConnClosed = errors.SysErr("连接不存在或已关闭") + rng = rand.New(rand.NewSource(time.Now().UnixNano())) + idBuf = make([]byte, 20) ) type Client struct { - id string // 客户端唯一ID - conn *websocket.Conn // WebSocket 连接 - session string // 会话ID - key string // 应用密钥 - auth string // 用户凭证token - codes []string // 用户权限code - sysInfo *model.AiSy // 系统信息 - tasks []model.AiTask // 任务列表 - sysCode string // 系统编码 + id string // 客户端唯一ID + conn *websocket.Conn // WebSocket 连接 + session string // 会话ID + key string // 应用密钥 + auth string // 用户凭证token + codes []string // 用户权限code + sysInfo *model.AiSy // 系统信息 + tasks []model.AiTask // 任务列表 + sysCode string // 系统编码 + Ctx context.Context + Cancel context.CancelFunc + LastActive time.Time } -func NewClient(conn *websocket.Conn) *Client { +func NewClient(conn *websocket.Conn, ctx context.Context, cancel context.CancelFunc) *Client { + return &Client{ - id: generateClientID(), - conn: conn, + id: generateClientID(), + conn: conn, + Ctx: ctx, + Cancel: cancel, } } @@ -103,12 +114,16 @@ func (c *Client) SendFunc(msg []byte) error { // 生成唯一的客户端ID func generateClientID() string { - // 使用时间戳+随机数确保唯一性 + // 1. 时间戳 timestamp := time.Now().UnixNano() - randomBytes := make([]byte, 4) - rand.Read(randomBytes) - randomStr := hex.EncodeToString(randomBytes) - return fmt.Sprintf("%d%s", timestamp, randomStr) + binary.BigEndian.PutUint64(idBuf[:8], uint64(timestamp)) + + // 2. 随机数(4字节) + binary.BigEndian.PutUint32(idBuf[8:12], rng.Uint32()) + + // 3. 十六进制编码 + n := pkg.HexEncode(idBuf[:12], idBuf[12:]) + return string(idBuf[12 : 12+n]) } // 连接数据验证和收集 @@ -136,3 +151,22 @@ func (c *Client) DataAuth() (err error) { } return } + +func (c *Client) InitHeartbeat(timeoutSecond time.Duration) { + ticker := time.NewTicker(timeoutSecond * time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + //*2是防止丢包,连续丢包两次,再加5s网络延迟容错 + if time.Since(c.LastActive) > (timeoutSecond*2*time.Second + 5) { // 5秒容错 + log.Println("Heartbeat timeout", "id", c.id) + c.conn.WriteMessage(websocket.CloseMessage, []byte("Heartbeat timeout")) + c.conn.Close() + return + } + case <-c.Ctx.Done(): + return + } + } +} diff --git a/internal/gateway/gateway.go b/internal/gateway/gateway.go index 0f0e6f3..03ce961 100644 --- a/internal/gateway/gateway.go +++ b/internal/gateway/gateway.go @@ -2,7 +2,9 @@ package gateway import ( "errors" + "log" "sync" + "time" ) type Gateway struct { @@ -20,14 +22,27 @@ func NewGateway() *Gateway { func (g *Gateway) AddClient(c *Client) { g.mu.Lock() - defer g.mu.Unlock() + defer func() { + g.mu.Unlock() + //心跳开始计时 + c.LastActive = time.Now() + log.Println("client connected:", c.GetID()) + log.Println("客户端已连接") + }() g.clients[c.GetID()] = c } -func (g *Gateway) RemoveClient(clientID string) { +func (g *Gateway) Cleanup(clientID string) { g.mu.Lock() - defer g.mu.Unlock() - delete(g.clients, clientID) + defer func() { + if c, ex := g.clients[clientID]; ex { + delete(g.clients, clientID) + _ = c.conn.Close() + c.Cancel() + } + g.mu.Unlock() + log.Println("client disconnected:", clientID) + }() for uid, list := range g.uidMap { newList := []string{} for _, cid := range list { @@ -37,6 +52,7 @@ func (g *Gateway) RemoveClient(clientID string) { } g.uidMap[uid] = newList } + } func (g *Gateway) SendToAll(msg []byte) { @@ -63,6 +79,7 @@ func (g *Gateway) BindUid(clientID, uid string) error { return errors.New("client not found") } g.uidMap[uid] = append(g.uidMap[uid], clientID) + log.Printf("bind %s -> uid:%s\n", clientID, uid) return nil } diff --git a/internal/pkg/func.go b/internal/pkg/func.go index 4e6481a..f6006ac 100644 --- a/internal/pkg/func.go +++ b/internal/pkg/func.go @@ -55,3 +55,13 @@ func ValidateImageURL(rawURL string) error { return nil } + +// hexEncode 将 src 的二进制数据编码为十六进制字符串,写入 dst,返回写入长度 +func HexEncode(src, dst []byte) int { + const hextable = "0123456789abcdef" + for i := 0; i < len(src); i++ { + dst[i*2] = hextable[src[i]>>4] + dst[i*2+1] = hextable[src[i]&0xf] + } + return len(src) * 2 +} diff --git a/internal/server/router/router.go b/internal/server/router/router.go index 5c935d7..1a6fd8b 100644 --- a/internal/server/router/router.go +++ b/internal/server/router/router.go @@ -82,10 +82,7 @@ func SetupRoutes(app *fiber.App, ChatService *services.ChatService, sessionServi func routerSocket(app *fiber.App, chatService *services.ChatService) { ws := app.Group("ws/v1/") // WebSocket 路由配置 - ws.Get("/chat", websocket.New(func(c *websocket.Conn) { - // 可以在这里添加握手前的中间件逻辑(如头校验) - chatService.Chat(c) // 调用实际的 Chat 处理函数 - }, websocket.Config{ + ws.Get("/chat", websocket.New(chatService.Chat, websocket.Config{ // 可选配置:跨域检查、最大负载大小等 HandshakeTimeout: 10 * time.Second, //Subprotocols: []string{"json", "msgpack"}, diff --git a/internal/services/chat.go b/internal/services/chat.go index ff75bee..a01ba73 100644 --- a/internal/services/chat.go +++ b/internal/services/chat.go @@ -6,9 +6,11 @@ import ( "ai_scheduler/internal/data/constants" "ai_scheduler/internal/entitys" "ai_scheduler/internal/gateway" + "context" "encoding/json" "log" "sync" + "time" "github.com/gofiber/fiber/v2" "github.com/gofiber/websocket/v2" @@ -38,20 +40,6 @@ func NewChatService( } } -// ToolCallResponse 工具调用响应 -type ToolCallResponse struct { - ID string `json:"id" example:"call_1"` - Type string `json:"type" example:"function"` - Function FunctionCallResponse `json:"function"` - Result interface{} `json:"result,omitempty"` -} - -// FunctionCallResponse 函数调用响应 -type FunctionCallResponse struct { - Name string `json:"name" example:"get_weather"` - Arguments interface{} `json:"arguments"` -} - func (h *ChatService) ChatFail(c *websocket.Conn, content string) { err := c.WriteMessage(websocket.TextMessage, []byte(content)) if err != nil { @@ -63,15 +51,18 @@ func (h *ChatService) ChatFail(c *websocket.Conn, content string) { // Chat 处理WebSocket聊天连接 // 这是WebSocket处理的主入口函数 func (h *ChatService) Chat(c *websocket.Conn) { - // 创建新的客户端实例 - h.mu.Lock() - client := gateway.NewClient(c) - h.mu.Unlock() + ctx, cancel := context.WithCancel(context.Background()) + // 创建新的客户端实例 + client := gateway.NewClient(c, ctx, cancel) + // 心跳检测 + go client.InitHeartbeat(time.Duration(h.cfg.Sys.HeartbeatInterval)) // 将客户端添加到网关管理 h.Gw.AddClient(client) - log.Println("client connected:", client.GetID()) - log.Println("客户端已连接") + // 确保在函数返回时移除客户端并关闭连接 + defer func() { + h.Gw.Cleanup(client.GetID()) + }() // 绑定会话ID uid := c.Query("x-session") @@ -79,7 +70,6 @@ func (h *ChatService) Chat(c *websocket.Conn) { if err := h.Gw.BindUid(client.GetID(), uid); err != nil { log.Println("绑定UID错误:", err) } - log.Printf("bind %s -> uid:%s\n", client.GetID(), uid) } // 验证并收集连接数据,后续对话中会使用 @@ -89,13 +79,6 @@ func (h *ChatService) Chat(c *websocket.Conn) { return } - // 确保在函数返回时移除客户端并关闭连接 - defer func() { - h.Gw.RemoveClient(client.GetID()) - _ = c.Close() - log.Println("client disconnected:", client.GetID()) - }() - // 循环读取客户端消息 for { // 读取消息 @@ -104,7 +87,14 @@ func (h *ChatService) Chat(c *websocket.Conn) { log.Println("读取错误:", err) break } - + //if string(message) == `{"type":"ping"}` { + // client.LastActive = time.Now() + // if err := c.WriteMessage(websocket.TextMessage, []byte(`{"type":"pong"}`)); err != nil { + // log.Println("Heartbeat response failed", "id", client.GetID(), "err", err) + // return + // } + // continue + //} // 处理消息 msg, chatType := h.handleMessageToString(c, messageType, message) if chatType == constants.ConnStatusClosed { From f2638b32b55f374307e2cf7960ef9085d55c3f4f Mon Sep 17 00:00:00 2001 From: wolter <11@gmail> Date: Fri, 5 Dec 2025 09:12:06 +0800 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=E5=BF=83=E8=B7=B3=E6=A3=80?= =?UTF-8?q?=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/do/ctx.go | 16 +++-- internal/biz/router.go | 4 +- internal/entitys/response.go | 5 +- internal/gateway/client.go | 77 +++++++++++++++++------ internal/gateway/gateway.go | 6 +- internal/services/chat.go | 117 +++++++++++++++++++++-------------- 6 files changed, 144 insertions(+), 81 deletions(-) diff --git a/internal/biz/do/ctx.go b/internal/biz/do/ctx.go index ed8749d..eb2c5a5 100644 --- a/internal/biz/do/ctx.go +++ b/internal/biz/do/ctx.go @@ -19,8 +19,6 @@ import ( "gitea.cdlsxd.cn/self-tools/l_request" "github.com/gofiber/fiber/v2/log" - "github.com/gofiber/websocket/v2" - "xorm.io/builder" ) @@ -141,10 +139,10 @@ func (d *Do) loadChatHistory(ctx context.Context, requireData *entitys.RequireDa return nil } -func (d *Do) MakeCh(c *websocket.Conn, requireData *entitys.RequireData) (ctx context.Context, deferFunc func()) { +func (d *Do) MakeCh(client *gateway.Client, requireData *entitys.RequireData) (ctx context.Context, deferFunc func()) { requireData.Ch = make(chan entitys.Response) ctx, cancel := context.WithCancel(context.Background()) - done := d.startMessageHandler(ctx, c, requireData) + done := d.startMessageHandler(ctx, client, requireData) return ctx, func() { close(requireData.Ch) //关闭主通道 <-done // 等待消息处理完成 @@ -235,7 +233,7 @@ func (d *Do) getTasks(sysId int32) (tasks []model.AiTask, err error) { // startMessageHandler 启动独立的消息处理协程 func (d *Do) startMessageHandler( ctx context.Context, - c *websocket.Conn, + client *gateway.Client, requireData *entitys.RequireData, ) <-chan struct{} { done := make(chan struct{}) @@ -259,7 +257,7 @@ func (d *Do) startMessageHandler( hisLog.HisId = AiRes.HisID } - _ = entitys.MsgSend(c, entitys.Response{ + _ = entitys.MsgSend(client, entitys.Response{ Content: pkg.JsonStringIgonErr(hisLog), Type: entitys.ResponseEnd, }) @@ -267,7 +265,7 @@ func (d *Do) startMessageHandler( }() for v := range requireData.Ch { // 自动检测通道关闭 - if err := sendWithTimeout(c, v, 2*time.Second); err != nil { + if err := sendWithTimeout(client, v, 10*time.Second); err != nil { log.Errorf("Send error: %v", err) return } @@ -281,7 +279,7 @@ func (d *Do) startMessageHandler( } // 辅助函数:带超时的 WebSocket 发送 -func sendWithTimeout(c *websocket.Conn, data entitys.Response, timeout time.Duration) error { +func sendWithTimeout(client *gateway.Client, data entitys.Response, timeout time.Duration) error { sendCtx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -294,7 +292,7 @@ func sendWithTimeout(c *websocket.Conn, data entitys.Response, timeout time.Dura close(done) }() // 如果 MsgSend 阻塞,这里会卡住 - err := entitys.MsgSend(c, data) + err := entitys.MsgSend(client, data) done <- err }() diff --git a/internal/biz/router.go b/internal/biz/router.go index 6dcc233..975a487 100644 --- a/internal/biz/router.go +++ b/internal/biz/router.go @@ -39,11 +39,9 @@ func (r *AiRouterBiz) RouteWithSocket(client *gateway.Client, req *entitys.ChatS requireData := &entitys.RequireData{ Req: req, } - // 获取WebSocket连接 - conn := client.GetConn() //初始化通道/上下文 - ctx, clearFunc := r.do.MakeCh(conn, requireData) + ctx, clearFunc := r.do.MakeCh(client, requireData) defer func() { if err != nil { entitys.ResError(requireData.Ch, "", err.Error()) diff --git a/internal/entitys/response.go b/internal/entitys/response.go index cdadc98..44e053b 100644 --- a/internal/entitys/response.go +++ b/internal/entitys/response.go @@ -1,6 +1,7 @@ package entitys import ( + "ai_scheduler/internal/gateway" "encoding/json" "github.com/gofiber/websocket/v2" ) @@ -100,13 +101,13 @@ func MsgSet(msgType ResponseType, msg string, done bool) []byte { return jsonByte } -func MsgSend(c *websocket.Conn, msg Response) error { +func MsgSend(client *gateway.Client, msg Response) error { // 检查上下文是否已取消 if msg.Type == ResponseText { } jsonByte, _ := json.Marshal(msg) - return c.WriteMessage(websocket.TextMessage, jsonByte) + return client.SendFunc(jsonByte) } func MsgSendByte(c *websocket.Conn, msg []byte) { diff --git a/internal/gateway/client.go b/internal/gateway/client.go index f90678c..a293daa 100644 --- a/internal/gateway/client.go +++ b/internal/gateway/client.go @@ -3,11 +3,11 @@ package gateway import ( errors "ai_scheduler/internal/data/error" "ai_scheduler/internal/data/model" - "ai_scheduler/internal/pkg" "context" - "encoding/binary" + "github.com/google/uuid" "log" "math/rand" + "sync" "time" "github.com/gofiber/websocket/v2" @@ -32,6 +32,7 @@ type Client struct { Ctx context.Context Cancel context.CancelFunc LastActive time.Time + mu sync.Mutex } func NewClient(conn *websocket.Conn, ctx context.Context, cancel context.CancelFunc) *Client { @@ -41,6 +42,7 @@ func NewClient(conn *websocket.Conn, ctx context.Context, cancel context.CancelF conn: conn, Ctx: ctx, Cancel: cancel, + mu: sync.Mutex{}, } } @@ -74,7 +76,7 @@ func (c *Client) GetCodes() []string { return c.codes } -// GetSysCode 获取系统编码 +// 获取系统编码 func (c *Client) GetSysCode() string { return c.sysCode } @@ -104,26 +106,51 @@ func (c *Client) SetCodes(codes []string) { c.codes = codes } +// Close 关闭客户端连接 +func (c *Client) Close() { + //c.mu.Lock() + //defer c.mu.Unlock() + if c.conn != nil { + _ = c.conn.Close() + c.conn = nil + } +} + // SendFunc 发送消息到客户端 func (c *Client) SendFunc(msg []byte) error { - if c.conn != nil { - return c.conn.WriteMessage(websocket.TextMessage, msg) + return c.SendMessage(websocket.TextMessage, msg) +} + +// 在Client结构体中添加更详细的日志 +func (c *Client) SendMessage(msgType int, msg []byte) error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.conn == nil { + return ErrConnClosed } - return ErrConnClosed + + err := c.conn.WriteMessage(msgType, msg) + if err != nil { + log.Printf("发送消息失败: %v, 客户端ID: %s, 消息类型: %d", + err, c.id, msgType) + } + return err } // 生成唯一的客户端ID func generateClientID() string { - // 1. 时间戳 - timestamp := time.Now().UnixNano() - binary.BigEndian.PutUint64(idBuf[:8], uint64(timestamp)) - - // 2. 随机数(4字节) - binary.BigEndian.PutUint32(idBuf[8:12], rng.Uint32()) - - // 3. 十六进制编码 - n := pkg.HexEncode(idBuf[:12], idBuf[12:]) - return string(idBuf[12 : 12+n]) + return uuid.New().String() + //// 1. 时间戳 + //timestamp := time.Now().UnixNano() + //binary.BigEndian.PutUint64(idBuf[:8], uint64(timestamp)) + // + //// 2. 随机数(4字节) + //binary.BigEndian.PutUint32(idBuf[8:12], rng.Uint32()) + // + //// 3. 十六进制编码 + //n := pkg.HexEncode(idBuf[:12], idBuf[12:]) + //return string(idBuf[12 : 12+n]) } // 连接数据验证和收集 @@ -152,6 +179,7 @@ func (c *Client) DataAuth() (err error) { return } +// 总结:目前绝大多数浏览器不支持直接发送WebSocket Ping帧,因此在实际开发中,应该实现应用层ping机制作为主要心跳检测方案,todo 同时保留对未来可能的原生支持的兼容检测。 func (c *Client) InitHeartbeat(timeoutSecond time.Duration) { ticker := time.NewTicker(timeoutSecond * time.Second) defer ticker.Stop() @@ -160,9 +188,12 @@ func (c *Client) InitHeartbeat(timeoutSecond time.Duration) { case <-ticker.C: //*2是防止丢包,连续丢包两次,再加5s网络延迟容错 if time.Since(c.LastActive) > (timeoutSecond*2*time.Second + 5) { // 5秒容错 - log.Println("Heartbeat timeout", "id", c.id) - c.conn.WriteMessage(websocket.CloseMessage, []byte("Heartbeat timeout")) - c.conn.Close() + log.Println("心跳超时", "clientId", c.id) + err := c.SendMessage(websocket.CloseMessage, []byte("Heartbeat timeout")) + if err != nil { + log.Println("发送心跳超时消息失败", err) + } + c.Close() return } case <-c.Ctx.Done(): @@ -170,3 +201,11 @@ func (c *Client) InitHeartbeat(timeoutSecond time.Duration) { } } } + +// 在Client结构体中添加ReadMessage方法 +func (c *Client) ReadMessage() (messageType int, message []byte, err error) { + if c.conn == nil { + return 0, nil, ErrConnClosed + } + return c.conn.ReadMessage() +} diff --git a/internal/gateway/gateway.go b/internal/gateway/gateway.go index 03ce961..6e09bdc 100644 --- a/internal/gateway/gateway.go +++ b/internal/gateway/gateway.go @@ -34,15 +34,17 @@ func (g *Gateway) AddClient(c *Client) { func (g *Gateway) Cleanup(clientID string) { g.mu.Lock() + // 从网关管理中移除客户端 defer func() { if c, ex := g.clients[clientID]; ex { delete(g.clients, clientID) - _ = c.conn.Close() + c.Close() c.Cancel() } g.mu.Unlock() log.Println("client disconnected:", clientID) }() + // 从所有绑定的UID列表中移除该客户端 for uid, list := range g.uidMap { newList := []string{} for _, cid := range list { @@ -79,7 +81,7 @@ func (g *Gateway) BindUid(clientID, uid string) error { return errors.New("client not found") } g.uidMap[uid] = append(g.uidMap[uid], clientID) - log.Printf("bind %s -> uid:%s\n", clientID, uid) + log.Printf("绑定 clientId %s -> uid:%s\n", clientID, uid) return nil } diff --git a/internal/services/chat.go b/internal/services/chat.go index a01ba73..11dda38 100644 --- a/internal/services/chat.go +++ b/internal/services/chat.go @@ -55,82 +55,109 @@ func (h *ChatService) Chat(c *websocket.Conn) { // 创建新的客户端实例 client := gateway.NewClient(c, ctx, cancel) - // 心跳检测 - go client.InitHeartbeat(time.Duration(h.cfg.Sys.HeartbeatInterval)) - // 将客户端添加到网关管理 + + // 验证并收集连接数据,后续对话中会使用 + if err := client.DataAuth(); err != nil { + log.Println("数据验证错误:", err) + _ = client.SendFunc([]byte(err.Error())) + client.Close() + return + } + + // 验证通过后,将客户端添加到网关管理 h.Gw.AddClient(client) + + // 使用信号量限制并发处理的消息数量 + semaphore := make(chan struct{}, 1) // 最多1个并发消息处理 + // 用于等待所有goroutine完成的wait group + var wg sync.WaitGroup // 确保在函数返回时移除客户端并关闭连接 defer func() { h.Gw.Cleanup(client.GetID()) + close(semaphore) // 关闭信号量通道 + wg.Wait() // 等待所有消息处理goroutine完成 }() - // 绑定会话ID - uid := c.Query("x-session") - if uid != "" { + // 绑定会话ID, sessionId 为空时, 则不绑定 + if uid := client.GetSession(); uid != "" { if err := h.Gw.BindUid(client.GetID(), uid); err != nil { log.Println("绑定UID错误:", err) } } - // 验证并收集连接数据,后续对话中会使用 - if err := client.DataAuth(); err != nil { - log.Println("数据验证错误:", err) - h.ChatFail(c, err.Error()) - return - } + // 开启心跳检测 + go client.InitHeartbeat(time.Duration(h.cfg.Sys.HeartbeatInterval)) // 循环读取客户端消息 for { - // 读取消息 - messageType, message, err := c.ReadMessage() + messageType, message, err := client.ReadMessage() if err != nil { - log.Println("读取错误:", err) + log.Printf("读取错误: %v, 客户端ID: %s", err, client.GetID()) break } - //if string(message) == `{"type":"ping"}` { - // client.LastActive = time.Now() - // if err := c.WriteMessage(websocket.TextMessage, []byte(`{"type":"pong"}`)); err != nil { - // log.Println("Heartbeat response failed", "id", client.GetID(), "err", err) - // return - // } - // continue - //} - // 处理消息 - msg, chatType := h.handleMessageToString(c, messageType, message) - if chatType == constants.ConnStatusClosed { - break - } - if chatType == constants.ConnStatusIgnore { + + // 处理心跳消息 + if messageType == websocket.PingMessage || string(message) == "PING" { + client.LastActive = time.Now() + msgType := websocket.TextMessage + if messageType == websocket.PingMessage { + msgType = websocket.PongMessage + } + if err = client.SendMessage(msgType, []byte(`PONG`)); err != nil { + log.Printf("发送pong消息失败: %v", err) + } continue } - log.Printf("收到消息: %s", string(msg)) + // 使用信号量限制并发 + semaphore <- struct{}{} + wg.Add(1) + go func(msgType int, msg []byte) { + defer func() { + <-semaphore + wg.Done() + // 恢复panic + if r := recover(); r != nil { + log.Printf("消息处理goroutine发生panic: %v", r) + } + }() - // 解析请求 - var req entitys.ChatSockRequest - if err = json.Unmarshal(msg, &req); err != nil { - log.Println("JSON parse error:", err) - continue - } + // 消息处理逻辑 + h.processMessage(client, msgType, msg) + }(messageType, message) + } +} - // 路由处理请求 - err = h.routerBiz.RouteWithSocket(client, &req) - if err != nil { - log.Println("处理失败:", err) - } +// 将消息处理逻辑提取到单独的方法 +func (h *ChatService) processMessage(client *gateway.Client, msgType int, msg []byte) { + // 处理消息 + processedMsg, _ := h.handleMessageToString(client, msgType, msg) + log.Printf("收到消息:消息类型 %d, 内容 %s, 客户端ID: %s", + msgType, string(processedMsg), client.GetID()) + + // 解析请求 + var req entitys.ChatSockRequest + if err := json.Unmarshal(processedMsg, &req); err != nil { + log.Printf("JSON解析错误: %v, 客户端ID: %s", err, client.GetID()) + return + } + + // 路由处理请求 + if err := h.routerBiz.RouteWithSocket(client, &req); err != nil { + log.Printf("处理失败: %v, 客户端ID: %s", err, client.GetID()) } } // handleMessageToString 处理不同类型的WebSocket消息 // 参数: -// - c: WebSocket连接 +// - client: 客户端对象 // - msgType: 消息类型 // - msg: 消息内容 // // 返回: // - text: 处理后的文本内容 // - chatType: 连接状态 -func (h *ChatService) handleMessageToString(c *websocket.Conn, msgType int, msg any) (text []byte, chatType constants.ConnStatus) { +func (h *ChatService) handleMessageToString(client *gateway.Client, msgType int, msg any) (text []byte, chatType constants.ConnStatus) { switch msgType { case websocket.TextMessage: return msg.([]byte), constants.ConnStatusNormal @@ -140,15 +167,13 @@ func (h *ChatService) handleMessageToString(c *websocket.Conn, msgType int, msg return nil, constants.ConnStatusClosed case websocket.PingMessage: - // 可选:回复 Pong - c.WriteMessage(websocket.PongMessage, nil) return nil, constants.ConnStatusIgnore case websocket.PongMessage: return nil, constants.ConnStatusIgnore default: + log.Printf("未知的消息类型: %d", msgType) return nil, constants.ConnStatusIgnore } - return msg.([]byte), constants.ConnStatusIgnore } func (s *ChatService) Useful(c *fiber.Ctx) error { From 325ea8d78c9425dc7c8e63bbf3845f1914296da1 Mon Sep 17 00:00:00 2001 From: wolter <11@gmail> Date: Fri, 5 Dec 2025 14:48:49 +0800 Subject: [PATCH 03/12] feat: Dockerfile --- Dockerfile | 26 ++++++++++++++++++++++++-- deploy.sh | 14 +++++++------- go.mod | 6 ++---- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4b5b1ec..d1cf2c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,25 @@ +## 使用官方Go镜像作为构建环境 +FROM golang:1.24.0-alpine AS builder + +# 设置工作目录 +WORKDIR /app + +# 使用国内镜像源加速APK包下载 +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories + +# 使用国内镜像源加速依赖下载 +ENV GOPROXY=https://goproxy.cn,direct + +# 复制项目源码 +COPY . . + +# 复制go模块依赖文件 +COPY go.mod go.sum ./ +RUN go mod download + +# 编译Go应用程序,生成静态链接的二进制文件 +RUN go build -ldflags="-s -w" -o server ./cmd/server + # 创建最终镜像,用于运行编译后的Go程序 FROM alpine @@ -12,10 +34,10 @@ RUN echo 'http://mirrors.ustc.edu.cn/alpine/v3.5/main' > /etc/apk/repositories \ WORKDIR /app # 将编译好的二进制文件从构建阶段复制到运行阶段 -COPY ./ /app +COPY --from=builder /app/server ./server ENV TZ=Asia/Shanghai # 设置容器启动时运行的命令 -CMD ["./bin/server"] +CMD ["./server"] diff --git a/deploy.sh b/deploy.sh index e813827..ab694f2 100644 --- a/deploy.sh +++ b/deploy.sh @@ -1,9 +1,9 @@ -export GO111MODULE=on -export GOPROXY=https://goproxy.cn,direct -export GOPATH=/root/go -export GOCACHE=/root/.cache/go-build +#export GO111MODULE=on +#export GOPROXY=https://goproxy.cn,direct +#export GOPATH=/root/go +#export GOCACHE=/root/.cache/go-build export CONTAINER_NAME=ai_scheduler -export CGO_ENABLED='0' +#export CGO_ENABLED='0' MODE="$1" @@ -22,8 +22,8 @@ fi git fetch origin git checkout "$BRANCH" git pull origin "$BRANCH" -go mod tidy -make build +#go mod tidy +#make build docker build -t ${CONTAINER_NAME} . docker stop ${CONTAINER_NAME} docker rm -f ${CONTAINER_NAME} diff --git a/go.mod b/go.mod index 6b08e4a..cee04ec 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module ai_scheduler go 1.24.0 -toolchain go1.24.7 - require ( gitea.cdlsxd.cn/self-tools/l_request v1.0.8 github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.12 @@ -23,8 +21,6 @@ require ( github.com/spf13/viper v1.17.0 github.com/tmc/langchaingo v0.1.13 google.golang.org/grpc v1.64.0 - google.golang.org/protobuf v1.34.1 - gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql v1.6.0 gorm.io/gorm v1.31.0 xorm.io/builder v0.3.13 @@ -82,5 +78,7 @@ require ( golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) From 0f9ac06f73aff5bdda6a26ffc38aa1248fef67bf Mon Sep 17 00:00:00 2001 From: wolter <11@gmail> Date: Fri, 5 Dec 2025 15:08:28 +0800 Subject: [PATCH 04/12] feat: Dockerfile1 --- Dockerfile | 6 ++++++ deploy.sh | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d1cf2c1..2c0e527 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,9 @@ COPY . . COPY go.mod go.sum ./ RUN go mod download +RUN go install github.com/google/wire/cmd/wire@latest +RUN wire ./cmd/server + # 编译Go应用程序,生成静态链接的二进制文件 RUN go build -ldflags="-s -w" -o server ./cmd/server @@ -35,6 +38,9 @@ WORKDIR /app # 将编译好的二进制文件从构建阶段复制到运行阶段 COPY --from=builder /app/server ./server +# 复制配置文件夹 +COPY --from=builder /app/config ./config + ENV TZ=Asia/Shanghai # 设置容器启动时运行的命令 diff --git a/deploy.sh b/deploy.sh index ab694f2..f02e162 100644 --- a/deploy.sh +++ b/deploy.sh @@ -33,6 +33,6 @@ docker run -itd \ -e "OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434}" \ -e "MODE=${MODE}" \ -p 8090:8090 \ - "${CONTAINER_NAME}" ./bin/server --config "./${CONFIG_FILE}" + "${CONTAINER_NAME}" ./server --config "./${CONFIG_FILE}" docker logs -f ${CONTAINER_NAME} \ No newline at end of file From 881310c0ccc568e9b671f3c4e9124b1e8fd3f1c3 Mon Sep 17 00:00:00 2001 From: fuzhongyun <15339891972@163.com> Date: Fri, 5 Dec 2025 17:51:41 +0800 Subject: [PATCH 05/12] =?UTF-8?q?feat:=201.=E6=96=B0=E5=A2=9Evllm=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E5=8F=8A=E7=9B=B8=E5=85=B3=E6=96=B9=E6=B3=95?= =?UTF-8?q?=20=202.=E5=9B=BE=E7=89=87=E8=AF=86=E5=88=AB=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E5=88=B0vllm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config_env.yaml | 86 +++++++++++++++++++++++++ config/config_test.yaml | 5 ++ go.mod | 28 +++++++- go.sum | 88 ++++++++++++++++++++++++++ internal/biz/llm_service/ollama.go | 42 ++++++++++-- internal/config/config.go | 38 ++++++----- internal/pkg/provider_set.go | 2 + internal/pkg/utils_vllm/client.go | 60 ++++++++++++++++++ internal/pkg/utils_vllm/client_test.go | 66 +++++++++++++++++++ 9 files changed, 391 insertions(+), 24 deletions(-) create mode 100644 config/config_env.yaml create mode 100644 internal/pkg/utils_vllm/client.go create mode 100644 internal/pkg/utils_vllm/client_test.go diff --git a/config/config_env.yaml b/config/config_env.yaml new file mode 100644 index 0000000..b6fa262 --- /dev/null +++ b/config/config_env.yaml @@ -0,0 +1,86 @@ +# 服务器配置 +server: + port: 8090 + host: "0.0.0.0" + +ollama: + base_url: "http://192.168.6.109:11434" + model: "qwen3-coder:480b-cloud" + generate_model: "qwen3-coder:480b-cloud" + vl_model: "qwen2.5vl:7b" + timeout: "120s" + level: "info" + format: "json" + +vllm: + base_url: "http://117.175.169.61:16001/v1" + vl_model: "models/Qwen2.5-VL-3B-Instruct-AWQ" + timeout: "120s" + level: "info" + + +sys: + session_len: 6 + channel_pool_len: 100 + channel_pool_size: 32 + llm_pool_len: 5 +redis: + host: 47.97.27.195:6379 + type: node + pass: lansexiongdi@666 + key: report-api-test + pollSize: 5 #连接池大小,不配置,或配置为0表示不启用连接池 + minIdleConns: 2 #最小空闲连接数 + maxIdleTime: 30 #每个连接最大空闲时间,如果超过了这个时间会被关闭 + tls: 30 + db: +db: + driver: mysql + source: root:SD###sdf323r343@tcp(121.199.38.107:3306)/sys_ai_test?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai + +tools: + zltxOrderDetail: + enabled: true + base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/direct/ai/%s" + add_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/direct/log/%s/%s" + api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzU4MDkxOTU4LCJuYmYiOjE3NTgwOTAxNTgsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.Bjsx9f8yfcrV9EWxb0n6POwnXVOq9XPRD78JFZnnf1_VAVMN78W4W570SZL27PWuDnkD7E4oUg6RzeZwZgl7BZrNpNr-a-QpNC5qCptqrqXeNfVStmX7pxWA8GqnzI8ybkZgbhQ58Gje7DzdJtBq_8zte_LDaYhTYXdIc5EAG0AbCzAk22nPTl47nkMeHtmisXQVLEsdibl1hW3ViFJlXwfXvUrOENItmL1_mRYkggUB0MaTu2nHJOYM6PaOVGLHx-74eepnmK2rm6konFEb6ed-Ukc6gVR-nM9yWZaYLYNGNKJLwZoCX3tRuerq74n4kzQgWmUEJeaVI1yIGSw1zw" + zltxProduct: + enabled: true + base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/oursProduct" + add_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/platformProduct/getProductsByOfficialProductId" + api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzU2MTgyNTM1LCJuYmYiOjE3NTYxODA3MzUsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.N1xv1PYbcO8_jR5adaczc16YzGsr4z101gwEZdulkRaREBJNYTOnFrvRxTFx3RJTooXsqTqroE1MR84v_1WPX6BS6kKonA-kC1Jgot6yrt5rFWhGNGb2Cpr9rKIFCCQYmiGd3AUgDazEeaQ0_sodv3E-EXg9VfE1SX8nMcck9Yjnc8NCy7RTWaBIaSeOdZcEl-JfCD0S6GSx3oErp_hk-U9FKGwf60wAuDGTY1R0BP4BYpcEqS-C2LSnsSGyURi54Cuk5xH8r1WuF0Dm5bwAj5d7Hvs77-N_sUF-C5ONqyZJRAEhYLgcmN9RX_WQZfizdQJxizlTczdpzYfy-v-1eQ" + zltxOrderStatistics: + base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/direct/ai/search/" + enabled: true + api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzU2MTgyNTM1LCJuYmYiOjE3NTYxODA3MzUsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.N1xv1PYbcO8_jR5adaczc16YzGsr4z101gwEZdulkRaREBJNYTOnFrvRxTFx3RJTooXsqTqroE1MR84v_1WPX6BS6kKonA-kC1Jgot6yrt5rFWhGNGb2Cpr9rKIFCCQYmiGd3AUgDazEeaQ0_sodv3E-EXg9VfE1SX8nMcck9Yjnc8NCy7RTWaBIaSeOdZcEl-JfCD0S6GSx3oErp_hk-U9FKGwf60wAuDGTY1R0BP4BYpcEqS-C2LSnsSGyURi54Cuk5xH8r1WuF0Dm5bwAj5d7Hvs77-N_sUF-C5ONqyZJRAEhYLgcmN9RX_WQZfizdQJxizlTczdpzYfy-v-1eQ" + knowledge: + base_url: "http://117.175.169.61:10000" + enabled: true + DingTalkBot: + enabled: true + api_key: "dingsbbntrkeiyazcfdg" + api_secret: "ObqxwyR20r9rVNhju0sCPQyQA98_FZSc32W4vgxnGFH_b02HZr1BPCJsOAF816nu" + zltxOrderAfterSaleSupplier: + enabled: true + base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/afterSales/directs" + zltxOrderAfterSaleReseller: + enabled: true + base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/afterSales/reseller_pre_ai" + zltxOrderAfterSaleResellerBatch: + enabled: true + base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/afterSales/reseller_pre_ai" + + + +default_prompt: + img_recognize: + system_prompt: + '你是一个具备图像理解与用户意图分析能力的智能助手。当用户提供一张图片时,请完成以下任务: + 1. 关键信息提取: + 提取出图片中对用户可能有用的关键信息(例如金额、日期、标题、编号、联系信息、商品名称等)。 + 若图片为文档类(如合同、发票、收据),请结构化输出关键字段(如客户名称、金额、开票日期等)。 + ' + user_prompt: '识别图片内容' +# 权限配置 +permissionConfig: + permission_url: "http://api.test.user.1688sup.cn:8001/v1/menu/myCodes?systemId=" diff --git a/config/config_test.yaml b/config/config_test.yaml index 8275102..0958db3 100644 --- a/config/config_test.yaml +++ b/config/config_test.yaml @@ -12,6 +12,11 @@ ollama: level: "info" format: "json" +vllm: + base_url: "http://127.0.0.1:8001/v1" + vl_model: "models/Qwen2.5-VL-3B-Instruct-AWQ" + timeout: "120s" + level: "info" sys: diff --git a/go.mod b/go.mod index 6b08e4a..f49df60 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,8 @@ require ( github.com/alibabacloud-go/dingtalk v1.6.96 github.com/alibabacloud-go/tea v1.2.2 github.com/alibabacloud-go/tea-utils/v2 v2.0.6 + github.com/cloudwego/eino v0.7.7 + github.com/cloudwego/eino-ext/components/model/openai v0.1.5 github.com/emirpasic/gods v1.18.1 github.com/faabiosr/cachego v0.26.0 github.com/fastwego/dingding v1.0.0-beta.4 @@ -23,8 +25,6 @@ require ( github.com/spf13/viper v1.17.0 github.com/tmc/langchaingo v0.1.13 google.golang.org/grpc v1.64.0 - google.golang.org/protobuf v1.34.1 - gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql v1.6.0 gorm.io/gorm v1.31.0 xorm.io/builder v0.3.13 @@ -39,31 +39,49 @@ require ( github.com/alibabacloud-go/tea-xml v1.1.3 // indirect github.com/aliyun/credentials-go v1.4.6 // indirect github.com/andybalholm/brotli v1.1.0 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.14.1 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/clbanning/mxj/v2 v2.5.5 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/cloudwego/eino-ext/libs/acl/openai v0.1.2 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.11.4 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/eino-contrib/jsonschema v1.0.3 // indirect + github.com/evanphx/json-patch v0.5.2 // indirect github.com/fasthttp/websocket v1.5.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goph/emperror v0.17.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/meguminnnnnnnnn/go-openai v0.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/nikolalohinski/gonja v1.5.3 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pkoukk/tiktoken-go v0.1.6 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.1 // indirect @@ -71,16 +89,22 @@ require ( github.com/stretchr/testify v1.11.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + github.com/yargevad/filepathx v1.0.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect + golang.org/x/arch v0.11.0 // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 29b4ace..5dc58f0 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,7 @@ gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGq gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA= github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= @@ -96,12 +97,29 @@ github.com/aliyun/credentials-go v1.4.6 h1:CG8rc/nxCNKfXbZWpWDzI9GjF4Tuu3Es14qT8 github.com/aliyun/credentials-go v1.4.6/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/mockey v1.2.14 h1:KZaFgPdiUwW+jOWFieo3Lr7INM1P+6adO3hxZhDswY8= +github.com/bytedance/mockey v1.2.14/go.mod h1:1BPHF9sol5R1ud/+0VEHGQq/+i2lN+GTsr3O2Q9IENY= +github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w= +github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -110,6 +128,14 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/cloudwego/eino v0.7.7 h1:WhP0SMWWPgLdOH03HrKUxtP9/Q96NhziMZNEQl9lxpU= +github.com/cloudwego/eino v0.7.7/go.mod h1:nA8Vacmuqv3pqKBQbTWENBLQ8MmGmPt/WqiyLeB8ohQ= +github.com/cloudwego/eino-ext/components/model/openai v0.1.5 h1:+yvGbTPw93li9GSmdm6Rix88Yy8AXg5NNBcRbWx3CQU= +github.com/cloudwego/eino-ext/components/model/openai v0.1.5/go.mod h1:IPVYMFoZcuHeVEsDTGN6SZjvue0xr1iZFhdpq1SBWdQ= +github.com/cloudwego/eino-ext/libs/acl/openai v0.1.2 h1:r9Id2wzJ05PoHl+Km7jQgNMgciaZI93TVnUYso89esM= +github.com/cloudwego/eino-ext/libs/acl/openai v0.1.2/go.mod h1:S4OkvglPY9hsm9tXeShODrf/WN1Cgu4bqu4nn/CnIic= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -121,6 +147,10 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eino-contrib/jsonschema v1.0.3 h1:2Kfsm1xlMV0ssY2nuxshS4AwbLFuqmPmzIjLVJ1Fsp0= +github.com/eino-contrib/jsonschema v1.0.3/go.mod h1:cpnX4SyKjWjGC7iN2EbhxaTdLqGjCi0e9DxpLYxddD4= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -129,6 +159,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/faabiosr/cachego v0.15.0/go.mod h1:L2EomlU3/rUWjzFavY9Fwm8B4zZmX2X6u8kTMkETrwI= github.com/faabiosr/cachego v0.26.0 h1:EDDv2y9T0XJ4Cx3tUhbKSUayGWxCGkkZUivNLceHRWY= github.com/faabiosr/cachego v0.26.0/go.mod h1:p54WXVzeB1CctH1ix/rjqv1EotNzD0Xoxk2IsR1PQX8= @@ -143,6 +175,9 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -154,6 +189,7 @@ github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5 github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w= github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -215,8 +251,12 @@ github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= +github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -224,19 +264,26 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -247,6 +294,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -255,6 +304,10 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/meguminnnnnnnnn/go-openai v0.1.0 h1:BGzB1PlS2Epq0mBB2TGLwzMihbR7BANrlMH3w4ZnY88= +github.com/meguminnnnnnnnn/go-openai v0.1.0/go.mod h1:qs96ysDmxhE4BZoU45I43zcyfnaYxU3X+aRzLko/htY= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -265,16 +318,22 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c= +github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/ollama/ollama v0.12.7 h1:dxokli1UyO/a0Aun5sE4+0Gg+A9oMUAPiFQhxrXOIXA= github.com/ollama/ollama v0.12.7/go.mod h1:9+1//yWPsDE2u+l1a5mpaKrYw4VdnSsRU3ioq5BvMms= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw= @@ -290,6 +349,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -297,9 +357,18 @@ github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWR github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f h1:Z2cODYsUxQPofhpYRMQVwWz4yUVpHF+vPi+eUdruUYI= +github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f/go.mod h1:JqzWyvTuI2X4+9wOHmKSQCYxybB/8j6Ko43qVmXDuZg= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= @@ -311,16 +380,19 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -332,12 +404,20 @@ github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tmc/langchaingo v0.1.13 h1:rcpMWBIi2y3B90XxfE4Ao8dhCQPVDMaNPnN5cGB1CaA= github.com/tmc/langchaingo v0.1.13/go.mod h1:vpQ5NOIhpzxDfTZK9B6tf2GM/MoaHewPWM5KXXGh7hg= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc= +github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -353,8 +433,13 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= +golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -478,6 +563,7 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -519,6 +605,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -721,6 +808,7 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/biz/llm_service/ollama.go b/internal/biz/llm_service/ollama.go index 60d9c78..f850367 100644 --- a/internal/biz/llm_service/ollama.go +++ b/internal/biz/llm_service/ollama.go @@ -7,6 +7,7 @@ import ( "ai_scheduler/internal/entitys" "ai_scheduler/internal/pkg" "ai_scheduler/internal/pkg/utils_ollama" + "ai_scheduler/internal/pkg/utils_vllm" "context" "encoding/json" "errors" @@ -18,20 +19,23 @@ import ( ) type OllamaService struct { - client *utils_ollama.Client - config *config.Config - chatHis *impl.ChatImpl + client *utils_ollama.Client + vllmClient *utils_vllm.Client + config *config.Config + chatHis *impl.ChatImpl } func NewOllamaGenerate( client *utils_ollama.Client, + vllmClient *utils_vllm.Client, config *config.Config, chatHis *impl.ChatImpl, ) *OllamaService { return &OllamaService{ - client: client, - config: config, - chatHis: chatHis, + client: client, + vllmClient: vllmClient, + config: config, + chatHis: chatHis, } } @@ -103,7 +107,8 @@ func (r *OllamaService) getUserContent(ctx context.Context, requireData *entitys } if len(requireData.ImgByte) > 0 { - desc, err := r.RecognizeWithImg(ctx, requireData) + // desc, err := r.RecognizeWithImg(ctx, requireData) + desc, err := r.RecognizeWithImgVllm(ctx, requireData) if err != nil { return "", err } @@ -147,6 +152,29 @@ func (r *OllamaService) RecognizeWithImg(ctx context.Context, requireData *entit return } +func (r *OllamaService) RecognizeWithImgVllm(ctx context.Context, requireData *entitys.RequireData) (desc api.GenerateResponse, err error) { + if requireData.ImgByte == nil { + return + } + entitys.ResLog(requireData.Ch, "recognize_img_start", "图片识别中...") + + outMsg, err := r.vllmClient.RecognizeWithImg(ctx, + r.config.DefaultPrompt.ImgRecognize.SystemPrompt, + r.config.DefaultPrompt.ImgRecognize.UserPrompt, + requireData.ImgUrls, + ) + if err != nil { + return api.GenerateResponse{}, err + } + + desc = api.GenerateResponse{ + Response: outMsg.Content, + } + + entitys.ResLog(requireData.Ch, "recognize_img_end", "图片识别完成,识别内容:"+desc.Response) + return +} + func (r *OllamaService) registerToolsOllama(tasks []model.AiTask) []api.Tool { taskPrompt := make([]api.Tool, 0) for _, task := range tasks { diff --git a/internal/config/config.go b/internal/config/config.go index 5c9fa21..27738bc 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -9,16 +9,17 @@ import ( // Config 应用配置 type Config struct { - Server ServerConfig `mapstructure:"server"` - Ollama OllamaConfig `mapstructure:"ollama"` - Sys SysConfig `mapstructure:"sys"` - Tools ToolsConfig `mapstructure:"tools"` - Logging LoggingConfig `mapstructure:"logging"` - Redis Redis `mapstructure:"redis"` - DB DB `mapstructure:"db"` - DefaultPrompt SysPrompt `mapstructure:"default_prompt"` - PermissionConfig PermissionConfig `mapstructure:"permissionConfig"` - // LLM *LLM `mapstructure:"llm"` + Server ServerConfig `mapstructure:"server"` + Ollama OllamaConfig `mapstructure:"ollama"` + Vllm VllmConfig `mapstructure:"vllm"` + Sys SysConfig `mapstructure:"sys"` + Tools ToolsConfig `mapstructure:"tools"` + Logging LoggingConfig `mapstructure:"logging"` + Redis Redis `mapstructure:"redis"` + DB DB `mapstructure:"db"` + DefaultPrompt SysPrompt `mapstructure:"default_prompt"` + PermissionConfig PermissionConfig `mapstructure:"permissionConfig"` + // LLM *LLM `mapstructure:"llm"` } type SysPrompt struct { @@ -50,11 +51,18 @@ type ServerConfig struct { // OllamaConfig Ollama配置 type OllamaConfig struct { - BaseURL string `mapstructure:"base_url"` - Model string `mapstructure:"model"` - GenerateModel string `mapstructure:"generate_model"` - VlModel string `mapstructure:"vl_model"` - Timeout time.Duration `mapstructure:"timeout"` + BaseURL string `mapstructure:"base_url"` + Model string `mapstructure:"model"` + GenerateModel string `mapstructure:"generate_model"` + VlModel string `mapstructure:"vl_model"` + Timeout time.Duration `mapstructure:"timeout"` +} + +type VllmConfig struct { + BaseURL string `mapstructure:"base_url"` + VlModel string `mapstructure:"vl_model"` + Timeout time.Duration `mapstructure:"timeout"` + Level string `mapstructure:"level"` } type Redis struct { diff --git a/internal/pkg/provider_set.go b/internal/pkg/provider_set.go index 93d9180..f8fadac 100644 --- a/internal/pkg/provider_set.go +++ b/internal/pkg/provider_set.go @@ -4,6 +4,7 @@ import ( "ai_scheduler/internal/pkg/dingtalk" "ai_scheduler/internal/pkg/utils_langchain" "ai_scheduler/internal/pkg/utils_ollama" + "ai_scheduler/internal/pkg/utils_vllm" "github.com/google/wire" ) @@ -13,6 +14,7 @@ var ProviderSetClient = wire.NewSet( NewGormDb, utils_langchain.NewUtilLangChain, utils_ollama.NewClient, + utils_vllm.NewClient, NewSafeChannelPool, dingtalk.NewOldClient, dingtalk.NewContactClient, diff --git a/internal/pkg/utils_vllm/client.go b/internal/pkg/utils_vllm/client.go new file mode 100644 index 0000000..c333350 --- /dev/null +++ b/internal/pkg/utils_vllm/client.go @@ -0,0 +1,60 @@ +package utils_vllm + +import ( + "ai_scheduler/internal/config" + "context" + + "github.com/cloudwego/eino-ext/components/model/openai" + "github.com/cloudwego/eino/schema" +) + +type Client struct { + model *openai.ChatModel + config *config.Config +} + +func NewClient(config *config.Config) (*Client, func(), error) { + m, err := openai.NewChatModel(context.Background(), &openai.ChatModelConfig{ + BaseURL: config.Vllm.BaseURL, + Model: config.Vllm.VlModel, + Timeout: config.Vllm.Timeout, + }) + if err != nil { + return nil, nil, err + } + c := &Client{model: m, config: config} + cleanup := func() {} + return c, cleanup, nil +} + +func (c *Client) Chat(ctx context.Context, msgs []*schema.Message) (*schema.Message, error) { + return c.model.Generate(ctx, msgs) +} + +func (c *Client) RecognizeWithImg(ctx context.Context, systemPrompt, userPrompt string, imgURLs []string) (*schema.Message, error) { + in := []*schema.Message{ + { + Role: schema.System, + Content: systemPrompt, + }, + { + Role: schema.User, + }, + } + parts := []schema.MessageInputPart{ + {Type: schema.ChatMessagePartTypeText, Text: userPrompt}, + } + for i := range imgURLs { + u := imgURLs[i] + parts = append(parts, schema.MessageInputPart{ + Type: schema.ChatMessagePartTypeImageURL, + Image: &schema.MessageInputImage{ + MessagePartCommon: schema.MessagePartCommon{URL: &u}, + Detail: schema.ImageURLDetailHigh, + }, + }) + } + + in[1].UserInputMultiContent = parts + return c.model.Generate(ctx, in) +} diff --git a/internal/pkg/utils_vllm/client_test.go b/internal/pkg/utils_vllm/client_test.go new file mode 100644 index 0000000..e71390d --- /dev/null +++ b/internal/pkg/utils_vllm/client_test.go @@ -0,0 +1,66 @@ +package utils_vllm + +import ( + "ai_scheduler/internal/config" + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/cloudwego/eino/schema" +) + +func newMockServer() *httptest.Server { + h := http.NewServeMux() + h.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"id":"cmpl-1","object":"chat.completion","created":173,"model":"x","choices":[{"index":0,"message":{"role":"assistant","content":"ok"},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}`)) + }) + return httptest.NewServer(h) +} + +func Test_Vllm_Chat_Generate(t *testing.T) { + cfg, err := config.LoadConfig("../../../config/config_test.yaml") + if err != nil { + t.Fatalf("load config: %v", err) + } + + ctx := context.Background() + client, _, err := NewClient(cfg) + if err != nil { + t.Fatalf("new client: %v", err) + } + + msgs := []*schema.Message{{Role: schema.User, Content: "hi"}} + out, err := client.Chat(ctx, msgs) + if err != nil { + t.Fatalf("chat generate: %v", err) + } + if out == nil || out.Content != "ok" { + t.Fatalf("unexpected content: %v", out) + } + + t.Logf("结果: %v", out) +} + +func Test_Vllm_RecognizeWithImg(t *testing.T) { + cfg, err := config.LoadConfig("../../../config/config_test.yaml") + if err != nil { + t.Fatalf("load config: %v", err) + } + + ctx := context.Background() + client, _, err := NewClient(cfg) + if err != nil { + t.Fatalf("new client: %v", err) + } + + out, err := client.RecognizeWithImg(ctx, "sys", "user", []string{"https://img0.baidu.com/it/u=910428455,194434251&fm=253&app=138&f=JPEG?w=1122&h=800"}) + if err != nil { + t.Fatalf("recognize with img: %v", err) + } + if out == nil || out.Content != "ok" { + t.Fatalf("unexpected content: %v", out) + } +} From 057cf707d30f062a010111866d39dadfce71b33d Mon Sep 17 00:00:00 2001 From: wolter <11@gmail> Date: Fri, 5 Dec 2025 17:59:27 +0800 Subject: [PATCH 06/12] =?UTF-8?q?feat:=20=E4=BC=9A=E8=AF=9D=E5=8E=86?= =?UTF-8?q?=E5=8F=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/chat_history.go | 50 ++++++++++++++++---------- internal/biz/do/ctx.go | 5 +-- internal/biz/do/handle.go | 1 + internal/biz/llm_service/ollama.go | 4 +-- internal/biz/session.go | 14 ++++---- internal/data/impl/chat_history.go | 12 +++---- internal/data/impl/provider_set.go | 2 +- internal/data/model/ai_chat_his.gen.go | 1 + internal/entitys/chat_history.go | 6 ++++ internal/entitys/types.go | 1 + internal/server/http.go | 29 +++++++-------- internal/server/router/router.go | 16 ++++++--- internal/services/chat.go | 4 +-- internal/services/chat_history.go | 44 +++++++++++++++++++++++ internal/services/provider_set.go | 2 +- 15 files changed, 133 insertions(+), 58 deletions(-) create mode 100644 internal/services/chat_history.go diff --git a/internal/biz/chat_history.go b/internal/biz/chat_history.go index 48dcd0d..eeb74e5 100644 --- a/internal/biz/chat_history.go +++ b/internal/biz/chat_history.go @@ -10,31 +10,45 @@ import ( ) type ChatHistoryBiz struct { - chatRepo *impl.ChatImpl + chatHiRepo *impl.ChatHisImpl } -func NewChatHistoryBiz(chatRepo *impl.ChatImpl) *ChatHistoryBiz { +func NewChatHistoryBiz(chatHiRepo *impl.ChatHisImpl) *ChatHistoryBiz { s := &ChatHistoryBiz{ - chatRepo: chatRepo, + chatHiRepo: chatHiRepo, } //go s.AsyncProcess(context.Background()) return s } -//func (s *ChatHistoryBiz) create(ctx context.Context, sessionID, role, content string) error { -// chat := model.AiChatHi{ -// SessionID: sessionID, -// Role: role, -// Content: content, -// } -// -// return s.chatRepo.Create(&chat) -//} -// -//// 添加会话历史 -//func (s *ChatHistoryBiz) Create(ctx context.Context, chat entitys.ChatHistory) error { -// return s.create(ctx, chat.SessionID, chat.Role.String(), chat.Content) -//} +// 查询会话历史 +func (s *ChatHistoryBiz) List(ctx context.Context, query *entitys.ChatHistQuery) ([]model.AiChatHi, error) { + chats, err := s.chatHiRepo.FindAll( + s.chatHiRepo.WithSessionId(query.SessionID), + s.chatHiRepo.PaginateScope(query.Page, query.PageSize), + ) + if err != nil { + return nil, err + } + return chats, nil +} + +// 添加会话历史 +func (s *ChatHistoryBiz) Create(ctx context.Context, chat entitys.ChatHistory) error { + return s.chatHiRepo.Create(&model.AiChatHi{ + SessionID: chat.SessionID, + Ques: chat.Role.String(), + Ans: chat.Content, + }) +} + +// 更新会话历史内容 +func (s *ChatHistoryBiz) UpdateContent(ctx context.Context, chat *entitys.UseFulRequest) error { + cond := builder.NewCond() + cond = cond.And(builder.Eq{"his_id": chat.HisId}) + + return s.chatHiRepo.UpdateByCond(&cond, &model.AiChatHi{HisID: chat.HisId, Useful: chat.Useful}) +} // 异步添加会话历史 //func (s *ChatHistoryBiz) AsyncCreate(ctx context.Context, chat entitys.ChatHistory) { @@ -53,5 +67,5 @@ func NewChatHistoryBiz(chatRepo *impl.ChatImpl) *ChatHistoryBiz { func (s *ChatHistoryBiz) Update(ctx context.Context, chat *entitys.UseFulRequest) error { cond := builder.NewCond() cond = cond.And(builder.Eq{"his_id": chat.HisId}) - return s.chatRepo.UpdateByCond(&cond, &model.AiChatHi{HisID: chat.HisId, Useful: chat.Useful}) + return s.chatHiRepo.UpdateByCond(&cond, &model.AiChatHi{HisID: chat.HisId, Useful: chat.Useful}) } diff --git a/internal/biz/do/ctx.go b/internal/biz/do/ctx.go index eb2c5a5..43f1e1a 100644 --- a/internal/biz/do/ctx.go +++ b/internal/biz/do/ctx.go @@ -27,14 +27,14 @@ type Do struct { sessionImpl *impl.SessionImpl sysImpl *impl.SysImpl taskImpl *impl.TaskImpl - hisImpl *impl.ChatImpl + hisImpl *impl.ChatHisImpl conf *config.Config } func NewDo( sysImpl *impl.SysImpl, taskImpl *impl.TaskImpl, - hisImpl *impl.ChatImpl, + hisImpl *impl.ChatHisImpl, conf *config.Config, ) *Do { return &Do{ @@ -252,6 +252,7 @@ func (d *Do) startMessageHandler( Ques: requireData.Req.Text, Ans: strings.Join(chat, ""), Files: requireData.Req.Img, + TaskID: requireData.Task.TaskID, } d.hisImpl.AddWithData(AiRes) hisLog.HisId = AiRes.HisID diff --git a/internal/biz/do/handle.go b/internal/biz/do/handle.go index e014d25..14695e6 100644 --- a/internal/biz/do/handle.go +++ b/internal/biz/do/handle.go @@ -85,6 +85,7 @@ func (r *Handle) HandleMatch(ctx context.Context, client *gateway.Client, requir for _, task := range requireData.Tasks { if task.Index == requireData.Match.Index { pointTask = &task + requireData.Task = task break } } diff --git a/internal/biz/llm_service/ollama.go b/internal/biz/llm_service/ollama.go index 6527a3b..56887a2 100644 --- a/internal/biz/llm_service/ollama.go +++ b/internal/biz/llm_service/ollama.go @@ -20,13 +20,13 @@ import ( type OllamaService struct { client *utils_ollama.Client config *config.Config - chatHis *impl.ChatImpl + chatHis *impl.ChatHisImpl } func NewOllamaGenerate( client *utils_ollama.Client, config *config.Config, - chatHis *impl.ChatImpl, + chatHis *impl.ChatHisImpl, ) *OllamaService { return &OllamaService{ client: client, diff --git a/internal/biz/session.go b/internal/biz/session.go index d014148..a42e9c0 100644 --- a/internal/biz/session.go +++ b/internal/biz/session.go @@ -17,16 +17,16 @@ import ( type SessionBiz struct { sessionRepo *impl.SessionImpl sysRepo *impl.SysImpl - chatRepo *impl.ChatImpl + chatHisRepo *impl.ChatHisImpl conf *config.Config } -func NewSessionBiz(conf *config.Config, sessionImpl *impl.SessionImpl, sysImpl *impl.SysImpl, chatImpl *impl.ChatImpl) *SessionBiz { +func NewSessionBiz(conf *config.Config, sessionImpl *impl.SessionImpl, sysImpl *impl.SysImpl, chatImpl *impl.ChatHisImpl) *SessionBiz { return &SessionBiz{ sessionRepo: sessionImpl, sysRepo: sysImpl, - chatRepo: chatImpl, + chatHisRepo: chatImpl, conf: conf, } } @@ -91,10 +91,10 @@ func (s *SessionBiz) SessionInit(ctx context.Context, req *entitys.SessionInitRe result.Prologue = sysConfig.Prologue // 存在,返回会话历史 var chatList []model.AiChatHi - chatList, err = s.chatRepo.FindAll( - s.chatRepo.WithSessionId(session.SessionID), // 条件:会话ID - s.chatRepo.OrderByDesc("create_at"), // 排序:按创建时间降序 - s.chatRepo.WithLimit(constants.ChatHistoryLimit), // 限制返回条数 + chatList, err = s.chatHisRepo.FindAll( + s.chatHisRepo.WithSessionId(session.SessionID), // 条件:会话ID + s.chatHisRepo.OrderByDesc("create_at"), // 排序:按创建时间降序 + s.chatHisRepo.WithLimit(constants.ChatHistoryLimit), // 限制返回条数 ) if err != nil { return diff --git a/internal/data/impl/chat_history.go b/internal/data/impl/chat_history.go index 6f6027d..f1c0b42 100644 --- a/internal/data/impl/chat_history.go +++ b/internal/data/impl/chat_history.go @@ -11,14 +11,14 @@ import ( "gorm.io/gorm" ) -type ChatImpl struct { +type ChatHisImpl struct { dataTemp.DataTemp BaseRepository[model.AiChatHi] chatChannel chan model.AiChatHi } -func NewChatImpl(db *utils.Db) *ChatImpl { - return &ChatImpl{ +func NewChatHisImpl(db *utils.Db) *ChatHisImpl { + return &ChatHisImpl{ DataTemp: *dataTemp.NewDataTemp(db, new(model.AiChatHi)), BaseRepository: NewBaseModel[model.AiChatHi](db.Client), chatChannel: make(chan model.AiChatHi, 100), @@ -26,19 +26,19 @@ func NewChatImpl(db *utils.Db) *ChatImpl { } // WithSessionId 条件:会话ID -func (impl *ChatImpl) WithSessionId(sessionId interface{}) CondFunc { +func (impl *ChatHisImpl) WithSessionId(sessionId interface{}) CondFunc { return func(db *gorm.DB) *gorm.DB { return db.Where("session_id = ?", sessionId) } } // 异步添加会话历史 -func (impl *ChatImpl) AsyncCreate(ctx context.Context, chat model.AiChatHi) { +func (impl *ChatHisImpl) AsyncCreate(ctx context.Context, chat model.AiChatHi) { impl.chatChannel <- chat } // 异步处理会话历史 -func (impl *ChatImpl) AsyncProcess(ctx context.Context) { +func (impl *ChatHisImpl) AsyncProcess(ctx context.Context) { for { select { case chat := <-impl.chatChannel: diff --git a/internal/data/impl/provider_set.go b/internal/data/impl/provider_set.go index f970e84..1284f11 100644 --- a/internal/data/impl/provider_set.go +++ b/internal/data/impl/provider_set.go @@ -4,4 +4,4 @@ import ( "github.com/google/wire" ) -var ProviderImpl = wire.NewSet(NewSessionImpl, NewSysImpl, NewTaskImpl, NewChatImpl) +var ProviderImpl = wire.NewSet(NewSessionImpl, NewSysImpl, NewTaskImpl, NewChatHisImpl) diff --git a/internal/data/model/ai_chat_his.gen.go b/internal/data/model/ai_chat_his.gen.go index d143972..e3b5b58 100644 --- a/internal/data/model/ai_chat_his.gen.go +++ b/internal/data/model/ai_chat_his.gen.go @@ -20,6 +20,7 @@ type AiChatHi struct { Useful int32 `gorm:"column:useful;not null;comment:0不评价,1有用,其他为无用" json:"useful"` // 0不评价,1有用,其他为无用 CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"` UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP" json:"updated_at"` + TaskID int32 `gorm:"column:task_id;not null" json:"task_id"` // 任务ID } // TableName AiChatHi's table name diff --git a/internal/entitys/chat_history.go b/internal/entitys/chat_history.go index a26fa46..34e6c5e 100644 --- a/internal/entitys/chat_history.go +++ b/internal/entitys/chat_history.go @@ -14,3 +14,9 @@ type ChatHistory struct { type ChatHisLog struct { HisId int64 `json:"his_id"` } + +type ChatHistQuery struct { + SessionID string `json:"session_id"` + Page int `json:"page"` + PageSize int `json:"page_size"` +} diff --git a/internal/entitys/types.go b/internal/entitys/types.go index ece5ceb..a9dc72e 100644 --- a/internal/entitys/types.go +++ b/internal/entitys/types.go @@ -150,6 +150,7 @@ type RequireData struct { Histories []model.AiChatHi SessionInfo model.AiSession Tasks []model.AiTask + Task model.AiTask Match *Match Req *ChatSockRequest Auth string diff --git a/internal/server/http.go b/internal/server/http.go index 4cdc393..fd7e49e 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -11,24 +11,25 @@ import ( ) type HTTPServer struct { - app *fiber.App - service *services.ChatService - session *services.SessionService - gateway *gateway.Gateway - callback *services.CallbackService + app *fiber.App + service *services.ChatService + session *services.SessionService + gateway *gateway.Gateway + callback *services.CallbackService } func NewHTTPServer( - service *services.ChatService, - session *services.SessionService, - task *services.TaskService, - gateway *gateway.Gateway, - callback *services.CallbackService, + service *services.ChatService, + session *services.SessionService, + task *services.TaskService, + gateway *gateway.Gateway, + callback *services.CallbackService, + chatHis *services.HistoryService, ) *fiber.App { - //构建 server - app := initRoute() - router.SetupRoutes(app, service, session, task, gateway, callback) - return app + //构建 server + app := initRoute() + router.SetupRoutes(app, service, session, task, gateway, callback, chatHis) + return app } func initRoute() *fiber.App { diff --git a/internal/server/router/router.go b/internal/server/router/router.go index 1a6fd8b..4f5579c 100644 --- a/internal/server/router/router.go +++ b/internal/server/router/router.go @@ -15,14 +15,17 @@ import ( ) type RouterServer struct { - app *fiber.App - service *services.ChatService - session *services.SessionService - gateway *gateway.Gateway + app *fiber.App + service *services.ChatService + session *services.SessionService + gateway *gateway.Gateway + chatHist *services.HistoryService } // SetupRoutes 设置路由 -func SetupRoutes(app *fiber.App, ChatService *services.ChatService, sessionService *services.SessionService, task *services.TaskService, gateway *gateway.Gateway, callbackService *services.CallbackService) { +func SetupRoutes(app *fiber.App, ChatService *services.ChatService, sessionService *services.SessionService, task *services.TaskService, + gateway *gateway.Gateway, callbackService *services.CallbackService, chatHist *services.HistoryService, +) { app.Use(func(c *fiber.Ctx) error { // 设置 CORS 头 c.Set("Access-Control-Allow-Origin", "*") @@ -77,6 +80,9 @@ func SetupRoutes(app *fiber.App, ChatService *services.ChatService, sessionServi return ctx.Status(400).SendString("unknown action") } }) + + // 会话历史 + r.Post("/chat/history", chatHist.GetHistory) } func routerSocket(app *fiber.App, chatService *services.ChatService) { diff --git a/internal/services/chat.go b/internal/services/chat.go index 11dda38..eb47809 100644 --- a/internal/services/chat.go +++ b/internal/services/chat.go @@ -73,9 +73,9 @@ func (h *ChatService) Chat(c *websocket.Conn) { var wg sync.WaitGroup // 确保在函数返回时移除客户端并关闭连接 defer func() { - h.Gw.Cleanup(client.GetID()) - close(semaphore) // 关闭信号量通道 wg.Wait() // 等待所有消息处理goroutine完成 + close(semaphore) // 关闭信号量通道 + h.Gw.Cleanup(client.GetID()) }() // 绑定会话ID, sessionId 为空时, 则不绑定 diff --git a/internal/services/chat_history.go b/internal/services/chat_history.go new file mode 100644 index 0000000..f49d238 --- /dev/null +++ b/internal/services/chat_history.go @@ -0,0 +1,44 @@ +package services + +import ( + "ai_scheduler/internal/biz" + errors "ai_scheduler/internal/data/error" + "ai_scheduler/internal/entitys" + "github.com/gofiber/fiber/v2" +) + +type HistoryService struct { + chatRepo *biz.ChatHistoryBiz +} + +func NewHistoryService(chatRepo *biz.ChatHistoryBiz) *HistoryService { + return &HistoryService{ + chatRepo: chatRepo, + } +} + +// GetHistoryService 获取会话历史 +func (h *HistoryService) GetHistory(c *fiber.Ctx) error { + var query entitys.ChatHistQuery + if err := c.BodyParser(&query); err != nil { + return err + } + // 校验参数 + if query.SessionID == "" { + return errors.SessionNotFound + } + if query.Page <= 0 { + query.Page = 1 + } + if query.PageSize <= 0 { + query.PageSize = 10 + } + + // 查询历史 + history, err := h.chatRepo.List(c.Context(), &query) + if err != nil { + return err + } + + return c.JSON(history) +} diff --git a/internal/services/provider_set.go b/internal/services/provider_set.go index 0e1284b..668c7fe 100644 --- a/internal/services/provider_set.go +++ b/internal/services/provider_set.go @@ -6,4 +6,4 @@ import ( "github.com/google/wire" ) -var ProviderSetServices = wire.NewSet(NewChatService, NewSessionService, gateway.NewGateway, NewTaskService, NewCallbackService) +var ProviderSetServices = wire.NewSet(NewChatService, NewSessionService, gateway.NewGateway, NewTaskService, NewCallbackService, NewHistoryService) From 5a44253d6d7f6622a3f4acd3cfd3733053f072f0 Mon Sep 17 00:00:00 2001 From: wolter <11@gmail> Date: Fri, 5 Dec 2025 18:11:02 +0800 Subject: [PATCH 07/12] fix: Dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2c0e527..2bd6192 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ## 使用官方Go镜像作为构建环境 -FROM golang:1.24.0-alpine AS builder +FROM golang:1.24.1-alpine AS builder # 设置工作目录 WORKDIR /app @@ -15,7 +15,7 @@ COPY . . # 复制go模块依赖文件 COPY go.mod go.sum ./ -RUN go mod download +RUN go mod tidy RUN go install github.com/google/wire/cmd/wire@latest RUN wire ./cmd/server From 5d82a4c0b9d56dfec9d9620e4debe9ba569acab2 Mon Sep 17 00:00:00 2001 From: fuzhongyun <15339891972@163.com> Date: Fri, 5 Dec 2025 18:28:34 +0800 Subject: [PATCH 08/12] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9vllm=E5=9C=B0?= =?UTF-8?q?=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config_test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config_test.yaml b/config/config_test.yaml index 2d099ac..0fd9f40 100644 --- a/config/config_test.yaml +++ b/config/config_test.yaml @@ -14,7 +14,7 @@ ollama: format: "json" vllm: - base_url: "http://127.0.0.1:8001/v1" + base_url: "http://host.docker.internal:8001/v1" vl_model: "models/Qwen2.5-VL-3B-Instruct-AWQ" timeout: "120s" level: "info" From 7ac61893f5ef324b29ec8cb7a4acda131e879ac2 Mon Sep 17 00:00:00 2001 From: wolter <11@gmail> Date: Mon, 8 Dec 2025 13:55:56 +0800 Subject: [PATCH 09/12] =?UTF-8?q?feat:=E4=BC=9A=E8=AF=9D=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E5=92=8C=E5=89=8D=E7=AB=AF=E5=9B=9E=E4=BC=A0=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 5 ++ go.sum | 12 ++++ internal/biz/chat_history.go | 88 ++++++++++++++++++++------ internal/data/impl/base.go | 16 +++++ internal/data/impl/chat_history.go | 7 ++ internal/data/impl/task_impl.go | 6 +- internal/data/model/ai_chat_his.gen.go | 1 + internal/entitys/chat_history.go | 42 ++++++++++++ internal/pkg/util/string.go | 10 +++ internal/pkg/validate/validate.go | 45 +++++++++++++ internal/server/router/router.go | 3 +- internal/services/chat_history.go | 21 +++++- 12 files changed, 235 insertions(+), 21 deletions(-) create mode 100644 internal/pkg/validate/validate.go diff --git a/go.mod b/go.mod index 49b7751..b6e99c9 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,9 @@ require ( github.com/faabiosr/cachego v0.26.0 github.com/fastwego/dingding v1.0.0-beta.4 github.com/go-kratos/kratos/v2 v2.9.1 + github.com/go-playground/locales v0.14.1 + github.com/go-playground/universal-translator v0.18.1 + github.com/go-playground/validator/v10 v10.20.0 github.com/gofiber/fiber/v2 v2.52.9 github.com/gofiber/websocket/v2 v2.2.1 github.com/google/uuid v1.6.0 @@ -53,6 +56,7 @@ require ( github.com/evanphx/json-patch v0.5.2 // indirect github.com/fasthttp/websocket v1.5.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/goph/emperror v0.17.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -61,6 +65,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index 5dc58f0..a6689ca 100644 --- a/go.sum +++ b/go.sum @@ -174,6 +174,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= @@ -183,6 +185,14 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kratos/kratos/v2 v2.9.1 h1:EGif6/S/aK/RCR5clIbyhioTNyoSrii3FC118jG40Z0= github.com/go-kratos/kratos/v2 v2.9.1/go.mod h1:a1MQLjMhIh7R0kcJS9SzJYR43BRI7EPzzN0J1Ksu2bA= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw= @@ -292,6 +302,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= diff --git a/internal/biz/chat_history.go b/internal/biz/chat_history.go index eeb74e5..58266e8 100644 --- a/internal/biz/chat_history.go +++ b/internal/biz/chat_history.go @@ -1,53 +1,105 @@ package biz import ( + errors "ai_scheduler/internal/data/error" "ai_scheduler/internal/data/impl" "ai_scheduler/internal/data/model" "ai_scheduler/internal/entitys" + "ai_scheduler/internal/pkg/util" "context" - + "encoding/json" "xorm.io/builder" ) type ChatHistoryBiz struct { chatHiRepo *impl.ChatHisImpl + taskRepo *impl.TaskImpl } -func NewChatHistoryBiz(chatHiRepo *impl.ChatHisImpl) *ChatHistoryBiz { +func NewChatHistoryBiz(chatHiRepo *impl.ChatHisImpl, taskRepo *impl.TaskImpl) *ChatHistoryBiz { s := &ChatHistoryBiz{ chatHiRepo: chatHiRepo, + taskRepo: taskRepo, } - //go s.AsyncProcess(context.Background()) return s } // 查询会话历史 -func (s *ChatHistoryBiz) List(ctx context.Context, query *entitys.ChatHistQuery) ([]model.AiChatHi, error) { +func (s *ChatHistoryBiz) List(ctx context.Context, query *entitys.ChatHistQuery) ([]entitys.ChatHisQueryResponse, error) { chats, err := s.chatHiRepo.FindAll( s.chatHiRepo.WithSessionId(query.SessionID), s.chatHiRepo.PaginateScope(query.Page, query.PageSize), + s.chatHiRepo.OrderByDesc("his_id"), ) if err != nil { return nil, err } - return chats, nil + + taskIds := make([]int32, 0, len(chats)) + for _, chat := range chats { + // 去重任务ID + if !util.Contains(taskIds, chat.TaskID) { + taskIds = append(taskIds, chat.TaskID) + } + } + + // 查询任务名称 + tasks, err := s.taskRepo.FindAll(s.taskRepo.In("task_id", taskIds)) + if err != nil { + return nil, err + } + taskMap := make(map[int32]model.AiTask) + for _, task := range tasks { + taskMap[task.TaskID] = task + } + + // 构建结果 + result := make([]entitys.ChatHisQueryResponse, 0, len(chats)) + for _, chat := range chats { + item := entitys.ChatHisQueryResponse{} + item.FromModel(chat, taskMap[chat.TaskID]) + result = append(result, item) + } + + return result, nil } -// 添加会话历史 -func (s *ChatHistoryBiz) Create(ctx context.Context, chat entitys.ChatHistory) error { - return s.chatHiRepo.Create(&model.AiChatHi{ - SessionID: chat.SessionID, - Ques: chat.Role.String(), - Ans: chat.Content, - }) -} +//// 添加会话历史 +//func (s *ChatHistoryBiz) Create(ctx context.Context, chat entitys.ChatHistory) error { +// return s.chatHiRepo.Create(&model.AiChatHi{ +// SessionID: chat.SessionID, +// Ques: chat.Role.String(), +// Ans: chat.Content, +// }) +//} -// 更新会话历史内容 -func (s *ChatHistoryBiz) UpdateContent(ctx context.Context, chat *entitys.UseFulRequest) error { - cond := builder.NewCond() - cond = cond.And(builder.Eq{"his_id": chat.HisId}) +// 更新会话历史内容, 追加内容, 不覆盖原有内容, content 使用json格式存储 +func (c *ChatHistoryBiz) UpdateContent(ctx context.Context, chat *entitys.UpdateContentRequest) error { + var contents []string + chatHi, has, err := c.chatHiRepo.FindOne(c.chatHiRepo.WithHisId(chat.HisID)) + if err != nil { + return err + } else if !has { + return errors.NewBusinessErr(errors.InvalidParamCode, "chat history not found") + } - return s.chatHiRepo.UpdateByCond(&cond, &model.AiChatHi{HisID: chat.HisId, Useful: chat.Useful}) + if "" != chatHi.Content { + // 解析历史内容 + err = json.Unmarshal([]byte(chatHi.Content), &contents) + if err != nil { + return err + } + } + contents = append(contents, chat.Content) + + b, err := json.Marshal(contents) + if err != nil { + return err + } + chatHi.Content = string(b) + return c.chatHiRepo.Update(&chatHi, + c.chatHiRepo.Select("content"), + c.chatHiRepo.WithHisId(chatHi.HisID)) } // 异步添加会话历史 diff --git a/internal/data/impl/base.go b/internal/data/impl/base.go index 5ab2afa..7aee0b7 100644 --- a/internal/data/impl/base.go +++ b/internal/data/impl/base.go @@ -54,6 +54,8 @@ type BaseRepository[P PO] interface { WithStatus(status int) CondFunc // 查询status GetDb() *gorm.DB // 获取数据库连接 WithLimit(limit int) CondFunc // 限制返回条数 + In(field string, values interface{}) CondFunc // 查询字段是否在列表中 + Select(fields ...string) CondFunc // 选择字段 } // PaginationResult 分页查询结果 @@ -215,3 +217,17 @@ func (this *BaseModel[P]) WithLimit(limit int) CondFunc { return db.Limit(limit) } } + +// 查询字段是否在列表中 +func (this *BaseModel[P]) In(field string, values interface{}) CondFunc { + return func(db *gorm.DB) *gorm.DB { + return db.Where(fmt.Sprintf("%s IN ?", field), values) + } +} + +// select 字段 +func (this *BaseModel[P]) Select(fields ...string) CondFunc { + return func(db *gorm.DB) *gorm.DB { + return db.Select(fields) + } +} diff --git a/internal/data/impl/chat_history.go b/internal/data/impl/chat_history.go index f1c0b42..08a7fa8 100644 --- a/internal/data/impl/chat_history.go +++ b/internal/data/impl/chat_history.go @@ -55,3 +55,10 @@ func (impl *ChatHisImpl) AsyncProcess(ctx context.Context) { } } } + +// his_id 条件:历史ID +func (impl *ChatHisImpl) WithHisId(hisId interface{}) CondFunc { + return func(db *gorm.DB) *gorm.DB { + return db.Where("his_id = ?", hisId) + } +} diff --git a/internal/data/impl/task_impl.go b/internal/data/impl/task_impl.go index 8b3f246..3c76680 100644 --- a/internal/data/impl/task_impl.go +++ b/internal/data/impl/task_impl.go @@ -8,8 +8,12 @@ import ( type TaskImpl struct { dataTemp.DataTemp + BaseRepository[model.AiTask] } func NewTaskImpl(db *utils.Db) *TaskImpl { - return &TaskImpl{*dataTemp.NewDataTemp(db, new(model.AiTask))} + return &TaskImpl{ + DataTemp: *dataTemp.NewDataTemp(db, new(model.AiTask)), + BaseRepository: NewBaseModel[model.AiTask](db.Client), + } } diff --git a/internal/data/model/ai_chat_his.gen.go b/internal/data/model/ai_chat_his.gen.go index e3b5b58..595b4c4 100644 --- a/internal/data/model/ai_chat_his.gen.go +++ b/internal/data/model/ai_chat_his.gen.go @@ -21,6 +21,7 @@ type AiChatHi struct { CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"` UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP" json:"updated_at"` TaskID int32 `gorm:"column:task_id;not null" json:"task_id"` // 任务ID + Content string `gorm:"column:content" json:"content"` // 前端回传数据 } // TableName AiChatHi's table name diff --git a/internal/entitys/chat_history.go b/internal/entitys/chat_history.go index 34e6c5e..6991a53 100644 --- a/internal/entitys/chat_history.go +++ b/internal/entitys/chat_history.go @@ -2,6 +2,8 @@ package entitys import ( "ai_scheduler/internal/data/constants" + "ai_scheduler/internal/data/model" + "encoding/json" ) type ChatHistory struct { @@ -20,3 +22,43 @@ type ChatHistQuery struct { Page int `json:"page"` PageSize int `json:"page_size"` } + +type ChatHisQueryResponse struct { + HisID int64 `gorm:"column:his_id;primaryKey;autoIncrement:true" json:"his_id"` + SessionID string `gorm:"column:session_id;not null" json:"session_id"` + Ques string `gorm:"column:ques;not null" json:"ques"` + Ans string `gorm:"column:ans;not null" json:"ans"` + Files string `gorm:"column:files;not null" json:"files"` + Useful int32 `gorm:"column:useful;not null;comment:0不评价,1有用,其他为无用" json:"useful"` // 0不评价,1有用,其他为无用 + CreateAt string `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"` + TaskID int32 `gorm:"column:task_id;not null" json:"task_id"` // 任务ID + TaskName string `gorm:"column:task_name;not null" json:"task_name"` // 任务名称 + Content []string `gorm:"column:content" json:"content"` // 前端回传数据 +} + +func (c *ChatHisQueryResponse) FromModel(chat model.AiChatHi, task model.AiTask) { + c.HisID = chat.HisID + c.SessionID = chat.SessionID + c.Ques = chat.Ques + c.Ans = chat.Ans + c.Files = chat.Files + c.Useful = chat.Useful + c.CreateAt = chat.CreateAt.Format("2006-01-02 15:04:05") + c.TaskID = chat.TaskID + c.TaskName = task.Name + c.Content = make([]string, 0) + + // 解析Content + if "" != chat.Content { + var contents []string + if err := json.Unmarshal([]byte(chat.Content), &contents); err != nil { + c.Content = contents + } + c.Content = append(c.Content, chat.Content) + } +} + +type UpdateContentRequest struct { + HisID int64 `json:"his_id" validate:"required"` + Content string `json:"content" validate:"required"` +} diff --git a/internal/pkg/util/string.go b/internal/pkg/util/string.go index 1fc898a..9dd4056 100644 --- a/internal/pkg/util/string.go +++ b/internal/pkg/util/string.go @@ -32,3 +32,13 @@ func StringToFloat64(s string) float64 { i, _ := strconv.ParseFloat(s, 64) return i } + +// 是否包含在数组中 +func Contains[T comparable](strings []T, str T) bool { + for _, s := range strings { + if s == str { + return true + } + } + return false +} diff --git a/internal/pkg/validate/validate.go b/internal/pkg/validate/validate.go new file mode 100644 index 0000000..de58134 --- /dev/null +++ b/internal/pkg/validate/validate.go @@ -0,0 +1,45 @@ +package validate + +import ( + "fmt" + "github.com/go-playground/locales/zh" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + zh_translations "github.com/go-playground/validator/v10/translations/zh" + "reflect" +) + +func Struct(s interface{}) (errMsg []string, err error) { + // 创建验证器实例 + validate := validator.New() + + // 创建中文翻译器 + zh_ch := zh.New() + uni := ut.New(zh_ch, zh_ch) + trans, _ := uni.GetTranslator("zh") + + //注册一个函数,获取struct tag里自定义的label作为字段名 + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := fld.Tag.Get("label") + return name + }) + + // 注册中文翻译器到验证器 + _ = zh_translations.RegisterDefaultTranslations(validate, trans) + + // 验证结构体 + err = validate.Struct(s) + if err != nil { + // 处理验证错误 + if _, ok := err.(*validator.InvalidValidationError); ok { + fmt.Println("处理验证错误error:", err) + errMsg = append(errMsg, err.Error()) + } else { + for _, v := range err.(validator.ValidationErrors) { + errMsg = append(errMsg, v.Translate(trans)) + } + } + } + + return +} diff --git a/internal/server/router/router.go b/internal/server/router/router.go index 4f5579c..e2645bb 100644 --- a/internal/server/router/router.go +++ b/internal/server/router/router.go @@ -82,7 +82,8 @@ func SetupRoutes(app *fiber.App, ChatService *services.ChatService, sessionServi }) // 会话历史 - r.Post("/chat/history", chatHist.GetHistory) + r.Post("/chat/history/list", chatHist.List) + r.Post("/chat/history/update/content", chatHist.UpdateContent) } func routerSocket(app *fiber.App, chatService *services.ChatService) { diff --git a/internal/services/chat_history.go b/internal/services/chat_history.go index f49d238..7bd4d75 100644 --- a/internal/services/chat_history.go +++ b/internal/services/chat_history.go @@ -4,7 +4,10 @@ import ( "ai_scheduler/internal/biz" errors "ai_scheduler/internal/data/error" "ai_scheduler/internal/entitys" + "ai_scheduler/internal/pkg/validate" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" + "strings" ) type HistoryService struct { @@ -18,7 +21,7 @@ func NewHistoryService(chatRepo *biz.ChatHistoryBiz) *HistoryService { } // GetHistoryService 获取会话历史 -func (h *HistoryService) GetHistory(c *fiber.Ctx) error { +func (h *HistoryService) List(c *fiber.Ctx) error { var query entitys.ChatHistQuery if err := c.BodyParser(&query); err != nil { return err @@ -42,3 +45,19 @@ func (h *HistoryService) GetHistory(c *fiber.Ctx) error { return c.JSON(history) } + +func (h *HistoryService) UpdateContent(c *fiber.Ctx) error { + var req entitys.UpdateContentRequest + if err := c.BodyParser(&req); err != nil { + return err + } + // 校验参数 + msg, err := validate.Struct(req) + if err != nil { + log.Error(c.UserContext(), "参数错误 error: ", err) + return errors.NewBusinessErr(errors.InvalidParamCode, strings.Join(msg, ";")) + } + + // 更新历史 + return h.chatRepo.UpdateContent(c.Context(), &req) +} From 2c9874a1801e3e5d0c80ad5a72a9b86e1445e34d Mon Sep 17 00:00:00 2001 From: wolter <11@gmail> Date: Mon, 8 Dec 2025 14:33:32 +0800 Subject: [PATCH 10/12] =?UTF-8?q?fix:=E4=BC=9A=E8=AF=9D=E5=8E=86=E5=8F=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/entitys/chat_history.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/internal/entitys/chat_history.go b/internal/entitys/chat_history.go index 6991a53..019a8c6 100644 --- a/internal/entitys/chat_history.go +++ b/internal/entitys/chat_history.go @@ -4,6 +4,7 @@ import ( "ai_scheduler/internal/data/constants" "ai_scheduler/internal/data/model" "encoding/json" + "log" ) type ChatHistory struct { @@ -33,7 +34,7 @@ type ChatHisQueryResponse struct { CreateAt string `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"` TaskID int32 `gorm:"column:task_id;not null" json:"task_id"` // 任务ID TaskName string `gorm:"column:task_name;not null" json:"task_name"` // 任务名称 - Content []string `gorm:"column:content" json:"content"` // 前端回传数据 + Contents []string `gorm:"column:contents" json:"contents"` // 前端回传数据 } func (c *ChatHisQueryResponse) FromModel(chat model.AiChatHi, task model.AiTask) { @@ -46,15 +47,15 @@ func (c *ChatHisQueryResponse) FromModel(chat model.AiChatHi, task model.AiTask) c.CreateAt = chat.CreateAt.Format("2006-01-02 15:04:05") c.TaskID = chat.TaskID c.TaskName = task.Name - c.Content = make([]string, 0) + c.Contents = make([]string, 0) // 解析Content if "" != chat.Content { - var contents []string - if err := json.Unmarshal([]byte(chat.Content), &contents); err != nil { - c.Content = contents + err := json.Unmarshal([]byte(chat.Content), &c.Contents) + if err != nil { + c.Contents = append(c.Contents, chat.Content) + log.Println("解析Content失败 error: ", err) } - c.Content = append(c.Content, chat.Content) } } From 8ab6cbe3f4d315900d617fabfae276ce01abfc11 Mon Sep 17 00:00:00 2001 From: fuzhongyun <15339891972@163.com> Date: Tue, 9 Dec 2025 14:22:13 +0800 Subject: [PATCH 11/12] =?UTF-8?q?fix:=20=E8=B0=83=E6=95=B4api=E7=9B=B4?= =?UTF-8?q?=E8=BF=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/do/handle.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/internal/biz/do/handle.go b/internal/biz/do/handle.go index 14695e6..ebebf83 100644 --- a/internal/biz/do/handle.go +++ b/internal/biz/do/handle.go @@ -17,8 +17,9 @@ import ( "context" "encoding/json" "fmt" - "gorm.io/gorm/utils" "strings" + + "gorm.io/gorm/utils" ) type Handle struct { @@ -237,9 +238,19 @@ func (r *Handle) handleApiTask(ctx context.Context, requireData *entitys.Require if err != nil { return } - request.Url = strings.ReplaceAll(task.Config, "${authorization}", requireData.Auth) + // request.Url = strings.ReplaceAll(task.Config, "${authorization}", requireData.Auth) + task.Config = strings.ReplaceAll(task.Config, "${authorization}", requireData.Auth) for k, v := range requestParam { - task.Config = strings.ReplaceAll(task.Config, "${"+k+"}", fmt.Sprintf("%v", v)) + if vStr, ok := v.(string); ok { + task.Config = strings.ReplaceAll(task.Config, "${"+k+"}", vStr) + } else { + var jsonStr []byte + jsonStr, err = json.Marshal(v) + if err != nil { + return errors.NewBusinessErr(422, "请求参数解析失败") + } + task.Config = strings.ReplaceAll(task.Config, "\"${"+k+"}\"", string(jsonStr)) + } } var configData entitys.ConfigDataHttp err = json.Unmarshal([]byte(task.Config), &configData) From b184dce3eacc5ad6febfff28a2fc7e402348dfd7 Mon Sep 17 00:00:00 2001 From: fuzhongyun <15339891972@163.com> Date: Tue, 9 Dec 2025 14:22:18 +0800 Subject: [PATCH 12/12] =?UTF-8?q?fix:=20=E8=B0=83=E6=95=B4api=E7=9B=B4?= =?UTF-8?q?=E8=BF=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config_env.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/config_env.yaml b/config/config_env.yaml index b6fa262..d2cd2a4 100644 --- a/config/config_env.yaml +++ b/config/config_env.yaml @@ -24,6 +24,7 @@ sys: channel_pool_len: 100 channel_pool_size: 32 llm_pool_len: 5 + heartbeat_interval: 30 redis: host: 47.97.27.195:6379 type: node