Begin adding AI memory
This commit is contained in:
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import json
|
||||
import time
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from core.models import ConversationEvent
|
||||
|
||||
@@ -16,6 +16,8 @@ class Command(BaseCommand):
|
||||
parser.add_argument("--service", default="")
|
||||
parser.add_argument("--user-id", default="")
|
||||
parser.add_argument("--limit", type=int, default=200)
|
||||
parser.add_argument("--require-types", default="")
|
||||
parser.add_argument("--fail-if-empty", action="store_true", default=False)
|
||||
parser.add_argument("--json", action="store_true", default=False)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
@@ -23,7 +25,14 @@ class Command(BaseCommand):
|
||||
service = str(options.get("service") or "").strip().lower()
|
||||
user_id = str(options.get("user_id") or "").strip()
|
||||
limit = max(1, int(options.get("limit") or 200))
|
||||
require_types_raw = str(options.get("require_types") or "").strip()
|
||||
fail_if_empty = bool(options.get("fail_if_empty"))
|
||||
as_json = bool(options.get("json"))
|
||||
required_types = [
|
||||
item.strip().lower()
|
||||
for item in require_types_raw.split(",")
|
||||
if item.strip()
|
||||
]
|
||||
|
||||
cutoff_ts = int(time.time() * 1000) - (minutes * 60 * 1000)
|
||||
queryset = ConversationEvent.objects.filter(ts__gte=cutoff_ts).order_by("-ts")
|
||||
@@ -48,6 +57,11 @@ class Command(BaseCommand):
|
||||
for row in rows:
|
||||
key = str(row.get("event_type") or "")
|
||||
event_type_counts[key] = int(event_type_counts.get(key) or 0) + 1
|
||||
missing_required_types = [
|
||||
event_type
|
||||
for event_type in required_types
|
||||
if int(event_type_counts.get(event_type) or 0) <= 0
|
||||
]
|
||||
|
||||
payload = {
|
||||
"minutes": minutes,
|
||||
@@ -55,6 +69,8 @@ class Command(BaseCommand):
|
||||
"user_id": user_id,
|
||||
"count": len(rows),
|
||||
"event_type_counts": event_type_counts,
|
||||
"required_types": required_types,
|
||||
"missing_required_types": missing_required_types,
|
||||
"sample": rows[:25],
|
||||
}
|
||||
|
||||
@@ -66,3 +82,14 @@ class Command(BaseCommand):
|
||||
f"event-ledger-smoke minutes={minutes} service={service or '-'} user={user_id or '-'} count={len(rows)}"
|
||||
)
|
||||
self.stdout.write(f"event_type_counts={event_type_counts}")
|
||||
if required_types:
|
||||
self.stdout.write(
|
||||
f"required_types={required_types} missing_required_types={missing_required_types}"
|
||||
)
|
||||
|
||||
if fail_if_empty and len(rows) == 0:
|
||||
raise CommandError("No recent ConversationEvent rows found.")
|
||||
if missing_required_types:
|
||||
raise CommandError(
|
||||
"Missing required event types: " + ", ".join(missing_required_types)
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ class Command(BaseCommand):
|
||||
parser.add_argument("--user-id", default="")
|
||||
parser.add_argument("--session-id", default="")
|
||||
parser.add_argument("--service", default="")
|
||||
parser.add_argument("--recent-only", action="store_true", default=False)
|
||||
parser.add_argument("--recent-minutes", type=int, default=0)
|
||||
parser.add_argument("--limit-sessions", type=int, default=50)
|
||||
parser.add_argument("--detail-limit", type=int, default=25)
|
||||
@@ -29,7 +30,10 @@ class Command(BaseCommand):
|
||||
user_id = str(options.get("user_id") or "").strip()
|
||||
session_id = str(options.get("session_id") or "").strip()
|
||||
service = str(options.get("service") or "").strip().lower()
|
||||
recent_only = bool(options.get("recent_only"))
|
||||
recent_minutes = max(0, int(options.get("recent_minutes") or 0))
|
||||
if recent_only and recent_minutes <= 0:
|
||||
recent_minutes = 120
|
||||
limit_sessions = max(1, int(options.get("limit_sessions") or 50))
|
||||
detail_limit = max(0, int(options.get("detail_limit") or 25))
|
||||
as_json = bool(options.get("json"))
|
||||
@@ -98,6 +102,7 @@ class Command(BaseCommand):
|
||||
"user_id": user_id,
|
||||
"session_id": session_id,
|
||||
"service": service,
|
||||
"recent_only": recent_only,
|
||||
"recent_minutes": recent_minutes,
|
||||
"limit_sessions": limit_sessions,
|
||||
"detail_limit": detail_limit,
|
||||
|
||||
72
core/management/commands/memory_search_query.py
Normal file
72
core/management/commands/memory_search_query.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from core.memory.search_backend import get_memory_search_backend
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Run a query against configured memory search backend."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("--user-id", required=True)
|
||||
parser.add_argument("--query", required=True)
|
||||
parser.add_argument("--conversation-id", default="")
|
||||
parser.add_argument("--statuses", default="active")
|
||||
parser.add_argument("--limit", type=int, default=20)
|
||||
parser.add_argument("--json", action="store_true", default=False)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
user_id_raw = str(options.get("user_id") or "").strip()
|
||||
query = str(options.get("query") or "").strip()
|
||||
conversation_id = str(options.get("conversation_id") or "").strip()
|
||||
statuses = tuple(
|
||||
item.strip().lower()
|
||||
for item in str(options.get("statuses") or "active").split(",")
|
||||
if item.strip()
|
||||
)
|
||||
limit = max(1, int(options.get("limit") or 20))
|
||||
as_json = bool(options.get("json"))
|
||||
|
||||
if not user_id_raw:
|
||||
raise CommandError("--user-id is required")
|
||||
if not query:
|
||||
raise CommandError("--query is required")
|
||||
|
||||
backend = get_memory_search_backend()
|
||||
hits = backend.search(
|
||||
user_id=int(user_id_raw),
|
||||
query=query,
|
||||
conversation_id=conversation_id,
|
||||
limit=limit,
|
||||
include_statuses=statuses,
|
||||
)
|
||||
payload = {
|
||||
"backend": getattr(backend, "name", "unknown"),
|
||||
"query": query,
|
||||
"user_id": int(user_id_raw),
|
||||
"conversation_id": conversation_id,
|
||||
"statuses": statuses,
|
||||
"count": len(hits),
|
||||
"hits": [
|
||||
{
|
||||
"memory_id": item.memory_id,
|
||||
"score": item.score,
|
||||
"summary": item.summary,
|
||||
"payload": item.payload,
|
||||
}
|
||||
for item in hits
|
||||
],
|
||||
}
|
||||
if as_json:
|
||||
self.stdout.write(json.dumps(payload, indent=2, sort_keys=True))
|
||||
return
|
||||
self.stdout.write(
|
||||
f"memory-search-query backend={payload['backend']} count={payload['count']} query={query!r}"
|
||||
)
|
||||
for row in payload["hits"]:
|
||||
self.stdout.write(
|
||||
f"- id={row['memory_id']} score={row['score']:.2f} summary={row['summary'][:120]}"
|
||||
)
|
||||
49
core/management/commands/memory_search_reindex.py
Normal file
49
core/management/commands/memory_search_reindex.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from core.memory.search_backend import get_memory_search_backend
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Reindex MemoryItem rows into the configured memory search backend."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("--user-id", default="")
|
||||
parser.add_argument("--statuses", default="active")
|
||||
parser.add_argument("--limit", type=int, default=2000)
|
||||
parser.add_argument("--json", action="store_true", default=False)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
user_id_raw = str(options.get("user_id") or "").strip()
|
||||
statuses = tuple(
|
||||
item.strip().lower()
|
||||
for item in str(options.get("statuses") or "active").split(",")
|
||||
if item.strip()
|
||||
)
|
||||
limit = max(1, int(options.get("limit") or 2000))
|
||||
as_json = bool(options.get("json"))
|
||||
|
||||
backend = get_memory_search_backend()
|
||||
result = backend.reindex(
|
||||
user_id=int(user_id_raw) if user_id_raw else None,
|
||||
include_statuses=statuses,
|
||||
limit=limit,
|
||||
)
|
||||
payload = {
|
||||
"backend": getattr(backend, "name", "unknown"),
|
||||
"user_id": user_id_raw,
|
||||
"statuses": statuses,
|
||||
"limit": limit,
|
||||
"result": result,
|
||||
}
|
||||
if as_json:
|
||||
self.stdout.write(json.dumps(payload, indent=2, sort_keys=True))
|
||||
return
|
||||
self.stdout.write(
|
||||
f"memory-search-reindex backend={payload['backend']} "
|
||||
f"user={user_id_raw or '-'} statuses={','.join(statuses) or '-'} "
|
||||
f"scanned={int(result.get('scanned') or 0)} indexed={int(result.get('indexed') or 0)}"
|
||||
)
|
||||
Reference in New Issue
Block a user