AnseMin commited on
Commit
575f1c7
Β·
1 Parent(s): c0c51c2

Enhance RAG (Retrieval-Augmented Generation) functionality and dependencies

Browse files

- Added RAG configuration management and integrated support for LangChain, ChromaDB, and sentence-transformers.
- Implemented document ingestion, chunking, and embedding management for RAG capabilities.
- Updated application to check and install necessary RAG dependencies automatically.
- Enhanced README to include RAG features and usage instructions.
- Refactored environment checks to ensure RAG dependencies are available.
- Improved UI to support document conversion and chat functionalities with RAG integration.
- Updated requirements.txt to include new RAG-related packages.

Files changed (21) hide show
  1. .gitignore +3 -0
  2. =0.1.0 +96 -0
  3. =0.2.0 +36 -0
  4. =0.3.0 +26 -0
  5. =0.5.0 +102 -0
  6. =2.0.0 +56 -0
  7. =3.0.0 +50 -0
  8. README.md +76 -12
  9. app.py +18 -0
  10. requirements.txt +10 -1
  11. setup.sh +13 -1
  12. src/core/config.py +72 -0
  13. src/core/environment.py +59 -0
  14. src/rag/__init__.py +17 -0
  15. src/rag/chat_service.py +367 -0
  16. src/rag/chunking.py +273 -0
  17. src/rag/embeddings.py +64 -0
  18. src/rag/ingestion.py +304 -0
  19. src/rag/memory.py +284 -0
  20. src/rag/vector_store.py +230 -0
  21. src/ui/ui.py +650 -96
.gitignore CHANGED
@@ -97,3 +97,6 @@ app_backup.py
97
 
98
  #Ignore .claude
99
  .claude/
 
 
 
 
97
 
98
  #Ignore .claude
99
  .claude/
100
+
101
+ # Ignore data folder
102
+ /data/
=0.1.0 ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Collecting langchain-chroma
2
+ Using cached langchain_chroma-0.2.4-py3-none-any.whl.metadata (1.1 kB)
3
+ Requirement already satisfied: langchain-core>=0.3.60 in ./.venv/lib/python3.12/site-packages (from langchain-chroma) (0.3.66)
4
+ Requirement already satisfied: numpy>=1.26.0 in ./.venv/lib/python3.12/site-packages (from langchain-chroma) (1.26.3)
5
+ Requirement already satisfied: chromadb>=1.0.9 in ./.venv/lib/python3.12/site-packages (from langchain-chroma) (1.0.13)
6
+ Requirement already satisfied: build>=1.0.3 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (1.2.2.post1)
7
+ Requirement already satisfied: pydantic>=1.9 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (2.11.7)
8
+ Requirement already satisfied: pybase64>=1.4.1 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (1.4.1)
9
+ Requirement already satisfied: uvicorn>=0.18.3 in ./.venv/lib/python3.12/site-packages (from uvicorn[standard]>=0.18.3->chromadb>=1.0.9->langchain-chroma) (0.34.3)
10
+ Requirement already satisfied: posthog>=2.4.0 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (5.4.0)
11
+ Requirement already satisfied: typing-extensions>=4.5.0 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (4.14.0)
12
+ Requirement already satisfied: onnxruntime>=1.14.1 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (1.22.0)
13
+ Requirement already satisfied: opentelemetry-api>=1.2.0 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (1.34.1)
14
+ Requirement already satisfied: opentelemetry-exporter-otlp-proto-grpc>=1.2.0 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (1.34.1)
15
+ Requirement already satisfied: opentelemetry-sdk>=1.2.0 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (1.34.1)
16
+ Requirement already satisfied: tokenizers>=0.13.2 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (0.21.1)
17
+ Requirement already satisfied: pypika>=0.48.9 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (0.48.9)
18
+ Requirement already satisfied: tqdm>=4.65.0 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (4.67.1)
19
+ Requirement already satisfied: overrides>=7.3.1 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (7.7.0)
20
+ Requirement already satisfied: importlib-resources in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (6.5.2)
21
+ Requirement already satisfied: grpcio>=1.58.0 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (1.73.0)
22
+ Requirement already satisfied: bcrypt>=4.0.1 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (4.3.0)
23
+ Requirement already satisfied: typer>=0.9.0 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (0.16.0)
24
+ Requirement already satisfied: kubernetes>=28.1.0 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (33.1.0)
25
+ Requirement already satisfied: tenacity>=8.2.3 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (8.5.0)
26
+ Requirement already satisfied: pyyaml>=6.0.0 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (6.0.2)
27
+ Requirement already satisfied: mmh3>=4.0.1 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (5.1.0)
28
+ Requirement already satisfied: orjson>=3.9.12 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (3.10.18)
29
+ Requirement already satisfied: httpx>=0.27.0 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (0.28.1)
30
+ Requirement already satisfied: rich>=10.11.0 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (14.0.0)
31
+ Requirement already satisfied: jsonschema>=4.19.0 in ./.venv/lib/python3.12/site-packages (from chromadb>=1.0.9->langchain-chroma) (4.24.0)
32
+ Requirement already satisfied: langsmith>=0.3.45 in ./.venv/lib/python3.12/site-packages (from langchain-core>=0.3.60->langchain-chroma) (0.4.1)
33
+ Requirement already satisfied: jsonpatch<2.0,>=1.33 in ./.venv/lib/python3.12/site-packages (from langchain-core>=0.3.60->langchain-chroma) (1.33)
34
+ Requirement already satisfied: packaging<25,>=23.2 in ./.venv/lib/python3.12/site-packages (from langchain-core>=0.3.60->langchain-chroma) (24.2)
35
+ Requirement already satisfied: pyproject_hooks in ./.venv/lib/python3.12/site-packages (from build>=1.0.3->chromadb>=1.0.9->langchain-chroma) (1.2.0)
36
+ Requirement already satisfied: anyio in ./.venv/lib/python3.12/site-packages (from httpx>=0.27.0->chromadb>=1.0.9->langchain-chroma) (4.9.0)
37
+ Requirement already satisfied: certifi in ./.venv/lib/python3.12/site-packages (from httpx>=0.27.0->chromadb>=1.0.9->langchain-chroma) (2025.6.15)
38
+ Requirement already satisfied: httpcore==1.* in ./.venv/lib/python3.12/site-packages (from httpx>=0.27.0->chromadb>=1.0.9->langchain-chroma) (1.0.9)
39
+ Requirement already satisfied: idna in ./.venv/lib/python3.12/site-packages (from httpx>=0.27.0->chromadb>=1.0.9->langchain-chroma) (3.10)
40
+ Requirement already satisfied: h11>=0.16 in ./.venv/lib/python3.12/site-packages (from httpcore==1.*->httpx>=0.27.0->chromadb>=1.0.9->langchain-chroma) (0.16.0)
41
+ Requirement already satisfied: jsonpointer>=1.9 in ./.venv/lib/python3.12/site-packages (from jsonpatch<2.0,>=1.33->langchain-core>=0.3.60->langchain-chroma) (3.0.0)
42
+ Requirement already satisfied: attrs>=22.2.0 in ./.venv/lib/python3.12/site-packages (from jsonschema>=4.19.0->chromadb>=1.0.9->langchain-chroma) (25.3.0)
43
+ Requirement already satisfied: jsonschema-specifications>=2023.03.6 in ./.venv/lib/python3.12/site-packages (from jsonschema>=4.19.0->chromadb>=1.0.9->langchain-chroma) (2025.4.1)
44
+ Requirement already satisfied: referencing>=0.28.4 in ./.venv/lib/python3.12/site-packages (from jsonschema>=4.19.0->chromadb>=1.0.9->langchain-chroma) (0.36.2)
45
+ Requirement already satisfied: rpds-py>=0.7.1 in ./.venv/lib/python3.12/site-packages (from jsonschema>=4.19.0->chromadb>=1.0.9->langchain-chroma) (0.25.1)
46
+ Requirement already satisfied: six>=1.9.0 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (1.17.0)
47
+ Requirement already satisfied: python-dateutil>=2.5.3 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (2.9.0.post0)
48
+ Requirement already satisfied: google-auth>=1.0.1 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (2.40.3)
49
+ Requirement already satisfied: websocket-client!=0.40.0,!=0.41.*,!=0.42.*,>=0.32.0 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (1.8.0)
50
+ Requirement already satisfied: requests in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (2.32.4)
51
+ Requirement already satisfied: requests-oauthlib in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (2.0.0)
52
+ Requirement already satisfied: oauthlib>=3.2.2 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (3.3.1)
53
+ Requirement already satisfied: urllib3>=1.24.2 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (2.5.0)
54
+ Requirement already satisfied: durationpy>=0.7 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (0.10)
55
+ Requirement already satisfied: requests-toolbelt<2.0.0,>=1.0.0 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core>=0.3.60->langchain-chroma) (1.0.0)
56
+ Requirement already satisfied: zstandard<0.24.0,>=0.23.0 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core>=0.3.60->langchain-chroma) (0.23.0)
57
+ Requirement already satisfied: coloredlogs in ./.venv/lib/python3.12/site-packages (from onnxruntime>=1.14.1->chromadb>=1.0.9->langchain-chroma) (15.0.1)
58
+ Requirement already satisfied: flatbuffers in ./.venv/lib/python3.12/site-packages (from onnxruntime>=1.14.1->chromadb>=1.0.9->langchain-chroma) (25.2.10)
59
+ Requirement already satisfied: protobuf in ./.venv/lib/python3.12/site-packages (from onnxruntime>=1.14.1->chromadb>=1.0.9->langchain-chroma) (5.29.5)
60
+ Requirement already satisfied: sympy in ./.venv/lib/python3.12/site-packages (from onnxruntime>=1.14.1->chromadb>=1.0.9->langchain-chroma) (1.14.0)
61
+ Requirement already satisfied: importlib-metadata<8.8.0,>=6.0 in ./.venv/lib/python3.12/site-packages (from opentelemetry-api>=1.2.0->chromadb>=1.0.9->langchain-chroma) (8.7.0)
62
+ Requirement already satisfied: googleapis-common-protos~=1.52 in ./.venv/lib/python3.12/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb>=1.0.9->langchain-chroma) (1.70.0)
63
+ Requirement already satisfied: opentelemetry-exporter-otlp-proto-common==1.34.1 in ./.venv/lib/python3.12/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb>=1.0.9->langchain-chroma) (1.34.1)
64
+ Requirement already satisfied: opentelemetry-proto==1.34.1 in ./.venv/lib/python3.12/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb>=1.0.9->langchain-chroma) (1.34.1)
65
+ Requirement already satisfied: opentelemetry-semantic-conventions==0.55b1 in ./.venv/lib/python3.12/site-packages (from opentelemetry-sdk>=1.2.0->chromadb>=1.0.9->langchain-chroma) (0.55b1)
66
+ Requirement already satisfied: backoff>=1.10.0 in ./.venv/lib/python3.12/site-packages (from posthog>=2.4.0->chromadb>=1.0.9->langchain-chroma) (2.2.1)
67
+ Requirement already satisfied: distro>=1.5.0 in ./.venv/lib/python3.12/site-packages (from posthog>=2.4.0->chromadb>=1.0.9->langchain-chroma) (1.9.0)
68
+ Requirement already satisfied: annotated-types>=0.6.0 in ./.venv/lib/python3.12/site-packages (from pydantic>=1.9->chromadb>=1.0.9->langchain-chroma) (0.7.0)
69
+ Requirement already satisfied: pydantic-core==2.33.2 in ./.venv/lib/python3.12/site-packages (from pydantic>=1.9->chromadb>=1.0.9->langchain-chroma) (2.33.2)
70
+ Requirement already satisfied: typing-inspection>=0.4.0 in ./.venv/lib/python3.12/site-packages (from pydantic>=1.9->chromadb>=1.0.9->langchain-chroma) (0.4.1)
71
+ Requirement already satisfied: markdown-it-py>=2.2.0 in ./.venv/lib/python3.12/site-packages (from rich>=10.11.0->chromadb>=1.0.9->langchain-chroma) (3.0.0)
72
+ Requirement already satisfied: pygments<3.0.0,>=2.13.0 in ./.venv/lib/python3.12/site-packages (from rich>=10.11.0->chromadb>=1.0.9->langchain-chroma) (2.19.2)
73
+ Requirement already satisfied: huggingface-hub<1.0,>=0.16.4 in ./.venv/lib/python3.12/site-packages (from tokenizers>=0.13.2->chromadb>=1.0.9->langchain-chroma) (0.33.0)
74
+ Requirement already satisfied: click>=8.0.0 in ./.venv/lib/python3.12/site-packages (from typer>=0.9.0->chromadb>=1.0.9->langchain-chroma) (8.2.1)
75
+ Requirement already satisfied: shellingham>=1.3.0 in ./.venv/lib/python3.12/site-packages (from typer>=0.9.0->chromadb>=1.0.9->langchain-chroma) (1.5.4)
76
+ Requirement already satisfied: httptools>=0.6.3 in ./.venv/lib/python3.12/site-packages (from uvicorn[standard]>=0.18.3->chromadb>=1.0.9->langchain-chroma) (0.6.4)
77
+ Requirement already satisfied: python-dotenv>=0.13 in ./.venv/lib/python3.12/site-packages (from uvicorn[standard]>=0.18.3->chromadb>=1.0.9->langchain-chroma) (1.1.0)
78
+ Requirement already satisfied: uvloop>=0.15.1 in ./.venv/lib/python3.12/site-packages (from uvicorn[standard]>=0.18.3->chromadb>=1.0.9->langchain-chroma) (0.21.0)
79
+ Requirement already satisfied: watchfiles>=0.13 in ./.venv/lib/python3.12/site-packages (from uvicorn[standard]>=0.18.3->chromadb>=1.0.9->langchain-chroma) (1.1.0)
80
+ Requirement already satisfied: websockets>=10.4 in ./.venv/lib/python3.12/site-packages (from uvicorn[standard]>=0.18.3->chromadb>=1.0.9->langchain-chroma) (15.0.1)
81
+ Requirement already satisfied: cachetools<6.0,>=2.0.0 in ./.venv/lib/python3.12/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (5.5.2)
82
+ Requirement already satisfied: pyasn1-modules>=0.2.1 in ./.venv/lib/python3.12/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (0.4.2)
83
+ Requirement already satisfied: rsa<5,>=3.1.4 in ./.venv/lib/python3.12/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (4.9.1)
84
+ Requirement already satisfied: filelock in ./.venv/lib/python3.12/site-packages (from huggingface-hub<1.0,>=0.16.4->tokenizers>=0.13.2->chromadb>=1.0.9->langchain-chroma) (3.18.0)
85
+ Requirement already satisfied: fsspec>=2023.5.0 in ./.venv/lib/python3.12/site-packages (from huggingface-hub<1.0,>=0.16.4->tokenizers>=0.13.2->chromadb>=1.0.9->langchain-chroma) (2025.5.1)
86
+ Requirement already satisfied: hf-xet<2.0.0,>=1.1.2 in ./.venv/lib/python3.12/site-packages (from huggingface-hub<1.0,>=0.16.4->tokenizers>=0.13.2->chromadb>=1.0.9->langchain-chroma) (1.1.5)
87
+ Requirement already satisfied: zipp>=3.20 in ./.venv/lib/python3.12/site-packages (from importlib-metadata<8.8.0,>=6.0->opentelemetry-api>=1.2.0->chromadb>=1.0.9->langchain-chroma) (3.23.0)
88
+ Requirement already satisfied: mdurl~=0.1 in ./.venv/lib/python3.12/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->chromadb>=1.0.9->langchain-chroma) (0.1.2)
89
+ Requirement already satisfied: charset_normalizer<4,>=2 in ./.venv/lib/python3.12/site-packages (from requests->kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (3.4.2)
90
+ Requirement already satisfied: sniffio>=1.1 in ./.venv/lib/python3.12/site-packages (from anyio->httpx>=0.27.0->chromadb>=1.0.9->langchain-chroma) (1.3.1)
91
+ Requirement already satisfied: humanfriendly>=9.1 in ./.venv/lib/python3.12/site-packages (from coloredlogs->onnxruntime>=1.14.1->chromadb>=1.0.9->langchain-chroma) (10.0)
92
+ Requirement already satisfied: mpmath<1.4,>=1.1.0 in ./.venv/lib/python3.12/site-packages (from sympy->onnxruntime>=1.14.1->chromadb>=1.0.9->langchain-chroma) (1.3.0)
93
+ Requirement already satisfied: pyasn1<0.7.0,>=0.6.1 in ./.venv/lib/python3.12/site-packages (from pyasn1-modules>=0.2.1->google-auth>=1.0.1->kubernetes>=28.1.0->chromadb>=1.0.9->langchain-chroma) (0.6.1)
94
+ Using cached langchain_chroma-0.2.4-py3-none-any.whl (11 kB)
95
+ Installing collected packages: langchain-chroma
96
+ Successfully installed langchain-chroma-0.2.4
=0.2.0 ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Collecting langchain-openai
2
+ Using cached langchain_openai-0.3.25-py3-none-any.whl.metadata (2.3 kB)
3
+ Requirement already satisfied: langchain-core<1.0.0,>=0.3.66 in ./.venv/lib/python3.12/site-packages (from langchain-openai) (0.3.66)
4
+ Requirement already satisfied: openai<2.0.0,>=1.86.0 in ./.venv/lib/python3.12/site-packages (from langchain-openai) (1.90.0)
5
+ Requirement already satisfied: tiktoken<1,>=0.7 in ./.venv/lib/python3.12/site-packages (from langchain-openai) (0.9.0)
6
+ Requirement already satisfied: langsmith>=0.3.45 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.66->langchain-openai) (0.4.1)
7
+ Requirement already satisfied: tenacity!=8.4.0,<10.0.0,>=8.1.0 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.66->langchain-openai) (8.5.0)
8
+ Requirement already satisfied: jsonpatch<2.0,>=1.33 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.66->langchain-openai) (1.33)
9
+ Requirement already satisfied: PyYAML>=5.3 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.66->langchain-openai) (6.0.2)
10
+ Requirement already satisfied: packaging<25,>=23.2 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.66->langchain-openai) (24.2)
11
+ Requirement already satisfied: typing-extensions>=4.7 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.66->langchain-openai) (4.14.0)
12
+ Requirement already satisfied: pydantic>=2.7.4 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.66->langchain-openai) (2.11.7)
13
+ Requirement already satisfied: anyio<5,>=3.5.0 in ./.venv/lib/python3.12/site-packages (from openai<2.0.0,>=1.86.0->langchain-openai) (4.9.0)
14
+ Requirement already satisfied: distro<2,>=1.7.0 in ./.venv/lib/python3.12/site-packages (from openai<2.0.0,>=1.86.0->langchain-openai) (1.9.0)
15
+ Requirement already satisfied: httpx<1,>=0.23.0 in ./.venv/lib/python3.12/site-packages (from openai<2.0.0,>=1.86.0->langchain-openai) (0.28.1)
16
+ Requirement already satisfied: jiter<1,>=0.4.0 in ./.venv/lib/python3.12/site-packages (from openai<2.0.0,>=1.86.0->langchain-openai) (0.10.0)
17
+ Requirement already satisfied: sniffio in ./.venv/lib/python3.12/site-packages (from openai<2.0.0,>=1.86.0->langchain-openai) (1.3.1)
18
+ Requirement already satisfied: tqdm>4 in ./.venv/lib/python3.12/site-packages (from openai<2.0.0,>=1.86.0->langchain-openai) (4.67.1)
19
+ Requirement already satisfied: regex>=2022.1.18 in ./.venv/lib/python3.12/site-packages (from tiktoken<1,>=0.7->langchain-openai) (2024.11.6)
20
+ Requirement already satisfied: requests>=2.26.0 in ./.venv/lib/python3.12/site-packages (from tiktoken<1,>=0.7->langchain-openai) (2.32.4)
21
+ Requirement already satisfied: idna>=2.8 in ./.venv/lib/python3.12/site-packages (from anyio<5,>=3.5.0->openai<2.0.0,>=1.86.0->langchain-openai) (3.10)
22
+ Requirement already satisfied: certifi in ./.venv/lib/python3.12/site-packages (from httpx<1,>=0.23.0->openai<2.0.0,>=1.86.0->langchain-openai) (2025.6.15)
23
+ Requirement already satisfied: httpcore==1.* in ./.venv/lib/python3.12/site-packages (from httpx<1,>=0.23.0->openai<2.0.0,>=1.86.0->langchain-openai) (1.0.9)
24
+ Requirement already satisfied: h11>=0.16 in ./.venv/lib/python3.12/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai<2.0.0,>=1.86.0->langchain-openai) (0.16.0)
25
+ Requirement already satisfied: jsonpointer>=1.9 in ./.venv/lib/python3.12/site-packages (from jsonpatch<2.0,>=1.33->langchain-core<1.0.0,>=0.3.66->langchain-openai) (3.0.0)
26
+ Requirement already satisfied: orjson<4.0.0,>=3.9.14 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.66->langchain-openai) (3.10.18)
27
+ Requirement already satisfied: requests-toolbelt<2.0.0,>=1.0.0 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.66->langchain-openai) (1.0.0)
28
+ Requirement already satisfied: zstandard<0.24.0,>=0.23.0 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.66->langchain-openai) (0.23.0)
29
+ Requirement already satisfied: annotated-types>=0.6.0 in ./.venv/lib/python3.12/site-packages (from pydantic>=2.7.4->langchain-core<1.0.0,>=0.3.66->langchain-openai) (0.7.0)
30
+ Requirement already satisfied: pydantic-core==2.33.2 in ./.venv/lib/python3.12/site-packages (from pydantic>=2.7.4->langchain-core<1.0.0,>=0.3.66->langchain-openai) (2.33.2)
31
+ Requirement already satisfied: typing-inspection>=0.4.0 in ./.venv/lib/python3.12/site-packages (from pydantic>=2.7.4->langchain-core<1.0.0,>=0.3.66->langchain-openai) (0.4.1)
32
+ Requirement already satisfied: charset_normalizer<4,>=2 in ./.venv/lib/python3.12/site-packages (from requests>=2.26.0->tiktoken<1,>=0.7->langchain-openai) (3.4.2)
33
+ Requirement already satisfied: urllib3<3,>=1.21.1 in ./.venv/lib/python3.12/site-packages (from requests>=2.26.0->tiktoken<1,>=0.7->langchain-openai) (2.5.0)
34
+ Using cached langchain_openai-0.3.25-py3-none-any.whl (69 kB)
35
+ Installing collected packages: langchain-openai
36
+ Successfully installed langchain-openai-0.3.25
=0.3.0 ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Requirement already satisfied: langchain-text-splitters in ./.venv/lib/python3.12/site-packages (0.3.8)
2
+ Requirement already satisfied: langchain-core<1.0.0,>=0.3.51 in ./.venv/lib/python3.12/site-packages (from langchain-text-splitters) (0.3.66)
3
+ Requirement already satisfied: langsmith>=0.3.45 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (0.4.1)
4
+ Requirement already satisfied: tenacity!=8.4.0,<10.0.0,>=8.1.0 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (8.5.0)
5
+ Requirement already satisfied: jsonpatch<2.0,>=1.33 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (1.33)
6
+ Requirement already satisfied: PyYAML>=5.3 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (6.0.2)
7
+ Requirement already satisfied: packaging<25,>=23.2 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (24.2)
8
+ Requirement already satisfied: typing-extensions>=4.7 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (4.14.0)
9
+ Requirement already satisfied: pydantic>=2.7.4 in ./.venv/lib/python3.12/site-packages (from langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (2.11.7)
10
+ Requirement already satisfied: jsonpointer>=1.9 in ./.venv/lib/python3.12/site-packages (from jsonpatch<2.0,>=1.33->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (3.0.0)
11
+ Requirement already satisfied: httpx<1,>=0.23.0 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (0.28.1)
12
+ Requirement already satisfied: orjson<4.0.0,>=3.9.14 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (3.10.18)
13
+ Requirement already satisfied: requests<3,>=2 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (2.32.4)
14
+ Requirement already satisfied: requests-toolbelt<2.0.0,>=1.0.0 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (1.0.0)
15
+ Requirement already satisfied: zstandard<0.24.0,>=0.23.0 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (0.23.0)
16
+ Requirement already satisfied: annotated-types>=0.6.0 in ./.venv/lib/python3.12/site-packages (from pydantic>=2.7.4->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (0.7.0)
17
+ Requirement already satisfied: pydantic-core==2.33.2 in ./.venv/lib/python3.12/site-packages (from pydantic>=2.7.4->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (2.33.2)
18
+ Requirement already satisfied: typing-inspection>=0.4.0 in ./.venv/lib/python3.12/site-packages (from pydantic>=2.7.4->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (0.4.1)
19
+ Requirement already satisfied: anyio in ./.venv/lib/python3.12/site-packages (from httpx<1,>=0.23.0->langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (4.9.0)
20
+ Requirement already satisfied: certifi in ./.venv/lib/python3.12/site-packages (from httpx<1,>=0.23.0->langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (2025.6.15)
21
+ Requirement already satisfied: httpcore==1.* in ./.venv/lib/python3.12/site-packages (from httpx<1,>=0.23.0->langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (1.0.9)
22
+ Requirement already satisfied: idna in ./.venv/lib/python3.12/site-packages (from httpx<1,>=0.23.0->langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (3.10)
23
+ Requirement already satisfied: h11>=0.16 in ./.venv/lib/python3.12/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (0.16.0)
24
+ Requirement already satisfied: charset_normalizer<4,>=2 in ./.venv/lib/python3.12/site-packages (from requests<3,>=2->langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (3.4.2)
25
+ Requirement already satisfied: urllib3<3,>=1.21.1 in ./.venv/lib/python3.12/site-packages (from requests<3,>=2->langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (2.5.0)
26
+ Requirement already satisfied: sniffio>=1.1 in ./.venv/lib/python3.12/site-packages (from anyio->httpx<1,>=0.23.0->langsmith>=0.3.45->langchain-core<1.0.0,>=0.3.51->langchain-text-splitters) (1.3.1)
=0.5.0 ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Collecting chromadb
2
+ Using cached chromadb-1.0.13-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (7.0 kB)
3
+ Requirement already satisfied: build>=1.0.3 in ./.venv/lib/python3.12/site-packages (from chromadb) (1.2.2.post1)
4
+ Requirement already satisfied: pydantic>=1.9 in ./.venv/lib/python3.12/site-packages (from chromadb) (2.11.7)
5
+ Requirement already satisfied: pybase64>=1.4.1 in ./.venv/lib/python3.12/site-packages (from chromadb) (1.4.1)
6
+ Requirement already satisfied: uvicorn>=0.18.3 in ./.venv/lib/python3.12/site-packages (from uvicorn[standard]>=0.18.3->chromadb) (0.34.3)
7
+ Requirement already satisfied: numpy>=1.22.5 in ./.venv/lib/python3.12/site-packages (from chromadb) (1.26.3)
8
+ Requirement already satisfied: posthog>=2.4.0 in ./.venv/lib/python3.12/site-packages (from chromadb) (5.4.0)
9
+ Requirement already satisfied: typing-extensions>=4.5.0 in ./.venv/lib/python3.12/site-packages (from chromadb) (4.14.0)
10
+ Requirement already satisfied: onnxruntime>=1.14.1 in ./.venv/lib/python3.12/site-packages (from chromadb) (1.22.0)
11
+ Requirement already satisfied: opentelemetry-api>=1.2.0 in ./.venv/lib/python3.12/site-packages (from chromadb) (1.34.1)
12
+ Collecting opentelemetry-exporter-otlp-proto-grpc>=1.2.0 (from chromadb)
13
+ Using cached opentelemetry_exporter_otlp_proto_grpc-1.34.1-py3-none-any.whl.metadata (2.4 kB)
14
+ Collecting opentelemetry-sdk>=1.2.0 (from chromadb)
15
+ Using cached opentelemetry_sdk-1.34.1-py3-none-any.whl.metadata (1.6 kB)
16
+ Requirement already satisfied: tokenizers>=0.13.2 in ./.venv/lib/python3.12/site-packages (from chromadb) (0.21.1)
17
+ Requirement already satisfied: pypika>=0.48.9 in ./.venv/lib/python3.12/site-packages (from chromadb) (0.48.9)
18
+ Requirement already satisfied: tqdm>=4.65.0 in ./.venv/lib/python3.12/site-packages (from chromadb) (4.67.1)
19
+ Requirement already satisfied: overrides>=7.3.1 in ./.venv/lib/python3.12/site-packages (from chromadb) (7.7.0)
20
+ Requirement already satisfied: importlib-resources in ./.venv/lib/python3.12/site-packages (from chromadb) (6.5.2)
21
+ Requirement already satisfied: grpcio>=1.58.0 in ./.venv/lib/python3.12/site-packages (from chromadb) (1.73.0)
22
+ Requirement already satisfied: bcrypt>=4.0.1 in ./.venv/lib/python3.12/site-packages (from chromadb) (4.3.0)
23
+ Requirement already satisfied: typer>=0.9.0 in ./.venv/lib/python3.12/site-packages (from chromadb) (0.16.0)
24
+ Requirement already satisfied: kubernetes>=28.1.0 in ./.venv/lib/python3.12/site-packages (from chromadb) (33.1.0)
25
+ Requirement already satisfied: tenacity>=8.2.3 in ./.venv/lib/python3.12/site-packages (from chromadb) (8.5.0)
26
+ Requirement already satisfied: pyyaml>=6.0.0 in ./.venv/lib/python3.12/site-packages (from chromadb) (6.0.2)
27
+ Requirement already satisfied: mmh3>=4.0.1 in ./.venv/lib/python3.12/site-packages (from chromadb) (5.1.0)
28
+ Requirement already satisfied: orjson>=3.9.12 in ./.venv/lib/python3.12/site-packages (from chromadb) (3.10.18)
29
+ Requirement already satisfied: httpx>=0.27.0 in ./.venv/lib/python3.12/site-packages (from chromadb) (0.28.1)
30
+ Requirement already satisfied: rich>=10.11.0 in ./.venv/lib/python3.12/site-packages (from chromadb) (14.0.0)
31
+ Requirement already satisfied: jsonschema>=4.19.0 in ./.venv/lib/python3.12/site-packages (from chromadb) (4.24.0)
32
+ Requirement already satisfied: packaging>=19.1 in ./.venv/lib/python3.12/site-packages (from build>=1.0.3->chromadb) (24.2)
33
+ Requirement already satisfied: pyproject_hooks in ./.venv/lib/python3.12/site-packages (from build>=1.0.3->chromadb) (1.2.0)
34
+ Requirement already satisfied: anyio in ./.venv/lib/python3.12/site-packages (from httpx>=0.27.0->chromadb) (4.9.0)
35
+ Requirement already satisfied: certifi in ./.venv/lib/python3.12/site-packages (from httpx>=0.27.0->chromadb) (2025.6.15)
36
+ Requirement already satisfied: httpcore==1.* in ./.venv/lib/python3.12/site-packages (from httpx>=0.27.0->chromadb) (1.0.9)
37
+ Requirement already satisfied: idna in ./.venv/lib/python3.12/site-packages (from httpx>=0.27.0->chromadb) (3.10)
38
+ Requirement already satisfied: h11>=0.16 in ./.venv/lib/python3.12/site-packages (from httpcore==1.*->httpx>=0.27.0->chromadb) (0.16.0)
39
+ Requirement already satisfied: attrs>=22.2.0 in ./.venv/lib/python3.12/site-packages (from jsonschema>=4.19.0->chromadb) (25.3.0)
40
+ Requirement already satisfied: jsonschema-specifications>=2023.03.6 in ./.venv/lib/python3.12/site-packages (from jsonschema>=4.19.0->chromadb) (2025.4.1)
41
+ Requirement already satisfied: referencing>=0.28.4 in ./.venv/lib/python3.12/site-packages (from jsonschema>=4.19.0->chromadb) (0.36.2)
42
+ Requirement already satisfied: rpds-py>=0.7.1 in ./.venv/lib/python3.12/site-packages (from jsonschema>=4.19.0->chromadb) (0.25.1)
43
+ Requirement already satisfied: six>=1.9.0 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb) (1.17.0)
44
+ Requirement already satisfied: python-dateutil>=2.5.3 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb) (2.9.0.post0)
45
+ Requirement already satisfied: google-auth>=1.0.1 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb) (2.40.3)
46
+ Requirement already satisfied: websocket-client!=0.40.0,!=0.41.*,!=0.42.*,>=0.32.0 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb) (1.8.0)
47
+ Requirement already satisfied: requests in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb) (2.32.4)
48
+ Requirement already satisfied: requests-oauthlib in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb) (2.0.0)
49
+ Requirement already satisfied: oauthlib>=3.2.2 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb) (3.3.1)
50
+ Requirement already satisfied: urllib3>=1.24.2 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb) (2.5.0)
51
+ Requirement already satisfied: durationpy>=0.7 in ./.venv/lib/python3.12/site-packages (from kubernetes>=28.1.0->chromadb) (0.10)
52
+ Requirement already satisfied: coloredlogs in ./.venv/lib/python3.12/site-packages (from onnxruntime>=1.14.1->chromadb) (15.0.1)
53
+ Requirement already satisfied: flatbuffers in ./.venv/lib/python3.12/site-packages (from onnxruntime>=1.14.1->chromadb) (25.2.10)
54
+ Requirement already satisfied: protobuf in ./.venv/lib/python3.12/site-packages (from onnxruntime>=1.14.1->chromadb) (6.31.1)
55
+ Requirement already satisfied: sympy in ./.venv/lib/python3.12/site-packages (from onnxruntime>=1.14.1->chromadb) (1.14.0)
56
+ Requirement already satisfied: importlib-metadata<8.8.0,>=6.0 in ./.venv/lib/python3.12/site-packages (from opentelemetry-api>=1.2.0->chromadb) (8.7.0)
57
+ Requirement already satisfied: googleapis-common-protos~=1.52 in ./.venv/lib/python3.12/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb) (1.70.0)
58
+ Requirement already satisfied: opentelemetry-exporter-otlp-proto-common==1.34.1 in ./.venv/lib/python3.12/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb) (1.34.1)
59
+ Requirement already satisfied: opentelemetry-proto==1.34.1 in ./.venv/lib/python3.12/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb) (1.34.1)
60
+ Collecting protobuf (from onnxruntime>=1.14.1->chromadb)
61
+ Using cached protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl.metadata (592 bytes)
62
+ Collecting opentelemetry-semantic-conventions==0.55b1 (from opentelemetry-sdk>=1.2.0->chromadb)
63
+ Using cached opentelemetry_semantic_conventions-0.55b1-py3-none-any.whl.metadata (2.5 kB)
64
+ Requirement already satisfied: backoff>=1.10.0 in ./.venv/lib/python3.12/site-packages (from posthog>=2.4.0->chromadb) (2.2.1)
65
+ Requirement already satisfied: distro>=1.5.0 in ./.venv/lib/python3.12/site-packages (from posthog>=2.4.0->chromadb) (1.9.0)
66
+ Requirement already satisfied: annotated-types>=0.6.0 in ./.venv/lib/python3.12/site-packages (from pydantic>=1.9->chromadb) (0.7.0)
67
+ Requirement already satisfied: pydantic-core==2.33.2 in ./.venv/lib/python3.12/site-packages (from pydantic>=1.9->chromadb) (2.33.2)
68
+ Requirement already satisfied: typing-inspection>=0.4.0 in ./.venv/lib/python3.12/site-packages (from pydantic>=1.9->chromadb) (0.4.1)
69
+ Requirement already satisfied: markdown-it-py>=2.2.0 in ./.venv/lib/python3.12/site-packages (from rich>=10.11.0->chromadb) (3.0.0)
70
+ Requirement already satisfied: pygments<3.0.0,>=2.13.0 in ./.venv/lib/python3.12/site-packages (from rich>=10.11.0->chromadb) (2.19.2)
71
+ Requirement already satisfied: huggingface-hub<1.0,>=0.16.4 in ./.venv/lib/python3.12/site-packages (from tokenizers>=0.13.2->chromadb) (0.33.0)
72
+ Requirement already satisfied: click>=8.0.0 in ./.venv/lib/python3.12/site-packages (from typer>=0.9.0->chromadb) (8.2.1)
73
+ Requirement already satisfied: shellingham>=1.3.0 in ./.venv/lib/python3.12/site-packages (from typer>=0.9.0->chromadb) (1.5.4)
74
+ Requirement already satisfied: httptools>=0.6.3 in ./.venv/lib/python3.12/site-packages (from uvicorn[standard]>=0.18.3->chromadb) (0.6.4)
75
+ Requirement already satisfied: python-dotenv>=0.13 in ./.venv/lib/python3.12/site-packages (from uvicorn[standard]>=0.18.3->chromadb) (1.1.0)
76
+ Requirement already satisfied: uvloop>=0.15.1 in ./.venv/lib/python3.12/site-packages (from uvicorn[standard]>=0.18.3->chromadb) (0.21.0)
77
+ Requirement already satisfied: watchfiles>=0.13 in ./.venv/lib/python3.12/site-packages (from uvicorn[standard]>=0.18.3->chromadb) (1.1.0)
78
+ Requirement already satisfied: websockets>=10.4 in ./.venv/lib/python3.12/site-packages (from uvicorn[standard]>=0.18.3->chromadb) (15.0.1)
79
+ Requirement already satisfied: cachetools<6.0,>=2.0.0 in ./.venv/lib/python3.12/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb) (5.5.2)
80
+ Requirement already satisfied: pyasn1-modules>=0.2.1 in ./.venv/lib/python3.12/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb) (0.4.2)
81
+ Requirement already satisfied: rsa<5,>=3.1.4 in ./.venv/lib/python3.12/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb) (4.9.1)
82
+ Requirement already satisfied: filelock in ./.venv/lib/python3.12/site-packages (from huggingface-hub<1.0,>=0.16.4->tokenizers>=0.13.2->chromadb) (3.18.0)
83
+ Requirement already satisfied: fsspec>=2023.5.0 in ./.venv/lib/python3.12/site-packages (from huggingface-hub<1.0,>=0.16.4->tokenizers>=0.13.2->chromadb) (2025.5.1)
84
+ Requirement already satisfied: hf-xet<2.0.0,>=1.1.2 in ./.venv/lib/python3.12/site-packages (from huggingface-hub<1.0,>=0.16.4->tokenizers>=0.13.2->chromadb) (1.1.5)
85
+ Requirement already satisfied: zipp>=3.20 in ./.venv/lib/python3.12/site-packages (from importlib-metadata<8.8.0,>=6.0->opentelemetry-api>=1.2.0->chromadb) (3.23.0)
86
+ Requirement already satisfied: mdurl~=0.1 in ./.venv/lib/python3.12/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->chromadb) (0.1.2)
87
+ Requirement already satisfied: charset_normalizer<4,>=2 in ./.venv/lib/python3.12/site-packages (from requests->kubernetes>=28.1.0->chromadb) (3.4.2)
88
+ Requirement already satisfied: sniffio>=1.1 in ./.venv/lib/python3.12/site-packages (from anyio->httpx>=0.27.0->chromadb) (1.3.1)
89
+ Requirement already satisfied: humanfriendly>=9.1 in ./.venv/lib/python3.12/site-packages (from coloredlogs->onnxruntime>=1.14.1->chromadb) (10.0)
90
+ Requirement already satisfied: mpmath<1.4,>=1.1.0 in ./.venv/lib/python3.12/site-packages (from sympy->onnxruntime>=1.14.1->chromadb) (1.3.0)
91
+ Requirement already satisfied: pyasn1<0.7.0,>=0.6.1 in ./.venv/lib/python3.12/site-packages (from pyasn1-modules>=0.2.1->google-auth>=1.0.1->kubernetes>=28.1.0->chromadb) (0.6.1)
92
+ Using cached chromadb-1.0.13-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (19.3 MB)
93
+ Using cached opentelemetry_exporter_otlp_proto_grpc-1.34.1-py3-none-any.whl (18 kB)
94
+ Using cached opentelemetry_sdk-1.34.1-py3-none-any.whl (118 kB)
95
+ Using cached opentelemetry_semantic_conventions-0.55b1-py3-none-any.whl (196 kB)
96
+ Using cached protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl (319 kB)
97
+ Installing collected packages: protobuf, opentelemetry-semantic-conventions, opentelemetry-sdk, opentelemetry-exporter-otlp-proto-grpc, chromadb
98
+ Attempting uninstall: protobuf
99
+ Found existing installation: protobuf 6.31.1
100
+ Uninstalling protobuf-6.31.1:
101
+ Successfully uninstalled protobuf-6.31.1
102
+ Successfully installed chromadb-1.0.13 opentelemetry-exporter-otlp-proto-grpc-1.34.1 opentelemetry-sdk-1.34.1 opentelemetry-semantic-conventions-0.55b1 protobuf-5.29.5
=2.0.0 ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Collecting langchain-google-genai
2
+ Using cached langchain_google_genai-2.1.5-py3-none-any.whl.metadata (5.2 kB)
3
+ Requirement already satisfied: filetype<2.0.0,>=1.2.0 in ./.venv/lib/python3.12/site-packages (from langchain-google-genai) (1.2.0)
4
+ Collecting google-ai-generativelanguage<0.7.0,>=0.6.18 (from langchain-google-genai)
5
+ Using cached google_ai_generativelanguage-0.6.18-py3-none-any.whl.metadata (9.8 kB)
6
+ Requirement already satisfied: langchain-core<0.4.0,>=0.3.62 in ./.venv/lib/python3.12/site-packages (from langchain-google-genai) (0.3.66)
7
+ Requirement already satisfied: pydantic<3,>=2 in ./.venv/lib/python3.12/site-packages (from langchain-google-genai) (2.11.7)
8
+ Collecting google-api-core!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.1 (from google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.1->google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai)
9
+ Using cached google_api_core-2.25.1-py3-none-any.whl.metadata (3.0 kB)
10
+ Requirement already satisfied: google-auth!=2.24.0,!=2.25.0,<3.0.0,>=2.14.1 in ./.venv/lib/python3.12/site-packages (from google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai) (2.40.3)
11
+ Requirement already satisfied: proto-plus<2.0.0,>=1.22.3 in ./.venv/lib/python3.12/site-packages (from google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai) (1.26.1)
12
+ Requirement already satisfied: protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<7.0.0,>=3.20.2 in ./.venv/lib/python3.12/site-packages (from google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai) (5.29.5)
13
+ Requirement already satisfied: langsmith>=0.3.45 in ./.venv/lib/python3.12/site-packages (from langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (0.4.1)
14
+ Requirement already satisfied: tenacity!=8.4.0,<10.0.0,>=8.1.0 in ./.venv/lib/python3.12/site-packages (from langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (8.5.0)
15
+ Requirement already satisfied: jsonpatch<2.0,>=1.33 in ./.venv/lib/python3.12/site-packages (from langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (1.33)
16
+ Requirement already satisfied: PyYAML>=5.3 in ./.venv/lib/python3.12/site-packages (from langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (6.0.2)
17
+ Requirement already satisfied: packaging<25,>=23.2 in ./.venv/lib/python3.12/site-packages (from langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (24.2)
18
+ Requirement already satisfied: typing-extensions>=4.7 in ./.venv/lib/python3.12/site-packages (from langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (4.14.0)
19
+ Requirement already satisfied: annotated-types>=0.6.0 in ./.venv/lib/python3.12/site-packages (from pydantic<3,>=2->langchain-google-genai) (0.7.0)
20
+ Requirement already satisfied: pydantic-core==2.33.2 in ./.venv/lib/python3.12/site-packages (from pydantic<3,>=2->langchain-google-genai) (2.33.2)
21
+ Requirement already satisfied: typing-inspection>=0.4.0 in ./.venv/lib/python3.12/site-packages (from pydantic<3,>=2->langchain-google-genai) (0.4.1)
22
+ Requirement already satisfied: googleapis-common-protos<2.0.0,>=1.56.2 in ./.venv/lib/python3.12/site-packages (from google-api-core!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.1->google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.1->google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai) (1.70.0)
23
+ Requirement already satisfied: requests<3.0.0,>=2.18.0 in ./.venv/lib/python3.12/site-packages (from google-api-core!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.1->google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.1->google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai) (2.32.4)
24
+ Requirement already satisfied: grpcio<2.0.0,>=1.33.2 in ./.venv/lib/python3.12/site-packages (from google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.1->google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai) (1.73.0)
25
+ Collecting grpcio-status<2.0.0,>=1.33.2 (from google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.1->google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai)
26
+ Using cached grpcio_status-1.73.0-py3-none-any.whl.metadata (1.1 kB)
27
+ Requirement already satisfied: cachetools<6.0,>=2.0.0 in ./.venv/lib/python3.12/site-packages (from google-auth!=2.24.0,!=2.25.0,<3.0.0,>=2.14.1->google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai) (5.5.2)
28
+ Requirement already satisfied: pyasn1-modules>=0.2.1 in ./.venv/lib/python3.12/site-packages (from google-auth!=2.24.0,!=2.25.0,<3.0.0,>=2.14.1->google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai) (0.4.2)
29
+ Requirement already satisfied: rsa<5,>=3.1.4 in ./.venv/lib/python3.12/site-packages (from google-auth!=2.24.0,!=2.25.0,<3.0.0,>=2.14.1->google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai) (4.9.1)
30
+ Requirement already satisfied: jsonpointer>=1.9 in ./.venv/lib/python3.12/site-packages (from jsonpatch<2.0,>=1.33->langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (3.0.0)
31
+ Requirement already satisfied: httpx<1,>=0.23.0 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (0.28.1)
32
+ Requirement already satisfied: orjson<4.0.0,>=3.9.14 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (3.10.18)
33
+ Requirement already satisfied: requests-toolbelt<2.0.0,>=1.0.0 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (1.0.0)
34
+ Requirement already satisfied: zstandard<0.24.0,>=0.23.0 in ./.venv/lib/python3.12/site-packages (from langsmith>=0.3.45->langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (0.23.0)
35
+ Collecting protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<7.0.0,>=3.20.2 (from google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai)
36
+ Using cached protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl.metadata (593 bytes)
37
+ Requirement already satisfied: anyio in ./.venv/lib/python3.12/site-packages (from httpx<1,>=0.23.0->langsmith>=0.3.45->langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (4.9.0)
38
+ Requirement already satisfied: certifi in ./.venv/lib/python3.12/site-packages (from httpx<1,>=0.23.0->langsmith>=0.3.45->langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (2025.6.15)
39
+ Requirement already satisfied: httpcore==1.* in ./.venv/lib/python3.12/site-packages (from httpx<1,>=0.23.0->langsmith>=0.3.45->langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (1.0.9)
40
+ Requirement already satisfied: idna in ./.venv/lib/python3.12/site-packages (from httpx<1,>=0.23.0->langsmith>=0.3.45->langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (3.10)
41
+ Requirement already satisfied: h11>=0.16 in ./.venv/lib/python3.12/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->langsmith>=0.3.45->langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (0.16.0)
42
+ Requirement already satisfied: pyasn1<0.7.0,>=0.6.1 in ./.venv/lib/python3.12/site-packages (from pyasn1-modules>=0.2.1->google-auth!=2.24.0,!=2.25.0,<3.0.0,>=2.14.1->google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai) (0.6.1)
43
+ Requirement already satisfied: charset_normalizer<4,>=2 in ./.venv/lib/python3.12/site-packages (from requests<3.0.0,>=2.18.0->google-api-core!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.1->google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.1->google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai) (3.4.2)
44
+ Requirement already satisfied: urllib3<3,>=1.21.1 in ./.venv/lib/python3.12/site-packages (from requests<3.0.0,>=2.18.0->google-api-core!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.1->google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.1->google-ai-generativelanguage<0.7.0,>=0.6.18->langchain-google-genai) (2.5.0)
45
+ Requirement already satisfied: sniffio>=1.1 in ./.venv/lib/python3.12/site-packages (from anyio->httpx<1,>=0.23.0->langsmith>=0.3.45->langchain-core<0.4.0,>=0.3.62->langchain-google-genai) (1.3.1)
46
+ Using cached langchain_google_genai-2.1.5-py3-none-any.whl (44 kB)
47
+ Using cached google_ai_generativelanguage-0.6.18-py3-none-any.whl (1.4 MB)
48
+ Using cached google_api_core-2.25.1-py3-none-any.whl (160 kB)
49
+ Downloading grpcio_status-1.73.0-py3-none-any.whl (14 kB)
50
+ Using cached protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl (321 kB)
51
+ Installing collected packages: protobuf, grpcio-status, google-api-core, google-ai-generativelanguage, langchain-google-genai
52
+ Attempting uninstall: protobuf
53
+ Found existing installation: protobuf 5.29.5
54
+ Uninstalling protobuf-5.29.5:
55
+ Successfully uninstalled protobuf-5.29.5
56
+ Successfully installed google-ai-generativelanguage-0.6.18 google-api-core-2.25.1 grpcio-status-1.73.0 langchain-google-genai-2.1.5 protobuf-6.31.1
=3.0.0 ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Collecting sentence-transformers
2
+ Using cached sentence_transformers-4.1.0-py3-none-any.whl.metadata (13 kB)
3
+ Requirement already satisfied: transformers<5.0.0,>=4.41.0 in ./.venv/lib/python3.12/site-packages (from sentence-transformers) (4.53.0.dev0)
4
+ Requirement already satisfied: tqdm in ./.venv/lib/python3.12/site-packages (from sentence-transformers) (4.67.1)
5
+ Requirement already satisfied: torch>=1.11.0 in ./.venv/lib/python3.12/site-packages (from sentence-transformers) (2.7.1)
6
+ Requirement already satisfied: scikit-learn in ./.venv/lib/python3.12/site-packages (from sentence-transformers) (1.7.0)
7
+ Requirement already satisfied: scipy in ./.venv/lib/python3.12/site-packages (from sentence-transformers) (1.16.0)
8
+ Requirement already satisfied: huggingface-hub>=0.20.0 in ./.venv/lib/python3.12/site-packages (from sentence-transformers) (0.33.0)
9
+ Requirement already satisfied: Pillow in ./.venv/lib/python3.12/site-packages (from sentence-transformers) (11.2.1)
10
+ Requirement already satisfied: typing_extensions>=4.5.0 in ./.venv/lib/python3.12/site-packages (from sentence-transformers) (4.14.0)
11
+ Requirement already satisfied: filelock in ./.venv/lib/python3.12/site-packages (from huggingface-hub>=0.20.0->sentence-transformers) (3.18.0)
12
+ Requirement already satisfied: fsspec>=2023.5.0 in ./.venv/lib/python3.12/site-packages (from huggingface-hub>=0.20.0->sentence-transformers) (2025.5.1)
13
+ Requirement already satisfied: packaging>=20.9 in ./.venv/lib/python3.12/site-packages (from huggingface-hub>=0.20.0->sentence-transformers) (24.2)
14
+ Requirement already satisfied: pyyaml>=5.1 in ./.venv/lib/python3.12/site-packages (from huggingface-hub>=0.20.0->sentence-transformers) (6.0.2)
15
+ Requirement already satisfied: requests in ./.venv/lib/python3.12/site-packages (from huggingface-hub>=0.20.0->sentence-transformers) (2.32.4)
16
+ Requirement already satisfied: hf-xet<2.0.0,>=1.1.2 in ./.venv/lib/python3.12/site-packages (from huggingface-hub>=0.20.0->sentence-transformers) (1.1.5)
17
+ Requirement already satisfied: setuptools in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (80.9.0)
18
+ Requirement already satisfied: sympy>=1.13.3 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (1.14.0)
19
+ Requirement already satisfied: networkx in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (3.5)
20
+ Requirement already satisfied: jinja2 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (3.1.6)
21
+ Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.6.77 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (12.6.77)
22
+ Requirement already satisfied: nvidia-cuda-runtime-cu12==12.6.77 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (12.6.77)
23
+ Requirement already satisfied: nvidia-cuda-cupti-cu12==12.6.80 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (12.6.80)
24
+ Requirement already satisfied: nvidia-cudnn-cu12==9.5.1.17 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (9.5.1.17)
25
+ Requirement already satisfied: nvidia-cublas-cu12==12.6.4.1 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (12.6.4.1)
26
+ Requirement already satisfied: nvidia-cufft-cu12==11.3.0.4 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (11.3.0.4)
27
+ Requirement already satisfied: nvidia-curand-cu12==10.3.7.77 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (10.3.7.77)
28
+ Requirement already satisfied: nvidia-cusolver-cu12==11.7.1.2 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (11.7.1.2)
29
+ Requirement already satisfied: nvidia-cusparse-cu12==12.5.4.2 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (12.5.4.2)
30
+ Requirement already satisfied: nvidia-cusparselt-cu12==0.6.3 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (0.6.3)
31
+ Requirement already satisfied: nvidia-nccl-cu12==2.26.2 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (2.26.2)
32
+ Requirement already satisfied: nvidia-nvtx-cu12==12.6.77 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (12.6.77)
33
+ Requirement already satisfied: nvidia-nvjitlink-cu12==12.6.85 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (12.6.85)
34
+ Requirement already satisfied: nvidia-cufile-cu12==1.11.1.6 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (1.11.1.6)
35
+ Requirement already satisfied: triton==3.3.1 in ./.venv/lib/python3.12/site-packages (from torch>=1.11.0->sentence-transformers) (3.3.1)
36
+ Requirement already satisfied: numpy>=1.17 in ./.venv/lib/python3.12/site-packages (from transformers<5.0.0,>=4.41.0->sentence-transformers) (1.26.3)
37
+ Requirement already satisfied: regex!=2019.12.17 in ./.venv/lib/python3.12/site-packages (from transformers<5.0.0,>=4.41.0->sentence-transformers) (2024.11.6)
38
+ Requirement already satisfied: tokenizers<0.22,>=0.21 in ./.venv/lib/python3.12/site-packages (from transformers<5.0.0,>=4.41.0->sentence-transformers) (0.21.1)
39
+ Requirement already satisfied: safetensors>=0.4.3 in ./.venv/lib/python3.12/site-packages (from transformers<5.0.0,>=4.41.0->sentence-transformers) (0.5.3)
40
+ Requirement already satisfied: joblib>=1.2.0 in ./.venv/lib/python3.12/site-packages (from scikit-learn->sentence-transformers) (1.5.1)
41
+ Requirement already satisfied: threadpoolctl>=3.1.0 in ./.venv/lib/python3.12/site-packages (from scikit-learn->sentence-transformers) (3.6.0)
42
+ Requirement already satisfied: mpmath<1.4,>=1.1.0 in ./.venv/lib/python3.12/site-packages (from sympy>=1.13.3->torch>=1.11.0->sentence-transformers) (1.3.0)
43
+ Requirement already satisfied: MarkupSafe>=2.0 in ./.venv/lib/python3.12/site-packages (from jinja2->torch>=1.11.0->sentence-transformers) (3.0.2)
44
+ Requirement already satisfied: charset_normalizer<4,>=2 in ./.venv/lib/python3.12/site-packages (from requests->huggingface-hub>=0.20.0->sentence-transformers) (3.4.2)
45
+ Requirement already satisfied: idna<4,>=2.5 in ./.venv/lib/python3.12/site-packages (from requests->huggingface-hub>=0.20.0->sentence-transformers) (3.10)
46
+ Requirement already satisfied: urllib3<3,>=1.21.1 in ./.venv/lib/python3.12/site-packages (from requests->huggingface-hub>=0.20.0->sentence-transformers) (2.5.0)
47
+ Requirement already satisfied: certifi>=2017.4.17 in ./.venv/lib/python3.12/site-packages (from requests->huggingface-hub>=0.20.0->sentence-transformers) (2025.6.15)
48
+ Using cached sentence_transformers-4.1.0-py3-none-any.whl (345 kB)
49
+ Installing collected packages: sentence-transformers
50
+ Successfully installed sentence-transformers-4.1.0
README.md CHANGED
@@ -12,12 +12,13 @@ pinned: false
12
  hf_oauth: true
13
  ---
14
 
15
- # Document to Markdown Converter
16
 
17
- A Hugging Face Space that converts various document formats to Markdown, now with MarkItDown integration!
18
 
19
- ## Features
20
 
 
21
  - Convert PDFs, Office documents, images, and more to Markdown
22
  - Multiple parser options:
23
  - MarkItDown: For comprehensive document conversion
@@ -25,6 +26,21 @@ A Hugging Face Space that converts various document formats to Markdown, now wit
25
  - GOT-OCR: For image-based OCR with LaTeX support
26
  - Gemini Flash: For AI-powered text extraction
27
  - Download converted documents as Markdown files
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  - Clean, responsive UI
29
 
30
  ## Using MarkItDown & Docling
@@ -54,8 +70,8 @@ This app integrates multiple powerful document conversion libraries:
54
  The application uses centralized configuration management. You can enhance functionality by setting these environment variables:
55
 
56
  ### πŸ”‘ **API Keys:**
57
- - `GOOGLE_API_KEY`: Used for Gemini Flash parser and LaTeX to Markdown conversion
58
- - `OPENAI_API_KEY`: Enables AI-based image descriptions in MarkItDown
59
  - `MISTRAL_API_KEY`: For Mistral OCR parser (if available)
60
 
61
  ### βš™οΈ **Configuration Options:**
@@ -82,15 +98,39 @@ The application uses centralized configuration management. You can enhance funct
82
  - `MODEL_TEMPERATURE`: Model temperature for AI responses (default: 0.1)
83
  - `MODEL_MAX_TOKENS`: Maximum tokens for AI responses (default: 4096)
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  ## Usage
86
 
87
- 1. Select a file to upload
88
- 2. Choose your preferred parser:
 
 
89
  - **"MarkItDown"** for comprehensive document conversion
90
  - **"Docling"** for advanced PDF understanding and table extraction
91
- 3. Select an OCR method based on your chosen parser
92
- 4. Click "Convert"
93
- 5. View the Markdown output and download the converted file
 
 
 
 
 
 
 
 
 
94
 
95
  ## Local Development
96
 
@@ -102,6 +142,11 @@ The application uses centralized configuration management. You can enhance funct
102
  OPENAI_API_KEY=your_openai_api_key_here
103
  MISTRAL_API_KEY=your_mistral_api_key_here
104
  DEBUG=true
 
 
 
 
 
105
  ```
106
  3. Install dependencies:
107
  ```bash
@@ -334,7 +379,7 @@ markit_v2/
334
  β”‚ β”œβ”€β”€ main.py # Application launcher
335
  β”‚ β”œβ”€β”€ core/ # Core functionality and utilities
336
  β”‚ β”‚ β”œβ”€β”€ __init__.py # Package initialization
337
- β”‚ β”‚ β”œβ”€β”€ config.py # πŸ†• Centralized configuration management
338
  β”‚ β”‚ β”œβ”€β”€ exceptions.py # πŸ†• Custom exception hierarchy
339
  β”‚ β”‚ β”œβ”€β”€ logging_config.py # πŸ†• Centralized logging setup
340
  β”‚ β”‚ β”œβ”€β”€ environment.py # πŸ†• Environment setup and dependency management
@@ -353,9 +398,17 @@ markit_v2/
353
  β”‚ β”‚ β”œβ”€β”€ got_ocr_parser.py # GOT-OCR parser for images
354
  β”‚ β”‚ β”œβ”€β”€ mistral_ocr_parser.py # πŸ†• Mistral OCR parser
355
  β”‚ β”‚ └── gemini_flash_parser.py # Gemini Flash parser
 
 
 
 
 
 
 
 
356
  β”‚ └── ui/ # User interface layer
357
  β”‚ β”œβ”€β”€ __init__.py # Package initialization
358
- β”‚ └── ui.py # Gradio UI with enhanced error handling
359
  β”œβ”€β”€ documents/ # Documentation and examples (gitignored)
360
  β”œβ”€β”€ tessdata/ # Tesseract OCR data (gitignored)
361
  └── tests/ # Tests (future)
@@ -370,6 +423,17 @@ markit_v2/
370
  - **Enhanced Parser Interface**: Validation, metadata, and cancellation support
371
  - **Lightweight Launcher**: Quick development startup with `run_app.py`
372
  - **Centralized Logging**: Configurable logging system (`src/core/logging_config.py`)
 
 
 
 
 
 
 
 
 
 
 
373
 
374
  ### ZeroGPU Integration Notes
375
 
 
12
  hf_oauth: true
13
  ---
14
 
15
+ # Document to Markdown Converter with RAG Chat
16
 
17
+ A Hugging Face Space that converts various document formats to Markdown and lets you chat with your documents using RAG (Retrieval-Augmented Generation)!
18
 
19
+ ## ✨ Key Features
20
 
21
+ ### Document Conversion
22
  - Convert PDFs, Office documents, images, and more to Markdown
23
  - Multiple parser options:
24
  - MarkItDown: For comprehensive document conversion
 
26
  - GOT-OCR: For image-based OCR with LaTeX support
27
  - Gemini Flash: For AI-powered text extraction
28
  - Download converted documents as Markdown files
29
+
30
+ ### πŸ€– RAG Chat with Documents
31
+ - **Chat with your converted documents** using advanced AI
32
+ - **Intelligent document retrieval** using vector embeddings
33
+ - **Markdown-aware chunking** that preserves tables and code blocks
34
+ - **Streaming chat responses** for real-time interaction
35
+ - **Chat history management** with session persistence
36
+ - **Usage limits** to prevent abuse on public spaces
37
+ - **Powered by Gemini 2.5 Flash** for high-quality responses
38
+ - **OpenAI embeddings** for accurate document retrieval
39
+
40
+ ### User Interface
41
+ - **Dual-tab interface**: Document Converter + Chat
42
+ - **Real-time status monitoring** for RAG system
43
+ - **Auto-ingestion** of converted documents into chat system
44
  - Clean, responsive UI
45
 
46
  ## Using MarkItDown & Docling
 
70
  The application uses centralized configuration management. You can enhance functionality by setting these environment variables:
71
 
72
  ### πŸ”‘ **API Keys:**
73
+ - `GOOGLE_API_KEY`: Used for Gemini Flash parser, LaTeX conversion, and **RAG chat functionality**
74
+ - `OPENAI_API_KEY`: Enables AI-based image descriptions in MarkItDown and **vector embeddings for RAG**
75
  - `MISTRAL_API_KEY`: For Mistral OCR parser (if available)
76
 
77
  ### βš™οΈ **Configuration Options:**
 
98
  - `MODEL_TEMPERATURE`: Model temperature for AI responses (default: 0.1)
99
  - `MODEL_MAX_TOKENS`: Maximum tokens for AI responses (default: 4096)
100
 
101
+ ### 🧠 **RAG Configuration:**
102
+ - `VECTOR_STORE_PATH`: Path for vector database storage (default: ./data/vector_store)
103
+ - `CHAT_HISTORY_PATH`: Path for chat history storage (default: ./data/chat_history)
104
+ - `EMBEDDING_MODEL`: OpenAI embedding model (default: text-embedding-3-small)
105
+ - `CHUNK_SIZE`: Document chunk size for RAG (default: 1000)
106
+ - `CHUNK_OVERLAP`: Overlap between chunks (default: 200)
107
+ - `MAX_MESSAGES_PER_SESSION`: Chat limit per session (default: 50)
108
+ - `MAX_MESSAGES_PER_HOUR`: Chat limit per hour (default: 100)
109
+ - `RETRIEVAL_K`: Number of documents to retrieve (default: 4)
110
+ - `RAG_MODEL`: Model for RAG chat (default: gemini-2.5-flash)
111
+ - `RAG_TEMPERATURE`: Temperature for RAG responses (default: 0.1)
112
+ - `RAG_MAX_TOKENS`: Max tokens for RAG responses (default: 4096)
113
+
114
  ## Usage
115
 
116
+ ### Document Conversion
117
+ 1. Go to the **"Document Converter"** tab
118
+ 2. Select a file to upload
119
+ 3. Choose your preferred parser:
120
  - **"MarkItDown"** for comprehensive document conversion
121
  - **"Docling"** for advanced PDF understanding and table extraction
122
+ 4. Select an OCR method based on your chosen parser
123
+ 5. Click "Convert"
124
+ 6. View the Markdown output and download the converted file
125
+ 7. **Documents are automatically added to the RAG system** for chat functionality
126
+
127
+ ### πŸ€– Chat with Documents
128
+ 1. Go to the **"Chat with Documents"** tab
129
+ 2. Check the system status to ensure RAG components are ready
130
+ 3. Ask questions about your converted documents
131
+ 4. Enjoy real-time streaming responses with document context
132
+ 5. Use "New Session" to start fresh conversations
133
+ 6. Monitor your usage limits in the status panel
134
 
135
  ## Local Development
136
 
 
142
  OPENAI_API_KEY=your_openai_api_key_here
143
  MISTRAL_API_KEY=your_mistral_api_key_here
144
  DEBUG=true
145
+
146
+ # RAG Configuration (optional - uses defaults if not set)
147
+ MAX_MESSAGES_PER_SESSION=50
148
+ MAX_MESSAGES_PER_HOUR=100
149
+ CHUNK_SIZE=1000
150
  ```
151
  3. Install dependencies:
152
  ```bash
 
379
  β”‚ β”œβ”€β”€ main.py # Application launcher
380
  β”‚ β”œβ”€β”€ core/ # Core functionality and utilities
381
  β”‚ β”‚ β”œβ”€β”€ __init__.py # Package initialization
382
+ β”‚ β”‚ β”œβ”€β”€ config.py # πŸ†• Centralized configuration management (with RAG settings)
383
  β”‚ β”‚ β”œβ”€β”€ exceptions.py # πŸ†• Custom exception hierarchy
384
  β”‚ β”‚ β”œβ”€β”€ logging_config.py # πŸ†• Centralized logging setup
385
  β”‚ β”‚ β”œβ”€β”€ environment.py # πŸ†• Environment setup and dependency management
 
398
  β”‚ β”‚ β”œβ”€β”€ got_ocr_parser.py # GOT-OCR parser for images
399
  β”‚ β”‚ β”œβ”€β”€ mistral_ocr_parser.py # πŸ†• Mistral OCR parser
400
  β”‚ β”‚ └── gemini_flash_parser.py # Gemini Flash parser
401
+ β”‚ β”œβ”€β”€ rag/ # πŸ†• RAG (Retrieval-Augmented Generation) system
402
+ β”‚ β”‚ β”œβ”€β”€ __init__.py # Package initialization
403
+ β”‚ β”‚ β”œβ”€β”€ embeddings.py # OpenAI embedding model management
404
+ β”‚ β”‚ β”œβ”€β”€ chunking.py # Markdown-aware document chunking
405
+ β”‚ β”‚ β”œβ”€β”€ vector_store.py # Chroma vector database management
406
+ β”‚ β”‚ β”œβ”€β”€ memory.py # Chat history and session management
407
+ β”‚ β”‚ β”œβ”€β”€ chat_service.py # RAG chat service with Gemini 2.5 Flash
408
+ β”‚ β”‚ └── ingestion.py # Document ingestion pipeline
409
  β”‚ └── ui/ # User interface layer
410
  β”‚ β”œβ”€β”€ __init__.py # Package initialization
411
+ β”‚ └── ui.py # Gradio UI with dual tabs (Converter + Chat)
412
  β”œβ”€β”€ documents/ # Documentation and examples (gitignored)
413
  β”œβ”€β”€ tessdata/ # Tesseract OCR data (gitignored)
414
  └── tests/ # Tests (future)
 
423
  - **Enhanced Parser Interface**: Validation, metadata, and cancellation support
424
  - **Lightweight Launcher**: Quick development startup with `run_app.py`
425
  - **Centralized Logging**: Configurable logging system (`src/core/logging_config.py`)
426
+ - **πŸ†• RAG System**: Complete RAG implementation with vector search and chat capabilities
427
+
428
+ ### 🧠 **RAG System Architecture:**
429
+ - **Embeddings Management** (`src/rag/embeddings.py`): OpenAI text-embedding-3-small integration
430
+ - **Markdown-Aware Chunking** (`src/rag/chunking.py`): Preserves tables and code blocks as whole units
431
+ - **Vector Store** (`src/rag/vector_store.py`): Chroma database with persistent storage
432
+ - **Chat Memory** (`src/rag/memory.py`): Session management and conversation history
433
+ - **Chat Service** (`src/rag/chat_service.py`): Streaming RAG responses with Gemini 2.5 Flash
434
+ - **Document Ingestion** (`src/rag/ingestion.py`): Automated pipeline for converting documents to RAG-ready format
435
+ - **Usage Limiting**: Anti-abuse measures for public deployment
436
+ - **Auto-Ingestion**: Seamless integration with document conversion workflow
437
 
438
  ### ZeroGPU Integration Notes
439
 
app.py CHANGED
@@ -46,6 +46,24 @@ except ImportError as e:
46
  except ImportError:
47
  print("Installing Docling...")
48
  subprocess.run([sys.executable, "-m", "pip", "install", "-q", "docling"], check=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  # Import main function with fallback strategies (HF Spaces compatibility)
51
  try:
 
46
  except ImportError:
47
  print("Installing Docling...")
48
  subprocess.run([sys.executable, "-m", "pip", "install", "-q", "docling"], check=False)
49
+
50
+ # Check RAG dependencies as fallback
51
+ try:
52
+ from langchain_openai import OpenAIEmbeddings
53
+ print("RAG dependencies are available")
54
+ except ImportError:
55
+ print("Installing RAG dependencies...")
56
+ rag_packages = [
57
+ "langchain>=0.3.0",
58
+ "langchain-openai>=0.2.0",
59
+ "langchain-google-genai>=2.0.0",
60
+ "langchain-chroma>=0.1.0",
61
+ "langchain-text-splitters>=0.3.0",
62
+ "chromadb>=0.5.0",
63
+ "sentence-transformers>=3.0.0"
64
+ ]
65
+ for package in rag_packages:
66
+ subprocess.run([sys.executable, "-m", "pip", "install", "-q", package], check=False)
67
 
68
  # Import main function with fallback strategies (HF Spaces compatibility)
69
  try:
requirements.txt CHANGED
@@ -33,4 +33,13 @@ markitdown[all]
33
  openai>=1.1.0 # For LLM image description support
34
 
35
  # Docling dependencies
36
- docling
 
 
 
 
 
 
 
 
 
 
33
  openai>=1.1.0 # For LLM image description support
34
 
35
  # Docling dependencies
36
+ docling
37
+
38
+ # RAG and LangChain dependencies
39
+ langchain>=0.3.0
40
+ langchain-openai>=0.2.0
41
+ langchain-google-genai>=2.0.0
42
+ langchain-chroma>=0.1.0
43
+ langchain-text-splitters>=0.3.0
44
+ chromadb>=0.5.0
45
+ sentence-transformers>=3.0.0
setup.sh CHANGED
@@ -14,7 +14,8 @@ if [ "$EUID" -eq 0 ]; then
14
  echo "Installing system dependencies..."
15
  apt-get update && apt-get install -y \
16
  wget \
17
- pkg-config
 
18
  echo "System dependencies installed successfully"
19
  else
20
  echo "Not running as root. Skipping system dependencies installation."
@@ -56,6 +57,17 @@ echo "Installing Docling..."
56
  pip install -q -U docling
57
  echo "Docling installed successfully"
58
 
 
 
 
 
 
 
 
 
 
 
 
59
  # Install the project in development mode only if setup.py or pyproject.toml exists
60
  if [ -f "setup.py" ] || [ -f "pyproject.toml" ]; then
61
  echo "Installing project in development mode..."
 
14
  echo "Installing system dependencies..."
15
  apt-get update && apt-get install -y \
16
  wget \
17
+ pkg-config \
18
+ ffmpeg
19
  echo "System dependencies installed successfully"
20
  else
21
  echo "Not running as root. Skipping system dependencies installation."
 
57
  pip install -q -U docling
58
  echo "Docling installed successfully"
59
 
60
+ # Install LangChain and RAG dependencies
61
+ echo "Installing LangChain and RAG dependencies..."
62
+ pip install -q -U langchain>=0.3.0
63
+ pip install -q -U langchain-openai>=0.2.0
64
+ pip install -q -U langchain-google-genai>=2.0.0
65
+ pip install -q -U langchain-chroma>=0.1.0
66
+ pip install -q -U langchain-text-splitters>=0.3.0
67
+ pip install -q -U chromadb>=0.5.0
68
+ pip install -q -U sentence-transformers>=3.0.0
69
+ echo "LangChain and RAG dependencies installed successfully"
70
+
71
  # Install the project in development mode only if setup.py or pyproject.toml exists
72
  if [ -f "setup.py" ] || [ -f "pyproject.toml" ]; then
73
  echo "Installing project in development mode..."
src/core/config.py CHANGED
@@ -75,6 +75,62 @@ class DoclingConfig:
75
  self.ocr_cpu_threads = int(os.getenv("OMP_NUM_THREADS", self.ocr_cpu_threads))
76
 
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  @dataclass
79
  class AppConfig:
80
  """Main application configuration."""
@@ -99,6 +155,7 @@ class Config:
99
  self.model = ModelConfig()
100
  self.docling = DoclingConfig()
101
  self.app = AppConfig()
 
102
 
103
  def validate(self) -> Dict[str, Any]:
104
  """Validate configuration and return validation results."""
@@ -115,6 +172,13 @@ class Config:
115
  if not self.api.mistral_api_key:
116
  validation_results["warnings"].append("Mistral API key not found - Mistral parser will be unavailable")
117
 
 
 
 
 
 
 
 
118
  # Check tesseract setup
119
  if not self.ocr.tesseract_path and not os.path.exists("/usr/bin/tesseract"):
120
  validation_results["warnings"].append("Tesseract not found in system PATH - OCR functionality may be limited")
@@ -126,6 +190,14 @@ class Config:
126
  validation_results["errors"].append(f"Cannot create temp directory {self.app.temp_dir}: {e}")
127
  validation_results["valid"] = False
128
 
 
 
 
 
 
 
 
 
129
  return validation_results
130
 
131
  def get_available_parsers(self) -> list:
 
75
  self.ocr_cpu_threads = int(os.getenv("OMP_NUM_THREADS", self.ocr_cpu_threads))
76
 
77
 
78
+ @dataclass
79
+ class RAGConfig:
80
+ """Configuration for RAG (Retrieval-Augmented Generation) functionality."""
81
+ # Vector store settings
82
+ vector_store_path: str = "./data/vector_store"
83
+ collection_name: str = "markit_documents"
84
+
85
+ # Chat history settings
86
+ chat_history_path: str = "./data/chat_history"
87
+
88
+ # Embedding settings
89
+ embedding_model: str = "text-embedding-3-small"
90
+ embedding_chunk_size: int = 1000
91
+
92
+ # Chunking settings
93
+ chunk_size: int = 1000
94
+ chunk_overlap: int = 200
95
+
96
+ # Chat limits
97
+ max_messages_per_session: int = 50
98
+ max_messages_per_hour: int = 100
99
+
100
+ # Retrieval settings
101
+ retrieval_k: int = 4
102
+ retrieval_score_threshold: float = 0.5
103
+
104
+ # LLM settings for RAG
105
+ rag_model: str = "gemini-2.5-flash"
106
+ rag_temperature: float = 0.1
107
+ rag_max_tokens: int = 4096
108
+
109
+ def __post_init__(self):
110
+ """Load RAG configuration from environment variables."""
111
+ # For HF Spaces, ensure data directories are created
112
+ if os.getenv("SPACE_ID"): # HF Spaces environment
113
+ base_data_path = "/tmp/data" if not os.access("./data", os.W_OK) else "./data"
114
+ self.vector_store_path = os.getenv("VECTOR_STORE_PATH", f"{base_data_path}/vector_store")
115
+ self.chat_history_path = os.getenv("CHAT_HISTORY_PATH", f"{base_data_path}/chat_history")
116
+ else:
117
+ self.vector_store_path = os.getenv("VECTOR_STORE_PATH", self.vector_store_path)
118
+ self.chat_history_path = os.getenv("CHAT_HISTORY_PATH", self.chat_history_path)
119
+
120
+ self.collection_name = os.getenv("VECTOR_STORE_COLLECTION", self.collection_name)
121
+ self.embedding_model = os.getenv("EMBEDDING_MODEL", self.embedding_model)
122
+ self.embedding_chunk_size = int(os.getenv("EMBEDDING_CHUNK_SIZE", self.embedding_chunk_size))
123
+ self.chunk_size = int(os.getenv("CHUNK_SIZE", self.chunk_size))
124
+ self.chunk_overlap = int(os.getenv("CHUNK_OVERLAP", self.chunk_overlap))
125
+ self.max_messages_per_session = int(os.getenv("MAX_MESSAGES_PER_SESSION", self.max_messages_per_session))
126
+ self.max_messages_per_hour = int(os.getenv("MAX_MESSAGES_PER_HOUR", self.max_messages_per_hour))
127
+ self.retrieval_k = int(os.getenv("RETRIEVAL_K", self.retrieval_k))
128
+ self.retrieval_score_threshold = float(os.getenv("RETRIEVAL_SCORE_THRESHOLD", self.retrieval_score_threshold))
129
+ self.rag_model = os.getenv("RAG_MODEL", self.rag_model)
130
+ self.rag_temperature = float(os.getenv("RAG_TEMPERATURE", self.rag_temperature))
131
+ self.rag_max_tokens = int(os.getenv("RAG_MAX_TOKENS", self.rag_max_tokens))
132
+
133
+
134
  @dataclass
135
  class AppConfig:
136
  """Main application configuration."""
 
155
  self.model = ModelConfig()
156
  self.docling = DoclingConfig()
157
  self.app = AppConfig()
158
+ self.rag = RAGConfig()
159
 
160
  def validate(self) -> Dict[str, Any]:
161
  """Validate configuration and return validation results."""
 
172
  if not self.api.mistral_api_key:
173
  validation_results["warnings"].append("Mistral API key not found - Mistral parser will be unavailable")
174
 
175
+ # Check RAG dependencies
176
+ if not self.api.openai_api_key:
177
+ validation_results["warnings"].append("OpenAI API key not found - RAG embeddings will be unavailable")
178
+
179
+ if not self.api.google_api_key:
180
+ validation_results["warnings"].append("Google API key not found - RAG chat will be unavailable")
181
+
182
  # Check tesseract setup
183
  if not self.ocr.tesseract_path and not os.path.exists("/usr/bin/tesseract"):
184
  validation_results["warnings"].append("Tesseract not found in system PATH - OCR functionality may be limited")
 
190
  validation_results["errors"].append(f"Cannot create temp directory {self.app.temp_dir}: {e}")
191
  validation_results["valid"] = False
192
 
193
+ # Check RAG directories
194
+ try:
195
+ os.makedirs(self.rag.vector_store_path, exist_ok=True)
196
+ os.makedirs(self.rag.chat_history_path, exist_ok=True)
197
+ except Exception as e:
198
+ validation_results["errors"].append(f"Cannot create RAG directories: {e}")
199
+ validation_results["valid"] = False
200
+
201
  return validation_results
202
 
203
  def get_available_parsers(self) -> list:
src/core/environment.py CHANGED
@@ -154,6 +154,64 @@ class EnvironmentManager:
154
  print(f"Error installing Docling: {e}")
155
  return False
156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  def load_environment_variables(self) -> bool:
158
  """Load environment variables from .env file."""
159
  try:
@@ -236,6 +294,7 @@ class EnvironmentManager:
236
  results["numpy"] = self.check_numpy()
237
  results["markitdown"] = self.check_markitdown()
238
  results["docling"] = self.check_docling()
 
239
 
240
  # Load environment variables
241
  results["env_vars"] = self.load_environment_variables()
 
154
  print(f"Error installing Docling: {e}")
155
  return False
156
 
157
+ def check_rag_dependencies(self) -> bool:
158
+ """Check and install RAG dependencies (LangChain, ChromaDB, etc.)."""
159
+ rag_packages = [
160
+ ("langchain", "langchain>=0.3.0"),
161
+ ("langchain_openai", "langchain-openai>=0.2.0"),
162
+ ("langchain_google_genai", "langchain-google-genai>=2.0.0"),
163
+ ("langchain_chroma", "langchain-chroma>=0.1.0"),
164
+ ("langchain_text_splitters", "langchain-text-splitters>=0.3.0"),
165
+ ("chromadb", "chromadb>=0.5.0"),
166
+ ("sentence_transformers", "sentence-transformers>=3.0.0")
167
+ ]
168
+
169
+ all_installed = True
170
+
171
+ for import_name, install_name in rag_packages:
172
+ try:
173
+ if import_name == "langchain_openai":
174
+ from langchain_openai import OpenAIEmbeddings
175
+ elif import_name == "langchain_google_genai":
176
+ from langchain_google_genai import ChatGoogleGenerativeAI
177
+ elif import_name == "langchain_chroma":
178
+ from langchain_chroma import Chroma
179
+ elif import_name == "langchain_text_splitters":
180
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
181
+ else:
182
+ __import__(import_name)
183
+
184
+ print(f"βœ… {import_name} is installed")
185
+
186
+ except ImportError:
187
+ print(f"WARNING: {import_name} not installed. Installing {install_name}...")
188
+ try:
189
+ subprocess.run([sys.executable, "-m", "pip", "install", "-q", install_name], check=False)
190
+ # Verify installation
191
+ if import_name == "langchain_openai":
192
+ from langchain_openai import OpenAIEmbeddings
193
+ elif import_name == "langchain_google_genai":
194
+ from langchain_google_genai import ChatGoogleGenerativeAI
195
+ elif import_name == "langchain_chroma":
196
+ from langchain_chroma import Chroma
197
+ elif import_name == "langchain_text_splitters":
198
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
199
+ else:
200
+ __import__(import_name)
201
+
202
+ print(f"βœ… {import_name} installed successfully")
203
+
204
+ except (ImportError, Exception) as e:
205
+ print(f"❌ Failed to install {import_name}: {e}")
206
+ all_installed = False
207
+
208
+ if all_installed:
209
+ print("βœ… All RAG dependencies are available")
210
+ else:
211
+ print("⚠️ Some RAG dependencies failed to install - chat functionality may be limited")
212
+
213
+ return all_installed
214
+
215
  def load_environment_variables(self) -> bool:
216
  """Load environment variables from .env file."""
217
  try:
 
294
  results["numpy"] = self.check_numpy()
295
  results["markitdown"] = self.check_markitdown()
296
  results["docling"] = self.check_docling()
297
+ results["rag_dependencies"] = self.check_rag_dependencies()
298
 
299
  # Load environment variables
300
  results["env_vars"] = self.load_environment_variables()
src/rag/__init__.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """RAG (Retrieval-Augmented Generation) module for document chat functionality."""
2
+
3
+ from .embeddings import embedding_manager
4
+ from .chunking import document_chunker
5
+ from .vector_store import vector_store_manager
6
+ from .memory import chat_memory_manager
7
+ from .chat_service import rag_chat_service
8
+ from .ingestion import document_ingestion_service
9
+
10
+ __all__ = [
11
+ "embedding_manager",
12
+ "document_chunker",
13
+ "vector_store_manager",
14
+ "chat_memory_manager",
15
+ "rag_chat_service",
16
+ "document_ingestion_service"
17
+ ]
src/rag/chat_service.py ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """RAG chat service with Gemini 2.5 Flash and streaming support."""
2
+
3
+ import os
4
+ import time
5
+ from typing import List, Dict, Any, Optional, Generator, Tuple
6
+ from langchain_google_genai import ChatGoogleGenerativeAI
7
+ from langchain_core.prompts import ChatPromptTemplate
8
+ from langchain_core.runnables import RunnablePassthrough
9
+ from langchain_core.output_parsers import StrOutputParser
10
+ from langchain_core.documents import Document
11
+ from src.rag.vector_store import vector_store_manager
12
+ from src.rag.memory import chat_memory_manager
13
+ from src.core.config import config
14
+ from src.core.logging_config import get_logger
15
+
16
+ logger = get_logger(__name__)
17
+
18
+ class ChatUsageLimiter:
19
+ """Manages chat usage limits to prevent abuse."""
20
+
21
+ def __init__(self, max_messages_per_session: int = 50, max_messages_per_hour: int = 100):
22
+ """
23
+ Initialize usage limiter.
24
+
25
+ Args:
26
+ max_messages_per_session: Maximum messages per chat session
27
+ max_messages_per_hour: Maximum messages per hour across all sessions
28
+ """
29
+ self.max_messages_per_session = max_messages_per_session
30
+ self.max_messages_per_hour = max_messages_per_hour
31
+ self.hourly_usage = {} # Track usage by hour
32
+
33
+ logger.info(f"Chat usage limiter initialized: {max_messages_per_session}/session, {max_messages_per_hour}/hour")
34
+
35
+ def check_session_limit(self, session_message_count: int) -> Tuple[bool, str]:
36
+ """
37
+ Check if session has exceeded message limit.
38
+
39
+ Args:
40
+ session_message_count: Number of messages in current session
41
+
42
+ Returns:
43
+ Tuple of (allowed, reason_if_not_allowed)
44
+ """
45
+ if session_message_count >= self.max_messages_per_session:
46
+ return False, f"Session limit reached ({self.max_messages_per_session} messages per session). Please start a new chat."
47
+ return True, ""
48
+
49
+ def check_hourly_limit(self) -> Tuple[bool, str]:
50
+ """
51
+ Check if hourly limit has been exceeded.
52
+
53
+ Returns:
54
+ Tuple of (allowed, reason_if_not_allowed)
55
+ """
56
+ current_hour = int(time.time()) // 3600
57
+
58
+ # Clean old entries (keep only last 2 hours)
59
+ hours_to_keep = [current_hour - 1, current_hour]
60
+ self.hourly_usage = {h: count for h, count in self.hourly_usage.items() if h in hours_to_keep}
61
+
62
+ current_usage = self.hourly_usage.get(current_hour, 0)
63
+
64
+ if current_usage >= self.max_messages_per_hour:
65
+ return False, f"Hourly limit reached ({self.max_messages_per_hour} messages per hour). Please try again later."
66
+
67
+ return True, ""
68
+
69
+ def record_usage(self) -> None:
70
+ """Record a message usage."""
71
+ current_hour = int(time.time()) // 3600
72
+ self.hourly_usage[current_hour] = self.hourly_usage.get(current_hour, 0) + 1
73
+
74
+ def can_send_message(self, session_message_count: int) -> Tuple[bool, str]:
75
+ """
76
+ Check if user can send a message.
77
+
78
+ Args:
79
+ session_message_count: Number of messages in current session
80
+
81
+ Returns:
82
+ Tuple of (allowed, reason_if_not_allowed)
83
+ """
84
+ # Check session limit
85
+ session_ok, session_reason = self.check_session_limit(session_message_count)
86
+ if not session_ok:
87
+ return False, session_reason
88
+
89
+ # Check hourly limit
90
+ hourly_ok, hourly_reason = self.check_hourly_limit()
91
+ if not hourly_ok:
92
+ return False, hourly_reason
93
+
94
+ return True, ""
95
+
96
+ class RAGChatService:
97
+ """RAG-powered chat service with document context."""
98
+
99
+ def __init__(self):
100
+ """Initialize the RAG chat service."""
101
+ self.usage_limiter = ChatUsageLimiter(
102
+ max_messages_per_session=config.rag.max_messages_per_session,
103
+ max_messages_per_hour=config.rag.max_messages_per_hour
104
+ )
105
+ self._llm = None
106
+ self._rag_chain = None
107
+
108
+ logger.info("RAG chat service initialized")
109
+
110
+ def get_llm(self) -> ChatGoogleGenerativeAI:
111
+ """Get or create the Gemini LLM instance."""
112
+ if self._llm is None:
113
+ try:
114
+ google_api_key = config.api.google_api_key or os.getenv("GOOGLE_API_KEY")
115
+
116
+ if not google_api_key:
117
+ raise ValueError("Google API key not found. Please set GOOGLE_API_KEY in environment variables.")
118
+
119
+ self._llm = ChatGoogleGenerativeAI(
120
+ model="gemini-2.5-flash", # Latest Gemini model
121
+ google_api_key=google_api_key,
122
+ temperature=0.1,
123
+ max_tokens=4096,
124
+ disable_streaming=False # Enable streaming (new parameter name)
125
+ )
126
+
127
+ logger.info("Gemini 2.5 Flash LLM initialized successfully")
128
+
129
+ except Exception as e:
130
+ logger.error(f"Failed to initialize Gemini LLM: {e}")
131
+ raise
132
+
133
+ return self._llm
134
+
135
+ def create_rag_chain(self):
136
+ """Create the RAG chain for document-aware conversations."""
137
+ if self._rag_chain is None:
138
+ try:
139
+ llm = self.get_llm()
140
+ retriever = vector_store_manager.get_retriever(
141
+ search_type="similarity",
142
+ search_kwargs={"k": 4}
143
+ )
144
+
145
+ # Create a prompt template for RAG
146
+ prompt_template = ChatPromptTemplate.from_template("""
147
+ You are a helpful assistant that answers questions based on the provided document context.
148
+
149
+ Instructions:
150
+ 1. Use the context provided to answer the user's question
151
+ 2. If the information is not in the context, say "I don't have enough information in the provided documents to answer that question"
152
+ 3. Always cite which parts of the documents you used for your answer
153
+ 4. Be concise but comprehensive
154
+ 5. If you find relevant tables or code blocks, include them in your response
155
+ 6. Maintain a conversational tone
156
+
157
+ Context from documents:
158
+ {context}
159
+
160
+ Chat History:
161
+ {chat_history}
162
+
163
+ User Question: {question}
164
+ """)
165
+
166
+ def format_docs(docs: List[Document]) -> str:
167
+ """Format retrieved documents for context."""
168
+ if not docs:
169
+ return "No relevant documents found."
170
+
171
+ formatted = []
172
+ for i, doc in enumerate(docs, 1):
173
+ source = doc.metadata.get('source', 'Unknown')
174
+ chunk_id = doc.metadata.get('chunk_id', f'chunk_{i}')
175
+
176
+ formatted.append(f"Document {i} (Source: {source}, ID: {chunk_id}):\n{doc.page_content}")
177
+
178
+ return "\n\n".join(formatted)
179
+
180
+ def format_chat_history() -> str:
181
+ """Format chat history for context."""
182
+ history = chat_memory_manager.get_conversation_history(max_messages=10)
183
+ if not history:
184
+ return "No previous conversation."
185
+
186
+ formatted = []
187
+ for user_msg, assistant_msg in history[-5:]: # Last 5 exchanges
188
+ formatted.append(f"User: {user_msg}")
189
+ formatted.append(f"Assistant: {assistant_msg}")
190
+
191
+ return "\n".join(formatted)
192
+
193
+ # Create the RAG chain
194
+ self._rag_chain = (
195
+ {
196
+ "context": retriever | format_docs,
197
+ "chat_history": lambda _: format_chat_history(),
198
+ "question": RunnablePassthrough()
199
+ }
200
+ | prompt_template
201
+ | llm
202
+ | StrOutputParser()
203
+ )
204
+
205
+ logger.info("RAG chain created successfully")
206
+
207
+ except Exception as e:
208
+ logger.error(f"Failed to create RAG chain: {e}")
209
+ raise
210
+
211
+ def get_rag_chain(self):
212
+ """Get the RAG chain, creating it if necessary."""
213
+ if self._rag_chain is None:
214
+ self.create_rag_chain()
215
+ return self._rag_chain
216
+
217
+ def chat_stream(self, user_message: str) -> Generator[str, None, None]:
218
+ """
219
+ Stream chat response using RAG.
220
+
221
+ Args:
222
+ user_message: User's message
223
+
224
+ Yields:
225
+ Chunks of the response as they're generated
226
+ """
227
+ try:
228
+ # Check usage limits
229
+ current_session = chat_memory_manager.current_session
230
+ session_message_count = len(current_session.messages) if current_session else 0
231
+
232
+ can_send, reason = self.usage_limiter.can_send_message(session_message_count)
233
+ if not can_send:
234
+ yield f"❌ {reason}"
235
+ return
236
+
237
+ # Record usage
238
+ self.usage_limiter.record_usage()
239
+
240
+ # Add user message to memory
241
+ chat_memory_manager.add_message("user", user_message)
242
+
243
+ # Get RAG chain
244
+ rag_chain = self.get_rag_chain()
245
+
246
+ # Stream the response
247
+ response_chunks = []
248
+ for chunk in rag_chain.stream(user_message):
249
+ if chunk:
250
+ response_chunks.append(chunk)
251
+ yield chunk
252
+
253
+ # Save complete response to memory
254
+ complete_response = "".join(response_chunks)
255
+ if complete_response.strip():
256
+ chat_memory_manager.add_message("assistant", complete_response)
257
+
258
+ # Save session periodically
259
+ chat_memory_manager.save_session()
260
+
261
+ except Exception as e:
262
+ error_msg = f"Error generating response: {str(e)}"
263
+ logger.error(error_msg)
264
+ yield f"❌ {error_msg}"
265
+
266
+ def chat(self, user_message: str) -> str:
267
+ """
268
+ Get a complete chat response (non-streaming).
269
+
270
+ Args:
271
+ user_message: User's message
272
+
273
+ Returns:
274
+ Complete response string
275
+ """
276
+ try:
277
+ # Check usage limits
278
+ current_session = chat_memory_manager.current_session
279
+ session_message_count = len(current_session.messages) if current_session else 0
280
+
281
+ can_send, reason = self.usage_limiter.can_send_message(session_message_count)
282
+ if not can_send:
283
+ return f"❌ {reason}"
284
+
285
+ # Record usage
286
+ self.usage_limiter.record_usage()
287
+
288
+ # Add user message to memory
289
+ chat_memory_manager.add_message("user", user_message)
290
+
291
+ # Get RAG chain
292
+ rag_chain = self.get_rag_chain()
293
+
294
+ # Get response
295
+ response = rag_chain.invoke(user_message)
296
+
297
+ # Save response to memory
298
+ if response.strip():
299
+ chat_memory_manager.add_message("assistant", response)
300
+ chat_memory_manager.save_session()
301
+
302
+ return response
303
+
304
+ except Exception as e:
305
+ error_msg = f"Error generating response: {str(e)}"
306
+ logger.error(error_msg)
307
+ return f"❌ {error_msg}"
308
+
309
+ def get_usage_stats(self) -> Dict[str, Any]:
310
+ """Get current usage statistics."""
311
+ current_session = chat_memory_manager.current_session
312
+ session_message_count = len(current_session.messages) if current_session else 0
313
+
314
+ current_hour = int(time.time()) // 3600
315
+ hourly_count = self.usage_limiter.hourly_usage.get(current_hour, 0)
316
+
317
+ return {
318
+ "session_messages": session_message_count,
319
+ "session_limit": self.usage_limiter.max_messages_per_session,
320
+ "hourly_messages": hourly_count,
321
+ "hourly_limit": self.usage_limiter.max_messages_per_hour,
322
+ "session_remaining": max(0, self.usage_limiter.max_messages_per_session - session_message_count),
323
+ "hourly_remaining": max(0, self.usage_limiter.max_messages_per_hour - hourly_count)
324
+ }
325
+
326
+ def start_new_session(self, document_sources: Optional[List[str]] = None) -> str:
327
+ """Start a new chat session."""
328
+ session_id = chat_memory_manager.create_session(document_sources)
329
+ logger.info(f"Started new chat session: {session_id}")
330
+ return session_id
331
+
332
+ def test_service(self) -> Dict[str, Any]:
333
+ """Test the RAG service components."""
334
+ results = {
335
+ "llm_available": False,
336
+ "vector_store_available": False,
337
+ "embeddings_available": False,
338
+ "errors": []
339
+ }
340
+
341
+ try:
342
+ # Test LLM
343
+ llm = self.get_llm()
344
+ test_response = llm.invoke("Test message")
345
+ results["llm_available"] = True
346
+ except Exception as e:
347
+ results["errors"].append(f"LLM test failed: {str(e)}")
348
+
349
+ try:
350
+ # Test vector store
351
+ vector_info = vector_store_manager.get_collection_info()
352
+ results["vector_store_available"] = "error" not in vector_info
353
+ results["document_count"] = vector_info.get("document_count", 0)
354
+ except Exception as e:
355
+ results["errors"].append(f"Vector store test failed: {str(e)}")
356
+
357
+ try:
358
+ # Test embeddings
359
+ from src.rag.embeddings import embedding_manager
360
+ results["embeddings_available"] = embedding_manager.test_embedding_model()
361
+ except Exception as e:
362
+ results["errors"].append(f"Embeddings test failed: {str(e)}")
363
+
364
+ return results
365
+
366
+ # Global RAG chat service instance
367
+ rag_chat_service = RAGChatService()
src/rag/chunking.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Text chunking strategies for RAG document processing."""
2
+
3
+ import re
4
+ from typing import List, Dict, Any, Tuple
5
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
6
+ from langchain_core.documents import Document
7
+ from src.core.logging_config import get_logger
8
+
9
+ logger = get_logger(__name__)
10
+
11
+ class MarkdownAwareChunker:
12
+ """Handles markdown-aware document chunking that preserves tables and structures."""
13
+
14
+ def __init__(self, chunk_size: int = 1000, chunk_overlap: int = 200):
15
+ """
16
+ Initialize the markdown-aware document chunker.
17
+
18
+ Args:
19
+ chunk_size: Maximum size of each chunk in characters
20
+ chunk_overlap: Number of characters to overlap between chunks
21
+ """
22
+ self.chunk_size = chunk_size
23
+ self.chunk_overlap = chunk_overlap
24
+
25
+ # Initialize the text splitter with markdown-aware settings
26
+ self.text_splitter = RecursiveCharacterTextSplitter(
27
+ chunk_size=chunk_size,
28
+ chunk_overlap=chunk_overlap,
29
+ length_function=len,
30
+ separators=[
31
+ "\n\n", # Paragraphs and sections
32
+ "\n# ", # H1 headers
33
+ "\n## ", # H2 headers
34
+ "\n### ", # H3 headers
35
+ "\n\n---\n\n", # Horizontal rules
36
+ "\n", # Lines
37
+ " ", # Words
38
+ ".", # Sentences
39
+ ",", # Clauses
40
+ "" # Characters
41
+ ],
42
+ keep_separator=True,
43
+ add_start_index=True
44
+ )
45
+
46
+ # Regex patterns for markdown structures
47
+ self.table_pattern = re.compile(
48
+ r'(\|.*\|.*\n)+(\|[-\s|:]+\|.*\n)(\|.*\|.*\n)*',
49
+ re.MULTILINE
50
+ )
51
+
52
+ self.code_block_pattern = re.compile(
53
+ r'```[\s\S]*?```',
54
+ re.MULTILINE
55
+ )
56
+
57
+ logger.info(f"Markdown-aware chunker initialized with chunk_size={chunk_size}, overlap={chunk_overlap}")
58
+
59
+ def extract_markdown_structures(self, content: str) -> Tuple[List[Tuple[int, int, str]], str]:
60
+ """
61
+ Extract markdown tables and code blocks, replacing them with placeholders.
62
+
63
+ Args:
64
+ content: Original markdown content
65
+
66
+ Returns:
67
+ Tuple of (structures_list, content_with_placeholders)
68
+ where structures_list contains (start, end, type, content) tuples
69
+ """
70
+ structures = []
71
+
72
+ # Find all tables
73
+ for match in self.table_pattern.finditer(content):
74
+ structures.append((
75
+ match.start(),
76
+ match.end(),
77
+ "table",
78
+ match.group()
79
+ ))
80
+
81
+ # Find all code blocks
82
+ for match in self.code_block_pattern.finditer(content):
83
+ structures.append((
84
+ match.start(),
85
+ match.end(),
86
+ "code_block",
87
+ match.group()
88
+ ))
89
+
90
+ # Sort by start position
91
+ structures.sort(key=lambda x: x[0])
92
+
93
+ # Replace structures with placeholders
94
+ content_with_placeholders = content
95
+ offset = 0
96
+
97
+ for i, (start, end, struct_type, struct_content) in enumerate(structures):
98
+ placeholder = f"\n\n__STRUCTURE_{i}_{struct_type.upper()}__\n\n"
99
+
100
+ # Adjust positions based on previous replacements
101
+ adjusted_start = start - offset
102
+ adjusted_end = end - offset
103
+
104
+ content_with_placeholders = (
105
+ content_with_placeholders[:adjusted_start] +
106
+ placeholder +
107
+ content_with_placeholders[adjusted_end:]
108
+ )
109
+
110
+ # Update offset for next replacement
111
+ offset += (end - start) - len(placeholder)
112
+
113
+ return structures, content_with_placeholders
114
+
115
+ def restore_structures(self, chunks: List[str], structures: List[Tuple[int, int, str, str]]) -> List[str]:
116
+ """
117
+ Restore markdown structures in chunks, keeping tables and code blocks intact.
118
+
119
+ Args:
120
+ chunks: List of text chunks with placeholders
121
+ structures: List of original structures
122
+
123
+ Returns:
124
+ List of chunks with restored structures
125
+ """
126
+ restored_chunks = []
127
+
128
+ for chunk in chunks:
129
+ restored_chunk = chunk
130
+
131
+ # Find placeholders in this chunk
132
+ placeholder_pattern = re.compile(r'__STRUCTURE_(\d+)_(\w+)__')
133
+
134
+ for match in placeholder_pattern.finditer(chunk):
135
+ structure_index = int(match.group(1))
136
+
137
+ if structure_index < len(structures):
138
+ original_structure = structures[structure_index][3]
139
+ restored_chunk = restored_chunk.replace(match.group(), original_structure)
140
+
141
+ restored_chunks.append(restored_chunk)
142
+
143
+ return restored_chunks
144
+
145
+ def chunk_document(self, content: str, source_metadata: Dict[str, Any]) -> List[Document]:
146
+ """
147
+ Chunk a markdown document while preserving tables and code blocks.
148
+
149
+ Args:
150
+ content: The markdown content to chunk
151
+ source_metadata: Metadata about the source document
152
+
153
+ Returns:
154
+ List of Document objects with chunked content and enhanced metadata
155
+ """
156
+ try:
157
+ # Extract markdown structures (tables, code blocks) and replace with placeholders
158
+ structures, content_with_placeholders = self.extract_markdown_structures(content)
159
+
160
+ # Create a document object with placeholders
161
+ doc = Document(
162
+ page_content=content_with_placeholders,
163
+ metadata=source_metadata
164
+ )
165
+
166
+ # Split the document into chunks
167
+ chunks = self.text_splitter.split_documents([doc])
168
+
169
+ # Restore markdown structures in chunks
170
+ chunk_contents = [chunk.page_content for chunk in chunks]
171
+ restored_contents = self.restore_structures(chunk_contents, structures)
172
+
173
+ # Create enhanced chunks with restored content
174
+ enhanced_chunks = []
175
+ for i, (chunk, restored_content) in enumerate(zip(chunks, restored_contents)):
176
+ # Add chunk-specific metadata
177
+ chunk.metadata.update({
178
+ "chunk_index": i,
179
+ "total_chunks": len(chunks),
180
+ "chunk_size": len(restored_content),
181
+ "chunk_id": f"{source_metadata.get('source_id', 'unknown')}_{i}",
182
+ "has_table": "table" in restored_content.lower() and "|" in restored_content,
183
+ "has_code": "```" in restored_content
184
+ })
185
+
186
+ # Update the chunk content with restored structures
187
+ chunk.page_content = restored_content
188
+ enhanced_chunks.append(chunk)
189
+
190
+ logger.info(f"Document chunked into {len(enhanced_chunks)} markdown-aware pieces")
191
+ return enhanced_chunks
192
+
193
+ except Exception as e:
194
+ logger.error(f"Error chunking markdown document: {e}")
195
+ # Fallback to regular chunking if markdown processing fails
196
+ return self._fallback_chunk(content, source_metadata)
197
+
198
+ def _fallback_chunk(self, content: str, source_metadata: Dict[str, Any]) -> List[Document]:
199
+ """Fallback chunking method if markdown-aware chunking fails."""
200
+ try:
201
+ doc = Document(page_content=content, metadata=source_metadata)
202
+ chunks = self.text_splitter.split_documents([doc])
203
+
204
+ for i, chunk in enumerate(chunks):
205
+ chunk.metadata.update({
206
+ "chunk_index": i,
207
+ "total_chunks": len(chunks),
208
+ "chunk_size": len(chunk.page_content),
209
+ "chunk_id": f"{source_metadata.get('source_id', 'unknown')}_{i}"
210
+ })
211
+
212
+ logger.warning(f"Used fallback chunking for {len(chunks)} pieces")
213
+ return chunks
214
+
215
+ except Exception as e:
216
+ logger.error(f"Error in fallback chunking: {e}")
217
+ raise
218
+
219
+ def chunk_multiple_documents(self, documents: List[Dict[str, Any]]) -> List[Document]:
220
+ """
221
+ Chunk multiple documents for batch processing.
222
+
223
+ Args:
224
+ documents: List of dictionaries with 'content' and 'metadata' keys
225
+
226
+ Returns:
227
+ List of chunked Document objects
228
+ """
229
+ all_chunks = []
230
+
231
+ for doc_data in documents:
232
+ content = doc_data.get('content', '')
233
+ metadata = doc_data.get('metadata', {})
234
+
235
+ if content.strip(): # Only process non-empty content
236
+ chunks = self.chunk_document(content, metadata)
237
+ all_chunks.extend(chunks)
238
+
239
+ logger.info(f"Chunked {len(documents)} documents into {len(all_chunks)} total chunks")
240
+ return all_chunks
241
+
242
+ def get_chunk_preview(self, chunks: List[Document], max_chunks: int = 5) -> str:
243
+ """
244
+ Generate a preview of chunks for debugging/logging.
245
+
246
+ Args:
247
+ chunks: List of Document chunks
248
+ max_chunks: Maximum number of chunks to include in preview
249
+
250
+ Returns:
251
+ String preview of chunks
252
+ """
253
+ preview = f"Document Chunks Preview ({len(chunks)} total chunks):\n"
254
+ preview += "=" * 50 + "\n"
255
+
256
+ for i, chunk in enumerate(chunks[:max_chunks]):
257
+ has_table = chunk.metadata.get('has_table', False)
258
+ has_code = chunk.metadata.get('has_code', False)
259
+
260
+ preview += f"Chunk {i + 1}:\n"
261
+ preview += f" Length: {len(chunk.page_content)} characters\n"
262
+ preview += f" Has Table: {has_table}, Has Code: {has_code}\n"
263
+ preview += f" Metadata: {chunk.metadata}\n"
264
+ preview += f" Content preview: {chunk.page_content[:100]}...\n"
265
+ preview += "-" * 30 + "\n"
266
+
267
+ if len(chunks) > max_chunks:
268
+ preview += f"... and {len(chunks) - max_chunks} more chunks\n"
269
+
270
+ return preview
271
+
272
+ # Global chunker instance with optimized settings for markdown RAG
273
+ document_chunker = MarkdownAwareChunker(chunk_size=1000, chunk_overlap=200)
src/rag/embeddings.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Embedding model management for RAG functionality."""
2
+
3
+ import os
4
+ from typing import Optional
5
+ from langchain_openai import OpenAIEmbeddings
6
+ from src.core.config import config
7
+ from src.core.logging_config import get_logger
8
+
9
+ logger = get_logger(__name__)
10
+
11
+ class EmbeddingManager:
12
+ """Manages embedding models for document vectorization."""
13
+
14
+ def __init__(self):
15
+ self._embedding_model: Optional[OpenAIEmbeddings] = None
16
+
17
+ def get_embedding_model(self) -> OpenAIEmbeddings:
18
+ """Get or create the OpenAI embedding model."""
19
+ if self._embedding_model is None:
20
+ try:
21
+ # Get OpenAI API key from config/environment
22
+ openai_api_key = config.api.openai_api_key or os.getenv("OPENAI_API_KEY")
23
+
24
+ if not openai_api_key:
25
+ raise ValueError("OpenAI API key not found. Please set OPENAI_API_KEY in environment variables.")
26
+
27
+ self._embedding_model = OpenAIEmbeddings(
28
+ model="text-embedding-3-small",
29
+ openai_api_key=openai_api_key,
30
+ chunk_size=1000, # Process documents in chunks
31
+ max_retries=3,
32
+ timeout=30
33
+ )
34
+
35
+ logger.info("OpenAI embedding model initialized successfully")
36
+
37
+ except Exception as e:
38
+ logger.error(f"Failed to initialize OpenAI embedding model: {e}")
39
+ raise
40
+
41
+ return self._embedding_model
42
+
43
+ def test_embedding_model(self) -> bool:
44
+ """Test if the embedding model is working correctly."""
45
+ try:
46
+ embedding_model = self.get_embedding_model()
47
+ # Test with a simple text
48
+ test_text = "This is a test for embedding functionality."
49
+ embedding = embedding_model.embed_query(test_text)
50
+
51
+ # Check if we got a valid embedding (list of floats)
52
+ if isinstance(embedding, list) and len(embedding) > 0 and isinstance(embedding[0], float):
53
+ logger.info("Embedding model test successful")
54
+ return True
55
+ else:
56
+ logger.error("Embedding model test failed: Invalid embedding format")
57
+ return False
58
+
59
+ except Exception as e:
60
+ logger.error(f"Embedding model test failed: {e}")
61
+ return False
62
+
63
+ # Global embedding manager instance
64
+ embedding_manager = EmbeddingManager()
src/rag/ingestion.py ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Document ingestion pipeline for RAG functionality."""
2
+
3
+ import os
4
+ import hashlib
5
+ from typing import List, Dict, Any, Optional, Tuple
6
+ from pathlib import Path
7
+ from datetime import datetime
8
+ from langchain_core.documents import Document
9
+ from src.rag.chunking import document_chunker
10
+ from src.rag.vector_store import vector_store_manager
11
+ from src.rag.embeddings import embedding_manager
12
+ from src.core.logging_config import get_logger
13
+
14
+ logger = get_logger(__name__)
15
+
16
+ class DocumentIngestionService:
17
+ """Service for ingesting documents into the RAG system."""
18
+
19
+ def __init__(self):
20
+ """Initialize the document ingestion service."""
21
+ self.processed_documents = set() # Track processed document hashes
22
+ logger.info("Document ingestion service initialized")
23
+
24
+ def create_document_hash(self, content: str) -> str:
25
+ """Create a hash for document content to avoid duplicates."""
26
+ return hashlib.sha256(content.encode('utf-8')).hexdigest()[:16]
27
+
28
+ def prepare_document_metadata(self,
29
+ source_path: Optional[str] = None,
30
+ doc_type: str = "markdown",
31
+ additional_metadata: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
32
+ """
33
+ Prepare metadata for a document.
34
+
35
+ Args:
36
+ source_path: Original document path
37
+ doc_type: Type of document (markdown, pdf, etc.)
38
+ additional_metadata: Additional metadata to include
39
+
40
+ Returns:
41
+ Dictionary with document metadata
42
+ """
43
+ metadata = {
44
+ "source": source_path or "user_upload",
45
+ "doc_type": doc_type,
46
+ "processed_at": datetime.now().isoformat(),
47
+ "source_id": self.create_document_hash(source_path or ""),
48
+ "ingestion_version": "1.0"
49
+ }
50
+
51
+ if additional_metadata:
52
+ metadata.update(additional_metadata)
53
+
54
+ return metadata
55
+
56
+ def ingest_markdown_content(self,
57
+ markdown_content: str,
58
+ source_path: Optional[str] = None,
59
+ metadata: Optional[Dict[str, Any]] = None) -> Tuple[bool, str, Dict[str, Any]]:
60
+ """
61
+ Ingest markdown content into the RAG system.
62
+
63
+ Args:
64
+ markdown_content: The markdown content to ingest
65
+ source_path: Optional source path/filename
66
+ metadata: Optional additional metadata
67
+
68
+ Returns:
69
+ Tuple of (success, message, ingestion_stats)
70
+ """
71
+ try:
72
+ if not markdown_content or not markdown_content.strip():
73
+ return False, "No content provided for ingestion", {}
74
+
75
+ # Create document hash to check for duplicates
76
+ content_hash = self.create_document_hash(markdown_content)
77
+
78
+ if content_hash in self.processed_documents:
79
+ logger.info(f"Document already processed: {content_hash}")
80
+ return True, "Document already exists in the system", {"status": "duplicate"}
81
+
82
+ # Prepare document metadata
83
+ doc_metadata = self.prepare_document_metadata(
84
+ source_path=source_path,
85
+ doc_type="markdown",
86
+ additional_metadata=metadata
87
+ )
88
+ doc_metadata["content_hash"] = content_hash
89
+ doc_metadata["content_length"] = len(markdown_content)
90
+
91
+ # Chunk the document using markdown-aware chunking
92
+ logger.info(f"Chunking document: {content_hash}")
93
+ chunks = document_chunker.chunk_document(markdown_content, doc_metadata)
94
+
95
+ if not chunks:
96
+ return False, "Failed to create document chunks", {}
97
+
98
+ # Add chunks to vector store
99
+ logger.info(f"Adding {len(chunks)} chunks to vector store")
100
+ doc_ids = vector_store_manager.add_documents(chunks)
101
+
102
+ if not doc_ids:
103
+ return False, "Failed to add documents to vector store", {}
104
+
105
+ # Mark document as processed
106
+ self.processed_documents.add(content_hash)
107
+
108
+ # Prepare ingestion statistics
109
+ ingestion_stats = {
110
+ "status": "success",
111
+ "content_hash": content_hash,
112
+ "total_chunks": len(chunks),
113
+ "document_ids": doc_ids,
114
+ "content_length": len(markdown_content),
115
+ "has_tables": any(chunk.metadata.get("has_table", False) for chunk in chunks),
116
+ "has_code": any(chunk.metadata.get("has_code", False) for chunk in chunks),
117
+ "processed_at": datetime.now().isoformat()
118
+ }
119
+
120
+ success_msg = f"Successfully ingested document with {len(chunks)} chunks"
121
+ logger.info(f"{success_msg}: {content_hash}")
122
+
123
+ return True, success_msg, ingestion_stats
124
+
125
+ except Exception as e:
126
+ error_msg = f"Error during document ingestion: {str(e)}"
127
+ logger.error(error_msg)
128
+ return False, error_msg, {"status": "error", "error": str(e)}
129
+
130
+ def ingest_from_conversion_result(self, conversion_result: Dict[str, Any]) -> Tuple[bool, str, Dict[str, Any]]:
131
+ """
132
+ Ingest a document from Markit conversion result.
133
+
134
+ Args:
135
+ conversion_result: Dictionary containing conversion results from Markit
136
+
137
+ Returns:
138
+ Tuple of (success, message, ingestion_stats)
139
+ """
140
+ try:
141
+ # Extract markdown content from conversion result
142
+ markdown_content = conversion_result.get("markdown_content", "")
143
+
144
+ if not markdown_content:
145
+ return False, "No markdown content found in conversion result", {}
146
+
147
+ # Extract metadata from conversion result
148
+ original_filename = conversion_result.get("original_filename", "unknown")
149
+ conversion_method = conversion_result.get("conversion_method", "unknown")
150
+
151
+ additional_metadata = {
152
+ "original_filename": original_filename,
153
+ "conversion_method": conversion_method,
154
+ "file_size": conversion_result.get("file_size", 0),
155
+ "conversion_time": conversion_result.get("conversion_time", 0)
156
+ }
157
+
158
+ # Ingest the markdown content
159
+ return self.ingest_markdown_content(
160
+ markdown_content=markdown_content,
161
+ source_path=original_filename,
162
+ metadata=additional_metadata
163
+ )
164
+
165
+ except Exception as e:
166
+ error_msg = f"Error ingesting from conversion result: {str(e)}"
167
+ logger.error(error_msg)
168
+ return False, error_msg, {"status": "error", "error": str(e)}
169
+
170
+ def get_ingestion_status(self) -> Dict[str, Any]:
171
+ """
172
+ Get current ingestion system status.
173
+
174
+ Returns:
175
+ Dictionary with system status information
176
+ """
177
+ status = {
178
+ "processed_documents": len(self.processed_documents),
179
+ "embedding_model_available": False,
180
+ "vector_store_available": False,
181
+ "system_ready": False
182
+ }
183
+
184
+ try:
185
+ # Check embedding model
186
+ status["embedding_model_available"] = embedding_manager.test_embedding_model()
187
+
188
+ # Check vector store
189
+ collection_info = vector_store_manager.get_collection_info()
190
+ status["vector_store_available"] = "error" not in collection_info
191
+ status["total_documents_in_store"] = collection_info.get("document_count", 0)
192
+
193
+ # System is ready if both components are available
194
+ status["system_ready"] = (
195
+ status["embedding_model_available"] and
196
+ status["vector_store_available"]
197
+ )
198
+
199
+ except Exception as e:
200
+ logger.error(f"Error checking ingestion status: {e}")
201
+ status["error"] = str(e)
202
+
203
+ return status
204
+
205
+ def clear_processed_documents(self) -> None:
206
+ """Clear the set of processed documents."""
207
+ self.processed_documents.clear()
208
+ logger.info("Cleared processed documents cache")
209
+
210
+ def test_ingestion_pipeline(self) -> Dict[str, Any]:
211
+ """
212
+ Test the complete ingestion pipeline with sample content.
213
+
214
+ Returns:
215
+ Dictionary with test results
216
+ """
217
+ test_results = {
218
+ "pipeline_test": False,
219
+ "chunking_test": False,
220
+ "embedding_test": False,
221
+ "vector_store_test": False,
222
+ "errors": []
223
+ }
224
+
225
+ try:
226
+ # Test with sample markdown content
227
+ test_content = """# Test Document
228
+
229
+ This is a test document for the RAG ingestion pipeline.
230
+
231
+ ## Features
232
+
233
+ - Document chunking
234
+ - Embedding generation
235
+ - Vector store integration
236
+
237
+ ## Sample Table
238
+
239
+ | Feature | Status | Priority |
240
+ |---------|--------|----------|
241
+ | Chunking | βœ… | High |
242
+ | Embeddings | βœ… | High |
243
+ | Vector Store | βœ… | Medium |
244
+
245
+ ```python
246
+ # Sample code block
247
+ def test_function():
248
+ return "Hello, RAG!"
249
+ ```
250
+
251
+ This document contains various markdown elements to test the ingestion pipeline.
252
+ """
253
+
254
+ # Test chunking
255
+ metadata = self.prepare_document_metadata(
256
+ source_path="test_document.md",
257
+ doc_type="markdown"
258
+ )
259
+
260
+ chunks = document_chunker.chunk_document(test_content, metadata)
261
+ test_results["chunking_test"] = len(chunks) > 0
262
+
263
+ if not test_results["chunking_test"]:
264
+ test_results["errors"].append("Chunking test failed: No chunks created")
265
+ return test_results
266
+
267
+ # Test embedding
268
+ test_results["embedding_test"] = embedding_manager.test_embedding_model()
269
+
270
+ if not test_results["embedding_test"]:
271
+ test_results["errors"].append("Embedding test failed")
272
+ return test_results
273
+
274
+ # Test vector store (add and retrieve)
275
+ doc_ids = vector_store_manager.add_documents(chunks[:1]) # Test with one chunk
276
+ test_results["vector_store_test"] = len(doc_ids) > 0
277
+
278
+ if test_results["vector_store_test"]:
279
+ # Test retrieval
280
+ search_results = vector_store_manager.similarity_search("test document", k=1)
281
+ test_results["vector_store_test"] = len(search_results) > 0
282
+
283
+ if not test_results["vector_store_test"]:
284
+ test_results["errors"].append("Vector store test failed")
285
+ return test_results
286
+
287
+ # Overall pipeline test
288
+ test_results["pipeline_test"] = (
289
+ test_results["chunking_test"] and
290
+ test_results["embedding_test"] and
291
+ test_results["vector_store_test"]
292
+ )
293
+
294
+ logger.info(f"Ingestion pipeline test completed: {test_results['pipeline_test']}")
295
+
296
+ except Exception as e:
297
+ error_msg = f"Pipeline test error: {str(e)}"
298
+ test_results["errors"].append(error_msg)
299
+ logger.error(error_msg)
300
+
301
+ return test_results
302
+
303
+ # Global document ingestion service instance
304
+ document_ingestion_service = DocumentIngestionService()
src/rag/memory.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Chat history and memory management for RAG conversations."""
2
+
3
+ import json
4
+ import os
5
+ from typing import List, Dict, Any, Optional, Tuple
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from dataclasses import dataclass, asdict
9
+ from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
10
+ from src.core.config import config
11
+ from src.core.logging_config import get_logger
12
+
13
+ logger = get_logger(__name__)
14
+
15
+ @dataclass
16
+ class ChatMessage:
17
+ """Represents a single chat message."""
18
+ role: str # "user" or "assistant"
19
+ content: str
20
+ timestamp: str
21
+ sources: Optional[List[str]] = None # Source documents used for the response
22
+
23
+ def to_dict(self) -> Dict[str, Any]:
24
+ """Convert to dictionary."""
25
+ return asdict(self)
26
+
27
+ @classmethod
28
+ def from_dict(cls, data: Dict[str, Any]) -> 'ChatMessage':
29
+ """Create from dictionary."""
30
+ return cls(**data)
31
+
32
+ @dataclass
33
+ class ChatSession:
34
+ """Represents a chat session with history."""
35
+ session_id: str
36
+ created_at: str
37
+ updated_at: str
38
+ messages: List[ChatMessage]
39
+ document_sources: List[str] # Documents available in this session
40
+
41
+ def to_dict(self) -> Dict[str, Any]:
42
+ """Convert to dictionary."""
43
+ return {
44
+ "session_id": self.session_id,
45
+ "created_at": self.created_at,
46
+ "updated_at": self.updated_at,
47
+ "messages": [msg.to_dict() for msg in self.messages],
48
+ "document_sources": self.document_sources
49
+ }
50
+
51
+ @classmethod
52
+ def from_dict(cls, data: Dict[str, Any]) -> 'ChatSession':
53
+ """Create from dictionary."""
54
+ messages = [ChatMessage.from_dict(msg) for msg in data.get("messages", [])]
55
+ return cls(
56
+ session_id=data["session_id"],
57
+ created_at=data["created_at"],
58
+ updated_at=data["updated_at"],
59
+ messages=messages,
60
+ document_sources=data.get("document_sources", [])
61
+ )
62
+
63
+ class ChatMemoryManager:
64
+ """Manages chat history and memory for RAG conversations."""
65
+
66
+ def __init__(self, persist_directory: Optional[str] = None):
67
+ """
68
+ Initialize the chat memory manager.
69
+
70
+ Args:
71
+ persist_directory: Directory to persist chat history
72
+ """
73
+ if persist_directory is None:
74
+ persist_directory = config.rag.chat_history_path
75
+
76
+ self.persist_directory = Path(persist_directory)
77
+ self.persist_directory.mkdir(parents=True, exist_ok=True)
78
+
79
+ self.current_session: Optional[ChatSession] = None
80
+
81
+ logger.info(f"ChatMemoryManager initialized with persist_directory={self.persist_directory}")
82
+
83
+ def create_session(self, document_sources: Optional[List[str]] = None) -> str:
84
+ """
85
+ Create a new chat session.
86
+
87
+ Args:
88
+ document_sources: List of document sources available for this session
89
+
90
+ Returns:
91
+ Session ID
92
+ """
93
+ session_id = f"session_{datetime.now().strftime('%Y%m%d_%H%M%S_%f')}"
94
+ now = datetime.now().isoformat()
95
+
96
+ self.current_session = ChatSession(
97
+ session_id=session_id,
98
+ created_at=now,
99
+ updated_at=now,
100
+ messages=[],
101
+ document_sources=document_sources or []
102
+ )
103
+
104
+ logger.info(f"Created new chat session: {session_id}")
105
+ return session_id
106
+
107
+ def add_message(self, role: str, content: str, sources: Optional[List[str]] = None) -> None:
108
+ """
109
+ Add a message to the current session.
110
+
111
+ Args:
112
+ role: "user" or "assistant"
113
+ content: Message content
114
+ sources: Source documents used (for assistant messages)
115
+ """
116
+ if self.current_session is None:
117
+ self.create_session()
118
+
119
+ message = ChatMessage(
120
+ role=role,
121
+ content=content,
122
+ timestamp=datetime.now().isoformat(),
123
+ sources=sources
124
+ )
125
+
126
+ self.current_session.messages.append(message)
127
+ self.current_session.updated_at = datetime.now().isoformat()
128
+
129
+ logger.info(f"Added {role} message to session {self.current_session.session_id}")
130
+
131
+ def get_conversation_history(self, max_messages: Optional[int] = None) -> List[Tuple[str, str]]:
132
+ """
133
+ Get conversation history in Gradio chat format.
134
+
135
+ Args:
136
+ max_messages: Maximum number of messages to return
137
+
138
+ Returns:
139
+ List of (user_message, assistant_message) tuples
140
+ """
141
+ if not self.current_session or not self.current_session.messages:
142
+ return []
143
+
144
+ messages = self.current_session.messages
145
+ if max_messages:
146
+ messages = messages[-max_messages:]
147
+
148
+ # Group messages into pairs
149
+ history = []
150
+ user_msg = None
151
+
152
+ for msg in messages:
153
+ if msg.role == "user":
154
+ user_msg = msg.content
155
+ elif msg.role == "assistant" and user_msg is not None:
156
+ history.append((user_msg, msg.content))
157
+ user_msg = None
158
+
159
+ return history
160
+
161
+ def get_context_messages(self, max_context_length: int = 4000) -> List[BaseMessage]:
162
+ """
163
+ Get recent messages formatted for LangChain context.
164
+
165
+ Args:
166
+ max_context_length: Maximum context length in characters
167
+
168
+ Returns:
169
+ List of LangChain message objects
170
+ """
171
+ if not self.current_session or not self.current_session.messages:
172
+ return []
173
+
174
+ context_messages = []
175
+ current_length = 0
176
+
177
+ # Start from the most recent messages and work backwards
178
+ for msg in reversed(self.current_session.messages):
179
+ msg_length = len(msg.content)
180
+
181
+ if current_length + msg_length > max_context_length:
182
+ break
183
+
184
+ if msg.role == "user":
185
+ context_messages.insert(0, HumanMessage(content=msg.content))
186
+ elif msg.role == "assistant":
187
+ context_messages.insert(0, AIMessage(content=msg.content))
188
+
189
+ current_length += msg_length
190
+
191
+ logger.info(f"Retrieved {len(context_messages)} context messages ({current_length} chars)")
192
+ return context_messages
193
+
194
+ def save_session(self) -> bool:
195
+ """
196
+ Save the current session to disk.
197
+
198
+ Returns:
199
+ True if successful, False otherwise
200
+ """
201
+ if not self.current_session:
202
+ return False
203
+
204
+ try:
205
+ session_file = self.persist_directory / f"{self.current_session.session_id}.json"
206
+
207
+ with open(session_file, 'w', encoding='utf-8') as f:
208
+ json.dump(self.current_session.to_dict(), f, indent=2, ensure_ascii=False)
209
+
210
+ logger.info(f"Saved session {self.current_session.session_id}")
211
+ return True
212
+
213
+ except Exception as e:
214
+ logger.error(f"Error saving session: {e}")
215
+ return False
216
+
217
+ def load_session(self, session_id: str) -> bool:
218
+ """
219
+ Load a session from disk.
220
+
221
+ Args:
222
+ session_id: Session ID to load
223
+
224
+ Returns:
225
+ True if successful, False otherwise
226
+ """
227
+ try:
228
+ session_file = self.persist_directory / f"{session_id}.json"
229
+
230
+ if not session_file.exists():
231
+ logger.warning(f"Session file not found: {session_id}")
232
+ return False
233
+
234
+ with open(session_file, 'r', encoding='utf-8') as f:
235
+ session_data = json.load(f)
236
+
237
+ self.current_session = ChatSession.from_dict(session_data)
238
+ logger.info(f"Loaded session {session_id}")
239
+ return True
240
+
241
+ except Exception as e:
242
+ logger.error(f"Error loading session {session_id}: {e}")
243
+ return False
244
+
245
+ def list_sessions(self) -> List[Dict[str, Any]]:
246
+ """
247
+ List all saved sessions.
248
+
249
+ Returns:
250
+ List of session metadata
251
+ """
252
+ sessions = []
253
+
254
+ try:
255
+ for session_file in self.persist_directory.glob("session_*.json"):
256
+ try:
257
+ with open(session_file, 'r', encoding='utf-8') as f:
258
+ session_data = json.load(f)
259
+
260
+ sessions.append({
261
+ "session_id": session_data["session_id"],
262
+ "created_at": session_data["created_at"],
263
+ "updated_at": session_data["updated_at"],
264
+ "message_count": len(session_data.get("messages", [])),
265
+ "document_sources": session_data.get("document_sources", [])
266
+ })
267
+
268
+ except Exception as e:
269
+ logger.warning(f"Error reading session file {session_file}: {e}")
270
+
271
+ except Exception as e:
272
+ logger.error(f"Error listing sessions: {e}")
273
+
274
+ # Sort by updated_at (most recent first)
275
+ sessions.sort(key=lambda x: x["updated_at"], reverse=True)
276
+ return sessions
277
+
278
+ def clear_current_session(self) -> None:
279
+ """Clear the current session."""
280
+ self.current_session = None
281
+ logger.info("Cleared current session")
282
+
283
+ # Global chat memory manager instance
284
+ chat_memory_manager = ChatMemoryManager()
src/rag/vector_store.py ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Vector store management using Chroma for document storage and retrieval."""
2
+
3
+ import os
4
+ from typing import List, Optional, Dict, Any, Tuple
5
+ from pathlib import Path
6
+ from langchain_chroma import Chroma
7
+ from langchain_core.documents import Document
8
+ from langchain_core.vectorstores import VectorStoreRetriever
9
+ from src.rag.embeddings import embedding_manager
10
+ from src.core.config import config
11
+ from src.core.logging_config import get_logger
12
+
13
+ logger = get_logger(__name__)
14
+
15
+ class VectorStoreManager:
16
+ """Manages Chroma vector store for document storage and retrieval."""
17
+
18
+ def __init__(self, persist_directory: Optional[str] = None, collection_name: str = "markit_documents"):
19
+ """
20
+ Initialize the vector store manager.
21
+
22
+ Args:
23
+ persist_directory: Directory to persist the vector database
24
+ collection_name: Name of the collection in the vector store
25
+ """
26
+ self.collection_name = collection_name
27
+
28
+ # Set default persist directory
29
+ if persist_directory is None:
30
+ persist_directory = config.rag.vector_store_path
31
+
32
+ self.persist_directory = str(Path(persist_directory).resolve())
33
+
34
+ # Ensure the directory exists
35
+ os.makedirs(self.persist_directory, exist_ok=True)
36
+
37
+ self._vector_store: Optional[Chroma] = None
38
+
39
+ logger.info(f"VectorStoreManager initialized with persist_directory={self.persist_directory}")
40
+
41
+ def get_vector_store(self) -> Chroma:
42
+ """Get or create the Chroma vector store."""
43
+ if self._vector_store is None:
44
+ try:
45
+ embedding_model = embedding_manager.get_embedding_model()
46
+
47
+ self._vector_store = Chroma(
48
+ collection_name=self.collection_name,
49
+ embedding_function=embedding_model,
50
+ persist_directory=self.persist_directory,
51
+ collection_metadata={"hnsw:space": "cosine"} # Use cosine similarity
52
+ )
53
+
54
+ logger.info(f"Vector store initialized with collection '{self.collection_name}'")
55
+
56
+ except Exception as e:
57
+ logger.error(f"Failed to initialize vector store: {e}")
58
+ raise
59
+
60
+ return self._vector_store
61
+
62
+ def add_documents(self, documents: List[Document]) -> List[str]:
63
+ """
64
+ Add documents to the vector store.
65
+
66
+ Args:
67
+ documents: List of Document objects to add
68
+
69
+ Returns:
70
+ List of document IDs that were added
71
+ """
72
+ try:
73
+ if not documents:
74
+ logger.warning("No documents provided to add to vector store")
75
+ return []
76
+
77
+ vector_store = self.get_vector_store()
78
+
79
+ # Generate unique IDs for documents
80
+ doc_ids = [f"doc_{i}_{hash(doc.page_content)}" for i, doc in enumerate(documents)]
81
+
82
+ # Add documents to the vector store
83
+ added_ids = vector_store.add_documents(documents=documents, ids=doc_ids)
84
+
85
+ logger.info(f"Added {len(added_ids)} documents to vector store")
86
+ return added_ids
87
+
88
+ except Exception as e:
89
+ logger.error(f"Error adding documents to vector store: {e}")
90
+ raise
91
+
92
+ def similarity_search(self, query: str, k: int = 4, score_threshold: Optional[float] = None) -> List[Document]:
93
+ """
94
+ Search for similar documents using semantic similarity.
95
+
96
+ Args:
97
+ query: Search query
98
+ k: Number of documents to return
99
+ score_threshold: Minimum similarity score threshold
100
+
101
+ Returns:
102
+ List of similar documents
103
+ """
104
+ try:
105
+ vector_store = self.get_vector_store()
106
+
107
+ if score_threshold is not None:
108
+ # Use similarity search with score threshold
109
+ docs_with_scores = vector_store.similarity_search_with_relevance_scores(
110
+ query=query,
111
+ k=k,
112
+ score_threshold=score_threshold
113
+ )
114
+ documents = [doc for doc, score in docs_with_scores]
115
+ else:
116
+ # Regular similarity search
117
+ documents = vector_store.similarity_search(query=query, k=k)
118
+
119
+ logger.info(f"Found {len(documents)} similar documents for query: '{query[:50]}...'")
120
+ return documents
121
+
122
+ except Exception as e:
123
+ logger.error(f"Error performing similarity search: {e}")
124
+ return []
125
+
126
+ def get_retriever(self, search_type: str = "similarity", search_kwargs: Optional[Dict[str, Any]] = None) -> VectorStoreRetriever:
127
+ """
128
+ Get a retriever for the vector store.
129
+
130
+ Args:
131
+ search_type: Type of search ("similarity", "mmr", "similarity_score_threshold")
132
+ search_kwargs: Additional search parameters
133
+
134
+ Returns:
135
+ VectorStoreRetriever object
136
+ """
137
+ try:
138
+ vector_store = self.get_vector_store()
139
+
140
+ if search_kwargs is None:
141
+ search_kwargs = {"k": 4}
142
+
143
+ retriever = vector_store.as_retriever(
144
+ search_type=search_type,
145
+ search_kwargs=search_kwargs
146
+ )
147
+
148
+ logger.info(f"Created retriever with search_type='{search_type}' and kwargs={search_kwargs}")
149
+ return retriever
150
+
151
+ except Exception as e:
152
+ logger.error(f"Error creating retriever: {e}")
153
+ raise
154
+
155
+ def get_collection_info(self) -> Dict[str, Any]:
156
+ """
157
+ Get information about the current collection.
158
+
159
+ Returns:
160
+ Dictionary with collection information
161
+ """
162
+ try:
163
+ vector_store = self.get_vector_store()
164
+
165
+ # Get collection count
166
+ count = vector_store._collection.count()
167
+
168
+ info = {
169
+ "collection_name": self.collection_name,
170
+ "persist_directory": self.persist_directory,
171
+ "document_count": count,
172
+ "embedding_model": "text-embedding-3-small"
173
+ }
174
+
175
+ logger.info(f"Collection info: {info}")
176
+ return info
177
+
178
+ except Exception as e:
179
+ logger.error(f"Error getting collection info: {e}")
180
+ return {"error": str(e)}
181
+
182
+ def delete_collection(self) -> bool:
183
+ """
184
+ Delete the current collection and reset the vector store.
185
+
186
+ Returns:
187
+ True if successful, False otherwise
188
+ """
189
+ try:
190
+ if self._vector_store is not None:
191
+ self._vector_store.delete_collection()
192
+ self._vector_store = None
193
+
194
+ logger.info(f"Deleted collection '{self.collection_name}'")
195
+ return True
196
+
197
+ except Exception as e:
198
+ logger.error(f"Error deleting collection: {e}")
199
+ return False
200
+
201
+ def search_with_metadata_filter(self, query: str, metadata_filter: Dict[str, Any], k: int = 4) -> List[Document]:
202
+ """
203
+ Search documents with metadata filtering.
204
+
205
+ Args:
206
+ query: Search query
207
+ metadata_filter: Metadata filter conditions
208
+ k: Number of documents to return
209
+
210
+ Returns:
211
+ List of filtered documents
212
+ """
213
+ try:
214
+ vector_store = self.get_vector_store()
215
+
216
+ documents = vector_store.similarity_search(
217
+ query=query,
218
+ k=k,
219
+ filter=metadata_filter
220
+ )
221
+
222
+ logger.info(f"Found {len(documents)} documents with metadata filter: {metadata_filter}")
223
+ return documents
224
+
225
+ except Exception as e:
226
+ logger.error(f"Error searching with metadata filter: {e}")
227
+ return []
228
+
229
+ # Global vector store manager instance
230
+ vector_store_manager = VectorStoreManager()
src/ui/ui.py CHANGED
@@ -14,6 +14,7 @@ from src.core.exceptions import (
14
  ConfigurationError
15
  )
16
  from src.core.logging_config import get_logger
 
17
 
18
  # Use centralized logging
19
  logger = get_logger(__name__)
@@ -166,11 +167,148 @@ def handle_convert(file_path, parser_name, ocr_method_name, output_format, is_ca
166
  html_output = f"<div class='output-container'>{formatted_content}</div>"
167
 
168
  logger.info("Conversion completed successfully")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  return html_output, download_file, gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  def create_ui():
172
  with gr.Blocks(css="""
173
- /* Simple output container with only one scrollbar */
 
 
 
 
 
174
  .output-container {
175
  max-height: 420px;
176
  overflow-y: auto;
@@ -178,7 +316,6 @@ def create_ui():
178
  padding: 10px;
179
  }
180
 
181
- /* Hide any scrollbars from parent containers */
182
  .gradio-container .prose {
183
  overflow: visible;
184
  }
@@ -190,115 +327,532 @@ def create_ui():
190
  margin-top: 10px;
191
  }
192
 
193
- /* Add margin above the provider/OCR options row */
194
  .provider-options-row {
195
  margin-top: 15px;
196
  margin-bottom: 15px;
197
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  """) as demo:
199
- # Simple title - no fancy HTML or CSS
200
- gr.Markdown("## Markit: Document to Markdown Converter")
201
-
202
- # State to track if cancellation is requested
203
- cancel_requested = gr.State(False)
204
- # State to store the conversion thread
205
- conversion_thread = gr.State(None)
206
- # State to store the output format (fixed to Markdown)
207
- output_format_state = gr.State("Markdown")
208
-
209
- # File input first
210
- file_input = gr.File(label="Upload Document", type="filepath")
211
 
212
- # Provider and OCR options below the file input
213
- with gr.Row(elem_classes=["provider-options-row"]):
214
- with gr.Column(scale=1):
215
- parser_names = ParserRegistry.get_parser_names()
 
 
 
 
 
 
 
 
216
 
217
- # Make MarkItDown the default parser if available
218
- default_parser = next((p for p in parser_names if p == "MarkItDown"), parser_names[0] if parser_names else "PyPdfium")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
220
- provider_dropdown = gr.Dropdown(
221
- label="Provider",
222
- choices=parser_names,
223
- value=default_parser,
224
- interactive=True
225
  )
226
- with gr.Column(scale=1):
227
- default_ocr_options = ParserRegistry.get_ocr_options(default_parser)
228
- default_ocr = default_ocr_options[0] if default_ocr_options else "No OCR"
229
 
230
- ocr_dropdown = gr.Dropdown(
231
- label="OCR Options",
232
- choices=default_ocr_options,
233
- value=default_ocr,
234
- interactive=True
 
 
 
 
 
 
 
 
 
 
235
  )
236
-
237
- # Simple output container with just one scrollbar
238
- file_display = gr.HTML(
239
- value="<div class='output-container'></div>",
240
- label="Converted Content"
241
- )
242
-
243
- file_download = gr.File(label="Download File")
244
-
245
- # Processing controls row
246
- with gr.Row(elem_classes=["processing-controls"]):
247
- convert_button = gr.Button("Convert", variant="primary")
248
- cancel_button = gr.Button("Cancel", variant="stop", visible=False)
249
 
250
- # Event handlers
251
- provider_dropdown.change(
252
- lambda p: gr.Dropdown(
253
- choices=["Plain Text", "Formatted Text"] if "GOT-OCR" in p else ParserRegistry.get_ocr_options(p),
254
- value="Plain Text" if "GOT-OCR" in p else (ParserRegistry.get_ocr_options(p)[0] if ParserRegistry.get_ocr_options(p) else None)
255
- ),
256
- inputs=[provider_dropdown],
257
- outputs=[ocr_dropdown]
258
- )
259
 
260
- # Reset cancel flag when starting conversion
261
- def start_conversion():
262
- global conversion_cancelled
263
- conversion_cancelled.clear()
264
- logger.info("Starting conversion with cancellation flag cleared")
265
- return gr.update(visible=False), gr.update(visible=True), False
 
 
 
 
 
 
 
 
 
266
 
267
- # Set cancel flag and terminate thread when cancel button is clicked
268
- def request_cancellation(thread):
269
- global conversion_cancelled
270
- conversion_cancelled.set()
271
- logger.info("Cancel button clicked, cancellation flag set")
272
-
273
- # Try to join the thread with a timeout
274
- if thread is not None:
275
- logger.info(f"Attempting to join conversion thread: {thread}")
276
- thread.join(timeout=0.5)
277
- if thread.is_alive():
278
- logger.warning("Thread did not finish within timeout")
279
-
280
- # Add immediate feedback to the user
281
- return gr.update(visible=True), gr.update(visible=False), True, None
 
 
 
 
282
 
283
- # Start conversion sequence
284
- convert_button.click(
285
- fn=start_conversion,
286
- inputs=[],
287
- outputs=[convert_button, cancel_button, cancel_requested],
288
- queue=False # Execute immediately
289
- ).then(
290
- fn=handle_convert,
291
- inputs=[file_input, provider_dropdown, ocr_dropdown, output_format_state, cancel_requested],
292
- outputs=[file_display, file_download, convert_button, cancel_button, conversion_thread]
293
- )
294
-
295
- # Handle cancel button click
296
- cancel_button.click(
297
- fn=request_cancellation,
298
- inputs=[conversion_thread],
299
- outputs=[convert_button, cancel_button, cancel_requested, conversion_thread],
300
- queue=False # Execute immediately
301
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
  return demo
304
 
 
14
  ConfigurationError
15
  )
16
  from src.core.logging_config import get_logger
17
+ from src.rag import rag_chat_service, document_ingestion_service
18
 
19
  # Use centralized logging
20
  logger = get_logger(__name__)
 
167
  html_output = f"<div class='output-container'>{formatted_content}</div>"
168
 
169
  logger.info("Conversion completed successfully")
170
+
171
+ # Auto-ingest the converted document for RAG
172
+ try:
173
+ conversion_result = {
174
+ "markdown_content": content,
175
+ "original_filename": Path(file_path).name if file_path else "unknown",
176
+ "conversion_method": parser_name,
177
+ "file_size": Path(file_path).stat().st_size if file_path and Path(file_path).exists() else 0,
178
+ "conversion_time": 0 # Could be tracked if needed
179
+ }
180
+
181
+ success, ingestion_msg, stats = document_ingestion_service.ingest_from_conversion_result(conversion_result)
182
+ if success:
183
+ logger.info(f"Document auto-ingested for RAG: {ingestion_msg}")
184
+ else:
185
+ logger.warning(f"Document ingestion failed: {ingestion_msg}")
186
+ except Exception as e:
187
+ logger.error(f"Error during auto-ingestion: {e}")
188
+
189
  return html_output, download_file, gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
190
 
191
+ def handle_chat_message(message, history):
192
+ """Handle a new chat message with streaming response."""
193
+ if not message or not message.strip():
194
+ return "", history
195
+
196
+ try:
197
+ # Add user message to history
198
+ history = history or []
199
+ history.append({"role": "user", "content": message})
200
+
201
+ # Add assistant message placeholder
202
+ history.append({"role": "assistant", "content": ""})
203
+
204
+ # Get response from RAG service
205
+ response_text = ""
206
+ for chunk in rag_chat_service.chat_stream(message):
207
+ response_text += chunk
208
+ # Update the last message in history with the current response
209
+ history[-1]["content"] = response_text
210
+ yield "", history
211
+
212
+ logger.info(f"Chat response completed for message: {message[:50]}...")
213
+
214
+ except Exception as e:
215
+ error_msg = f"Error generating response: {str(e)}"
216
+ logger.error(error_msg)
217
+ if history and len(history) > 0:
218
+ history[-1]["content"] = f"❌ {error_msg}"
219
+ else:
220
+ history = [
221
+ {"role": "user", "content": message},
222
+ {"role": "assistant", "content": f"❌ {error_msg}"}
223
+ ]
224
+ yield "", history
225
+
226
+ def start_new_chat_session():
227
+ """Start a new chat session."""
228
+ try:
229
+ session_id = rag_chat_service.start_new_session()
230
+ logger.info(f"Started new chat session: {session_id}")
231
+ return [], f"βœ… New chat session started: {session_id}"
232
+ except Exception as e:
233
+ error_msg = f"Error starting new session: {str(e)}"
234
+ logger.error(error_msg)
235
+ return [], f"❌ {error_msg}"
236
+
237
+ def get_chat_status():
238
+ """Get current chat system status."""
239
+ try:
240
+ # Check ingestion status
241
+ ingestion_status = document_ingestion_service.get_ingestion_status()
242
+
243
+ # Check usage stats
244
+ usage_stats = rag_chat_service.get_usage_stats()
245
+
246
+ # Modern status card design with better styling
247
+ status_html = f"""
248
+ <div class="status-card">
249
+ <div class="status-header">
250
+ <h3>πŸ’¬ Chat System Status</h3>
251
+ <div class="status-indicator {'status-ready' if ingestion_status.get('system_ready', False) else 'status-not-ready'}">
252
+ {'🟒 READY' if ingestion_status.get('system_ready', False) else 'πŸ”΄ NOT READY'}
253
+ </div>
254
+ </div>
255
+
256
+ <div class="status-grid">
257
+ <div class="status-item">
258
+ <div class="status-label">Documents Processed</div>
259
+ <div class="status-value">{ingestion_status.get('processed_documents', 0)}</div>
260
+ </div>
261
+ <div class="status-item">
262
+ <div class="status-label">Vector Store</div>
263
+ <div class="status-value">{ingestion_status.get('total_documents_in_store', 0)} docs</div>
264
+ </div>
265
+ <div class="status-item">
266
+ <div class="status-label">Session Usage</div>
267
+ <div class="status-value">{usage_stats.get('session_messages', 0)}/{usage_stats.get('session_limit', 50)}</div>
268
+ </div>
269
+ <div class="status-item">
270
+ <div class="status-label">Hourly Usage</div>
271
+ <div class="status-value">{usage_stats.get('hourly_messages', 0)}/{usage_stats.get('hourly_limit', 100)}</div>
272
+ </div>
273
+ </div>
274
+
275
+ <div class="status-services">
276
+ <div class="service-status {'service-ready' if ingestion_status.get('embedding_model_available', False) else 'service-error'}">
277
+ <span class="service-icon">🧠</span>
278
+ <span>Embedding Model</span>
279
+ <span class="service-indicator">{'βœ…' if ingestion_status.get('embedding_model_available', False) else '❌'}</span>
280
+ </div>
281
+ <div class="service-status {'service-ready' if ingestion_status.get('vector_store_available', False) else 'service-error'}">
282
+ <span class="service-icon">πŸ—„οΈ</span>
283
+ <span>Vector Store</span>
284
+ <span class="service-indicator">{'βœ…' if ingestion_status.get('vector_store_available', False) else '❌'}</span>
285
+ </div>
286
+ </div>
287
+ </div>
288
+ """
289
+
290
+ return status_html
291
+
292
+ except Exception as e:
293
+ error_msg = f"Error getting chat status: {str(e)}"
294
+ logger.error(error_msg)
295
+ return f"""
296
+ <div class="status-card status-error">
297
+ <div class="status-header">
298
+ <h3>❌ System Error</h3>
299
+ </div>
300
+ <p class="error-message">{error_msg}</p>
301
+ </div>
302
+ """
303
+
304
  def create_ui():
305
  with gr.Blocks(css="""
306
+ /* Global styles */
307
+ .gradio-container {
308
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
309
+ }
310
+
311
+ /* Document converter styles */
312
  .output-container {
313
  max-height: 420px;
314
  overflow-y: auto;
 
316
  padding: 10px;
317
  }
318
 
 
319
  .gradio-container .prose {
320
  overflow: visible;
321
  }
 
327
  margin-top: 10px;
328
  }
329
 
 
330
  .provider-options-row {
331
  margin-top: 15px;
332
  margin-bottom: 15px;
333
  }
334
+
335
+ /* Chat Tab Styles - Complete redesign */
336
+ .chat-tab-container {
337
+ max-width: 1200px;
338
+ margin: 0 auto;
339
+ padding: 20px;
340
+ }
341
+
342
+ .chat-header {
343
+ text-align: center;
344
+ margin-bottom: 30px;
345
+ padding: 20px;
346
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
347
+ border-radius: 15px;
348
+ color: white;
349
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
350
+ }
351
+
352
+ .chat-header h2 {
353
+ margin: 0;
354
+ font-size: 1.8em;
355
+ font-weight: 600;
356
+ }
357
+
358
+ .chat-header p {
359
+ margin: 10px 0 0 0;
360
+ opacity: 0.9;
361
+ font-size: 1.1em;
362
+ }
363
+
364
+ /* Status Card Styling */
365
+ .status-card {
366
+ background: #ffffff;
367
+ border: 1px solid #e1e5e9;
368
+ border-radius: 12px;
369
+ padding: 20px;
370
+ margin-bottom: 25px;
371
+ box-shadow: 0 2px 10px rgba(0,0,0,0.05);
372
+ transition: all 0.3s ease;
373
+ }
374
+
375
+ .status-card:hover {
376
+ box-shadow: 0 4px 20px rgba(0,0,0,0.1);
377
+ }
378
+
379
+ .status-header {
380
+ display: flex;
381
+ justify-content: space-between;
382
+ align-items: center;
383
+ margin-bottom: 20px;
384
+ padding-bottom: 15px;
385
+ border-bottom: 2px solid #f0f2f5;
386
+ }
387
+
388
+ .status-header h3 {
389
+ margin: 0;
390
+ color: #2c3e50;
391
+ font-size: 1.3em;
392
+ font-weight: 600;
393
+ }
394
+
395
+ .status-indicator {
396
+ padding: 8px 16px;
397
+ border-radius: 25px;
398
+ font-weight: 600;
399
+ font-size: 0.9em;
400
+ letter-spacing: 0.5px;
401
+ }
402
+
403
+ .status-ready {
404
+ background: #d4edda;
405
+ color: #155724;
406
+ border: 1px solid #c3e6cb;
407
+ }
408
+
409
+ .status-not-ready {
410
+ background: #f8d7da;
411
+ color: #721c24;
412
+ border: 1px solid #f5c6cb;
413
+ }
414
+
415
+ .status-grid {
416
+ display: grid;
417
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
418
+ gap: 15px;
419
+ margin-bottom: 20px;
420
+ }
421
+
422
+ .status-item {
423
+ background: #f8f9fa;
424
+ padding: 15px;
425
+ border-radius: 8px;
426
+ text-align: center;
427
+ border: 1px solid #e9ecef;
428
+ }
429
+
430
+ .status-label {
431
+ font-size: 0.85em;
432
+ color: #6c757d;
433
+ margin-bottom: 5px;
434
+ font-weight: 500;
435
+ }
436
+
437
+ .status-value {
438
+ font-size: 1.4em;
439
+ font-weight: 700;
440
+ color: #495057;
441
+ }
442
+
443
+ .status-services {
444
+ display: flex;
445
+ gap: 15px;
446
+ flex-wrap: wrap;
447
+ }
448
+
449
+ .service-status {
450
+ display: flex;
451
+ align-items: center;
452
+ gap: 8px;
453
+ padding: 10px 15px;
454
+ border-radius: 8px;
455
+ font-weight: 500;
456
+ flex: 1;
457
+ min-width: 200px;
458
+ }
459
+
460
+ .service-ready {
461
+ background: #d4edda;
462
+ color: #155724;
463
+ border: 1px solid #c3e6cb;
464
+ }
465
+
466
+ .service-error {
467
+ background: #f8d7da;
468
+ color: #721c24;
469
+ border: 1px solid #f5c6cb;
470
+ }
471
+
472
+ .service-icon {
473
+ font-size: 1.2em;
474
+ }
475
+
476
+ .service-indicator {
477
+ margin-left: auto;
478
+ }
479
+
480
+ .status-error {
481
+ border-color: #dc3545;
482
+ background: #f8d7da;
483
+ }
484
+
485
+ .error-message {
486
+ color: #721c24;
487
+ margin: 0;
488
+ font-weight: 500;
489
+ }
490
+
491
+ /* Control buttons styling */
492
+ .control-buttons {
493
+ display: flex;
494
+ gap: 12px;
495
+ justify-content: flex-end;
496
+ margin-bottom: 25px;
497
+ }
498
+
499
+ .control-btn {
500
+ padding: 10px 20px;
501
+ border-radius: 8px;
502
+ font-weight: 500;
503
+ transition: all 0.3s ease;
504
+ border: none;
505
+ cursor: pointer;
506
+ }
507
+
508
+ .btn-refresh {
509
+ background: #17a2b8;
510
+ color: white;
511
+ }
512
+
513
+ .btn-refresh:hover {
514
+ background: #138496;
515
+ transform: translateY(-1px);
516
+ }
517
+
518
+ .btn-new-session {
519
+ background: #28a745;
520
+ color: white;
521
+ }
522
+
523
+ .btn-new-session:hover {
524
+ background: #218838;
525
+ transform: translateY(-1px);
526
+ }
527
+
528
+ /* Chat interface styling */
529
+ .chat-main-container {
530
+ background: #ffffff;
531
+ border-radius: 15px;
532
+ box-shadow: 0 4px 20px rgba(0,0,0,0.08);
533
+ overflow: hidden;
534
+ margin-bottom: 25px;
535
+ }
536
+
537
+ .chat-container {
538
+ background: #ffffff;
539
+ border-radius: 12px;
540
+ border: 1px solid #e1e5e9;
541
+ overflow: hidden;
542
+ }
543
+
544
+ /* Custom chatbot styling */
545
+ .gradio-chatbot {
546
+ border: none !important;
547
+ background: #ffffff;
548
+ }
549
+
550
+ .gradio-chatbot .message {
551
+ padding: 15px 20px;
552
+ margin: 10px;
553
+ border-radius: 12px;
554
+ }
555
+
556
+ .gradio-chatbot .message.user {
557
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
558
+ color: white;
559
+ margin-left: 50px;
560
+ }
561
+
562
+ .gradio-chatbot .message.assistant {
563
+ background: #f8f9fa;
564
+ border: 1px solid #e9ecef;
565
+ margin-right: 50px;
566
+ }
567
+
568
+ /* Input area styling */
569
+ .chat-input-container {
570
+ background: #ffffff;
571
+ padding: 20px;
572
+ border-top: 1px solid #e1e5e9;
573
+ border-radius: 0 0 15px 15px;
574
+ }
575
+
576
+ .input-row {
577
+ display: flex;
578
+ gap: 12px;
579
+ align-items: center;
580
+ }
581
+
582
+ .message-input {
583
+ flex: 1;
584
+ border: 2px solid #e1e5e9;
585
+ border-radius: 25px;
586
+ padding: 12px 20px;
587
+ font-size: 1em;
588
+ transition: all 0.3s ease;
589
+ resize: none;
590
+ max-height: 120px;
591
+ min-height: 48px;
592
+ }
593
+
594
+ .message-input:focus {
595
+ border-color: #667eea;
596
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
597
+ outline: none;
598
+ }
599
+
600
+ .send-button {
601
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
602
+ color: white;
603
+ border: none;
604
+ border-radius: 12px;
605
+ padding: 12px 24px;
606
+ min-width: 80px;
607
+ height: 48px;
608
+ margin-right: 10px;
609
+ cursor: pointer;
610
+ transition: all 0.3s ease;
611
+ display: flex;
612
+ align-items: center;
613
+ justify-content: center;
614
+ font-size: 1em;
615
+ font-weight: 600;
616
+ letter-spacing: 0.5px;
617
+ }
618
+
619
+ .send-button:hover {
620
+ transform: scale(1.05);
621
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
622
+ }
623
+
624
+ /* Session info styling */
625
+ .session-info {
626
+ background: #e7f3ff;
627
+ border: 1px solid #b3d9ff;
628
+ border-radius: 8px;
629
+ padding: 15px;
630
+ color: #0056b3;
631
+ font-weight: 500;
632
+ text-align: center;
633
+ }
634
+
635
+ /* Responsive design */
636
+ @media (max-width: 768px) {
637
+ .chat-tab-container {
638
+ padding: 10px;
639
+ }
640
+
641
+ .status-grid {
642
+ grid-template-columns: repeat(2, 1fr);
643
+ }
644
+
645
+ .service-status {
646
+ min-width: 100%;
647
+ }
648
+
649
+ .control-buttons {
650
+ flex-direction: column;
651
+ gap: 8px;
652
+ }
653
+
654
+ .gradio-chatbot .message.user {
655
+ margin-left: 20px;
656
+ }
657
+
658
+ .gradio-chatbot .message.assistant {
659
+ margin-right: 20px;
660
+ }
661
+ }
662
  """) as demo:
663
+ # Modern title with better styling
664
+ gr.Markdown("""
665
+ # πŸš€ Markit
666
+ ## Document to Markdown Converter with RAG Chat
667
+ """)
 
 
 
 
 
 
 
668
 
669
+ with gr.Tabs():
670
+ # Document Converter Tab
671
+ with gr.TabItem("πŸ“„ Document Converter"):
672
+ # State to track if cancellation is requested
673
+ cancel_requested = gr.State(False)
674
+ # State to store the conversion thread
675
+ conversion_thread = gr.State(None)
676
+ # State to store the output format (fixed to Markdown)
677
+ output_format_state = gr.State("Markdown")
678
+
679
+ # File input first
680
+ file_input = gr.File(label="Upload Document", type="filepath")
681
 
682
+ # Provider and OCR options below the file input
683
+ with gr.Row(elem_classes=["provider-options-row"]):
684
+ with gr.Column(scale=1):
685
+ parser_names = ParserRegistry.get_parser_names()
686
+
687
+ # Make MarkItDown the default parser if available
688
+ default_parser = next((p for p in parser_names if p == "MarkItDown"), parser_names[0] if parser_names else "PyPdfium")
689
+
690
+ provider_dropdown = gr.Dropdown(
691
+ label="Provider",
692
+ choices=parser_names,
693
+ value=default_parser,
694
+ interactive=True
695
+ )
696
+ with gr.Column(scale=1):
697
+ default_ocr_options = ParserRegistry.get_ocr_options(default_parser)
698
+ default_ocr = default_ocr_options[0] if default_ocr_options else "No OCR"
699
+
700
+ ocr_dropdown = gr.Dropdown(
701
+ label="OCR Options",
702
+ choices=default_ocr_options,
703
+ value=default_ocr,
704
+ interactive=True
705
+ )
706
 
707
+ # Simple output container with just one scrollbar
708
+ file_display = gr.HTML(
709
+ value="<div class='output-container'></div>",
710
+ label="Converted Content"
 
711
  )
 
 
 
712
 
713
+ file_download = gr.File(label="Download File")
714
+
715
+ # Processing controls row
716
+ with gr.Row(elem_classes=["processing-controls"]):
717
+ convert_button = gr.Button("Convert", variant="primary")
718
+ cancel_button = gr.Button("Cancel", variant="stop", visible=False)
719
+
720
+ # Event handlers for document converter
721
+ provider_dropdown.change(
722
+ lambda p: gr.Dropdown(
723
+ choices=["Plain Text", "Formatted Text"] if "GOT-OCR" in p else ParserRegistry.get_ocr_options(p),
724
+ value="Plain Text" if "GOT-OCR" in p else (ParserRegistry.get_ocr_options(p)[0] if ParserRegistry.get_ocr_options(p) else None)
725
+ ),
726
+ inputs=[provider_dropdown],
727
+ outputs=[ocr_dropdown]
728
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
729
 
730
+ # Reset cancel flag when starting conversion
731
+ def start_conversion():
732
+ global conversion_cancelled
733
+ conversion_cancelled.clear()
734
+ logger.info("Starting conversion with cancellation flag cleared")
735
+ return gr.update(visible=False), gr.update(visible=True), False
 
 
 
736
 
737
+ # Set cancel flag and terminate thread when cancel button is clicked
738
+ def request_cancellation(thread):
739
+ global conversion_cancelled
740
+ conversion_cancelled.set()
741
+ logger.info("Cancel button clicked, cancellation flag set")
742
+
743
+ # Try to join the thread with a timeout
744
+ if thread is not None:
745
+ logger.info(f"Attempting to join conversion thread: {thread}")
746
+ thread.join(timeout=0.5)
747
+ if thread.is_alive():
748
+ logger.warning("Thread did not finish within timeout")
749
+
750
+ # Add immediate feedback to the user
751
+ return gr.update(visible=True), gr.update(visible=False), True, None
752
 
753
+ # Start conversion sequence
754
+ convert_button.click(
755
+ fn=start_conversion,
756
+ inputs=[],
757
+ outputs=[convert_button, cancel_button, cancel_requested],
758
+ queue=False # Execute immediately
759
+ ).then(
760
+ fn=handle_convert,
761
+ inputs=[file_input, provider_dropdown, ocr_dropdown, output_format_state, cancel_requested],
762
+ outputs=[file_display, file_download, convert_button, cancel_button, conversion_thread]
763
+ )
764
+
765
+ # Handle cancel button click
766
+ cancel_button.click(
767
+ fn=request_cancellation,
768
+ inputs=[conversion_thread],
769
+ outputs=[convert_button, cancel_button, cancel_requested, conversion_thread],
770
+ queue=False # Execute immediately
771
+ )
772
 
773
+ # Chat Tab - Completely redesigned
774
+ with gr.TabItem("πŸ’¬ Chat with Documents"):
775
+ with gr.Column(elem_classes=["chat-tab-container"]):
776
+ # Modern header
777
+ gr.HTML("""
778
+ <div class="chat-header">
779
+ <h2>πŸ’¬ Chat with your converted documents</h2>
780
+ <p>Ask questions about your documents using advanced RAG technology</p>
781
+ </div>
782
+ """)
783
+
784
+ # Status section with modern design
785
+ status_display = gr.HTML(value=get_chat_status())
786
+
787
+ # Control buttons
788
+ with gr.Row(elem_classes=["control-buttons"]):
789
+ refresh_status_btn = gr.Button("πŸ”„ Refresh Status", elem_classes=["control-btn", "btn-refresh"])
790
+ new_session_btn = gr.Button("πŸ†• New Session", elem_classes=["control-btn", "btn-new-session"])
791
+
792
+ # Main chat interface
793
+ with gr.Column(elem_classes=["chat-main-container"]):
794
+ chatbot = gr.Chatbot(
795
+ elem_classes=["chat-container"],
796
+ height=500,
797
+ show_label=False,
798
+ show_share_button=False,
799
+ bubble_full_width=False,
800
+ type="messages",
801
+ placeholder="Start a conversation by asking questions about your documents..."
802
+ )
803
+
804
+ # Input area
805
+ with gr.Row(elem_classes=["input-row"]):
806
+ msg_input = gr.Textbox(
807
+ placeholder="Ask questions about your documents...",
808
+ show_label=False,
809
+ scale=5,
810
+ lines=1,
811
+ max_lines=3,
812
+ elem_classes=["message-input"]
813
+ )
814
+ send_btn = gr.Button("Submit", elem_classes=["send-button"], scale=0)
815
+
816
+ # Session info with better styling
817
+ session_info = gr.HTML(
818
+ value='<div class="session-info">No active session - Click "New Session" to start</div>'
819
+ )
820
+
821
+ # Event handlers for chat
822
+ def clear_input():
823
+ return ""
824
+
825
+ # Send message when button clicked or Enter pressed
826
+ msg_input.submit(
827
+ fn=handle_chat_message,
828
+ inputs=[msg_input, chatbot],
829
+ outputs=[msg_input, chatbot]
830
+ )
831
+
832
+ send_btn.click(
833
+ fn=handle_chat_message,
834
+ inputs=[msg_input, chatbot],
835
+ outputs=[msg_input, chatbot]
836
+ )
837
+
838
+ # New session handler with improved feedback
839
+ def enhanced_new_session():
840
+ history, info = start_new_chat_session()
841
+ session_html = f'<div class="session-info">{info}</div>'
842
+ return history, session_html
843
+
844
+ new_session_btn.click(
845
+ fn=enhanced_new_session,
846
+ inputs=[],
847
+ outputs=[chatbot, session_info]
848
+ )
849
+
850
+ # Refresh status handler
851
+ refresh_status_btn.click(
852
+ fn=get_chat_status,
853
+ inputs=[],
854
+ outputs=[status_display]
855
+ )
856
 
857
  return demo
858