from __future__ import annotations from contextlib import asynccontextmanager from fastapi import FastAPI, HTTPException, Query from fastapi.middleware.cors import CORSMiddleware from .config import settings from .db import repository from .embeddings import embedding_client from .registry import agents from .schemas import ChatRequest, ChatResponse, ResetRequest from .adapters.base import AdapterUnavailable @asynccontextmanager async def lifespan(_: FastAPI): await repository.initialize() yield if repository.pool: await repository.pool.close() app = FastAPI(title="Memory Agents Web API", version="0.1.0", lifespan=lifespan) app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000", "http://127.0.0.1:3000"], allow_origin_regex=( r"^https?://(?:192\.168\.\d+\.\d+|10\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[0-1])\.\d+\.\d+):3000$" ), allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/api/health") async def health() -> dict: result = { "status": "ok", "storage": repository.storage_mode, "workspace_id": settings.workspace_id, "llm_configured": bool(settings.llm_api_key), "embedding_configured": embedding_client.configured, } if repository.initialization_error: result["storage_error"] = repository.initialization_error return result @app.get("/api/systems") async def systems(): return { "storage": repository.storage_mode, "systems": [agent.status for agent in agents.values()], } @app.post("/api/chat", response_model=ChatResponse) async def chat(request: ChatRequest): agent = agents[request.system] try: return await agent.chat(request.message) except AdapterUnavailable as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc except Exception as exc: raise HTTPException(status_code=502, detail=f"{request.system} 执行失败:{exc}") from exc @app.get("/api/memories") async def memories(system: str = Query(...), query: str | None = None): if system not in agents: raise HTTPException(status_code=404, detail="未知记忆系统") return {"system": system, "memories": await agents[system].memories(query)} @app.delete("/api/memories/{memory_id}") async def delete_memory(memory_id: str, system: str = Query(...)): if system not in agents: raise HTTPException(status_code=404, detail="未知记忆系统") deleted = await agents[system].delete_memory(memory_id) if not deleted: raise HTTPException(status_code=404, detail="记忆不存在或已删除") return {"status": "ok", "system": system, "memory_id": memory_id} @app.get("/api/audit") async def audit(system: str | None = None): if system is not None and system not in agents: raise HTTPException(status_code=404, detail="未知记忆系统") if system: events = await agents[system].audit() else: events = await repository.list_audit() return {"system": system, "events": events} @app.post("/api/reset") async def reset(request: ResetRequest): if request.system is None: # Some adapters own storage outside the common repository. Calling each # adapter keeps framework files/indexes (notably ReMe) in sync with the # PostgreSQL projection when the user requests a global reset. for agent in agents.values(): await agent.reset() elif request.system in agents: await agents[request.system].reset() else: raise HTTPException(status_code=404, detail="未知记忆系统") return {"status": "ok", "system": request.system}