services.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. from datetime import datetime
  2. from core_domain import MemoryScopeType, MemoryStatus
  3. from app.application.retrieval import (
  4. build_hash_embedding,
  5. cosine_similarity,
  6. keyword_score,
  7. rerank_score,
  8. )
  9. from app.bootstrap.settings import MemoryServiceSettings
  10. from app.db.models import MemoryItem
  11. from app.domain.repositories import MemoryItemRepository
  12. from app.schemas.memory import MemoryCreateRequest, MemorySearchRequest, MemoryStatusUpdateRequest
  13. class MemoryApplicationService:
  14. def __init__(
  15. self,
  16. *,
  17. memory_repository: MemoryItemRepository,
  18. settings: MemoryServiceSettings | None = None) -> None:
  19. self.memory_repository = memory_repository
  20. self.settings = settings or MemoryServiceSettings()
  21. def create_memory(self, payload: MemoryCreateRequest) -> MemoryItem:
  22. embedding_json = build_hash_embedding(
  23. payload.content_text,
  24. dimensions=self.settings.embedding_dimensions)
  25. return self.memory_repository.create(
  26. scope_type=payload.scope_type,
  27. scope_id=payload.scope_id,
  28. memory_type=payload.memory_type,
  29. content_text=payload.content_text,
  30. content_json=payload.content_json,
  31. metadata_json=payload.metadata_json,
  32. embedding_model=self.settings.embedding_model,
  33. embedding_json=embedding_json,
  34. owner_agent_id=payload.owner_agent_id,
  35. user_id=payload.user_id,
  36. session_id=payload.session_id,
  37. source_ref=payload.source_ref,
  38. importance_score=payload.importance_score,
  39. expires_time=payload.expires_time)
  40. def list_memories(
  41. self,
  42. *,
  43. scope_type: MemoryScopeType | None = None,
  44. scope_id: str | None = None,
  45. status: MemoryStatus | None = "active",
  46. limit: int = 100) -> list[MemoryItem]:
  47. return self.memory_repository.list_by_scope(
  48. scope_type=scope_type,
  49. scope_id=scope_id,
  50. status=status,
  51. limit=limit)
  52. def search_memories(
  53. self,
  54. payload: MemorySearchRequest) -> list[tuple[MemoryItem, float, dict[str, float | str]]]:
  55. query_embedding = build_hash_embedding(
  56. payload.query,
  57. dimensions=self.settings.embedding_dimensions)
  58. candidates = self.memory_repository.search_candidates(
  59. scope_type=payload.scope_type,
  60. scope_id=payload.scope_id,
  61. owner_agent_id=payload.owner_agent_id,
  62. user_id=payload.user_id,
  63. session_id=payload.session_id,
  64. limit=max(payload.limit * 10, payload.limit))
  65. scored_items = [
  66. self._score(item=item, query=payload.query, query_embedding=query_embedding)
  67. for item in candidates
  68. ]
  69. scored_items.sort(key=lambda item: item[1], reverse=True)
  70. items = [item for item, _, _ in scored_items[: payload.limit]]
  71. now = datetime.utcnow()
  72. self.memory_repository.touch_many(memory_ids=[item.id for item in items], accessed_time=now)
  73. return scored_items[: payload.limit]
  74. def update_memory_status(
  75. self,
  76. *,
  77. memory_id: str,
  78. payload: MemoryStatusUpdateRequest) -> MemoryItem | None:
  79. return self.memory_repository.update_status(
  80. memory_id=memory_id,
  81. status=payload.status)
  82. def _score(
  83. self,
  84. *,
  85. item: MemoryItem,
  86. query: str,
  87. query_embedding: list[float]) -> tuple[MemoryItem, float, dict[str, float | str]]:
  88. keyword = keyword_score(query, item.content_text)
  89. vector = cosine_similarity(query_embedding, item.embedding_json)
  90. score = rerank_score(
  91. keyword=keyword,
  92. vector=vector,
  93. importance_score=item.importance_score)
  94. return item, score, {
  95. "keyword_score": round(keyword, 6),
  96. "vector_score": round(vector, 6),
  97. "importance_score": float(item.importance_score),
  98. "embedding_model": item.embedding_model or self.settings.embedding_model,
  99. "rerank_mode": "hybrid-local",
  100. }