dineshb commited on
Commit
dd39e2e
Β·
verified Β·
1 Parent(s): b7570a0

Upload 8 files

Browse files
Files changed (8) hide show
  1. DEPLOYMENT.md +120 -0
  2. Dockerfile +21 -8
  3. README.md +289 -112
  4. app.py +288 -12
  5. assets/.gitkeep +0 -1
  6. assets/rag_architecture.svg +179 -0
  7. eval_sample.csv +4 -0
  8. requirements.txt +6 -3
DEPLOYMENT.md ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DocuChat_AI Deployment Guide
2
+
3
+ This guide covers deploying DocuChat_AI as a Streamlit app on Hugging Face Spaces, with optional private access, OCR support, and evaluation workflows.
4
+
5
+ ## 1. Recommended Hugging Face Setup
6
+
7
+ For the standard app:
8
+
9
+ ```text
10
+ SDK: Streamlit
11
+ Python: 3.10
12
+ Hardware: CPU Basic or higher
13
+ ```
14
+
15
+ Required files:
16
+
17
+ ```text
18
+ app.py
19
+ README.md
20
+ requirements.txt
21
+ runtime.txt
22
+ .gitattributes
23
+ assets/
24
+ ```
25
+
26
+ Required secret:
27
+
28
+ ```text
29
+ GROQ_API_KEY=your_groq_key_here
30
+ ```
31
+
32
+ Optional private access secret:
33
+
34
+ ```text
35
+ APP_PASSWORD=your_private_app_password
36
+ ```
37
+
38
+ If `APP_PASSWORD` is set, the app shows a password screen before users can access the workspace. If it is not set, the app runs in public demo mode.
39
+
40
+ ## 2. OCR Deployment Notes
41
+
42
+ DocuChat_AI includes an OCR fallback for scanned PDFs using:
43
+
44
+ ```text
45
+ pypdfium2
46
+ pytesseract
47
+ Pillow
48
+ Tesseract OCR binary
49
+ ```
50
+
51
+ Python packages are installed from `requirements.txt`, but the Tesseract system binary may not be available in a normal Streamlit Space.
52
+
53
+ For best OCR support, deploy using the included `Dockerfile`, which installs:
54
+
55
+ ```text
56
+ tesseract-ocr
57
+ poppler-utils
58
+ ```
59
+
60
+ Recommended OCR setup:
61
+
62
+ ```text
63
+ SDK: Docker
64
+ Hardware: CPU Upgrade if processing larger scanned PDFs
65
+ ```
66
+
67
+ If OCR dependencies are missing, the app fails gracefully and still works for normal text-based PDFs, DOCX, and TXT files.
68
+
69
+ ## 3. Evaluation Dashboard
70
+
71
+ The Evaluation tab supports labeled RAG testing with CSV files.
72
+
73
+ Required CSV columns:
74
+
75
+ ```text
76
+ question,expected_answer
77
+ ```
78
+
79
+ Optional column:
80
+
81
+ ```text
82
+ expected_source
83
+ ```
84
+
85
+ Example:
86
+
87
+ ```csv
88
+ question,expected_answer,expected_source
89
+ What is the contract deadline?,The deadline is 15 June 2026.,contract.pdf page 3
90
+ Who is the policy owner?,The policy owner is the HR department.,policy.pdf
91
+ ```
92
+
93
+ Metrics shown:
94
+
95
+ ```text
96
+ Correctness score
97
+ Faithfulness score
98
+ Confidence score
99
+ Citation coverage
100
+ Retrieved chunks
101
+ ```
102
+
103
+ The correctness and faithfulness scores are LLM-judged and should be treated as practical evaluation signals, not formal benchmark results.
104
+
105
+ ## 4. Environment Variables
106
+
107
+ | Variable | Required | Description |
108
+ | --- | --- | --- |
109
+ | `GROQ_API_KEY` | Yes | API key for Groq LLM inference |
110
+ | `APP_PASSWORD` | No | Enables private password-protected app access |
111
+
112
+ ## 5. Production Recommendations
113
+
114
+ - Use Hugging Face Secrets for all keys.
115
+ - Use Docker mode if OCR is important.
116
+ - Keep uploads temporary unless you add explicit user accounts and storage consent.
117
+ - Add `APP_PASSWORD` for portfolio demos shared with recruiters.
118
+ - Use the Evaluation tab with a small labeled test set before demos.
119
+ - Use CPU Upgrade for larger PDFs or frequent OCR use.
120
+
Dockerfile CHANGED
@@ -1,23 +1,36 @@
 
1
  FROM python:3.10-slim
2
 
 
3
  WORKDIR /app
4
 
5
- ENV PYTHONDONTWRITEBYTECODE=1
6
- ENV PYTHONUNBUFFERED=1
7
- ENV PIP_NO_CACHE_DIR=1
8
-
9
- RUN apt-get update && apt-get install -y --no-install-recommends \
10
  build-essential \
11
  curl \
 
 
 
 
12
  && rm -rf /var/lib/apt/lists/*
13
 
 
14
  COPY requirements.txt .
15
- RUN pip install --upgrade pip && pip install -r requirements.txt
16
 
 
 
 
 
17
  COPY . .
18
 
 
 
 
 
19
  EXPOSE 8501
20
 
21
- HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health || exit 1
 
22
 
23
- CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
 
1
+ # 1. Use an official lightweight Python image
2
  FROM python:3.10-slim
3
 
4
+ # 2. Set the working directory inside the container
5
  WORKDIR /app
6
 
7
+ # 3. Install system dependencies for FAISS and PDF processing
8
+ RUN apt-get update && apt-get install -y \
 
 
 
9
  build-essential \
10
  curl \
11
+ software-properties-common \
12
+ git \
13
+ tesseract-ocr \
14
+ poppler-utils \
15
  && rm -rf /var/lib/apt/lists/*
16
 
17
+ # 4. Copy only requirements first to leverage Docker caching
18
  COPY requirements.txt .
 
19
 
20
+ # 5. Install Python dependencies
21
+ RUN pip3 install --no-cache-dir -r requirements.txt
22
+
23
+ # 6. Copy the rest of your application code
24
  COPY . .
25
 
26
+ # 7. Create the temporary directory for document uploads
27
+ RUN mkdir -p temp_docs && chmod 777 temp_docs
28
+
29
+ # 8. Expose the default Streamlit port
30
  EXPOSE 8501
31
 
32
+ # 9. Healthcheck to ensure the app is running
33
+ HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
34
 
35
+ # 10. Start the application
36
+ ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
  title: DocuChat_AI
3
- emoji: πŸ’»
4
  colorFrom: blue
5
  colorTo: indigo
6
  sdk: streamlit
@@ -11,181 +11,358 @@ pinned: false
11
  license: mit
12
  ---
13
 
14
- # Document Intelligence RAG Assistant
15
 
16
- Upload documents, generate summaries, and ask grounded questions using Retrieval-Augmented Generation.
17
 
18
- ## Demo
 
 
19
 
20
- [Live Demo](YOUR_HUGGING_FACE_SPACE_URL)
 
 
 
 
 
 
 
 
 
 
21
 
22
- Screenshots:
 
 
 
 
 
 
 
23
 
24
- - `assets/app_home.png`
25
- - `assets/document_processing.png`
26
- - `assets/chat_with_sources.png`
27
 
28
- ## Problem Statement
29
 
30
- Professionals often need to quickly understand long PDFs, policies, resumes, contracts, research papers, reports, meeting notes, and business documents. Reading every page manually is slow, and generic chatbots can hallucinate when they are not grounded in the source material.
31
 
32
- Document Intelligence RAG Assistant helps users upload documents, generate structured summaries, ask questions, inspect source citations, and export useful outputs from a clean Streamlit interface.
33
 
34
- ## Key Features
35
 
36
- - PDF, TXT, and DOCX upload
37
- - Document summarization with multiple modes
38
- - Question answering over uploaded files
39
- - FAISS vector search
40
- - HuggingFace sentence-transformer embeddings
41
- - Groq / LLM-powered generation
42
- - Source citations with file, page, chunk, and preview text
43
- - Chat history within the current session
44
- - Export summary and chat history as Markdown
45
- - Streamlit user interface
46
- - Hugging Face Spaces deployment support
47
 
48
- ## Architecture
49
 
50
- The app follows a lightweight RAG pipeline:
51
 
52
- Document Upload -> Text Extraction -> Chunking -> Embeddings -> FAISS Vector Store -> Retriever -> LLM -> Grounded Answer with Sources
53
 
54
- ```mermaid
55
- flowchart LR
56
- A[Upload Documents] --> B[Extract Text]
57
- B --> C[Split into Chunks]
58
- C --> D[Generate Embeddings]
59
- D --> E[FAISS Vector Store]
60
- E --> F[Retriever]
61
- F --> G[LLM]
62
- G --> H[Answer with Citations]
63
- ```
 
 
 
 
 
 
 
 
 
64
 
65
- ## Tech Stack
66
 
67
- - Python
68
- - Streamlit
69
- - LangChain
70
- - FAISS
71
- - HuggingFace sentence-transformers
72
- - Groq API / LLM provider
73
- - PyPDF and document loaders
74
- - Docker / Hugging Face Spaces
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- ## Local Setup
77
 
78
- 1. Clone the repository:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
  ```bash
81
- git clone YOUR_REPOSITORY_URL
82
- cd YOUR_REPOSITORY_NAME
83
  ```
84
 
85
- 2. Create and activate a virtual environment:
86
 
87
  ```bash
88
  python -m venv .venv
89
- .venv\Scripts\activate
90
  ```
91
 
92
- On macOS/Linux:
93
 
94
  ```bash
95
- python -m venv .venv
 
 
 
96
  source .venv/bin/activate
97
  ```
98
 
99
- 3. Install dependencies:
100
 
101
  ```bash
102
  pip install -r requirements.txt
103
  ```
104
 
105
- 4. Create a `.env` file:
 
 
106
 
107
  ```env
108
- GROQ_API_KEY=your_api_key_here
109
  ```
110
 
111
- 5. Run the app:
 
 
 
 
112
 
113
  ```bash
114
  streamlit run app.py
115
  ```
116
 
117
- ## Hugging Face Spaces Deployment
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
  1. Create a new Hugging Face Space.
120
- 2. Choose Streamlit as the Space SDK.
121
- 3. Upload `app.py`, `README.md`, `requirements.txt`, `runtime.txt`, `.gitattributes`, and the `assets/` folder. `Dockerfile` is included for GitHub/Docker users, but a normal Streamlit Space does not require it.
122
- 4. Add `GROQ_API_KEY` under Space Settings -> Secrets.
123
- 5. Confirm `app.py` is the entry point.
124
- 6. Restart the Space if dependencies are updated.
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
- The app is designed to run on Hugging Face Spaces CPU. It uses CPU-compatible embeddings and avoids GPU-only dependencies.
127
 
128
- ## Usage Guide
129
 
130
- 1. Upload one or more PDF, TXT, or DOCX files.
131
- 2. Click **Process documents** to extract text, chunk content, generate embeddings, and build a FAISS index.
132
- 3. Choose a summary mode and click **Generate summary**.
133
- 4. Ask document-specific questions in the chat tab.
134
- 5. Open source citations under each answer to inspect retrieved evidence.
135
- 6. Export the summary or chat history as Markdown.
 
136
 
137
- ## Example Questions
 
 
138
 
139
- - "Summarize this document in 5 bullet points."
140
- - "What are the key risks mentioned?"
141
- - "Extract all dates, people, and organizations."
142
- - "What are the main recommendations?"
143
- - "Explain the document for a non-technical audience."
144
- - "What evidence supports this answer?"
145
 
146
- ## Project Structure
 
 
147
 
148
  ```text
149
- .
150
- β”œβ”€β”€ app.py
151
- β”œβ”€β”€ README.md
152
- β”œβ”€β”€ requirements.txt
153
- β”œβ”€β”€ runtime.txt
154
- β”œβ”€β”€ Dockerfile
155
- β”œβ”€β”€ .gitattributes
156
- └── assets/
157
- └── .gitkeep
158
  ```
159
 
160
- ## Privacy and Security
161
 
162
- - API keys are read from Hugging Face Space secrets, environment variables, or a password input field.
163
- - Uploaded documents are processed through temporary files and are not intentionally stored by the app.
164
- - The FAISS index and chat history live only in the active Streamlit session.
165
- - Users should avoid uploading highly sensitive documents to public demo deployments.
166
 
167
- ## Limitations
 
 
 
 
168
 
169
- - Quality depends on uploaded document text extraction.
170
- - Scanned PDFs may require OCR, which is not included in this version.
171
- - LLM answers are grounded in retrieved chunks, but users should verify important outputs.
172
- - Free Hugging Face Spaces may have CPU and memory limits.
173
- - Very large documents may need smaller files or reduced chunk settings.
174
 
175
- ## Future Improvements
176
 
177
- - OCR for scanned PDFs
178
- - Multi-document comparison
179
- - Persistent vector database option
180
- - User authentication
181
- - Retrieval quality evaluation metrics
182
- - CSV, Excel, and HTML support
183
- - Better citation highlighting inside source documents
184
 
185
- ## Why This Project Matters
186
 
187
- This project demonstrates practical skills across RAG architecture, document NLP, vector search, LLM integration, Streamlit product UI, Hugging Face deployment, and AI-assisted workflow automation. It is relevant for Data Analyst, Data Scientist, AI Engineer, and RAG Engineer portfolios because it turns unstructured documents into searchable, explainable, and exportable insights.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
- ## License
190
 
191
- MIT License.
 
1
  ---
2
  title: DocuChat_AI
3
+ emoji: πŸ“„
4
  colorFrom: blue
5
  colorTo: indigo
6
  sdk: streamlit
 
11
  license: mit
12
  ---
13
 
14
+ <div align="center">
15
 
16
+ <img src="https://capsule-render.vercel.app/api?type=waving&height=230&color=0:2563EB,100:14B8A6&text=DocuChat_AI&fontColor=ffffff&fontSize=56&fontAlignY=35&desc=Document%20Intelligence%20RAG%20Assistant&descAlignY=58&descSize=20&animation=fadeIn" />
17
 
18
+ <p>
19
+ <img src="https://readme-typing-svg.herokuapp.com?font=Fira+Code&size=17&duration=2800&pause=1000&color=2563EB&center=true&vCenter=true&width=800&lines=Chat+with+PDF%2C+DOCX+and+TXT+documents;AI-powered+summaries+with+source-backed+answers;RAG+pipeline+using+LangChain%2C+FAISS+and+Groq;Built+with+Streamlit+for+fast+document+intelligence" alt="Typing SVG" />
20
+ </p>
21
 
22
+ <p>
23
+ <a href="https://huggingface.co/spaces/dineshb/DocuChat_AI">
24
+ <img src="https://img.shields.io/badge/Live_Demo-Hugging_Face-FFD21E?style=for-the-badge&logo=huggingface&logoColor=black" alt="Live Demo" />
25
+ </a>
26
+ <a href="https://github.com/dineshbarri/DocuChat_AI">
27
+ <img src="https://img.shields.io/badge/GitHub-Repository-181717?style=for-the-badge&logo=github&logoColor=white" alt="GitHub Repository" />
28
+ </a>
29
+ <a href="LICENSE">
30
+ <img src="https://img.shields.io/badge/License-MIT-2EA44F?style=for-the-badge" alt="MIT License" />
31
+ </a>
32
+ </p>
33
 
34
+ <p>
35
+ <img src="https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white" />
36
+ <img src="https://img.shields.io/badge/Streamlit-FF4B4B?style=for-the-badge&logo=streamlit&logoColor=white" />
37
+ <img src="https://img.shields.io/badge/LangChain-1C3C3C?style=for-the-badge" />
38
+ <img src="https://img.shields.io/badge/FAISS-Vector_Search-2563EB?style=for-the-badge" />
39
+ <img src="https://img.shields.io/badge/Groq-LLM_API-F55036?style=for-the-badge" />
40
+ <img src="https://img.shields.io/badge/HuggingFace-Embeddings-FFD21E?style=for-the-badge&logo=huggingface&logoColor=black" />
41
+ </p>
42
 
43
+ **DocuChat_AI is a high-end AI document chatbot that lets users upload documents, generate summaries, extract insights, and ask grounded questions with source citations.**
 
 
44
 
45
+ </div>
46
 
47
+ ---
48
 
49
+ ## πŸš€ Live Application
50
 
51
+ Try the app here:
52
 
53
+ ### πŸ‘‰ [Launch DocuChat_AI on Hugging Face](https://huggingface.co/spaces/dineshb/DocuChat_AI)
 
 
 
 
 
 
 
 
 
 
54
 
55
+ DocuChat_AI is designed for students, analysts, recruiters, researchers, founders, and professionals who need to understand long documents quickly without manually reading every page.
56
 
57
+ ---
58
 
59
+ ## πŸ–ΌοΈ Application Screenshots
60
 
61
+ > Add your screenshots inside the `assets/` folder using the exact filenames below. GitHub will automatically render them in this README.
62
+
63
+ <div align="center">
64
+
65
+ ### 1. Premium Home Workspace
66
+ <img src="assets/app_home.png" width="90%" alt="DocuChat_AI home workspace" />
67
+
68
+ ### 2. Document Processing and Indexing
69
+ <img src="assets/document_processing.png" width="90%" alt="Document upload and processing workflow" />
70
+
71
+ ### 3. Chat with Source Citations
72
+ <img src="assets/chat_with_sources.png" width="90%" alt="Document question answering with citations" />
73
+
74
+ ### 4. Summary and Analysis Tools
75
+ <img src="assets/summary_tools.png" width="90%" alt="Summary and analysis tabs" />
76
+
77
+ </div>
78
+
79
+ ---
80
 
81
+ ## πŸ“Œ Project Overview
82
 
83
+ Most professionals deal with long PDFs, reports, resumes, policy documents, contracts, research papers, business notes, and technical files. Reading everything manually takes time, and generic chatbots can hallucinate when they are not grounded in the source document.
84
+
85
+ **DocuChat_AI solves this using Retrieval-Augmented Generation (RAG).**
86
+
87
+ The app extracts text from uploaded documents, splits it into searchable chunks, converts those chunks into embeddings, stores them in a FAISS vector index, retrieves the most relevant context for each question, and generates answers using an LLM while keeping responses grounded in the uploaded content.
88
+
89
+ ---
90
+
91
+ ## ✨ Key Features
92
+
93
+ | Feature | Description |
94
+ | --- | --- |
95
+ | πŸ“„ Multi-document upload | Supports PDF, DOCX, and TXT files |
96
+ | 🧠 RAG-based Q&A | Ask natural-language questions about uploaded documents |
97
+ | πŸ“‹ Smart summaries | Generate executive summaries, study notes, email briefs, and key takeaways |
98
+ | πŸ”Ž Source citations | View retrieved document chunks used to answer each question |
99
+ | 🧩 Workflow tabs | Chat, Summaries, Extract, Analyze, and Deliverables |
100
+ | ⚑ Quick action buttons | One-click prompts for risks, decisions, action items, FAQs, and presentation outlines |
101
+ | πŸ“Š Index stats | Track processed files, pages, chunks, and processing time |
102
+ | 🧾 Document classification | LLM-based classification into Research Paper, Contract, CV, Invoice, Policy, Report, or Other |
103
+ | 🧬 Entity extraction | Extract people, organizations, dates, money values, and locations |
104
+ | ⚠️ Risk detection | Surface legal risks, missing information, deadlines, and action items |
105
+ | πŸ“ˆ RAG quality metrics | Show retrieved chunks, confidence score, citation coverage, and context usage |
106
+ | πŸ§ͺ Evaluation dashboard | Upload labeled test questions and score correctness, faithfulness, and citation coverage |
107
+ | πŸ” OCR-ready processing | Optional scanned PDF OCR support for Docker deployments |
108
+ | πŸ” Optional authentication | Protect demos with an `APP_PASSWORD` environment secret |
109
+ | πŸ’¬ Chat history | Maintains conversation context during the active session |
110
+ | ⬇️ Export support | Export chat history as Markdown |
111
+ | πŸ” Secure key input | Uses Hugging Face Secrets, environment variables, or password input |
112
+
113
+ ---
114
+
115
+ ## 🧠 What Users Can Do
116
+
117
+ - Upload a long PDF and ask: **"What are the main risks in this document?"**
118
+ - Turn a document into an **executive summary**
119
+ - Extract **names, dates, metrics, definitions, and action items**
120
+ - Generate **meeting notes, FAQs, email briefs, and presentation outlines**
121
+ - Ask follow-up questions using conversation history
122
+ - Inspect citations to understand where the answer came from
123
+
124
+ ---
125
 
126
+ ## πŸ—οΈ RAG Architecture
127
 
128
+ <div align="center">
129
+ <img src="assets/rag_architecture.svg" width="100%" alt="DocuChat AI RAG architecture diagram" />
130
+ </div>
131
+
132
+ ### Pipeline Flow
133
+
134
+ 1. **Document Upload** - User uploads PDF, DOCX, or TXT files.
135
+ 2. **Text Extraction** - The app extracts readable text using document loaders.
136
+ 3. **Chunking** - Text is split into manageable chunks with overlap.
137
+ 4. **Embedding Generation** - Chunks are converted into vector embeddings.
138
+ 5. **Vector Search** - FAISS retrieves the most relevant chunks for each query.
139
+ 6. **LLM Generation** - Groq-powered models generate answers using retrieved context.
140
+ 7. **Citation Display** - The app shows source previews used for the answer.
141
+
142
+ ---
143
+
144
+ ## πŸ› οΈ Tech Stack
145
+
146
+ | Layer | Tools |
147
+ | --- | --- |
148
+ | Frontend | Streamlit |
149
+ | LLM | Groq API |
150
+ | RAG Framework | LangChain |
151
+ | Vector Database | FAISS |
152
+ | Embeddings | Hugging Face Sentence Transformers |
153
+ | Document Loading | PyPDF, DOCX2TXT, LangChain loaders |
154
+ | Text Splitting | LangChain text splitters + tiktoken |
155
+ | Deployment | Hugging Face Spaces |
156
+ | Language | Python |
157
+
158
+ ---
159
+
160
+ ## πŸ“ Repository Structure
161
+
162
+ ```text
163
+ DocuChat_AI/
164
+ β”‚
165
+ β”œβ”€β”€ app.py # Main Streamlit application
166
+ β”œβ”€β”€ README.md # Project documentation
167
+ β”œβ”€β”€ requirements.txt # Python dependencies
168
+ β”œβ”€β”€ runtime.txt # Python runtime version for Hugging Face
169
+ β”œβ”€β”€ Dockerfile # Optional container deployment file
170
+ β”œβ”€β”€ DEPLOYMENT.md # Deployment, auth, OCR, and evaluation guide
171
+ β”œβ”€β”€ eval_sample.csv # Sample labeled evaluation file
172
+ β”œβ”€β”€ LICENSE # MIT License
173
+ β”œβ”€β”€ .gitignore # Local files and secrets ignored by Git
174
+ β”œβ”€β”€ .gitattributes # Hugging Face / Git file handling
175
+ β”‚
176
+ └── assets/
177
+ β”œβ”€β”€ app_home.png # Home UI screenshot
178
+ β”œβ”€β”€ document_processing.png # Upload and processing screenshot
179
+ β”œβ”€β”€ chat_with_sources.png # Chat answer and citations screenshot
180
+ └── summary_tools.png # Summary / analysis tools screenshot
181
+ ```
182
+
183
+ ---
184
+
185
+ ## βš™οΈ Local Setup
186
+
187
+ ### 1. Clone the Repository
188
 
189
  ```bash
190
+ git clone https://github.com/dineshbarri/DocuChat_AI.git
191
+ cd DocuChat_AI
192
  ```
193
 
194
+ ### 2. Create a Virtual Environment
195
 
196
  ```bash
197
  python -m venv .venv
 
198
  ```
199
 
200
+ Activate it:
201
 
202
  ```bash
203
+ # Windows
204
+ .venv\Scripts\activate
205
+
206
+ # macOS / Linux
207
  source .venv/bin/activate
208
  ```
209
 
210
+ ### 3. Install Dependencies
211
 
212
  ```bash
213
  pip install -r requirements.txt
214
  ```
215
 
216
+ ### 4. Add Your Groq API Key
217
+
218
+ Create a `.env` file:
219
 
220
  ```env
221
+ GROQ_API_KEY=your_groq_api_key_here
222
  ```
223
 
224
+ You can create a Groq API key from:
225
+
226
+ πŸ‘‰ [https://console.groq.com/](https://console.groq.com/)
227
+
228
+ ### 5. Run the App
229
 
230
  ```bash
231
  streamlit run app.py
232
  ```
233
 
234
+ Open the local URL shown in your terminal.
235
+
236
+ ---
237
+
238
+ ## πŸ”‘ How to Get a Groq API Key
239
+
240
+ 1. Go to [Groq Console](https://console.groq.com/)
241
+ 2. Sign in or create an account
242
+ 3. Open **API Keys**
243
+ 4. Create a new API key
244
+ 5. Copy the key
245
+ 6. Paste it into the app sidebar or save it in `.env`
246
+
247
+ For Hugging Face Spaces, add it as a secret:
248
+
249
+ ```text
250
+ GROQ_API_KEY = your_key_here
251
+ ```
252
+
253
+ ---
254
+
255
+ ## 🚒 Hugging Face Deployment
256
 
257
  1. Create a new Hugging Face Space.
258
+ 2. Choose **Streamlit** as the SDK.
259
+ 3. Upload:
260
+ - `app.py`
261
+ - `README.md`
262
+ - `requirements.txt`
263
+ - `runtime.txt`
264
+ - `.gitattributes`
265
+ - `assets/`
266
+ 4. Add `GROQ_API_KEY` in **Settings -> Secrets**.
267
+ 5. Optional: add `APP_PASSWORD` in **Settings -> Secrets** to protect the app.
268
+ 6. Restart the Space after dependency changes.
269
+
270
+ For scanned PDF OCR, use the included Dockerfile because OCR needs system packages such as Tesseract.
271
+
272
+ See [DEPLOYMENT.md](DEPLOYMENT.md) for the full deployment guide.
273
+
274
+ ---
275
 
276
+ ## πŸ’‘ Example Questions
277
 
278
+ Try these after uploading and processing a document:
279
 
280
+ ```text
281
+ Summarize this document in 6 crisp bullet points.
282
+ ```
283
+
284
+ ```text
285
+ What are the key risks, warnings, or limitations?
286
+ ```
287
 
288
+ ```text
289
+ Extract all names, dates, numbers, and important terms.
290
+ ```
291
 
292
+ ```text
293
+ Create an action-item checklist from this document.
294
+ ```
 
 
 
295
 
296
+ ```text
297
+ Explain this document like I am new to the topic.
298
+ ```
299
 
300
  ```text
301
+ Create a presentation outline from this document.
 
 
 
 
 
 
 
 
302
  ```
303
 
304
+ ---
305
 
306
+ ## πŸ” Privacy and Security
 
 
 
307
 
308
+ - Uploaded files are processed through temporary files.
309
+ - The app does not intentionally store user documents.
310
+ - The FAISS index and chat history live inside the active Streamlit session.
311
+ - API keys should be stored in environment variables or Hugging Face Secrets.
312
+ - Avoid uploading highly confidential documents to public demo environments.
313
 
314
+ ---
 
 
 
 
315
 
316
+ ## ⚠️ Limitations
317
 
318
+ - Scanned PDF OCR requires optional OCR dependencies and works best in Docker mode.
319
+ - Very large documents may be limited by CPU and memory on free hosting.
320
+ - Complex tables may not extract perfectly from PDFs.
321
+ - LLM answers should be reviewed for critical legal, medical, financial, or compliance use cases.
322
+ - Citation quality depends on document extraction and retrieval quality.
323
+
324
+ ---
325
 
326
+ ## πŸš€ Future Improvements
327
 
328
+ - Multi-document comparison mode
329
+ - Persistent user workspaces
330
+ - Better page-level citation highlighting
331
+ - CSV, Excel, PPTX, and web page support
332
+ - Reranking for improved retrieval accuracy
333
+ - Authentication and private document libraries
334
+ - Downloadable summary reports
335
+ - Evaluation dashboard for retrieval quality
336
+
337
+ ---
338
+
339
+ ## πŸ§‘β€πŸ’» Author
340
+
341
+ <div align="center">
342
+
343
+ ### Built by **Dinesh Barri**
344
+
345
+ Data Analyst | AI Automation Engineer | Founder @ Plemdo AI
346
+
347
+ <p>
348
+ <a href="https://dineshbarri.dev">
349
+ <img src="https://img.shields.io/badge/Portfolio-dineshbarri.dev-FF5722?style=for-the-badge&logo=vercel&logoColor=white" />
350
+ </a>
351
+ <a href="https://github.com/dineshbarri">
352
+ <img src="https://img.shields.io/badge/GitHub-dineshbarri-181717?style=for-the-badge&logo=github&logoColor=white" />
353
+ </a>
354
+ <a href="https://www.linkedin.com/in/dinesh-barri-7654b010b/">
355
+ <img src="https://img.shields.io/badge/LinkedIn-Dinesh%20Barri-0077B5?style=for-the-badge&logo=linkedin&logoColor=white" />
356
+ </a>
357
+ <a href="mailto:dineshbarri1997@gmail.com">
358
+ <img src="https://img.shields.io/badge/Email-dineshbarri1997%40gmail.com-D14836?style=for-the-badge&logo=gmail&logoColor=white" />
359
+ </a>
360
+ </p>
361
+
362
+ </div>
363
+
364
+ ---
365
 
366
+ ## ⭐ Support
367
 
368
+ If you found this project useful, consider giving it a star on GitHub. It helps the project reach more developers, recruiters, and AI builders.
app.py CHANGED
@@ -3,6 +3,8 @@ import time
3
  import json
4
  import hashlib
5
  import tempfile
 
 
6
  import streamlit as st
7
  from dotenv import load_dotenv
8
  from datetime import datetime
@@ -15,6 +17,7 @@ from langchain_community.vectorstores import FAISS
15
  from langchain_community.document_loaders import PyPDFLoader, TextLoader, Docx2txtLoader
16
  from langchain_huggingface import HuggingFaceEmbeddings
17
  from langchain_core.messages import HumanMessage, AIMessage
 
18
  from langchain.chains import create_history_aware_retriever
19
  from langchain_core.output_parsers import StrOutputParser
20
 
@@ -23,7 +26,7 @@ from langchain_core.output_parsers import StrOutputParser
23
  # ─────────────────────────────────────────────
24
  load_dotenv()
25
  st.set_page_config(
26
- page_title="DocuChat_AI",
27
  page_icon="πŸ“„",
28
  layout="wide",
29
  initial_sidebar_state="expanded"
@@ -290,6 +293,9 @@ for key, default in {
290
  "doc_stats": {},
291
  "doc_intelligence": {},
292
  "rag_metrics": {},
 
 
 
293
  "last_file_hash": "",
294
  "full_raw_text": "", # BUG FIX: Stores text so summary can use it without reloading
295
  "pending_query": "",
@@ -336,7 +342,62 @@ def compute_files_hash(files) -> str:
336
  h.update(str(f.size).encode())
337
  return h.hexdigest()
338
 
339
- def load_documents(files) -> list:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  """Safely load documents using tempfile (No local folder clutter)."""
341
  docs = []
342
  for file in files:
@@ -354,6 +415,12 @@ def load_documents(files) -> list:
354
  loader = TextLoader(temp_path, encoding="utf-8")
355
 
356
  loaded = loader.load()
 
 
 
 
 
 
357
  docs.extend(loaded[:MAX_PAGES])
358
  except Exception as e:
359
  st.error(f"⚠️ Error loading `{file.name}`: {e}")
@@ -363,7 +430,7 @@ def load_documents(files) -> list:
363
  return docs
364
 
365
  def export_chat() -> str:
366
- lines = [f"# DocuChat_AI Export β€” {datetime.now().strftime('%Y-%m-%d %H:%M')}\n"]
367
  for m in st.session_state.messages:
368
  role = "πŸ‘€ User" if m["role"] == "user" else "πŸ€– Assistant"
369
  lines.append(f"**{role}:** {m['content']}\n")
@@ -483,6 +550,148 @@ def calculate_rag_metrics(retrieved_docs, top_k: int, context_chars: int, answer
483
  "generated_at": datetime.now().strftime("%H:%M:%S"),
484
  }
485
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  def queue_prompt(prompt: str):
487
  st.session_state.pending_query = prompt
488
 
@@ -496,7 +705,7 @@ def render_hero():
496
  <div class="hero-layout">
497
  <div>
498
  <div class="hero-kicker">Document intelligence workspace</div>
499
- <h1>DocuChat_AI (Document Intelligence RAG Assistant)</h1>
500
  <p>Upload documents, generate summaries, extract insights, and ask grounded questions with source citations.</p>
501
  </div>
502
  <aside class="signature-card">
@@ -537,9 +746,9 @@ def render_capability_cards():
537
  """
538
  <div class="feature-grid">
539
  <div class="feature-card"><strong>Ask Anything</strong><span>Chat with PDFs, Word files, and text documents using grounded answers.</span></div>
540
- <div class="feature-card"><strong>Instant Briefs</strong><span>Generate executive summaries, study notes, and stakeholder-ready updates.</span></div>
541
- <div class="feature-card"><strong>Deep Extraction</strong><span>Pull out action items, risks, decisions, dates, names, and definitions.</span></div>
542
- <div class="feature-card"><strong>Source Citations</strong><span>Inspect the retrieved document chunks used to answer each question.</span></div>
543
  </div>
544
  """,
545
  unsafe_allow_html=True,
@@ -627,6 +836,60 @@ def render_intelligence_panel():
627
  q4.metric("Context Used", f"{metrics.get('context_utilization', 0)}%")
628
  st.caption(f"Last evaluated at {metrics.get('generated_at', 'N/A')}. These are lightweight heuristic metrics for visibility, not formal benchmark scores.")
629
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630
  def render_workspace():
631
  render_hero()
632
  render_status_panel()
@@ -635,12 +898,13 @@ def render_workspace():
635
  st.markdown('<div class="section-title">Document command center</div>', unsafe_allow_html=True)
636
  st.markdown('<p class="section-copy">Choose a workflow or start with a suggested prompt. Each button sends a ready-made instruction to the assistant.</p>', unsafe_allow_html=True)
637
 
638
- chat_tab, summary_tab, extract_tab, analyze_tab, intel_tab, deliver_tab = st.tabs([
639
  "Chat",
640
  "Summaries",
641
  "Extract",
642
  "Analyze",
643
  "Intelligence",
 
644
  "Deliverables",
645
  ])
646
 
@@ -681,6 +945,10 @@ def render_workspace():
681
  st.markdown("#### AI document intelligence")
682
  render_intelligence_panel()
683
 
 
 
 
 
684
  with deliver_tab:
685
  st.markdown("#### Ready-to-use outputs")
686
  c1, c2, c3 = st.columns(3)
@@ -694,8 +962,14 @@ def render_workspace():
694
  # ─────────────────────────────────────────────
695
  # SIDEBAR UI
696
  # ─────────────────────────────────────────────
 
 
697
  with st.sidebar:
698
- st.title("πŸ“„ DocuChat_AI")
 
 
 
 
699
 
700
  if st.session_state.vectors:
701
  st.success("βœ… Vector DB Ready", icon="🟒")
@@ -715,6 +989,8 @@ with st.sidebar:
715
  with st.expander("βš™οΈ Advanced Settings"):
716
  temperature = st.slider("Temperature", 0.0, 1.0, 0.3, 0.05)
717
  top_k = st.slider("Retrieved Chunks (Top-K)", 2, 10, 4)
 
 
718
 
719
  st.divider()
720
 
@@ -736,7 +1012,7 @@ with st.sidebar:
736
  elif not uploaded_files:
737
  st.warning("⚠️ Upload files first.")
738
  else:
739
- file_hash = compute_files_hash(uploaded_files)
740
  force_reprocess = (file_hash != st.session_state.last_file_hash)
741
 
742
  if not force_reprocess and st.session_state.vectors:
@@ -747,7 +1023,7 @@ with st.sidebar:
747
  t0 = time.time()
748
 
749
  st.write("πŸ“₯ Loading files into memory...")
750
- raw_docs = load_documents(uploaded_files)
751
 
752
  if not raw_docs:
753
  status.update(label="No content found!", state="error")
@@ -799,7 +1075,7 @@ with st.sidebar:
799
 
800
  # BUG FIX: Use the saved text instead of raw_docs
801
  if not st.session_state.full_raw_text:
802
- temp_docs = load_documents(uploaded_files)
803
  st.session_state.full_raw_text = " ".join([d.page_content for d in temp_docs])
804
 
805
  full_text = st.session_state.full_raw_text[:6000]
 
3
  import json
4
  import hashlib
5
  import tempfile
6
+ import csv
7
+ import io
8
  import streamlit as st
9
  from dotenv import load_dotenv
10
  from datetime import datetime
 
17
  from langchain_community.document_loaders import PyPDFLoader, TextLoader, Docx2txtLoader
18
  from langchain_huggingface import HuggingFaceEmbeddings
19
  from langchain_core.messages import HumanMessage, AIMessage
20
+ from langchain_core.documents import Document
21
  from langchain.chains import create_history_aware_retriever
22
  from langchain_core.output_parsers import StrOutputParser
23
 
 
26
  # ─────────────────────────────────────────────
27
  load_dotenv()
28
  st.set_page_config(
29
+ page_title="DocuChat_AT",
30
  page_icon="πŸ“„",
31
  layout="wide",
32
  initial_sidebar_state="expanded"
 
293
  "doc_stats": {},
294
  "doc_intelligence": {},
295
  "rag_metrics": {},
296
+ "eval_results": [],
297
+ "eval_summary": {},
298
+ "auth_ok": False,
299
  "last_file_hash": "",
300
  "full_raw_text": "", # BUG FIX: Stores text so summary can use it without reloading
301
  "pending_query": "",
 
342
  h.update(str(f.size).encode())
343
  return h.hexdigest()
344
 
345
+ def require_authentication():
346
+ app_password = os.getenv("APP_PASSWORD", "").strip()
347
+ if not app_password:
348
+ st.session_state.auth_ok = True
349
+ return
350
+ if st.session_state.auth_ok:
351
+ return
352
+
353
+ st.markdown("### πŸ” Private Workspace")
354
+ st.caption("This deployment is protected. Enter the app password to continue.")
355
+ password = st.text_input("App Password", type="password", placeholder="Enter workspace password")
356
+ if st.button("Unlock Workspace", type="primary"):
357
+ if password == app_password:
358
+ st.session_state.auth_ok = True
359
+ st.rerun()
360
+ else:
361
+ st.error("Incorrect password.")
362
+ st.stop()
363
+
364
+ def ocr_pdf_pages(pdf_path: str, source_name: str, max_pages: int = 8) -> list:
365
+ """Optional OCR for scanned PDFs. Requires pypdfium2, pytesseract, Pillow, and Tesseract binary."""
366
+ try:
367
+ import pypdfium2 as pdfium
368
+ import pytesseract
369
+ except Exception as e:
370
+ raise RuntimeError("OCR packages are not installed. Install pypdfium2, pytesseract, and Pillow.") from e
371
+
372
+ docs = []
373
+ pdf = pdfium.PdfDocument(pdf_path)
374
+ page_count = min(len(pdf), max_pages)
375
+ for page_index in range(page_count):
376
+ page = pdf[page_index]
377
+ bitmap = page.render(scale=2.0)
378
+ image = bitmap.to_pil()
379
+ text = pytesseract.image_to_string(image)
380
+ if text.strip():
381
+ docs.append(
382
+ Document(
383
+ page_content=text,
384
+ metadata={
385
+ "source": source_name,
386
+ "page": page_index,
387
+ "extraction": "ocr",
388
+ },
389
+ )
390
+ )
391
+ return docs
392
+
393
+ def normalize_source_metadata(docs: list, source_name: str, file_type: str, extraction: str = "text") -> list:
394
+ for doc in docs:
395
+ doc.metadata["source"] = source_name
396
+ doc.metadata["file_type"] = file_type
397
+ doc.metadata.setdefault("extraction", extraction)
398
+ return docs
399
+
400
+ def load_documents(files, use_ocr: bool = False, ocr_page_limit: int = 8) -> list:
401
  """Safely load documents using tempfile (No local folder clutter)."""
402
  docs = []
403
  for file in files:
 
415
  loader = TextLoader(temp_path, encoding="utf-8")
416
 
417
  loaded = loader.load()
418
+ loaded = normalize_source_metadata(loaded, file.name, ext, "text")
419
+ if ext == ".pdf" and use_ocr:
420
+ extracted_chars = sum(len(doc.page_content.strip()) for doc in loaded)
421
+ if extracted_chars < 250:
422
+ st.write(f"πŸ” Running OCR for scanned PDF: {file.name}")
423
+ loaded = ocr_pdf_pages(temp_path, file.name, max_pages=ocr_page_limit)
424
  docs.extend(loaded[:MAX_PAGES])
425
  except Exception as e:
426
  st.error(f"⚠️ Error loading `{file.name}`: {e}")
 
430
  return docs
431
 
432
  def export_chat() -> str:
433
+ lines = [f"# DocuChat_AT Export β€” {datetime.now().strftime('%Y-%m-%d %H:%M')}\n"]
434
  for m in st.session_state.messages:
435
  role = "πŸ‘€ User" if m["role"] == "user" else "πŸ€– Assistant"
436
  lines.append(f"**{role}:** {m['content']}\n")
 
550
  "generated_at": datetime.now().strftime("%H:%M:%S"),
551
  }
552
 
553
+ def build_retriever(llm, top_k: int):
554
+ retriever = st.session_state.vectors.as_retriever(
555
+ search_type="mmr",
556
+ search_kwargs={"k": top_k, "fetch_k": top_k * 3},
557
+ )
558
+ ctx_prompt = ChatPromptTemplate.from_messages([
559
+ ("system", "Given the chat history and the latest user question, rephrase it as a standalone search query. Return ONLY the reformulated query."),
560
+ MessagesPlaceholder("chat_history"),
561
+ ("human", "{input}"),
562
+ ])
563
+ return create_history_aware_retriever(llm, retriever, ctx_prompt)
564
+
565
+ def format_retrieved_context(retrieved_docs: list) -> tuple[str, int]:
566
+ context_parts = []
567
+ total_chars = 0
568
+ for doc in retrieved_docs:
569
+ if total_chars + len(doc.page_content) <= MAX_CONTEXT_CHARS:
570
+ context_parts.append(doc.page_content)
571
+ total_chars += len(doc.page_content)
572
+ else:
573
+ remaining = MAX_CONTEXT_CHARS - total_chars
574
+ if remaining > 200:
575
+ context_parts.append(doc.page_content[:remaining])
576
+ total_chars += remaining
577
+ break
578
+ return "\n\n---\n\n".join(context_parts), total_chars
579
+
580
+ def answer_from_documents(llm, user_query: str, top_k: int, chat_history=None) -> dict:
581
+ history = chat_history if chat_history is not None else st.session_state.chat_history
582
+ history_aware_retriever = build_retriever(llm, top_k)
583
+ retrieved_docs = history_aware_retriever.invoke({
584
+ "input": user_query,
585
+ "chat_history": history,
586
+ })
587
+ formatted_context, total_chars = format_retrieved_context(retrieved_docs)
588
+ qa_prompt = ChatPromptTemplate.from_messages([
589
+ ("system", "You are an expert assistant. Answer using ONLY the provided context. If the answer isn't in the context, say so clearly.\n\nContext:\n{context}"),
590
+ MessagesPlaceholder("chat_history"),
591
+ ("human", "{input}"),
592
+ ])
593
+ qa_chain = qa_prompt | llm | StrOutputParser()
594
+ answer = qa_chain.invoke({
595
+ "input": user_query,
596
+ "chat_history": history,
597
+ "context": formatted_context,
598
+ })
599
+ return {
600
+ "answer": answer,
601
+ "retrieved_docs": retrieved_docs,
602
+ "context": formatted_context,
603
+ "context_chars": total_chars,
604
+ "metrics": calculate_rag_metrics(retrieved_docs, top_k, total_chars, answer),
605
+ }
606
+
607
+ def parse_eval_csv(uploaded_file) -> list:
608
+ raw = uploaded_file.getvalue().decode("utf-8-sig")
609
+ reader = csv.DictReader(io.StringIO(raw))
610
+ rows = []
611
+ for row in reader:
612
+ question = (row.get("question") or row.get("Question") or "").strip()
613
+ expected = (row.get("expected_answer") or row.get("Expected Answer") or row.get("answer") or "").strip()
614
+ expected_source = (row.get("expected_source") or row.get("source") or "").strip()
615
+ if question and expected:
616
+ rows.append({
617
+ "question": question,
618
+ "expected_answer": expected,
619
+ "expected_source": expected_source,
620
+ })
621
+ return rows
622
+
623
+ def judge_eval_answer(llm, question: str, expected_answer: str, actual_answer: str, context: str) -> dict:
624
+ judge_prompt = ChatPromptTemplate.from_template(
625
+ """
626
+ You are evaluating a RAG answer. Return ONLY valid JSON.
627
+
628
+ Score:
629
+ - correctness_score: 0-100, how well the actual answer matches the expected answer.
630
+ - faithfulness_score: 0-100, whether the actual answer is supported by the retrieved context.
631
+ - notes: one short sentence.
632
+
633
+ JSON schema:
634
+ {{
635
+ "correctness_score": 0-100,
636
+ "faithfulness_score": 0-100,
637
+ "notes": "short note"
638
+ }}
639
+
640
+ Question: {question}
641
+ Expected answer: {expected_answer}
642
+ Actual answer: {actual_answer}
643
+ Retrieved context: {context}
644
+ """
645
+ )
646
+ parsed = safe_json_loads((judge_prompt | llm | StrOutputParser()).invoke({
647
+ "question": question,
648
+ "expected_answer": expected_answer,
649
+ "actual_answer": actual_answer,
650
+ "context": context[:6000],
651
+ }))
652
+ return {
653
+ "correctness_score": int(parsed.get("correctness_score", 0) or 0),
654
+ "faithfulness_score": int(parsed.get("faithfulness_score", 0) or 0),
655
+ "notes": parsed.get("notes", "Evaluation completed."),
656
+ }
657
+
658
+ def run_eval_suite(eval_rows: list, llm, top_k: int) -> tuple[list, dict]:
659
+ results = []
660
+ for index, row in enumerate(eval_rows, start=1):
661
+ rag = answer_from_documents(llm, row["question"], top_k, chat_history=[])
662
+ judge = judge_eval_answer(
663
+ llm,
664
+ row["question"],
665
+ row["expected_answer"],
666
+ rag["answer"],
667
+ rag["context"],
668
+ )
669
+ results.append({
670
+ "test_id": index,
671
+ "question": row["question"],
672
+ "expected_answer": row["expected_answer"],
673
+ "actual_answer": rag["answer"],
674
+ "retrieved_chunks": rag["metrics"]["retrieved_chunks"],
675
+ "citation_coverage": rag["metrics"]["citation_coverage"],
676
+ "confidence_score": rag["metrics"]["confidence_score"],
677
+ "correctness_score": judge["correctness_score"],
678
+ "faithfulness_score": judge["faithfulness_score"],
679
+ "notes": judge["notes"],
680
+ })
681
+
682
+ if not results:
683
+ return [], {}
684
+
685
+ summary = {
686
+ "tests": len(results),
687
+ "avg_correctness": round(sum(r["correctness_score"] for r in results) / len(results), 1),
688
+ "avg_faithfulness": round(sum(r["faithfulness_score"] for r in results) / len(results), 1),
689
+ "avg_confidence": round(sum(r["confidence_score"] for r in results) / len(results), 1),
690
+ "avg_citation_coverage": round(sum(r["citation_coverage"] for r in results) / len(results), 1),
691
+ "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
692
+ }
693
+ return results, summary
694
+
695
  def queue_prompt(prompt: str):
696
  st.session_state.pending_query = prompt
697
 
 
705
  <div class="hero-layout">
706
  <div>
707
  <div class="hero-kicker">Document intelligence workspace</div>
708
+ <h1>DocuChat_AT (Document Intelligence RAG Assistant)</h1>
709
  <p>Upload documents, generate summaries, extract insights, and ask grounded questions with source citations.</p>
710
  </div>
711
  <aside class="signature-card">
 
746
  """
747
  <div class="feature-grid">
748
  <div class="feature-card"><strong>Ask Anything</strong><span>Chat with PDFs, Word files, and text documents using grounded answers.</span></div>
749
+ <div class="feature-card"><strong>AI Intelligence</strong><span>Classify documents, extract entities, detect risks, and identify actions.</span></div>
750
+ <div class="feature-card"><strong>Eval Dashboard</strong><span>Run labeled test questions and score correctness, faithfulness, and citations.</span></div>
751
+ <div class="feature-card"><strong>OCR Ready</strong><span>Optional scanned PDF OCR with graceful fallback for Streamlit deployments.</span></div>
752
  </div>
753
  """,
754
  unsafe_allow_html=True,
 
836
  q4.metric("Context Used", f"{metrics.get('context_utilization', 0)}%")
837
  st.caption(f"Last evaluated at {metrics.get('generated_at', 'N/A')}. These are lightweight heuristic metrics for visibility, not formal benchmark scores.")
838
 
839
+ def render_evaluation_panel():
840
+ if not st.session_state.vectors:
841
+ st.info("Process documents before running an evaluation suite.")
842
+ return
843
+
844
+ st.markdown("##### Upload labeled test questions")
845
+ st.caption("CSV columns required: question, expected_answer. Optional: expected_source.")
846
+ eval_file = st.file_uploader("Evaluation CSV", type=["csv"], key="eval_csv")
847
+
848
+ c1, c2 = st.columns([1, 1])
849
+ with c1:
850
+ run_eval = st.button("πŸ§ͺ Run Evaluation", type="primary", use_container_width=True)
851
+ with c2:
852
+ clear_eval = st.button("Clear Evaluation", use_container_width=True)
853
+
854
+ if clear_eval:
855
+ st.session_state.eval_results = []
856
+ st.session_state.eval_summary = {}
857
+ st.rerun()
858
+
859
+ if run_eval:
860
+ if not eval_file:
861
+ st.warning("Upload a labeled CSV first.")
862
+ else:
863
+ rows = parse_eval_csv(eval_file)
864
+ if not rows:
865
+ st.error("No valid rows found. Use columns: question, expected_answer.")
866
+ else:
867
+ with st.spinner(f"Running {len(rows)} RAG evaluation tests..."):
868
+ st.session_state.eval_results, st.session_state.eval_summary = run_eval_suite(rows, llm, top_k)
869
+ st.success("Evaluation complete.")
870
+
871
+ summary = st.session_state.get("eval_summary", {})
872
+ results = st.session_state.get("eval_results", [])
873
+ if summary:
874
+ m1, m2, m3, m4 = st.columns(4)
875
+ m1.metric("Tests", summary.get("tests", 0))
876
+ m2.metric("Correctness", f"{summary.get('avg_correctness', 0)}%")
877
+ m3.metric("Faithfulness", f"{summary.get('avg_faithfulness', 0)}%")
878
+ m4.metric("Citation Coverage", f"{summary.get('avg_citation_coverage', 0)}%")
879
+ st.caption(f"Generated at {summary.get('generated_at')}. Scores are LLM-judged and should be reviewed for critical use cases.")
880
+
881
+ if results:
882
+ st.markdown("##### Test Results")
883
+ st.dataframe(results, use_container_width=True, hide_index=True)
884
+ export = json.dumps({"summary": summary, "results": results}, indent=2)
885
+ st.download_button(
886
+ "⬇️ Download Evaluation JSON",
887
+ data=export,
888
+ file_name=f"docuchat_eval_{datetime.now().strftime('%Y%m%d_%H%M')}.json",
889
+ mime="application/json",
890
+ use_container_width=True,
891
+ )
892
+
893
  def render_workspace():
894
  render_hero()
895
  render_status_panel()
 
898
  st.markdown('<div class="section-title">Document command center</div>', unsafe_allow_html=True)
899
  st.markdown('<p class="section-copy">Choose a workflow or start with a suggested prompt. Each button sends a ready-made instruction to the assistant.</p>', unsafe_allow_html=True)
900
 
901
+ chat_tab, summary_tab, extract_tab, analyze_tab, intel_tab, eval_tab, deliver_tab = st.tabs([
902
  "Chat",
903
  "Summaries",
904
  "Extract",
905
  "Analyze",
906
  "Intelligence",
907
+ "Evaluation",
908
  "Deliverables",
909
  ])
910
 
 
945
  st.markdown("#### AI document intelligence")
946
  render_intelligence_panel()
947
 
948
+ with eval_tab:
949
+ st.markdown("#### RAG evaluation dashboard")
950
+ render_evaluation_panel()
951
+
952
  with deliver_tab:
953
  st.markdown("#### Ready-to-use outputs")
954
  c1, c2, c3 = st.columns(3)
 
962
  # ─────────────────────────────────────────────
963
  # SIDEBAR UI
964
  # ─────────────────────────────────────────────
965
+ require_authentication()
966
+
967
  with st.sidebar:
968
+ st.title("πŸ“„ DocuChat_AT")
969
+ if os.getenv("APP_PASSWORD", "").strip():
970
+ st.success("πŸ” Private mode enabled")
971
+ else:
972
+ st.caption("Public demo mode")
973
 
974
  if st.session_state.vectors:
975
  st.success("βœ… Vector DB Ready", icon="🟒")
 
989
  with st.expander("βš™οΈ Advanced Settings"):
990
  temperature = st.slider("Temperature", 0.0, 1.0, 0.3, 0.05)
991
  top_k = st.slider("Retrieved Chunks (Top-K)", 2, 10, 4)
992
+ use_ocr = st.checkbox("Enable OCR fallback for scanned PDFs", value=False)
993
+ ocr_page_limit = st.slider("OCR Page Limit", 1, 25, 8)
994
 
995
  st.divider()
996
 
 
1012
  elif not uploaded_files:
1013
  st.warning("⚠️ Upload files first.")
1014
  else:
1015
+ file_hash = f"{compute_files_hash(uploaded_files)}-ocr-{use_ocr}-{ocr_page_limit}"
1016
  force_reprocess = (file_hash != st.session_state.last_file_hash)
1017
 
1018
  if not force_reprocess and st.session_state.vectors:
 
1023
  t0 = time.time()
1024
 
1025
  st.write("πŸ“₯ Loading files into memory...")
1026
+ raw_docs = load_documents(uploaded_files, use_ocr=use_ocr, ocr_page_limit=ocr_page_limit)
1027
 
1028
  if not raw_docs:
1029
  status.update(label="No content found!", state="error")
 
1075
 
1076
  # BUG FIX: Use the saved text instead of raw_docs
1077
  if not st.session_state.full_raw_text:
1078
+ temp_docs = load_documents(uploaded_files, use_ocr=use_ocr, ocr_page_limit=ocr_page_limit)
1079
  st.session_state.full_raw_text = " ".join([d.page_content for d in temp_docs])
1080
 
1081
  full_text = st.session_state.full_raw_text[:6000]
assets/.gitkeep CHANGED
@@ -1 +0,0 @@
1
-
 
 
assets/rag_architecture.svg ADDED
eval_sample.csv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ question,expected_answer,expected_source
2
+ What is the main purpose of this document?,Replace this with the expected answer from your uploaded document.,Document page or section
3
+ What are the most important risks mentioned?,Replace this with the expected risk statement.,Document page or section
4
+ Who are the key people or organizations mentioned?,Replace this with the expected names or organizations.,Document page or section
requirements.txt CHANGED
@@ -6,8 +6,11 @@ langchain-community==0.2.16
6
  langchain-groq==0.1.10
7
  langchain-huggingface==0.0.3
8
  langchain-text-splitters==0.2.4
9
- faiss-cpu==1.13.2
10
- sentence-transformers==3.1.1
11
  pypdf==4.3.1
12
  docx2txt==0.8
13
- tiktoken==0.7.0
 
 
 
 
6
  langchain-groq==0.1.10
7
  langchain-huggingface==0.0.3
8
  langchain-text-splitters==0.2.4
9
+ faiss-cpu
10
+ tiktoken==0.7.0
11
  pypdf==4.3.1
12
  docx2txt==0.8
13
+ sentence-transformers==3.1.1
14
+ pypdfium2==4.30.0
15
+ pytesseract==0.3.13
16
+ Pillow==10.4.0