services.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  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 (
  13. MemoryCreateRequest,
  14. MemorySearchRequest,
  15. MemoryStatusUpdateRequest,
  16. )
  17. class MemoryApplicationService:
  18. def __init__(
  19. self,
  20. *,
  21. memory_repository: MemoryItemRepository,
  22. settings: MemoryServiceSettings | None = None,
  23. ) -> None:
  24. self.memory_repository = memory_repository
  25. self.settings = settings or MemoryServiceSettings()
  26. def create_memory(self, payload: MemoryCreateRequest) -> MemoryItem:
  27. embedding_json = build_hash_embedding(
  28. payload.content_text,
  29. dimensions=self.settings.embedding_dimensions,
  30. )
  31. return self.memory_repository.create(
  32. tenant_id=payload.tenant_id,
  33. scope_type=payload.scope_type,
  34. scope_id=payload.scope_id,
  35. memory_type=payload.memory_type,
  36. content_text=payload.content_text,
  37. content_json=payload.content_json,
  38. metadata_json=payload.metadata_json,
  39. embedding_model=self.settings.embedding_model,
  40. embedding_json=embedding_json,
  41. owner_agent_id=payload.owner_agent_id,
  42. user_id=payload.user_id,
  43. session_id=payload.session_id,
  44. source_ref=payload.source_ref,
  45. importance_score=payload.importance_score,
  46. expires_time=payload.expires_time,
  47. )
  48. def list_memories(
  49. self,
  50. *,
  51. tenant_id: str,
  52. scope_type: MemoryScopeType | None = None,
  53. scope_id: str | None = None,
  54. status: MemoryStatus | None = "active",
  55. limit: int = 100,
  56. ) -> list[MemoryItem]:
  57. return self.memory_repository.list_by_scope(
  58. tenant_id=tenant_id,
  59. scope_type=scope_type,
  60. scope_id=scope_id,
  61. status=status,
  62. limit=limit,
  63. )
  64. def search_memories(
  65. self,
  66. payload: MemorySearchRequest,
  67. ) -> list[tuple[MemoryItem, float, dict[str, float | str]]]:
  68. query_embedding = build_hash_embedding(
  69. payload.query,
  70. dimensions=self.settings.embedding_dimensions,
  71. )
  72. candidates = self.memory_repository.search_candidates(
  73. tenant_id=payload.tenant_id,
  74. scope_type=payload.scope_type,
  75. scope_id=payload.scope_id,
  76. owner_agent_id=payload.owner_agent_id,
  77. user_id=payload.user_id,
  78. session_id=payload.session_id,
  79. limit=max(payload.limit * 10, payload.limit),
  80. )
  81. scored_items = [
  82. self._score(item=item, query=payload.query, query_embedding=query_embedding)
  83. for item in candidates
  84. ]
  85. scored_items.sort(key=lambda item: item[1], reverse=True)
  86. items = [item for item, _, _ in scored_items[: payload.limit]]
  87. now = datetime.utcnow()
  88. self.memory_repository.touch_many(memory_ids=[item.id for item in items], accessed_time=now)
  89. return scored_items[: payload.limit]
  90. def update_memory_status(
  91. self,
  92. *,
  93. memory_id: str,
  94. payload: MemoryStatusUpdateRequest,
  95. ) -> MemoryItem | None:
  96. return self.memory_repository.update_status(
  97. tenant_id=payload.tenant_id,
  98. memory_id=memory_id,
  99. status=payload.status,
  100. )
  101. def _score(
  102. self,
  103. *,
  104. item: MemoryItem,
  105. query: str,
  106. query_embedding: list[float],
  107. ) -> tuple[MemoryItem, float, dict[str, float | str]]:
  108. keyword = keyword_score(query, item.content_text)
  109. vector = cosine_similarity(query_embedding, item.embedding_json)
  110. score = rerank_score(
  111. keyword=keyword,
  112. vector=vector,
  113. importance_score=item.importance_score,
  114. )
  115. return item, score, {
  116. "keyword_score": round(keyword, 6),
  117. "vector_score": round(vector, 6),
  118. "importance_score": float(item.importance_score),
  119. "embedding_model": item.embedding_model or self.settings.embedding_model,
  120. "rerank_mode": "hybrid-local",
  121. }