fix:用户画像修复
This commit is contained in:
@@ -17,11 +17,22 @@ class EmptyMemoryProvider:
|
|||||||
|
|
||||||
|
|
||||||
class MemoryServiceProvider:
|
class MemoryServiceProvider:
|
||||||
"""将现有 MemoryService 的统一上下文转换为投顾侧记忆列表。"""
|
"""将现有 MemoryService 的统一上下文转换为投顾侧记忆列表。
|
||||||
|
|
||||||
def __init__(self, memory_service, *, session_prefix: str = "advisor-agent"):
|
投顾推荐场景**需要**长期记忆(偏好标签、投资目标),因此显式开启
|
||||||
|
``include_long_term=True``。客服 Agent 走默认值 False,不重复召回。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
memory_service,
|
||||||
|
*,
|
||||||
|
session_prefix: str = "advisor-agent",
|
||||||
|
include_long_term: bool = True,
|
||||||
|
):
|
||||||
self.memory_service = memory_service
|
self.memory_service = memory_service
|
||||||
self.session_prefix = session_prefix
|
self.session_prefix = session_prefix
|
||||||
|
self.include_long_term = include_long_term
|
||||||
self.last_warnings: list[str] = []
|
self.last_warnings: list[str] = []
|
||||||
|
|
||||||
async def recall(self, *, customer_id: int, query: str) -> list[dict]:
|
async def recall(self, *, customer_id: int, query: str) -> list[dict]:
|
||||||
@@ -31,6 +42,7 @@ class MemoryServiceProvider:
|
|||||||
customer_id=customer_id,
|
customer_id=customer_id,
|
||||||
session_id=f"{self.session_prefix}:{customer_id}",
|
session_id=f"{self.session_prefix}:{customer_id}",
|
||||||
query=query,
|
query=query,
|
||||||
|
include_long_term=self.include_long_term,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self.last_warnings = [f"advisor_memory_recall_failed:{type(exc).__name__}"]
|
self.last_warnings = [f"advisor_memory_recall_failed:{type(exc).__name__}"]
|
||||||
|
|||||||
+2
-1
@@ -5,7 +5,7 @@ from fastapi import APIRouter
|
|||||||
|
|
||||||
from api.chat import client_agent, customer_agent, knowledge
|
from api.chat import client_agent, customer_agent, knowledge
|
||||||
from api.routers import product, questionnaire
|
from api.routers import product, questionnaire
|
||||||
from api.routers import account, auth, holdings, purchase, redeem, risk, work_order
|
from api.routers import account, auth, holdings, profile, purchase, redeem, risk, work_order
|
||||||
from api.routers import advisor_agent, health, nl2sql, nl2sql_admin
|
from api.routers import advisor_agent, health, nl2sql, nl2sql_admin
|
||||||
from api.advisor import audit, customers, dashboard, data_query, diagnosis, drafts, report, todos, visits
|
from api.advisor import audit, customers, dashboard, data_query, diagnosis, drafts, report, todos, visits
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ api_router.include_router(purchase.router, prefix="/api", tags=["交易"])
|
|||||||
api_router.include_router(redeem.router, prefix="/api", tags=["交易"])
|
api_router.include_router(redeem.router, prefix="/api", tags=["交易"])
|
||||||
api_router.include_router(risk.router, prefix="/api", tags=["风控"])
|
api_router.include_router(risk.router, prefix="/api", tags=["风控"])
|
||||||
api_router.include_router(work_order.router, prefix="/api", tags=["工单"])
|
api_router.include_router(work_order.router, prefix="/api", tags=["工单"])
|
||||||
|
api_router.include_router(profile.router, prefix="/api", tags=["用户画像"])
|
||||||
api_router.include_router(customer_agent.router, prefix="/api/agent/customer", tags=["客服Agent"])
|
api_router.include_router(customer_agent.router, prefix="/api/agent/customer", tags=["客服Agent"])
|
||||||
api_router.include_router(client_agent.router, prefix="/api/agent/client", tags=["ClientAgent"])
|
api_router.include_router(client_agent.router, prefix="/api/agent/client", tags=["ClientAgent"])
|
||||||
api_router.include_router(knowledge.router, prefix="/api/knowledge", tags=["知识库"])
|
api_router.include_router(knowledge.router, prefix="/api/knowledge", tags=["知识库"])
|
||||||
|
|||||||
+38
-10
@@ -48,13 +48,15 @@ class MemoryService:
|
|||||||
self.relations = relations or CustomerRelationMemory()
|
self.relations = relations or CustomerRelationMemory()
|
||||||
self.products = products or CustomerProductMemory()
|
self.products = products or CustomerProductMemory()
|
||||||
self.holdings = holdings or CustomerHoldingsMemory()
|
self.holdings = holdings or CustomerHoldingsMemory()
|
||||||
|
self.long_term = long_term or LongTermMemoryService()
|
||||||
|
# composed 依赖上面三个已初始化的组件:必须先赋值 self.long_term,
|
||||||
|
# 否则默认分支会把 None 传进去(历史 bug)。
|
||||||
self.composed = composed or ComposedProfileService(
|
self.composed = composed or ComposedProfileService(
|
||||||
profile=self.profile,
|
profile=self.profile,
|
||||||
long_term=self.long_term,
|
long_term=self.long_term,
|
||||||
holdings=self.holdings,
|
holdings=self.holdings,
|
||||||
redis=self.short_term.redis,
|
redis=self.short_term.redis,
|
||||||
)
|
)
|
||||||
self.long_term = long_term or LongTermMemoryService()
|
|
||||||
self.archiver = archiver or ConversationArchiver(short_term=self.short_term)
|
self.archiver = archiver or ConversationArchiver(short_term=self.short_term)
|
||||||
self.rank_tool = rank_tool or FinalConfidenceRankTool()
|
self.rank_tool = rank_tool or FinalConfidenceRankTool()
|
||||||
self.interest_tracker = interest_tracker or InterestTopicTracker(self.short_term.redis)
|
self.interest_tracker = interest_tracker or InterestTopicTracker(self.short_term.redis)
|
||||||
@@ -72,8 +74,24 @@ class MemoryService:
|
|||||||
session_id: str,
|
session_id: str,
|
||||||
query: str | None = None,
|
query: str | None = None,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
|
include_long_term: bool = False,
|
||||||
|
include_composed: bool = False,
|
||||||
) -> CustomerMemoryContext:
|
) -> CustomerMemoryContext:
|
||||||
"""召回并组装客服 Agent 当前请求的全部可用记忆。"""
|
"""召回并组装客服 Agent 当前请求的全部可用记忆。
|
||||||
|
|
||||||
|
默认**不含**长期记忆与最终画像:客服 Agent 的职责是知识问答/开户引导/
|
||||||
|
公司信息,合规上不推荐产品,长期记忆(偏好标签、投资目标)对其没有
|
||||||
|
合法用途,却要付出 memory_unit 查询 + Embedding + Milvus 检索的代价。
|
||||||
|
|
||||||
|
长期记忆与最终画像的两个合法消费方各自显式开启:
|
||||||
|
|
||||||
|
- 投顾 Agent:``include_long_term=True``(推荐场景需要偏好与目标);
|
||||||
|
- 最终画像接口(``/profile/composed``,仅员工):直接走
|
||||||
|
``ComposedProfileService.compose_response``,不经本方法。
|
||||||
|
|
||||||
|
写入路径(``save_memory`` / ``record_interest_signal``)不受影响,
|
||||||
|
客服会话仍持续沉淀长期记忆,供投顾与画像使用。
|
||||||
|
"""
|
||||||
if limit < 0:
|
if limit < 0:
|
||||||
raise ValueError("limit 必须是非负整数")
|
raise ValueError("limit 必须是非负整数")
|
||||||
warnings: list[str] = []
|
warnings: list[str] = []
|
||||||
@@ -81,6 +99,7 @@ class MemoryService:
|
|||||||
warnings.extend(self.short_term.last_warnings)
|
warnings.extend(self.short_term.last_warnings)
|
||||||
|
|
||||||
async with self._db() as db:
|
async with self._db() as db:
|
||||||
|
if include_long_term:
|
||||||
try:
|
try:
|
||||||
refresh_confidence = getattr(self.long_term, "refresh_confidence", None)
|
refresh_confidence = getattr(self.long_term, "refresh_confidence", None)
|
||||||
if refresh_confidence is not None:
|
if refresh_confidence is not None:
|
||||||
@@ -106,6 +125,9 @@ class MemoryService:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
customer_products = []
|
customer_products = []
|
||||||
warnings.append(f"customer_product_recall_failed:{type(exc).__name__}")
|
warnings.append(f"customer_product_recall_failed:{type(exc).__name__}")
|
||||||
|
|
||||||
|
memories: list[MemoryUnitDTO] = []
|
||||||
|
if include_long_term:
|
||||||
try:
|
try:
|
||||||
memories, memory_warnings = await self.long_term.recall(
|
memories, memory_warnings = await self.long_term.recall(
|
||||||
db, customer_id, limit=max(limit, 10), query=query
|
db, customer_id, limit=max(limit, 10), query=query
|
||||||
@@ -115,13 +137,9 @@ class MemoryService:
|
|||||||
memories = []
|
memories = []
|
||||||
warnings.append(f"long_term_recall_failed:{type(exc).__name__}")
|
warnings.append(f"long_term_recall_failed:{type(exc).__name__}")
|
||||||
|
|
||||||
ranked = self.rank_tool.rank(
|
|
||||||
[memory.model_dump(mode="json") for memory in memories],
|
|
||||||
top_k=limit,
|
|
||||||
)
|
|
||||||
ranked_memories = [MemoryUnitDTO.model_validate(item) for item in ranked]
|
|
||||||
|
|
||||||
holdings_summary = None
|
holdings_summary = None
|
||||||
|
composed_payload: dict[str, Any] | None = None
|
||||||
|
if include_composed:
|
||||||
try:
|
try:
|
||||||
holdings_summary, holdings_warnings = await self.holdings.summary(
|
holdings_summary, holdings_warnings = await self.holdings.summary(
|
||||||
db, customer_id
|
db, customer_id
|
||||||
@@ -129,20 +147,30 @@ class MemoryService:
|
|||||||
warnings.extend(holdings_warnings)
|
warnings.extend(holdings_warnings)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
warnings.append(f"holdings_recall_failed:{type(exc).__name__}")
|
warnings.append(f"holdings_recall_failed:{type(exc).__name__}")
|
||||||
composed_payload: dict[str, Any] | None = None
|
|
||||||
try:
|
try:
|
||||||
|
# ComposedProfileService 期望 list[dict](与 _recall_memories
|
||||||
|
# 的返回一致),这里传已序列化的记忆,避免在块外再触碰会话。
|
||||||
composed_payload, composed_warnings = await self.composed.compose(
|
composed_payload, composed_warnings = await self.composed.compose(
|
||||||
db,
|
db,
|
||||||
customer_id=customer_id,
|
customer_id=customer_id,
|
||||||
query=query,
|
query=query,
|
||||||
profile=profile,
|
profile=profile,
|
||||||
memories=ranked,
|
memories=[
|
||||||
|
memory.model_dump(mode="json") for memory in memories
|
||||||
|
],
|
||||||
holdings_summary=holdings_summary,
|
holdings_summary=holdings_summary,
|
||||||
)
|
)
|
||||||
warnings.extend(composed_warnings)
|
warnings.extend(composed_warnings)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
warnings.append(f"composed_profile_failed:{type(exc).__name__}")
|
warnings.append(f"composed_profile_failed:{type(exc).__name__}")
|
||||||
|
|
||||||
|
ranked = self.rank_tool.rank(
|
||||||
|
[memory.model_dump(mode="json") for memory in memories],
|
||||||
|
top_k=limit,
|
||||||
|
)
|
||||||
|
ranked_memories = [MemoryUnitDTO.model_validate(item) for item in ranked]
|
||||||
|
|
||||||
return build_customer_memory_context(
|
return build_customer_memory_context(
|
||||||
customer_id=customer_id,
|
customer_id=customer_id,
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user