Files
ragflow/internal/router/router.go

547 lines
22 KiB
Go
Raw Normal View History

//
// 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 router
import (
"github.com/gin-gonic/gin"
"ragflow/internal/handler"
)
type Router struct {
feat[Go]: implement GET /agents/templates (issue #15240) (#15573) ## Summary Port the canvas-template catalogue endpoint to the Go API server. Listed in the Go-API port checklist of #15240. Mirrors `list_agent_template` in `api/apps/restful_apis/agent_api.py`: returns every row from the `canvas_template` table so that the UI can render the template gallery on the New-Agent screen. ## What - `internal/dao/canvas_template.go` — new `CanvasTemplateDAO.GetAll()` ordered by `create_time desc` (newest templates first). - `internal/service/agent.go` — wire the new DAO into `AgentService` and expose `ListTemplates() ([]*entity.CanvasTemplate, error)`. - `internal/handler/agent.go` — new `AgentHandler.ListTemplates` HTTP handler (auth-gated, mirrors Python `@login_required`). - `internal/router/router.go` — `agents.GET("/templates", r.agentHandler.ListTemplates)` registered alongside the existing `GET /agents`. - `internal/handler/agent_test.go` — three new tests covering: success path, empty-list → JSON array (not `null`), and the auth gate. ## Notes - `CanvasTemplate` entity, GORM tags, and DB migration already exist in `internal/entity/canvas.go` and `internal/dao/database.go` — no schema change required. - The handler coerces a `nil` slice to `[]*entity.CanvasTemplate{}` so the JSON payload is always an array (the frontend does `data.map(...)` on it). ## Test plan - [x] `go vet ./internal/handler ./internal/service ./internal/dao ./internal/router` clean - [x] Three unit tests added; existing `TestListAgents_Success` untouched - [ ] CI runs `go test ./internal/handler` with cgo binding linked ## Related - Tracker: #15240
2026-06-04 19:13:30 -07:00
authHandler *handler.AuthHandler
userHandler *handler.UserHandler
tenantHandler *handler.TenantHandler
documentHandler *handler.DocumentHandler
datasetsHandler *handler.DatasetsHandler
systemHandler *handler.SystemHandler
knowledgebaseHandler *handler.KnowledgebaseHandler
chunkHandler *handler.ChunkHandler
llmHandler *handler.LLMHandler
chatHandler *handler.ChatHandler
chatSessionHandler *handler.ChatSessionHandler
connectorHandler *handler.ConnectorHandler
searchHandler *handler.SearchHandler
fileHandler *handler.FileHandler
memoryHandler *handler.MemoryHandler
mcpHandler *handler.MCPHandler
skillSearchHandler *handler.SkillSearchHandler
providerHandler *handler.ProviderHandler
agentHandler *handler.AgentHandler
feat: implement POST /api/v1/searchbots/retrieval_test (#15710) ## What problem does this PR solve? Implements `POST /api/v1/searchbots/retrieval_test` in the Go API server, aligning with the Python `bot_api.py` counterpart. Also applies security hardening and consistency fixes discovered during CTO-level code review: - **Missing endpoint**: `retrieval_test` was not available in Go, requiring Python fallback - **Security**: Both `chunkHandler` and `searchBotHandler` leaked `err.Error()` to API consumers - **Python alignment**: Default values, empty question handling, and `top_k <= 0` validation differed from Python behavior - **Test gaps**: `chunkHandler.RetrievalTest` had zero unit tests; several edge cases uncovered ## Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Bug Fix (non-breaking change which fixes an issue) - [x] Refactoring ## Summary ### New Endpoint - `POST /api/v1/searchbots/retrieval_test` — retrieval test with full field support (page, size, top_k, use_kg, cross_languages, keyword, similarity_threshold, vector_similarity_weight) ### New Type - `common.StringSlice` — JSON type that accepts both `"kb1"` and `["kb1", "kb2"]`, matching Python API flexibility ### Security - Both `searchBotHandler` and `chunkHandler` now use `common.Warn()` + generic error messages instead of leaking `err.Error()` to API consumers - All error responses include consistent `"data": nil` shape - `chunkHandler.RetrievalTest` uses interface-based DI (`chunkService`) to enable testability ### Python Alignment - Handler-level defaults align with Python `bot_api.py` (page=1, size=30, top_k=1024, similarity_threshold=0.0, vector_similarity_weight=0.3) - `top_k <= 0` validation matching Python behavior - Empty/whitespace question returns 200 + empty result (matches `chunk_api.py`) - `chunkHandler` `Datasets` field uses `common.StringSlice` for string-or-array flexibility ### Refactoring - `ChunkServiceIface` → `ChunkRetriever`, `chunkSvcIface` → `chunkService` (Go-conventional naming) - Extracted `applyRetrievalDefaults`, `toRetrievalServiceRequest` from handler body - Regex moved to package-level var in `parseRelatedQuestions` - `service.RetrievalTestRequest.Datasets` type changed to `common.StringSlice` - `chunkHandler` now uses consumer-side interface for DI ### Tests - 37 unit tests across both handlers: auth, validation, defaults, StringSlice edge cases, empty/whitespace KbID, service errors, JSON format, `top_k <= 0`, field mapping verification ## Files Changed | File | Change | |------|--------| | `cmd/server_main.go` | Wire new handler + chunkService + difyRetrievalHandler | | `internal/common/json_types.go` | New StringSlice type | | `internal/common/json_types_test.go` | StringSlice tests | | `internal/handler/chunk.go` | Interface-based DI, security, Python alignment, defaults | | `internal/handler/chunk_test.go` | New — 9 comprehensive tests | | `internal/handler/searchbot.go` | New endpoint + refactoring + `top_k <= 0` validation | | `internal/handler/searchbot_test.go` | 18 tests covering all edge cases | | `internal/router/router.go` | Register new route + difyRetrievalHandler | | `internal/service/chunk.go` | Datasets type → StringSlice, Question binding relaxed | 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 16:16:56 +08:00
searchBotHandler *handler.SearchBotHandler
difyRetrievalHandler *handler.DifyRetrievalHandler
pluginHandler *handler.PluginHandler
}
// NewRouter create router
func NewRouter(
authHandler *handler.AuthHandler,
userHandler *handler.UserHandler,
tenantHandler *handler.TenantHandler,
documentHandler *handler.DocumentHandler,
datasetsHandler *handler.DatasetsHandler,
systemHandler *handler.SystemHandler,
knowledgebaseHandler *handler.KnowledgebaseHandler,
chunkHandler *handler.ChunkHandler,
llmHandler *handler.LLMHandler,
chatHandler *handler.ChatHandler,
chatSessionHandler *handler.ChatSessionHandler,
connectorHandler *handler.ConnectorHandler,
searchHandler *handler.SearchHandler,
fileHandler *handler.FileHandler,
memoryHandler *handler.MemoryHandler,
mcpHandler *handler.MCPHandler,
skillSearchHandler *handler.SkillSearchHandler,
providerHandler *handler.ProviderHandler,
agentHandler *handler.AgentHandler,
feat: implement POST /api/v1/searchbots/retrieval_test (#15710) ## What problem does this PR solve? Implements `POST /api/v1/searchbots/retrieval_test` in the Go API server, aligning with the Python `bot_api.py` counterpart. Also applies security hardening and consistency fixes discovered during CTO-level code review: - **Missing endpoint**: `retrieval_test` was not available in Go, requiring Python fallback - **Security**: Both `chunkHandler` and `searchBotHandler` leaked `err.Error()` to API consumers - **Python alignment**: Default values, empty question handling, and `top_k <= 0` validation differed from Python behavior - **Test gaps**: `chunkHandler.RetrievalTest` had zero unit tests; several edge cases uncovered ## Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Bug Fix (non-breaking change which fixes an issue) - [x] Refactoring ## Summary ### New Endpoint - `POST /api/v1/searchbots/retrieval_test` — retrieval test with full field support (page, size, top_k, use_kg, cross_languages, keyword, similarity_threshold, vector_similarity_weight) ### New Type - `common.StringSlice` — JSON type that accepts both `"kb1"` and `["kb1", "kb2"]`, matching Python API flexibility ### Security - Both `searchBotHandler` and `chunkHandler` now use `common.Warn()` + generic error messages instead of leaking `err.Error()` to API consumers - All error responses include consistent `"data": nil` shape - `chunkHandler.RetrievalTest` uses interface-based DI (`chunkService`) to enable testability ### Python Alignment - Handler-level defaults align with Python `bot_api.py` (page=1, size=30, top_k=1024, similarity_threshold=0.0, vector_similarity_weight=0.3) - `top_k <= 0` validation matching Python behavior - Empty/whitespace question returns 200 + empty result (matches `chunk_api.py`) - `chunkHandler` `Datasets` field uses `common.StringSlice` for string-or-array flexibility ### Refactoring - `ChunkServiceIface` → `ChunkRetriever`, `chunkSvcIface` → `chunkService` (Go-conventional naming) - Extracted `applyRetrievalDefaults`, `toRetrievalServiceRequest` from handler body - Regex moved to package-level var in `parseRelatedQuestions` - `service.RetrievalTestRequest.Datasets` type changed to `common.StringSlice` - `chunkHandler` now uses consumer-side interface for DI ### Tests - 37 unit tests across both handlers: auth, validation, defaults, StringSlice edge cases, empty/whitespace KbID, service errors, JSON format, `top_k <= 0`, field mapping verification ## Files Changed | File | Change | |------|--------| | `cmd/server_main.go` | Wire new handler + chunkService + difyRetrievalHandler | | `internal/common/json_types.go` | New StringSlice type | | `internal/common/json_types_test.go` | StringSlice tests | | `internal/handler/chunk.go` | Interface-based DI, security, Python alignment, defaults | | `internal/handler/chunk_test.go` | New — 9 comprehensive tests | | `internal/handler/searchbot.go` | New endpoint + refactoring + `top_k <= 0` validation | | `internal/handler/searchbot_test.go` | 18 tests covering all edge cases | | `internal/router/router.go` | Register new route + difyRetrievalHandler | | `internal/service/chunk.go` | Datasets type → StringSlice, Question binding relaxed | 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 16:16:56 +08:00
searchBotHandler *handler.SearchBotHandler,
difyRetrievalHandler *handler.DifyRetrievalHandler,
pluginHandler *handler.PluginHandler,
) *Router {
return &Router{
feat[Go]: implement GET /agents/templates (issue #15240) (#15573) ## Summary Port the canvas-template catalogue endpoint to the Go API server. Listed in the Go-API port checklist of #15240. Mirrors `list_agent_template` in `api/apps/restful_apis/agent_api.py`: returns every row from the `canvas_template` table so that the UI can render the template gallery on the New-Agent screen. ## What - `internal/dao/canvas_template.go` — new `CanvasTemplateDAO.GetAll()` ordered by `create_time desc` (newest templates first). - `internal/service/agent.go` — wire the new DAO into `AgentService` and expose `ListTemplates() ([]*entity.CanvasTemplate, error)`. - `internal/handler/agent.go` — new `AgentHandler.ListTemplates` HTTP handler (auth-gated, mirrors Python `@login_required`). - `internal/router/router.go` — `agents.GET("/templates", r.agentHandler.ListTemplates)` registered alongside the existing `GET /agents`. - `internal/handler/agent_test.go` — three new tests covering: success path, empty-list → JSON array (not `null`), and the auth gate. ## Notes - `CanvasTemplate` entity, GORM tags, and DB migration already exist in `internal/entity/canvas.go` and `internal/dao/database.go` — no schema change required. - The handler coerces a `nil` slice to `[]*entity.CanvasTemplate{}` so the JSON payload is always an array (the frontend does `data.map(...)` on it). ## Test plan - [x] `go vet ./internal/handler ./internal/service ./internal/dao ./internal/router` clean - [x] Three unit tests added; existing `TestListAgents_Success` untouched - [ ] CI runs `go test ./internal/handler` with cgo binding linked ## Related - Tracker: #15240
2026-06-04 19:13:30 -07:00
authHandler: authHandler,
userHandler: userHandler,
tenantHandler: tenantHandler,
documentHandler: documentHandler,
datasetsHandler: datasetsHandler,
systemHandler: systemHandler,
knowledgebaseHandler: knowledgebaseHandler,
chunkHandler: chunkHandler,
llmHandler: llmHandler,
chatHandler: chatHandler,
chatSessionHandler: chatSessionHandler,
connectorHandler: connectorHandler,
searchHandler: searchHandler,
fileHandler: fileHandler,
memoryHandler: memoryHandler,
mcpHandler: mcpHandler,
skillSearchHandler: skillSearchHandler,
providerHandler: providerHandler,
agentHandler: agentHandler,
feat: implement POST /api/v1/searchbots/retrieval_test (#15710) ## What problem does this PR solve? Implements `POST /api/v1/searchbots/retrieval_test` in the Go API server, aligning with the Python `bot_api.py` counterpart. Also applies security hardening and consistency fixes discovered during CTO-level code review: - **Missing endpoint**: `retrieval_test` was not available in Go, requiring Python fallback - **Security**: Both `chunkHandler` and `searchBotHandler` leaked `err.Error()` to API consumers - **Python alignment**: Default values, empty question handling, and `top_k <= 0` validation differed from Python behavior - **Test gaps**: `chunkHandler.RetrievalTest` had zero unit tests; several edge cases uncovered ## Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Bug Fix (non-breaking change which fixes an issue) - [x] Refactoring ## Summary ### New Endpoint - `POST /api/v1/searchbots/retrieval_test` — retrieval test with full field support (page, size, top_k, use_kg, cross_languages, keyword, similarity_threshold, vector_similarity_weight) ### New Type - `common.StringSlice` — JSON type that accepts both `"kb1"` and `["kb1", "kb2"]`, matching Python API flexibility ### Security - Both `searchBotHandler` and `chunkHandler` now use `common.Warn()` + generic error messages instead of leaking `err.Error()` to API consumers - All error responses include consistent `"data": nil` shape - `chunkHandler.RetrievalTest` uses interface-based DI (`chunkService`) to enable testability ### Python Alignment - Handler-level defaults align with Python `bot_api.py` (page=1, size=30, top_k=1024, similarity_threshold=0.0, vector_similarity_weight=0.3) - `top_k <= 0` validation matching Python behavior - Empty/whitespace question returns 200 + empty result (matches `chunk_api.py`) - `chunkHandler` `Datasets` field uses `common.StringSlice` for string-or-array flexibility ### Refactoring - `ChunkServiceIface` → `ChunkRetriever`, `chunkSvcIface` → `chunkService` (Go-conventional naming) - Extracted `applyRetrievalDefaults`, `toRetrievalServiceRequest` from handler body - Regex moved to package-level var in `parseRelatedQuestions` - `service.RetrievalTestRequest.Datasets` type changed to `common.StringSlice` - `chunkHandler` now uses consumer-side interface for DI ### Tests - 37 unit tests across both handlers: auth, validation, defaults, StringSlice edge cases, empty/whitespace KbID, service errors, JSON format, `top_k <= 0`, field mapping verification ## Files Changed | File | Change | |------|--------| | `cmd/server_main.go` | Wire new handler + chunkService + difyRetrievalHandler | | `internal/common/json_types.go` | New StringSlice type | | `internal/common/json_types_test.go` | StringSlice tests | | `internal/handler/chunk.go` | Interface-based DI, security, Python alignment, defaults | | `internal/handler/chunk_test.go` | New — 9 comprehensive tests | | `internal/handler/searchbot.go` | New endpoint + refactoring + `top_k <= 0` validation | | `internal/handler/searchbot_test.go` | 18 tests covering all edge cases | | `internal/router/router.go` | Register new route + difyRetrievalHandler | | `internal/service/chunk.go` | Datasets type → StringSlice, Question binding relaxed | 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 16:16:56 +08:00
searchBotHandler: searchBotHandler,
difyRetrievalHandler: difyRetrievalHandler,
pluginHandler: pluginHandler,
}
}
// Setup setup routes
func (r *Router) Setup(engine *gin.Engine) {
feat: implement GET /api/v1/agents/<agent_id>/versions API (#15629) ## Summary Implement the `GET /api/v1/agents/<agent_id>/versions` endpoint in Go, listing all version snapshots for an agent canvas in descending update time order. ### Changes - **New**: `internal/dao/user_canvas_version.go` — `UserCanvasVersionDAO` with `ListByCanvasID` (ordered by update_time DESC) and `GetByID` - **Modified**: `internal/service/agent.go` — Added `CheckCanvasAccess`, `ListVersions`, `GetVersion` methods - **Modified**: `internal/handler/agent.go` — Added `ListAgentVersions` handler with auth check - **Modified**: `internal/router/router.go` — Registered `GET /:agent_id/versions` route - **New**: `internal/service/agent_test.go` — 5 service-level tests (SQLite in-memory DB, zero mock) - **Modified**: `internal/handler/agent_test.go` — 3 handler-level tests (real DB, pre-authenticated context) ### Testing All 8 tests pass with zero mocking (in-memory SQLite replaces MySQL): ``` === RUN TestListVersions_Success --- PASS === RUN TestListVersions_Empty --- PASS === RUN TestCheckCanvasAccess_Owner --- PASS === RUN TestCheckCanvasAccess_NotOwner --- PASS === RUN TestCheckCanvasAccess_NotFound --- PASS === RUN TestListAgentVersionsHandler_Success --- PASS === RUN TestListAgentVersionsHandler_NoPermission --- PASS === RUN TestListAgentVersionsHandler_CanvasNotFound --- PASS ``` Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:36:26 +08:00
// Mark all responses from Go with a header for debugging.
engine.Use(func(c *gin.Context) {
c.Header("X-API-Source", "go")
c.Next()
})
// Log all HTTP requests.
engine.Use(gin.Logger())
// Health check
engine.GET("/health", r.systemHandler.Health)
// System endpoints
engine.GET("/v1/system/configs", r.systemHandler.GetConfigs)
//engine.POST("/v1/user/register", r.userHandler.Register)
// User logout endpoint
engine.GET("/v1/user/logout", r.userHandler.Logout)
// OAuth callbacks are invoked by third-party providers and cannot rely on
// the RAGFlow auth middleware.
engine.GET("/connectors/gmail/oauth/web/callback", r.connectorHandler.GmailWebOAuthCallback)
engine.GET("/connectors/google-drive/oauth/web/callback", r.connectorHandler.GoogleDriveWebOAuthCallback)
apiNoAuth := engine.Group("/api/v1")
{
apiNoAuth.GET("/system/ping", r.systemHandler.Ping)
apiNoAuth.GET("/system/config", r.systemHandler.GetConfig)
apiNoAuth.GET("/system/version", r.systemHandler.GetVersion)
apiNoAuth.GET("/system/healthz", r.systemHandler.Healthz)
// User login channels endpoint
apiNoAuth.GET("/auth/login/channels", r.userHandler.GetLoginChannels)
// User login by email endpoint
apiNoAuth.POST("/auth/login", r.userHandler.LoginByEmail)
// OAuth / OIDC login routes. The static "channels" segment is
// registered before the wildcard, so gin's tree resolves
// /auth/login/channels to GetLoginChannels and other values to
// OAuthLogin without conflict.
apiNoAuth.GET("/auth/login/:channel", r.userHandler.OAuthLogin)
apiNoAuth.GET("/auth/oauth/:channel/callback", r.userHandler.OAuthCallback)
// Register
apiNoAuth.POST("/users", r.userHandler.Register)
// Document images are embedded directly in pages and match Python's public route.
apiNoAuth.GET("/documents/images/:image_id", r.documentHandler.GetDocumentImage)
// Google redirects here after Gmail / Google Drive web OAuth completes.
apiNoAuth.GET("/connectors/gmail/oauth/web/callback", r.connectorHandler.GmailWebOAuthCallback)
apiNoAuth.GET("/connectors/google-drive/oauth/web/callback", r.connectorHandler.GoogleDriveWebOAuthCallback)
}
// Protected routes
authorized := engine.Group("")
authorized.Use(r.authHandler.AuthMiddleware())
{
// User info endpoint
authorized.GET("/v1/user/info", r.userHandler.Info)
// User tenant info endpoint
authorized.GET("/v1/user/tenant_info", r.tenantHandler.TenantInfo)
// Tenant list endpoint
authorized.GET("/v1/tenant/list", r.tenantHandler.TenantList)
// User settings endpoint
authorized.POST("/v1/user/setting", r.userHandler.Setting)
// User change password endpoint
authorized.POST("/v1/user/setting/password", r.userHandler.ChangePassword)
// User set tenant info endpoint
authorized.POST("/v1/user/set_tenant_info", r.userHandler.SetTenantInfo)
// API v1 route group
v1 := authorized.Group("/api/v1")
{
// Auth routes
auth := v1.Group("/auth")
{
// User logout endpoint
auth.POST("/logout", r.userHandler.Logout)
}
// Users routes
users := v1.Group("/users")
{
users.GET("/me", r.userHandler.Info)
// User settings endpoint
users.PATCH("/me", r.userHandler.Setting)
// User tenant info endpoint
users.GET("/me/models", r.tenantHandler.TenantInfo)
// User set tenant info endpoint
users.PATCH("/me/models", r.userHandler.SetTenantInfo)
}
tenants := v1.Group("/tenants")
{
tenants.GET("", r.tenantHandler.TenantList)
tenants.PATCH("/:tenant_id", r.tenantHandler.AcceptTenantInvite)
tenants.GET("/:tenant_id/users", r.tenantHandler.ListTenantMembers)
tenants.POST("/:tenant_id/users", r.tenantHandler.AddTenantMember)
tenants.DELETE("/:tenant_id/users", r.tenantHandler.RemoveTenantMember)
}
v1.GET("/tenant/list", r.tenantHandler.TenantList)
// Document routes
documents := v1.Group("/documents")
{
documents.POST("", r.documentHandler.CreateDocument)
documents.GET("", r.documentHandler.ListDocuments)
fix(go-api): sync document handler interface and enforce preview acce… (#15688) ### Description This PR syncs the `documentServiceIface` interface and introduces handler methods for document preview, artifact fetching, and downloading in the Go API. It also ensures that strict dataset alignment and access checks are enforced when retrieving or downloading documents. Furthermore, this PR introduces comprehensive unit tests for both the newly added Handler and Service methods to ensure robustness and prevent future regressions. ### Key Changes * **Router & Handler Integration**: * Added and wired new API endpoints in `internal/router/router.go`. * Synchronized the `documentServiceIface` with `GetDocumentArtifact`, `GetDocumentPreview`, and `DownloadDocument`. * Implemented handlers for these endpoints in `internal/handler/document.go`. * **Access & Validation Enforcement**: * Refactored `internal/service/document.go` to strictly check if a document belongs to the requested dataset before allowing downloads or previews. * Added robust artifact file sanitization (`sanitizeArtifactFilename`) and attachment handling (`shouldForceArtifactAttachment`). * **Comprehensive Unit Testing**: * **Handler Layer (`internal/handler/document_test.go`)**: Added mock service implementations and Gin router tests covering success, not-found, and internal error states for all 3 new endpoints. * **Service Layer (`internal/service/document_test.go`)**: Added extensive business logic tests including dataset mismatch checks, non-existent document checks, and artifact file validation.
2026-06-08 11:37:06 +08:00
documents.GET("/artifact/:filename", r.documentHandler.GetDocumentArtifact)
documents.GET("/:id/preview", r.documentHandler.GetDocumentPreview)
documents.GET("/:id", r.documentHandler.GetDocumentByID)
documents.PUT("/:id", r.documentHandler.UpdateDocument)
documents.DELETE("/:id", r.documentHandler.DeleteDocument)
}
// Chat routes
chats := v1.Group("/chats")
{
chats.GET("", r.chatHandler.ListChats)
chats.GET("/:chat_id", r.chatHandler.GetChat)
chats.GET("/:chat_id/sessions", r.chatSessionHandler.ListChatSessions)
}
// Searchbot routes
feat: implement POST /api/v1/searchbots/retrieval_test (#15710) ## What problem does this PR solve? Implements `POST /api/v1/searchbots/retrieval_test` in the Go API server, aligning with the Python `bot_api.py` counterpart. Also applies security hardening and consistency fixes discovered during CTO-level code review: - **Missing endpoint**: `retrieval_test` was not available in Go, requiring Python fallback - **Security**: Both `chunkHandler` and `searchBotHandler` leaked `err.Error()` to API consumers - **Python alignment**: Default values, empty question handling, and `top_k <= 0` validation differed from Python behavior - **Test gaps**: `chunkHandler.RetrievalTest` had zero unit tests; several edge cases uncovered ## Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Bug Fix (non-breaking change which fixes an issue) - [x] Refactoring ## Summary ### New Endpoint - `POST /api/v1/searchbots/retrieval_test` — retrieval test with full field support (page, size, top_k, use_kg, cross_languages, keyword, similarity_threshold, vector_similarity_weight) ### New Type - `common.StringSlice` — JSON type that accepts both `"kb1"` and `["kb1", "kb2"]`, matching Python API flexibility ### Security - Both `searchBotHandler` and `chunkHandler` now use `common.Warn()` + generic error messages instead of leaking `err.Error()` to API consumers - All error responses include consistent `"data": nil` shape - `chunkHandler.RetrievalTest` uses interface-based DI (`chunkService`) to enable testability ### Python Alignment - Handler-level defaults align with Python `bot_api.py` (page=1, size=30, top_k=1024, similarity_threshold=0.0, vector_similarity_weight=0.3) - `top_k <= 0` validation matching Python behavior - Empty/whitespace question returns 200 + empty result (matches `chunk_api.py`) - `chunkHandler` `Datasets` field uses `common.StringSlice` for string-or-array flexibility ### Refactoring - `ChunkServiceIface` → `ChunkRetriever`, `chunkSvcIface` → `chunkService` (Go-conventional naming) - Extracted `applyRetrievalDefaults`, `toRetrievalServiceRequest` from handler body - Regex moved to package-level var in `parseRelatedQuestions` - `service.RetrievalTestRequest.Datasets` type changed to `common.StringSlice` - `chunkHandler` now uses consumer-side interface for DI ### Tests - 37 unit tests across both handlers: auth, validation, defaults, StringSlice edge cases, empty/whitespace KbID, service errors, JSON format, `top_k <= 0`, field mapping verification ## Files Changed | File | Change | |------|--------| | `cmd/server_main.go` | Wire new handler + chunkService + difyRetrievalHandler | | `internal/common/json_types.go` | New StringSlice type | | `internal/common/json_types_test.go` | StringSlice tests | | `internal/handler/chunk.go` | Interface-based DI, security, Python alignment, defaults | | `internal/handler/chunk_test.go` | New — 9 comprehensive tests | | `internal/handler/searchbot.go` | New endpoint + refactoring + `top_k <= 0` validation | | `internal/handler/searchbot_test.go` | 18 tests covering all edge cases | | `internal/router/router.go` | Register new route + difyRetrievalHandler | | `internal/service/chunk.go` | Datasets type → StringSlice, Question binding relaxed | 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 16:16:56 +08:00
v1.POST("/searchbots/related_questions", r.searchBotHandler.Handle)
v1.POST("/searchbots/retrieval_test", r.searchBotHandler.RetrievalTest)
// Dataset routes
datasets := v1.Group("/datasets")
{
datasets.GET("", r.datasetsHandler.ListDatasets)
datasets.GET("/:dataset_id", r.datasetsHandler.GetDataset)
datasets.GET("/:dataset_id/graph", r.datasetsHandler.GetKnowledgeGraph)
datasets.DELETE("/:dataset_id/tags", r.datasetsHandler.RemoveTags)
datasets.DELETE("/:dataset_id/graph", r.datasetsHandler.DeleteKnowledgeGraph)
datasets.POST("", r.datasetsHandler.CreateDataset)
datasets.DELETE("", r.datasetsHandler.DeleteDatasets)
datasets.POST("/search", r.datasetsHandler.SearchDatasets)
datasets.GET("/metadata/flattened", r.datasetsHandler.ListMetadataFlattened)
// Dataset ingestion logs
datasets.GET("/:dataset_id/ingestions/summary", r.datasetsHandler.GetIngestionSummary)
datasets.GET("/:dataset_id/ingestions", r.datasetsHandler.ListIngestionLogs)
datasets.GET("/:dataset_id/ingestions/:log_id", r.datasetsHandler.GetIngestionLog)
// Metadata Config
datasets.GET("/:dataset_id/metadata/config", r.datasetsHandler.GetMetadataConfig)
datasets.PUT("/:dataset_id/metadata/config", r.datasetsHandler.UpdateMetadataConfig)
// Dataset documents
datasets.GET("/:dataset_id/documents", r.documentHandler.ListDocuments)
fix(go-api): sync document handler interface and enforce preview acce… (#15688) ### Description This PR syncs the `documentServiceIface` interface and introduces handler methods for document preview, artifact fetching, and downloading in the Go API. It also ensures that strict dataset alignment and access checks are enforced when retrieving or downloading documents. Furthermore, this PR introduces comprehensive unit tests for both the newly added Handler and Service methods to ensure robustness and prevent future regressions. ### Key Changes * **Router & Handler Integration**: * Added and wired new API endpoints in `internal/router/router.go`. * Synchronized the `documentServiceIface` with `GetDocumentArtifact`, `GetDocumentPreview`, and `DownloadDocument`. * Implemented handlers for these endpoints in `internal/handler/document.go`. * **Access & Validation Enforcement**: * Refactored `internal/service/document.go` to strictly check if a document belongs to the requested dataset before allowing downloads or previews. * Added robust artifact file sanitization (`sanitizeArtifactFilename`) and attachment handling (`shouldForceArtifactAttachment`). * **Comprehensive Unit Testing**: * **Handler Layer (`internal/handler/document_test.go`)**: Added mock service implementations and Gin router tests covering success, not-found, and internal error states for all 3 new endpoints. * **Service Layer (`internal/service/document_test.go`)**: Added extensive business logic tests including dataset mismatch checks, non-existent document checks, and artifact file validation.
2026-06-08 11:37:06 +08:00
datasets.GET("/:dataset_id/documents/:document_id", r.documentHandler.DownloadDocument)
feat: migrate DELETE /api/v1/datasets/:dataset_id/documents to Go (#15577) ## Summary Migrate the batch document deletion endpoint from Python to Go. Two modes supported: explicit `ids` list and `delete_all`. ## Changes | File | Change | |------|--------| | `internal/dao/file2document.go` | Add `GetByDocumentID`, `DeleteByDocumentID` | | `internal/dao/file2document_test.go` | 5 new tests | | `internal/dao/kb_test.go` | 2 new tests (`DecreaseDocumentNum`) | | `internal/service/document.go` | Add `deleteDocumentFull` + `DeleteDocuments`, refactor `DeleteDocument` | | `internal/service/document_test.go` | 10 new tests | | `internal/handler/document.go` | Add `documentServiceIface` + `DeleteDocuments` handler | | `internal/handler/document_test.go` | 7 new tests | | `internal/router/router.go` | Register `DELETE /:dataset_id/documents` | | `cmd/server_main.go` | Support `RAGFLOW_DICT_PATH` env var | | `internal/binding/rag_analyzer.go` | Use `-lpcre2-8` dynamic linking | | `internal/dao/database.go` | Skip Error 1091/1138 during migration | | `internal/service/llm.go` | Fix vet warning | ## Per-document cleanup - Delete tasks from DB - Hard-delete document + decrement KB counters - Delete chunks from document engine (nil-guarded) - Delete metadata from document engine (nil-guarded) - Remove file2document mapping + file record + storage blob ## Test Results **24 unit tests all passing** (7 DAO + 10 service + 7 handler) using SQLite :memory: + gin.TestMode. See [test report](docs/test_report_delete_documents.md) for manual integration test results. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:55:53 +08:00
datasets.DELETE("/:dataset_id/documents", r.documentHandler.DeleteDocuments)
// Dataset document chunk
datasets.GET("/:dataset_id/documents/:document_id/chunks/:chunk_id", r.chunkHandler.Get)
Go: add ingestion server (#15094) ### What problem does this PR solve? 1. Go ingestion server will connected with admin server with gRPC stream 2. Go ingestion server will be responsible for ingestion tasks ``` RAGFlow(admin)> list ingestors; +-----------------+-----------+----------------------------------+---------------------------+----------+------------+--------------+--------+------------+---------------+ | address | cpu_usage | id | last_heartbeat | name | process_id | rss_usage | status | task_count | vms_usage | +-----------------+-----------+----------------------------------+---------------------------+----------+------------+--------------+--------+------------+---------------+ | 127.0.0.1:58564 | 0 | bdd1870eea2646e0aacb8a2cd3307aa2 | 2026-05-24T18:16:17+08:00 | ingestor | 680152 | 212.72265625 | active | 0 | 2589.12109375 | +-----------------+-----------+----------------------------------+---------------------------+----------+------------+--------------+--------+------------+---------------+ RAGFlow(admin)> start ingestion 'abc'; +----------------------------------+ | task_id | +----------------------------------+ | e714777639ca4760ab427b5f211e81ad | +----------------------------------+ RAGFlow(admin)> stop ingestion 'f7bd39d0a724457eb5fdce6d81699776'; +----------------------------------+ | task_id | +----------------------------------+ | f7bd39d0a724457eb5fdce6d81699776 | +----------------------------------+ RAGFlow(admin)> list tasks; +-----+----------------------------------+-------+------+----------------------------------+---------------------------+------------+------------+ | ETA | assign_to | error | from | id | last_update | start_time | status | +-----+----------------------------------+-------+------+----------------------------------+---------------------------+------------+------------+ | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | eae6431da72a40e796cff3a03008091b | 2026-05-24T19:46:03+08:00 | | COMPLETED | | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | 6cccdd174bd049ecb05a774bbb47593f | 2026-05-24T19:46:03+08:00 | | COMPLETED | | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | ef360d777e57485799adb96b30f2b4b8 | 2026-05-24T19:46:03+08:00 | | CANCELED | | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | bcc5c5448cb64de48b6b6171c36fb790 | 2026-05-24T19:46:03+08:00 | | CANCELED | | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | bfc25384c43a443294fe2da979a38ac2 | 2026-05-24T19:46:03+08:00 | | DISPATCHED | | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | 84960537b85d413b8990a9efd5952d67 | 2026-05-24T19:46:04+08:00 | | DISPATCHED | | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | 3d223c1b51e24b36861a3bfb2f1d58d4 | 2026-05-24T19:46:03+08:00 | | CANCELED | | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | e433b0e356b846c89c301621a3c54494 | 2026-05-24T19:46:03+08:00 | | COMPLETED | | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | 7c93a3880f074ebd8eca14e6b51bb7ef | 2026-05-24T19:46:03+08:00 | | COMPLETED | | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | df2e4ef51aaf4390bff9a23f2692486e | 2026-05-24T19:46:04+08:00 | | DISPATCHED | | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | 7377c53010194ef7a83aa206698d66ff | 2026-05-24T19:46:05+08:00 | | DISPATCHED | | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | df64d1a1f9d348e3a2f174c4d7d69e73 | 2026-05-24T19:46:05+08:00 | | DISPATCHED | | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | b59834512e2847e1bdf13ace04b8a456 | 2026-05-24T19:46:06+08:00 | | DISPATCHED | | 0 | 17937da188b84f23a5c10bb87588944b | | CLI | 0064bb0ab69344028d1ecfda053826f4 | 2026-05-24T19:46:03+08:00 | | QUEUED | +-----+----------------------------------+-------+------+----------------------------------+---------------------------+------------+------------+ ``` ### Type of change - [x] New Feature (non-breaking change which adds functionality) --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-25 14:00:08 +08:00
datasets.POST("/:dataset_id/documents/parse", r.documentHandler.ParseDocuments)
datasets.POST("/:dataset_id/documents/stop", r.documentHandler.StopParseDocuments)
datasets.DELETE("/:dataset_id/documents/:document_id/chunks", r.chunkHandler.RemoveChunks)
}
// Search routes
searches := v1.Group("/searches")
{
searches.GET("", r.searchHandler.ListSearches)
searches.POST("", r.searchHandler.CreateSearch)
searches.GET("/:search_id", r.searchHandler.GetSearch)
searches.PUT("/:search_id", r.searchHandler.UpdateSearch)
searches.DELETE("/:search_id", r.searchHandler.DeleteSearch)
}
file := v1.Group("/files")
{
file.POST("", r.fileHandler.UploadFile)
file.GET("", r.fileHandler.ListFiles)
file.DELETE("", r.fileHandler.DeleteFiles)
file.POST("/move", r.fileHandler.MoveFiles)
file.GET("/:id/ancestors", r.fileHandler.GetFileAncestors)
file.GET("/:id/parent", r.fileHandler.GetParentFolder)
file.GET("/:id", r.fileHandler.Download)
}
// Author routes
authors := v1.Group("/authors")
{
authors.GET("/:author_id/documents", r.documentHandler.GetDocumentsByAuthorID)
}
// Memory routes
memory := v1.Group("/memories")
{
memory.POST("", r.memoryHandler.CreateMemory)
memory.PUT("/:memory_id", r.memoryHandler.UpdateMemory)
memory.DELETE("/:memory_id", r.memoryHandler.DeleteMemory)
memory.GET("", r.memoryHandler.ListMemories)
memory.GET("/:memory_id/config", r.memoryHandler.GetMemoryConfig)
memory.GET("/:memory_id", r.memoryHandler.GetMemoryMessages)
}
// TODO: Message routes - Implementation pending - depends on CanvasService, TaskService and embedding engine
// message := v1.Group("/messages")
// {
// message.POST("", r.memoryHandler.AddMessage)
// message.DELETE("/:memory_id/:message_id", r.memoryHandler.ForgetMessage)
// message.PUT("/:memory_id/:message_id", r.memoryHandler.UpdateMessage)
// message.GET("/search", r.memoryHandler.SearchMessage)
// message.GET("", r.memoryHandler.GetMessages)
// message.GET("/:memory_id/:message_id/content", r.memoryHandler.GetMessageContent)
// }
// Skill search routes
skills := v1.Group("/skills")
{
// Skill Space management
skills.GET("/spaces", r.skillSearchHandler.ListSpaces)
skills.POST("/spaces", r.skillSearchHandler.CreateSpace)
skills.GET("/spaces/:space_id", r.skillSearchHandler.GetSpace)
skills.PUT("/spaces/:space_id", r.skillSearchHandler.UpdateSpace)
skills.DELETE("/spaces/:space_id", r.skillSearchHandler.DeleteSpace)
skills.GET("/space/by-folder", r.skillSearchHandler.GetSpaceByFolder)
// Skill search config
skills.GET("/config", r.skillSearchHandler.GetConfig)
skills.POST("/config", r.skillSearchHandler.UpdateConfig)
// Skill search and indexing
skills.POST("/search", r.skillSearchHandler.Search)
skills.POST("/index", r.skillSearchHandler.IndexSkills)
skills.DELETE("/index", r.skillSearchHandler.DeleteSkillIndex)
skills.POST("/reindex", r.skillSearchHandler.Reindex)
}
// provider pool route group
provider := v1.Group("/providers")
{
provider.GET("/", r.providerHandler.ListProviders)
Fix go cli models command and api (#14166) ### What problem does this PR solve? ``` RAGFlow(user)> list providers; +--------------------------------------+----------+-------------------------------------------+--------------+ | base_url | name | tags | total_models | +--------------------------------------+----------+-------------------------------------------+--------------+ | https://open.bigmodel.cn/api/paas/v4 | ZHIPU-AI | LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION | 21 | | https://api.x.ai/v1 | xAI | LLM | 6 | +--------------------------------------+----------+-------------------------------------------+--------------+ RAGFlow(user)> show provider 'zhipu-ai'; +--------------------------------------+----------+-------------------------------------------+--------------+ | base_url | name | tags | total_models | +--------------------------------------+----------+-------------------------------------------+--------------+ | https://open.bigmodel.cn/api/paas/v4 | ZHIPU-AI | LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION | 21 | +--------------------------------------+----------+-------------------------------------------+--------------+ RAGFlow(user)> delete provider 'zhipu-ai'; SUCCESS RAGFlow(user)> add provider 'zhipu-ai'; SUCCESS RAGFlow(user)> create provider 'zhipu-ai' instance 'ccc' 'ccxxccxx'; SUCCESS RAGFlow(user)> list instances from 'zhipu-ai'; +---------------------------------------------------+----------------------------------+--------------+----------------------------------+--------+ | apiKey | id | instanceName | providerID | status | +---------------------------------------------------+----------------------------------+--------------+----------------------------------+--------+ | ccxxccxx | 640dd7ee398711f1bdd838a74640adcc | ccc | d1d59de5398411f1bdd838a74640adcc | active | +---------------------------------------------------+----------------------------------+--------------+----------------------------------+--------+ RAGFlow(user)> list models from 'zhipu-ai'; +----------+------------+---------------+---------------+ | features | max_tokens | model_types | name | +----------+------------+---------------+---------------+ | map[] | 128000 | [chat] | glm-4.7 | | map[] | 128000 | [chat] | glm-4.5 | | map[] | 128000 | [chat] | glm-4.5-x | | map[] | 128000 | [chat] | glm-4.5-air | | map[] | 128000 | [chat] | glm-4.5-airx | | map[] | 128000 | [chat] | glm-4.5-flash | | map[] | 64000 | [image2text] | glm-4.5v | | map[] | 128000 | [chat] | glm-4-plus | | map[] | 128000 | [chat] | glm-4-0520 | | map[] | 128000 | [chat] | glm-4 | | map[] | 8000 | [chat] | glm-4-airx | | map[] | 128000 | [chat] | glm-4-air | | map[] | 128000 | [chat] | glm-4-flash | | map[] | 128000 | [chat] | glm-4-flashx | | map[] | 1000000 | [chat] | glm-4-long | | map[] | 128000 | [chat] | glm-3-turbo | | map[] | 2000 | [image2text] | glm-4v | | map[] | 8192 | [chat] | glm-4-9b | | map[] | 512 | [embedding] | embedding-2 | | map[] | 512 | [embedding] | embedding-3 | | map[] | 4096 | [speech2text] | glm-asr | +----------+------------+---------------+---------------+ RAGFlow(user)> disable model 'glm-4.5-flash' from 'zhipu-ai' 'ccc'; SUCCESS RAGFlow(user)> drop instance 'ccc' from 'zhipu-ai'; SUCCESS RAGFlow(user)> list instances from 'zhipu-ai'; No data to print ``` Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-04-17 09:55:25 +08:00
provider.PUT("/", r.providerHandler.AddProvider)
provider.GET("/:provider_name", r.providerHandler.ShowProvider)
provider.DELETE("/:provider_name", r.providerHandler.DeleteProvider)
provider.GET("/:provider_name/models", r.providerHandler.ListModels)
provider.GET("/:provider_name/models/:model_name", r.providerHandler.ShowModel)
provider.POST("/:provider_name/instances", r.providerHandler.CreateProviderInstance)
provider.GET("/:provider_name/instances", r.providerHandler.ListProviderInstances)
provider.GET("/:provider_name/instances/:instance_name", r.providerHandler.ShowProviderInstance)
provider.GET("/:provider_name/instances/:instance_name/balance", r.providerHandler.ShowInstanceBalance)
provider.GET("/:provider_name/instances/:instance_name/connection", r.providerHandler.CheckInstanceConnection)
provider.GET("/:provider_name/connection", r.providerHandler.CheckConnection)
Go: add file parse command (#14892) ### What problem does this PR solve? ``` RAGFlow(user)> ocr with 'hunyuanocr@test@gitee' file './picture.png' +----------------------------------------------------------+ | text | +----------------------------------------------------------+ | 生活不是等待风暴过去,而是学会在雨中翩翩起舞。 ——佚名 | +----------------------------------------------------------+ RAGFlow(user)> list 'test@gitee' tasks; +---------+----------------------------------+ | status | task_id | +---------+----------------------------------+ | success | C3FX4MQNKY5MGC6ZFMIXIAMJKHCEBQB5 | +---------+----------------------------------+ RAGFlow(user)> show 'test@gitee' task 'C3FX4MQNKY5MGC6ZFMIXIAMJKHCEBQB5'; +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+ | content | index | +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+ | # PDF 1: Purpose of RAGFlow RAGFlow is an open source Retrieval-Augmented Generation (RAG) engine designed to turn raw documents into reliable context for large language models.Its purpose is to make it practical to build an Al assistant that can ans... | 1 | +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+ ``` ### Type of change - [x] New Feature (non-breaking change which adds functionality) --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-15 12:29:52 +08:00
provider.GET("/:provider_name/instances/:instance_name/tasks", r.providerHandler.ListTasks)
provider.GET("/:provider_name/instances/:instance_name/tasks/:task_id", r.providerHandler.ShowTask)
provider.PUT("/:provider_name/instances/:instance_name", r.providerHandler.AlterProviderInstance)
Fix go cli models command and api (#14166) ### What problem does this PR solve? ``` RAGFlow(user)> list providers; +--------------------------------------+----------+-------------------------------------------+--------------+ | base_url | name | tags | total_models | +--------------------------------------+----------+-------------------------------------------+--------------+ | https://open.bigmodel.cn/api/paas/v4 | ZHIPU-AI | LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION | 21 | | https://api.x.ai/v1 | xAI | LLM | 6 | +--------------------------------------+----------+-------------------------------------------+--------------+ RAGFlow(user)> show provider 'zhipu-ai'; +--------------------------------------+----------+-------------------------------------------+--------------+ | base_url | name | tags | total_models | +--------------------------------------+----------+-------------------------------------------+--------------+ | https://open.bigmodel.cn/api/paas/v4 | ZHIPU-AI | LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION | 21 | +--------------------------------------+----------+-------------------------------------------+--------------+ RAGFlow(user)> delete provider 'zhipu-ai'; SUCCESS RAGFlow(user)> add provider 'zhipu-ai'; SUCCESS RAGFlow(user)> create provider 'zhipu-ai' instance 'ccc' 'ccxxccxx'; SUCCESS RAGFlow(user)> list instances from 'zhipu-ai'; +---------------------------------------------------+----------------------------------+--------------+----------------------------------+--------+ | apiKey | id | instanceName | providerID | status | +---------------------------------------------------+----------------------------------+--------------+----------------------------------+--------+ | ccxxccxx | 640dd7ee398711f1bdd838a74640adcc | ccc | d1d59de5398411f1bdd838a74640adcc | active | +---------------------------------------------------+----------------------------------+--------------+----------------------------------+--------+ RAGFlow(user)> list models from 'zhipu-ai'; +----------+------------+---------------+---------------+ | features | max_tokens | model_types | name | +----------+------------+---------------+---------------+ | map[] | 128000 | [chat] | glm-4.7 | | map[] | 128000 | [chat] | glm-4.5 | | map[] | 128000 | [chat] | glm-4.5-x | | map[] | 128000 | [chat] | glm-4.5-air | | map[] | 128000 | [chat] | glm-4.5-airx | | map[] | 128000 | [chat] | glm-4.5-flash | | map[] | 64000 | [image2text] | glm-4.5v | | map[] | 128000 | [chat] | glm-4-plus | | map[] | 128000 | [chat] | glm-4-0520 | | map[] | 128000 | [chat] | glm-4 | | map[] | 8000 | [chat] | glm-4-airx | | map[] | 128000 | [chat] | glm-4-air | | map[] | 128000 | [chat] | glm-4-flash | | map[] | 128000 | [chat] | glm-4-flashx | | map[] | 1000000 | [chat] | glm-4-long | | map[] | 128000 | [chat] | glm-3-turbo | | map[] | 2000 | [image2text] | glm-4v | | map[] | 8192 | [chat] | glm-4-9b | | map[] | 512 | [embedding] | embedding-2 | | map[] | 512 | [embedding] | embedding-3 | | map[] | 4096 | [speech2text] | glm-asr | +----------+------------+---------------+---------------+ RAGFlow(user)> disable model 'glm-4.5-flash' from 'zhipu-ai' 'ccc'; SUCCESS RAGFlow(user)> drop instance 'ccc' from 'zhipu-ai'; SUCCESS RAGFlow(user)> list instances from 'zhipu-ai'; No data to print ``` Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-04-17 09:55:25 +08:00
provider.DELETE("/:provider_name/instances", r.providerHandler.DropProviderInstance)
provider.GET("/:provider_name/instances/:instance_name/models", r.providerHandler.ListInstanceModels)
provider.PATCH("/:provider_name/instances/:instance_name/models/*model_name", r.providerHandler.EnableOrDisableModel)
provider.POST("/:provider_name/instances/:instance_name/models", r.providerHandler.AddModel)
provider.DELETE("/:provider_name/instances/:instance_name/models", r.providerHandler.DropInstanceModels)
v1.POST("/chat/completions", r.providerHandler.ChatToModel)
v1.POST("/embeddings", r.providerHandler.EmbedText)
v1.POST("/rerank", r.providerHandler.RerankDocument)
v1.POST("/audio/transcriptions", r.providerHandler.TranscribeAudio)
v1.POST("/audio/speech", r.providerHandler.AudioSpeech)
v1.POST("/file/ocr", r.providerHandler.OCRFile)
Go: add file parse command (#14892) ### What problem does this PR solve? ``` RAGFlow(user)> ocr with 'hunyuanocr@test@gitee' file './picture.png' +----------------------------------------------------------+ | text | +----------------------------------------------------------+ | 生活不是等待风暴过去,而是学会在雨中翩翩起舞。 ——佚名 | +----------------------------------------------------------+ RAGFlow(user)> list 'test@gitee' tasks; +---------+----------------------------------+ | status | task_id | +---------+----------------------------------+ | success | C3FX4MQNKY5MGC6ZFMIXIAMJKHCEBQB5 | +---------+----------------------------------+ RAGFlow(user)> show 'test@gitee' task 'C3FX4MQNKY5MGC6ZFMIXIAMJKHCEBQB5'; +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+ | content | index | +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+ | # PDF 1: Purpose of RAGFlow RAGFlow is an open source Retrieval-Augmented Generation (RAG) engine designed to turn raw documents into reliable context for large language models.Its purpose is to make it practical to build an Al assistant that can ans... | 1 | +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+ ``` ### Type of change - [x] New Feature (non-breaking change which adds functionality) --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-15 12:29:52 +08:00
v1.POST("/file/parse", r.providerHandler.ParseFile)
}
model := v1.Group("/models")
{
model.GET("/", r.tenantHandler.GetModels)
model.PATCH("/", r.tenantHandler.SetModels)
}
// Agent routes
agents := v1.Group("/agents")
{
agents.GET("", r.agentHandler.ListAgents)
feat[Go]: implement GET /agents/templates (issue #15240) (#15573) ## Summary Port the canvas-template catalogue endpoint to the Go API server. Listed in the Go-API port checklist of #15240. Mirrors `list_agent_template` in `api/apps/restful_apis/agent_api.py`: returns every row from the `canvas_template` table so that the UI can render the template gallery on the New-Agent screen. ## What - `internal/dao/canvas_template.go` — new `CanvasTemplateDAO.GetAll()` ordered by `create_time desc` (newest templates first). - `internal/service/agent.go` — wire the new DAO into `AgentService` and expose `ListTemplates() ([]*entity.CanvasTemplate, error)`. - `internal/handler/agent.go` — new `AgentHandler.ListTemplates` HTTP handler (auth-gated, mirrors Python `@login_required`). - `internal/router/router.go` — `agents.GET("/templates", r.agentHandler.ListTemplates)` registered alongside the existing `GET /agents`. - `internal/handler/agent_test.go` — three new tests covering: success path, empty-list → JSON array (not `null`), and the auth gate. ## Notes - `CanvasTemplate` entity, GORM tags, and DB migration already exist in `internal/entity/canvas.go` and `internal/dao/database.go` — no schema change required. - The handler coerces a `nil` slice to `[]*entity.CanvasTemplate{}` so the JSON payload is always an array (the frontend does `data.map(...)` on it). ## Test plan - [x] `go vet ./internal/handler ./internal/service ./internal/dao ./internal/router` clean - [x] Three unit tests added; existing `TestListAgents_Success` untouched - [ ] CI runs `go test ./internal/handler` with cgo binding linked ## Related - Tracker: #15240
2026-06-04 19:13:30 -07:00
agents.GET("/templates", r.agentHandler.ListTemplates)
feat: implement GET /api/v1/agents/<agent_id>/versions API (#15629) ## Summary Implement the `GET /api/v1/agents/<agent_id>/versions` endpoint in Go, listing all version snapshots for an agent canvas in descending update time order. ### Changes - **New**: `internal/dao/user_canvas_version.go` — `UserCanvasVersionDAO` with `ListByCanvasID` (ordered by update_time DESC) and `GetByID` - **Modified**: `internal/service/agent.go` — Added `CheckCanvasAccess`, `ListVersions`, `GetVersion` methods - **Modified**: `internal/handler/agent.go` — Added `ListAgentVersions` handler with auth check - **Modified**: `internal/router/router.go` — Registered `GET /:agent_id/versions` route - **New**: `internal/service/agent_test.go` — 5 service-level tests (SQLite in-memory DB, zero mock) - **Modified**: `internal/handler/agent_test.go` — 3 handler-level tests (real DB, pre-authenticated context) ### Testing All 8 tests pass with zero mocking (in-memory SQLite replaces MySQL): ``` === RUN TestListVersions_Success --- PASS === RUN TestListVersions_Empty --- PASS === RUN TestCheckCanvasAccess_Owner --- PASS === RUN TestCheckCanvasAccess_NotOwner --- PASS === RUN TestCheckCanvasAccess_NotFound --- PASS === RUN TestListAgentVersionsHandler_Success --- PASS === RUN TestListAgentVersionsHandler_NoPermission --- PASS === RUN TestListAgentVersionsHandler_CanvasNotFound --- PASS ``` Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:36:26 +08:00
agents.GET("/:agent_id/versions", r.agentHandler.ListAgentVersions)
agents.GET("/:agent_id/versions/:version_id", r.agentHandler.GetAgentVersion)
feat: implement POST /api/v1/agents/<agent_id>/upload API (#15633) ## Summary Implement the `POST /api/v1/agents/<agent_id>/upload` endpoint in Go, allowing file uploads associated with agent canvases. ### Changes - **Modified**: `internal/service/agent.go` — Added `CheckCanvasAccess` method (owner + team-level permission semantics) - **Modified**: `internal/handler/agent.go` — Added `UploadAgentFile` handler with auth check, multipart file parsing, and delegation to `FileService`. Added `fileUploader` interface for testability. - **Modified**: `internal/router/router.go` — Registered `POST /:agent_id/upload` route - **Modified**: `cmd/server_main.go` — Wired `fileService` into `AgentHandler` - **New**: `internal/service/agent_test.go` — 4 service-level tests for `CheckCanvasAccess` (owner, team member, private denial, not found) - **New**: `internal/handler/agent_upload_test.go` — 3 handler-level tests (success with fake file service, cross-user denial, empty file rejection) ### Testing All 7 tests pass with zero mocking of the DB layer (in-memory SQLite): ``` === RUN TestCheckCanvasAccess_Owner --- PASS === RUN TestCheckCanvasAccess_NotOwner --- PASS === RUN TestCheckCanvasAccess_PrivateCanvas_Denied --- PASS === RUN TestCheckCanvasAccess_NotFound --- PASS === RUN TestUploadAgentFileHandler_Success --- PASS === RUN TestUploadAgentFileHandler_NoPermission --- PASS === RUN TestUploadAgentFileHandler_NoFiles --- PASS ``` Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:21:47 +08:00
agents.POST("/:agent_id/upload", r.agentHandler.UploadAgentFile)
agents.PUT("/:agent_id/tags", r.agentHandler.UpdateAgentTags)
}
// Plugin routes
plugin := v1.Group("/plugin")
{
plugin.GET("/tools", r.pluginHandler.ListLLMTools)
}
connector := v1.Group("/connectors")
{
connector.GET("/", r.connectorHandler.ListConnectors)
connector.POST("/", r.connectorHandler.CreateConnector)
connector.POST("/google/oauth/web/start", r.connectorHandler.StartGoogleWebOAuth)
connector.POST("/google/oauth/web/result", r.connectorHandler.PollGoogleWebOAuthResult)
connector.GET("/:connector_id", r.connectorHandler.GetConnector)
connector.GET("/:connector_id/logs", r.connectorHandler.ListLogs)
connector.DELETE("/:connector_id", r.connectorHandler.DeleteConnector)
connector.POST("/:connector_id/rebuild", r.connectorHandler.RebuildConnector)
connector.POST("/:connector_id/test", r.connectorHandler.TestConnector)
}
feat(go-api): implement MCP server management endpoints (#15281) ## Summary Ports the MCP (Model Context Protocol) server management endpoints that power `web/src/pages/user-setting/mcp/` from Python (`api/apps/restful_apis/mcp_api.py`) to Go. There were no MCP routes in the Go server before this change. Closes #15275 (subtask of #15240). ## Endpoints implemented (base path `/api/v1`) | Method | Path | Description | |--------|------|-------------| | GET | `/mcp/servers` | List tenant servers (keyword / order / pagination) | | POST | `/mcp/servers` | Create a server | | GET | `/mcp/servers/{mcp_id}` | Get one (`?mode=download` exports config) | | PUT | `/mcp/servers/{mcp_id}` | Update a server | | DELETE | `/mcp/servers/{mcp_id}` | Delete a server | | POST | `/mcp/import` | Bulk import from JSON config | | POST | `/mcp/servers/{mcp_id}/test` | Connect + list tools (see notes) | ## Implementation Follows the existing `handler → service → dao` layering (per PR #14790): - **entity** (`internal/entity/mcp.go`): added `MCPServerType` constants and `IsValidMCPServerType` over the existing `MCPServer` model. - **dao** (`internal/dao/mcp.go`): new `MCPServerDAO` with tenant-scoped CRUD, a keyword filter, and a **whitelisted order-column map** (guards against SQL injection via the caller-supplied `orderby`). - **service** (`internal/service/mcp.go`): new `MCPService` — list/get/export/create/update/delete/import/test — mirroring `MCPServerService` and the `mcp_api` request validation, with sentinel errors for clean code mapping. - **handler** (`internal/handler/mcp.go`): new `MCPHandler` with the seven handlers and Python-compatible response codes. - **router / server_main**: registered the `/mcp` group and wired the handler. ## Deviations from Python (documented in code) 1. **Bulk import is at `POST /mcp/import`, not `/mcp/servers/import`.** gin (v1.9.1) cannot register a static segment and a path param at the same tree node, so `/mcp/servers/import` would collide with `/mcp/servers/:mcp_id` and panic at startup. The frontend should call `/mcp/import`. 2. **No live tool discovery on create/update/import.** The Python path runs `get_mcp_tools` over SSE / streamable-HTTP and stores `variables.tools`. The Go server has no MCP client yet, so these persist `variables`/`headers` but leave `variables.tools` unpopulated. 3. **`/test` returns a data error (`ErrMCPTestUnsupported`)** until a Go MCP client lands. Per the issue, the live-connection path is scoped as a follow-up; the handler still validates `url` + `server_type`. ## Testing - Added `internal/service/mcp_test.go` covering `IsValidMCPServerType` and the `TestServer` validation/short-circuit paths (no DB required). - No Go toolchain was available in the dev environment, so `go build ./...` / `go vet ./...` verification is left to CI. ## Follow-ups - Go MCP client (SSE / streamable-HTTP) to enable live tool discovery and the real `/test` behavior. - Reconcile the `/mcp/import` vs `/mcp/servers/import` path with the frontend. ---------
2026-06-04 23:25:09 -06:00
// MCP server routes. Per-server CRUD ships via separate PRs that
// share the same handler/service: GET list (#15253), GET by id
// (#15254), POST create (#15260, merged), PUT (#15261), DELETE
// (#15262, merged). This PR adds only the non-overlapping
// endpoints: import and test.
mcp := v1.Group("/mcp")
{
mcp.POST("/servers", r.mcpHandler.CreateMCPServer)
mcp.GET("/servers", r.mcpHandler.ListMCPServers)
mcp.PUT("/servers/:mcp_id", r.mcpHandler.UpdateMCPServer)
mcp.DELETE("/servers/:mcp_id", r.mcpHandler.DeleteMCPServer)
mcp.POST("/servers/import", r.mcpHandler.ImportMCPServers)
mcp.POST("/servers/:mcp_id/test", r.mcpHandler.TestMCPServer)
}
system := v1.Group("/system")
{
system.GET("/configs", r.systemHandler.GetConfigs)
system.GET("/status", r.systemHandler.GetStatus)
system.GET("/stats", r.systemHandler.GetStats)
config := system.Group("/config")
{
config.GET("/log", r.systemHandler.GetLogLevel)
config.PUT("/log", r.systemHandler.SetLogLevel)
}
//log := system.Group("/log")
//{
// // /api/v1/system/log GET
// log.GET("", r.systemHandler.GetLogLevel)
// // /api/v1/system/log PUT
// log.PUT("", r.systemHandler.SetLogLevel)
//}
tokens := system.Group("/tokens")
{
// list tokens /api/v1/system/tokens GET
tokens.GET("", r.systemHandler.ListTokens)
// create token /api/v1/system/tokens POST
tokens.POST("", r.systemHandler.CreateToken)
// delete token /api/v1/system/tokens/:token DELETE
tokens.DELETE("/:token", r.systemHandler.DeleteToken)
}
}
}
// Knowledge base routes
kb := v1.Group("/kb")
{
kb.POST("/update", r.knowledgebaseHandler.UpdateKB)
kb.POST("/update_metadata_setting", r.knowledgebaseHandler.UpdateMetadataSetting)
kb.GET("/detail", r.knowledgebaseHandler.GetDetail)
kb.GET("/tags", r.knowledgebaseHandler.ListTagsFromKbs)
kb.GET("/get_meta", r.knowledgebaseHandler.GetMeta)
kb.GET("/basic_info", r.knowledgebaseHandler.GetBasicInfo)
// KB ID specific routes
kbByID := kb.Group("/:kb_id")
{
kbByID.GET("/tags", r.knowledgebaseHandler.ListTags)
kbByID.POST("/rename_tag", r.knowledgebaseHandler.RenameTag)
kbByID.GET("/knowledge_graph", r.knowledgebaseHandler.KnowledgeGraph)
kbByID.DELETE("/knowledge_graph", r.knowledgebaseHandler.DeleteKnowledgeGraph)
}
}
// Tenant routes (per-tenant resources)
tenant := v1.Group("/tenant")
{
tenant.POST("/chunk_store", r.tenantHandler.CreateChunkStore) // Internal API only for GO
tenant.DELETE("/chunk_store", r.tenantHandler.DeleteChunkStore) // Internal API only for GO
tenant.POST("/metadata_store", r.tenantHandler.CreateMetadataStore) // Internal API only for GO
tenant.DELETE("/metadata_store", r.tenantHandler.DeleteMetadataStore) // Internal API only for GO
tenant.POST("/insert_chunks_from_file", r.tenantHandler.InsertChunksFromFile) // Internal API only for GO
tenant.POST("/insert_metadata_from_file", r.tenantHandler.InsertMetadataFromFile) // Internal API only for GO
}
// Document routes
doc := v1.Group("/document")
{
doc.POST("/list", r.documentHandler.ListDocuments)
doc.POST("/metadata/summary", r.documentHandler.MetadataSummary)
doc.POST("/set_meta", r.documentHandler.SetMeta)
doc.POST("/delete_meta", r.documentHandler.DeleteMeta) // Internal API only for GO
}
v1.GET("/thumbnails", r.documentHandler.GetThumbnail)
// Chunk routes
chunk := v1.Group("/chunk")
{
chunk.POST("/list", r.chunkHandler.List)
chunk.POST("/update", r.chunkHandler.UpdateChunk) // Internal API only for GO
}
// Chat routes
chat := authorized.Group("/v1/dialog")
{
chat.POST("/next", r.chatHandler.ListChatsNext)
chat.POST("/set", r.chatHandler.SetDialog)
chat.POST("/rm", r.chatHandler.RemoveChats)
}
// Chat session (conversation) routes
session := authorized.Group("/v1/conversation")
{
session.POST("/set", r.chatSessionHandler.SetChatSession)
session.POST("/rm", r.chatSessionHandler.RemoveChatSessions)
session.GET("/list", r.chatSessionHandler.ListChatSessions)
session.POST("/completion", r.chatSessionHandler.Completion)
}
// Connector routes
connector := authorized.Group("/v1/connector")
{
connector.GET("/list", r.connectorHandler.ListConnectors)
connector.GET("/:connector_id", r.connectorHandler.GetConnector)
connector.POST("/:connector_id/rebuild", r.connectorHandler.RebuildConnector)
}
// File routes
file := authorized.Group("/v1/file")
{
file.GET("/root_folder", r.fileHandler.GetRootFolder)
file.GET("/parent_folder", r.fileHandler.GetParentFolder)
file.GET("/all_parent_folder", r.fileHandler.GetAllParentFolders)
}
}
// Dify retrieval routes
dify := authorized.Group("/api/v1/dify")
{
dify.POST("/retrieval", r.difyRetrievalHandler.Retrieval)
dify.GET("/retrieval", r.difyRetrievalHandler.Retrieval)
}
apiNoAuth.GET("/dify/retrieval/health", r.difyRetrievalHandler.HealthCheck)
// Handle undefined routes
engine.NoRoute(handler.HandleNoRoute)
}