Files
ragflow/internal/router/router.go

703 lines
31 KiB
Go
Raw Permalink 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/common"
"ragflow/internal/handler"
)
type Router struct {
Go: new CLI command, list all models and show model (#15786) ### What problem does this PR solve? ``` RAGFlow(user)> list models; +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | alias | max_tokens | model_types | name | thinking | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | | 1048576 | [chat] | deepseek-v4-flash | map[clear_thinking:true default_value:true] | | | 1048576 | [chat] | deepseek-v4-pro | map[clear_thinking:true default_value:true] | | | 1024000 | [chat] | minimax-m3 | map[clear_thinking:true default_value:true] | | | 64000 | [vision] | glm-4.5v | map[clear_thinking:true default_value:true] | | [baai/bge-m3] | 8192 | [embedding] | bge-m3 | | | [baai/bge-reranker-v2-m3] | 1024 | [rerank] | bge-reranker-v2-m3 | | | | | [tts] | step-audio-tts-3b | | | [qwen/qwen3-asr-1.7b] | | [asr] | qwen3-asr-1.7b | | | [paddleocr-vl-1.5] | | [ocr] | paddleocr-vl-0.9b | | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ RAGFlow(user)> show model 'minimax-m3'; +--------------+---------------------------------------------+ | field | value | +--------------+---------------------------------------------+ | name | minimax-m3 | | max_tokens | 1024000 | | model_types | [chat] | | thinking | map[clear_thinking:true default_value:true] | | class | | | alias | | | ModelTypeMap | | +--------------+---------------------------------------------+ RAGFlow(user)> show model 'baai/bge-m3'; +--------------+---------------+ | field | value | +--------------+---------------+ | model_types | [embedding] | | thinking | | | class | | | alias | [baai/bge-m3] | | ModelTypeMap | | | name | bge-m3 | | max_tokens | 8192 | +--------------+---------------+ ``` --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-06-08 21:38:15 +08:00
authHandler *handler.AuthHandler
userHandler *handler.UserHandler
tenantHandler *handler.TenantHandler
documentHandler *handler.DocumentHandler
datasetsHandler *handler.DatasetsHandler
systemHandler *handler.SystemHandler
chunkHandler *handler.ChunkHandler
llmHandler *handler.LLMHandler
chatHandler *handler.ChatHandler
chatChannelHandler *handler.ChatChannelHandler
langfuseHandler *handler.LangfuseHandler
openaiChatHandler *handler.OpenAIChatHandler
Go: new CLI command, list all models and show model (#15786) ### What problem does this PR solve? ``` RAGFlow(user)> list models; +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | alias | max_tokens | model_types | name | thinking | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | | 1048576 | [chat] | deepseek-v4-flash | map[clear_thinking:true default_value:true] | | | 1048576 | [chat] | deepseek-v4-pro | map[clear_thinking:true default_value:true] | | | 1024000 | [chat] | minimax-m3 | map[clear_thinking:true default_value:true] | | | 64000 | [vision] | glm-4.5v | map[clear_thinking:true default_value:true] | | [baai/bge-m3] | 8192 | [embedding] | bge-m3 | | | [baai/bge-reranker-v2-m3] | 1024 | [rerank] | bge-reranker-v2-m3 | | | | | [tts] | step-audio-tts-3b | | | [qwen/qwen3-asr-1.7b] | | [asr] | qwen3-asr-1.7b | | | [paddleocr-vl-1.5] | | [ocr] | paddleocr-vl-0.9b | | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ RAGFlow(user)> show model 'minimax-m3'; +--------------+---------------------------------------------+ | field | value | +--------------+---------------------------------------------+ | name | minimax-m3 | | max_tokens | 1024000 | | model_types | [chat] | | thinking | map[clear_thinking:true default_value:true] | | class | | | alias | | | ModelTypeMap | | +--------------+---------------------------------------------+ RAGFlow(user)> show model 'baai/bge-m3'; +--------------+---------------+ | field | value | +--------------+---------------+ | model_types | [embedding] | | thinking | | | class | | | alias | [baai/bge-m3] | | ModelTypeMap | | | name | bge-m3 | | max_tokens | 8192 | +--------------+---------------+ ``` --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-06-08 21:38:15 +08:00
chatSessionHandler *handler.ChatSessionHandler
connectorHandler *handler.ConnectorHandler
searchHandler *handler.SearchHandler
fileHandler *handler.FileHandler
memoryHandler *handler.MemoryHandler
mcpHandler *handler.MCPHandler
mcpServerHandler *handler.MCPServerHandler
Go: new CLI command, list all models and show model (#15786) ### What problem does this PR solve? ``` RAGFlow(user)> list models; +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | alias | max_tokens | model_types | name | thinking | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | | 1048576 | [chat] | deepseek-v4-flash | map[clear_thinking:true default_value:true] | | | 1048576 | [chat] | deepseek-v4-pro | map[clear_thinking:true default_value:true] | | | 1024000 | [chat] | minimax-m3 | map[clear_thinking:true default_value:true] | | | 64000 | [vision] | glm-4.5v | map[clear_thinking:true default_value:true] | | [baai/bge-m3] | 8192 | [embedding] | bge-m3 | | | [baai/bge-reranker-v2-m3] | 1024 | [rerank] | bge-reranker-v2-m3 | | | | | [tts] | step-audio-tts-3b | | | [qwen/qwen3-asr-1.7b] | | [asr] | qwen3-asr-1.7b | | | [paddleocr-vl-1.5] | | [ocr] | paddleocr-vl-0.9b | | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ RAGFlow(user)> show model 'minimax-m3'; +--------------+---------------------------------------------+ | field | value | +--------------+---------------------------------------------+ | name | minimax-m3 | | max_tokens | 1024000 | | model_types | [chat] | | thinking | map[clear_thinking:true default_value:true] | | class | | | alias | | | ModelTypeMap | | +--------------+---------------------------------------------+ RAGFlow(user)> show model 'baai/bge-m3'; +--------------+---------------+ | field | value | +--------------+---------------+ | model_types | [embedding] | | thinking | | | class | | | alias | [baai/bge-m3] | | ModelTypeMap | | | name | bge-m3 | | max_tokens | 8192 | +--------------+---------------+ ``` --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-06-08 21:38:15 +08:00
skillSearchHandler *handler.SkillSearchHandler
providerHandler *handler.ProviderHandler
agentHandler *handler.AgentHandler
searchBotHandler *handler.SearchBotHandler
difyRetrievalHandler *handler.DifyRetrievalHandler
pluginHandler *handler.PluginHandler
modelHandler *handler.ModelHandler
Add git-like file commit API (#15978) ### What problem does this PR solve? | # | Method | Endpoint | Description | Git Equivalent | |---|--------|----------|-------------|----------------| | 1 | `POST` | `/api/v1/{prefix}/{folder_id}/commits` | Create a snapshot commit with file changes (add/modify/delete/rename) | `git add` + `git commit` | | 2 | `GET` | `/api/v1/{prefix}/{folder_id}/commits` | List commit history (paginated) | `git log` | | 3 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}` | Get commit detail with file changes | `git show` | | 4 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/files` | List file changes in a commit | `git show --name-status` | | 5 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/diff?from=...&to=...` | Compare two commits and return differences | `git diff` | | 6 | `GET` | `/api/v1/{prefix}/{folder_id}/changes` | Get uncommitted changes (add/modify/delete) | `git status` | | 7 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/tree` | Get the folder tree snapshot at commit time | `git ls-tree` | | 8 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/files/{file_id}/content` | Get a file's content as it existed in a specific commit | `git show HEAD:file` | | 9 | `GET` | `/api/v1/{prefix}/{file_id}/versions` | Get version history for a specific file across all commits | `git log -- file` | Where `{prefix}/{id}` can be: - `folders/{folder_id}` — direct folder access - `workspaces/{workspace_id}` — alias of `folders/{folder_id}` - `datasets/{dataset_id}` — resolves to the dataset's folder - `memories/{memory_id}` — resolves to the memory's folder - `skills/{skill_id}` — resolves to the skill's folder ### Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Documentation Update
2026-06-15 11:19:56 +08:00
fileCommitHandler *handler.FileCommitHandler
feat(agent): Go port — canvas engine, 22 components, DSL v2, 13 endpoints (#15952) Ports the agent canvas subsystem from Python to Go. ## What's included ### Canvas Engine (Phase 0/1) - State engine, scheduler, variable resolver, Redis checkpoint store, cancel protocol - **209 tests** across canvas / component / io packages ### 22 Components (P0–P4) | Tier | Components | |---|---| | P0 T1+T2+T3 | LLM, Agent, ExitLoop, Switch, Categorize, Begin, Message, Invoke | | P1 T3 | VariableAggregator, VariableAssigner, StringTransform, ListOperations, DataOperations | | P2 T3 | Iteration, IterationItem, Loop, LoopItem | | P3 T3 | UserFillUp, Fillup | | P4 T5 | Browser, ExcelProcessor, DocsGenerator | ### DSL v2 Schema (Phase 2.5) - Typed v2 in-memory model with v1-to-v2 auto-detect converter - v1 legacy field stripping per plan §2.11.7 ### HTTP Endpoints & Bug Fixes (Plans PR1–PR3) - **DELETE SQL bug fix**: gorm v2 `Where("id = ?", id).Delete(...)` pattern - **CreateAgent validation**: title/DSL required, duplicate check, 103 envelope - **13 new endpoints**: templates, prompts, tags, sessions CRUD, chat/completions (SSE + non-stream stubs), rerun, test_db_connection, logs, webhook/logs - **756 Go unit tests** (745 → 756, +18) - **17 → 0 Python integration test failures** (test_agents.py + test_session_management/) ### Tools 21 eino tools: HTTPHelper, search tools, financial/data tools, mandatory stubs ### Infrastructure OTel observability, NATS message queue, DeepDoc gRPC client, SSRF guards, IDOR mitigation
2026-06-12 22:58:28 +08:00
adminRuntimeHandler *handler.AdminRuntimeHandler
feat[Go]: port agent attachment download, chatbot + agentbot completion/info endpoints from Python (#16405) ## Summary Ports five Python agent APIs to Go under the v1 Gin router: - `GET /api/v1/agents/attachments/<attachment_id>/download` - `POST /api/v1/chatbots/<dialog_id>/completions` (SSE) - `GET /api/v1/chatbots/<dialog_id>/info` - `POST /api/v1/agentbots/<agent_id>/completions` (SSE) - `GET /api/v1/agentbots/<agent_id>/inputs` Mirrors the existing Python wire shape (`{code, message, data:{answer,reference,...}}` per Python `canvas_service.completion`) so the iframe SDK and existing JS widgets keep working. ## Behavioural parity with Python | # | Concern | How it's met | |---|---------|--------------| | R0 | Bot routes must not require regular user session | Routes mount on `apiNoAuth` (router.go:198-202), with `BetaAuthMiddleware` only | | R3 | Two SSE formats in Go drift | F2: `AgentChatCompletions` and `AgentbotCompletion` share `service.WriteChatbotRunEvent` | | R7 | `GetBySessionID` returns `(nil, nil)` on miss | Defensive nil-check before `session.UserID != tenantID` | | R8 | Begin component name vs ID | `FindBeginComponentID` resolves name → ID first, then `ExtractComponentInputForm(dsl, beginID)` | | R9 | Defensive PromptConfig parsing | `stringFromMap` helper used for `prologue` and `tavily_api_key` | | R10 | `BetaAuthMiddleware` Bearer-prefix pre-filter | Removed — `GetUserByToken` is called unconditionally, falls back to `GetUserByBetaAPIToken` | | F8 | Multi-turn chatbot history | `ChatbotCompletion` reads prior turns from `session.Message`, appends user turn, calls LLM, persists new pair via new `API4ConversationDAO.Update` | | F9 | UUID gate stricter than plan | Removed — only `filepath.Base` + CR/LF/quote header sanitization remains | | H2 | Defence-in-depth IDOR | `AgentbotCompletion` calls `loadCanvas` before delegating to `RunAgent` | | M2 | SSE error leakage | `WriteChatbotFrame` emits generic `"an internal error occurred"`; real error logged via `common.Error` | ## Verification ```bash $ go vet ./... # clean (only pre-existing issues) $ go build ./... # success $ go test ./internal/handler/ ./internal/service/ ./internal/agent/dsl/ ./internal/common/ ./internal/dao/ ok ragflow/internal/handler 0.617s ok ragflow/internal/service 1.729s ok ragflow/internal/agent/dsl 0.008s ok ragflow/internal/common 0.087s ok ragflow/internal/dao 0.083s ``` 1199 tests pass across 5 packages. ## Known follow-ups (out of scope for this PR) - **F1**: token-level streaming in `ChatbotCompletion` (currently emits one frame per turn) - **F3**: per-route `auth_types` attribute in Go (currently applied via route group middleware) --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-06-27 16:52:21 +08:00
botHandler *handler.BotHandler
componentsHandler *handler.ComponentsHandler
}
// NewRouter create router
func NewRouter(
authHandler *handler.AuthHandler,
userHandler *handler.UserHandler,
tenantHandler *handler.TenantHandler,
documentHandler *handler.DocumentHandler,
datasetsHandler *handler.DatasetsHandler,
systemHandler *handler.SystemHandler,
chunkHandler *handler.ChunkHandler,
llmHandler *handler.LLMHandler,
chatHandler *handler.ChatHandler,
chatChannelHandler *handler.ChatChannelHandler,
langfuseHandler *handler.LangfuseHandler,
chatSessionHandler *handler.ChatSessionHandler,
connectorHandler *handler.ConnectorHandler,
searchHandler *handler.SearchHandler,
fileHandler *handler.FileHandler,
memoryHandler *handler.MemoryHandler,
mcpHandler *handler.MCPHandler,
mcpServerHandler *handler.MCPServerHandler,
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,
Go: new CLI command, list all models and show model (#15786) ### What problem does this PR solve? ``` RAGFlow(user)> list models; +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | alias | max_tokens | model_types | name | thinking | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | | 1048576 | [chat] | deepseek-v4-flash | map[clear_thinking:true default_value:true] | | | 1048576 | [chat] | deepseek-v4-pro | map[clear_thinking:true default_value:true] | | | 1024000 | [chat] | minimax-m3 | map[clear_thinking:true default_value:true] | | | 64000 | [vision] | glm-4.5v | map[clear_thinking:true default_value:true] | | [baai/bge-m3] | 8192 | [embedding] | bge-m3 | | | [baai/bge-reranker-v2-m3] | 1024 | [rerank] | bge-reranker-v2-m3 | | | | | [tts] | step-audio-tts-3b | | | [qwen/qwen3-asr-1.7b] | | [asr] | qwen3-asr-1.7b | | | [paddleocr-vl-1.5] | | [ocr] | paddleocr-vl-0.9b | | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ RAGFlow(user)> show model 'minimax-m3'; +--------------+---------------------------------------------+ | field | value | +--------------+---------------------------------------------+ | name | minimax-m3 | | max_tokens | 1024000 | | model_types | [chat] | | thinking | map[clear_thinking:true default_value:true] | | class | | | alias | | | ModelTypeMap | | +--------------+---------------------------------------------+ RAGFlow(user)> show model 'baai/bge-m3'; +--------------+---------------+ | field | value | +--------------+---------------+ | model_types | [embedding] | | thinking | | | class | | | alias | [baai/bge-m3] | | ModelTypeMap | | | name | bge-m3 | | max_tokens | 8192 | +--------------+---------------+ ``` --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-06-08 21:38:15 +08:00
modelHandler *handler.ModelHandler,
Add git-like file commit API (#15978) ### What problem does this PR solve? | # | Method | Endpoint | Description | Git Equivalent | |---|--------|----------|-------------|----------------| | 1 | `POST` | `/api/v1/{prefix}/{folder_id}/commits` | Create a snapshot commit with file changes (add/modify/delete/rename) | `git add` + `git commit` | | 2 | `GET` | `/api/v1/{prefix}/{folder_id}/commits` | List commit history (paginated) | `git log` | | 3 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}` | Get commit detail with file changes | `git show` | | 4 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/files` | List file changes in a commit | `git show --name-status` | | 5 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/diff?from=...&to=...` | Compare two commits and return differences | `git diff` | | 6 | `GET` | `/api/v1/{prefix}/{folder_id}/changes` | Get uncommitted changes (add/modify/delete) | `git status` | | 7 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/tree` | Get the folder tree snapshot at commit time | `git ls-tree` | | 8 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/files/{file_id}/content` | Get a file's content as it existed in a specific commit | `git show HEAD:file` | | 9 | `GET` | `/api/v1/{prefix}/{file_id}/versions` | Get version history for a specific file across all commits | `git log -- file` | Where `{prefix}/{id}` can be: - `folders/{folder_id}` — direct folder access - `workspaces/{workspace_id}` — alias of `folders/{folder_id}` - `datasets/{dataset_id}` — resolves to the dataset's folder - `memories/{memory_id}` — resolves to the memory's folder - `skills/{skill_id}` — resolves to the skill's folder ### Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Documentation Update
2026-06-15 11:19:56 +08:00
fileCommitHandler *handler.FileCommitHandler,
feat(agent): Go port — canvas engine, 22 components, DSL v2, 13 endpoints (#15952) Ports the agent canvas subsystem from Python to Go. ## What's included ### Canvas Engine (Phase 0/1) - State engine, scheduler, variable resolver, Redis checkpoint store, cancel protocol - **209 tests** across canvas / component / io packages ### 22 Components (P0–P4) | Tier | Components | |---|---| | P0 T1+T2+T3 | LLM, Agent, ExitLoop, Switch, Categorize, Begin, Message, Invoke | | P1 T3 | VariableAggregator, VariableAssigner, StringTransform, ListOperations, DataOperations | | P2 T3 | Iteration, IterationItem, Loop, LoopItem | | P3 T3 | UserFillUp, Fillup | | P4 T5 | Browser, ExcelProcessor, DocsGenerator | ### DSL v2 Schema (Phase 2.5) - Typed v2 in-memory model with v1-to-v2 auto-detect converter - v1 legacy field stripping per plan §2.11.7 ### HTTP Endpoints & Bug Fixes (Plans PR1–PR3) - **DELETE SQL bug fix**: gorm v2 `Where("id = ?", id).Delete(...)` pattern - **CreateAgent validation**: title/DSL required, duplicate check, 103 envelope - **13 new endpoints**: templates, prompts, tags, sessions CRUD, chat/completions (SSE + non-stream stubs), rerun, test_db_connection, logs, webhook/logs - **756 Go unit tests** (745 → 756, +18) - **17 → 0 Python integration test failures** (test_agents.py + test_session_management/) ### Tools 21 eino tools: HTTPHelper, search tools, financial/data tools, mandatory stubs ### Infrastructure OTel observability, NATS message queue, DeepDoc gRPC client, SSRF guards, IDOR mitigation
2026-06-12 22:58:28 +08:00
adminRuntimeHandler *handler.AdminRuntimeHandler,
openaiChatHandler *handler.OpenAIChatHandler,
feat[Go]: port agent attachment download, chatbot + agentbot completion/info endpoints from Python (#16405) ## Summary Ports five Python agent APIs to Go under the v1 Gin router: - `GET /api/v1/agents/attachments/<attachment_id>/download` - `POST /api/v1/chatbots/<dialog_id>/completions` (SSE) - `GET /api/v1/chatbots/<dialog_id>/info` - `POST /api/v1/agentbots/<agent_id>/completions` (SSE) - `GET /api/v1/agentbots/<agent_id>/inputs` Mirrors the existing Python wire shape (`{code, message, data:{answer,reference,...}}` per Python `canvas_service.completion`) so the iframe SDK and existing JS widgets keep working. ## Behavioural parity with Python | # | Concern | How it's met | |---|---------|--------------| | R0 | Bot routes must not require regular user session | Routes mount on `apiNoAuth` (router.go:198-202), with `BetaAuthMiddleware` only | | R3 | Two SSE formats in Go drift | F2: `AgentChatCompletions` and `AgentbotCompletion` share `service.WriteChatbotRunEvent` | | R7 | `GetBySessionID` returns `(nil, nil)` on miss | Defensive nil-check before `session.UserID != tenantID` | | R8 | Begin component name vs ID | `FindBeginComponentID` resolves name → ID first, then `ExtractComponentInputForm(dsl, beginID)` | | R9 | Defensive PromptConfig parsing | `stringFromMap` helper used for `prologue` and `tavily_api_key` | | R10 | `BetaAuthMiddleware` Bearer-prefix pre-filter | Removed — `GetUserByToken` is called unconditionally, falls back to `GetUserByBetaAPIToken` | | F8 | Multi-turn chatbot history | `ChatbotCompletion` reads prior turns from `session.Message`, appends user turn, calls LLM, persists new pair via new `API4ConversationDAO.Update` | | F9 | UUID gate stricter than plan | Removed — only `filepath.Base` + CR/LF/quote header sanitization remains | | H2 | Defence-in-depth IDOR | `AgentbotCompletion` calls `loadCanvas` before delegating to `RunAgent` | | M2 | SSE error leakage | `WriteChatbotFrame` emits generic `"an internal error occurred"`; real error logged via `common.Error` | ## Verification ```bash $ go vet ./... # clean (only pre-existing issues) $ go build ./... # success $ go test ./internal/handler/ ./internal/service/ ./internal/agent/dsl/ ./internal/common/ ./internal/dao/ ok ragflow/internal/handler 0.617s ok ragflow/internal/service 1.729s ok ragflow/internal/agent/dsl 0.008s ok ragflow/internal/common 0.087s ok ragflow/internal/dao 0.083s ``` 1199 tests pass across 5 packages. ## Known follow-ups (out of scope for this PR) - **F1**: token-level streaming in `ChatbotCompletion` (currently emits one frame per turn) - **F3**: per-route `auth_types` attribute in Go (currently applied via route group middleware) --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-06-27 16:52:21 +08:00
botHandler *handler.BotHandler,
componentsHandler *handler.ComponentsHandler,
) *Router {
return &Router{
Go: new CLI command, list all models and show model (#15786) ### What problem does this PR solve? ``` RAGFlow(user)> list models; +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | alias | max_tokens | model_types | name | thinking | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | | 1048576 | [chat] | deepseek-v4-flash | map[clear_thinking:true default_value:true] | | | 1048576 | [chat] | deepseek-v4-pro | map[clear_thinking:true default_value:true] | | | 1024000 | [chat] | minimax-m3 | map[clear_thinking:true default_value:true] | | | 64000 | [vision] | glm-4.5v | map[clear_thinking:true default_value:true] | | [baai/bge-m3] | 8192 | [embedding] | bge-m3 | | | [baai/bge-reranker-v2-m3] | 1024 | [rerank] | bge-reranker-v2-m3 | | | | | [tts] | step-audio-tts-3b | | | [qwen/qwen3-asr-1.7b] | | [asr] | qwen3-asr-1.7b | | | [paddleocr-vl-1.5] | | [ocr] | paddleocr-vl-0.9b | | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ RAGFlow(user)> show model 'minimax-m3'; +--------------+---------------------------------------------+ | field | value | +--------------+---------------------------------------------+ | name | minimax-m3 | | max_tokens | 1024000 | | model_types | [chat] | | thinking | map[clear_thinking:true default_value:true] | | class | | | alias | | | ModelTypeMap | | +--------------+---------------------------------------------+ RAGFlow(user)> show model 'baai/bge-m3'; +--------------+---------------+ | field | value | +--------------+---------------+ | model_types | [embedding] | | thinking | | | class | | | alias | [baai/bge-m3] | | ModelTypeMap | | | name | bge-m3 | | max_tokens | 8192 | +--------------+---------------+ ``` --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-06-08 21:38:15 +08:00
authHandler: authHandler,
userHandler: userHandler,
tenantHandler: tenantHandler,
documentHandler: documentHandler,
datasetsHandler: datasetsHandler,
systemHandler: systemHandler,
chunkHandler: chunkHandler,
llmHandler: llmHandler,
chatHandler: chatHandler,
chatChannelHandler: chatChannelHandler,
langfuseHandler: langfuseHandler,
openaiChatHandler: openaiChatHandler,
Go: new CLI command, list all models and show model (#15786) ### What problem does this PR solve? ``` RAGFlow(user)> list models; +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | alias | max_tokens | model_types | name | thinking | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | | 1048576 | [chat] | deepseek-v4-flash | map[clear_thinking:true default_value:true] | | | 1048576 | [chat] | deepseek-v4-pro | map[clear_thinking:true default_value:true] | | | 1024000 | [chat] | minimax-m3 | map[clear_thinking:true default_value:true] | | | 64000 | [vision] | glm-4.5v | map[clear_thinking:true default_value:true] | | [baai/bge-m3] | 8192 | [embedding] | bge-m3 | | | [baai/bge-reranker-v2-m3] | 1024 | [rerank] | bge-reranker-v2-m3 | | | | | [tts] | step-audio-tts-3b | | | [qwen/qwen3-asr-1.7b] | | [asr] | qwen3-asr-1.7b | | | [paddleocr-vl-1.5] | | [ocr] | paddleocr-vl-0.9b | | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ RAGFlow(user)> show model 'minimax-m3'; +--------------+---------------------------------------------+ | field | value | +--------------+---------------------------------------------+ | name | minimax-m3 | | max_tokens | 1024000 | | model_types | [chat] | | thinking | map[clear_thinking:true default_value:true] | | class | | | alias | | | ModelTypeMap | | +--------------+---------------------------------------------+ RAGFlow(user)> show model 'baai/bge-m3'; +--------------+---------------+ | field | value | +--------------+---------------+ | model_types | [embedding] | | thinking | | | class | | | alias | [baai/bge-m3] | | ModelTypeMap | | | name | bge-m3 | | max_tokens | 8192 | +--------------+---------------+ ``` --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-06-08 21:38:15 +08:00
chatSessionHandler: chatSessionHandler,
connectorHandler: connectorHandler,
searchHandler: searchHandler,
fileHandler: fileHandler,
memoryHandler: memoryHandler,
mcpHandler: mcpHandler,
mcpServerHandler: mcpServerHandler,
Go: new CLI command, list all models and show model (#15786) ### What problem does this PR solve? ``` RAGFlow(user)> list models; +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | alias | max_tokens | model_types | name | thinking | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | | 1048576 | [chat] | deepseek-v4-flash | map[clear_thinking:true default_value:true] | | | 1048576 | [chat] | deepseek-v4-pro | map[clear_thinking:true default_value:true] | | | 1024000 | [chat] | minimax-m3 | map[clear_thinking:true default_value:true] | | | 64000 | [vision] | glm-4.5v | map[clear_thinking:true default_value:true] | | [baai/bge-m3] | 8192 | [embedding] | bge-m3 | | | [baai/bge-reranker-v2-m3] | 1024 | [rerank] | bge-reranker-v2-m3 | | | | | [tts] | step-audio-tts-3b | | | [qwen/qwen3-asr-1.7b] | | [asr] | qwen3-asr-1.7b | | | [paddleocr-vl-1.5] | | [ocr] | paddleocr-vl-0.9b | | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ RAGFlow(user)> show model 'minimax-m3'; +--------------+---------------------------------------------+ | field | value | +--------------+---------------------------------------------+ | name | minimax-m3 | | max_tokens | 1024000 | | model_types | [chat] | | thinking | map[clear_thinking:true default_value:true] | | class | | | alias | | | ModelTypeMap | | +--------------+---------------------------------------------+ RAGFlow(user)> show model 'baai/bge-m3'; +--------------+---------------+ | field | value | +--------------+---------------+ | model_types | [embedding] | | thinking | | | class | | | alias | [baai/bge-m3] | | ModelTypeMap | | | name | bge-m3 | | max_tokens | 8192 | +--------------+---------------+ ``` --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-06-08 21:38:15 +08:00
skillSearchHandler: skillSearchHandler,
providerHandler: providerHandler,
agentHandler: agentHandler,
searchBotHandler: searchBotHandler,
difyRetrievalHandler: difyRetrievalHandler,
pluginHandler: pluginHandler,
modelHandler: modelHandler,
Add git-like file commit API (#15978) ### What problem does this PR solve? | # | Method | Endpoint | Description | Git Equivalent | |---|--------|----------|-------------|----------------| | 1 | `POST` | `/api/v1/{prefix}/{folder_id}/commits` | Create a snapshot commit with file changes (add/modify/delete/rename) | `git add` + `git commit` | | 2 | `GET` | `/api/v1/{prefix}/{folder_id}/commits` | List commit history (paginated) | `git log` | | 3 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}` | Get commit detail with file changes | `git show` | | 4 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/files` | List file changes in a commit | `git show --name-status` | | 5 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/diff?from=...&to=...` | Compare two commits and return differences | `git diff` | | 6 | `GET` | `/api/v1/{prefix}/{folder_id}/changes` | Get uncommitted changes (add/modify/delete) | `git status` | | 7 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/tree` | Get the folder tree snapshot at commit time | `git ls-tree` | | 8 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/files/{file_id}/content` | Get a file's content as it existed in a specific commit | `git show HEAD:file` | | 9 | `GET` | `/api/v1/{prefix}/{file_id}/versions` | Get version history for a specific file across all commits | `git log -- file` | Where `{prefix}/{id}` can be: - `folders/{folder_id}` — direct folder access - `workspaces/{workspace_id}` — alias of `folders/{folder_id}` - `datasets/{dataset_id}` — resolves to the dataset's folder - `memories/{memory_id}` — resolves to the memory's folder - `skills/{skill_id}` — resolves to the skill's folder ### Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Documentation Update
2026-06-15 11:19:56 +08:00
fileCommitHandler: fileCommitHandler,
feat(agent): Go port — canvas engine, 22 components, DSL v2, 13 endpoints (#15952) Ports the agent canvas subsystem from Python to Go. ## What's included ### Canvas Engine (Phase 0/1) - State engine, scheduler, variable resolver, Redis checkpoint store, cancel protocol - **209 tests** across canvas / component / io packages ### 22 Components (P0–P4) | Tier | Components | |---|---| | P0 T1+T2+T3 | LLM, Agent, ExitLoop, Switch, Categorize, Begin, Message, Invoke | | P1 T3 | VariableAggregator, VariableAssigner, StringTransform, ListOperations, DataOperations | | P2 T3 | Iteration, IterationItem, Loop, LoopItem | | P3 T3 | UserFillUp, Fillup | | P4 T5 | Browser, ExcelProcessor, DocsGenerator | ### DSL v2 Schema (Phase 2.5) - Typed v2 in-memory model with v1-to-v2 auto-detect converter - v1 legacy field stripping per plan §2.11.7 ### HTTP Endpoints & Bug Fixes (Plans PR1–PR3) - **DELETE SQL bug fix**: gorm v2 `Where("id = ?", id).Delete(...)` pattern - **CreateAgent validation**: title/DSL required, duplicate check, 103 envelope - **13 new endpoints**: templates, prompts, tags, sessions CRUD, chat/completions (SSE + non-stream stubs), rerun, test_db_connection, logs, webhook/logs - **756 Go unit tests** (745 → 756, +18) - **17 → 0 Python integration test failures** (test_agents.py + test_session_management/) ### Tools 21 eino tools: HTTPHelper, search tools, financial/data tools, mandatory stubs ### Infrastructure OTel observability, NATS message queue, DeepDoc gRPC client, SSRF guards, IDOR mitigation
2026-06-12 22:58:28 +08:00
adminRuntimeHandler: adminRuntimeHandler,
feat[Go]: port agent attachment download, chatbot + agentbot completion/info endpoints from Python (#16405) ## Summary Ports five Python agent APIs to Go under the v1 Gin router: - `GET /api/v1/agents/attachments/<attachment_id>/download` - `POST /api/v1/chatbots/<dialog_id>/completions` (SSE) - `GET /api/v1/chatbots/<dialog_id>/info` - `POST /api/v1/agentbots/<agent_id>/completions` (SSE) - `GET /api/v1/agentbots/<agent_id>/inputs` Mirrors the existing Python wire shape (`{code, message, data:{answer,reference,...}}` per Python `canvas_service.completion`) so the iframe SDK and existing JS widgets keep working. ## Behavioural parity with Python | # | Concern | How it's met | |---|---------|--------------| | R0 | Bot routes must not require regular user session | Routes mount on `apiNoAuth` (router.go:198-202), with `BetaAuthMiddleware` only | | R3 | Two SSE formats in Go drift | F2: `AgentChatCompletions` and `AgentbotCompletion` share `service.WriteChatbotRunEvent` | | R7 | `GetBySessionID` returns `(nil, nil)` on miss | Defensive nil-check before `session.UserID != tenantID` | | R8 | Begin component name vs ID | `FindBeginComponentID` resolves name → ID first, then `ExtractComponentInputForm(dsl, beginID)` | | R9 | Defensive PromptConfig parsing | `stringFromMap` helper used for `prologue` and `tavily_api_key` | | R10 | `BetaAuthMiddleware` Bearer-prefix pre-filter | Removed — `GetUserByToken` is called unconditionally, falls back to `GetUserByBetaAPIToken` | | F8 | Multi-turn chatbot history | `ChatbotCompletion` reads prior turns from `session.Message`, appends user turn, calls LLM, persists new pair via new `API4ConversationDAO.Update` | | F9 | UUID gate stricter than plan | Removed — only `filepath.Base` + CR/LF/quote header sanitization remains | | H2 | Defence-in-depth IDOR | `AgentbotCompletion` calls `loadCanvas` before delegating to `RunAgent` | | M2 | SSE error leakage | `WriteChatbotFrame` emits generic `"an internal error occurred"`; real error logged via `common.Error` | ## Verification ```bash $ go vet ./... # clean (only pre-existing issues) $ go build ./... # success $ go test ./internal/handler/ ./internal/service/ ./internal/agent/dsl/ ./internal/common/ ./internal/dao/ ok ragflow/internal/handler 0.617s ok ragflow/internal/service 1.729s ok ragflow/internal/agent/dsl 0.008s ok ragflow/internal/common 0.087s ok ragflow/internal/dao 0.083s ``` 1199 tests pass across 5 packages. ## Known follow-ups (out of scope for this PR) - **F1**: token-level streaming in `ChatbotCompletion` (currently emits one frame per turn) - **F3**: per-route `auth_types` attribute in Go (currently applied via route group middleware) --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-06-27 16:52:21 +08:00
botHandler: botHandler,
componentsHandler: componentsHandler,
}
}
// 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(common.GinLogger())
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
// 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)
engine.GET("/connectors/box/oauth/web/callback", r.connectorHandler.BoxWebOAuthCallback)
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)
// searchbots
apiNoAuth.GET("/searchbots/detail", r.searchBotHandler.SearchbotDetail)
// 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)
// 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)
apiNoAuth.GET("/connectors/box/oauth/web/callback", r.connectorHandler.BoxWebOAuthCallback)
feat(go-api): port forgot-password flow to Go (#15282) (#15290) ## Summary Implements **chunk 1** of #15282 — the four `/api/v1/auth/password/...` endpoints from the login-page Go port. **Chunk 2 (OAuth/OIDC) is deferred** to its own subtask, matching the issue author's own confidence-low recommendation ("multi-provider, stateful redirect flow with external dependencies; recommend its own subtask"). New endpoints, all registered under `apiNoAuth` (forgot-password users are unauthenticated by definition): | Method | Path | Status | |--------|------|--------| | `POST` | `/api/v1/auth/password/forgot/captcha` | new | | `POST` | `/api/v1/auth/password/forgot/otp` | new | | `POST` | `/api/v1/auth/password/forgot/otp/verify` | new | | `POST` | `/api/v1/auth/password/reset` | new | ## Wire compatibility with the Python backend The two backends share state through Redis, so the Go port had to use identical keys, encodings, and constants. Either backend can now validate a code the other minted. - **Redis keys**: `captcha:<email>`, `otp:<email>`, `otp_attempts:<email>`, `otp_last_sent:<email>`, `otp_lock:<email>`, `otp:verified:<email>` — same as `api/utils/web_utils.py`. - **Stored OTP value**: `"<hex_hash>:<hex_salt>"` — same as Python. - **Hash**: HMAC-SHA256 with a `crypto/rand` 16-byte salt — same as `hash_code()`. - **Constants**: `OTP_LENGTH=4`, `OTP_TTL=5min`, `ATTEMPT_LIMIT=5`, `ATTEMPT_LOCK_SECONDS=30min`, `RESEND_COOLDOWN_SECONDS=60s` — all match `api/utils/web_utils.py`. - **Email body**: matches `RESET_CODE_EMAIL_TMPL` byte-for-byte. ## Files ### New | File | Purpose | |---|---| | `internal/utility/otp.go` | OTP/captcha constants, Redis key builders (`CaptchaRedisKey`, `OTPRedisKeys`, `OTPVerifiedRedisKey`), `HashOTPCode`, `GenerateOTPCode` / `GenerateCaptchaCode` / `GenerateOTPSalt` via `crypto/rand`, and `EncodeOTPStorageValue` / `DecodeOTPStorageValue` matching Python's storage shape. | | `internal/utility/smtp.go` | Minimal stdlib `net/smtp` sender. `SendResetCodeEmail(to, otp, ttlMin)` builds an RFC 5322 plain-text message and dispatches via implicit TLS / STARTTLS / plain — same selectors as Python `aiosmtplib`. Returns `SMTPNotConfiguredError` if the config block is empty. | ### Modified | File | Change | |---|---| | `internal/server/config.go` | New `SMTPConfig` struct + `Config.SMTP` field. Field names mirror the `smtp:` keys in `common/settings.py` (`mail_server`, `mail_port`, `mail_use_ssl`, `mail_use_tls`, `mail_username`, `mail_password`, `mail_from_name`, `mail_from_address`, `mail_frontend_url`) so a single `conf/service_conf.yaml` powers both backends. | | `internal/service/user.go` | Four methods — `ForgotIssueCaptcha`, `ForgotSendOTP`, `ForgotVerifyOTP`, `ForgotResetPassword`. Reuses the existing `decryptPassword`, `HashPassword`, `userDAO.Update`, and `utility.GenerateToken` so the reset+auto-login path is identical to `LoginByEmail`. | | `internal/handler/user.go` | Four handlers in the same `c.JSON` shape as `LoginByEmail`. The reset handler rotates the access token and emits an `Authorization` header for auto-login (matches Python `construct_response(auth=user.get_id())`). | | `internal/router/router.go` | Routes registered under `apiNoAuth`, with an explanatory comment on why they sit outside the auth middleware. | ## Known divergence — captcha rendering The Python endpoint returns a rendered `image/JPEG` from the `python-captcha` library. The Go side has **no image-captcha dependency vendored** in `go.mod`, and hand-rolling a raster generator was out of scope for this PR. This commit returns JSON `{captcha: "<text>"}` instead. Implications: - **Backend gate is identical** — the OTP step still verifies the user-submitted captcha string against the Redis value, so the security model is unchanged. - **Frontend impact**: the password-reset page rendering needs a small tweak (text display instead of `<img>`) until a Go captcha library is wired in. - The handler comments call this out explicitly so the next PR knows what to swap. Possible follow-ups (any one closes the gap): 1. Add `github.com/mojocn/base64Captcha` or `github.com/dchest/captcha` to `go.mod` and replace the JSON response with an `image/JPEG`. 2. Hand-roll a 5x7 bitmap font + `image/png` writer using only the stdlib. 3. Render a server-side SVG (cheap, but trivially OCR-able — only useful as a UI shim). ## Test plan - [ ] **Captcha**: `POST /api/v1/auth/password/forgot/captcha?email=<existing>` returns `{code: 0, data: {captcha: "ABCD"}}`. Redis shows `captcha:<email>` with that value and ~60s TTL. Unknown email returns `code: CodeDataError`. - [ ] **OTP send**: `POST /api/v1/auth/password/forgot/otp` with the right captcha mints an OTP, stores `<hash>:<salt>` under `otp:<email>` for 5 min, sends an email, returns success. With a wrong captcha returns `CodeAuthenticationError`. Hitting it again within 60s returns "you still have to wait …" with `CodeNotEffective`. - [ ] **OTP verify**: correct OTP → `code: 0`, OTP keys cleared, `otp:verified:<email>` = `"1"`. Wrong OTP → `code: CodeAuthenticationError`, attempt counter bumped; after 5 wrong tries `otp_lock:<email>` is set and further attempts hit `CodeNotEffective`. - [ ] **Reset**: with the verified flag set, supply a new password (RSA-encrypted+base64, same as `LoginByEmail`). Returns `code: 0`, `Authorization` header set, verified flag deleted. Without the verified flag returns `CodeAuthenticationError`. - [ ] **Wire-compat smoke**: mint an OTP from the Python backend, verify it via the Go endpoint, and vice versa. Should both succeed. - [ ] **SMTP misconfigured**: drop `smtp.mail_server` from `conf/service_conf.yaml`. The OTP-send endpoint should now return "failed to send email" without panicking; check the log for the `SMTPNotConfiguredError` warning. - [ ] **End-to-end FE**: hit the password-reset flow from `web/src/pages/login-next/`. Confirm the text-captcha shim works after the FE tweak. - [ ] `go build ./...` and `go vet ./...` — I could not run these in the sandbox; please confirm a clean build before merging. - [ ] `uv run pytest` to confirm no Python regressions (shared Redis schema). ### Type of change - [x] New Feature (non-breaking change which adds functionality)
2026-06-10 06:27:56 -07:00
// Forgot-password flow (fixes #15282).
// Routes are intentionally registered before any auth middleware:
// a user who has forgotten their password is, by definition,
// unauthenticated.
apiNoAuth.POST("/auth/password/forgot/captcha", r.userHandler.ForgotCaptcha)
apiNoAuth.POST("/auth/password/forgot/otp", r.userHandler.ForgotSendOTP)
apiNoAuth.POST("/auth/password/forgot/otp/verify", r.userHandler.ForgotVerifyOTP)
apiNoAuth.POST("/auth/password/reset", r.userHandler.ForgotResetPassword)
apiNoAuth.GET("/dify/retrieval/health", r.difyRetrievalHandler.HealthCheck)
2026-06-29 16:44:21 +08:00
}
// Beta-token routes. Mirrors python's
// @login_required(auth_types=AUTH_BETA) on bot_api.py bot endpoints.
apiBetaAuth := engine.Group("/api/v1")
apiBetaAuth.Use(r.authHandler.BetaAuthMiddleware())
{
searchbotGroup := apiBetaAuth.Group("/searchbots")
searchbotGroup.POST("/related_questions", r.searchBotHandler.Handle)
searchbotGroup.POST("/retrieval_test", r.searchBotHandler.RetrievalTest)
searchbotGroup.POST("/ask", r.searchBotHandler.Ask)
searchbotGroup.POST("/mindmap", r.searchBotHandler.MindMap)
feat[Go]: port agent attachment download, chatbot + agentbot completion/info endpoints from Python (#16405) ## Summary Ports five Python agent APIs to Go under the v1 Gin router: - `GET /api/v1/agents/attachments/<attachment_id>/download` - `POST /api/v1/chatbots/<dialog_id>/completions` (SSE) - `GET /api/v1/chatbots/<dialog_id>/info` - `POST /api/v1/agentbots/<agent_id>/completions` (SSE) - `GET /api/v1/agentbots/<agent_id>/inputs` Mirrors the existing Python wire shape (`{code, message, data:{answer,reference,...}}` per Python `canvas_service.completion`) so the iframe SDK and existing JS widgets keep working. ## Behavioural parity with Python | # | Concern | How it's met | |---|---------|--------------| | R0 | Bot routes must not require regular user session | Routes mount on `apiNoAuth` (router.go:198-202), with `BetaAuthMiddleware` only | | R3 | Two SSE formats in Go drift | F2: `AgentChatCompletions` and `AgentbotCompletion` share `service.WriteChatbotRunEvent` | | R7 | `GetBySessionID` returns `(nil, nil)` on miss | Defensive nil-check before `session.UserID != tenantID` | | R8 | Begin component name vs ID | `FindBeginComponentID` resolves name → ID first, then `ExtractComponentInputForm(dsl, beginID)` | | R9 | Defensive PromptConfig parsing | `stringFromMap` helper used for `prologue` and `tavily_api_key` | | R10 | `BetaAuthMiddleware` Bearer-prefix pre-filter | Removed — `GetUserByToken` is called unconditionally, falls back to `GetUserByBetaAPIToken` | | F8 | Multi-turn chatbot history | `ChatbotCompletion` reads prior turns from `session.Message`, appends user turn, calls LLM, persists new pair via new `API4ConversationDAO.Update` | | F9 | UUID gate stricter than plan | Removed — only `filepath.Base` + CR/LF/quote header sanitization remains | | H2 | Defence-in-depth IDOR | `AgentbotCompletion` calls `loadCanvas` before delegating to `RunAgent` | | M2 | SSE error leakage | `WriteChatbotFrame` emits generic `"an internal error occurred"`; real error logged via `common.Error` | ## Verification ```bash $ go vet ./... # clean (only pre-existing issues) $ go build ./... # success $ go test ./internal/handler/ ./internal/service/ ./internal/agent/dsl/ ./internal/common/ ./internal/dao/ ok ragflow/internal/handler 0.617s ok ragflow/internal/service 1.729s ok ragflow/internal/agent/dsl 0.008s ok ragflow/internal/common 0.087s ok ragflow/internal/dao 0.083s ``` 1199 tests pass across 5 packages. ## Known follow-ups (out of scope for this PR) - **F1**: token-level streaming in `ChatbotCompletion` (currently emits one frame per turn) - **F3**: per-route `auth_types` attribute in Go (currently applied via route group middleware) --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-06-27 16:52:21 +08:00
if r.botHandler != nil {
2026-06-29 16:44:21 +08:00
chatbotGroup := apiBetaAuth.Group("/chatbots")
betaMW := r.authHandler.BetaAuthMiddleware()
RegisterChatbotRoutes(chatbotGroup, betaMW, r.botHandler)
2026-06-29 16:44:21 +08:00
agentbotGroup := apiBetaAuth.Group("/agentbots")
RegisterAgentbotRoutes(agentbotGroup, betaMW, r.botHandler)
feat[Go]: port agent attachment download, chatbot + agentbot completion/info endpoints from Python (#16405) ## Summary Ports five Python agent APIs to Go under the v1 Gin router: - `GET /api/v1/agents/attachments/<attachment_id>/download` - `POST /api/v1/chatbots/<dialog_id>/completions` (SSE) - `GET /api/v1/chatbots/<dialog_id>/info` - `POST /api/v1/agentbots/<agent_id>/completions` (SSE) - `GET /api/v1/agentbots/<agent_id>/inputs` Mirrors the existing Python wire shape (`{code, message, data:{answer,reference,...}}` per Python `canvas_service.completion`) so the iframe SDK and existing JS widgets keep working. ## Behavioural parity with Python | # | Concern | How it's met | |---|---------|--------------| | R0 | Bot routes must not require regular user session | Routes mount on `apiNoAuth` (router.go:198-202), with `BetaAuthMiddleware` only | | R3 | Two SSE formats in Go drift | F2: `AgentChatCompletions` and `AgentbotCompletion` share `service.WriteChatbotRunEvent` | | R7 | `GetBySessionID` returns `(nil, nil)` on miss | Defensive nil-check before `session.UserID != tenantID` | | R8 | Begin component name vs ID | `FindBeginComponentID` resolves name → ID first, then `ExtractComponentInputForm(dsl, beginID)` | | R9 | Defensive PromptConfig parsing | `stringFromMap` helper used for `prologue` and `tavily_api_key` | | R10 | `BetaAuthMiddleware` Bearer-prefix pre-filter | Removed — `GetUserByToken` is called unconditionally, falls back to `GetUserByBetaAPIToken` | | F8 | Multi-turn chatbot history | `ChatbotCompletion` reads prior turns from `session.Message`, appends user turn, calls LLM, persists new pair via new `API4ConversationDAO.Update` | | F9 | UUID gate stricter than plan | Removed — only `filepath.Base` + CR/LF/quote header sanitization remains | | H2 | Defence-in-depth IDOR | `AgentbotCompletion` calls `loadCanvas` before delegating to `RunAgent` | | M2 | SSE error leakage | `WriteChatbotFrame` emits generic `"an internal error occurred"`; real error logged via `common.Error` | ## Verification ```bash $ go vet ./... # clean (only pre-existing issues) $ go build ./... # success $ go test ./internal/handler/ ./internal/service/ ./internal/agent/dsl/ ./internal/common/ ./internal/dao/ ok ragflow/internal/handler 0.617s ok ragflow/internal/service 1.729s ok ragflow/internal/agent/dsl 0.008s ok ragflow/internal/common 0.087s ok ragflow/internal/dao 0.083s ``` 1199 tests pass across 5 packages. ## Known follow-ups (out of scope for this PR) - **F1**: token-level streaming in `ChatbotCompletion` (currently emits one frame per turn) - **F3**: per-route `auth_types` attribute in Go (currently applied via route group middleware) --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-06-27 16:52:21 +08:00
}
feat(go): implement chatbots/<dialog_id>/info and searchbots/detail (#15420) ### What problem does this PR solve? Part of #15240 (rewriting the RAGFlow API server in Go). Implements the two public bot endpoints from `api/apps/restful_apis/bot_api.py`: - **`GET /api/v1/chatbots/<dialog_id>/info`** (`chatbots_inputs`) — returns `{title, avatar, prologue, has_tavily_key}` for a dialog the authenticated tenant owns (tenant match + `status == VALID`), otherwise `"Authentication error: no access to this chatbot!"`. - **`GET /api/v1/searchbots/detail`** (`detail_share_embedded`) — returns search-app detail for a `search_id` the tenant can access. Permission is checked across the tenant's joined tenants; denial returns `"Has no permission for this operation."` (operating error, `data: false`) and a missing app returns `"Can't find this Search App!"`. Both endpoints authenticate with an SDK **beta token** (`Authorization: Bearer <beta>`) rather than a session — the token is resolved to a tenant via `APIToken.query(beta=token)`, backed by a new `APITokenDAO.GetByBeta`. Because they perform their own token-based auth, the routes are registered on the unauthenticated route group (mirroring the Python blueprint, which has no `@login_required`). Both live in a new `internal/handler/bot.go` + `internal/service/bot.go` since they share the same source module. Handler unit tests cover the auth, success, and error-mapping paths. ### Type of change - [x] New Feature (non-breaking change which adds functionality) --------- Co-authored-by: Claude Code <claude@anthropic.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Ling Qin <qinling0210@163.com>
2026-07-02 00:46:00 -10:00
// Public bot endpoints (authenticated with an SDK beta token, not a session)
apiBetaAuth.GET("/documents/:id/preview", r.documentHandler.GetDocumentPreview)
apiBetaAuth.GET("/documents/images/:image_id", r.documentHandler.GetDocumentImage)
apiBetaAuth.GET("/thumbnails", r.documentHandler.GetThumbnail)
// MCP server endpoint — exposes RAGFlow capabilities as MCP tools.
// Uses BetaAuthMiddleware to resolve the user from the
// Authorization header.
if r.mcpServerHandler != nil {
apiBetaAuth.POST("/mcp", r.mcpServerHandler.HandleMCP)
}
}
// 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)
}
// Tenant routes (per-tenant resources)
tenant := v1.Group("/tenant")
{
tenant.GET("/list", r.tenantHandler.TenantList)
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
documents := v1.Group("/documents")
{
documents.POST("", r.documentHandler.CreateDocument)
feat(go-api): Align document metadata batch APIs and upload_info with Python (#16269) ## Summary Align the Go implementations of these APIs with the Python behavior: - `POST /api/v1/datasets/:dataset_id/metadata/update` - `PATCH /api/v1/datasets/:dataset_id/documents/metadatas` - `POST /api/v1/documents/upload` ## What changed - Added the Go routes and handlers for the 3 APIs. - Aligned batch document metadata updates with Python semantics: - support `match` in update items - support list append / replace behavior - support deleting specific list values - remove metadata entirely when it becomes empty - create metadata for documents that previously had none when updates apply - count `updated` only when a document actually changes - Aligned `documents/upload` file uploads with Python-style `upload_info` behavior: - store upload-info blobs in the per-user downloads bucket - return lightweight upload descriptors instead of normal file-management responses - Improved URL upload behavior: - SSRF-guarded fetch with redirect validation - redirect limit aligned to Python behavior - normalize filename and MIME type - add `.pdf` when the fetched content is PDF - normalize HTML content into readable text instead of storing raw HTML shells ## Validation ### Unit tests Passed: - `go test ./internal/service` - `go test ./internal/handler` Also verified targeted cases for: - batch metadata update semantics - upload_info URL handling - upload_info download bucket behavior ### curl checks Verified the new Go endpoints with `curl` and compared the response shape and behavior with Python for: - `POST /api/v1/datasets/{dataset_id}/metadata/update` - `PATCH /api/v1/datasets/{dataset_id}/documents/metadatas` - `POST /api/v1/documents/upload` The Go responses were checked against Python for: - argument validation - success response shape - metadata update results - upload_info result structure - file vs URL input handling
2026-06-24 14:52:47 +08:00
documents.POST("/upload", r.documentHandler.UploadInfo)
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", r.documentHandler.GetDocumentByID)
documents.PUT("/:id", r.documentHandler.UpdateDocument)
documents.DELETE("/:id", r.documentHandler.DeleteDocument)
documents.POST("/ingest", r.documentHandler.Ingest)
}
// Chat routes
chats := v1.Group("/chats")
{
chats.GET("", r.chatHandler.ListChats)
chats.POST("", r.chatHandler.Create)
chats.DELETE("", r.chatHandler.BulkDeleteChats)
chats.DELETE("/:chat_id", r.chatHandler.DeleteChat)
chats.GET("/:chat_id", r.chatHandler.GetChat)
chats.PUT("/:chat_id", r.chatHandler.UpdateChat)
chats.PATCH("/:chat_id", r.chatHandler.PatchChat)
chats.GET("/:chat_id/sessions", r.chatSessionHandler.ListChatSessions)
chats.POST("/:chat_id/sessions", r.chatSessionHandler.CreateSession)
chats.DELETE("/:chat_id/sessions", r.chatSessionHandler.DeleteSessions)
chats.GET("/:chat_id/sessions/:session_id", r.chatSessionHandler.GetSession)
chats.PATCH("/:chat_id/sessions/:session_id", r.chatSessionHandler.UpdateSession)
chats.DELETE("/:chat_id/sessions/:session_id/messages/:msg_id", r.chatSessionHandler.DeleteSessionMessage)
chats.PUT("/:chat_id/sessions/:session_id/messages/:msg_id/feedback", r.chatSessionHandler.UpdateMessageFeedback)
}
chat := v1.Group("/chat")
{
chat.POST("/completions", r.chatSessionHandler.ChatCompletions)
chat.POST("/mindmap", r.chatHandler.MindMap)
chat.POST("/recommendation", r.chatHandler.Recommendation)
}
v1.POST("/openai/:chat_id/chat/completions", r.openaiChatHandler.OpenAIChatCompletions)
// Dataset routes
datasets := v1.Group("/datasets")
{
datasets.GET("", r.datasetsHandler.ListDatasets)
datasets.GET("/tags/aggregation", r.datasetsHandler.AggregateTags)
datasets.GET("/:dataset_id", r.datasetsHandler.GetDataset)
datasets.PUT("/:dataset_id", r.datasetsHandler.UpdateDataset)
datasets.GET("/:dataset_id/graph", r.datasetsHandler.GetKnowledgeGraph)
datasets.GET("/:dataset_id/tags", r.datasetsHandler.ListTags)
datasets.PUT("/:dataset_id/tags", r.datasetsHandler.RenameTag)
datasets.DELETE("/:dataset_id/tags", r.datasetsHandler.RemoveTags)
datasets.POST("/:dataset_id/embedding", r.datasetsHandler.RunEmbedding)
datasets.POST("/:dataset_id/embedding/check", r.datasetsHandler.CheckEmbedding)
datasets.POST("/:dataset_id/documents/batch-update-status", r.documentHandler.BatchUpdateDocumentStatus)
datasets.GET("/:dataset_id/index", r.datasetsHandler.TraceIndex)
datasets.POST("/:dataset_id/index", r.datasetsHandler.RunIndex)
datasets.DELETE("/:dataset_id/index", r.datasetsHandler.DeleteIndex)
datasets.DELETE("/:dataset_id/:index_type", r.datasetsHandler.DeleteIndex)
//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.POST("/:dataset_id/search", r.datasetsHandler.SearchDataset)
datasets.GET("/metadata/flattened", r.datasetsHandler.ListMetadataFlattened)
datasets.GET("/:dataset_id/metadata/summary", r.documentHandler.MetadataSummaryByDataset)
// 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)
datasets.POST("/:dataset_id/documents", r.documentHandler.UploadDocuments)
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)
datasets.PATCH("/:dataset_id/documents/:document_id", r.documentHandler.UpdateDatasetDocument)
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)
datasets.POST("/:dataset_id/documents/:document_id/chunks", r.chunkHandler.AddChunk)
// Dataset document chunk
datasets.GET("/:dataset_id/documents/:document_id/chunks", r.chunkHandler.ListChunks)
datasets.PATCH("/:dataset_id/documents/:document_id/chunks", r.chunkHandler.SwitchChunks)
datasets.GET("/:dataset_id/documents/:document_id/chunks/:chunk_id", r.chunkHandler.Get)
datasets.POST("/:dataset_id/chunks", r.chunkHandler.Parse)
datasets.PATCH("/:dataset_id/documents/:document_id/chunks/:chunk_id", r.chunkHandler.UpdateChunk)
Go: use NATS as the message queue (#15327) ### What problem does this PR solve? ``` RAGFlow(admin)> mq publish 'msg2'; SUCCESS RAGFlow(admin)> mq publish 'msg3'; SUCCESS RAGFlow(admin)> mq list; +---------+---------------+ | message | subject | +---------+---------------+ | msg1 | tasks.RAGFLOW | | msg2 | tasks.RAGFLOW | | msg3 | tasks.RAGFLOW | +---------+---------------+ RAGFlow(admin)> mq pull 2; +---------+---------------+ | message | subject | +---------+---------------+ | msg1 | tasks.RAGFLOW | | msg2 | tasks.RAGFLOW | +---------+---------------+ RAGFlow(admin)> mq pull noack; +---------+---------------+ | message | subject | +---------+---------------+ | abc | tasks.RAGFLOW | +---------+---------------+ RAGFlow(admin)> mq show +-------------------+----------------+--------+---------------+---------------+-------------------+---------------+ | ack_pending_count | consumer_count | memory | message_count | pending_count | redelivered_count | waiting_count | +-------------------+----------------+--------+---------------+---------------+-------------------+---------------+ | 2 | 1 | 0 | 2 | 0 | 1 | 0 | +-------------------+----------------+--------+---------------+---------------+-------------------+---------------+ RAGFlow(admin)> list ingestors; +--------------+-------------------------------------------+--------+ | host | name | status | +--------------+-------------------------------------------+--------+ | 192.168.1.38 | ingestor-8f0e4bd5650a4ac58b0151969fbf6935 | alive | +--------------+-------------------------------------------+--------+ RAGFlow(admin)> list ingestion tasks; +----------------------------------+----------------------------------+-----------+------+-------------+----------------------------------+ | document_id | id | status | step | user | user_id | +----------------------------------+----------------------------------+-----------+------+-------------+----------------------------------+ | ffe64fae423411f1a2d938a74640adcc | 90d3d0f6528941c1ac8eb0360effccc4 | COMPLETED | 5 | aaa@aaa.com | 2ba4881420fa11f19e9c38a74640adcc | +----------------------------------+----------------------------------+-----------+------+-------------+----------------------------------+ RAGFlow(admin)> remove ingestion tasks '90d3d0f6528941c1ac8eb0360effccc4'; +---------+----------------------------------+ | delete | task_id | +---------+----------------------------------+ | success | 90d3d0f6528941c1ac8eb0360effccc4 | +---------+----------------------------------+ RAGFlow(admin)> stop ingestion tasks 'e89e20d9a25848a1b79bd9345ddbfe1d'; +----------+----------------------------------+ | status | task_id | +----------+----------------------------------+ | STOPPING | e89e20d9a25848a1b79bd9345ddbfe1d | +----------+----------------------------------+ # Publish a message RAGFlow(admin)> mq publish 'cdd'; SUCCESS # List current tasks in the message queue RAGFlow(admin)> mq list +----------------------------------+---------------+ | message | subject | +----------------------------------+---------------+ | 7ce392a3c1624cd2be4b5276e8825059 | tasks.RAGFLOW | +----------------------------------+---------------+ # Consume a task from the message queue RAGFlow(admin)> mq pull +------+-----+----------------+ | ack | id | type | +------+-----+----------------+ | true | cdd | ingestion_test | +------+-----+----------------+ # User mode # List ingestion tasks, followed by dataset id RAGFlow(user)> list ingestion tasks from '0abe79f9423311f1ad8d38a74640adcc'; +---------------------------+---------------+----------------------------------+----------------------------------+----------------------------------+--------+-----------+---------------------------+---------------+----------------------------------+ | create_date | create_time | dataset_id | document_id | id | schema | status | update_date | update_time | user_id | +---------------------------+---------------+----------------------------------+----------------------------------+----------------------------------+--------+-----------+---------------------------+---------------+----------------------------------+ | 2026-05-30T20:21:06+08:00 | 1780143666289 | 0abe79f9423311f1ad8d38a74640adcc | ffe64fae423411f1a2d938a74640adcc | 8d758cd14a8b4ba8ab505003fb52017d | | COMPLETED | 2026-05-30T20:21:26+08:00 | 1780143686431 | 2ba4881420fa11f19e9c38a74640adcc | +---------------------------+---------------+----------------------------------+----------------------------------+----------------------------------+--------+-----------+---------------------------+---------------+----------------------------------+ RAGFlow(user)> list ingestion tasks; +---------------------------+---------------+----------------------------------+----------------------------------+----------------------------------+--------+-----------+---------------------------+---------------+----------------------------------+ | create_date | create_time | dataset_id | document_id | id | schema | status | update_date | update_time | user_id | +---------------------------+---------------+----------------------------------+----------------------------------+----------------------------------+--------+-----------+---------------------------+---------------+----------------------------------+ | 2026-06-02T19:02:31+08:00 | 1780398151417 | 0abe79f9423311f1ad8d38a74640adcc | ffe64fae423411f1a2d938a74640adcc | e89e20d9a25848a1b79bd9345ddbfe1d | | COMPLETED | 2026-06-02T19:02:52+08:00 | 1780398172208 | 2ba4881420fa11f19e9c38a74640adcc | +---------------------------+---------------+----------------------------------+----------------------------------+----------------------------------+--------+-----------+---------------------------+---------------+----------------------------------+ # Create an ingestion task # First argument is document id, second argument is dataset id RAGFlow(user)> start ingestion 'ffe64fae423411f1a2d938a74640adcc' from '0abe79f9423311f1ad8d38a74640adcc'; +----------------------------------+-------------------------------------------+ | document_id | result | +----------------------------------+-------------------------------------------+ | ffe64fae423411f1a2d938a74640adcc | task_id: 8d758cd14a8b4ba8ab505003fb52017d | +----------------------------------+-------------------------------------------+ # Pause an ingestion task, first argument is ingestion id RAGFlow(user)> stop ingestion '8d758cd14a8b4ba8ab505003fb52017d'; +---------------------------+---------------+----------------------------------+----------------------------------+----------------------------------+--------+-----------+---------------------------+---------------+----------------------------------+ | create_date | create_time | dataset_id | document_id | id | schema | status | update_date | update_time | user_id | +---------------------------+---------------+----------------------------------+----------------------------------+----------------------------------+--------+-----------+---------------------------+---------------+----------------------------------+ | 2026-05-30T20:21:06+08:00 | 1780143666289 | 0abe79f9423311f1ad8d38a74640adcc | ffe64fae423411f1a2d938a74640adcc | 8d758cd14a8b4ba8ab505003fb52017d | | COMPLETED | 2026-05-30T20:21:26+08:00 | 1780143686431 | 2ba4881420fa11f19e9c38a74640adcc | +---------------------------+---------------+----------------------------------+----------------------------------+----------------------------------+--------+-----------+---------------------------+---------------+----------------------------------+ # Delete an ingestion task RAGFlow(api/default)> remove ingestion tasks 'f366450a27d54677aec1c7090add30f0'; +---------+----------------------------------+ | remove | task_id | +---------+----------------------------------+ | success | f366450a27d54677aec1c7090add30f0 | +---------+----------------------------------+ ``` ### Type of change - [x] New Feature (non-breaking change which adds functionality) --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-06-12 14:56:44 +08:00
datasets.POST("/:dataset_id/documents/parse", r.documentHandler.StartIngestionTask)
datasets.GET("/ingestion/tasks", r.documentHandler.ListIngestionTasks)
datasets.PUT("/ingestion/tasks", r.documentHandler.StopIngestionTasks)
datasets.DELETE("/ingestion/tasks", r.documentHandler.RemoveIngestionTasks)
//datasets.POST("/:dataset_id/documents/parse", r.documentHandler.ParseDocuments)
//datasets.POST("/:dataset_id/documents/stop", r.documentHandler.StopParseDocuments)
datasets.DELETE("/:dataset_id/chunks", r.chunkHandler.StopParsing)
datasets.DELETE("/:dataset_id/documents/:document_id/chunks", r.chunkHandler.RemoveChunks)
datasets.PUT("/:dataset_id/documents/:document_id/metadata/config", r.datasetsHandler.UpdateDocumentMetadataConfig)
feat(go-api): Align document metadata batch APIs and upload_info with Python (#16269) ## Summary Align the Go implementations of these APIs with the Python behavior: - `POST /api/v1/datasets/:dataset_id/metadata/update` - `PATCH /api/v1/datasets/:dataset_id/documents/metadatas` - `POST /api/v1/documents/upload` ## What changed - Added the Go routes and handlers for the 3 APIs. - Aligned batch document metadata updates with Python semantics: - support `match` in update items - support list append / replace behavior - support deleting specific list values - remove metadata entirely when it becomes empty - create metadata for documents that previously had none when updates apply - count `updated` only when a document actually changes - Aligned `documents/upload` file uploads with Python-style `upload_info` behavior: - store upload-info blobs in the per-user downloads bucket - return lightweight upload descriptors instead of normal file-management responses - Improved URL upload behavior: - SSRF-guarded fetch with redirect validation - redirect limit aligned to Python behavior - normalize filename and MIME type - add `.pdf` when the fetched content is PDF - normalize HTML content into readable text instead of storing raw HTML shells ## Validation ### Unit tests Passed: - `go test ./internal/service` - `go test ./internal/handler` Also verified targeted cases for: - batch metadata update semantics - upload_info URL handling - upload_info download bucket behavior ### curl checks Verified the new Go endpoints with `curl` and compared the response shape and behavior with Python for: - `POST /api/v1/datasets/{dataset_id}/metadata/update` - `PATCH /api/v1/datasets/{dataset_id}/documents/metadatas` - `POST /api/v1/documents/upload` The Go responses were checked against Python for: - argument validation - success response shape - metadata update results - upload_info result structure - file vs URL input handling
2026-06-24 14:52:47 +08:00
datasets.POST("/:dataset_id/metadata/update", r.documentHandler.MetadataBatchUpdate)
datasets.PATCH("/:dataset_id/documents/metadatas", r.documentHandler.UpdateDocumentMetadatas)
}
// 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)
searches.POST("/:search_id/completion", r.searchHandler.Completion)
searches.POST("/:search_id/completions", r.searchHandler.Completion)
}
files := v1.Group("/files")
{
files.POST("", r.fileHandler.UploadFile)
files.GET("", r.fileHandler.ListFiles)
files.DELETE("", r.fileHandler.DeleteFiles)
files.POST("/move", r.fileHandler.MoveFiles)
files.POST("/link-to-datasets", r.fileHandler.LinkToDatasets)
files.GET("/:id/ancestors", r.fileHandler.GetFileAncestors)
files.GET("/:id/parent", r.fileHandler.GetParentFolder)
files.GET("/:id", r.fileHandler.Download)
files.GET("/:id/versions", r.fileCommitHandler.GetFileVersionHistory)
}
// 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)
}
Add git-like file commit API (#15978) ### What problem does this PR solve? | # | Method | Endpoint | Description | Git Equivalent | |---|--------|----------|-------------|----------------| | 1 | `POST` | `/api/v1/{prefix}/{folder_id}/commits` | Create a snapshot commit with file changes (add/modify/delete/rename) | `git add` + `git commit` | | 2 | `GET` | `/api/v1/{prefix}/{folder_id}/commits` | List commit history (paginated) | `git log` | | 3 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}` | Get commit detail with file changes | `git show` | | 4 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/files` | List file changes in a commit | `git show --name-status` | | 5 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/diff?from=...&to=...` | Compare two commits and return differences | `git diff` | | 6 | `GET` | `/api/v1/{prefix}/{folder_id}/changes` | Get uncommitted changes (add/modify/delete) | `git status` | | 7 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/tree` | Get the folder tree snapshot at commit time | `git ls-tree` | | 8 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/files/{file_id}/content` | Get a file's content as it existed in a specific commit | `git show HEAD:file` | | 9 | `GET` | `/api/v1/{prefix}/{file_id}/versions` | Get version history for a specific file across all commits | `git log -- file` | Where `{prefix}/{id}` can be: - `folders/{folder_id}` — direct folder access - `workspaces/{workspace_id}` — alias of `folders/{folder_id}` - `datasets/{dataset_id}` — resolves to the dataset's folder - `memories/{memory_id}` — resolves to the memory's folder - `skills/{skill_id}` — resolves to the skill's folder ### Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Documentation Update
2026-06-15 11:19:56 +08:00
// File commit routes — /folders/ takes folder_id directly
commitFolders := v1.Group("/folders")
{
commitFolders.POST("/:folder_id/commits", r.fileCommitHandler.CreateCommit)
commitFolders.GET("/:folder_id/commits", r.fileCommitHandler.ListCommits)
commitFolders.GET("/:folder_id/commits/diff", r.fileCommitHandler.DiffCommits)
commitFolders.GET("/:folder_id/commits/:commit_id", r.fileCommitHandler.GetCommit)
commitFolders.GET("/:folder_id/commits/:commit_id/files", r.fileCommitHandler.ListCommitFiles)
commitFolders.GET("/:folder_id/commits/:commit_id/tree", r.fileCommitHandler.GetCommitTree)
commitFolders.GET("/:folder_id/commits/:commit_id/files/:file_id/content", r.fileCommitHandler.GetCommitFileContent)
commitFolders.GET("/:folder_id/changes", r.fileCommitHandler.GetUncommittedChanges)
}
// /workspace/{workspace_id}/commits — alias for /folders/ (workspace_id == folder_id)
commitWorkspace := v1.Group("/workspace")
{
commitWorkspace.POST("/:folder_id/commits", r.fileCommitHandler.CreateCommit)
commitWorkspace.GET("/:folder_id/commits", r.fileCommitHandler.ListCommits)
commitWorkspace.GET("/:folder_id/commits/diff", r.fileCommitHandler.DiffCommits)
commitWorkspace.GET("/:folder_id/commits/:commit_id", r.fileCommitHandler.GetCommit)
commitWorkspace.GET("/:folder_id/commits/:commit_id/files", r.fileCommitHandler.ListCommitFiles)
commitWorkspace.GET("/:folder_id/commits/:commit_id/tree", r.fileCommitHandler.GetCommitTree)
commitWorkspace.GET("/:folder_id/commits/:commit_id/files/:file_id/content", r.fileCommitHandler.GetCommitFileContent)
commitWorkspace.GET("/:folder_id/changes", r.fileCommitHandler.GetUncommittedChanges)
}
// /datasets/{dataset_id}/commits — resolve dataset_id → folder_id via middleware
commitDatasets := v1.Group("/datasets/:dataset_id")
commitDatasets.Use(handler.CommitFolderResolver(r.fileCommitHandler, "datasets", "dataset_id"))
{
commitDatasets.POST("/commits", r.fileCommitHandler.CreateCommit)
commitDatasets.GET("/commits", r.fileCommitHandler.ListCommits)
commitDatasets.GET("/commits/diff", r.fileCommitHandler.DiffCommits)
commitDatasets.GET("/commits/:commit_id", r.fileCommitHandler.GetCommit)
commitDatasets.GET("/commits/:commit_id/files", r.fileCommitHandler.ListCommitFiles)
commitDatasets.GET("/commits/:commit_id/tree", r.fileCommitHandler.GetCommitTree)
commitDatasets.GET("/commits/:commit_id/files/:file_id/content", r.fileCommitHandler.GetCommitFileContent)
commitDatasets.GET("/changes", r.fileCommitHandler.GetUncommittedChanges)
}
Add git-like file commit API (#15978) ### What problem does this PR solve? | # | Method | Endpoint | Description | Git Equivalent | |---|--------|----------|-------------|----------------| | 1 | `POST` | `/api/v1/{prefix}/{folder_id}/commits` | Create a snapshot commit with file changes (add/modify/delete/rename) | `git add` + `git commit` | | 2 | `GET` | `/api/v1/{prefix}/{folder_id}/commits` | List commit history (paginated) | `git log` | | 3 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}` | Get commit detail with file changes | `git show` | | 4 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/files` | List file changes in a commit | `git show --name-status` | | 5 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/diff?from=...&to=...` | Compare two commits and return differences | `git diff` | | 6 | `GET` | `/api/v1/{prefix}/{folder_id}/changes` | Get uncommitted changes (add/modify/delete) | `git status` | | 7 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/tree` | Get the folder tree snapshot at commit time | `git ls-tree` | | 8 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/files/{file_id}/content` | Get a file's content as it existed in a specific commit | `git show HEAD:file` | | 9 | `GET` | `/api/v1/{prefix}/{file_id}/versions` | Get version history for a specific file across all commits | `git log -- file` | Where `{prefix}/{id}` can be: - `folders/{folder_id}` — direct folder access - `workspaces/{workspace_id}` — alias of `folders/{folder_id}` - `datasets/{dataset_id}` — resolves to the dataset's folder - `memories/{memory_id}` — resolves to the memory's folder - `skills/{skill_id}` — resolves to the skill's folder ### Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Documentation Update
2026-06-15 11:19:56 +08:00
// 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)
}
feat: Implement API of ragflow server in Go (#15256) ## Summary - Implemented the Go API endpoint for Memory message forgetting: - `DELETE /api/v1/messages/{memory_id}:{message_id}` - Added route registration for the Memory message DELETE endpoint only. - Added request path validation for `memory_id:message_id`. - Added service logic to mark a message as forgotten by setting `forget_at`. - Preserved Python-compatible response behavior: - Success returns `code: 0`, `message: true`, `data: null`. - Added focused unit tests for message path parsing and invalid message ID handling. - Fixed Linux cgo linker config to use the installed shared PCRE2 library so Go tests/builds can run in this environment. ## Related Issue Closes: #15240 ## Change Type - [x] Feature - [x] Test - [x] Build / CI compatibility ## Implemented API - `DELETE /api/v1/messages/{memory_id}:{message_id}` ## Real Behavior Proof Validated with targeted Go tests: ```bash /tmp/go1.25.0/bin/go test ./internal/handler ./internal/router ``` Result: ```text ok ragflow/internal/handler ? ragflow/internal/router [no test files] ``` Validated server entrypoint build: ```bash /tmp/go1.25.0/bin/go build -o /tmp/ragflow-server-main ./cmd/server_main.go ``` Result: ```text build succeeded ``` Validated patch formatting: ```bash git diff --check ``` Result: ```text no whitespace errors ``` ## Checklist - [x] Implemented only `DELETE /api/v1/messages/{memory_id}:{message_id}`. - [x] Did not implement unrelated Memory message APIs. - [x] Added route registration. - [x] Added handler validation. - [x] Added service-level memory access check. - [x] Added tests. - [x] Ran targeted Go tests. - [x] Ran server build validation. - [x] Ran `git diff --check`.
2026-06-10 20:27:35 +07:00
// Message routes
message := v1.Group("/messages")
{
message.GET("", r.memoryHandler.GetMessages)
message.POST("", r.memoryHandler.AddMessage)
feat: Implement API of ragflow server in Go (#15256) ## Summary - Implemented the Go API endpoint for Memory message forgetting: - `DELETE /api/v1/messages/{memory_id}:{message_id}` - Added route registration for the Memory message DELETE endpoint only. - Added request path validation for `memory_id:message_id`. - Added service logic to mark a message as forgotten by setting `forget_at`. - Preserved Python-compatible response behavior: - Success returns `code: 0`, `message: true`, `data: null`. - Added focused unit tests for message path parsing and invalid message ID handling. - Fixed Linux cgo linker config to use the installed shared PCRE2 library so Go tests/builds can run in this environment. ## Related Issue Closes: #15240 ## Change Type - [x] Feature - [x] Test - [x] Build / CI compatibility ## Implemented API - `DELETE /api/v1/messages/{memory_id}:{message_id}` ## Real Behavior Proof Validated with targeted Go tests: ```bash /tmp/go1.25.0/bin/go test ./internal/handler ./internal/router ``` Result: ```text ok ragflow/internal/handler ? ragflow/internal/router [no test files] ``` Validated server entrypoint build: ```bash /tmp/go1.25.0/bin/go build -o /tmp/ragflow-server-main ./cmd/server_main.go ``` Result: ```text build succeeded ``` Validated patch formatting: ```bash git diff --check ``` Result: ```text no whitespace errors ``` ## Checklist - [x] Implemented only `DELETE /api/v1/messages/{memory_id}:{message_id}`. - [x] Did not implement unrelated Memory message APIs. - [x] Added route registration. - [x] Added handler validation. - [x] Added service-level memory access check. - [x] Added tests. - [x] Ran targeted Go tests. - [x] Ran server build validation. - [x] Ran `git diff --check`.
2026-06-10 20:27:35 +07:00
message.DELETE("/:memory_message", r.memoryHandler.ForgetMessage)
message.PUT("/:memory_message", r.memoryHandler.UpdateMessage)
message.GET("/:memory_message/content", r.memoryHandler.GetMessageContent)
message.GET("/search", r.memoryHandler.SearchMessage)
feat: Implement API of ragflow server in Go (#15256) ## Summary - Implemented the Go API endpoint for Memory message forgetting: - `DELETE /api/v1/messages/{memory_id}:{message_id}` - Added route registration for the Memory message DELETE endpoint only. - Added request path validation for `memory_id:message_id`. - Added service logic to mark a message as forgotten by setting `forget_at`. - Preserved Python-compatible response behavior: - Success returns `code: 0`, `message: true`, `data: null`. - Added focused unit tests for message path parsing and invalid message ID handling. - Fixed Linux cgo linker config to use the installed shared PCRE2 library so Go tests/builds can run in this environment. ## Related Issue Closes: #15240 ## Change Type - [x] Feature - [x] Test - [x] Build / CI compatibility ## Implemented API - `DELETE /api/v1/messages/{memory_id}:{message_id}` ## Real Behavior Proof Validated with targeted Go tests: ```bash /tmp/go1.25.0/bin/go test ./internal/handler ./internal/router ``` Result: ```text ok ragflow/internal/handler ? ragflow/internal/router [no test files] ``` Validated server entrypoint build: ```bash /tmp/go1.25.0/bin/go build -o /tmp/ragflow-server-main ./cmd/server_main.go ``` Result: ```text build succeeded ``` Validated patch formatting: ```bash git diff --check ``` Result: ```text no whitespace errors ``` ## Checklist - [x] Implemented only `DELETE /api/v1/messages/{memory_id}:{message_id}`. - [x] Did not implement unrelated Memory message APIs. - [x] Added route registration. - [x] Added handler validation. - [x] Added service-level memory access check. - [x] Added tests. - [x] Ran targeted Go tests. - [x] Ran server build validation. - [x] Ran `git diff --check`.
2026-06-10 20:27:35 +07:00
}
// 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)
feat(agent): Go port — canvas engine, 22 components, DSL v2, 13 endpoints (#15952) Ports the agent canvas subsystem from Python to Go. ## What's included ### Canvas Engine (Phase 0/1) - State engine, scheduler, variable resolver, Redis checkpoint store, cancel protocol - **209 tests** across canvas / component / io packages ### 22 Components (P0–P4) | Tier | Components | |---|---| | P0 T1+T2+T3 | LLM, Agent, ExitLoop, Switch, Categorize, Begin, Message, Invoke | | P1 T3 | VariableAggregator, VariableAssigner, StringTransform, ListOperations, DataOperations | | P2 T3 | Iteration, IterationItem, Loop, LoopItem | | P3 T3 | UserFillUp, Fillup | | P4 T5 | Browser, ExcelProcessor, DocsGenerator | ### DSL v2 Schema (Phase 2.5) - Typed v2 in-memory model with v1-to-v2 auto-detect converter - v1 legacy field stripping per plan §2.11.7 ### HTTP Endpoints & Bug Fixes (Plans PR1–PR3) - **DELETE SQL bug fix**: gorm v2 `Where("id = ?", id).Delete(...)` pattern - **CreateAgent validation**: title/DSL required, duplicate check, 103 envelope - **13 new endpoints**: templates, prompts, tags, sessions CRUD, chat/completions (SSE + non-stream stubs), rerun, test_db_connection, logs, webhook/logs - **756 Go unit tests** (745 → 756, +18) - **17 → 0 Python integration test failures** (test_agents.py + test_session_management/) ### Tools 21 eino tools: HTTPHelper, search tools, financial/data tools, mandatory stubs ### Infrastructure OTel observability, NATS message queue, DeepDoc gRPC client, SSRF guards, IDOR mitigation
2026-06-12 22:58:28 +08:00
provider.POST("/: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/to_model", 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")
{
feat(agent): Go port — canvas engine, 22 components, DSL v2, 13 endpoints (#15952) Ports the agent canvas subsystem from Python to Go. ## What's included ### Canvas Engine (Phase 0/1) - State engine, scheduler, variable resolver, Redis checkpoint store, cancel protocol - **209 tests** across canvas / component / io packages ### 22 Components (P0–P4) | Tier | Components | |---|---| | P0 T1+T2+T3 | LLM, Agent, ExitLoop, Switch, Categorize, Begin, Message, Invoke | | P1 T3 | VariableAggregator, VariableAssigner, StringTransform, ListOperations, DataOperations | | P2 T3 | Iteration, IterationItem, Loop, LoopItem | | P3 T3 | UserFillUp, Fillup | | P4 T5 | Browser, ExcelProcessor, DocsGenerator | ### DSL v2 Schema (Phase 2.5) - Typed v2 in-memory model with v1-to-v2 auto-detect converter - v1 legacy field stripping per plan §2.11.7 ### HTTP Endpoints & Bug Fixes (Plans PR1–PR3) - **DELETE SQL bug fix**: gorm v2 `Where("id = ?", id).Delete(...)` pattern - **CreateAgent validation**: title/DSL required, duplicate check, 103 envelope - **13 new endpoints**: templates, prompts, tags, sessions CRUD, chat/completions (SSE + non-stream stubs), rerun, test_db_connection, logs, webhook/logs - **756 Go unit tests** (745 → 756, +18) - **17 → 0 Python integration test failures** (test_agents.py + test_session_management/) ### Tools 21 eino tools: HTTPHelper, search tools, financial/data tools, mandatory stubs ### Infrastructure OTel observability, NATS message queue, DeepDoc gRPC client, SSRF guards, IDOR mitigation
2026-06-12 22:58:28 +08:00
// GET /models returns the tenant's added models across
// all instances. Front-end useFetchAllAddedModels consumes this.
feat(agent): Go port — canvas engine, 22 components, DSL v2, 13 endpoints (#15952) Ports the agent canvas subsystem from Python to Go. ## What's included ### Canvas Engine (Phase 0/1) - State engine, scheduler, variable resolver, Redis checkpoint store, cancel protocol - **209 tests** across canvas / component / io packages ### 22 Components (P0–P4) | Tier | Components | |---|---| | P0 T1+T2+T3 | LLM, Agent, ExitLoop, Switch, Categorize, Begin, Message, Invoke | | P1 T3 | VariableAggregator, VariableAssigner, StringTransform, ListOperations, DataOperations | | P2 T3 | Iteration, IterationItem, Loop, LoopItem | | P3 T3 | UserFillUp, Fillup | | P4 T5 | Browser, ExcelProcessor, DocsGenerator | ### DSL v2 Schema (Phase 2.5) - Typed v2 in-memory model with v1-to-v2 auto-detect converter - v1 legacy field stripping per plan §2.11.7 ### HTTP Endpoints & Bug Fixes (Plans PR1–PR3) - **DELETE SQL bug fix**: gorm v2 `Where("id = ?", id).Delete(...)` pattern - **CreateAgent validation**: title/DSL required, duplicate check, 103 envelope - **13 new endpoints**: templates, prompts, tags, sessions CRUD, chat/completions (SSE + non-stream stubs), rerun, test_db_connection, logs, webhook/logs - **756 Go unit tests** (745 → 756, +18) - **17 → 0 Python integration test failures** (test_agents.py + test_session_management/) ### Tools 21 eino tools: HTTPHelper, search tools, financial/data tools, mandatory stubs ### Infrastructure OTel observability, NATS message queue, DeepDoc gRPC client, SSRF guards, IDOR mitigation
2026-06-12 22:58:28 +08:00
model.GET("/", r.providerHandler.ListTenantAddedModels)
model.PATCH("/", r.tenantHandler.SetModels)
// Tenant default-model selection (used by the agent page's useFetchDefaultModels hook)
feat(agent): Go port — canvas engine, 22 components, DSL v2, 13 endpoints (#15952) Ports the agent canvas subsystem from Python to Go. ## What's included ### Canvas Engine (Phase 0/1) - State engine, scheduler, variable resolver, Redis checkpoint store, cancel protocol - **209 tests** across canvas / component / io packages ### 22 Components (P0–P4) | Tier | Components | |---|---| | P0 T1+T2+T3 | LLM, Agent, ExitLoop, Switch, Categorize, Begin, Message, Invoke | | P1 T3 | VariableAggregator, VariableAssigner, StringTransform, ListOperations, DataOperations | | P2 T3 | Iteration, IterationItem, Loop, LoopItem | | P3 T3 | UserFillUp, Fillup | | P4 T5 | Browser, ExcelProcessor, DocsGenerator | ### DSL v2 Schema (Phase 2.5) - Typed v2 in-memory model with v1-to-v2 auto-detect converter - v1 legacy field stripping per plan §2.11.7 ### HTTP Endpoints & Bug Fixes (Plans PR1–PR3) - **DELETE SQL bug fix**: gorm v2 `Where("id = ?", id).Delete(...)` pattern - **CreateAgent validation**: title/DSL required, duplicate check, 103 envelope - **13 new endpoints**: templates, prompts, tags, sessions CRUD, chat/completions (SSE + non-stream stubs), rerun, test_db_connection, logs, webhook/logs - **756 Go unit tests** (745 → 756, +18) - **17 → 0 Python integration test failures** (test_agents.py + test_session_management/) ### Tools 21 eino tools: HTTPHelper, search tools, financial/data tools, mandatory stubs ### Infrastructure OTel observability, NATS message queue, DeepDoc gRPC client, SSRF guards, IDOR mitigation
2026-06-12 22:58:28 +08:00
model.GET("/default", r.tenantHandler.GetDefaultModels)
model.PATCH("/default", r.tenantHandler.SetDefaultModels)
}
Go: new CLI command, list all models and show model (#15786) ### What problem does this PR solve? ``` RAGFlow(user)> list models; +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | alias | max_tokens | model_types | name | thinking | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | | 1048576 | [chat] | deepseek-v4-flash | map[clear_thinking:true default_value:true] | | | 1048576 | [chat] | deepseek-v4-pro | map[clear_thinking:true default_value:true] | | | 1024000 | [chat] | minimax-m3 | map[clear_thinking:true default_value:true] | | | 64000 | [vision] | glm-4.5v | map[clear_thinking:true default_value:true] | | [baai/bge-m3] | 8192 | [embedding] | bge-m3 | | | [baai/bge-reranker-v2-m3] | 1024 | [rerank] | bge-reranker-v2-m3 | | | | | [tts] | step-audio-tts-3b | | | [qwen/qwen3-asr-1.7b] | | [asr] | qwen3-asr-1.7b | | | [paddleocr-vl-1.5] | | [ocr] | paddleocr-vl-0.9b | | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ RAGFlow(user)> show model 'minimax-m3'; +--------------+---------------------------------------------+ | field | value | +--------------+---------------------------------------------+ | name | minimax-m3 | | max_tokens | 1024000 | | model_types | [chat] | | thinking | map[clear_thinking:true default_value:true] | | class | | | alias | | | ModelTypeMap | | +--------------+---------------------------------------------+ RAGFlow(user)> show model 'baai/bge-m3'; +--------------+---------------+ | field | value | +--------------+---------------+ | model_types | [embedding] | | thinking | | | class | | | alias | [baai/bge-m3] | | ModelTypeMap | | | name | bge-m3 | | max_tokens | 8192 | +--------------+---------------+ ``` --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-06-08 21:38:15 +08:00
allModels := v1.Group("/all-models")
{
allModels.GET("", r.modelHandler.ListAllModels)
allModels.GET("/:model_name", r.modelHandler.ShowModel)
Go: new CLI command, list all models and show model (#15786) ### What problem does this PR solve? ``` RAGFlow(user)> list models; +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | alias | max_tokens | model_types | name | thinking | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ | | 1048576 | [chat] | deepseek-v4-flash | map[clear_thinking:true default_value:true] | | | 1048576 | [chat] | deepseek-v4-pro | map[clear_thinking:true default_value:true] | | | 1024000 | [chat] | minimax-m3 | map[clear_thinking:true default_value:true] | | | 64000 | [vision] | glm-4.5v | map[clear_thinking:true default_value:true] | | [baai/bge-m3] | 8192 | [embedding] | bge-m3 | | | [baai/bge-reranker-v2-m3] | 1024 | [rerank] | bge-reranker-v2-m3 | | | | | [tts] | step-audio-tts-3b | | | [qwen/qwen3-asr-1.7b] | | [asr] | qwen3-asr-1.7b | | | [paddleocr-vl-1.5] | | [ocr] | paddleocr-vl-0.9b | | +---------------------------+------------+-------------+--------------------+---------------------------------------------+ RAGFlow(user)> show model 'minimax-m3'; +--------------+---------------------------------------------+ | field | value | +--------------+---------------------------------------------+ | name | minimax-m3 | | max_tokens | 1024000 | | model_types | [chat] | | thinking | map[clear_thinking:true default_value:true] | | class | | | alias | | | ModelTypeMap | | +--------------+---------------------------------------------+ RAGFlow(user)> show model 'baai/bge-m3'; +--------------+---------------+ | field | value | +--------------+---------------+ | model_types | [embedding] | | thinking | | | class | | | alias | [baai/bge-m3] | | ModelTypeMap | | | name | bge-m3 | | max_tokens | 8192 | +--------------+---------------+ ``` --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-06-08 21:38:15 +08:00
}
// Agent routes
agents := v1.Group("/agents")
feat(agent): Go port — canvas engine, 22 components, DSL v2, 13 endpoints (#15952) Ports the agent canvas subsystem from Python to Go. ## What's included ### Canvas Engine (Phase 0/1) - State engine, scheduler, variable resolver, Redis checkpoint store, cancel protocol - **209 tests** across canvas / component / io packages ### 22 Components (P0–P4) | Tier | Components | |---|---| | P0 T1+T2+T3 | LLM, Agent, ExitLoop, Switch, Categorize, Begin, Message, Invoke | | P1 T3 | VariableAggregator, VariableAssigner, StringTransform, ListOperations, DataOperations | | P2 T3 | Iteration, IterationItem, Loop, LoopItem | | P3 T3 | UserFillUp, Fillup | | P4 T5 | Browser, ExcelProcessor, DocsGenerator | ### DSL v2 Schema (Phase 2.5) - Typed v2 in-memory model with v1-to-v2 auto-detect converter - v1 legacy field stripping per plan §2.11.7 ### HTTP Endpoints & Bug Fixes (Plans PR1–PR3) - **DELETE SQL bug fix**: gorm v2 `Where("id = ?", id).Delete(...)` pattern - **CreateAgent validation**: title/DSL required, duplicate check, 103 envelope - **13 new endpoints**: templates, prompts, tags, sessions CRUD, chat/completions (SSE + non-stream stubs), rerun, test_db_connection, logs, webhook/logs - **756 Go unit tests** (745 → 756, +18) - **17 → 0 Python integration test failures** (test_agents.py + test_session_management/) ### Tools 21 eino tools: HTTPHelper, search tools, financial/data tools, mandatory stubs ### Infrastructure OTel observability, NATS message queue, DeepDoc gRPC client, SSRF guards, IDOR mitigation
2026-06-12 22:58:28 +08:00
RegisterAgentRoutes(agents, r.agentHandler)
// Plugin routes
plugin := v1.Group("/plugin")
{
plugin.GET("/tools", r.pluginHandler.ListLLMTools)
}
// Component catalog — Phase 4 of
// port-rag-flow-pipeline-to-go.md. Optional
// ?category=ingestion,agent,shared filter; defaults to
// all categories. The data source is
// runtime.DefaultRegistry.
if r.componentsHandler != nil {
v1.GET("/components", r.componentsHandler.Get)
}
feat(agent): Go port — canvas engine, 22 components, DSL v2, 13 endpoints (#15952) Ports the agent canvas subsystem from Python to Go. ## What's included ### Canvas Engine (Phase 0/1) - State engine, scheduler, variable resolver, Redis checkpoint store, cancel protocol - **209 tests** across canvas / component / io packages ### 22 Components (P0–P4) | Tier | Components | |---|---| | P0 T1+T2+T3 | LLM, Agent, ExitLoop, Switch, Categorize, Begin, Message, Invoke | | P1 T3 | VariableAggregator, VariableAssigner, StringTransform, ListOperations, DataOperations | | P2 T3 | Iteration, IterationItem, Loop, LoopItem | | P3 T3 | UserFillUp, Fillup | | P4 T5 | Browser, ExcelProcessor, DocsGenerator | ### DSL v2 Schema (Phase 2.5) - Typed v2 in-memory model with v1-to-v2 auto-detect converter - v1 legacy field stripping per plan §2.11.7 ### HTTP Endpoints & Bug Fixes (Plans PR1–PR3) - **DELETE SQL bug fix**: gorm v2 `Where("id = ?", id).Delete(...)` pattern - **CreateAgent validation**: title/DSL required, duplicate check, 103 envelope - **13 new endpoints**: templates, prompts, tags, sessions CRUD, chat/completions (SSE + non-stream stubs), rerun, test_db_connection, logs, webhook/logs - **756 Go unit tests** (745 → 756, +18) - **17 → 0 Python integration test failures** (test_agents.py + test_session_management/) ### Tools 21 eino tools: HTTPHelper, search tools, financial/data tools, mandatory stubs ### Infrastructure OTel observability, NATS message queue, DeepDoc gRPC client, SSRF guards, IDOR mitigation
2026-06-12 22:58:28 +08:00
// Admin routes — Phase 6 per-tenant canvas runtime override.
// RegisterAdminRuntimeRoutes lives in admin_routes.go; a nil
// handler is tolerated and yields a no-op registration.
admin := v1.Group("/admin")
RegisterAdminRuntimeRoutes(admin, r.adminRuntimeHandler)
connectors := v1.Group("/connectors")
{
connectors.GET("/", r.connectorHandler.ListConnectors)
connectors.POST("/", r.connectorHandler.CreateConnector)
connectors.POST("/google/oauth/web/start", r.connectorHandler.StartGoogleWebOAuth)
connectors.POST("/google/oauth/web/result", r.connectorHandler.PollGoogleWebOAuthResult)
connectors.POST("/box/oauth/web/start", r.connectorHandler.StartBoxWebOAuth)
connectors.POST("/box/oauth/web/result", r.connectorHandler.PollBoxWebOAuthResult)
connectors.GET("/:connector_id", r.connectorHandler.GetConnector)
connectors.PATCH("/:connector_id", r.connectorHandler.UpdateConnector)
connectors.GET("/:connector_id/logs", r.connectorHandler.ListLogs)
connectors.DELETE("/:connector_id", r.connectorHandler.DeleteConnector)
connectors.POST("/:connector_id/rebuild", r.connectorHandler.RebuildConnector)
connectors.POST("/:connector_id/test", r.connectorHandler.TestConnector)
}
// 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)
}
// MCP server routes.
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 := v1.Group("/mcp")
{
mcp.POST("/servers", r.mcpHandler.CreateMCPServer)
mcp.GET("/servers", r.mcpHandler.ListMCPServers)
mcp.GET("/servers/:mcp_id", r.mcpHandler.GetMCPServer)
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.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)
}
2026-06-25 20:36:50 +08:00
// Variables/Settings
system.GET("/variables", r.systemHandler.ListVariables)
system.PUT("/variables", r.systemHandler.SetVariable)
system.GET("/variables/:var_name", r.systemHandler.ShowVariable)
2026-06-25 20:36:50 +08:00
// Environments
system.GET("/environments", r.systemHandler.ListEnvironments)
tokens := system.Group("/tokens")
{
// list tokens /api/v1/system/tokens GET
tokens.GET("", r.systemHandler.ListAPIKeys)
// create token /api/v1/system/tokens POST
tokens.POST("", r.systemHandler.CreateKey)
// delete token /api/v1/system/tokens/:key DELETE
tokens.DELETE("/:key", r.systemHandler.DeleteKey)
}
keys := system.Group("/keys")
{
// list keys /api/v1/system/keys GET
keys.GET("", r.systemHandler.ListAPIKeys)
// create key /api/v1/system/keys POST
keys.POST("", r.systemHandler.CreateKey)
// delete key /api/v1/system/keys/:key DELETE
keys.DELETE("/:key", r.systemHandler.DeleteKey)
}
}
// 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
}
// Chunk routes
chunk := v1.Group("/chunk")
{
chunk.POST("/list", r.chunkHandler.List)
chunk.POST("/update", r.chunkHandler.UpdateChunk) // Internal API only for GO
}
// Chat Channel
chanChannel := v1.Group("/chat-channels")
{
chanChannel.POST("", r.chatChannelHandler.CreateChatChannel)
chanChannel.GET("", r.chatChannelHandler.ListChatChannel)
chanChannel.GET("/:channel_id", r.chatChannelHandler.GetChatChannel)
chanChannel.PATCH("/:channel_id", r.chatChannelHandler.UpdateChatChannel)
chanChannel.DELETE("/:channel_id", r.chatChannelHandler.DeleteChatChannel)
}
// Langfuse tracing keys
langfuse := v1.Group("/langfuse")
{
langfuse.POST("/api-key", r.langfuseHandler.SetAPIKey)
langfuse.PUT("/api-key", r.langfuseHandler.SetAPIKey)
langfuse.GET("/api-key", r.langfuseHandler.GetAPIKey)
langfuse.DELETE("/api-key", r.langfuseHandler.DeleteAPIKey)
}
// Dify retrieval routes
dify := v1.Group("/dify")
{
dify.POST("/retrieval", r.difyRetrievalHandler.Retrieval)
dify.GET("/retrieval", r.difyRetrievalHandler.Retrieval)
}
}
}
// Handle undefined routes
engine.NoRoute(handler.HandleNoRoute)
}