2024-10-02 20:11:28 +08:00
|
|
|
import os
|
2025-03-06 23:55:20 +08:00
|
|
|
import asyncio
|
2025-03-17 19:58:00 +08:00
|
|
|
import traceback
|
2024-10-02 20:11:28 +08:00
|
|
|
from typing import List, Optional
|
|
|
|
|
from fastapi import APIRouter, File, UploadFile, HTTPException, Depends, Body
|
|
|
|
|
|
2025-02-27 19:35:25 +08:00
|
|
|
from src.utils import logger, hashstr
|
2025-03-16 01:11:13 +08:00
|
|
|
from src import executor, dbm, retriever, config
|
2024-10-02 20:11:28 +08:00
|
|
|
|
|
|
|
|
data = APIRouter(prefix="/data")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@data.get("/")
|
2025-03-06 23:55:20 +08:00
|
|
|
async def get_databases():
|
2024-10-02 20:11:28 +08:00
|
|
|
try:
|
2025-03-16 01:11:13 +08:00
|
|
|
database = dbm.get_databases()
|
2024-10-02 20:11:28 +08:00
|
|
|
except Exception as e:
|
|
|
|
|
return {"message": f"获取数据库列表失败 {e}", "databases": []}
|
|
|
|
|
return database
|
|
|
|
|
|
|
|
|
|
@data.post("/")
|
|
|
|
|
async def create_database(
|
|
|
|
|
database_name: str = Body(...),
|
|
|
|
|
description: str = Body(...),
|
|
|
|
|
db_type: str = Body(...),
|
2024-10-08 22:16:17 +08:00
|
|
|
dimension: Optional[int] = Body(None)
|
2024-10-02 20:11:28 +08:00
|
|
|
):
|
|
|
|
|
logger.debug(f"Create database {database_name}")
|
2025-03-16 01:11:13 +08:00
|
|
|
database_info = dbm.create_database(
|
2024-10-02 20:11:28 +08:00
|
|
|
database_name,
|
|
|
|
|
description,
|
|
|
|
|
db_type,
|
|
|
|
|
dimension=dimension
|
|
|
|
|
)
|
|
|
|
|
return database_info
|
|
|
|
|
|
|
|
|
|
@data.delete("/")
|
2024-10-14 16:51:20 +08:00
|
|
|
async def delete_database(db_id):
|
2024-10-02 20:11:28 +08:00
|
|
|
logger.debug(f"Delete database {db_id}")
|
2025-03-16 01:11:13 +08:00
|
|
|
dbm.delete_database(db_id)
|
2024-10-02 20:11:28 +08:00
|
|
|
return {"message": "删除成功"}
|
|
|
|
|
|
|
|
|
|
@data.post("/query-test")
|
|
|
|
|
async def query_test(query: str = Body(...), meta: dict = Body(...)):
|
|
|
|
|
logger.debug(f"Query test in {meta}: {query}")
|
2025-03-16 01:11:13 +08:00
|
|
|
result = retriever.query_knowledgebase(query, history=None, refs={"meta": meta})
|
2024-10-02 20:11:28 +08:00
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
@data.post("/add-by-file")
|
|
|
|
|
async def create_document_by_file(db_id: str = Body(...), files: List[str] = Body(...)):
|
|
|
|
|
logger.debug(f"Add document in {db_id} by file: {files}")
|
2025-03-06 23:55:20 +08:00
|
|
|
try:
|
|
|
|
|
# 使用线程池执行耗时操作
|
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
|
await loop.run_in_executor(
|
|
|
|
|
executor, # 使用与chat_router相同的线程池
|
2025-03-16 01:11:13 +08:00
|
|
|
lambda: dbm.add_files(db_id, files)
|
2025-03-06 23:55:20 +08:00
|
|
|
)
|
|
|
|
|
return {"message": "文件添加完成", "status": "success"}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"添加文件失败: {e}")
|
|
|
|
|
return {"message": f"添加文件失败: {e}", "status": "failed"}
|
2024-10-02 20:11:28 +08:00
|
|
|
|
2024-10-08 22:16:17 +08:00
|
|
|
@data.get("/info")
|
2024-10-02 20:11:28 +08:00
|
|
|
async def get_database_info(db_id: str):
|
|
|
|
|
logger.debug(f"Get database {db_id} info")
|
2025-03-16 01:11:13 +08:00
|
|
|
database = dbm.get_database_info(db_id)
|
2024-10-02 20:11:28 +08:00
|
|
|
if database is None:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Database not found")
|
|
|
|
|
return database
|
|
|
|
|
|
|
|
|
|
@data.delete("/document")
|
|
|
|
|
async def delete_document(db_id: str = Body(...), file_id: str = Body(...)):
|
|
|
|
|
logger.debug(f"DELETE document {file_id} info in {db_id}")
|
2025-03-16 01:11:13 +08:00
|
|
|
dbm.delete_file(db_id, file_id)
|
2024-10-02 20:11:28 +08:00
|
|
|
return {"message": "删除成功"}
|
|
|
|
|
|
|
|
|
|
@data.get("/document")
|
|
|
|
|
async def get_document_info(db_id: str, file_id: str):
|
|
|
|
|
logger.debug(f"GET document {file_id} info in {db_id}")
|
2024-10-08 22:16:17 +08:00
|
|
|
|
|
|
|
|
try:
|
2025-03-16 01:11:13 +08:00
|
|
|
info = dbm.get_file_info(db_id, file_id)
|
2024-10-08 22:16:17 +08:00
|
|
|
except Exception as e:
|
2025-03-17 19:58:00 +08:00
|
|
|
logger.error(f"Failed to get file info, {e}, {db_id=}, {file_id=}, {traceback.format_exc()}")
|
2024-10-08 22:16:17 +08:00
|
|
|
info = {"message": "Failed to get file info", "status": "failed"}, 500
|
|
|
|
|
|
2024-10-02 20:11:28 +08:00
|
|
|
return info
|
|
|
|
|
|
|
|
|
|
@data.post("/upload")
|
|
|
|
|
async def upload_file(file: UploadFile = File(...)):
|
|
|
|
|
if not file.filename:
|
|
|
|
|
raise HTTPException(status_code=400, detail="No selected file")
|
|
|
|
|
|
2025-03-16 01:11:13 +08:00
|
|
|
upload_dir = os.path.join(config.save_dir, "data/uploads")
|
2024-10-02 20:11:28 +08:00
|
|
|
os.makedirs(upload_dir, exist_ok=True)
|
2024-10-23 16:24:29 +08:00
|
|
|
basename, ext = os.path.splitext(file.filename)
|
2025-03-06 23:55:20 +08:00
|
|
|
filename = f"{basename}_{hashstr(basename, 4, with_salt=True)}{ext}".lower()
|
2024-10-02 20:11:28 +08:00
|
|
|
file_path = os.path.join(upload_dir, filename)
|
|
|
|
|
|
|
|
|
|
with open(file_path, "wb") as buffer:
|
|
|
|
|
buffer.write(await file.read())
|
|
|
|
|
|
|
|
|
|
return {"message": "File successfully uploaded", "file_path": file_path}
|
|
|
|
|
|
|
|
|
|
@data.get("/graph")
|
|
|
|
|
async def get_graph_info():
|
2025-03-16 01:11:13 +08:00
|
|
|
graph_info = dbm.get_graph()
|
2025-03-08 22:49:13 +08:00
|
|
|
|
|
|
|
|
# 获取未索引节点数量
|
|
|
|
|
unindexed_count = 0
|
2025-03-16 01:11:13 +08:00
|
|
|
if dbm.is_graph_running():
|
2025-03-08 22:49:13 +08:00
|
|
|
# 调用GraphDatabase的query_nodes_without_embedding方法
|
2025-03-16 01:11:13 +08:00
|
|
|
unindexed_nodes = dbm.graph_base.query_nodes_without_embedding()
|
2025-03-08 22:49:13 +08:00
|
|
|
unindexed_count = len(unindexed_nodes) if unindexed_nodes else 0
|
|
|
|
|
|
|
|
|
|
# 将未索引节点数量添加到返回结果中
|
|
|
|
|
graph_info["graph"]["unindexed_node_count"] = unindexed_count
|
|
|
|
|
|
2024-10-02 20:11:28 +08:00
|
|
|
return graph_info
|
|
|
|
|
|
2025-03-08 22:49:13 +08:00
|
|
|
@data.post("/graph/index-nodes")
|
|
|
|
|
async def index_nodes(data: dict = Body(default={})):
|
2025-03-16 01:11:13 +08:00
|
|
|
if not dbm.is_graph_running():
|
2025-03-08 22:49:13 +08:00
|
|
|
raise HTTPException(status_code=400, detail="图数据库未启动")
|
|
|
|
|
|
|
|
|
|
# 获取参数或使用默认值
|
|
|
|
|
kgdb_name = data.get('kgdb_name', 'neo4j')
|
|
|
|
|
|
|
|
|
|
# 调用GraphDatabase的add_embedding_to_nodes方法
|
2025-03-16 01:11:13 +08:00
|
|
|
count = dbm.graph_base.add_embedding_to_nodes(kgdb_name=kgdb_name)
|
2025-03-08 22:49:13 +08:00
|
|
|
|
|
|
|
|
return {"status": "success", "message": f"已成功为{count}个节点添加嵌入向量", "indexed_count": count}
|
|
|
|
|
|
2024-10-02 20:11:28 +08:00
|
|
|
@data.get("/graph/node")
|
|
|
|
|
async def get_graph_node(entity_name: str):
|
2025-03-16 01:11:13 +08:00
|
|
|
result = dbm.graph_base.query_node(entity_name=entity_name)
|
|
|
|
|
return {"result": retriever.format_query_results(result), "message": "success"}
|
2024-10-02 20:11:28 +08:00
|
|
|
|
|
|
|
|
@data.get("/graph/nodes")
|
|
|
|
|
async def get_graph_nodes(kgdb_name: str, num: int):
|
2025-03-16 01:11:13 +08:00
|
|
|
if not config.enable_knowledge_graph:
|
2024-10-02 20:11:28 +08:00
|
|
|
raise HTTPException(status_code=400, detail="Knowledge graph is not enabled")
|
|
|
|
|
|
|
|
|
|
logger.debug(f"Get graph nodes in {kgdb_name} with {num} nodes")
|
2025-03-16 01:11:13 +08:00
|
|
|
result = dbm.graph_base.get_sample_nodes(kgdb_name, num)
|
|
|
|
|
return {"result": retriever.format_general_results(result), "message": "success"}
|
2024-10-02 20:11:28 +08:00
|
|
|
|
2025-02-28 00:27:58 +08:00
|
|
|
@data.post("/graph/add-by-jsonl")
|
2024-10-14 16:51:20 +08:00
|
|
|
async def add_graph_entity(file_path: str = Body(...), kgdb_name: Optional[str] = Body(None)):
|
2025-03-16 01:11:13 +08:00
|
|
|
if not config.enable_knowledge_graph:
|
2024-10-02 20:11:28 +08:00
|
|
|
raise HTTPException(status_code=400, detail="Knowledge graph is not enabled")
|
|
|
|
|
|
|
|
|
|
if not file_path.endswith('.jsonl'):
|
|
|
|
|
raise HTTPException(status_code=400, detail="file_path must be a jsonl file")
|
|
|
|
|
|
2025-03-16 01:11:13 +08:00
|
|
|
dbm.graph_base.jsonl_file_add_entity(file_path, kgdb_name)
|
2024-10-02 20:11:28 +08:00
|
|
|
return {"message": "Entity successfully added"}
|
|
|
|
|
|