mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
bc3fa60011
Passthrough servers (parserless vLLM/llama.cpp, LM Studio, bare gateways) emit reasoning as literal <think>/<reasoning> blocks inside content, and only three of nine drained lanes stripped them: web_fetch tool results persisted raw think blocks into every following turn (#940), judge verdicts parsed through tag noise, and a draft verdict inside a think block could shadow the real one at the output guard. One rule at the seam now. drain_stream accumulates content in RUNS bounded by interleaving signals (provider-parsed reasoning deltas, tool-call deltas) with the interactive consumer's within-chunk ordering — reasoning, then content, then the tool-call close — and splits each run through split_inline_reasoning, the one-shot form of the interactive lane's ThinkTagSplitter: a pure raw split, exactly equivalent to the streaming form on every catalog case. One trim policy exists and the drain owns it: blank edge lines are trimmed once over the joined runs when a tag was consumed, so tag residue dies at the edges while genuine inter-run paragraph separators survive. Extracted text is appended to result.reasoning after any server-parsed reasoning with a blank-line boundary and rides the native lane as the reasoning_text synth block. Orphan CLOSE tags deliberately pass through byte-identical: a close whose open never arrived is indistinguishable from prose QUOTING the tag, and drained lanes routinely quote third-party text — reclassifying would let a malicious page containing the literal tag destroy the extraction that cites it. The title lane keeps a local rfind peel as display-string formatting. The citations footer folds only onto non-blank content — sourcing for an answer that does not exist is dropped rather than handed to emptiness checks as a footer-only "answer". Every private strip is deleted: the title lane's strip, the summarizer strip, _strip_reasoning itself, and the optimizer's five regexes (_strip_markdown_fence is now the one fence rule, applied to normalized model output only, never to or-fallback values). Think-only and whitespace-only responses drain to blank content, and every lane's no-answer fallback gates on blankness: web_fetch returns an honest extraction-error card, the intent judge takes the empty-retry ladder, the task-agent synthesis reports "(no output)", and the optimizer keeps the current observer system and prompt verbatim on no-answer passes. Final-say reads (optimizer analyst, eval final_content, the notify hook) use trajectory.final_assistant_text — the last assistant turn only, never an earlier narration presented as the conclusion — while last_assistant_text is the salvage walk (task_agent partial-work recovery), skipping tool-call-only, all-reasoning, and whitespace-only turns. Perception memoizes every completed description immediately, including an empty one — one perceive per key, ever — under a commit-lock guard so an empty result never overwrites a concurrently memoized real description; an all-reasoning perception model pins the placeholder until restart, and the remediation is server-side (a reasoning parser or the template thinking toggle on the perception alias). A true double-reasoning shape (inline-extracted text alongside a native reasoning block) logs chars-only at the drain, where it is distinguishable from the routine reasoning_delta mirror. The dialect's semantics are pinned as one table (tests/_reasoning_dialect.py) driven through shared fixtures (think_tag_stream, seam_provider): one-shot conformance, the exact one-shot/streaming equivalence property, the drain seam rules including quoted-tag safety, run-boundary and separator-preservation pins, per-lane pins for all nine lanes, and the empty-content assistant wire shape. Closes #965. Closes #940.
128 lines
4.4 KiB
Python
128 lines
4.4 KiB
Python
"""#965 per-lane pins for the optimizer: content arrives IR-clean from the
|
|
drain seam, and the emptiness fallbacks are load-bearing.
|
|
|
|
The optimizer's five private regex strips are gone; these rows pin (a) that
|
|
tagged model output still yields clean prompts/systems (the seam does the
|
|
work now), and (b) the two ``or``-fallback flips: an all-reasoning pass
|
|
keeps the CURRENT observer system / prompt verbatim instead of wiping the
|
|
observer system or evaluating an empty-prompt evolution node.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from tests._session_helpers import seam_provider
|
|
from turnstone.optimizer import _observe_and_update_optimizer, _propose_prompt_modification
|
|
|
|
_OBSERVER_SYSTEM = "You observe optimization runs and refine the optimizer system."
|
|
|
|
|
|
def test_observer_tagged_output_yields_clean_system() -> None:
|
|
out = _observe_and_update_optimizer(
|
|
client=object(),
|
|
model="m",
|
|
optimizer_system=_OBSERVER_SYSTEM,
|
|
iterations=[],
|
|
provider=seam_provider("<think>weighing the history</think>Refined observer text."),
|
|
)
|
|
assert out == "Refined observer text."
|
|
|
|
|
|
def test_observer_think_only_keeps_current_system_verbatim() -> None:
|
|
# Before the seam, the private regex emptied the text AFTER the
|
|
# ``or``-fallback check and the observer system was WIPED.
|
|
out = _observe_and_update_optimizer(
|
|
client=object(),
|
|
model="m",
|
|
optimizer_system=_OBSERVER_SYSTEM,
|
|
iterations=[],
|
|
provider=seam_provider("<think>no conclusion reached</think>"),
|
|
)
|
|
assert out == _OBSERVER_SYSTEM
|
|
|
|
|
|
def test_proposal_tagged_output_yields_clean_prompt() -> None:
|
|
out = _propose_prompt_modification(
|
|
client=object(),
|
|
model="m",
|
|
current_prompt="Old prompt.",
|
|
test_cases=[],
|
|
iteration_result={"cases": {}},
|
|
history=[],
|
|
provider=seam_provider("<think>rework section two</think>New prompt text."),
|
|
)
|
|
assert out == "New prompt text."
|
|
|
|
|
|
def test_proposal_think_only_keeps_current_prompt() -> None:
|
|
# An all-reasoning pass reads as "no changes" downstream — never an
|
|
# empty-prompt evolution-tree node.
|
|
out = _propose_prompt_modification(
|
|
client=object(),
|
|
model="m",
|
|
current_prompt="Old prompt.",
|
|
test_cases=[],
|
|
iteration_result={"cases": {}},
|
|
history=[],
|
|
provider=seam_provider("<think>hmm</think>"),
|
|
)
|
|
assert out == "Old prompt."
|
|
|
|
|
|
def test_observer_whitespace_only_content_keeps_current_system() -> None:
|
|
# Falsiness is checked AFTER normalization: whitespace-only content
|
|
# (tag-free, so seam-byte-identical and truthy) must not strip to ""
|
|
# past the fallback and wipe the observer system.
|
|
out = _observe_and_update_optimizer(
|
|
client=object(),
|
|
model="m",
|
|
optimizer_system=_OBSERVER_SYSTEM,
|
|
iterations=[],
|
|
provider=seam_provider("\n\n \n"),
|
|
)
|
|
assert out == _OBSERVER_SYSTEM
|
|
|
|
|
|
def test_proposal_whitespace_only_content_keeps_current_prompt() -> None:
|
|
out = _propose_prompt_modification(
|
|
client=object(),
|
|
model="m",
|
|
current_prompt="Old prompt.",
|
|
test_cases=[],
|
|
iteration_result={"cases": {}},
|
|
history=[],
|
|
provider=seam_provider("\n\n"),
|
|
)
|
|
assert out == "Old prompt."
|
|
|
|
|
|
def test_proposal_fallback_prompt_is_never_fence_stripped() -> None:
|
|
# The fence-strip normalizes MODEL output only; a no-answer pass keeps
|
|
# a fence-bearing current prompt VERBATIM, never reduced to its fence
|
|
# innards.
|
|
fenced_prompt = "Do the task.\n```python\nexample()\n```\nBe precise."
|
|
out = _propose_prompt_modification(
|
|
client=object(),
|
|
model="m",
|
|
current_prompt=fenced_prompt,
|
|
test_cases=[],
|
|
iteration_result={"cases": {}},
|
|
history=[],
|
|
provider=seam_provider("<think>no conclusion</think>"),
|
|
)
|
|
assert out == fenced_prompt
|
|
|
|
|
|
def test_proposal_model_output_fence_is_unwrapped() -> None:
|
|
# The fence rule applies to MODEL output (only): a fenced proposal
|
|
# yields its innards, and prose outside the fences is discarded.
|
|
out = _propose_prompt_modification(
|
|
client=object(),
|
|
model="m",
|
|
current_prompt="Old prompt.",
|
|
test_cases=[],
|
|
iteration_result={"cases": {}},
|
|
history=[],
|
|
provider=seam_provider("Here you go:\n```\nNew prompt text.\n```\nHope that helps!"),
|
|
)
|
|
assert out == "New prompt text."
|