Skip to content

Commit 9bf338d

Browse files
committed
feat(rag): implement guideline ingestion and update UI
Details: - Added platform indicator to Slack draft cards header. - Created ingest_guideline_task to download and vectorize PDFs from Slack URLs. - Wired modal_upload_guideline in feedback.py to trigger the new worker. - Updated .gitignore for new storage paths.
1 parent f72c60b commit 9bf338d

File tree

5 files changed

+117
-6
lines changed

5 files changed

+117
-6
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
knowledge_base/doctor_style
33
knowledge_base/medical_guidelines
44
test_output.txt
5+
AGENTS.md
6+
n8nac-config.json
7+
workflows
58
# Python
69
__pycache__/
710
*.py[cod]

backend/api/routes/feedback.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from backend.config.lexicon import SLACK_UI
99
from backend.config.settings import settings
1010
from backend.workers.tasks.generate_draft import generate_draft_task
11+
from backend.workers.tasks.ingest_guideline import ingest_guideline_task
1112
from backend.workers.tasks.publish_post import publish_post_task
1213
from slack_app.utils.block_builder import (
1314
build_app_home,
@@ -275,10 +276,29 @@ async def slack_interactions(request: Request):
275276

276277
logger.info("slack_file_uploaded", user_id=user_id, file_name=file_name)
277278

278-
# TODO: Тут ми створимо таск `ingest_document_task`, який буде
279-
# скачувати файл за file_url (використовуючи Slack Token),
280-
# парсити PDF/TXT та векторизувати його у Qdrant.
281-
# await ingest_document_task.kiq(file_url=file_url, file_name=file_name)
279+
# --- СЦЕНАРІЙ 3: Завантаження гайдлайну ---
280+
elif callback_id == "modal_upload_guideline":
281+
files = (
282+
state_values.get("block_file_upload", {})
283+
.get("input_file", {})
284+
.get("files", [])
285+
)
286+
if not files:
287+
return Response(status_code=400)
288+
289+
file_info = files[0]
290+
file_url = file_info.get("url_private_download") # РОЗКОМЕНТОВАНО
291+
file_name = file_info.get("name")
292+
293+
logger.info("slack_file_uploaded", user_id=user_id, file_name=file_name)
294+
295+
await ingest_guideline_task.kiq(file_url=file_url, file_name=file_name)
296+
297+
return Response(
298+
content=json.dumps({"response_action": "clear"}),
299+
media_type="application/json",
300+
status_code=200,
301+
)
282302

283303
# Закриваємо модалку
284304
return Response(

backend/workers/tasks/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .generate_draft import generate_draft_task
2+
from .ingest_guideline import ingest_guideline_task
23
from .publish_post import publish_post_task
34

45
# from .scheduled_post import scheduled_post_task
@@ -7,6 +8,7 @@
78
__all__ = [
89
"generate_draft_task",
910
"publish_post_task",
10-
# "scheduled_post_task",
11+
# "scheduled_post_task",
1112
"vectorize_published_post_task",
13+
"ingest_guideline_task",
1214
]
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from pathlib import Path
2+
3+
import httpx
4+
import structlog
5+
from llama_index.core import SimpleDirectoryReader, StorageContext, VectorStoreIndex
6+
from llama_index.core.settings import Settings
7+
from llama_index.embeddings.openai import ( # type: ignore[reportMissingTypeStubs]
8+
OpenAIEmbedding,
9+
)
10+
from llama_index.vector_stores.qdrant import ( # type: ignore[reportMissingTypeStubs]
11+
QdrantVectorStore,
12+
)
13+
from qdrant_client import AsyncQdrantClient
14+
15+
from backend.config.settings import settings
16+
from backend.workers.broker import broker
17+
18+
logger = structlog.get_logger()
19+
20+
# Налаштування OpenAI Embedding
21+
_raw_key = settings.OPENAI_API_KEY
22+
openai_key: str = (
23+
_raw_key.get_secret_value()
24+
if hasattr(_raw_key, "get_secret_value")
25+
else str(_raw_key)
26+
)
27+
Settings.embed_model = OpenAIEmbedding(
28+
model=settings.OPENAI_MODEL_EMBEDDING, api_key=openai_key
29+
)
30+
31+
32+
@broker.task(task_name="ingest_guideline_task", timeout=300)
33+
async def ingest_guideline_task(file_url: str, file_name: str) -> None:
34+
logger.info("ingest_guideline_started", file_name=file_name)
35+
36+
try:
37+
# 1. Завантаження файлу зі Slack
38+
save_dir = Path("knowledge_base/medical_guidelines")
39+
save_dir.mkdir(parents=True, exist_ok=True)
40+
file_path = save_dir / file_name
41+
42+
slack_token = (
43+
settings.SLACK_BOT_TOKEN.get_secret_value()
44+
if hasattr(settings.SLACK_BOT_TOKEN, "get_secret_value")
45+
else settings.SLACK_BOT_TOKEN
46+
)
47+
48+
async with httpx.AsyncClient() as client:
49+
response = await client.get(
50+
file_url, headers={"Authorization": f"Bearer {slack_token}"}
51+
)
52+
response.raise_for_status()
53+
with open(file_path, "wb") as f:
54+
f.write(response.content)
55+
56+
logger.info("file_downloaded_successfully", path=str(file_path))
57+
58+
# 2. Векторизація через LlamaIndex
59+
# Використовуємо SimpleDirectoryReader для конкретного файлу
60+
documents = SimpleDirectoryReader(input_files=[str(file_path)]).load_data()
61+
62+
# Підключаємось до Qdrant (колекція medical_knowledge)
63+
qdrant_url = getattr(settings, "QDRANT_URL", "http://127.0.0.1:6333")
64+
aclient = AsyncQdrantClient(url=qdrant_url)
65+
vector_store = QdrantVectorStore(
66+
aclient=aclient, collection_name="medical_knowledge"
67+
)
68+
storage_context = StorageContext.from_defaults(vector_store=vector_store)
69+
70+
# Створюємо індекс і вставляємо документи
71+
index = VectorStoreIndex.from_vector_store( # type: ignore[reportUnknownMemberType]
72+
vector_store=vector_store, storage_context=storage_context
73+
)
74+
for doc in documents:
75+
doc.metadata["source"] = file_name
76+
await index.ainsert(doc)
77+
78+
logger.info(
79+
"guideline_ingestion_success", file_name=file_name, chunks=len(documents)
80+
)
81+
82+
# TODO: Додати notify_slack_on_complete для сповіщення користувача про успіх (опціонально)
83+
84+
except Exception as e:
85+
logger.error("guideline_ingestion_failed", file_name=file_name, error=str(e))
86+
raise

slack_app/utils/block_builder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def build_draft_card(
1212
"type": "header",
1313
"text": {
1414
"type": "plain_text",
15-
"text": SLACK_UI["draft_ready_header"].format(topic=topic),
15+
"text": f"{SLACK_UI['draft_ready_header'].format(topic=topic)} | 📢 {platform.upper()}",
1616
"emoji": True,
1717
},
1818
},

0 commit comments

Comments
 (0)