Spaces:
Runtime error
Runtime error
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.
- .gitignore +3 -0
- =0.1.0 +96 -0
- =0.2.0 +36 -0
- =0.3.0 +26 -0
- =0.5.0 +102 -0
- =2.0.0 +56 -0
- =3.0.0 +50 -0
- README.md +76 -12
- app.py +18 -0
- requirements.txt +10 -1
- setup.sh +13 -1
- src/core/config.py +72 -0
- src/core/environment.py +59 -0
- src/rag/__init__.py +17 -0
- src/rag/chat_service.py +367 -0
- src/rag/chunking.py +273 -0
- src/rag/embeddings.py +64 -0
- src/rag/ingestion.py +304 -0
- src/rag/memory.py +284 -0
- src/rag/vector_store.py +230 -0
- 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
|
| 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
|
| 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 |
-
|
| 88 |
-
|
|
|
|
|
|
|
| 89 |
- **"MarkItDown"** for comprehensive document conversion
|
| 90 |
- **"Docling"** for advanced PDF understanding and table extraction
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 200 |
-
gr.Markdown("
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 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 |
-
|
| 213 |
-
|
| 214 |
-
with gr.
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
|
| 217 |
-
#
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 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 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
inputs=[provider_dropdown],
|
| 257 |
-
outputs=[ocr_dropdown]
|
| 258 |
-
)
|
| 259 |
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 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 |
|