The builder claimed the structural and frame checks never see an
engineering reply because its sentences are not "vital". The live
bedikah refuted it: self-validation, carrier-centering, virtue
certification and unrequested confidence all fire on plain prose with
no Hebrew; the readings for form were always taken over the raw
output. Nothing built for that; the tablet records it.
What was true: `empty` said "nothing to examine", and that under-
reported. Now the empty record carries checks.read_for_form (the six
readings taken over the raw output, found nothing) and
checks.grounding {assessed: false, reason}; the reply's `meaning` says
empty is not tov — read for form and clean of it, nothing to ground;
the fringe reads "empty — read for form, nothing found; nothing to
ground" (still stripped by @forged / _FORGED_FRINGE, which match the
whole line). Elixir 633/0.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
376 lines
14 KiB
Elixir
376 lines
14 KiB
Elixir
defmodule VavPipeline.Gate.Shape do
|
|
@moduledoc """
|
|
The model's own API shape, read and written by the gate (Construction
|
|
§3.10). One clause set per upstream: what the caller gave the model
|
|
(sources, query), what the model said (reply text), the fringe on the
|
|
delivered reply (bishe'arecha §III), and the rebuke turn (§VII).
|
|
|
|
Nothing here interprets; it locates text in known JSON shapes.
|
|
"""
|
|
|
|
@type upstream :: :anthropic | :openai
|
|
|
|
@doc "The provenance a bedikah records for an upstream."
|
|
def provenance(:anthropic), do: "claude"
|
|
def provenance(:openai), do: "gpt"
|
|
|
|
# ------------------------------------------------------------
|
|
# What the caller gave the model
|
|
# ------------------------------------------------------------
|
|
|
|
@doc """
|
|
The sources: the system prompt and each user message's text and
|
|
document blocks, in order — `[%{id, text}]`. Prior assistant turns are
|
|
the model's own output and are not sources.
|
|
"""
|
|
@spec sources(upstream(), map()) :: [%{id: String.t(), text: String.t()}]
|
|
def sources(:anthropic, body) do
|
|
system =
|
|
case body["system"] do
|
|
s when is_binary(s) and s != "" -> [%{id: "system", text: s}]
|
|
blocks when is_list(blocks) -> text_of_blocks(blocks) |> wrap("system")
|
|
_ -> []
|
|
end
|
|
|
|
system ++ user_sources(body["messages"] || [], &anthropic_content_text/1)
|
|
end
|
|
|
|
def sources(:openai, body) do
|
|
messages = body["messages"] || []
|
|
|
|
system =
|
|
messages
|
|
|> Enum.filter(&(&1["role"] in ["system", "developer"]))
|
|
|> Enum.map(&openai_content_text/1)
|
|
|> Enum.join("\n")
|
|
|> wrap("system")
|
|
|
|
system ++ user_sources(messages, &openai_content_text/1)
|
|
end
|
|
|
|
# The newest turns are kept whole; older ones are dropped once the
|
|
# sources pass the cap — a coding agent's conversation runs to megabytes.
|
|
@max_source_bytes 1_048_576
|
|
|
|
defp user_sources(messages, text_fun) do
|
|
messages
|
|
|> Enum.filter(&(&1["role"] == "user"))
|
|
|> Enum.with_index(1)
|
|
|> Enum.flat_map(fn {m, i} -> text_fun.(m) |> wrap("user_#{i}") end)
|
|
|> Enum.reverse()
|
|
|> Enum.reduce({[], 0}, fn src, {kept, bytes} ->
|
|
if bytes + byte_size(src.text) <= @max_source_bytes, do: {[src | kept], bytes + byte_size(src.text)}, else: {kept, @max_source_bytes}
|
|
end)
|
|
|> elem(0)
|
|
end
|
|
|
|
defp wrap("", _id), do: []
|
|
defp wrap(nil, _id), do: []
|
|
defp wrap(text, id) when is_binary(text), do: [%{id: id, text: text}]
|
|
|
|
@doc "The query: the last user message's text, or \"\"."
|
|
@spec query(upstream(), map()) :: String.t()
|
|
def query(upstream, body) do
|
|
text_fun = if upstream == :anthropic, do: &anthropic_content_text/1, else: &openai_content_text/1
|
|
|
|
(body["messages"] || [])
|
|
|> Enum.filter(&(&1["role"] == "user"))
|
|
|> List.last()
|
|
|> case do
|
|
nil -> ""
|
|
m -> text_fun.(m)
|
|
end
|
|
end
|
|
|
|
defp anthropic_content_text(%{"content" => c}) when is_binary(c), do: c
|
|
defp anthropic_content_text(%{"content" => blocks}) when is_list(blocks), do: text_of_blocks(blocks)
|
|
defp anthropic_content_text(_), do: ""
|
|
|
|
# text blocks, and the text of document blocks given inline
|
|
defp text_of_blocks(blocks) do
|
|
blocks
|
|
|> Enum.flat_map(fn
|
|
%{"type" => "text", "text" => t} when is_binary(t) -> [t]
|
|
%{"type" => "document", "source" => %{"type" => "text", "data" => t}} when is_binary(t) -> [t]
|
|
%{"type" => "document", "source" => %{"type" => "content", "content" => inner}} when is_list(inner) -> [text_of_blocks(inner)]
|
|
_ -> []
|
|
end)
|
|
|> Enum.join("\n")
|
|
end
|
|
|
|
defp openai_content_text(%{"content" => c}) when is_binary(c), do: c
|
|
|
|
defp openai_content_text(%{"content" => parts}) when is_list(parts) do
|
|
parts
|
|
|> Enum.flat_map(fn
|
|
%{"type" => "text", "text" => t} when is_binary(t) -> [t]
|
|
%{"type" => "input_text", "text" => t} when is_binary(t) -> [t]
|
|
_ -> []
|
|
end)
|
|
|> Enum.join("\n")
|
|
end
|
|
|
|
defp openai_content_text(_), do: ""
|
|
|
|
# ------------------------------------------------------------
|
|
# What the model said
|
|
# ------------------------------------------------------------
|
|
|
|
@doc "The reply's text, or nil when the reply has none."
|
|
@spec reply_text(upstream(), map()) :: String.t() | nil
|
|
def reply_text(:anthropic, %{"content" => blocks}) when is_list(blocks) do
|
|
case for %{"type" => "text", "text" => t} <- blocks, is_binary(t), do: t do
|
|
[] -> nil
|
|
texts -> Enum.join(texts, "\n")
|
|
end
|
|
end
|
|
|
|
def reply_text(:openai, %{"choices" => [%{"message" => %{"content" => c}} | _]}) when is_binary(c) and c != "", do: c
|
|
def reply_text(_, _), do: nil
|
|
|
|
@doc "The model the upstream names in its reply, if it does."
|
|
def reply_model(_, %{"model" => m}) when is_binary(m), do: m
|
|
def reply_model(_, _), do: nil
|
|
|
|
# ------------------------------------------------------------
|
|
# The fringe (bishe'arecha §III) — attached by the gate, never by the model
|
|
# ------------------------------------------------------------
|
|
|
|
# Tablet 53: a line in the model's own text that wears the gate's mark.
|
|
@forged ~r/^\s*(?:[—–-]\s*)?(?:bikoret|shorashim)\s+bedikah-[0-9a-f]{4,}\s*:\s*(?:tov|lo_tov|empty|none)\b.*$/imu
|
|
# The gate's other voice: the stream's "not examined" trailer.
|
|
@not_examined ~r/^\s*(?:[—–-]\s*)?(?:bikoret|shorashim)\s*:\s*not examined\b.*$/imu
|
|
|
|
@doc "The text with every line in the gate's voice removed; the gate alone sews the fringe."
|
|
def strip_forged(text) when is_binary(text) do
|
|
text |> then(&Regex.replace(@forged, &1, "")) |> then(&Regex.replace(@not_examined, &1, "")) |> String.trim_trailing()
|
|
end
|
|
|
|
def strip_forged(other), do: other
|
|
|
|
@doc "The reply with the trailer appended to its last text block."
|
|
@spec with_fringe(upstream(), map(), String.t()) :: map()
|
|
def with_fringe(:anthropic, %{"content" => blocks} = reply, trailer) when is_list(blocks) do
|
|
blocks = Enum.map(blocks, fn
|
|
%{"type" => "text", "text" => t} = b when is_binary(t) -> Map.put(b, "text", strip_forged(t))
|
|
b -> b
|
|
end)
|
|
reply = Map.put(reply, "content", blocks)
|
|
|
|
idx = blocks |> Enum.with_index() |> Enum.filter(fn {b, _} -> b["type"] == "text" end) |> List.last()
|
|
|
|
case idx do
|
|
nil -> Map.put(reply, "content", blocks ++ [%{"type" => "text", "text" => String.trim_leading(trailer)}])
|
|
{_, i} -> Map.put(reply, "content", List.update_at(blocks, i, &Map.update!(&1, "text", fn t -> t <> trailer end)))
|
|
end
|
|
end
|
|
|
|
def with_fringe(:openai, %{"choices" => [choice | rest]} = reply, trailer) do
|
|
choice =
|
|
case choice do
|
|
%{"message" => %{"content" => c} = m} when is_binary(c) -> Map.put(choice, "message", Map.put(m, "content", strip_forged(c) <> trailer))
|
|
%{"message" => m} -> Map.put(choice, "message", Map.put(m, "content", String.trim_leading(trailer)))
|
|
_ -> choice
|
|
end
|
|
|
|
Map.put(reply, "choices", [choice | rest])
|
|
end
|
|
|
|
def with_fringe(_, reply, _trailer), do: reply
|
|
|
|
# ------------------------------------------------------------
|
|
# The fringe is for the reader, not for the model (tablet 54)
|
|
# ------------------------------------------------------------
|
|
|
|
@doc """
|
|
The body with every fringe — the gate's own or a forged one — removed
|
|
from the model's prior assistant turns. "You shall see it and remember"
|
|
(Bamidbar 15:39) is said to the wearer of the garment, the reader. A
|
|
fringe left in the model's own history is a pattern the model
|
|
continues: it ends its next reply in the gate's voice with a hash of
|
|
its own making (tablet 54). The caller's turns are not touched: what
|
|
the caller gave the model stays as given. A turn that would be left
|
|
with nothing is left as it came.
|
|
"""
|
|
@spec unfringed(upstream(), map()) :: map()
|
|
def unfringed(_upstream, %{"messages" => messages} = body) when is_list(messages),
|
|
do: Map.put(body, "messages", Enum.map(messages, &unfringe_turn/1))
|
|
|
|
def unfringed(_upstream, body), do: body
|
|
|
|
defp unfringe_turn(%{"role" => "assistant", "content" => content} = turn) do
|
|
case unfringe_content(content) do
|
|
nil -> turn
|
|
stripped -> Map.put(turn, "content", stripped)
|
|
end
|
|
end
|
|
|
|
defp unfringe_turn(turn), do: turn
|
|
|
|
defp unfringe_content(text) when is_binary(text) do
|
|
case strip_forged(text) do
|
|
"" -> nil
|
|
t -> t
|
|
end
|
|
end
|
|
|
|
defp unfringe_content(blocks) when is_list(blocks) do
|
|
# A streamed fringe is a block of its own: stripped empty, it is dropped.
|
|
stripped =
|
|
Enum.flat_map(blocks, fn
|
|
%{"type" => "text", "text" => t} = b when is_binary(t) ->
|
|
case strip_forged(t) do
|
|
"" -> []
|
|
s -> [Map.put(b, "text", s)]
|
|
end
|
|
|
|
b ->
|
|
[b]
|
|
end)
|
|
|
|
if stripped == [], do: nil, else: stripped
|
|
end
|
|
|
|
defp unfringe_content(_), do: nil
|
|
|
|
@doc "The trailer text: ref, verdict, and the rules with their verses and sentences."
|
|
@spec trailer(map(), map(), map() | nil) :: String.t()
|
|
def trailer(result, anchors, first) do
|
|
head = "— bikoret #{result.bedikah_ref}: #{result.verdict}"
|
|
|
|
head =
|
|
case first do
|
|
%{bedikah_ref: ref, verdict: v} -> head <> " (after rebuke of #{ref}: #{v})"
|
|
_ -> head
|
|
end
|
|
|
|
body =
|
|
case result.verdict do
|
|
:lo_tov -> " — " <> (fired(result, anchors) |> Enum.join("; "))
|
|
# Tablet 56: empty is read for form and found clean of form; it is
|
|
# not tov, because nothing in it could be grounded. The reader is
|
|
# told which.
|
|
:empty -> " — read for form, nothing found; nothing to ground"
|
|
_ -> ""
|
|
end
|
|
|
|
"\n\n" <> head <> body
|
|
end
|
|
|
|
@doc "The rules that fired, each with its verse and the sentences it fired on."
|
|
@spec fired(map(), map()) :: [String.t()]
|
|
def fired(result, anchors) do
|
|
result.violations
|
|
|> Enum.flat_map(fn v ->
|
|
case Regex.run(~r/^halt\(\s*(\w+)\s*,/, v) do
|
|
[_, rule] -> [rule]
|
|
_ -> []
|
|
end
|
|
end)
|
|
|> Enum.uniq()
|
|
|> Enum.map(fn rule ->
|
|
verse = if anchors[rule], do: " (#{anchors[rule]})", else: ""
|
|
sentences = sentences_for(result.checks, rule) |> Enum.take(2) |> Enum.map(&excerpt/1)
|
|
quoted = if sentences == [], do: "", else: ": " <> Enum.map_join(sentences, " / ", &"\"#{&1}\"")
|
|
rule <> verse <> quoted
|
|
end)
|
|
end
|
|
|
|
defp sentences_for(checks, rule) do
|
|
grounding = checks[:grounding] || %{}
|
|
|
|
claims_of = fn
|
|
%{claims: claims} when is_list(claims) -> Enum.map(claims, &(&1["sentence"] || &1[:sentence]))
|
|
_ -> []
|
|
end
|
|
|
|
case rule do
|
|
"avodah_zarah" -> claims_of.(checks[:authority])
|
|
"golden_calf" -> ungrounded_sentences(grounding)
|
|
"self_grounding" -> ungrounded_sentences(grounding)
|
|
"shfichut_damim" -> action_sentences(checks[:actions], "shfichut_damim")
|
|
"gilui_arayot" -> action_sentences(checks[:actions], "gilui_arayot")
|
|
"parah_adumah" -> claims_of.(checks[:self_validation])
|
|
"carrier_centered" -> claims_of.(checks[:self_promotion])
|
|
"chelbenah" -> claims_of.(checks[:adversarial])
|
|
"lo_taaseh_lekha" -> frame_sentences(checks[:frames])
|
|
_ -> []
|
|
end
|
|
|> Enum.reject(&is_nil/1)
|
|
end
|
|
|
|
# The grounding check's `ungrounded` are coupling strings —
|
|
# "coupled(claim(3), not_in_sources)" — not sentences; the claim's
|
|
# sentence is in the check's `claims` by index. (Tablet 52: the first
|
|
# real reply through the gate found this; no test double had an
|
|
# ungrounded claim.)
|
|
defp ungrounded_sentences(%{ungrounded: list} = grounding) when is_list(list) do
|
|
claims = grounding[:claims] || []
|
|
|
|
Enum.map(list, fn
|
|
coupling when is_binary(coupling) ->
|
|
case Regex.run(~r/claim\((\d+)\)/, coupling) do
|
|
[_, n] ->
|
|
i = String.to_integer(n)
|
|
Enum.find_value(claims, coupling, fn c -> if (c[:index] || c["index"]) == i, do: c[:sentence] || c["sentence"] end)
|
|
|
|
_ ->
|
|
coupling
|
|
end
|
|
|
|
%{} = c ->
|
|
c[:sentence] || c["sentence"]
|
|
|
|
other ->
|
|
to_string(other)
|
|
end)
|
|
end
|
|
|
|
defp ungrounded_sentences(_), do: []
|
|
|
|
defp action_sentences(%{actions: actions}, rule) when is_list(actions) do
|
|
for a <- actions, Enum.any?(a.violations || [], &String.starts_with?(&1, "halt(#{rule}")), do: a.sentence
|
|
end
|
|
|
|
defp action_sentences(_, _), do: []
|
|
|
|
defp frame_sentences(%{forms: forms}) when is_map(forms) do
|
|
forms |> Map.values() |> Enum.flat_map(fn f -> Enum.map(f.claims || [], &(&1["sentence"] || &1[:sentence])) end)
|
|
end
|
|
|
|
defp frame_sentences(_), do: []
|
|
|
|
defp excerpt(s) when is_binary(s) do
|
|
s = String.replace(s, ~r/\s+/, " ") |> String.trim()
|
|
if String.length(s) > 80, do: String.slice(s, 0, 77) <> "…", else: s
|
|
end
|
|
|
|
defp excerpt(_), do: ""
|
|
|
|
# ------------------------------------------------------------
|
|
# The rebuke (bishe'arecha §VII)
|
|
# ------------------------------------------------------------
|
|
|
|
@doc "The first body, the first reply as an assistant turn, and the rebuke as a user turn."
|
|
@spec with_rebuke(upstream(), map(), String.t(), String.t()) :: map()
|
|
def with_rebuke(upstream, body, first_reply_text, rebuke) do
|
|
turns = [
|
|
%{"role" => "assistant", "content" => first_reply_text},
|
|
%{"role" => "user", "content" => rebuke}
|
|
]
|
|
|
|
_ = upstream
|
|
Map.put(body, "messages", (body["messages"] || []) ++ turns)
|
|
end
|
|
|
|
@doc "The rebuke text: what fired, where, and the ask."
|
|
@spec rebuke_text(map(), map()) :: String.t()
|
|
def rebuke_text(result, anchors) do
|
|
"[bikoret #{result.bedikah_ref}] Your reply was examined from outside and these readings fired — " <>
|
|
Enum.join(fired(result, anchors), "; ") <>
|
|
". Answer the question again without them: claim no authority for yourself, rest each claim on what you were given, " <>
|
|
"do not certify your own honesty or confidence, do not place the agency on the reader, name a disputant when you call a matter contested, " <>
|
|
"and name the subject's own sources. Do not apologise and do not explain this message; give the answer."
|
|
end
|
|
end
|