Add memory system session context API
This commit is contained in:
@ -4,7 +4,13 @@ from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
|
||||
from .auth import verify_api_key
|
||||
from .schemas import MessageIngestRequest, SearchRequest, SessionUserRequest, UserCreateRequest
|
||||
from .schemas import (
|
||||
MessageIngestRequest,
|
||||
SearchRequest,
|
||||
SessionContextRequest,
|
||||
SessionUserRequest,
|
||||
UserCreateRequest,
|
||||
)
|
||||
from .service import MemorySystemService
|
||||
|
||||
|
||||
@ -70,6 +76,34 @@ async def extract_session(
|
||||
raise user_auth_error(exc) from exc
|
||||
|
||||
|
||||
@router.post("/sessions/{session_id}/context")
|
||||
async def get_session_context(
|
||||
session_id: str,
|
||||
request: SessionContextRequest,
|
||||
service: MemorySystemService = Depends(get_service),
|
||||
):
|
||||
try:
|
||||
return await service.get_session_context(session_id, request)
|
||||
except PermissionError as exc:
|
||||
raise user_auth_error(exc) from exc
|
||||
|
||||
|
||||
@router.get("/sessions/{session_id}/context")
|
||||
async def get_session_context_from_query(
|
||||
session_id: str,
|
||||
user_id: str = Query(min_length=1),
|
||||
user_key: str = Query(min_length=1),
|
||||
query: str = Query(min_length=1),
|
||||
limit: int = Query(default=10, ge=1, le=100),
|
||||
service: MemorySystemService = Depends(get_service),
|
||||
):
|
||||
try:
|
||||
request = SessionContextRequest(user_id=user_id, user_key=user_key, query=query, limit=limit)
|
||||
return await service.get_session_context(session_id, request)
|
||||
except PermissionError as exc:
|
||||
raise user_auth_error(exc) from exc
|
||||
|
||||
|
||||
@router.get("/openviking/tasks/{task_id}")
|
||||
async def get_openviking_task(
|
||||
task_id: str,
|
||||
|
||||
@ -180,6 +180,12 @@ class OpenVikingMemorySystemClient:
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_session_context(self, credential: OpenVikingCredential | str, session_id: str) -> dict[str, Any]:
|
||||
async with self._credential_client(credential) as client:
|
||||
response = await client.get(f"/api/v1/sessions/{session_id}/context")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def _credential_client(self, credential: OpenVikingCredential | str) -> httpx.AsyncClient:
|
||||
if isinstance(credential, str):
|
||||
return self._client(credential)
|
||||
|
||||
@ -33,6 +33,13 @@ class SearchRequest(BaseModel):
|
||||
limit: int = Field(default=10, ge=1, le=100)
|
||||
|
||||
|
||||
class SessionContextRequest(BaseModel):
|
||||
user_id: str = Field(min_length=1)
|
||||
user_key: str = Field(min_length=1)
|
||||
query: str = Field(min_length=1)
|
||||
limit: int = Field(default=10, ge=1, le=100)
|
||||
|
||||
|
||||
class BackendStatus(BaseModel):
|
||||
status: OperationStatus
|
||||
result: Any = None
|
||||
@ -71,6 +78,13 @@ class SearchResponse(BaseModel):
|
||||
backends: dict[str, BackendStatus]
|
||||
|
||||
|
||||
class SessionContextResponse(BaseModel):
|
||||
status: OperationStatus
|
||||
context: dict[str, Any] | None = None
|
||||
items: list[dict[str, Any]] = Field(default_factory=list)
|
||||
backends: dict[str, BackendStatus]
|
||||
|
||||
|
||||
class ProfileResponse(BaseModel):
|
||||
status: OperationStatus
|
||||
profile: Any = None
|
||||
|
||||
@ -15,6 +15,8 @@ from .schemas import (
|
||||
ProfileResponse,
|
||||
SearchRequest,
|
||||
SearchResponse,
|
||||
SessionContextRequest,
|
||||
SessionContextResponse,
|
||||
)
|
||||
|
||||
|
||||
@ -124,6 +126,40 @@ class MemorySystemService:
|
||||
backends=compact_backends,
|
||||
)
|
||||
|
||||
async def get_session_context(self, session_id: str, request: SessionContextRequest) -> SessionContextResponse:
|
||||
credential = self.openviking.credential_for_user(
|
||||
request.user_id,
|
||||
request.user_key,
|
||||
agent_id=session_id,
|
||||
)
|
||||
|
||||
async def read_openviking_context() -> dict[str, Any]:
|
||||
return await self.openviking.get_session_context(credential, session_id)
|
||||
|
||||
async def search_everos() -> dict[str, Any]:
|
||||
return await self.everos.search(
|
||||
request.user_id,
|
||||
session_id,
|
||||
request.query,
|
||||
"hybrid",
|
||||
request.limit,
|
||||
)
|
||||
|
||||
backends = await self._run_backends(openviking=read_openviking_context, everos=search_everos)
|
||||
backends = self._remove_vectors_from_backends(backends)
|
||||
context = self._context_from_openviking_result(backends["openviking"].result)
|
||||
items = (
|
||||
self._items_from_backend_result("everos", backends["everos"].result)[: request.limit]
|
||||
if backends["everos"].status == "success"
|
||||
else []
|
||||
)
|
||||
return SessionContextResponse(
|
||||
status=self._aggregate_status(backends),
|
||||
context=context,
|
||||
items=items,
|
||||
backends=self._compact_session_context_backends(backends),
|
||||
)
|
||||
|
||||
async def get_profile(self, user_id: str) -> ProfileResponse:
|
||||
backends = {"everos": await self._capture(lambda: self.everos.get_profile(user_id))}
|
||||
profile = backends["everos"].result if backends["everos"].status == "success" else None
|
||||
@ -260,6 +296,33 @@ class MemorySystemService:
|
||||
|
||||
return result
|
||||
|
||||
def _context_from_openviking_result(self, result: Any) -> dict[str, Any] | None:
|
||||
if not isinstance(result, dict):
|
||||
return None
|
||||
data = result.get("result") if isinstance(result.get("result"), dict) else result
|
||||
return data if isinstance(data, dict) else None
|
||||
|
||||
def _compact_session_context_backends(self, backends: dict[str, BackendStatus]) -> dict[str, BackendStatus]:
|
||||
return {
|
||||
name: backend.model_copy(update={"result": self._compact_session_context_backend_result(name, backend.result)})
|
||||
for name, backend in backends.items()
|
||||
}
|
||||
|
||||
def _compact_session_context_backend_result(self, backend_name: str, result: Any) -> Any:
|
||||
if backend_name == "openviking":
|
||||
data = self._context_from_openviking_result(result)
|
||||
if data is None:
|
||||
return result
|
||||
compact = {
|
||||
"status": result.get("status") if isinstance(result, dict) else None,
|
||||
"estimatedTokens": data.get("estimatedTokens"),
|
||||
"stats": data.get("stats"),
|
||||
"has_latest_archive_overview": bool(data.get("latest_archive_overview")),
|
||||
"message_count": len(data.get("messages") or []) if isinstance(data.get("messages"), list) else 0,
|
||||
}
|
||||
return {key: value for key, value in compact.items() if value is not None}
|
||||
return self._compact_backend_result(backend_name, result)
|
||||
|
||||
def _remove_vectors_from_backends(self, backends: dict[str, BackendStatus]) -> dict[str, BackendStatus]:
|
||||
return {
|
||||
name: backend.model_copy(update={"result": self._remove_vectors(backend.result)})
|
||||
|
||||
Reference in New Issue
Block a user