|
@@ -0,0 +1,137 @@
|
|
|
|
|
+from fastapi import FastAPI, Request
|
|
|
|
|
+from fastapi.responses import JSONResponse
|
|
|
|
|
+from mem0 import Memory
|
|
|
|
|
+
|
|
|
|
|
+app = FastAPI()
|
|
|
|
|
+
|
|
|
|
|
+config = {
|
|
|
|
|
+ "llm": {
|
|
|
|
|
+ "provider": "groq",
|
|
|
|
|
+ "config": {
|
|
|
|
|
+ "model": "llama-3.1-8b-instant",
|
|
|
|
|
+ "temperature": 0.1,
|
|
|
|
|
+ "max_tokens": 1500
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ "vector_store": {
|
|
|
|
|
+ "provider": "chroma",
|
|
|
|
|
+ "config": {
|
|
|
|
|
+ "host": "192.168.0.200",
|
|
|
|
|
+ "port": 8001,
|
|
|
|
|
+ "collection_name": "openclaw_mem"
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ "embedder": {
|
|
|
|
|
+ "provider": "ollama",
|
|
|
|
|
+ "config": {
|
|
|
|
|
+ "model": "nomic-embed-text",
|
|
|
|
|
+ "ollama_base_url": "http://192.168.0.200:11434"
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+memory = Memory.from_config(config)
|
|
|
|
|
+
|
|
|
|
|
+# Patch Chroma empty-filter crash (mem0 sometimes calls search with {} filters)
|
|
|
|
|
+orig_search = memory.vector_store.search
|
|
|
|
|
+
|
|
|
|
|
+def is_effectively_empty(filters):
|
|
|
|
|
+ if not filters:
|
|
|
|
|
+ return True
|
|
|
|
|
+ if filters == {"AND": []} or filters == {"OR": []}:
|
|
|
|
|
+ return True
|
|
|
|
|
+ return False
|
|
|
|
|
+
|
|
|
|
|
+NOOP_WHERE = {"$and": [
|
|
|
|
|
+ {"user_id": {"$ne": ""}},
|
|
|
|
|
+ {"user_id": {"$ne": ""}}
|
|
|
|
|
+]}
|
|
|
|
|
+
|
|
|
|
|
+def safe_search(query, vectors, limit=10, filters=None):
|
|
|
|
|
+ if is_effectively_empty(filters):
|
|
|
|
|
+ return memory.vector_store.collection.query(
|
|
|
|
|
+ query_embeddings=vectors,
|
|
|
|
|
+ n_results=limit,
|
|
|
|
|
+ where=NOOP_WHERE
|
|
|
|
|
+ )
|
|
|
|
|
+ try:
|
|
|
|
|
+ return orig_search(query=query, vectors=vectors, limit=limit, filters=filters)
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ if "Expected where" in str(e):
|
|
|
|
|
+ return memory.vector_store.collection.query(
|
|
|
|
|
+ query_embeddings=vectors,
|
|
|
|
|
+ n_results=limit,
|
|
|
|
|
+ where=NOOP_WHERE
|
|
|
|
|
+ )
|
|
|
|
|
+ raise
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+memory.vector_store.search = safe_search
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@app.post("/memories")
|
|
|
|
|
+async def add_memory(req: Request):
|
|
|
|
|
+ data = await req.json()
|
|
|
|
|
+ text = data.get("text")
|
|
|
|
|
+ user_id = data.get("userId") or data.get("user_id") or "default"
|
|
|
|
|
+ if not text:
|
|
|
|
|
+ return JSONResponse({"error": "Empty 'text' field"}, status_code=400)
|
|
|
|
|
+
|
|
|
|
|
+ result = memory.add(text, user_id=user_id)
|
|
|
|
|
+ print("add_memory:", {"user_id": user_id, "text": text[:80], "result": result})
|
|
|
|
|
+ return result
|
|
|
|
|
+
|
|
|
|
|
+@app.post("/memories/search")
|
|
|
|
|
+async def search(req: Request):
|
|
|
|
|
+ data = await req.json()
|
|
|
|
|
+ query = (data.get("query") or "").strip()
|
|
|
|
|
+ user_id = data.get("userId") or data.get("user_id") or "default"
|
|
|
|
|
+
|
|
|
|
|
+ if not query:
|
|
|
|
|
+ return {"results": []}
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ result = memory.search(query, user_id=user_id)
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ # fallback: get_all + simple text filter
|
|
|
|
|
+ all_res = memory.get_all(user_id=user_id)
|
|
|
|
|
+ if isinstance(all_res, dict):
|
|
|
|
|
+ items = all_res.get("results", [])
|
|
|
|
|
+ elif isinstance(all_res, list):
|
|
|
|
|
+ items = all_res
|
|
|
|
|
+ else:
|
|
|
|
|
+ items = []
|
|
|
|
|
+
|
|
|
|
|
+ q = query.lower()
|
|
|
|
|
+ items = [r for r in items if q in (r.get("memory", "").lower())]
|
|
|
|
|
+ result = {"results": items}
|
|
|
|
|
+
|
|
|
|
|
+ print("search:", {"user_id": user_id, "query": query, "count": len(result.get("results", []))})
|
|
|
|
|
+ limit = int(data.get("limit", 5))
|
|
|
|
|
+ items = result.get("results", [])
|
|
|
|
|
+ items = sorted(items, key=lambda r: r.get("score", float("inf")))[:limit]
|
|
|
|
|
+ result = {"results": items}
|
|
|
|
|
+ print("search:", {"user_id": user_id, "query": query, "count": len(result.get("results", []))})
|
|
|
|
|
+
|
|
|
|
|
+ return result
|
|
|
|
|
+
|
|
|
|
|
+@app.delete("/memories")
|
|
|
|
|
+async def delete(req: Request):
|
|
|
|
|
+ data = await req.json()
|
|
|
|
|
+ return memory.delete(data.get("filter", {}))
|
|
|
|
|
+
|
|
|
|
|
+@app.post("/memories/recent")
|
|
|
|
|
+async def recent(req: Request):
|
|
|
|
|
+ data = await req.json()
|
|
|
|
|
+ user_id = data.get("userId") or data.get("user_id") or "default"
|
|
|
|
|
+ if not user_id:
|
|
|
|
|
+ return JSONResponse({"error":"Missing userId"}, status_code=400)
|
|
|
|
|
+ print("recent payload:", data, "user_id:", user_id)
|
|
|
|
|
+ limit = int(data.get("limit", 5))
|
|
|
|
|
+ try:
|
|
|
|
|
+ results = memory.get_all(user_id=user_id)
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ results = memory.search(query="*", user_id=user_id)
|
|
|
|
|
+ items = results.get("results", [])
|
|
|
|
|
+ items = sorted(items, key=lambda r: r.get("created_at", ""), reverse=True)
|
|
|
|
|
+ return {"results": items[:limit]}
|