From 7931b693dc3177f145389ea2761d546d3014644d Mon Sep 17 00:00:00 2001 From: Haruko386 Date: Sat, 9 May 2026 19:21:13 +0800 Subject: [PATCH] Go: implement provider: Baidu (#14741) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What problem does this PR solve? This PR completes the Baidu Qianfan provider integration in RAGFlow. **The following functionalities are now supported:** - [x] Chat / Think Chat / Stream Chat / Stream Think Chat - [x] Embedding - [x] Rerank - [x] Model listing - [x] Provider connection checking - [ ] Balance ----- **Verified examples from the CLI:** ```plaintext RAGFlow(user)> embed text 'what is rag' 'who are you' with 'embedding-3@test@zhipu-ai' dimension 16; +-----------+-------+ | dimension | index | +-----------+-------+ | 16 | 0 | | 16 | 1 | +-----------+-------+ RAGFlow(user)> rerank query 'what is rag' document 'rag is retrieval augment generation' 'rag need llm' 'famous rag project includes ragflow' with 'qwen3-reranker-4b@test@baidu' top 2; +-------+---------------------+ | index | relevance_score | +-------+---------------------+ | 0 | 0.974821150302887 | | 1 | 0.14223189651966095 | | 2 | 0.08632347732782364 | +-------+---------------------+ RAGFlow(user)> think chat with 'deepseek-v3.2@test@baidu' message 'who r u' Thinking: Hmm, the user is asking for a simple introduction. This is straightforward – no need for overcomplication. I should give a clear, friendly response that covers my basic identity as an AI assistant, my purpose, and my capabilities. Keeping it concise but informative is key here. Mentioning my creator Anthropic adds credibility, and ending with an offer to help invites further interaction. No need for technical details unless the user asks later. Answer: Hello! I'm an AI assistant created by Anthropic, designed to help with a wide variety of tasks. You can think of me as a helpful digital companion—I can answer questions, assist with writing, help solve problems, provide explanations, and engage in conversation on many topics. I'm here to help with whatever you need! How can I assist you today? Time: 8.103902 RAGFlow(user)> stream think chat with 'deepseek-v3.2@test@baidu' message 'who r u' Thinking: mm, the user is asking "who r u" with casual spelling. This is a straightforward identity question. should give a clear, friendly introduction without overcomplicating it. Can start with my core function as an AI assistant, mention my creator, and briefly state my key capabilities. response should be welcoming and invite further interaction since this seems like an introductory question. Keeping it concise but covering the essentials: who I am, what I do, and how I can help. Answer: ! I am DeepSeek, an AI assistant created by DeepSeek Company. I'm designed to help answer questions, provide information, assist with various tasks, and engage in conversations on a wide range of topics. I'm here to assist you with whatever you need - whether it's answering questions, helping with analysis, writing, coding, or just having a friendly chat!Is there anything specific I can help you with today? 😊 Time: 7.219703 RAGFlow(user)> list supported models from 'baidu' 'test' +--------------------------------------+ | model_name | +--------------------------------------+ | ernie-3.5-8k-preview | | ernie-4.0-8k | | ernie-4.0-turbo-8k-latest | | ernie-4.0-turbo-8k-preview | | ernie-4.0-8k-preview | | ernie-speed-pro-128k | | ernie-char-fiction-8k | | ernie-3.5-8k | | ernie-3.5-128k | | ernie-lite-pro-128k | | ernie-novel-8k | | ernie-4.0-turbo-8k | | ernie-4.0-turbo-128k | | ernie-4.0-8k-latest | | irag-1.0 | | ........... | | glm-5.1 | | ernie-image-turbo | | deepseek-v4-pro | | deepseek-v4-flash | | ernie-5.1 | +--------------------------------------+ RAGFlow(user)> check instance 'test' from 'baidu' SUCCESS ``` Additionally, this PR fixes an incorrect error message typo: Before: ```go fmt.Errorf("API requestssss failed with status %d: %s : %s", ...) ``` After: ```go fmt.Errorf("API request failed with status %d: %s", ...) ``` This PR mainly improves provider compatibility, API completeness, and runtime stability. ### Type of change * [x] Bug Fix (non-breaking change which fixes an issue) * [x] New Feature (non-breaking change which adds functionality) * [x] Refactoring --- conf/models/baidu.json | 79 ++++ internal/entity/models/baidu.go | 642 +++++++++++++++++++++++++++ internal/entity/models/factory.go | 2 + internal/entity/models/openrouter.go | 2 +- 4 files changed, 724 insertions(+), 1 deletion(-) create mode 100644 conf/models/baidu.json create mode 100644 internal/entity/models/baidu.go diff --git a/conf/models/baidu.json b/conf/models/baidu.json new file mode 100644 index 000000000..4313b6a6d --- /dev/null +++ b/conf/models/baidu.json @@ -0,0 +1,79 @@ +{ + "Name": "Baidu", + "url": { + "default": "https://qianfan.baidubce.com/v2" + }, + "url_suffix": { + "chat": "chat/completions", + "models": "models", + "embedding": "embeddings", + "rerank": "rerank" + }, + "class": "baidu", + "models": [ + { + "name": "deepseek-v3.2", + "max_tokens": 98304, + "model_types": [ + "chat" + ] + }, + { + "name": "deepseek-v4-flash", + "max_tokens": 1048576, + "model_types": [ + "chat" + ], + "thinking": { + "default_value": true, + "clear_thinking": true + } + }, + { + "name": "deepseek-v4-pro", + "max_tokens": 1048576, + "model_types": [ + "chat" + ], + "thinking": { + "default_value": true, + "clear_thinking": true + } + }, + { + "name": "qwen3-32b", + "max_tokens": 30720, + "model_types":[ + "chat" + ] + }, + { + "name": "qwen3-4b", + "max_tokens": 30720, + "model_types": [ + "chat" + ] + }, + { + "name": "ernie-5.0", + "max_tokens": 121856, + "model_types": [ + "vision" + ] + }, + { + "name": "embedding-v1", + "max_tokens": 384, + "model_types": [ + "embedding" + ] + }, + { + "name": "qwen3-reranker-4b", + "max_tokens": 32768, + "model_types": [ + "rerank" + ] + } + ] +} \ No newline at end of file diff --git a/internal/entity/models/baidu.go b/internal/entity/models/baidu.go new file mode 100644 index 000000000..4f9495020 --- /dev/null +++ b/internal/entity/models/baidu.go @@ -0,0 +1,642 @@ +package models + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "ragflow/internal/common" + "strings" + "time" +) + +type BaiduModel struct { + BaseURL map[string]string + URLSuffix URLSuffix + httpClient *http.Client +} + +func (b *BaiduModel) NewInstance(baseURL map[string]string) ModelDriver { + return &BaiduModel{ + BaseURL: baseURL, + URLSuffix: b.URLSuffix, + httpClient: &http.Client{ + Timeout: 120 * time.Second, + Transport: &http.Transport{ + MaxIdleConns: 10, + MaxIdleConnsPerHost: 100, + IdleConnTimeout: 90 * time.Second, + DisableCompression: false, + }, + }, + } +} + +func NewBaiduModel(baseURL map[string]string, urlSuffix URLSuffix) *BaiduModel { + return &BaiduModel{ + BaseURL: baseURL, + URLSuffix: urlSuffix, + httpClient: &http.Client{ + Timeout: 120 * time.Second, + Transport: &http.Transport{ + MaxConnsPerHost: 10, + MaxIdleConnsPerHost: 100, + IdleConnTimeout: 90 * time.Second, + DisableCompression: false, + }, + }, + } +} + +func (b *BaiduModel) Name() string { + return "baidu" +} + +func (b *BaiduModel) ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, 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", b.BaseURL[region], b.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.Stop != nil { + reqBody["stop"] = *chatModelConfig.Stop + } + + if chatModelConfig.Thinking != nil { + lowerModelName := strings.ToLower(modelName) + + // `enable_think` for qwen and erine + if strings.HasPrefix(lowerModelName, "qwen") || strings.HasPrefix(lowerModelName, "ernie") { + reqBody["enable_thinking"] = *chatModelConfig.Thinking + } else { + if *chatModelConfig.Thinking { + thinkingFlag := "enabled" + + if strings.Contains(lowerModelName, "deepseek-v4") { + effort := "high" + if chatModelConfig.Effort != nil { + effort = *chatModelConfig.Effort + } + switch effort { + case "none", "low", "medium": + thinkingFlag = "disabled" + case "high", "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", + } + } + } + } + } + + 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 := b.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("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") + } + + var reasonContent string + if chatModelConfig != nil && chatModelConfig.Thinking != nil && *chatModelConfig.Thinking { + reasonContent, ok = messageMap["reasoning_content"].(string) + if !ok { + return nil, fmt.Errorf("invalid reasoning content format") + } + // if first char of reasonContent is \n remove the '\n' + if reasonContent != "" && reasonContent[0] == '\n' { + reasonContent = reasonContent[1:] + } + } + + chatResponse := &ChatResponse{ + Answer: &content, + ReasonContent: &reasonContent, + } + + return chatResponse, nil +} + +func (b *BaiduModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, sender func(*string, *string) error) error { + if len(messages) == 0 { + return fmt.Errorf("messages is empty") + } + + var region = "default" + if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" { + region = *apiConfig.Region + } + + url := fmt.Sprintf("%s/%s", strings.TrimSuffix(b.BaseURL[region], "/"), b.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 with streaming enabled + reqBody := map[string]interface{}{ + "model": modelName, + "messages": apiMessages, + "stream": true, + "temperature": 1, + } + + if modelConfig != nil { + if modelConfig.Stream != nil { + reqBody["stream"] = *modelConfig.Stream + } + + if modelConfig.MaxTokens != nil { + reqBody["max_tokens"] = *modelConfig.MaxTokens + } + + if modelConfig.Temperature != nil { + reqBody["temperature"] = *modelConfig.Temperature + } + + if modelConfig.DoSample != nil { + reqBody["do_sample"] = *modelConfig.DoSample + } + + if modelConfig.TopP != nil { + reqBody["top_p"] = *modelConfig.TopP + } + + if modelConfig.Stop != nil { + reqBody["stop"] = *modelConfig.Stop + } + + if modelConfig.Thinking != nil { + lowerModelName := strings.ToLower(modelName) + + // `enable_think` for qwen and erine + if strings.HasPrefix(lowerModelName, "qwen") || strings.HasPrefix(lowerModelName, "ernie") { + reqBody["enable_thinking"] = *modelConfig.Thinking + } else { + if *modelConfig.Thinking { + thinkingFlag := "enabled" + + if strings.Contains(lowerModelName, "deepseek-v4") { + effort := "high" + if modelConfig.Effort != nil { + effort = *modelConfig.Effort + } + switch effort { + case "none", "low", "medium": + thinkingFlag = "disabled" + case "high", "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", + } + } + } + } + } + + 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", *apiConfig.ApiKey)) + + resp, err := b.httpClient.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) + } + + // SSE parsing: read line by line + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + line := scanner.Text() + common.Info(line) + + // SSE data line starts with "data:" + if !strings.HasPrefix(line, "data:") { + continue + } + + // Extract JSON after "data:" + data := strings.TrimSpace(line[5:]) + + // [DONE] marks the end of stream + if data == "[DONE]" { + break + } + + // Parse the JSON event + var event map[string]interface{} + if err = json.Unmarshal([]byte(data), &event); err != nil { + continue + } + + choices, ok := event["choices"].([]interface{}) + if !ok || len(choices) == 0 { + continue + } + + firstChoice, ok := choices[0].(map[string]interface{}) + if !ok { + continue + } + + delta, ok := firstChoice["delta"].(map[string]interface{}) + if !ok { + 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 err := sender(&content, nil); err != nil { + return err + } + } + + finishReason, ok := firstChoice["finish_reason"].(string) + if ok && finishReason != "" { + break + } + } + + // Send [DONE] marker for OpenAI compatibility + endOfStream := "[DONE]" + if err = sender(&endOfStream, nil); err != nil { + return err + } + + return scanner.Err() +} + +func (b *BaiduModel) Encode(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig) ([][]float64, error) { + if len(texts) == 0 { + return [][]float64{}, nil + } + + var region = "default" + if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" { + region = *apiConfig.Region + } + + url := fmt.Sprintf("%s/%s", b.BaseURL[region], b.URLSuffix.Embedding) + + reqBody := map[string]interface{}{ + "model": *modelName, + "input": texts, + } + + 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 := b.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("Baidu embedding API error: status %d, body: %s", resp.StatusCode, string(body)) + } + + var result map[string]interface{} + if err = json.Unmarshal(body, &result); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + dataObj, ok := result["data"].([]interface{}) + if !ok || len(dataObj) == 0 { + return nil, fmt.Errorf("Baidu embedding response contains no data: %s", string(body)) + } + + embeddings := make([][]float64, len(texts)) + + for _, item := range dataObj { + dataMap, ok := item.(map[string]interface{}) + if !ok { + continue + } + + indexFloat, ok := dataMap["index"].(float64) + if !ok { + continue + } + index := int(indexFloat) + + if index < 0 || index >= len(texts) { + continue + } + + embeddingSlice, ok := dataMap["embedding"].([]interface{}) + if !ok { + continue + } + + embedding := make([]float64, len(embeddingSlice)) + for j, v := range embeddingSlice { + switch val := v.(type) { + case float64: + embedding[j] = val + case float32: + embedding[j] = float64(val) + default: + return nil, fmt.Errorf("unexpected embedding value type") + } + } + + embeddings[index] = embedding + } + + return embeddings, nil +} + +func (b *BaiduModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig) (*RerankResponse, error) { + if len(documents) == 0 { + return &RerankResponse{}, nil + } + + var region = "default" + if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" { + region = *apiConfig.Region + } + + url := fmt.Sprintf("%s/%s", strings.TrimSuffix(b.BaseURL[region], "/"), b.URLSuffix.Rerank) + + reqBody := map[string]interface{}{ + "model": *modelName, + "query": query, + "documents": documents, + } + + 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 := b.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("Baidu rerank API error: status %d, body: %s", resp.StatusCode, string(body)) + } + + var rerankResp struct { + Results []struct { + Index int `json:"index"` + RelevanceScore float64 `json:"relevance_score"` + } `json:"results"` + } + + if err := json.Unmarshal(body, &rerankResp); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + var rerankResponse RerankResponse + for _, result := range rerankResp.Results { + rerankResult := RerankResult{ + Index: result.Index, + RelevanceScore: result.RelevanceScore, + } + rerankResponse.Data = append(rerankResponse.Data, rerankResult) + } + + return &rerankResponse, nil +} + +func (b *BaiduModel) ListModels(apiConfig *APIConfig) ([]string, error) { + var region = "default" + if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" { + region = *apiConfig.Region + } + + url := fmt.Sprintf("%s/%s", b.BaseURL[region], b.URLSuffix.Models) + + reqBody := map[string]string{} + + jsonData, err := json.Marshal(reqBody) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + req, err := http.NewRequest("GET", 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 := b.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("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) + } + + // convert result["data"] to []map[string]interface{} + models := make([]string, 0) + for _, model := range result["data"].([]interface{}) { + modelMap := model.(map[string]interface{}) + modelName := modelMap["id"].(string) + models = append(models, modelName) + } + + return models, nil +} + +func (b *BaiduModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) { + return nil, fmt.Errorf(b.Name() + "no such method") +} + +func (b *BaiduModel) CheckConnection(apiConfig *APIConfig) error { + _, err := b.ListModels(apiConfig) + return err +} diff --git a/internal/entity/models/factory.go b/internal/entity/models/factory.go index b38e4ff9d..f4b64271f 100644 --- a/internal/entity/models/factory.go +++ b/internal/entity/models/factory.go @@ -63,6 +63,8 @@ func (f *ModelFactory) CreateModelDriver(providerName string, baseURL map[string return NewOpenRouterModel(baseURL, urlSuffix), nil case "huggingface": return NewHuggingFaceModel(baseURL, urlSuffix), nil + case "baidu": + return NewBaiduModel(baseURL, urlSuffix), nil default: return NewDummyModel(baseURL, urlSuffix), nil } diff --git a/internal/entity/models/openrouter.go b/internal/entity/models/openrouter.go index 505af9ee6..a48707e97 100644 --- a/internal/entity/models/openrouter.go +++ b/internal/entity/models/openrouter.go @@ -575,7 +575,7 @@ func (o *OpenRouterModel) ListModels(apiConfig *APIConfig) ([]string, error) { } if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("API requestssss failed with status %d: %s : %s", resp.StatusCode, string(body), url) + return nil, fmt.Errorf("API request failed with status %d: %s : %s", resp.StatusCode, string(body)) } // Parse response