Files
openai--openai-agents-python/examples/memory/file_session.py
T
2026-07-13 12:39:17 +08:00

125 lines
4.9 KiB
Python

"""
Simple file-backed session implementation for examples.
Persists conversation history as JSON on disk so runs can resume across processes.
"""
from __future__ import annotations
import asyncio
import json
from datetime import datetime
from pathlib import Path
from typing import Any
from uuid import uuid4
from agents.memory.session import Session
from agents.memory.session_settings import SessionSettings
class FileSession(Session):
"""Persist session items to a JSON file on disk."""
session_settings: SessionSettings | None = None
def __init__(self, *, dir: str | Path | None = None, session_id: str | None = None) -> None:
self._dir = Path(dir) if dir is not None else Path.cwd() / ".agents-sessions"
self.session_id = session_id or ""
# Ensure the directory exists up front so subsequent file operations do not race.
self._dir.mkdir(parents=True, exist_ok=True)
async def _ensure_session_id(self) -> str:
if not self.session_id:
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
# Prefix with wall-clock time so recent sessions are easy to spot on disk.
self.session_id = f"{timestamp}-{uuid4().hex[:12]}"
await asyncio.to_thread(self._dir.mkdir, parents=True, exist_ok=True)
file_path = self._items_path(self.session_id)
if not file_path.exists():
await asyncio.to_thread(file_path.write_text, "[]", encoding="utf-8")
return self.session_id
async def get_session_id(self) -> str:
"""Return the session id, creating one if needed."""
return await self._ensure_session_id()
async def get_items(self, limit: int | None = None) -> list[Any]:
session_id = await self._ensure_session_id()
items = await self._read_items(session_id)
if limit is not None and limit >= 0:
return items[-limit:]
return items
async def add_items(self, items: list[Any]) -> None:
if not items:
return
session_id = await self._ensure_session_id()
current = await self._read_items(session_id)
# Deep-copy via JSON to avoid persisting live references that might mutate later.
cloned = json.loads(json.dumps(items))
await self._write_items(session_id, current + cloned)
async def pop_item(self) -> Any | None:
session_id = await self._ensure_session_id()
items = await self._read_items(session_id)
if not items:
return None
popped = items.pop()
await self._write_items(session_id, items)
return popped
async def clear_session(self) -> None:
if not self.session_id:
return
file_path = self._items_path(self.session_id)
state_path = self._state_path(self.session_id)
try:
await asyncio.to_thread(file_path.unlink)
except FileNotFoundError:
pass
try:
await asyncio.to_thread(state_path.unlink)
except FileNotFoundError:
pass
self.session_id = ""
def _items_path(self, session_id: str) -> Path:
return self._dir / f"{session_id}.json"
def _state_path(self, session_id: str) -> Path:
return self._dir / f"{session_id}-state.json"
async def _read_items(self, session_id: str) -> list[Any]:
file_path = self._items_path(session_id)
try:
data = await asyncio.to_thread(file_path.read_text, "utf-8")
parsed = json.loads(data)
return parsed if isinstance(parsed, list) else []
except FileNotFoundError:
return []
async def _write_items(self, session_id: str, items: list[Any]) -> None:
file_path = self._items_path(session_id)
payload = json.dumps(items, indent=2, ensure_ascii=False)
await asyncio.to_thread(self._dir.mkdir, parents=True, exist_ok=True)
await asyncio.to_thread(file_path.write_text, payload, encoding="utf-8")
async def load_state_json(self) -> dict[str, Any] | None:
"""Load a previously saved RunState JSON payload, if present."""
session_id = await self._ensure_session_id()
state_path = self._state_path(session_id)
try:
data = await asyncio.to_thread(state_path.read_text, "utf-8")
parsed = json.loads(data)
return parsed if isinstance(parsed, dict) else None
except FileNotFoundError:
return None
async def save_state_json(self, state: dict[str, Any]) -> None:
"""Persist the serialized RunState JSON payload alongside session items."""
session_id = await self._ensure_session_id()
state_path = self._state_path(session_id)
payload = json.dumps(state, indent=2, ensure_ascii=False)
await asyncio.to_thread(self._dir.mkdir, parents=True, exist_ok=True)
await asyncio.to_thread(state_path.write_text, payload, encoding="utf-8")