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/__init__.py +8 -0
- realitydiff/__main__.py +4 -0
- realitydiff/api.py +270 -0
- realitydiff/blame.py +85 -0
- realitydiff/claim.py +104 -0
- realitydiff/cli.py +168 -0
- realitydiff/compress.py +94 -0
- realitydiff/config.py +66 -0
- realitydiff/diffing.py +210 -0
- realitydiff/hashing.py +24 -0
- realitydiff/models.py +239 -0
- realitydiff/reasoner.py +440 -0
- realitydiff/schedule.py +217 -0
- realitydiff/service.py +173 -0
- realitydiff/static/app.js +814 -0
- realitydiff/static/favicon.svg +4 -0
- realitydiff/static/index.html +175 -0
- realitydiff/static/styles.css +477 -0
- realitydiff/store.py +296 -0
- realitydiff/watcher.py +42 -0
- realitydiff/xai.py +506 -0
- realitydiff-0.4.0.dist-info/METADATA +115 -0
- realitydiff-0.4.0.dist-info/RECORD +26 -0
- realitydiff-0.4.0.dist-info/WHEEL +4 -0
- realitydiff-0.4.0.dist-info/entry_points.txt +2 -0
- realitydiff-0.4.0.dist-info/licenses/LICENSE +21 -0
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())
|