hindsight-api 0.0.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. hindsight_api/__init__.py +38 -0
  2. hindsight_api/api/__init__.py +105 -0
  3. hindsight_api/api/http.py +1872 -0
  4. hindsight_api/api/mcp.py +157 -0
  5. hindsight_api/engine/__init__.py +47 -0
  6. hindsight_api/engine/cross_encoder.py +97 -0
  7. hindsight_api/engine/db_utils.py +93 -0
  8. hindsight_api/engine/embeddings.py +113 -0
  9. hindsight_api/engine/entity_resolver.py +575 -0
  10. hindsight_api/engine/llm_wrapper.py +269 -0
  11. hindsight_api/engine/memory_engine.py +3095 -0
  12. hindsight_api/engine/query_analyzer.py +519 -0
  13. hindsight_api/engine/response_models.py +222 -0
  14. hindsight_api/engine/retain/__init__.py +50 -0
  15. hindsight_api/engine/retain/bank_utils.py +423 -0
  16. hindsight_api/engine/retain/chunk_storage.py +82 -0
  17. hindsight_api/engine/retain/deduplication.py +104 -0
  18. hindsight_api/engine/retain/embedding_processing.py +62 -0
  19. hindsight_api/engine/retain/embedding_utils.py +54 -0
  20. hindsight_api/engine/retain/entity_processing.py +90 -0
  21. hindsight_api/engine/retain/fact_extraction.py +1027 -0
  22. hindsight_api/engine/retain/fact_storage.py +176 -0
  23. hindsight_api/engine/retain/link_creation.py +121 -0
  24. hindsight_api/engine/retain/link_utils.py +651 -0
  25. hindsight_api/engine/retain/orchestrator.py +405 -0
  26. hindsight_api/engine/retain/types.py +206 -0
  27. hindsight_api/engine/search/__init__.py +15 -0
  28. hindsight_api/engine/search/fusion.py +122 -0
  29. hindsight_api/engine/search/observation_utils.py +132 -0
  30. hindsight_api/engine/search/reranking.py +103 -0
  31. hindsight_api/engine/search/retrieval.py +503 -0
  32. hindsight_api/engine/search/scoring.py +161 -0
  33. hindsight_api/engine/search/temporal_extraction.py +64 -0
  34. hindsight_api/engine/search/think_utils.py +255 -0
  35. hindsight_api/engine/search/trace.py +215 -0
  36. hindsight_api/engine/search/tracer.py +447 -0
  37. hindsight_api/engine/search/types.py +160 -0
  38. hindsight_api/engine/task_backend.py +223 -0
  39. hindsight_api/engine/utils.py +203 -0
  40. hindsight_api/metrics.py +227 -0
  41. hindsight_api/migrations.py +163 -0
  42. hindsight_api/models.py +309 -0
  43. hindsight_api/pg0.py +425 -0
  44. hindsight_api/web/__init__.py +12 -0
  45. hindsight_api/web/server.py +143 -0
  46. hindsight_api-0.0.13.dist-info/METADATA +41 -0
  47. hindsight_api-0.0.13.dist-info/RECORD +48 -0
  48. hindsight_api-0.0.13.dist-info/WHEEL +4 -0
hindsight_api/pg0.py ADDED
@@ -0,0 +1,425 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ import os
5
+ import platform
6
+ import re
7
+ import shutil
8
+ import stat
9
+ import subprocess
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+ import httpx
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # pg0 configuration
18
+ BINARY_NAME = "pg0"
19
+ DEFAULT_PORT = 5555
20
+ DEFAULT_USERNAME = "hindsight"
21
+ DEFAULT_PASSWORD = "hindsight"
22
+ DEFAULT_DATABASE = "hindsight"
23
+
24
+
25
+ def get_platform_binary_name() -> str:
26
+ """Get the appropriate binary name for the current platform.
27
+
28
+ Supported platforms:
29
+ - macOS ARM64 (darwin-aarch64)
30
+ - Linux x86_64 (gnu)
31
+ - Linux ARM64 (gnu)
32
+ - Windows x86_64
33
+ """
34
+ system = platform.system().lower()
35
+ machine = platform.machine().lower()
36
+
37
+ # Normalize architecture names
38
+ if machine in ("x86_64", "amd64"):
39
+ arch = "x86_64"
40
+ elif machine in ("arm64", "aarch64"):
41
+ arch = "aarch64"
42
+ else:
43
+ raise RuntimeError(
44
+ f"Embedded PostgreSQL is not supported on architecture: {machine}. "
45
+ f"Supported architectures: x86_64/amd64 (Linux, Windows), aarch64/arm64 (macOS, Linux)"
46
+ )
47
+
48
+ if system == "darwin" and arch == "aarch64":
49
+ return "pg0-darwin-aarch64"
50
+ elif system == "linux" and arch == "x86_64":
51
+ return "pg0-linux-x86_64-gnu"
52
+ elif system == "linux" and arch == "aarch64":
53
+ return "pg0-linux-aarch64-gnu"
54
+ elif system == "windows" and arch == "x86_64":
55
+ return "pg0-windows-x86_64.exe"
56
+ else:
57
+ raise RuntimeError(
58
+ f"Embedded PostgreSQL is not supported on {system}-{arch}. "
59
+ f"Supported platforms: darwin-aarch64 (macOS ARM), linux-x86_64-gnu, linux-aarch64-gnu, windows-x86_64"
60
+ )
61
+
62
+
63
+ def get_download_url(
64
+ version: str = "latest",
65
+ repo: str = "vectorize-io/pg0",
66
+ ) -> str:
67
+ """Get the download URL for pg0 binary."""
68
+ binary_name = get_platform_binary_name()
69
+
70
+ if version == "latest":
71
+ return f"https://github.com/{repo}/releases/latest/download/{binary_name}"
72
+ else:
73
+ return f"https://github.com/{repo}/releases/download/{version}/{binary_name}"
74
+
75
+
76
+ def _find_pg0_binary() -> Optional[Path]:
77
+ """Find pg0 binary in PATH or default install location."""
78
+ # First check PATH
79
+ pg0_in_path = shutil.which("pg0")
80
+ if pg0_in_path:
81
+ return Path(pg0_in_path)
82
+
83
+ # Fall back to default install location
84
+ default_path = Path.home() / ".hindsight" / "bin" / "pg0"
85
+ if default_path.exists() and os.access(default_path, os.X_OK):
86
+ return default_path
87
+
88
+ return None
89
+
90
+
91
+ class EmbeddedPostgres:
92
+ """
93
+ Manages an embedded PostgreSQL server instance using pg0.
94
+
95
+ This class handles:
96
+ - Finding or downloading the pg0 CLI
97
+ - Starting/stopping the PostgreSQL server
98
+ - Getting the connection URI
99
+
100
+ Example:
101
+ pg = EmbeddedPostgres()
102
+ await pg.ensure_installed()
103
+ await pg.start()
104
+ uri = await pg.get_uri()
105
+ # ... use uri with asyncpg ...
106
+ await pg.stop()
107
+ """
108
+
109
+ def __init__(
110
+ self,
111
+ version: str = "latest",
112
+ port: int = DEFAULT_PORT,
113
+ username: str = DEFAULT_USERNAME,
114
+ password: str = DEFAULT_PASSWORD,
115
+ database: str = DEFAULT_DATABASE,
116
+ name: str = "hindsight",
117
+ ):
118
+ """
119
+ Initialize the embedded PostgreSQL manager.
120
+
121
+ Args:
122
+ version: Version of pg0 to download if not found. Defaults to "latest"
123
+ port: Port to listen on. Defaults to 5555
124
+ username: Username for the database. Defaults to "hindsight"
125
+ password: Password for the database. Defaults to "hindsight"
126
+ database: Database name to create. Defaults to "hindsight"
127
+ name: Instance name for pg0. Defaults to "hindsight"
128
+ """
129
+ self.version = version
130
+ self.port = port
131
+ self.username = username
132
+ self.password = password
133
+ self.database = database
134
+ self.name = name
135
+
136
+ # Will be set when binary is found/installed
137
+ self._binary_path: Optional[Path] = _find_pg0_binary()
138
+
139
+ @property
140
+ def binary_path(self) -> Path:
141
+ """Get the path to the pg0 binary."""
142
+ if self._binary_path is None:
143
+ # Default install location
144
+ return Path.home() / ".hindsight" / "bin" / "pg0"
145
+ return self._binary_path
146
+
147
+ def is_installed(self) -> bool:
148
+ """Check if pg0 is available (in PATH or installed)."""
149
+ self._binary_path = _find_pg0_binary()
150
+ return self._binary_path is not None
151
+
152
+ async def ensure_installed(self) -> None:
153
+ """
154
+ Ensure pg0 is available.
155
+
156
+ First checks PATH, then default location, then downloads if needed.
157
+ """
158
+ if self.is_installed():
159
+ logger.debug(f"pg0 found at {self._binary_path}")
160
+ return
161
+
162
+ logger.info("pg0 not found, downloading...")
163
+
164
+ # Log platform information
165
+ binary_name = get_platform_binary_name()
166
+ logger.info(f"Detected platform: system={platform.system()}, machine={platform.machine()}")
167
+
168
+ # Install to default location
169
+ install_dir = Path.home() / ".hindsight" / "bin"
170
+ install_dir.mkdir(parents=True, exist_ok=True)
171
+ install_path = install_dir / "pg0"
172
+
173
+ # Download the binary
174
+ download_url = get_download_url(self.version)
175
+ logger.info(f"Downloading from {download_url}")
176
+
177
+ try:
178
+ async with httpx.AsyncClient(follow_redirects=True, timeout=300.0) as client:
179
+ response = await client.get(download_url)
180
+ response.raise_for_status()
181
+
182
+ # Write binary to disk
183
+ with open(install_path, "wb") as f:
184
+ f.write(response.content)
185
+
186
+ # Make executable on Unix
187
+ if platform.system() != "Windows":
188
+ st = os.stat(install_path)
189
+ os.chmod(install_path, st.st_mode | stat.S_IEXEC)
190
+
191
+ self._binary_path = install_path
192
+ logger.info(f"Installed pg0 to {install_path}")
193
+
194
+ except httpx.HTTPError as e:
195
+ raise RuntimeError(f"Failed to download pg0: {e}") from e
196
+
197
+ def _run_command(self, *args: str, capture_output: bool = True) -> subprocess.CompletedProcess:
198
+ """Run a pg0 command synchronously."""
199
+ cmd = [str(self.binary_path), *args]
200
+ return subprocess.run(cmd, capture_output=capture_output, text=True)
201
+
202
+ async def _run_command_async(self, *args: str, timeout: int = 120) -> tuple[int, str, str]:
203
+ """Run a pg0 command asynchronously."""
204
+ cmd = [str(self.binary_path), *args]
205
+
206
+ def run_sync():
207
+ try:
208
+ result = subprocess.run(
209
+ cmd,
210
+ stdin=subprocess.DEVNULL,
211
+ stdout=subprocess.PIPE,
212
+ stderr=subprocess.PIPE,
213
+ text=True,
214
+ timeout=timeout,
215
+ )
216
+ return result.returncode, result.stdout, result.stderr
217
+ except subprocess.TimeoutExpired:
218
+ return 1, "", "Command timed out"
219
+
220
+ loop = asyncio.get_event_loop()
221
+ return await loop.run_in_executor(None, run_sync)
222
+
223
+ def _extract_uri_from_output(self, output: str) -> Optional[str]:
224
+ """Extract the PostgreSQL URI from pg0 start output."""
225
+ match = re.search(r"Connection URI:\s*(postgresql://[^\s]+)", output)
226
+ if match:
227
+ return match.group(1)
228
+ return None
229
+
230
+ async def start(self, max_retries: int = 3, retry_delay: float = 2.0) -> str:
231
+ """
232
+ Start the PostgreSQL server with retry logic.
233
+
234
+ Args:
235
+ max_retries: Maximum number of start attempts (default: 3)
236
+ retry_delay: Initial delay between retries in seconds (default: 2.0)
237
+
238
+ Returns:
239
+ The connection URI for the started server.
240
+
241
+ Raises:
242
+ RuntimeError: If the server fails to start after all retries.
243
+ """
244
+ if not self.is_installed():
245
+ raise RuntimeError("pg0 is not installed. Call ensure_installed() first.")
246
+
247
+ logger.info(f"Starting embedded PostgreSQL (name: {self.name}, port: {self.port})...")
248
+
249
+ last_error = None
250
+ for attempt in range(1, max_retries + 1):
251
+ returncode, stdout, stderr = await self._run_command_async(
252
+ "start",
253
+ "--name", self.name,
254
+ "--port", str(self.port),
255
+ "--username", self.username,
256
+ "--password", self.password,
257
+ "--database", self.database,
258
+ timeout=300,
259
+ )
260
+
261
+ # Try to extract URI from output
262
+ uri = self._extract_uri_from_output(stdout)
263
+ if uri:
264
+ logger.info(f"PostgreSQL started on port {self.port}")
265
+ return uri
266
+
267
+ # Check if pg0 info can find the running instance
268
+ try:
269
+ uri = await self.get_uri()
270
+ logger.info(f"PostgreSQL started on port {self.port}")
271
+ return uri
272
+ except RuntimeError:
273
+ pass
274
+
275
+ # Start failed, log and retry
276
+ last_error = stderr or f"pg0 start returned exit code {returncode}"
277
+ if attempt < max_retries:
278
+ delay = retry_delay * (2 ** (attempt - 1))
279
+ logger.warning(f"pg0 start attempt {attempt}/{max_retries} failed: {last_error.strip()}")
280
+ logger.info(f"Retrying in {delay:.1f}s...")
281
+ await asyncio.sleep(delay)
282
+ else:
283
+ logger.warning(f"pg0 start attempt {attempt}/{max_retries} failed: {last_error.strip()}")
284
+
285
+ # All retries exhausted - use constructed URI as fallback
286
+ uri = f"postgresql://{self.username}:{self.password}@localhost:{self.port}/{self.database}"
287
+ logger.warning(f"All pg0 start attempts failed, using constructed URI: {uri}")
288
+ return uri
289
+
290
+ async def stop(self) -> None:
291
+ """Stop the PostgreSQL server."""
292
+ if not self.is_installed():
293
+ return
294
+
295
+ logger.info(f"Stopping embedded PostgreSQL (name: {self.name})...")
296
+
297
+ returncode, stdout, stderr = await self._run_command_async("stop", "--name", self.name)
298
+
299
+ if returncode != 0:
300
+ if "not running" in stderr.lower():
301
+ return
302
+ raise RuntimeError(f"Failed to stop PostgreSQL: {stderr}")
303
+
304
+ logger.info("Embedded PostgreSQL stopped")
305
+
306
+ async def _get_info(self) -> dict:
307
+ """Get info from pg0 using the `info -o json` command."""
308
+ if not self.is_installed():
309
+ raise RuntimeError("pg0 is not installed.")
310
+
311
+ returncode, stdout, stderr = await self._run_command_async(
312
+ "info", "--name", self.name, "-o", "json"
313
+ )
314
+
315
+ if returncode != 0:
316
+ raise RuntimeError(f"Failed to get PostgreSQL info: {stderr}")
317
+
318
+ try:
319
+ return json.loads(stdout.strip())
320
+ except json.JSONDecodeError as e:
321
+ raise RuntimeError(f"Failed to parse pg0 info output: {e}")
322
+
323
+ async def get_uri(self) -> str:
324
+ """Get the connection URI for the PostgreSQL server."""
325
+ info = await self._get_info()
326
+ uri = info.get("uri")
327
+ if not uri:
328
+ raise RuntimeError("PostgreSQL server is not running or URI not available")
329
+ return uri
330
+
331
+ async def status(self) -> dict:
332
+ """Get the status of the PostgreSQL server."""
333
+ if not self.is_installed():
334
+ return {"installed": False, "running": False}
335
+
336
+ try:
337
+ info = await self._get_info()
338
+ return {
339
+ "installed": True,
340
+ "running": info.get("running", False),
341
+ "uri": info.get("uri"),
342
+ }
343
+ except RuntimeError:
344
+ return {"installed": True, "running": False}
345
+
346
+ async def is_running(self) -> bool:
347
+ """Check if the PostgreSQL server is currently running."""
348
+ if not self.is_installed():
349
+ return False
350
+ try:
351
+ info = await self._get_info()
352
+ return info.get("running", False)
353
+ except RuntimeError:
354
+ return False
355
+
356
+ async def ensure_running(self) -> str:
357
+ """
358
+ Ensure the PostgreSQL server is running.
359
+
360
+ Installs if needed, starts if not running.
361
+
362
+ Returns:
363
+ The connection URI.
364
+ """
365
+ await self.ensure_installed()
366
+
367
+ if await self.is_running():
368
+ return await self.get_uri()
369
+
370
+ return await self.start()
371
+
372
+ def uninstall(self) -> None:
373
+ """Remove the pg0 binary (only if we installed it)."""
374
+ default_path = Path.home() / ".hindsight" / "bin" / "pg0"
375
+ if default_path.exists():
376
+ default_path.unlink()
377
+ logger.info(f"Removed {default_path}")
378
+
379
+ def clear_data(self) -> None:
380
+ """Remove all PostgreSQL data (destructive!)."""
381
+ result = self._run_command("drop", "--name", self.name, "--force")
382
+ if result.returncode == 0:
383
+ logger.info(f"Dropped pg0 instance {self.name}")
384
+ else:
385
+ logger.warning(f"Failed to drop pg0 instance {self.name}: {result.stderr}")
386
+
387
+
388
+ # Convenience functions
389
+
390
+ _default_instance: Optional[EmbeddedPostgres] = None
391
+
392
+
393
+ def get_embedded_postgres() -> EmbeddedPostgres:
394
+ """Get or create the default EmbeddedPostgres instance."""
395
+ global _default_instance
396
+
397
+ if _default_instance is None:
398
+ _default_instance = EmbeddedPostgres()
399
+
400
+ return _default_instance
401
+
402
+
403
+ async def start_embedded_postgres() -> str:
404
+ """
405
+ Quick start function for embedded PostgreSQL.
406
+
407
+ Downloads, installs, and starts PostgreSQL in one call.
408
+
409
+ Returns:
410
+ Connection URI string
411
+
412
+ Example:
413
+ db_url = await start_embedded_postgres()
414
+ conn = await asyncpg.connect(db_url)
415
+ """
416
+ pg = get_embedded_postgres()
417
+ return await pg.ensure_running()
418
+
419
+
420
+ async def stop_embedded_postgres() -> None:
421
+ """Stop the default embedded PostgreSQL instance."""
422
+ global _default_instance
423
+
424
+ if _default_instance:
425
+ await _default_instance.stop()
@@ -0,0 +1,12 @@
1
+ """
2
+ Web interface for memory system.
3
+
4
+ Provides FastAPI app and visualization interface.
5
+ """
6
+ from hindsight_api.api import create_app
7
+
8
+ # Note: Don't import app from .server here to avoid circular import warnings
9
+ # when running with `python -m hindsight_api.web.server`
10
+ # If you need the app, import it directly: from hindsight_api.web.server import app
11
+
12
+ __all__ = ["create_app"]
@@ -0,0 +1,143 @@
1
+ """
2
+ FastAPI server for memory graph visualization and API.
3
+
4
+ Provides REST API endpoints for memory operations and serves
5
+ the interactive visualization interface.
6
+ """
7
+ import warnings
8
+
9
+ # Filter deprecation warnings from third-party libraries
10
+ warnings.filterwarnings("ignore", message="websockets.legacy is deprecated")
11
+ warnings.filterwarnings("ignore", message="websockets.server.WebSocketServerProtocol is deprecated")
12
+
13
+ import asyncio
14
+ import atexit
15
+ import logging
16
+ import os
17
+ import argparse
18
+ import signal
19
+ import sys
20
+
21
+ from hindsight_api import MemoryEngine
22
+ from hindsight_api.api import create_app
23
+
24
+ # Disable tokenizers parallelism to avoid warnings
25
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
26
+
27
+
28
+ def _cleanup_pg0():
29
+ """Synchronous cleanup function to stop pg0 on exit."""
30
+ global _memory
31
+ if _memory is not None and _memory._pg0 is not None:
32
+ try:
33
+ # Run async stop in a new event loop
34
+ loop = asyncio.new_event_loop()
35
+ loop.run_until_complete(_memory._pg0.stop())
36
+ loop.close()
37
+ print("\npg0 stopped.")
38
+ except Exception as e:
39
+ print(f"\nError stopping pg0: {e}")
40
+
41
+
42
+ # Register cleanup on normal exit
43
+ atexit.register(_cleanup_pg0)
44
+
45
+
46
+ def _signal_handler(signum, frame):
47
+ """Handle SIGINT/SIGTERM to ensure pg0 cleanup."""
48
+ print(f"\nReceived signal {signum}, shutting down...")
49
+ _cleanup_pg0()
50
+ sys.exit(0)
51
+
52
+
53
+ # Register signal handlers for graceful shutdown
54
+ signal.signal(signal.SIGINT, _signal_handler)
55
+ signal.signal(signal.SIGTERM, _signal_handler)
56
+
57
+
58
+ # Create app at module level (required for uvicorn import string)
59
+ _memory = MemoryEngine(
60
+ db_url=os.getenv("HINDSIGHT_API_DATABASE_URL", "pg0"),
61
+ memory_llm_provider=os.getenv("HINDSIGHT_API_LLM_PROVIDER", "groq"),
62
+ memory_llm_api_key=os.getenv("HINDSIGHT_API_LLM_API_KEY"),
63
+ memory_llm_model=os.getenv("HINDSIGHT_API_LLM_MODEL", "openai/gpt-oss-120b"),
64
+ memory_llm_base_url=os.getenv("HINDSIGHT_API_LLM_BASE_URL") or None,
65
+ )
66
+
67
+ # Check if MCP should be enabled
68
+ mcp_enabled = os.getenv("HINDSIGHT_API_MCP_ENABLED", "true").lower() == "true"
69
+
70
+ # Create unified app with both HTTP and optionally MCP
71
+ app = create_app(
72
+ memory=_memory,
73
+ http_api_enabled=True,
74
+ mcp_api_enabled=mcp_enabled,
75
+ mcp_mount_path="/mcp"
76
+ )
77
+
78
+
79
+ if __name__ == "__main__":
80
+ import uvicorn
81
+
82
+ # Get log level from environment variable (default: info)
83
+ env_log_level = os.environ.get("HINDSIGHT_API_LOG_LEVEL", "info").lower()
84
+ if env_log_level not in ["critical", "error", "warning", "info", "debug", "trace"]:
85
+ env_log_level = "info"
86
+
87
+ # Parse CLI arguments
88
+ parser = argparse.ArgumentParser(description="Memory Graph API Server")
89
+ parser.add_argument("--host", default="0.0.0.0", help="Host to bind to (default: 0.0.0.0)")
90
+ parser.add_argument("--port", type=int, default=8888, help="Port to bind to (default: 8888)")
91
+ parser.add_argument("--reload", action="store_true", help="Enable auto-reload on code changes")
92
+ parser.add_argument("--workers", type=int, default=1, help="Number of worker processes (default: 1)")
93
+ parser.add_argument("--log-level", default=env_log_level, choices=["critical", "error", "warning", "info", "debug", "trace"],
94
+ help=f"Log level (default: {env_log_level}, from HINDSIGHT_API_LOG_LEVEL)")
95
+ parser.add_argument("--access-log", action="store_true", help="Enable access log")
96
+ parser.add_argument("--no-access-log", dest="access_log", action="store_false", help="Disable access log")
97
+ parser.add_argument("--proxy-headers", action="store_true", help="Enable X-Forwarded-Proto, X-Forwarded-For headers")
98
+ parser.add_argument("--forwarded-allow-ips", default=None, help="Comma separated list of IPs to trust with proxy headers")
99
+ parser.add_argument("--ssl-keyfile", default=None, help="SSL key file")
100
+ parser.add_argument("--ssl-certfile", default=None, help="SSL certificate file")
101
+ parser.set_defaults(access_log=False)
102
+
103
+ args = parser.parse_args()
104
+
105
+ # Configure Python logging based on log level
106
+ log_level_map = {
107
+ "critical": logging.CRITICAL,
108
+ "error": logging.ERROR,
109
+ "warning": logging.WARNING,
110
+ "info": logging.INFO,
111
+ "debug": logging.DEBUG,
112
+ "trace": logging.DEBUG, # Python doesn't have TRACE, use DEBUG
113
+ }
114
+ logging.basicConfig(
115
+ level=log_level_map.get(args.log_level, logging.INFO),
116
+ format="%(asctime)s - %(levelname)s - %(name)s - %(message)s"
117
+ )
118
+ logging.info(f"Starting Hindsight API on {args.host}:{args.port}")
119
+
120
+ app_ref = "hindsight_api.web.server:app"
121
+
122
+ # Prepare uvicorn config
123
+ uvicorn_config = {
124
+ "app": app_ref,
125
+ "host": args.host,
126
+ "port": args.port,
127
+ "reload": args.reload,
128
+ "workers": args.workers,
129
+ "log_level": args.log_level,
130
+ "access_log": args.access_log,
131
+ "proxy_headers": args.proxy_headers,
132
+ "ws": "wsproto", # Use wsproto instead of websockets to avoid deprecation warnings
133
+ }
134
+
135
+ # Add optional parameters if provided
136
+ if args.forwarded_allow_ips:
137
+ uvicorn_config["forwarded_allow_ips"] = args.forwarded_allow_ips
138
+ if args.ssl_keyfile:
139
+ uvicorn_config["ssl_keyfile"] = args.ssl_keyfile
140
+ if args.ssl_certfile:
141
+ uvicorn_config["ssl_certfile"] = args.ssl_certfile
142
+
143
+ uvicorn.run(**uvicorn_config)
@@ -0,0 +1,41 @@
1
+ Metadata-Version: 2.4
2
+ Name: hindsight-api
3
+ Version: 0.0.13
4
+ Summary: Temporal + Semantic + Entity Memory System for AI agents using PostgreSQL
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: alembic>=1.17.1
7
+ Requires-Dist: asyncpg>=0.29.0
8
+ Requires-Dist: dateparser>=1.2.2
9
+ Requires-Dist: fastapi[standard]>=0.120.3
10
+ Requires-Dist: fastmcp>=2.0.0
11
+ Requires-Dist: greenlet>=3.2.4
12
+ Requires-Dist: httpx>=0.27.0
13
+ Requires-Dist: langchain-text-splitters>=0.3.0
14
+ Requires-Dist: openai>=1.0.0
15
+ Requires-Dist: opentelemetry-api>=1.20.0
16
+ Requires-Dist: opentelemetry-exporter-prometheus>=0.41b0
17
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.41b0
18
+ Requires-Dist: opentelemetry-sdk>=1.20.0
19
+ Requires-Dist: pgvector>=0.4.1
20
+ Requires-Dist: psycopg2-binary>=2.9.11
21
+ Requires-Dist: pydantic>=2.0.0
22
+ Requires-Dist: python-dateutil>=2.8.0
23
+ Requires-Dist: python-dotenv>=1.0.0
24
+ Requires-Dist: rich>=13.0.0
25
+ Requires-Dist: sentence-transformers>=2.2.0
26
+ Requires-Dist: sqlalchemy>=2.0.44
27
+ Requires-Dist: tiktoken>=0.12.0
28
+ Requires-Dist: torch>=2.0.0
29
+ Requires-Dist: transformers>=4.30.0
30
+ Requires-Dist: uvicorn>=0.38.0
31
+ Requires-Dist: wsproto>=1.0.0
32
+ Provides-Extra: test
33
+ Requires-Dist: filelock>=3.0.0; extra == 'test'
34
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'test'
35
+ Requires-Dist: pytest-timeout>=2.4.0; extra == 'test'
36
+ Requires-Dist: pytest-xdist>=3.0.0; extra == 'test'
37
+ Requires-Dist: pytest>=7.0.0; extra == 'test'
38
+ Requires-Dist: testcontainers[postgres]>=4.0.0; extra == 'test'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # Memory
@@ -0,0 +1,48 @@
1
+ hindsight_api/__init__.py,sha256=yQWYWUWEhvs1OY1coENhZV_CuOAWmN_YKZXQMIvGN94,851
2
+ hindsight_api/metrics.py,sha256=j4-eeqVjjcGQxAxS_GgEaBNm10KdUxrGS_I2d1IM1hY,7255
3
+ hindsight_api/migrations.py,sha256=VY-ILJLWEY1IaeJgQ2jlAVUtPLzq_41Dytg_DjuF0GA,6402
4
+ hindsight_api/models.py,sha256=1vMn9jmDQvohfmxZXr1SYnhz5vhz52nrTd93A_lkVNE,12606
5
+ hindsight_api/pg0.py,sha256=scFcYngOwbZ2oOQb7TysnUHgNgPyiN30pjPcIqMDmao,14158
6
+ hindsight_api/api/__init__.py,sha256=cDqM1tXOk1aq5Hy__vJ97O4XOQUB4Qt-ea_szTnbQ3o,3037
7
+ hindsight_api/api/http.py,sha256=anjh8axWcWF1dyqW3CnE9TUObLKxryjeQxT_keQEMak,71551
8
+ hindsight_api/api/mcp.py,sha256=NbRSbEGih7zCFMmwddLgD_UYv-WjvwYWHLq2-vzz4SA,7862
9
+ hindsight_api/engine/__init__.py,sha256=5DU5DvnJdzkrgNgKchpzkiJr-37I-kE1tegJg2LF04k,1214
10
+ hindsight_api/engine/cross_encoder.py,sha256=kfwLiqlQUfvOgLyrkRReO1wWlO020lGbLXY8U0jKiPA,2875
11
+ hindsight_api/engine/db_utils.py,sha256=p1Ne70wPP327xdPI_XjMfnagilY8sknbkhEIZuED6DU,2724
12
+ hindsight_api/engine/embeddings.py,sha256=a0wox2SCIE7ezgy-B5_23Cp1_icYiUR3g06hPpzi_ck,3586
13
+ hindsight_api/engine/entity_resolver.py,sha256=y_KWDkWaJwKluhGgJYAr_Amg4GTzyJAnrmRKnsyevsk,21737
14
+ hindsight_api/engine/llm_wrapper.py,sha256=HeAJDwNZjDkWR-6SVIrRJ7XmP4V-euciMrSLfjOLRYg,11378
15
+ hindsight_api/engine/memory_engine.py,sha256=3vUcZRSUIOYCMf_R_tqDjT1Q3FpAoVSrhG026weJEYQ,127985
16
+ hindsight_api/engine/query_analyzer.py,sha256=K0QCg7tsbqtwC7TR5wt3FPoP8QDuZsX9r0Zljc8nnYo,19733
17
+ hindsight_api/engine/response_models.py,sha256=eRafLz6JTRUsTZadfZxffMYNEz6kho3cF31m8xc4t4c,8783
18
+ hindsight_api/engine/task_backend.py,sha256=ojxMC9PeHdnkWVs2ozeqycjI_1mmpkDa0_Qfej9AHrg,7287
19
+ hindsight_api/engine/utils.py,sha256=VAjpZSbdiwhlE6cDlYfTt_-5hIJ--0xtfixETK0LPSk,6910
20
+ hindsight_api/engine/retain/__init__.py,sha256=L_QuR1YLHsJ7OCmVFNsZe8WDjbsTTHL-wCiUXtw1aUE,1230
21
+ hindsight_api/engine/retain/bank_utils.py,sha256=fvUR7bAlStLJ1z_kbs3AuF2XzPrMtXgHCA1u8uoU5nI,14482
22
+ hindsight_api/engine/retain/chunk_storage.py,sha256=rjmfnllS185tmjJGkMjWZ9q_6hJO4N6Ll9jgPx6f5xo,2081
23
+ hindsight_api/engine/retain/deduplication.py,sha256=9YXgVI_m1Mtz5Cv46ZceCEs0GwpLqTPHrZ-vlWlXk6I,3313
24
+ hindsight_api/engine/retain/embedding_processing.py,sha256=cHTt3rPvDCWBWVPfSeg6bwH8HoXYGmP4bvS21boNONI,1734
25
+ hindsight_api/engine/retain/embedding_utils.py,sha256=Q24h_iw6pRAW2vDWPvauWY1o3bXLzW3eWvSxDALDiE0,1588
26
+ hindsight_api/engine/retain/entity_processing.py,sha256=meHOjsFzdvh1tbe6YlTofhcUs2Y6TcAN3S-0EKOvFP0,2705
27
+ hindsight_api/engine/retain/fact_extraction.py,sha256=D7nnDn7U0UhsAwbo9qahPSpGxiRP-L5tdHT1JAIKM44,45254
28
+ hindsight_api/engine/retain/fact_storage.py,sha256=gRRQf_FCLsj5lUvdlOaxJsS5JosM6IhO_pik8Ur8VFg,5717
29
+ hindsight_api/engine/retain/link_creation.py,sha256=XJx7U3HboJLHtGgt_tHGsCa58lGo2ZyywzMNosrY9Xc,3154
30
+ hindsight_api/engine/retain/link_utils.py,sha256=PAXalIhAPZGcJv8EugcpwNgoWZ2D_ciVU3brHL-m090,26226
31
+ hindsight_api/engine/retain/orchestrator.py,sha256=I-EVH2REQLE3CypvWjcB9iZoJcl6dhXo3QPJMeWUz_4,17524
32
+ hindsight_api/engine/retain/types.py,sha256=AJPYxMy0Fh7zje2TPKXjPnr1QxaU7aVBCvjfCaPqvt8,6218
33
+ hindsight_api/engine/search/__init__.py,sha256=7X6U10bVw0JRWxQdE5RCfVpawDlSUldi1mPoCzY0V0A,363
34
+ hindsight_api/engine/search/fusion.py,sha256=so6LU7kWRR-VJd1Pxlu8idRJ7P2WLCoDwXUnb8jQifo,4309
35
+ hindsight_api/engine/search/observation_utils.py,sha256=SPrDx6M0daJ_zLLkk78GlQIG3EL7DqMKSu_etKerUfU,4331
36
+ hindsight_api/engine/search/reranking.py,sha256=Bk5i5kal5yy4CM8m2uSxAumLPgLeHdncBX6wk4WTmEI,3525
37
+ hindsight_api/engine/search/retrieval.py,sha256=kfQTU34LPLgB1QVcCAv7v2IPhOB2ag68xJ8RzvdSP10,19661
38
+ hindsight_api/engine/search/scoring.py,sha256=feFPalpbIMndp8j2Ab0zvu7fRq3c43Wmzrjw3piQ0eM,5167
39
+ hindsight_api/engine/search/temporal_extraction.py,sha256=5klrZdza3mkgk5A15_m_j4IIfOHMc6fUR9UJuzLa790,1812
40
+ hindsight_api/engine/search/think_utils.py,sha256=OOrMKEpkHvMD0-vmLZPk18s2AISLqPCJO68CyRccQDI,9629
41
+ hindsight_api/engine/search/trace.py,sha256=GT86_LVKMyG2mw6EJzPjafvbqaot6XVy5fZ033pMXG8,11036
42
+ hindsight_api/engine/search/tracer.py,sha256=mcM9qZpj3YFudrBCESwc6YKNAiWIMx1lScXWn5ru-ok,15017
43
+ hindsight_api/engine/search/types.py,sha256=qIeHW_gT7f291vteTZXygAM8oAaPp2dq6uEdvOyOwzs,5488
44
+ hindsight_api/web/__init__.py,sha256=WABqyqiAVFJJWOhKCytkj5Vcb61eAsRib3Ek7IMX6_U,378
45
+ hindsight_api/web/server.py,sha256=oPNJ_z4DO38MdK7Juyh2LdH0ipZ_BQF48cUM-4B_Uw0,5379
46
+ hindsight_api-0.0.13.dist-info/METADATA,sha256=Lr36kApNId06zH-6m9LNNXR4zYLefSBMn2NqCSnMN24,1496
47
+ hindsight_api-0.0.13.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
48
+ hindsight_api-0.0.13.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any