Spaces:
Running
Running
File size: 3,927 Bytes
8810b99 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | """Offline warm-up of the pathway report cache.
Retrieval and evidence gating are deterministic in (path, corpus), but a full
run costs minutes of CPU per path. This script computes every pathway report
once, offline, so the running app serves them from cache instead of recomputing
an identical answer on each request.
Run it after the knowledge base changes (the cache key carries a corpus
fingerprint, so stale entries are ignored automatically rather than served).
python precompute_rag_cache.py # all reachable targets
python precompute_rag_cache.py As2O3 PbO # only these targets
Interrupting is safe: finished paths stay cached, so a re-run resumes.
"""
from __future__ import annotations
import os
import sys
import time
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# Some network node names carry zero-width characters, and the tracer prints the
# target name on a miss. On a GBK console that print raises UnicodeEncodeError
# and aborts the run, so force UTF-8 output and never fail on an odd glyph.
for _stream in (sys.stdout, sys.stderr):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
except (AttributeError, ValueError):
pass
def main(argv: list[str]) -> int:
from search_subgraph import CRNTracer, GRAPHML_PATH
from tracernet.crn import pathways as crn_pathways
from tracernet.crn.repository import CRNRepository
from rag_module import RAGService
project = os.path.dirname(os.path.abspath(__file__))
repository = CRNRepository.discover([project])
tracer = CRNTracer(GRAPHML_PATH, crn_repository=repository)
graph = tracer.G
service = RAGService(repository)
print(f"[init] graph={graph.number_of_nodes()} nodes, "
f"corpus signature={service._report_cache_signature()}", flush=True)
targets = argv or sorted(graph.nodes())
print(f"[plan] tracing {len(targets)} candidate targets ...", flush=True)
# Collect unique paths across every target. The same path is reachable from
# several targets, and computing it twice would cost minutes for nothing.
jobs: dict[str, tuple[str, object]] = {}
for target in targets:
try:
_resolved, _graph, _report, all_paths = crn_pathways.trace_material_pathways(
tracer, graph, target, max_depth=10, limit=100
)
except Exception as error: # a target with no upstream path is normal
print(f"[trace] {target}: skipped ({type(error).__name__})", flush=True)
continue
for info in all_paths or []:
path_str = info.get("path_str")
if path_str and path_str not in jobs:
jobs[path_str] = (info.get("source"), info.get("full_path"))
print(f"[plan] {len(jobs)} unique pathways to warm", flush=True)
started = time.time()
done = 0
for index, (path_str, (source, full_path)) in enumerate(jobs.items(), 1):
step = time.time()
try:
# Evidence only: the narrative is generated per request, so warming
# it here would be discarded work -- and with an API key configured
# it would spend one LLM call per path for nothing.
service.cached_path_evidence(source, path_str, full_path)
done += 1
status = "ok"
except Exception as error:
status = f"FAILED {type(error).__name__}: {error}"
elapsed = time.time() - step
total = time.time() - started
print(f"[{index}/{len(jobs)}] {elapsed:6.1f}s total {total/60:5.1f}m "
f"{status} {path_str[:90]}", flush=True)
print(f"[done] warmed {done}/{len(jobs)} pathways in "
f"{(time.time()-started)/60:.1f} minutes", flush=True)
print(f"[done] cache directory: {service._report_cache_dir()}", flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
|