Simplify chat and support multimodal chat (#14523)

### What problem does this PR solve?

Simplify chat and support multimodal chat

### Type of change

- [x] Refactoring
This commit is contained in:
qinling0210
2026-04-30 15:25:01 +08:00
committed by GitHub
parent f45ce00347
commit 265f92c83e
17 changed files with 912 additions and 487 deletions

View File

@@ -418,8 +418,9 @@ func (c *HTTPClient) RequestStream(method, path string, useAPIBase bool, authKin
}
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
}
return resp.Body, nil

View File

@@ -1545,14 +1545,29 @@ func (c *RAGFlowClient) ChatToModel(cmd *Command) (ResponseIf, error) {
effort := cmd.Params["effort"].(string)
verbosity := cmd.Params["verbosity"].(string)
url := fmt.Sprintf("/chat/completions")
url := "/chat/completions"
message = strings.TrimSpace(message)
var content interface{} = message
if strings.HasPrefix(message, "[") && strings.HasSuffix(message, "]") {
var parts []map[string]interface{}
if err := json.Unmarshal([]byte(message), &parts); err == nil {
content = parts
}
}
formattedMessage := []map[string]interface{}{
{
"role": "user",
"content": content,
},
}
payload := map[string]interface{}{
"provider_name": providerName,
"instance_name": instanceName,
"model_name": modelName,
"message": message,
"stream": stream, // use stream API
"messages": formattedMessage,
"stream": stream,
"thinking": thinking,
}

View File

@@ -60,54 +60,62 @@ func (z *AliyunModel) Name() string {
return "siliconflow"
}
// Chat sends a message and returns response
func (z *AliyunModel) Chat(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if message == nil {
return nil, fmt.Errorf("message is nil")
func (z *AliyunModel) ChatWithMessages(modelName string, apiConfig *APIConfig, messages []Message, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if len(messages) == 0 {
return nil, fmt.Errorf("messages is empty")
}
var region = "default"
if apiConfig.Region != nil {
if apiConfig != nil && apiConfig.Region != nil {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.Chat)
// Convert messages to the format expected by API
apiMessages := make([]map[string]interface{}, len(messages))
for i, msg := range messages {
apiMessages[i] = map[string]interface{}{
"role": msg.Role,
"content": msg.Content,
}
}
// Build request body
reqBody := map[string]interface{}{
"model": modelName,
"messages": []map[string]string{
{"role": "user", "content": *message},
},
"model": modelName,
"messages": apiMessages,
"stream": false,
"temperature": 1,
}
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig != nil {
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.Stop != nil {
reqBody["stop"] = *chatModelConfig.Stop
}
if chatModelConfig.Stop != nil {
reqBody["stop"] = *chatModelConfig.Stop
}
if chatModelConfig.Thinking != nil {
if *chatModelConfig.Thinking {
reqBody["enable_thinking"] = true
} else {
reqBody["enable_thinking"] = false
if chatModelConfig.Thinking != nil {
if *chatModelConfig.Thinking {
reqBody["enable_thinking"] = true
} else {
reqBody["enable_thinking"] = false
}
}
}
@@ -122,7 +130,9 @@ func (z *AliyunModel) Chat(modelName, message *string, apiConfig *APIConfig, cha
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
if apiConfig != nil && apiConfig.ApiKey != nil {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
}
resp, err := z.httpClient.Do(req)
if err != nil {
@@ -166,7 +176,7 @@ func (z *AliyunModel) Chat(modelName, message *string, apiConfig *APIConfig, cha
}
var reasonContent string
if chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
if chatModelConfig != nil && chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
reasonContent, ok = messageMap["reasoning_content"].(string)
if !ok {
return nil, fmt.Errorf("invalid content format")
@@ -177,8 +187,6 @@ func (z *AliyunModel) Chat(modelName, message *string, apiConfig *APIConfig, cha
}
}
//thinking, answer := GetThinkingAndAnswer(chatModelConfig.ModelType, &content)
chatResponse := &ChatResponse{
Answer: &answer,
ReasonContent: &reasonContent,
@@ -187,11 +195,6 @@ func (z *AliyunModel) Chat(modelName, message *string, apiConfig *APIConfig, cha
return chatResponse, nil
}
// ChatWithMessages sends multiple messages with roles and returns response
func (z *AliyunModel) ChatWithMessages(modelName string, apiKey *string, messages []Message, chatModelConfig *ChatConfig) (string, error) {
return "", fmt.Errorf("%s, ChatWithMessages not implemented", z.Name())
}
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)
func (z *AliyunModel) ChatStreamlyWithSender(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig, sender func(*string, *string) error) error {
var region = "default"

View File

@@ -60,86 +60,93 @@ func (z *DeepSeekModel) Name() string {
return "deepseek"
}
// Chat sends a message and returns response
func (z *DeepSeekModel) Chat(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if message == nil {
return nil, fmt.Errorf("message is nil")
func (z *DeepSeekModel) ChatWithMessages(modelName string, apiConfig *APIConfig, messages []Message, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if len(messages) == 0 {
return nil, fmt.Errorf("messages is empty")
}
var region = "default"
if apiConfig.Region != nil {
if apiConfig != nil && apiConfig.Region != nil {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.Chat)
// Convert messages to the format expected by API
apiMessages := make([]map[string]interface{}, len(messages))
for i, msg := range messages {
apiMessages[i] = map[string]interface{}{
"role": msg.Role,
"content": msg.Content,
}
}
// Build request body
reqBody := map[string]interface{}{
"model": modelName,
"messages": []map[string]string{
{"role": "user", "content": *message},
},
"model": modelName,
"messages": apiMessages,
"stream": false,
"temperature": 1,
}
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig != nil {
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.Stop != nil {
reqBody["stop"] = *chatModelConfig.Stop
}
if chatModelConfig.Stop != nil {
reqBody["stop"] = *chatModelConfig.Stop
}
if chatModelConfig.Thinking != nil {
if *chatModelConfig.Thinking {
var thinkingFlag string
switch *chatModelConfig.Effort {
case "none":
thinkingFlag = "disabled"
chatModelConfig.Thinking = nil
break
case "low":
thinkingFlag = "disabled"
chatModelConfig.Thinking = nil
break
case "medium":
thinkingFlag = "disabled"
chatModelConfig.Thinking = nil
break
case "high":
thinkingFlag = "enabled"
reqBody["reasoning_effort"] = "high"
break
case "default":
thinkingFlag = "enabled"
reqBody["reasoning_effort"] = "high"
break
case "max":
thinkingFlag = "enabled"
reqBody["reasoning_effort"] = "max"
break
default:
return nil, fmt.Errorf("invalid effort level")
}
reqBody["thinking"] = map[string]interface{}{
"type": thinkingFlag,
}
} else {
reqBody["thinking"] = map[string]interface{}{
"type": "disabled",
if chatModelConfig.Thinking != nil {
if *chatModelConfig.Thinking {
var thinkingFlag string
effort := "high"
if chatModelConfig.Effort != nil {
effort = *chatModelConfig.Effort
}
switch effort {
case "none":
thinkingFlag = "disabled"
chatModelConfig.Thinking = nil
case "low":
thinkingFlag = "disabled"
chatModelConfig.Thinking = nil
case "medium":
thinkingFlag = "disabled"
chatModelConfig.Thinking = nil
case "high":
thinkingFlag = "enabled"
reqBody["reasoning_effort"] = "high"
case "default":
thinkingFlag = "enabled"
reqBody["reasoning_effort"] = "high"
case "max":
thinkingFlag = "enabled"
reqBody["reasoning_effort"] = "max"
default:
thinkingFlag = "enabled"
reqBody["reasoning_effort"] = effort
}
reqBody["thinking"] = map[string]interface{}{
"type": thinkingFlag,
}
} else {
reqBody["thinking"] = map[string]interface{}{
"type": "disabled",
}
}
}
}
@@ -155,7 +162,9 @@ func (z *DeepSeekModel) Chat(modelName, message *string, apiConfig *APIConfig, c
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
if apiConfig != nil && apiConfig.ApiKey != nil {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
}
resp, err := z.httpClient.Do(req)
if err != nil {
@@ -199,7 +208,7 @@ func (z *DeepSeekModel) Chat(modelName, message *string, apiConfig *APIConfig, c
}
var reasonContent string
if chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
if chatModelConfig != nil && chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
reasonContent, ok = messageMap["reasoning_content"].(string)
if !ok {
return nil, fmt.Errorf("invalid content format")
@@ -218,11 +227,6 @@ func (z *DeepSeekModel) Chat(modelName, message *string, apiConfig *APIConfig, c
return chatResponse, nil
}
// ChatWithMessages sends multiple messages with roles and returns response
func (z *DeepSeekModel) ChatWithMessages(modelName string, apiKey *string, messages []Message, chatModelConfig *ChatConfig) (string, error) {
return "", fmt.Errorf("%s, ChatWithMessages not implemented", z.Name())
}
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)
func (z *DeepSeekModel) ChatStreamlyWithSender(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig, sender func(*string, *string) error) error {
var region = "default"

View File

@@ -42,14 +42,9 @@ func (z *DummyModel) Name() string {
return "dummy"
}
// Chat sends a message and returns response
func (z *DummyModel) Chat(modelName, message *string, apiConfig *APIConfig, modelConfig *ChatConfig) (*ChatResponse, error) {
return nil, fmt.Errorf("not implemented")
}
// ChatWithMessages sends multiple messages with roles and returns response
func (z *DummyModel) ChatWithMessages(modelName string, apiKey *string, messages []Message, modelConfig *ChatConfig) (string, error) {
return "", fmt.Errorf("not implemented")
func (z *DummyModel) ChatWithMessages(modelName string, apiConfig *APIConfig, messages []Message, chatModelConfig *ChatConfig) (*ChatResponse, error) {
return nil, fmt.Errorf("not implemented")
}
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)

View File

@@ -60,64 +60,70 @@ func (z *GiteeModel) Name() string {
return "gitee"
}
// Chat sends a message and returns response
func (z *GiteeModel) Chat(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if message == nil {
return nil, fmt.Errorf("message is nil")
// ChatWithMessages sends multiple messages with roles and returns response
func (z *GiteeModel) ChatWithMessages(modelName string, apiConfig *APIConfig, messages []Message, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if apiConfig == nil || apiConfig.ApiKey == nil || *apiConfig.ApiKey == "" {
return nil, fmt.Errorf("api key is nil or empty")
}
var region = "default"
if len(messages) == 0 {
return nil, fmt.Errorf("messages is empty")
}
region := "default"
if apiConfig.Region != nil {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.Chat)
// I need to get the model type, such as qwen3 is the prefix, the model type will be qwen. glm is the prefix, the model type will be glm. such as the model name: qwen3-0.6b, the model type will be qwen3
// the model name is glm-4.7, the model type will be glm
modelType := strings.Split(*modelName, "-")[0]
if modelType == "qwen" || modelType == "glm" {
url = fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.AsyncChat)
// Convert messages to the format expected by API
apiMessages := make([]map[string]interface{}, len(messages))
for i, msg := range messages {
apiMessages[i] = map[string]interface{}{
"role": msg.Role,
"content": msg.Content,
}
}
logger.Info(fmt.Sprintf("GiteeAPI messages: %+v", apiMessages))
// Build request body
reqBody := map[string]interface{}{
"model": modelName,
"messages": []map[string]string{
{"role": "user", "content": *message},
},
"model": modelName,
"messages": apiMessages,
"stream": false,
"temperature": 1,
}
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig != nil {
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.Stop != nil {
reqBody["stop"] = *chatModelConfig.Stop
}
if chatModelConfig.Stop != nil {
reqBody["stop"] = *chatModelConfig.Stop
}
if chatModelConfig.Thinking != nil {
if *chatModelConfig.Thinking {
reqBody["thinking"] = map[string]interface{}{
"type": "enabled",
}
} else {
reqBody["thinking"] = map[string]interface{}{
"type": "disabled",
if chatModelConfig.Thinking != nil {
if *chatModelConfig.Thinking {
reqBody["thinking"] = map[string]interface{}{
"type": "enabled",
}
} else {
reqBody["thinking"] = map[string]interface{}{
"type": "disabled",
}
}
}
}
@@ -127,6 +133,8 @@ func (z *GiteeModel) Chat(modelName, message *string, apiConfig *APIConfig, chat
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
logger.Info(fmt.Sprintf("GiteeAPI request body: %s", string(jsonData)))
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
@@ -152,7 +160,7 @@ func (z *GiteeModel) Chat(modelName, message *string, apiConfig *APIConfig, chat
// Parse response
var result map[string]interface{}
if err = json.Unmarshal(body, &result); err != nil {
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
@@ -176,21 +184,33 @@ func (z *GiteeModel) Chat(modelName, message *string, apiConfig *APIConfig, chat
return nil, fmt.Errorf("invalid content format")
}
thinking, answer := GetThinkingAndAnswer(chatModelConfig.ModelClass, &content)
// Handle thinking/reasoning if enabled
var reasonContent string
if chatModelConfig != nil && chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
// Try to get reasoning_content directly first
if rc, ok := messageMap["reasoning_content"].(string); ok && rc != "" {
reasonContent = rc
if reasonContent[0] == '\n' {
reasonContent = reasonContent[1:]
}
} else {
// Fall back to parsing <think> tags from content
reasoning, answer := GetThinkingAndAnswer(chatModelConfig.ModelClass, &content)
if reasoning != nil {
reasonContent = *reasoning
content = *answer
}
}
}
chatResponse := &ChatResponse{
Answer: answer,
ReasonContent: thinking,
Answer: &content,
ReasonContent: &reasonContent,
}
return chatResponse, nil
}
// ChatWithMessages sends multiple messages with roles and returns response
func (z *GiteeModel) ChatWithMessages(modelName string, apiKey *string, messages []Message, chatModelConfig *ChatConfig) (string, error) {
return "", fmt.Errorf("%s, ChatWithMessages not implemented", z.Name())
}
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)
func (z *GiteeModel) ChatStreamlyWithSender(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig, sender func(*string, *string) error) error {
var region = "default"

View File

@@ -46,8 +46,15 @@ func (z *GoogleModel) Name() string {
return "google"
}
// Chat sends a message and returns response
func (z *GoogleModel) Chat(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
func (z *GoogleModel) ChatWithMessages(modelName string, apiConfig *APIConfig, messages []Message, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if apiConfig == nil || apiConfig.ApiKey == nil || *apiConfig.ApiKey == "" {
return nil, fmt.Errorf("api key is nil or empty")
}
if len(messages) == 0 {
return nil, fmt.Errorf("messages is empty")
}
ctx := context.Background()
client, err := genai.NewClient(ctx, &genai.ClientConfig{
APIKey: *apiConfig.ApiKey,
@@ -57,39 +64,59 @@ func (z *GoogleModel) Chat(modelName, message *string, apiConfig *APIConfig, cha
return nil, err
}
contents := []*genai.Content{
genai.NewContentFromText(*message, genai.RoleUser),
// 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))
}
}
}
generateContentConfig := &genai.GenerateContentConfig{}
generateContentConfig.ThinkingConfig = &genai.ThinkingConfig{}
if chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
generateContentConfig.ThinkingConfig.IncludeThoughts = true
} else {
generateContentConfig.ThinkingConfig.IncludeThoughts = false
}
response, err := client.Models.GenerateContent(ctx, *modelName, contents, generateContentConfig)
// Generate content (non-streaming)
response, err := client.Models.GenerateContent(ctx, modelName, contents, nil)
if err != nil {
return nil, err
}
content := response.Text()
var responseContent string
if chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
responseContent = response.Candidates[0].Content.Parts[0].Text
}
// Extract text from response
answer := response.Text()
chatResponse := &ChatResponse{
Answer: &content,
ReasonContent: &responseContent,
}
return chatResponse, nil
}
// ChatWithMessages sends multiple messages with roles and returns response
func (z *GoogleModel) ChatWithMessages(modelName string, apiKey *string, messages []Message, modelConfig *ChatConfig) (string, error) {
return "", fmt.Errorf("not implemented")
return &ChatResponse{Answer: &answer}, nil
}
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)

View File

@@ -179,8 +179,135 @@ func (z *MinimaxModel) Chat(modelName, message *string, apiConfig *APIConfig, mo
}
// ChatWithMessages sends multiple messages with roles and returns response
func (z *MinimaxModel) ChatWithMessages(modelName string, apiKey *string, messages []Message, chatModelConfig *ChatConfig) (string, error) {
return "", fmt.Errorf("%s, ChatWithMessages not implemented", z.Name())
func (z *MinimaxModel) ChatWithMessages(modelName string, apiConfig *APIConfig, messages []Message, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if apiConfig == nil || apiConfig.ApiKey == nil || *apiConfig.ApiKey == "" {
return nil, fmt.Errorf("api key is nil or empty")
}
if len(messages) == 0 {
return nil, fmt.Errorf("messages is empty")
}
var region = "default"
if apiConfig.Region != nil && *apiConfig.Region != "" {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.Chat)
// Convert messages to API format
apiMessages := make([]map[string]interface{}, len(messages))
for i, msg := range messages {
apiMessages[i] = map[string]interface{}{
"role": msg.Role,
"content": msg.Content,
}
}
// Build request body
reqBody := map[string]interface{}{
"model": modelName,
"messages": apiMessages,
"stream": false,
"temperature": 1,
}
if chatModelConfig != nil {
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.DoSample != nil {
reqBody["do_sample"] = *chatModelConfig.DoSample
}
if chatModelConfig.Thinking != nil {
reqBody["thinking"] = *chatModelConfig.Thinking
}
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
resp, err := z.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to send request: %d %s", resp.StatusCode, string(body))
}
// Parse response
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
choices, ok := result["choices"].([]interface{})
if !ok {
return nil, fmt.Errorf("no choices in response")
}
firstChoice, ok := choices[0].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("no choices in response")
}
messageMap, ok := firstChoice["message"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("no message in response")
}
content, ok := messageMap["content"].(string)
if !ok {
return nil, fmt.Errorf("no message in response")
}
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 reasonContent != "" && reasonContent[0] == '\n' {
reasonContent = reasonContent[1:]
}
}
chatResponse := &ChatResponse{
Answer: &content,
ReasonContent: &reasonContent,
}
return chatResponse, nil
}
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)

View File

@@ -60,52 +60,65 @@ func (z *MoonshotModel) Name() string {
return "moonshot"
}
// Chat sends a message and returns response
func (k *MoonshotModel) Chat(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if message == nil {
return nil, fmt.Errorf("message is nil")
func (k *MoonshotModel) ChatWithMessages(modelName string, apiConfig *APIConfig, messages []Message, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if len(messages) == 0 {
return nil, fmt.Errorf("messages is empty")
}
var region = "default"
if apiConfig.Region != nil {
if apiConfig != nil && apiConfig.Region != nil {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", k.BaseURL[region], k.URLSuffix.Chat)
// Convert messages to the format expected by API
apiMessages := make([]map[string]interface{}, len(messages))
for i, msg := range messages {
apiMessages[i] = map[string]interface{}{
"role": msg.Role,
"content": msg.Content,
}
}
// Build request body
reqBody := map[string]interface{}{
"model": modelName,
"messages": []map[string]string{
{"role": "user", "content": *message},
},
"stream": false,
"model": modelName,
"messages": apiMessages,
"stream": false,
"temperature": 1,
}
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig != nil {
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.Stop != nil {
reqBody["stop"] = *chatModelConfig.Stop
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.Thinking != nil {
if *chatModelConfig.Thinking {
reqBody["thinking"] = map[string]interface{}{
"type": "enabled",
}
} else {
reqBody["thinking"] = map[string]interface{}{
"type": "disabled",
if chatModelConfig.Stop != nil {
reqBody["stop"] = *chatModelConfig.Stop
}
if chatModelConfig.Thinking != nil {
if *chatModelConfig.Thinking {
reqBody["thinking"] = map[string]interface{}{
"type": "enabled",
}
} else {
reqBody["thinking"] = map[string]interface{}{
"type": "disabled",
}
}
}
}
@@ -121,7 +134,9 @@ func (k *MoonshotModel) Chat(modelName, message *string, apiConfig *APIConfig, c
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
if apiConfig != nil && apiConfig.ApiKey != nil {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
}
resp, err := k.httpClient.Do(req)
if err != nil {
@@ -165,7 +180,7 @@ func (k *MoonshotModel) Chat(modelName, message *string, apiConfig *APIConfig, c
}
var reasonContent string
if chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
if chatModelConfig != nil && chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
reasonContent, ok = messageMap["reasoning_content"].(string)
if !ok {
return nil, fmt.Errorf("invalid content format")
@@ -184,11 +199,6 @@ func (k *MoonshotModel) Chat(modelName, message *string, apiConfig *APIConfig, c
return chatResponse, nil
}
// ChatWithMessages sends multiple messages with roles and returns response
func (k *MoonshotModel) ChatWithMessages(modelName string, apiKey *string, messages []Message, chatModelConfig *ChatConfig) (string, error) {
return "", fmt.Errorf("%s, ChatWithMessages not implemented", k.Name())
}
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)
func (k *MoonshotModel) ChatStreamlyWithSender(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig, sender func(*string, *string) error) error {
var region = "default"

View File

@@ -79,65 +79,58 @@ type SiliconflowRerankResponse struct {
} `json:"results"`
}
// Chat sends a message and returns response
func (z *SiliconflowModel) Chat(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if message == nil {
return nil, fmt.Errorf("message is nil")
// ChatWithMessages sends multiple messages with roles and returns response
func (z *SiliconflowModel) ChatWithMessages(modelName string, apiConfig *APIConfig, messages []Message, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if apiConfig == nil || apiConfig.ApiKey == nil || *apiConfig.ApiKey == "" {
return nil, fmt.Errorf("api key is nil or empty")
}
var region = "default"
if len(messages) == 0 {
return nil, fmt.Errorf("messages is empty")
}
region := "default"
if apiConfig.Region != nil {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.Chat)
// I need to get the model type, such as qwen3 is the prefix, the model type will be qwen. glm is the prefix, the model type will be glm. such as the model name: qwen3-0.6b, the model type will be qwen3
// the model name is glm-4.7, the model type will be glm
modelType := strings.Split(*modelName, "-")[0]
if modelType == "qwen" || modelType == "glm" {
url = fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.AsyncChat)
// Convert messages to the format expected by API
apiMessages := make([]map[string]interface{}, len(messages))
for i, msg := range messages {
apiMessages[i] = map[string]interface{}{
"role": msg.Role,
"content": msg.Content,
}
}
// Build request body
reqBody := map[string]interface{}{
"model": modelName,
"messages": []map[string]string{
{"role": "user", "content": *message},
},
"model": modelName,
"messages": apiMessages,
"stream": false,
"temperature": 1,
}
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig != nil {
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.Stop != nil {
reqBody["stop"] = *chatModelConfig.Stop
}
if chatModelConfig.Thinking != nil {
if *chatModelConfig.Thinking {
reqBody["thinking"] = map[string]interface{}{
"type": "enabled",
}
} else {
reqBody["thinking"] = map[string]interface{}{
"type": "disabled",
}
if chatModelConfig.Stop != nil {
reqBody["stop"] = *chatModelConfig.Stop
}
}
@@ -171,7 +164,7 @@ func (z *SiliconflowModel) Chat(modelName, message *string, apiConfig *APIConfig
// Parse response
var result map[string]interface{}
if err = json.Unmarshal(body, &result); err != nil {
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
@@ -195,21 +188,32 @@ func (z *SiliconflowModel) Chat(modelName, message *string, apiConfig *APIConfig
return nil, fmt.Errorf("invalid content format")
}
thinking, answer := GetThinkingAndAnswer(chatModelConfig.ModelClass, &content)
var reasonContent string
if chatModelConfig != nil && chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
reasonContent, ok = messageMap["reasoning_content"].(string)
if !ok {
// If reasoning_content not in response, try parsing from content tags
reasoning, answer := GetThinkingAndAnswer(chatModelConfig.ModelClass, &content)
if reasoning != nil {
reasonContent = *reasoning
content = *answer
}
} else {
// if first char of reasonContent is \n remove the '\n'
if reasonContent != "" && reasonContent[0] == '\n' {
reasonContent = reasonContent[1:]
}
}
}
chatResponse := &ChatResponse{
Answer: answer,
ReasonContent: thinking,
Answer: &content,
ReasonContent: &reasonContent,
}
return chatResponse, nil
}
// ChatWithMessages sends multiple messages with roles and returns response
func (z *SiliconflowModel) ChatWithMessages(modelName string, apiKey *string, messages []Message, chatModelConfig *ChatConfig) (string, error) {
return "", fmt.Errorf("%s, ChatWithMessages not implemented", z.Name())
}
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)
func (z *SiliconflowModel) ChatStreamlyWithSender(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig, sender func(*string, *string) error) error {
var region = "default"
@@ -289,10 +293,6 @@ func (z *SiliconflowModel) ChatStreamlyWithSender(modelName, message *string, ap
return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
}
reserveText := ""
thinkingPhase := false
answerPhase := false
// SSE parsing: read line by line
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
@@ -333,34 +333,17 @@ func (z *SiliconflowModel) ChatStreamlyWithSender(modelName, message *string, ap
continue
}
reasoningContent, ok := delta["reasoning_content"].(string)
if ok && reasoningContent != "" {
if err := sender(nil, &reasoningContent); err != nil {
return err
}
}
content, ok := delta["content"].(string)
if ok && content != "" {
if content == "<think>" {
thinkingPhase = true
continue
} else if content == "</think>" {
thinkingPhase = false
answerPhase = true
continue
}
if thinkingPhase {
if err = sender(nil, &content); err != nil {
return err
}
reserveText = ""
} else if answerPhase {
if err = sender(&content, nil); err != nil {
return err
}
reserveText = ""
} else {
content = strings.Trim(content, "\n")
content = strings.Trim(content, " ")
if content != "" {
reserveText += content
}
if err := sender(&content, nil); err != nil {
return err
}
}
@@ -370,12 +353,6 @@ func (z *SiliconflowModel) ChatStreamlyWithSender(modelName, message *string, ap
}
}
if reserveText != "" {
if err = sender(&reserveText, nil); err != nil {
return err
}
}
// Send [DONE] marker for OpenAI compatibility
endOfStream := "[DONE]"
if err = sender(&endOfStream, nil); err != nil {

View File

@@ -1,9 +1,14 @@
package models
// Message represents a chat message with role
// Message represents a chat message with role and content
//
// Content is interface{} to support different formats:
// - string: plain text message (e.g., "Hello")
// - []interface{}: multimodal content array where each element is map[string]interface{}
// (e.g., [{"type": "text", "text": "..."}, {"type": "image_url", "image_url": {"url": "..."}}])
type Message struct {
Role string
Content string
Role string `json:"role"`
Content interface{} `json:"content"`
}
// EmbeddingModel interface for embedding models
@@ -12,10 +17,8 @@ type ModelDriver interface {
Name() string
// Chat sends a message and returns response
Chat(modelName, message *string, apiConfig *APIConfig, modelConfig *ChatConfig) (*ChatResponse, error)
// ChatWithMessages sends multiple messages with roles (system, user, etc.) and returns response
ChatWithMessages(modelName string, apiKey *string, messages []Message, modelConfig *ChatConfig) (string, error)
// ChatWithMessages sends multiple messages with role and content
ChatWithMessages(modelName string, apiConfig *APIConfig, messages []Message, chatModelConfig *ChatConfig) (*ChatResponse, error)
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)
ChatStreamlyWithSender(modelName, message *string, apiConfig *APIConfig, modelConfig *ChatConfig, sender func(*string, *string) error) error
// Encode encodes a list of texts into embeddings

View File

@@ -197,8 +197,137 @@ func (z *VllmModel) Chat(modelName, message *string, apiConfig *APIConfig, chatM
}
// ChatWithMessages sends multiple messages with roles and returns response
func (z *VllmModel) ChatWithMessages(modelName string, apiKey *string, messages []Message, modelConfig *ChatConfig) (string, error) {
return "", fmt.Errorf("not implemented")
func (z *VllmModel) ChatWithMessages(modelName string, apiConfig *APIConfig, messages []Message, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if len(messages) == 0 {
return nil, fmt.Errorf("messages is empty")
}
var region = "default"
if apiConfig.Region != nil {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.Chat)
// For qwen/glm models, use async chat endpoint
modelType := strings.Split(modelName, "-")[0]
if modelType == "qwen" || modelType == "glm" {
url = fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.AsyncChat)
}
// Convert messages to API format
apiMessages := make([]map[string]interface{}, len(messages))
for i, msg := range messages {
apiMessages[i] = map[string]interface{}{
"role": msg.Role,
"content": msg.Content,
}
}
// Build request body
reqBody := map[string]interface{}{
"model": modelName,
"messages": apiMessages,
"stream": false,
"temperature": 1,
}
if chatModelConfig != nil {
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.Stop != nil {
reqBody["stop"] = *chatModelConfig.Stop
}
if chatModelConfig.Thinking != nil {
if *chatModelConfig.Thinking {
reqBody["thinking"] = map[string]interface{}{
"type": "enabled",
}
} else {
reqBody["thinking"] = map[string]interface{}{
"type": "disabled",
}
}
}
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
resp, err := z.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
}
// Parse response
var result map[string]interface{}
if err = json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
choices, ok := result["choices"].([]interface{})
if !ok || len(choices) == 0 {
return nil, fmt.Errorf("no choices in response")
}
firstChoice, ok := choices[0].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid choice format")
}
messageMap, ok := firstChoice["message"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid message format")
}
content, ok := messageMap["content"].(string)
if !ok {
return nil, fmt.Errorf("invalid content format")
}
thinking, answer := GetThinkingAndAnswer(chatModelConfig.ModelClass, &content)
chatResponse := &ChatResponse{
Answer: answer,
ReasonContent: thinking,
}
return chatResponse, nil
}
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)

View File

@@ -211,8 +211,163 @@ func (z *VolcEngine) Chat(modelName, message *string, apiConfig *APIConfig, mode
}
// ChatWithMessages sends multiple messages with roles and returns response
func (z *VolcEngine) ChatWithMessages(modelName string, apiKey *string, messages []Message, chatModelConfig *ChatConfig) (string, error) {
return "", fmt.Errorf("%s, ChatWithMessages not implemented", z.Name())
func (z *VolcEngine) ChatWithMessages(modelName string, apiConfig *APIConfig, messages []Message, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if len(messages) == 0 {
return nil, fmt.Errorf("messages is empty")
}
var region = "default"
if apiConfig != nil && apiConfig.Region != nil {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.Chat)
// Convert messages to API format
apiMessages := make([]map[string]interface{}, len(messages))
for i, msg := range messages {
apiMessages[i] = map[string]interface{}{
"role": msg.Role,
"content": msg.Content,
}
}
// Build request body
reqBody := map[string]interface{}{
"model": modelName,
"messages": apiMessages,
"stream": false,
"temperature": 1,
}
if chatModelConfig != nil {
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.Thinking != nil {
if *chatModelConfig.Thinking {
var thinkingFlag string
effort := "medium"
if chatModelConfig.Effort != nil {
effort = *chatModelConfig.Effort
}
switch effort {
case "none", "minimal":
thinkingFlag = "disabled"
reqBody["reasoning_effort"] = "minimal"
case "low":
thinkingFlag = "enabled"
reqBody["reasoning_effort"] = "low"
case "medium":
thinkingFlag = "enabled"
reqBody["reasoning_effort"] = "medium"
case "auto", "default":
thinkingFlag = "enabled"
reqBody["reasoning_effort"] = "medium"
case "high":
thinkingFlag = "enabled"
reqBody["reasoning_effort"] = "high"
default:
thinkingFlag = "enabled"
reqBody["reasoning_effort"] = effort
}
reqBody["thinking"] = map[string]interface{}{
"type": thinkingFlag,
}
} else {
reqBody["thinking"] = map[string]interface{}{
"type": "disabled",
}
}
}
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
if apiConfig != nil && apiConfig.ApiKey != nil {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
}
resp, err := z.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
}
// Parse response
var result map[string]interface{}
if err = json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
choices, ok := result["choices"].([]interface{})
if !ok || len(choices) == 0 {
return nil, fmt.Errorf("no choices in response")
}
firstChoice, ok := choices[0].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid choice format")
}
messageMap, ok := firstChoice["message"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid message format")
}
content, ok := messageMap["content"].(string)
if !ok {
return nil, fmt.Errorf("invalid content format")
}
var reasonContent string
if chatModelConfig != nil && chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
reasonContent, ok = messageMap["reasoning_content"].(string)
if !ok {
return nil, fmt.Errorf("invalid reasonContent format")
}
if reasonContent != "" && reasonContent[0] == '\n' {
reasonContent = reasonContent[1:]
}
}
chatResponse := &ChatResponse{
Answer: &content,
ReasonContent: &reasonContent,
}
return chatResponse, nil
}
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)

View File

@@ -60,57 +60,69 @@ func (z *ZhipuAIModel) Name() string {
return "zhipu"
}
// Chat sends a message and returns response
func (z *ZhipuAIModel) Chat(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if message == nil {
return nil, fmt.Errorf("message is nil")
// ChatWithMessages sends multiple messages with roles and returns response
func (z *ZhipuAIModel) ChatWithMessages(modelName string, apiConfig *APIConfig, messages []Message, chatModelConfig *ChatConfig) (*ChatResponse, error) {
if apiConfig == nil || apiConfig.ApiKey == nil || *apiConfig.ApiKey == "" {
return nil, fmt.Errorf("api key is nil or empty")
}
var region = "default"
if len(messages) == 0 {
return nil, fmt.Errorf("messages is empty")
}
region := "default"
if apiConfig.Region != nil {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.Chat)
// Convert messages to the format expected by API
apiMessages := make([]map[string]interface{}, len(messages))
for i, msg := range messages {
apiMessages[i] = map[string]interface{}{
"role": msg.Role,
"content": msg.Content,
}
}
// Build request body
reqBody := map[string]interface{}{
"model": modelName,
"messages": []map[string]string{
{"role": "user", "content": *message},
},
"model": modelName,
"messages": apiMessages,
"stream": false,
"temperature": 1,
}
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig != nil {
if chatModelConfig.Stream != nil {
reqBody["stream"] = *chatModelConfig.Stream
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
if chatModelConfig.Stop != nil {
reqBody["stop"] = *chatModelConfig.Stop
}
if chatModelConfig.Stop != nil {
reqBody["stop"] = *chatModelConfig.Stop
}
if chatModelConfig.Thinking != nil {
if *chatModelConfig.Thinking {
reqBody["thinking"] = map[string]interface{}{
"type": "enabled",
}
} else {
reqBody["thinking"] = map[string]interface{}{
"type": "disabled",
if chatModelConfig.Thinking != nil {
if *chatModelConfig.Thinking {
reqBody["thinking"] = map[string]interface{}{
"type": "enabled",
}
} else {
reqBody["thinking"] = map[string]interface{}{
"type": "disabled",
}
}
}
}
@@ -145,7 +157,7 @@ func (z *ZhipuAIModel) Chat(modelName, message *string, apiConfig *APIConfig, ch
// Parse response
var result map[string]interface{}
if err = json.Unmarshal(body, &result); err != nil {
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
@@ -170,7 +182,7 @@ func (z *ZhipuAIModel) Chat(modelName, message *string, apiConfig *APIConfig, ch
}
var reasonContent string
if chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
if chatModelConfig != nil && chatModelConfig.Thinking != nil && *chatModelConfig.Thinking {
reasonContent, ok = messageMap["reasoning_content"].(string)
if !ok {
return nil, fmt.Errorf("invalid content format")
@@ -189,106 +201,6 @@ func (z *ZhipuAIModel) Chat(modelName, message *string, apiConfig *APIConfig, ch
return chatResponse, nil
}
// ChatWithMessages sends multiple messages with roles and returns response
func (z *ZhipuAIModel) ChatWithMessages(modelName string, apiKey *string, messages []Message, chatModelConfig *ChatConfig) (string, error) {
if apiKey == nil || *apiKey == "" {
return "", fmt.Errorf("api key is nil or empty")
}
if len(messages) == 0 {
return "", fmt.Errorf("messages is empty")
}
url := fmt.Sprintf("%s/%s", z.BaseURL["default"], z.URLSuffix.Chat)
// Convert messages to the format expected by API
apiMessages := make([]map[string]string, len(messages))
for i, msg := range messages {
apiMessages[i] = map[string]string{
"role": msg.Role,
"content": msg.Content,
}
}
// Build request body
reqBody := map[string]interface{}{
"model": modelName,
"messages": apiMessages,
"stream": false,
"temperature": 1,
}
if chatModelConfig != nil {
if chatModelConfig.MaxTokens != nil {
reqBody["max_tokens"] = *chatModelConfig.MaxTokens
}
if chatModelConfig.Temperature != nil {
reqBody["temperature"] = *chatModelConfig.Temperature
}
if chatModelConfig.TopP != nil {
reqBody["top_p"] = *chatModelConfig.TopP
}
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return "", fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiKey))
resp, err := z.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
}
// Parse response
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return "", fmt.Errorf("failed to parse response: %w", err)
}
choices, ok := result["choices"].([]interface{})
if !ok || len(choices) == 0 {
return "", fmt.Errorf("no choices in response")
}
firstChoice, ok := choices[0].(map[string]interface{})
if !ok {
return "", fmt.Errorf("invalid choice format")
}
messageMap, ok := firstChoice["message"].(map[string]interface{})
if !ok {
return "", fmt.Errorf("invalid message format")
}
content, ok := messageMap["content"].(string)
if !ok {
return "", fmt.Errorf("invalid content format")
}
return content, nil
}
// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel)
func (z *ZhipuAIModel) ChatStreamlyWithSender(modelName, message *string, apiConfig *APIConfig, chatModelConfig *ChatConfig, sender func(*string, *string) error) error {
var region = "default"

View File

@@ -756,14 +756,14 @@ func (h *ProviderHandler) DropInstanceModels(c *gin.Context) {
}
type ChatToModelRequest struct {
ProviderName *string `json:"provider_name"`
InstanceName *string `json:"instance_name"`
ModelName *string `json:"model_name"`
Message string `json:"message" binding:"required"`
Stream bool `json:"stream"`
Thinking bool `json:"thinking"`
Effort *string `json:"effort"`
Verbosity *string `json:"verbosity"`
ProviderName *string `json:"provider_name"`
InstanceName *string `json:"instance_name"`
ModelName *string `json:"model_name"`
Messages []map[string]interface{} `json:"messages"`
Stream bool `json:"stream"`
Thinking bool `json:"thinking"`
Effort *string `json:"effort"`
Verbosity *string `json:"verbosity"`
}
func (h *ProviderHandler) ChatToModel(c *gin.Context) {
@@ -828,6 +828,24 @@ func (h *ProviderHandler) ChatToModel(c *gin.Context) {
// Check if it's a stream request
if req.Stream {
// Streaming with multimodal messages not yet supported
hasMultimodal := false
for _, msg := range req.Messages {
if content, ok := msg["content"]; ok {
if _, isArray := content.([]interface{}); isArray {
hasMultimodal = true
break
}
}
}
if hasMultimodal {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "Streaming with multimodal messages not yet supported",
})
return
}
// Set SSE headers
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
@@ -859,7 +877,7 @@ func (h *ProviderHandler) ChatToModel(c *gin.Context) {
}
// Stream response using sender function (best performance, no channel)
errorCode, err := h.modelProviderService.ChatToModelStreamWithSender(*req.ProviderName, *req.InstanceName, *req.ModelName, userID, req.Message, &apiConfig, &chatConfig, sender)
errorCode, err := h.modelProviderService.ChatToModelStreamWithSender(*req.ProviderName, *req.InstanceName, *req.ModelName, userID, req.Messages[0]["content"].(string), &apiConfig, &chatConfig, sender)
if errorCode != common.CodeSuccess {
c.SSEvent("error", err.Error())
@@ -868,7 +886,19 @@ func (h *ProviderHandler) ChatToModel(c *gin.Context) {
}
// Non-stream response
response, errorCode, err := h.modelProviderService.ChatToModel(*req.ProviderName, *req.InstanceName, *req.ModelName, userID, req.Message, &apiConfig, &chatConfig)
var response *models.ChatResponse
var errorCode common.ErrorCode
var err error
// Convert []map[string]interface{} to []models.Message
messages := make([]models.Message, len(req.Messages))
for i, msg := range req.Messages {
role, _ := msg["role"].(string)
content := msg["content"]
messages[i] = models.Message{Role: role, Content: content}
}
response, errorCode, err = h.modelProviderService.ChatToModelWithMessages(*req.ProviderName, *req.InstanceName, *req.ModelName, userID, messages, &apiConfig, &chatConfig)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": errorCode,

View File

@@ -549,9 +549,12 @@ func (s *ChatSessionService) asyncChatSolo(dialog *entity.Chat, session *entity.
}
for _, msg := range processedMessages {
role, _ := msg["role"].(string)
content, _ := msg["content"].(string)
if role != "" && content != "" && role != "system" {
msgs = append(msgs, modelModule.Message{Role: role, Content: content})
if role == "" || role == "system" {
continue
}
if msg["content"] != nil {
msgs = append(msgs, modelModule.Message{Role: role, Content: msg["content"]})
}
}
@@ -559,7 +562,7 @@ func (s *ChatSessionService) asyncChatSolo(dialog *entity.Chat, session *entity.
chatConfig := s.buildChatConfig(dialog, config)
// Perform chat
response, err := chatModel.ModelDriver.ChatWithMessages(*chatModel.ModelName, chatModel.APIConfig.ApiKey, msgs, chatConfig)
response, err := chatModel.ModelDriver.ChatWithMessages(*chatModel.ModelName, chatModel.APIConfig, msgs, chatConfig)
if err != nil {
logger.Error("asyncChatSolo chat failed", err)
return nil, err
@@ -568,11 +571,11 @@ func (s *ChatSessionService) asyncChatSolo(dialog *entity.Chat, session *entity.
logger.Info("asyncChatSolo completed",
zap.String("tenant_id", dialog.TenantID),
zap.String("llm_id", dialog.LLMID),
zap.Int("response_length", len(response)))
zap.Int("response_length", len(*response.Answer)))
// Structure the answer
ans := map[string]interface{}{
"answer": response,
"answer": *response.Answer,
"reference": reference[len(reference)-1],
"final": true,
}

View File

@@ -674,7 +674,14 @@ func (m *ModelProviderService) UpdateModelStatus(providerName, instanceName, mod
return common.CodeSuccess, nil
}
func (m *ModelProviderService) ChatToModel(providerName, instanceName, modelName, userID, message string, apiConfig *modelModule.APIConfig, modelConfig *modelModule.ChatConfig) (*modelModule.ChatResponse, common.ErrorCode, error) {
// ChatToModelWithMessages sends messages to the model with messages array
func (m *ModelProviderService) ChatToModelWithMessages(providerName, instanceName, modelName, userID string, messages []modelModule.Message, apiConfig *modelModule.APIConfig, modelConfig *modelModule.ChatConfig) (*modelModule.ChatResponse, common.ErrorCode, error) {
if apiConfig == nil {
apiConfig = &modelModule.APIConfig{}
}
if modelConfig == nil {
modelConfig = &modelModule.ChatConfig{}
}
// Get tenant ID from user
tenants, err := m.userTenantDAO.GetByUserIDAndRole(userID, "owner")
@@ -725,10 +732,13 @@ func (m *ModelProviderService) ChatToModel(providerName, instanceName, modelName
apiConfig.ApiKey = &instance.APIKey
var response *modelModule.ChatResponse
response, err = providerInfo.ModelDriver.Chat(&modelName, &message, apiConfig, modelConfig)
response, err = providerInfo.ModelDriver.ChatWithMessages(modelName, apiConfig, messages, modelConfig)
if err != nil {
return nil, common.CodeServerError, err
}
if response == nil {
return nil, common.CodeServerError, errors.New("empty chat response")
}
return response, common.CodeSuccess, nil
}
@@ -750,9 +760,6 @@ func (m *ModelProviderService) ChatToModel(providerName, instanceName, modelName
apiConfig.Region = &region
apiConfig.ApiKey = &instance.APIKey
modelTypes := extra["model_types"]
println(modelTypes)
modelConfig.ModelClass = &providerInfo.Class
newURL := map[string]string{
@@ -761,10 +768,14 @@ func (m *ModelProviderService) ChatToModel(providerName, instanceName, modelName
newProviderInfo := providerInfo.ModelDriver.NewInstance(newURL)
var response *modelModule.ChatResponse
response, err = newProviderInfo.Chat(&modelName, &message, apiConfig, modelConfig)
response, err = newProviderInfo.ChatWithMessages(modelName, apiConfig, messages, modelConfig)
if err != nil {
return nil, common.CodeServerError, err
}
if response == nil {
return nil, common.CodeServerError, errors.New("empty chat response")
}
return response, common.CodeSuccess, nil
}
@@ -783,13 +794,16 @@ func (m *ModelProviderService) ChatWithMessagesToModelByApiKey(providerName, mod
return nil, common.CodeNotFound, errors.New(fmt.Sprintf("provider %s model %s not found", providerName, modelName))
}
var response string
response, err = providerInfo.ModelDriver.ChatWithMessages(modelName, &apiKey, messages, nil)
var response *modelModule.ChatResponse
response, err = providerInfo.ModelDriver.ChatWithMessages(modelName, &modelModule.APIConfig{ApiKey: &apiKey}, messages, nil)
if err != nil {
return nil, common.CodeServerError, err
}
if response == nil {
return nil, common.CodeServerError, errors.New("empty chat response")
}
return &response, common.CodeSuccess, nil
return response.Answer, common.CodeSuccess, nil
}
// ChatToModelStreamWithSender streams chat response directly via sender function (best performance, no channel)