mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-25 18:03:29 +08:00
Go: add tools for a lot of providers (#17341)
### Summary As title --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
@@ -166,7 +166,8 @@ func (a *AI302Model) ChatWithMessages(ctx context.Context, modelName string, mes
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -185,6 +186,7 @@ func (a *AI302Model) ChatWithMessages(ctx context.Context, modelName string, mes
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -263,6 +265,7 @@ func (a *AI302Model) ChatStreamlyWithSender(ctx context.Context, modelName strin
|
||||
return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
if _, err = ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
choices, ok := event["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
@@ -279,6 +282,8 @@ func (a *AI302Model) ChatStreamlyWithSender(ctx context.Context, modelName strin
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err = sender(nil, &reasoningContent); err != nil {
|
||||
@@ -298,6 +303,8 @@ func (a *AI302Model) ChatStreamlyWithSender(ctx context.Context, modelName strin
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
|
||||
setSortedToolCallsResult(modelConfig, accumulatedToolCalls)
|
||||
|
||||
// Send [DONE] marker for OpenAI compatibility
|
||||
endOfStream := "[DONE]"
|
||||
return sender(&endOfStream, nil)
|
||||
|
||||
@@ -139,7 +139,8 @@ func (a *AstraflowModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -151,6 +152,7 @@ func (a *AstraflowModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
return &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -205,6 +207,7 @@ func (a *AstraflowModel) ChatStreamlyWithSender(ctx context.Context, modelName s
|
||||
}
|
||||
|
||||
sawTerminal := false
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
if apiErr, ok := event["error"]; ok {
|
||||
return fmt.Errorf("astraflow: upstream stream error: %v", apiErr)
|
||||
@@ -219,6 +222,7 @@ func (a *AstraflowModel) ChatStreamlyWithSender(ctx context.Context, modelName s
|
||||
return nil
|
||||
}
|
||||
if delta, ok := firstChoice["delta"].(map[string]interface{}); ok {
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
if r, ok := delta["reasoning_content"].(string); ok && r != "" {
|
||||
rr := r
|
||||
if err := sender(nil, &rr); err != nil {
|
||||
@@ -240,6 +244,7 @@ func (a *AstraflowModel) ChatStreamlyWithSender(ctx context.Context, modelName s
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
if !done && !sawTerminal {
|
||||
return fmt.Errorf("astraflow: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
@@ -198,31 +198,39 @@ func (a *AvianModel) ChatStreamlyWithSender(ctx context.Context, modelName strin
|
||||
}
|
||||
|
||||
sawTerminal := false
|
||||
done, err := ParseSSEStream[avianChatResponse](resp.Body, func(event avianChatResponse) error {
|
||||
if event.Error != nil {
|
||||
return fmt.Errorf("avian: upstream stream error: %v", event.Error)
|
||||
}
|
||||
if len(event.Choices) == 0 {
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
done, err := ParseSSEStream[map[string]any](resp.Body, func(event map[string]any) error {
|
||||
choices, ok := event["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
return nil
|
||||
}
|
||||
choice := event.Choices[0]
|
||||
if reasoning := choice.Delta.ReasoningContent; reasoning != "" {
|
||||
if err := sender(nil, &reasoning); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if reasoning := choice.Delta.Reasoning; reasoning != "" {
|
||||
if err := sender(nil, &reasoning); err != nil {
|
||||
|
||||
firstChoice, ok := choices[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
delta, ok := firstChoice["delta"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err = sender(nil, &reasoningContent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if choice.Delta.Content != "" {
|
||||
if err := sender(&choice.Delta.Content, nil); err != nil {
|
||||
|
||||
content, ok := delta["content"].(string)
|
||||
if ok && content != "" {
|
||||
if err = sender(&content, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if choice.FinishReason != "" || event.FinishReason != "" {
|
||||
sawTerminal = true
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -232,6 +240,8 @@ func (a *AvianModel) ChatStreamlyWithSender(ctx context.Context, modelName strin
|
||||
return fmt.Errorf("avian: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
|
||||
endOfStream := "[DONE]"
|
||||
return sender(&endOfStream, nil)
|
||||
}
|
||||
|
||||
@@ -117,7 +117,8 @@ func (b *BaichuanModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("no message in response")
|
||||
}
|
||||
|
||||
@@ -126,6 +127,7 @@ func (b *BaichuanModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &emptyReason,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -177,6 +179,7 @@ func (b *BaichuanModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
|
||||
// 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 {
|
||||
common.Info(fmt.Sprintf("%v", event))
|
||||
|
||||
@@ -195,6 +198,8 @@ func (b *BaichuanModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
content, ok := delta["content"].(string)
|
||||
if ok && content != "" {
|
||||
if err := sender(&content, nil); err != nil {
|
||||
@@ -215,6 +220,8 @@ func (b *BaichuanModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
return fmt.Errorf("baichuan: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
setSortedToolCallsResult(modelConfig, accumulatedToolCalls)
|
||||
|
||||
// Send [DONE] marker for OpenAI compatibility
|
||||
endOfStream := "[DONE]"
|
||||
return sender(&endOfStream, nil)
|
||||
|
||||
@@ -224,6 +224,7 @@ func (c *CoHereModel) ChatStreamlyWithSender(ctx context.Context, modelName stri
|
||||
}
|
||||
|
||||
sawTerminal := false
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
eventType, ok := event["type"].(string)
|
||||
if !ok {
|
||||
@@ -240,6 +241,9 @@ func (c *CoHereModel) ChatStreamlyWithSender(ctx context.Context, modelName stri
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
msg, ok := delta["message"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
@@ -270,6 +274,8 @@ func (c *CoHereModel) ChatStreamlyWithSender(ctx context.Context, modelName stri
|
||||
return fmt.Errorf("Cohere: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
setSortedToolCallsResult(modelConfig, accumulatedToolCalls)
|
||||
|
||||
endOfStream := "[DONE]"
|
||||
return sender(&endOfStream, nil)
|
||||
}
|
||||
|
||||
@@ -287,29 +287,43 @@ func (c *CometAPIModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
}
|
||||
|
||||
sawTerminal := false
|
||||
done, err := ParseSSEStream[cometapiChatResponsePayload](resp.Body, func(event cometapiChatResponsePayload) error {
|
||||
if len(event.Choices) == 0 {
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
choice := event.Choices[0]
|
||||
reasoningContent := choice.Delta.ReasoningContent
|
||||
content := choice.Delta.Content
|
||||
|
||||
if reasoningContent != "" {
|
||||
if err := sender(nil, &reasoningContent); err != nil {
|
||||
firstChoice, ok := choices[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
delta, ok := firstChoice["delta"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err = sender(nil, &reasoningContent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if content != "" {
|
||||
if err := sender(&content, nil); err != nil {
|
||||
content, ok := delta["content"].(string)
|
||||
if ok && content != "" {
|
||||
if err = sender(&content, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if choice.FinishReason != "" {
|
||||
if finishReason, ok := firstChoice["finish_reason"].(string); ok && finishReason != "" {
|
||||
sawTerminal = true
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -319,6 +333,8 @@ func (c *CometAPIModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
return fmt.Errorf("cometapi: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
|
||||
endOfStream := "[DONE]"
|
||||
if err := sender(&endOfStream, nil); err != nil {
|
||||
return err
|
||||
|
||||
@@ -137,7 +137,8 @@ func (d *DeepInfraModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -216,6 +217,7 @@ func (d *DeepInfraModel) ChatStreamlyWithSender(ctx context.Context, modelName s
|
||||
}
|
||||
|
||||
sawTerminal := false
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
common.Info(fmt.Sprintf("%v", event))
|
||||
|
||||
@@ -234,6 +236,8 @@ func (d *DeepInfraModel) ChatStreamlyWithSender(ctx context.Context, modelName s
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err := sender(nil, &reasoningContent); err != nil {
|
||||
@@ -257,6 +261,7 @@ func (d *DeepInfraModel) ChatStreamlyWithSender(ctx context.Context, modelName s
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(modelConfig, accumulatedToolCalls)
|
||||
if !done && !sawTerminal {
|
||||
return fmt.Errorf("deepinfra: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
@@ -221,35 +221,45 @@ func (f *FuturMixModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
return fmt.Errorf("futurmix chat stream API error: %s, body: %s", resp.Status, string(body))
|
||||
}
|
||||
|
||||
sawTerminal := false
|
||||
done, err := ParseSSEStream[futurmixChatResponse](resp.Body, func(event futurmixChatResponse) error {
|
||||
if len(event.Choices) == 0 {
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
if _, err = ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
choices, ok := event["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
return nil
|
||||
}
|
||||
choice := event.Choices[0]
|
||||
if choice.Delta.ReasoningContent != "" {
|
||||
r := choice.Delta.ReasoningContent
|
||||
if err := sender(nil, &r); err != nil {
|
||||
|
||||
firstChoice, ok := choices[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
delta, ok := firstChoice["delta"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err = sender(nil, &reasoningContent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if choice.Delta.Content != "" {
|
||||
c := choice.Delta.Content
|
||||
if err := sender(&c, nil); err != nil {
|
||||
|
||||
content, ok := delta["content"].(string)
|
||||
if ok && content != "" {
|
||||
if err = sender(&content, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if choice.FinishReason != "" {
|
||||
sawTerminal = true
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
if !done && !sawTerminal {
|
||||
return fmt.Errorf("futurmix: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
|
||||
endOfStream := "[DONE]"
|
||||
return sender(&endOfStream, nil)
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"ragflow/internal/common"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -264,6 +263,8 @@ func (g *GiteeModel) ChatStreamlyWithSender(ctx context.Context, modelName strin
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
content, ok := delta["content"].(string)
|
||||
if ok && content != "" {
|
||||
common.Info(content)
|
||||
@@ -296,56 +297,6 @@ func (g *GiteeModel) ChatStreamlyWithSender(ctx context.Context, modelName strin
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
finishReason, ok := firstChoice["finish_reason"].(string)
|
||||
if ok && finishReason != "" {
|
||||
sawTerminal = true
|
||||
@@ -365,18 +316,7 @@ func (g *GiteeModel) ChatStreamlyWithSender(ctx context.Context, modelName strin
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
|
||||
// Send [DONE] marker for OpenAI compatibility
|
||||
endOfStream := "[DONE]"
|
||||
|
||||
@@ -18,6 +18,7 @@ package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"ragflow/internal/common"
|
||||
"strings"
|
||||
@@ -119,6 +120,240 @@ func (g *GoogleModel) baseURL(apiConfig *APIConfig) string {
|
||||
return strings.TrimSpace(baseURL)
|
||||
}
|
||||
|
||||
func googleChatContents(messages []Message) []*genai.Content {
|
||||
var contents []*genai.Content
|
||||
toolCallNames := make(map[string]string)
|
||||
|
||||
for _, msg := range messages {
|
||||
switch msg.Role {
|
||||
case "tool":
|
||||
name := toolCallNames[msg.ToolCallID]
|
||||
if name == "" {
|
||||
name = msg.ToolCallID
|
||||
}
|
||||
contents = append(contents, &genai.Content{
|
||||
Role: genai.RoleUser,
|
||||
Parts: []*genai.Part{{
|
||||
FunctionResponse: &genai.FunctionResponse{
|
||||
ID: msg.ToolCallID,
|
||||
Name: name,
|
||||
Response: googleFunctionResponse(msg.Content),
|
||||
},
|
||||
}},
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
var role genai.Role
|
||||
switch msg.Role {
|
||||
case "model", "assistant":
|
||||
role = genai.RoleModel
|
||||
default:
|
||||
role = genai.RoleUser
|
||||
}
|
||||
|
||||
parts := googleMessageParts(msg.Content)
|
||||
for _, toolCall := range msg.ToolCalls {
|
||||
id, _ := toolCall["id"].(string)
|
||||
fn, _ := toolCall["function"].(map[string]interface{})
|
||||
name, _ := fn["name"].(string)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
args := map[string]any{}
|
||||
if arguments, ok := fn["arguments"].(string); ok && strings.TrimSpace(arguments) != "" {
|
||||
_ = json.Unmarshal([]byte(arguments), &args)
|
||||
}
|
||||
if id != "" {
|
||||
toolCallNames[id] = name
|
||||
}
|
||||
parts = append(parts, &genai.Part{FunctionCall: &genai.FunctionCall{
|
||||
ID: id,
|
||||
Name: name,
|
||||
Args: args,
|
||||
}})
|
||||
}
|
||||
if len(parts) > 0 {
|
||||
contents = append(contents, genai.NewContentFromParts(parts, role))
|
||||
}
|
||||
}
|
||||
|
||||
return contents
|
||||
}
|
||||
|
||||
func googleMessageParts(content interface{}) []*genai.Part {
|
||||
switch c := content.(type) {
|
||||
case string:
|
||||
if c == "" {
|
||||
return nil
|
||||
}
|
||||
return []*genai.Part{genai.NewPartFromText(c)}
|
||||
case []interface{}:
|
||||
var parts []*genai.Part
|
||||
for _, item := range c {
|
||||
itemMap, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
contentType, _ := itemMap["type"].(string)
|
||||
switch contentType {
|
||||
case "text":
|
||||
if text, ok := itemMap["text"].(string); ok && text != "" {
|
||||
parts = append(parts, genai.NewPartFromText(text))
|
||||
}
|
||||
case "image_url":
|
||||
if imgMap, ok := itemMap["image_url"].(map[string]interface{}); ok {
|
||||
if url, ok := imgMap["url"].(string); ok && url != "" {
|
||||
parts = append(parts, genai.NewPartFromURI(url, "image/jpeg"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return parts
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func googleFunctionResponse(content interface{}) map[string]any {
|
||||
switch c := content.(type) {
|
||||
case map[string]any:
|
||||
return c
|
||||
case string:
|
||||
var response map[string]any
|
||||
if err := json.Unmarshal([]byte(c), &response); err == nil && response != nil {
|
||||
return response
|
||||
}
|
||||
return map[string]any{"output": c}
|
||||
default:
|
||||
return map[string]any{"output": c}
|
||||
}
|
||||
}
|
||||
|
||||
func googleGenerateContentConfig(chatModelConfig *ChatConfig) *genai.GenerateContentConfig {
|
||||
if chatModelConfig == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := &genai.GenerateContentConfig{}
|
||||
if chatModelConfig.Temperature != nil {
|
||||
value := float32(*chatModelConfig.Temperature)
|
||||
cfg.Temperature = &value
|
||||
}
|
||||
if chatModelConfig.TopP != nil {
|
||||
value := float32(*chatModelConfig.TopP)
|
||||
cfg.TopP = &value
|
||||
}
|
||||
if chatModelConfig.MaxTokens != nil {
|
||||
cfg.MaxOutputTokens = int32(*chatModelConfig.MaxTokens)
|
||||
}
|
||||
if chatModelConfig.Stop != nil {
|
||||
cfg.StopSequences = *chatModelConfig.Stop
|
||||
}
|
||||
if tools := googleTools(chatModelConfig.Tools); len(tools) > 0 {
|
||||
cfg.Tools = tools
|
||||
cfg.ToolConfig = &genai.ToolConfig{
|
||||
FunctionCallingConfig: &genai.FunctionCallingConfig{Mode: googleFunctionCallingMode(chatModelConfig.ToolChoice)},
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Temperature == nil && cfg.TopP == nil && cfg.MaxOutputTokens == 0 && len(cfg.StopSequences) == 0 && len(cfg.Tools) == 0 {
|
||||
return nil
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func googleFunctionCallingMode(toolChoice *string) genai.FunctionCallingConfigMode {
|
||||
if toolChoice == nil {
|
||||
return genai.FunctionCallingConfigModeAuto
|
||||
}
|
||||
switch strings.ToLower(strings.TrimSpace(*toolChoice)) {
|
||||
case "none":
|
||||
return genai.FunctionCallingConfigModeNone
|
||||
case "required", "any":
|
||||
return genai.FunctionCallingConfigModeAny
|
||||
default:
|
||||
return genai.FunctionCallingConfigModeAuto
|
||||
}
|
||||
}
|
||||
|
||||
func googleTools(rawTools interface{}) []*genai.Tool {
|
||||
var declarations []*genai.FunctionDeclaration
|
||||
for _, rawTool := range normalizeToolList(rawTools) {
|
||||
toolMap, ok := rawTool.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fn, ok := toolMap["function"].(map[string]interface{})
|
||||
if !ok {
|
||||
fn = toolMap
|
||||
}
|
||||
name, _ := fn["name"].(string)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
description, _ := fn["description"].(string)
|
||||
declaration := &genai.FunctionDeclaration{
|
||||
Name: name,
|
||||
Description: description,
|
||||
}
|
||||
if parameters, ok := fn["parameters"]; ok {
|
||||
declaration.ParametersJsonSchema = parameters
|
||||
}
|
||||
declarations = append(declarations, declaration)
|
||||
}
|
||||
if len(declarations) == 0 {
|
||||
return nil
|
||||
}
|
||||
return []*genai.Tool{{FunctionDeclarations: declarations}}
|
||||
}
|
||||
|
||||
func normalizeToolList(rawTools interface{}) []interface{} {
|
||||
switch tools := rawTools.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case []interface{}:
|
||||
return tools
|
||||
case []map[string]interface{}:
|
||||
result := make([]interface{}, 0, len(tools))
|
||||
for _, tool := range tools {
|
||||
result = append(result, tool)
|
||||
}
|
||||
return result
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func googleToolCalls(functionCalls []*genai.FunctionCall) []map[string]interface{} {
|
||||
if len(functionCalls) == 0 {
|
||||
return nil
|
||||
}
|
||||
toolCalls := make([]map[string]interface{}, 0, len(functionCalls))
|
||||
for idx, functionCall := range functionCalls {
|
||||
if functionCall == nil || functionCall.Name == "" {
|
||||
continue
|
||||
}
|
||||
id := functionCall.ID
|
||||
if id == "" {
|
||||
id = fmt.Sprintf("gemini-call-%d", idx)
|
||||
}
|
||||
arguments, err := json.Marshal(functionCall.Args)
|
||||
if err != nil {
|
||||
arguments = []byte("{}")
|
||||
}
|
||||
toolCalls = append(toolCalls, map[string]interface{}{
|
||||
"id": id,
|
||||
"type": "function",
|
||||
"function": map[string]interface{}{
|
||||
"name": functionCall.Name,
|
||||
"arguments": string(arguments),
|
||||
},
|
||||
})
|
||||
}
|
||||
return toolCalls
|
||||
}
|
||||
|
||||
func (g *GoogleModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) {
|
||||
if err := g.baseModel.APIConfigCheck(apiConfig); err != nil {
|
||||
return nil, err
|
||||
@@ -136,51 +371,10 @@ func (g *GoogleModel) ChatWithMessages(ctx context.Context, modelName string, me
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert messages to Google SDK format
|
||||
var contents []*genai.Content
|
||||
for _, msg := range messages {
|
||||
var role genai.Role
|
||||
switch msg.Role {
|
||||
case "user":
|
||||
role = genai.RoleUser
|
||||
case "model", "assistant":
|
||||
role = genai.RoleModel
|
||||
default:
|
||||
role = genai.RoleUser
|
||||
}
|
||||
|
||||
// Handle content based on type
|
||||
switch c := msg.Content.(type) {
|
||||
case string:
|
||||
contents = append(contents, genai.NewContentFromText(c, role))
|
||||
case []interface{}:
|
||||
// Multimodal content - group parts within a single content
|
||||
var parts []*genai.Part
|
||||
for _, item := range c {
|
||||
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||
contentType, _ := itemMap["type"].(string)
|
||||
switch contentType {
|
||||
case "text":
|
||||
if text, ok := itemMap["text"].(string); ok {
|
||||
parts = append(parts, genai.NewPartFromText(text))
|
||||
}
|
||||
case "image_url":
|
||||
if imgMap, ok := itemMap["image_url"].(map[string]interface{}); ok {
|
||||
if url, ok := imgMap["url"].(string); ok {
|
||||
parts = append(parts, genai.NewPartFromURI(url, "image/jpeg"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(parts) > 0 {
|
||||
contents = append(contents, genai.NewContentFromParts(parts, role))
|
||||
}
|
||||
}
|
||||
}
|
||||
contents := googleChatContents(messages)
|
||||
|
||||
// Generate content (non-streaming)
|
||||
response, err := client.Models.GenerateContent(ctx, modelName, contents, nil)
|
||||
response, err := client.Models.GenerateContent(ctx, modelName, contents, googleGenerateContentConfig(chatModelConfig))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -188,7 +382,7 @@ func (g *GoogleModel) ChatWithMessages(ctx context.Context, modelName string, me
|
||||
// Extract text from response
|
||||
answer := response.Text()
|
||||
|
||||
return &ChatResponse{Answer: &answer}, nil
|
||||
return &ChatResponse{Answer: &answer, ToolCalls: googleToolCalls(response.FunctionCalls())}, nil
|
||||
}
|
||||
|
||||
// ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel)
|
||||
@@ -212,59 +406,21 @@ func (g *GoogleModel) ChatStreamlyWithSender(ctx context.Context, modelName stri
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert messages to Google SDK format
|
||||
var contents []*genai.Content
|
||||
for _, msg := range messages {
|
||||
var role genai.Role
|
||||
switch msg.Role {
|
||||
case "user":
|
||||
role = genai.RoleUser
|
||||
case "model", "assistant":
|
||||
role = genai.RoleModel
|
||||
default:
|
||||
role = genai.RoleUser
|
||||
}
|
||||
|
||||
// Handle content based on type
|
||||
switch c := msg.Content.(type) {
|
||||
case string:
|
||||
contents = append(contents, genai.NewContentFromText(c, role))
|
||||
case []interface{}:
|
||||
// Multimodal content - group parts within a single content
|
||||
var parts []*genai.Part
|
||||
for _, item := range c {
|
||||
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||
contentType, _ := itemMap["type"].(string)
|
||||
switch contentType {
|
||||
case "text":
|
||||
if text, ok := itemMap["text"].(string); ok {
|
||||
parts = append(parts, genai.NewPartFromText(text))
|
||||
}
|
||||
case "image_url":
|
||||
if imgMap, ok := itemMap["image_url"].(map[string]interface{}); ok {
|
||||
if url, ok := imgMap["url"].(string); ok {
|
||||
parts = append(parts, genai.NewPartFromURI(url, "image/jpeg"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(parts) > 0 {
|
||||
contents = append(contents, genai.NewContentFromParts(parts, role))
|
||||
}
|
||||
}
|
||||
}
|
||||
contents := googleChatContents(messages)
|
||||
var toolCalls []map[string]interface{}
|
||||
|
||||
for response, err := range client.Models.GenerateContentStream(
|
||||
ctx,
|
||||
modelName,
|
||||
contents,
|
||||
nil,
|
||||
googleGenerateContentConfig(chatModelConfig),
|
||||
) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
toolCalls = append(toolCalls, googleToolCalls(response.FunctionCalls())...)
|
||||
|
||||
content := response.Text()
|
||||
|
||||
var responseContent string
|
||||
@@ -291,6 +447,10 @@ func (g *GoogleModel) ChatStreamlyWithSender(ctx context.Context, modelName stri
|
||||
}
|
||||
}
|
||||
|
||||
if chatModelConfig != nil && len(toolCalls) > 0 {
|
||||
chatModelConfig.ToolCallsResult = &toolCalls
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -391,6 +392,103 @@ func TestCollectGoogleModelNamesReturnsPageError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleGenerateContentConfigConvertsTools(t *testing.T) {
|
||||
toolChoice := "required"
|
||||
cfg := googleGenerateContentConfig(&ChatConfig{
|
||||
Tools: []map[string]interface{}{{
|
||||
"type": "function",
|
||||
"function": map[string]interface{}{
|
||||
"name": "search_my_dataset",
|
||||
"description": "Search dataset.",
|
||||
"parameters": map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"query": map[string]interface{}{"type": "string"},
|
||||
},
|
||||
"required": []string{"query"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
ToolChoice: &toolChoice,
|
||||
})
|
||||
if cfg == nil || len(cfg.Tools) != 1 || len(cfg.Tools[0].FunctionDeclarations) != 1 {
|
||||
t.Fatalf("tools = %#v, want one function declaration", cfg)
|
||||
}
|
||||
declaration := cfg.Tools[0].FunctionDeclarations[0]
|
||||
if declaration.Name != "search_my_dataset" || declaration.Description != "Search dataset." {
|
||||
t.Fatalf("declaration = %#v", declaration)
|
||||
}
|
||||
if declaration.ParametersJsonSchema == nil {
|
||||
t.Fatal("ParametersJsonSchema is nil")
|
||||
}
|
||||
if cfg.ToolConfig == nil || cfg.ToolConfig.FunctionCallingConfig == nil {
|
||||
t.Fatalf("ToolConfig = %#v", cfg.ToolConfig)
|
||||
}
|
||||
if cfg.ToolConfig.FunctionCallingConfig.Mode != genai.FunctionCallingConfigModeAny {
|
||||
t.Fatalf("mode = %s, want ANY", cfg.ToolConfig.FunctionCallingConfig.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleChatContentsConvertsToolHistory(t *testing.T) {
|
||||
contents := googleChatContents([]Message{
|
||||
{
|
||||
Role: "assistant",
|
||||
Content: nil,
|
||||
ToolCalls: []map[string]interface{}{{
|
||||
"id": "call-1",
|
||||
"type": "function",
|
||||
"function": map[string]interface{}{
|
||||
"name": "search_my_dataset",
|
||||
"arguments": `{"query":"marigold"}`,
|
||||
},
|
||||
}},
|
||||
},
|
||||
{Role: "tool", ToolCallID: "call-1", Content: "flower result"},
|
||||
})
|
||||
if len(contents) != 2 {
|
||||
t.Fatalf("contents len = %d, want 2", len(contents))
|
||||
}
|
||||
functionCall := contents[0].Parts[0].FunctionCall
|
||||
if functionCall == nil || functionCall.ID != "call-1" || functionCall.Name != "search_my_dataset" {
|
||||
t.Fatalf("function call = %#v", functionCall)
|
||||
}
|
||||
if functionCall.Args["query"] != "marigold" {
|
||||
t.Fatalf("args = %#v", functionCall.Args)
|
||||
}
|
||||
functionResponse := contents[1].Parts[0].FunctionResponse
|
||||
if functionResponse == nil || functionResponse.ID != "call-1" || functionResponse.Name != "search_my_dataset" {
|
||||
t.Fatalf("function response = %#v", functionResponse)
|
||||
}
|
||||
if functionResponse.Response["output"] != "flower result" {
|
||||
t.Fatalf("response = %#v", functionResponse.Response)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleToolCallsConvertsFunctionCalls(t *testing.T) {
|
||||
toolCalls := googleToolCalls([]*genai.FunctionCall{{
|
||||
ID: "call-1",
|
||||
Name: "search_my_dataset",
|
||||
Args: map[string]any{"query": "marigold"},
|
||||
}})
|
||||
if len(toolCalls) != 1 {
|
||||
t.Fatalf("tool calls len = %d, want 1", len(toolCalls))
|
||||
}
|
||||
if toolCalls[0]["id"] != "call-1" || toolCalls[0]["type"] != "function" {
|
||||
t.Fatalf("tool call = %#v", toolCalls[0])
|
||||
}
|
||||
function, _ := toolCalls[0]["function"].(map[string]interface{})
|
||||
if function["name"] != "search_my_dataset" {
|
||||
t.Fatalf("function = %#v", function)
|
||||
}
|
||||
var args map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(function["arguments"].(string)), &args); err != nil {
|
||||
t.Fatalf("arguments JSON: %v", err)
|
||||
}
|
||||
if args["query"] != "marigold" {
|
||||
t.Fatalf("arguments = %#v", args)
|
||||
}
|
||||
}
|
||||
|
||||
func stringPtr(value string) *string {
|
||||
return &value
|
||||
}
|
||||
|
||||
@@ -120,7 +120,8 @@ func (g *GPUStackModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
return nil, fmt.Errorf("invalid message format")
|
||||
}
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -132,6 +133,7 @@ func (g *GPUStackModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
return &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -189,6 +191,7 @@ func (g *GPUStackModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
}
|
||||
|
||||
sawTerminal := false
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
if apiErr, ok := event["error"]; ok {
|
||||
return fmt.Errorf("gpustack: upstream stream error: %v", apiErr)
|
||||
@@ -207,6 +210,8 @@ func (g *GPUStackModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
if r, ok := delta["reasoning_content"].(string); ok && r != "" {
|
||||
if err := sender(nil, &r); err != nil {
|
||||
return err
|
||||
@@ -229,6 +234,7 @@ func (g *GPUStackModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
if !done && !sawTerminal {
|
||||
return fmt.Errorf("gpustack: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
@@ -216,30 +216,51 @@ func (g *GroqModel) ChatStreamlyWithSender(ctx context.Context, modelName string
|
||||
}
|
||||
|
||||
sawTerminal := false
|
||||
done, err := ParseSSEStream[groqChatResponse](resp.Body, func(event groqChatResponse) error {
|
||||
if event.Error != nil {
|
||||
return fmt.Errorf("groq: upstream stream error: %v", event.Error)
|
||||
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))
|
||||
|
||||
tokenUsage, found, usageErr := decodeOpenAICompatibleStreamUsage(event)
|
||||
if usageErr != nil {
|
||||
return usageErr
|
||||
}
|
||||
if len(event.Choices) == 0 {
|
||||
if found {
|
||||
applyStreamUsage(chatModelConfig, modelUsage, tokenUsage)
|
||||
}
|
||||
|
||||
choices, ok := event["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
choice := event.Choices[0]
|
||||
reasoning := choice.Delta.ReasoningContent
|
||||
if reasoning == "" {
|
||||
reasoning = choice.Delta.Reasoning
|
||||
firstChoice, ok := choices[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if reasoning != "" {
|
||||
if err := sender(nil, &reasoning); err != nil {
|
||||
|
||||
delta, ok := firstChoice["delta"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
content, ok := delta["content"].(string)
|
||||
if ok && content != "" {
|
||||
if err := sender(&content, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if choice.Delta.Content != "" {
|
||||
if err := sender(&choice.Delta.Content, nil); err != nil {
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err := sender(nil, &reasoningContent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if choice.FinishReason != "" || event.FinishReason != "" {
|
||||
|
||||
finishReason, ok := firstChoice["finish_reason"].(string)
|
||||
if ok && finishReason != "" {
|
||||
sawTerminal = true
|
||||
}
|
||||
return nil
|
||||
@@ -248,9 +269,10 @@ func (g *GroqModel) ChatStreamlyWithSender(ctx context.Context, modelName string
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
if !done && !sawTerminal {
|
||||
return fmt.Errorf("groq: stream ended before [DONE] or finish_reason")
|
||||
return fmt.Errorf("deepseek: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
endOfStream := "[DONE]"
|
||||
return sender(&endOfStream, nil)
|
||||
}
|
||||
|
||||
@@ -133,7 +133,8 @@ func (h *HuggingFaceModel) ChatWithMessages(ctx context.Context, modelName strin
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -152,6 +153,7 @@ func (h *HuggingFaceModel) ChatWithMessages(ctx context.Context, modelName strin
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -212,6 +214,7 @@ func (h *HuggingFaceModel) ChatStreamlyWithSender(ctx context.Context, modelName
|
||||
return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
common.Info(fmt.Sprintf("%v", event))
|
||||
|
||||
@@ -230,6 +233,8 @@ func (h *HuggingFaceModel) ChatStreamlyWithSender(ctx context.Context, modelName
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err := sender(nil, &reasoningContent); err != nil {
|
||||
@@ -248,6 +253,7 @@ func (h *HuggingFaceModel) ChatStreamlyWithSender(ctx context.Context, modelName
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(modelConfig, accumulatedToolCalls)
|
||||
|
||||
// Send [DONE] marker for OpenAI compatibility
|
||||
endOfStream := "[DONE]"
|
||||
|
||||
@@ -117,7 +117,8 @@ func (h *HunyuanModel) ChatWithMessages(ctx context.Context, modelName string, m
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -129,6 +130,7 @@ func (h *HunyuanModel) ChatWithMessages(ctx context.Context, modelName string, m
|
||||
return &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -180,6 +182,7 @@ func (h *HunyuanModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
}
|
||||
|
||||
sawTerminal := false
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
if apiErr, ok := event["error"]; ok {
|
||||
return fmt.Errorf("hunyuan: upstream stream error: %v", apiErr)
|
||||
@@ -194,6 +197,7 @@ func (h *HunyuanModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
return nil
|
||||
}
|
||||
if delta, ok := firstChoice["delta"].(map[string]interface{}); ok {
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
if r, ok := delta["reasoning_content"].(string); ok && r != "" {
|
||||
rr := r
|
||||
if err := sender(nil, &rr); err != nil {
|
||||
@@ -215,6 +219,7 @@ func (h *HunyuanModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
if !done && !sawTerminal {
|
||||
return fmt.Errorf("hunyuan: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
@@ -143,7 +143,8 @@ func (j *JieKouAIModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -162,6 +163,7 @@ func (j *JieKouAIModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -225,6 +227,7 @@ func (j *JieKouAIModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
}
|
||||
|
||||
// SSE parsing: read line by line
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
choices, ok := event["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
@@ -241,6 +244,8 @@ func (j *JieKouAIModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err := sender(nil, &reasoningContent); err != nil {
|
||||
@@ -259,6 +264,7 @@ func (j *JieKouAIModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(modelConfig, accumulatedToolCalls)
|
||||
|
||||
// Send [DONE] marker for OpenAI compatibility
|
||||
endOfStream := "[DONE]"
|
||||
|
||||
@@ -146,7 +146,8 @@ func (l *LmStudioModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -164,6 +165,7 @@ func (l *LmStudioModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -233,6 +235,7 @@ func (l *LmStudioModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
}
|
||||
|
||||
// SSE parsing: read line by line
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
common.Info(fmt.Sprintf("%v", event))
|
||||
|
||||
@@ -251,6 +254,8 @@ func (l *LmStudioModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err := sender(nil, &reasoningContent); err != nil {
|
||||
@@ -269,6 +274,7 @@ func (l *LmStudioModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(modelConfig, accumulatedToolCalls)
|
||||
|
||||
// Send [DONE] marker for OpenAI compatibility
|
||||
endOfStream := "[DONE]"
|
||||
|
||||
@@ -154,7 +154,8 @@ func (l *LocalAIModel) ChatWithMessages(ctx context.Context, modelName string, m
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -165,6 +166,7 @@ func (l *LocalAIModel) ChatWithMessages(ctx context.Context, modelName string, m
|
||||
return &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -248,6 +250,7 @@ func (l *LocalAIModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
}()
|
||||
|
||||
sawTerminal := false
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
streamDone, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
lastActiveMu.Lock()
|
||||
lastActive = time.Now()
|
||||
@@ -268,6 +271,8 @@ func (l *LocalAIModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
if reasoning := extractLocalAIReasoning(delta); reasoning != "" {
|
||||
if err := sender(nil, &reasoning); err != nil {
|
||||
return err
|
||||
@@ -293,6 +298,7 @@ func (l *LocalAIModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
}
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
if !streamDone && !sawTerminal {
|
||||
return fmt.Errorf("localai: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
@@ -123,7 +123,8 @@ func (l *LongCatModel) ChatWithMessages(ctx context.Context, modelName string, m
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -140,6 +141,7 @@ func (l *LongCatModel) ChatWithMessages(ctx context.Context, modelName string, m
|
||||
return &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -200,6 +202,7 @@ func (l *LongCatModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
}
|
||||
|
||||
sawTerminal := false
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
if apiErr, ok := event["error"]; ok {
|
||||
return fmt.Errorf("longcat: upstream stream error: %v", apiErr)
|
||||
@@ -220,6 +223,8 @@ func (l *LongCatModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
if r, ok := delta["reasoning_content"].(string); ok && r != "" {
|
||||
if err := sender(nil, &r); err != nil {
|
||||
return err
|
||||
@@ -242,6 +247,7 @@ func (l *LongCatModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
if !done && !sawTerminal {
|
||||
return fmt.Errorf("longcat: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
@@ -127,10 +127,12 @@ func (m *MistralModel) ChatWithMessages(ctx context.Context, modelName string, m
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
|
||||
return &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -226,6 +228,7 @@ func (m *MistralModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -242,6 +245,8 @@ func (m *MistralModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
content, ok := delta["content"].(string)
|
||||
if ok && content != "" {
|
||||
if err := sender(&content, nil); err != nil {
|
||||
@@ -258,6 +263,7 @@ func (m *MistralModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
if !done && !sawTerminal {
|
||||
return fmt.Errorf("mistral: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
@@ -252,6 +252,7 @@ func (m *ModelScopeModel) ChatStreamlyWithSender(ctx context.Context, modelName
|
||||
}()
|
||||
|
||||
sawTerminal := false
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
streamDone, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
lastActiveMu.Lock()
|
||||
lastActive = time.Now()
|
||||
@@ -267,6 +268,7 @@ func (m *ModelScopeModel) ChatStreamlyWithSender(ctx context.Context, modelName
|
||||
}
|
||||
|
||||
if delta, ok := firstChoice["delta"].(map[string]interface{}); ok {
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
if reasoning := modelscopeReasoningFromMap(delta); reasoning != "" {
|
||||
if err := sender(nil, &reasoning); err != nil {
|
||||
return err
|
||||
@@ -294,6 +296,8 @@ func (m *ModelScopeModel) ChatStreamlyWithSender(ctx context.Context, modelName
|
||||
return fmt.Errorf("modelscope: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
|
||||
endOfStream := "[DONE]"
|
||||
return sender(&endOfStream, nil)
|
||||
}
|
||||
|
||||
@@ -233,24 +233,51 @@ func (n *N1NModel) ChatStreamlyWithSender(ctx context.Context, modelName string,
|
||||
}
|
||||
|
||||
sawTerminal := false
|
||||
done, err := ParseSSEStream[n1nChatResponse](resp.Body, func(event n1nChatResponse) error {
|
||||
if len(event.Choices) == 0 {
|
||||
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))
|
||||
|
||||
tokenUsage, found, usageErr := decodeOpenAICompatibleStreamUsage(event)
|
||||
if usageErr != nil {
|
||||
return usageErr
|
||||
}
|
||||
if found {
|
||||
applyStreamUsage(chatModelConfig, modelUsage, tokenUsage)
|
||||
}
|
||||
|
||||
choices, ok := event["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
return nil
|
||||
}
|
||||
choice := event.Choices[0]
|
||||
if choice.Delta.ReasoningContent != "" {
|
||||
r := choice.Delta.ReasoningContent
|
||||
if err := sender(nil, &r); err != nil {
|
||||
|
||||
firstChoice, ok := choices[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
delta, ok := firstChoice["delta"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
content, ok := delta["content"].(string)
|
||||
if ok && content != "" {
|
||||
if err := sender(&content, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if choice.Delta.Content != "" {
|
||||
c := choice.Delta.Content
|
||||
if err := sender(&c, nil); err != nil {
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err := sender(nil, &reasoningContent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if choice.FinishReason != "" {
|
||||
|
||||
finishReason, ok := firstChoice["finish_reason"].(string)
|
||||
if ok && finishReason != "" {
|
||||
sawTerminal = true
|
||||
}
|
||||
return nil
|
||||
@@ -259,9 +286,11 @@ func (n *N1NModel) ChatStreamlyWithSender(ctx context.Context, modelName string,
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
if !done && !sawTerminal {
|
||||
return fmt.Errorf("n1n: stream ended before [DONE] or finish_reason")
|
||||
return fmt.Errorf("deepseek: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
|
||||
endOfStream := "[DONE]"
|
||||
if err := sender(&endOfStream, nil); err != nil {
|
||||
return err
|
||||
|
||||
@@ -256,7 +256,8 @@ func (n *NovitaModel) ChatWithMessages(ctx context.Context, modelName string, me
|
||||
}
|
||||
|
||||
rawContent, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -269,18 +270,22 @@ func (n *NovitaModel) ChatWithMessages(ctx context.Context, modelName string, me
|
||||
// `reasoning_content` field, with `content` already cleaned.
|
||||
// Handle both so the visible Answer is always tag-free and any
|
||||
// reasoning the upstream supplied is preserved.
|
||||
visible, reasoning := splitNovitaThink(rawContent)
|
||||
if r, ok := messageMap["reasoning_content"].(string); ok && r != "" {
|
||||
if reasoning != "" {
|
||||
reasoning += "\n" + r
|
||||
} else {
|
||||
reasoning = r
|
||||
visible, reasoning := "", ""
|
||||
if ok {
|
||||
visible, reasoning = splitNovitaThink(rawContent)
|
||||
if r, ok := messageMap["reasoning_content"].(string); ok && r != "" {
|
||||
if reasoning != "" {
|
||||
reasoning += "\n" + r
|
||||
} else {
|
||||
reasoning = r
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &ChatResponse{
|
||||
Answer: &visible,
|
||||
ReasonContent: &reasoning,
|
||||
ToolCalls: toolCalls,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,8 @@ func (n *NvidiaModel) ChatWithMessages(ctx context.Context, modelName string, me
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -151,6 +152,7 @@ func (n *NvidiaModel) ChatWithMessages(ctx context.Context, modelName string, me
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -215,6 +217,7 @@ func (n *NvidiaModel) ChatStreamlyWithSender(ctx context.Context, modelName stri
|
||||
return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
choices, ok := event["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
@@ -231,6 +234,8 @@ func (n *NvidiaModel) ChatStreamlyWithSender(ctx context.Context, modelName stri
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err := sender(nil, &reasoningContent); err != nil {
|
||||
@@ -250,6 +255,8 @@ func (n *NvidiaModel) ChatStreamlyWithSender(ctx context.Context, modelName stri
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
|
||||
setSortedToolCallsResult(modelConfig, accumulatedToolCalls)
|
||||
|
||||
endOfStream := "[DONE]"
|
||||
if err = sender(&endOfStream, nil); err != nil {
|
||||
return err
|
||||
|
||||
@@ -130,7 +130,8 @@ func (o *OpenRouterModel) ChatWithMessages(ctx context.Context, modelName string
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("no message in response")
|
||||
}
|
||||
|
||||
@@ -148,6 +149,7 @@ func (o *OpenRouterModel) ChatWithMessages(ctx context.Context, modelName string
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -210,6 +212,7 @@ func (o *OpenRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName
|
||||
return fmt.Errorf("invalid status code: %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
common.Info(fmt.Sprintf("%v", event))
|
||||
|
||||
@@ -228,6 +231,8 @@ func (o *OpenRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err := sender(nil, &reasoningContent); err != nil {
|
||||
@@ -246,6 +251,7 @@ func (o *OpenRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(modelConfig, accumulatedToolCalls)
|
||||
|
||||
// Send [DONE] marker for OpenAI compatibility
|
||||
endOfStream := "[DONE]"
|
||||
|
||||
@@ -188,6 +188,7 @@ func (o *OrcaRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName
|
||||
|
||||
// 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 {
|
||||
common.Info(fmt.Sprintf("%v", event))
|
||||
|
||||
@@ -206,6 +207,8 @@ func (o *OrcaRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
content, ok := delta["content"].(string)
|
||||
if ok && content != "" {
|
||||
if err := sender(&content, nil); err != nil {
|
||||
@@ -222,6 +225,7 @@ func (o *OrcaRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(modelConfig, accumulatedToolCalls)
|
||||
_ = done
|
||||
_ = sawTerminal
|
||||
|
||||
|
||||
@@ -122,7 +122,8 @@ func (s *StepFunModel) ChatWithMessages(ctx context.Context, modelName string, m
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -180,6 +181,7 @@ func (s *StepFunModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -196,6 +198,8 @@ func (s *StepFunModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
content, ok := delta["content"].(string)
|
||||
if ok && content != "" {
|
||||
if err := sender(&content, nil); err != nil {
|
||||
@@ -212,6 +216,7 @@ func (s *StepFunModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
if !done && !sawTerminal {
|
||||
return fmt.Errorf("stepfun: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
@@ -127,7 +127,8 @@ func (t *TokenHubModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -145,6 +146,7 @@ func (t *TokenHubModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -199,6 +201,7 @@ func (t *TokenHubModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
choices, ok := event["choices"].([]interface{})
|
||||
if !ok || len(choices) == 0 {
|
||||
@@ -215,6 +218,8 @@ func (t *TokenHubModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if !ok || reasoningContent == "" {
|
||||
reasoningContent, _ = delta["reasoning"].(string)
|
||||
@@ -237,6 +242,7 @@ func (t *TokenHubModel) ChatStreamlyWithSender(ctx context.Context, modelName st
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(modelConfig, accumulatedToolCalls)
|
||||
|
||||
// Send [DONE] marker for OpenAI compatibility
|
||||
endOfStream := "[DONE]"
|
||||
|
||||
@@ -118,7 +118,8 @@ func (t *TokenPonyModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -130,6 +131,7 @@ func (t *TokenPonyModel) ChatWithMessages(ctx context.Context, modelName string,
|
||||
return &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -181,6 +183,7 @@ func (t *TokenPonyModel) ChatStreamlyWithSender(ctx context.Context, modelName s
|
||||
}
|
||||
|
||||
sawTerminal := false
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
if apiErr, ok := event["error"]; ok {
|
||||
return fmt.Errorf("tokenpony: upstream stream error: %v", apiErr)
|
||||
@@ -196,6 +199,7 @@ func (t *TokenPonyModel) ChatStreamlyWithSender(ctx context.Context, modelName s
|
||||
}
|
||||
|
||||
if delta, ok := firstChoice["delta"].(map[string]interface{}); ok {
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
if r, ok := delta["reasoning_content"].(string); ok && r != "" {
|
||||
rr := r
|
||||
if err := sender(nil, &rr); err != nil {
|
||||
@@ -217,6 +221,7 @@ func (t *TokenPonyModel) ChatStreamlyWithSender(ctx context.Context, modelName s
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
if !done && !sawTerminal {
|
||||
return fmt.Errorf("tokenpony: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
@@ -127,7 +127,8 @@ func (u *UpstageModel) ChatWithMessages(ctx context.Context, modelName string, m
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -139,6 +140,7 @@ func (u *UpstageModel) ChatWithMessages(ctx context.Context, modelName string, m
|
||||
return &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -199,6 +201,7 @@ func (u *UpstageModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
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 {
|
||||
@@ -215,6 +218,8 @@ func (u *UpstageModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
if r, ok := delta["reasoning"].(string); ok && r != "" {
|
||||
if err := sender(nil, &r); err != nil {
|
||||
return err
|
||||
@@ -241,6 +246,8 @@ func (u *UpstageModel) ChatStreamlyWithSender(ctx context.Context, modelName str
|
||||
return fmt.Errorf("upstage: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
|
||||
endOfStream := "[DONE]"
|
||||
if err := sender(&endOfStream, nil); err != nil {
|
||||
return err
|
||||
|
||||
@@ -146,7 +146,8 @@ func (v *VllmModel) ChatWithMessages(ctx context.Context, modelName string, mess
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -164,6 +165,7 @@ func (v *VllmModel) ChatWithMessages(ctx context.Context, modelName string, mess
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -234,6 +236,7 @@ func (v *VllmModel) ChatStreamlyWithSender(ctx context.Context, modelName string
|
||||
}
|
||||
|
||||
// SSE parsing: read line by line
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
common.Info(fmt.Sprintf("%v", event))
|
||||
|
||||
@@ -252,6 +255,8 @@ func (v *VllmModel) ChatStreamlyWithSender(ctx context.Context, modelName string
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err := sender(nil, &reasoningContent); err != nil {
|
||||
@@ -270,6 +275,7 @@ func (v *VllmModel) ChatStreamlyWithSender(ctx context.Context, modelName string
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(modelConfig, accumulatedToolCalls)
|
||||
|
||||
// Send [DONE] marker for OpenAI compatibility
|
||||
endOfStream := "[DONE]"
|
||||
|
||||
@@ -133,7 +133,8 @@ func (x *XAIModel) ChatWithMessages(ctx context.Context, modelName string, messa
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("invalid content format")
|
||||
}
|
||||
|
||||
@@ -150,6 +151,7 @@ func (x *XAIModel) ChatWithMessages(ctx context.Context, modelName string, messa
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -201,6 +203,7 @@ func (x *XAIModel) ChatStreamlyWithSender(ctx context.Context, modelName string,
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -217,6 +220,8 @@ func (x *XAIModel) ChatStreamlyWithSender(ctx context.Context, modelName string,
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err := sender(nil, &reasoningContent); err != nil {
|
||||
@@ -240,6 +245,7 @@ func (x *XAIModel) ChatStreamlyWithSender(ctx context.Context, modelName string,
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls)
|
||||
if !done && !sawTerminal {
|
||||
return fmt.Errorf("xai: stream ended before [DONE] or finish_reason")
|
||||
}
|
||||
|
||||
@@ -127,7 +127,8 @@ func (x *XunFeiModel) ChatWithMessages(ctx context.Context, modelName string, me
|
||||
}
|
||||
|
||||
content, ok := messageMap["content"].(string)
|
||||
if !ok {
|
||||
toolCalls := extractToolCalls(messageMap)
|
||||
if !ok && len(toolCalls) == 0 {
|
||||
return nil, fmt.Errorf("no message in response")
|
||||
}
|
||||
|
||||
@@ -145,6 +146,7 @@ func (x *XunFeiModel) ChatWithMessages(ctx context.Context, modelName string, me
|
||||
chatResponse := &ChatResponse{
|
||||
Answer: &content,
|
||||
ReasonContent: &reasonContent,
|
||||
ToolCalls: toolCalls,
|
||||
}
|
||||
|
||||
return chatResponse, nil
|
||||
@@ -209,6 +211,7 @@ func (x *XunFeiModel) ChatStreamlyWithSender(ctx context.Context, modelName stri
|
||||
}
|
||||
|
||||
// SSE parsing: read line by line
|
||||
accumulatedToolCalls := make(map[int]map[string]any)
|
||||
if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error {
|
||||
if data, marshalErr := json.Marshal(event); marshalErr == nil {
|
||||
common.Info(string(data))
|
||||
@@ -229,6 +232,8 @@ func (x *XunFeiModel) ChatStreamlyWithSender(ctx context.Context, modelName stri
|
||||
return nil
|
||||
}
|
||||
|
||||
accumulateToolCallDeltas(delta, accumulatedToolCalls)
|
||||
|
||||
reasoningContent, ok := delta["reasoning_content"].(string)
|
||||
if ok && reasoningContent != "" {
|
||||
if err := sender(nil, &reasoningContent); err != nil {
|
||||
@@ -247,6 +252,7 @@ func (x *XunFeiModel) ChatStreamlyWithSender(ctx context.Context, modelName stri
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to scan response body: %w", err)
|
||||
}
|
||||
setSortedToolCallsResult(modelConfig, accumulatedToolCalls)
|
||||
|
||||
// Send [DONE] marker for OpenAI compatibility
|
||||
endOfStream := "[DONE]"
|
||||
|
||||
Reference in New Issue
Block a user