优化ai广场

This commit is contained in:
2026-09-10 22:31:04 +08:00
parent 9f60450ffa
commit 1304e71b7a
4 changed files with 314 additions and 54 deletions
+59 -7
View File
@@ -63,6 +63,8 @@ type chatSendPayload struct {
McpEnabled *bool `json:"mcp_enabled"`
// McpServerIDs 限定参与本轮对话的 MCP 服务器;为空表示使用全部已启用的服务器
McpServerIDs []uint64 `json:"mcp_server_ids"`
// Images 用户上传的图片(base64 dataURL),仅多模态模型可用
Images []string `json:"images"`
}
// mcpEnabled 解析本轮是否启用 MCP(未传默认开启)
@@ -78,6 +80,49 @@ type openaiMessage struct {
Content string `json:"content,omitempty"`
ToolCalls []openaiToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
// Images 仅 user 消息使用,存放 base64 dataURL,用于多模态输入
Images []string `json:"-"`
}
// openAIMessages 将内部消息转换为 OpenAI 兼容的请求结构。
// 当且仅当 user 消息携带图片时,content 会被构造为多模态 parts(text + image_url)。
func openAIMessages(messages []openaiMessage) []map[string]interface{} {
out := make([]map[string]interface{}, 0, len(messages))
for _, m := range messages {
item := map[string]interface{}{"role": m.Role}
if len(m.Images) > 0 {
parts := []map[string]interface{}{}
if strings.TrimSpace(m.Content) != "" {
parts = append(parts, map[string]interface{}{"type": "text", "text": m.Content})
}
for _, img := range m.Images {
parts = append(parts, map[string]interface{}{
"type": "image_url",
"image_url": map[string]interface{}{"url": img},
})
}
item["content"] = parts
} else {
item["content"] = m.Content
}
if len(m.ToolCalls) > 0 {
item["tool_calls"] = m.ToolCalls
}
if m.ToolCallID != "" {
item["tool_call_id"] = m.ToolCallID
}
out = append(out, item)
}
return out
}
// marshalImages 将图片列表序列化为可存储的 JSON 字符串(无图片返回空串)
func marshalImages(imgs []string) string {
if len(imgs) == 0 {
return ""
}
b, _ := json.Marshal(imgs)
return string(b)
}
type openaiToolCall struct {
@@ -522,7 +567,12 @@ func loadHistory(sessionID uint64) []openaiMessage {
All(&history)
out := make([]openaiMessage, 0, len(history)+1)
for i := len(history) - 1; i >= 0; i-- {
out = append(out, openaiMessage{Role: history[i].Role, Content: history[i].Content})
h := history[i]
imgs := []string{}
if strings.TrimSpace(h.Images) != "" {
_ = json.Unmarshal([]byte(h.Images), &imgs)
}
out = append(out, openaiMessage{Role: h.Role, Content: h.Content, Images: imgs})
}
return out
}
@@ -547,7 +597,7 @@ func (c *BackendAiChatController) Send() {
c.chatJsonErr(400, 400, "参数格式错误")
return
}
if strings.TrimSpace(p.Content) == "" {
if strings.TrimSpace(p.Content) == "" && len(p.Images) == 0 {
c.chatJsonErr(400, 400, "消息内容不能为空")
return
}
@@ -570,7 +620,7 @@ func (c *BackendAiChatController) Send() {
systemPrompt := loadSystemPrompt(claims)
messages := loadHistory(session.ID)
messages = append(messages, openaiMessage{Role: "user", Content: p.Content})
messages = append(messages, openaiMessage{Role: "user", Content: p.Content, Images: p.Images})
// 保存用户消息
userMsg := models.BackendAiChatMessage{
@@ -578,6 +628,7 @@ func (c *BackendAiChatController) Send() {
SessionID: session.ID,
Role: "user",
Content: p.Content,
Images: marshalImages(p.Images),
CreateTime: time.Now(),
}
_, _ = models.Orm.Insert(&userMsg)
@@ -661,7 +712,7 @@ func (c *BackendAiChatController) SendStream() {
c.chatJsonErr(400, 400, "参数格式错误")
return
}
if strings.TrimSpace(p.Content) == "" {
if strings.TrimSpace(p.Content) == "" && len(p.Images) == 0 {
c.chatJsonErr(400, 400, "消息内容不能为空")
return
}
@@ -684,7 +735,7 @@ func (c *BackendAiChatController) SendStream() {
systemPrompt := loadSystemPrompt(claims)
messages := loadHistory(session.ID)
messages = append(messages, openaiMessage{Role: "user", Content: p.Content})
messages = append(messages, openaiMessage{Role: "user", Content: p.Content, Images: p.Images})
// 保存用户消息
userMsg := models.BackendAiChatMessage{
@@ -692,6 +743,7 @@ func (c *BackendAiChatController) SendStream() {
SessionID: session.ID,
Role: "user",
Content: p.Content,
Images: marshalImages(p.Images),
CreateTime: time.Now(),
}
_, _ = models.Orm.Insert(&userMsg)
@@ -842,7 +894,7 @@ func callAITools(provider models.BackendAiProvider, model string, systemPrompt s
}
reqBody := map[string]interface{}{
"model": model,
"messages": reqMessages,
"messages": openAIMessages(reqMessages),
}
if len(tools) > 0 {
reqBody["tools"] = tools
@@ -1024,7 +1076,7 @@ func callOpenAIStreamTools(provider models.BackendAiProvider, model string, syst
}
reqBody := map[string]interface{}{
"model": model,
"messages": reqMessages,
"messages": openAIMessages(reqMessages),
"stream": true,
}
if len(tools) > 0 {
@@ -0,0 +1,3 @@
-- 为 AI 聊天消息表增加图片字段(用户上传的图片,base64 dataURL 的 JSON 数组)
ALTER TABLE `yz_backend_ai_chat_message`
ADD COLUMN `images` TEXT NULL COMMENT '用户上传的图片(base64 dataURL 的 JSON 数组)';
+2
View File
@@ -9,6 +9,8 @@ type BackendAiChatMessage struct {
SessionID uint64 `orm:"column(session_id)" json:"session_id"`
Role string `orm:"column(role);size(20)" json:"role"` // user / assistant
Content string `orm:"column(content);type(text)" json:"content"`
// Images 用户上传的图片(base64 dataURL 的 JSON 数组),仅 user 消息使用
Images string `orm:"column(images);type(text)" json:"images"`
// Tokens 本次响应消耗的 token 总数(0 表示未统计到)
Tokens int `orm:"column(tokens)" json:"tokens"`
// DurationMs AI 响应耗时(毫秒,含工具调用轮次)