feat(memory,conversation): 记忆富化/证据包、时间线幂等字段与对话分段全链路
数据库 - 新增迁移 0003:timeline_events.memory_source_id 外键 → memory_sources,便于按 ingest 源做时间线幂等 后端 - 记忆 - 新增 ingest 后 LLM 富化(摘要/事实/时间线),可配置开关与最大字符数 - 新增证据包组装:合并 chunk、摘要、事实、时间线、故事等检索结果;支持空 query 时是否仍带 rolling 等开关 - repo/retriever/service/router/schemas/summarizer/timeline/extractor 等扩展;文档 memory-retrieval.md 更新 后端 - 对话 WS - 增加 PING/PONG;分段 ASR 日志与空音频处理;转写失败与「无助手回复」错误提示更明确 - 助手多段回复持久化使用统一分隔符,与分段逻辑一致 后端 - Agent - reply_limits:按 [SPLIT] 与段落拆段,并保证非空 fallback,供 WS 与 TTS 多段下发 后端 - 回忆录任务 - transcript ingest 记录 source_id;任务成功结?
This commit is contained in:
@@ -85,6 +85,11 @@ async def websocket_endpoint(
|
||||
return
|
||||
|
||||
await manager.connect(websocket, conversation_id)
|
||||
logger.info(
|
||||
"WebSocket 已连接 conversation_id={} user_id={}",
|
||||
conversation_id,
|
||||
user_id,
|
||||
)
|
||||
|
||||
quota_service = QuotaService(db=db)
|
||||
conversation_service = ConversationService(db=db, quota_service=quota_service)
|
||||
@@ -156,25 +161,30 @@ async def websocket_endpoint(
|
||||
missing_fields=missing_profile,
|
||||
nickname=user.nickname or "",
|
||||
)
|
||||
await ConversationHistoryStore(db).record_ai_only_turn(
|
||||
conversation_id, greetings
|
||||
)
|
||||
for i, text in enumerate(greetings):
|
||||
await manager.send_message(
|
||||
conversation_id,
|
||||
{
|
||||
"type": MessageType.AGENT_RESPONSE,
|
||||
"conversation_id": conversation_id,
|
||||
"data": {
|
||||
"text": text,
|
||||
"index": i,
|
||||
"total": len(greetings),
|
||||
ai_msg_id = await ConversationHistoryStore(
|
||||
db
|
||||
).record_ai_only_turn(conversation_id, greetings)
|
||||
if ai_msg_id:
|
||||
ng = len(greetings)
|
||||
for i, text in enumerate(greetings):
|
||||
await manager.send_message(
|
||||
conversation_id,
|
||||
{
|
||||
"type": MessageType.AGENT_RESPONSE,
|
||||
"conversation_id": conversation_id,
|
||||
"data": {
|
||||
"text": text,
|
||||
"index": i,
|
||||
"total": ng,
|
||||
"assistant_message_id": ai_msg_id,
|
||||
},
|
||||
"timestamp": datetime.now(
|
||||
timezone.utc
|
||||
).isoformat(),
|
||||
},
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
},
|
||||
)
|
||||
if i < len(greetings) - 1:
|
||||
await asyncio.sleep(0.5)
|
||||
)
|
||||
if i < ng - 1:
|
||||
await asyncio.sleep(0.5)
|
||||
except Exception as e:
|
||||
logger.error(f"发送资料收集开场白失败: {e}", exc_info=True)
|
||||
else:
|
||||
@@ -193,25 +203,30 @@ async def websocket_endpoint(
|
||||
user_profile_context=user_profile_context,
|
||||
)
|
||||
)
|
||||
await ConversationHistoryStore(db).record_ai_only_turn(
|
||||
conversation_id, opening_messages
|
||||
)
|
||||
for i, text in enumerate(opening_messages):
|
||||
await manager.send_message(
|
||||
conversation_id,
|
||||
{
|
||||
"type": MessageType.AGENT_RESPONSE,
|
||||
"conversation_id": conversation_id,
|
||||
"data": {
|
||||
"text": text,
|
||||
"index": i,
|
||||
"total": len(opening_messages),
|
||||
ai_msg_id = await ConversationHistoryStore(
|
||||
db
|
||||
).record_ai_only_turn(conversation_id, opening_messages)
|
||||
if ai_msg_id:
|
||||
no = len(opening_messages)
|
||||
for i, text in enumerate(opening_messages):
|
||||
await manager.send_message(
|
||||
conversation_id,
|
||||
{
|
||||
"type": MessageType.AGENT_RESPONSE,
|
||||
"conversation_id": conversation_id,
|
||||
"data": {
|
||||
"text": text,
|
||||
"index": i,
|
||||
"total": no,
|
||||
"assistant_message_id": ai_msg_id,
|
||||
},
|
||||
"timestamp": datetime.now(
|
||||
timezone.utc
|
||||
).isoformat(),
|
||||
},
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
},
|
||||
)
|
||||
if i < len(opening_messages) - 1:
|
||||
await asyncio.sleep(0.5)
|
||||
)
|
||||
if i < no - 1:
|
||||
await asyncio.sleep(0.5)
|
||||
except Exception as e:
|
||||
logger.error(f"发送空对话开场白失败: {e}", exc_info=True)
|
||||
|
||||
@@ -225,6 +240,29 @@ async def websocket_endpoint(
|
||||
break
|
||||
message = await websocket.receive_json()
|
||||
msg_type = message.get("type")
|
||||
if msg_type == MessageType.AUDIO_SEGMENT:
|
||||
_d = message.get("data") or {}
|
||||
logger.info(
|
||||
"WebSocket 收到消息 type={} conversation_id={} "
|
||||
"segment_index={} is_last={} duration_s={} audio_b64_len={}",
|
||||
msg_type,
|
||||
conversation_id,
|
||||
_d.get("segment_index"),
|
||||
bool(_d.get("is_last")),
|
||||
int(_d.get("duration") or 0),
|
||||
len(_d.get("audio_base64") or ""),
|
||||
)
|
||||
elif msg_type is not None:
|
||||
logger.info(
|
||||
"WebSocket 收到消息 type={} conversation_id={}",
|
||||
msg_type,
|
||||
conversation_id,
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"WebSocket 收到缺少 type 的 JSON conversation_id={}",
|
||||
conversation_id,
|
||||
)
|
||||
|
||||
if msg_type == MessageType.TEXT:
|
||||
text_message = message.get("data", {}).get("text", "")
|
||||
@@ -628,6 +666,25 @@ async def websocket_endpoint(
|
||||
)
|
||||
break
|
||||
|
||||
elif msg_type == MessageType.PING:
|
||||
await manager.send_message(
|
||||
conversation_id,
|
||||
{
|
||||
"type": MessageType.PONG,
|
||||
"conversation_id": conversation_id,
|
||||
"data": {},
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
},
|
||||
)
|
||||
|
||||
else:
|
||||
if msg_type is not None:
|
||||
logger.warning(
|
||||
"WebSocket 未识别的消息 type={} conversation_id={}",
|
||||
msg_type,
|
||||
conversation_id,
|
||||
)
|
||||
|
||||
except RuntimeError as e:
|
||||
error_msg = str(e)
|
||||
if (
|
||||
@@ -659,9 +716,11 @@ async def websocket_endpoint(
|
||||
except Exception as send_error:
|
||||
logger.warning(f"发送错误消息失败: {send_error}")
|
||||
break
|
||||
except WebSocketDisconnect:
|
||||
logger.debug(
|
||||
"WebSocket 断开连接: conversation_id={}", conversation_id
|
||||
except WebSocketDisconnect as disc:
|
||||
logger.info(
|
||||
"WebSocket 断开连接(收消息循环): conversation_id={} code={}",
|
||||
conversation_id,
|
||||
getattr(disc, "code", None),
|
||||
)
|
||||
break
|
||||
except Exception as e:
|
||||
@@ -680,8 +739,12 @@ async def websocket_endpoint(
|
||||
logger.warning(f"发送错误消息失败: {send_error}")
|
||||
break
|
||||
|
||||
except WebSocketDisconnect:
|
||||
logger.debug("WebSocket 断开连接: conversation_id={}", conversation_id)
|
||||
except WebSocketDisconnect as disc:
|
||||
logger.info(
|
||||
"WebSocket 断开连接: conversation_id={} code={}",
|
||||
conversation_id,
|
||||
getattr(disc, "code", None),
|
||||
)
|
||||
await manager.disconnect(conversation_id)
|
||||
cleanup_segment_states(conversation_id)
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user