mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-17 05:07:23 +08:00
Go: add tools for moonshot, baidu and minimax (#17000)
### Summary As title related to #16990
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
"name": "MiniMax",
|
||||
"rank": 987,
|
||||
"url": {
|
||||
"default": "https://api.minimaxi.com/",
|
||||
"global": "https://api.minimax.io/"
|
||||
"default": "https://api.minimaxi.com",
|
||||
"global": "https://api.minimax.io"
|
||||
},
|
||||
"url_suffix": {
|
||||
"chat": "v1/text/chatcompletion_v2",
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"ragflow/internal/common"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -65,12 +66,18 @@ func (b *BaiduModel) ChatWithMessages(modelName string, messages []Message, apiC
|
||||
url := fmt.Sprintf("%s/%s", resolvedBaseURL, b.baseModel.URLSuffix.Chat)
|
||||
|
||||
// Convert messages to API format
|
||||
apiMessages := make([]map[string]interface{}, len(messages))
|
||||
apiMessages := make([]map[string]any, len(messages))
|
||||
for i, msg := range messages {
|
||||
apiMessages[i] = map[string]interface{}{
|
||||
apiMessages[i] = map[string]any{
|
||||
"role": msg.Role,
|
||||
"content": msg.Content,
|
||||
}
|
||||
if msg.ToolCallID != "" {
|
||||
apiMessages[i]["tool_call_id"] = msg.ToolCallID
|
||||
}
|
||||
if len(msg.ToolCalls) > 0 {
|
||||
apiMessages[i]["tool_calls"] = msg.ToolCalls
|
||||
}
|
||||
}
|
||||
|
||||
// Build request body
|
||||
@@ -142,6 +149,12 @@ func (b *BaiduModel) ChatWithMessages(modelName string, messages []Message, apiC
|
||||
}
|
||||
}
|
||||
}
|
||||
if chatModelConfig.Tools != nil {
|
||||
reqBody["tools"] = chatModelConfig.Tools
|
||||
}
|
||||
if chatModelConfig.ToolChoice != nil {
|
||||
reqBody["tool_choice"] = *chatModelConfig.ToolChoice
|
||||
}
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(reqBody)
|
||||
@@ -201,6 +214,15 @@ func (b *BaiduModel) ChatWithMessages(modelName string, messages []Message, apiC
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
var toolCalls []map[string]any
|
||||
if tcs, ok := messageMap["tool_calls"].([]any); ok {
|
||||
for _, tc := range tcs {
|
||||
if toolCall, ok := tc.(map[string]any); ok {
|
||||
toolCalls = append(toolCalls, toolCall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var reasonContent string
|
||||
if chatModelConfig != nil && chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
|
||||
reasonContent, ok = messageMap["reasoning_content"].(string)
|
||||
@@ -216,6 +238,12 @@ func (b *BaiduModel) ChatWithMessages(modelName string, messages []Message, apiC
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
if pt, ct, tt := extractUsageFromMap(result); tt > 0 {
|
||||
chatResponse.Usage = &ChatUsage{
|
||||
PromptTokens: pt, CompletionTokens: ct, TotalTokens: tt,
|
||||
}
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -243,14 +271,19 @@ func (b *BaiduModel) ChatStreamlyWithSender(modelName string, messages []Message
|
||||
"role": msg.Role,
|
||||
"content": msg.Content,
|
||||
}
|
||||
if msg.ToolCallID != "" {
|
||||
apiMessages[i]["tool_call_id"] = msg.ToolCallID
|
||||
}
|
||||
if len(msg.ToolCalls) > 0 {
|
||||
apiMessages[i]["tool_calls"] = msg.ToolCalls
|
||||
}
|
||||
}
|
||||
|
||||
// Build request body with streaming enabled
|
||||
reqBody := map[string]interface{}{
|
||||
"model": modelName,
|
||||
"messages": apiMessages,
|
||||
"stream": true,
|
||||
"temperature": 1,
|
||||
"model": modelName,
|
||||
"messages": apiMessages,
|
||||
"stream": true,
|
||||
}
|
||||
|
||||
if modelConfig != nil {
|
||||
@@ -318,6 +351,13 @@ func (b *BaiduModel) ChatStreamlyWithSender(modelName string, messages []Message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if modelConfig.Tools != nil {
|
||||
reqBody["tools"] = modelConfig.Tools
|
||||
}
|
||||
if modelConfig.ToolChoice != nil {
|
||||
reqBody["tool_choice"] = *modelConfig.ToolChoice
|
||||
}
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(reqBody)
|
||||
@@ -349,6 +389,7 @@ func (b *BaiduModel) ChatStreamlyWithSender(modelName string, messages []Message
|
||||
|
||||
// SSE parsing: read line by line
|
||||
sawTerminal := false
|
||||
accumulatedToolCalls := make(map[int]map[string]interface{})
|
||||
done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
common.Info(fmt.Sprintf("%v", event))
|
||||
|
||||
@@ -367,6 +408,56 @@ func (b *BaiduModel) ChatStreamlyWithSender(modelName string, messages []Message
|
||||
return nil
|
||||
}
|
||||
|
||||
if tcs, ok := delta["tool_calls"].([]interface{}); ok {
|
||||
for _, tc := range tcs {
|
||||
tcMap, ok := tc.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
idxF, ok := tcMap["index"].(float64)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
idx := int(idxF)
|
||||
existing, hasExisting := accumulatedToolCalls[idx]
|
||||
if !hasExisting {
|
||||
accumulatedToolCalls[idx] = cloneMap(tcMap)
|
||||
continue
|
||||
}
|
||||
if id, ok := tcMap["id"].(string); ok && id != "" {
|
||||
if eid, ok := existing["id"].(string); ok {
|
||||
existing["id"] = eid + id
|
||||
} else {
|
||||
existing["id"] = id
|
||||
}
|
||||
}
|
||||
if typ, ok := tcMap["type"].(string); ok && typ != "" {
|
||||
existing["type"] = typ
|
||||
}
|
||||
if fn, ok := tcMap["function"].(map[string]interface{}); ok {
|
||||
ef, ok := existing["function"].(map[string]interface{})
|
||||
if !ok {
|
||||
ef = make(map[string]interface{})
|
||||
existing["function"] = ef
|
||||
}
|
||||
if name, ok := fn["name"].(string); ok && name != "" {
|
||||
if en, ok := ef["name"].(string); ok {
|
||||
ef["name"] = en + name
|
||||
} else {
|
||||
ef["name"] = name
|
||||
}
|
||||
}
|
||||
if args, ok := fn["arguments"].(string); ok && args != "" {
|
||||
if ea, ok := ef["arguments"].(string); ok {
|
||||
ef["arguments"] = ea + args
|
||||
} else {
|
||||
ef["arguments"] = args
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err = sender(nil, &reasoningContent); err != nil {
|
||||
@@ -395,6 +486,19 @@ func (b *BaiduModel) ChatStreamlyWithSender(modelName string, messages []Message
|
||||
return fmt.Errorf("baidu: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
if len(accumulatedToolCalls) > 0 && modelConfig != nil {
|
||||
indices := make([]int, 0, len(accumulatedToolCalls))
|
||||
for idx := range accumulatedToolCalls {
|
||||
indices = append(indices, idx)
|
||||
}
|
||||
sort.Ints(indices)
|
||||
tcs := make([]map[string]interface{}, 0, len(accumulatedToolCalls))
|
||||
for _, idx := range indices {
|
||||
tcs = append(tcs, accumulatedToolCalls[idx])
|
||||
}
|
||||
modelConfig.ToolCallsResult = &tcs
|
||||
}
|
||||
|
||||
// Send [DONE] marker for OpenAI compatibility
|
||||
endOfStream := "[DONE]"
|
||||
if err = sender(&endOfStream, nil); err != nil {
|
||||
|
||||
@@ -81,6 +81,7 @@ func (b *BaseModel) GetBaseURL(apiConfig *APIConfig) (string, error) {
|
||||
}
|
||||
return "", fmt.Errorf("no base URL configured for region %q", region)
|
||||
}
|
||||
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||
|
||||
return baseURL, nil
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -85,14 +86,19 @@ func (m *MinimaxModel) ChatWithMessages(modelName string, messages []Message, ap
|
||||
"role": msg.Role,
|
||||
"content": msg.Content,
|
||||
}
|
||||
if msg.ToolCallID != "" {
|
||||
apiMessages[i]["tool_call_id"] = msg.ToolCallID
|
||||
}
|
||||
if len(msg.ToolCalls) > 0 {
|
||||
apiMessages[i]["tool_calls"] = msg.ToolCalls
|
||||
}
|
||||
}
|
||||
|
||||
// Build request body
|
||||
reqBody := map[string]interface{}{
|
||||
"model": modelName,
|
||||
"messages": apiMessages,
|
||||
"stream": false,
|
||||
"temperature": 1,
|
||||
"model": modelName,
|
||||
"messages": apiMessages,
|
||||
"stream": false,
|
||||
}
|
||||
|
||||
if chatModelConfig != nil {
|
||||
@@ -112,7 +118,7 @@ func (m *MinimaxModel) ChatWithMessages(modelName string, messages []Message, ap
|
||||
reqBody["do_sample"] = *chatModelConfig.DoSample
|
||||
}
|
||||
|
||||
if chatModelConfig != nil && chatModelConfig.Thinking != nil {
|
||||
if chatModelConfig.Thinking != nil {
|
||||
if *chatModelConfig.Thinking {
|
||||
reqBody["thinking"] = map[string]interface{}{
|
||||
"type": "adaptive",
|
||||
@@ -124,6 +130,10 @@ func (m *MinimaxModel) ChatWithMessages(modelName string, messages []Message, ap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if chatModelConfig.Tools != nil {
|
||||
reqBody["tools"] = chatModelConfig.Tools
|
||||
}
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(reqBody)
|
||||
@@ -178,25 +188,36 @@ func (m *MinimaxModel) ChatWithMessages(modelName string, messages []Message, ap
|
||||
return nil, fmt.Errorf("no message in response")
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no message in response")
|
||||
}
|
||||
content, _ := messageMap["content"].(string)
|
||||
|
||||
var reasonContent string
|
||||
if chatModelConfig != nil && chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
|
||||
reasonContent, ok = messageMap["reasoning_content"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
if rc, ok := messageMap["reasoning_content"].(string); ok {
|
||||
reasonContent = rc
|
||||
if reasonContent != "" && reasonContent[0] == '\n' {
|
||||
reasonContent = reasonContent[1:]
|
||||
}
|
||||
}
|
||||
if reasonContent != "" && reasonContent[0] == '\n' {
|
||||
reasonContent = reasonContent[1:]
|
||||
}
|
||||
|
||||
var toolCalls []map[string]any
|
||||
if tcs, ok := messageMap["tool_calls"].([]any); ok {
|
||||
for _, tc := range tcs {
|
||||
if tcMap, ok := tc.(map[string]any); ok {
|
||||
toolCalls = append(toolCalls, tcMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
if pt, ct, tt := extractUsageFromMap(result); tt > 0 {
|
||||
chatResponse.Usage = &ChatUsage{
|
||||
PromptTokens: pt, CompletionTokens: ct, TotalTokens: tt,
|
||||
}
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -232,14 +253,19 @@ func (m *MinimaxModel) ChatStreamlyWithSender(modelName string, messages []Messa
|
||||
"role": msg.Role,
|
||||
"content": msg.Content,
|
||||
}
|
||||
if msg.ToolCallID != "" {
|
||||
apiMessages[i]["tool_call_id"] = msg.ToolCallID
|
||||
}
|
||||
if len(msg.ToolCalls) > 0 {
|
||||
apiMessages[i]["tool_calls"] = msg.ToolCalls
|
||||
}
|
||||
}
|
||||
|
||||
// Build request body with streaming enabled
|
||||
reqBody := map[string]interface{}{
|
||||
"model": modelName,
|
||||
"messages": apiMessages,
|
||||
"stream": true,
|
||||
"temperature": 1,
|
||||
"model": modelName,
|
||||
"messages": apiMessages,
|
||||
"stream": true,
|
||||
}
|
||||
|
||||
if modelConfig != nil {
|
||||
@@ -275,6 +301,10 @@ func (m *MinimaxModel) ChatStreamlyWithSender(modelName string, messages []Messa
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if modelConfig.Tools != nil {
|
||||
reqBody["tools"] = modelConfig.Tools
|
||||
}
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(reqBody)
|
||||
@@ -307,6 +337,7 @@ func (m *MinimaxModel) ChatStreamlyWithSender(modelName string, messages []Messa
|
||||
|
||||
// SSE parsing: read line by line
|
||||
sawTerminal := false
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
choices, ok := event["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
@@ -323,6 +354,36 @@ func (m *MinimaxModel) ChatStreamlyWithSender(modelName string, messages []Messa
|
||||
return nil
|
||||
}
|
||||
|
||||
if tcs, ok := delta["tool_calls"].([]interface{}); ok {
|
||||
for _, tc := range tcs {
|
||||
tcMap, ok := tc.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
idxF, ok := tcMap["index"].(float64)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
idx := int(idxF)
|
||||
existing, hasExisting := accumulatedToolCalls[idx]
|
||||
if hasExisting {
|
||||
if fn, ok := tcMap["function"].(map[string]interface{}); ok {
|
||||
if args, ok := fn["arguments"].(string); ok {
|
||||
if ef, ok := existing["function"].(map[string]interface{}); ok {
|
||||
if ea, ok := ef["arguments"].(string); ok {
|
||||
ef["arguments"] = ea + args
|
||||
} else {
|
||||
ef["arguments"] = args
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
accumulatedToolCalls[idx] = cloneMap(tcMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content, ok := delta["content"].(string)
|
||||
if ok && content != "" {
|
||||
if err := sender(&content, nil); err != nil {
|
||||
@@ -351,6 +412,19 @@ func (m *MinimaxModel) ChatStreamlyWithSender(modelName string, messages []Messa
|
||||
return fmt.Errorf("minimax: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
if len(accumulatedToolCalls) > 0 && modelConfig != nil {
|
||||
indices := make([]int, 0, len(accumulatedToolCalls))
|
||||
for idx := range accumulatedToolCalls {
|
||||
indices = append(indices, idx)
|
||||
}
|
||||
sort.Ints(indices)
|
||||
tcs := make([]map[string]interface{}, 0, len(accumulatedToolCalls))
|
||||
for _, idx := range indices {
|
||||
tcs = append(tcs, accumulatedToolCalls[idx])
|
||||
}
|
||||
modelConfig.ToolCallsResult = &tcs
|
||||
}
|
||||
|
||||
// Send [DONE] marker for OpenAI compatibility
|
||||
endOfStream := "[DONE]"
|
||||
return sender(&endOfStream, nil)
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -77,20 +78,25 @@ func (m *MoonshotModel) ChatWithMessages(modelName string, messages []Message, a
|
||||
url := fmt.Sprintf("%s/%s", resolvedBaseURL, m.baseModel.URLSuffix.Chat)
|
||||
|
||||
// Convert messages to the format expected by API
|
||||
apiMessages := make([]map[string]interface{}, len(messages))
|
||||
apiMessages := make([]map[string]any, len(messages))
|
||||
for i, msg := range messages {
|
||||
apiMessages[i] = map[string]interface{}{
|
||||
apiMessages[i] = map[string]any{
|
||||
"role": msg.Role,
|
||||
"content": msg.Content,
|
||||
}
|
||||
if msg.ToolCallID != "" {
|
||||
apiMessages[i]["tool_call_id"] = msg.ToolCallID
|
||||
}
|
||||
if len(msg.ToolCalls) > 0 {
|
||||
apiMessages[i]["tool_calls"] = msg.ToolCalls
|
||||
}
|
||||
}
|
||||
|
||||
// Build request body
|
||||
reqBody := map[string]interface{}{
|
||||
"model": modelName,
|
||||
"messages": apiMessages,
|
||||
"stream": false,
|
||||
"temperature": 0.6,
|
||||
"model": modelName,
|
||||
"messages": apiMessages,
|
||||
"stream": false,
|
||||
}
|
||||
|
||||
if chatModelConfig != nil {
|
||||
@@ -121,6 +127,15 @@ func (m *MoonshotModel) ChatWithMessages(modelName string, messages []Message, a
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if chatModelConfig.Tools != nil {
|
||||
reqBody["tools"] = chatModelConfig.Tools
|
||||
toolChoice := "auto"
|
||||
if chatModelConfig.ToolChoice != nil {
|
||||
toolChoice = *chatModelConfig.ToolChoice
|
||||
}
|
||||
reqBody["tool_choice"] = toolChoice
|
||||
}
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(reqBody)
|
||||
@@ -175,9 +190,9 @@ func (m *MoonshotModel) ChatWithMessages(modelName string, messages []Message, a
|
||||
return nil, fmt.Errorf("invalid message format")
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
var content string
|
||||
if c, ok := messageMap["content"].(string); ok {
|
||||
content = c
|
||||
}
|
||||
|
||||
var reasonContent string
|
||||
@@ -189,9 +204,24 @@ func (m *MoonshotModel) ChatWithMessages(modelName string, messages []Message, a
|
||||
}
|
||||
}
|
||||
|
||||
var toolCalls []map[string]any
|
||||
if tcs, ok := messageMap["tool_calls"].([]interface{}); ok {
|
||||
for _, tc := range tcs {
|
||||
if tcMap, ok := tc.(map[string]any); ok {
|
||||
toolCalls = append(toolCalls, tcMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
if pt, ct, tt := extractUsageFromMap(result); tt > 0 {
|
||||
chatResponse.Usage = &ChatUsage{
|
||||
PromptTokens: pt, CompletionTokens: ct, TotalTokens: tt,
|
||||
}
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -227,6 +257,12 @@ func (m *MoonshotModel) ChatStreamlyWithSender(modelName string, messages []Mess
|
||||
"role": msg.Role,
|
||||
"content": msg.Content,
|
||||
}
|
||||
if msg.ToolCallID != "" {
|
||||
apiMessages[i]["tool_call_id"] = msg.ToolCallID
|
||||
}
|
||||
if len(msg.ToolCalls) > 0 {
|
||||
apiMessages[i]["tool_calls"] = msg.ToolCalls
|
||||
}
|
||||
}
|
||||
|
||||
// Build request body with streaming enabled
|
||||
@@ -268,6 +304,15 @@ func (m *MoonshotModel) ChatStreamlyWithSender(modelName string, messages []Mess
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if chatModelConfig.Tools != nil {
|
||||
reqBody["tools"] = chatModelConfig.Tools
|
||||
toolChoice := "auto"
|
||||
if chatModelConfig.ToolChoice != nil {
|
||||
toolChoice = *chatModelConfig.ToolChoice
|
||||
}
|
||||
reqBody["tool_choice"] = toolChoice
|
||||
}
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(reqBody)
|
||||
@@ -300,6 +345,10 @@ func (m *MoonshotModel) ChatStreamlyWithSender(modelName string, messages []Mess
|
||||
|
||||
// SSE parsing: read line by line
|
||||
sawTerminal := false
|
||||
if chatModelConfig != nil {
|
||||
chatModelConfig.ToolCallsResult = nil
|
||||
}
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
choices, ok := event["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
@@ -316,6 +365,36 @@ func (m *MoonshotModel) ChatStreamlyWithSender(modelName string, messages []Mess
|
||||
return nil
|
||||
}
|
||||
|
||||
if tcs, ok := delta["tool_calls"].([]interface{}); ok {
|
||||
for _, tc := range tcs {
|
||||
tcMap, ok := tc.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
idxF, ok := tcMap["index"].(float64)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
idx := int(idxF)
|
||||
existing, hasExisting := accumulatedToolCalls[idx]
|
||||
if hasExisting {
|
||||
if fn, ok := tcMap["function"].(map[string]interface{}); ok {
|
||||
if args, ok := fn["arguments"].(string); ok {
|
||||
if ef, ok := existing["function"].(map[string]interface{}); ok {
|
||||
if ea, ok := ef["arguments"].(string); ok {
|
||||
ef["arguments"] = ea + args
|
||||
} else {
|
||||
ef["arguments"] = args
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
accumulatedToolCalls[idx] = cloneMap(tcMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err := sender(nil, &reasoningContent); err != nil {
|
||||
@@ -344,6 +423,19 @@ func (m *MoonshotModel) ChatStreamlyWithSender(modelName string, messages []Mess
|
||||
return fmt.Errorf("moonshot: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
if len(accumulatedToolCalls) > 0 && chatModelConfig != nil {
|
||||
indices := make([]int, 0, len(accumulatedToolCalls))
|
||||
for idx := range accumulatedToolCalls {
|
||||
indices = append(indices, idx)
|
||||
}
|
||||
sort.Ints(indices)
|
||||
tcs := make([]map[string]interface{}, 0, len(accumulatedToolCalls))
|
||||
for _, idx := range indices {
|
||||
tcs = append(tcs, accumulatedToolCalls[idx])
|
||||
}
|
||||
chatModelConfig.ToolCallsResult = &tcs
|
||||
}
|
||||
|
||||
// Send [DONE] marker for OpenAI compatibility
|
||||
endOfStream := "[DONE]"
|
||||
if err = sender(&endOfStream, nil); err != nil {
|
||||
|
||||
@@ -129,6 +129,57 @@ func TestMoonshotChatForcesNonStreaming(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoonshotChatSupportsTools(t *testing.T) {
|
||||
srv := newMoonshotServer(t, func(t *testing.T, _ *http.Request, body map[string]interface{}, w http.ResponseWriter) {
|
||||
tools, ok := body["tools"].([]interface{})
|
||||
if !ok || len(tools) != 1 {
|
||||
t.Errorf("tools=%#v, want one tool", body["tools"])
|
||||
}
|
||||
if body["tool_choice"] != "auto" {
|
||||
t.Errorf("tool_choice=%v, want auto", body["tool_choice"])
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"choices": []map[string]interface{}{{
|
||||
"message": map[string]interface{}{
|
||||
"content": nil,
|
||||
"tool_calls": []map[string]interface{}{{
|
||||
"id": "call_weather",
|
||||
"type": "function",
|
||||
"function": map[string]string{
|
||||
"name": "get_weather",
|
||||
"arguments": `{"city":"北京"}`,
|
||||
},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
})
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
apiKey := "test-key"
|
||||
tools := []map[string]interface{}{{
|
||||
"type": "function",
|
||||
"function": map[string]interface{}{
|
||||
"name": "get_weather",
|
||||
},
|
||||
}}
|
||||
resp, err := newMoonshotForTest(srv.URL).ChatWithMessages(
|
||||
"kimi-k2.6",
|
||||
[]Message{{Role: "user", Content: "北京今天天气怎么样?"}},
|
||||
&APIConfig{ApiKey: &apiKey},
|
||||
&ChatConfig{Tools: tools},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("ChatWithMessages: %v", err)
|
||||
}
|
||||
if resp.Answer == nil || *resp.Answer != "" {
|
||||
t.Errorf("Answer=%v, want empty", resp.Answer)
|
||||
}
|
||||
if len(resp.ToolCalls) != 1 || resp.ToolCalls[0]["id"] != "call_weather" {
|
||||
t.Errorf("ToolCalls=%#v, want call_weather", resp.ToolCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoonshotStreamForcesStreaming(t *testing.T) {
|
||||
srv := newMoonshotServer(t, func(t *testing.T, r *http.Request, body map[string]interface{}, w http.ResponseWriter) {
|
||||
if r.Method != http.MethodPost {
|
||||
|
||||
Reference in New Issue
Block a user