Add memory system session context API

This commit is contained in:
2026-05-26 12:24:56 +08:00
parent d73f59f38d
commit a89807b174
10 changed files with 358 additions and 2 deletions

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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)})