feat: 重构异步模型字段并更新依赖

This commit is contained in:
2026-06-08 18:01:54 +08:00
parent ee6677c1f8
commit e1461cf0f0
12 changed files with 219 additions and 335 deletions

View File

@@ -2,13 +2,11 @@ package util
import (
"encoding/json"
"strconv"
"fmt"
"github.com/gogf/gf/v2/container/gvar"
gfgjson "github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/util/gconv"
tGjson "github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
// ConvertToMessages 将原始数据转换为消息列表
@@ -17,7 +15,7 @@ func ConvertToMessages(raw any) []map[string]any {
return nil
}
j := gfgjson.New(raw)
j := gjson.New(raw)
messages := j.Get("messages")
if !messages.IsNil() {
return gconv.Maps(messages.Val())
@@ -66,7 +64,7 @@ func JSONPretty(v any) string {
return gconv.String(v)
}
b, _ := json.MarshalIndent(tmp, "", " ")
b, _ := json.Marshal(tmp)
return string(b)
}
@@ -102,132 +100,98 @@ func ParseJSONFieldFromGvar(source any, target any) {
}
}
// MergeConsult 将 consult 附件合并到模型生成的 messages 结构中
//
// 参数说明:
// - req: 请求参数 map需包含 "consult" 字段,值为 []any每个元素是 {"type":"xxx","url":"..."}
// - messages: 模型生成的返回结构(如 rounds[...].messages[...].content 数组)
// - extendMapping: 附加映射配置,格式:
// {"attachments": {"image": {"template": {...}, "target_path": "...", "field_mapping": {...}}, ...}}
//
// 返回值:合并后的完整 map。
// MergeConsult 将 consult 附件合并到模型生成的 messages 结构中
func MergeConsult(req map[string]any, messages map[string]any, extendMapping map[string]any) map[string]any {
if len(req) == 0 || len(messages) == 0 || len(extendMapping) == 0 {
return messages
}
reqJSON, _ := json.Marshal(req)
msgJSON, _ := json.Marshal(messages)
extJSON, _ := json.Marshal(extendMapping)
reqStr := string(reqJSON)
msgStr := string(msgJSON)
extStr := string(extJSON)
// 获取 consult 数组
consultResult := tGjson.Get(reqStr, "consult")
if !consultResult.Exists() || !consultResult.IsArray() {
// 1) 获取 consult 数组
consult := gconv.Interfaces(req["consult"])
if len(consult) == 0 {
return messages
}
// 获取 attachments 配置
attachmentsResult := tGjson.Get(extStr, "attachments")
if !attachmentsResult.Exists() || !attachmentsResult.IsObject() {
// 2) 获取配置
targetPath := gconv.String(extendMapping["target_content_path"])
if targetPath == "" {
return messages
}
consultArr := consultResult.Array()
attachmentsMap := attachmentsResult.Map()
templates := gconv.Map(extendMapping["attachment_templates"])
if len(templates) == 0 {
return messages
}
for _, consultItem := range consultArr {
if !consultItem.IsObject() {
continue
}
// 3) 转为 gjson 操作
msgJson := gjson.New(messages)
itemType := consultItem.Get("type").String()
// 固定:如果有 rounds 结构,路径替换为 rounds.0.{targetPath}
if arr := msgJson.Get("rounds.0").Array(); arr != nil {
targetPath = "rounds.0." + targetPath
}
// 4) 遍历 consult按类型生成附件并追加
for _, item := range consult {
itemJson := gjson.New(item)
itemType := itemJson.Get("type").String()
if itemType == "" {
continue
}
// 查找对应类型的附件配置
attachResult, ok := attachmentsMap[itemType]
if !ok || !attachResult.IsObject() {
// 查找对应模板
tmpl := gconv.Map(templates[itemType])
if len(tmpl) == 0 {
continue
}
// 获取模板
templateResult := attachResult.Get("template")
if !templateResult.Exists() || !templateResult.IsObject() {
// 生成附件对象
attachment := buildAttachment(tmpl, itemJson.Get("url").String())
if attachment == nil {
continue
}
// 深拷贝模板
filledTemplateStr := templateResult.Raw
// 获取当前数组长度,用索引追加
arr := msgJson.Get(targetPath).Array()
idx := len(arr)
indexPath := fmt.Sprintf("%s.%d", targetPath, idx)
_ = msgJson.Set(indexPath, attachment)
}
// 应用字段映射
fieldMappingResult := attachResult.Get("field_mapping")
if fieldMappingResult.Exists() && fieldMappingResult.IsObject() {
fieldMapping := fieldMappingResult.Map()
for fieldPath, valueSource := range fieldMapping {
sourceKey := valueSource.String()
valueResult := consultItem.Get(sourceKey)
if valueResult.Exists() {
var err error
filledTemplateStr, err = sjson.SetRaw(filledTemplateStr, fieldPath, valueResult.Raw)
if err != nil {
continue
}
}
return msgJson.Map()
}
// buildAttachment 根据模板和用户数据生成附件对象
func buildAttachment(tmpl map[string]any, url string) map[string]any {
typ := gconv.String(tmpl["type"])
if typ == "" || url == "" {
return nil
}
// 深拷贝 body 并填充 url
body := gconv.Map(tmpl["body"])
bodyJson := gjson.New(body)
bodyJson = fillEmpty(bodyJson, url)
return map[string]any{
"type": typ,
typ: bodyJson.Map(),
}
}
// fillEmpty 递归查找空字符串并替换
func fillEmpty(j *gjson.Json, value string) *gjson.Json {
m := j.Map()
for k, v := range m {
switch vv := v.(type) {
case string:
if vv == "" {
_ = j.Set(k, value)
}
}
// 获取目标路径
targetPath := attachResult.Get("target_path").String()
if targetPath == "" {
continue
}
// 检查目标路径是否存在且为数组
targetResult := tGjson.Get(msgStr, targetPath)
if !targetResult.Exists() || !targetResult.IsArray() {
continue
}
// 追加到数组末尾
arrLen := len(targetResult.Array())
appendPath := targetPath + "." + strconv.Itoa(arrLen)
var err error
msgStr, err = sjson.SetRaw(msgStr, appendPath, filledTemplateStr)
if err != nil {
continue
case map[string]any:
_ = j.Set(k, fillEmpty(gjson.New(vv), value).Map())
}
}
// 转回 map[string]any
var result map[string]any
if err := json.Unmarshal([]byte(msgStr), &result); err != nil {
return messages
}
return result
}
// GetUserMessage 获取用户消息
func GetUserMessage(taskReq map[string]any) map[string]any {
// 先取 requestPayload
rp, ok := taskReq["requestPayload"].(map[string]any)
if !ok {
return nil
}
// 再取 messages
messages, ok := rp["messages"].([]any)
if !ok {
return nil
}
for _, msg := range messages {
m, ok := msg.(map[string]any)
if ok && m["role"] == "user" {
return m
}
}
return nil
return j
}

View File

@@ -1,11 +1,9 @@
package util
import (
"net/url"
"strings"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/util/gconv"
)
// ReverseMap 映射 payload 到 mapping
@@ -22,86 +20,80 @@ func ReverseMap(mapping map[string]any, payload map[string]any) map[string]any {
return jsonObj.Map()
}
// MapResponsePayload 映射模型响应为标准格式
func MapResponsePayload(mapping map[string]any, responseBytes []byte) ([]byte, error) {
if len(mapping) == 0 {
return responseBytes, nil
}
// ExtractUserText 从 messages map 中提取用户文本,返回标准的 user message 结构
func ExtractUserText(messages map[string]any) map[string]any {
var texts []string
responseJson := gjson.New(responseBytes)
resultJson := gjson.New("{}")
for standardField, modelPath := range mapping {
path := gconv.String(modelPath)
if path == "" {
continue
}
val := responseJson.Get(path)
if val.IsNil() {
continue
}
resultJson.Set(standardField, val.Val())
}
return []byte(resultJson.String()), nil
}
// ParseHeadMsgHeaders 支持多个 header 绑定,逗号分隔:
// 示例:
// - X-API-Key:qwen3-tts-key,operation:true,count:123
// - X-API-Key:"qwen3-tts-key",operation:"true"
//
// 说明:
// - HTTP Header 最终都是字符串,这里做的是“值的字符串化表达”。
// - 若 value 用双引号包裹,会去掉外层引号再注入,便于在配置中区分字符串/布尔/数字等表达(以及避免值中包含特殊字符时歧义)。
func ParseHeadMsgHeaders(headMsg string) map[string]string {
headMsg = strings.TrimSpace(headMsg)
if headMsg == "" {
return nil
}
out := map[string]string{}
parts := strings.Split(headMsg, ",")
for _, p := range parts {
p = strings.TrimSpace(p)
if p == "" {
continue
}
// HeaderName:HeaderValue推荐 / HeaderName=HeaderValue兼容
if strings.Contains(p, ":") {
kv := strings.SplitN(p, ":", 2)
k := strings.TrimSpace(kv[0])
v := strings.TrimSpace(kv[1])
v = strings.Trim(v, "\"")
if k != "" && v != "" {
out[k] = v
// 1) rounds 结构:遍历每轮
if rounds, ok := messages["rounds"].([]any); ok {
for _, round := range rounds {
if rm, ok := round.(map[string]any); ok {
if msgs, ok := rm["messages"].([]any); ok {
texts = append(texts, extractTextFromRoleUser(msgs)...)
}
}
continue
}
if strings.Contains(p, "=") {
kv := strings.SplitN(p, "=", 2)
k := strings.TrimSpace(kv[0])
v := strings.TrimSpace(kv[1])
v = strings.Trim(v, "\"")
if k != "" && v != "" {
out[k] = v
}
continue
}
} else if msgs, ok := messages["messages"].([]any); ok {
// 2) messages 结构
texts = extractTextFromRoleUser(msgs)
}
if len(out) == 0 {
return nil
// 3) 构建返回结构
return map[string]any{
"role": "user",
"content": strings.Join(texts, "\n"),
}
return out
}
// PayloadToQuery 将 payload 转为 url.Values
func PayloadToQuery(payload map[string]any) (url.Values, error) {
q := url.Values{}
for k, v := range payload {
if v == nil {
// extractTextFromRoleUser 从 messages 数组中提取所有 role=user 的文本
func extractTextFromRoleUser(msgs []any) []string {
var texts []string
for _, msg := range msgs {
m, ok := msg.(map[string]any)
if !ok {
continue
}
q.Set(k, gconv.String(v))
if role, _ := m["role"].(string); role != "user" {
continue
}
texts = append(texts, extractAllText(m["content"])...)
}
return q, nil
return texts
}
// extractAllText 从 content 中提取所有文本(递归,最大兼容)
func extractAllText(content any) []string {
switch c := content.(type) {
case string:
return []string{c}
case []any:
var texts []string
for _, item := range c {
m, ok := item.(map[string]any)
if !ok {
continue
}
if t, ok := m["text"].(string); ok && t != "" {
texts = append(texts, t)
continue
}
for _, v := range m {
texts = append(texts, extractAllText(v)...)
}
}
return texts
case map[string]any:
if t, ok := c["text"].(string); ok && t != "" {
return []string{t}
}
var texts []string
for _, v := range c {
texts = append(texts, extractAllText(v)...)
}
return texts
}
return nil
}