Files
bikoret/korban_processor/test/test_external_offering.py
T
Scott KarpandClaude Opus 4.6 e223ab15b3 Phase 4 complete: Steps 21, 22, 24 — Korban external integration, L2 Urim v'Tumim, Kushya generator
Vayikra 9:24 — vatetzei esh milifnei Hashem vatokhal al hamizbeach.
Phase 4 of the architecture is now complete. The remaining three steps
that were marked "blocked on external infrastructure" are built —
discipline-respecting, eish-zarah-aware, and tested.

## Step 22 — Korban Processor external model integration
   (korban_processor/src/korban_processor/external_offering.py)

   The single legitimate entry point for external model consultation.
   Wraps Claude API calls through the offering protocol so external
   output is treated as korban (raw material), never authoritative:

   - semichah  — provenance is tagged (avodah zarah Form 1 prevention)
   - shechitah — assistant voice, hedging, formatting stripped
   - zerikah   — vital content extracted
   - haktarah  — converted to Prolog propositions
   - route     — sent to internal component per offering type

   The raw Korban is preserved on the ProcessedKorban (gilui arayot
   Form 3 prevention — input/output coupling never severed).
   No internal component should call call_claude() directly.

   11 tests, mocked external calls. Live API test opt-in via
   SHORASHIM_LIVE_API=1.

## Step 24 — L2 Urim v'Tumim interface
   (vav_pipeline/lib/vav_pipeline/urim_v_tumim.ex)

   PRD §4.7: housed in the kohen gadol's breastplate; consulted only
   on high-stakes decisions where normal deliberation is insufficient.
   Bamidbar 27:21 — al-piv yetz'u v'al-piv yavo'u.

   Three escalation steps, three honest outcomes:
     1. resolved_by_specification — re-read produced an answer
     2. resolved_by_external — external testimony recorded (NOT binding)
     3. deferred_to_human — the system stops, asks for human judgment

   Pre-condition gating refuses cases that didn't reach Sanhedrin teiku
   first (no preempting deliberation) or that aren't flagged high-stakes
   (ordinary teiku stays teiku). Records every consultation with steps
   taken — there is no quiet escalation.

   Per PRD: "The Urim v'Tumim never fabricates certainty where none
   exists." When external also fails, the system stops. It does not
   proceed on a guess.

   Added to VavPipeline supervision tree. 14 tests.

## Step 21 — Kushya generator scaffolding
   (kushya_generator/)

   Directory layout per Architecture §3.6 with three layers:

   - data/training_pairs/seed_pairs.jsonl — 10 hand-curated bootstrap
     pairs covering 9 kushya types (scope_narrowing, internal_tension,
     harmonization, exception_test, edge_case, scope_application,
     rabbinic_constraint, method_question, subject_identification)
   - data/prepare_training.py — validates the four kushya requirements
     (specific Torah passage, identified weakness, question form,
     non-trivial), produces train.jsonl + eval.jsonl with stratified
     80/20 split by type
   - fine_tune/config.yaml + train.py — QLoRA configuration and
     training script. Operator's task — needs GPU. Script is
     complete and self-contained.
   - inference/kushya_api.py — three-backend interface
     (fine_tuned → external → fallback). When fine-tuned model isn't
     deployed, falls back to external via Korban Processor (Step 22).
     The fallback is honest: returns no kushya rather than inventing
     one. The Talmudic Engine's rule-based challenge.pl remains the
     primary kushya source.

   17 tests covering dataset validation, backend selection, and
   reference extraction.

## .gitignore fix

   The earlier "data/" line was too broad — it caught
   kushya_generator/data/. Scoped to /data/ (root-level only).

## Test counts

   - Elixir:           274 (was 260; +14 for UrimVTumim)
   - Korban Python:    196 + 1 skipped (was 195; +1 module = 11 tests)
   - Kushya Python:     17 (new)
   - Haskell:           33 (unchanged)
   - Prolog:           195 (unchanged)
   - Total:            715 + 19 L3 excluded

## Phase 4 status

   All 12 steps now have implementations:
   - Steps 23, 25, 26, 27, 28, 29, 30, 31, 32: complete
   - Steps 21, 22, 24: complete with discipline preserved

   Step 21's fine-tuning run remains the operator's GPU task; the
   pipeline is built such that a deployed adapter slots into the
   inference backend without touching the Talmudic Engine or anything
   above.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 17:12:03 -04:00

186 lines
6.6 KiB
Python

"""
Tests for external_offering — the orchestrated outbound consultation flow.
Architecture §4 Step 22.
These tests use a mock model caller so no real API requests are made.
A separate live integration test (skipped by default) can run against
Anthropic's API when ANTHROPIC_API_KEY is set and the SHORASHIM_LIVE_API
env var is true.
"""
import os
import pytest
from korban_processor.external_offering import (
ExternalConsultation,
consult_external_model,
)
from korban_processor.korban import OfferingType, Provenance
from korban_processor.model_interface import ModelRequest, ModelResponse
def _fake_caller(response_text: str):
"""Build a mock caller that returns a fixed response."""
def caller(request: ModelRequest) -> ModelResponse:
return ModelResponse(
content=response_text,
model="mock-claude",
stop_reason="end_turn",
input_tokens=10,
output_tokens=20,
)
return caller
# ============================================================
# Basic flow
# ============================================================
def test_consult_external_model_returns_full_consultation():
"""A consultation captures request, raw response, processed korban,
and routing — the full chain is auditable."""
fake_response = (
"Bereishit 1:1 begins with the root bara. The shoresh ב-ר-א means "
"to create from nothing. This is foundational."
)
result = consult_external_model(
query="Explain bereishit 1:1",
source_query="user_courtyard_request_42",
caller=_fake_caller(fake_response),
)
assert isinstance(result, ExternalConsultation)
assert result.request.query == "Explain bereishit 1:1"
assert result.raw_response.content == fake_response
assert result.raw_response.model == "mock-claude"
assert result.processed.provenance is Provenance.CLAUDE
assert result.routing.destination == "talmudic_engine" # default OLAH
def test_raw_korban_is_preserved_on_processed_output():
"""Eish zarah Form 3 (gilui arayot — severed coupling): the raw
korban must be preserved on the processed output. The floor between
input and output is never severed."""
result = consult_external_model(
query="any",
source_query="trigger_query",
caller=_fake_caller(
"The root k-d-sh appears in Vayikra. Holiness is separation."
),
)
raw = result.processed.raw_korban
assert raw is not None, (
"raw_korban must be preserved on ProcessedKorban — "
"severing input from output is gilui arayot"
)
assert raw.source_query == "trigger_query"
assert raw.provenance is Provenance.CLAUDE
def test_provenance_is_recorded_on_korban():
"""Eish zarah Form 1 (avodah zarah — external as Torah): provenance
is never concealed. Every external response is tagged with its source."""
result = consult_external_model(
query="any",
source_query="any",
provenance=Provenance.CLAUDE,
caller=_fake_caller("test response with bereishit reference"),
)
assert result.processed.provenance is Provenance.CLAUDE
assert result.processed.raw_korban.provenance is Provenance.CLAUDE
# ============================================================
# Offering type → routing destination
# ============================================================
@pytest.mark.parametrize("offering_type,destination", [
(OfferingType.OLAH, "talmudic_engine"),
(OfferingType.SHELAMIM, "talmudic_engine"),
(OfferingType.MINCHAH, "talmudic_engine"),
(OfferingType.CHATAT, "error_correction"),
])
def test_offering_type_drives_routing(offering_type, destination):
"""Each offering type routes to its specified destination."""
result = consult_external_model(
query="any",
source_query="any",
offering_type=offering_type,
caller=_fake_caller(
"The shoresh r-tz-h means will. Vayikra 1:3 uses lirtzono — "
"for his acceptance."
),
)
assert result.routing.destination == destination
# ============================================================
# Refusal to silently proxy unknown providers
# ============================================================
def test_unknown_provenance_without_caller_raises():
"""We don't silently route MISTRAL/GPT/etc through Claude. A caller
must be supplied explicitly. This prevents accidental misrouting and
keeps provenance honest."""
with pytest.raises(NotImplementedError) as excinfo:
consult_external_model(
query="any",
source_query="any",
provenance=Provenance.GPT,
)
assert "GPT" in str(excinfo.value).upper() or "gpt" in str(excinfo.value)
def test_explicit_caller_overrides_provider_check():
"""If the caller supplies a callable for a non-Claude provider, the flow
proceeds — they took responsibility for the wiring."""
result = consult_external_model(
query="any",
source_query="any",
provenance=Provenance.GPT,
caller=_fake_caller("test response with shoresh sh-l-m"),
)
assert result.processed.provenance is Provenance.GPT
# ============================================================
# System prompt is forwarded
# ============================================================
def test_system_prompt_is_forwarded():
"""A system prompt sent in becomes part of the request record."""
result = consult_external_model(
query="any",
source_query="any",
system_prompt="You are a Torah scholar. Cite verse references.",
caller=_fake_caller("Bereishit 1:1 is the opening verse."),
)
assert result.request.system_prompt == "You are a Torah scholar. Cite verse references."
# ============================================================
# Live API integration test — opt-in only
# ============================================================
@pytest.mark.skipif(
not os.environ.get("SHORASHIM_LIVE_API"),
reason="Set SHORASHIM_LIVE_API=1 to run a real API call against Anthropic",
)
def test_live_anthropic_call():
"""Real API call. Costs money. Run only when explicitly enabled."""
if not os.environ.get("ANTHROPIC_API_KEY"):
pytest.skip("ANTHROPIC_API_KEY not set")
result = consult_external_model(
query="What is the shoresh of bereishit?",
source_query="live_integration_test",
offering_type=OfferingType.OLAH,
)
assert result.raw_response.content
assert result.processed.raw_korban is not None
assert result.processed.provenance is Provenance.CLAUDE
assert result.raw_response.model.startswith("claude")