realitydiff 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
realitydiff/service.py ADDED
@@ -0,0 +1,173 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import logging
5
+ import threading
6
+ import time
7
+ from pathlib import Path
8
+
9
+ from realitydiff.claim import Claim
10
+ from realitydiff.diffing import diff_states
11
+ from realitydiff.models import Commit, WatchResult
12
+ from realitydiff.reasoner import Reasoner
13
+ from realitydiff.store import BeliefStore
14
+ from realitydiff.xai import LanguageModel, XAIClient
15
+
16
+ log = logging.getLogger("realitydiff.service")
17
+
18
+
19
+ class RealityDiff:
20
+ def __init__(self, store: BeliefStore, model: LanguageModel | None = None) -> None:
21
+ self.store = store
22
+ self.model = model or XAIClient()
23
+ self.reasoner = Reasoner(self.model)
24
+ self._watch_locks: dict[str, threading.Lock] = {}
25
+ self._watch_meta = threading.Lock()
26
+
27
+ @classmethod
28
+ def open(cls, path: str | Path, model: LanguageModel | None = None) -> "RealityDiff":
29
+ return cls(BeliefStore(path), model=model)
30
+
31
+ def claim(self, claim_id: str) -> Claim:
32
+ record = self.store.get_claim(claim_id)
33
+ if record is None:
34
+ raise KeyError(claim_id)
35
+ return Claim(self.store, record)
36
+
37
+ def open_claim(self, statement: str, *, author: str = "reasoner") -> tuple[Claim, Commit]:
38
+ statement = statement.strip()
39
+ if len(statement) < 3:
40
+ raise ValueError("Claim is too short")
41
+ record = self.store.create_claim(statement)
42
+ try:
43
+ state, reason, meta = self.reasoner.initialize(statement, conv_id=record.id)
44
+ commit = Commit.create(
45
+ claim_id=record.id,
46
+ parent_id=None,
47
+ state=state,
48
+ author=author,
49
+ message="Initial belief",
50
+ reason=reason,
51
+ previous_confidence=None,
52
+ )
53
+ self.store.append_commit(commit)
54
+ self.store.set_model_cache(
55
+ record.id,
56
+ response_id=meta.get("response_id"),
57
+ compaction=meta.get("compaction"),
58
+ )
59
+ self._maybe_close(record.id, state, meta)
60
+ return self.claim(record.id), commit
61
+ except Exception:
62
+ self.store.delete_claim(record.id)
63
+ raise
64
+
65
+ def watch(self, claim_id: str, *, author: str = "watcher") -> WatchResult:
66
+ lock = self._lock_for(claim_id)
67
+ if not lock.acquire(blocking=False):
68
+ return WatchResult(
69
+ claim_id=claim_id,
70
+ changed=False,
71
+ in_progress=True,
72
+ detail="Watch already running; UI stays usable.",
73
+ )
74
+ try:
75
+ living = self.claim(claim_id)
76
+ if living.record.closed:
77
+ return WatchResult(
78
+ claim_id=claim_id,
79
+ changed=False,
80
+ closed=True,
81
+ detail="Claim already closed.",
82
+ )
83
+ head = living.head
84
+ if head is None:
85
+ raise RuntimeError("Claim has no commits yet")
86
+ compaction = None
87
+ if living.record.compaction_json:
88
+ try:
89
+ compaction = json.loads(living.record.compaction_json)
90
+ except json.JSONDecodeError:
91
+ compaction = None
92
+ new_state, reason, changed, meta = self.reasoner.update(
93
+ living.record.title,
94
+ head.state,
95
+ conv_id=living.id,
96
+ previous_response_id=living.record.last_response_id,
97
+ compaction=compaction,
98
+ )
99
+ self.store.mark_watched(claim_id)
100
+ self.store.set_model_cache(
101
+ claim_id,
102
+ response_id=meta.get("response_id"),
103
+ compaction=meta.get("compaction") or compaction,
104
+ )
105
+ cached = int(meta.get("cached_tokens") or 0)
106
+ closed = bool(new_state.outcome) or bool(
107
+ isinstance(meta.get("close_claim"), dict) and meta.get("close_claim", {}).get("ok")
108
+ )
109
+ if not changed:
110
+ if closed:
111
+ self._maybe_close(claim_id, new_state, meta)
112
+ return WatchResult(
113
+ claim_id=claim_id,
114
+ changed=False,
115
+ detail=reason,
116
+ cached_tokens=cached,
117
+ closed=bool(self.claim(claim_id).record.closed),
118
+ )
119
+ self.store.set_working_state(claim_id, new_state)
120
+ commit = living.commit(
121
+ "Claim closed" if new_state.outcome else "Watch update",
122
+ author=author,
123
+ reason=reason,
124
+ )
125
+ self._maybe_close(claim_id, commit.state, meta)
126
+ diff = diff_states(
127
+ head.state,
128
+ commit.state,
129
+ from_id=head.id,
130
+ to_id=commit.id,
131
+ reason=reason,
132
+ )
133
+ return WatchResult(
134
+ claim_id=claim_id,
135
+ changed=True,
136
+ commit=commit,
137
+ diff=diff,
138
+ detail=reason,
139
+ cached_tokens=cached,
140
+ closed=bool(commit.state.outcome),
141
+ )
142
+ finally:
143
+ lock.release()
144
+
145
+ def watch_due(self, *, author: str = "watcher") -> list[WatchResult]:
146
+ results: list[WatchResult] = []
147
+ now = time.time()
148
+ for record in self.store.list_claims():
149
+ if record.closed or not record.watching or not record.head:
150
+ continue
151
+ last = record.last_watched_at.timestamp() if record.last_watched_at else 0.0
152
+ if now - last < record.watch_interval_seconds:
153
+ continue
154
+ try:
155
+ results.append(self.watch(record.id, author=author))
156
+ except Exception:
157
+ log.exception("watch failed for %s", record.id)
158
+ self.store.mark_watched(record.id)
159
+ return results
160
+
161
+ def _maybe_close(self, claim_id: str, state, meta: dict | None = None) -> None:
162
+ close = (meta or {}).get("close_claim") if isinstance((meta or {}).get("close_claim"), dict) else None
163
+ outcome = state.outcome
164
+ if outcome is None and not (close and close.get("ok")):
165
+ return
166
+ verdict = outcome.verdict if outcome else close.get("verdict")
167
+ if verdict not in {"confirmed", "falsified", "settled"}:
168
+ return
169
+ self.store.close_claim(claim_id, verdict=verdict, closed_at=outcome.closed_at if outcome else None)
170
+
171
+ def _lock_for(self, claim_id: str) -> threading.Lock:
172
+ with self._watch_meta:
173
+ return self._watch_locks.setdefault(claim_id, threading.Lock())