langflow-base-nightly 0.5.0.dev36__py3-none-any.whl → 0.5.0.dev37__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langflow/api/v1/knowledge_bases.py +16 -9
- langflow/api/v2/files.py +3 -1
- langflow/base/data/kb_utils.py +33 -0
- langflow/base/models/model.py +3 -3
- langflow/components/agents/mcp_component.py +40 -55
- langflow/components/data/kb_ingest.py +116 -43
- langflow/components/data/kb_retrieval.py +24 -26
- langflow/components/processing/save_file.py +6 -32
- langflow/components/vectorstores/astradb.py +30 -19
- langflow/frontend/assets/{SlackIcon-B260Qg_R.js → SlackIcon-CnvyOamQ.js} +1 -1
- langflow/frontend/assets/{Wikipedia-BB2mbgyd.js → Wikipedia-nyTEXdr2.js} +1 -1
- langflow/frontend/assets/{Wolfram-DytXC9hF.js → Wolfram-BYMQkNSq.js} +1 -1
- langflow/frontend/assets/{index-BdIWbCEL.js → index-8WdfSTTz.js} +1 -1
- langflow/frontend/assets/{index-D87Zw62M.js → index-8yMsjVV2.js} +1 -1
- langflow/frontend/assets/{index-DyJDHm2D.js → index-B1YN7oMV.js} +1 -1
- langflow/frontend/assets/{index-BEDxAk3N.js → index-B3Sur4Z3.js} +1 -1
- langflow/frontend/assets/{index-DhzEUXfr.js → index-B748uLP1.js} +1 -1
- langflow/frontend/assets/{index-4eRtaV45.js → index-BB15_iOb.js} +1 -1
- langflow/frontend/assets/{index-C_1RBTul.js → index-BBxAPk1y.js} +1 -1
- langflow/frontend/assets/{index-DHlEwAxb.js → index-BCCGvqay.js} +1 -1
- langflow/frontend/assets/{index-ci4XHjbJ.js → index-BChjg6Az.js} +3 -3
- langflow/frontend/assets/{index-B9Mo3ndZ.js → index-BEMw2Np8.js} +1 -1
- langflow/frontend/assets/{index-BKvKC-12.js → index-BFp_O-c9.js} +1 -1
- langflow/frontend/assets/{index-Ym6gz0T6.js → index-BIQQCMvz.js} +1 -1
- langflow/frontend/assets/{index-CwIxqYlT.js → index-BIXaW2aY.js} +1 -1
- langflow/frontend/assets/{index-BxkZkBgQ.js → index-BIzTEqFh.js} +1 -1
- langflow/frontend/assets/{index-C76aBV_h.js → index-BLGYN-9b.js} +1 -1
- langflow/frontend/assets/{index-B-c82Fnu.js → index-BOB_zsjl.js} +1 -1
- langflow/frontend/assets/{index-BbsND1Qg.js → index-BOeo01QB.js} +1 -1
- langflow/frontend/assets/{index-DztLFiip.js → index-BQ6NUdMY.js} +1 -1
- langflow/frontend/assets/{index-G_U_kPAd.js → index-BR0bkVqX.js} +1 -1
- langflow/frontend/assets/{index-R7q8cAek.js → index-BRYjyhAd.js} +1 -1
- langflow/frontend/assets/{index-Ccb5B8zG.js → index-BRxvproo.js} +1 -1
- langflow/frontend/assets/{index-B8y58M9b.js → index-BTEW9e8P.js} +1 -1
- langflow/frontend/assets/{index-DdzVmJHE.js → index-BTrsh9LS.js} +1 -1
- langflow/frontend/assets/{index-CkSzjCqM.js → index-BVEZDXxS.js} +1 -1
- langflow/frontend/assets/{index-BLROcaSz.js → index-BWmPX4iQ.js} +1 -1
- langflow/frontend/assets/{index-Ct9_T9ox.js → index-BX5D-USa.js} +1 -1
- langflow/frontend/assets/{index-DtJyCbzF.js → index-BZgXW854.js} +1 -1
- langflow/frontend/assets/{index-CkQ-bJ4G.js → index-BbJjt5m4.js} +1 -1
- langflow/frontend/assets/{index-D5PeCofu.js → index-BbRm7beF.js} +1 -1
- langflow/frontend/assets/{index-Uq2ij_SS.js → index-Bd6WtbKA.js} +1 -1
- langflow/frontend/assets/{index-sS6XLk3j.js → index-BhIOhlCH.js} +1 -1
- langflow/frontend/assets/{index-dkS0ek2S.js → index-BkPYpfgw.js} +1 -1
- langflow/frontend/assets/{index-BOYTBrh9.js → index-BmX5CoED.js} +1 -1
- langflow/frontend/assets/{index-CMGZGIx_.js → index-Bnqod3vk.js} +1 -1
- langflow/frontend/assets/{index-Bisa4IQF.js → index-Boso-xEw.js} +1 -1
- langflow/frontend/assets/{index-DqSH4x-R.js → index-BqPpO6KG.js} +1 -1
- langflow/frontend/assets/{index-B5ed-sAv.js → index-Bsa0xZyL.js} +1 -1
- langflow/frontend/assets/{index-CoUlHbtg.js → index-Bv8h2Z-q.js} +1 -1
- langflow/frontend/assets/{index-tOy_uloT.js → index-BvT7L317.js} +1 -1
- langflow/frontend/assets/{index-D-zkHcob.js → index-BvwZfF2i.js} +1 -1
- langflow/frontend/assets/{index-DxIs8VSp.js → index-Bvxg4_ux.js} +1 -1
- langflow/frontend/assets/{index-CqDUqHfd.js → index-BxEuHa76.js} +1 -1
- langflow/frontend/assets/{index-DX7XsAcx.js → index-BzEUlaw_.js} +1 -1
- langflow/frontend/assets/{index-BCK-ZyIh.js → index-BzL_EoKd.js} +1 -1
- langflow/frontend/assets/{index-BNbWMmAV.js → index-C-2hghRJ.js} +1 -1
- langflow/frontend/assets/{index-CWWo2zOA.js → index-C26RqKWL.js} +1 -1
- langflow/frontend/assets/{index-BcgB3rXH.js → index-C6jri9Wm.js} +1 -1
- langflow/frontend/assets/{index-mBjJYD9q.js → index-C7QWbnLK.js} +1 -1
- langflow/frontend/assets/{index-D0HmkH0H.js → index-C82JjCPD.js} +1 -1
- langflow/frontend/assets/{index-Cpgkb0Q3.js → index-CCePCqkT.js} +1 -1
- langflow/frontend/assets/{index-IFGgPiye.js → index-CCxGSSTT.js} +1 -1
- langflow/frontend/assets/{index-BOEf7-ty.js → index-CFDvOtKC.js} +1 -1
- langflow/frontend/assets/{index-Ba3RTMXI.js → index-CJo_cyWW.js} +1 -1
- langflow/frontend/assets/{index-Cx__T92e.js → index-CLPdN-q6.js} +1 -1
- langflow/frontend/assets/{index-CF4dtI6S.js → index-CQMoqLAu.js} +1 -1
- langflow/frontend/assets/{index-3qMh9x6K.js → index-CTrt1Q_j.js} +1 -1
- langflow/frontend/assets/{index-rcdQpNcU.js → index-CVQmT7ZL.js} +1 -1
- langflow/frontend/assets/{index-Dpz3oBf5.js → index-CWdkbVsd.js} +1 -1
- langflow/frontend/assets/{index-D0s9f6Re.js → index-CYDAYm-i.js} +1 -1
- langflow/frontend/assets/{index-BjENqyKe.js → index-CYe8Ipef.js} +1 -1
- langflow/frontend/assets/{index-ByFXr9Iq.js → index-CZQ9rXNa.js} +1 -1
- langflow/frontend/assets/{index-VcXZzovW.js → index-C_TdzfAn.js} +1 -1
- langflow/frontend/assets/{index-LrMzDsq9.js → index-C_veJlEb.js} +1 -1
- langflow/frontend/assets/{index-BdYgKk1d.js → index-CaQ_H9ww.js} +1 -1
- langflow/frontend/assets/{index-CHFO5O4g.js → index-Car-zdor.js} +1 -1
- langflow/frontend/assets/{index-DZzbmg3J.js → index-ChXJpBz4.js} +1 -1
- langflow/frontend/assets/{index-C7x9R_Yo.js → index-CmplyEaa.js} +1 -1
- langflow/frontend/assets/{index-Cd5zuUUK.js → index-CpcbQZIF.js} +1 -1
- langflow/frontend/assets/{index-D9eflZfP.js → index-CpvYQ0ug.js} +1 -1
- langflow/frontend/assets/{index-DS1EgA10.js → index-CvcEzq4x.js} +1 -1
- langflow/frontend/assets/{index-hOkEW3JP.js → index-CxvP91st.js} +1 -1
- langflow/frontend/assets/{index-DasrI03Y.js → index-CyPvTB63.js} +1 -1
- langflow/frontend/assets/{index-BJrY2Fiu.js → index-D-9TI74R.js} +1 -1
- langflow/frontend/assets/{index-BlBl2tvQ.js → index-D3DDfngy.js} +1 -1
- langflow/frontend/assets/{index-DzeIsaBm.js → index-D5_DsUJc.js} +1 -1
- langflow/frontend/assets/{index-AY5Dm2mG.js → index-D6PSjHxP.js} +1 -1
- langflow/frontend/assets/{index-C9N80hP8.js → index-D8GJngXa.js} +1 -1
- langflow/frontend/assets/{index-BxWXWRmZ.js → index-D8lOi1GI.js} +1 -1
- langflow/frontend/assets/{index-DWkMJnbd.js → index-DCRk27Tp.js} +1 -1
- langflow/frontend/assets/{index-BnLT29qW.js → index-DF5VwgU6.js} +1 -1
- langflow/frontend/assets/{index-7xXgqu09.js → index-DGRMNe9n.js} +1 -1
- langflow/frontend/assets/{index-3TJWUdmx.js → index-DHq8TQPB.js} +1 -1
- langflow/frontend/assets/{index-BVtf6m9S.js → index-DIDDfmlJ.js} +1 -1
- langflow/frontend/assets/{index-B2ggrBuR.js → index-DIkNW9Cd.js} +1 -1
- langflow/frontend/assets/{index-r1LZg-PY.js → index-DJB12jIC.js} +1 -1
- langflow/frontend/assets/{index-DS9I4y48.js → index-DK1Ptcc4.js} +1 -1
- langflow/frontend/assets/{index-CG7cp0nD.js → index-DKHNourL.js} +1 -1
- langflow/frontend/assets/{index-BeNby7qF.js → index-DPCzHdsC.js} +1 -1
- langflow/frontend/assets/{index-COL0eiWI.js → index-DVlceYFD.js} +1 -1
- langflow/frontend/assets/{index-DK8vNpXK.js → index-DZTC5pdT.js} +1 -1
- langflow/frontend/assets/{index-Baka5dKE.js → index-Db71w3lq.js} +1 -1
- langflow/frontend/assets/{index-Du9aJK7m.js → index-DbMFlnHE.js} +1 -1
- langflow/frontend/assets/{index-CvQ0w8Pj.js → index-DfngcQxO.js} +1 -1
- langflow/frontend/assets/{index-DIqSyDVO.js → index-DfxYyS3M.js} +1 -1
- langflow/frontend/assets/{index-3uOAA_XX.js → index-Dg-63Si_.js} +1 -1
- langflow/frontend/assets/{index-BsBWP-Dh.js → index-DjQETUy8.js} +1 -1
- langflow/frontend/assets/{index-CDFLVFB4.js → index-DkXy1WFo.js} +1 -1
- langflow/frontend/assets/{index-B8TlNgn-.js → index-DkelbYy7.js} +1 -1
- langflow/frontend/assets/{index-GODbXlHC.js → index-DmMDPoi0.js} +1 -1
- langflow/frontend/assets/{index-DpQKtcXu.js → index-DnEGCgih.js} +1 -1
- langflow/frontend/assets/{index-VHmUHUUU.js → index-DpClkXIV.js} +1 -1
- langflow/frontend/assets/{index-BRWNIt9F.js → index-Dq5ilsem.js} +1 -1
- langflow/frontend/assets/{index-DDNNv4C0.js → index-Dqd4RjYA.js} +1 -1
- langflow/frontend/assets/{index-C2Xd7UkR.js → index-Dsps-jKu.js} +1 -1
- langflow/frontend/assets/{index-BVHvIhT5.js → index-Du_18NCU.js} +1 -1
- langflow/frontend/assets/{index-C7V5U9yH.js → index-DysKpOuj.js} +1 -1
- langflow/frontend/assets/{index-Bxml6wXu.js → index-DytJENYD.js} +1 -1
- langflow/frontend/assets/{index-BWq9GTzt.js → index-DzW2mfkK.js} +1 -1
- langflow/frontend/assets/{index-js8ceOaP.js → index-FUxmznS-.js} +1 -1
- langflow/frontend/assets/{index-DuAeoC-H.js → index-Gkrq-vzm.js} +1 -1
- langflow/frontend/assets/{index-DPX6X_bw.js → index-HK3bVMYA.js} +1 -1
- langflow/frontend/assets/{index-BEKoRwsX.js → index-LbYjHKkn.js} +1 -1
- langflow/frontend/assets/{index-C8KD3LPb.js → index-OazXJdEl.js} +1 -1
- langflow/frontend/assets/{index-DpJiH-Rk.js → index-Q9vDw0Xl.js} +1 -1
- langflow/frontend/assets/{index-DWr_zPkx.js → index-Ui4xUImO.js} +1 -1
- langflow/frontend/assets/{index-BejHxU5W.js → index-WPFivmdQ.js} +1 -1
- langflow/frontend/assets/{index-lKEJpUsF.js → index-_UcqeEjm.js} +1 -1
- langflow/frontend/assets/{index-VZnN0P6C.js → index-ajRge-Mg.js} +1 -1
- langflow/frontend/assets/{index-BQB-iDYl.js → index-cvZdgWHQ.js} +1 -1
- langflow/frontend/assets/{index-AlJ7td-D.js → index-dcnYpT9N.js} +1 -1
- langflow/frontend/assets/{index-DKEXZFUO.js → index-l7bzB8Ex.js} +1 -1
- langflow/frontend/assets/index-nVwHLjuV.js +1 -0
- langflow/frontend/assets/{index-BtJ2o21k.js → index-pCQ_yw8m.js} +1 -1
- langflow/frontend/assets/{index-B536IPXH.js → index-rXV1G1aB.js} +1 -1
- langflow/frontend/assets/{index-BIkqesA-.js → index-tVYiABdp.js} +1 -1
- langflow/frontend/assets/{index-CJwYfDBz.js → index-xuIrH2Dq.js} +1 -1
- langflow/frontend/assets/{index-BXMhmvTj.js → index-yCHsaqs8.js} +1 -1
- langflow/frontend/assets/{index-BqUeOc7Y.js → index-ya2uXE8v.js} +1 -1
- langflow/frontend/assets/lazyIconImports-t6wEndt1.js +2 -0
- langflow/frontend/assets/{use-post-add-user-HN0rRnhv.js → use-post-add-user-BrBYH9eR.js} +1 -1
- langflow/frontend/index.html +1 -1
- langflow/initial_setup/starter_projects/Hybrid Search RAG.json +2 -2
- langflow/initial_setup/starter_projects/Knowledge Ingestion.json +2 -2
- langflow/initial_setup/starter_projects/Knowledge Retrieval.json +2 -2
- langflow/initial_setup/starter_projects/News Aggregator.json +2 -19
- langflow/initial_setup/starter_projects/Nvidia Remix.json +2 -19
- langflow/initial_setup/starter_projects/Vector Store RAG.json +4 -4
- langflow/processing/process.py +1 -1
- {langflow_base_nightly-0.5.0.dev36.dist-info → langflow_base_nightly-0.5.0.dev37.dist-info}/METADATA +1 -1
- {langflow_base_nightly-0.5.0.dev36.dist-info → langflow_base_nightly-0.5.0.dev37.dist-info}/RECORD +154 -153
- langflow/frontend/assets/lazyIconImports-Bh1TFfvH.js +0 -2
- {langflow_base_nightly-0.5.0.dev36.dist-info → langflow_base_nightly-0.5.0.dev37.dist-info}/WHEEL +0 -0
- {langflow_base_nightly-0.5.0.dev36.dist-info → langflow_base_nightly-0.5.0.dev37.dist-info}/entry_points.txt +0 -0
|
@@ -9,6 +9,7 @@ from langchain_chroma import Chroma
|
|
|
9
9
|
from loguru import logger
|
|
10
10
|
from pydantic import BaseModel
|
|
11
11
|
|
|
12
|
+
from langflow.api.utils import CurrentActiveUser
|
|
12
13
|
from langflow.services.deps import get_settings_service
|
|
13
14
|
|
|
14
15
|
router = APIRouter(tags=["Knowledge Bases"], prefix="/knowledge_bases")
|
|
@@ -290,17 +291,19 @@ def get_kb_metadata(kb_path: Path) -> dict:
|
|
|
290
291
|
|
|
291
292
|
@router.get("", status_code=HTTPStatus.OK)
|
|
292
293
|
@router.get("/", status_code=HTTPStatus.OK)
|
|
293
|
-
async def list_knowledge_bases() -> list[KnowledgeBaseInfo]:
|
|
294
|
+
async def list_knowledge_bases(current_user: CurrentActiveUser) -> list[KnowledgeBaseInfo]:
|
|
294
295
|
"""List all available knowledge bases."""
|
|
295
296
|
try:
|
|
296
297
|
kb_root_path = get_kb_root_path()
|
|
298
|
+
kb_user = current_user.username
|
|
299
|
+
kb_path = kb_root_path / kb_user
|
|
297
300
|
|
|
298
|
-
if not
|
|
301
|
+
if not kb_path.exists():
|
|
299
302
|
return []
|
|
300
303
|
|
|
301
304
|
knowledge_bases = []
|
|
302
305
|
|
|
303
|
-
for kb_dir in
|
|
306
|
+
for kb_dir in kb_path.iterdir():
|
|
304
307
|
if not kb_dir.is_dir() or kb_dir.name.startswith("."):
|
|
305
308
|
continue
|
|
306
309
|
|
|
@@ -340,11 +343,12 @@ async def list_knowledge_bases() -> list[KnowledgeBaseInfo]:
|
|
|
340
343
|
|
|
341
344
|
|
|
342
345
|
@router.get("/{kb_name}", status_code=HTTPStatus.OK)
|
|
343
|
-
async def get_knowledge_base(kb_name: str) -> KnowledgeBaseInfo:
|
|
346
|
+
async def get_knowledge_base(kb_name: str, current_user: CurrentActiveUser) -> KnowledgeBaseInfo:
|
|
344
347
|
"""Get detailed information about a specific knowledge base."""
|
|
345
348
|
try:
|
|
346
349
|
kb_root_path = get_kb_root_path()
|
|
347
|
-
|
|
350
|
+
kb_user = current_user.username
|
|
351
|
+
kb_path = kb_root_path / kb_user / kb_name
|
|
348
352
|
|
|
349
353
|
if not kb_path.exists() or not kb_path.is_dir():
|
|
350
354
|
raise HTTPException(status_code=404, detail=f"Knowledge base '{kb_name}' not found")
|
|
@@ -374,11 +378,12 @@ async def get_knowledge_base(kb_name: str) -> KnowledgeBaseInfo:
|
|
|
374
378
|
|
|
375
379
|
|
|
376
380
|
@router.delete("/{kb_name}", status_code=HTTPStatus.OK)
|
|
377
|
-
async def delete_knowledge_base(kb_name: str) -> dict[str, str]:
|
|
381
|
+
async def delete_knowledge_base(kb_name: str, current_user: CurrentActiveUser) -> dict[str, str]:
|
|
378
382
|
"""Delete a specific knowledge base."""
|
|
379
383
|
try:
|
|
380
384
|
kb_root_path = get_kb_root_path()
|
|
381
|
-
|
|
385
|
+
kb_user = current_user.username
|
|
386
|
+
kb_path = kb_root_path / kb_user / kb_name
|
|
382
387
|
|
|
383
388
|
if not kb_path.exists() or not kb_path.is_dir():
|
|
384
389
|
raise HTTPException(status_code=404, detail=f"Knowledge base '{kb_name}' not found")
|
|
@@ -396,15 +401,17 @@ async def delete_knowledge_base(kb_name: str) -> dict[str, str]:
|
|
|
396
401
|
|
|
397
402
|
@router.delete("", status_code=HTTPStatus.OK)
|
|
398
403
|
@router.delete("/", status_code=HTTPStatus.OK)
|
|
399
|
-
async def delete_knowledge_bases_bulk(request: BulkDeleteRequest) -> dict[str, object]:
|
|
404
|
+
async def delete_knowledge_bases_bulk(request: BulkDeleteRequest, current_user: CurrentActiveUser) -> dict[str, object]:
|
|
400
405
|
"""Delete multiple knowledge bases."""
|
|
401
406
|
try:
|
|
402
407
|
kb_root_path = get_kb_root_path()
|
|
408
|
+
kb_user = current_user.username
|
|
409
|
+
kb_user_path = kb_root_path / kb_user
|
|
403
410
|
deleted_count = 0
|
|
404
411
|
not_found_kbs = []
|
|
405
412
|
|
|
406
413
|
for kb_name in request.kb_names:
|
|
407
|
-
kb_path =
|
|
414
|
+
kb_path = kb_user_path / kb_name
|
|
408
415
|
|
|
409
416
|
if not kb_path.exists() or not kb_path.is_dir():
|
|
410
417
|
not_found_kbs.append(kb_name)
|
langflow/api/v2/files.py
CHANGED
|
@@ -123,7 +123,9 @@ async def upload_user_file(
|
|
|
123
123
|
unique_filename = new_filename
|
|
124
124
|
else:
|
|
125
125
|
# For normal files, ensure unique name by appending a count if necessary
|
|
126
|
-
stmt = select(UserFile).where(
|
|
126
|
+
stmt = select(UserFile).where(
|
|
127
|
+
col(UserFile.name).like(f"{root_filename}%"), UserFile.user_id == current_user.id
|
|
128
|
+
)
|
|
127
129
|
existing_files = await session.exec(stmt)
|
|
128
130
|
files = existing_files.all() # Fetch all matching records
|
|
129
131
|
|
langflow/base/data/kb_utils.py
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import math
|
|
2
2
|
from collections import Counter
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from langflow.services.database.models.user.crud import get_user_by_id
|
|
7
|
+
from langflow.services.deps import session_scope
|
|
3
8
|
|
|
4
9
|
|
|
5
10
|
def compute_tfidf(documents: list[str], query_terms: list[str]) -> list[float]:
|
|
@@ -102,3 +107,31 @@ def compute_bm25(documents: list[str], query_terms: list[str], k1: float = 1.2,
|
|
|
102
107
|
scores.append(doc_score)
|
|
103
108
|
|
|
104
109
|
return scores
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
async def get_knowledge_bases(kb_root: Path, user_id: UUID | str) -> list[str]:
|
|
113
|
+
"""Retrieve a list of available knowledge bases.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
A list of knowledge base names.
|
|
117
|
+
"""
|
|
118
|
+
if not kb_root.exists():
|
|
119
|
+
return []
|
|
120
|
+
|
|
121
|
+
# Get the current user
|
|
122
|
+
async with session_scope() as db:
|
|
123
|
+
if not user_id:
|
|
124
|
+
msg = "User ID is required for fetching knowledge bases."
|
|
125
|
+
raise ValueError(msg)
|
|
126
|
+
user_id = UUID(user_id) if isinstance(user_id, str) else user_id
|
|
127
|
+
current_user = await get_user_by_id(db, user_id)
|
|
128
|
+
if not current_user:
|
|
129
|
+
msg = f"User with ID {user_id} not found."
|
|
130
|
+
raise ValueError(msg)
|
|
131
|
+
kb_user = current_user.username
|
|
132
|
+
kb_path = kb_root / kb_user
|
|
133
|
+
|
|
134
|
+
if not kb_path.exists():
|
|
135
|
+
return []
|
|
136
|
+
|
|
137
|
+
return [str(d.name) for d in kb_path.iterdir() if not d.name.startswith(".") and d.is_dir()]
|
langflow/base/models/model.py
CHANGED
|
@@ -252,7 +252,7 @@ class LCModelComponent(Component):
|
|
|
252
252
|
if stream:
|
|
253
253
|
lf_message, result = await self._handle_stream(runnable, inputs)
|
|
254
254
|
else:
|
|
255
|
-
message = runnable.
|
|
255
|
+
message = await runnable.ainvoke(inputs)
|
|
256
256
|
result = message.content if hasattr(message, "content") else message
|
|
257
257
|
if isinstance(message, AIMessage):
|
|
258
258
|
status_message = self.build_status_message(message)
|
|
@@ -288,7 +288,7 @@ class LCModelComponent(Component):
|
|
|
288
288
|
else:
|
|
289
289
|
session_id = None
|
|
290
290
|
model_message = Message(
|
|
291
|
-
text=runnable.
|
|
291
|
+
text=runnable.astream(inputs),
|
|
292
292
|
sender=MESSAGE_SENDER_AI,
|
|
293
293
|
sender_name="AI",
|
|
294
294
|
properties={"icon": self.icon, "state": "partial"},
|
|
@@ -298,7 +298,7 @@ class LCModelComponent(Component):
|
|
|
298
298
|
lf_message = await self.send_message(model_message)
|
|
299
299
|
result = lf_message.text
|
|
300
300
|
else:
|
|
301
|
-
message = runnable.
|
|
301
|
+
message = await runnable.ainvoke(inputs)
|
|
302
302
|
result = message.content if hasattr(message, "content") else message
|
|
303
303
|
return lf_message, result
|
|
304
304
|
|
|
@@ -16,16 +16,15 @@ from langflow.base.mcp.util import (
|
|
|
16
16
|
)
|
|
17
17
|
from langflow.custom.custom_component.component_with_cache import ComponentWithCache
|
|
18
18
|
from langflow.inputs.inputs import InputTypes # noqa: TC001
|
|
19
|
-
from langflow.io import DropdownInput, McpInput, MessageTextInput, Output
|
|
19
|
+
from langflow.io import DropdownInput, McpInput, MessageTextInput, Output
|
|
20
20
|
from langflow.io.schema import flatten_schema, schema_to_langflow_inputs
|
|
21
21
|
from langflow.logging import logger
|
|
22
22
|
from langflow.schema.dataframe import DataFrame
|
|
23
23
|
from langflow.schema.message import Message
|
|
24
24
|
|
|
25
25
|
# Import get_server from the backend API
|
|
26
|
-
from langflow.services.auth.utils import create_user_longterm_token, get_current_user
|
|
27
26
|
from langflow.services.database.models.user.crud import get_user_by_id
|
|
28
|
-
from langflow.services.deps import
|
|
27
|
+
from langflow.services.deps import get_settings_service, get_storage_service, session_scope
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
class MCPToolsComponent(ComponentWithCache):
|
|
@@ -96,13 +95,6 @@ class MCPToolsComponent(ComponentWithCache):
|
|
|
96
95
|
show=False,
|
|
97
96
|
tool_mode=False,
|
|
98
97
|
),
|
|
99
|
-
SecretStrInput(
|
|
100
|
-
name="api_key",
|
|
101
|
-
display_name="Langflow API Key",
|
|
102
|
-
info="Langflow API key for authentication when fetching MCP servers and tools.",
|
|
103
|
-
required=False,
|
|
104
|
-
advanced=True,
|
|
105
|
-
),
|
|
106
98
|
]
|
|
107
99
|
|
|
108
100
|
outputs = [
|
|
@@ -161,19 +153,11 @@ class MCPToolsComponent(ComponentWithCache):
|
|
|
161
153
|
return self.tools, {"name": server_name, "config": server_config_from_value}
|
|
162
154
|
|
|
163
155
|
try:
|
|
164
|
-
async
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
token=None,
|
|
170
|
-
query_param=self.api_key,
|
|
171
|
-
header_param=None,
|
|
172
|
-
db=db,
|
|
173
|
-
)
|
|
174
|
-
else:
|
|
175
|
-
user_id, _ = await create_user_longterm_token(db)
|
|
176
|
-
current_user = await get_user_by_id(db, user_id)
|
|
156
|
+
async with session_scope() as db:
|
|
157
|
+
if not self.user_id:
|
|
158
|
+
msg = "User ID is required for fetching MCP tools."
|
|
159
|
+
raise ValueError(msg)
|
|
160
|
+
current_user = await get_user_by_id(db, self.user_id)
|
|
177
161
|
|
|
178
162
|
# Try to get server config from DB/API
|
|
179
163
|
server_config = await get_server(
|
|
@@ -184,39 +168,38 @@ class MCPToolsComponent(ComponentWithCache):
|
|
|
184
168
|
settings_service=get_settings_service(),
|
|
185
169
|
)
|
|
186
170
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
171
|
+
# If get_server returns empty but we have a config, use it
|
|
172
|
+
if not server_config and server_config_from_value:
|
|
173
|
+
server_config = server_config_from_value
|
|
174
|
+
|
|
175
|
+
if not server_config:
|
|
176
|
+
self.tools = []
|
|
177
|
+
return [], {"name": server_name, "config": server_config}
|
|
178
|
+
|
|
179
|
+
_, tool_list, tool_cache = await update_tools(
|
|
180
|
+
server_name=server_name,
|
|
181
|
+
server_config=server_config,
|
|
182
|
+
mcp_stdio_client=self.stdio_client,
|
|
183
|
+
mcp_sse_client=self.sse_client,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
self.tool_names = [tool.name for tool in tool_list if hasattr(tool, "name")]
|
|
187
|
+
self._tool_cache = tool_cache
|
|
188
|
+
self.tools = tool_list
|
|
189
|
+
# Cache the result using shared cache
|
|
190
|
+
cache_data = {
|
|
191
|
+
"tools": tool_list,
|
|
192
|
+
"tool_names": self.tool_names,
|
|
193
|
+
"tool_cache": tool_cache,
|
|
194
|
+
"config": server_config,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
# Safely update the servers cache
|
|
198
|
+
current_servers_cache = safe_cache_get(self._shared_component_cache, "servers", {})
|
|
199
|
+
if isinstance(current_servers_cache, dict):
|
|
200
|
+
current_servers_cache[server_name] = cache_data
|
|
201
|
+
safe_cache_set(self._shared_component_cache, "servers", current_servers_cache)
|
|
194
202
|
|
|
195
|
-
_, tool_list, tool_cache = await update_tools(
|
|
196
|
-
server_name=server_name,
|
|
197
|
-
server_config=server_config,
|
|
198
|
-
mcp_stdio_client=self.stdio_client,
|
|
199
|
-
mcp_sse_client=self.sse_client,
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
self.tool_names = [tool.name for tool in tool_list if hasattr(tool, "name")]
|
|
203
|
-
self._tool_cache = tool_cache
|
|
204
|
-
self.tools = tool_list
|
|
205
|
-
# Cache the result using shared cache
|
|
206
|
-
cache_data = {
|
|
207
|
-
"tools": tool_list,
|
|
208
|
-
"tool_names": self.tool_names,
|
|
209
|
-
"tool_cache": tool_cache,
|
|
210
|
-
"config": server_config,
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
# Safely update the servers cache
|
|
214
|
-
current_servers_cache = safe_cache_get(self._shared_component_cache, "servers", {})
|
|
215
|
-
if isinstance(current_servers_cache, dict):
|
|
216
|
-
current_servers_cache[server_name] = cache_data
|
|
217
|
-
safe_cache_set(self._shared_component_cache, "servers", current_servers_cache)
|
|
218
|
-
|
|
219
|
-
return tool_list, {"name": server_name, "config": server_config}
|
|
220
203
|
except (TimeoutError, asyncio.TimeoutError) as e:
|
|
221
204
|
msg = f"Timeout updating tool list: {e!s}"
|
|
222
205
|
logger.exception(msg)
|
|
@@ -225,6 +208,8 @@ class MCPToolsComponent(ComponentWithCache):
|
|
|
225
208
|
msg = f"Error updating tool list: {e!s}"
|
|
226
209
|
logger.exception(msg)
|
|
227
210
|
raise ValueError(msg) from e
|
|
211
|
+
else:
|
|
212
|
+
return tool_list, {"name": server_name, "config": server_config}
|
|
228
213
|
|
|
229
214
|
async def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:
|
|
230
215
|
"""Toggle the visibility of connection-specific fields based on the selected mode."""
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
4
|
+
import contextlib
|
|
3
5
|
import hashlib
|
|
4
6
|
import json
|
|
5
7
|
import re
|
|
@@ -14,6 +16,7 @@ from cryptography.fernet import InvalidToken
|
|
|
14
16
|
from langchain_chroma import Chroma
|
|
15
17
|
from loguru import logger
|
|
16
18
|
|
|
19
|
+
from langflow.base.data.kb_utils import get_knowledge_bases
|
|
17
20
|
from langflow.base.models.openai_constants import OPENAI_EMBEDDING_MODEL_NAMES
|
|
18
21
|
from langflow.custom import Component
|
|
19
22
|
from langflow.io import BoolInput, DataFrameInput, DropdownInput, IntInput, Output, SecretStrInput, StrInput, TableInput
|
|
@@ -21,7 +24,8 @@ from langflow.schema.data import Data
|
|
|
21
24
|
from langflow.schema.dotdict import dotdict # noqa: TC001
|
|
22
25
|
from langflow.schema.table import EditMode
|
|
23
26
|
from langflow.services.auth.utils import decrypt_api_key, encrypt_api_key
|
|
24
|
-
from langflow.services.
|
|
27
|
+
from langflow.services.database.models.user.crud import get_user_by_id
|
|
28
|
+
from langflow.services.deps import get_settings_service, get_variable_service, session_scope
|
|
25
29
|
|
|
26
30
|
HUGGINGFACE_MODEL_NAMES = ["sentence-transformers/all-MiniLM-L6-v2", "sentence-transformers/all-mpnet-base-v2"]
|
|
27
31
|
COHERE_MODEL_NAMES = ["embed-english-v3.0", "embed-multilingual-v3.0"]
|
|
@@ -43,6 +47,10 @@ class KBIngestionComponent(Component):
|
|
|
43
47
|
icon = "database"
|
|
44
48
|
name = "KBIngestion"
|
|
45
49
|
|
|
50
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
51
|
+
super().__init__(*args, **kwargs)
|
|
52
|
+
self._cached_kb_path: Path | None = None
|
|
53
|
+
|
|
46
54
|
@dataclass
|
|
47
55
|
class NewKnowledgeBaseInput:
|
|
48
56
|
functionality: str = "create"
|
|
@@ -76,7 +84,7 @@ class KBIngestionComponent(Component):
|
|
|
76
84
|
display_name="API Key",
|
|
77
85
|
info="Provider API key for embedding model",
|
|
78
86
|
required=True,
|
|
79
|
-
load_from_db=
|
|
87
|
+
load_from_db=False,
|
|
80
88
|
),
|
|
81
89
|
},
|
|
82
90
|
},
|
|
@@ -91,11 +99,7 @@ class KBIngestionComponent(Component):
|
|
|
91
99
|
display_name="Knowledge",
|
|
92
100
|
info="Select the knowledge to load data from.",
|
|
93
101
|
required=True,
|
|
94
|
-
options=[
|
|
95
|
-
str(d.name) for d in KNOWLEDGE_BASES_ROOT_PATH.iterdir() if not d.name.startswith(".") and d.is_dir()
|
|
96
|
-
]
|
|
97
|
-
if KNOWLEDGE_BASES_ROOT_PATH.exists()
|
|
98
|
-
else [],
|
|
102
|
+
options=[],
|
|
99
103
|
refresh_button=True,
|
|
100
104
|
dialog_inputs=asdict(NewKnowledgeBaseInput()),
|
|
101
105
|
),
|
|
@@ -329,22 +333,23 @@ class KBIngestionComponent(Component):
|
|
|
329
333
|
|
|
330
334
|
return metadata
|
|
331
335
|
|
|
332
|
-
def _create_vector_store(
|
|
336
|
+
async def _create_vector_store(
|
|
333
337
|
self, df_source: pd.DataFrame, config_list: list[dict[str, Any]], embedding_model: str, api_key: str
|
|
334
338
|
) -> None:
|
|
335
339
|
"""Create vector store following Local DB component pattern."""
|
|
336
340
|
try:
|
|
337
341
|
# Set up vector store directory
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
342
|
+
vector_store_dir = await self._kb_path()
|
|
343
|
+
if not vector_store_dir:
|
|
344
|
+
msg = "Knowledge base path is not set. Please create a new knowledge base first."
|
|
345
|
+
raise ValueError(msg)
|
|
341
346
|
vector_store_dir.mkdir(parents=True, exist_ok=True)
|
|
342
347
|
|
|
343
348
|
# Create embeddings model
|
|
344
349
|
embedding_function = self._build_embeddings(embedding_model, api_key)
|
|
345
350
|
|
|
346
351
|
# Convert DataFrame to Data objects (following Local DB pattern)
|
|
347
|
-
data_objects = self._convert_df_to_data_objects(df_source, config_list)
|
|
352
|
+
data_objects = await self._convert_df_to_data_objects(df_source, config_list)
|
|
348
353
|
|
|
349
354
|
# Create vector store
|
|
350
355
|
chroma = Chroma(
|
|
@@ -367,16 +372,18 @@ class KBIngestionComponent(Component):
|
|
|
367
372
|
except (OSError, ValueError, RuntimeError) as e:
|
|
368
373
|
self.log(f"Error creating vector store: {e}")
|
|
369
374
|
|
|
370
|
-
def _convert_df_to_data_objects(
|
|
375
|
+
async def _convert_df_to_data_objects(
|
|
376
|
+
self, df_source: pd.DataFrame, config_list: list[dict[str, Any]]
|
|
377
|
+
) -> list[Data]:
|
|
371
378
|
"""Convert DataFrame to Data objects for vector store."""
|
|
372
379
|
data_objects: list[Data] = []
|
|
373
380
|
|
|
374
381
|
# Set up vector store directory
|
|
375
|
-
|
|
382
|
+
kb_path = await self._kb_path()
|
|
376
383
|
|
|
377
384
|
# If we don't allow duplicates, we need to get the existing hashes
|
|
378
385
|
chroma = Chroma(
|
|
379
|
-
persist_directory=str(
|
|
386
|
+
persist_directory=str(kb_path),
|
|
380
387
|
collection_name=self.knowledge_base,
|
|
381
388
|
)
|
|
382
389
|
|
|
@@ -466,10 +473,34 @@ class KBIngestionComponent(Component):
|
|
|
466
473
|
# Check allowed characters (condition 3)
|
|
467
474
|
return re.match(r"^[a-zA-Z0-9_-]+$", name) is not None
|
|
468
475
|
|
|
476
|
+
async def _kb_path(self) -> Path | None:
|
|
477
|
+
# Check if we already have the path cached
|
|
478
|
+
cached_path = getattr(self, "_cached_kb_path", None)
|
|
479
|
+
if cached_path is not None:
|
|
480
|
+
return cached_path
|
|
481
|
+
|
|
482
|
+
# If not cached, compute it
|
|
483
|
+
async with session_scope() as db:
|
|
484
|
+
if not self.user_id:
|
|
485
|
+
msg = "User ID is required for fetching knowledge base path."
|
|
486
|
+
raise ValueError(msg)
|
|
487
|
+
current_user = await get_user_by_id(db, self.user_id)
|
|
488
|
+
if not current_user:
|
|
489
|
+
msg = f"User with ID {self.user_id} not found."
|
|
490
|
+
raise ValueError(msg)
|
|
491
|
+
kb_user = current_user.username
|
|
492
|
+
|
|
493
|
+
kb_root = self._get_kb_root()
|
|
494
|
+
|
|
495
|
+
# Cache the result
|
|
496
|
+
self._cached_kb_path = kb_root / kb_user / self.knowledge_base
|
|
497
|
+
|
|
498
|
+
return self._cached_kb_path
|
|
499
|
+
|
|
469
500
|
# ---------------------------------------------------------------------
|
|
470
501
|
# OUTPUT METHODS
|
|
471
502
|
# ---------------------------------------------------------------------
|
|
472
|
-
def build_kb_info(self) -> Data:
|
|
503
|
+
async def build_kb_info(self) -> Data:
|
|
473
504
|
"""Main ingestion routine → returns a dict with KB metadata."""
|
|
474
505
|
try:
|
|
475
506
|
# Get source DataFrame
|
|
@@ -479,11 +510,11 @@ class KBIngestionComponent(Component):
|
|
|
479
510
|
config_list = self._validate_column_config(df_source)
|
|
480
511
|
column_metadata = self._build_column_metadata(config_list, df_source)
|
|
481
512
|
|
|
482
|
-
# Prepare KB folder (using File Component patterns)
|
|
483
|
-
kb_root = self._get_kb_root()
|
|
484
|
-
kb_path = kb_root / self.knowledge_base
|
|
485
|
-
|
|
486
513
|
# Read the embedding info from the knowledge base folder
|
|
514
|
+
kb_path = await self._kb_path()
|
|
515
|
+
if not kb_path:
|
|
516
|
+
msg = "Knowledge base path is not set. Please create a new knowledge base first."
|
|
517
|
+
raise ValueError(msg)
|
|
487
518
|
metadata_path = kb_path / "embedding_metadata.json"
|
|
488
519
|
|
|
489
520
|
# If the API key is not provided, try to read it from the metadata file
|
|
@@ -506,7 +537,7 @@ class KBIngestionComponent(Component):
|
|
|
506
537
|
)
|
|
507
538
|
|
|
508
539
|
# Create vector store following Local DB component pattern
|
|
509
|
-
self._create_vector_store(df_source, config_list, embedding_model=embedding_model, api_key=api_key)
|
|
540
|
+
await self._create_vector_store(df_source, config_list, embedding_model=embedding_model, api_key=api_key)
|
|
510
541
|
|
|
511
542
|
# Save KB files (using File Component storage patterns)
|
|
512
543
|
self._save_kb_files(kb_path, config_list)
|
|
@@ -532,40 +563,77 @@ class KBIngestionComponent(Component):
|
|
|
532
563
|
self.status = f"❌ KB ingestion failed: {e}"
|
|
533
564
|
return Data(data={"error": str(e), "kb_name": self.knowledge_base})
|
|
534
565
|
|
|
535
|
-
def
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
566
|
+
async def _get_api_key_variable(self, field_value: dict[str, Any]):
|
|
567
|
+
async with session_scope() as db:
|
|
568
|
+
if not self.user_id:
|
|
569
|
+
msg = "User ID is required for fetching global variables."
|
|
570
|
+
raise ValueError(msg)
|
|
571
|
+
current_user = await get_user_by_id(db, self.user_id)
|
|
572
|
+
if not current_user:
|
|
573
|
+
msg = f"User with ID {self.user_id} not found."
|
|
574
|
+
raise ValueError(msg)
|
|
575
|
+
variable_service = get_variable_service()
|
|
576
|
+
|
|
577
|
+
# Process the api_key field variable
|
|
578
|
+
return await variable_service.get_variable(
|
|
579
|
+
user_id=current_user.id,
|
|
580
|
+
name=field_value["03_api_key"],
|
|
581
|
+
field="",
|
|
582
|
+
session=db,
|
|
583
|
+
)
|
|
548
584
|
|
|
549
|
-
def update_build_config(
|
|
585
|
+
async def update_build_config(
|
|
586
|
+
self,
|
|
587
|
+
build_config: dotdict,
|
|
588
|
+
field_value: Any,
|
|
589
|
+
field_name: str | None = None,
|
|
590
|
+
) -> dotdict:
|
|
550
591
|
"""Update build configuration based on provider selection."""
|
|
551
592
|
# Create a new knowledge base
|
|
552
593
|
if field_name == "knowledge_base":
|
|
594
|
+
async with session_scope() as db:
|
|
595
|
+
if not self.user_id:
|
|
596
|
+
msg = "User ID is required for fetching knowledge base list."
|
|
597
|
+
raise ValueError(msg)
|
|
598
|
+
current_user = await get_user_by_id(db, self.user_id)
|
|
599
|
+
if not current_user:
|
|
600
|
+
msg = f"User with ID {self.user_id} not found."
|
|
601
|
+
raise ValueError(msg)
|
|
602
|
+
kb_user = current_user.username
|
|
553
603
|
if isinstance(field_value, dict) and "01_new_kb_name" in field_value:
|
|
554
604
|
# Validate the knowledge base name - Make sure it follows these rules:
|
|
555
605
|
if not self.is_valid_collection_name(field_value["01_new_kb_name"]):
|
|
556
606
|
msg = f"Invalid knowledge base name: {field_value['01_new_kb_name']}"
|
|
557
607
|
raise ValueError(msg)
|
|
558
608
|
|
|
609
|
+
api_key = field_value.get("03_api_key", None)
|
|
610
|
+
with contextlib.suppress(Exception):
|
|
611
|
+
# If the API key is a variable, resolve it
|
|
612
|
+
api_key = await self._get_api_key_variable(field_value)
|
|
613
|
+
|
|
614
|
+
# Make sure api_key is a string
|
|
615
|
+
if not isinstance(api_key, str):
|
|
616
|
+
msg = "API key must be a string."
|
|
617
|
+
raise ValueError(msg)
|
|
618
|
+
|
|
559
619
|
# We need to test the API Key one time against the embedding model
|
|
560
|
-
embed_model = self._build_embeddings(
|
|
561
|
-
embedding_model=field_value["02_embedding_model"], api_key=field_value["03_api_key"]
|
|
562
|
-
)
|
|
620
|
+
embed_model = self._build_embeddings(embedding_model=field_value["02_embedding_model"], api_key=api_key)
|
|
563
621
|
|
|
564
|
-
# Try to generate a dummy embedding to validate the API key
|
|
565
|
-
|
|
622
|
+
# Try to generate a dummy embedding to validate the API key without blocking the event loop
|
|
623
|
+
try:
|
|
624
|
+
await asyncio.wait_for(
|
|
625
|
+
asyncio.to_thread(embed_model.embed_query, "test"),
|
|
626
|
+
timeout=10,
|
|
627
|
+
)
|
|
628
|
+
except TimeoutError as e:
|
|
629
|
+
msg = "Embedding validation timed out. Please verify network connectivity and key."
|
|
630
|
+
raise ValueError(msg) from e
|
|
631
|
+
except Exception as e:
|
|
632
|
+
msg = f"Embedding validation failed: {e!s}"
|
|
633
|
+
raise ValueError(msg) from e
|
|
566
634
|
|
|
567
635
|
# Create the new knowledge base directory
|
|
568
|
-
kb_path = KNOWLEDGE_BASES_ROOT_PATH / field_value["01_new_kb_name"]
|
|
636
|
+
kb_path = KNOWLEDGE_BASES_ROOT_PATH / kb_user / field_value["01_new_kb_name"]
|
|
569
637
|
kb_path.mkdir(parents=True, exist_ok=True)
|
|
570
638
|
|
|
571
639
|
# Save the embedding metadata
|
|
@@ -573,11 +641,16 @@ class KBIngestionComponent(Component):
|
|
|
573
641
|
self._save_embedding_metadata(
|
|
574
642
|
kb_path=kb_path,
|
|
575
643
|
embedding_model=field_value["02_embedding_model"],
|
|
576
|
-
api_key=
|
|
644
|
+
api_key=api_key,
|
|
577
645
|
)
|
|
578
646
|
|
|
579
647
|
# Update the knowledge base options dynamically
|
|
580
|
-
build_config["knowledge_base"]["options"] =
|
|
648
|
+
build_config["knowledge_base"]["options"] = await get_knowledge_bases(
|
|
649
|
+
KNOWLEDGE_BASES_ROOT_PATH,
|
|
650
|
+
user_id=self.user_id,
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
# If the selected knowledge base is not available, reset it
|
|
581
654
|
if build_config["knowledge_base"]["value"] not in build_config["knowledge_base"]["options"]:
|
|
582
655
|
build_config["knowledge_base"]["value"] = None
|
|
583
656
|
|