hindsight-api 0.0.15__py3-none-any.whl → 0.0.17__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/api/__init__.py +6 -7
- hindsight_api/api/mcp.py +137 -80
- hindsight_api/cli.py +128 -0
- hindsight_api/web/server.py +1 -35
- {hindsight_api-0.0.15.dist-info → hindsight_api-0.0.17.dist-info}/METADATA +1 -1
- {hindsight_api-0.0.15.dist-info → hindsight_api-0.0.17.dist-info}/RECORD +8 -6
- hindsight_api-0.0.17.dist-info/entry_points.txt +2 -0
- {hindsight_api-0.0.15.dist-info → hindsight_api-0.0.17.dist-info}/WHEEL +0 -0
hindsight_api/api/__init__.py
CHANGED
|
@@ -62,14 +62,13 @@ def create_app(
|
|
|
62
62
|
# Mount MCP server if enabled
|
|
63
63
|
if mcp_api_enabled:
|
|
64
64
|
try:
|
|
65
|
-
from .mcp import
|
|
65
|
+
from .mcp import create_mcp_app
|
|
66
66
|
|
|
67
|
-
# Create MCP
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
logger.info(f"MCP server enabled at {mcp_mount_path}")
|
|
67
|
+
# Create MCP app with dynamic bank_id support
|
|
68
|
+
# Supports: /mcp/{bank_id}/sse (bank-specific SSE endpoint)
|
|
69
|
+
mcp_app = create_mcp_app(memory=memory)
|
|
70
|
+
app.mount(mcp_mount_path, mcp_app)
|
|
71
|
+
logger.info(f"MCP server enabled at {mcp_mount_path}/{{bank_id}}/sse")
|
|
73
72
|
except ImportError as e:
|
|
74
73
|
logger.error(f"MCP server requested but dependencies not available: {e}")
|
|
75
74
|
logger.error("Install with: pip install hindsight-api[mcp]")
|
hindsight_api/api/mcp.py
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
|
+
from contextvars import ContextVar
|
|
7
|
+
from typing import Optional
|
|
6
8
|
|
|
7
9
|
from fastmcp import FastMCP
|
|
8
10
|
from hindsight_api import MemoryEngine
|
|
@@ -17,6 +19,14 @@ logging.basicConfig(
|
|
|
17
19
|
)
|
|
18
20
|
logger = logging.getLogger(__name__)
|
|
19
21
|
|
|
22
|
+
# Context variable to hold the current bank_id from the URL path
|
|
23
|
+
_current_bank_id: ContextVar[Optional[str]] = ContextVar("current_bank_id", default=None)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_current_bank_id() -> Optional[str]:
|
|
27
|
+
"""Get the current bank_id from context (set from URL path)."""
|
|
28
|
+
return _current_bank_id.get()
|
|
29
|
+
|
|
20
30
|
|
|
21
31
|
def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
|
22
32
|
"""
|
|
@@ -28,125 +38,71 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
|
|
28
38
|
Returns:
|
|
29
39
|
Configured FastMCP server instance
|
|
30
40
|
"""
|
|
31
|
-
# Create FastMCP server
|
|
32
41
|
mcp = FastMCP("hindsight-mcp-server")
|
|
33
42
|
|
|
34
43
|
@mcp.tool()
|
|
35
|
-
async def
|
|
44
|
+
async def retain(content: str, context: str = "general") -> str:
|
|
36
45
|
"""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
**⚠️ PER-USER TOOL - REQUIRES USER IDENTIFICATION:**
|
|
40
|
-
- This tool is STRICTLY per-user. Each user MUST have a unique `bank_id`.
|
|
41
|
-
- ONLY use this tool if you have a valid user identifier (user ID, email, session ID, etc.) to map to `bank_id`.
|
|
42
|
-
- DO NOT use this tool if you cannot identify the specific user.
|
|
43
|
-
- DO NOT share memories between different users - each user's memories are isolated by their `bank_id`.
|
|
44
|
-
- If you don't have a user identifier, DO NOT use this tool at all.
|
|
46
|
+
Store important information to long-term memory.
|
|
45
47
|
|
|
46
48
|
Use this tool PROACTIVELY whenever the user shares:
|
|
47
|
-
- Personal facts, preferences, or interests
|
|
48
|
-
- Important events or milestones
|
|
49
|
-
- User history, experiences, or background
|
|
50
|
-
- Decisions, opinions, or stated preferences
|
|
51
|
-
- Goals, plans, or future intentions
|
|
52
|
-
- Relationships or people mentioned
|
|
49
|
+
- Personal facts, preferences, or interests
|
|
50
|
+
- Important events or milestones
|
|
51
|
+
- User history, experiences, or background
|
|
52
|
+
- Decisions, opinions, or stated preferences
|
|
53
|
+
- Goals, plans, or future intentions
|
|
54
|
+
- Relationships or people mentioned
|
|
53
55
|
- Work context, projects, or responsibilities
|
|
54
|
-
- Any other information the user would want remembered for future conversations
|
|
55
|
-
|
|
56
|
-
**When to use**: Immediately after user shares personal information. Don't ask permission - just store it naturally.
|
|
57
|
-
|
|
58
|
-
**Context guidelines**: Use descriptive contexts like "personal_preferences", "work_history", "family", "hobbies",
|
|
59
|
-
"career_goals", "project_details", etc. This helps organize and retrieve related memories later.
|
|
60
56
|
|
|
61
57
|
Args:
|
|
62
|
-
bank_id: **REQUIRED** - The unique, persistent identifier for this specific user (e.g., user_id, email, session_id).
|
|
63
|
-
This MUST be consistent across all interactions with the same user.
|
|
64
|
-
Example: "user_12345", "alice@example.com", "session_abc123"
|
|
65
58
|
content: The fact/memory to store (be specific and include relevant details)
|
|
66
|
-
context:
|
|
67
|
-
explanation: Optional explanation for why this memory is being stored
|
|
59
|
+
context: Category for the memory (e.g., 'preferences', 'work', 'hobbies', 'family'). Default: 'general'
|
|
68
60
|
"""
|
|
69
61
|
try:
|
|
70
|
-
|
|
71
|
-
if explanation:
|
|
72
|
-
pass # Explanation provided
|
|
73
|
-
|
|
74
|
-
# Store memory using put_batch_async
|
|
62
|
+
bank_id = get_current_bank_id()
|
|
75
63
|
await memory.put_batch_async(
|
|
76
64
|
bank_id=bank_id,
|
|
77
65
|
contents=[{"content": content, "context": context}]
|
|
78
66
|
)
|
|
79
|
-
return
|
|
67
|
+
return "Memory stored successfully"
|
|
80
68
|
except Exception as e:
|
|
81
|
-
logger.error(f"Error storing
|
|
69
|
+
logger.error(f"Error storing memory: {e}", exc_info=True)
|
|
82
70
|
return f"Error: {str(e)}"
|
|
83
71
|
|
|
84
72
|
@mcp.tool()
|
|
85
|
-
async def
|
|
73
|
+
async def recall(query: str, max_results: int = 10) -> str:
|
|
86
74
|
"""
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
- If you don't have a user identifier, DO NOT use this tool at all.
|
|
95
|
-
|
|
96
|
-
Use this tool PROACTIVELY at the start of conversations or when making recommendations to:
|
|
97
|
-
- Check user's preferences before making suggestions (e.g., "what foods does the user like?")
|
|
98
|
-
- Recall user's history to provide continuity (e.g., "what projects has the user worked on?")
|
|
99
|
-
- Remember user's goals and context (e.g., "what is the user trying to accomplish?")
|
|
100
|
-
- Avoid repeating information or asking questions you should already know
|
|
101
|
-
- Personalize responses based on user's background, interests, and past interactions
|
|
102
|
-
- Reference past conversations or events the user mentioned
|
|
103
|
-
|
|
104
|
-
**When to use**:
|
|
105
|
-
- Start of conversation: Search for relevant context about the user
|
|
106
|
-
- Before recommendations: Check user preferences and past experiences
|
|
107
|
-
- When user asks about something they may have mentioned before
|
|
108
|
-
- To provide continuity across conversations
|
|
109
|
-
|
|
110
|
-
**Search tips**: Use natural language queries like "user's programming language preferences",
|
|
111
|
-
"user's work experience", "user's dietary restrictions", "what does the user know about X?"
|
|
75
|
+
Search memories to provide personalized, context-aware responses.
|
|
76
|
+
|
|
77
|
+
Use this tool PROACTIVELY to:
|
|
78
|
+
- Check user's preferences before making suggestions
|
|
79
|
+
- Recall user's history to provide continuity
|
|
80
|
+
- Remember user's goals and context
|
|
81
|
+
- Personalize responses based on past interactions
|
|
112
82
|
|
|
113
83
|
Args:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
Example: "user_12345", "alice@example.com", "session_abc123"
|
|
117
|
-
query: Natural language search query to find relevant memories
|
|
118
|
-
max_tokens: Maximum tokens for search context (default: 4096)
|
|
119
|
-
explanation: Optional explanation for why this search is being performed
|
|
84
|
+
query: Natural language search query (e.g., "user's food preferences", "what projects is user working on")
|
|
85
|
+
max_results: Maximum number of results to return (default: 10)
|
|
120
86
|
"""
|
|
121
87
|
try:
|
|
122
|
-
|
|
123
|
-
logger.info(f"hindsight_search called with: query={query!r}, max_tokens={max_tokens}, explanation={explanation!r}")
|
|
124
|
-
|
|
125
|
-
# Log explanation if provided
|
|
126
|
-
if explanation:
|
|
127
|
-
pass # Explanation provided
|
|
128
|
-
|
|
129
|
-
# Search using recall_async
|
|
88
|
+
bank_id = get_current_bank_id()
|
|
130
89
|
from hindsight_api.engine.memory_engine import Budget
|
|
131
90
|
search_result = await memory.recall_async(
|
|
132
91
|
bank_id=bank_id,
|
|
133
92
|
query=query,
|
|
134
|
-
fact_type=["world", "bank", "opinion"],
|
|
135
|
-
max_tokens=max_tokens,
|
|
93
|
+
fact_type=["world", "bank", "opinion"],
|
|
136
94
|
budget=Budget.LOW
|
|
137
95
|
)
|
|
138
96
|
|
|
139
|
-
# Convert results to dict format
|
|
140
97
|
results = [
|
|
141
98
|
{
|
|
142
99
|
"id": fact.id,
|
|
143
100
|
"text": fact.text,
|
|
144
101
|
"type": fact.fact_type,
|
|
145
102
|
"context": fact.context,
|
|
146
|
-
"event_date": fact.event_date,
|
|
147
|
-
"document_id": fact.document_id
|
|
103
|
+
"event_date": fact.event_date,
|
|
148
104
|
}
|
|
149
|
-
for fact in search_result.results
|
|
105
|
+
for fact in search_result.results[:max_results]
|
|
150
106
|
]
|
|
151
107
|
|
|
152
108
|
return json.dumps({"results": results}, indent=2)
|
|
@@ -155,3 +111,104 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
|
|
155
111
|
return json.dumps({"error": str(e), "results": []})
|
|
156
112
|
|
|
157
113
|
return mcp
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class MCPMiddleware:
|
|
117
|
+
"""ASGI middleware that extracts bank_id from path and sets context."""
|
|
118
|
+
|
|
119
|
+
def __init__(self, app, memory: MemoryEngine):
|
|
120
|
+
self.app = app
|
|
121
|
+
self.memory = memory
|
|
122
|
+
self.mcp_server = create_mcp_server(memory)
|
|
123
|
+
# Use sse_app - http_app requires lifespan management that's complex with middleware
|
|
124
|
+
import warnings
|
|
125
|
+
with warnings.catch_warnings():
|
|
126
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
|
127
|
+
self.mcp_app = self.mcp_server.sse_app()
|
|
128
|
+
|
|
129
|
+
async def __call__(self, scope, receive, send):
|
|
130
|
+
if scope["type"] != "http":
|
|
131
|
+
await self.mcp_app(scope, receive, send)
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
path = scope.get("path", "")
|
|
135
|
+
|
|
136
|
+
# Strip any mount prefix (e.g., /mcp) that FastAPI might not have stripped
|
|
137
|
+
root_path = scope.get("root_path", "")
|
|
138
|
+
if root_path and path.startswith(root_path):
|
|
139
|
+
path = path[len(root_path):] or "/"
|
|
140
|
+
|
|
141
|
+
# Also handle case where mount path wasn't stripped (e.g., /mcp/...)
|
|
142
|
+
if path.startswith("/mcp/"):
|
|
143
|
+
path = path[4:] # Remove /mcp prefix
|
|
144
|
+
|
|
145
|
+
# Extract bank_id from path: /{bank_id}/ or /{bank_id}
|
|
146
|
+
# http_app expects requests at /
|
|
147
|
+
if not path.startswith("/") or len(path) <= 1:
|
|
148
|
+
# No bank_id in path - return error
|
|
149
|
+
await self._send_error(send, 400, "bank_id required in path: /mcp/{bank_id}/")
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
# Extract bank_id from first path segment
|
|
153
|
+
parts = path[1:].split("/", 1)
|
|
154
|
+
if not parts[0]:
|
|
155
|
+
await self._send_error(send, 400, "bank_id required in path: /mcp/{bank_id}/")
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
bank_id = parts[0]
|
|
159
|
+
new_path = "/" + parts[1] if len(parts) > 1 else "/"
|
|
160
|
+
|
|
161
|
+
# Set bank_id context
|
|
162
|
+
token = _current_bank_id.set(bank_id)
|
|
163
|
+
try:
|
|
164
|
+
new_scope = scope.copy()
|
|
165
|
+
new_scope["path"] = new_path
|
|
166
|
+
|
|
167
|
+
# Wrap send to rewrite the SSE endpoint URL to include bank_id
|
|
168
|
+
# The SSE app sends "event: endpoint\ndata: /messages\n" but we need
|
|
169
|
+
# the client to POST to /{bank_id}/messages instead
|
|
170
|
+
async def send_wrapper(message):
|
|
171
|
+
if message["type"] == "http.response.body":
|
|
172
|
+
body = message.get("body", b"")
|
|
173
|
+
if body and b"/messages" in body:
|
|
174
|
+
# Rewrite /messages to /{bank_id}/messages in SSE endpoint event
|
|
175
|
+
body = body.replace(
|
|
176
|
+
b"data: /messages",
|
|
177
|
+
f"data: /{bank_id}/messages".encode()
|
|
178
|
+
)
|
|
179
|
+
message = {**message, "body": body}
|
|
180
|
+
await send(message)
|
|
181
|
+
|
|
182
|
+
await self.mcp_app(new_scope, receive, send_wrapper)
|
|
183
|
+
finally:
|
|
184
|
+
_current_bank_id.reset(token)
|
|
185
|
+
|
|
186
|
+
async def _send_error(self, send, status: int, message: str):
|
|
187
|
+
"""Send an error response."""
|
|
188
|
+
body = json.dumps({"error": message}).encode()
|
|
189
|
+
await send({
|
|
190
|
+
"type": "http.response.start",
|
|
191
|
+
"status": status,
|
|
192
|
+
"headers": [(b"content-type", b"application/json")],
|
|
193
|
+
})
|
|
194
|
+
await send({
|
|
195
|
+
"type": "http.response.body",
|
|
196
|
+
"body": body,
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def create_mcp_app(memory: MemoryEngine):
|
|
201
|
+
"""
|
|
202
|
+
Create an ASGI app that handles MCP requests.
|
|
203
|
+
|
|
204
|
+
URL pattern: /mcp/{bank_id}/
|
|
205
|
+
|
|
206
|
+
The bank_id is extracted from the URL path and made available to tools.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
memory: MemoryEngine instance
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
ASGI application
|
|
213
|
+
"""
|
|
214
|
+
return MCPMiddleware(None, memory)
|
hindsight_api/cli.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for Hindsight API.
|
|
3
|
+
|
|
4
|
+
Run the server with:
|
|
5
|
+
hindsight-api
|
|
6
|
+
|
|
7
|
+
Stop with Ctrl+C.
|
|
8
|
+
"""
|
|
9
|
+
import argparse
|
|
10
|
+
import asyncio
|
|
11
|
+
import atexit
|
|
12
|
+
import os
|
|
13
|
+
import signal
|
|
14
|
+
import sys
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
import uvicorn
|
|
18
|
+
|
|
19
|
+
from . import MemoryEngine
|
|
20
|
+
from .api import create_app
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Disable tokenizers parallelism to avoid warnings
|
|
24
|
+
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
25
|
+
|
|
26
|
+
# Global reference for cleanup
|
|
27
|
+
_memory: Optional[MemoryEngine] = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _cleanup():
|
|
31
|
+
"""Synchronous cleanup function to stop resources on exit."""
|
|
32
|
+
global _memory
|
|
33
|
+
if _memory is not None and _memory._pg0 is not None:
|
|
34
|
+
try:
|
|
35
|
+
loop = asyncio.new_event_loop()
|
|
36
|
+
loop.run_until_complete(_memory._pg0.stop())
|
|
37
|
+
loop.close()
|
|
38
|
+
print("\npg0 stopped.")
|
|
39
|
+
except Exception as e:
|
|
40
|
+
print(f"\nError stopping pg0: {e}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _signal_handler(signum, frame):
|
|
44
|
+
"""Handle SIGINT/SIGTERM to ensure cleanup."""
|
|
45
|
+
print(f"\nReceived signal {signum}, shutting down...")
|
|
46
|
+
_cleanup()
|
|
47
|
+
sys.exit(0)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main():
|
|
51
|
+
"""Main entry point for the CLI."""
|
|
52
|
+
global _memory
|
|
53
|
+
|
|
54
|
+
parser = argparse.ArgumentParser(
|
|
55
|
+
prog="hindsight-api",
|
|
56
|
+
description="Hindsight API Server",
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--host", default="0.0.0.0",
|
|
60
|
+
help="Host to bind to (default: 0.0.0.0)"
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"--port", type=int, default=8888,
|
|
64
|
+
help="Port to bind to (default: 8888)"
|
|
65
|
+
)
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"--log-level", default="info",
|
|
68
|
+
choices=["critical", "error", "warning", "info", "debug", "trace"],
|
|
69
|
+
help="Log level (default: info)"
|
|
70
|
+
)
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
"--access-log", action="store_true",
|
|
73
|
+
help="Enable access log"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
args = parser.parse_args()
|
|
77
|
+
|
|
78
|
+
# Register cleanup handlers
|
|
79
|
+
atexit.register(_cleanup)
|
|
80
|
+
signal.signal(signal.SIGINT, _signal_handler)
|
|
81
|
+
signal.signal(signal.SIGTERM, _signal_handler)
|
|
82
|
+
|
|
83
|
+
# Get configuration from environment variables
|
|
84
|
+
db_url = os.getenv("HINDSIGHT_API_DATABASE_URL", "pg0")
|
|
85
|
+
llm_provider = os.getenv("HINDSIGHT_API_LLM_PROVIDER", "groq")
|
|
86
|
+
llm_api_key = os.getenv("HINDSIGHT_API_LLM_API_KEY", "")
|
|
87
|
+
llm_model = os.getenv("HINDSIGHT_API_LLM_MODEL", "openai/gpt-oss-20b")
|
|
88
|
+
llm_base_url = os.getenv("HINDSIGHT_API_LLM_BASE_URL") or None
|
|
89
|
+
|
|
90
|
+
# Create MemoryEngine
|
|
91
|
+
_memory = MemoryEngine(
|
|
92
|
+
db_url=db_url,
|
|
93
|
+
memory_llm_provider=llm_provider,
|
|
94
|
+
memory_llm_api_key=llm_api_key,
|
|
95
|
+
memory_llm_model=llm_model,
|
|
96
|
+
memory_llm_base_url=llm_base_url,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Create FastAPI app
|
|
100
|
+
app = create_app(
|
|
101
|
+
memory=_memory,
|
|
102
|
+
http_api_enabled=True,
|
|
103
|
+
mcp_api_enabled=True,
|
|
104
|
+
mcp_mount_path="/mcp",
|
|
105
|
+
run_migrations=True,
|
|
106
|
+
initialize_memory=True,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Prepare uvicorn config
|
|
110
|
+
uvicorn_config = {
|
|
111
|
+
"app": app,
|
|
112
|
+
"host": args.host,
|
|
113
|
+
"port": args.port,
|
|
114
|
+
"log_level": args.log_level,
|
|
115
|
+
"access_log": args.access_log,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
print(f"\nStarting Hindsight API...")
|
|
119
|
+
print(f" URL: http://{args.host}:{args.port}")
|
|
120
|
+
print(f" Database: {db_url}")
|
|
121
|
+
print(f" LLM Provider: {llm_provider}")
|
|
122
|
+
print()
|
|
123
|
+
|
|
124
|
+
uvicorn.run(**uvicorn_config)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
main()
|
hindsight_api/web/server.py
CHANGED
|
@@ -10,13 +10,9 @@ import warnings
|
|
|
10
10
|
warnings.filterwarnings("ignore", message="websockets.legacy is deprecated")
|
|
11
11
|
warnings.filterwarnings("ignore", message="websockets.server.WebSocketServerProtocol is deprecated")
|
|
12
12
|
|
|
13
|
-
import asyncio
|
|
14
|
-
import atexit
|
|
15
13
|
import logging
|
|
16
14
|
import os
|
|
17
15
|
import argparse
|
|
18
|
-
import signal
|
|
19
|
-
import sys
|
|
20
16
|
|
|
21
17
|
from hindsight_api import MemoryEngine
|
|
22
18
|
from hindsight_api.api import create_app
|
|
@@ -25,36 +21,6 @@ from hindsight_api.api import create_app
|
|
|
25
21
|
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
26
22
|
|
|
27
23
|
|
|
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
24
|
# Create app at module level (required for uvicorn import string)
|
|
59
25
|
_memory = MemoryEngine(
|
|
60
26
|
db_url=os.getenv("HINDSIGHT_API_DATABASE_URL", "pg0"),
|
|
@@ -85,7 +51,7 @@ if __name__ == "__main__":
|
|
|
85
51
|
env_log_level = "info"
|
|
86
52
|
|
|
87
53
|
# Parse CLI arguments
|
|
88
|
-
parser = argparse.ArgumentParser(description="
|
|
54
|
+
parser = argparse.ArgumentParser(description="Hindsight API Server")
|
|
89
55
|
parser.add_argument("--host", default="0.0.0.0", help="Host to bind to (default: 0.0.0.0)")
|
|
90
56
|
parser.add_argument("--port", type=int, default=8888, help="Port to bind to (default: 8888)")
|
|
91
57
|
parser.add_argument("--reload", action="store_true", help="Enable auto-reload on code changes")
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
hindsight_api/__init__.py,sha256=yQWYWUWEhvs1OY1coENhZV_CuOAWmN_YKZXQMIvGN94,851
|
|
2
|
+
hindsight_api/cli.py,sha256=-dxAHsET_pHd6NlA3ufI4KEKQA3fL3YapCvDB_x2ax8,3303
|
|
2
3
|
hindsight_api/metrics.py,sha256=j4-eeqVjjcGQxAxS_GgEaBNm10KdUxrGS_I2d1IM1hY,7255
|
|
3
4
|
hindsight_api/migrations.py,sha256=VY-ILJLWEY1IaeJgQ2jlAVUtPLzq_41Dytg_DjuF0GA,6402
|
|
4
5
|
hindsight_api/models.py,sha256=1vMn9jmDQvohfmxZXr1SYnhz5vhz52nrTd93A_lkVNE,12606
|
|
5
6
|
hindsight_api/pg0.py,sha256=scFcYngOwbZ2oOQb7TysnUHgNgPyiN30pjPcIqMDmao,14158
|
|
6
|
-
hindsight_api/api/__init__.py,sha256=
|
|
7
|
+
hindsight_api/api/__init__.py,sha256=lXxJythXFV1DXIQ--4QfIo5pHYmDYJnd41dfAssNTTA,3017
|
|
7
8
|
hindsight_api/api/http.py,sha256=anjh8axWcWF1dyqW3CnE9TUObLKxryjeQxT_keQEMak,71551
|
|
8
|
-
hindsight_api/api/mcp.py,sha256=
|
|
9
|
+
hindsight_api/api/mcp.py,sha256=1fqeKBh3K0lJ5jodYysOTnDOWNjSzA08g8v2_k8HOlU,7734
|
|
9
10
|
hindsight_api/engine/__init__.py,sha256=5DU5DvnJdzkrgNgKchpzkiJr-37I-kE1tegJg2LF04k,1214
|
|
10
11
|
hindsight_api/engine/cross_encoder.py,sha256=kfwLiqlQUfvOgLyrkRReO1wWlO020lGbLXY8U0jKiPA,2875
|
|
11
12
|
hindsight_api/engine/db_utils.py,sha256=p1Ne70wPP327xdPI_XjMfnagilY8sknbkhEIZuED6DU,2724
|
|
@@ -42,7 +43,8 @@ hindsight_api/engine/search/trace.py,sha256=GT86_LVKMyG2mw6EJzPjafvbqaot6XVy5fZ0
|
|
|
42
43
|
hindsight_api/engine/search/tracer.py,sha256=mcM9qZpj3YFudrBCESwc6YKNAiWIMx1lScXWn5ru-ok,15017
|
|
43
44
|
hindsight_api/engine/search/types.py,sha256=qIeHW_gT7f291vteTZXygAM8oAaPp2dq6uEdvOyOwzs,5488
|
|
44
45
|
hindsight_api/web/__init__.py,sha256=WABqyqiAVFJJWOhKCytkj5Vcb61eAsRib3Ek7IMX6_U,378
|
|
45
|
-
hindsight_api/web/server.py,sha256=
|
|
46
|
-
hindsight_api-0.0.
|
|
47
|
-
hindsight_api-0.0.
|
|
48
|
-
hindsight_api-0.0.
|
|
46
|
+
hindsight_api/web/server.py,sha256=l-Tw8G9IRdcSay-KWiUT4VlIJBzxbe-TV0rjX0fwLMc,4464
|
|
47
|
+
hindsight_api-0.0.17.dist-info/METADATA,sha256=zan_1uOEwAxlipqrDA0Rc65zs-e-AqxRoT38ihKtLQw,1496
|
|
48
|
+
hindsight_api-0.0.17.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
49
|
+
hindsight_api-0.0.17.dist-info/entry_points.txt,sha256=53Fn-VxtkqreZhOPTJB_FupH7e5GyiMY3gzEp22d8xs,57
|
|
50
|
+
hindsight_api-0.0.17.dist-info/RECORD,,
|
|
File without changes
|