AI Agent commited on
Commit
d2aec69
·
1 Parent(s): 56cb286

Remove excess documentation and CLI scripts to keep HF repo lean

Browse files
NITDAA_ARCHITECTURE_DESIGN.md DELETED
@@ -1,32 +0,0 @@
1
- # Nitdaa Sibling Project - Architecture Design
2
- **Version 1.0 Public Release**
3
-
4
- ## 1. System Overview
5
- Nitdaa is the mobile-friendly MVP sibling to the full HealthExpert enterprise platform. It removes administrative overhead, heavy RBAC UI components, and complex multi-pane layouts in favor of a lean, mobile-first edge interface.
6
-
7
- ## 2. Core Differences from Base HealthExpert
8
- - **UI/UX**: Transitioned from a 3-pane desktop layout to a single-pane vertical mobile layout, featuring dynamic inline controls (e.g. the bottom input panel temporarily transforms into a 5-star & thumbs feedback interface after response generation).
9
- - **Admin Constraints**: Admin controls (Kill Switch, DB Purge) are entirely hidden in the HTML to prevent public tampering.
10
- - **Session Audit**: Uses `nitdaa_sessions.json` and `nitdaa_summary.json` for granular system telemetry rather than database-backed logs. Captures granular feedback (1-5 stars, thumbs, and text) instantly upon user action.
11
- - **Resumable Event Streaming**: The architecture uses an asynchronous Job ID system (`/api/query/start` and `/api/query/stream`) to survive mobile background disconnections.
12
- - **Concurrent RAG Isolation**: Vector and Graph retrieval pipelines operate inside a Thread Pool with thread-local database connections. Heavy PyTorch inference layers (CrossEncoder and SentenceTransformer) are strictly serialized via threading locks to prevent `OutOfMemory` crashes on resource-constrained infrastructure, while keeping I/O concurrency high.
13
-
14
- ## 3. Data Flow
15
- 1. **Startup & Sync**: Flask initializes. `sync_he_data()` runs asynchronously to pull the latest dataset from `Sam-max1/he-data`. If differences are found, it gracefully purges vector/graph databases and rebuilds. Concurrently, it performs a 2-way remote synchronization of logs with `Sam-max1/mat_data`.
16
- 2. **Security & Connection**: Client connections face an immediate Math CAPTCHA. All API calls pass through `Flask-Limiter` and Strict HTTP Content Security Policies.
17
- 3. **Ingestion**: If new files are detected, `process_document_pipeline` splits them into 512-token chunks, vectorizes them into ChromaDB, and extracts graph entities locally using `spaCy`.
18
- 4. **Retrieval (Concurrent)**: When a query hits `/api/query/start`, an asynchronous thread pool handles Tri-modal Hybrid Retrieval (Vector + BM25 + Graph) allowing multiple users to fetch context simultaneously.
19
- 5. **Synthesis & Feedback**: A Cross-Encoder reranks the chunks, and the context is fed into the local LLM generation queue. The frontend streams events using Server-Sent Events with offset recovery. Upon completion, users optionally submit ratings securely stored in `nitdaa_summary.json`.
20
-
21
- ## 4. Resource Allocation
22
- Nitdaa is tuned for the HuggingFace Spaces free tier:
23
- - **vCPU constraints**: Threads are explicitly clamped to prevent CPU thrashing.
24
- - **Vector Limit**: A hard limit of 10,000 chunks is enforced globally to prevent `OutOfMemory` crashes.
25
- - **Session Quotas**: Max 5 file uploads per session to prevent abuse.
26
-
27
- ## 5. Project Intelligence & Deployment
28
- - **HuggingFace Space**: [Sam-max1/nitdaa](https://huggingface.co/spaces/Sam-max1/nitdaa)
29
- - **HuggingFace ID**: `sam-max1`
30
- - **Authentication**: `HF_TOKEN` is provided via the system keyring for secure access to HuggingFace resources and pushing updates.
31
- - **Deployment Protocol**: Code base updates are pushed directly to the HuggingFace Space repository. Note: Core functionality should not be altered, deleted, or impaired unless explicitly requested.
32
- - **Minimalist Deployment**: Nitdaa must always contain only the bare minimum files required to run the application. Extraneous resources, local databases, or unneeded assets must be excluded from tracking to ensure only the bare minimum is pushed to HuggingFace.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
NITDAA_ARCHITECTURE_DESIGN.pdf DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:d06a069f37aa6efe19cc97d445336a38689930c94aabb1e059948b90238ed5c4
3
- size 72541
 
 
 
 
NITDAA_HEALTHEXPERT_USER_GUIDE.md DELETED
@@ -1,36 +0,0 @@
1
- # Nitdaa HealthExpert Mobile MVP - User Guide
2
- **Version 1.0 Public Release**
3
-
4
- ## Overview
5
- Nitdaa HealthExpert is a mobile-optimized, single-pane web interface designed to provide users with direct access to the HealthExpert AI knowledge base. Deployed on HuggingFace Spaces, Nitdaa brings the power of Agentic AI, Hybrid RAG, and Edge Compute into a sandboxed, responsive environment.
6
-
7
- ## 1. Accessing the Application
8
- - Navigate to the official public application link: [https://sam-max1-nitdaa.hf.space/](https://sam-max1-nitdaa.hf.space/)
9
- - **Security Check (CAPTCHA)**: Upon your first visit, a security modal will challenge you with a simple math problem (e.g. `5 + 3 = ?`). You must correctly solve this problem to unlock the application dashboard.
10
- - The application will then load automatically. Note that the first boot may take 3-5 minutes as the system pulls the latest knowledge datasets securely from the cloud.
11
- - Once the **Status Pill** in the top-right corner turns **Green** (Ready), the system is online.
12
-
13
- ## 2. Using the Dashboard
14
- The dashboard is designed for mobile efficiency.
15
- - **Top Header**: Displays the application branding ("Healthcare Policy Expert") and current status.
16
- - **Scrollable Output Window**: The large central area displays the conversation history. It automatically scrolls down as new answers are generated.
17
- - **Input Area**: Located at the bottom of the screen.
18
-
19
- ## 3. Asking Questions
20
- You can query the system in two ways:
21
- 1. **Quick Questions Dropdown**: Tap the dropdown menu above the text input to select a preset question. Selecting a question will automatically submit it to the AI.
22
- 2. **Manual Input**: Type your query directly into the text box and press the **Send** button.
23
-
24
- ## 4. Understanding Output
25
- When a question is submitted:
26
- - Your question will instantly appear in the output window.
27
- - The system will display a **"Generating answer.. (1.2s)"** timer.
28
- - Once context is retrieved from the Vector and Graph databases, the LLM will stream the Markdown-formatted answer back to the screen.
29
- - Analysis may take upto 300 seconds due to slow shared resources. Please keep patience.
30
- - **Providing Feedback**: Once an answer completes, the bottom input area will temporarily switch to a Feedback panel. You will see Thumbs Up (👍) / Thumbs Down (👎) buttons alongside a 5-star rating widget (where colors transition from red to green). Selecting an icon captures your feedback immediately. You may optionally enter text feedback and click **Submit**, or click **Cancel** to revert the panel back to the standard question box.
31
- - Responses remain preserved in the scrollable window for the duration of your session.
32
-
33
- ## 5. Security & Session Handling
34
- - **Multi-tenant Isolation**: Your conversation is strictly isolated from other users using an invisible `X-Session-Token`. Multiple users can query the system concurrently without interference.
35
- - **Resumable Mobile Sessions**: If your mobile device goes to sleep or you navigate to another app, the system safely buffers your answer. When you return, the streaming will seamlessly resume without missing a word.
36
- - **Session Expiration**: If the page is reloaded, or if you are inactive for an extended period, your session token resets, and previous context is cleared to ensure privacy.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
NITDAA_HEALTHEXPERT_USER_GUIDE.pdf DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:0107e7f398e33e944638cc391375987e0eaa82cfd50f2e15525c94be98fad98e
3
- size 65952
 
 
 
 
NITDAA_HF_DEPLOYMENT_GUIDE.md DELETED
@@ -1,36 +0,0 @@
1
- # Nitdaa Sibling Project - HF Deployment Guide
2
- **Version 1.0 Public Release**
3
-
4
- ## Overview
5
- This document outlines the specific steps and constraints required to deploy the Nitdaa mobile sibling to HuggingFace Spaces free tier.
6
-
7
- ## 1. Prerequisites
8
- - A HuggingFace account.
9
- - A HuggingFace Space configured with the **Docker** SDK.
10
- - Hardware allocation: Free Tier (2 vCPU, 16GB Disk, 12GB RAM).
11
-
12
- ## 2. HuggingFace Secrets Configuration
13
- To enable dynamic Dataset Synchronization, you must configure the following variable:
14
- - `HF_TOKEN`: A HuggingFace access token with "Read" and "Write" permissions to the `Sam-max1/he-data` and `Sam-max1/mat_data` datasets. This token is available via the system keyring or environment variables.
15
- - `ADMIN_MODE`: Set to `0` to completely lock down the UI for public usage.
16
-
17
- ## 2.1 Minimalist Deployment Rule
18
- Nitdaa must always contain **only the bare minimum files required** to run the application in HF and the bare minimum files to be pushed to HF. Exclude all extraneous assets, development scripts, or unnecessary documentation from the Hugging Face repository.
19
-
20
- ## 3. Deployment Steps
21
- 1. Clone the `nitdaa` folder locally.
22
- 2. Initialize a git repository pointing to your HF Space remote.
23
- 3. Use the HuggingFace CLI to upload large directories without timing out:
24
- ```bash
25
- huggingface-cli upload <your_username>/nitdaa . . --repo-type space
26
- ```
27
- 4. HuggingFace will automatically detect the `Dockerfile` in the root and begin building.
28
-
29
- ## 4. Cold Boot Times
30
- Due to the CPU-bound nature of the free tier:
31
- - **Docker Build**: ~5-8 minutes (first boot only).
32
- - **Dataset Sync**: Variable depending on the size of `he_data`.
33
- - **Model Load**: The `Qwen3.5-2B` GGUF model and PyTorch embeddings take ~1-2 minutes to spin up into memory on boot.
34
-
35
- ## 5. Security Safeguards
36
- Nitdaa implements headless API blocking. Any direct programmatic access to the REST endpoints (without proper CORS headers or session tokens) will automatically return `403 Forbidden` to prevent bot-net abuse on the public URL.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docker-compose.yml DELETED
@@ -1,29 +0,0 @@
1
- services:
2
- neo4j:
3
- image: neo4j:5.18-community
4
- container_name: healthexpert-neo4j
5
- ports:
6
- - "7474:7474" # Neo4j Browser UI
7
- - "7687:7687" # Bolt protocol
8
- environment:
9
- - NEO4J_AUTH=neo4j/healthexpert
10
- - NEO4J_server_memory_heap_initial__size=512m
11
- - NEO4J_server_memory_heap_max__size=1G
12
- - NEO4J_PLUGINS=["apoc"]
13
- - NEO4J_dbms_security_procedures_unrestricted=apoc.*
14
- - NEO4J_dbms_security_procedures_allowlist=apoc.*
15
- volumes:
16
- - neo4j_data:/data
17
- - neo4j_logs:/logs
18
- - neo4j_import:/var/lib/neo4j/import
19
- healthcheck:
20
- test: ["CMD", "wget", "-q", "--spider", "http://localhost:7474"]
21
- interval: 10s
22
- timeout: 5s
23
- retries: 10
24
- restart: unless-stopped
25
-
26
- volumes:
27
- neo4j_data:
28
- neo4j_logs:
29
- neo4j_import:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
healthexpert.py DELETED
@@ -1,111 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Document AI Expert — Standalone CLI
4
- =========================================
5
- Usage:
6
- python healthexpert.py ingest <file_path>
7
- python healthexpert.py query "<question>"
8
- python healthexpert.py list
9
- python healthexpert.py clear <source_name>
10
- python healthexpert.py status
11
- """
12
- from __future__ import annotations
13
- import sys, os
14
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
15
-
16
- import argparse
17
- import config
18
- from pipeline import vector_store, graph_store, embedder, document_loader, chunker
19
- from agents.crew import run_ingest_crew, run_query_crew
20
-
21
-
22
- def cmd_ingest(file_path: str) -> None:
23
- if not os.path.exists(file_path):
24
- print(f"[ERROR] File not found: {file_path}")
25
- sys.exit(1)
26
- print(f"[INGEST] Processing: {file_path}")
27
- result = run_ingest_crew(file_path)
28
- print(f"\n[RESULT]\n{result}")
29
-
30
-
31
- def cmd_query(question: str) -> None:
32
- if vector_store.count() == 0:
33
- print("[ERROR] No documents ingested. Run: python healthexpert.py ingest <file>")
34
- sys.exit(1)
35
- print(f"[QUERY] {question}\n")
36
- answer = run_query_crew(question)
37
- print("\n" + "=" * 60)
38
- print(answer)
39
- print("=" * 60)
40
-
41
-
42
- def cmd_list() -> None:
43
- docs = vector_store.list_documents()
44
- if not docs:
45
- print("[INFO] No documents ingested yet.")
46
- return
47
- print(f"\n{'SOURCE':<40} {'TYPE':<10}")
48
- print("-" * 52)
49
- for d in docs:
50
- print(f"{d['source']:<40} {d['file_type']:<10}")
51
- print(f"\nTotal: {len(docs)} document(s), {vector_store.count()} chunk(s)")
52
-
53
-
54
- def cmd_clear(source_name: str) -> None:
55
- n = vector_store.delete_document(source_name)
56
- graph_store.delete_source(source_name)
57
- print(f"[CLEAR] Deleted {n} chunks for '{source_name}'")
58
-
59
-
60
- def cmd_status() -> None:
61
- print("\n── Document AI Expert Status ──")
62
- print(f" LLM Endpoint : {config.LLM_BASE_URL}")
63
- print(f" LLM Model : {config.LLM_MODEL_ID}")
64
- print(f" Embedding : {config.EMBEDDING_MODEL} ({config.EMBEDDING_DEVICE})")
65
- print(f" Vector DB : {vector_store.count()} chunks [{config.WEAVIATE_URL}]")
66
- g = graph_store.get_stats()
67
- if g.get("available"):
68
- print(f" Graph DB : {g['nodes']} nodes, {g['relationships']} relationships")
69
- else:
70
- print(" Graph DB : OFFLINE (vector-only mode)")
71
- print()
72
-
73
-
74
- def main():
75
- parser = argparse.ArgumentParser(
76
- description="Document AI Expert — CLI",
77
- formatter_class=argparse.RawDescriptionHelpFormatter,
78
- epilog=__doc__,
79
- )
80
- sub = parser.add_subparsers(dest="command")
81
-
82
- p_ingest = sub.add_parser("ingest", help="Ingest a document")
83
- p_ingest.add_argument("file", help="Path to document file")
84
-
85
- p_query = sub.add_parser("query", help="Ask a question")
86
- p_query.add_argument("question", help="Question string")
87
-
88
- sub.add_parser("list", help="List ingested documents")
89
- sub.add_parser("status", help="Show system status")
90
-
91
- p_clear = sub.add_parser("clear", help="Remove a document")
92
- p_clear.add_argument("source", help="Source filename to remove")
93
-
94
- args = parser.parse_args()
95
-
96
- if args.command == "ingest":
97
- cmd_ingest(args.file)
98
- elif args.command == "query":
99
- cmd_query(args.question)
100
- elif args.command == "list":
101
- cmd_list()
102
- elif args.command == "clear":
103
- cmd_clear(args.source)
104
- elif args.command == "status":
105
- cmd_status()
106
- else:
107
- parser.print_help()
108
-
109
-
110
- if __name__ == "__main__":
111
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
manage_db.py DELETED
@@ -1,93 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Manage HealthExpert databases.
3
-
4
- Database plan:
5
- ┌─────────────────┬────────────────────────────────────────────────────┐
6
- │ Database │ Management │
7
- ├─────────────────┼────────────────────────────────────────────────────┤
8
- │ ChromaDB │ Embedded (in-process). No Docker container. │
9
- │ (Vector Store) │ Data stored at: data/chroma_db/ │
10
- │ │ Managed by: pipeline/vector_store.py │
11
- │ │ Use /api/admin/purge (UI) or wipe data/chroma_db/ │
12
- ├─────────────────┼────────────────────────────────────────────────────┤
13
- │ Kuzu │ Embedded (in-process). No Docker container. │
14
- │ (Graph DB) │ Data stored at: data/kuzu_db/ │
15
- │ │ Managed by: pipeline/graph_store.py │
16
- │ │ Use /api/admin/purge (UI) or wipe data/kuzu_db/ │
17
- └─────────────────┴────────────────────────────────────────────────────┘
18
-
19
- Usage:
20
- python manage_db.py # show status of all databases
21
- python manage_db.py -chroma # show ChromaDB data directory info
22
- python manage_db.py -kuzu # show Kuzu data directory info
23
- """
24
- import argparse
25
- import sys
26
- import os
27
- from pathlib import Path
28
-
29
- CHROMA_DIR = Path(__file__).parent / "data" / "chroma_db"
30
- KUZU_DIR = Path(__file__).parent / "data" / "kuzu_db"
31
-
32
-
33
- def _dir_info(path: Path) -> dict:
34
- exists = path.exists()
35
- size_mb = 0.0
36
- file_count = 0
37
- if exists:
38
- for f in path.rglob("*"):
39
- if f.is_file():
40
- size_mb += f.stat().st_size / 1024 ** 2
41
- file_count += 1
42
- return {"exists": exists, "path": str(path), "size_mb": size_mb, "files": file_count}
43
-
44
-
45
- def chroma_status() -> None:
46
- info = _dir_info(CHROMA_DIR)
47
- print("\n── ChromaDB (Vector Store) ──────────────────────────────────")
48
- print(f" Type : Embedded (in-process, no server)")
49
- print(f" Data dir : {info['path']}")
50
- if info["exists"]:
51
- print(f" Status : PRESENT ({info['files']} files, {info['size_mb']:.2f} MB)")
52
- else:
53
- print(f" Status : EMPTY (will be created on first ingest)")
54
- print("─" * 60)
55
-
56
- def kuzu_status() -> None:
57
- info = _dir_info(KUZU_DIR)
58
- print("\n── Kuzu (Graph DB) ──────────────────────────────────────────")
59
- print(f" Type : Embedded (in-process, no server)")
60
- print(f" Data dir : {info['path']}")
61
- if info["exists"]:
62
- print(f" Status : PRESENT ({info['files']} files, {info['size_mb']:.2f} MB)")
63
- else:
64
- print(f" Status : EMPTY (will be created on first ingest)")
65
- print("─" * 60)
66
-
67
-
68
- def status() -> None:
69
- print("\n══════════════════════════════════════════════════════════════")
70
- print(" HealthExpert — Database Status")
71
- print("══════════════════════════════════════════════════════════════")
72
- chroma_status()
73
- kuzu_status()
74
- print("")
75
-
76
-
77
- def main() -> None:
78
- parser = argparse.ArgumentParser(
79
- description="Manage HealthExpert databases",
80
- formatter_class=argparse.RawDescriptionHelpFormatter,
81
- epilog=__doc__,
82
- )
83
- parser.add_argument("-chroma", action="store_true", help="Show ChromaDB info")
84
- parser.add_argument("-kuzu", action="store_true", help="Show Kuzu DB info")
85
- args = parser.parse_args()
86
-
87
- if args.chroma: chroma_status()
88
- elif args.kuzu: kuzu_status()
89
- else: status()
90
-
91
-
92
- if __name__ == "__main__":
93
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
manage_llm.py DELETED
@@ -1,159 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Manage HealthExpert LLM Microservices.
3
-
4
- This script allows you to spin up, spin down, and check the status
5
- of the local LLM inference endpoints (gen_llm.py and embed_llm.py).
6
-
7
- Usage:
8
- python manage_llm.py status # show status of LLM servers
9
- python manage_llm.py up # start both LLM servers
10
- python manage_llm.py down # stop both LLM servers
11
- """
12
- import argparse
13
- import subprocess
14
- import time
15
- import os
16
- import sys
17
- from pathlib import Path
18
- import urllib.request
19
-
20
- GEN_PORT = 8002
21
- EMBED_PORT = 8003
22
- BASE_DIR = Path(__file__).parent.resolve()
23
-
24
- def _check_port(port: int) -> bool:
25
- """Check if a port is actively listening by making a simple HTTP request."""
26
- try:
27
- # Just a healthcheck to see if server responds, we expect 404 or 200
28
- req = urllib.request.Request(f"http://127.0.0.1:{port}/")
29
- urllib.request.urlopen(req, timeout=1)
30
- return True
31
- except urllib.error.URLError as e:
32
- # If it's an HTTPError (e.g. 404 Not Found), the server is alive
33
- if hasattr(e, 'code'):
34
- return True
35
- # ConnectionRefusedError usually means nothing is listening
36
- return False
37
- except Exception:
38
- return False
39
-
40
- def _kill_port(port: int) -> None:
41
- """Kill any process listening on the given port."""
42
- try:
43
- # Using fuser to kill processes on the port
44
- subprocess.run(["fuser", "-k", f"{port}/tcp"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
45
- except FileNotFoundError:
46
- try:
47
- # Fallback to lsof if fuser is not available
48
- pids = subprocess.check_output(["lsof", "-t", f"-i:{port}"]).decode().strip().split('\n')
49
- for pid in pids:
50
- if pid:
51
- subprocess.run(["kill", "-9", pid])
52
- except Exception:
53
- pass
54
-
55
- def status() -> None:
56
- """Show the status of the LLM servers."""
57
- gen_alive = _check_port(GEN_PORT)
58
- embed_alive = _check_port(EMBED_PORT)
59
-
60
- print("\n══════════════════════════════════════════════════════════════")
61
- print(" HealthExpert — LLM Microservices Status")
62
- print("══════════════════════════════════════════════════════════════")
63
-
64
- print("\n── Generation LLM (agents/gen_llm.py) ───────────────────────")
65
- print(f" Port : {GEN_PORT}")
66
- print(f" Status : {'🟢 RUNNING' if gen_alive else '🔴 STOPPED'}")
67
-
68
- print("\n── Embedding LLM (agents/embed_llm.py) ──────────────────────")
69
- print(f" Port : {EMBED_PORT}")
70
- print(f" Status : {'🟢 RUNNING' if embed_alive else '🔴 STOPPED'}")
71
- print("─" * 62)
72
- print("")
73
-
74
- def down() -> None:
75
- """Stop the LLM servers."""
76
- print("Stopping LLM services...")
77
- _kill_port(GEN_PORT)
78
- _kill_port(EMBED_PORT)
79
-
80
- # Also kill by script name as a fallback
81
- subprocess.run(["pkill", "-f", "agents/gen_llm.py"], stderr=subprocess.DEVNULL)
82
- subprocess.run(["pkill", "-f", "agents/embed_llm.py"], stderr=subprocess.DEVNULL)
83
-
84
- time.sleep(1)
85
- print("LLM services stopped.")
86
-
87
- def up(hf_mode: bool = False) -> None:
88
- """Start the LLM servers."""
89
- gen_alive = _check_port(GEN_PORT)
90
- embed_alive = _check_port(EMBED_PORT)
91
-
92
- if gen_alive and embed_alive:
93
- print("Both LLM services are already running.")
94
- return
95
-
96
- print("Starting LLM services...")
97
-
98
- env = os.environ.copy()
99
- if hf_mode:
100
- env["HF_MODE"] = "1"
101
- print("Running in HF CPU mode (HF_MODE=1)")
102
-
103
- # Start Embed LLM
104
- if not embed_alive:
105
- print(f"[1/2] Starting embed_llm on port {EMBED_PORT}...")
106
- subprocess.Popen(
107
- [sys.executable, str(BASE_DIR / "agents" / "embed_llm.py")],
108
- cwd=BASE_DIR,
109
- stdout=subprocess.DEVNULL,
110
- stderr=subprocess.DEVNULL,
111
- start_new_session=True,
112
- env=env
113
- )
114
- else:
115
- print(f"[1/2] embed_llm is already running on port {EMBED_PORT}.")
116
-
117
- # Start Gen LLM
118
- if not gen_alive:
119
- print(f"[2/2] Starting gen_llm on port {GEN_PORT}...")
120
- subprocess.Popen(
121
- [sys.executable, str(BASE_DIR / "agents" / "gen_llm.py")],
122
- cwd=BASE_DIR,
123
- stdout=subprocess.DEVNULL,
124
- stderr=subprocess.DEVNULL,
125
- start_new_session=True,
126
- env=env
127
- )
128
- else:
129
- print(f"[2/2] gen_llm is already running on port {GEN_PORT}.")
130
-
131
- print("\nWaiting for services to initialize...")
132
- time.sleep(3)
133
- status()
134
-
135
-
136
- def main() -> None:
137
- parser = argparse.ArgumentParser(
138
- description="Manage HealthExpert LLM microservices",
139
- formatter_class=argparse.RawDescriptionHelpFormatter,
140
- epilog=__doc__,
141
- )
142
-
143
- # Optional positional argument for the command
144
- parser.add_argument("command", nargs="?", choices=["up", "down", "status"], default="status",
145
- help="Action to perform (default: status)")
146
- parser.add_argument("-hf", "--hf", action="store_true", help="Start servers in HuggingFace/CPU mode")
147
-
148
- args = parser.parse_args()
149
-
150
- if args.command == "up":
151
- up(hf_mode=args.hf)
152
- elif args.command == "down":
153
- down()
154
- else:
155
- status()
156
-
157
-
158
- if __name__ == "__main__":
159
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pytest.ini DELETED
@@ -1,17 +0,0 @@
1
- [pytest]
2
- testpaths = tests
3
- python_files = test_*.py
4
- python_classes = Test*
5
- python_functions = test_*
6
- markers =
7
- unit: Unit tests for individual components
8
- integration: Integration tests for workflows
9
- slow: Tests that take longer than 1 second
10
- requires_gpu: Tests that require GPU
11
- requires_services: Tests that require running services
12
- addopts =
13
- -v
14
- --tb=short
15
- --strict-markers
16
- -ra
17
- minversion = 7.0