Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .DS_Store
Binary file not shown.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
OLLAMA_MODEL=qwen3:30b
GRAPHRAG_API_KEY=ollama
84 changes: 84 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Solace-AI: GraphRAG Final Reconciled Dockerfile
# Optimized for Python 3.12 / Podman / macOS Volume Permissions (2026)
FROM python:3.12-slim

# 1. Install uv for lightning-fast installs (2026 standard)
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

# Set environment variables for container stability
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
DEBIAN_FRONTEND=noninteractive \
PIP_DEFAULT_TIMEOUT=1000 \
PIP_RETRIES=10 \
POETRY_DYNAMIC_VERSIONING_BYPASS="0.0.0"

WORKDIR /app

# 2. Install system dependencies required for GraphRAG and Git builds
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential curl git ffmpeg && rm -rf /var/lib/apt/lists/*

# 3. Install stable background dependencies using uv
# Pinned versions preserved exactly from your original requirement
RUN uv pip install --system --no-cache-dir \
gradio==4.44.1 \
huggingface_hub==0.23.4 \
pydantic==2.7.4 \
jinja2==3.1.4 \
starlette==0.38.2 \
fastapi \
uvicorn \
python-dotenv \
pandas \
tiktoken \
langchain==0.2.16 \
langchain-community==0.2.16 \
langchain-core==0.2.38 \
aiohttp \
pyyaml \
requests \
duckduckgo-search \
ollama \
plotly

# 4. Install GraphRAG separately (v0.1.1)
RUN uv pip install --system --no-cache-dir \
git+https://github.com/microsoft/graphrag.git@v0.1.1

# 5. THE PATH FIX: Copy everything from the current directory
COPY . .

# 6. THE SURGICAL FIXES (All Original Functionality Preserved + Critical Fixes)
# A. Revert import path for v0.1.1 compatibility
RUN sed -i 's/graphrag.query.llm.text_utils/graphrag.query.context_builder.entity_extraction/g' api.py

# B. Force FastAPI and Gradio to bind to 0.0.0.0
RUN sed -i 's/127.0.0.1/0.0.0.0/g' api.py app.py index_app.py

# C. Update Ollama hostname to host.docker.internal for Mac GPU bridge
RUN sed -i 's/localhost:11434/host.docker.internal:11434/g' app.py index_app.py api.py

# D. Dynamic pathing for the Gradio client patch
RUN sed -i 's/def _json_schema_to_python_type(schema, defs):/def _json_schema_to_python_type(schema, defs):\n if isinstance(schema, bool):\n return "Any"/g' $(python -c "import site; print(site.getsitepackages()[0])")/gradio_client/utils.py

# E. UI PATH DISCOVERY FIX: Hard-code root to /app to resolve "No folder selected"
RUN sed -i "s|root_dir = os.path.dirname(os.path.abspath(__file__))|root_dir = '/app'|g" app.py

# F. INTERNAL NETWORKING FIX: Force the Indexer to talk to the API container
# This stops the "Connection refused" error when indexer tries to find localhost
RUN sed -i "s|localhost:8012|graphrag-api:8012|g" index_app.py || true

# 7. Setup persistent directories and wipe "ghost" build artifacts
RUN mkdir -p indexing lancedb input cache output
RUN chmod -R 777 /app

# 8. PERMISSION FIX: Run as root for Podman/macOS Volume compatibility
# This ensures that the Indexer can write .tmp files and parquet files to your Mac
USER root

# Expose ports: API (8012), Chat UI (7860), Indexer (7861)
EXPOSE 8012 7860 7861

# 9. Start the API
CMD ["python", "api.py", "--host", "0.0.0.0", "--port", "8012"]
55 changes: 39 additions & 16 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,35 @@
import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="gradio_client.documentation")

# --- PATH & DISCOVERY HELPERS ---
ROOT_DIR = "/app" if os.path.exists("/app/indexing") else "."
INDEXING_ROOT = os.path.join(ROOT_DIR, "indexing")

def list_output_folders(root):
"""Dynamically lists indexing runs in the output folder."""
output_dir = os.path.join(root, "output")
if not os.path.exists(output_dir):
return []
return sorted([f for f in os.listdir(output_dir) if os.path.isdir(os.path.join(output_dir, f)) and f != ".DS_Store"], reverse=True)

def fetch_models(base_url, api_key, service_type):
"""Fetches live models and filters out the 123b/24b strings that cause 404s."""
try:
if service_type.lower() == "ollama" or "11434" in str(base_url):
clean_url = base_url.replace("/v1", "")
response = requests.get(f"{clean_url}/api/tags", timeout=5)
data = response.json()
models = [m['name'] for m in data.get('models', [])]
else:
response = requests.get(f"{base_url}/v1/models", headers={"Authorization": f"Bearer {api_key}"}, timeout=5)
data = response.json()
models = [m['id'] for m in data.get('data', [])]

# PURGE GHOSTS: This stops the 404 error by removing non-existent model strings
ghosts = ["mistral-large:123b-instruct-2407-q4_0", "mistral-small:24b"]
return [m for m in models if m not in ghosts]
except:
return []

load_dotenv('indexing/.env')

Expand Down Expand Up @@ -388,32 +417,26 @@ def parse_query_response(response: str):
print(f"Error parsing query response: {str(e)}")
return response

def send_message(query_type, query, history, system_message, temperature, max_tokens, preset, community_level, response_type, custom_cli_args, selected_folder):
def send_message(query_type, query, history, system_message, temperature, max_tokens, preset, community_level, response_type, custom_cli_args, selected_folder, selected_model):
try:
if query_type in ["global", "local"]:
cli_args = construct_cli_args(query_type, preset, community_level, response_type, custom_cli_args, query, selected_folder)
logging.info(f"Executing {query_type} search with command: {' '.join(cli_args)}")
result = run_graphrag_query(cli_args)
parsed_result = parse_query_response(result)
logging.info(f"Parsed query result: {parsed_result}")
else: # Direct chat
llm_model = os.getenv("LLM_MODEL")
api_base = os.getenv("LLM_API_BASE")
logging.info(f"Executing direct chat with model: {llm_model}")

# THE FIX: Use 'selected_model' from the UI instead of os.getenv
logging.info(f"Executing direct chat with live selection: {selected_model}")
try:
result = chat_with_llm(query, history, system_message, temperature, max_tokens, llm_model, api_base)
parsed_result = result # No parsing needed for direct chat
logging.info(f"Direct chat result: {parsed_result[:100]}...") # Log first 100 chars of result
result = chat_with_llm(query, history, system_message, temperature, max_tokens, selected_model, api_base)
parsed_result = result
except Exception as chat_error:
logging.error(f"Error in chat_with_llm: {str(chat_error)}")
raise RuntimeError(f"Direct chat failed: {str(chat_error)}")

history.append((query, parsed_result))
except Exception as e:
error_message = f"An error occurred: {str(e)}"
logging.error(error_message)
logging.exception("Exception details:")
history.append((query, error_message))

return history, gr.update(value=""), update_logs()
Expand Down Expand Up @@ -1554,9 +1577,7 @@ def create_gradio_interface():
)
selected_folder = gr.Dropdown(
label="Select Index Folder to Chat With",
choices=list_output_folders("./indexing"),
value=None,
interactive=True
choices=list_output_folders(ROOT_DIR)
)
refresh_folder_btn = gr.Button("Refresh Folders", variant="secondary")
clear_chat_btn = gr.Button("Clear Chat", variant="secondary")
Expand Down Expand Up @@ -1703,7 +1724,8 @@ def update_custom_options(preset):
community_level,
response_type,
custom_cli_args,
selected_folder
selected_folder,
llm_model_dropdown
],
outputs=[chatbot, query_input, log_output]
)
Expand All @@ -1721,7 +1743,8 @@ def update_custom_options(preset):
community_level,
response_type,
custom_cli_args,
selected_folder
selected_folder,
llm_model_dropdown
],
outputs=[chatbot, query_input, log_output]
)
Expand Down
66 changes: 66 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# cbt-graph-rag-containerised: Dynamic Model Discovery (2026)
version: '3.8'

services:
graphrag-api:
build: .
container_name: graphrag-api
environment:
- PYTHONPATH=/app
- GRAPHRAG_API_KEY=ollama
- OLLAMA_BASE_URL=http://host.docker.internal:11434/v1
ports:
- "8012:8012"
volumes:
- ./input:/app/input
- ./indexing:/app/indexing
- ./lancedb:/app/lancedb
- ./indexing/output:/app/output
- ./indexing/output:/app/indexing/output
command: python api.py --host 0.0.0.0 --port 8012

graphrag-ui:
build: .
container_name: graphrag-ui
environment:
- PYTHONPATH=/app
- GRAPHRAG_API_KEY=ollama
# THE FIX: Point UI to the API service name
- API_URL=http://graphrag-api:8012
- GRADIO_SERVER_NAME=0.0.0.0
- LLM_API_BASE=http://host.docker.internal:11434/v1
- EMBEDDINGS_API_BASE=http://host.docker.internal:11434/v1
ports:
- "7860:7860"
volumes:
- .:/app # Mount the whole root so .env updates are persisted to your Mac
- ./indexing/output:/app/output
- ./indexing/output:/app/indexing/output
depends_on:
- graphrag-api
command: python app.py

graphrag-indexer:
build: .
container_name: graphrag-indexer
environment:
- PYTHONPATH=/app
- GRAPHRAG_API_KEY=ollama
- API_URL=http://graphrag-api:8012
- GRADIO_SERVER_NAME=0.0.0.0
- LLM_API_BASE=http://host.docker.internal:11434/v1
- EMBEDDINGS_API_BASE=http://host.docker.internal:11434/v1
ports:
- "7861:7861"
volumes:
- .:/app
- ./indexing/output:/app/output
- ./indexing/output:/app/indexing/output
depends_on:
- graphrag-api
command: python index_app.py

networks:
default:
name: graphrag-network
driver: bridge
Binary file modified indexing/.DS_Store
100644 → 100755
Binary file not shown.
12 changes: 8 additions & 4 deletions indexing/.env
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
LLM_PROVIDER=openai
LLM_API_BASE=http://localhost:11434/v1
LLM_MODEL='mistral-large:123b-instruct-2407-q4_0'
LLM_API_BASE=http://host.docker.internal:11434/v1
LLM_MODEL=mistral-small:24b
LLM_API_KEY=12345

EMBEDDINGS_PROVIDER=openai
EMBEDDINGS_API_BASE=http://localhost:11434
EMBEDDINGS_MODEL='snowflake-arctic-embed:335m'
EMBEDDINGS_API_BASE=http://host.docker.internal:11434/v1
EMBEDDINGS_MODEL=nomic-embed-text:latest
EMBEDDINGS_API_KEY=12345


Expand All @@ -17,3 +17,7 @@ EMBEDDINGS_SERVICE_TYPE=openai_embedding

API_URL=http://localhost:8012
API_PORT=8012
CONTEXT_WINDOW=4096
SYSTEM_MESSAGE=You are a helpful AI assistant.
TEMPERATURE=0.5
MAX_TOKENS=1024
15 changes: 15 additions & 0 deletions indexing/input/MSA_Novalytica_Distributor.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
MASTER SOFTWARE RESELLER AGREEMENT

EFFECTIVE DATE: January 1, 2025

PARTIES: Novalytica, Inc. ("Vendor") and Global Tech Distribution Corp ("Distributor")

1. APPOINTMENT > Vendor hereby appoints Distributor as a non-exclusive distributor of the Vendor's enterprise software products within the agreed territory.

2. BUNDLING AND RESALE > Distributor may grant downstream System Integrators (SIs) the right to bundle Vendor software with authorized hardware and services.

3. COMPLIANCE AND RESTRICTIONS > 3.1 General Compliance: Distributor and its authorized SIs shall comply with all applicable local and international trade laws.

3.2 Government Sales Restriction: Resale of enterprise software licenses to municipal or national government entities is strictly prohibited without 30 days prior written consent from Novalytica, Inc. Failure to obtain consent will result in immediate termination of the downstream license and potential liability for the Distributor.

4. TERM AND TERMINATION > This agreement shall remain in effect for three (3) years from the Effective Date unless terminated earlier due to a material breach.
27 changes: 27 additions & 0 deletions indexing/input/PO_SingaporeLTA_Acme.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
OFFICIAL PURCHASE ORDER

PO NUMBER: LTA-2026-88492

DATE OF ISSUE: April 2, 2026

BUYER: > Singapore Land Transport Authority (LTA)

1 Hampshire Road

Singapore 219428

VENDOR: > Acme Systems LLC

Enterprise Solutions Division

LINE ITEMS: > 1. Custom AI Traffic Analytics Dashboard - Qty: 1 - $125,000

2. High-Performance Compute Cluster Setup - Qty: 1 - $85,000

3. Data Analytics Enterprise Software Bundle (Includes Quantus AI Studio & Novalytica Enterprise License) - Qty: 1 - $150,000

4. 24/7 Managed Support Services (1 Year) - Qty: 1 - $40,000

TOTAL: $400,000 USD

TERMS: Net 30. Delivery expected by May 15, 2026.
17 changes: 17 additions & 0 deletions indexing/input/Partner_Agreement_AcmeSystems.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
SYSTEM INTEGRATOR PARTNER AGREEMENT

EFFECTIVE DATE: June 15, 2025

PARTIES: Global Tech Distribution Corp ("Distributor") and Acme Systems LLC ("System Integrator")

1. PARTNER STATUS > Distributor grants Acme Systems LLC the status of "Authorized Platinum Integrator."

2. AUTHORIZED PORTFOLIO > Acme Systems LLC is hereby authorized to procure, bundle, and resell the following software platforms provided by the Distributor:

Quantus AI Studio

Novalytica Enterprise

NexusSearch Cloud

3. LIABILITY AND INDEMNIFICATION > Acme Systems LLC assumes full responsibility for front-line deployment and integration services. Distributor shall not be held liable for SLA breaches caused by Acme Systems LLC's custom integrations. Acme Systems LLC must adhere to all original Vendor MSAs passed through the Distributor.
Empty file.
Empty file modified indexing/output/.DS_Store
100644 → 100755
Empty file.
Empty file modified indexing/output/20240722-073448/.DS_Store
100644 → 100755
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file modified indexing/output/20240722-073448/artifacts/merged_graph.graphml
100644 → 100755
Empty file.
Empty file.
Empty file modified indexing/output/20240722-073448/artifacts/stats.json
100644 → 100755
Empty file.
Empty file.
Empty file modified indexing/output/20240722-073448/artifacts/top_level_nodes.json
100644 → 100755
Empty file.
Empty file modified indexing/output/20240722-073448/reports/indexing-engine.log
100644 → 100755
Empty file.
Empty file modified indexing/output/20240722-073448/reports/logs.json
100644 → 100755
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
30 changes: 30 additions & 0 deletions indexing/output/20260415-034514/artifacts/stats.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"total_runtime": 7.076313495635986,
"num_documents": 4,
"input_load_time": 0,
"workflows": {
"create_base_text_units": {
"overall": 6.424093008041382,
"0_orderby": 0.00046443939208984375,
"1_zip": 0.0004918575286865234,
"2_aggregate_override": 0.0011968612670898438,
"3_chunk": 6.416795015335083,
"4_select": 0.0006856918334960938,
"5_unroll": 0.0008931159973144531,
"6_rename": 0.0003285408020019531,
"7_genid": 0.0008454322814941406,
"8_unzip": 0.0004532337188720703,
"9_copy": 0.00020647048950195312,
"10_filter": 0.0015630722045898438
},
"create_base_extracted_entities": {
"overall": 0.38549017906188965,
"0_entity_extract": 0.3832550048828125,
"1_merge_graphs": 0.002129077911376953
},
"create_summarized_entities": {
"overall": 0.0012483596801757812,
"0_summarize_descriptions": 0.0011658668518066406
}
}
}
Loading