hindsight-api 0.1.4__py3-none-any.whl → 0.1.6__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.
- hindsight_api/__init__.py +10 -9
- hindsight_api/alembic/env.py +5 -8
- hindsight_api/alembic/versions/5a366d414dce_initial_schema.py +266 -180
- hindsight_api/alembic/versions/b7c4d8e9f1a2_add_chunks_table.py +32 -32
- hindsight_api/alembic/versions/c8e5f2a3b4d1_add_retain_params_to_documents.py +11 -11
- hindsight_api/alembic/versions/d9f6a3b4c5e2_rename_bank_to_interactions.py +7 -12
- hindsight_api/alembic/versions/e0a1b2c3d4e5_disposition_to_3_traits.py +23 -15
- hindsight_api/alembic/versions/rename_personality_to_disposition.py +30 -21
- hindsight_api/api/__init__.py +10 -10
- hindsight_api/api/http.py +575 -593
- hindsight_api/api/mcp.py +31 -33
- hindsight_api/banner.py +13 -6
- hindsight_api/config.py +17 -12
- hindsight_api/engine/__init__.py +9 -9
- hindsight_api/engine/cross_encoder.py +23 -27
- hindsight_api/engine/db_utils.py +5 -4
- hindsight_api/engine/embeddings.py +22 -21
- hindsight_api/engine/entity_resolver.py +81 -75
- hindsight_api/engine/llm_wrapper.py +74 -88
- hindsight_api/engine/memory_engine.py +663 -673
- hindsight_api/engine/query_analyzer.py +100 -97
- hindsight_api/engine/response_models.py +105 -106
- hindsight_api/engine/retain/__init__.py +9 -16
- hindsight_api/engine/retain/bank_utils.py +34 -58
- hindsight_api/engine/retain/chunk_storage.py +4 -12
- hindsight_api/engine/retain/deduplication.py +9 -28
- hindsight_api/engine/retain/embedding_processing.py +4 -11
- hindsight_api/engine/retain/embedding_utils.py +3 -4
- hindsight_api/engine/retain/entity_processing.py +7 -17
- hindsight_api/engine/retain/fact_extraction.py +155 -165
- hindsight_api/engine/retain/fact_storage.py +11 -23
- hindsight_api/engine/retain/link_creation.py +11 -39
- hindsight_api/engine/retain/link_utils.py +166 -95
- hindsight_api/engine/retain/observation_regeneration.py +39 -52
- hindsight_api/engine/retain/orchestrator.py +72 -62
- hindsight_api/engine/retain/types.py +49 -43
- hindsight_api/engine/search/__init__.py +15 -1
- hindsight_api/engine/search/fusion.py +6 -15
- hindsight_api/engine/search/graph_retrieval.py +234 -0
- hindsight_api/engine/search/mpfp_retrieval.py +438 -0
- hindsight_api/engine/search/observation_utils.py +9 -16
- hindsight_api/engine/search/reranking.py +4 -7
- hindsight_api/engine/search/retrieval.py +388 -193
- hindsight_api/engine/search/scoring.py +5 -7
- hindsight_api/engine/search/temporal_extraction.py +8 -11
- hindsight_api/engine/search/think_utils.py +115 -39
- hindsight_api/engine/search/trace.py +68 -38
- hindsight_api/engine/search/tracer.py +49 -35
- hindsight_api/engine/search/types.py +22 -16
- hindsight_api/engine/task_backend.py +21 -26
- hindsight_api/engine/utils.py +25 -10
- hindsight_api/main.py +21 -40
- hindsight_api/mcp_local.py +190 -0
- hindsight_api/metrics.py +44 -30
- hindsight_api/migrations.py +10 -8
- hindsight_api/models.py +60 -72
- hindsight_api/pg0.py +64 -337
- hindsight_api/server.py +3 -6
- {hindsight_api-0.1.4.dist-info → hindsight_api-0.1.6.dist-info}/METADATA +6 -5
- hindsight_api-0.1.6.dist-info/RECORD +64 -0
- {hindsight_api-0.1.4.dist-info → hindsight_api-0.1.6.dist-info}/entry_points.txt +1 -0
- hindsight_api-0.1.4.dist-info/RECORD +0 -61
- {hindsight_api-0.1.4.dist-info → hindsight_api-0.1.6.dist-info}/WHEEL +0 -0
hindsight_api/pg0.py
CHANGED
|
@@ -1,407 +1,134 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import json
|
|
3
2
|
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
3
|
|
|
13
|
-
import
|
|
4
|
+
from pg0 import Pg0
|
|
14
5
|
|
|
15
6
|
logger = logging.getLogger(__name__)
|
|
16
7
|
|
|
17
|
-
# pg0 configuration
|
|
18
|
-
BINARY_NAME = "pg0"
|
|
19
|
-
DEFAULT_PORT = 5555
|
|
20
8
|
DEFAULT_USERNAME = "hindsight"
|
|
21
9
|
DEFAULT_PASSWORD = "hindsight"
|
|
22
10
|
DEFAULT_DATABASE = "hindsight"
|
|
23
11
|
|
|
24
12
|
|
|
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
13
|
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
|
-
"""
|
|
14
|
+
"""Manages an embedded PostgreSQL server instance using pg0-embedded."""
|
|
108
15
|
|
|
109
16
|
def __init__(
|
|
110
17
|
self,
|
|
111
|
-
|
|
112
|
-
port: int = DEFAULT_PORT,
|
|
18
|
+
port: int | None = None,
|
|
113
19
|
username: str = DEFAULT_USERNAME,
|
|
114
20
|
password: str = DEFAULT_PASSWORD,
|
|
115
21
|
database: str = DEFAULT_DATABASE,
|
|
116
22
|
name: str = "hindsight",
|
|
23
|
+
**kwargs,
|
|
117
24
|
):
|
|
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
|
|
25
|
+
self.port = port # None means pg0 will auto-assign
|
|
131
26
|
self.username = username
|
|
132
27
|
self.password = password
|
|
133
28
|
self.database = database
|
|
134
29
|
self.name = name
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
self.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
"""
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
instructing the user to install pg0 manually.
|
|
158
|
-
"""
|
|
159
|
-
if self.is_installed():
|
|
160
|
-
logger.debug(f"pg0 found at {self._binary_path}")
|
|
161
|
-
return
|
|
162
|
-
|
|
163
|
-
raise RuntimeError(
|
|
164
|
-
"pg0 is not installed. Please install it manually:\n"
|
|
165
|
-
" curl -fsSL https://github.com/vectorize-io/pg0/releases/latest/download/pg0-linux-amd64 -o ~/.local/bin/pg0 && chmod +x ~/.local/bin/pg0\n"
|
|
166
|
-
"Or visit: https://github.com/vectorize-io/pg0/releases"
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
def _run_command(self, *args: str, capture_output: bool = True) -> subprocess.CompletedProcess:
|
|
170
|
-
"""Run a pg0 command synchronously."""
|
|
171
|
-
cmd = [str(self.binary_path), *args]
|
|
172
|
-
return subprocess.run(cmd, capture_output=capture_output, text=True)
|
|
173
|
-
|
|
174
|
-
async def _run_command_async(self, *args: str, timeout: int = 120) -> tuple[int, str, str]:
|
|
175
|
-
"""Run a pg0 command asynchronously."""
|
|
176
|
-
cmd = [str(self.binary_path), *args]
|
|
177
|
-
|
|
178
|
-
def run_sync():
|
|
179
|
-
try:
|
|
180
|
-
result = subprocess.run(
|
|
181
|
-
cmd,
|
|
182
|
-
stdin=subprocess.DEVNULL,
|
|
183
|
-
stdout=subprocess.PIPE,
|
|
184
|
-
stderr=subprocess.PIPE,
|
|
185
|
-
text=True,
|
|
186
|
-
timeout=timeout,
|
|
187
|
-
)
|
|
188
|
-
return result.returncode, result.stdout, result.stderr
|
|
189
|
-
except subprocess.TimeoutExpired:
|
|
190
|
-
return 1, "", "Command timed out"
|
|
191
|
-
|
|
192
|
-
loop = asyncio.get_event_loop()
|
|
193
|
-
return await loop.run_in_executor(None, run_sync)
|
|
194
|
-
|
|
195
|
-
def _extract_uri_from_output(self, output: str) -> Optional[str]:
|
|
196
|
-
"""Extract the PostgreSQL URI from pg0 start output."""
|
|
197
|
-
match = re.search(r"Connection URI:\s*(postgresql://[^\s]+)", output)
|
|
198
|
-
if match:
|
|
199
|
-
return match.group(1)
|
|
200
|
-
return None
|
|
201
|
-
|
|
202
|
-
async def _get_version(self) -> str:
|
|
203
|
-
"""Get the pg0 version."""
|
|
204
|
-
returncode, stdout, stderr = await self._run_command_async("--version", timeout=10)
|
|
205
|
-
if returncode == 0 and stdout:
|
|
206
|
-
return stdout.strip()
|
|
207
|
-
return "unknown"
|
|
208
|
-
|
|
209
|
-
async def start(self, max_retries: int = 3, retry_delay: float = 2.0) -> str:
|
|
210
|
-
"""
|
|
211
|
-
Start the PostgreSQL server with retry logic.
|
|
212
|
-
|
|
213
|
-
Args:
|
|
214
|
-
max_retries: Maximum number of start attempts (default: 3)
|
|
215
|
-
retry_delay: Initial delay between retries in seconds (default: 2.0)
|
|
216
|
-
|
|
217
|
-
Returns:
|
|
218
|
-
The connection URI for the started server.
|
|
219
|
-
|
|
220
|
-
Raises:
|
|
221
|
-
RuntimeError: If the server fails to start after all retries.
|
|
222
|
-
"""
|
|
223
|
-
if not self.is_installed():
|
|
224
|
-
raise RuntimeError("pg0 is not installed. Call ensure_installed() first.")
|
|
225
|
-
|
|
226
|
-
# Log pg0 version
|
|
227
|
-
version = await self._get_version()
|
|
228
|
-
logger.info(f"Starting embedded PostgreSQL with pg0 {version} (name: {self.name}, port: {self.port})...")
|
|
229
|
-
|
|
30
|
+
self._pg0: Pg0 | None = None
|
|
31
|
+
|
|
32
|
+
def _get_pg0(self) -> Pg0:
|
|
33
|
+
if self._pg0 is None:
|
|
34
|
+
kwargs = {
|
|
35
|
+
"name": self.name,
|
|
36
|
+
"username": self.username,
|
|
37
|
+
"password": self.password,
|
|
38
|
+
"database": self.database,
|
|
39
|
+
}
|
|
40
|
+
# Only set port if explicitly specified
|
|
41
|
+
if self.port is not None:
|
|
42
|
+
kwargs["port"] = self.port
|
|
43
|
+
self._pg0 = Pg0(**kwargs)
|
|
44
|
+
return self._pg0
|
|
45
|
+
|
|
46
|
+
async def start(self, max_retries: int = 5, retry_delay: float = 4.0) -> str:
|
|
47
|
+
"""Start the PostgreSQL server with retry logic."""
|
|
48
|
+
port_info = f"port={self.port}" if self.port else "port=auto"
|
|
49
|
+
logger.info(f"Starting embedded PostgreSQL (name={self.name}, {port_info})...")
|
|
50
|
+
|
|
51
|
+
pg0 = self._get_pg0()
|
|
230
52
|
last_error = None
|
|
231
|
-
for attempt in range(1, max_retries + 1):
|
|
232
|
-
returncode, stdout, stderr = await self._run_command_async(
|
|
233
|
-
"start",
|
|
234
|
-
"--name", self.name,
|
|
235
|
-
"--port", str(self.port),
|
|
236
|
-
"--username", self.username,
|
|
237
|
-
"--password", self.password,
|
|
238
|
-
"--database", self.database,
|
|
239
|
-
timeout=300,
|
|
240
|
-
)
|
|
241
53
|
|
|
242
|
-
|
|
243
|
-
uri = self._extract_uri_from_output(stdout)
|
|
244
|
-
if uri:
|
|
245
|
-
logger.info(f"PostgreSQL started on port {self.port}")
|
|
246
|
-
return uri
|
|
247
|
-
|
|
248
|
-
# Check if pg0 info can find the running instance
|
|
54
|
+
for attempt in range(1, max_retries + 1):
|
|
249
55
|
try:
|
|
250
|
-
|
|
251
|
-
|
|
56
|
+
loop = asyncio.get_event_loop()
|
|
57
|
+
info = await loop.run_in_executor(None, pg0.start)
|
|
58
|
+
# Get URI from pg0 (includes auto-assigned port)
|
|
59
|
+
uri = info.uri
|
|
60
|
+
logger.info(f"PostgreSQL started: {uri}")
|
|
252
61
|
return uri
|
|
253
|
-
except
|
|
254
|
-
|
|
62
|
+
except Exception as e:
|
|
63
|
+
last_error = str(e)
|
|
64
|
+
if attempt < max_retries:
|
|
65
|
+
delay = retry_delay * (2 ** (attempt - 1))
|
|
66
|
+
logger.debug(f"pg0 start attempt {attempt}/{max_retries} failed: {last_error}")
|
|
67
|
+
logger.debug(f"Retrying in {delay:.1f}s...")
|
|
68
|
+
await asyncio.sleep(delay)
|
|
69
|
+
else:
|
|
70
|
+
logger.debug(f"pg0 start attempt {attempt}/{max_retries} failed: {last_error}")
|
|
255
71
|
|
|
256
|
-
# Start failed, log and retry
|
|
257
|
-
last_error = stderr or f"pg0 start returned exit code {returncode}"
|
|
258
|
-
if attempt < max_retries:
|
|
259
|
-
delay = retry_delay * (2 ** (attempt - 1))
|
|
260
|
-
logger.debug(f"pg0 start attempt {attempt}/{max_retries} failed: {last_error.strip()}")
|
|
261
|
-
logger.debug(f"Retrying in {delay:.1f}s...")
|
|
262
|
-
await asyncio.sleep(delay)
|
|
263
|
-
else:
|
|
264
|
-
logger.debug(f"pg0 start attempt {attempt}/{max_retries} failed: {last_error.strip()}")
|
|
265
|
-
|
|
266
|
-
# All retries exhausted - fail
|
|
267
72
|
raise RuntimeError(
|
|
268
|
-
f"Failed to start embedded PostgreSQL after {max_retries} attempts. "
|
|
269
|
-
f"Last error: {last_error.strip() if last_error else 'unknown'}"
|
|
73
|
+
f"Failed to start embedded PostgreSQL after {max_retries} attempts. Last error: {last_error}"
|
|
270
74
|
)
|
|
271
75
|
|
|
272
76
|
async def stop(self) -> None:
|
|
273
77
|
"""Stop the PostgreSQL server."""
|
|
274
|
-
|
|
275
|
-
return
|
|
276
|
-
|
|
78
|
+
pg0 = self._get_pg0()
|
|
277
79
|
logger.info(f"Stopping embedded PostgreSQL (name: {self.name})...")
|
|
278
80
|
|
|
279
|
-
returncode, stdout, stderr = await self._run_command_async("stop", "--name", self.name)
|
|
280
|
-
|
|
281
|
-
if returncode != 0:
|
|
282
|
-
if "not running" in stderr.lower():
|
|
283
|
-
return
|
|
284
|
-
raise RuntimeError(f"Failed to stop PostgreSQL: {stderr}")
|
|
285
|
-
|
|
286
|
-
logger.info("Embedded PostgreSQL stopped")
|
|
287
|
-
|
|
288
|
-
async def _get_info(self) -> dict:
|
|
289
|
-
"""Get info from pg0 using the `info -o json` command."""
|
|
290
|
-
if not self.is_installed():
|
|
291
|
-
raise RuntimeError("pg0 is not installed.")
|
|
292
|
-
|
|
293
|
-
returncode, stdout, stderr = await self._run_command_async(
|
|
294
|
-
"info", "--name", self.name, "-o", "json"
|
|
295
|
-
)
|
|
296
|
-
|
|
297
|
-
if returncode != 0:
|
|
298
|
-
raise RuntimeError(f"Failed to get PostgreSQL info: {stderr}")
|
|
299
|
-
|
|
300
81
|
try:
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
82
|
+
loop = asyncio.get_event_loop()
|
|
83
|
+
await loop.run_in_executor(None, pg0.stop)
|
|
84
|
+
logger.info("Embedded PostgreSQL stopped")
|
|
85
|
+
except Exception as e:
|
|
86
|
+
if "not running" in str(e).lower():
|
|
87
|
+
return
|
|
88
|
+
raise RuntimeError(f"Failed to stop PostgreSQL: {e}")
|
|
304
89
|
|
|
305
90
|
async def get_uri(self) -> str:
|
|
306
91
|
"""Get the connection URI for the PostgreSQL server."""
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
return uri
|
|
312
|
-
|
|
313
|
-
async def status(self) -> dict:
|
|
314
|
-
"""Get the status of the PostgreSQL server."""
|
|
315
|
-
if not self.is_installed():
|
|
316
|
-
return {"installed": False, "running": False}
|
|
317
|
-
|
|
318
|
-
try:
|
|
319
|
-
info = await self._get_info()
|
|
320
|
-
return {
|
|
321
|
-
"installed": True,
|
|
322
|
-
"running": info.get("running", False),
|
|
323
|
-
"uri": info.get("uri"),
|
|
324
|
-
}
|
|
325
|
-
except RuntimeError:
|
|
326
|
-
return {"installed": True, "running": False}
|
|
92
|
+
pg0 = self._get_pg0()
|
|
93
|
+
loop = asyncio.get_event_loop()
|
|
94
|
+
info = await loop.run_in_executor(None, pg0.info)
|
|
95
|
+
return info.uri
|
|
327
96
|
|
|
328
97
|
async def is_running(self) -> bool:
|
|
329
98
|
"""Check if the PostgreSQL server is currently running."""
|
|
330
|
-
if not self.is_installed():
|
|
331
|
-
return False
|
|
332
99
|
try:
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
100
|
+
pg0 = self._get_pg0()
|
|
101
|
+
loop = asyncio.get_event_loop()
|
|
102
|
+
info = await loop.run_in_executor(None, pg0.info)
|
|
103
|
+
return info is not None and info.running
|
|
104
|
+
except Exception:
|
|
336
105
|
return False
|
|
337
106
|
|
|
338
107
|
async def ensure_running(self) -> str:
|
|
339
|
-
"""
|
|
340
|
-
Ensure the PostgreSQL server is running.
|
|
341
|
-
|
|
342
|
-
Installs if needed, starts if not running.
|
|
343
|
-
|
|
344
|
-
Returns:
|
|
345
|
-
The connection URI.
|
|
346
|
-
"""
|
|
347
|
-
await self.ensure_installed()
|
|
348
|
-
|
|
108
|
+
"""Ensure the PostgreSQL server is running, starting it if needed."""
|
|
349
109
|
if await self.is_running():
|
|
350
110
|
return await self.get_uri()
|
|
351
|
-
|
|
352
111
|
return await self.start()
|
|
353
112
|
|
|
354
|
-
def uninstall(self) -> None:
|
|
355
|
-
"""Remove the pg0 binary (only if we installed it)."""
|
|
356
|
-
default_path = Path.home() / ".hindsight" / "bin" / "pg0"
|
|
357
|
-
if default_path.exists():
|
|
358
|
-
default_path.unlink()
|
|
359
|
-
logger.info(f"Removed {default_path}")
|
|
360
113
|
|
|
361
|
-
|
|
362
|
-
"""Remove all PostgreSQL data (destructive!)."""
|
|
363
|
-
result = self._run_command("drop", "--name", self.name, "--force")
|
|
364
|
-
if result.returncode == 0:
|
|
365
|
-
logger.info(f"Dropped pg0 instance {self.name}")
|
|
366
|
-
else:
|
|
367
|
-
logger.warning(f"Failed to drop pg0 instance {self.name}: {result.stderr}")
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
# Convenience functions
|
|
371
|
-
|
|
372
|
-
_default_instance: Optional[EmbeddedPostgres] = None
|
|
114
|
+
_default_instance: EmbeddedPostgres | None = None
|
|
373
115
|
|
|
374
116
|
|
|
375
117
|
def get_embedded_postgres() -> EmbeddedPostgres:
|
|
376
118
|
"""Get or create the default EmbeddedPostgres instance."""
|
|
377
119
|
global _default_instance
|
|
378
|
-
|
|
379
120
|
if _default_instance is None:
|
|
380
121
|
_default_instance = EmbeddedPostgres()
|
|
381
|
-
|
|
382
122
|
return _default_instance
|
|
383
123
|
|
|
384
124
|
|
|
385
125
|
async def start_embedded_postgres() -> str:
|
|
386
|
-
"""
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
Downloads, installs, and starts PostgreSQL in one call.
|
|
390
|
-
|
|
391
|
-
Returns:
|
|
392
|
-
Connection URI string
|
|
393
|
-
|
|
394
|
-
Example:
|
|
395
|
-
db_url = await start_embedded_postgres()
|
|
396
|
-
conn = await asyncpg.connect(db_url)
|
|
397
|
-
"""
|
|
398
|
-
pg = get_embedded_postgres()
|
|
399
|
-
return await pg.ensure_running()
|
|
126
|
+
"""Quick start function for embedded PostgreSQL."""
|
|
127
|
+
return await get_embedded_postgres().ensure_running()
|
|
400
128
|
|
|
401
129
|
|
|
402
130
|
async def stop_embedded_postgres() -> None:
|
|
403
131
|
"""Stop the default embedded PostgreSQL instance."""
|
|
404
132
|
global _default_instance
|
|
405
|
-
|
|
406
133
|
if _default_instance:
|
|
407
134
|
await _default_instance.stop()
|
hindsight_api/server.py
CHANGED
|
@@ -6,6 +6,7 @@ This module provides the ASGI app for uvicorn import string usage:
|
|
|
6
6
|
|
|
7
7
|
For CLI usage, use the hindsight-api command instead.
|
|
8
8
|
"""
|
|
9
|
+
|
|
9
10
|
import os
|
|
10
11
|
import warnings
|
|
11
12
|
|
|
@@ -29,15 +30,11 @@ config.configure_logging()
|
|
|
29
30
|
_memory = MemoryEngine()
|
|
30
31
|
|
|
31
32
|
# Create unified app with both HTTP and optionally MCP
|
|
32
|
-
app = create_app(
|
|
33
|
-
memory=_memory,
|
|
34
|
-
http_api_enabled=True,
|
|
35
|
-
mcp_api_enabled=config.mcp_enabled,
|
|
36
|
-
mcp_mount_path="/mcp"
|
|
37
|
-
)
|
|
33
|
+
app = create_app(memory=_memory, http_api_enabled=True, mcp_api_enabled=config.mcp_enabled, mcp_mount_path="/mcp")
|
|
38
34
|
|
|
39
35
|
|
|
40
36
|
if __name__ == "__main__":
|
|
41
37
|
# When run directly, delegate to the CLI
|
|
42
38
|
from hindsight_api.main import main
|
|
39
|
+
|
|
43
40
|
main()
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hindsight-api
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Temporal + Semantic + Entity Memory System for AI agents using PostgreSQL
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: alembic>=1.17.1
|
|
7
7
|
Requires-Dist: asyncpg>=0.29.0
|
|
8
8
|
Requires-Dist: dateparser>=1.2.2
|
|
9
9
|
Requires-Dist: fastapi[standard]>=0.120.3
|
|
10
|
-
Requires-Dist: fastmcp>=2.
|
|
10
|
+
Requires-Dist: fastmcp>=2.3.0
|
|
11
11
|
Requires-Dist: google-genai>=1.0.0
|
|
12
12
|
Requires-Dist: greenlet>=3.2.4
|
|
13
13
|
Requires-Dist: httpx>=0.27.0
|
|
@@ -17,17 +17,18 @@ Requires-Dist: opentelemetry-api>=1.20.0
|
|
|
17
17
|
Requires-Dist: opentelemetry-exporter-prometheus>=0.41b0
|
|
18
18
|
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.41b0
|
|
19
19
|
Requires-Dist: opentelemetry-sdk>=1.20.0
|
|
20
|
+
Requires-Dist: pg0-embedded>=0.11.0
|
|
20
21
|
Requires-Dist: pgvector>=0.4.1
|
|
21
22
|
Requires-Dist: psycopg2-binary>=2.9.11
|
|
22
23
|
Requires-Dist: pydantic>=2.0.0
|
|
23
24
|
Requires-Dist: python-dateutil>=2.8.0
|
|
24
25
|
Requires-Dist: python-dotenv>=1.0.0
|
|
25
26
|
Requires-Dist: rich>=13.0.0
|
|
26
|
-
Requires-Dist: sentence-transformers
|
|
27
|
+
Requires-Dist: sentence-transformers<3.3.0,>=3.0.0
|
|
27
28
|
Requires-Dist: sqlalchemy>=2.0.44
|
|
28
29
|
Requires-Dist: tiktoken>=0.12.0
|
|
29
|
-
Requires-Dist: torch
|
|
30
|
-
Requires-Dist: transformers
|
|
30
|
+
Requires-Dist: torch<2.6.0,>=2.0.0
|
|
31
|
+
Requires-Dist: transformers<4.46.0,>=4.30.0
|
|
31
32
|
Requires-Dist: uvicorn>=0.38.0
|
|
32
33
|
Requires-Dist: wsproto>=1.0.0
|
|
33
34
|
Provides-Extra: test
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
hindsight_api/__init__.py,sha256=paLZxYov7BBgbOSl1RTNtFYvqHXlZTzTEDygR3kAFLc,1140
|
|
2
|
+
hindsight_api/banner.py,sha256=BXn-jhkXe4xi-YV4JeuaVvjYhTMs96O43XoOMv4Cd28,4591
|
|
3
|
+
hindsight_api/config.py,sha256=qxdxmeQWJozKIjMUe-Aza9hGIgT34HxMnXs0gh3uXXs,5538
|
|
4
|
+
hindsight_api/main.py,sha256=TO5zLzXpu-CjGfvLHdZFEHerrJbNns2cQGlc4gofaPI,5970
|
|
5
|
+
hindsight_api/mcp_local.py,sha256=ozKMv-PrehzZTLawscP-TDePwqiOH962HCQzTa9x8AY,6646
|
|
6
|
+
hindsight_api/metrics.py,sha256=sQI5MhC2xj9ONZ6Hdjf6r6r3NbYYd3ExyVOn1Uky49A,7239
|
|
7
|
+
hindsight_api/migrations.py,sha256=bN9ejR3cn7EAP3LFkpAjnWsUm9kykgzbzqeCB9HMPvA,7315
|
|
8
|
+
hindsight_api/models.py,sha256=vLFkxykmK8KOoN_sQz4SsiJS6vqOjFIv_82BuKg8qD8,12329
|
|
9
|
+
hindsight_api/pg0.py,sha256=y8EE3v1q2OUJbsSHl-hG_sPZEIWQrgkxrGcf-kuEECE,4624
|
|
10
|
+
hindsight_api/server.py,sha256=OrSd0G-79U07EXFc838c1vzUL-1O6wuxTMqUmMINpGY,1247
|
|
11
|
+
hindsight_api/alembic/README,sha256=MVlc9TYmr57RbhXET6QxgyCcwWP7w-vLkEsirENqiIQ,38
|
|
12
|
+
hindsight_api/alembic/env.py,sha256=x5MoBxdfRunSve1zARCOZ8KZDg2M3-NYrjJsR1hePg4,4753
|
|
13
|
+
hindsight_api/alembic/script.py.mako,sha256=04kgeBtNMa4cCnG8CfQcKt6P6rnloIfj8wy0u_DBydM,704
|
|
14
|
+
hindsight_api/alembic/versions/5a366d414dce_initial_schema.py,sha256=g3G7fV70Z10PZxwTrTmR34OAlEZjQTLJKr-Ol54JqrQ,17665
|
|
15
|
+
hindsight_api/alembic/versions/b7c4d8e9f1a2_add_chunks_table.py,sha256=MaHFU4JczUIFLeUMBTKIV3ocuclil55N9fPPim-HRfk,2599
|
|
16
|
+
hindsight_api/alembic/versions/c8e5f2a3b4d1_add_retain_params_to_documents.py,sha256=ChqkHANauZb4-nBt2uepoZN3q0vRzN6aRsWTGueULiA,1146
|
|
17
|
+
hindsight_api/alembic/versions/d9f6a3b4c5e2_rename_bank_to_interactions.py,sha256=28KBE6mAAj2PEXm0iALJOiMcjn4eTx_X5nUPaJ1aRYk,1480
|
|
18
|
+
hindsight_api/alembic/versions/e0a1b2c3d4e5_disposition_to_3_traits.py,sha256=LLj0Tl6Kjw5uZPo7lCcG2IH8lFSPEmc9jG_riMF3Bj0,2370
|
|
19
|
+
hindsight_api/alembic/versions/rename_personality_to_disposition.py,sha256=gpMSG8hdvqn9__lGgS0EE2de1nki1YAaCEI3pDdJRzA,2281
|
|
20
|
+
hindsight_api/api/__init__.py,sha256=zoDWA86ttx-UriC35UIgdPswIrau7GuMWTN63wYsUdM,2916
|
|
21
|
+
hindsight_api/api/http.py,sha256=eYVnsQwx5LW3MvdcAQjiLw6WwBobw5aGeYSPYLIFllE,70922
|
|
22
|
+
hindsight_api/api/mcp.py,sha256=I9C_jUz6d7efMstBfOyoqnnsB94JU6c2mMk6eQ_KDz0,7462
|
|
23
|
+
hindsight_api/engine/__init__.py,sha256=z6srTnyvGQM0eya7E8r2yagTAc4C7IhHauUK8NM-gW8,1406
|
|
24
|
+
hindsight_api/engine/cross_encoder.py,sha256=5WmUx9yfJdIwZ0nA218O-mMKQJ7EKaPOtwhMiDbG8KQ,10483
|
|
25
|
+
hindsight_api/engine/db_utils.py,sha256=0T5tL2SZ49JQihfyZYlTDThIfocKzkr1OpxQpJzPCGE,2687
|
|
26
|
+
hindsight_api/engine/embeddings.py,sha256=IEdP5-p6oTJRRKV2JzUEojByJGShUEmkInCyA9wM8tg,10219
|
|
27
|
+
hindsight_api/engine/entity_resolver.py,sha256=XHAViwIaEXMUhp9Eifvx0b3MZ4QwiFvH_k_ZHTwczxE,23151
|
|
28
|
+
hindsight_api/engine/llm_wrapper.py,sha256=-EQnDoMr4BHTcp24dGNiOnYVGA5uGYmGFT5qQHhxGwY,21134
|
|
29
|
+
hindsight_api/engine/memory_engine.py,sha256=l6NeQ_xHj8YfOs6Jpr6k6nYCUFRpZYIYZgf8H6JB7U8,135992
|
|
30
|
+
hindsight_api/engine/query_analyzer.py,sha256=DKFxmyyVVc59zwKbbGx4D22UVp6TxmD7jAa7cg9FGSU,19641
|
|
31
|
+
hindsight_api/engine/response_models.py,sha256=QeESHC7oh84SYPDrR6FqHjiGBZnTAzo61IDB-qwVTSY,8737
|
|
32
|
+
hindsight_api/engine/task_backend.py,sha256=XT0C-QFWfdcOHJjplkoarlnwvz-kCF2l6wAGW1dTJkw,7144
|
|
33
|
+
hindsight_api/engine/utils.py,sha256=TwuipFRvN0Pu196JLakzQ71E3GAwySc5q6pByC81Ak4,6991
|
|
34
|
+
hindsight_api/engine/retain/__init__.py,sha256=t6q3-_kf4iYTl9j2PVB6laqMSs6UuPeXBSYMW6HT1sA,1152
|
|
35
|
+
hindsight_api/engine/retain/bank_utils.py,sha256=-Q_GW_F1rmT6Twxgk7aLPmfintLp6TQhC5xT0i5hZzg,13970
|
|
36
|
+
hindsight_api/engine/retain/chunk_storage.py,sha256=yIofSL6RwMOIBR_xo1sTOUdkYQoRZBfjdqYuH-dj1EY,2012
|
|
37
|
+
hindsight_api/engine/retain/deduplication.py,sha256=kqs7I7eIc_ppvgAF9GlzL6fSGuEEzrgw17-7NdyUDis,3099
|
|
38
|
+
hindsight_api/engine/retain/embedding_processing.py,sha256=R35oyKYIKjuqC-yZl5Ru56F8xRe0N6KW_9p5PZ9CBi0,1649
|
|
39
|
+
hindsight_api/engine/retain/embedding_utils.py,sha256=uulXIBiA7XNsj16K1VGawR3s5jV-hsAmvmoCi-IodpU,1565
|
|
40
|
+
hindsight_api/engine/retain/entity_processing.py,sha256=5EYzyH_JjbhYQ0zQ8gX6xs0wCH6vmxMYUe6_qVJdvQA,2547
|
|
41
|
+
hindsight_api/engine/retain/fact_extraction.py,sha256=E9AswSrqx3X74gj5-qstbm2wqPv4kUMddkdn5yExKvI,50166
|
|
42
|
+
hindsight_api/engine/retain/fact_storage.py,sha256=SmWbdNTrOJW6MOHGOQ094f5DJSqasYp6yXGuxjh4_IA,5513
|
|
43
|
+
hindsight_api/engine/retain/link_creation.py,sha256=KP2kGU2VCymJptgw0hjaSdsjvncBgNp3P_A4OB_qx-w,3082
|
|
44
|
+
hindsight_api/engine/retain/link_utils.py,sha256=sB4aI3Ai7ukm1yQ6C_sotZ1inljjtDM8I9kSr8-a12o,32697
|
|
45
|
+
hindsight_api/engine/retain/observation_regeneration.py,sha256=HdKiVakeAEfBBUpKYYn2Rbb9jrx4FBhICEMZJ-sFQWU,7960
|
|
46
|
+
hindsight_api/engine/retain/orchestrator.py,sha256=obOpfzpAAg6PfTxR45RnKPvXnndNfUlmShkImj1cYOA,17418
|
|
47
|
+
hindsight_api/engine/retain/types.py,sha256=sez-Rq5nNNUnu6Z04QrmnR-CbrkXeQ1myAXHj9X79Pw,6379
|
|
48
|
+
hindsight_api/engine/search/__init__.py,sha256=YPz_4g7IOabx078Xwg3RBfbOpJ649NRwNfe0gTI9P1U,802
|
|
49
|
+
hindsight_api/engine/search/fusion.py,sha256=cY81BH9U5RyWrPXbQnrDBghtelDMckZWCke9aqMyNnQ,4220
|
|
50
|
+
hindsight_api/engine/search/graph_retrieval.py,sha256=AiC2oSRuZBdD5MmtIk0xSG7CxI5E6uQZe2-IbPqvJQw,8544
|
|
51
|
+
hindsight_api/engine/search/mpfp_retrieval.py,sha256=_OJUxgOYw169OjIxfOjpowg1gstXvVC0VhmJaClBJz8,13849
|
|
52
|
+
hindsight_api/engine/search/observation_utils.py,sha256=rlvGA4oFomMZNCZiJvPIQ0iwGaq9XqhRM530unqziCE,4243
|
|
53
|
+
hindsight_api/engine/search/reranking.py,sha256=RZSKe3JDkLfEdTAdgbS-xZka6Jq4mmTBPDXBpyH73zA,3278
|
|
54
|
+
hindsight_api/engine/search/retrieval.py,sha256=vs-kX5U5Vq4VV6lKq5Aoq1TzlJTtw5vDfnU4gPJ49Aw,25190
|
|
55
|
+
hindsight_api/engine/search/scoring.py,sha256=7jbBtdnow7JU0d8xdW-ZqYvP4s-TYX2tqPhu2DiqHUI,5132
|
|
56
|
+
hindsight_api/engine/search/temporal_extraction.py,sha256=j7hPqpx2jMdR2BqgFrL-rrV2Hzq8HV24MtjYLJqVl2U,1732
|
|
57
|
+
hindsight_api/engine/search/think_utils.py,sha256=rTRyoefRkZc65gcPQtffKiqHinpi7rrRD3m6i57fxNY,13900
|
|
58
|
+
hindsight_api/engine/search/trace.py,sha256=UTCmNRfAvIvDFGm5ifkuUk6JOKYrLlA_rPA72Zz_DfI,11217
|
|
59
|
+
hindsight_api/engine/search/tracer.py,sha256=6OFlkRy_41gr2kgJZ1cmxnerUO069wPfnmiQrMvkOpg,15459
|
|
60
|
+
hindsight_api/engine/search/types.py,sha256=2cK-5oynPTWc7UxnA7TFnwzNkcujCfOUvVf5VCk_srM,5594
|
|
61
|
+
hindsight_api-0.1.6.dist-info/METADATA,sha256=5vGOGWT1fI8hafqlmoIbTtBA8nARhZGBGvO5dxUqfMU,1524
|
|
62
|
+
hindsight_api-0.1.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
63
|
+
hindsight_api-0.1.6.dist-info/entry_points.txt,sha256=vqZv5WLHbSx8vyec5RtMlUqtE_ul7DTgEVODSmou6Og,109
|
|
64
|
+
hindsight_api-0.1.6.dist-info/RECORD,,
|