feat:客户agent以及记忆模块功能开发
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""客户画像中期记忆:MySQL 事实源 + Redis Cache-Aside。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
from config.database.redis import client as redis_client
|
||||
from repositories.fin_customer_profile import FinCustomerProfileRepo
|
||||
|
||||
|
||||
class CustomerProfileMemory:
|
||||
"""提供客户画像读取、缓存失效和刷新能力。"""
|
||||
|
||||
CACHE_TTL = 7 * 24 * 60 * 60
|
||||
|
||||
def __init__(self, *, redis=None, repository_factory=FinCustomerProfileRepo):
|
||||
self.redis = redis or redis_client()
|
||||
self.repository_factory = repository_factory
|
||||
|
||||
@staticmethod
|
||||
def cache_key(customer_id: int) -> str:
|
||||
"""生成客户画像缓存 Key。"""
|
||||
return f"profile:{customer_id}"
|
||||
|
||||
async def get(self, db, customer_id: int) -> tuple[dict[str, Any] | None, list[str]]:
|
||||
"""优先读取缓存,未命中后回源 MySQL,并返回 warnings。"""
|
||||
warnings: list[str] = []
|
||||
key = self.cache_key(customer_id)
|
||||
try:
|
||||
cached = await self.redis.get(key)
|
||||
if cached:
|
||||
return json.loads(cached), warnings
|
||||
except Exception as exc:
|
||||
warnings.append(f"profile_cache_read_failed:{type(exc).__name__}")
|
||||
|
||||
try:
|
||||
profile = await self.repository_factory(db).get_by_customer_id(customer_id)
|
||||
except Exception as exc:
|
||||
warnings.append(f"profile_mysql_read_failed:{type(exc).__name__}")
|
||||
return None, warnings
|
||||
if profile is None:
|
||||
return None, warnings
|
||||
|
||||
payload = self._to_dict(profile)
|
||||
try:
|
||||
await self.redis.set(key, json.dumps(payload, ensure_ascii=False), ex=self.CACHE_TTL)
|
||||
except Exception as exc:
|
||||
warnings.append(f"profile_cache_write_failed:{type(exc).__name__}")
|
||||
return payload, warnings
|
||||
|
||||
async def invalidate(self, customer_id: int) -> list[str]:
|
||||
"""删除客户画像缓存,确保更新后不会长期读取旧值。"""
|
||||
try:
|
||||
await self.redis.delete(self.cache_key(customer_id))
|
||||
return []
|
||||
except Exception as exc:
|
||||
return [f"profile_cache_invalidate_failed:{type(exc).__name__}"]
|
||||
|
||||
async def refresh(self, db, customer_id: int) -> tuple[dict[str, Any] | None, list[str]]:
|
||||
"""先删除缓存,再从 MySQL 读取并重新缓存画像。"""
|
||||
warnings = await self.invalidate(customer_id)
|
||||
profile, read_warnings = await self.get(db, customer_id)
|
||||
return profile, warnings + read_warnings
|
||||
|
||||
@staticmethod
|
||||
def _to_dict(profile) -> dict[str, Any]:
|
||||
"""将 ORM 画像转换为可安全写入 Redis 的字典。"""
|
||||
data = {
|
||||
"customer_id": profile.customer_id,
|
||||
"risk_level": profile.risk_level,
|
||||
"risk_score": profile.risk_score,
|
||||
"investment_experience": profile.investment_experience,
|
||||
"annual_income_range": profile.annual_income_range,
|
||||
"total_assets": profile.total_assets,
|
||||
"asset_allocation": profile.asset_allocation,
|
||||
"product_preference": profile.product_preference,
|
||||
"customer_level": profile.customer_level,
|
||||
"confidence_score": profile.confidence_score,
|
||||
"profile_version": profile.profile_version,
|
||||
"update_time": profile.update_time.isoformat() if profile.update_time else None,
|
||||
}
|
||||
return json.loads(json.dumps(data, default=lambda value: str(value), ensure_ascii=False))
|
||||
|
||||
|
||||
__all__ = ["CustomerProfileMemory"]
|
||||
|
||||
Reference in New Issue
Block a user