Files
pegas-memory/pegas_memory/db.py
T
n1kt4r eca8a11e23 feat(pegas-memory): Tier-1 self-improvement tools (commit + staleness)
- memory_commit: closes the 'commit hole' — writes verified answers from
  closed gaps back into atoms (mandatory dedup); leaves gap open if no
  verified answer exists. Reads verified status from PostgreSQL (source of
  truth), not the Qdrant projection.
- memory_stale: staleness guard with two signals — content-staleness
  (re-fetch + LLM verdict, needs proxy for foreign sites) and age-staleness
  (proxy-free fallback by pm_document.fetched_at, max_age_days).
- db: populate pm_document.fetched_at on upsert (freshness signal).
- docs: README/AGENTS/ARCHITECTURE/EXAMPLES updated (18 tools).
- self_improve.py: new module (227 lines).
2026-10-15 22:06:30 +05:00

86 lines
3.5 KiB
Python

"""PostgreSQL — источник истины (psycopg)."""
from __future__ import annotations
import hashlib
import json
import psycopg
from psycopg.rows import dict_row
from . import config
def connect():
return psycopg.connect(config.PG_DSN, row_factory=dict_row)
def _as_text(content) -> str:
"""Привести content к строке: dict/list -> JSON, иначе str."""
if isinstance(content, str):
return content
import json as _json
return _json.dumps(content, ensure_ascii=False, sort_keys=True)
def content_hash(content) -> str:
return hashlib.sha256(_as_text(content).encode("utf-8")).hexdigest()
def upsert_document(conn, *, kind: str, uri: str | None, title: str | None,
content_hash: str | None = None, authority: float = 0.5,
meta: dict | None = None) -> int:
"""Вставить/вернуть документ по uri."""
with conn.cursor() as cur:
if uri:
cur.execute("SELECT id FROM pm_document WHERE uri = %s", (uri,))
row = cur.fetchone()
if row:
return row["id"]
cur.execute(
"""INSERT INTO pm_document (kind, uri, title, content_hash, authority, meta, fetched_at)
VALUES (%s, %s, %s, %s, %s, %s, now())
ON CONFLICT (uri) DO UPDATE SET title = EXCLUDED.title
RETURNING id""",
(kind, uri, title, content_hash, authority, json.dumps(meta or {}, ensure_ascii=False)),
)
return cur.fetchone()["id"]
def upsert_atom(conn, *, type: str, claim: str | None, content,
confidence: float = 0.5, status: str = "candidate",
doc_id: int | None = None, locator: dict | None = None,
metadata: dict | None = None) -> tuple[int, bool]:
"""Вставить атом по content_hash. Возвращает (id, created).
content может быть str (text/fact/definition/code) или dict/list
(logical_rule/table) — dict сериализуется в JSON.
"""
content = _as_text(content)
h = content_hash(content)
with conn.cursor() as cur:
cur.execute("SELECT id FROM pm_atom WHERE content_hash = %s", (h,))
row = cur.fetchone()
if row:
return row["id"], False
cur.execute(
"""INSERT INTO pm_atom (type, claim, content, content_hash, confidence,
status, doc_id, locator, metadata)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING id""",
(type, claim, content, h, confidence, status, doc_id,
json.dumps(locator or {}, ensure_ascii=False),
json.dumps(metadata or {}, ensure_ascii=False)),
)
return cur.fetchone()["id"], True
def upsert_link(conn, *, src: int, dst: int, kind: str, confidence: float = 0.5) -> int:
with conn.cursor() as cur:
cur.execute(
"""INSERT INTO pm_atom_link (src, dst, kind, confidence)
VALUES (%s, %s, %s, %s)
ON CONFLICT (src, dst, kind) DO NOTHING
RETURNING id""",
(src, dst, kind, confidence),
)
row = cur.fetchone()
return row["id"] if row else 0
def enqueue_outbox(conn, *, entity_type: str, entity_id: int, op: str = "upsert"):
with conn.cursor() as cur:
cur.execute(
"INSERT INTO pm_outbox (entity_type, entity_id, op) VALUES (%s, %s, %s)",
(entity_type, entity_id, op),
)