diff --git a/client/example.go b/client/example.go index 62833ae1..67a6f5de 100644 --- a/client/example.go +++ b/client/example.go @@ -64,7 +64,7 @@ func ExampleUsage() { "source": "local", "type": "document", } - knowledge, err := apiClient.CreateKnowledgeFromFile(context.Background(), createdKB.ID, filePath, metadata, nil) + knowledge, err := apiClient.CreateKnowledgeFromFile(context.Background(), createdKB.ID, filePath, metadata, nil, "") if err != nil { fmt.Printf("Failed to upload knowledge file: %v\n", err) } else { diff --git a/client/faq.go b/client/faq.go index 38bb7c11..035f596b 100644 --- a/client/faq.go +++ b/client/faq.go @@ -113,7 +113,7 @@ type faqSimpleResponse struct { // ListFAQEntries returns paginated FAQ entries under a knowledge base. func (c *Client) ListFAQEntries(ctx context.Context, - knowledgeBaseID string, page, pageSize int, tagID string, + knowledgeBaseID string, page, pageSize int, tagID string, keyword string, ) (*FAQEntriesPage, error) { path := fmt.Sprintf("/api/v1/knowledge-bases/%s/faq/entries", knowledgeBaseID) query := url.Values{} @@ -126,6 +126,9 @@ func (c *Client) ListFAQEntries(ctx context.Context, if tagID != "" { query.Add("tag_id", tagID) } + if keyword != "" { + query.Add("keyword", keyword) + } resp, err := c.doRequest(ctx, http.MethodGet, path, nil, query) if err != nil { diff --git a/client/knowledge.go b/client/knowledge.go index 29edad97..f1faaf11 100644 --- a/client/knowledge.go +++ b/client/knowledge.go @@ -22,8 +22,9 @@ import ( // Knowledge represents knowledge information type Knowledge struct { ID string `json:"id"` - TenantID uint `json:"tenant_id"` + TenantID uint64 `json:"tenant_id"` KnowledgeBaseID string `json:"knowledge_base_id"` + TagID string `json:"tag_id"` Type string `json:"type"` Title string `json:"title"` Description string `json:"description"` @@ -34,7 +35,9 @@ type Knowledge struct { FileName string `json:"file_name"` FileType string `json:"file_type"` FileSize int64 `json:"file_size"` + FileHash string `json:"file_hash"` FilePath string `json:"file_path"` + StorageSize int64 `json:"storage_size"` Metadata map[string]string `json:"metadata"` // Extensible metadata for storing machine information, paths, etc. CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` @@ -78,8 +81,14 @@ var ErrDuplicateFile = errors.New("file already exists") var ErrDuplicateURL = errors.New("URL already exists") // CreateKnowledgeFromFile creates a knowledge entry from a local file path +// Parameters: +// - knowledgeBaseID: The ID of the knowledge base +// - filePath: The local file path +// - metadata: Optional metadata for the knowledge entry +// - enableMultimodel: Optional flag to enable multimodal processing +// - customFileName: Optional custom file name (useful for folder uploads with path) func (c *Client) CreateKnowledgeFromFile(ctx context.Context, - knowledgeBaseID string, filePath string, metadata map[string]string, enableMultimodel *bool, + knowledgeBaseID string, filePath string, metadata map[string]string, enableMultimodel *bool, customFileName string, ) (*Knowledge, error) { // Open the local file file, err := os.Open(filePath) @@ -133,6 +142,13 @@ func (c *Client) CreateKnowledgeFromFile(ctx context.Context, } } + // Add custom file name if provided + if customFileName != "" { + if err := writer.WriteField("fileName", customFileName); err != nil { + return nil, fmt.Errorf("failed to write fileName field: %w", err) + } + } + // Close the multipart writer err = writer.Close() if err != nil { @@ -249,12 +265,16 @@ func (c *Client) ListKnowledge(ctx context.Context, knowledgeBaseID string, page int, pageSize int, + tagID string, ) ([]Knowledge, int64, error) { path := fmt.Sprintf("/api/v1/knowledge-bases/%s/knowledge", knowledgeBaseID) queryParams := url.Values{} queryParams.Add("page", strconv.Itoa(page)) queryParams.Add("page_size", strconv.Itoa(pageSize)) + if tagID != "" { + queryParams.Add("tag_id", tagID) + } resp, err := c.doRequest(ctx, http.MethodGet, path, nil, queryParams) if err != nil { diff --git a/client/knowledgebase.go b/client/knowledgebase.go index 54a59c80..48808ca1 100644 --- a/client/knowledgebase.go +++ b/client/knowledgebase.go @@ -8,7 +8,6 @@ import ( "context" "fmt" "net/http" - "net/url" "time" ) @@ -17,34 +16,44 @@ type KnowledgeBase struct { ID string `json:"id"` Name string `json:"name"` // Name must be unique within the same tenant Type string `json:"type"` + IsTemporary bool `json:"is_temporary"` Description string `json:"description"` - TenantID uint `json:"tenant_id"` // Changed to uint type + TenantID uint64 `json:"tenant_id"` ChunkingConfig ChunkingConfig `json:"chunking_config"` ImageProcessingConfig ImageProcessingConfig `json:"image_processing_config"` FAQConfig *FAQConfig `json:"faq_config"` EmbeddingModelID string `json:"embedding_model_id"` - SummaryModelID string `json:"summary_model_id"` // Summary model ID + SummaryModelID string `json:"summary_model_id"` + VLMConfig VLMConfig `json:"vlm_config"` + StorageConfig StorageConfig `json:"cos_config"` + ExtractConfig *ExtractConfig `json:"extract_config"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` + // Computed fields (not stored in database) + KnowledgeCount int64 `json:"knowledge_count"` + ChunkCount int64 `json:"chunk_count"` + IsProcessing bool `json:"is_processing"` + ProcessingCount int64 `json:"processing_count"` } // KnowledgeBaseConfig represents knowledge base configuration type KnowledgeBaseConfig struct { ChunkingConfig ChunkingConfig `json:"chunking_config"` ImageProcessingConfig ImageProcessingConfig `json:"image_processing_config"` + FAQConfig *FAQConfig `json:"faq_config"` } // ChunkingConfig represents document chunking configuration type ChunkingConfig struct { - ChunkSize int `json:"chunk_size"` // Chunk size - ChunkOverlap int `json:"chunk_overlap"` // Overlap size - Separators []string `json:"separators"` // Separators - EnableMultimodal bool `json:"enable_multimodal"` // Whether to enable multimodal processing + ChunkSize int `json:"chunk_size"` // Chunk size + ChunkOverlap int `json:"chunk_overlap"` // Overlap size + Separators []string `json:"separators"` // Separators } // FAQConfig represents faq-specific configuration type FAQConfig struct { - IndexMode string `json:"index_mode"` + IndexMode string `json:"index_mode"` + QuestionIndexMode string `json:"question_index_mode"` } // ImageProcessingConfig represents image processing configuration @@ -52,6 +61,44 @@ type ImageProcessingConfig struct { ModelID string `json:"model_id"` // Multimodal model ID } +// VLMConfig represents the VLM configuration +type VLMConfig struct { + Enabled bool `json:"enabled"` + ModelID string `json:"model_id"` +} + +// StorageConfig represents the storage configuration +type StorageConfig struct { + SecretID string `json:"secret_id"` + SecretKey string `json:"secret_key"` + Region string `json:"region"` + BucketName string `json:"bucket_name"` + AppID string `json:"app_id"` + PathPrefix string `json:"path_prefix"` + Provider string `json:"provider"` +} + +// ExtractConfig represents the extract configuration for a knowledge base +type ExtractConfig struct { + Enabled bool `json:"enabled"` + Text string `json:"text,omitempty"` + Tags []string `json:"tags,omitempty"` + Nodes []*GraphNode `json:"nodes,omitempty"` + Relations []*GraphRelation `json:"relations,omitempty"` +} + +// GraphNode represents a node in the graph extraction configuration +type GraphNode struct { + Name string `json:"name"` +} + +// GraphRelation represents a relation in the graph extraction configuration +type GraphRelation struct { + Node1 string `json:"node1"` + Node2 string `json:"node2"` + Type string `json:"type"` +} + // KnowledgeBaseResponse knowledge base response type KnowledgeBaseResponse struct { Success bool `json:"success"` @@ -181,14 +228,23 @@ func (c *Client) DeleteKnowledgeBase(ctx context.Context, knowledgeBaseID string return parseResponse(resp, &response) } +// SearchParams represents the search parameters for hybrid search +type SearchParams struct { + QueryText string `json:"query_text"` + VectorThreshold float64 `json:"vector_threshold"` + KeywordThreshold float64 `json:"keyword_threshold"` + MatchCount int `json:"match_count"` + DisableKeywordsMatch bool `json:"disable_keywords_match"` + DisableVectorMatch bool `json:"disable_vector_match"` +} + // HybridSearch performs hybrid search -func (c *Client) HybridSearch(ctx context.Context, knowledgeBaseID string, query string) ([]*SearchResult, error) { +// Note: The backend route is GET but expects JSON body, which is non-standard. +// This client uses POST with JSON body for better compatibility. +func (c *Client) HybridSearch(ctx context.Context, knowledgeBaseID string, params *SearchParams) ([]*SearchResult, error) { path := fmt.Sprintf("/api/v1/knowledge-bases/%s/hybrid-search", knowledgeBaseID) - queryParams := url.Values{} - queryParams.Add("query", query) - - resp, err := c.doRequest(ctx, http.MethodGet, path, nil, queryParams) + resp, err := c.doRequest(ctx, http.MethodGet, path, params, nil) if err != nil { return nil, err } diff --git a/client/tag.go b/client/tag.go new file mode 100644 index 00000000..e467b32d --- /dev/null +++ b/client/tag.go @@ -0,0 +1,157 @@ +package client + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strconv" + "time" +) + +// Tag represents a knowledge base tag. +type Tag struct { + ID string `json:"id"` + TenantID uint64 `json:"tenant_id"` + KnowledgeBaseID string `json:"knowledge_base_id"` + Name string `json:"name"` + Color string `json:"color"` + SortOrder int `json:"sort_order"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// TagWithStats represents tag information along with usage statistics. +type TagWithStats struct { + Tag + KnowledgeCount int64 `json:"knowledge_count"` + ChunkCount int64 `json:"chunk_count"` +} + +// CreateTagPayload is used to create a new tag. +type CreateTagPayload struct { + Name string `json:"name"` + Color string `json:"color,omitempty"` + SortOrder int `json:"sort_order,omitempty"` +} + +// UpdateTagPayload is used to update an existing tag. +type UpdateTagPayload struct { + Name *string `json:"name,omitempty"` + Color *string `json:"color,omitempty"` + SortOrder *int `json:"sort_order,omitempty"` +} + +// TagsPage contains paginated tag results. +type TagsPage struct { + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"page_size"` + Tags []TagWithStats `json:"data"` +} + +// TagsResponse wraps the paginated tags response. +type TagsResponse struct { + Success bool `json:"success"` + Data *TagsPage `json:"data"` + Message string `json:"message,omitempty"` + Code string `json:"code,omitempty"` +} + +// TagResponse wraps a single tag response. +type TagResponse struct { + Success bool `json:"success"` + Data *Tag `json:"data"` + Message string `json:"message,omitempty"` + Code string `json:"code,omitempty"` +} + +type tagSimpleResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Code string `json:"code,omitempty"` +} + +// ListTags returns paginated tags under a knowledge base. +func (c *Client) ListTags(ctx context.Context, + knowledgeBaseID string, page, pageSize int, keyword string, +) (*TagsPage, error) { + path := fmt.Sprintf("/api/v1/knowledge-bases/%s/tags", knowledgeBaseID) + query := url.Values{} + if page > 0 { + query.Add("page", strconv.Itoa(page)) + } + if pageSize > 0 { + query.Add("page_size", strconv.Itoa(pageSize)) + } + if keyword != "" { + query.Add("keyword", keyword) + } + + resp, err := c.doRequest(ctx, http.MethodGet, path, nil, query) + if err != nil { + return nil, err + } + + var response TagsResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + if response.Data == nil { + return &TagsPage{}, nil + } + return response.Data, nil +} + +// CreateTag creates a new tag under a knowledge base. +func (c *Client) CreateTag(ctx context.Context, + knowledgeBaseID string, payload *CreateTagPayload, +) (*Tag, error) { + path := fmt.Sprintf("/api/v1/knowledge-bases/%s/tags", knowledgeBaseID) + resp, err := c.doRequest(ctx, http.MethodPost, path, payload, nil) + if err != nil { + return nil, err + } + + var response TagResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + return response.Data, nil +} + +// UpdateTag updates an existing tag. +func (c *Client) UpdateTag(ctx context.Context, + knowledgeBaseID, tagID string, payload *UpdateTagPayload, +) (*Tag, error) { + path := fmt.Sprintf("/api/v1/knowledge-bases/%s/tags/%s", knowledgeBaseID, tagID) + resp, err := c.doRequest(ctx, http.MethodPut, path, payload, nil) + if err != nil { + return nil, err + } + + var response TagResponse + if err := parseResponse(resp, &response); err != nil { + return nil, err + } + return response.Data, nil +} + +// DeleteTag deletes a tag. Set force to true to delete even if the tag is referenced. +func (c *Client) DeleteTag(ctx context.Context, + knowledgeBaseID, tagID string, force bool, +) error { + path := fmt.Sprintf("/api/v1/knowledge-bases/%s/tags/%s", knowledgeBaseID, tagID) + query := url.Values{} + if force { + query.Add("force", "true") + } + + resp, err := c.doRequest(ctx, http.MethodDelete, path, nil, query) + if err != nil { + return err + } + + var response tagSimpleResponse + return parseResponse(resp, &response) +} diff --git a/docs/API.md b/docs/API.md index e4c03e25..a289790b 100644 --- a/docs/API.md +++ b/docs/API.md @@ -13,6 +13,8 @@ - [知识管理 API](#知识管理api) - [模型管理 API](#模型管理api) - [分块管理 API](#分块管理api) + - [标签管理 API](#标签管理api) + - [FAQ管理 API](#faq管理api) - [会话管理 API](#会话管理api) - [聊天功能 API](#聊天功能api) - [消息管理 API](#消息管理api) @@ -72,10 +74,12 @@ WeKnora API 按功能分为以下几类: 3. **知识管理**:上传、检索和管理知识内容 4. **模型管理**:配置和管理各种AI模型 5. **分块管理**:管理知识的分块内容 -6. **会话管理**:创建和管理对话会话 -7. **聊天功能**:基于知识库进行问答 -8. **消息管理**:获取和管理对话消息 -9. **评估功能**:评估模型性能 +6. **标签管理**:管理知识库的标签分类 +7. **FAQ管理**:管理FAQ问答对 +8. **会话管理**:创建和管理对话会话 +9. **聊天功能**:基于知识库进行问答 +10. **消息管理**:获取和管理对话消息 +11. **评估功能**:评估模型性能 ## API 详细说明 @@ -335,6 +339,7 @@ curl --location 'http://localhost:8080/api/v1/tenants' \ | PUT | `/knowledge-bases/:id` | 更新知识库 | | DELETE | `/knowledge-bases/:id` | 删除知识库 | | POST | `/knowledge-bases/copy` | 拷贝知识库 | +| GET | `/knowledge-bases/:id/hybrid-search` | 混合搜索(向量+关键词) | #### POST `/knowledge-bases` - 创建知识库 @@ -640,6 +645,59 @@ curl --location --request DELETE 'http://localhost:8080/api/v1/knowledge-bases/b } ``` +#### GET `/knowledge-bases/:id/hybrid-search` - 混合搜索 + +执行向量搜索和关键词搜索的混合检索。 + +**注意**:此接口使用 GET 方法但需要 JSON 请求体。 + +**请求参数**: +- `query_text`: 搜索查询文本(必填) +- `vector_threshold`: 向量相似度阈值(0-1,可选) +- `keyword_threshold`: 关键词匹配阈值(可选) +- `match_count`: 返回结果数量(可选) +- `disable_keywords_match`: 是否禁用关键词匹配(可选) +- `disable_vector_match`: 是否禁用向量匹配(可选) + +**请求**: + +```curl +curl --location --request GET 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/hybrid-search' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "query_text": "如何使用知识库", + "vector_threshold": 0.5, + "match_count": 10 +}' +``` + +**响应**: + +```json +{ + "data": [ + { + "id": "chunk-00000001", + "content": "知识库是用于存储和检索知识的系统...", + "knowledge_id": "knowledge-00000001", + "chunk_index": 0, + "knowledge_title": "知识库使用指南", + "start_at": 0, + "end_at": 500, + "seq": 1, + "score": 0.95, + "chunk_type": "text", + "image_info": "", + "metadata": {}, + "knowledge_filename": "guide.pdf", + "knowledge_source": "file" + } + ], + "success": true +} +``` +
### 知识管理API @@ -648,16 +706,25 @@ curl --location --request DELETE 'http://localhost:8080/api/v1/knowledge-bases/b | ------ | ------------------------------------- | ------------------------ | | POST | `/knowledge-bases/:id/knowledge/file` | 从文件创建知识 | | POST | `/knowledge-bases/:id/knowledge/url` | 从 URL 创建知识 | +| POST | `/knowledge-bases/:id/knowledge/manual` | 创建手工 Markdown 知识 | | GET | `/knowledge-bases/:id/knowledge` | 获取知识库下的知识列表 | | GET | `/knowledge/:id` | 获取知识详情 | | DELETE | `/knowledge/:id` | 删除知识 | | GET | `/knowledge/:id/download` | 下载知识文件 | | PUT | `/knowledge/:id` | 更新知识 | +| PUT | `/knowledge/manual/:id` | 更新手工 Markdown 知识 | | PUT | `/knowledge/image/:id/:chunk_id` | 更新图像分块信息 | +| PUT | `/knowledge/tags` | 批量更新知识标签 | | GET | `/knowledge/batch` | 批量获取知识 | #### POST `/knowledge-bases/:id/knowledge/file` - 从文件创建知识 +**表单参数**: +- `file`: 上传的文件(必填) +- `metadata`: JSON 格式的元数据(可选) +- `enable_multimodel`: 是否启用多模态处理(可选,true/false) +- `fileName`: 自定义文件名,用于文件夹上传时保留路径(可选) + **请求**: ```curl @@ -746,12 +813,17 @@ curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/knowle } ``` -#### GET `/knowledge-bases/:id/knowledge?page=&page_size` - 获取知识库下的知识列表 +#### GET `/knowledge-bases/:id/knowledge` - 获取知识库下的知识列表 + +**查询参数**: +- `page`: 页码(默认 1) +- `page_size`: 每页条数(默认 20) +- `tag_id`: 按标签ID筛选(可选) **请求**: ```curl -curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/knowledge?page_size=1&page=1' \ +curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/knowledge?page_size=1&page=1&tag_id=tag-00000001' \ --header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ --header 'Content-Type: application/json' ``` @@ -1306,6 +1378,404 @@ curl --location --request DELETE 'http://localhost:8080/api/v1/chunks/4c4e7c1a-0 +### 标签管理API + +| 方法 | 路径 | 描述 | +| ------ | ------------------------------------- | ------------------------ | +| GET | `/knowledge-bases/:id/tags` | 获取知识库标签列表 | +| POST | `/knowledge-bases/:id/tags` | 创建标签 | +| PUT | `/knowledge-bases/:id/tags/:tag_id` | 更新标签 | +| DELETE | `/knowledge-bases/:id/tags/:tag_id` | 删除标签 | + +#### GET `/knowledge-bases/:id/tags` - 获取知识库标签列表 + +**查询参数**: +- `page`: 页码(默认 1) +- `page_size`: 每页条数(默认 20) +- `keyword`: 标签名称关键字搜索(可选) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/tags?page=1&page_size=10' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "total": 2, + "page": 1, + "page_size": 10, + "data": [ + { + "id": "tag-00000001", + "tenant_id": 1, + "knowledge_base_id": "kb-00000001", + "name": "技术文档", + "color": "#1890ff", + "sort_order": 1, + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T10:00:00+08:00", + "knowledge_count": 5, + "chunk_count": 120 + }, + { + "id": "tag-00000002", + "tenant_id": 1, + "knowledge_base_id": "kb-00000001", + "name": "常见问题", + "color": "#52c41a", + "sort_order": 2, + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T10:00:00+08:00", + "knowledge_count": 3, + "chunk_count": 45 + } + ] + }, + "success": true +} +``` + +#### POST `/knowledge-bases/:id/tags` - 创建标签 + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/tags' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "name": "产品手册", + "color": "#faad14", + "sort_order": 3 +}' +``` + +**响应**: + +```json +{ + "data": { + "id": "tag-00000003", + "tenant_id": 1, + "knowledge_base_id": "kb-00000001", + "name": "产品手册", + "color": "#faad14", + "sort_order": 3, + "created_at": "2025-08-12T11:00:00+08:00", + "updated_at": "2025-08-12T11:00:00+08:00" + }, + "success": true +} +``` + +#### PUT `/knowledge-bases/:id/tags/:tag_id` - 更新标签 + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/tags/tag-00000003' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "name": "产品手册更新", + "color": "#ff4d4f" +}' +``` + +**响应**: + +```json +{ + "data": { + "id": "tag-00000003", + "tenant_id": 1, + "knowledge_base_id": "kb-00000001", + "name": "产品手册更新", + "color": "#ff4d4f", + "sort_order": 3, + "created_at": "2025-08-12T11:00:00+08:00", + "updated_at": "2025-08-12T11:30:00+08:00" + }, + "success": true +} +``` + +#### DELETE `/knowledge-bases/:id/tags/:tag_id` - 删除标签 + +**查询参数**: +- `force`: 设置为 `true` 时强制删除(即使标签被引用) + +**请求**: + +```curl +curl --location --request DELETE 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/tags/tag-00000003?force=true' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "success": true +} +``` + + + +### FAQ管理API + +| 方法 | 路径 | 描述 | +| ------ | ------------------------------------------- | ------------------------ | +| GET | `/knowledge-bases/:id/faq/entries` | 获取FAQ条目列表 | +| POST | `/knowledge-bases/:id/faq/entries` | 批量导入FAQ条目 | +| PUT | `/knowledge-bases/:id/faq/entries/:entry_id`| 更新单个FAQ条目 | +| PUT | `/knowledge-bases/:id/faq/entries/status` | 批量更新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` - 获取FAQ条目列表 + +**查询参数**: +- `page`: 页码(默认 1) +- `page_size`: 每页条数(默认 20) +- `tag_id`: 按标签ID筛选(可选) +- `keyword`: 关键字搜索(可选) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries?page=1&page_size=10' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' +``` + +**响应**: + +```json +{ + "data": { + "total": 100, + "page": 1, + "page_size": 10, + "data": [ + { + "id": "faq-00000001", + "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` - 批量导入FAQ条目 + +**请求参数**: +- `mode`: 导入模式,`append`(追加)或 `replace`(替换) +- `entries`: FAQ条目数组 +- `knowledge_id`: 关联的知识ID(可选) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "mode": "append", + "entries": [ + { + "standard_question": "如何联系客服?", + "similar_questions": ["客服电话", "在线客服"], + "answers": ["您可以通过拨打400-xxx-xxxx联系我们的客服。"], + "tag_id": "tag-00000001" + }, + { + "standard_question": "退款政策是什么?", + "answers": ["我们提供7天无理由退款服务。"] + } + ] +}' +``` + +**响应**: + +```json +{ + "data": { + "task_id": "task-00000001" + }, + "success": true +} +``` + +注:批量导入为异步操作,返回任务ID用于追踪进度。 + +#### PUT `/knowledge-bases/:id/faq/entries/:entry_id` - 更新单个FAQ条目 + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/faq-00000001' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "standard_question": "如何重置账户密码?", + "similar_questions": ["忘记密码怎么办", "密码找回", "重置密码"], + "answers": ["您可以通过以下步骤重置密码:1. 点击登录页面的"忘记密码" 2. 输入注册邮箱 3. 查收重置邮件"], + "is_enabled": true +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +#### PUT `/knowledge-bases/:id/faq/entries/status` - 批量更新FAQ启用状态 + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/status' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "updates": { + "faq-00000001": true, + "faq-00000002": false, + "faq-00000003": true + } +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +#### PUT `/knowledge-bases/:id/faq/entries/tags` - 批量更新FAQ标签 + +**请求**: + +```curl +curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/tags' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "updates": { + "faq-00000001": "tag-00000001", + "faq-00000002": "tag-00000002", + "faq-00000003": null + } +}' +``` + +注:设置为 `null` 可清除标签关联。 + +**响应**: + +```json +{ + "success": true +} +``` + +#### DELETE `/knowledge-bases/:id/faq/entries` - 批量删除FAQ条目 + +**请求**: + +```curl +curl --location --request DELETE 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "ids": ["faq-00000001", "faq-00000002"] +}' +``` + +**响应**: + +```json +{ + "success": true +} +``` + +#### POST `/knowledge-bases/:id/faq/search` - 混合搜索FAQ + +**请求参数**: +- `query_text`: 搜索查询文本 +- `vector_threshold`: 向量相似度阈值(0-1) +- `match_count`: 返回结果数量(最大200) + +**请求**: + +```curl +curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/search' \ +--header 'X-API-Key: sk-vQHV2NZI_LK5W7wHQvH3yGYExX8YnhaHwZipUYbiZKCYJbBQ' \ +--header 'Content-Type: application/json' \ +--data '{ + "query_text": "如何重置密码", + "vector_threshold": 0.5, + "match_count": 10 +}' +``` + +**响应**: + +```json +{ + "data": [ + { + "id": "faq-00000001", + "chunk_id": "chunk-00000001", + "knowledge_id": "knowledge-00000001", + "knowledge_base_id": "kb-00000001", + "tag_id": "tag-00000001", + "is_enabled": true, + "standard_question": "如何重置密码?", + "similar_questions": ["忘记密码怎么办", "密码找回"], + "answers": ["您可以通过点击登录页面的'忘记密码'链接来重置密码。"], + "chunk_type": "faq", + "score": 0.95, + "match_type": "vector", + "created_at": "2025-08-12T10:00:00+08:00", + "updated_at": "2025-08-12T10:00:00+08:00" + } + ], + "success": true +} +``` + + + ### 会话管理API | 方法 | 路径 | 描述 | diff --git a/frontend/src/api/knowledge-base/index.ts b/frontend/src/api/knowledge-base/index.ts index 53ce0a5b..17aac945 100644 --- a/frontend/src/api/knowledge-base/index.ts +++ b/frontend/src/api/knowledge-base/index.ts @@ -95,8 +95,12 @@ export function getChunkByIdOnly(chunkId: string) { return get(`/api/v1/chunks/by-id/${chunkId}`); } -export function listKnowledgeTags(kbId: string) { - return get(`/api/v1/knowledge-bases/${kbId}/tags`); +export function listKnowledgeTags( + kbId: string, + params?: { page?: number; page_size?: number; keyword?: string }, +) { + const query = buildQuery(params); + return get(`/api/v1/knowledge-bases/${kbId}/tags${query}`); } export function createKnowledgeBaseTag( @@ -138,7 +142,10 @@ const buildQuery = (params?: Record