diff --git a/cmd/admin_server.go b/cmd/admin_server.go index 9e87663916..99c438def6 100644 --- a/cmd/admin_server.go +++ b/cmd/admin_server.go @@ -18,6 +18,7 @@ package main import ( "context" + "errors" "flag" "fmt" "net/http" @@ -38,15 +39,6 @@ import ( "ragflow/internal/utility" ) -// AdminServer admin server -type AdminServer struct { - router *admin.Router - handler *admin.Handler - service *admin.Service - engine *gin.Engine - port string -} - func main() { var configPath string flag.StringVar(&configPath, "config", "", "Path to configuration file") @@ -161,7 +153,7 @@ func main() { go func() { logger.Info(fmt.Sprintf("Admin Go Version: %s", utility.GetRAGFlowVersion())) logger.Info(fmt.Sprintf("Starting RAGFlow admin server on port: %d", cfg.Admin.Port)) - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { logger.Fatal("Failed to start server", zap.Error(err)) } }() diff --git a/cmd/server_main.go b/cmd/server_main.go index d1db4ad762..66a56e789a 100644 --- a/cmd/server_main.go +++ b/cmd/server_main.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "flag" "fmt" "net/http" @@ -65,23 +66,22 @@ func main() { } // Override port with command line argument if provided + config := server.GetConfig() if portFlag > 0 { - config := server.GetConfig() config.Server.Port = portFlag logger.Info("Port overridden by command line argument", zap.Int("port", portFlag)) } + if config.Server.Port == 0 { + logger.Fatal("Server port is not configured. Please specify via --port flag or config file.") + } + // Load model providers configuration if err := server.LoadModelProviders(""); err != nil { logger.Fatal("Failed to load model providers", zap.Error(err)) } logger.Info("Model providers loaded", zap.Int("count", len(server.GetModelProviders()))) - config := server.GetConfig() - if config.Server.Port == 0 { - logger.Fatal("Server port is not configured. Please specify via --port flag or config file.") - } - // Reinitialize logger with configured level if different if config.Log.Level != "" && config.Log.Level != "info" { if err := logger.Init(config.Log.Level); err != nil { @@ -232,15 +232,15 @@ func startServer(config *server.Config) { ) logger.Info(fmt.Sprintf("RAGFlow Go Version: %s", utility.GetRAGFlowVersion())) logger.Info(fmt.Sprintf("Server starting on port: %d", config.Server.Port)) - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { logger.Fatal("Failed to start server", zap.Error(err)) } }() // Get local IP address for heartbeat reporting - localIP := utility.GetLocalIP() - if localIP == "" { - localIP = "127.0.0.1" + localIP, err := utility.GetLocalIP() + if err != nil { + logger.Fatal("fail to get local ip address") } // Initialize and start heartbeat reporter to admin server @@ -251,7 +251,7 @@ func startServer(config *server.Config) { localIP, config.Server.Port, ) - if err := heartbeatService.InitHTTPClient(); err != nil { + if err = heartbeatService.InitHTTPClient(); err != nil { logger.Warn("Failed to initialize heartbeat service", zap.Error(err)) } else { // Start heartbeat reporter with 30 seconds interval @@ -280,7 +280,7 @@ func startServer(config *server.Config) { defer cancel() // Shutdown server - if err := srv.Shutdown(ctx); err != nil { + if err = srv.Shutdown(ctx); err != nil { logger.Fatal("Server forced to shutdown", zap.Error(err)) } } diff --git a/conf/models/volcengine.json b/conf/models/volcengine.json new file mode 100644 index 0000000000..3c16adc88c --- /dev/null +++ b/conf/models/volcengine.json @@ -0,0 +1,20 @@ +{ + "name": "VolcEngine", + "url": { + "default": "https://ark.cn-beijing.volces.com/api/v3" + }, + "url_suffix": { + "chat": "chat/completions", + "files": "files" + }, + "series": "volcengine", + "models": [ + { + "name": "doubao-seed-2-0-pro-260215", + "max_tokens": 262144, + "model_types": [ + "chat" + ] + } + ] +} \ No newline at end of file diff --git a/internal/admin/handler.go b/internal/admin/handler.go index f02bd02e53..61f77d509b 100644 --- a/internal/admin/handler.go +++ b/internal/admin/handler.go @@ -105,7 +105,7 @@ func responseWithCode(c *gin.Context, message string, httpCode int, errorCode co } } -// Health health check +// Health check func (h *Handler) Health(c *gin.Context) { c.JSON(200, gin.H{"status": "ok"}) } @@ -135,7 +135,7 @@ func (h *Handler) Login(c *gin.Context) { } // Use userService.LoginByEmail with adminLogin=true - // This allows default admin account to login admin system + // This allows default admin account to log in admin system user, code, err := h.userService.LoginByEmail(&req) if err != nil { c.JSON(http.StatusOK, gin.H{ @@ -1277,5 +1277,5 @@ func (h *Handler) Reports(c *gin.Context) { return } - responseWithCode(c, message, int(http.StatusOK), errCode) + responseWithCode(c, message, http.StatusOK, errCode) } diff --git a/internal/entity/models/factory.go b/internal/entity/models/factory.go index a0ccaa8dca..e6e0c5f1da 100644 --- a/internal/entity/models/factory.go +++ b/internal/entity/models/factory.go @@ -49,6 +49,8 @@ func (f *ModelFactory) CreateModelDriver(providerName string, baseURL map[string return NewGoogleModel(baseURL, urlSuffix), nil case "aliyun": return NewAliyunModel(baseURL, urlSuffix), nil + case "volcengine": + return NewVolcEngine(baseURL, urlSuffix), nil default: return NewDummyModel(baseURL, urlSuffix), nil } diff --git a/internal/entity/models/minimax.go b/internal/entity/models/minimax.go index 836e639b02..011ac4725b 100644 --- a/internal/entity/models/minimax.go +++ b/internal/entity/models/minimax.go @@ -23,14 +23,14 @@ import ( "time" ) -// MinimaxModel implements ModelDriver for Zhipu AI +// MinimaxModel implements ModelDriver for Minimax type MinimaxModel struct { BaseURL map[string]string URLSuffix URLSuffix httpClient *http.Client // Reusable HTTP client with connection pool } -// NewMinimaxModel creates a new Zhipu AI model instance +// NewMinimaxModel creates a new Minimax model instance func NewMinimaxModel(baseURL map[string]string, urlSuffix URLSuffix) *MinimaxModel { return &MinimaxModel{ BaseURL: baseURL, diff --git a/internal/entity/models/volcengine.go b/internal/entity/models/volcengine.go new file mode 100644 index 0000000000..cfe84296ba --- /dev/null +++ b/internal/entity/models/volcengine.go @@ -0,0 +1,114 @@ +// +// Copyright 2026 The InfiniFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package models + +import ( + "fmt" + "io" + "net/http" + "time" +) + +// VolcEngine implements ModelDriver for VolcEngine +type VolcEngine struct { + BaseURL map[string]string + URLSuffix URLSuffix + httpClient *http.Client // Reusable HTTP client with connection pool +} + +// NewVolcEngine creates a new VolcEngine model instance +func NewVolcEngine(baseURL map[string]string, urlSuffix URLSuffix) *VolcEngine { + return &VolcEngine{ + BaseURL: baseURL, + URLSuffix: urlSuffix, + httpClient: &http.Client{ + Timeout: 120 * time.Second, + Transport: &http.Transport{ + MaxIdleConns: 100, + MaxIdleConnsPerHost: 10, + IdleConnTimeout: 90 * time.Second, + DisableCompression: false, + }, + }, + } +} + +func (z *VolcEngine) Name() string { + return "volcengine" +} + +// Chat sends a message and returns response +func (z *VolcEngine) Chat(modelName, message *string, apiConfig *APIConfig, modelConfig *ChatConfig) (*ChatResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +// 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()) +} + +// ChatStreamlyWithSender sends a message and streams response via sender function (best performance, no channel) +func (z *VolcEngine) ChatStreamlyWithSender(modelName, message *string, apiConfig *APIConfig, modelConfig *ChatConfig, sender func(*string, *string) error) error { + return fmt.Errorf("%s, no such method", z.Name()) +} + +// EncodeToEmbedding encodes a list of texts into embeddings +func (z *VolcEngine) EncodeToEmbedding(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig) ([][]float64, error) { + return nil, fmt.Errorf("not implemented") +} + +func (z *VolcEngine) ListModels(apiConfig *APIConfig) ([]string, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *VolcEngine) Balance(apiConfig *APIConfig) (map[string]interface{}, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *VolcEngine) CheckConnection(apiConfig *APIConfig) error { + var region = "default" + if apiConfig.Region != nil { + region = *apiConfig.Region + } + + url := fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.Files) + + req, err := http.NewRequest("GET", url, nil) + 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 := 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)) + } + + return nil +} diff --git a/internal/service/user.go b/internal/service/user.go index 56819c335c..1e550fb886 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -152,10 +152,10 @@ func (s *UserService) Register(req *RegisterRequest) (*entity.User, common.Error now := time.Now().Unix() user.CreateTime = &now user.UpdateTime = &now - now_date := time.Now().Truncate(time.Second) - user.CreateDate = &now_date - user.UpdateDate = &now_date - user.LastLoginTime = &now_date + nowDate := time.Now().Truncate(time.Second) + user.CreateDate = &nowDate + user.UpdateDate = &nowDate + user.LastLoginTime = &nowDate tenantName := req.Nickname + "'s Kingdom" @@ -193,8 +193,8 @@ func (s *UserService) Register(req *RegisterRequest) (*entity.User, common.Error } tenant.CreateTime = &now tenant.UpdateTime = &now - tenant.CreateDate = &now_date - tenant.UpdateDate = &now_date + tenant.CreateDate = &nowDate + tenant.UpdateDate = &nowDate userTenantID := utility.GenerateToken() userTenant := &entity.UserTenant{ @@ -207,8 +207,8 @@ func (s *UserService) Register(req *RegisterRequest) (*entity.User, common.Error } userTenant.CreateTime = &now userTenant.UpdateTime = &now - userTenant.CreateDate = &now_date - userTenant.UpdateDate = &now_date + userTenant.CreateDate = &nowDate + userTenant.UpdateDate = &nowDate fileID := utility.GenerateToken() rootFile := &entity.File{ @@ -222,8 +222,8 @@ func (s *UserService) Register(req *RegisterRequest) (*entity.User, common.Error } rootFile.CreateTime = &now rootFile.UpdateTime = &now - rootFile.CreateDate = &now_date - rootFile.UpdateDate = &now_date + rootFile.CreateDate = &nowDate + rootFile.UpdateDate = &nowDate tenantDAO := dao.NewTenantDAO() userTenantDAO := dao.NewUserTenantDAO() @@ -567,7 +567,7 @@ func (s *UserService) constantTimeCompare(a, b []byte) bool { } // loadPrivateKey loads and decrypts the RSA private key from conf/private.pem -// nolint:staticcheck // DecryptPEMBlock is deprecated but still works for traditional PEM encryption +// nolint:static check // DecryptPEMBlock is deprecated but still works for traditional PEM encryption func (s *UserService) loadPrivateKey() (*rsa.PrivateKey, error) { // Read private key file keyData, err := os.ReadFile("conf/private.pem") diff --git a/internal/utility/network.go b/internal/utility/network.go index bf8ad98201..c851bfd5f0 100644 --- a/internal/utility/network.go +++ b/internal/utility/network.go @@ -17,33 +17,25 @@ package utility import ( + "errors" "net" ) // GetLocalIP returns the first non-loopback local IP address of the host -func GetLocalIP() string { - addrs, err := net.InterfaceAddrs() +func GetLocalIP() (string, error) { + addresses, err := net.InterfaceAddrs() if err != nil { - return "" + return "", err } - for _, addr := range addrs { + for _, addr := range addresses { // Check the address type and skip loopback addresses - if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - return ipnet.IP.String() + if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() { + if ipNet.IP.To4() != nil { + return ipNet.IP.String(), nil } } } - return "" -} - -// GetLocalIPWithFallback returns the local IP address with a fallback value -func GetLocalIPWithFallback(fallback string) string { - ip := GetLocalIP() - if ip == "" { - return fallback - } - return ip + return "", errors.New("no ip address") }