tillu-AI commited on
Commit
27ca8e7
·
verified ·
1 Parent(s): d6ef0ad

upload app/api/memory.py

Browse files
Files changed (1) hide show
  1. app/api/memory.py +183 -0
app/api/memory.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Memory API endpoints for semantic search and storage
3
+ """
4
+ import time
5
+ from typing import Optional
6
+ from fastapi import APIRouter, HTTPException, Depends, Header
7
+
8
+ from app.models.api import (
9
+ MemorySearchRequest, MemorySearchResponse, MemoryItem
10
+ )
11
+ from app.utils.database import db
12
+ from app.utils.cache import cache
13
+ from app.utils.logging import get_logger
14
+
15
+ logger = get_logger("memory_api")
16
+ router = APIRouter(prefix="/api/v1/memory")
17
+
18
+
19
+ async def verify_auth(authorization: Optional[str] = Header(None)):
20
+ """Verify bearer token authentication"""
21
+ if not authorization:
22
+ raise HTTPException(status_code=401, detail="Authorization header missing")
23
+ if not authorization.startswith("Bearer "):
24
+ raise HTTPException(status_code=401, detail="Invalid authorization format")
25
+ return {"user_id": "test-user-id"}
26
+
27
+
28
+ @router.post("/search", response_model=MemorySearchResponse)
29
+ async def search_memory(
30
+ request: MemorySearchRequest,
31
+ auth: dict = Depends(verify_auth)
32
+ ):
33
+ """
34
+ Semantic memory query across all stores.
35
+ Uses pgvector similarity search with embeddings.
36
+ """
37
+ user_id = auth["user_id"]
38
+ start_time = time.time()
39
+
40
+ logger.info("Memory search", query=request.query, limit=request.limit)
41
+
42
+ try:
43
+ # PHASE 2: Semantic search with embeddings
44
+ from app.memory.semantic_search import semantic_search
45
+
46
+ results = await semantic_search.search_all(
47
+ user_id=user_id,
48
+ query=request.query,
49
+ max_results_per_source=request.limit
50
+ )
51
+
52
+ # Format results from all sources
53
+ items = []
54
+
55
+ # Add knowledge base results
56
+ for item in results.get("knowledge", []):
57
+ items.append(MemoryItem(
58
+ id=item["id"],
59
+ content=item["content"],
60
+ content_type=item.get("content_type", "fact"),
61
+ category=item.get("category"),
62
+ source_type="knowledge_base",
63
+ confidence_score=item.get("confidence_score", 0.8),
64
+ similarity=item.get("similarity", 0.75),
65
+ created_at=item["created_at"]
66
+ ))
67
+
68
+ # Add news results
69
+ for item in results.get("news", []):
70
+ items.append(MemoryItem(
71
+ id=item["id"],
72
+ content=f"{item.get('title', '')}: {item.get('summary', '')}",
73
+ content_type="news",
74
+ category="current_events",
75
+ source_type="news_article",
76
+ confidence_score=item.get("relevance_score", 0.7),
77
+ similarity=item.get("similarity", 0.7),
78
+ created_at=item["fetched_at"]
79
+ ))
80
+
81
+ # Add research results
82
+ for item in results.get("research", []):
83
+ items.append(MemoryItem(
84
+ id=item["id"],
85
+ content=item.get("executive_summary", item.get("query", "")),
86
+ content_type="research",
87
+ category="research_session",
88
+ source_type="research",
89
+ confidence_score=0.85,
90
+ similarity=item.get("similarity", 0.75),
91
+ created_at=item["created_at"]
92
+ ))
93
+
94
+ # Sort by similarity
95
+ items.sort(key=lambda x: x.similarity, reverse=True)
96
+
97
+ # Apply limit
98
+ items = items[:request.limit]
99
+
100
+ search_time_ms = int((time.time() - start_time) * 1000)
101
+
102
+ logger.info(
103
+ "Memory search complete",
104
+ knowledge=len(results.get("knowledge", [])),
105
+ news=len(results.get("news", [])),
106
+ research=len(results.get("research", [])),
107
+ search_time_ms=search_time_ms
108
+ )
109
+
110
+ return MemorySearchResponse(
111
+ query=request.query,
112
+ results=items,
113
+ total_found=len(items),
114
+ search_time_ms=search_time_ms
115
+ )
116
+
117
+ except Exception as e:
118
+ logger.error("Memory search error", error=str(e))
119
+ raise HTTPException(status_code=500, detail="Search failed")
120
+
121
+
122
+ @router.post("/store")
123
+ async def store_memory(
124
+ content: str,
125
+ content_type: str = "fact",
126
+ category: Optional[str] = None,
127
+ auth: dict = Depends(verify_auth)
128
+ ):
129
+ """
130
+ Explicitly store a knowledge item.
131
+ Generates embedding automatically (Phase 2).
132
+ """
133
+ user_id = auth["user_id"]
134
+
135
+ logger.info("Storing memory", content_type=content_type, category=category)
136
+
137
+ try:
138
+ # PHASE 2: Store with embedding
139
+ from app.memory.semantic_search import semantic_search
140
+
141
+ result = await semantic_search.store_with_embedding(
142
+ user_id=user_id,
143
+ content=content,
144
+ content_type=content_type,
145
+ category=category,
146
+ source_type="user",
147
+ source_metadata={"api_endpoint": "/memory/store"}
148
+ )
149
+
150
+ if result:
151
+ return {
152
+ "success": True,
153
+ "memory_id": result["id"],
154
+ "embedding_generated": True,
155
+ "message": "Memory stored with embedding successfully"
156
+ }
157
+ else:
158
+ raise HTTPException(status_code=500, detail="Failed to store memory")
159
+
160
+ except Exception as e:
161
+ logger.error("Memory store error", error=str(e))
162
+ raise HTTPException(status_code=500, detail="Store failed")
163
+
164
+
165
+ @router.delete("/{memory_id}")
166
+ async def delete_memory(
167
+ memory_id: str,
168
+ auth: dict = Depends(verify_auth)
169
+ ):
170
+ """Delete a memory item"""
171
+ user_id = auth["user_id"]
172
+
173
+ logger.info("Deleting memory", memory_id=memory_id)
174
+
175
+ result = await db.delete(
176
+ "knowledge_base",
177
+ {"id": memory_id, "user_id": user_id}
178
+ )
179
+
180
+ if result:
181
+ return {"success": True, "message": "Memory deleted"}
182
+ else:
183
+ raise HTTPException(status_code=404, detail="Memory not found")