diff --git a/client/knowledgebase.go b/client/knowledgebase.go index b2cfdf71..1a10cbf9 100644 --- a/client/knowledgebase.go +++ b/client/knowledgebase.go @@ -371,13 +371,11 @@ type SearchParams struct { DisableVectorMatch bool `json:"disable_vector_match"` } -// HybridSearch performs hybrid search -// Note: The backend route is GET but expects JSON body, which is non-standard. -// This client uses POST with JSON body for better compatibility. +// HybridSearch performs hybrid search. func (c *Client) HybridSearch(ctx context.Context, knowledgeBaseID string, params *SearchParams) ([]*SearchResult, error) { path := fmt.Sprintf("/api/v1/knowledge-bases/%s/hybrid-search", knowledgeBaseID) - resp, err := c.doRequest(ctx, http.MethodGet, path, params, nil) + resp, err := c.doRequest(ctx, http.MethodPost, path, params, nil) if err != nil { return nil, err } diff --git a/docs/api/knowledge-base.md b/docs/api/knowledge-base.md index c15d51e2..86b1c976 100644 --- a/docs/api/knowledge-base.md +++ b/docs/api/knowledge-base.md @@ -18,7 +18,8 @@ | PUT | `/knowledge-bases/:id` | 更新知识库 | | DELETE | `/knowledge-bases/:id` | 删除知识库 | | PUT | `/knowledge-bases/:id/pin` | 置顶/取消置顶知识库 | -| GET | `/knowledge-bases/:id/hybrid-search` | 混合搜索(向量+关键词) | +| POST | `/knowledge-bases/:id/hybrid-search` | 混合搜索(向量+关键词,推荐) | +| GET | `/knowledge-bases/:id/hybrid-search` | 混合搜索(兼容旧客户端,需 JSON 请求体) | | POST | `/knowledge-bases/copy` | 拷贝知识库(异步任务) | | GET | `/knowledge-bases/copy/progress/:task_id` | 获取拷贝进度 | | GET | `/knowledge-bases/:id/move-targets` | 获取可迁移目标知识库列表 | @@ -358,11 +359,11 @@ curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-0 **响应**: 字段结构同 `POST /knowledge-bases` 响应(包含 Phase 2 的 `vector_store_*` 元数据字段),本接口操作后 `is_pinned` 翻转、`pinned_at` 同步更新。 -## GET `/knowledge-bases/:id/hybrid-search` - 混合搜索 +## POST `/knowledge-bases/:id/hybrid-search` - 混合搜索 -在指定知识库内执行向量召回 + 关键词召回的混合检索。 +在指定知识库内执行向量召回 + 关键词召回的混合检索。请求参数通过 JSON 请求体传递(`SearchParams`)。 -**注意**:此接口使用 `GET` 方法但 **需要 JSON 请求体**(`SearchParams`),并非通过 query string 传参。 +> **兼容说明**:`GET` 方法同样可用(需携带 JSON 请求体),供旧版客户端兼容;新集成请使用 `POST`。 **路径参数**: @@ -389,7 +390,7 @@ curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-0 **请求**: ```curl -curl --location --request GET 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/hybrid-search' \ +curl --location --request POST 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/hybrid-search' \ --header 'X-API-Key: sk-xxxxx' \ --header 'Content-Type: application/json' \ --data '{ diff --git a/docs/docs.go b/docs/docs.go index bd8edfc7..72db5524 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -4524,7 +4524,61 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "在知识库中执行向量和关键词混合搜索", + "description": "在知识库中执行向量和关键词混合搜索。推荐使用 POST;GET 携带 JSON 请求体仍受支持(兼容旧客户端)。", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "知识库" + ], + "summary": "混合搜索", + "parameters": [ + { + "type": "string", + "description": "知识库ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "搜索参数", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.SearchParams" + } + } + ], + "responses": { + "200": { + "description": "搜索结果", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "请求参数错误", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "在知识库中执行向量和关键词混合搜索。推荐使用 POST;GET 携带 JSON 请求体仍受支持(兼容旧客户端)。", "consumes": [ "application/json" ], @@ -6350,21 +6404,21 @@ const docTemplate = `{ } } }, - "/knowledgebase/{kb_id}/wiki/categories": { + "/knowledgebase/{kb_id}/wiki/folders": { "get": { "security": [ { "Bearer": [] } ], - "description": "Retrieve direct child directories for a wiki page type and optional parent_path", + "description": "Retrieve the direct child folders of a parent folder (parent_id empty = root level), each with its page count and a has-children flag for the directory tree.", "produces": [ "application/json" ], "tags": [ "Wiki" ], - "summary": "List wiki category paths", + "summary": "List wiki folders", "parameters": [ { "type": "string", @@ -6375,27 +6429,8 @@ const docTemplate = `{ }, { "type": "string", - "description": "Wiki page type; comma-separated for multiple (e.g. entity,concept)", - "name": "page_type", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "Parent category path, slash-separated", - "name": "parent_path", - "in": "query" - }, - { - "type": "integer", - "description": "Page number (1-based)", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "description": "Page size", - "name": "page_size", + "description": "Parent folder id (empty = root)", + "name": "parent_id", "in": "query" } ], @@ -6403,7 +6438,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiCategoryPathListResponse" + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolderListResponse" } }, "400": { @@ -6413,6 +6448,178 @@ const docTemplate = `{ } } } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Create a new (initially empty) directory node under parent_id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Wiki" + ], + "summary": "Create a wiki folder", + "parameters": [ + { + "type": "string", + "description": "Knowledge base ID", + "name": "kb_id", + "in": "path", + "required": true + }, + { + "description": "Folder data", + "name": "folder", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolderCreateRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolder" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, + "/knowledgebase/{kb_id}/wiki/folders/{folder_id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Rename and/or reparent a folder; the whole subtree's paths and the affected pages' cached paths are recomputed", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Wiki" + ], + "summary": "Rename or move a wiki folder", + "parameters": [ + { + "type": "string", + "description": "Knowledge base ID", + "name": "kb_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Folder ID", + "name": "folder_id", + "in": "path", + "required": true + }, + { + "description": "Folder update", + "name": "folder", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolderUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolder" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Delete a folder that has no pages and no child folders", + "tags": [ + "Wiki" + ], + "summary": "Delete an empty wiki folder", + "parameters": [ + { + "type": "string", + "description": "Knowledge base ID", + "name": "kb_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Folder ID", + "name": "folder_id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } } }, "/knowledgebase/{kb_id}/wiki/graph": { @@ -6722,6 +6929,58 @@ const docTemplate = `{ } } }, + "/knowledgebase/{kb_id}/wiki/move-page": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Relocate a page (identified by slug in the body) into a folder (folder_id empty = root); the page's cached category path is recomputed", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Wiki" + ], + "summary": "Move a wiki page into a folder", + "parameters": [ + { + "type": "string", + "description": "Knowledge base ID", + "name": "kb_id", + "in": "path", + "required": true + }, + { + "description": "Move target", + "name": "move", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiPageMoveRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiPage" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, "/knowledgebase/{kb_id}/wiki/pages": { "get": { "security": [ @@ -17163,43 +17422,6 @@ const docTemplate = `{ "WebSearchProviderTypeSearxng" ] }, - "github_com_Tencent_WeKnora_internal_types.WikiCategoryPath": { - "type": "object", - "properties": { - "count": { - "type": "integer" - }, - "path": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "github_com_Tencent_WeKnora_internal_types.WikiCategoryPathListResponse": { - "type": "object", - "properties": { - "page": { - "type": "integer" - }, - "page_size": { - "type": "integer" - }, - "paths": { - "type": "array", - "items": { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiCategoryPath" - } - }, - "total": { - "type": "integer" - }, - "total_pages": { - "type": "integer" - } - } - }, "github_com_Tencent_WeKnora_internal_types.WikiConfig": { "type": "object", "properties": { @@ -17246,6 +17468,127 @@ const docTemplate = `{ "WikiExtractionExhaustive" ] }, + "github_com_Tencent_WeKnora_internal_types.WikiFolder": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "deleted_at": { + "$ref": "#/definitions/gorm.DeletedAt" + }, + "depth": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "knowledge_base_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parent_id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sort_order": { + "type": "integer" + }, + "tenant_id": { + "type": "integer" + }, + "updated_at": { + "type": "string" + } + } + }, + "github_com_Tencent_WeKnora_internal_types.WikiFolderCreateRequest": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "parent_id": { + "type": "string" + } + } + }, + "github_com_Tencent_WeKnora_internal_types.WikiFolderListResponse": { + "type": "object", + "properties": { + "folders": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolderNode" + } + }, + "parent_id": { + "type": "string" + } + } + }, + "github_com_Tencent_WeKnora_internal_types.WikiFolderNode": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "deleted_at": { + "$ref": "#/definitions/gorm.DeletedAt" + }, + "depth": { + "type": "integer" + }, + "has_children": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "knowledge_base_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "page_count": { + "type": "integer" + }, + "parent_id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sort_order": { + "type": "integer" + }, + "tenant_id": { + "type": "integer" + }, + "updated_at": { + "type": "string" + } + } + }, + "github_com_Tencent_WeKnora_internal_types.WikiFolderUpdateRequest": { + "type": "object", + "properties": { + "move_parent": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "parent_id": { + "type": "string" + } + } + }, "github_com_Tencent_WeKnora_internal_types.WikiGraphData": { "type": "object", "properties": { @@ -17474,7 +17817,7 @@ const docTemplate = `{ } }, "category_path": { - "description": "CategoryPath is the directory breadcrumb that groups this page in the\nwiki browser, e.g. [\"AI\", \"LLM 应用\", \"RAG\"]. It is intentionally\nlabel-based so intermediate directory nodes do not need to be real pages.", + "description": "CategoryPath is the directory breadcrumb that groups this page in the\nwiki browser, e.g. [\"AI\", \"LLM 应用\", \"RAG\"]. Derived cache of the\nfolder chain identified by FolderID.", "type": "array", "items": { "type": "string" @@ -17507,6 +17850,10 @@ const docTemplate = `{ "description": "Depth is len(CategoryPath), cached for filtering / display.", "type": "integer" }, + "folder_id": { + "description": "FolderID is the single source of truth for where this page sits in the\ndirectory tree — a reference to wiki_folders.id (\"\" = wiki root). The\nCategoryPath / WikiPath / Depth fields below are denormalized caches\nrecomputed from this folder's chain on every write so list/index/search\nqueries don't have to join wiki_folders.", + "type": "string" + }, "id": { "description": "Unique identifier (UUID)", "type": "string" @@ -17541,7 +17888,7 @@ const docTemplate = `{ "type": "string" }, "parent_slug": { - "description": "ParentSlug optionally points at the wiki page that should act as this\npage's semantic parent in the directory tree. The parent may be empty\nwhen the page is grouped only by CategoryPath.", + "description": "ParentSlug optionally points at the wiki page that should act as this\npage's semantic parent in the directory tree. The parent may be empty\nwhen the page is grouped only by FolderID.", "type": "string" }, "slug": { @@ -17656,6 +18003,20 @@ const docTemplate = `{ } } }, + "github_com_Tencent_WeKnora_internal_types.WikiPageMoveRequest": { + "type": "object", + "required": [ + "slug" + ], + "properties": { + "folder_id": { + "type": "string" + }, + "slug": { + "type": "string" + } + } + }, "github_com_Tencent_WeKnora_internal_types.WikiStats": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 39cdafc7..c881897e 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -4517,7 +4517,61 @@ "ApiKeyAuth": [] } ], - "description": "在知识库中执行向量和关键词混合搜索", + "description": "在知识库中执行向量和关键词混合搜索。推荐使用 POST;GET 携带 JSON 请求体仍受支持(兼容旧客户端)。", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "知识库" + ], + "summary": "混合搜索", + "parameters": [ + { + "type": "string", + "description": "知识库ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "搜索参数", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.SearchParams" + } + } + ], + "responses": { + "200": { + "description": "搜索结果", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "请求参数错误", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "在知识库中执行向量和关键词混合搜索。推荐使用 POST;GET 携带 JSON 请求体仍受支持(兼容旧客户端)。", "consumes": [ "application/json" ], @@ -6343,21 +6397,21 @@ } } }, - "/knowledgebase/{kb_id}/wiki/categories": { + "/knowledgebase/{kb_id}/wiki/folders": { "get": { "security": [ { "Bearer": [] } ], - "description": "Retrieve direct child directories for a wiki page type and optional parent_path", + "description": "Retrieve the direct child folders of a parent folder (parent_id empty = root level), each with its page count and a has-children flag for the directory tree.", "produces": [ "application/json" ], "tags": [ "Wiki" ], - "summary": "List wiki category paths", + "summary": "List wiki folders", "parameters": [ { "type": "string", @@ -6368,27 +6422,8 @@ }, { "type": "string", - "description": "Wiki page type; comma-separated for multiple (e.g. entity,concept)", - "name": "page_type", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "Parent category path, slash-separated", - "name": "parent_path", - "in": "query" - }, - { - "type": "integer", - "description": "Page number (1-based)", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "description": "Page size", - "name": "page_size", + "description": "Parent folder id (empty = root)", + "name": "parent_id", "in": "query" } ], @@ -6396,7 +6431,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiCategoryPathListResponse" + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolderListResponse" } }, "400": { @@ -6406,6 +6441,178 @@ } } } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Create a new (initially empty) directory node under parent_id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Wiki" + ], + "summary": "Create a wiki folder", + "parameters": [ + { + "type": "string", + "description": "Knowledge base ID", + "name": "kb_id", + "in": "path", + "required": true + }, + { + "description": "Folder data", + "name": "folder", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolderCreateRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolder" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, + "/knowledgebase/{kb_id}/wiki/folders/{folder_id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Rename and/or reparent a folder; the whole subtree's paths and the affected pages' cached paths are recomputed", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Wiki" + ], + "summary": "Rename or move a wiki folder", + "parameters": [ + { + "type": "string", + "description": "Knowledge base ID", + "name": "kb_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Folder ID", + "name": "folder_id", + "in": "path", + "required": true + }, + { + "description": "Folder update", + "name": "folder", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolderUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolder" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Delete a folder that has no pages and no child folders", + "tags": [ + "Wiki" + ], + "summary": "Delete an empty wiki folder", + "parameters": [ + { + "type": "string", + "description": "Knowledge base ID", + "name": "kb_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Folder ID", + "name": "folder_id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } } }, "/knowledgebase/{kb_id}/wiki/graph": { @@ -6715,6 +6922,58 @@ } } }, + "/knowledgebase/{kb_id}/wiki/move-page": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Relocate a page (identified by slug in the body) into a folder (folder_id empty = root); the page's cached category path is recomputed", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Wiki" + ], + "summary": "Move a wiki page into a folder", + "parameters": [ + { + "type": "string", + "description": "Knowledge base ID", + "name": "kb_id", + "in": "path", + "required": true + }, + { + "description": "Move target", + "name": "move", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiPageMoveRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiPage" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, "/knowledgebase/{kb_id}/wiki/pages": { "get": { "security": [ @@ -17156,43 +17415,6 @@ "WebSearchProviderTypeSearxng" ] }, - "github_com_Tencent_WeKnora_internal_types.WikiCategoryPath": { - "type": "object", - "properties": { - "count": { - "type": "integer" - }, - "path": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "github_com_Tencent_WeKnora_internal_types.WikiCategoryPathListResponse": { - "type": "object", - "properties": { - "page": { - "type": "integer" - }, - "page_size": { - "type": "integer" - }, - "paths": { - "type": "array", - "items": { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiCategoryPath" - } - }, - "total": { - "type": "integer" - }, - "total_pages": { - "type": "integer" - } - } - }, "github_com_Tencent_WeKnora_internal_types.WikiConfig": { "type": "object", "properties": { @@ -17239,6 +17461,127 @@ "WikiExtractionExhaustive" ] }, + "github_com_Tencent_WeKnora_internal_types.WikiFolder": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "deleted_at": { + "$ref": "#/definitions/gorm.DeletedAt" + }, + "depth": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "knowledge_base_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parent_id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sort_order": { + "type": "integer" + }, + "tenant_id": { + "type": "integer" + }, + "updated_at": { + "type": "string" + } + } + }, + "github_com_Tencent_WeKnora_internal_types.WikiFolderCreateRequest": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "parent_id": { + "type": "string" + } + } + }, + "github_com_Tencent_WeKnora_internal_types.WikiFolderListResponse": { + "type": "object", + "properties": { + "folders": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolderNode" + } + }, + "parent_id": { + "type": "string" + } + } + }, + "github_com_Tencent_WeKnora_internal_types.WikiFolderNode": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "deleted_at": { + "$ref": "#/definitions/gorm.DeletedAt" + }, + "depth": { + "type": "integer" + }, + "has_children": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "knowledge_base_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "page_count": { + "type": "integer" + }, + "parent_id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sort_order": { + "type": "integer" + }, + "tenant_id": { + "type": "integer" + }, + "updated_at": { + "type": "string" + } + } + }, + "github_com_Tencent_WeKnora_internal_types.WikiFolderUpdateRequest": { + "type": "object", + "properties": { + "move_parent": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "parent_id": { + "type": "string" + } + } + }, "github_com_Tencent_WeKnora_internal_types.WikiGraphData": { "type": "object", "properties": { @@ -17467,7 +17810,7 @@ } }, "category_path": { - "description": "CategoryPath is the directory breadcrumb that groups this page in the\nwiki browser, e.g. [\"AI\", \"LLM 应用\", \"RAG\"]. It is intentionally\nlabel-based so intermediate directory nodes do not need to be real pages.", + "description": "CategoryPath is the directory breadcrumb that groups this page in the\nwiki browser, e.g. [\"AI\", \"LLM 应用\", \"RAG\"]. Derived cache of the\nfolder chain identified by FolderID.", "type": "array", "items": { "type": "string" @@ -17500,6 +17843,10 @@ "description": "Depth is len(CategoryPath), cached for filtering / display.", "type": "integer" }, + "folder_id": { + "description": "FolderID is the single source of truth for where this page sits in the\ndirectory tree — a reference to wiki_folders.id (\"\" = wiki root). The\nCategoryPath / WikiPath / Depth fields below are denormalized caches\nrecomputed from this folder's chain on every write so list/index/search\nqueries don't have to join wiki_folders.", + "type": "string" + }, "id": { "description": "Unique identifier (UUID)", "type": "string" @@ -17534,7 +17881,7 @@ "type": "string" }, "parent_slug": { - "description": "ParentSlug optionally points at the wiki page that should act as this\npage's semantic parent in the directory tree. The parent may be empty\nwhen the page is grouped only by CategoryPath.", + "description": "ParentSlug optionally points at the wiki page that should act as this\npage's semantic parent in the directory tree. The parent may be empty\nwhen the page is grouped only by FolderID.", "type": "string" }, "slug": { @@ -17649,6 +17996,20 @@ } } }, + "github_com_Tencent_WeKnora_internal_types.WikiPageMoveRequest": { + "type": "object", + "required": [ + "slug" + ], + "properties": { + "folder_id": { + "type": "string" + }, + "slug": { + "type": "string" + } + } + }, "github_com_Tencent_WeKnora_internal_types.WikiStats": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ec4d4f8a..d0258311 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -3347,30 +3347,6 @@ definitions: - WebSearchProviderTypeOllama - WebSearchProviderTypeBaidu - WebSearchProviderTypeSearxng - github_com_Tencent_WeKnora_internal_types.WikiCategoryPath: - properties: - count: - type: integer - path: - items: - type: string - type: array - type: object - github_com_Tencent_WeKnora_internal_types.WikiCategoryPathListResponse: - properties: - page: - type: integer - page_size: - type: integer - paths: - items: - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.WikiCategoryPath' - type: array - total: - type: integer - total_pages: - type: integer - type: object github_com_Tencent_WeKnora_internal_types.WikiConfig: properties: extraction_granularity: @@ -3420,6 +3396,85 @@ definitions: - WikiExtractionFocused - WikiExtractionStandard - WikiExtractionExhaustive + github_com_Tencent_WeKnora_internal_types.WikiFolder: + properties: + created_at: + type: string + deleted_at: + $ref: '#/definitions/gorm.DeletedAt' + depth: + type: integer + id: + type: string + knowledge_base_id: + type: string + name: + type: string + parent_id: + type: string + path: + type: string + sort_order: + type: integer + tenant_id: + type: integer + updated_at: + type: string + type: object + github_com_Tencent_WeKnora_internal_types.WikiFolderCreateRequest: + properties: + name: + type: string + parent_id: + type: string + type: object + github_com_Tencent_WeKnora_internal_types.WikiFolderListResponse: + properties: + folders: + items: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolderNode' + type: array + parent_id: + type: string + type: object + github_com_Tencent_WeKnora_internal_types.WikiFolderNode: + properties: + created_at: + type: string + deleted_at: + $ref: '#/definitions/gorm.DeletedAt' + depth: + type: integer + has_children: + type: boolean + id: + type: string + knowledge_base_id: + type: string + name: + type: string + page_count: + type: integer + parent_id: + type: string + path: + type: string + sort_order: + type: integer + tenant_id: + type: integer + updated_at: + type: string + type: object + github_com_Tencent_WeKnora_internal_types.WikiFolderUpdateRequest: + properties: + move_parent: + type: boolean + name: + type: string + parent_id: + type: string + type: object github_com_Tencent_WeKnora_internal_types.WikiGraphData: properties: edges: @@ -3590,8 +3645,8 @@ definitions: category_path: description: |- CategoryPath is the directory breadcrumb that groups this page in the - wiki browser, e.g. ["AI", "LLM 应用", "RAG"]. It is intentionally - label-based so intermediate directory nodes do not need to be real pages. + wiki browser, e.g. ["AI", "LLM 应用", "RAG"]. Derived cache of the + folder chain identified by FolderID. items: type: string type: array @@ -3620,6 +3675,14 @@ definitions: depth: description: Depth is len(CategoryPath), cached for filtering / display. type: integer + folder_id: + description: |- + FolderID is the single source of truth for where this page sits in the + directory tree — a reference to wiki_folders.id ("" = wiki root). The + CategoryPath / WikiPath / Depth fields below are denormalized caches + recomputed from this folder's chain on every write so list/index/search + queries don't have to join wiki_folders. + type: string id: description: Unique identifier (UUID) type: string @@ -3649,7 +3712,7 @@ definitions: description: |- ParentSlug optionally points at the wiki page that should act as this page's semantic parent in the directory tree. The parent may be empty - when the page is grouped only by CategoryPath. + when the page is grouped only by FolderID. type: string slug: description: |- @@ -3743,6 +3806,15 @@ definitions: total_pages: type: integer type: object + github_com_Tencent_WeKnora_internal_types.WikiPageMoveRequest: + properties: + folder_id: + type: string + slug: + type: string + required: + - slug + type: object github_com_Tencent_WeKnora_internal_types.WikiStats: properties: is_active: @@ -7687,7 +7759,41 @@ paths: get: consumes: - application/json - description: 在知识库中执行向量和关键词混合搜索 + description: 在知识库中执行向量和关键词混合搜索。推荐使用 POST;GET 携带 JSON 请求体仍受支持(兼容旧客户端)。 + parameters: + - description: 知识库ID + in: path + name: id + required: true + type: string + - description: 搜索参数 + in: body + name: request + required: true + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.SearchParams' + produces: + - application/json + responses: + "200": + description: 搜索结果 + schema: + additionalProperties: true + type: object + "400": + description: 请求参数错误 + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + security: + - Bearer: [] + - ApiKeyAuth: [] + summary: 混合搜索 + tags: + - 知识库 + post: + consumes: + - application/json + description: 在知识库中执行向量和关键词混合搜索。推荐使用 POST;GET 携带 JSON 请求体仍受支持(兼容旧客户端)。 parameters: - description: 知识库ID in: path @@ -8903,47 +9009,147 @@ paths: summary: Auto-fix wiki issues tags: - Wiki - /knowledgebase/{kb_id}/wiki/categories: + /knowledgebase/{kb_id}/wiki/folders: get: - description: Retrieve direct child directories for a wiki page type and optional - parent_path + description: Retrieve the direct child folders of a parent folder (parent_id + empty = root level), each with its page count and a has-children flag for + the directory tree. parameters: - description: Knowledge base ID in: path name: kb_id required: true type: string - - description: Wiki page type; comma-separated for multiple (e.g. entity,concept) + - description: Parent folder id (empty = root) in: query - name: page_type - required: true + name: parent_id type: string - - description: Parent category path, slash-separated - in: query - name: parent_path - type: string - - description: Page number (1-based) - in: query - name: page - type: integer - - description: Page size - in: query - name: page_size - type: integer produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.WikiCategoryPathListResponse' + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolderListResponse' "400": description: Bad Request schema: $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' security: - Bearer: [] - summary: List wiki category paths + summary: List wiki folders + tags: + - Wiki + post: + consumes: + - application/json + description: Create a new (initially empty) directory node under parent_id + parameters: + - description: Knowledge base ID + in: path + name: kb_id + required: true + type: string + - description: Folder data + in: body + name: folder + required: true + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolderCreateRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolder' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + "409": + description: Conflict + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + security: + - Bearer: [] + summary: Create a wiki folder + tags: + - Wiki + /knowledgebase/{kb_id}/wiki/folders/{folder_id}: + delete: + description: Delete a folder that has no pages and no child folders + parameters: + - description: Knowledge base ID + in: path + name: kb_id + required: true + type: string + - description: Folder ID + in: path + name: folder_id + required: true + type: string + responses: + "204": + description: No Content + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + "404": + description: Not Found + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + security: + - Bearer: [] + summary: Delete an empty wiki folder + tags: + - Wiki + put: + consumes: + - application/json + description: Rename and/or reparent a folder; the whole subtree's paths and + the affected pages' cached paths are recomputed + parameters: + - description: Knowledge base ID + in: path + name: kb_id + required: true + type: string + - description: Folder ID + in: path + name: folder_id + required: true + type: string + - description: Folder update + in: body + name: folder + required: true + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolderUpdateRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.WikiFolder' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + "404": + description: Not Found + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + "409": + description: Conflict + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + security: + - Bearer: [] + summary: Rename or move a wiki folder tags: - Wiki /knowledgebase/{kb_id}/wiki/graph: @@ -9153,6 +9359,40 @@ paths: summary: Get wiki operation log tags: - Wiki + /knowledgebase/{kb_id}/wiki/move-page: + put: + consumes: + - application/json + description: Relocate a page (identified by slug in the body) into a folder + (folder_id empty = root); the page's cached category path is recomputed + parameters: + - description: Knowledge base ID + in: path + name: kb_id + required: true + type: string + - description: Move target + in: body + name: move + required: true + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.WikiPageMoveRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.WikiPage' + "404": + description: Not Found + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + security: + - Bearer: [] + summary: Move a wiki page into a folder + tags: + - Wiki /knowledgebase/{kb_id}/wiki/pages: get: description: List wiki pages with optional filtering and pagination diff --git a/internal/handler/knowledgebase.go b/internal/handler/knowledgebase.go index f3dd7228..4f0ad18d 100644 --- a/internal/handler/knowledgebase.go +++ b/internal/handler/knowledgebase.go @@ -271,7 +271,7 @@ func (h *KnowledgeBaseHandler) resolveKBStoreView( // HybridSearch godoc // @Summary 混合搜索 -// @Description 在知识库中执行向量和关键词混合搜索 +// @Description 在知识库中执行向量和关键词混合搜索。推荐使用 POST;GET 携带 JSON 请求体仍受支持(兼容旧客户端)。 // @Tags 知识库 // @Accept json // @Produce json @@ -281,6 +281,7 @@ func (h *KnowledgeBaseHandler) resolveKBStoreView( // @Failure 400 {object} errors.AppError "请求参数错误" // @Security Bearer // @Security ApiKeyAuth +// @Router /knowledge-bases/{id}/hybrid-search [post] // @Router /knowledge-bases/{id}/hybrid-search [get] func (h *KnowledgeBaseHandler) HybridSearch(c *gin.Context) { ctx := c.Request.Context() diff --git a/internal/router/router.go b/internal/router/router.go index bb9790db..25d783ab 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -393,6 +393,8 @@ func RegisterKnowledgeBaseRoutes(r *gin.RouterGroup, handler *handler.KnowledgeB // so callers can't poke at KBs they can't see. kb.PUT("/:id/pin", g.Viewer(), g.KBAccessRead("id"), handler.TogglePinKnowledgeBase) // 混合搜索 — Viewer+ 且对 KB 有 read 权限 (read-only) + // POST is preferred; GET with JSON body is kept for backward compatibility (#1727). + kb.POST("/:id/hybrid-search", g.Viewer(), g.KBAccessRead("id"), handler.HybridSearch) kb.GET("/:id/hybrid-search", g.Viewer(), g.KBAccessRead("id"), handler.HybridSearch) // 拷贝知识库 — Contributor+ (副本归调用者所有;不需要原 KB 的所有权) kb.POST("/copy", g.Contributor(), handler.CopyKnowledgeBase) diff --git a/mcp-server/weknora_mcp_server.py b/mcp-server/weknora_mcp_server.py index 49904b8e..92240aa2 100644 --- a/mcp-server/weknora_mcp_server.py +++ b/mcp-server/weknora_mcp_server.py @@ -193,7 +193,7 @@ class WeKnoraClient: **config, # Include thresholds and match count } return self._request( - "GET", f"/knowledge-bases/{kb_id}/hybrid-search", json=data + "POST", f"/knowledge-bases/{kb_id}/hybrid-search", json=data ) # Knowledge Management - Methods for creating and managing knowledge entries