Spaces:
Sleeping
Sleeping
Commit ·
0e365b9
1
Parent(s): 3dbf4e8
Added app folder and backend services
Browse files- app/__pycache__/main.cpython-310.pyc +0 -0
- app/__pycache__/main.cpython-311.pyc +0 -0
- app/__pycache__/main.cpython-313.pyc +0 -0
- app/main.py +33 -0
- app/routes/__pycache__/chat.cpython-310.pyc +0 -0
- app/routes/__pycache__/chat.cpython-311.pyc +0 -0
- app/routes/__pycache__/chat.cpython-313.pyc +0 -0
- app/routes/chat.py +284 -0
- app/services/__pycache__/graph_builder.cpython-310.pyc +0 -0
- app/services/__pycache__/graph_builder.cpython-311.pyc +0 -0
- app/services/__pycache__/graph_builder.cpython-313.pyc +0 -0
- app/services/__pycache__/graph_visualizer.cpython-311.pyc +0 -0
- app/services/__pycache__/graph_visualizer.cpython-313.pyc +0 -0
- app/services/__pycache__/memory_engine.cpython-310.pyc +0 -0
- app/services/__pycache__/memory_engine.cpython-311.pyc +0 -0
- app/services/__pycache__/nlp_triplet.cpython-310.pyc +0 -0
- app/services/__pycache__/nlp_triplet.cpython-311.pyc +0 -0
- app/services/graph_builder.py +28 -0
- app/services/graph_visualizer.py +20 -0
- app/services/memory_engine.py +99 -0
- app/services/nlp_triplet.py +38 -0
app/__pycache__/main.cpython-310.pyc
ADDED
|
Binary file (368 Bytes). View file
|
|
|
app/__pycache__/main.cpython-311.pyc
ADDED
|
Binary file (512 Bytes). View file
|
|
|
app/__pycache__/main.cpython-313.pyc
ADDED
|
Binary file (423 Bytes). View file
|
|
|
app/main.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
|
| 4 |
+
# Router importu
|
| 5 |
+
from app.routes import chat # <--- chat.py dosyan buradan geliyor
|
| 6 |
+
|
| 7 |
+
app = FastAPI(
|
| 8 |
+
title="AI Memory Graph",
|
| 9 |
+
version="0.1.0",
|
| 10 |
+
description="Triplet extraction, graph, query & QA"
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
# CORS (UI veya farklı origin’den çağrı için)
|
| 14 |
+
app.add_middleware(
|
| 15 |
+
CORSMiddleware,
|
| 16 |
+
allow_origins=["*"], # ilk aşamada geniş bırak
|
| 17 |
+
allow_credentials=True,
|
| 18 |
+
allow_methods=["*"],
|
| 19 |
+
allow_headers=["*"],
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
# 🔗 Chat router'ını ekle
|
| 23 |
+
app.include_router(chat.router, prefix="", tags=["memory-graph"])
|
| 24 |
+
|
| 25 |
+
# ✅ Root path: GET + HEAD
|
| 26 |
+
@app.api_route("/", methods=["GET", "HEAD"])
|
| 27 |
+
def root():
|
| 28 |
+
return {"status": "ok", "service": "ai-memory-graph"}
|
| 29 |
+
|
| 30 |
+
# Health endpoint (kontrol için)
|
| 31 |
+
@app.get("/healthz")
|
| 32 |
+
def healthz():
|
| 33 |
+
return {"status": "ok"}
|
app/routes/__pycache__/chat.cpython-310.pyc
ADDED
|
Binary file (6.19 kB). View file
|
|
|
app/routes/__pycache__/chat.cpython-311.pyc
ADDED
|
Binary file (11.5 kB). View file
|
|
|
app/routes/__pycache__/chat.cpython-313.pyc
ADDED
|
Binary file (1.14 kB). View file
|
|
|
app/routes/chat.py
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, Query
|
| 2 |
+
from pydantic import BaseModel
|
| 3 |
+
from typing import List
|
| 4 |
+
import os
|
| 5 |
+
import json
|
| 6 |
+
|
| 7 |
+
from app.services.nlp_triplet import extract_triplets_from_text
|
| 8 |
+
from app.services.graph_builder import build_graph_from_triplets, export_graph_as_edges
|
| 9 |
+
from app.services.memory_engine import (
|
| 10 |
+
group_by_author,
|
| 11 |
+
count_predicates,
|
| 12 |
+
most_common_subjects,
|
| 13 |
+
get_triplets_by_author,
|
| 14 |
+
get_triplets_by_subject,
|
| 15 |
+
get_triplets_by_predicate,
|
| 16 |
+
query_memory,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
router = APIRouter()
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class Message(BaseModel):
|
| 23 |
+
sender: str
|
| 24 |
+
text: str
|
| 25 |
+
timestamp: str
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# ----------------------------------------------------------
|
| 29 |
+
# Triplet Extraction
|
| 30 |
+
# ----------------------------------------------------------
|
| 31 |
+
@router.post("/extract")
|
| 32 |
+
async def extract_triplets(messages: List[Message]):
|
| 33 |
+
all_triplets = []
|
| 34 |
+
|
| 35 |
+
for msg in messages:
|
| 36 |
+
extracted = extract_triplets_from_text(msg.text)
|
| 37 |
+
for triplet in extracted:
|
| 38 |
+
triplet["timestamp"] = msg.timestamp
|
| 39 |
+
triplet["author"] = msg.sender
|
| 40 |
+
all_triplets.append(triplet)
|
| 41 |
+
|
| 42 |
+
graph = build_graph_from_triplets(all_triplets)
|
| 43 |
+
edges = export_graph_as_edges(graph)
|
| 44 |
+
|
| 45 |
+
return {
|
| 46 |
+
"triplets": all_triplets,
|
| 47 |
+
"graph": edges
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# ----------------------------------------------------------
|
| 52 |
+
# Triplet Statistics Summary
|
| 53 |
+
# ----------------------------------------------------------
|
| 54 |
+
@router.post("/memory-summary")
|
| 55 |
+
async def memory_summary(messages: List[Message]):
|
| 56 |
+
all_triplets = []
|
| 57 |
+
|
| 58 |
+
for msg in messages:
|
| 59 |
+
extracted = extract_triplets_from_text(msg.text)
|
| 60 |
+
for triplet in extracted:
|
| 61 |
+
triplet["timestamp"] = msg.timestamp
|
| 62 |
+
triplet["author"] = msg.sender
|
| 63 |
+
all_triplets.append(triplet)
|
| 64 |
+
|
| 65 |
+
summary = {
|
| 66 |
+
"total_triplets": len(all_triplets),
|
| 67 |
+
"by_user": group_by_author(all_triplets),
|
| 68 |
+
"predicate_counts": count_predicates(all_triplets),
|
| 69 |
+
"common_subjects": most_common_subjects(all_triplets)
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
return summary
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
# ----------------------------------------------------------
|
| 76 |
+
# Triplet Query (Filtered)
|
| 77 |
+
# ----------------------------------------------------------
|
| 78 |
+
@router.post("/query")
|
| 79 |
+
async def query_triplets(
|
| 80 |
+
messages: List[Message],
|
| 81 |
+
author: str = Query(None),
|
| 82 |
+
subject: str = Query(None),
|
| 83 |
+
predicate: str = Query(None)
|
| 84 |
+
):
|
| 85 |
+
all_triplets = []
|
| 86 |
+
|
| 87 |
+
for msg in messages:
|
| 88 |
+
extracted = extract_triplets_from_text(msg.text)
|
| 89 |
+
for triplet in extracted:
|
| 90 |
+
triplet["timestamp"] = msg.timestamp
|
| 91 |
+
triplet["author"] = msg.sender
|
| 92 |
+
all_triplets.append(triplet)
|
| 93 |
+
|
| 94 |
+
filtered = all_triplets
|
| 95 |
+
|
| 96 |
+
if author:
|
| 97 |
+
filtered = get_triplets_by_author(filtered, author)
|
| 98 |
+
if subject:
|
| 99 |
+
filtered = get_triplets_by_subject(filtered, subject)
|
| 100 |
+
if predicate:
|
| 101 |
+
filtered = get_triplets_by_predicate(filtered, predicate)
|
| 102 |
+
|
| 103 |
+
return {
|
| 104 |
+
"total_triplets": len(filtered),
|
| 105 |
+
"results": filtered
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
# ----------------------------------------------------------
|
| 110 |
+
# Natural Language QA over Memory
|
| 111 |
+
# ----------------------------------------------------------
|
| 112 |
+
@router.get("/qa")
|
| 113 |
+
async def qa_query(question: str = Query(..., description="Doğal dilde soru girin")):
|
| 114 |
+
filters = extract_query_from_question(question)
|
| 115 |
+
memory = load_memory()
|
| 116 |
+
|
| 117 |
+
print("🚀 SORU:", question)
|
| 118 |
+
print("🔍 FILTERS:", filters)
|
| 119 |
+
|
| 120 |
+
results = query_memory(
|
| 121 |
+
memory,
|
| 122 |
+
author=filters.get("author"),
|
| 123 |
+
predicate=filters.get("predicate"),
|
| 124 |
+
subject=filters.get("subject"),
|
| 125 |
+
object_=filters.get("object"),
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
print("📦 SONUÇ TRIPLETLER:", results)
|
| 129 |
+
|
| 130 |
+
answer = format_answer_smart(results, filters)
|
| 131 |
+
|
| 132 |
+
return {
|
| 133 |
+
"soru": question,
|
| 134 |
+
"filters": filters,
|
| 135 |
+
"cevap": answer,
|
| 136 |
+
"triplet_sayisi": len(results),
|
| 137 |
+
"tripletler": results
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
@router.delete("/triplet/delete/{triplet_id}")
|
| 143 |
+
async def delete_triplet(triplet_id: str):
|
| 144 |
+
memory = load_memory()
|
| 145 |
+
changed = False
|
| 146 |
+
|
| 147 |
+
for author, triplets in memory.items():
|
| 148 |
+
original_count = len(triplets)
|
| 149 |
+
memory[author] = [t for t in triplets if t.get("id") != triplet_id]
|
| 150 |
+
if len(memory[author]) < original_count:
|
| 151 |
+
changed = True
|
| 152 |
+
|
| 153 |
+
if changed:
|
| 154 |
+
_save_memory(memory)
|
| 155 |
+
return {"status": "deleted", "id": triplet_id}
|
| 156 |
+
else:
|
| 157 |
+
return {"status": "not found", "id": triplet_id}
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
@router.put("/triplet/update/{triplet_id}")
|
| 162 |
+
async def update_triplet(triplet_id: str, updated_fields: dict):
|
| 163 |
+
memory = load_memory()
|
| 164 |
+
updated = False
|
| 165 |
+
|
| 166 |
+
for author, triplets in memory.items():
|
| 167 |
+
for t in triplets:
|
| 168 |
+
if t.get("id") == triplet_id:
|
| 169 |
+
t.update(updated_fields) # ✅ güncellenen alanları uygula
|
| 170 |
+
updated = True
|
| 171 |
+
break
|
| 172 |
+
|
| 173 |
+
if updated:
|
| 174 |
+
_save_memory(memory)
|
| 175 |
+
return {"status": "updated", "id": triplet_id, "new_data": updated_fields}
|
| 176 |
+
else:
|
| 177 |
+
return {"status": "not found", "id": triplet_id}
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
# ----------------------------------------------------------
|
| 183 |
+
# Helpers
|
| 184 |
+
# ----------------------------------------------------------
|
| 185 |
+
def extract_query_from_question(question: str) -> dict:
|
| 186 |
+
filters = {}
|
| 187 |
+
q = question.lower()
|
| 188 |
+
|
| 189 |
+
# --- AUTHOR eşleştirmeleri ---
|
| 190 |
+
if "ayşe" in q:
|
| 191 |
+
filters["author"] = "Ayşe"
|
| 192 |
+
if "erdem" in q:
|
| 193 |
+
filters["author"] = "Erdem"
|
| 194 |
+
if "ali" in q:
|
| 195 |
+
filters["author"] = "Ali"
|
| 196 |
+
|
| 197 |
+
if "ne dedi" in q:
|
| 198 |
+
filters["return"] = "triplet"
|
| 199 |
+
return filters # erken çık
|
| 200 |
+
|
| 201 |
+
# --- SUBJECT eşleştirmeleri ---
|
| 202 |
+
if "redis" in q:
|
| 203 |
+
filters["subject"] = "Redis"
|
| 204 |
+
if "react" in q:
|
| 205 |
+
filters["subject"] = "React"
|
| 206 |
+
if "fastapi" in q:
|
| 207 |
+
filters["subject"] = "FastAPI"
|
| 208 |
+
if "we" in q:
|
| 209 |
+
filters["subject"] = "we"
|
| 210 |
+
if "ben" in q or (" i " in q): # boşluklarla eşleştir, yanlış anlamasın
|
| 211 |
+
filters["subject"] = "I"
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
# --- OBJECT eşleştirmeleri ---
|
| 215 |
+
if "mongodb" in q:
|
| 216 |
+
filters["object"] = "MongoDB"
|
| 217 |
+
if "ui" in q:
|
| 218 |
+
filters["object"] = "UI"
|
| 219 |
+
if "data" in q:
|
| 220 |
+
filters["object"] = "data"
|
| 221 |
+
if "joins" in q:
|
| 222 |
+
filters["object"] = "joins"
|
| 223 |
+
if "backend" in q:
|
| 224 |
+
filters["object"] = "backend"
|
| 225 |
+
|
| 226 |
+
# --- PREDICATE eşleştirmeleri ---
|
| 227 |
+
if "öner" in q or "tavsiye" in q:
|
| 228 |
+
filters["predicate"] = "suggest"
|
| 229 |
+
if "seviyor" in q or "sever" in q or "beğen" in q:
|
| 230 |
+
filters["predicate"] = "like"
|
| 231 |
+
if "destekliyor" in q or "destek" in q:
|
| 232 |
+
filters["predicate"] = "support"
|
| 233 |
+
if "düşünüyor" in q or "düşün" in q:
|
| 234 |
+
filters["predicate"] = "think"
|
| 235 |
+
if "önbellek" in q or "cache" in q:
|
| 236 |
+
filters["predicate"] = "cache"
|
| 237 |
+
|
| 238 |
+
# --- Geri dönüş tipi ---
|
| 239 |
+
if "ne dedi" in q or "kim ne dedi" in q:
|
| 240 |
+
filters["return"] = "triplet"
|
| 241 |
+
if "kim" in q:
|
| 242 |
+
filters["return"] = "subject"
|
| 243 |
+
|
| 244 |
+
return filters
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def format_answer_smart(triplets: list, filters: dict) -> str:
|
| 249 |
+
if not triplets:
|
| 250 |
+
return "Üzgünüm, bu soruya dair bir bilgi bulamadım."
|
| 251 |
+
|
| 252 |
+
return_type = filters.get("return")
|
| 253 |
+
messages = []
|
| 254 |
+
|
| 255 |
+
for triplet in triplets:
|
| 256 |
+
subj = triplet.get("subject")
|
| 257 |
+
pred = triplet.get("predicate")
|
| 258 |
+
obj = triplet.get("object")
|
| 259 |
+
author = triplet.get("author")
|
| 260 |
+
|
| 261 |
+
if return_type == "subject":
|
| 262 |
+
messages.append(f"{obj} ile ilgili eylemi gerçekleştiren kişi: {subj}")
|
| 263 |
+
elif return_type == "triplet":
|
| 264 |
+
messages.append(f"{author} dedi ki: \"{subj} {pred} {obj}\"")
|
| 265 |
+
else:
|
| 266 |
+
sentence = f"{author} dedi ki: \"{subj} {pred} {obj}\""
|
| 267 |
+
messages.append(sentence)
|
| 268 |
+
|
| 269 |
+
return "\n".join(messages)
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def load_memory():
|
| 273 |
+
base_dir = os.path.dirname(os.path.abspath(__file__)) # backend/app/routes/
|
| 274 |
+
full_path = os.path.abspath(os.path.join(base_dir, "..", "..", "memory_export.json"))
|
| 275 |
+
with open(full_path, "r", encoding="utf-8") as f:
|
| 276 |
+
return json.load(f)
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def _save_memory(memory: dict):
|
| 280 |
+
base_dir = os.path.dirname(os.path.abspath(__file__))
|
| 281 |
+
full_path = os.path.abspath(os.path.join(base_dir, "..", "..", "memory_export.json"))
|
| 282 |
+
with open(full_path, "w", encoding="utf-8") as f:
|
| 283 |
+
json.dump(memory, f, indent=2, ensure_ascii=False)
|
| 284 |
+
|
app/services/__pycache__/graph_builder.cpython-310.pyc
ADDED
|
Binary file (879 Bytes). View file
|
|
|
app/services/__pycache__/graph_builder.cpython-311.pyc
ADDED
|
Binary file (1.51 kB). View file
|
|
|
app/services/__pycache__/graph_builder.cpython-313.pyc
ADDED
|
Binary file (1.31 kB). View file
|
|
|
app/services/__pycache__/graph_visualizer.cpython-311.pyc
ADDED
|
Binary file (1.33 kB). View file
|
|
|
app/services/__pycache__/graph_visualizer.cpython-313.pyc
ADDED
|
Binary file (1.03 kB). View file
|
|
|
app/services/__pycache__/memory_engine.cpython-310.pyc
ADDED
|
Binary file (1.35 kB). View file
|
|
|
app/services/__pycache__/memory_engine.cpython-311.pyc
ADDED
|
Binary file (7.74 kB). View file
|
|
|
app/services/__pycache__/nlp_triplet.cpython-310.pyc
ADDED
|
Binary file (799 Bytes). View file
|
|
|
app/services/__pycache__/nlp_triplet.cpython-311.pyc
ADDED
|
Binary file (1.23 kB). View file
|
|
|
app/services/graph_builder.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# backend/app/services/graph_builder.py
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from typing import List, Dict
|
| 5 |
+
|
| 6 |
+
def build_graph_from_triplets(triplets: List[Dict]) -> nx.DiGraph:
|
| 7 |
+
G = nx.DiGraph()
|
| 8 |
+
|
| 9 |
+
for triplet in triplets:
|
| 10 |
+
subject = triplet["subject"]
|
| 11 |
+
predicate = triplet["predicate"]
|
| 12 |
+
obj = triplet["object"]
|
| 13 |
+
|
| 14 |
+
G.add_node(subject)
|
| 15 |
+
G.add_node(obj)
|
| 16 |
+
G.add_edge(subject, obj, label=predicate)
|
| 17 |
+
|
| 18 |
+
return G
|
| 19 |
+
|
| 20 |
+
def export_graph_as_edges(graph: nx.DiGraph) -> List[Dict]:
|
| 21 |
+
edges = []
|
| 22 |
+
for u, v, data in graph.edges(data=True):
|
| 23 |
+
edges.append({
|
| 24 |
+
"from": u,
|
| 25 |
+
"to": v,
|
| 26 |
+
"relation": data.get("label", "")
|
| 27 |
+
})
|
| 28 |
+
return edges
|
app/services/graph_visualizer.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# backend/app/services/graph_visualizer.py
|
| 2 |
+
|
| 3 |
+
from pyvis.network import Network
|
| 4 |
+
import networkx as nx
|
| 5 |
+
|
| 6 |
+
def visualize_graph(graph: nx.DiGraph, output_path="graph.html"):
|
| 7 |
+
net = Network(height="600px", width="100%", directed=True)
|
| 8 |
+
net.barnes_hut() # güzel bir düzenleme algoritması
|
| 9 |
+
|
| 10 |
+
for node in graph.nodes():
|
| 11 |
+
net.add_node(node, label=node)
|
| 12 |
+
|
| 13 |
+
for source, target, data in graph.edges(data=True):
|
| 14 |
+
label = data.get("label", "")
|
| 15 |
+
net.add_edge(source, target, label=label)
|
| 16 |
+
|
| 17 |
+
net.write_html(output_path)
|
| 18 |
+
## import webbrowser
|
| 19 |
+
## webbrowser.open(output_path)
|
| 20 |
+
|
app/services/memory_engine.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List, Dict
|
| 2 |
+
from collections import defaultdict
|
| 3 |
+
|
| 4 |
+
def group_by_author(triplets: List[Dict]) -> Dict[str, List[Dict]]:
|
| 5 |
+
memory = defaultdict(list)
|
| 6 |
+
for t in triplets:
|
| 7 |
+
memory[t["author"]].append(t)
|
| 8 |
+
return dict(memory)
|
| 9 |
+
|
| 10 |
+
def count_predicates(triplets: List[Dict]) -> Dict[str, int]:
|
| 11 |
+
counts = defaultdict(int)
|
| 12 |
+
for t in triplets:
|
| 13 |
+
counts[t["predicate"]] += 1
|
| 14 |
+
return dict(counts)
|
| 15 |
+
|
| 16 |
+
def most_common_subjects(triplets: List[Dict], top_n=3) -> List[str]:
|
| 17 |
+
counts = defaultdict(int)
|
| 18 |
+
for t in triplets:
|
| 19 |
+
counts[t["subject"]] += 1
|
| 20 |
+
sorted_subjects = sorted(counts.items(), key=lambda x: x[1], reverse=True)
|
| 21 |
+
return [s[0] for s in sorted_subjects[:top_n]]
|
| 22 |
+
|
| 23 |
+
def get_triplets_by_author(triplets: List[Dict], author: str) -> List[Dict]:
|
| 24 |
+
return [t for t in triplets if t.get("author", "").lower() == author.lower()]
|
| 25 |
+
|
| 26 |
+
def get_triplets_by_subject(triplets: List[Dict], subject: str) -> List[Dict]:
|
| 27 |
+
return [t for t in triplets if t.get("subject", "").lower() == subject.lower()]
|
| 28 |
+
|
| 29 |
+
def get_triplets_by_predicate(triplets: List[Dict], predicate: str) -> List[Dict]:
|
| 30 |
+
return [t for t in triplets if t.get("predicate", "").lower() == predicate.lower()]
|
| 31 |
+
|
| 32 |
+
import json
|
| 33 |
+
|
| 34 |
+
import uuid
|
| 35 |
+
import json
|
| 36 |
+
from collections import defaultdict
|
| 37 |
+
from typing import List, Dict
|
| 38 |
+
|
| 39 |
+
def export_memory_to_json(triplets: List[Dict], output_path="backend/memory_export.json") -> None:
|
| 40 |
+
memory = defaultdict(list)
|
| 41 |
+
|
| 42 |
+
for t in triplets:
|
| 43 |
+
memory[t["author"]].append({
|
| 44 |
+
"id": str(uuid.uuid4()), # ✅ benzersiz id
|
| 45 |
+
"subject": t["subject"],
|
| 46 |
+
"predicate": t["predicate"],
|
| 47 |
+
"object": t["object"],
|
| 48 |
+
"timestamp": t["timestamp"]
|
| 49 |
+
})
|
| 50 |
+
|
| 51 |
+
with open(output_path, "w", encoding="utf-8") as f:
|
| 52 |
+
json.dump(memory, f, indent=2, ensure_ascii=False)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def query_memory(memory: dict, author=None, subject=None, predicate=None, object_=None):
|
| 58 |
+
results = []
|
| 59 |
+
|
| 60 |
+
for user, triplets in memory.items():
|
| 61 |
+
for triplet in triplets:
|
| 62 |
+
triplet_with_author = dict(triplet) # orijinali değiştirmeyelim
|
| 63 |
+
triplet_with_author["author"] = user
|
| 64 |
+
|
| 65 |
+
if author and user != author:
|
| 66 |
+
continue
|
| 67 |
+
if subject and triplet.get("subject") != subject:
|
| 68 |
+
continue
|
| 69 |
+
if predicate and triplet.get("predicate") != predicate:
|
| 70 |
+
continue
|
| 71 |
+
if object_ and triplet.get("object") != object_:
|
| 72 |
+
continue
|
| 73 |
+
|
| 74 |
+
results.append(triplet_with_author)
|
| 75 |
+
|
| 76 |
+
return results
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
# app/services/memory_engine.py
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
import os
|
| 84 |
+
|
| 85 |
+
def load_memory(path=None):
|
| 86 |
+
if path is None:
|
| 87 |
+
base_dir = os.path.dirname(os.path.abspath(__file__))
|
| 88 |
+
path = os.path.join(base_dir, "..", "..", "memory_export.json")
|
| 89 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 90 |
+
return json.load(f)
|
| 91 |
+
|
| 92 |
+
def save_memory(memory: dict, path=None):
|
| 93 |
+
if path is None:
|
| 94 |
+
base_dir = os.path.dirname(os.path.abspath(__file__))
|
| 95 |
+
path = os.path.join(base_dir, "..", "..", "memory_export.json")
|
| 96 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 97 |
+
json.dump(memory, f, indent=2, ensure_ascii=False)
|
| 98 |
+
|
| 99 |
+
|
app/services/nlp_triplet.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# backend/app/services/nlp_triplet.py
|
| 2 |
+
|
| 3 |
+
import spacy
|
| 4 |
+
from typing import List, Dict
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
MODEL_NAME = os.getenv("SPACY_MODEL", "en_core_web_trf")
|
| 8 |
+
try:
|
| 9 |
+
nlp = spacy.load(MODEL_NAME)
|
| 10 |
+
except Exception:
|
| 11 |
+
# Transformer yoksa hafif modele düş
|
| 12 |
+
nlp = spacy.load("en_core_web_sm")
|
| 13 |
+
|
| 14 |
+
def extract_triplets_from_text(text: str) -> List[Dict]:
|
| 15 |
+
doc = nlp(text)
|
| 16 |
+
triplets = []
|
| 17 |
+
|
| 18 |
+
for sent in doc.sents:
|
| 19 |
+
subject = ""
|
| 20 |
+
verb = ""
|
| 21 |
+
obj = ""
|
| 22 |
+
|
| 23 |
+
for token in sent:
|
| 24 |
+
if token.dep_ in ("nsubj", "nsubjpass"):
|
| 25 |
+
subject = token.text
|
| 26 |
+
if token.dep_ in ("dobj", "pobj", "attr"):
|
| 27 |
+
obj = token.text
|
| 28 |
+
if token.head.pos_ == "VERB":
|
| 29 |
+
verb = token.head.lemma_
|
| 30 |
+
|
| 31 |
+
if subject and verb and obj:
|
| 32 |
+
triplets.append({
|
| 33 |
+
"subject": subject,
|
| 34 |
+
"predicate": verb,
|
| 35 |
+
"object": obj
|
| 36 |
+
})
|
| 37 |
+
|
| 38 |
+
return triplets
|