Skip to content

Commit 4410ed0

Browse files
committed
✨ Add user-level isolation when select knowledge base
1 parent 57981b6 commit 4410ed0

24 files changed

+762
-282
lines changed

backend/agents/create_agent_info.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from database.agent_db import search_agent_info_by_agent_id, search_tools_for_sub_agent, query_sub_agents, \
1010
query_or_create_main_agent_id
1111
from services.elasticsearch_service import ElasticSearchService
12+
from services.tenant_config_service import get_selected_knowledge_list
1213
from utils.config_utils import config_manager
1314
from utils.user_utils import get_user_info
1415

@@ -45,13 +46,19 @@ async def create_agent_config(agent_id, tenant_id, user_id):
4546
system_prompt = agent_info.get("prompt")
4647

4748
# special logic
48-
for tool in tool_list:
49-
if "KnowledgeBaseSearchTool" == tool.class_name:
50-
knowledge_base_summary = "\n\n### 本地知识库信息 ###\n"
51-
for knowledge_name in json.loads(config_manager.get_config("SELECTED_KB_NAMES", "[]")):
52-
message = ElasticSearchService().get_summary(index_name=knowledge_name)
53-
knowledge_base_summary += f"{knowledge_name}:{message['summary']}\n"
54-
system_prompt += knowledge_base_summary
49+
try:
50+
for tool in tool_list:
51+
if "KnowledgeBaseSearchTool" == tool.class_name:
52+
knowledge_base_summary = "\n\n### 本地知识库信息 ###\n"
53+
54+
knowledge_info_list = get_selected_knowledge_list(tenant_id=tenant_id, user_id=user_id)
55+
for knowledge_info in knowledge_info_list:
56+
knowledge_name = knowledge_info.get("index_name")
57+
message = ElasticSearchService().get_summary(index_name=knowledge_name)
58+
knowledge_base_summary += f"{knowledge_name}:{message['summary']}\n"
59+
system_prompt += knowledge_base_summary
60+
except Exception as e:
61+
logger.error(f"add knowledge base summary to system prompt failed, error: {e}")
5562

5663
agent_config = AgentConfig(
5764
name="" if agent_info["name"] is None else agent_info["name"],
@@ -82,7 +89,9 @@ async def create_tool_config_list(agent_id, tenant_id, user_id):
8289

8390
# special logic for knowledge base search tool
8491
if tool_config.class_name == "KnowledgeBaseSearchTool":
85-
tool_config.metadata = {"index_names": json.loads(config_manager.get_config("SELECTED_KB_NAMES", "[]"))}
92+
knowledge_info_list = get_selected_knowledge_list(tenant_id=tenant_id, user_id=user_id)
93+
index_names = [knowledge_info.get("index_name") for knowledge_info in knowledge_info_list]
94+
tool_config.metadata = {"index_names": index_names}
8695
tool_config_list.append(tool_config)
8796
return tool_config_list
8897

backend/apps/base_app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from .tool_config_app import router as tool_config_router
1717
from .prompt_app import router as prompt_router
1818
from .knowledge_summary_app import router as summary_router
19+
from .tenant_config_app import router as tenant_config_router
1920

2021
app = FastAPI(root_path="/api")
2122

@@ -40,6 +41,7 @@
4041
app.include_router(tool_config_router)
4142
app.include_router(summary_router)
4243
app.include_router(prompt_router)
44+
app.include_router(tenant_config_router)
4345

4446
# Global exception handler for HTTP exceptions
4547
@app.exception_handler(HTTPException)

backend/apps/config_sync_app.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,6 @@ async def save_config(config: GlobalConfig):
5252
env_key = f"{model_prefix}_DIMENSION"
5353
env_config[env_key] = safe_value(model_config.get("dimension"))
5454

55-
# Process knowledge base configuration - use key names directly without prefix, store lists in JSON format
56-
for key, value in config_dict.get("data", {}).items():
57-
env_key = get_env_key(key)
58-
if isinstance(value, list):
59-
env_config[env_key] = safe_list(value)
60-
else:
61-
env_config[env_key] = safe_value(value)
62-
6355
# Batch update environment variables
6456
for key, value in env_config.items():
6557
config_manager.set_config(key, value)
@@ -165,11 +157,6 @@ async def load_config():
165157
"modelUrl": config_manager.get_config("TTS_MODEL_URL", "")
166158
}
167159
}
168-
},
169-
"data": {
170-
"selectedKbNames": json.loads(config_manager.get_config("SELECTED_KB_NAMES", "[]")),
171-
"selectedKbModels": json.loads(config_manager.get_config("SELECTED_KB_MODELS", "[]")),
172-
"selectedKbSources": json.loads(config_manager.get_config("SELECTED_KB_SOURCES", "[]"))
173160
}
174161
}
175162

backend/apps/knowledge_app.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from typing import Optional
2+
3+
from fastapi import HTTPException, Query, Body, Path, Depends, APIRouter, Header
4+
from consts.model import ChangeSummaryRequest
5+
from fastapi.responses import StreamingResponse
6+
from nexent.vector_database.elasticsearch_core import ElasticSearchCore
7+
from services.elasticsearch_service import ElasticSearchService, get_es_core
8+
from database.utils import get_current_user_id
9+
router = APIRouter(prefix="/summary")
10+
11+
@router.post("/{index_name}/auto_summary")
12+
async def auto_summary(
13+
index_name: str = Path(..., description="Name of the index to get documents from"),
14+
batch_size: int = Query(1000, description="Number of documents to retrieve per batch"),
15+
es_core: ElasticSearchCore = Depends(get_es_core),
16+
authorization: Optional[str] = Header(None)
17+
):
18+
"""Summary Elasticsearch index_name by model"""
19+
try:
20+
user_id = get_current_user_id(authorization)
21+
service = ElasticSearchService()
22+
23+
return await service.summary_index_name(
24+
index_name=index_name,
25+
batch_size=batch_size,
26+
es_core=es_core,
27+
user_id=user_id
28+
)
29+
except Exception as e:
30+
return StreamingResponse(
31+
f"data: {{\"status\": \"error\", \"message\": \"知识库摘要生成失败: {e}\"}}\n\n",
32+
media_type="text/event-stream",
33+
status_code=500
34+
)
35+
36+
37+
@router.post("/{index_name}/summary")
38+
def change_summary(
39+
index_name: str = Path(..., description="Name of the index to get documents from"),
40+
change_summary_request: ChangeSummaryRequest = Body(None, description="knowledge base summary"),
41+
authorization: Optional[str] = Header(None)
42+
):
43+
"""Summary Elasticsearch index_name by user"""
44+
try:
45+
user_id = get_current_user_id(authorization)
46+
summary_result = change_summary_request.summary_result
47+
return ElasticSearchService().change_summary(index_name=index_name,summary_result=summary_result,user_id=user_id)
48+
except Exception as e:
49+
raise HTTPException(status_code=500, detail=f"知识库摘要更新失败: {str(e)}")
50+
51+
52+
@router.get("/{index_name}/summary")
53+
def get_summary(
54+
index_name: str = Path(..., description="Name of the index to get documents from"),
55+
):
56+
"""Get Elasticsearch index_name Summary"""
57+
try:
58+
# Try to list indices as a health check
59+
return ElasticSearchService().get_summary(index_name=index_name)
60+
except Exception as e:
61+
raise HTTPException(status_code=500, detail=f"获取知识库摘要失败: {str(e)}")

backend/apps/tenant_config_app.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from fastapi import APIRouter, Depends, Header, Query, Path, Body
2+
from typing import Optional, List
3+
from services.tenant_config_service import get_selected_knowledge_list, update_selected_knowledge
4+
from fastapi.responses import JSONResponse
5+
6+
from utils.user_utils import get_user_info
7+
8+
router = APIRouter(prefix="/tenant_config")
9+
10+
@router.get("/load_knowledge_list")
11+
def load_knowledge_list(
12+
authorization: Optional[str] = Header(None)
13+
):
14+
try:
15+
user_id, tenant_id = get_user_info()
16+
selected_knowledge_info = get_selected_knowledge_list(tenant_id=tenant_id, user_id=user_id)
17+
content = {"selectedKbNames": [item["index_name"] for item in selected_knowledge_info],
18+
"selectedKbModels": [item["knowledge_embedding_model"] for item in selected_knowledge_info],
19+
"selectedKbSources": [item["knowledge_sources"] for item in selected_knowledge_info]}
20+
21+
return JSONResponse(
22+
status_code=200,
23+
content={"content": content, "status": "success"}
24+
)
25+
except Exception as e:
26+
return JSONResponse(
27+
status_code=400,
28+
content={"message": f"Failed to load configuration: {str(e)}", "status": "error"}
29+
)
30+
31+
@router.post("/update_knowledge_list")
32+
def update_knowledge_list(
33+
authorization: Optional[str] = Header(None),
34+
knowledge_list: List[str] = Body(None)
35+
):
36+
try:
37+
user_id, tenant_id = get_user_info()
38+
result = update_selected_knowledge(tenant_id=tenant_id, user_id=user_id, index_name_list=knowledge_list)
39+
if result:
40+
return JSONResponse(
41+
status_code=200,
42+
content={"message": "update success", "status": "success"}
43+
)
44+
else:
45+
return JSONResponse(
46+
status_code=400,
47+
content={"message": "update failed", "status": "error"}
48+
)
49+
except Exception as e:
50+
return JSONResponse(
51+
status_code=400,
52+
content={"message": f"Failed to update configuration: {str(e)}", "status": "error"}
53+
)

backend/consts/model.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,9 @@ class AppConfig(BaseModel):
8484
avatarUri: Optional[str] = None
8585

8686

87-
class KnowledgeBaseConfig(BaseModel):
88-
selectedKbNames: List[str]
89-
selectedKbModels: List[str]
90-
selectedKbSources: List[str]
91-
92-
9387
class GlobalConfig(BaseModel):
9488
app: AppConfig
9589
models: ModelConfig
96-
data: KnowledgeBaseConfig
9790

9891

9992
# Request models

backend/database/db_models.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,31 @@ class KnowledgeRecord(TableBase):
215215
knowledge_id = Column(Integer, Sequence("knowledge_record_t_knowledge_id_seq", schema="nexent"), primary_key=True, nullable=False, doc="Knowledge base ID, unique primary key")
216216
index_name = Column(String(100), doc="Knowledge base name")
217217
knowledge_describe = Column(String(300), doc="Knowledge base description")
218+
knowledge_embedding_model = Column(String(300), doc="Knowledge base embedding model")
219+
knowledge_sources = Column(String(300), doc="Knowledge base sources")
218220
tenant_id = Column(String(100), doc="Tenant ID")
219221
delete_flag = Column(String(1), default="N", doc="Knowledge base status. Currently defaults to 1, if knowledge base status is 0, then this knowledge base is unavailable")
220222
create_time = Column(TIMESTAMP(timezone=False), server_default=func.now(), doc="Creation time, audit field")
221223
update_time = Column(TIMESTAMP(timezone=False), server_default=func.now(), doc="Update date, audit field")
222224
updated_by = Column(String(100), doc="ID of the last updater, audit field")
223-
created_by = Column(String(100), doc="ID of the creator, audit field")
225+
created_by = Column(String(100), doc="ID of the creator, audit field")
226+
227+
228+
class TenantConfig(TableBase):
229+
"""
230+
Tenant configuration information table
231+
"""
232+
__tablename__ = "tenant_config_t"
233+
__table_args__ = {"schema": SCHEMA}
234+
235+
tenant_config_id = Column(Integer, Sequence("tenant_config_t_tenant_config_id_seq", schema=SCHEMA), primary_key=True, nullable=False, doc="ID")
236+
tenant_id = Column(String(100), doc="Tenant ID")
237+
user_id = Column(String(100), doc="User ID")
238+
value_type = Column(String(100), doc=" the data type of config_value, optional values: single/multi", default="single")
239+
config_key = Column(String(100), doc="the key of the config")
240+
config_value = Column(String(100), doc="the value of the config")
241+
create_time = Column(TIMESTAMP(timezone=False), server_default=func.now(), doc="Creation time")
242+
update_time = Column(TIMESTAMP(timezone=False), server_default=func.now(), doc="Update time")
243+
created_by = Column(String(100), doc="Creator")
244+
updated_by = Column(String(100), doc="Updater")
245+
delete_flag = Column(String(1), default="N", doc="Whether it is deleted. Optional values: Y/N")

backend/database/knowledge_db.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Dict, Any, Optional
1+
from typing import Dict, Any, Optional, List
22
from sqlalchemy import func
33
from sqlalchemy.exc import SQLAlchemyError
44

@@ -27,6 +27,8 @@ def create_knowledge_record(query: Dict[str, Any]) -> int:
2727
"knowledge_describe": query.get("knowledge_describe", ""),
2828
"created_by": query.get("user_id"),
2929
"updated_by": query.get("user_id"),
30+
"knowledge_embedding_model": query.get("knowledge_embedding_model"),
31+
"knowledge_sources": query.get("knowledge_sources", "elasticsearch")
3032
}
3133

3234
# Create new record
@@ -132,3 +134,34 @@ def get_knowledge_record(query: Optional[Dict[str, Any]] = None) -> Dict[str, An
132134
return {}
133135
except SQLAlchemyError as e:
134136
raise e
137+
138+
def get_knowledge_info_by_knowledge_ids(knowledge_ids: List[str]) -> List[Dict[str, Any]]:
139+
try:
140+
with get_db_session() as session:
141+
result = session.query(KnowledgeRecord).filter(
142+
KnowledgeRecord.knowledge_id.in_(knowledge_ids),
143+
KnowledgeRecord.delete_flag != 'Y'
144+
).all()
145+
knowledge_info = []
146+
for item in result:
147+
knowledge_info.append({
148+
"knowledge_id": item.knowledge_id,
149+
"index_name": item.index_name,
150+
"knowledge_embedding_model": item.knowledge_embedding_model,
151+
"knowledge_sources": item.knowledge_sources
152+
})
153+
return knowledge_info
154+
except SQLAlchemyError as e:
155+
raise e
156+
157+
def get_knowledge_ids_by_index_names(index_names: List[str]) -> List[str]:
158+
try:
159+
with get_db_session() as session:
160+
result = session.query(KnowledgeRecord.knowledge_id).filter(
161+
KnowledgeRecord.index_name.in_(index_names),
162+
KnowledgeRecord.delete_flag != 'Y'
163+
).all()
164+
return [item.knowledge_id for item in result]
165+
except SQLAlchemyError as e:
166+
raise e
167+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from typing import Optional, Dict, List, Any
2+
3+
from .client import db_client, get_db_session
4+
from .db_models import TenantConfig
5+
6+
7+
def get_tenant_config_info(tenant_id: str, user_id: str, select_key: str):
8+
with get_db_session() as session:
9+
result = session.query(TenantConfig).filter(TenantConfig.tenant_id == tenant_id,
10+
TenantConfig.user_id == user_id,
11+
TenantConfig.config_key == select_key,
12+
TenantConfig.delete_flag == "N").all()
13+
record_info = []
14+
for item in result:
15+
record_info.append({
16+
"config_value": item.config_value,
17+
"tenant_config_id": item.tenant_config_id
18+
})
19+
return record_info
20+
21+
22+
def insert_config(insert_data: Dict[str, Any]):
23+
with get_db_session() as session:
24+
try:
25+
session.add(TenantConfig(**insert_data))
26+
session.commit()
27+
return True
28+
except Exception as e:
29+
session.rollback()
30+
return False
31+
32+
33+
def delete_config_by_tenant_config_id(tenant_config_id: int):
34+
with get_db_session() as session:
35+
try:
36+
session.query(TenantConfig).filter(TenantConfig.tenant_config_id == tenant_config_id,
37+
TenantConfig.delete_flag == "N").update({"delete_flag": "Y"})
38+
session.commit()
39+
return True
40+
except Exception as e:
41+
session.rollback()
42+
return False
43+
44+
def update_config_by_tenant_config_id(tenant_config_id: int, update_value: str):
45+
with get_db_session() as session:
46+
try:
47+
session.query(TenantConfig).filter(TenantConfig.tenant_config_id == tenant_config_id,
48+
TenantConfig.delete_flag == "N").update({"config_value": update_value})
49+
session.commit()
50+
return True
51+
except Exception as e:
52+
session.rollback()
53+
return False

backend/database/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55

66
def get_current_user_id(authorization: Optional[str] = Header(None)) -> Optional[str]:
77
if not authorization:
8-
return None
8+
return ""

0 commit comments

Comments
 (0)