feat: message persistence, traceability and empty response auto-retry
This commit is contained in:
parent
16ac592855
commit
2c5e90104d
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Reference in New Issue