package service import ( "sort" "strings" modelUtils "model-gateway/service/utils" "github.com/gogf/gf/v2/util/gconv" ) // streamToolCallAcc 流式 tool_call 按 index 累加的碎片(OpenAI 兼容 delta 格式) type streamToolCallAcc struct { id string typ string fnName string fnArgs strings.Builder } // streamToolCallDelta OpenAI 兼容流式 tool_call 增量片段,字段名集中于此 type streamToolCallDelta struct { Index int `json:"index"` Id string `json:"id"` Type string `json:"type"` Function struct { Name string `json:"name"` Arguments string `json:"arguments"` } `json:"function"` } // toStreamToolCallDeltas 把 JSON 反序列化的 any 数组在边界转成强类型片段 func toStreamToolCallDeltas(rawCalls []any) []streamToolCallDelta { var deltas []streamToolCallDelta if err := gconv.Structs(rawCalls, &deltas); err != nil { return nil } return deltas } // accumulateStreamToolCallsByPath 按配置路径从 chunk 读取 tool_calls 数组并累加。 // 路径未命中或值非数组时无副作用(不创建任何槽位)。 func accumulateStreamToolCallsByPath(chunk map[string]any, toolsPath string, acc map[int]*streamToolCallAcc) { raw := modelUtils.GetByPathValue(chunk, modelUtils.CleanFieldPath(toolsPath)) rawCalls, _ := raw.([]any) accumulateToolCallFragments(toStreamToolCallDeltas(rawCalls), acc) } // accumulateToolCallFragments 按 index 累加 tool_calls 增量片段: // id/type/function.name 首片段补齐,function.arguments 为字符串片段需按 index 拼接。 func accumulateToolCallFragments(rawCalls []streamToolCallDelta, acc map[int]*streamToolCallAcc) { for _, d := range rawCalls { slot, ok := acc[d.Index] if !ok { slot = &streamToolCallAcc{} acc[d.Index] = slot } if d.Id != "" { slot.id = d.Id } if d.Type != "" { slot.typ = d.Type } if d.Function.Name != "" { slot.fnName = d.Function.Name } slot.fnArgs.WriteString(d.Function.Arguments) } } // finalizeStreamToolCalls 把累加结果按 index 升序转为 []map[string]any,形状对齐 dto.ModelTool。 // 无有效工具返回 nil。 func finalizeStreamToolCalls(acc map[int]*streamToolCallAcc) []map[string]any { if len(acc) == 0 { return nil } idx := make([]int, 0, len(acc)) for i := range acc { idx = append(idx, i) } sort.Ints(idx) tools := make([]map[string]any, 0, len(idx)) for _, i := range idx { s := acc[i] fn := map[string]any{} if s.fnName != "" { fn["name"] = s.fnName } if s.fnArgs.Len() > 0 { fn["arguments"] = s.fnArgs.String() } tool := map[string]any{"function": fn} if s.id != "" { tool["id"] = s.id } if s.typ != "" { tool["type"] = s.typ } tools = append(tools, tool) } return tools }