From f052000d591abc33a35c09cb725a1a8d2d5b0e63 Mon Sep 17 00:00:00 2001
From: renzhiyuan <465386466@qq.com>
Date: Thu, 9 Oct 2025 11:38:37 +0800
Subject: [PATCH 1/4] =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
internal/biz/router.go | 33 +++++++++++++++++++++++++++++++
internal/data/error/error_code.go | 4 ++++
internal/entitys/types.go | 1 +
internal/pkg/func.go | 30 ++++++++++++++++++++++++++++
internal/tools/normal_chat.go | 8 +++++++-
5 files changed, 75 insertions(+), 1 deletion(-)
diff --git a/internal/biz/router.go b/internal/biz/router.go
index df7e82c..c88fa62 100644
--- a/internal/biz/router.go
+++ b/internal/biz/router.go
@@ -80,6 +80,13 @@ func (r *AiRouterBiz) RouteWithSocket(c *websocket.Conn, req *entitys.ChatSockRe
cancel()
}()
+ //获取图片信息
+ err = r.getImgData(req.Img, &requireData)
+ if err != nil {
+ log.Errorf("GetImgData error: %v", err)
+ return
+ }
+
//获取系统信息
err = r.getRequireData(req.Text, &requireData)
if err != nil {
@@ -173,6 +180,32 @@ func sendWithTimeout(c *websocket.Conn, data entitys.Response, timeout time.Dura
}
}
+func (r *AiRouterBiz) getImgData(imgUrl string, requireData *entitys.RequireData) (err error) {
+ return
+ if len(imgUrl) == 0 {
+ return
+ }
+ if err = pkg.ValidateImageURL(imgUrl); err != nil {
+ return err
+ }
+ req := l_request.Request{
+ Method: "GET",
+ Url: imgUrl,
+ }
+ res, err := req.Send()
+ if err != nil {
+ return
+ }
+ if _, ex := res.Headers["Content-Type"]; !ex {
+ return errors.ParamErr("图片格式错误:Content-Type未获取")
+ }
+ if !strings.HasPrefix(res.Headers["Content-Type"], "image/") {
+ return errors.ParamErr("expected image content, got %s", res.Headers["Content-Type"])
+ }
+ requireData.ImgByte = append(requireData.ImgByte, res.Content)
+ return
+}
+
func (r *AiRouterBiz) recognize(ctx context.Context, requireData *entitys.RequireData) (err error) {
requireData.Ch <- entitys.Response{
Index: "",
diff --git a/internal/data/error/error_code.go b/internal/data/error/error_code.go
index e6c7230..e9cc67d 100644
--- a/internal/data/error/error_code.go
+++ b/internal/data/error/error_code.go
@@ -46,6 +46,10 @@ func SysErr(message string, arg ...any) *BusinessErr {
return &BusinessErr{code: SystemError.code, message: fmt.Sprintf(message, arg)}
}
+func ParamErr(message string, arg ...any) *BusinessErr {
+ return &BusinessErr{code: ParamError.code, message: fmt.Sprintf(message, arg)}
+}
+
func (e *BusinessErr) Wrap(err error) *BusinessErr {
return NewBusinessErr(e.code, err.Error())
}
diff --git a/internal/entitys/types.go b/internal/entitys/types.go
index ca4eb96..0395b29 100644
--- a/internal/entitys/types.go
+++ b/internal/entitys/types.go
@@ -151,6 +151,7 @@ type RequireData struct {
Auth string
Ch chan Response
KnowledgeConf KnowledgeBaseRequest
+ ImgByte []api.ImageData
}
type KnowledgeBaseRequest struct {
diff --git a/internal/pkg/func.go b/internal/pkg/func.go
index 0583a46..27ce2a3 100644
--- a/internal/pkg/func.go
+++ b/internal/pkg/func.go
@@ -3,6 +3,10 @@ package pkg
import (
"ai_scheduler/internal/entitys"
"encoding/json"
+ "errors"
+ "fmt"
+ "net/url"
+ "strings"
)
func JsonStringIgonErr(data interface{}) string {
@@ -25,3 +29,29 @@ func IsChannelClosed(ch chan entitys.ResponseData) bool {
return false // channel 未关闭(但可能有数据未读取)
}
}
+
+// ValidateImageURL 验证图片 URL 是否有效
+func ValidateImageURL(rawURL string) error {
+ // 1. 基础格式验证
+ parsed, err := url.Parse(rawURL)
+ if err != nil {
+ return fmt.Errorf("invalid URL format: %v", err)
+ }
+
+ // 2. 检查协议是否为 http/https
+ if parsed.Scheme != "http" && parsed.Scheme != "https" {
+ return errors.New("URL must use http or https protocol")
+ }
+
+ // 3. 检查是否有空的主机名
+ if parsed.Host == "" {
+ return errors.New("URL missing host")
+ }
+
+ // 4. 检查路径是否为空(可选)
+ if strings.TrimSpace(parsed.Path) == "" {
+ return errors.New("URL path is empty")
+ }
+
+ return nil
+}
diff --git a/internal/tools/normal_chat.go b/internal/tools/normal_chat.go
index 489eb2e..9ef8903 100644
--- a/internal/tools/normal_chat.go
+++ b/internal/tools/normal_chat.go
@@ -56,6 +56,12 @@ func (w *NormalChatTool) Execute(ctx context.Context, requireData *entitys.Requi
// getMockZltxOrderDetail 获取模拟直连天下订单详情数据
func (w *NormalChatTool) chat(requireData *entitys.RequireData, chat *NormalChat) (err error) {
+ requireData.Ch <- entitys.Response{
+ Index: w.Name(),
+ Content: "",
+ Type: entitys.ResponseStream,
+ }
+
err = w.llm.ChatStream(context.TODO(), requireData.Ch, []api.Message{
{
Role: "system",
@@ -67,7 +73,7 @@ func (w *NormalChatTool) chat(requireData *entitys.RequireData, chat *NormalChat
},
{
Role: "user",
- Content: requireData.UserInput,
+ Content: chat.ChatContent,
},
}, w.Name())
if err != nil {
From 366a0b1e85458ebb2c73094001ef5b21bbdcb634 Mon Sep 17 00:00:00 2001
From: renzhiyuan <465386466@qq.com>
Date: Thu, 9 Oct 2025 15:09:00 +0800
Subject: [PATCH 2/4] =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
config/config.yaml | 1 +
internal/biz/llm_service/ollama.go | 43 +++++++++++++++++++++++++----
internal/biz/router.go | 2 +-
internal/config/config.go | 7 +++--
internal/pkg/utils_ollama/client.go | 15 ++++++++--
internal/tools/manager.go | 2 +-
internal/tools/normal_chat.go | 20 ++++++++------
internal/tools/zltx_order_detail.go | 2 +-
8 files changed, 69 insertions(+), 23 deletions(-)
diff --git a/config/config.yaml b/config/config.yaml
index ea79049..9f9ec5d 100644
--- a/config/config.yaml
+++ b/config/config.yaml
@@ -6,6 +6,7 @@ server:
ollama:
base_url: "http://127.0.0.1:11434"
model: "qwen3-coder:480b-cloud"
+ generate_model: "deepseek-v3.1:671b-cloud"
timeout: "120s"
level: "info"
format: "json"
diff --git a/internal/biz/llm_service/ollama.go b/internal/biz/llm_service/ollama.go
index 00929aa..9a7ee25 100644
--- a/internal/biz/llm_service/ollama.go
+++ b/internal/biz/llm_service/ollama.go
@@ -1,6 +1,7 @@
package llm_service
import (
+ "ai_scheduler/internal/config"
"ai_scheduler/internal/data/model"
"ai_scheduler/internal/entitys"
"ai_scheduler/internal/pkg"
@@ -16,18 +17,24 @@ import (
type OllamaService struct {
client *utils_ollama.Client
+ config *config.Config
}
func NewOllamaGenerate(
client *utils_ollama.Client,
+ config *config.Config,
) *OllamaService {
return &OllamaService{
client: client,
+ config: config,
}
}
func (r *OllamaService) IntentRecognize(ctx context.Context, requireData *entitys.RequireData) (msg string, err error) {
- prompt := r.getPrompt(requireData.Sys, requireData.Histories, requireData.UserInput, requireData.Tasks)
+ prompt, err := r.getPrompt(ctx, requireData)
+ if err != nil {
+ return
+ }
toolDefinitions := r.registerToolsOllama(requireData.Tasks)
match, err := r.client.ToolSelect(context.TODO(), prompt, toolDefinitions)
if err != nil {
@@ -53,21 +60,45 @@ func (r *OllamaService) IntentRecognize(ctx context.Context, requireData *entity
return
}
-func (r *OllamaService) getPrompt(sysInfo model.AiSy, history []model.AiChatHi, reqInput string, tasks []model.AiTask) []api.Message {
+func (r *OllamaService) getPrompt(ctx context.Context, requireData *entitys.RequireData) ([]api.Message, error) {
+
var (
prompt = make([]api.Message, 0)
)
prompt = append(prompt, api.Message{
Role: "system",
- Content: buildSystemPrompt(sysInfo.SysPrompt),
+ Content: buildSystemPrompt(requireData.Sys.SysPrompt),
}, api.Message{
Role: "assistant",
- Content: fmt.Sprintf("聊天记录:%s", pkg.JsonStringIgonErr(buildAssistant(history))),
+ Content: fmt.Sprintf("聊天记录:%s", pkg.JsonStringIgonErr(buildAssistant(requireData.Histories))),
}, api.Message{
Role: "user",
- Content: reqInput,
+ Content: requireData.UserInput,
})
- return prompt
+
+ if len(requireData.ImgByte) > 0 {
+ _, err := r.RecognizeWithImg(ctx, requireData)
+ if err != nil {
+ return nil, err
+ }
+
+ }
+ return prompt, nil
+}
+
+func (r *OllamaService) RecognizeWithImg(ctx context.Context, requireData *entitys.RequireData) (desc api.GenerateResponse, err error) {
+ requireData.Ch <- entitys.Response{
+ Index: "",
+ Content: "图片识别中。。。",
+ Type: entitys.ResponseLoading,
+ }
+ desc, err = r.client.Generation(ctx, &api.GenerateRequest{
+ Model: r.config.Ollama.GenerateModel,
+ Stream: new(bool),
+ System: "识别图片内容",
+ Prompt: requireData.UserInput,
+ })
+ return
}
func (r *OllamaService) registerToolsOllama(tasks []model.AiTask) []api.Tool {
diff --git a/internal/biz/router.go b/internal/biz/router.go
index c88fa62..d26933c 100644
--- a/internal/biz/router.go
+++ b/internal/biz/router.go
@@ -181,7 +181,7 @@ func sendWithTimeout(c *websocket.Conn, data entitys.Response, timeout time.Dura
}
func (r *AiRouterBiz) getImgData(imgUrl string, requireData *entitys.RequireData) (err error) {
- return
+
if len(imgUrl) == 0 {
return
}
diff --git a/internal/config/config.go b/internal/config/config.go
index 2e80b18..5cb97d8 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -39,9 +39,10 @@ type ServerConfig struct {
// OllamaConfig Ollama配置
type OllamaConfig struct {
- BaseURL string `mapstructure:"base_url"`
- Model string `mapstructure:"model"`
- Timeout time.Duration `mapstructure:"timeout"`
+ BaseURL string `mapstructure:"base_url"`
+ Model string `mapstructure:"model"`
+ GenerateModel string `mapstructure:"generate_model"`
+ Timeout time.Duration `mapstructure:"timeout"`
}
type Redis struct {
diff --git a/internal/pkg/utils_ollama/client.go b/internal/pkg/utils_ollama/client.go
index fddbf0a..a5bc9ca 100644
--- a/internal/pkg/utils_ollama/client.go
+++ b/internal/pkg/utils_ollama/client.go
@@ -59,10 +59,13 @@ func (c *Client) ToolSelect(ctx context.Context, messages []api.Message, tools [
return
}
-func (c *Client) ChatStream(ctx context.Context, ch chan entitys.Response, messages []api.Message, index string) (err error) {
+func (c *Client) ChatStream(ctx context.Context, ch chan entitys.Response, messages []api.Message, index string, model string) (err error) {
+ if len(model) == 0 {
+ model = c.config.Model
+ }
// 构建聊天请求
req := &api.ChatRequest{
- Model: c.config.Model,
+ Model: model,
Messages: messages,
Stream: nil,
Think: &api.ThinkValue{Value: true},
@@ -90,6 +93,14 @@ func (c *Client) ChatStream(ctx context.Context, ch chan entitys.Response, messa
return
}
+func (c *Client) Generation(ctx context.Context, generateRequest *api.GenerateRequest) (result api.GenerateResponse, err error) {
+ err = c.client.Generate(ctx, generateRequest, func(resp api.GenerateResponse) error {
+ result = resp
+ return nil
+ })
+ return
+}
+
// convertResponse 转换响应格式
func (c *Client) convertResponse(resp *api.ChatResponse) *entitys.ChatResponse {
//result := &entitys.ChatResponse{
diff --git a/internal/tools/manager.go b/internal/tools/manager.go
index 4ef85a6..8136efb 100644
--- a/internal/tools/manager.go
+++ b/internal/tools/manager.go
@@ -71,7 +71,7 @@ func NewManager(config *config.Config, llm *utils_ollama.Client) *Manager {
}
// 普通对话
- chat := NewNormalChatTool(m.llm)
+ chat := NewNormalChatTool(m.llm, config)
m.tools[chat.Name()] = chat
return m
diff --git a/internal/tools/normal_chat.go b/internal/tools/normal_chat.go
index 9ef8903..56010fc 100644
--- a/internal/tools/normal_chat.go
+++ b/internal/tools/normal_chat.go
@@ -1,6 +1,7 @@
package tools
import (
+ "ai_scheduler/internal/config"
"ai_scheduler/internal/entitys"
"ai_scheduler/internal/pkg"
"ai_scheduler/internal/pkg/utils_ollama"
@@ -13,12 +14,13 @@ import (
// NormalChatTool 普通对话
type NormalChatTool struct {
- llm *utils_ollama.Client
+ llm *utils_ollama.Client
+ config *config.Config
}
// NewNormalChatTool 实例普通对话
-func NewNormalChatTool(llm *utils_ollama.Client) *NormalChatTool {
- return &NormalChatTool{llm: llm}
+func NewNormalChatTool(llm *utils_ollama.Client, config *config.Config) *NormalChatTool {
+ return &NormalChatTool{llm: llm, config: config}
}
// Name 返回工具名称
@@ -56,11 +58,11 @@ func (w *NormalChatTool) Execute(ctx context.Context, requireData *entitys.Requi
// getMockZltxOrderDetail 获取模拟直连天下订单详情数据
func (w *NormalChatTool) chat(requireData *entitys.RequireData, chat *NormalChat) (err error) {
- requireData.Ch <- entitys.Response{
- Index: w.Name(),
- Content: "",
- Type: entitys.ResponseStream,
- }
+ //requireData.Ch <- entitys.Response{
+ // Index: w.Name(),
+ // Content: "",
+ // Type: entitys.ResponseStream,
+ //}
err = w.llm.ChatStream(context.TODO(), requireData.Ch, []api.Message{
{
@@ -75,7 +77,7 @@ func (w *NormalChatTool) chat(requireData *entitys.RequireData, chat *NormalChat
Role: "user",
Content: chat.ChatContent,
},
- }, w.Name())
+ }, w.Name(), w.config.Ollama.GenerateModel)
if err != nil {
return fmt.Errorf(":%s", err)
}
diff --git a/internal/tools/zltx_order_detail.go b/internal/tools/zltx_order_detail.go
index 84ff6f6..6c9bd9c 100644
--- a/internal/tools/zltx_order_detail.go
+++ b/internal/tools/zltx_order_detail.go
@@ -173,7 +173,7 @@ func (w *ZltxOrderDetailTool) getZltxOrderDetail(requireData *entitys.RequireDat
Role: "user",
Content: requireData.UserInput,
},
- }, w.Name())
+ }, w.Name(), "")
if err != nil {
return fmt.Errorf("订单日志解析失败:%s", err)
}
From d0dae2d43d40731fb2bf4b8abf2c894c479cf2a1 Mon Sep 17 00:00:00 2001
From: renzhiyuan <465386466@qq.com>
Date: Thu, 9 Oct 2025 15:17:23 +0800
Subject: [PATCH 3/4] =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
config/config.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/config.yaml b/config/config.yaml
index 9f9ec5d..8b63456 100644
--- a/config/config.yaml
+++ b/config/config.yaml
@@ -6,7 +6,7 @@ server:
ollama:
base_url: "http://127.0.0.1:11434"
model: "qwen3-coder:480b-cloud"
- generate_model: "deepseek-v3.1:671b-cloud"
+ generate_model: "qwen3-coder:480b-cloud"
timeout: "120s"
level: "info"
format: "json"
From f2d64b0a4ec95d62ea8be37d66816c8d45151ff5 Mon Sep 17 00:00:00 2001
From: renzhiyuan <465386466@qq.com>
Date: Thu, 9 Oct 2025 15:57:02 +0800
Subject: [PATCH 4/4] =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
internal/tools/zltx_product.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/internal/tools/zltx_product.go b/internal/tools/zltx_product.go
index 1d7b346..4a9cbf8 100644
--- a/internal/tools/zltx_product.go
+++ b/internal/tools/zltx_product.go
@@ -3,6 +3,7 @@ package tools
import (
"ai_scheduler/internal/config"
"ai_scheduler/internal/entitys"
+ "ai_scheduler/internal/pkg"
"context"
"encoding/json"
"fmt"
@@ -167,7 +168,7 @@ func (z ZltxProductTool) getZltxProduct(body *ZltxProductRequest, requireData *e
return fmt.Errorf("解析商品数据失败:%w", err)
}
if resp.Code != 200 {
- return fmt.Errorf("商品查询失败:%s", resp.Error)
+ return fmt.Errorf("商品查询失败:%s", pkg.JsonStringIgonErr(resp))
}
if resp.Data.List == nil || len(resp.Data.List) == 0 {
var respData ZltxProductDataById