grantforge-api / backend /core /generation_state.py
GrantForge Bot
Deploy sha-9a5957fcdef15b7e2623f8b147cda6026475aee0 — source build (no GHCR)
3a3734f
Raw
History Blame Contribute Delete
3.15 kB
"""Generation state — hierarchia źródeł prawdy i helpery persystencji.
Problem (audyt): stan generacji był rozproszony w ``external_context`` JSON,
checkpointach LangGraph (sqlite) i GSD orchestratorze.
Hierarchia odczytu (od najbardziej autorytatywnego):
1. **GSD orchestrator** — ``gsd/gsd_state.py`` + sqlite saver (aktywny run)
2. **Hybrid checkpoint** — ``external_context['orchestrator_checkpoint_v5']``
3. **Legacy LangGraph generator** — ``data/generator_checkpoints.db`` (DEPRECATED)
4. **Pozostałe klucze w external_context** — cache / pochodne (nie źródło prawdy)
Zapis: używaj ``persist_generation_checkpoint`` aby synchronizować warstwy 1↔2.
"""
from __future__ import annotations
import logging
from copy import deepcopy
from typing import Any
logger = logging.getLogger(__name__)
CHECKPOINT_V5_KEY = "orchestrator_checkpoint_v5"
GSD_STATE_KEY = "gsd_state"
GENERATION_META_KEY = "generation_meta"
DERIVED_CONTEXT_KEYS = frozenset(
{
"holistic_review",
"risk_score",
"blocks_export_recommendation",
"v5_grounding_certificate",
"compliance_checklist",
"regulation_section_titles",
"credibility_flags",
}
)
def get_authoritative_checkpoint(project) -> dict[str, Any] | None:
"""Zwraca hybrid checkpoint v5 z projektu lub None."""
ext = getattr(project, "external_context", None) or {}
ck = ext.get(CHECKPOINT_V5_KEY)
return ck if isinstance(ck, dict) else None
def get_generation_meta(project) -> dict[str, Any]:
"""Metadane generacji (ścieżka orkiestracji, ostatnia faza)."""
ext = getattr(project, "external_context", None) or {}
meta = ext.get(GENERATION_META_KEY) or {}
return meta if isinstance(meta, dict) else {}
def persist_generation_checkpoint(
db,
project,
checkpoint: dict[str, Any],
*,
orchestration_path: str = "gsd",
merge_meta: dict[str, Any] | None = None,
) -> None:
"""Zapisuje checkpoint do ``external_context`` (single durable source w Postgres/SQLite)."""
from sqlalchemy.orm.attributes import flag_modified
ext = deepcopy(getattr(project, "external_context", None) or {})
ext[CHECKPOINT_V5_KEY] = checkpoint
meta = dict(ext.get(GENERATION_META_KEY) or {})
meta["orchestration_path"] = orchestration_path
meta["last_checkpoint_stage"] = checkpoint.get("stage") or checkpoint.get("current_orchestrator_stage")
if merge_meta:
meta.update(merge_meta)
ext[GENERATION_META_KEY] = meta
project.external_context = ext
flag_modified(project, "external_context")
db.commit()
logger.debug(
"[generation_state] Persisted checkpoint stage=%s path=%s project=%s",
meta.get("last_checkpoint_stage"),
orchestration_path,
getattr(project, "id", "?"),
)
def is_legacy_langgraph_checkpoint(config: dict[str, Any] | None) -> bool:
"""Czy config wskazuje na legacy LangGraph thread (generator agent)."""
if not config:
return False
tid = config.get("configurable", {}).get("thread_id", "")
return isinstance(tid, str) and tid.startswith("gen-")