Chat round (explain on selection, page starters, reactions, streamed notes, thinking), negatives on node fixed values and combines, Pack builder quality fixes from 31 real-model eval rounds, and small-model support (scoped tools, checked tool arguments on local servers, two-phase steps, fixed prefix shown in Test connection).
58 lines
1.9 KiB
Python
58 lines
1.9 KiB
Python
"""Per-site random streams for the resolver.
|
|
|
|
A resolve context that carries a `stream_key` gives every pick site its
|
|
own RNG, derived from the key plus the site: the token text, the words
|
|
right after it, and how many identical sites came before it. So a
|
|
`{a|b}` rolls the same whether or not an earlier rule fired, an earlier
|
|
value was added or an earlier pick gained an option. Recursion extends
|
|
the key, so a pick inside a ref or a branch is its own site too.
|
|
|
|
A context without a `stream_key` (unit tests, the corpus, previews) keeps
|
|
the old single shared stream.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import random
|
|
from collections.abc import Iterator
|
|
from contextlib import contextmanager
|
|
from typing import Any
|
|
|
|
|
|
def stream_rng(key: str) -> random.Random:
|
|
digest = hashlib.sha256(key.encode()).hexdigest()
|
|
return random.Random(int(digest[:16], 16))
|
|
|
|
|
|
@contextmanager
|
|
def sub_stream(ctx: Any, label: str) -> Iterator[None]:
|
|
"""Run the body on the child stream `label` of ctx's current stream.
|
|
|
|
No-op when ctx has no stream key. Restores the parent stream after,
|
|
so whatever the body draws never moves the parent's next draw.
|
|
"""
|
|
base = getattr(ctx, "stream_key", None)
|
|
if not base:
|
|
yield
|
|
return
|
|
key = f"{base}|{label}"
|
|
saved = ctx.rng
|
|
ctx.rng = stream_rng(key)
|
|
ctx.stream_key = key
|
|
try:
|
|
yield
|
|
finally:
|
|
ctx.rng = saved
|
|
ctx.stream_key = base
|
|
|
|
|
|
def site_label(raw: str, after: str, seen: dict[str, int]) -> str:
|
|
"""A pick site's label: its token text, the words right after it, and
|
|
how many identical sites came before. The words tell two identical
|
|
picks apart ("@{Colors} jacket" vs "@{Colors} jeans"), so adding one
|
|
never moves the other; the count settles exact repeats."""
|
|
site = f"{raw}>{after.strip()}"
|
|
n = seen.get(site, 0)
|
|
seen[site] = n + 1
|
|
return f"{site}#{n}"
|