feat: add customer profile candidate flow
This commit is contained in:
@@ -40,6 +40,7 @@ from app.service.memory_recall_service import MemoryRecallService
|
||||
from app.service.memory_service import CacheDeleteAdapter, MemoryService
|
||||
from app.service.memory_taxonomy import BUSINESS_EVENT_TYPES
|
||||
from app.service.model_gateway import DatabaseModelEndpointResolver, ModelGenerationService
|
||||
from app.worker.customer_profile_candidate_worker import CustomerProfileCandidateWorker
|
||||
from app.worker.episode_worker import (
|
||||
EpisodeConsumptionResult,
|
||||
EpisodeExtractionConsumer,
|
||||
@@ -145,6 +146,20 @@ class WorkerRuntime:
|
||||
signals=MemoryService.detect_memory_signals(message),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def should_request_profile_candidate(
|
||||
*, agent_type: str, context: RequestContext, message: str,
|
||||
) -> bool:
|
||||
"""客服只为已登录客户生成候选,不为访客生成正式画像数据。"""
|
||||
if agent_type != "customer_service" or "visitor" in context.roles:
|
||||
return False
|
||||
if not {"customer", "authenticated_user"}.intersection(context.roles):
|
||||
return False
|
||||
if context.data_scope != "self":
|
||||
return False
|
||||
# 只有明确的长期偏好、约束或目标才进入候选队列,普通问答不触发模型抽取。
|
||||
return bool(MemoryService.detect_memory_signals(message))
|
||||
|
||||
async def dispatch_one(self, *, run_id: str | None = None) -> bool:
|
||||
# Outbox acknowledges a durable SQL queue entry, not an in-memory task.
|
||||
async with SessionFactory() as session:
|
||||
@@ -162,6 +177,13 @@ class WorkerRuntime:
|
||||
session, extractor=self.memory_extraction, cache=self.memory_cache
|
||||
).handle(payload)
|
||||
|
||||
async def dispatch_profile_candidate(payload: dict[str, Any]) -> None:
|
||||
if "message_id" not in payload or "customer_id" not in payload:
|
||||
raise ValueError("profile candidate payload is incomplete")
|
||||
await CustomerProfileCandidateWorker(
|
||||
session, extractor=self.memory_extraction, cache=self.memory_cache
|
||||
).handle(payload)
|
||||
|
||||
async def dispatch_run_completed(payload: dict[str, Any]) -> None:
|
||||
# 结果消息与审计已由 complete_run 同事务落库,此事件只承担
|
||||
# "运行已完成"的对外通知职责。当前没有独立外部消费者,
|
||||
@@ -224,6 +246,7 @@ class WorkerRuntime:
|
||||
handlers: dict[str, Callable[[dict[str, Any]], Awaitable[None]]] = {
|
||||
"agent.run_requested": dispatch,
|
||||
"memory.extraction_requested": dispatch_memory_extraction,
|
||||
"customer_profile.candidate_requested": dispatch_profile_candidate,
|
||||
"agent.run_completed": dispatch_run_completed,
|
||||
"config.cache_invalidate_requested": dispatch_cache_invalidate,
|
||||
"memory.deletion_requested": dispatch_memory_deletion,
|
||||
@@ -536,6 +559,9 @@ class WorkerRuntime:
|
||||
result=result,
|
||||
business_events=business_events,
|
||||
),
|
||||
profile_candidate_requested=self.should_request_profile_candidate(
|
||||
agent_type=run.agent_type, context=context, message=request.message,
|
||||
),
|
||||
)
|
||||
# 只有数据库成功保存完整用户/助手轮次后才写短期 Redis;Redis 故障不应让已完成
|
||||
# 的客服回答回滚或重试。短期组件会再次脱敏,形成持久化链路的第二道保护。
|
||||
|
||||
Reference in New Issue
Block a user