wip: 客服Agent + RAG + 画像收尾(基于 6516ccb)

This commit is contained in:
2026-09-11 14:46:40 +08:00
parent 870fd0d44e
commit e4c4099aaa
82 changed files with 13901 additions and 2426 deletions
+50
View File
@@ -20,6 +20,8 @@ from app.repository.agent_run_repository import AgentRunRepository
from app.service.agent.bootstrap import (
get_agent_factory,
get_memory_cache_adapter,
get_memory_embedding_service,
get_milvus_knowledge_writer,
get_model_service,
)
from app.service.agent.executor import AgentExecutor
@@ -40,6 +42,7 @@ from app.worker.episode_worker import (
EpisodeExtractionConsumer,
EpisodeWorker,
)
from app.worker.knowledge_vector_worker import build_knowledge_handlers
from app.worker.memory_extraction_worker import MemoryExtractionWorker
from app.worker.outbox_worker import OutboxWorker
@@ -56,6 +59,11 @@ OUTBOX_DISPATCH_LIMIT = 10
EPISODE_CONSUME_LIMIT = 20
PROJECTION_AUDIT_ACTION = "memory.projection_cleanup"
#: 组装层默认值的哨兵。知识写路径的三个依赖都要区分"没传(取生产装配)"与"显式
#: `None`(显式降级)":`None` 若同时表示两者,测试里就无法在不改环境变量的前提下
#: 构造"Milvus 未配置"的场景,降级行为也就无法被固定。
_UNSET: Any = object()
@dataclass(frozen=True)
class ProjectionCleanupOutcome:
@@ -80,6 +88,9 @@ class WorkerRuntime:
memory_cache: CacheDeleteAdapter | None = None,
projection_cleaner: ProjectionCleaner | None = None,
relationships: Any = None,
knowledge_writer: Any = _UNSET,
knowledge_embedder: Any = _UNSET,
knowledge_endpoint_resolver: Any = _UNSET,
) -> None:
self.factory = factory if factory is not None else get_agent_factory()
self.settings = settings or get_settings()
@@ -110,6 +121,23 @@ class WorkerRuntime:
from app.service.agent.bootstrap import get_relationship_service
self.relationships = get_relationship_service()
# 知识向量同步:Milvus 写适配器 + 嵌入服务 + 端点解析器全部由组装层注入
# (`app/service/agent/bootstrap.py`)。写适配器构造是**惰性**的(不连 Milvus),
# 所以这里取默认值不会让 worker 起不来;真连不上时在写入时抛
# `RecoverableAgentError`,交给 OutboxWorker 的退避重试与死信机制。
self.knowledge_writer = (
get_milvus_knowledge_writer() if knowledge_writer is _UNSET else knowledge_writer
)
self.knowledge_embedder = (
get_memory_embedding_service() if knowledge_embedder is _UNSET
else knowledge_embedder
)
self.knowledge_endpoint_resolver = (
DatabaseModelEndpointResolver() if knowledge_endpoint_resolver is _UNSET
else knowledge_endpoint_resolver
)
# 降级告警只打一次:dispatch 是轮询热路径,每轮一条 warning 会把日志淹掉。
self._knowledge_degraded_logged = False
# episode 聚合是低频批处理,按轮次节流而不是每轮都查。
self._episode_rounds = 0
@@ -201,6 +229,28 @@ class WorkerRuntime:
# 画像重建:记忆写入后自动触发,使「记忆 → 画像 → 图」全链路无需手工介入
"profile.rebuild_requested": dispatch_profile_rebuild,
}
# 知识向量同步/删除:Task 5 交付了 handler 与写适配器,但先前没有任何生产装配
# 调用它们 —— 事件类型不在上面的白名单里,`OutboxWorker.publish_one` 的
# `event_type.in_(tuple(self.handlers))` 就永远领不到这些行,现库 408 条
# `knowledge.vector_sync_requested` 因此永久 pending、Milvus 零向量、
# 检索永远返回空。这里把两个 handler 合并进同一个字典(**同一个 `session`**,
# handler 不得 commit,事务仍归 OutboxWorker)。
if self.knowledge_writer is not None:
handlers.update(build_knowledge_handlers(
session,
writer=self.knowledge_writer,
embedder=self.knowledge_embedder,
endpoint_resolver=self.knowledge_endpoint_resolver,
))
elif not self._knowledge_degraded_logged:
# 显式降级 + 留痕:不注册 handler,知识事件保持 pending(库里可查、可重放),
# 绝不伪造"已同步"。与 `projection_cleaner` 的降级口径一致。
self._knowledge_degraded_logged = True
logger.warning(
"knowledge vector handlers not registered: milvus writer unavailable "
"(check settings.milvus_uri); knowledge.vector_sync_requested / "
"knowledge.vector_delete_requested stay pending"
)
return await OutboxWorker(session, handlers).publish_one(aggregate_id=run_id)
async def dispatch_batch(self, *, limit: int = OUTBOX_DISPATCH_LIMIT) -> int: