File size: 5,959 Bytes
27ca8e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
"""
Memory API endpoints for semantic search and storage
"""
import time
from typing import Optional
from fastapi import APIRouter, HTTPException, Depends, Header

from app.models.api import (
    MemorySearchRequest, MemorySearchResponse, MemoryItem
)
from app.utils.database import db
from app.utils.cache import cache
from app.utils.logging import get_logger

logger = get_logger("memory_api")
router = APIRouter(prefix="/api/v1/memory")


async def verify_auth(authorization: Optional[str] = Header(None)):
    """Verify bearer token authentication"""
    if not authorization:
        raise HTTPException(status_code=401, detail="Authorization header missing")
    if not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Invalid authorization format")
    return {"user_id": "test-user-id"}


@router.post("/search", response_model=MemorySearchResponse)
async def search_memory(
    request: MemorySearchRequest,
    auth: dict = Depends(verify_auth)
):
    """
    Semantic memory query across all stores.
    Uses pgvector similarity search with embeddings.
    """
    user_id = auth["user_id"]
    start_time = time.time()
    
    logger.info("Memory search", query=request.query, limit=request.limit)
    
    try:
        # PHASE 2: Semantic search with embeddings
        from app.memory.semantic_search import semantic_search
        
        results = await semantic_search.search_all(
            user_id=user_id,
            query=request.query,
            max_results_per_source=request.limit
        )
        
        # Format results from all sources
        items = []
        
        # Add knowledge base results
        for item in results.get("knowledge", []):
            items.append(MemoryItem(
                id=item["id"],
                content=item["content"],
                content_type=item.get("content_type", "fact"),
                category=item.get("category"),
                source_type="knowledge_base",
                confidence_score=item.get("confidence_score", 0.8),
                similarity=item.get("similarity", 0.75),
                created_at=item["created_at"]
            ))
        
        # Add news results
        for item in results.get("news", []):
            items.append(MemoryItem(
                id=item["id"],
                content=f"{item.get('title', '')}: {item.get('summary', '')}",
                content_type="news",
                category="current_events",
                source_type="news_article",
                confidence_score=item.get("relevance_score", 0.7),
                similarity=item.get("similarity", 0.7),
                created_at=item["fetched_at"]
            ))
        
        # Add research results
        for item in results.get("research", []):
            items.append(MemoryItem(
                id=item["id"],
                content=item.get("executive_summary", item.get("query", "")),
                content_type="research",
                category="research_session",
                source_type="research",
                confidence_score=0.85,
                similarity=item.get("similarity", 0.75),
                created_at=item["created_at"]
            ))
        
        # Sort by similarity
        items.sort(key=lambda x: x.similarity, reverse=True)
        
        # Apply limit
        items = items[:request.limit]
        
        search_time_ms = int((time.time() - start_time) * 1000)
        
        logger.info(
            "Memory search complete",
            knowledge=len(results.get("knowledge", [])),
            news=len(results.get("news", [])),
            research=len(results.get("research", [])),
            search_time_ms=search_time_ms
        )
        
        return MemorySearchResponse(
            query=request.query,
            results=items,
            total_found=len(items),
            search_time_ms=search_time_ms
        )
        
    except Exception as e:
        logger.error("Memory search error", error=str(e))
        raise HTTPException(status_code=500, detail="Search failed")


@router.post("/store")
async def store_memory(
    content: str,
    content_type: str = "fact",
    category: Optional[str] = None,
    auth: dict = Depends(verify_auth)
):
    """
    Explicitly store a knowledge item.
    Generates embedding automatically (Phase 2).
    """
    user_id = auth["user_id"]
    
    logger.info("Storing memory", content_type=content_type, category=category)
    
    try:
        # PHASE 2: Store with embedding
        from app.memory.semantic_search import semantic_search
        
        result = await semantic_search.store_with_embedding(
            user_id=user_id,
            content=content,
            content_type=content_type,
            category=category,
            source_type="user",
            source_metadata={"api_endpoint": "/memory/store"}
        )
        
        if result:
            return {
                "success": True,
                "memory_id": result["id"],
                "embedding_generated": True,
                "message": "Memory stored with embedding successfully"
            }
        else:
            raise HTTPException(status_code=500, detail="Failed to store memory")
            
    except Exception as e:
        logger.error("Memory store error", error=str(e))
        raise HTTPException(status_code=500, detail="Store failed")


@router.delete("/{memory_id}")
async def delete_memory(
    memory_id: str,
    auth: dict = Depends(verify_auth)
):
    """Delete a memory item"""
    user_id = auth["user_id"]
    
    logger.info("Deleting memory", memory_id=memory_id)
    
    result = await db.delete(
        "knowledge_base",
        {"id": memory_id, "user_id": user_id}
    )
    
    if result:
        return {"success": True, "message": "Memory deleted"}
    else:
        raise HTTPException(status_code=404, detail="Memory not found")