125 lines
4.0 KiB
Python
125 lines
4.0 KiB
Python
"""Session and Message data models for multi-turn conversations."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime, timezone
|
|
from enum import Enum
|
|
|
|
|
|
class SessionStatus(str, Enum):
|
|
"""Session lifecycle states."""
|
|
|
|
ACTIVE = "active"
|
|
PAUSED = "paused"
|
|
CLOSED = "closed"
|
|
|
|
|
|
class MessageRole(str, Enum):
|
|
"""Message role — mirrors OpenAI chat message roles."""
|
|
|
|
SYSTEM = "system"
|
|
USER = "user"
|
|
ASSISTANT = "assistant"
|
|
TOOL = "tool"
|
|
|
|
|
|
@dataclass
|
|
class Message:
|
|
"""A single message within a conversation session.
|
|
|
|
Maps directly to the ``messages`` list consumed by the ReAct engine.
|
|
"""
|
|
|
|
message_id: str
|
|
session_id: str
|
|
role: MessageRole
|
|
content: str
|
|
tool_call_id: str | None = None
|
|
agent_name: str | None = None
|
|
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
metadata: dict[str, object] = field(default_factory=dict)
|
|
|
|
def to_dict(self) -> dict[str, object]:
|
|
return {
|
|
"message_id": self.message_id,
|
|
"session_id": self.session_id,
|
|
"role": self.role.value,
|
|
"content": self.content,
|
|
"tool_call_id": self.tool_call_id,
|
|
"agent_name": self.agent_name,
|
|
"created_at": self.created_at.isoformat(),
|
|
"metadata": self.metadata,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, object]) -> Message:
|
|
return cls(
|
|
message_id=data["message_id"],
|
|
session_id=data["session_id"],
|
|
role=MessageRole(data["role"]),
|
|
content=data["content"],
|
|
tool_call_id=data.get("tool_call_id"),
|
|
agent_name=data.get("agent_name"),
|
|
created_at=datetime.fromisoformat(data["created_at"]) if data.get("created_at") else datetime.now(timezone.utc),
|
|
metadata=data.get("metadata", {}),
|
|
)
|
|
|
|
def to_chat_message(self) -> dict[str, str]:
|
|
"""Convert to OpenAI-compatible chat message dict.
|
|
|
|
Returns a dict suitable for the ``messages`` parameter of LLM chat APIs.
|
|
"""
|
|
msg: dict[str, str] = {"role": self.role.value, "content": self.content}
|
|
if self.tool_call_id is not None:
|
|
msg["tool_call_id"] = self.tool_call_id
|
|
return msg
|
|
|
|
|
|
@dataclass
|
|
class Session:
|
|
"""A conversation session binding a user to an Agent.
|
|
|
|
Sessions track lifecycle state and accumulate Messages. They are
|
|
persisted via :class:`SessionStore` backends.
|
|
"""
|
|
|
|
session_id: str
|
|
agent_name: str
|
|
status: SessionStatus = SessionStatus.ACTIVE
|
|
metadata: dict[str, object] = field(default_factory=dict)
|
|
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
|
|
def to_dict(self) -> dict[str, object]:
|
|
return {
|
|
"session_id": self.session_id,
|
|
"agent_name": self.agent_name,
|
|
"status": self.status.value,
|
|
"metadata": self.metadata,
|
|
"created_at": self.created_at.isoformat(),
|
|
"updated_at": self.updated_at.isoformat(),
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, object]) -> Session:
|
|
return cls(
|
|
session_id=data["session_id"],
|
|
agent_name=data["agent_name"],
|
|
status=SessionStatus(data.get("status", "active")),
|
|
metadata=data.get("metadata", {}),
|
|
created_at=datetime.fromisoformat(data["created_at"]) if data.get("created_at") else datetime.now(timezone.utc),
|
|
updated_at=datetime.fromisoformat(data["updated_at"]) if data.get("updated_at") else datetime.now(timezone.utc),
|
|
)
|
|
|
|
@staticmethod
|
|
def new_session_id() -> str:
|
|
"""Generate a new session ID."""
|
|
return str(uuid.uuid4())
|
|
|
|
@staticmethod
|
|
def new_message_id() -> str:
|
|
"""Generate a new message ID."""
|
|
return str(uuid.uuid4())
|