feat: support vertex as llm provider (#233)
* feat: support vertex as llm provider * fix * fix: add uv index-strategy to resolve dependency conflicts with pytorch index When using pytorch index for faster torch downloads in CI, filelock dependency resolution was failing because pytorch index only has older versions. Adding unsafe-best-match strategy allows uv to search all configured indexes. Also fix type checking warnings from ty. * fix: add index-strategy to root pyproject.toml for workspace-level uv resolution * chore: regenerate client SDKs after Vertex AI support
This commit is contained in:
parent
657fe023b2
commit
c2ac7d0440
97 changed files with 2146 additions and 1296 deletions
|
|
@ -2,7 +2,7 @@
|
|||
# Copy this file to .env and fill in your values
|
||||
|
||||
# LLM Configuration (Required)
|
||||
# Supported providers: openai, groq, ollama, gemini, anthropic, lmstudio
|
||||
# Supported providers: openai, groq, ollama, gemini, anthropic, lmstudio, vertexai
|
||||
HINDSIGHT_API_LLM_PROVIDER=openai
|
||||
HINDSIGHT_API_LLM_API_KEY=your-api-key-here
|
||||
HINDSIGHT_API_LLM_MODEL=o3-mini
|
||||
|
|
@ -13,6 +13,13 @@ HINDSIGHT_API_LLM_BASE_URL=https://api.openai.com/v1
|
|||
# HINDSIGHT_API_LLM_API_KEY=your-anthropic-api-key
|
||||
# HINDSIGHT_API_LLM_MODEL=claude-sonnet-4-20250514
|
||||
|
||||
# Example: Google Vertex AI configuration
|
||||
# HINDSIGHT_API_LLM_PROVIDER=vertexai
|
||||
# HINDSIGHT_API_LLM_MODEL=google/gemini-2.0-flash-001
|
||||
# HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=your-gcp-project-id
|
||||
# HINDSIGHT_API_LLM_VERTEXAI_REGION=us-central1
|
||||
# HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/path/to/service-account-key.json # Optional, uses ADC if not set
|
||||
|
||||
# Example: LM Studio local configuration (Qwen 2.5 32B recommended)
|
||||
# HINDSIGHT_API_LLM_PROVIDER=lmstudio
|
||||
# HINDSIGHT_API_LLM_API_KEY=lmstudio
|
||||
|
|
|
|||
|
|
@ -108,6 +108,11 @@ ENV_MCP_LOCAL_BANK_ID = "HINDSIGHT_API_MCP_LOCAL_BANK_ID"
|
|||
ENV_MCP_INSTRUCTIONS = "HINDSIGHT_API_MCP_INSTRUCTIONS"
|
||||
ENV_MENTAL_MODEL_REFRESH_CONCURRENCY = "HINDSIGHT_API_MENTAL_MODEL_REFRESH_CONCURRENCY"
|
||||
|
||||
# Vertex AI configuration
|
||||
ENV_LLM_VERTEXAI_PROJECT_ID = "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID"
|
||||
ENV_LLM_VERTEXAI_REGION = "HINDSIGHT_API_LLM_VERTEXAI_REGION"
|
||||
ENV_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY = "HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY"
|
||||
|
||||
# Retain settings
|
||||
ENV_RETAIN_MAX_COMPLETION_TOKENS = "HINDSIGHT_API_RETAIN_MAX_COMPLETION_TOKENS"
|
||||
ENV_RETAIN_CHUNK_SIZE = "HINDSIGHT_API_RETAIN_CHUNK_SIZE"
|
||||
|
|
@ -156,6 +161,11 @@ DEFAULT_LLM_INITIAL_BACKOFF = 1.0 # Initial backoff in seconds for retry expone
|
|||
DEFAULT_LLM_MAX_BACKOFF = 60.0 # Max backoff cap in seconds for retry exponential backoff
|
||||
DEFAULT_LLM_TIMEOUT = 120.0 # seconds
|
||||
|
||||
# Vertex AI defaults
|
||||
DEFAULT_LLM_VERTEXAI_PROJECT_ID = None # Required for Vertex AI
|
||||
DEFAULT_LLM_VERTEXAI_REGION = "us-central1"
|
||||
DEFAULT_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY = None # Optional, uses ADC if not set
|
||||
|
||||
DEFAULT_EMBEDDINGS_PROVIDER = "local"
|
||||
DEFAULT_EMBEDDINGS_LOCAL_MODEL = "BAAI/bge-small-en-v1.5"
|
||||
DEFAULT_EMBEDDINGS_LOCAL_FORCE_CPU = False # Force CPU mode for local embeddings (avoids MPS/XPC issues on macOS)
|
||||
|
|
@ -312,6 +322,11 @@ class HindsightConfig:
|
|||
llm_max_backoff: float
|
||||
llm_timeout: float
|
||||
|
||||
# Vertex AI configuration
|
||||
llm_vertexai_project_id: str | None
|
||||
llm_vertexai_region: str
|
||||
llm_vertexai_service_account_key: str | None
|
||||
|
||||
# Per-operation LLM configuration (None = use default LLM config)
|
||||
retain_llm_provider: str | None
|
||||
retain_llm_api_key: str | None
|
||||
|
|
@ -430,6 +445,11 @@ class HindsightConfig:
|
|||
llm_initial_backoff=float(os.getenv(ENV_LLM_INITIAL_BACKOFF, str(DEFAULT_LLM_INITIAL_BACKOFF))),
|
||||
llm_max_backoff=float(os.getenv(ENV_LLM_MAX_BACKOFF, str(DEFAULT_LLM_MAX_BACKOFF))),
|
||||
llm_timeout=float(os.getenv(ENV_LLM_TIMEOUT, str(DEFAULT_LLM_TIMEOUT))),
|
||||
# Vertex AI
|
||||
llm_vertexai_project_id=os.getenv(ENV_LLM_VERTEXAI_PROJECT_ID) or DEFAULT_LLM_VERTEXAI_PROJECT_ID,
|
||||
llm_vertexai_region=os.getenv(ENV_LLM_VERTEXAI_REGION, DEFAULT_LLM_VERTEXAI_REGION),
|
||||
llm_vertexai_service_account_key=os.getenv(ENV_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY)
|
||||
or DEFAULT_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY,
|
||||
# Per-operation LLM config (None = use default)
|
||||
retain_llm_provider=os.getenv(ENV_RETAIN_LLM_PROVIDER) or None,
|
||||
retain_llm_api_key=os.getenv(ENV_RETAIN_LLM_API_KEY) or None,
|
||||
|
|
|
|||
|
|
@ -614,7 +614,7 @@ class FlashRankCrossEncoder(CrossEncoderModel):
|
|||
return
|
||||
|
||||
try:
|
||||
from flashrank import Ranker # type: ignore[import-untyped]
|
||||
from flashrank import Ranker
|
||||
except ImportError:
|
||||
raise ImportError("flashrank is required for FlashRankCrossEncoder. Install it with: pip install flashrank")
|
||||
|
||||
|
|
@ -641,7 +641,7 @@ class FlashRankCrossEncoder(CrossEncoderModel):
|
|||
|
||||
def _predict_sync(self, pairs: list[tuple[str, str]]) -> list[float]:
|
||||
"""Synchronous predict - processes each query group."""
|
||||
from flashrank import RerankRequest # type: ignore[import-untyped]
|
||||
from flashrank import RerankRequest
|
||||
|
||||
if not pairs:
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -545,7 +545,7 @@ class CohereEmbeddings(Embeddings):
|
|||
model=self.model,
|
||||
input_type=self.input_type,
|
||||
)
|
||||
if response.embeddings:
|
||||
if response.embeddings and isinstance(response.embeddings, list):
|
||||
self._dimension = len(response.embeddings[0])
|
||||
|
||||
logger.info(f"Embeddings: Cohere provider initialized (model: {self.model}, dim: {self._dimension})")
|
||||
|
|
|
|||
|
|
@ -16,6 +16,15 @@ from google.genai import errors as genai_errors
|
|||
from google.genai import types as genai_types
|
||||
from openai import APIConnectionError, APIStatusError, AsyncOpenAI, LengthFinishReasonError
|
||||
|
||||
# Vertex AI imports (conditional)
|
||||
try:
|
||||
import google.auth
|
||||
from google.oauth2 import service_account
|
||||
|
||||
VERTEXAI_AVAILABLE = True
|
||||
except ImportError:
|
||||
VERTEXAI_AVAILABLE = False
|
||||
|
||||
from ..config import (
|
||||
DEFAULT_LLM_MAX_CONCURRENT,
|
||||
DEFAULT_LLM_TIMEOUT,
|
||||
|
|
@ -88,7 +97,7 @@ class LLMProvider:
|
|||
self.groq_service_tier = groq_service_tier or os.getenv(ENV_LLM_GROQ_SERVICE_TIER, "auto")
|
||||
|
||||
# Validate provider
|
||||
valid_providers = ["openai", "groq", "ollama", "gemini", "anthropic", "lmstudio", "mock"]
|
||||
valid_providers = ["openai", "groq", "ollama", "gemini", "anthropic", "lmstudio", "vertexai", "mock"]
|
||||
if self.provider not in valid_providers:
|
||||
raise ValueError(f"Invalid LLM provider: {self.provider}. Must be one of: {', '.join(valid_providers)}")
|
||||
|
||||
|
|
@ -96,6 +105,9 @@ class LLMProvider:
|
|||
self._mock_calls: list[dict] = []
|
||||
self._mock_response: Any = None
|
||||
|
||||
# Vertex AI token refresher
|
||||
self._vertexai_refresher: Any = None
|
||||
|
||||
# Set default base URLs
|
||||
if not self.base_url:
|
||||
if self.provider == "groq":
|
||||
|
|
@ -105,8 +117,65 @@ class LLMProvider:
|
|||
elif self.provider == "lmstudio":
|
||||
self.base_url = "http://localhost:1234/v1"
|
||||
|
||||
# Validate API key (not needed for ollama, lmstudio, or mock)
|
||||
if self.provider not in ("ollama", "lmstudio", "mock") and not self.api_key:
|
||||
# Handle Vertex AI provider
|
||||
if self.provider == "vertexai":
|
||||
if not VERTEXAI_AVAILABLE:
|
||||
raise ValueError("Vertex AI requires 'google-auth' package. Install with: pip install google-auth")
|
||||
|
||||
from ..config import get_config
|
||||
|
||||
config = get_config()
|
||||
|
||||
project_id = config.llm_vertexai_project_id
|
||||
if not project_id:
|
||||
raise ValueError(
|
||||
"HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID is required for Vertex AI provider. "
|
||||
"Set it to your GCP project ID."
|
||||
)
|
||||
|
||||
region = config.llm_vertexai_region or "us-central1"
|
||||
service_account_key = config.llm_vertexai_service_account_key
|
||||
|
||||
# Try ADC first
|
||||
credentials = None
|
||||
auth_method = None
|
||||
|
||||
try:
|
||||
credentials, _ = google.auth.default(scopes=["https://www.googleapis.com/auth/cloud-platform"])
|
||||
auth_method = "ADC"
|
||||
logger.info("Vertex AI: Using Application Default Credentials")
|
||||
except google.auth.exceptions.DefaultCredentialsError:
|
||||
logger.debug("Vertex AI: ADC not available, trying service account")
|
||||
|
||||
# Fall back to service account key file
|
||||
if credentials is None and service_account_key:
|
||||
try:
|
||||
credentials = service_account.Credentials.from_service_account_file(
|
||||
service_account_key,
|
||||
scopes=["https://www.googleapis.com/auth/cloud-platform"],
|
||||
)
|
||||
auth_method = "Service Account"
|
||||
logger.info(f"Vertex AI: Using service account key: {service_account_key}")
|
||||
except Exception as e:
|
||||
logger.error(f"Vertex AI: Failed to load service account key: {e}")
|
||||
|
||||
if credentials is None:
|
||||
raise ValueError(
|
||||
"Vertex AI authentication failed. Either:\n"
|
||||
" 1. Set up ADC: gcloud auth application-default login\n"
|
||||
" 2. Set HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY to path of service account JSON key"
|
||||
)
|
||||
|
||||
# Initialize token refresher
|
||||
from .vertexai_token_refresher import VertexAITokenRefresher
|
||||
|
||||
self._vertexai_refresher = VertexAITokenRefresher(credentials, project_id, region)
|
||||
self.base_url = self._vertexai_refresher.get_base_url()
|
||||
|
||||
logger.info(f"Vertex AI: project={project_id}, region={region}, auth={auth_method}")
|
||||
|
||||
# Validate API key (not needed for ollama, lmstudio, vertexai, or mock)
|
||||
if self.provider not in ("ollama", "lmstudio", "vertexai", "mock") and not self.api_key:
|
||||
raise ValueError(f"API key not found for {self.provider}")
|
||||
|
||||
# Get timeout config (set HINDSIGHT_API_LLM_TIMEOUT for local LLMs that need longer timeouts)
|
||||
|
|
@ -132,6 +201,31 @@ class LLMProvider:
|
|||
if self.timeout:
|
||||
anthropic_kwargs["timeout"] = self.timeout
|
||||
self._anthropic_client = AsyncAnthropic(**anthropic_kwargs)
|
||||
elif self.provider == "vertexai":
|
||||
# Custom transport for token injection
|
||||
class TokenInjectingTransport(httpx.AsyncHTTPTransport):
|
||||
def __init__(self, refresher, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._refresher = refresher
|
||||
|
||||
async def handle_async_request(self, request):
|
||||
token = self._refresher.get_token()
|
||||
request.headers["Authorization"] = f"Bearer {token}"
|
||||
return await super().handle_async_request(request)
|
||||
|
||||
transport = TokenInjectingTransport(self._vertexai_refresher)
|
||||
client_kwargs = {
|
||||
"api_key": "dummy", # Required by AsyncOpenAI but unused (we inject token via transport)
|
||||
"base_url": self.base_url,
|
||||
"max_retries": 0,
|
||||
"http_client": httpx.AsyncClient(transport=transport),
|
||||
}
|
||||
if self.timeout:
|
||||
client_kwargs["timeout"] = self.timeout
|
||||
self._client = AsyncOpenAI(**client_kwargs)
|
||||
|
||||
# Start background refresh
|
||||
self._vertexai_refresher.start_refresh_task()
|
||||
elif self.provider in ("ollama", "lmstudio"):
|
||||
# Use dummy key if not provided for local
|
||||
api_key = self.api_key or "local"
|
||||
|
|
@ -342,11 +436,13 @@ class LLMProvider:
|
|||
schema_msg = f"\n\nYou must respond with valid JSON matching this schema:\n{json.dumps(schema, indent=2)}"
|
||||
|
||||
if call_params["messages"] and call_params["messages"][0].get("role") == "system":
|
||||
call_params["messages"][0]["content"] += schema_msg
|
||||
first_msg = call_params["messages"][0]
|
||||
if isinstance(first_msg, dict) and isinstance(first_msg.get("content"), str):
|
||||
first_msg["content"] += schema_msg
|
||||
elif call_params["messages"]:
|
||||
call_params["messages"][0]["content"] = (
|
||||
schema_msg + "\n\n" + call_params["messages"][0]["content"]
|
||||
)
|
||||
first_msg = call_params["messages"][0]
|
||||
if isinstance(first_msg, dict) and isinstance(first_msg.get("content"), str):
|
||||
first_msg["content"] = schema_msg + "\n\n" + first_msg["content"]
|
||||
if self.provider not in ("lmstudio", "ollama"):
|
||||
# LM Studio and Ollama don't support json_object response format reliably
|
||||
# We rely on the schema in the system message instead
|
||||
|
|
@ -917,18 +1013,20 @@ class LLMProvider:
|
|||
tool_calls: list[LLMToolCall] = []
|
||||
|
||||
if response.candidates and response.candidates[0].content:
|
||||
for part in response.candidates[0].content.parts:
|
||||
if hasattr(part, "text") and part.text:
|
||||
content = part.text
|
||||
if hasattr(part, "function_call") and part.function_call:
|
||||
fc = part.function_call
|
||||
tool_calls.append(
|
||||
LLMToolCall(
|
||||
id=f"gemini_{len(tool_calls)}",
|
||||
name=fc.name,
|
||||
arguments=dict(fc.args) if fc.args else {},
|
||||
parts = response.candidates[0].content.parts
|
||||
if parts:
|
||||
for part in parts:
|
||||
if hasattr(part, "text") and part.text:
|
||||
content = part.text
|
||||
if hasattr(part, "function_call") and part.function_call:
|
||||
fc = part.function_call
|
||||
tool_calls.append(
|
||||
LLMToolCall(
|
||||
id=f"gemini_{len(tool_calls)}",
|
||||
name=fc.name,
|
||||
arguments=dict(fc.args) if fc.args else {},
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
finish_reason = "tool_calls" if tool_calls else "stop"
|
||||
|
||||
|
|
@ -1504,6 +1602,12 @@ class LLMProvider:
|
|||
"""Clear the recorded mock calls."""
|
||||
self._mock_calls = []
|
||||
|
||||
async def cleanup(self) -> None:
|
||||
"""Clean up resources (e.g., stop token refresh tasks)."""
|
||||
if self._vertexai_refresher is not None:
|
||||
await self._vertexai_refresher.stop()
|
||||
logger.debug("Vertex AI token refresher stopped")
|
||||
|
||||
@classmethod
|
||||
def for_memory(cls) -> "LLMProvider":
|
||||
"""Create provider for memory operations from environment variables."""
|
||||
|
|
|
|||
|
|
@ -789,7 +789,7 @@ class MemoryEngine(MemoryEngineInterface):
|
|||
kwargs = {"name": self._pg0_instance_name}
|
||||
if self._pg0_port is not None:
|
||||
kwargs["port"] = self._pg0_port
|
||||
pg0 = EmbeddedPostgres(**kwargs) # type: ignore[invalid-argument-type] - dict kwargs
|
||||
pg0 = EmbeddedPostgres(**kwargs)
|
||||
# Check if pg0 is already running before we start it
|
||||
was_already_running = await pg0.is_running()
|
||||
self.db_url = await pg0.ensure_running()
|
||||
|
|
@ -1200,7 +1200,7 @@ class MemoryEngine(MemoryEngineInterface):
|
|||
List of created unit IDs
|
||||
"""
|
||||
# Build content dict
|
||||
content_dict: RetainContentDict = {"content": content, "context": context} # type: ignore[typeddict-item] - building incrementally
|
||||
content_dict: RetainContentDict = {"content": content, "context": context}
|
||||
if event_date:
|
||||
content_dict["event_date"] = event_date
|
||||
if document_id:
|
||||
|
|
|
|||
120
hindsight-api/hindsight_api/engine/vertexai_token_refresher.py
Normal file
120
hindsight-api/hindsight_api/engine/vertexai_token_refresher.py
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
"""Vertex AI token refresher with background refresh and caching."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VertexAITokenRefresher:
|
||||
"""
|
||||
Background token refresher for Vertex AI.
|
||||
|
||||
Refreshes Google Cloud access tokens every 50 minutes to ensure they don't expire (60-min default).
|
||||
Thread-safe token caching for concurrent access from multiple async tasks.
|
||||
"""
|
||||
|
||||
def __init__(self, credentials: Any, project_id: str, region: str):
|
||||
"""
|
||||
Initialize the token refresher.
|
||||
|
||||
Args:
|
||||
credentials: Google Cloud credentials object (from google.auth.default or service_account)
|
||||
project_id: GCP project ID
|
||||
region: GCP region (e.g., "us-central1")
|
||||
"""
|
||||
self._credentials = credentials
|
||||
self._project_id = project_id
|
||||
self._region = region
|
||||
|
||||
# Thread-safe token cache
|
||||
self._token: str | None = None
|
||||
self._token_expiry: datetime | None = None
|
||||
self._lock = threading.Lock()
|
||||
|
||||
# Background refresh task
|
||||
self._refresh_task: asyncio.Task | None = None
|
||||
self._stop_event = asyncio.Event()
|
||||
|
||||
# Initial token fetch (synchronous, must complete before returning)
|
||||
self._refresh_token_sync()
|
||||
|
||||
def _refresh_token_sync(self) -> None:
|
||||
"""Synchronously refresh the token (thread-safe)."""
|
||||
try:
|
||||
import google.auth.transport.requests
|
||||
|
||||
request = google.auth.transport.requests.Request()
|
||||
self._credentials.refresh(request)
|
||||
|
||||
with self._lock:
|
||||
self._token = self._credentials.token
|
||||
self._token_expiry = self._credentials.expiry
|
||||
|
||||
logger.debug(f"Vertex AI token refreshed, expires at {self._token_expiry}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to refresh Vertex AI token: {e}")
|
||||
raise
|
||||
|
||||
async def _refresh_loop(self) -> None:
|
||||
"""Background refresh loop (runs every 50 minutes)."""
|
||||
while not self._stop_event.is_set():
|
||||
try:
|
||||
# Wait 50 minutes or until stop event
|
||||
await asyncio.wait_for(self._stop_event.wait(), timeout=50 * 60)
|
||||
# If we get here, stop was signaled
|
||||
break
|
||||
except asyncio.TimeoutError:
|
||||
# 50 minutes passed, refresh token
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
await loop.run_in_executor(None, self._refresh_token_sync)
|
||||
except Exception as e:
|
||||
logger.error(f"Background token refresh failed: {e}")
|
||||
# Continue loop - next API call will fail with auth error
|
||||
|
||||
def start_refresh_task(self) -> None:
|
||||
"""Start the background refresh task."""
|
||||
if self._refresh_task is None or self._refresh_task.done():
|
||||
self._refresh_task = asyncio.create_task(self._refresh_loop())
|
||||
logger.info("Vertex AI token refresh task started (refreshes every 50 minutes)")
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""Stop the background refresh task."""
|
||||
if self._refresh_task is not None and not self._refresh_task.done():
|
||||
self._stop_event.set()
|
||||
try:
|
||||
await asyncio.wait_for(self._refresh_task, timeout=5.0)
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning("Vertex AI token refresh task did not stop within 5 seconds")
|
||||
logger.info("Vertex AI token refresh task stopped")
|
||||
|
||||
def get_token(self) -> str:
|
||||
"""
|
||||
Get current access token (thread-safe).
|
||||
|
||||
Returns:
|
||||
Current Google Cloud access token
|
||||
|
||||
Raises:
|
||||
RuntimeError: If token is not available
|
||||
"""
|
||||
with self._lock:
|
||||
if self._token is None:
|
||||
raise RuntimeError("Vertex AI token not available")
|
||||
return self._token
|
||||
|
||||
def get_base_url(self) -> str:
|
||||
"""
|
||||
Get the Vertex AI OpenAI-compatible endpoint URL.
|
||||
|
||||
Returns:
|
||||
Base URL for Vertex AI OpenAI API
|
||||
"""
|
||||
return (
|
||||
f"https://{self._region}-aiplatform.googleapis.com/v1beta1/"
|
||||
f"projects/{self._project_id}/locations/{self._region}/endpoints/openapi"
|
||||
)
|
||||
|
|
@ -180,6 +180,9 @@ def main():
|
|||
llm_initial_backoff=config.llm_initial_backoff,
|
||||
llm_max_backoff=config.llm_max_backoff,
|
||||
llm_timeout=config.llm_timeout,
|
||||
llm_vertexai_project_id=config.llm_vertexai_project_id,
|
||||
llm_vertexai_region=config.llm_vertexai_region,
|
||||
llm_vertexai_service_account_key=config.llm_vertexai_service_account_key,
|
||||
retain_llm_provider=config.retain_llm_provider,
|
||||
retain_llm_api_key=config.retain_llm_api_key,
|
||||
retain_llm_model=config.retain_llm_model,
|
||||
|
|
@ -380,7 +383,7 @@ def main():
|
|||
|
||||
threading.Thread(target=run_idle_checker, daemon=True).start()
|
||||
|
||||
uvicorn.run(**uvicorn_config) # type: ignore[invalid-argument-type] - dict kwargs
|
||||
uvicorn.run(**uvicorn_config)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class EmbeddedPostgres:
|
|||
# Only set port if explicitly specified
|
||||
if self.port is not None:
|
||||
kwargs["port"] = self.port
|
||||
self._pg0 = Pg0(**kwargs) # type: ignore[invalid-argument-type] - dict kwargs
|
||||
self._pg0 = Pg0(**kwargs)
|
||||
return self._pg0
|
||||
|
||||
async def start(self, max_retries: int = 5, retry_delay: float = 4.0) -> str:
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ dependencies = [
|
|||
"opentelemetry-exporter-prometheus>=0.41b0",
|
||||
"dateparser>=1.2.2",
|
||||
"google-genai>=1.0.0",
|
||||
"google-auth>=2.0.0",
|
||||
"anthropic>=0.40.0",
|
||||
"typer>=0.9.0",
|
||||
"cohere>=5.0.0",
|
||||
|
|
@ -141,6 +142,11 @@ known-third-party = ["alembic"]
|
|||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
|
||||
[tool.uv]
|
||||
# Allow uv to search all configured indexes for packages, not just the first one
|
||||
# This prevents dependency resolution failures when using pytorch index + PyPI
|
||||
index-strategy = "unsafe-best-match"
|
||||
|
||||
[tool.ty]
|
||||
# Type checking configuration
|
||||
# ty is an extremely fast Python type checker from Astral (same team as ruff/uv)
|
||||
|
|
|
|||
303
hindsight-api/tests/test_vertexai_provider.py
Normal file
303
hindsight-api/tests/test_vertexai_provider.py
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
"""
|
||||
Test Vertex AI provider integration including token refresh and API calls.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
# Skip all tests if google-auth not available
|
||||
pytest.importorskip("google.auth")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_token_refresher_initialization():
|
||||
"""Test token refresher initialization with mocked credentials."""
|
||||
from hindsight_api.engine.vertexai_token_refresher import VertexAITokenRefresher
|
||||
|
||||
# Mock credentials
|
||||
mock_credentials = MagicMock()
|
||||
mock_credentials.token = "test-token-123"
|
||||
mock_credentials.expiry = None
|
||||
|
||||
with patch("google.auth.transport.requests.Request"):
|
||||
refresher = VertexAITokenRefresher(mock_credentials, "test-project", "us-central1")
|
||||
|
||||
# Verify token was fetched
|
||||
assert refresher.get_token() == "test-token-123"
|
||||
|
||||
# Verify base URL is correctly formatted
|
||||
expected_url = (
|
||||
"https://us-central1-aiplatform.googleapis.com/v1beta1/"
|
||||
"projects/test-project/locations/us-central1/endpoints/openapi"
|
||||
)
|
||||
assert refresher.get_base_url() == expected_url
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_token_refresher_background_refresh():
|
||||
"""Test that background refresh task starts and stops correctly."""
|
||||
from hindsight_api.engine.vertexai_token_refresher import VertexAITokenRefresher
|
||||
|
||||
# Mock credentials
|
||||
mock_credentials = MagicMock()
|
||||
mock_credentials.token = "test-token-123"
|
||||
mock_credentials.expiry = None
|
||||
|
||||
with patch("google.auth.transport.requests.Request"):
|
||||
refresher = VertexAITokenRefresher(mock_credentials, "test-project", "us-central1")
|
||||
|
||||
# Start refresh task
|
||||
refresher.start_refresh_task()
|
||||
assert refresher._refresh_task is not None
|
||||
assert not refresher._refresh_task.done()
|
||||
|
||||
# Stop refresh task
|
||||
await refresher.stop()
|
||||
assert refresher._refresh_task.done()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_token_refresher_thread_safety():
|
||||
"""Test that token access is thread-safe."""
|
||||
from hindsight_api.engine.vertexai_token_refresher import VertexAITokenRefresher
|
||||
|
||||
# Mock credentials
|
||||
mock_credentials = MagicMock()
|
||||
mock_credentials.token = "test-token-123"
|
||||
mock_credentials.expiry = None
|
||||
|
||||
with patch("google.auth.transport.requests.Request"):
|
||||
refresher = VertexAITokenRefresher(mock_credentials, "test-project", "us-central1")
|
||||
|
||||
# Access token from multiple tasks concurrently
|
||||
async def get_token_task():
|
||||
return refresher.get_token()
|
||||
|
||||
results = await asyncio.gather(*[get_token_task() for _ in range(10)])
|
||||
|
||||
# All should return the same token
|
||||
assert all(token == "test-token-123" for token in results)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_token_refresher_no_token_error():
|
||||
"""Test that getting token without refresh raises error."""
|
||||
from hindsight_api.engine.vertexai_token_refresher import VertexAITokenRefresher
|
||||
|
||||
# Mock credentials that fail to refresh
|
||||
mock_credentials = MagicMock()
|
||||
mock_credentials.token = None
|
||||
|
||||
with patch("google.auth.transport.requests.Request") as mock_request:
|
||||
mock_request.side_effect = Exception("Refresh failed")
|
||||
|
||||
with pytest.raises(Exception, match="Refresh failed"):
|
||||
VertexAITokenRefresher(mock_credentials, "test-project", "us-central1")
|
||||
|
||||
|
||||
def test_llm_wrapper_vertexai_missing_dependency():
|
||||
"""Test error when google-auth is not available."""
|
||||
from hindsight_api.engine import llm_wrapper
|
||||
|
||||
# Temporarily disable Vertex AI availability
|
||||
original_available = llm_wrapper.VERTEXAI_AVAILABLE
|
||||
try:
|
||||
llm_wrapper.VERTEXAI_AVAILABLE = False
|
||||
|
||||
with pytest.raises(ValueError, match="google-auth"):
|
||||
from hindsight_api.engine.llm_wrapper import LLMProvider
|
||||
|
||||
LLMProvider(
|
||||
provider="vertexai",
|
||||
api_key="",
|
||||
base_url="",
|
||||
model="google/gemini-2.0-flash-001",
|
||||
)
|
||||
finally:
|
||||
llm_wrapper.VERTEXAI_AVAILABLE = original_available
|
||||
|
||||
|
||||
def test_llm_wrapper_vertexai_missing_project_id():
|
||||
"""Test error when project ID is not configured."""
|
||||
with patch.dict(os.environ, {"HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID": ""}, clear=False):
|
||||
# Clear config cache to reload from env
|
||||
from hindsight_api.config import clear_config_cache
|
||||
|
||||
clear_config_cache()
|
||||
|
||||
with pytest.raises(ValueError, match="HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID"):
|
||||
from hindsight_api.engine.llm_wrapper import LLMProvider
|
||||
|
||||
LLMProvider(
|
||||
provider="vertexai",
|
||||
api_key="",
|
||||
base_url="",
|
||||
model="google/gemini-2.0-flash-001",
|
||||
)
|
||||
|
||||
# Restore config cache
|
||||
clear_config_cache()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_llm_wrapper_vertexai_adc_auth():
|
||||
"""Test Vertex AI with ADC authentication (mocked)."""
|
||||
from hindsight_api.engine.llm_wrapper import LLMProvider
|
||||
|
||||
mock_credentials = MagicMock()
|
||||
mock_credentials.token = "test-token-adc"
|
||||
mock_credentials.expiry = None
|
||||
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID": "test-project"},
|
||||
clear=False,
|
||||
):
|
||||
# Clear config cache to reload from env
|
||||
from hindsight_api.config import clear_config_cache
|
||||
|
||||
clear_config_cache()
|
||||
|
||||
with patch("google.auth.default", return_value=(mock_credentials, "test-project")):
|
||||
with patch("google.auth.transport.requests.Request"):
|
||||
provider = LLMProvider(
|
||||
provider="vertexai",
|
||||
api_key="",
|
||||
base_url="",
|
||||
model="google/gemini-2.0-flash-001",
|
||||
)
|
||||
|
||||
assert provider.provider == "vertexai"
|
||||
assert provider._vertexai_refresher is not None
|
||||
assert "aiplatform.googleapis.com" in provider.base_url
|
||||
|
||||
# Cleanup
|
||||
await provider.cleanup()
|
||||
|
||||
# Restore config cache
|
||||
clear_config_cache()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_llm_wrapper_vertexai_sa_auth():
|
||||
"""Test Vertex AI with service account authentication (mocked)."""
|
||||
from hindsight_api.engine.llm_wrapper import LLMProvider
|
||||
import google.auth.exceptions
|
||||
|
||||
mock_credentials = MagicMock()
|
||||
mock_credentials.token = "test-token-sa"
|
||||
mock_credentials.expiry = None
|
||||
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID": "test-project",
|
||||
"HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY": "/path/to/key.json",
|
||||
},
|
||||
clear=False,
|
||||
):
|
||||
# Clear config cache to reload from env
|
||||
from hindsight_api.config import clear_config_cache
|
||||
|
||||
clear_config_cache()
|
||||
|
||||
# Mock ADC failure, SA success
|
||||
with patch(
|
||||
"google.auth.default",
|
||||
side_effect=google.auth.exceptions.DefaultCredentialsError("ADC not available"),
|
||||
):
|
||||
with patch(
|
||||
"google.oauth2.service_account.Credentials.from_service_account_file",
|
||||
return_value=mock_credentials,
|
||||
):
|
||||
with patch("google.auth.transport.requests.Request"):
|
||||
provider = LLMProvider(
|
||||
provider="vertexai",
|
||||
api_key="",
|
||||
base_url="",
|
||||
model="google/gemini-2.0-flash-001",
|
||||
)
|
||||
|
||||
assert provider.provider == "vertexai"
|
||||
assert provider._vertexai_refresher is not None
|
||||
|
||||
# Cleanup
|
||||
await provider.cleanup()
|
||||
|
||||
# Restore config cache
|
||||
clear_config_cache()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_llm_wrapper_vertexai_auth_failure():
|
||||
"""Test Vertex AI with both ADC and SA auth failing."""
|
||||
import google.auth.exceptions
|
||||
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID": "test-project"},
|
||||
clear=False,
|
||||
):
|
||||
# Clear config cache to reload from env
|
||||
from hindsight_api.config import clear_config_cache
|
||||
|
||||
clear_config_cache()
|
||||
|
||||
# Mock both ADC and SA failures
|
||||
with patch(
|
||||
"google.auth.default",
|
||||
side_effect=google.auth.exceptions.DefaultCredentialsError("ADC failed"),
|
||||
):
|
||||
with pytest.raises(ValueError, match="authentication failed"):
|
||||
from hindsight_api.engine.llm_wrapper import LLMProvider
|
||||
|
||||
LLMProvider(
|
||||
provider="vertexai",
|
||||
api_key="",
|
||||
base_url="",
|
||||
model="google/gemini-2.0-flash-001",
|
||||
)
|
||||
|
||||
# Restore config cache
|
||||
clear_config_cache()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
not os.getenv("HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID"),
|
||||
reason="Vertex AI integration tests require HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID",
|
||||
)
|
||||
async def test_vertexai_integration_actual_api():
|
||||
"""
|
||||
Integration test with actual Vertex AI API.
|
||||
|
||||
Requires:
|
||||
- HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID
|
||||
- ADC or HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY
|
||||
"""
|
||||
from hindsight_api.engine.llm_wrapper import LLMProvider
|
||||
|
||||
provider = LLMProvider(
|
||||
provider="vertexai",
|
||||
api_key="",
|
||||
base_url="",
|
||||
model="google/gemini-2.0-flash-001",
|
||||
)
|
||||
|
||||
try:
|
||||
# Simple test call
|
||||
response = await provider.call(
|
||||
messages=[{"role": "user", "content": "Say 'ok' and nothing else"}],
|
||||
max_completion_tokens=10,
|
||||
)
|
||||
|
||||
assert response is not None
|
||||
assert isinstance(response, str)
|
||||
assert len(response) > 0
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
await provider.cleanup()
|
||||
|
|
@ -7,14 +7,14 @@
|
|||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.4.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
""" # noqa: E501
|
||||
|
||||
|
||||
__version__ = "0.4.2"
|
||||
__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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1\n"\
|
||||
"Version of the API: 0.4.2\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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
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.1
|
||||
The version of the OpenAPI document: 0.4.2
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ hindsight-admin run-db-migration --schema tenant_acme
|
|||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `HINDSIGHT_API_LLM_PROVIDER` | Provider: `openai`, `anthropic`, `gemini`, `groq`, `ollama`, `lmstudio` | `openai` |
|
||||
| `HINDSIGHT_API_LLM_PROVIDER` | Provider: `openai`, `anthropic`, `gemini`, `groq`, `ollama`, `lmstudio`, `vertexai` | `openai` |
|
||||
| `HINDSIGHT_API_LLM_API_KEY` | API key for LLM provider | - |
|
||||
| `HINDSIGHT_API_LLM_MODEL` | Model name | `gpt-5-mini` |
|
||||
| `HINDSIGHT_API_LLM_BASE_URL` | Custom LLM endpoint | Provider default |
|
||||
|
|
@ -97,6 +97,14 @@ export HINDSIGHT_API_LLM_PROVIDER=anthropic
|
|||
export HINDSIGHT_API_LLM_API_KEY=sk-ant-xxxxxxxxxxxx
|
||||
export HINDSIGHT_API_LLM_MODEL=claude-sonnet-4-20250514
|
||||
|
||||
# Vertex AI (Google Cloud)
|
||||
export HINDSIGHT_API_LLM_PROVIDER=vertexai
|
||||
export HINDSIGHT_API_LLM_MODEL=google/gemini-2.0-flash-001
|
||||
export HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=your-gcp-project-id
|
||||
export HINDSIGHT_API_LLM_VERTEXAI_REGION=us-central1
|
||||
# Optional: use ADC (gcloud auth application-default login) or provide service account key:
|
||||
# export HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/path/to/service-account-key.json
|
||||
|
||||
# Ollama (local, no API key)
|
||||
export HINDSIGHT_API_LLM_PROVIDER=ollama
|
||||
export HINDSIGHT_API_LLM_BASE_URL=http://localhost:11434/v1
|
||||
|
|
@ -114,6 +122,56 @@ export HINDSIGHT_API_LLM_API_KEY=your-api-key
|
|||
export HINDSIGHT_API_LLM_MODEL=your-model-name
|
||||
```
|
||||
|
||||
#### Vertex AI Setup
|
||||
|
||||
Google Cloud's Vertex AI provides OpenAI-compatible endpoints for Gemini models. Hindsight supports two authentication methods:
|
||||
|
||||
**Prerequisites:**
|
||||
- GCP project with Vertex AI API enabled
|
||||
- IAM role `roles/aiplatform.user` for your credentials
|
||||
|
||||
**Environment Variables:**
|
||||
|
||||
| Variable | Description | Required |
|
||||
|----------|-------------|----------|
|
||||
| `HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID` | Your GCP project ID | Yes |
|
||||
| `HINDSIGHT_API_LLM_VERTEXAI_REGION` | GCP region (e.g., `us-central1`) | No (default: `us-central1`) |
|
||||
| `HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY` | Path to service account JSON key file | No (uses ADC if not set) |
|
||||
|
||||
**Authentication Methods:**
|
||||
|
||||
1. **Application Default Credentials (ADC)** - Recommended for development
|
||||
```bash
|
||||
# Setup ADC
|
||||
gcloud auth application-default login
|
||||
|
||||
# Configure Hindsight
|
||||
export HINDSIGHT_API_LLM_PROVIDER=vertexai
|
||||
export HINDSIGHT_API_LLM_MODEL=google/gemini-2.0-flash-001
|
||||
export HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=your-project-id
|
||||
```
|
||||
|
||||
2. **Service Account Key** - Recommended for production
|
||||
```bash
|
||||
# Create service account and download key
|
||||
gcloud iam service-accounts create hindsight-api
|
||||
gcloud projects add-iam-policy-binding your-project-id \
|
||||
--member="serviceAccount:hindsight-api@your-project-id.iam.gserviceaccount.com" \
|
||||
--role="roles/aiplatform.user"
|
||||
gcloud iam service-accounts keys create key.json \
|
||||
--iam-account=hindsight-api@your-project-id.iam.gserviceaccount.com
|
||||
|
||||
# Configure Hindsight
|
||||
export HINDSIGHT_API_LLM_PROVIDER=vertexai
|
||||
export HINDSIGHT_API_LLM_MODEL=google/gemini-2.0-flash-001
|
||||
export HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=your-project-id
|
||||
export HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/path/to/key.json
|
||||
```
|
||||
|
||||
**Authentication Priority:** Hindsight tries ADC first, then falls back to service account key file if configured.
|
||||
|
||||
**Token Management:** Access tokens expire after 60 minutes. Hindsight automatically refreshes tokens every 50 minutes in the background.
|
||||
|
||||
### Per-Operation LLM Configuration
|
||||
|
||||
Different memory operations have different requirements. **Retain** (fact extraction) benefits from models with strong structured output capabilities, while **Reflect** (reasoning/response generation) can use lighter, faster models. Configure separate LLM models for each operation to optimize for cost and performance.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"name": "Apache 2.0",
|
||||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
},
|
||||
"version": "0.4.1"
|
||||
"version": "0.4.2"
|
||||
},
|
||||
"paths": {
|
||||
"/health": {
|
||||
|
|
|
|||
|
|
@ -2,4 +2,7 @@
|
|||
members = ["hindsight", "hindsight-api", "hindsight-dev", "hindsight-mcp-server", "hindsight-clients/python", "hindsight-embed"]
|
||||
|
||||
[tool.uv]
|
||||
# Allow uv to search all configured indexes for packages, not just the first one
|
||||
# This prevents dependency resolution failures when using pytorch index + PyPI
|
||||
index-strategy = "unsafe-best-match"
|
||||
dev-dependencies = []
|
||||
|
|
|
|||
Loading…
Reference in a new issue