// // 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 handler contains all HTTP request handlers // This file implements Memory-related API endpoint handlers // Each method corresponds to an API endpoint in the Python memory_api.py package handler import ( "net/http" "os" "strconv" "strings" "time" "github.com/gin-gonic/gin" "ragflow/internal/common" "ragflow/internal/service" ) // MemoryHandler handles Memory-related HTTP requests // Responsible for processing all Memory-related HTTP requests // Each method corresponds to an API endpoint, implementing the same logic as Python memory_api.py type MemoryHandler struct { memoryService *service.MemoryService // Reference to Memory business service layer } // NewMemoryHandler creates a new MemoryHandler instance // // Parameters: // - memoryService: Pointer to MemoryService business service layer // // Returns: // - *MemoryHandler: Initialized handler instance func NewMemoryHandler(memoryService *service.MemoryService) *MemoryHandler { return &MemoryHandler{ memoryService: memoryService, } } // CreateMemory handles POST request for creating Memory // API Path: POST /api/v1/memories // // Function: // - Creates a new memory record // - Supports automatic system_prompt generation // - Supports name deduplication (if name exists, adds sequence number) // // Request Parameters (JSON Body): // - name (required): Memory name, max 128 characters // - memory_type (required): Memory type array, supports ["raw", "semantic", "episodic", "procedural"] // - embd_id (required): Embedding model ID // - llm_id (required): LLM model ID // - tenant_embd_id (optional): Tenant embedding model ID // - tenant_llm_id (optional): Tenant LLM model ID // // Response Format: // - code: Status code (0=success, other=error) // - message: true on success, error message on failure // - data: Memory object on success // // Business Logic (matching Python create_memory): // 1. Validate user login status // 2. Parse and validate request parameters // 3. Call service layer to create memory // 4. Return creation result func (h *MemoryHandler) CreateMemory(c *gin.Context) { // Check if API timing is enabled // If RAGFLOW_API_TIMING environment variable is set, request processing time will be logged timingEnabled := os.Getenv("RAGFLOW_API_TIMING") var tStart time.Time if timingEnabled != "" { tStart = time.Now() } // Get current logged-in user information // GetUser is a context value set by the authentication middleware user, errorCode, errorMessage := GetUser(c) if errorCode != common.CodeSuccess { jsonError(c, errorCode, errorMessage) return } userID := user.ID // Parse JSON request body var req service.CreateMemoryRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusOK, gin.H{ "code": common.CodeBadRequest, "message": err.Error(), "data": nil, }) return } // Validate required field: name if req.Name == "" { c.JSON(http.StatusOK, gin.H{ "code": common.CodeArgumentError, "message": "name is required", "data": nil, }) return } // Validate required field: memory_type (must be non-empty array) if len(req.MemoryType) == 0 { c.JSON(http.StatusOK, gin.H{ "code": common.CodeArgumentError, "message": "memory_type is required and must be a list", "data": nil, }) return } // Validate required field: embd_id if req.EmbdID == "" { c.JSON(http.StatusOK, gin.H{ "code": common.CodeArgumentError, "message": "embd_id is required", "data": nil, }) return } // Validate required field: llm_id if req.LLMID == "" { c.JSON(http.StatusOK, gin.H{ "code": common.CodeArgumentError, "message": "llm_id is required", "data": nil, }) return } // Record request parsing completion time (for timing) tParsed := time.Now() // Call service layer to create memory result, err := h.memoryService.CreateMemory(userID, &req) if err != nil { // Log error if timing is enabled if timingEnabled != "" { totalMs := float64(time.Since(tStart).Microseconds()) / 1000.0 parseMs := float64(tParsed.Sub(tStart).Microseconds()) / 1000.0 _ = parseMs _ = totalMs } errMsg := err.Error() // Determine if it's an argument error and return appropriate error code if isArgumentError(errMsg) { c.JSON(http.StatusOK, gin.H{ "code": common.CodeArgumentError, "message": errMsg, "data": nil, }) return } // Other errors return server error c.JSON(http.StatusOK, gin.H{ "code": common.CodeServerError, "message": errMsg, "data": nil, }) return } // Log success if timing is enabled if timingEnabled != "" { totalMs := float64(time.Since(tStart).Microseconds()) / 1000.0 parseMs := float64(tParsed.Sub(tStart).Microseconds()) / 1000.0 validateAndDbMs := totalMs - parseMs _ = parseMs _ = validateAndDbMs _ = totalMs } // Return success response c.JSON(http.StatusOK, gin.H{ "code": common.CodeSuccess, "message": "success", "data": result, }) } // UpdateMemory handles PUT request for updating Memory // API Path: PUT /api/v1/memories/:memory_id // // Function: // - Updates configuration information for the specified memory // - Supports partial updates: only update passed fields // // Request Parameters (JSON Body): // - name (optional): Memory name // - permissions (optional): Permission setting ["me", "team", "all"] // - llm_id (optional): LLM model ID // - embd_id (optional): Embedding model ID // - tenant_llm_id (optional): Tenant LLM model ID // - tenant_embd_id (optional): Tenant embedding model ID // - memory_type (optional): Memory type array // - memory_size (optional): Memory size, range (0, 5242880] // - forgetting_policy (optional): Forgetting policy, default "FIFO" // - temperature (optional): Temperature parameter, range [0, 1] // - avatar (optional): Avatar URL // - description (optional): Description // - system_prompt (optional): System prompt // - user_prompt (optional): User prompt // // Business Rules: // - name length <= 128 characters // - Cannot update tenant_embd_id, embd_id, memory_type when memory_size > 0 // - When updating memory_type, system_prompt is automatically regenerated if it's the default func (h *MemoryHandler) UpdateMemory(c *gin.Context) { // Get memory_id from URL path memoryID := c.Param("memory_id") if memoryID == "" { c.JSON(http.StatusOK, gin.H{ "code": common.CodeArgumentError, "message": "memory_id is required", "data": nil, }) return } // Get current logged-in user information user, errorCode, errorMessage := GetUser(c) if errorCode != common.CodeSuccess { jsonError(c, errorCode, errorMessage) return } userID := user.ID // Parse JSON request body var req service.UpdateMemoryRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusOK, gin.H{ "code": common.CodeBadRequest, "message": err.Error(), "data": nil, }) return } // Call service layer to update memory result, err := h.memoryService.UpdateMemory(userID, memoryID, &req) if err != nil { errMsg := err.Error() // Check if it's a "not found" error if strings.Contains(errMsg, "not found") { c.JSON(http.StatusOK, gin.H{ "code": common.CodeNotFound, "message": errMsg, "data": nil, }) return } // Check if it's an argument error if isArgumentError(errMsg) { c.JSON(http.StatusOK, gin.H{ "code": common.CodeArgumentError, "message": errMsg, "data": nil, }) return } // Other errors return server error c.JSON(http.StatusOK, gin.H{ "code": common.CodeServerError, "message": errMsg, "data": nil, }) return } // Return success response c.JSON(http.StatusOK, gin.H{ "code": common.CodeSuccess, "message": "success", "data": result, }) } // DeleteMemory handles DELETE request for deleting Memory // API Path: DELETE /api/v1/memories/:memory_id // // Function: // - Deletes the specified memory record // - Also deletes associated message data // // Business Logic: // 1. Check if memory exists // 2. Delete memory record // 3. Delete associated message index func (h *MemoryHandler) DeleteMemory(c *gin.Context) { // Get memory_id from URL path memoryID := c.Param("memory_id") if memoryID == "" { c.JSON(http.StatusOK, gin.H{ "code": common.CodeArgumentError, "message": "memory_id is required", "data": nil, }) return } // Call service layer to delete memory err := h.memoryService.DeleteMemory(memoryID) if err != nil { errMsg := err.Error() // Check if it's a "not found" error if strings.Contains(errMsg, "not found") { c.JSON(http.StatusOK, gin.H{ "code": common.CodeNotFound, "message": errMsg, "data": nil, }) return } // Other errors return server error c.JSON(http.StatusOK, gin.H{ "code": common.CodeServerError, "message": errMsg, "data": nil, }) return } // Return success response c.JSON(http.StatusOK, gin.H{ "code": common.CodeSuccess, "message": "success", "data": nil, }) } // ListMemories handles GET request for listing Memories // API Path: GET /api/v1/memories // // Function: // - Lists memories accessible to the current user // - Supports multiple filter conditions // - Supports pagination and keyword search // // Query Parameters: // - memory_type (optional): Memory type filter, supports comma-separated multiple types // - tenant_id (optional): Tenant ID filter // - storage_type (optional): Storage type filter // - keywords (optional): Keyword search (fuzzy match on name) // - page (optional): Page number, default 1 // - page_size (optional): Items per page, default 50 // // Response Format: // - code: Status code // - message: true // - data.memory_list: Array of Memory objects // - data.total_count: Total record count func (h *MemoryHandler) ListMemories(c *gin.Context) { // Get current logged-in user information user, errorCode, errorMessage := GetUser(c) if errorCode != common.CodeSuccess { jsonError(c, errorCode, errorMessage) return } // Parse query parameters memoryTypesParam := c.Query("memory_type") tenantIDsParam := c.Query("tenant_id") storageType := c.Query("storage_type") keywords := c.Query("keywords") pageStr := c.DefaultQuery("page", "1") pageSizeStr := c.DefaultQuery("page_size", "50") // Convert pagination parameters to integers page, _ := strconv.Atoi(pageStr) pageSize, _ := strconv.Atoi(pageSizeStr) // Validate pagination parameters if page < 1 { page = 1 } if pageSize < 1 { pageSize = 50 } // Parse memory_type parameter (supports comma separation) var memoryTypes []string if memoryTypesParam != "" { if strings.Contains(memoryTypesParam, ",") { memoryTypes = strings.Split(memoryTypesParam, ",") } else { memoryTypes = []string{memoryTypesParam} } } // Parse tenant_id parameter // If not specified, service will get all tenants associated with the user var tenantIDs []string if tenantIDsParam != "" { if strings.Contains(tenantIDsParam, ",") { tenantIDs = strings.Split(tenantIDsParam, ",") } else { tenantIDs = []string{tenantIDsParam} } } // Call service layer to get memory list result, err := h.memoryService.ListMemories(user.ID, tenantIDs, memoryTypes, storageType, keywords, page, pageSize) if err != nil { c.JSON(http.StatusOK, gin.H{ "code": common.CodeServerError, "message": err.Error(), "data": nil, }) return } // Return success response c.JSON(http.StatusOK, gin.H{ "code": common.CodeSuccess, "message": "success", "data": result, }) } // GetMemoryConfig handles GET request for getting Memory configuration // API Path: GET /api/v1/memories/:memory_id/config // // Function: // - Gets complete configuration information for the specified memory // - Includes owner name (obtained via JOIN with user table) // // Response Format: // - code: Status code // - message: true // - data: Memory object, including owner_name field func (h *MemoryHandler) GetMemoryConfig(c *gin.Context) { // Get memory_id from URL path memoryID := c.Param("memory_id") if memoryID == "" { c.JSON(http.StatusOK, gin.H{ "code": common.CodeArgumentError, "message": "memory_id is required", "data": nil, }) return } // Call service layer to get memory configuration result, err := h.memoryService.GetMemoryConfig(memoryID) if err != nil { errMsg := err.Error() // Check if it's a "not found" error if strings.Contains(errMsg, "not found") { c.JSON(http.StatusOK, gin.H{ "code": common.CodeNotFound, "message": errMsg, "data": nil, }) return } // Other errors return server error c.JSON(http.StatusOK, gin.H{ "code": common.CodeServerError, "message": errMsg, "data": nil, }) return } // Return success response c.JSON(http.StatusOK, gin.H{ "code": common.CodeSuccess, "message": "success", "data": result, }) } // GetMemoryMessages handles GET request for getting Memory messages // API Path: GET /api/v1/memories/:memory_id // // Function: // - Gets message list associated with the specified memory // - Supports filtering by agent_id // - Supports keyword search and pagination // // Query Parameters: // - agent_id (optional): Agent ID filter, supports comma-separated multiple // - keywords (optional): Keyword search // - page (optional): Page number, default 1 // - page_size (optional): Items per page, default 50 // // Response Format: // - code: Status code // - message: true // - data.messages: Array of message objects // - data.storage_type: Storage type // // TODO: Implementation pending - depends on CanvasService and TaskService func (h *MemoryHandler) GetMemoryMessages(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "code": common.CodeServerError, "message": "GetMemoryMessages not implemented - pending CanvasService and TaskService dependencies", "data": nil, }) } // AddMessage handles POST request for adding messages // API Path: POST /api/v1/messages // // Function: // - Adds messages to one or more memories // - Messages will be embedded and saved to vector database // - Creates asynchronous task for processing // // Request Parameters (JSON Body): // - memory_id (required): Memory ID or ID array // - agent_id (required): Agent ID // - session_id (required): Session ID // - user_input (required): User input // - agent_response (required): Agent response // - user_id (optional): User ID // // TODO: Implementation pending - depends on embedding engine func (h *MemoryHandler) AddMessage(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "code": common.CodeServerError, "message": "AddMessage not implemented - pending embedding engine dependency", "data": nil, }) } // ForgetMessage handles DELETE request for forgetting messages // API Path: DELETE /api/v1/messages/:memory_id/:message_id // // Function: // - Soft-deletes the specified message (sets forget_at timestamp) // - Message is not immediately deleted from database, but marked as "forgotten" // // Parameter Format: // - memory_id: Memory ID // - message_id: Message ID (integer) // // TODO: Implementation pending - depends on embedding engine func (h *MemoryHandler) ForgetMessage(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "code": common.CodeServerError, "message": "ForgetMessage not implemented - pending embedding engine dependency", "data": nil, }) } // UpdateMessage handles PUT request for updating message status // API Path: PUT /api/v1/messages/:memory_id/:message_id // // Function: // - Updates status of the specified message // - status is a boolean, converted to integer for storage (true=1, false=0) // // Request Parameters (JSON Body): // - status (required): Message status, boolean // // TODO: Implementation pending - depends on embedding engine func (h *MemoryHandler) UpdateMessage(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "code": common.CodeServerError, "message": "UpdateMessage not implemented - pending embedding engine dependency", "data": nil, }) } // SearchMessage handles GET request for searching messages // API Path: GET /api/v1/messages/search // // Function: // - Searches messages across multiple memories // - Supports vector similarity search and keyword search // - Fuses results from both search methods // // Query Parameters: // - memory_id (optional): Memory ID list, supports comma separation // - query (optional): Search query text // - similarity_threshold (optional): Similarity threshold, default 0.2 // - keywords_similarity_weight (optional): Keyword weight, default 0.7 // - top_n (optional): Number of results to return, default 5 // - agent_id (optional): Agent ID filter // - session_id (optional): Session ID filter // - user_id (optional): User ID filter // // TODO: Implementation pending - depends on embedding engine func (h *MemoryHandler) SearchMessage(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "code": common.CodeServerError, "message": "SearchMessage not implemented - pending embedding engine dependency", "data": nil, }) } // GetMessages handles GET request for getting message list // API Path: GET /api/v1/messages // // Function: // - Gets recent messages from specified memories // - Supports filtering by agent_id and session_id // // Query Parameters: // - memory_id (required): Memory ID list, supports comma separation // - agent_id (optional): Agent ID filter // - session_id (optional): Session ID filter // - limit (optional): Number of results to return, default 10 // // TODO: Implementation pending - depends on embedding engine func (h *MemoryHandler) GetMessages(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "code": common.CodeServerError, "message": "GetMessages not implemented - pending embedding engine dependency", "data": nil, }) } // GetMessageContent handles GET request for getting message content // API Path: GET /api/v1/messages/:memory_id/:message_id/content // // Function: // - Gets complete content of the specified message // - doc_id format: memory_id + "_" + message_id // // Parameter Format: // - memory_id: Memory ID // - message_id: Message ID (integer) // // TODO: Implementation pending - depends on embedding engine func (h *MemoryHandler) GetMessageContent(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "code": common.CodeServerError, "message": "GetMessageContent not implemented - pending embedding engine dependency", "data": nil, }) } // isArgumentError determines if an error message is an argument error // // Function: // - Checks if the error message contains any argument validation-related prefixes // - Used to distinguish argument errors from server errors // // Parameters: // - msg: Error message string // // Returns: // - bool: true if it's an argument error, false otherwise func isArgumentError(msg string) bool { // Define list of argument error prefixes // Matches Python ArgumentException error messages argumentErrorPrefixes := []string{ "memory name cannot be empty", // Memory name cannot be empty "memory name exceeds limit", // Memory name exceeds limit "memory type must be a list", // memory_type must be a list "memory type is not supported", // Unsupported memory_type } // Check if error message starts with any prefix for _, prefix := range argumentErrorPrefixes { if len(msg) >= len(prefix) && msg[:len(prefix)] == prefix { return true } } return false }