diff --git a/client/agent_manage.go b/client/agent_manage.go new file mode 100644 index 00000000..511553dc --- /dev/null +++ b/client/agent_manage.go @@ -0,0 +1,206 @@ +// Package client provides the implementation for interacting with the WeKnora API +// The Agent management interfaces are used to manage custom agents (CRUD operations) +package client + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" +) + +// Agent represents an agent entity +type Agent struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Avatar string `json:"avatar"` + IsBuiltin bool `json:"is_builtin"` + TenantID uint64 `json:"tenant_id"` + CreatedBy string `json:"created_by"` + Config *AgentConfig `json:"config"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// AgentConfig represents the configuration for an agent +type AgentConfig struct { + AgentMode string `json:"agent_mode"` // "quick-answer" or "smart-reasoning" + SystemPrompt string `json:"system_prompt,omitempty"` + ContextTemplate string `json:"context_template,omitempty"` + ModelID string `json:"model_id,omitempty"` + RerankModelID string `json:"rerank_model_id,omitempty"` + Temperature float64 `json:"temperature,omitempty"` + MaxCompletionTokens int `json:"max_completion_tokens,omitempty"` + MaxIterations int `json:"max_iterations,omitempty"` + AllowedTools []string `json:"allowed_tools,omitempty"` + ReflectionEnabled bool `json:"reflection_enabled,omitempty"` + MCPSelectionMode string `json:"mcp_selection_mode,omitempty"` // "all", "selected", "none" + MCPServices []string `json:"mcp_services,omitempty"` + KBSelectionMode string `json:"kb_selection_mode,omitempty"` // "all", "selected", "none" + KnowledgeBases []string `json:"knowledge_bases,omitempty"` + SupportedFileTypes []string `json:"supported_file_types,omitempty"` + FAQPriorityEnabled bool `json:"faq_priority_enabled,omitempty"` + FAQDirectAnswerThreshold float64 `json:"faq_direct_answer_threshold,omitempty"` + FAQScoreBoost float64 `json:"faq_score_boost,omitempty"` + WebSearchEnabled bool `json:"web_search_enabled,omitempty"` + WebSearchMaxResults int `json:"web_search_max_results,omitempty"` + MultiTurnEnabled bool `json:"multi_turn_enabled,omitempty"` + HistoryTurns int `json:"history_turns,omitempty"` + EmbeddingTopK int `json:"embedding_top_k,omitempty"` + KeywordThreshold float64 `json:"keyword_threshold,omitempty"` + VectorThreshold float64 `json:"vector_threshold,omitempty"` + RerankTopK int `json:"rerank_top_k,omitempty"` + RerankThreshold float64 `json:"rerank_threshold,omitempty"` + EnableQueryExpansion bool `json:"enable_query_expansion,omitempty"` + EnableRewrite bool `json:"enable_rewrite,omitempty"` + RewritePromptSystem string `json:"rewrite_prompt_system,omitempty"` + RewritePromptUser string `json:"rewrite_prompt_user,omitempty"` + FallbackStrategy string `json:"fallback_strategy,omitempty"` // "fixed" or "model" + FallbackResponse string `json:"fallback_response,omitempty"` + FallbackPrompt string `json:"fallback_prompt,omitempty"` +} + +// CreateAgentRequest represents the request to create an agent +type CreateAgentRequest struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Avatar string `json:"avatar,omitempty"` + Config *AgentConfig `json:"config,omitempty"` +} + +// UpdateAgentRequest represents the request to update an agent +type UpdateAgentRequest struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Avatar string `json:"avatar,omitempty"` + Config *AgentConfig `json:"config,omitempty"` +} + +// AgentResponse represents the API response containing a single agent +type AgentResponse struct { + Success bool `json:"success"` + Data Agent `json:"data"` +} + +// AgentListResponse represents the API response containing a list of agents +type AgentListResponse struct { + Success bool `json:"success"` + Data []Agent `json:"data"` +} + +// AgentPlaceholdersResponse represents the API response for placeholder definitions +type AgentPlaceholdersResponse struct { + Success bool `json:"success"` + Data map[string]json.RawMessage `json:"data"` +} + +// CreateAgent creates a new custom agent +func (c *Client) CreateAgent(ctx context.Context, request *CreateAgentRequest) (*Agent, error) { + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/agents", request, nil) + if err != nil { + return nil, err + } + + var response AgentResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + + return &response.Data, nil +} + +// ListAgents retrieves all agents for the current tenant +func (c *Client) ListAgents(ctx context.Context) ([]Agent, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/agents", nil, nil) + if err != nil { + return nil, err + } + + var response AgentListResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + + return response.Data, nil +} + +// GetAgent retrieves an agent by its ID +func (c *Client) GetAgent(ctx context.Context, agentID string) (*Agent, error) { + path := fmt.Sprintf("/api/v1/agents/%s", agentID) + resp, err := c.doRequest(ctx, http.MethodGet, path, nil, nil) + if err != nil { + return nil, err + } + + var response AgentResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + + return &response.Data, nil +} + +// UpdateAgent updates an existing agent +func (c *Client) UpdateAgent(ctx context.Context, agentID string, request *UpdateAgentRequest) (*Agent, error) { + path := fmt.Sprintf("/api/v1/agents/%s", agentID) + resp, err := c.doRequest(ctx, http.MethodPut, path, request, nil) + if err != nil { + return nil, err + } + + var response AgentResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + + return &response.Data, nil +} + +// DeleteAgent deletes a custom agent by its ID +func (c *Client) DeleteAgent(ctx context.Context, agentID string) error { + path := fmt.Sprintf("/api/v1/agents/%s", agentID) + resp, err := c.doRequest(ctx, http.MethodDelete, path, nil, nil) + if err != nil { + return err + } + + var response struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + } + + return parseResponse(resp, &response) +} + +// CopyAgent creates a copy of an existing agent +func (c *Client) CopyAgent(ctx context.Context, agentID string) (*Agent, error) { + path := fmt.Sprintf("/api/v1/agents/%s/copy", agentID) + resp, err := c.doRequest(ctx, http.MethodPost, path, nil, nil) + if err != nil { + return nil, err + } + + var response AgentResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + + return &response.Data, nil +} + +// GetAgentPlaceholders retrieves all available prompt placeholder definitions +func (c *Client) GetAgentPlaceholders(ctx context.Context) (map[string]json.RawMessage, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/agents/placeholders", nil, nil) + if err != nil { + return nil, err + } + + var response AgentPlaceholdersResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + + return response.Data, nil +} diff --git a/client/chunk.go b/client/chunk.go index b594efed..535e78f9 100644 --- a/client/chunk.go +++ b/client/chunk.go @@ -154,6 +154,39 @@ func (c *Client) DeleteChunk(ctx context.Context, knowledgeID string, chunkID st return parseResponse(resp, &response) } +// GetChunkByIDOnly retrieves a chunk by its ID without requiring knowledge ID +func (c *Client) GetChunkByIDOnly(ctx context.Context, chunkID string) (*Chunk, error) { + path := fmt.Sprintf("/api/v1/chunks/get-by-id/%s", chunkID) + resp, err := c.doRequest(ctx, http.MethodGet, path, nil, nil) + if err != nil { + return nil, err + } + + var response ChunkResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + + return &response.Data, nil +} + +// DeleteGeneratedQuestion deletes a generated question from a chunk +func (c *Client) DeleteGeneratedQuestion(ctx context.Context, chunkID string, questionID string) error { + path := fmt.Sprintf("/api/v1/chunks/%s/delete-question", chunkID) + req := map[string]string{"question_id": questionID} + resp, err := c.doRequest(ctx, http.MethodDelete, path, req, nil) + if err != nil { + return err + } + + var response struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + } + + return parseResponse(resp, &response) +} + // DeleteChunksByKnowledgeID deletes all chunks under a knowledge document // Batch deletes all chunks under the specified knowledge document // Parameters: diff --git a/client/initialization.go b/client/initialization.go new file mode 100644 index 00000000..c26f2e31 --- /dev/null +++ b/client/initialization.go @@ -0,0 +1,255 @@ +package client + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" +) + +// InitializationConfig represents the initialization configuration for a knowledge base +type InitializationConfig struct { + ChatModelID string `json:"chat_model_id,omitempty"` + EmbeddingModelID string `json:"embedding_model_id,omitempty"` + RerankModelID string `json:"rerank_model_id,omitempty"` + MultimodalID string `json:"multimodal_id,omitempty"` +} + +// OllamaModelInfo represents info about an Ollama model +type OllamaModelInfo struct { + Name string `json:"name"` + Size int64 `json:"size"` + ModifiedAt string `json:"modified_at"` +} + +// DownloadTask represents an Ollama model download task +type DownloadTask struct { + ID string `json:"id"` + ModelName string `json:"modelName"` + Status string `json:"status"` + Progress float64 `json:"progress"` + Message string `json:"message"` + StartTime time.Time `json:"startTime"` + EndTime *time.Time `json:"endTime,omitempty"` +} + +// ModelCheckResult represents the result of checking a remote model +type ModelCheckResult struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` +} + +// GetInitializationConfig gets the current initialization config for a knowledge base +func (c *Client) GetInitializationConfig(ctx context.Context, kbID string) (*InitializationConfig, error) { + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/initialization/config/%s", kbID), nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data *InitializationConfig `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// InitializeByKB initializes a knowledge base with model configuration +func (c *Client) InitializeByKB(ctx context.Context, kbID string, config *InitializationConfig) error { + resp, err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/api/v1/initialization/initialize/%s", kbID), config, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// UpdateKBConfig updates the model configuration for a knowledge base +func (c *Client) UpdateKBConfig(ctx context.Context, kbID string, config *InitializationConfig) error { + resp, err := c.doRequest(ctx, http.MethodPut, fmt.Sprintf("/api/v1/initialization/config/%s", kbID), config, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// CheckOllamaStatus checks if Ollama is running and accessible +func (c *Client) CheckOllamaStatus(ctx context.Context) (bool, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/initialization/ollama/status", nil, nil) + if err != nil { + return false, err + } + var result struct { + Success bool `json:"success"` + Data struct { + Available bool `json:"available"` + } `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return false, err + } + return result.Data.Available, nil +} + +// ListOllamaModels lists all locally available Ollama models +func (c *Client) ListOllamaModels(ctx context.Context) ([]OllamaModelInfo, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/initialization/ollama/models", nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data []OllamaModelInfo `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// CheckOllamaModels checks if specific Ollama models are available +func (c *Client) CheckOllamaModels(ctx context.Context, models []string) (map[string]bool, error) { + req := map[string][]string{"models": models} + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/initialization/ollama/models/check", req, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data map[string]bool `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// DownloadOllamaModel starts downloading an Ollama model +func (c *Client) DownloadOllamaModel(ctx context.Context, modelName string) (*DownloadTask, error) { + req := map[string]string{"model": modelName} + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/initialization/ollama/models/download", req, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data *DownloadTask `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// GetOllamaDownloadProgress gets the download progress of an Ollama model +func (c *Client) GetOllamaDownloadProgress(ctx context.Context, taskID string) (*DownloadTask, error) { + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/initialization/ollama/download/progress/%s", taskID), nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data *DownloadTask `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// ListOllamaDownloadTasks lists all Ollama download tasks +func (c *Client) ListOllamaDownloadTasks(ctx context.Context) ([]*DownloadTask, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/initialization/ollama/download/tasks", nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data []*DownloadTask `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// CheckRemoteModel checks if a remote model API is accessible +func (c *Client) CheckRemoteModel(ctx context.Context, params map[string]string) (*ModelCheckResult, error) { + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/initialization/remote/check", params, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data *ModelCheckResult `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// TestEmbeddingModel tests an embedding model +func (c *Client) TestEmbeddingModel(ctx context.Context, params map[string]string) (*ModelCheckResult, error) { + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/initialization/embedding/test", params, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data *ModelCheckResult `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// CheckRerankModel checks if a rerank model is accessible +func (c *Client) CheckRerankModel(ctx context.Context, params map[string]string) (*ModelCheckResult, error) { + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/initialization/rerank/check", params, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data *ModelCheckResult `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// TestMultimodalFunction tests multimodal model functionality +func (c *Client) TestMultimodalFunction(ctx context.Context, params map[string]string) (*ModelCheckResult, error) { + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/initialization/multimodal/test", params, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data *ModelCheckResult `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// ExtractTextRelations extracts text relations for knowledge graph +func (c *Client) ExtractTextRelations(ctx context.Context, params any) (json.RawMessage, error) { + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/initialization/extract/text-relation", params, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data json.RawMessage `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} diff --git a/client/knowledge.go b/client/knowledge.go index b2fa7934..f0c22106 100644 --- a/client/knowledge.go +++ b/client/knowledge.go @@ -432,3 +432,177 @@ func (c *Client) UpdateImageInfo(ctx context.Context, return parseResponse(resp, &response) } + +// CreateManualKnowledgeRequest contains the parameters for creating a manual Markdown knowledge entry. +type CreateManualKnowledgeRequest struct { + Title string `json:"title"` + Content string `json:"content"` + TagID string `json:"tag_id,omitempty"` +} + +// UpdateManualKnowledgeRequest contains the parameters for updating a manual Markdown knowledge entry. +type UpdateManualKnowledgeRequest struct { + Title string `json:"title,omitempty"` + Content string `json:"content,omitempty"` +} + +// BatchUpdateKnowledgeTagsRequest contains the mapping of knowledge IDs to tag IDs. +type BatchUpdateKnowledgeTagsRequest struct { + Updates map[string]*string `json:"updates"` // knowledge_id -> tag_id (nil to clear) +} + +// CreateManualKnowledge creates a knowledge entry from manual Markdown content. +func (c *Client) CreateManualKnowledge(ctx context.Context, knowledgeBaseID string, request *CreateManualKnowledgeRequest) (*Knowledge, error) { + path := fmt.Sprintf("/api/v1/knowledge-bases/%s/knowledge/manual", knowledgeBaseID) + resp, err := c.doRequest(ctx, http.MethodPost, path, request, nil) + if err != nil { + return nil, err + } + + var response KnowledgeResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + + return &response.Data, nil +} + +// UpdateManualKnowledge updates a manual Markdown knowledge entry. +func (c *Client) UpdateManualKnowledge(ctx context.Context, knowledgeID string, request *UpdateManualKnowledgeRequest) (*Knowledge, error) { + path := fmt.Sprintf("/api/v1/knowledge/manual/%s", knowledgeID) + resp, err := c.doRequest(ctx, http.MethodPut, path, request, nil) + if err != nil { + return nil, err + } + + var response KnowledgeResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + + return &response.Data, nil +} + +// FilterKnowledgeResponse represents the response from filter knowledge API +type FilterKnowledgeResponse struct { + Success bool `json:"success"` + Data []Knowledge `json:"data"` + HasMore bool `json:"has_more"` +} + +// FilterKnowledge searches/filters knowledge entries across knowledge bases +func (c *Client) FilterKnowledge(ctx context.Context, keyword string, offset, limit int, fileTypes []string, agentID string) ([]Knowledge, bool, error) { + queryParams := url.Values{} + if keyword != "" { + queryParams.Set("keyword", keyword) + } + queryParams.Set("offset", strconv.Itoa(offset)) + queryParams.Set("limit", strconv.Itoa(limit)) + if len(fileTypes) > 0 { + for _, ft := range fileTypes { + queryParams.Add("file_types", ft) + } + } + if agentID != "" { + queryParams.Set("agent_id", agentID) + } + + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/knowledge/search", nil, queryParams) + if err != nil { + return nil, false, err + } + + var response FilterKnowledgeResponse + if err := parseResponse(resp, &response); err != nil { + return nil, false, err + } + + return response.Data, response.HasMore, nil +} + +// MoveKnowledgeRequest contains the parameters for moving knowledge between KBs +type MoveKnowledgeRequest struct { + KnowledgeIDs []string `json:"knowledge_ids"` + SourceKBID string `json:"source_kb_id"` + TargetKBID string `json:"target_kb_id"` + Mode string `json:"mode"` // "reuse_vectors" or "reparse" +} + +// MoveKnowledgeResponse represents the response from move knowledge API +type MoveKnowledgeResponse struct { + TaskID string `json:"task_id"` + SourceKBID string `json:"source_kb_id"` + TargetKBID string `json:"target_kb_id"` + KnowledgeCount int `json:"knowledge_count"` + Message string `json:"message"` +} + +// MoveKnowledge moves knowledge items from one knowledge base to another (async task) +func (c *Client) MoveKnowledge(ctx context.Context, req *MoveKnowledgeRequest) (*MoveKnowledgeResponse, error) { + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/knowledge/move", req, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data *MoveKnowledgeResponse `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// KnowledgeMoveProgress represents the progress of a knowledge move task +type KnowledgeMoveProgress struct { + TaskID string `json:"task_id"` + Status string `json:"status"` + Progress int `json:"progress"` + Total int `json:"total"` + Processed int `json:"processed"` + Message string `json:"message"` + Error string `json:"error,omitempty"` +} + +// GetKnowledgeMoveProgress gets the progress of a knowledge move task +func (c *Client) GetKnowledgeMoveProgress(ctx context.Context, taskID string) (*KnowledgeMoveProgress, error) { + path := fmt.Sprintf("/api/v1/knowledge/move/progress/%s", taskID) + resp, err := c.doRequest(ctx, http.MethodGet, path, nil, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data *KnowledgeMoveProgress `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// PreviewKnowledgeFile returns the file content for inline preview. +// The caller is responsible for reading and closing the response body. +func (c *Client) PreviewKnowledgeFile(ctx context.Context, knowledgeID string) (*http.Response, error) { + path := fmt.Sprintf("/api/v1/knowledge/%s/preview", knowledgeID) + return c.doRequest(ctx, http.MethodGet, path, nil, nil) +} + +// BatchUpdateKnowledgeTags batch updates knowledge tags. +// The updates map contains knowledge_id -> tag_id mappings. Set tag_id to nil to clear the tag. +func (c *Client) BatchUpdateKnowledgeTags(ctx context.Context, updates map[string]*string) error { + request := &BatchUpdateKnowledgeTagsRequest{Updates: updates} + resp, err := c.doRequest(ctx, http.MethodPut, "/api/v1/knowledge/tags", request, nil) + if err != nil { + return err + } + + var batchResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + } + + return parseResponse(resp, &batchResponse) +} diff --git a/client/knowledgebase.go b/client/knowledgebase.go index 9224b71a..1721ee02 100644 --- a/client/knowledgebase.go +++ b/client/knowledgebase.go @@ -311,6 +311,46 @@ func (c *Client) HybridSearch(ctx context.Context, knowledgeBaseID string, param return response.Data, nil } +// TogglePinKnowledgeBase toggles the pin status of a knowledge base +func (c *Client) TogglePinKnowledgeBase(ctx context.Context, knowledgeBaseID string) (*KnowledgeBase, error) { + path := fmt.Sprintf("/api/v1/knowledge-bases/%s/pin", knowledgeBaseID) + resp, err := c.doRequest(ctx, http.MethodPost, path, nil, nil) + if err != nil { + return nil, err + } + + var response KnowledgeBaseResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + + return &response.Data, nil +} + +// MoveTarget represents a knowledge base that can receive moved knowledge +type MoveTarget struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Description string `json:"description"` +} + +// ListMoveTargets lists knowledge bases eligible as move targets for the given source KB +func (c *Client) ListMoveTargets(ctx context.Context, knowledgeBaseID string) ([]KnowledgeBase, error) { + path := fmt.Sprintf("/api/v1/knowledge-bases/%s/move-targets", knowledgeBaseID) + resp, err := c.doRequest(ctx, http.MethodGet, path, nil, nil) + if err != nil { + return nil, err + } + + var response KnowledgeBaseListResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + + return response.Data, nil +} + // CopyKnowledgeBase copies a knowledge base asynchronously and returns task info func (c *Client) CopyKnowledgeBase(ctx context.Context, request *CopyKnowledgeBaseRequest) (*CopyKnowledgeBaseResponse, error) { path := "/api/v1/knowledge-bases/copy" diff --git a/client/mcp_service.go b/client/mcp_service.go new file mode 100644 index 00000000..fa19978f --- /dev/null +++ b/client/mcp_service.go @@ -0,0 +1,207 @@ +package client + +import ( + "context" + "encoding/json" + "fmt" + "net/http" +) + +// MCPTransportType represents the transport type for MCP service +type MCPTransportType string + +const ( + MCPTransportSSE MCPTransportType = "sse" + MCPTransportHTTPStreamable MCPTransportType = "http-streamable" + MCPTransportStdio MCPTransportType = "stdio" +) + +// MCPService represents an MCP service configuration +type MCPService struct { + ID string `json:"id"` + TenantID uint64 `json:"tenant_id"` + Name string `json:"name"` + Description string `json:"description"` + Enabled bool `json:"enabled"` + TransportType MCPTransportType `json:"transport_type"` + URL *string `json:"url,omitempty"` + Headers map[string]string `json:"headers"` + AuthConfig *MCPAuthConfig `json:"auth_config"` + AdvancedConfig *MCPAdvancedConfig `json:"advanced_config"` + StdioConfig *MCPStdioConfig `json:"stdio_config,omitempty"` + EnvVars map[string]string `json:"env_vars,omitempty"` + IsBuiltin bool `json:"is_builtin"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +// MCPAuthConfig represents authentication configuration for MCP service +type MCPAuthConfig struct { + APIKey string `json:"api_key,omitempty"` + Token string `json:"token,omitempty"` + CustomHeaders map[string]string `json:"custom_headers,omitempty"` +} + +// MCPAdvancedConfig represents advanced configuration for MCP service +type MCPAdvancedConfig struct { + Timeout int `json:"timeout"` + RetryCount int `json:"retry_count"` + RetryDelay int `json:"retry_delay"` +} + +// MCPStdioConfig represents stdio transport configuration +type MCPStdioConfig struct { + Command string `json:"command"` + Args []string `json:"args"` +} + +// MCPTool represents a tool exposed by an MCP service +type MCPTool struct { + Name string `json:"name"` + Description string `json:"description"` + InputSchema json.RawMessage `json:"inputSchema"` +} + +// MCPResource represents a resource exposed by an MCP service +type MCPResource struct { + URI string `json:"uri"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + MimeType string `json:"mimeType,omitempty"` +} + +// MCPTestResult represents the result of testing an MCP service connection +type MCPTestResult struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Tools []*MCPTool `json:"tools,omitempty"` + Resources []*MCPResource `json:"resources,omitempty"` +} + +// CreateMCPService creates a new MCP service +func (c *Client) CreateMCPService(ctx context.Context, service *MCPService) (*MCPService, error) { + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/mcp-services", service, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data *MCPService `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// ListMCPServices lists all MCP services for the current tenant +func (c *Client) ListMCPServices(ctx context.Context) ([]*MCPService, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/mcp-services", nil, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data []*MCPService `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// GetMCPService gets an MCP service by ID +func (c *Client) GetMCPService(ctx context.Context, serviceID string) (*MCPService, error) { + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/mcp-services/%s", serviceID), nil, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data *MCPService `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// UpdateMCPService updates an MCP service +func (c *Client) UpdateMCPService(ctx context.Context, serviceID string, updates map[string]interface{}) (*MCPService, error) { + resp, err := c.doRequest(ctx, http.MethodPut, fmt.Sprintf("/api/v1/mcp-services/%s", serviceID), updates, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data *MCPService `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// DeleteMCPService deletes an MCP service +func (c *Client) DeleteMCPService(ctx context.Context, serviceID string) error { + resp, err := c.doRequest(ctx, http.MethodDelete, fmt.Sprintf("/api/v1/mcp-services/%s", serviceID), nil, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// TestMCPService tests an MCP service connection +func (c *Client) TestMCPService(ctx context.Context, serviceID string) (*MCPTestResult, error) { + resp, err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/api/v1/mcp-services/%s/test", serviceID), nil, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data *MCPTestResult `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// GetMCPServiceTools gets the tools provided by an MCP service +func (c *Client) GetMCPServiceTools(ctx context.Context, serviceID string) ([]*MCPTool, error) { + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/mcp-services/%s/tools", serviceID), nil, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data []*MCPTool `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// GetMCPServiceResources gets the resources provided by an MCP service +func (c *Client) GetMCPServiceResources(ctx context.Context, serviceID string) ([]*MCPResource, error) { + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/mcp-services/%s/resources", serviceID), nil, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data []*MCPResource `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} diff --git a/client/message.go b/client/message.go index 575bb8f6..4699bb0a 100644 --- a/client/message.go +++ b/client/message.go @@ -102,6 +102,76 @@ func (c *Client) GetMessagesBefore( return c.LoadMessages(ctx, sessionID, limit, &beforeTime) } +// SearchMessagesRequest defines the request structure for searching messages +type SearchMessagesRequest struct { + Query string `json:"query"` + Mode string `json:"mode"` + Limit int `json:"limit"` + SessionIDs []string `json:"session_ids,omitempty"` +} + +// MessageSearchGroupItem represents a grouped search result item +type MessageSearchGroupItem struct { + RequestID string `json:"request_id"` + SessionID string `json:"session_id"` + SessionTitle string `json:"session_title"` + QueryContent string `json:"query_content"` + AnswerContent string `json:"answer_content"` + Score float64 `json:"score"` + MatchType string `json:"match_type"` + CreatedAt time.Time `json:"created_at"` +} + +// MessageSearchResult represents the result of a message search +type MessageSearchResult struct { + Items []*MessageSearchGroupItem `json:"items"` + Total int `json:"total"` +} + +// ChatHistoryKBStats represents statistics about the chat history knowledge base +type ChatHistoryKBStats struct { + Enabled bool `json:"enabled"` + EmbeddingModelID string `json:"embedding_model_id,omitempty"` + KnowledgeBaseID string `json:"knowledge_base_id,omitempty"` + KnowledgeBaseName string `json:"knowledge_base_name,omitempty"` + IndexedMessageCount int64 `json:"indexed_message_count"` + HasIndexedMessages bool `json:"has_indexed_messages"` +} + +// SearchMessages searches chat history messages +func (c *Client) SearchMessages(ctx context.Context, req *SearchMessagesRequest) (*MessageSearchResult, error) { + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/messages/search", req, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data *MessageSearchResult `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// GetChatHistoryKBStats gets chat history knowledge base statistics +func (c *Client) GetChatHistoryKBStats(ctx context.Context) (*ChatHistoryKBStats, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/messages/chat-history-stats", nil, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data *ChatHistoryKBStats `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + // DeleteMessage deletes a message func (c *Client) DeleteMessage(ctx context.Context, sessionID string, messageID string) error { path := fmt.Sprintf("/api/v1/messages/%s/%s", sessionID, messageID) diff --git a/client/model.go b/client/model.go index ecde9f65..4d295855 100644 --- a/client/model.go +++ b/client/model.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "net/http" + "net/url" ) // ModelType model type @@ -153,3 +154,40 @@ func (c *Client) DeleteModel(ctx context.Context, modelID string) error { return parseResponse(resp, &response) } + +// ModelProvider represents a model provider with its supported types and default URLs +type ModelProvider struct { + Value string `json:"value"` + Label string `json:"label"` + Description string `json:"description"` + DefaultURLs map[string]string `json:"defaultUrls"` + ModelTypes []string `json:"modelTypes"` +} + +// ModelProviderListResponse represents the API response for listing model providers +type ModelProviderListResponse struct { + Success bool `json:"success"` + Data []ModelProvider `json:"data"` +} + +// ListModelProviders retrieves the list of supported model providers. +// modelType is optional and can be used to filter by type: "chat", "embedding", "rerank", "vllm". +func (c *Client) ListModelProviders(ctx context.Context, modelType string) ([]ModelProvider, error) { + var queryParams url.Values + if modelType != "" { + queryParams = url.Values{} + queryParams.Add("model_type", modelType) + } + + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/models/providers", nil, queryParams) + if err != nil { + return nil, err + } + + var response ModelProviderListResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + + return response.Data, nil +} \ No newline at end of file diff --git a/client/organization.go b/client/organization.go new file mode 100644 index 00000000..ee6c4023 --- /dev/null +++ b/client/organization.go @@ -0,0 +1,630 @@ +package client + +import ( + "context" + "fmt" + "net/http" + "net/url" + "time" +) + +// Organization represents a collaboration organization +type Organization struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Avatar string `json:"avatar,omitempty"` + OwnerID string `json:"owner_id"` + InviteCode string `json:"invite_code,omitempty"` + InviteCodeExpiresAt *time.Time `json:"invite_code_expires_at,omitempty"` + InviteCodeValidityDays int `json:"invite_code_validity_days"` + RequireApproval bool `json:"require_approval"` + Searchable bool `json:"searchable"` + MemberLimit int `json:"member_limit"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// OrganizationResponse represents an organization in API responses (with counts) +type OrganizationResponse struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Avatar string `json:"avatar,omitempty"` + OwnerID string `json:"owner_id"` + InviteCode string `json:"invite_code,omitempty"` + InviteCodeExpiresAt *time.Time `json:"invite_code_expires_at,omitempty"` + InviteCodeValidityDays int `json:"invite_code_validity_days"` + RequireApproval bool `json:"require_approval"` + Searchable bool `json:"searchable"` + MemberLimit int `json:"member_limit"` + MemberCount int `json:"member_count"` + ShareCount int `json:"share_count"` + AgentShareCount int `json:"agent_share_count"` + PendingJoinRequestCount int `json:"pending_join_request_count"` + IsOwner bool `json:"is_owner"` + MyRole string `json:"my_role,omitempty"` + HasPendingUpgrade bool `json:"has_pending_upgrade"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// CreateOrganizationRequest represents a request to create an organization +type CreateOrganizationRequest struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Avatar string `json:"avatar,omitempty"` + InviteCodeValidityDays *int `json:"invite_code_validity_days,omitempty"` + MemberLimit *int `json:"member_limit,omitempty"` +} + +// UpdateOrganizationRequest represents a request to update an organization +type UpdateOrganizationRequest struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Avatar *string `json:"avatar,omitempty"` + RequireApproval *bool `json:"require_approval,omitempty"` + Searchable *bool `json:"searchable,omitempty"` + InviteCodeValidityDays *int `json:"invite_code_validity_days,omitempty"` + MemberLimit *int `json:"member_limit,omitempty"` +} + +// OrganizationMemberResponse represents a member in API responses +type OrganizationMemberResponse struct { + ID string `json:"id"` + UserID string `json:"user_id"` + Username string `json:"username"` + Email string `json:"email"` + Avatar string `json:"avatar"` + Role string `json:"role"` + TenantID uint64 `json:"tenant_id"` + JoinedAt time.Time `json:"joined_at"` +} + +// KnowledgeBaseShareResponse represents a KB share record in API responses +type KnowledgeBaseShareResponse struct { + ID string `json:"id"` + KnowledgeBaseID string `json:"knowledge_base_id"` + KnowledgeBaseName string `json:"knowledge_base_name"` + OrganizationID string `json:"organization_id"` + OrganizationName string `json:"organization_name"` + SharedByUserID string `json:"shared_by_user_id"` + SharedByUsername string `json:"shared_by_username"` + SourceTenantID uint64 `json:"source_tenant_id"` + Permission string `json:"permission"` + MyRoleInOrg string `json:"my_role_in_org"` + MyPermission string `json:"my_permission"` + CreatedAt time.Time `json:"created_at"` +} + +// AgentShareResponse represents an agent share record in API responses +type AgentShareResponse struct { + ID string `json:"id"` + AgentID string `json:"agent_id"` + AgentName string `json:"agent_name"` + OrganizationID string `json:"organization_id"` + OrganizationName string `json:"organization_name"` + SharedByUserID string `json:"shared_by_user_id"` + SharedByUsername string `json:"shared_by_username"` + SourceTenantID uint64 `json:"source_tenant_id"` + Permission string `json:"permission"` + CreatedAt time.Time `json:"created_at"` +} + +// JoinRequestResponse represents a join request in API responses +type JoinRequestResponse struct { + ID string `json:"id"` + UserID string `json:"user_id"` + Username string `json:"username"` + Email string `json:"email"` + Message string `json:"message"` + RequestType string `json:"request_type"` + PrevRole string `json:"prev_role"` + RequestedRole string `json:"requested_role"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` + ReviewedAt *time.Time `json:"reviewed_at,omitempty"` +} + +// SharedKnowledgeBaseInfo represents a shared knowledge base +type SharedKnowledgeBaseInfo struct { + ShareID string `json:"share_id"` + OrganizationID string `json:"organization_id"` + OrgName string `json:"org_name"` + Permission string `json:"permission"` + SourceTenantID uint64 `json:"source_tenant_id"` + SharedAt time.Time `json:"shared_at"` +} + +// SharedAgentInfo represents a shared agent +type SharedAgentInfo struct { + ShareID string `json:"share_id"` + OrganizationID string `json:"organization_id"` + OrgName string `json:"org_name"` + Permission string `json:"permission"` + SourceTenantID uint64 `json:"source_tenant_id"` + SharedAt time.Time `json:"shared_at"` +} + +// --- Organization CRUD --- + +// CreateOrganization creates a new organization +func (c *Client) CreateOrganization(ctx context.Context, req *CreateOrganizationRequest) (*OrganizationResponse, error) { + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/organizations", req, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data *OrganizationResponse `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// ListMyOrganizations lists organizations the current user belongs to +func (c *Client) ListMyOrganizations(ctx context.Context) ([]OrganizationResponse, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/organizations", nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data struct { + Organizations []OrganizationResponse `json:"organizations"` + } `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data.Organizations, nil +} + +// GetOrganization gets an organization by ID +func (c *Client) GetOrganization(ctx context.Context, orgID string) (*OrganizationResponse, error) { + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/organizations/%s", orgID), nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data *OrganizationResponse `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// UpdateOrganization updates an organization +func (c *Client) UpdateOrganization(ctx context.Context, orgID string, req *UpdateOrganizationRequest) (*OrganizationResponse, error) { + resp, err := c.doRequest(ctx, http.MethodPut, fmt.Sprintf("/api/v1/organizations/%s", orgID), req, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data *OrganizationResponse `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// DeleteOrganization deletes an organization +func (c *Client) DeleteOrganization(ctx context.Context, orgID string) error { + resp, err := c.doRequest(ctx, http.MethodDelete, fmt.Sprintf("/api/v1/organizations/%s", orgID), nil, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// --- Organization membership --- + +// JoinOrganizationByInviteCode joins an organization using an invite code +func (c *Client) JoinOrganizationByInviteCode(ctx context.Context, inviteCode string) error { + req := map[string]string{"invite_code": inviteCode} + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/organizations/join", req, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// SubmitJoinRequest submits a join request for organizations that require approval +func (c *Client) SubmitJoinRequest(ctx context.Context, inviteCode, message, role string) error { + req := map[string]string{ + "invite_code": inviteCode, + "message": message, + "role": role, + } + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/organizations/join-request", req, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// SearchOrganizations searches for discoverable organizations +func (c *Client) SearchOrganizations(ctx context.Context, keyword string, page, pageSize int) ([]OrganizationResponse, error) { + q := url.Values{} + if keyword != "" { + q.Set("keyword", keyword) + } + if page > 0 { + q.Set("page", fmt.Sprintf("%d", page)) + } + if pageSize > 0 { + q.Set("page_size", fmt.Sprintf("%d", pageSize)) + } + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/organizations/search", nil, q) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data struct { + Organizations []OrganizationResponse `json:"organizations"` + } `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data.Organizations, nil +} + +// JoinByOrganizationID joins a searchable organization by its ID +func (c *Client) JoinByOrganizationID(ctx context.Context, orgID, message, role string) error { + req := map[string]string{ + "organization_id": orgID, + "message": message, + "role": role, + } + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/organizations/join-by-id", req, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// PreviewOrganizationByInviteCode previews an organization before joining +func (c *Client) PreviewOrganizationByInviteCode(ctx context.Context, code string) (*OrganizationResponse, error) { + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/organizations/preview/%s", code), nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data *OrganizationResponse `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// LeaveOrganization leaves an organization +func (c *Client) LeaveOrganization(ctx context.Context, orgID string) error { + resp, err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/api/v1/organizations/%s/leave", orgID), nil, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// RequestRoleUpgrade requests a role upgrade in an organization +func (c *Client) RequestRoleUpgrade(ctx context.Context, orgID, requestedRole, message string) error { + req := map[string]string{ + "requested_role": requestedRole, + "message": message, + } + resp, err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/api/v1/organizations/%s/request-upgrade", orgID), req, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// GenerateInviteCode generates a new invite code for an organization +func (c *Client) GenerateInviteCode(ctx context.Context, orgID string) (string, error) { + resp, err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/api/v1/organizations/%s/invite-code", orgID), nil, nil) + if err != nil { + return "", err + } + var result struct { + Success bool `json:"success"` + Data struct { + InviteCode string `json:"invite_code"` + } `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return "", err + } + return result.Data.InviteCode, nil +} + +// SearchUsersForInvite searches users to invite into an organization (admin only) +func (c *Client) SearchUsersForInvite(ctx context.Context, orgID, keyword string) ([]UserInfo, error) { + q := url.Values{} + if keyword != "" { + q.Set("keyword", keyword) + } + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/organizations/%s/search-users", orgID), nil, q) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data []UserInfo `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// InviteMember directly invites a user to an organization (admin only) +func (c *Client) InviteMember(ctx context.Context, orgID, userID, role string) error { + req := map[string]string{ + "user_id": userID, + "role": role, + } + resp, err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/api/v1/organizations/%s/invite", orgID), req, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// ListMembers lists members of an organization +func (c *Client) ListOrgMembers(ctx context.Context, orgID string) ([]OrganizationMemberResponse, error) { + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/organizations/%s/members", orgID), nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data struct { + Members []OrganizationMemberResponse `json:"members"` + } `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data.Members, nil +} + +// UpdateMemberRole updates a member's role in an organization +func (c *Client) UpdateMemberRole(ctx context.Context, orgID, userID, role string) error { + req := map[string]string{"role": role} + resp, err := c.doRequest(ctx, http.MethodPut, fmt.Sprintf("/api/v1/organizations/%s/members/%s", orgID, userID), req, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// RemoveMember removes a member from an organization +func (c *Client) RemoveMember(ctx context.Context, orgID, userID string) error { + resp, err := c.doRequest(ctx, http.MethodDelete, fmt.Sprintf("/api/v1/organizations/%s/members/%s", orgID, userID), nil, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// --- Join request management --- + +// ListJoinRequests lists pending join requests (admin only) +func (c *Client) ListJoinRequests(ctx context.Context, orgID string) ([]JoinRequestResponse, error) { + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/organizations/%s/join-requests", orgID), nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data struct { + Requests []JoinRequestResponse `json:"requests"` + } `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data.Requests, nil +} + +// ReviewJoinRequest reviews a join request (approve/reject) +func (c *Client) ReviewJoinRequest(ctx context.Context, orgID, requestID string, approved bool, message, role string) error { + req := map[string]any{ + "approved": approved, + "message": message, + "role": role, + } + resp, err := c.doRequest(ctx, http.MethodPut, fmt.Sprintf("/api/v1/organizations/%s/join-requests/%s/review", orgID, requestID), req, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// --- Knowledge base sharing --- + +// ShareKnowledgeBase shares a knowledge base with an organization +func (c *Client) ShareKnowledgeBase(ctx context.Context, kbID, orgID, permission string) (*KnowledgeBaseShareResponse, error) { + req := map[string]string{ + "organization_id": orgID, + "permission": permission, + } + resp, err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/api/v1/knowledge-bases/%s/shares", kbID), req, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data *KnowledgeBaseShareResponse `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// ListKBShares lists shares of a knowledge base +func (c *Client) ListKBShares(ctx context.Context, kbID string) ([]KnowledgeBaseShareResponse, error) { + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/knowledge-bases/%s/shares", kbID), nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data struct { + Shares []KnowledgeBaseShareResponse `json:"shares"` + } `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data.Shares, nil +} + +// UpdateSharePermission updates a KB share's permission +func (c *Client) UpdateSharePermission(ctx context.Context, kbID, shareID, permission string) error { + req := map[string]string{"permission": permission} + resp, err := c.doRequest(ctx, http.MethodPut, fmt.Sprintf("/api/v1/knowledge-bases/%s/shares/%s", kbID, shareID), req, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// RemoveKBShare removes a KB share +func (c *Client) RemoveKBShare(ctx context.Context, kbID, shareID string) error { + resp, err := c.doRequest(ctx, http.MethodDelete, fmt.Sprintf("/api/v1/knowledge-bases/%s/shares/%s", kbID, shareID), nil, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// --- Agent sharing --- + +// ShareAgent shares an agent with an organization +func (c *Client) ShareAgent(ctx context.Context, agentID, orgID, permission string) (*AgentShareResponse, error) { + req := map[string]string{ + "organization_id": orgID, + "permission": permission, + } + resp, err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/api/v1/agents/%s/shares", agentID), req, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data *AgentShareResponse `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// ListAgentShares lists shares of an agent +func (c *Client) ListAgentShares(ctx context.Context, agentID string) ([]AgentShareResponse, error) { + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/agents/%s/shares", agentID), nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data struct { + Shares []AgentShareResponse `json:"shares"` + } `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data.Shares, nil +} + +// RemoveAgentShare removes an agent share +func (c *Client) RemoveAgentShare(ctx context.Context, agentID, shareID string) error { + resp, err := c.doRequest(ctx, http.MethodDelete, fmt.Sprintf("/api/v1/agents/%s/shares/%s", agentID, shareID), nil, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// --- Organization shared resources --- + +// ListOrgShares lists knowledge bases shared to an organization +func (c *Client) ListOrgShares(ctx context.Context, orgID string) ([]KnowledgeBaseShareResponse, error) { + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/organizations/%s/shares", orgID), nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data struct { + Shares []KnowledgeBaseShareResponse `json:"shares"` + } `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data.Shares, nil +} + +// ListOrgAgentShares lists agents shared to an organization +func (c *Client) ListOrgAgentShares(ctx context.Context, orgID string) ([]AgentShareResponse, error) { + resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/organizations/%s/agent-shares", orgID), nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data struct { + Shares []AgentShareResponse `json:"shares"` + } `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data.Shares, nil +} + +// ListSharedKnowledgeBases lists all knowledge bases shared to the current user +func (c *Client) ListSharedKnowledgeBases(ctx context.Context) ([]SharedKnowledgeBaseInfo, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/shared-knowledge-bases", nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data []SharedKnowledgeBaseInfo `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// ListSharedAgents lists all agents shared to the current user +func (c *Client) ListSharedAgents(ctx context.Context) ([]SharedAgentInfo, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/shared-agents", nil, nil) + if err != nil { + return nil, err + } + var result struct { + Success bool `json:"success"` + Data []SharedAgentInfo `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} diff --git a/client/session.go b/client/session.go index c6487167..9ea3621b 100644 --- a/client/session.go +++ b/client/session.go @@ -147,6 +147,25 @@ func (c *Client) DeleteSession(ctx context.Context, sessionID string) error { return parseResponse(resp, &response) } +// BatchDeleteSessions deletes multiple sessions by their IDs. +func (c *Client) BatchDeleteSessions(ctx context.Context, sessionIDs []string) error { + request := struct { + IDs []string `json:"ids"` + }{IDs: sessionIDs} + + resp, err := c.doRequest(ctx, http.MethodDelete, "/api/v1/sessions/batch", request, nil) + if err != nil { + return err + } + + var response struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + } + + return parseResponse(resp, &response) +} + // GenerateTitleRequest title generation request type GenerateTitleRequest struct { Messages []Message `json:"messages"` diff --git a/client/skill.go b/client/skill.go new file mode 100644 index 00000000..11655fe1 --- /dev/null +++ b/client/skill.go @@ -0,0 +1,34 @@ +package client + +import ( + "context" + "net/http" +) + +// SkillInfo represents skill metadata +type SkillInfo struct { + Name string `json:"name"` + Description string `json:"description"` +} + +// SkillListResponse represents the response from listing skills +type SkillListResponse struct { + Success bool `json:"success"` + Data []SkillInfo `json:"data"` + SkillsAvailable bool `json:"skills_available"` +} + +// ListSkills lists all preloaded agent skills +func (c *Client) ListSkills(ctx context.Context) ([]SkillInfo, bool, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/skills", nil, nil) + if err != nil { + return nil, false, err + } + + var response SkillListResponse + if err := parseResponse(resp, &response); err != nil { + return nil, false, err + } + + return response.Data, response.SkillsAvailable, nil +} diff --git a/client/system.go b/client/system.go new file mode 100644 index 00000000..b3377a8b --- /dev/null +++ b/client/system.go @@ -0,0 +1,174 @@ +package client + +import ( + "context" + "encoding/json" + "net/http" +) + +// SystemInfo represents system version and configuration information +type SystemInfo struct { + Version string `json:"version"` + Edition string `json:"edition"` + CommitID string `json:"commit_id,omitempty"` + BuildTime string `json:"build_time,omitempty"` + GoVersion string `json:"go_version,omitempty"` + KeywordIndexEngine string `json:"keyword_index_engine,omitempty"` + VectorStoreEngine string `json:"vector_store_engine,omitempty"` + GraphDatabaseEngine string `json:"graph_database_engine,omitempty"` + MinioEnabled bool `json:"minio_enabled,omitempty"` + DBVersion string `json:"db_version,omitempty"` +} + +// ParserEngine represents a document parser engine +type ParserEngine struct { + Name string `json:"name"` + Label string `json:"label"` + Description string `json:"description"` + Available bool `json:"available"` +} + +// StorageEngineStatusItem describes one storage engine's availability +type StorageEngineStatusItem struct { + Name string `json:"name"` + Available bool `json:"available"` + Description string `json:"description"` +} + +// StorageEngineStatusResponse is the response for storage engine status +type StorageEngineStatusResponse struct { + Engines []StorageEngineStatusItem `json:"engines"` + MinioEnvAvailable bool `json:"minio_env_available"` +} + +// StorageCheckRequest is the body for storage engine connectivity check +type StorageCheckRequest struct { + Provider string `json:"provider"` + MinIO json.RawMessage `json:"minio,omitempty"` + COS json.RawMessage `json:"cos,omitempty"` + TOS json.RawMessage `json:"tos,omitempty"` + S3 json.RawMessage `json:"s3,omitempty"` +} + +// StorageCheckResponse is the response for storage engine check +type StorageCheckResponse struct { + OK bool `json:"ok"` + Message string `json:"message"` + BucketCreated bool `json:"bucket_created,omitempty"` +} + +// MinioBucketInfo represents MinIO bucket information +type MinioBucketInfo struct { + Name string `json:"name"` + Policy string `json:"policy"` + CreatedAt string `json:"created_at,omitempty"` +} + +// GetSystemInfo gets system version and configuration information +func (c *Client) GetSystemInfo(ctx context.Context) (*SystemInfo, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/system/info", nil, nil) + if err != nil { + return nil, err + } + var result struct { + Code int `json:"code"` + Data *SystemInfo `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// ListParserEngines lists available document parser engines +func (c *Client) ListParserEngines(ctx context.Context) ([]ParserEngine, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/system/parser-engines", nil, nil) + if err != nil { + return nil, err + } + var result struct { + Code int `json:"code"` + Data []ParserEngine `json:"data"` + Connected bool `json:"connected"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// CheckParserEngines checks parser engine availability with given config overrides +func (c *Client) CheckParserEngines(ctx context.Context, config any) ([]ParserEngine, error) { + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/system/parser-engines/check", config, nil) + if err != nil { + return nil, err + } + var result struct { + Code int `json:"code"` + Data []ParserEngine `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// ReconnectDocReader reconnects the document parser service to a new address +func (c *Client) ReconnectDocReader(ctx context.Context, addr string) error { + req := map[string]string{"addr": addr} + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/system/docreader/reconnect", req, nil) + if err != nil { + return err + } + return parseResponse(resp, nil) +} + +// GetStorageEngineStatus gets the availability status of all storage engines +func (c *Client) GetStorageEngineStatus(ctx context.Context) (*StorageEngineStatusResponse, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/system/storage-engine-status", nil, nil) + if err != nil { + return nil, err + } + var result struct { + Code int `json:"code"` + Data *StorageEngineStatusResponse `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// CheckStorageEngine tests connectivity for a storage engine +func (c *Client) CheckStorageEngine(ctx context.Context, req *StorageCheckRequest) (*StorageCheckResponse, error) { + resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/system/storage-engine-check", req, nil) + if err != nil { + return nil, err + } + var result struct { + Code int `json:"code"` + Data *StorageCheckResponse `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// ListMinioBuckets lists all MinIO buckets with their access policies +func (c *Client) ListMinioBuckets(ctx context.Context) ([]MinioBucketInfo, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/system/minio/buckets", nil, nil) + if err != nil { + return nil, err + } + var result struct { + Code int `json:"code"` + Data struct { + Buckets []MinioBucketInfo `json:"buckets"` + } `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data.Buckets, nil +} diff --git a/client/tenant.go b/client/tenant.go index b7b9bbff..266489ca 100644 --- a/client/tenant.go +++ b/client/tenant.go @@ -6,8 +6,11 @@ package client import ( "context" + "encoding/json" "fmt" "net/http" + "net/url" + "strconv" "time" ) @@ -138,3 +141,90 @@ func (c *Client) ListTenants(ctx context.Context) ([]Tenant, error) { return response.Data.Items, nil } + +// ListAllTenants retrieves all tenants in the system (requires cross-tenant access) +func (c *Client) ListAllTenants(ctx context.Context) ([]Tenant, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/tenants/all", nil, nil) + if err != nil { + return nil, err + } + + var response TenantListResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + + return response.Data.Items, nil +} + +// TenantSearchResponse represents the API response for searching tenants +type TenantSearchResponse struct { + Success bool `json:"success"` + Data struct { + Items []Tenant `json:"items"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"page_size"` + } `json:"data"` +} + +// SearchTenants searches tenants with pagination (requires cross-tenant access) +func (c *Client) SearchTenants(ctx context.Context, keyword string, tenantID uint64, page, pageSize int) ([]Tenant, int64, error) { + queryParams := url.Values{} + if keyword != "" { + queryParams.Set("keyword", keyword) + } + if tenantID > 0 { + queryParams.Set("tenant_id", strconv.FormatUint(tenantID, 10)) + } + queryParams.Set("page", strconv.Itoa(page)) + queryParams.Set("page_size", strconv.Itoa(pageSize)) + + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/tenants/search", nil, queryParams) + if err != nil { + return nil, 0, err + } + + var response TenantSearchResponse + if err := parseResponse(resp, &response); err != nil { + return nil, 0, err + } + + return response.Data.Items, response.Data.Total, nil +} + +// GetTenantKV retrieves a tenant KV configuration by key +func (c *Client) GetTenantKV(ctx context.Context, key string) (json.RawMessage, error) { + path := fmt.Sprintf("/api/v1/tenants/kv/%s", key) + resp, err := c.doRequest(ctx, http.MethodGet, path, nil, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data json.RawMessage `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} + +// UpdateTenantKV updates a tenant KV configuration by key +func (c *Client) UpdateTenantKV(ctx context.Context, key string, value any) (json.RawMessage, error) { + path := fmt.Sprintf("/api/v1/tenants/kv/%s", key) + resp, err := c.doRequest(ctx, http.MethodPut, path, value, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data json.RawMessage `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} diff --git a/client/web_search.go b/client/web_search.go new file mode 100644 index 00000000..559164b2 --- /dev/null +++ b/client/web_search.go @@ -0,0 +1,32 @@ +package client + +import ( + "context" + "encoding/json" + "net/http" +) + +// WebSearchProvider represents a web search provider +type WebSearchProvider struct { + Name string `json:"name"` + Label string `json:"label"` + Description string `json:"description,omitempty"` + Enabled bool `json:"enabled"` +} + +// GetWebSearchProviders returns the list of available web search providers +func (c *Client) GetWebSearchProviders(ctx context.Context) ([]json.RawMessage, error) { + resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/web-search/providers", nil, nil) + if err != nil { + return nil, err + } + + var result struct { + Success bool `json:"success"` + Data []json.RawMessage `json:"data"` + } + if err := parseResponse(resp, &result); err != nil { + return nil, err + } + return result.Data, nil +} diff --git a/docs/api/README.md b/docs/api/README.md index af94109b..63f46a93 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -59,6 +59,7 @@ WeKnora API 按功能分为以下几类: | 分类 | 描述 | 文档链接 | |------|------|----------| +| 认证管理 | 用户注册、登录、令牌管理 | [auth.md](./auth.md) | | 租户管理 | 创建和管理租户账户 | [tenant.md](./tenant.md) | | 知识库管理 | 创建、查询和管理知识库 | [knowledge-base.md](./knowledge-base.md) | | 知识管理 | 上传、检索和管理知识内容 | [knowledge.md](./knowledge.md) | @@ -72,3 +73,9 @@ WeKnora API 按功能分为以下几类: | 聊天功能 | 基于知识库和 Agent 进行问答 | [chat.md](./chat.md) | | 消息管理 | 获取和管理对话消息 | [message.md](./message.md) | | 评估功能 | 评估模型性能 | [evaluation.md](./evaluation.md) | +| 初始化管理 | 知识库模型配置与 Ollama 管理 | [initialization.md](./initialization.md) | +| 系统管理 | 系统信息、解析引擎、存储引擎 | [system.md](./system.md) | +| MCP 服务 | MCP 工具服务管理 | [mcp-service.md](./mcp-service.md) | +| 组织管理 | 组织、成员、知识库/智能体共享 | [organization.md](./organization.md) | +| Skills | 预装智能体技能 | [skill.md](./skill.md) | +| 网络搜索 | 网络搜索服务商 | [web-search.md](./web-search.md) | diff --git a/docs/api/chunk.md b/docs/api/chunk.md index 15ac564c..fd4fb06f 100644 --- a/docs/api/chunk.md +++ b/docs/api/chunk.md @@ -5,8 +5,11 @@ | 方法 | 路径 | 描述 | | ------ | --------------------------- | ------------------------ | | GET | `/chunks/:knowledge_id` | 获取知识的分块列表 | +| PUT | `/chunks/:knowledge_id/:id` | 更新分块 | | DELETE | `/chunks/:knowledge_id/:id` | 删除分块 | | DELETE | `/chunks/:knowledge_id` | 删除知识下的所有分块 | +| GET | `/chunks/get-by-id/:id` | 根据ID直接获取分块 | +| DELETE | `/chunks/:id/delete-question` | 删除分块的生成问题 | ## GET `/chunks/:knowledge_id?page=&page_size=` - 获取知识的分块列表 @@ -56,6 +59,63 @@ curl --location 'http://localhost:8080/api/v1/chunks/4c4e7c1a-09cf-485b-a7b5-24b } ``` +## PUT `/chunks/:knowledge_id/:id` - 更新分块 + +更新指定分块的内容和属性。 + +**请求参数**: +- `content`: 分块内容(可选) +- `chunk_index`: 分块索引(可选) +- `is_enabled`: 是否启用(可选) +- `start_at`: 起始位置(可选) +- `end_at`: 结束位置(可选) +- `image_info`: 图片信息(可选) + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/chunks/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5/df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "content": "更新后的分块内容", + "is_enabled": true +}' +``` + +**响应**: + +```json +{ + "data": { + "id": "df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7", + "tenant_id": 1, + "knowledge_id": "4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5", + "knowledge_base_id": "kb-00000001", + "tag_id": "", + "content": "更新后的分块内容", + "chunk_index": 0, + "is_enabled": true, + "status": 2, + "start_at": 0, + "end_at": 964, + "pre_chunk_id": "", + "next_chunk_id": "", + "chunk_type": "text", + "parent_chunk_id": "", + "relation_chunks": null, + "indirect_relation_chunks": null, + "metadata": null, + "content_hash": "", + "image_info": "", + "created_at": "2025-08-12T11:52:36.168632+08:00", + "updated_at": "2025-08-12T12:00:00.000000+08:00", + "deleted_at": null + }, + "success": true +} +``` + ## DELETE `/chunks/:knowledge_id/:id` - 删除分块 **请求**: @@ -93,3 +153,72 @@ curl --location --request DELETE 'http://localhost:8080/api/v1/chunks/4c4e7c1a-0 "success": true } ``` + +## GET `/chunks/get-by-id/:id` - 根据ID直接获取分块 + +根据分块ID直接获取分块信息,无需提供知识ID。 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/chunks/get-by-id/df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "id": "df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7", + "tenant_id": 1, + "knowledge_id": "4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5", + "knowledge_base_id": "kb-00000001", + "tag_id": "", + "content": "彗星xxxx", + "chunk_index": 0, + "is_enabled": true, + "status": 2, + "start_at": 0, + "end_at": 964, + "pre_chunk_id": "", + "next_chunk_id": "", + "chunk_type": "text", + "parent_chunk_id": "", + "relation_chunks": null, + "indirect_relation_chunks": null, + "metadata": null, + "content_hash": "", + "image_info": "", + "created_at": "2025-08-12T11:52:36.168632+08:00", + "updated_at": "2025-08-12T11:52:53.376871+08:00", + "deleted_at": null + }, + "success": true +} +``` + +## DELETE `/chunks/:id/delete-question` - 删除分块的生成问题 + +删除指定分块关联的生成问题。 + +**请求**: + +```curl +curl --location --request DELETE 'http://localhost:8080/api/v1/chunks/df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7/delete-question' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "question_id": "q-00000001" +}' +``` + +**响应**: + +```json +{ + "message": "Question deleted successfully", + "success": true +} +``` diff --git a/docs/api/faq.md b/docs/api/faq.md index 7aa141c1..274b2cc0 100644 --- a/docs/api/faq.md +++ b/docs/api/faq.md @@ -7,11 +7,16 @@ | GET | `/knowledge-bases/:id/faq/entries` | 获取FAQ条目列表 | | POST | `/knowledge-bases/:id/faq/entries` | 批量导入FAQ条目 | | POST | `/knowledge-bases/:id/faq/entry` | 创建单个FAQ条目 | +| GET | `/knowledge-bases/:id/faq/entries/:entry_id`| 获取单个FAQ条目 | | PUT | `/knowledge-bases/:id/faq/entries/:entry_id`| 更新单个FAQ条目 | -| PUT | `/knowledge-bases/:id/faq/entries/status` | 批量更新FAQ启用状态 | +| POST | `/knowledge-bases/:id/faq/entries/:entry_id/similar-questions` | 添加相似问题 | +| PUT | `/knowledge-bases/:id/faq/entries/fields` | 批量更新FAQ字段 | | PUT | `/knowledge-bases/:id/faq/entries/tags` | 批量更新FAQ标签 | | DELETE | `/knowledge-bases/:id/faq/entries` | 批量删除FAQ条目 | | POST | `/knowledge-bases/:id/faq/search` | 混合搜索FAQ | +| GET | `/knowledge-bases/:id/faq/entries/export` | 导出FAQ条目(CSV) | +| GET | `/faq/import/progress/:task_id` | 获取FAQ导入进度 | +| PUT | `/knowledge-bases/:id/faq/import/last-result/display` | 更新导入结果显示状态 | ## GET `/knowledge-bases/:id/faq/entries` - 获取FAQ条目列表 @@ -209,20 +214,109 @@ curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-0 } ``` -## PUT `/knowledge-bases/:id/faq/entries/status` - 批量更新FAQ启用状态 +## GET `/knowledge-bases/:id/faq/entries/:entry_id` - 获取单个FAQ条目 + +根据 seq_id 获取单个 FAQ 条目的详细信息。 **请求**: ```curl -curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/status' \ +curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/1' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "id": "faq-00000001", + "seq_id": 1, + "chunk_id": "chunk-00000001", + "knowledge_id": "knowledge-00000001", + "knowledge_base_id": "kb-00000001", + "tag_id": "tag-00000001", + "is_enabled": true, + "standard_question": "如何重置密码?", + "similar_questions": ["忘记密码怎么办", "密码找回"], + "negative_questions": [], + "answers": ["您可以通过点击登录页面的'忘记密码'链接来重置密码。"], + "index_mode": "hybrid", + "chunk_type": "faq", + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T10:00:00+08:00" + }, + "success": true +} +``` + +## POST `/knowledge-bases/:id/faq/entries/:entry_id/similar-questions` - 添加相似问题 + +为指定的 FAQ 条目追加相似问法。 + +**请求参数**: +- `similar_questions`: 要追加的相似问题数组(必填) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/1/similar-questions' \ --header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ --header 'Content-Type: application/json' \ --data '{ - "updates": { - "faq-00000001": true, - "faq-00000002": false, - "faq-00000003": true - } + "similar_questions": ["怎样修改密码", "密码重置方法"] +}' +``` + +**响应**: + +```json +{ + "data": { + "id": "faq-00000001", + "seq_id": 1, + "standard_question": "如何重置密码?", + "similar_questions": ["忘记密码怎么办", "密码找回", "怎样修改密码", "密码重置方法"], + "answers": ["您可以通过点击登录页面的'忘记密码'链接来重置密码。"], + "is_enabled": true, + "chunk_type": "faq", + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T11:00:00+08:00" + }, + "success": true +} +``` + +## PUT `/knowledge-bases/:id/faq/entries/fields` - 批量更新FAQ字段 + +支持按条目ID或按标签ID批量更新 FAQ 条目的多个字段(启用状态、推荐状态、标签等)。 + +**请求参数**: +- `by_id`: 按条目 seq_id 更新(可选),键为 seq_id,值为要更新的字段 +- `by_tag`: 按标签 seq_id 更新(可选),键为 tag_seq_id,值为要更新的字段 +- `exclude_ids`: 排除的条目 seq_id 列表(与 by_tag 配合使用,可选) + +每个更新对象支持的字段: +- `is_enabled`: 是否启用(可选) +- `is_recommended`: 是否推荐(可选) +- `tag_id`: 标签ID(可选) + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/fields' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "by_id": { + "1": {"is_enabled": true, "is_recommended": false}, + "2": {"is_enabled": false} + }, + "by_tag": { + "100": {"is_enabled": true} + }, + "exclude_ids": [3, 4] }' ``` @@ -327,3 +421,82 @@ curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/se "success": true } ``` + +## GET `/knowledge-bases/:id/faq/entries/export` - 导出FAQ条目 + +将知识库下的所有 FAQ 条目导出为 CSV 文件。 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/export' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--output faq_export.csv +``` + +**响应**: + +CSV 文件下载(Content-Type: text/csv) + +## GET `/faq/import/progress/:task_id` - 获取FAQ导入进度 + +查询异步 FAQ 导入任务的执行进度。任务 ID 由批量导入接口返回。 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/faq/import/progress/task-00000001' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "task_id": "task-00000001", + "status": "completed", + "total": 100, + "success_count": 98, + "failed_count": 2, + "failed_entries": [ + { + "index": 5, + "standard_question": "重复的问题", + "error": "标准问与已有FAQ重复" + } + ], + "success_entries": [] + }, + "success": true +} +``` + +注:`status` 可能的值为 `pending`、`processing`、`completed`、`failed`。 + +## PUT `/knowledge-bases/:id/faq/import/last-result/display` - 更新导入结果显示状态 + +更新上一次 FAQ 导入结果的显示状态,用于控制前端是否展示导入结果提示。 + +**请求参数**: +- `display_status`: 显示状态(如 `"dismissed"`) + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/import/last-result/display' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "display_status": "dismissed" +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` diff --git a/docs/api/initialization.md b/docs/api/initialization.md new file mode 100644 index 00000000..b23fd3a0 --- /dev/null +++ b/docs/api/initialization.md @@ -0,0 +1,402 @@ +# 初始化配置 API + +[返回目录](./README.md) + +| 方法 | 路径 | 描述 | +| ------ | ------------------------------------------------- | -------------------------- | +| GET | `/initialization/config/:kb_id` | 获取知识库初始化配置 | +| POST | `/initialization/initialize/:kb_id` | 初始化知识库模型配置 | +| PUT | `/initialization/config/:kb_id` | 更新知识库模型配置 | +| GET | `/initialization/ollama/status` | 检查 Ollama 状态 | +| GET | `/initialization/ollama/models` | 获取本地 Ollama 模型列表 | +| POST | `/initialization/ollama/models/check` | 检查 Ollama 模型是否可用 | +| POST | `/initialization/ollama/models/download` | 下载 Ollama 模型 | +| GET | `/initialization/ollama/download/progress/:task_id` | 获取下载进度 | +| GET | `/initialization/ollama/download/tasks` | 获取所有下载任务 | +| POST | `/initialization/remote/check` | 检查远程模型 API | +| POST | `/initialization/embedding/test` | 测试嵌入模型 | +| POST | `/initialization/rerank/check` | 检查重排序模型 | +| POST | `/initialization/multimodal/test` | 测试多模态模型 | +| POST | `/initialization/extract/text-relation` | 提取文本关系 | + +## GET `/initialization/config/:kb_id` - 获取知识库初始化配置 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/initialization/config/kb-00000001' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "chat_model_id": "model-00000001", + "embedding_model_id": "model-00000002", + "rerank_model_id": "model-00000003", + "multimodal_id": "model-00000004" + }, + "success": true +} +``` + +## POST `/initialization/initialize/:kb_id` - 初始化知识库模型配置 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/initialization/initialize/kb-00000001' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "chat_model_id": "model-00000001", + "embedding_model_id": "model-00000002", + "rerank_model_id": "model-00000003", + "multimodal_id": "model-00000004" +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +## PUT `/initialization/config/:kb_id` - 更新知识库模型配置 + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/initialization/config/kb-00000001' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "chat_model_id": "model-00000010", + "embedding_model_id": "model-00000002" +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +## GET `/initialization/ollama/status` - 检查 Ollama 状态 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/initialization/ollama/status' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "available": true + }, + "success": true +} +``` + +## GET `/initialization/ollama/models` - 获取本地 Ollama 模型列表 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/initialization/ollama/models' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": [ + { + "name": "llama3:8b", + "size": 4661211648, + "modified_at": "2025-08-10T15:30:00+08:00" + }, + { + "name": "nomic-embed-text:latest", + "size": 274302976, + "modified_at": "2025-08-11T09:00:00+08:00" + } + ], + "success": true +} +``` + +## POST `/initialization/ollama/models/check` - 检查 Ollama 模型是否可用 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/initialization/ollama/models/check' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "models": ["llama3:8b", "nomic-embed-text:latest", "mistral:7b"] +}' +``` + +**响应**: + +```json +{ + "data": { + "llama3:8b": true, + "nomic-embed-text:latest": true, + "mistral:7b": false + }, + "success": true +} +``` + +## POST `/initialization/ollama/models/download` - 下载 Ollama 模型 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/initialization/ollama/models/download' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "model": "mistral:7b" +}' +``` + +**响应**: + +```json +{ + "data": { + "id": "task-00000001", + "modelName": "mistral:7b", + "status": "downloading", + "progress": 0, + "message": "开始下载", + "startTime": "2025-08-12T10:00:00+08:00" + }, + "success": true +} +``` + +## GET `/initialization/ollama/download/progress/:task_id` - 获取下载进度 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/initialization/ollama/download/progress/task-00000001' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "id": "task-00000001", + "modelName": "mistral:7b", + "status": "downloading", + "progress": 45.6, + "message": "正在下载 2.1GB / 4.6GB", + "startTime": "2025-08-12T10:00:00+08:00" + }, + "success": true +} +``` + +## GET `/initialization/ollama/download/tasks` - 获取所有下载任务 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/initialization/ollama/download/tasks' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": [ + { + "id": "task-00000001", + "modelName": "mistral:7b", + "status": "completed", + "progress": 100, + "message": "下载完成", + "startTime": "2025-08-12T10:00:00+08:00", + "endTime": "2025-08-12T10:15:00+08:00" + }, + { + "id": "task-00000002", + "modelName": "llama3:70b", + "status": "downloading", + "progress": 30.2, + "message": "正在下载 12.5GB / 41.4GB", + "startTime": "2025-08-12T10:20:00+08:00" + } + ], + "success": true +} +``` + +## POST `/initialization/remote/check` - 检查远程模型 API + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/initialization/remote/check' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "api_url": "https://api.openai.com/v1", + "api_key": "sk-xxxxx", + "model": "gpt-4o" +}' +``` + +**响应**: + +```json +{ + "data": { + "success": true, + "message": "模型可用" + }, + "success": true +} +``` + +## POST `/initialization/embedding/test` - 测试嵌入模型 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/initialization/embedding/test' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "api_url": "https://api.openai.com/v1", + "api_key": "sk-xxxxx", + "model": "text-embedding-3-small" +}' +``` + +**响应**: + +```json +{ + "data": { + "success": true, + "message": "嵌入模型测试通过" + }, + "success": true +} +``` + +## POST `/initialization/rerank/check` - 检查重排序模型 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/initialization/rerank/check' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "api_url": "https://api.cohere.ai/v1", + "api_key": "sk-xxxxx", + "model": "rerank-english-v3.0" +}' +``` + +**响应**: + +```json +{ + "data": { + "success": true, + "message": "重排序模型可用" + }, + "success": true +} +``` + +## POST `/initialization/multimodal/test` - 测试多模态模型 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/initialization/multimodal/test' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "api_url": "https://api.openai.com/v1", + "api_key": "sk-xxxxx", + "model": "gpt-4o" +}' +``` + +**响应**: + +```json +{ + "data": { + "success": true, + "message": "多模态模型测试通过" + }, + "success": true +} +``` + +## POST `/initialization/extract/text-relation` - 提取文本关系 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/initialization/extract/text-relation' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "text": "WeKnora 是一个知识管理平台,支持多种文档格式的解析和检索。", + "model_id": "model-00000001" +}' +``` + +**响应**: + +```json +{ + "data": { + "entities": [ + {"name": "WeKnora", "type": "Product"}, + {"name": "知识管理平台", "type": "Concept"} + ], + "relations": [ + { + "source": "WeKnora", + "target": "知识管理平台", + "relation": "is_a" + } + ] + }, + "success": true +} +``` diff --git a/docs/api/knowledge-base.md b/docs/api/knowledge-base.md index a06c65cc..82581b04 100644 --- a/docs/api/knowledge-base.md +++ b/docs/api/knowledge-base.md @@ -10,7 +10,10 @@ | PUT | `/knowledge-bases/:id` | 更新知识库 | | DELETE | `/knowledge-bases/:id` | 删除知识库 | | POST | `/knowledge-bases/copy` | 拷贝知识库 | +| GET | `/knowledge-bases/copy/progress/:task_id` | 获取拷贝进度 | | GET | `/knowledge-bases/:id/hybrid-search` | 混合搜索(向量+关键词) | +| POST | `/knowledge-bases/:id/pin` | 置顶/取消置顶知识库 | +| GET | `/knowledge-bases/:id/move-targets` | 获取可迁移目标知识库列表 | ## POST `/knowledge-bases` - 创建知识库 @@ -316,6 +319,68 @@ curl --location --request DELETE 'http://localhost:8080/api/v1/knowledge-bases/b } ``` +## POST `/knowledge-bases/copy` - 拷贝知识库 + +异步拷贝一个知识库,包括知识库配置和所有知识内容。返回任务ID用于查询拷贝进度。 + +**请求参数**: +- `source_id`: 源知识库ID(必填) +- `name`: 新知识库名称(可选,默认使用原名称加"(副本)"后缀) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge-bases/copy' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "source_id": "kb-00000001", + "name": "知识库副本" +}' +``` + +**响应**: + +```json +{ + "data": { + "task_id": "task-copy-00000001", + "target_id": "kb-00000002" + }, + "success": true +} +``` + +## GET `/knowledge-bases/copy/progress/:task_id` - 获取拷贝进度 + +查询知识库拷贝任务的执行进度。 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge-bases/copy/progress/task-copy-00000001' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "task_id": "task-copy-00000001", + "status": "completed", + "total": 10, + "finished": 10, + "source_id": "kb-00000001", + "target_id": "kb-00000002" + }, + "success": true +} +``` + +注:`status` 可能的值为 `pending`、`processing`、`completed`、`failed`。 + ## GET `/knowledge-bases/:id/hybrid-search` - 混合搜索 执行向量搜索和关键词搜索的混合检索。 @@ -368,3 +433,63 @@ curl --location --request GET 'http://localhost:8080/api/v1/knowledge-bases/kb-0 "success": true } ``` + +## POST `/knowledge-bases/:id/pin` - 置顶/取消置顶知识库 + +切换知识库的置顶状态。无需请求体,每次调用会自动切换当前置顶状态。 + +**请求**: + +```curl +curl --location --request POST 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/pin' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "id": "kb-00000001", + "name": "Default Knowledge Base", + "description": "System Default Knowledge Base", + "tenant_id": 1, + "is_pinned": true, + "created_at": "2025-08-11T20:10:41.817794+08:00", + "updated_at": "2025-08-12T15:00:00.000000+08:00", + "deleted_at": null + }, + "success": true +} +``` + +## GET `/knowledge-bases/:id/move-targets` - 获取可迁移目标知识库列表 + +获取当前知识库可以迁移知识到的目标知识库列表。返回结果会排除当前知识库本身。 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/move-targets' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": [ + { + "id": "kb-00000002", + "name": "技术文档知识库", + "description": "技术文档相关知识", + "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3", + "created_at": "2025-08-12T11:30:09.206238+08:00", + "updated_at": "2025-08-12T11:30:09.206238+08:00" + } + ], + "success": true +} +``` diff --git a/docs/api/knowledge.md b/docs/api/knowledge.md index dafd26fe..2c1bd005 100644 --- a/docs/api/knowledge.md +++ b/docs/api/knowledge.md @@ -16,6 +16,11 @@ | PUT | `/knowledge/image/:id/:chunk_id` | 更新图像分块信息 | | PUT | `/knowledge/tags` | 批量更新知识标签 | | GET | `/knowledge/batch` | 批量获取知识 | +| POST | `/knowledge/:id/reparse` | 重新解析知识 | +| GET | `/knowledge/search` | 搜索/过滤知识条目 | +| POST | `/knowledge/move` | 迁移知识到另一个知识库 | +| GET | `/knowledge/move/progress/:task_id` | 获取知识迁移进度 | +| GET | `/knowledge/:id/preview` | 预览知识文件 | ## POST `/knowledge-bases/:id/knowledge/file` - 从文件创建知识 @@ -311,3 +316,346 @@ curl --location 'http://localhost:8080/api/v1/knowledge/4c4e7c1a-09cf-485b-a7b5- ``` attachment ``` + +## PUT `/knowledge/:id` - 更新知识 + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/knowledge/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "title": "更新的标题", + "description": "更新的描述", + "tag_id": "tag-00000001" +}' +``` + +**响应**: + +```json +{ + "message": "Updated successfully", + "success": true +} +``` + +## POST `/knowledge-bases/:id/knowledge/manual` - 创建手工 Markdown 知识 + +创建手工 Markdown 知识条目,适用于直接编写内容而非上传文件的场景。 + +**请求参数**: +- `title`: 知识标题(必填) +- `content`: Markdown 内容(必填) +- `tag_id`: 标签ID(可选) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/knowledge/manual' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "title": "产品使用指南", + "content": "# 产品使用指南\n\n## 快速入门\n\n这是一份产品使用指南...", + "tag_id": "tag-00000001" +}' +``` + +**响应**: + +```json +{ + "data": { + "id": "5a3b2c1d-0e9f-4a8b-7c6d-5e4f3a2b1c0d", + "tenant_id": 1, + "knowledge_base_id": "kb-00000001", + "type": "manual", + "title": "产品使用指南", + "description": "", + "source": "", + "parse_status": "processing", + "enable_status": "disabled", + "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3", + "file_name": "", + "file_type": "md", + "file_size": 0, + "file_hash": "", + "file_path": "", + "storage_size": 0, + "metadata": null, + "created_at": "2025-08-12T12:00:00.000000+08:00", + "updated_at": "2025-08-12T12:00:00.000000+08:00", + "processed_at": null, + "error_message": "", + "deleted_at": null + }, + "success": true +} +``` + +## PUT `/knowledge/manual/:id` - 更新手工 Markdown 知识 + +**请求参数**: +- `title`: 新标题(可选) +- `content`: 新 Markdown 内容(可选) + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/knowledge/manual/5a3b2c1d-0e9f-4a8b-7c6d-5e4f3a2b1c0d' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "title": "产品使用指南 V2", + "content": "# 产品使用指南 V2\n\n## 更新内容\n\n..." +}' +``` + +**响应**: + +```json +{ + "data": { + "id": "5a3b2c1d-0e9f-4a8b-7c6d-5e4f3a2b1c0d", + "tenant_id": 1, + "knowledge_base_id": "kb-00000001", + "type": "manual", + "title": "产品使用指南 V2", + "parse_status": "processing", + "created_at": "2025-08-12T12:00:00.000000+08:00", + "updated_at": "2025-08-12T12:30:00.000000+08:00" + }, + "success": true +} +``` + +## PUT `/knowledge/image/:id/:chunk_id` - 更新图像分块信息 + +更新知识条目中指定分块的图像描述信息。 + +**请求参数**: +- `image_info`: 图像信息(JSON 格式字符串) + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/knowledge/image/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5/df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "image_info": "{\"description\": \"产品架构图\", \"alt_text\": \"WeKnora 系统架构\"}" +}' +``` + +**响应**: + +```json +{ + "message": "Updated successfully", + "success": true +} +``` + +## PUT `/knowledge/tags` - 批量更新知识标签 + +批量更新多个知识条目的标签关联。 + +**请求参数**: +- `updates`: 知识ID到标签ID的映射(设为 `null` 可清除标签) + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/knowledge/tags' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "updates": { + "4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5": "tag-00000001", + "9c8af585-ae15-44ce-8f73-45ad18394651": null + } +}' +``` + +注:设置为 `null` 可清除标签关联。 + +**响应**: + +```json +{ + "success": true +} +``` + +## POST `/knowledge/:id/reparse` - 重新解析知识 + +触发知识的异步重新解析。此操作会删除现有的文档内容,然后使用最新的解析配置重新解析知识。 +适用于解析配置更新后需要刷新内容,或者原始解析失败需要重试的场景。 + +**请求**: + +```curl +curl --location --request POST 'http://localhost:8080/api/v1/knowledge/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5/reparse' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "id": "4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5", + "tenant_id": 1, + "knowledge_base_id": "kb-00000001", + "type": "file", + "title": "彗星.txt", + "parse_status": "pending", + "enable_status": "enabled", + "created_at": "2025-08-12T11:52:36.168632+08:00", + "updated_at": "2025-08-12T13:00:00.000000+08:00" + }, + "success": true +} +``` + +注:重新解析为异步操作,返回后 `parse_status` 将变为 `pending`,随后进入 `processing` 状态。 + +## GET `/knowledge/search` - 搜索/过滤知识条目 + +按关键词搜索和过滤知识条目,支持按文件类型和 Agent ID 筛选。 + +**查询参数**: +- `keyword`: 搜索关键词(可选) +- `offset`: 偏移量(默认 0) +- `limit`: 返回数量(默认 20) +- `file_types`: 文件类型过滤,多个类型用逗号分隔(可选) +- `agent_id`: 按 Agent ID 筛选(可选) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge/search?keyword=%E5%BD%97%E6%98%9F&offset=0&limit=10&file_types=txt,pdf' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "data": [ + { + "id": "4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5", + "tenant_id": 1, + "knowledge_base_id": "kb-00000001", + "type": "file", + "title": "彗星.txt", + "description": "彗星是由冰和尘埃构成的太阳系小天体...", + "file_name": "彗星.txt", + "file_type": "txt", + "file_size": 7710, + "parse_status": "completed", + "enable_status": "enabled", + "created_at": "2025-08-12T11:52:36.168632+08:00", + "updated_at": "2025-08-12T11:52:53.376871+08:00" + } + ], + "has_more": false + }, + "success": true +} +``` + +## POST `/knowledge/move` - 迁移知识到另一个知识库 + +将知识条目从一个知识库迁移到另一个知识库。此操作为异步任务,返回任务ID用于查询迁移进度。 + +**请求参数**: +- `knowledge_ids`: 待迁移的知识ID列表(必填) +- `source_kb_id`: 源知识库ID(必填) +- `target_kb_id`: 目标知识库ID(必填) +- `mode`: 迁移模式,`reuse_vectors` 复用向量数据,`reparse` 重新解析(必填) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge/move' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "knowledge_ids": ["4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5"], + "source_kb_id": "kb-00000001", + "target_kb_id": "kb-00000002", + "mode": "reuse_vectors" +}' +``` + +**响应**: + +```json +{ + "data": { + "task_id": "task-move-00000001", + "source_kb_id": "kb-00000001", + "target_kb_id": "kb-00000002", + "knowledge_count": 1, + "message": "知识迁移任务已创建" + }, + "success": true +} +``` + +## GET `/knowledge/move/progress/:task_id` - 获取知识迁移进度 + +查询知识迁移任务的执行进度。 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge/move/progress/task-move-00000001' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "task_id": "task-move-00000001", + "status": "completed", + "progress": 100, + "total": 1, + "processed": 1, + "message": "迁移完成", + "error": "" + }, + "success": true +} +``` + +注:`status` 可能的值为 `pending`、`processing`、`completed`、`failed`。 + +## GET `/knowledge/:id/preview` - 预览知识文件 + +在浏览器中内联预览知识文件内容。响应会设置相应的 `Content-Type` 和 `Content-Disposition` 头,用于浏览器端直接展示文件。 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5/preview' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' +``` + +**响应**: + +``` +Content-Type: text/plain; charset=utf-8 +Content-Disposition: inline; filename="彗星.txt" + +(文件内容) +``` diff --git a/docs/api/mcp-service.md b/docs/api/mcp-service.md new file mode 100644 index 00000000..cb1ed3f8 --- /dev/null +++ b/docs/api/mcp-service.md @@ -0,0 +1,396 @@ +# MCP Service API + +[返回目录](./README.md) + +| 方法 | 路径 | 描述 | +| ------ | --------------------------------- | ---------------------- | +| POST | `/mcp-services` | 创建 MCP 服务 | +| GET | `/mcp-services` | 获取 MCP 服务列表 | +| GET | `/mcp-services/:id` | 获取 MCP 服务详情 | +| PUT | `/mcp-services/:id` | 更新 MCP 服务 | +| DELETE | `/mcp-services/:id` | 删除 MCP 服务 | +| POST | `/mcp-services/:id/test` | 测试 MCP 服务连接 | +| GET | `/mcp-services/:id/tools` | 获取 MCP 服务工具列表 | +| GET | `/mcp-services/:id/resources` | 获取 MCP 服务资源列表 | + +## POST `/mcp-services` - 创建 MCP 服务 + +**请求参数**: +- `name`: 服务名称(必填) +- `description`: 服务描述(可选) +- `transport_type`: 传输类型,可选值:`sse`、`http-streamable`、`stdio`(必填) +- `url`: 服务地址,当 transport_type 为 `sse` 或 `http-streamable` 时必填 +- `headers`: 自定义请求头(可选) +- `auth_config`: 认证配置(可选),包含 `api_key`、`token`、`custom_headers` +- `advanced_config`: 高级配置(可选),包含 `timeout`、`retry_count`、`retry_delay` +- `stdio_config`: stdio 传输配置(可选),包含 `command`、`args` +- `env_vars`: 环境变量(可选) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/mcp-services' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "name": "天气查询服务", + "description": "提供全球天气信息查询", + "transport_type": "sse", + "url": "https://mcp.example.com/weather/sse", + "headers": { + "X-Custom-Header": "value" + }, + "auth_config": { + "api_key": "weather-api-key-xxxxx" + }, + "advanced_config": { + "timeout": 30, + "retry_count": 3, + "retry_delay": 1 + } +}' +``` + +**响应**: + +```json +{ + "data": { + "id": "mcp-00000001", + "tenant_id": 1, + "name": "天气查询服务", + "description": "提供全球天气信息查询", + "enabled": true, + "transport_type": "sse", + "url": "https://mcp.example.com/weather/sse", + "headers": { + "X-Custom-Header": "value" + }, + "auth_config": { + "api_key": "weather-api-key-xxxxx" + }, + "advanced_config": { + "timeout": 30, + "retry_count": 3, + "retry_delay": 1 + }, + "is_builtin": false, + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T10:00:00+08:00" + }, + "success": true +} +``` + +**创建 stdio 类型的 MCP 服务**: + +```curl +curl --location 'http://localhost:8080/api/v1/mcp-services' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "name": "本地文件服务", + "description": "通过 stdio 访问本地文件系统", + "transport_type": "stdio", + "stdio_config": { + "command": "/usr/local/bin/mcp-file-server", + "args": ["--root", "/data"] + }, + "env_vars": { + "MCP_LOG_LEVEL": "info" + } +}' +``` + +## GET `/mcp-services` - 获取 MCP 服务列表 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/mcp-services' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": [ + { + "id": "mcp-00000001", + "tenant_id": 1, + "name": "天气查询服务", + "description": "提供全球天气信息查询", + "enabled": true, + "transport_type": "sse", + "url": "https://mcp.example.com/weather/sse", + "headers": {}, + "auth_config": { + "api_key": "weather-api-key-xxxxx" + }, + "advanced_config": { + "timeout": 30, + "retry_count": 3, + "retry_delay": 1 + }, + "is_builtin": false, + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T10:00:00+08:00" + }, + { + "id": "mcp-00000002", + "tenant_id": 1, + "name": "本地文件服务", + "description": "通过 stdio 访问本地文件系统", + "enabled": true, + "transport_type": "stdio", + "headers": {}, + "auth_config": null, + "advanced_config": null, + "stdio_config": { + "command": "/usr/local/bin/mcp-file-server", + "args": ["--root", "/data"] + }, + "env_vars": { + "MCP_LOG_LEVEL": "info" + }, + "is_builtin": false, + "created_at": "2025-08-12T11:00:00+08:00", + "updated_at": "2025-08-12T11:00:00+08:00" + } + ], + "success": true +} +``` + +## GET `/mcp-services/:id` - 获取 MCP 服务详情 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/mcp-services/mcp-00000001' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "id": "mcp-00000001", + "tenant_id": 1, + "name": "天气查询服务", + "description": "提供全球天气信息查询", + "enabled": true, + "transport_type": "sse", + "url": "https://mcp.example.com/weather/sse", + "headers": {}, + "auth_config": { + "api_key": "weather-api-key-xxxxx" + }, + "advanced_config": { + "timeout": 30, + "retry_count": 3, + "retry_delay": 1 + }, + "is_builtin": false, + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T10:00:00+08:00" + }, + "success": true +} +``` + +## PUT `/mcp-services/:id` - 更新 MCP 服务 + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/mcp-services/mcp-00000001' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "name": "天气查询服务(更新)", + "description": "提供全球天气信息查询,支持实时数据", + "enabled": false +}' +``` + +**响应**: + +```json +{ + "data": { + "id": "mcp-00000001", + "tenant_id": 1, + "name": "天气查询服务(更新)", + "description": "提供全球天气信息查询,支持实时数据", + "enabled": false, + "transport_type": "sse", + "url": "https://mcp.example.com/weather/sse", + "headers": {}, + "auth_config": { + "api_key": "weather-api-key-xxxxx" + }, + "advanced_config": { + "timeout": 30, + "retry_count": 3, + "retry_delay": 1 + }, + "is_builtin": false, + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T12:00:00+08:00" + }, + "success": true +} +``` + +## DELETE `/mcp-services/:id` - 删除 MCP 服务 + +**请求**: + +```curl +curl --location --request DELETE 'http://localhost:8080/api/v1/mcp-services/mcp-00000001' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +## POST `/mcp-services/:id/test` - 测试 MCP 服务连接 + +**请求**: + +```curl +curl --location --request POST 'http://localhost:8080/api/v1/mcp-services/mcp-00000001/test' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "success": true, + "message": "连接成功", + "tools": [ + { + "name": "get_weather", + "description": "获取指定城市的天气信息", + "inputSchema": { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "城市名称" + } + }, + "required": ["city"] + } + } + ], + "resources": [ + { + "uri": "weather://cities", + "name": "城市列表", + "description": "支持查询的城市列表", + "mimeType": "application/json" + } + ] + }, + "success": true +} +``` + +## GET `/mcp-services/:id/tools` - 获取 MCP 服务工具列表 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/mcp-services/mcp-00000001/tools' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": [ + { + "name": "get_weather", + "description": "获取指定城市的天气信息", + "inputSchema": { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "城市名称" + } + }, + "required": ["city"] + } + }, + { + "name": "get_forecast", + "description": "获取未来天气预报", + "inputSchema": { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "城市名称" + }, + "days": { + "type": "integer", + "description": "预报天数" + } + }, + "required": ["city"] + } + } + ], + "success": true +} +``` + +## GET `/mcp-services/:id/resources` - 获取 MCP 服务资源列表 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/mcp-services/mcp-00000001/resources' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": [ + { + "uri": "weather://cities", + "name": "城市列表", + "description": "支持查询的城市列表", + "mimeType": "application/json" + }, + { + "uri": "weather://config", + "name": "服务配置", + "description": "当前服务配置信息", + "mimeType": "application/json" + } + ], + "success": true +} +``` diff --git a/docs/api/message.md b/docs/api/message.md index b419d676..9b9f59a4 100644 --- a/docs/api/message.md +++ b/docs/api/message.md @@ -6,6 +6,8 @@ | ------ | ---------------------------- | ------------------------ | | GET | `/messages/:session_id/load` | 获取最近的会话消息列表 | | DELETE | `/messages/:session_id/:id` | 删除消息 | +| POST | `/messages/search` | 搜索历史对话 | +| GET | `/messages/chat-history-stats` | 获取聊天历史知识库统计 | ## GET `/messages/:session_id/load` - 获取最近的会话消息列表 @@ -178,3 +180,78 @@ curl --location --request DELETE 'http://localhost:8080/api/v1/messages/ceb9babb "success": true } ``` + +## POST `/messages/search` - 搜索历史对话 + +搜索历史对话消息,支持混合搜索、关键词搜索和向量搜索模式。 + +**请求参数**: +- `query`: 搜索关键词(必填) +- `mode`: 搜索模式,可选 `hybrid`、`keyword`、`vector`(可选,默认 `hybrid`) +- `limit`: 返回结果数量(可选,默认 20) +- `session_ids`: 限定搜索的会话ID列表(可选) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/messages/search' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "query": "彗星的结构", + "mode": "hybrid", + "limit": 20, + "session_ids": [] +}' +``` + +**响应**: + +```json +{ + "data": { + "items": [ + { + "request_id": "3475c004-0ada-4306-9d30-d7f5efce50d2", + "session_id": "ceb9babb-1e30-41d7-817d-fd584954304b", + "session_title": "彗星知识问答", + "query_content": "彗尾的形状", + "answer_content": "彗尾的形状主要取决于...", + "score": 0.85, + "match_type": "hybrid", + "created_at": "2025-08-12T14:30:39.732246+08:00" + } + ], + "total": 1 + }, + "success": true +} +``` + +## GET `/messages/chat-history-stats` - 获取聊天历史知识库统计 + +获取当前租户的聊天历史知识库索引统计信息。 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/messages/chat-history-stats' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "enabled": true, + "embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3", + "knowledge_base_id": "kb-chat-00000001", + "knowledge_base_name": "聊天历史知识库", + "indexed_message_count": 1024, + "has_indexed_messages": true + }, + "success": true +} +``` diff --git a/docs/api/organization.md b/docs/api/organization.md new file mode 100644 index 00000000..f6e4c41a --- /dev/null +++ b/docs/api/organization.md @@ -0,0 +1,983 @@ +# 组织管理 API + +[返回目录](./README.md) + +## 组织 CRUD + +| 方法 | 路径 | 描述 | +| ------ | ------------------------- | ---------------- | +| POST | `/organizations` | 创建组织 | +| GET | `/organizations` | 获取我的组织列表 | +| GET | `/organizations/:id` | 获取组织详情 | +| PUT | `/organizations/:id` | 更新组织 | +| DELETE | `/organizations/:id` | 删除组织 | + +## 成员管理 + +| 方法 | 路径 | 描述 | +| ------ | --------------------------------------------- | ------------------ | +| POST | `/organizations/join` | 通过邀请码加入组织 | +| POST | `/organizations/join-request` | 提交加入申请 | +| GET | `/organizations/search` | 搜索组织 | +| POST | `/organizations/join-by-id` | 通过组织ID加入 | +| GET | `/organizations/preview/:invite_code` | 预览组织信息 | +| POST | `/organizations/:id/leave` | 离开组织 | +| POST | `/organizations/:id/request-upgrade` | 请求角色升级 | +| POST | `/organizations/:id/invite-code` | 生成邀请码 | +| GET | `/organizations/:id/search-users` | 搜索可邀请用户 | +| POST | `/organizations/:id/invite` | 邀请成员 | +| GET | `/organizations/:id/members` | 获取成员列表 | +| PUT | `/organizations/:id/members/:user_id` | 更新成员角色 | +| DELETE | `/organizations/:id/members/:user_id` | 移除成员 | + +## 加入请求 + +| 方法 | 路径 | 描述 | +| ---- | ------------------------------------------------------- | ---------------- | +| GET | `/organizations/:id/join-requests` | 获取加入请求列表 | +| PUT | `/organizations/:id/join-requests/:request_id/review` | 审核加入请求 | + +## 知识库共享 + +| 方法 | 路径 | 描述 | +| ------ | --------------------------------------------- | ---------------- | +| POST | `/knowledge-bases/:id/shares` | 共享知识库 | +| GET | `/knowledge-bases/:id/shares` | 获取知识库共享列表 | +| PUT | `/knowledge-bases/:id/shares/:share_id` | 更新共享权限 | +| DELETE | `/knowledge-bases/:id/shares/:share_id` | 取消知识库共享 | + +## 智能体共享 + +| 方法 | 路径 | 描述 | +| ------ | --------------------------------------- | ---------------- | +| POST | `/agents/:id/shares` | 共享智能体 | +| GET | `/agents/:id/shares` | 获取智能体共享列表 | +| DELETE | `/agents/:id/shares/:share_id` | 取消智能体共享 | + +## 共享资源 + +| 方法 | 路径 | 描述 | +| ---- | --------------------------- | ------------------ | +| GET | `/shared-knowledge-bases` | 获取共享知识库列表 | +| GET | `/shared-agents` | 获取共享智能体列表 | + +--- + +## POST `/organizations` - 创建组织 + +**请求参数**: +- `name`: 组织名称(必填) +- `description`: 组织描述(可选) +- `avatar`: 组织头像 URL(可选) +- `invite_code_validity_days`: 邀请码有效天数(可选) +- `member_limit`: 成员上限(可选) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/organizations' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "name": "AI 技术团队", + "description": "专注于 AI 技术研究与知识管理", + "invite_code_validity_days": 7, + "member_limit": 50 +}' +``` + +**响应**: + +```json +{ + "data": { + "id": "org-00000001", + "name": "AI 技术团队", + "description": "专注于 AI 技术研究与知识管理", + "avatar": "", + "owner_id": "user-00000001", + "invite_code": "", + "invite_code_validity_days": 7, + "require_approval": false, + "searchable": false, + "member_limit": 50, + "member_count": 1, + "share_count": 0, + "agent_share_count": 0, + "pending_join_request_count": 0, + "is_owner": true, + "my_role": "owner", + "has_pending_upgrade": false, + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T10:00:00+08:00" + }, + "success": true +} +``` + +## GET `/organizations` - 获取我的组织列表 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/organizations' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "organizations": [ + { + "id": "org-00000001", + "name": "AI 技术团队", + "description": "专注于 AI 技术研究与知识管理", + "avatar": "", + "owner_id": "user-00000001", + "invite_code_validity_days": 7, + "require_approval": false, + "searchable": false, + "member_limit": 50, + "member_count": 3, + "share_count": 2, + "agent_share_count": 1, + "pending_join_request_count": 0, + "is_owner": true, + "my_role": "owner", + "has_pending_upgrade": false, + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T10:00:00+08:00" + } + ] + }, + "success": true +} +``` + +## GET `/organizations/:id` - 获取组织详情 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/organizations/org-00000001' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "id": "org-00000001", + "name": "AI 技术团队", + "description": "专注于 AI 技术研究与知识管理", + "avatar": "", + "owner_id": "user-00000001", + "invite_code": "ABC123XY", + "invite_code_expires_at": "2025-08-19T10:00:00+08:00", + "invite_code_validity_days": 7, + "require_approval": false, + "searchable": true, + "member_limit": 50, + "member_count": 3, + "share_count": 2, + "agent_share_count": 1, + "pending_join_request_count": 1, + "is_owner": true, + "my_role": "owner", + "has_pending_upgrade": false, + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T10:00:00+08:00" + }, + "success": true +} +``` + +## PUT `/organizations/:id` - 更新组织 + +**请求参数**(均为可选): +- `name`: 组织名称 +- `description`: 组织描述 +- `avatar`: 组织头像 URL +- `require_approval`: 是否需要审核加入 +- `searchable`: 是否可被搜索 +- `invite_code_validity_days`: 邀请码有效天数 +- `member_limit`: 成员上限 + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/organizations/org-00000001' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "description": "专注于 AI 技术研究与知识管理(更新)", + "require_approval": true, + "searchable": true +}' +``` + +**响应**: + +```json +{ + "data": { + "id": "org-00000001", + "name": "AI 技术团队", + "description": "专注于 AI 技术研究与知识管理(更新)", + "avatar": "", + "owner_id": "user-00000001", + "invite_code_validity_days": 7, + "require_approval": true, + "searchable": true, + "member_limit": 50, + "member_count": 3, + "share_count": 2, + "agent_share_count": 1, + "pending_join_request_count": 0, + "is_owner": true, + "my_role": "owner", + "has_pending_upgrade": false, + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T12:00:00+08:00" + }, + "success": true +} +``` + +## DELETE `/organizations/:id` - 删除组织 + +**请求**: + +```curl +curl --location --request DELETE 'http://localhost:8080/api/v1/organizations/org-00000001' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +--- + +## POST `/organizations/join` - 通过邀请码加入组织 + +**请求参数**: +- `invite_code`: 邀请码(必填) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/organizations/join' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "invite_code": "ABC123XY" +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +## POST `/organizations/join-request` - 提交加入申请 + +当组织开启了审核加入(`require_approval: true`)时使用。 + +**请求参数**: +- `invite_code`: 邀请码(必填) +- `message`: 申请留言(可选) +- `role`: 申请角色(可选) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/organizations/join-request' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "invite_code": "ABC123XY", + "message": "希望加入团队参与知识库建设", + "role": "editor" +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +## GET `/organizations/search` - 搜索组织 + +**查询参数**: +- `keyword`: 搜索关键字(可选) +- `page`: 页码(默认 1) +- `page_size`: 每页条数(默认 20) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/organizations/search?keyword=AI&page=1&page_size=10' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "organizations": [ + { + "id": "org-00000001", + "name": "AI 技术团队", + "description": "专注于 AI 技术研究与知识管理", + "avatar": "", + "owner_id": "user-00000001", + "invite_code_validity_days": 7, + "require_approval": true, + "searchable": true, + "member_limit": 50, + "member_count": 3, + "share_count": 2, + "agent_share_count": 1, + "pending_join_request_count": 0, + "is_owner": false, + "my_role": "", + "has_pending_upgrade": false, + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T10:00:00+08:00" + } + ] + }, + "success": true +} +``` + +## POST `/organizations/join-by-id` - 通过组织ID加入 + +**请求参数**: +- `organization_id`: 组织 ID(必填) +- `message`: 申请留言(可选) +- `role`: 申请角色(可选) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/organizations/join-by-id' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "organization_id": "org-00000001", + "message": "希望加入贵团队", + "role": "viewer" +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +## GET `/organizations/preview/:invite_code` - 预览组织信息 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/organizations/preview/ABC123XY' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "id": "org-00000001", + "name": "AI 技术团队", + "description": "专注于 AI 技术研究与知识管理", + "avatar": "", + "owner_id": "user-00000001", + "invite_code_validity_days": 7, + "require_approval": true, + "searchable": true, + "member_limit": 50, + "member_count": 3, + "share_count": 0, + "agent_share_count": 0, + "pending_join_request_count": 0, + "is_owner": false, + "my_role": "", + "has_pending_upgrade": false, + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T10:00:00+08:00" + }, + "success": true +} +``` + +## POST `/organizations/:id/leave` - 离开组织 + +**请求**: + +```curl +curl --location --request POST 'http://localhost:8080/api/v1/organizations/org-00000001/leave' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +## POST `/organizations/:id/request-upgrade` - 请求角色升级 + +**请求参数**: +- `requested_role`: 期望角色(必填) +- `message`: 申请理由(可选) + +**请求**: + +```curl +curl --location --request POST 'http://localhost:8080/api/v1/organizations/org-00000001/request-upgrade' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "requested_role": "admin", + "message": "需要管理员权限来管理知识库共享" +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +## POST `/organizations/:id/invite-code` - 生成邀请码 + +**请求**: + +```curl +curl --location --request POST 'http://localhost:8080/api/v1/organizations/org-00000001/invite-code' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "invite_code": "NEW1CODE" + }, + "success": true +} +``` + +## GET `/organizations/:id/search-users` - 搜索可邀请用户 + +**查询参数**: +- `keyword`: 用户名或邮箱关键字(可选) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/organizations/org-00000001/search-users?keyword=zhang' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": [ + { + "id": "user-00000002", + "username": "zhangsan", + "email": "zhangsan@example.com" + }, + { + "id": "user-00000003", + "username": "zhangwei", + "email": "zhangwei@example.com" + } + ], + "success": true +} +``` + +## POST `/organizations/:id/invite` - 邀请成员 + +**请求参数**: +- `user_id`: 用户 ID(必填) +- `role`: 角色(必填) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/organizations/org-00000001/invite' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "user_id": "user-00000002", + "role": "editor" +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +## GET `/organizations/:id/members` - 获取成员列表 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/organizations/org-00000001/members' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "members": [ + { + "id": "mem-00000001", + "user_id": "user-00000001", + "username": "admin", + "email": "admin@example.com", + "avatar": "", + "role": "owner", + "tenant_id": 1, + "joined_at": "2025-08-12T10:00:00+08:00" + }, + { + "id": "mem-00000002", + "user_id": "user-00000002", + "username": "zhangsan", + "email": "zhangsan@example.com", + "avatar": "", + "role": "editor", + "tenant_id": 2, + "joined_at": "2025-08-13T09:00:00+08:00" + } + ] + }, + "success": true +} +``` + +## PUT `/organizations/:id/members/:user_id` - 更新成员角色 + +**请求参数**: +- `role`: 新角色(必填) + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/organizations/org-00000001/members/user-00000002' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "role": "admin" +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +## DELETE `/organizations/:id/members/:user_id` - 移除成员 + +**请求**: + +```curl +curl --location --request DELETE 'http://localhost:8080/api/v1/organizations/org-00000001/members/user-00000002' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +--- + +## GET `/organizations/:id/join-requests` - 获取加入请求列表 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/organizations/org-00000001/join-requests' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "requests": [ + { + "id": "jr-00000001", + "user_id": "user-00000003", + "username": "zhangwei", + "email": "zhangwei@example.com", + "message": "希望加入团队参与知识库建设", + "request_type": "join", + "prev_role": "", + "requested_role": "editor", + "status": "pending", + "created_at": "2025-08-14T10:00:00+08:00" + } + ] + }, + "success": true +} +``` + +## PUT `/organizations/:id/join-requests/:request_id/review` - 审核加入请求 + +**请求参数**: +- `approved`: 是否批准(必填,布尔值) +- `message`: 审核留言(可选) +- `role`: 分配角色(可选,批准时生效) + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/organizations/org-00000001/join-requests/jr-00000001/review' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "approved": true, + "message": "欢迎加入", + "role": "editor" +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +--- + +## POST `/knowledge-bases/:id/shares` - 共享知识库 + +**请求参数**: +- `organization_id`: 目标组织 ID(必填) +- `permission`: 权限级别(必填) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/shares' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "organization_id": "org-00000001", + "permission": "read" +}' +``` + +**响应**: + +```json +{ + "data": { + "id": "kbs-00000001", + "knowledge_base_id": "kb-00000001", + "knowledge_base_name": "技术文档库", + "organization_id": "org-00000001", + "organization_name": "AI 技术团队", + "shared_by_user_id": "user-00000001", + "shared_by_username": "admin", + "source_tenant_id": 1, + "permission": "read", + "my_role_in_org": "owner", + "my_permission": "read", + "created_at": "2025-08-15T10:00:00+08:00" + }, + "success": true +} +``` + +## GET `/knowledge-bases/:id/shares` - 获取知识库共享列表 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/shares' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "shares": [ + { + "id": "kbs-00000001", + "knowledge_base_id": "kb-00000001", + "knowledge_base_name": "技术文档库", + "organization_id": "org-00000001", + "organization_name": "AI 技术团队", + "shared_by_user_id": "user-00000001", + "shared_by_username": "admin", + "source_tenant_id": 1, + "permission": "read", + "my_role_in_org": "owner", + "my_permission": "read", + "created_at": "2025-08-15T10:00:00+08:00" + } + ] + }, + "success": true +} +``` + +## PUT `/knowledge-bases/:id/shares/:share_id` - 更新共享权限 + +**请求参数**: +- `permission`: 新权限级别(必填) + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/shares/kbs-00000001' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "permission": "write" +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +## DELETE `/knowledge-bases/:id/shares/:share_id` - 取消知识库共享 + +**请求**: + +```curl +curl --location --request DELETE 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/shares/kbs-00000001' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +--- + +## POST `/agents/:id/shares` - 共享智能体 + +**请求参数**: +- `organization_id`: 目标组织 ID(必填) +- `permission`: 权限级别(必填) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/agents/agent-00000001/shares' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "organization_id": "org-00000001", + "permission": "read" +}' +``` + +**响应**: + +```json +{ + "data": { + "id": "as-00000001", + "agent_id": "agent-00000001", + "agent_name": "智能客服助手", + "organization_id": "org-00000001", + "organization_name": "AI 技术团队", + "shared_by_user_id": "user-00000001", + "shared_by_username": "admin", + "source_tenant_id": 1, + "permission": "read", + "created_at": "2025-08-15T11:00:00+08:00" + }, + "success": true +} +``` + +## GET `/agents/:id/shares` - 获取智能体共享列表 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/agents/agent-00000001/shares' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "shares": [ + { + "id": "as-00000001", + "agent_id": "agent-00000001", + "agent_name": "智能客服助手", + "organization_id": "org-00000001", + "organization_name": "AI 技术团队", + "shared_by_user_id": "user-00000001", + "shared_by_username": "admin", + "source_tenant_id": 1, + "permission": "read", + "created_at": "2025-08-15T11:00:00+08:00" + } + ] + }, + "success": true +} +``` + +## DELETE `/agents/:id/shares/:share_id` - 取消智能体共享 + +**请求**: + +```curl +curl --location --request DELETE 'http://localhost:8080/api/v1/agents/agent-00000001/shares/as-00000001' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +--- + +## GET `/shared-knowledge-bases` - 获取共享知识库列表 + +获取当前用户通过组织共享获得的所有知识库。 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/shared-knowledge-bases' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": [ + { + "share_id": "kbs-00000001", + "organization_id": "org-00000001", + "org_name": "AI 技术团队", + "permission": "read", + "source_tenant_id": 1, + "shared_at": "2025-08-15T10:00:00+08:00" + } + ], + "success": true +} +``` + +## GET `/shared-agents` - 获取共享智能体列表 + +获取当前用户通过组织共享获得的所有智能体。 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/shared-agents' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": [ + { + "share_id": "as-00000001", + "organization_id": "org-00000001", + "org_name": "AI 技术团队", + "permission": "read", + "source_tenant_id": 1, + "shared_at": "2025-08-15T11:00:00+08:00" + } + ], + "success": true +} +``` diff --git a/docs/api/skill.md b/docs/api/skill.md new file mode 100644 index 00000000..136881ea --- /dev/null +++ b/docs/api/skill.md @@ -0,0 +1,52 @@ +# Skills API + +[返回目录](./README.md) + +| 方法 | 路径 | 描述 | +| ---- | --------- | ------------------ | +| GET | `/skills` | 获取预装 Skills 列表 | + +## GET `/skills` - 获取预装 Skills 列表 + +获取系统中所有预装的智能体技能列表。 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/skills' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": [ + { + "name": "web_search", + "description": "搜索互联网获取最新信息" + }, + { + "name": "code_interpreter", + "description": "执行代码并返回结果" + }, + { + "name": "image_generation", + "description": "根据文本描述生成图片" + } + ], + "skills_available": true, + "success": true +} +``` + +当系统未配置 Skills 时,`skills_available` 返回 `false`,`data` 为空数组: + +```json +{ + "data": [], + "skills_available": false, + "success": true +} +``` diff --git a/docs/api/system.md b/docs/api/system.md new file mode 100644 index 00000000..eeab0cc0 --- /dev/null +++ b/docs/api/system.md @@ -0,0 +1,229 @@ +# 系统管理 API + +[返回目录](./README.md) + +| 方法 | 路径 | 描述 | +| ------ | --------------------------------- | ---------------------- | +| GET | `/system/info` | 获取系统信息 | +| GET | `/system/parser-engines` | 获取解析引擎列表 | +| POST | `/system/parser-engines/check` | 检查解析引擎可用性 | +| POST | `/system/docreader/reconnect` | 重连文档解析服务 | +| GET | `/system/storage-engine-status` | 获取存储引擎状态 | +| POST | `/system/storage-engine-check` | 检查存储引擎连通性 | +| GET | `/system/minio/buckets` | 获取 MinIO 桶列表 | + +## GET `/system/info` - 获取系统信息 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/system/info' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "version": "1.2.0", + "edition": "community", + "commit_id": "a1b2c3d", + "build_time": "2025-08-12T08:00:00Z", + "go_version": "go1.21.5", + "keyword_index_engine": "bleve", + "vector_store_engine": "milvus", + "graph_database_engine": "neo4j", + "minio_enabled": true, + "db_version": "20250810_001" + }, + "success": true +} +``` + +## GET `/system/parser-engines` - 获取解析引擎列表 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/system/parser-engines' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": [ + { + "name": "docreader", + "label": "DocReader", + "description": "高精度文档解析引擎", + "available": true + }, + { + "name": "tika", + "label": "Apache Tika", + "description": "通用文档解析引擎", + "available": false + } + ], + "connected": true, + "success": true +} +``` + +## POST `/system/parser-engines/check` - 检查解析引擎可用性 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/system/parser-engines/check' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "addr": "http://docreader:8000" +}' +``` + +**响应**: + +```json +{ + "data": [ + { + "name": "docreader", + "label": "DocReader", + "description": "高精度文档解析引擎", + "available": true + } + ], + "success": true +} +``` + +## POST `/system/docreader/reconnect` - 重连文档解析服务 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/system/docreader/reconnect' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "addr": "http://docreader:8000" +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +## GET `/system/storage-engine-status` - 获取存储引擎状态 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/system/storage-engine-status' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "engines": [ + { + "name": "minio", + "available": true, + "description": "MinIO 对象存储" + }, + { + "name": "cos", + "available": false, + "description": "腾讯云 COS 对象存储" + }, + { + "name": "s3", + "available": false, + "description": "AWS S3 对象存储" + } + ], + "minio_env_available": true + }, + "success": true +} +``` + +## POST `/system/storage-engine-check` - 检查存储引擎连通性 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/system/storage-engine-check' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "provider": "minio", + "minio": { + "endpoint": "localhost:9000", + "access_key": "minioadmin", + "secret_key": "minioadmin", + "bucket": "weknora", + "use_ssl": false + } +}' +``` + +**响应**: + +```json +{ + "data": { + "ok": true, + "message": "连接成功", + "bucket_created": false + }, + "success": true +} +``` + +## GET `/system/minio/buckets` - 获取 MinIO 桶列表 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/system/minio/buckets' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "buckets": [ + { + "name": "weknora", + "policy": "read-write", + "created_at": "2025-08-01T10:00:00+08:00" + }, + { + "name": "weknora-backup", + "policy": "read-only", + "created_at": "2025-08-05T14:00:00+08:00" + } + ] + }, + "success": true +} +``` diff --git a/docs/api/tenant.md b/docs/api/tenant.md index f24527c3..fd8b912b 100644 --- a/docs/api/tenant.md +++ b/docs/api/tenant.md @@ -9,6 +9,10 @@ | PUT | `/tenants/:id` | 更新租户信息 | | DELETE | `/tenants/:id` | 删除租户 | | GET | `/tenants` | 获取租户列表 | +| GET | `/tenants/all` | 获取所有租户列表(需跨租户权限) | +| GET | `/tenants/search` | 搜索租户(需跨租户权限) | +| GET | `/tenants/kv/:key` | 获取租户KV配置 | +| PUT | `/tenants/kv/:key` | 更新租户KV配置 | ## POST `/tenants` - 创建新租户 @@ -241,3 +245,155 @@ curl --location 'http://localhost:8080/api/v1/tenants' \ "success": true } ``` + +## GET `/tenants/all` - 获取所有租户列表 + +获取系统中所有租户列表,需要跨租户权限。 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/tenants/all' \ +--header 'Content-Type: application/json' \ +--header 'X-API-Key: sk-An7_t_izCKFIJ4iht9Xjcjnj_MC48ILvwezEDki9ScfIa7KA' +``` + +**响应**: + +```json +{ + "data": { + "items": [ + { + "id": 10001, + "name": "weknora-1", + "description": "weknora tenants 1", + "status": "active", + "business": "wechat", + "created_at": "2025-08-11T20:37:28.39698+08:00", + "updated_at": "2025-08-11T20:37:28.405693+08:00" + }, + { + "id": 10002, + "name": "weknora-2", + "description": "weknora tenants 2", + "status": "active", + "business": "wechat", + "created_at": "2025-08-11T20:52:58.05679+08:00", + "updated_at": "2025-08-11T20:52:58.060495+08:00" + } + ] + }, + "success": true +} +``` + +## GET `/tenants/search` - 搜索租户 + +按关键词搜索租户,需要跨租户权限。 + +**查询参数**: +- `keyword`: 搜索关键词(可选) +- `tenant_id`: 按租户ID筛选(可选) +- `page`: 页码(默认 1) +- `page_size`: 每页条数(默认 20) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/tenants/search?keyword=weknora&page=1&page_size=10' \ +--header 'Content-Type: application/json' \ +--header 'X-API-Key: sk-An7_t_izCKFIJ4iht9Xjcjnj_MC48ILvwezEDki9ScfIa7KA' +``` + +**响应**: + +```json +{ + "data": { + "items": [ + { + "id": 10002, + "name": "weknora", + "description": "weknora tenants", + "status": "active", + "business": "wechat", + "created_at": "2025-08-11T20:52:58.05679+08:00", + "updated_at": "2025-08-11T20:52:58.060495+08:00" + } + ], + "total": 1, + "page": 1, + "page_size": 10 + }, + "success": true +} +``` + +## GET `/tenants/kv/:key` - 获取租户KV配置 + +获取指定键名的租户配置项。 + +**支持的 key 值**: +- `agent-config`: Agent 配置 +- `web-search-config`: 网页搜索配置 +- `conversation-config`: 对话配置 +- `prompt-templates`: 提示词模板 +- `parser-engine-config`: 解析引擎配置 +- `storage-engine-config`: 存储引擎配置 +- `chat-history-config`: 聊天历史配置 +- `retrieval-config`: 检索配置 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/tenants/kv/agent-config' \ +--header 'Content-Type: application/json' \ +--header 'X-API-Key: sk-An7_t_izCKFIJ4iht9Xjcjnj_MC48ILvwezEDki9ScfIa7KA' +``` + +**响应**: + +```json +{ + "data": { + "key": "agent-config", + "value": { + "enabled": true, + "max_iterations": 10 + } + }, + "success": true +} +``` + +## PUT `/tenants/kv/:key` - 更新租户KV配置 + +更新指定键名的租户配置项。请求体内容根据不同的 key 值而有所不同。 + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/tenants/kv/agent-config' \ +--header 'Content-Type: application/json' \ +--header 'X-API-Key: sk-An7_t_izCKFIJ4iht9Xjcjnj_MC48ILvwezEDki9ScfIa7KA' \ +--data '{ + "enabled": true, + "max_iterations": 20 +}' +``` + +**响应**: + +```json +{ + "data": { + "key": "agent-config", + "value": { + "enabled": true, + "max_iterations": 20 + } + }, + "success": true +} +``` diff --git a/docs/api/web-search.md b/docs/api/web-search.md new file mode 100644 index 00000000..c05ba578 --- /dev/null +++ b/docs/api/web-search.md @@ -0,0 +1,47 @@ +# Web Search API + +[返回目录](./README.md) + +| 方法 | 路径 | 描述 | +| ---- | ------------------------ | ---------------------- | +| GET | `/web-search/providers` | 获取网络搜索服务商列表 | + +## GET `/web-search/providers` - 获取网络搜索服务商列表 + +获取系统中可用的网络搜索服务商列表。 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/web-search/providers' \ +--header 'X-API-Key: sk-xxxxx' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": [ + { + "name": "google", + "label": "Google Search", + "description": "通过 Google 自定义搜索 API 进行网络搜索", + "enabled": true + }, + { + "name": "bing", + "label": "Bing Search", + "description": "通过 Bing Search API 进行网络搜索", + "enabled": true + }, + { + "name": "serpapi", + "label": "SerpAPI", + "description": "通过 SerpAPI 进行搜索引擎结果抓取", + "enabled": false + } + ], + "success": true +} +```