Compare commits

...
5 Commits
93 changed files with 391 additions and 587 deletions
+4 -1
View File
@@ -52,7 +52,10 @@ class IdleTimeoutMiddleware:
logger.info(f"Idle timeout reached ({self.idle_timeout}s), shutting down daemon")
# Give a moment for any in-flight requests
await asyncio.sleep(1)
os._exit(0)
# Send SIGTERM to ourselves to trigger graceful shutdown
import signal
os.kill(os.getpid(), signal.SIGTERM)
class DaemonLock:
@@ -178,108 +178,16 @@ class LocalSTCrossEncoder(CrossEncoderModel):
else:
logger.info("Reranker: local provider initialized (using existing executor)")
def _is_xpc_error(self, error: Exception) -> bool:
"""
Check if an error is an XPC connection error (macOS daemon issue).
On macOS, long-running daemons can lose XPC connections to system services
when the process is idle for extended periods.
"""
error_str = str(error).lower()
return "xpc_error_connection_invalid" in error_str or "xpc error" in error_str
def _reinitialize_model_sync(self) -> None:
"""
Clear and reinitialize the cross-encoder model synchronously.
This is used to recover from XPC errors on macOS where the
PyTorch/MPS backend loses its connection to system services.
"""
logger.warning(f"Reinitializing reranker model {self.model_name} due to backend error")
# Clear existing model
self._model = None
# Force garbage collection to free resources
import gc
import torch
gc.collect()
# If using CUDA/MPS, clear the cache
if torch.cuda.is_available():
torch.cuda.empty_cache()
elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
try:
torch.mps.empty_cache()
except AttributeError:
pass # Method might not exist in all PyTorch versions
# Reinitialize the model
try:
from sentence_transformers import CrossEncoder
except ImportError:
raise ImportError(
"sentence-transformers is required for LocalSTCrossEncoder. "
"Install it with: pip install sentence-transformers"
)
# Determine device based on hardware availability
if self.force_cpu:
device = "cpu"
else:
# Wrap in try-except to gracefully handle any device detection issues
device = "cpu" # Default to CPU
try:
has_gpu = torch.cuda.is_available() or (
hasattr(torch.backends, "mps") and torch.backends.mps.is_available()
)
if has_gpu:
device = None # Let sentence-transformers auto-detect GPU/MPS
except Exception as e:
logger.warning(f"Failed to detect GPU/MPS during reinit, falling back to CPU: {e}")
self._model = CrossEncoder(
self.model_name,
device=device,
model_kwargs={"low_cpu_mem_usage": False},
)
logger.info("Reranker: local provider reinitialized successfully")
def _predict_with_recovery(self, pairs: list[tuple[str, str]]) -> list[float]:
"""
Predict with automatic recovery from XPC errors.
This runs synchronously in the thread pool.
"""
max_retries = 1
for attempt in range(max_retries + 1):
try:
scores = self._model.predict(pairs, show_progress_bar=False)
return scores.tolist() if hasattr(scores, "tolist") else list(scores)
except Exception as e:
# Check if this is an XPC error (macOS daemon issue)
if self._is_xpc_error(e) and attempt < max_retries:
logger.warning(f"XPC error detected in reranker (attempt {attempt + 1}): {e}")
try:
self._reinitialize_model_sync()
logger.info("Reranker reinitialized successfully, retrying prediction")
continue
except Exception as reinit_error:
logger.error(f"Failed to reinitialize reranker: {reinit_error}")
raise Exception(f"Failed to recover from XPC error: {str(e)}")
else:
# Not an XPC error or out of retries
raise
def _predict_sync(self, pairs: list[tuple[str, str]]) -> list[float]:
"""Synchronous prediction wrapper for thread pool execution."""
scores = self._model.predict(pairs, show_progress_bar=False)
return scores.tolist() if hasattr(scores, "tolist") else list(scores)
async def predict(self, pairs: list[tuple[str, str]]) -> list[float]:
"""
Score query-document pairs for relevance.
Uses a dedicated thread pool with limited workers to prevent CPU thrashing.
Automatically recovers from XPC errors on macOS by reinitializing the model.
Args:
pairs: List of (query, document) tuples to score
@@ -294,7 +202,7 @@ class LocalSTCrossEncoder(CrossEncoderModel):
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
LocalSTCrossEncoder._executor,
self._predict_with_recovery,
self._predict_sync,
pairs,
)
@@ -166,82 +166,10 @@ class LocalSTEmbeddings(Embeddings):
self._dimension = self._model.get_sentence_embedding_dimension()
logger.info(f"Embeddings: local provider initialized (dim: {self._dimension})")
def _is_xpc_error(self, error: Exception) -> bool:
"""
Check if an error is an XPC connection error (macOS daemon issue).
On macOS, long-running daemons can lose XPC connections to system services
when the process is idle for extended periods.
"""
error_str = str(error).lower()
return "xpc_error_connection_invalid" in error_str or "xpc error" in error_str
def _reinitialize_model_sync(self) -> None:
"""
Clear and reinitialize the embedding model synchronously.
This is used to recover from XPC errors on macOS where the
PyTorch/MPS backend loses its connection to system services.
"""
logger.warning(f"Reinitializing embedding model {self.model_name} due to backend error")
# Clear existing model
self._model = None
# Force garbage collection to free resources
import gc
import torch
gc.collect()
# If using CUDA/MPS, clear the cache
if torch.cuda.is_available():
torch.cuda.empty_cache()
elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
try:
torch.mps.empty_cache()
except AttributeError:
pass # Method might not exist in all PyTorch versions
# Reinitialize the model (inline version of initialize() but synchronous)
try:
from sentence_transformers import SentenceTransformer
except ImportError:
raise ImportError(
"sentence-transformers is required for LocalSTEmbeddings. "
"Install it with: pip install sentence-transformers"
)
# Determine device based on hardware availability
if self.force_cpu:
device = "cpu"
else:
# Wrap in try-except to gracefully handle any device detection issues
device = "cpu" # Default to CPU
try:
has_gpu = torch.cuda.is_available() or (
hasattr(torch.backends, "mps") and torch.backends.mps.is_available()
)
if has_gpu:
device = None # Let sentence-transformers auto-detect GPU/MPS
except Exception as e:
logger.warning(f"Failed to detect GPU/MPS during reinit, falling back to CPU: {e}")
self._model = SentenceTransformer(
self.model_name,
device=device,
model_kwargs={"low_cpu_mem_usage": False},
)
logger.info("Embeddings: local provider reinitialized successfully")
def encode(self, texts: list[str]) -> list[list[float]]:
"""
Generate embeddings for a list of texts.
Automatically recovers from XPC errors on macOS by reinitializing the model.
Args:
texts: List of text strings to encode
@@ -251,26 +179,8 @@ class LocalSTEmbeddings(Embeddings):
if self._model is None:
raise RuntimeError("Embeddings not initialized. Call initialize() first.")
# Try encoding with automatic recovery from XPC errors
max_retries = 1
for attempt in range(max_retries + 1):
try:
embeddings = self._model.encode(texts, convert_to_numpy=True, show_progress_bar=False)
return [emb.tolist() for emb in embeddings]
except Exception as e:
# Check if this is an XPC error (macOS daemon issue)
if self._is_xpc_error(e) and attempt < max_retries:
logger.warning(f"XPC error detected in embedding generation (attempt {attempt + 1}): {e}")
try:
self._reinitialize_model_sync()
logger.info("Model reinitialized successfully, retrying embedding generation")
continue
except Exception as reinit_error:
logger.error(f"Failed to reinitialize model: {reinit_error}")
raise Exception(f"Failed to recover from XPC error: {str(e)}")
else:
# Not an XPC error or out of retries
raise
embeddings = self._model.encode(texts, convert_to_numpy=True, show_progress_bar=False)
return [emb.tolist() for emb in embeddings]
class RemoteTEIEmbeddings(Embeddings):
+3 -9
View File
@@ -140,13 +140,6 @@ def main():
args.port = DEFAULT_DAEMON_PORT
args.host = "127.0.0.1" # Only bind to localhost for security
# Force CPU mode for daemon to avoid macOS MPS/XPC issues
# MPS (Metal Performance Shaders) has unstable XPC connections in background processes
# that can cause assertion failures and process crashes at the C++ level
# (which Python exception handlers cannot catch)
os.environ["HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU"] = "1"
os.environ["HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU"] = "1"
# Check if another daemon is already running
daemon_lock = DaemonLock()
if not daemon_lock.acquire():
@@ -353,6 +346,7 @@ def main():
# Start idle checker in daemon mode
if idle_middleware is not None:
# Start the idle checker in a background thread with its own event loop
import logging
import threading
def run_idle_checker():
@@ -363,8 +357,8 @@ def main():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(idle_middleware._check_idle())
except Exception:
pass
except Exception as e:
logging.error(f"Idle checker error: {e}", exc_info=True)
threading.Thread(target=run_idle_checker, daemon=True).start()
@@ -1,148 +0,0 @@
"""
Tests for XPC error recovery in LocalSTCrossEncoder.
This tests the automatic reinitialization of the cross-encoder model when
XPC connection errors occur on macOS (common in long-running daemon processes).
"""
import asyncio
from unittest.mock import MagicMock, patch
import pytest
from hindsight_api.engine.cross_encoder import LocalSTCrossEncoder
class TestCrossEncoderXPCErrorRecovery:
"""Tests for XPC error detection and recovery in LocalSTCrossEncoder."""
@pytest.fixture
def cross_encoder(self):
"""Create a LocalSTCrossEncoder instance."""
return LocalSTCrossEncoder(model_name="cross-encoder/ms-marco-TinyBERT-L-2-v2")
def test_is_xpc_error_detection(self, cross_encoder):
"""Test that XPC errors are correctly detected."""
# Test various XPC error message formats
xpc_error = Exception("Compiler encountered XPC_ERROR_CONNECTION_INVALID (is the OS shutting down?)")
assert cross_encoder._is_xpc_error(xpc_error)
xpc_error2 = Exception("XPC error occurred")
assert cross_encoder._is_xpc_error(xpc_error2)
# Test that non-XPC errors are not detected
normal_error = Exception("Some other error")
assert not cross_encoder._is_xpc_error(normal_error)
@pytest.mark.asyncio
async def test_predict_with_xpc_recovery(self, cross_encoder):
"""Test that predict() recovers from XPC errors by reinitializing."""
# Initialize the cross-encoder
await cross_encoder.initialize()
# Track calls to reinitialize
reinit_called = False
original_reinit = cross_encoder._reinitialize_model_sync
def track_reinit():
nonlocal reinit_called
reinit_called = True
original_reinit()
# Track predict attempts
predict_attempts = []
original_predict = cross_encoder._model.predict
def mock_predict(*args, **kwargs):
predict_attempts.append(1)
# Only fail on first attempt
if len(predict_attempts) == 1:
raise RuntimeError("Compiler encountered XPC_ERROR_CONNECTION_INVALID (is the OS shutting down?)")
else:
# After reinit: succeed
return original_predict(*args, **kwargs)
# Mock the initial predict to fail, reinit happens, then new model succeeds
with patch.object(cross_encoder, "_reinitialize_model_sync", side_effect=track_reinit):
with patch.object(cross_encoder._model, "predict", side_effect=mock_predict):
# This should trigger XPC error on first attempt, then recover and succeed
result = await cross_encoder.predict([("query", "document")])
# Verify we got a result
assert result is not None
assert len(result) == 1
assert isinstance(result[0], float)
assert reinit_called # Should have reinitialized
assert len(predict_attempts) >= 1 # At least one attempt was made
@pytest.mark.asyncio
async def test_predict_fails_on_non_xpc_error(self, cross_encoder):
"""Test that predict() does not retry for non-XPC errors."""
# Initialize the cross-encoder
await cross_encoder.initialize()
# Create a mock that raises a non-XPC error
def mock_predict(*args, **kwargs):
raise RuntimeError("Some other error")
# Patch the model's predict method
with patch.object(cross_encoder._model, "predict", side_effect=mock_predict):
# This should fail without retry
with pytest.raises(RuntimeError) as exc_info:
await cross_encoder.predict([("query", "document")])
assert "Some other error" in str(exc_info.value)
@pytest.mark.asyncio
async def test_reinitialize_clears_model(self, cross_encoder):
"""Test that _reinitialize_model_sync properly clears and reinits the model."""
# Initialize the cross-encoder
await cross_encoder.initialize()
original_model = cross_encoder._model
assert original_model is not None
# Reinitialize
cross_encoder._reinitialize_model_sync()
# Model should be reinitialized (new instance)
assert cross_encoder._model is not None
assert cross_encoder._model is not original_model
# Should still work
result = await cross_encoder.predict([("test query", "test document")])
assert len(result) == 1
assert isinstance(result[0], float)
@pytest.mark.asyncio
async def test_xpc_recovery_exhausts_retries(self, cross_encoder):
"""Test that XPC recovery gives up after max retries."""
# Initialize the cross-encoder
await cross_encoder.initialize()
# Track reinit calls
reinit_count = 0
original_reinit = cross_encoder._reinitialize_model_sync
def track_and_fail_reinit():
nonlocal reinit_count
reinit_count += 1
# Call original reinit, but the new model will also be mocked to fail
original_reinit()
# After reinit, patch the new model too
cross_encoder._model.predict = MagicMock(
side_effect=RuntimeError("Compiler encountered XPC_ERROR_CONNECTION_INVALID")
)
# Mock that always raises XPC error
cross_encoder._model.predict = MagicMock(
side_effect=RuntimeError("Compiler encountered XPC_ERROR_CONNECTION_INVALID")
)
with patch.object(cross_encoder, "_reinitialize_model_sync", side_effect=track_and_fail_reinit):
# Should try once, reinitialize, try again, and fail
with pytest.raises(Exception) as exc_info:
await cross_encoder.predict([("query", "document")])
assert "XPC_ERROR_CONNECTION_INVALID" in str(exc_info.value) or "Failed to recover" in str(exc_info.value)
assert reinit_count == 1 # Should have tried to reinitialize once
@@ -1,148 +0,0 @@
"""
Tests for XPC error recovery in LocalSTEmbeddings.
This tests the automatic reinitialization of the embedding model when
XPC connection errors occur on macOS (common in long-running daemon processes).
"""
import asyncio
from unittest.mock import MagicMock, patch
import pytest
from hindsight_api.engine.embeddings import LocalSTEmbeddings
class TestXPCErrorRecovery:
"""Tests for XPC error detection and recovery in LocalSTEmbeddings."""
@pytest.fixture
def embeddings(self):
"""Create a LocalSTEmbeddings instance."""
return LocalSTEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
def test_is_xpc_error_detection(self, embeddings):
"""Test that XPC errors are correctly detected."""
# Test various XPC error message formats
xpc_error = Exception("Compiler encountered XPC_ERROR_CONNECTION_INVALID (is the OS shutting down?)")
assert embeddings._is_xpc_error(xpc_error)
xpc_error2 = Exception("XPC error occurred")
assert embeddings._is_xpc_error(xpc_error2)
# Test that non-XPC errors are not detected
normal_error = Exception("Some other error")
assert not embeddings._is_xpc_error(normal_error)
@pytest.mark.asyncio
async def test_encode_with_xpc_recovery(self, embeddings):
"""Test that encode() recovers from XPC errors by reinitializing."""
# Initialize the embeddings
await embeddings.initialize()
# Track calls to reinitialize
reinit_called = False
original_reinit = embeddings._reinitialize_model_sync
def track_reinit():
nonlocal reinit_called
reinit_called = True
original_reinit()
# Track encode attempts
encode_attempts = []
original_encode = embeddings._model.encode
def mock_encode(*args, **kwargs):
encode_attempts.append(1)
# Only fail on first attempt
if len(encode_attempts) == 1:
raise RuntimeError("Compiler encountered XPC_ERROR_CONNECTION_INVALID (is the OS shutting down?)")
else:
# After reinit: succeed
return original_encode(*args, **kwargs)
# Mock the initial encode to fail, reinit happens, then new model succeeds
with patch.object(embeddings, "_reinitialize_model_sync", side_effect=track_reinit):
with patch.object(embeddings._model, "encode", side_effect=mock_encode):
# This should trigger XPC error on first attempt, then recover and succeed
result = embeddings.encode(["test text"])
# Verify we got a result
assert result is not None
assert len(result) == 1
assert len(result[0]) > 0 # Should have embedding vector
assert reinit_called # Should have reinitialized
assert len(encode_attempts) >= 1 # At least one attempt was made
@pytest.mark.asyncio
async def test_encode_fails_on_non_xpc_error(self, embeddings):
"""Test that encode() does not retry for non-XPC errors."""
# Initialize the embeddings
await embeddings.initialize()
# Create a mock that raises a non-XPC error
def mock_encode(*args, **kwargs):
raise RuntimeError("Some other error")
# Patch the model's encode method
with patch.object(embeddings._model, "encode", side_effect=mock_encode):
# This should fail without retry
with pytest.raises(RuntimeError) as exc_info:
embeddings.encode(["test text"])
assert "Some other error" in str(exc_info.value)
@pytest.mark.asyncio
async def test_reinitialize_clears_model(self, embeddings):
"""Test that _reinitialize_model_sync properly clears and reinits the model."""
# Initialize the embeddings
await embeddings.initialize()
original_model = embeddings._model
assert original_model is not None
# Reinitialize
embeddings._reinitialize_model_sync()
# Model should be reinitialized (new instance)
assert embeddings._model is not None
assert embeddings._model is not original_model
# Should still work
result = embeddings.encode(["test"])
assert len(result) == 1
assert len(result[0]) > 0
@pytest.mark.asyncio
async def test_xpc_recovery_exhausts_retries(self, embeddings):
"""Test that XPC recovery gives up after max retries."""
# Initialize the embeddings
await embeddings.initialize()
# Track reinit calls
reinit_count = 0
original_reinit = embeddings._reinitialize_model_sync
def track_and_fail_reinit():
nonlocal reinit_count
reinit_count += 1
# Call original reinit, but the new model will also be mocked to fail
original_reinit()
# After reinit, patch the new model too
embeddings._model.encode = MagicMock(
side_effect=RuntimeError("Compiler encountered XPC_ERROR_CONNECTION_INVALID")
)
# Mock that always raises XPC error
embeddings._model.encode = MagicMock(
side_effect=RuntimeError("Compiler encountered XPC_ERROR_CONNECTION_INVALID")
)
with patch.object(embeddings, "_reinitialize_model_sync", side_effect=track_and_fail_reinit):
# Should try once, reinitialize, try again, and fail
with pytest.raises(RuntimeError) as exc_info:
embeddings.encode(["test"])
assert "XPC_ERROR_CONNECTION_INVALID" in str(exc_info.value)
assert reinit_count == 1 # Should have tried to reinitialize once
@@ -7,14 +7,14 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
__version__ = "0.4.1"
__version__ = "0.0.7"
# import apis into sdk package
from hindsight_client_api.api.banks_api import BanksApi
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -489,7 +489,7 @@ class Configuration:
return "Python SDK Debug Report:\n"\
"OS: {env}\n"\
"Python Version: {pyversion}\n"\
"Version of the API: 0.4.0\n"\
"Version of the API: 0.4.1\n"\
"SDK Package Version: 0.0.7".\
format(env=sys.platform, pyversion=sys.version)
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -6,7 +6,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.0
The version of the OpenAPI document: 0.4.1
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
+262
View File
@@ -0,0 +1,262 @@
---
sidebar_position: 4
---
# Embedded SDK (hindsight-embed)
Zero-configuration local memory system with automatic daemon management. Perfect for development, prototyping, and single-user applications.
## Overview
`hindsight-embed` is a zero-configuration SDK that wraps the Hindsight API and PostgreSQL database into a single auto-managed local daemon. It's designed for development, prototyping, and single-user applications where you want memory capabilities without infrastructure overhead.
**How it works:**
1. **First command triggers startup**: When you run any `hindsight-embed` command, it checks if a local daemon is running
2. **Auto-daemon management**: If no daemon exists, it automatically spawns `hindsight-api --daemon` in the background
3. **Embedded database**: The daemon uses `pg0` (embedded PostgreSQL) — no separate database installation required
4. **Command forwarding**: Your command is forwarded to the local daemon via HTTP (localhost:8889)
5. **Auto-shutdown**: After 5 minutes of inactivity (configurable), the daemon gracefully shuts down to free resources
**Key features:**
- **Zero setup** — One `configure` command and you're ready
- **Automatic lifecycle** — Daemon starts on-demand, stops when idle
- **Isolated storage** — Each bank gets its own embedded PostgreSQL database
- **Local-only** — Binds to `127.0.0.1:8889`, not accessible from network
- **Production-grade engine** — Uses the same memory engine as the full API service
Think of it as SQLite for long-term memory — all the power of Hindsight without managing servers.
## Installation
Install via `uvx` (recommended - always latest version):
```bash
# Run directly without installation
uvx hindsight-embed@latest configure
# Or use pipx for persistent installation
pipx install hindsight-embed
```
## Quick Start
### 1. Configure
```bash
# Interactive configuration
hindsight-embed configure
# Or non-interactive via environment variables
export HINDSIGHT_EMBED_LLM_PROVIDER=openai
export HINDSIGHT_EMBED_LLM_API_KEY=sk-xxxxxxxxxxxx
export HINDSIGHT_EMBED_LLM_MODEL=gpt-4o-mini
hindsight-embed configure
```
Configuration is saved to `~/.hindsight/embed`:
```bash
HINDSIGHT_EMBED_LLM_PROVIDER=openai
HINDSIGHT_EMBED_LLM_MODEL=gpt-4o-mini
HINDSIGHT_EMBED_BANK_ID=default
HINDSIGHT_EMBED_LLM_API_KEY=sk-xxxxxxxxxxxx
# Daemon settings (macOS: force CPU to avoid MPS/XPC issues)
HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU=1
HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU=1
```
### 2. Use Memory Operations
```bash
# Store a memory
hindsight-embed memory retain default "User prefers dark mode"
# Query memories
hindsight-embed memory recall default "user preferences"
# Reasoning with memory
hindsight-embed memory reflect default "What color scheme should I use?"
```
The daemon starts automatically on first use!
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `HINDSIGHT_EMBED_LLM_API_KEY` | **Required**. API key for LLM provider | - |
| `HINDSIGHT_EMBED_LLM_PROVIDER` | LLM provider: `openai`, `anthropic`, `gemini`, `groq`, `ollama` | `openai` |
| `HINDSIGHT_EMBED_LLM_MODEL` | Model name | `gpt-4o-mini` |
| `HINDSIGHT_EMBED_BANK_ID` | Default memory bank ID | `default` |
| `HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT` | Seconds before daemon auto-exits when idle (0 = never) | `300` |
**Provider Examples:**
```bash
# OpenAI
export HINDSIGHT_EMBED_LLM_PROVIDER=openai
export HINDSIGHT_EMBED_LLM_API_KEY=sk-xxxxxxxxxxxx
export HINDSIGHT_EMBED_LLM_MODEL=gpt-4o
# Groq (fast inference)
export HINDSIGHT_EMBED_LLM_PROVIDER=groq
export HINDSIGHT_EMBED_LLM_API_KEY=gsk_xxxxxxxxxxxx
export HINDSIGHT_EMBED_LLM_MODEL=llama-3.3-70b-versatile
# Anthropic
export HINDSIGHT_EMBED_LLM_PROVIDER=anthropic
export HINDSIGHT_EMBED_LLM_API_KEY=sk-ant-xxxxxxxxxxxx
export HINDSIGHT_EMBED_LLM_MODEL=claude-sonnet-4-20250514
```
## Daemon Management
### Idle Timeout
Customize how long the daemon stays alive when idle:
```bash
# Never timeout (daemon runs until manually stopped)
export HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT=0
# Shorter timeout: 1 minute
export HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT=60
# Longer timeout: 30 minutes
export HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT=1800
```
### Daemon Commands
```bash
# Check daemon status
hindsight-embed daemon status
# View daemon logs in real-time
hindsight-embed daemon logs -f
# Stop daemon manually
hindsight-embed daemon stop
```
## Commands
All memory operations follow the same interface as the CLI:
### Retain (Store Memory)
```bash
hindsight-embed memory retain <bank_id> "content"
# With context
hindsight-embed memory retain <bank_id> "content" --context "source information"
# Background processing
hindsight-embed memory retain <bank_id> "content" --async
```
### Recall (Search)
```bash
hindsight-embed memory recall <bank_id> "query"
# With budget control
hindsight-embed memory recall <bank_id> "query" --budget high
# Show trace
hindsight-embed memory recall <bank_id> "query" --trace
```
### Reflect (Generate Response)
```bash
hindsight-embed memory reflect <bank_id> "prompt"
# With additional context
hindsight-embed memory reflect <bank_id> "prompt" --context "additional info"
```
### Bank Management
```bash
# List all banks
hindsight-embed bank list
# View bank stats
hindsight-embed bank stats <bank_id>
# Set bank name
hindsight-embed bank name <bank_id> "My Assistant"
# Set bank mission
hindsight-embed bank mission <bank_id> "I am a helpful AI assistant"
```
## Troubleshooting
### Daemon Won't Start
Check the daemon logs:
```bash
hindsight-embed daemon logs
# Or watch in real-time
hindsight-embed daemon logs -f
```
Common issues:
- **Missing API key**: Set `HINDSIGHT_EMBED_LLM_API_KEY`
- **Port conflict**: Another service using port 8889
- **Permissions**: Check `~/.hindsight/` directory permissions
### Daemon Exits Immediately
Check if you have the idle timeout set too low:
```bash
# Disable idle timeout for debugging
export HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT=0
hindsight-embed daemon status
```
### Reset Configuration
```bash
# Remove config file and reconfigure
rm ~/.hindsight/embed
hindsight-embed configure
```
## Advanced Configuration
While `hindsight-embed` aims to be zero-config, you can customize the underlying API behavior by setting `HINDSIGHT_API_*` variables in `~/.hindsight/embed`:
```bash
# Example: Custom embedding model
HINDSIGHT_API_EMBEDDINGS_PROVIDER=openai
HINDSIGHT_API_EMBEDDINGS_OPENAI_MODEL=text-embedding-3-large
# Example: Verbose extraction
HINDSIGHT_API_RETAIN_EXTRACTION_MODE=verbose
```
See [Configuration](/developer/configuration) for all available `HINDSIGHT_API_*` options.
## When to Use
**Perfect for:**
- Development and prototyping
- Single-user applications
- Local-first tools
- Quick experiments with Hindsight
**Not suitable for:**
- Production multi-user deployments
- Network-accessible services
- High-availability requirements
- Multi-tenant applications
For production deployments, use the [API Service](/developer/services) with external PostgreSQL instead.
+1 -1
View File
@@ -10,7 +10,7 @@
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "0.4.0"
"version": "0.4.1"
},
"paths": {
"/health": {
+19
View File
@@ -15,6 +15,7 @@ Environment variables:
HINDSIGHT_EMBED_LLM_PROVIDER: Optional. LLM provider (default: "openai").
HINDSIGHT_EMBED_LLM_MODEL: Optional. LLM model (default: "gpt-4o-mini").
HINDSIGHT_EMBED_BANK_ID: Optional. Memory bank ID (default: "default").
HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT: Optional. Seconds before daemon auto-exits when idle (default: 300).
"""
import argparse
@@ -159,6 +160,15 @@ def _do_configure_from_env():
if api_key:
f.write(f"HINDSIGHT_EMBED_LLM_API_KEY={api_key}\n")
# Force CPU mode for embeddings/reranker on macOS to avoid MPS/XPC crashes in daemon mode
# On Linux, users can set these to 0 to use CUDA if available
import platform
if platform.system() == "Darwin": # macOS
f.write("\n# Daemon settings (macOS: force CPU to avoid MPS/XPC issues)\n")
f.write("HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU=1\n")
f.write("HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU=1\n")
CONFIG_FILE.chmod(0o600)
print()
@@ -324,6 +334,15 @@ def _do_configure_interactive():
if api_key:
f.write(f"HINDSIGHT_EMBED_LLM_API_KEY={api_key}\n")
# Force CPU mode for embeddings/reranker on macOS to avoid MPS/XPC crashes in daemon mode
# On Linux, users can set these to 0 to use CUDA if available
import platform
if platform.system() == "Darwin": # macOS
f.write("\n# Daemon settings (macOS: force CPU to avoid MPS/XPC issues)\n")
f.write("HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU=1\n")
f.write("HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU=1\n")
CONFIG_FILE.chmod(0o600)
# Stop existing daemon if running (it needs to pick up new config)
@@ -18,7 +18,8 @@ logger = logging.getLogger(__name__)
DAEMON_PORT = 8889
DAEMON_URL = f"http://127.0.0.1:{DAEMON_PORT}"
DAEMON_STARTUP_TIMEOUT = 180 # seconds - needs to be long for first run (downloads dependencies)
DAEMON_IDLE_TIMEOUT = 300 # 5 minutes - auto-exit after idle
# Default idle timeout: 5 minutes - users can override with HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT env var
DEFAULT_DAEMON_IDLE_TIMEOUT = 300
# CLI paths - check multiple locations
CLI_INSTALL_DIRS = [
@@ -75,7 +76,10 @@ def _start_daemon(config: dict) -> bool:
env["HINDSIGHT_API_DATABASE_URL"] = f"pg0://hindsight-embed-{bank_id}"
env["HINDSIGHT_API_LOG_LEVEL"] = "info"
cmd = _find_hindsight_api_command() + ["--daemon", "--idle-timeout", str(DAEMON_IDLE_TIMEOUT)]
# Get idle timeout from environment or use default
idle_timeout = int(os.getenv("HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT", str(DEFAULT_DAEMON_IDLE_TIMEOUT)))
cmd = _find_hindsight_api_command() + ["--daemon", "--idle-timeout", str(idle_timeout)]
# Create log directory
log_dir = Path.home() / ".hindsight"
Generated
+5 -5
View File
@@ -1295,7 +1295,7 @@ wheels = [
[[package]]
name = "hindsight-all"
version = "0.4.0"
version = "0.4.1"
source = { editable = "hindsight" }
dependencies = [
{ name = "hindsight-api" },
@@ -1319,7 +1319,7 @@ provides-extras = ["test"]
[[package]]
name = "hindsight-api"
version = "0.4.0"
version = "0.4.1"
source = { editable = "hindsight-api" }
dependencies = [
{ name = "aiohttp" },
@@ -1447,7 +1447,7 @@ dev = [
[[package]]
name = "hindsight-client"
version = "0.4.0"
version = "0.4.1"
source = { editable = "hindsight-clients/python" }
dependencies = [
{ name = "aiohttp" },
@@ -1481,7 +1481,7 @@ provides-extras = ["test"]
[[package]]
name = "hindsight-dev"
version = "0.4.0"
version = "0.4.1"
source = { editable = "hindsight-dev" }
dependencies = [
{ name = "hindsight-api" },
@@ -1527,7 +1527,7 @@ dev = [
[[package]]
name = "hindsight-embed"
version = "0.4.0"
version = "0.4.1"
source = { editable = "hindsight-embed" }
dependencies = [
{ name = "httpx" },