Files
mini-openclaw/backend/api/sessions.py
NathanAlex7 42ce992a0f chore: mini-openclaw 初始版本
Next.js 前端 + FastAPI/LangChain 后端透明 agent平台
- 含 .gitignore, 敏感文件(sessions数据/.env/venv等)已排除
- 待后续添加 trajectory 自进化机制
2026-09-01 16:40:10 +08:00

124 lines
4.4 KiB
Python

"""Session CRUD API — list / create / rename / delete / raw messages / generate title."""
import os
import uuid
from pathlib import Path
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from graph.session_manager import session_manager
from graph.prompt_builder import build_system_prompt
router = APIRouter()
BASE_DIR = Path(__file__).resolve().parent.parent
# ── Request models ──────────────────────────────────────────
class RenameRequest(BaseModel):
title: str
# ── Endpoints ───────────────────────────────────────────────
@router.get("/sessions")
async def list_sessions():
"""List all sessions with title and metadata."""
sessions = session_manager.list_sessions()
return {"sessions": sessions}
@router.post("/sessions")
async def create_session():
"""Create a new empty session."""
session_id = f"session-{uuid.uuid4().hex[:12]}"
meta = session_manager.create_session(session_id)
return meta
@router.put("/sessions/{session_id}")
async def rename_session(session_id: str, req: RenameRequest):
"""Rename an existing session."""
try:
session_manager.rename_session(session_id, req.title)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Session not found")
return {"id": session_id, "title": req.title}
@router.delete("/sessions/{session_id}")
async def delete_session(session_id: str):
"""Delete a session."""
session_manager.delete_session(session_id)
return {"status": "deleted", "id": session_id}
@router.get("/sessions/{session_id}/messages")
async def get_raw_messages(session_id: str):
"""Get complete raw messages including system prompt."""
data = session_manager.get_raw_messages(session_id)
system_prompt = build_system_prompt(BASE_DIR)
# Prepend system prompt as the first message
all_messages = [{"role": "system", "content": system_prompt}] + data.get("messages", [])
return {"session_id": session_id, "title": data.get("title", ""), "messages": all_messages}
@router.get("/sessions/{session_id}/history")
async def get_session_history(session_id: str):
"""Get conversation history for display (no system prompt, includes tool_calls)."""
messages = session_manager.load_session(session_id)
return {"session_id": session_id, "messages": messages}
@router.post("/sessions/{session_id}/generate-title")
async def generate_title(session_id: str):
"""Use DeepSeek to generate a short title from the first conversation turn."""
messages = session_manager.load_session(session_id)
if not messages:
raise HTTPException(status_code=400, detail="No messages to generate title from")
# Get the first user message and first assistant reply
first_user = ""
first_assistant = ""
for msg in messages:
if msg["role"] == "user" and not first_user:
first_user = msg["content"][:200]
elif msg["role"] == "assistant" and not first_assistant:
first_assistant = msg["content"][:200]
if first_user and first_assistant:
break
if not first_user:
raise HTTPException(status_code=400, detail="No user message found")
try:
from langchain_deepseek import ChatDeepSeek
from langchain_core.messages import HumanMessage as HM
llm = ChatDeepSeek(
model=os.getenv("DEEPSEEK_MODEL", "deepseek-chat"),
api_key=os.getenv("DEEPSEEK_API_KEY"),
api_base=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
temperature=0.3,
)
prompt = (
f"根据以下对话内容,生成一个不超过10个字的中文标题,只输出标题文本,不要加引号或标点。\n\n"
f"用户: {first_user}\n"
f"助手: {first_assistant}"
)
result = await llm.ainvoke([HM(content=prompt)])
title = result.content.strip().strip('"\'""''')[:20]
session_manager.update_title(session_id, title)
return {"session_id": session_id, "title": title}
except Exception as e:
# Fallback: use first few chars of user message
fallback_title = first_user[:10].strip()
session_manager.update_title(session_id, fallback_title)
return {"session_id": session_id, "title": fallback_title}