feat(server): 扩展知识库服务,添加knowledge API端点和schema定义,前端新增knowledge服务模块

This commit is contained in:
caoxiaozhu
2026-05-15 06:56:17 +00:00
parent 7209c75ad8
commit 4b1dae7ebc
38 changed files with 774 additions and 8012 deletions

View File

@@ -1,21 +1,33 @@
from __future__ import annotations
from datetime import UTC, datetime
from typing import Annotated
from fastapi import APIRouter, Body, Depends, HTTPException, Query, status
from fastapi.responses import FileResponse
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.api.deps import CurrentUserContext, get_current_user, require_admin_user
from app.api.deps import CurrentUserContext, get_current_user, get_db, require_admin_user
from app.core.agent_enums import AgentName, AgentPermissionLevel, AgentRunSource, AgentRunStatus
from app.models.agent_asset import AgentAsset
from app.schemas.common import ErrorResponse
from app.schemas.knowledge import (
KnowledgeActionResponse,
KnowledgeDocumentDetailRead,
KnowledgeLibraryRead,
LlmWikiDocumentDetailRead,
LlmWikiIndexRead,
LlmWikiSummaryUpdateWrite,
KnowledgeOnlyOfficeCallbackRead,
KnowledgeOnlyOfficeCallbackWrite,
KnowledgeOnlyOfficeConfigRead,
LlmWikiSyncRead,
LlmWikiSyncWrite,
)
from app.services.agent_runs import AgentRunService
from app.services.knowledge import KnowledgeService
from app.services.llm_wiki import LlmWikiService
router = APIRouter(prefix="/knowledge")
@@ -38,6 +50,176 @@ def get_knowledge_library(
return KnowledgeService().list_library()
@router.get(
"/llm-wiki",
response_model=LlmWikiIndexRead,
summary="查询 LLM Wiki 索引",
description="返回知识库解析目录中的文档索引和同步次数,仅供管理员查看知识候选与规则候选草稿。",
responses={
status.HTTP_401_UNAUTHORIZED: {
"model": ErrorResponse,
"description": "未提供知识库访问用户头。",
},
status.HTTP_403_FORBIDDEN: {
"model": ErrorResponse,
"description": "只有管理员可以查看 LLM Wiki 草稿内容。",
},
},
)
def get_llm_wiki_index(
_: Annotated[CurrentUserContext, Depends(require_admin_user)],
db: Annotated[Session, Depends(get_db)],
) -> LlmWikiIndexRead:
return LlmWikiService(db).get_index()
@router.get(
"/llm-wiki/documents/{document_id}",
response_model=LlmWikiDocumentDetailRead,
summary="读取 LLM Wiki 文档解析结果",
description="返回指定知识文档的解析文本、分块、知识候选与规则候选,仅供管理员查看。",
responses={
status.HTTP_401_UNAUTHORIZED: {
"model": ErrorResponse,
"description": "未提供知识库访问用户头。",
},
status.HTTP_403_FORBIDDEN: {
"model": ErrorResponse,
"description": "只有管理员可以查看 LLM Wiki 草稿内容。",
},
status.HTTP_404_NOT_FOUND: {
"model": ErrorResponse,
"description": "指定文档尚未生成 LLM Wiki。",
},
},
)
def get_llm_wiki_document_detail(
document_id: str,
_: Annotated[CurrentUserContext, Depends(require_admin_user)],
db: Annotated[Session, Depends(get_db)],
) -> LlmWikiDocumentDetailRead:
try:
return LlmWikiService(db).get_document_detail(document_id)
except FileNotFoundError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="指定文档尚未生成 LLM Wiki。") from exc
@router.patch(
"/llm-wiki/documents/{document_id}",
response_model=LlmWikiDocumentDetailRead,
summary="更新 LLM Wiki 知识总结",
description="管理员可修改指定知识文档的 LLM Wiki 知识总结预览,不直接改动原始文件。",
responses={
status.HTTP_401_UNAUTHORIZED: {
"model": ErrorResponse,
"description": "未提供知识库访问用户头。",
},
status.HTTP_403_FORBIDDEN: {
"model": ErrorResponse,
"description": "只有管理员可以修改 LLM Wiki 草稿内容。",
},
status.HTTP_404_NOT_FOUND: {
"model": ErrorResponse,
"description": "指定文档尚未生成 LLM Wiki。",
},
},
)
def update_llm_wiki_document_summary(
document_id: str,
payload: LlmWikiSummaryUpdateWrite,
_: Annotated[CurrentUserContext, Depends(require_admin_user)],
db: Annotated[Session, Depends(get_db)],
) -> LlmWikiDocumentDetailRead:
try:
return LlmWikiService(db).update_document_summary(document_id, payload)
except FileNotFoundError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="指定文档尚未生成 LLM Wiki。") from exc
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
@router.post(
"/llm-wiki/sync",
response_model=LlmWikiSyncRead,
summary="触发 Hermes 形成 LLM Wiki 与规则草稿",
description="按知识库文档变化情况增量触发系统 Hermes形成知识候选和规则草稿。",
responses={
status.HTTP_401_UNAUTHORIZED: {
"model": ErrorResponse,
"description": "未提供知识库访问用户头。",
},
status.HTTP_403_FORBIDDEN: {
"model": ErrorResponse,
"description": "只有管理员可以触发 LLM Wiki 同步。",
},
},
)
def sync_llm_wiki(
payload: LlmWikiSyncWrite,
current_user: Annotated[CurrentUserContext, Depends(require_admin_user)],
db: Annotated[Session, Depends(get_db)],
) -> LlmWikiSyncRead:
run_service = AgentRunService(db)
task_asset = db.scalar(
select(AgentAsset).where(AgentAsset.code == "task.hermes.llm_wiki_rule_formation")
)
run = run_service.create_run(
agent=AgentName.HERMES.value,
source=AgentRunSource.SCHEDULE.value,
user_id=current_user.username,
task_id=task_asset.id if task_asset is not None else None,
permission_level=AgentPermissionLevel.READ.value,
status=AgentRunStatus.RUNNING.value,
result_summary="Hermes 正在形成 LLM Wiki 与规则草稿。",
)
try:
result = LlmWikiService(db).sync_folder(
folder=payload.folder,
current_user=current_user,
document_ids=payload.document_ids,
force=payload.force,
)
run_service.record_tool_call(
run_id=run.run_id,
tool_type="llm",
tool_name="system_hermes_llm_wiki_sync",
request_json=payload.model_dump(),
response_json=result.model_dump(),
status="succeeded",
duration_ms=0,
)
run_service.update_run(
run.run_id,
status=AgentRunStatus.SUCCEEDED.value,
result_summary=result.summary,
finished_at=datetime.now(UTC),
)
return result
except Exception as exc:
run_service.record_tool_call(
run_id=run.run_id,
tool_type="llm",
tool_name="system_hermes_llm_wiki_sync",
request_json=payload.model_dump(),
response_json={"error": str(exc)},
status="failed",
duration_ms=0,
error_message=str(exc),
)
run_service.update_run(
run.run_id,
status=AgentRunStatus.FAILED.value,
error_message=str(exc),
finished_at=datetime.now(UTC),
)
if isinstance(exc, ValueError):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
if isinstance(exc, FileNotFoundError):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
@router.get(
"/documents/{document_id}",
response_model=KnowledgeDocumentDetailRead,