feat: Windows native support — run Hindsight without Docker (#699)
* feat: Windows native support — run Hindsight without Docker on Windows
Four compatibility fixes that allow Hindsight to run natively on Windows
with an external PostgreSQL + pgvector installation:
1. **pyproject.toml**: Conditional event loop dependency
- `winloop` on Windows (sys_platform == 'win32')
- `uvloop` on Linux/macOS (sys_platform != 'win32')
2. **main.py**: winloop integration via `winloop.install()`
- Patches asyncio event loop policy globally before uvicorn starts
- uvicorn sees "asyncio" but runs winloop underneath (same perf as uvloop)
- Falls back to default asyncio if winloop unavailable
3. **metrics.py**: Guard `resource` module import
- `resource` is Unix-only (getrusage, getrlimit)
- Conditional import with None fallback
- Skip process metrics collection on Windows
4. **fact_storage.py**: Cross-platform strftime
- `%-d` (no-padding day) is glibc-only, fails on Windows
- Replaced with `%d` + `.replace(" 0", " ")` for same output
## Windows Setup Guide
### Prerequisites
- Python 3.11+
- PostgreSQL 17 with pgvector extension
- Ollama (for local embeddings) or external embedding provider
### Install PostgreSQL + pgvector on Windows
```bash
winget install PostgreSQL.PostgreSQL.17
# Build pgvector from source (requires Visual Studio Build Tools)
git clone https://github.com/pgvector/pgvector.git
# In x64 Native Tools Command Prompt:
set PGROOT=C:\Program Files\PostgreSQL\17
nmake /F Makefile.win
nmake /F Makefile.win install
# Enable extension
psql -U postgres -d hindsight -c "CREATE EXTENSION IF NOT EXISTS vector;"
```
### Install and Run Hindsight
```bash
pip install -e ".[embedded-db]"
# Set environment variables
set HINDSIGHT_API_LLM_PROVIDER=openai
set HINDSIGHT_API_LLM_API_KEY=your-api-key
set HINDSIGHT_API_LLM_BASE_URL=https://your-llm-endpoint/v1
set HINDSIGHT_API_LLM_MODEL=your-model
set HINDSIGHT_API_DATABASE_URL=postgresql://postgres@localhost:5432/hindsight
set HINDSIGHT_API_EMBEDDING_PROVIDER=ollama
set HINDSIGHT_API_PORT=8889
hindsight-api
```
Data persists in PostgreSQL on your local disk — survives reboots,
updates, and anything that would wipe a Docker volume.
Tested on Windows 11 with PostgreSQL 17.9, pgvector 0.8.2,
Python 3.11, RTX 5080 (CUDA embeddings + reranking).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: handle strftime ValueError on Windows in fact_storage
The strftime call on occurred_start/occurred_end can raise ValueError
on Windows when the datetime object has unexpected format properties.
Wrap in try/except to gracefully skip date signal rather than crash
the entire retain batch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6bb83f4600
commit
c5700ff5b4
4 changed files with 34 additions and 13 deletions
|
|
@ -81,9 +81,15 @@ async def insert_facts_batch(
|
|||
if fact.entities:
|
||||
signal_parts.extend(e.name for e in fact.entities)
|
||||
if fact.occurred_start:
|
||||
signal_parts.append(fact.occurred_start.strftime("%B %-d %Y"))
|
||||
try:
|
||||
signal_parts.append(fact.occurred_start.strftime("%B %d %Y").lstrip("0").replace(" 0", " "))
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
if fact.occurred_end and fact.occurred_end != fact.occurred_start:
|
||||
signal_parts.append(fact.occurred_end.strftime("%B %-d %Y"))
|
||||
try:
|
||||
signal_parts.append(fact.occurred_end.strftime("%B %d %Y").lstrip("0").replace(" 0", " "))
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
text_signals_list.append(" ".join(signal_parts) if signal_parts else None)
|
||||
|
||||
# Batch insert all facts
|
||||
|
|
|
|||
|
|
@ -211,14 +211,23 @@ def main():
|
|||
# Prepare uvicorn config
|
||||
# When using workers or reload, we must use import string so each worker can import the app
|
||||
use_import_string = args.workers > 1 or args.reload
|
||||
# Check for uvloop availability
|
||||
# Check for uvloop/winloop availability
|
||||
import sys
|
||||
loop_impl = "asyncio"
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
import winloop
|
||||
winloop.install() # Patches asyncio globally — uvicorn uses "asyncio" but gets winloop
|
||||
loop_impl = "asyncio" # Tell uvicorn "asyncio" — it's now winloop underneath
|
||||
print("winloop installed as asyncio event loop policy (Windows uvloop port)")
|
||||
except ImportError:
|
||||
print("winloop not installed, using default asyncio event loop")
|
||||
else:
|
||||
try:
|
||||
import uvloop # noqa: F401
|
||||
|
||||
loop_impl = "uvloop"
|
||||
print("uvloop available, will use for event loop")
|
||||
except ImportError:
|
||||
loop_impl = "asyncio"
|
||||
print("uvloop not installed, using default asyncio event loop")
|
||||
|
||||
uvicorn_config = {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ This module provides metrics for:
|
|||
|
||||
import logging
|
||||
import os
|
||||
import resource
|
||||
try:
|
||||
import resource
|
||||
except ImportError:
|
||||
resource = None # Windows doesn't have resource module
|
||||
import threading
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
|
|
@ -455,6 +458,8 @@ class MetricsCollector(MetricsCollectorBase):
|
|||
|
||||
def _setup_process_metrics(self):
|
||||
"""Set up observable gauges for process metrics."""
|
||||
if resource is None:
|
||||
return # Skip process metrics on Windows
|
||||
|
||||
def get_cpu_times(_options):
|
||||
"""Get process CPU times."""
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ dependencies = [
|
|||
"litellm>=1.0.0,<=1.82.6", # 1.82.7+ contains a supply chain attack (malicious .pth credential stealer)
|
||||
"markitdown[pdf,docx,pptx,xlsx,xls]>=0.1.4", # File to markdown conversion
|
||||
"obstore>=0.4.0", # S3/GCS/Azure object storage client (Rust-backed)
|
||||
"uvloop>=0.22.1",
|
||||
"winloop>=0.1.0; sys_platform == 'win32'",
|
||||
"uvloop>=0.22.1; sys_platform != 'win32'",
|
||||
# Transitive dependency security fixes
|
||||
"pyasn1>=0.6.3", # DoS vulnerability fix
|
||||
"urllib3>=2.6.3", # Decompression-bomb safeguards bypass fix
|
||||
|
|
|
|||
Loading…
Reference in a new issue