botp
/

Solomon / mlx /src /solomon_mlx /api.py
orz99's picture ArcherHume's picture
Duplicate from DoccyHealth/Solomon
1d2de8a
Raw
History Blame Contribute Delete
13.8 kB
"""Public document-state API and four-state answer semantics."""
import copy
import json
import math
from pathlib import Path
from ._vendor.contract import _state_parts, decision, parse_questions, present
from ._vendor.prompts import boolean_block, label_block, listwise_block
from ._vendor.semantics import listed_probs, p_yes
from .artifacts import digest, sha256
TASKS = ("boolean", "single", "ordered", "multilabel", "entity")
def branches(spec):
task, req = spec["task"], spec["request"]
if task == "boolean":
return [(boolean_block(req["question"]), 4, "boolean/state4")]
if task in ("single", "ordered"):
block, width = listwise_block(
req["question"], req["options"], ordered=task == "ordered", reserved=task == "single"
)
return [(block, width, task + ("/choiceR" if task == "single" else "/choiceS"))]
if task == "entity":
return [
(boolean_block(req["template"].replace("{entity}", c)), 4, "entity/state4")
for c in req["entities"]
]
return [(*label_block(req["question"], c), "multilabel/state4") for c in req["labels"]]
def distributions(spec, rows, temperature):
if spec["task"] in ("boolean", "entity", "multilabel"):
values = [p_yes(r["letter_logits"], temperature) for r in rows]
return [[p, 1 - p] for p in values]
return [listed_probs(rows[0]["letter_logits"], len(spec["texts"]), temperature).tolist()]
def ordering_score(values):
"""Product of per-unit top probabilities; not a calibrated joint probability."""
if not values:
raise ValueError("At least one answer unit is required")
for p in values:
if len(p) < 2 or not all(math.isfinite(v) and v >= 0 for v in p) or abs(sum(p) - 1) > 1e-6:
raise ValueError("Invalid answer distribution")
return math.prod(max(p) for p in values)
class DocumentState:
def __init__(self, owner, data):
self._owner, self._data, self.closed = owner, data, False
self.image_hashes = {p["image"]: sha256(p["image"]) for p in data["parts"] if "image" in p}
@property
def prefix_tokens(self):
self._check()
return len(self._data["prefix_ids"])
def _check(self):
if self.closed:
raise ValueError("Document state is closed")
if any(sha256(path) != value for path, value in self.image_hashes.items()):
raise ValueError("Document image changed after prefill")
def save(self, path):
"""Save a source-bound replay recipe, never pickle executable cache objects."""
self._check()
body = {
"format": "solomon-mlx-replay-v1",
"runtime": self._owner.identity["fingerprint"],
"parts": self._data["parts"],
"image_hashes": self.image_hashes,
"prefix_ids_sha256": digest(self._data["prefix_ids"]),
}
Path(path).write_text(json.dumps({**body, "sha256": digest(body)}, indent=2))
def close(self):
with self._owner.engine.lock:
self._data.clear()
self.closed = True
def __enter__(self):
self._check()
return self
def __exit__(self, *args):
self.close()
class Solomon:
@classmethod
def load(
cls,
model_dir,
profile="quality",
*,
chunk_size=2048,
max_tokens=40960,
page_selector=None,
calibration=None,
):
if profile != "quality":
raise ValueError("Only full BF16 quality is implemented; quantization is secondary")
from .engine import Engine
return cls(
Engine(model_dir, chunk_size=chunk_size, max_tokens=max_tokens),
page_selector=page_selector,
calibration=calibration,
)
def __init__(self, engine, *, page_selector=None, calibration=None):
self.engine, self.identity, self.page_selector = engine, engine.identity, page_selector
self.temperatures = dict.fromkeys(TASKS, 1.0)
self.calibration_status = "uncalibrated"
if calibration is not None:
artifact = json.loads(Path(calibration).read_text())
payload = {k: v for k, v in artifact.items() if k != "sha256"}
if (
artifact.get("sha256") != digest(payload)
or artifact["runtime"] != self.identity["fingerprint"]
):
raise ValueError("Calibration checksum or MLX runtime identity mismatch")
temps = artifact["temperatures"]
if set(temps) != set(TASKS) or any(
isinstance(v, bool)
or not isinstance(v, (int, float))
or not math.isfinite(v)
or not 0 < v <= 20
for v in temps.values()
):
raise ValueError("Invalid temperatures")
self.temperatures, self.calibration_status = temps, "profile_fitted"
def prefill(self, document):
parts = copy.deepcopy(_state_parts(document))
if not parts:
parts = [{"text": ""}]
for p in parts:
if not isinstance(p, dict) or set(p) not in ({"text"}, {"image"}):
raise ValueError("Each document part must contain only text or image")
if "text" in p and not isinstance(p["text"], str):
raise ValueError("Text parts must be strings")
if "image" in p:
p["image"] = str(Path(p["image"]).resolve(strict=True))
hashes = {p["image"]: sha256(p["image"]) for p in parts if "image" in p}
state = DocumentState(self, self.engine.prefill(parts))
if state.image_hashes != hashes:
state.close()
raise ValueError("Image changed while document was being prefilled")
return state
def replay(self, path):
body = json.loads(Path(path).read_text())
expected = body.pop("sha256")
if (
digest(body) != expected
or body["format"] != "solomon-mlx-replay-v1"
or body["runtime"] != self.identity["fingerprint"]
):
raise ValueError("Replay checksum or runtime mismatch")
if any(sha256(p) != h for p, h in body["image_hashes"].items()):
raise ValueError("Replay image changed")
state = self.prefill(body["parts"])
if digest(state._data["prefix_ids"]) != body["prefix_ids_sha256"]:
state.close()
raise ValueError("Replay tokenization differs")
return state
def _answer(self, state, spec, execution="cached"):
rows = [self.engine.ask(state._data, b, n, h, execution=execution) for b, n, h in branches(spec)]
dists = distributions(spec, rows, self.temperatures[spec["task"]])
return {
**present(spec, dists),
"ordering_score": ordering_score(dists),
"temperature": self.temperatures[spec["task"]],
}, rows
def decide(
self,
*,
state,
questions,
evidence="support",
evidence_max_calls=64,
execution="cached",
diagnostics=False,
):
if not isinstance(state, DocumentState) or state._owner is not self:
raise ValueError("State belongs to a different model instance")
if evidence not in ("none", "support", "sufficiency", "removal"):
raise ValueError("Invalid evidence level")
if type(evidence_max_calls) is not int or not 0 <= evidence_max_calls <= 512:
raise ValueError("Invalid evidence call budget")
specs = parse_questions(questions)
with self.engine.lock:
state._check()
answers, usage = {}, {"branches": 0, "input_tokens": 0, "evidence_calls": 0}
for spec in specs:
answer, rows = self._answer(state, spec, execution)
body = self._evidence(state, spec, answer, evidence, evidence_max_calls)
answer.update(
evidence=body["references"], evidence_status=body["status"], evidence_detail=body
)
if diagnostics:
answer["branches"] = rows
answers[spec["id"]] = answer
usage["branches"] += len(rows)
usage["input_tokens"] += sum(
r["branch_tokens"] if execution == "cached" else r["prompt_tokens"] for r in rows
)
usage["evidence_calls"] += body.get("calls", 0)
return {
"answers": answers,
"usage": usage,
"runtime": self.identity,
"calibration_status": self.calibration_status,
"answer_policy": "always_answers",
}
def _fresh(self, document, spec):
with self.prefill(document) as state:
answer, _ = self._answer(state, spec)
return decision(spec, answer)
def _evidence(self, state, spec, answer, level, budget):
from ._vendor import evidence_v3 as v3
from ._vendor.evidence import image_pages, validate_pages, validate_spans
from ._vendor.retrieval import lexical_select, remove
body = {
"references": [],
"status": "not_requested",
"calls": 0,
"verification": "none",
"faithfulness_established": False,
}
if level == "none":
return body
parts, req = state._data["parts"], spec["request"]
task = spec["task"]
if task == "entity":
questions = [req["template"].replace("{entity}", c) for c in req["entities"]]
elif task == "multilabel":
questions = [req["question"] + " Label: " + c for c in req["labels"]]
else:
questions = [req["question"] + (" " + " ".join(req["options"]) if "options" in req else "")]
images = [p["image"] for p in parts if "image" in p]
needed = (1 if images else len(questions)) if level in ("sufficiency", "removal") else 0
needed += int(level == "removal")
if needed > budget:
return {**body, "status": "budget_exhausted", "required_calls": needed}
baseline = decision(spec, answer)
if images:
if self.page_selector is None:
return {**body, "status": "unsupported_page_selector", "pages_available": len(images)}
pages = image_pages(images)
selector = self.page_selector
plan = None
if hasattr(selector, "plan"):
plan = selector.plan(
pages, questions, **({"task": task} if getattr(selector, "task_aware", False) else {})
)
if type(plan.get("calls")) is not int or plan["calls"] < 0:
raise ValueError("Invalid page selector call estimate")
if needed + plan["calls"] > budget:
return {**body, "status": "budget_exhausted", "required_calls": needed + plan["calls"]}
selection = (
selector.execute(plan) if plan is not None else selector(copy.deepcopy(pages), questions)
)
calls = selection.get("cost", {}).get("calls", 0)
if calls != (plan["calls"] if plan is not None else 0):
raise ValueError("Page selector exceeded its declared call budget")
refs = validate_pages(pages, selection["evidence"])
body["calls"] = calls
selected = {r["page"] for r in refs}
page, remainder = 0, []
for part in parts:
if "image" in part:
page += 1
if page in selected:
continue
remainder.append(part)
subsets = [([{"image": r["path"]} for r in refs], spec)]
else:
text = "".join(p["text"] for p in parts)
selection = lexical_select(text, questions)
refs = validate_spans(text, selection["evidence"])
structure = v3.Structure(text)
packages, subsets = [], []
for i, q in enumerate(questions):
subject = req["entities"][i] if task == "entity" else None
package = v3.build(text, q, refs, subject=subject, structure=structure)
unit = copy.deepcopy(spec)
if "candidates" in spec:
candidate = spec["candidates"][i]
unit["candidates"] = [candidate]
unit["request"]["entities" if task == "entity" else "labels"] = [candidate]
subsets.append((package["text"], unit))
packages.append({k: v for k, v in package.items() if k != "text"})
body["packages"] = packages
remainder = remove(text, refs)
body.update(
references=refs, status="found" if refs else "no_support_found", verification="retrieval_only"
)
if level in ("sufficiency", "removal"):
predictions = [self._fresh(doc, unit) for doc, unit in subsets]
assembled = (
{k: v for d in predictions for k, v in d.items()}
if "candidates" in spec and not images
else predictions[0]
)
body["evidence_only"] = {"prediction": assembled, "agrees_with_full": assembled == baseline}
body["calls"] += len(subsets)
body["verification"] = "fresh_source_reencoding"
if level == "removal":
removed = self._fresh(remainder, spec)
body["evidence_removed"] = {"prediction": removed, "agrees_with_full": removed == baseline}
body["calls"] += 1
return body