feat: message persistence, traceability and empty response auto-retry

This commit is contained in:
chiguyong 2026-06-16 08:13:22 +08:00
parent 16ac592855
commit 2c5e90104d
2 changed files with 97 additions and 21 deletions

View File

@ -680,6 +680,9 @@ def create_app(
from agentkit.server.routes.portal import _conversation_store
_conversation_store.set_session_manager(app.state.session_manager)
# Restore conversation history from persistent store
await _conversation_store.restore_from_store()
# Initialize evolution store if configured
if server_config and hasattr(server_config, "evolution") and server_config.evolution:
try:

View File

@ -117,6 +117,41 @@ class ConversationStore:
"""Set or update the session manager for persistence."""
self._session_manager = sm
async def restore_from_store(self) -> None:
"""Restore recent conversations from SessionManager on startup.
Loads the most recent sessions and their messages so that
ConversationStore is populated after a server restart.
"""
if self._session_manager is None:
return
try:
sessions = await self._session_manager.list_sessions(limit=self._max)
for session in sessions:
sid = session.session_id
if sid in self._conversations:
continue
# Reconstruct Conversation from persisted session
conv = Conversation(
id=sid,
created_at=session.created_at,
updated_at=session.updated_at,
)
messages = await self._session_manager.get_messages(sid)
for msg in messages:
conv.messages.append(ChatMessage(
role=msg.role.value,
content=msg.content,
timestamp=msg.created_at,
metadata=msg.metadata,
))
self._conversations[sid] = conv
logger.info(
f"Restored {len(self._conversations)} conversations from SessionManager"
)
except Exception as e:
logger.warning(f"Failed to restore conversations from SessionManager: {e}")
def get_or_create(self, conversation_id: str | None = None) -> Conversation:
if conversation_id and conversation_id in self._conversations:
conv = self._conversations[conversation_id]
@ -277,6 +312,7 @@ class ChatRequest(BaseModel):
class ChatResponse(BaseModel):
conversation_id: str
message: str
timestamp: str = ""
matched_skill: str | None = None
routing_method: str | None = None
confidence: float | None = None
@ -459,6 +495,7 @@ async def chat(request: ChatRequest, req: Request, _auth: None = Depends(_verify
return ChatResponse(
conversation_id=conv.id,
message=response_text,
timestamp=datetime.now(timezone.utc).isoformat(),
matched_skill=matched_skill,
routing_method=routing_method,
confidence=confidence,
@ -640,9 +677,9 @@ def _derive_conversation_title(conv: Conversation) -> str:
async def get_conversation(
conversation_id: str, limit: int = 50, _auth: None = Depends(_verify_api_key)
):
"""Get conversation history."""
if conversation_id not in _conversation_store._conversations:
raise HTTPException(status_code=404, detail=f"Conversation '{conversation_id}' not found")
"""Get conversation history, with fallback to SessionManager for persisted data."""
# Try in-memory first
if conversation_id in _conversation_store._conversations:
conv = _conversation_store._conversations[conversation_id]
history = _conversation_store.get_history(conversation_id, limit=limit)
return {
@ -662,6 +699,42 @@ async def get_conversation(
"updated_at": conv.updated_at.isoformat(),
}
# Fallback: load from SessionManager (persistent store)
sm = _conversation_store._session_manager
if sm is not None:
try:
session = await sm.get_session(conversation_id)
if session is not None:
messages = await sm.get_messages(conversation_id, limit=limit)
return {
"id": session.session_id,
"title": _derive_title_from_messages(messages),
"messages": [
{
"id": f"{session.session_id}-{i}",
"role": m.role.value,
"content": m.content,
"timestamp": m.created_at.isoformat(),
"metadata": m.metadata,
}
for i, m in enumerate(messages)
],
"created_at": session.created_at.isoformat(),
"updated_at": session.updated_at.isoformat(),
}
except Exception as e:
logger.warning(f"Failed to load conversation from SessionManager: {e}")
raise HTTPException(status_code=404, detail=f"Conversation '{conversation_id}' not found")
def _derive_title_from_messages(messages: list) -> str:
"""Derive title from a list of Message objects (SessionManager format)."""
for msg in messages:
if msg.role.value == "user" and msg.content:
return msg.content[:20] + ("..." if len(msg.content) > 20 else "")
return "对话"
@router.websocket("/portal/ws")
async def portal_websocket(websocket: WebSocket):