Refine knownledge compilation REST APIs (#17624)

This commit is contained in:
Wang Qi
2026-07-31 17:05:57 +08:00
committed by GitHub
parent 03b7b04e2c
commit 9db2e2314a
8 changed files with 76 additions and 93 deletions

View File

@@ -25,7 +25,7 @@ from api.utils.api_utils import get_json_result, server_error_response
_validate_template_payload = validate_template_payload
@manager.route("/compilation_templates/builtins", methods=["GET"]) # noqa: F821
@manager.route("/compilation-templates/builtins", methods=["GET"]) # noqa: F821
@login_required
def list_builtin_templates() -> Response:
"""Built-in template palette — used as the per-child pre-fill in the
@@ -54,7 +54,7 @@ def list_builtin_templates() -> Response:
return server_error_response(exc)
@manager.route("/compilation_templates/wiki_presets", methods=["GET"]) # noqa: F821
@manager.route("/compilation-templates/wiki-presets", methods=["GET"]) # noqa: F821
@login_required
def list_wiki_presets() -> Response:
"""Wiki page-structure presets loaded from

View File

@@ -66,7 +66,7 @@ def _validate_group_payload(req: dict, require_all: bool = True) -> str:
return ""
@manager.route("/compilation_template_groups", methods=["GET"]) # noqa: F821
@manager.route("/compilation-template-groups", methods=["GET"]) # noqa: F821
@login_required
def list_groups() -> Response:
keywords = request.args.get("keywords", "")
@@ -86,7 +86,7 @@ def list_groups() -> Response:
return server_error_response(exc)
@manager.route("/compilation_template_groups/<group_id>", methods=["GET"]) # noqa: F821
@manager.route("/compilation-template-groups/<group_id>", methods=["GET"]) # noqa: F821
@login_required
def detail(group_id: str) -> Response:
try:
@@ -98,7 +98,7 @@ def detail(group_id: str) -> Response:
return server_error_response(exc)
@manager.route("/compilation_template_groups", methods=["POST"]) # noqa: F821
@manager.route("/compilation-template-groups", methods=["POST"]) # noqa: F821
@login_required
@validate_request("name", "templates")
async def create() -> Response:
@@ -125,7 +125,7 @@ async def create() -> Response:
return server_error_response(exc)
@manager.route("/compilation_template_groups/<group_id>", methods=["PUT"]) # noqa: F821
@manager.route("/compilation-template-groups/<group_id>", methods=["PUT"]) # noqa: F821
@login_required
async def update(group_id: str) -> Response:
req = await get_request_json()
@@ -160,7 +160,7 @@ async def update(group_id: str) -> Response:
return server_error_response(exc)
@manager.route("/compilation_template_groups/<group_id>", methods=["DELETE"]) # noqa: F821
@manager.route("/compilation-template-groups/<group_id>", methods=["DELETE"]) # noqa: F821
@login_required
def delete(group_id: str) -> Response:
try:

View File

@@ -16,7 +16,7 @@
import logging
from peewee import OperationalError
from quart import request
from quart import request, make_response
from common.constants import RetCode
from api.apps import login_required, current_user
from api.utils.api_utils import get_error_argument_result, get_error_data_result, get_json_result, get_result, add_tenant_id_to_kwargs
@@ -578,22 +578,19 @@ async def get_knowledge_graph(tenant_id, dataset_id):
return get_error_data_result(message="Internal server error")
@manager.route("/datasets/<dataset_id>/any_artifact", methods=["GET"]) # noqa: F821
@manager.route("/datasets/<dataset_id>/artifacts", methods=["HEAD"]) # noqa: F821
@login_required
@add_tenant_id_to_kwargs
async def has_any_wiki(tenant_id, dataset_id):
"""Probe whether this dataset has any compiled artifact pages.
GET /api/v1/datasets/<dataset_id>/any_artifact
Success: {"code": 0, "data": {"has": bool}}
The frontend uses this to decide whether to surface the Artifact tab
in the dataset sidebar.
"""
try:
success, result = await dataset_api_service.has_any_wiki(dataset_id, tenant_id)
if success:
return get_result(data=result)
return get_result(data=False, message=result, code=RetCode.AUTHENTICATION_ERROR)
response = await make_response("", 200 if success and result["has"] else 404)
return response
except Exception as e:
logging.exception(e)
return get_error_data_result(message="Internal server error")
@@ -633,13 +630,13 @@ async def list_wiki_pages(tenant_id, dataset_id):
return get_error_data_result(message="Internal server error")
@manager.route("/datasets/<dataset_id>/artifacts_topics", methods=["GET"]) # noqa: F821
@manager.route("/datasets/<dataset_id>/artifacts/topics", methods=["GET"]) # noqa: F821
@login_required
@add_tenant_id_to_kwargs
async def list_wiki_topics(tenant_id, dataset_id):
"""List wiki topics for the dataset Artifact tab.
GET /api/v1/datasets/<dataset_id>/artifacts_topics?page=1&page_size=200
GET /api/v1/datasets/<dataset_id>/artifacts/topics?page=1&page_size=200
Success: {"code": 0, "data": {"total": int, "items": [{topic, title, slug}]}}
"""
try:
@@ -708,13 +705,13 @@ async def get_wiki_graph(tenant_id, dataset_id):
return get_error_data_result(message="Internal server error")
@manager.route("/datasets/<dataset_id>/artifacts_structure", methods=["GET"]) # noqa: F821
@manager.route("/datasets/<dataset_id>/artifacts/structure", methods=["GET"]) # noqa: F821
@login_required
@add_tenant_id_to_kwargs
async def get_dataset_structure(tenant_id, dataset_id):
"""Return the dataset-scope (KB-wide) structure graph for one kind.
GET /api/v1/datasets/<dataset_id>/artifacts_structure?kind=<kind>
GET /api/v1/datasets/<dataset_id>/artifacts/structure?kind=<kind>
where ``kind`` is one of:
graph | mindmap | timeline | session_essence | session_graph
@@ -755,13 +752,13 @@ async def get_dataset_structure(tenant_id, dataset_id):
return get_error_data_result(message="Internal server error")
@manager.route("/datasets/<dataset_id>/artifacts_structure", methods=["DELETE"]) # noqa: F821
@manager.route("/datasets/<dataset_id>/artifacts/structure", methods=["DELETE"]) # noqa: F821
@login_required
@add_tenant_id_to_kwargs
def delete_dataset_structure(tenant_id, dataset_id):
"""Delete the dataset-scope (KB-wide) structure graph for one kind.
DELETE /api/v1/datasets/<dataset_id>/artifacts_structure?kind=<kind>
DELETE /api/v1/datasets/<dataset_id>/artifacts/structure?kind=<kind>
Optional query param: wipe=false cancels the task without deleting stored rows.
"""
kind = request.args.get("kind", "")
@@ -874,20 +871,15 @@ async def get_wiki_page(tenant_id, dataset_id, page_type, slug):
return get_error_data_result(message="Internal server error")
@manager.route("/datasets/<dataset_id>/any_skill", methods=["GET"]) # noqa: F821
@manager.route("/datasets/<dataset_id>/skills", methods=["HEAD"]) # noqa: F821
@login_required
@add_tenant_id_to_kwargs
async def has_any_skill(tenant_id, dataset_id):
"""Probe whether this dataset has a compiled Corpus2Skill tree.
GET /api/v1/datasets/<dataset_id>/any_skill
Success: {"code": 0, "data": {"has": bool}}
"""
"""Probe whether this dataset has a compiled Corpus2Skill tree."""
try:
success, result = await dataset_api_service.has_any_skill(dataset_id, tenant_id)
if success:
return get_result(data=result)
return get_result(data=False, message=result, code=RetCode.AUTHENTICATION_ERROR)
response = await make_response("", 200 if success and result["has"] else 404)
return response
except Exception as e:
logging.exception(e)
return get_error_data_result(message="Internal server error")
@@ -960,13 +952,13 @@ async def get_skill_page(tenant_id, dataset_id, skill_kwd):
return get_error_data_result(message="Internal server error")
@manager.route("/datasets/<dataset_id>/nav", methods=["GET"]) # noqa: F821
@manager.route("/datasets/<dataset_id>/navigation", methods=["GET"]) # noqa: F821
@login_required
@add_tenant_id_to_kwargs
async def list_dataset_nav(tenant_id, dataset_id):
"""First level of the dataset navigation tree — the top-level clusters.
GET /api/v1/datasets/<dataset_id>/nav
GET /api/v1/datasets/<dataset_id>/navigation
Success: {"code": 0, "data": {"total": <n>, "items": [{name, description, doc_count, type, has_children}, ...]}}
"""
try:
@@ -982,13 +974,13 @@ async def list_dataset_nav(tenant_id, dataset_id):
return get_error_data_result(message="Internal server error")
@manager.route("/datasets/<dataset_id>/nav/<path:name>/children", methods=["GET"]) # noqa: F821
@manager.route("/datasets/<dataset_id>/navigation/<path:name>/children", methods=["GET"]) # noqa: F821
@login_required
@add_tenant_id_to_kwargs
async def list_dataset_nav_children(tenant_id, dataset_id, name):
"""Direct children of a navigation node (hierarchical, one level per call).
GET /api/v1/datasets/<dataset_id>/nav/<name>/children
GET /api/v1/datasets/<dataset_id>/navigation/<name>/children
Success: {"code": 0, "data": {"total": <n>, "items": [{name, description, doc_count, type, doc_id, has_children}, ...]}}
"""
try:
@@ -1005,13 +997,13 @@ async def list_dataset_nav_children(tenant_id, dataset_id, name):
return get_error_data_result(message="Internal server error")
@manager.route("/datasets/<dataset_id>/nav", methods=["DELETE"]) # noqa: F821
@manager.route("/datasets/<dataset_id>/navigation", methods=["DELETE"]) # noqa: F821
@login_required
@add_tenant_id_to_kwargs
async def delete_dataset_nav(tenant_id, dataset_id):
"""Delete the entire dataset navigation tree.
DELETE /api/v1/datasets/<dataset_id>/nav
DELETE /api/v1/datasets/<dataset_id>/navigation
Success: {"code": 0, "data": {"deleted": <n>}}
"""
try:
@@ -1027,13 +1019,13 @@ async def delete_dataset_nav(tenant_id, dataset_id):
return get_error_data_result(message="Internal server error")
@manager.route("/datasets/<dataset_id>/nav/<path:name>", methods=["DELETE"]) # noqa: F821
@manager.route("/datasets/<dataset_id>/navigation/<path:name>", methods=["DELETE"]) # noqa: F821
@login_required
@add_tenant_id_to_kwargs
async def delete_dataset_nav_node(tenant_id, dataset_id, name):
"""Delete one navigation node and its whole subtree.
DELETE /api/v1/datasets/<dataset_id>/nav/<name>
DELETE /api/v1/datasets/<dataset_id>/navigation/<name>
Success: {"code": 0, "data": {"deleted": <n>}}
"""
try:

View File

@@ -362,13 +362,12 @@ def _register_commit_routes(prefix, param_name, resolver_type=None):
# Register datasets first, workspace second, folders last —
# the last call's handlers overwrite module-level names for test access.
_register_commit_routes("/datasets/<entity_id>", "entity_id", resolver_type="datasets")
_register_commit_routes("/workspace/<entity_id>", "entity_id") # alias — workspace_id == folder_id
_register_commit_routes("/folders/<entity_id>", "entity_id") # direct — entity_id == folder_id (wins)
_register_commit_routes("/workspaces/<entity_id>", "entity_id")
# /memories and /skills routes are not mounted until resolvers are implemented.
# ── File version history (shared across all entity types) ─────────────────
@manager.route("/files/<file_id>/versions", methods=["GET"]) # noqa: F821
@manager.route("/workspace-files/<file_id>/versions", methods=["GET"]) # noqa: F821
@login_required
async def get_file_version_history(file_id):
try:

View File

@@ -17,7 +17,7 @@
"""Shared structure-graph subgraph sampling.
Both the per-document (``/datasets/<id>/documents/<doc>/structure/graph``) and
the dataset-wide (``/datasets/<id>/artifacts_structure``) endpoints render
the dataset-wide (``/datasets/<id>/artifacts/structure``) endpoints render
per-template structure graphs. For large graphs we don't return every
entity/relation — we fetch a representative subgraph from the raw
``knowledge_graph_kwd`` rows (which carry ``mention_count_int`` / ``name_kwd`` /

View File

@@ -6955,7 +6955,7 @@ curl --request POST \
"data": [
{
"id": "b330ec2e91ec11efbc510242ac120004",
"name": "test1.txt",
"name": "test1.txt",
"size": 17966,
"type": "doc",
"parent_id": "527fa74891e811ef9c650242ac120006",
@@ -7168,7 +7168,7 @@ curl --request POST \
Success:
```json
{
{
"code": 0,
"data": {
"id": "b330ec2e91ec11efbc510242ac120004",
@@ -7709,17 +7709,16 @@ or
##### Request example
```bash
curl --request POST \
curl --request POST \
--url http://{address}/api/v1/workspaces/{workspace_id}/commits \
--header 'Content-Type: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <YOUR_API_KEY>' \
--header 'Authorization: Bearer <YOUR_API_KEY>' \
--data '{
"message": "update config files",
"files": [
{"file_id": "file_uuid", "file_name": "config.json", "operation": "modify", "content": "{\"key\": \"value\"}"},
{"file_id": "file_uuid", "file_name": "readme.md", "operation": "add", "content": "# New README"}
{"file_id": "file_uuid", "file_name": "readme.md", "operation": "add", "content": "# New README"}
]
}'
```
@@ -7732,7 +7731,7 @@ This endpoint also supports:
Each file change object supports the following fields:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
|-------|------|----------|-------------|
| `file_id` | `string` | Yes | The file ID |
| `file_name` | `string` | Only for add/rename | The file name |
| `operation` | `string` | Yes | `"add"`, `"modify"`, `"delete"`, or `"rename"` |
@@ -7797,17 +7796,16 @@ Failure:
##### Request example
```bash
curl --request GET \
--url 'http://{address}/api/v1/workspaces/{workspace_id}/commits?page=1&page_size=15' \
--header 'Authorization: Bearer <YOUR_API_KEY>'
--header 'Authorization: Bearer <YOUR_API_KEY>'
```
##### Request parameters
- `"page"`: (*Query parameter*), `int`, *Optional*
Page number. Defaults to 1.
Page number. Defaults to 1.
- `"page_size"`: (*Query parameter*), `int`, *Optional*
Number of items per page. Defaults to 15.
- `"order_by"`: (*Query parameter*), `string`, *Optional*
@@ -7820,7 +7818,7 @@ Also available at:
Success:
```json
{
{
"code": 0,
"data": {
"total": 2,
@@ -7865,17 +7863,16 @@ Success:
--url http://{address}/api/v1/workspaces/{workspace_id}/commits/{commit_id} \
--header 'Authorization: Bearer <YOUR_API_KEY>'
```
```
##### Request parameters
- `"folder_id"`: (*Path parameter*), `string`, *Required*
- `"folder_id"`: (*Path parameter*), `string`, *Required*
The folder ID.
- `"commit_id"`: (*Path parameter*), `string`, *Required*
The commit ID.
#### Response
Success:
```json
@@ -7883,7 +7880,7 @@ Also available at:
"code": 0,
"data": {
"id": "commit_uuid",
"folder_id": "folder_uuid",
"folder_id": "folder_uuid",
"parent_id": null,
"message": "added config files",
"author_id": "user_uuid",
@@ -7936,17 +7933,16 @@ Failure:
--url http://{address}/api/v1/workspaces/{workspace_id}/commits/{commit_id}/files \
--header 'Authorization: Bearer <YOUR_API_KEY>'
```
```
#### Response
Success:
Success:
```json
{
"code": 0,
"data": [
{
{
"id": "item_uuid",
"file_id": "file_uuid",
"operation": "add",
@@ -7954,7 +7950,7 @@ Also available at:
"new_hash": "abcd1234",
"old_location": null,
"new_location": ".objects/abcd1234",
"old_name": null,
"old_name": null,
"new_name": null
}
]
@@ -7985,17 +7981,16 @@ Success:
##### Request example
```bash
curl --request GET \
--url 'http://{address}/api/v1/workspaces/{workspace_id}/commits/diff?from=from_commit_id&to=to_commit_id' \
--header 'Authorization: Bearer <YOUR_API_KEY>'
--header 'Authorization: Bearer <YOUR_API_KEY>'
```
##### Request parameters
- `"from"`: (*Query parameter*), `string`, *Required*
The source commit ID.
The source commit ID.
- `"to"`: (*Query parameter*), `string`, *Required*
The target commit ID.
@@ -8008,7 +8003,7 @@ Also available at:
"code": 0,
"data": [
{
"file_id": "file_uuid",
"file_id": "file_uuid",
"file_name": "config.json",
"operation": "modify",
"old_hash": "abc123",
@@ -8053,17 +8048,16 @@ Failure:
--url http://{address}/api/v1/workspaces/{workspace_id}/changes \
--header 'Authorization: Bearer <YOUR_API_KEY>'
```
```
#### Response
Success:
Success:
```json
{
"code": 0,
"data": [
{
{
"file_id": "file_uuid",
"file_name": "new.txt",
"operation": "add"
@@ -8071,7 +8065,7 @@ Also available at:
{
"file_id": "file_uuid",
"file_name": "config.json",
"operation": "modify"
"operation": "modify"
},
{
"file_id": "file_uuid",
@@ -8106,17 +8100,16 @@ Success:
--url http://{address}/api/v1/workspaces/{workspace_id}/commits/{commit_id}/tree \
--header 'Authorization: Bearer <YOUR_API_KEY>'
```
```
#### Response
Success:
Success:
```json
{
"code": 0,
"data": {
"id": "folder_uuid",
"id": "folder_uuid",
"name": "workspace_name",
"type": "folder",
"children": [
@@ -8124,7 +8117,7 @@ Also available at:
"id": "file_uuid",
"name": "config.json",
"type": "file",
"hash": "abcd1234",
"hash": "abcd1234",
"size": 1024,
"status": "1",
"location": ".objects/abcd1234"
@@ -8174,17 +8167,16 @@ Success:
--url http://{address}/api/v1/workspaces/{workspace_id}/commits/{commit_id}/files/{file_id}/content \
--header 'Authorization: Bearer <YOUR_API_KEY>'
```
```
#### Response
Success:
Success:
```json
{
"code": 0,
"data": {
"content": "file content as it existed in that commit"
"content": "file content as it existed in that commit"
}
}
```
@@ -8192,7 +8184,7 @@ Also available at:
Failure:
```json
{
{
"code": 102,
"message": "File not found in this commit"
}
@@ -8220,16 +8212,16 @@ Failure:
--url http://{address}/api/v1/workspace-files/{file_id}/versions \
--header 'Authorization: Bearer <YOUR_API_KEY>'
```
#### Response
Success:
```json
{
"code": 0,
"data": [
{
{
"commit_id": "commit_uuid",
"operation": "modify",
"hash": "def456",
@@ -8237,7 +8229,7 @@ Returns the version history for a specific file across all commits.
"message": "updated file"
},
{
"commit_id": "commit_uuid",
"commit_id": "commit_uuid",
"operation": "add",
"hash": "abc123",
"create_time": 1718100000000,

View File

@@ -743,7 +743,7 @@ def test_get_file_version_history(monkeypatch):
@pytest.mark.p2
def test_workspace_alias(monkeypatch):
"""Verify /workspace/ alias routes work the same as /folders/."""
"""Verify /workspaces/ alias routes work the same as /folders/."""
module = _load_module(monkeypatch)
FileTestModel.create(id="f1", parent_id="ws-folder", tenant_id="t1", created_by="test-user", name="a.txt", type="txt")

View File

@@ -162,7 +162,7 @@ export default {
artifactsAlteration: (datasetId: string) =>
`${restAPIv1}/datasets/${datasetId}/artifacts/alteration`,
artifactsTopicList: (datasetId: string) =>
`${restAPIv1}/datasets/${datasetId}/artifacts_topics`,
`${restAPIv1}/datasets/${datasetId}/artifacts/topics`,
getArtifactPage: (datasetId: string, pageType: string, slug: string) =>
`${restAPIv1}/datasets/${datasetId}/artifacts/${pageType}/${slug}`,
listWikiCommits: (datasetId: string) =>
@@ -172,7 +172,7 @@ export default {
getArtifactGraph: (datasetId: string) =>
`${restAPIv1}/datasets/${datasetId}/artifacts/graph`,
artifactsStructure: (datasetId: string) =>
`${restAPIv1}/datasets/${datasetId}/artifacts_structure`,
`${restAPIv1}/datasets/${datasetId}/artifacts/structure`,
clearWiki: (datasetId: string) =>
`${restAPIv1}/datasets/${datasetId}/artifacts`,
getDatasetSkillTree: (datasetId: string) =>
@@ -190,16 +190,16 @@ export default {
.map((s) => encodeURIComponent(s))
.join('/')}`,
getDatasetNav: (datasetId: string) =>
`${restAPIv1}/datasets/${datasetId}/nav`,
`${restAPIv1}/datasets/${datasetId}/navigation`,
getDatasetNavChildren: (datasetId: string, name: string) =>
`${restAPIv1}/datasets/${datasetId}/nav/${name
`${restAPIv1}/datasets/${datasetId}/navigation/${name
.split('/')
.map((s) => encodeURIComponent(s))
.join('/')}/children`,
deleteDatasetNav: (datasetId: string) =>
`${restAPIv1}/datasets/${datasetId}/nav`,
`${restAPIv1}/datasets/${datasetId}/navigation`,
deleteDatasetNavNode: (datasetId: string, name: string) =>
`${restAPIv1}/datasets/${datasetId}/nav/${name
`${restAPIv1}/datasets/${datasetId}/navigation/${name
.split('/')
.map((s) => encodeURIComponent(s))
.join('/')}`,
@@ -383,13 +383,13 @@ export default {
// explore
// compilation templates
compilationTemplates: `${restAPIv1}/compilation_templates`,
compilationTemplates: `${restAPIv1}/compilation-templates`,
compilationTemplate: (id: string) =>
`${restAPIv1}/compilation_templates/${id}`,
compilationTemplateGroups: `${restAPIv1}/compilation_template_groups`,
`${restAPIv1}/compilation-templates/${id}`,
compilationTemplateGroups: `${restAPIv1}/compilation-template-groups`,
compilationTemplateGroup: (id: string) =>
`${restAPIv1}/compilation_template_groups/${id}`,
wikiPresets: `${restAPIv1}/compilation_templates/wiki_presets`,
`${restAPIv1}/compilation-template-groups/${id}`,
wikiPresets: `${restAPIv1}/compilation-templates/wiki-presets`,
// mcp server
listMcpServer: `${restAPIv1}/mcp/servers`,