fischer-agentkit/src/agentkit/session/models.py

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())