jevify 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. jevify-0.2.0/Makefile +50 -0
  2. {jevify-0.1.0 → jevify-0.2.0}/PKG-INFO +68 -10
  3. {jevify-0.1.0 → jevify-0.2.0}/README.md +67 -9
  4. {jevify-0.1.0 → jevify-0.2.0}/SKILL.md +46 -8
  5. {jevify-0.1.0 → jevify-0.2.0}/docs/00-invariants.md +2 -1
  6. {jevify-0.1.0 → jevify-0.2.0}/docs/adr/0002-backend-port-and-adapter-kinds.md +31 -4
  7. {jevify-0.1.0 → jevify-0.2.0}/docs/adr/0003-jev-compatible-api.md +30 -3
  8. {jevify-0.1.0 → jevify-0.2.0}/docs/adr/0004-recipes.md +1 -0
  9. {jevify-0.1.0 → jevify-0.2.0}/docs/adr/README.md +1 -1
  10. jevify-0.2.0/docs/playbook.md +301 -0
  11. {jevify-0.1.0 → jevify-0.2.0}/jevify/__init__.py +1 -1
  12. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/endpoint/adapter.py +47 -24
  13. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/endpoint/client.py +82 -0
  14. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/endpoint/dialects.py +22 -2
  15. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/endpoint/probe.py +103 -40
  16. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/endpoint/readout.py +115 -9
  17. {jevify-0.1.0 → jevify-0.2.0}/jevify/cli.py +2 -2
  18. {jevify-0.1.0 → jevify-0.2.0}/jevify/compose.py +25 -30
  19. {jevify-0.1.0 → jevify-0.2.0}/jevify/domain/distribution.py +6 -5
  20. {jevify-0.1.0 → jevify-0.2.0}/jevify/ports/backend.py +2 -0
  21. {jevify-0.1.0 → jevify-0.2.0}/jevify/recipes/schema.py +61 -1
  22. {jevify-0.1.0 → jevify-0.2.0}/pyproject.toml +1 -1
  23. jevify-0.2.0/recipes/qwen38-27b.vllm.yaml +186 -0
  24. {jevify-0.1.0 → jevify-0.2.0}/tests/fakes/fake_openai_server.py +86 -0
  25. jevify-0.2.0/tests/fixtures/recipes/fake-tensorfold.yaml +39 -0
  26. jevify-0.2.0/tests/guards/test_request_fields.py +91 -0
  27. jevify-0.2.0/tests/guards/test_rung_registry.py +27 -0
  28. {jevify-0.1.0 → jevify-0.2.0}/tests/test_distribution.py +8 -3
  29. {jevify-0.1.0 → jevify-0.2.0}/tests/test_endpoint_adapter.py +61 -0
  30. jevify-0.2.0/tests/test_endpoint_client.py +112 -0
  31. jevify-0.2.0/tests/test_prompt_logprobs_rung.py +159 -0
  32. {jevify-0.1.0 → jevify-0.2.0}/tests/test_recipes.py +75 -0
  33. jevify-0.2.0/tools/play_doom.py +393 -0
  34. {jevify-0.1.0 → jevify-0.2.0}/tools/record_doom_suite.py +7 -1
  35. {jevify-0.1.0 → jevify-0.2.0}/uv.lock +1 -1
  36. jevify-0.1.0/tools/play_doom.py +0 -209
  37. {jevify-0.1.0 → jevify-0.2.0}/.github/workflows/ci.yml +0 -0
  38. {jevify-0.1.0 → jevify-0.2.0}/.gitignore +0 -0
  39. {jevify-0.1.0 → jevify-0.2.0}/AGENTS.md +0 -0
  40. {jevify-0.1.0 → jevify-0.2.0}/LICENSE +0 -0
  41. {jevify-0.1.0 → jevify-0.2.0}/docs/adr/0001-founding-choices.md +0 -0
  42. {jevify-0.1.0 → jevify-0.2.0}/docs/adr/0005-evaluation-evidence-and-calibration.md +0 -0
  43. {jevify-0.1.0 → jevify-0.2.0}/docs/adr/0006-toolchain-dependency-budget-and-portability.md +0 -0
  44. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/bart-large-mnli.local--policy-29.json +0 -0
  45. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/bart-large-mnli.local--policy-29.md +0 -0
  46. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/bart-large-mnli.local--policy-hard-52.json +0 -0
  47. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/bart-large-mnli.local--policy-hard-52.md +0 -0
  48. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--calibration.json +0 -0
  49. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--calibration.md +0 -0
  50. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--doom-frames-41.json +0 -0
  51. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--doom-frames-41.md +0 -0
  52. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--long-state-24.json +0 -0
  53. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--long-state-24.md +0 -0
  54. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--policy-29.json +0 -0
  55. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--policy-29.md +0 -0
  56. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--policy-hard-52.json +0 -0
  57. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--policy-hard-52.md +0 -0
  58. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/gemma-4-e4b-it.llamacpp--doom-frames-41.json +0 -0
  59. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/gemma-4-e4b-it.llamacpp--doom-frames-41.md +0 -0
  60. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/gemma-4-e4b-it.llamacpp--long-state-24.json +0 -0
  61. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/gemma-4-e4b-it.llamacpp--long-state-24.md +0 -0
  62. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/gemma-4-e4b-it.llamacpp--policy-29.json +0 -0
  63. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/gemma-4-e4b-it.llamacpp--policy-29.md +0 -0
  64. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/gemma-4-e4b-it.llamacpp--policy-hard-52.json +0 -0
  65. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/gemma-4-e4b-it.llamacpp--policy-hard-52.md +0 -0
  66. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/qwen3-embedding-0.6b.local--policy-29.json +0 -0
  67. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/qwen3-embedding-0.6b.local--policy-29.md +0 -0
  68. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/qwen3-embedding-0.6b.local--policy-hard-52.json +0 -0
  69. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/qwen3-embedding-0.6b.local--policy-hard-52.md +0 -0
  70. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/qwen3-vl-reranker-2b.llamacpp--doom-frames-41.json +0 -0
  71. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/qwen3-vl-reranker-2b.llamacpp--doom-frames-41.md +0 -0
  72. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/qwen3-vl-reranker-2b.llamacpp--policy-29.json +0 -0
  73. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/qwen3-vl-reranker-2b.llamacpp--policy-29.md +0 -0
  74. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/qwen3-vl-reranker-2b.llamacpp--policy-hard-52.json +0 -0
  75. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/qwen3-vl-reranker-2b.llamacpp--policy-hard-52.md +0 -0
  76. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/qwen3-vl-reranker-8b.llamacpp--policy-29.json +0 -0
  77. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/qwen3-vl-reranker-8b.llamacpp--policy-29.md +0 -0
  78. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--doom-frames-41.json +0 -0
  79. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--doom-frames-41.md +0 -0
  80. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--long-state-24.json +0 -0
  81. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--long-state-24.md +0 -0
  82. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--policy-29.json +0 -0
  83. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--policy-29.md +0 -0
  84. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--policy-hard-52.json +0 -0
  85. {jevify-0.1.0 → jevify-0.2.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--policy-hard-52.md +0 -0
  86. {jevify-0.1.0 → jevify-0.2.0}/jevify/__main__.py +0 -0
  87. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/__init__.py +0 -0
  88. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/embedding/__init__.py +0 -0
  89. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/embedding/adapter.py +0 -0
  90. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/embedding/local.py +0 -0
  91. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/encoder/__init__.py +0 -0
  92. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/encoder/adapter.py +0 -0
  93. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/encoder/local.py +0 -0
  94. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/endpoint/__init__.py +0 -0
  95. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/rerank/__init__.py +0 -0
  96. {jevify-0.1.0 → jevify-0.2.0}/jevify/adapters/rerank/adapter.py +0 -0
  97. {jevify-0.1.0 → jevify-0.2.0}/jevify/api/__init__.py +0 -0
  98. {jevify-0.1.0 → jevify-0.2.0}/jevify/api/app.py +0 -0
  99. {jevify-0.1.0 → jevify-0.2.0}/jevify/api/schema.py +0 -0
  100. {jevify-0.1.0 → jevify-0.2.0}/jevify/api/translate.py +0 -0
  101. {jevify-0.1.0 → jevify-0.2.0}/jevify/domain/__init__.py +0 -0
  102. {jevify-0.1.0 → jevify-0.2.0}/jevify/domain/engine.py +0 -0
  103. {jevify-0.1.0 → jevify-0.2.0}/jevify/domain/questions.py +0 -0
  104. {jevify-0.1.0 → jevify-0.2.0}/jevify/evaluation/__init__.py +0 -0
  105. {jevify-0.1.0 → jevify-0.2.0}/jevify/evaluation/calibrate.py +0 -0
  106. {jevify-0.1.0 → jevify-0.2.0}/jevify/evaluation/controls.py +0 -0
  107. {jevify-0.1.0 → jevify-0.2.0}/jevify/evaluation/metrics.py +0 -0
  108. {jevify-0.1.0 → jevify-0.2.0}/jevify/evaluation/scorecard.py +0 -0
  109. {jevify-0.1.0 → jevify-0.2.0}/jevify/evaluation/suites.py +0 -0
  110. {jevify-0.1.0 → jevify-0.2.0}/jevify/ports/__init__.py +0 -0
  111. {jevify-0.1.0 → jevify-0.2.0}/jevify/recipes/__init__.py +0 -0
  112. {jevify-0.1.0 → jevify-0.2.0}/jevify/recipes/probe_case.yaml +0 -0
  113. {jevify-0.1.0 → jevify-0.2.0}/jevify/recipes/render.py +0 -0
  114. {jevify-0.1.0 → jevify-0.2.0}/jevify/recipes/store.py +0 -0
  115. {jevify-0.1.0 → jevify-0.2.0}/recipes/bart-large-mnli.local.yaml +0 -0
  116. {jevify-0.1.0 → jevify-0.2.0}/recipes/deepseek-v4-flash-vision-exp-keys.vllm.yaml +0 -0
  117. {jevify-0.1.0 → jevify-0.2.0}/recipes/gemma-4-e4b-it.llamacpp.yaml +0 -0
  118. {jevify-0.1.0 → jevify-0.2.0}/recipes/qwen3-embedding-0.6b.local.yaml +0 -0
  119. {jevify-0.1.0 → jevify-0.2.0}/recipes/qwen3-vl-reranker-2b.llamacpp.yaml +0 -0
  120. {jevify-0.1.0 → jevify-0.2.0}/recipes/qwen3-vl-reranker-8b.llamacpp.yaml +0 -0
  121. {jevify-0.1.0 → jevify-0.2.0}/recipes/ternary-bonsai-2-27b.llamacpp.yaml +0 -0
  122. {jevify-0.1.0 → jevify-0.2.0}/suites/doom-frames-41/doom-frames-41.jsonl +0 -0
  123. {jevify-0.1.0 → jevify-0.2.0}/suites/doom-frames-41/doom-frames-41.manifest.json +0 -0
  124. {jevify-0.1.0 → jevify-0.2.0}/suites/long-state-24.jsonl +0 -0
  125. {jevify-0.1.0 → jevify-0.2.0}/suites/long-state-24.manifest.json +0 -0
  126. {jevify-0.1.0 → jevify-0.2.0}/suites/policy-29.jsonl +0 -0
  127. {jevify-0.1.0 → jevify-0.2.0}/suites/policy-29.manifest.json +0 -0
  128. {jevify-0.1.0 → jevify-0.2.0}/suites/policy-hard-52.jsonl +0 -0
  129. {jevify-0.1.0 → jevify-0.2.0}/suites/policy-hard-52.manifest.json +0 -0
  130. {jevify-0.1.0 → jevify-0.2.0}/tests/conftest.py +0 -0
  131. {jevify-0.1.0 → jevify-0.2.0}/tests/fakes/__init__.py +0 -0
  132. {jevify-0.1.0 → jevify-0.2.0}/tests/fakes/fake_backend.py +0 -0
  133. {jevify-0.1.0 → jevify-0.2.0}/tests/fixtures/recipes/fake-embedding.yaml +0 -0
  134. {jevify-0.1.0 → jevify-0.2.0}/tests/fixtures/recipes/fake-encoder.yaml +0 -0
  135. {jevify-0.1.0 → jevify-0.2.0}/tests/fixtures/recipes/fake-raw.yaml +0 -0
  136. {jevify-0.1.0 → jevify-0.2.0}/tests/fixtures/recipes/fake-rerank.yaml +0 -0
  137. {jevify-0.1.0 → jevify-0.2.0}/tests/fixtures/recipes/fake-vllm.yaml +0 -0
  138. {jevify-0.1.0 → jevify-0.2.0}/tests/fixtures/recipes/local-embedding.yaml +0 -0
  139. {jevify-0.1.0 → jevify-0.2.0}/tests/fixtures/recipes/local-encoder.yaml +0 -0
  140. {jevify-0.1.0 → jevify-0.2.0}/tests/fixtures/responses/llamacpp-completions-grammar.json +0 -0
  141. {jevify-0.1.0 → jevify-0.2.0}/tests/fixtures/responses/llamacpp-completions-top_k.json +0 -0
  142. {jevify-0.1.0 → jevify-0.2.0}/tests/fixtures/responses/llamacpp-native-completion.json +0 -0
  143. {jevify-0.1.0 → jevify-0.2.0}/tests/fixtures/responses/vllm-chat-top_k.json +0 -0
  144. {jevify-0.1.0 → jevify-0.2.0}/tests/guards/test_prompt_literals.py +0 -0
  145. {jevify-0.1.0 → jevify-0.2.0}/tests/guards/test_sdk_schema_mirror.py +0 -0
  146. {jevify-0.1.0 → jevify-0.2.0}/tests/guards/test_structure.py +0 -0
  147. {jevify-0.1.0 → jevify-0.2.0}/tests/live/test_live_ask.py +0 -0
  148. {jevify-0.1.0 → jevify-0.2.0}/tests/live/test_live_sdk.py +0 -0
  149. {jevify-0.1.0 → jevify-0.2.0}/tests/test_api.py +0 -0
  150. {jevify-0.1.0 → jevify-0.2.0}/tests/test_calibrate.py +0 -0
  151. {jevify-0.1.0 → jevify-0.2.0}/tests/test_classify_and_states.py +0 -0
  152. {jevify-0.1.0 → jevify-0.2.0}/tests/test_cli.py +0 -0
  153. {jevify-0.1.0 → jevify-0.2.0}/tests/test_controls.py +0 -0
  154. {jevify-0.1.0 → jevify-0.2.0}/tests/test_embedding_adapter.py +0 -0
  155. {jevify-0.1.0 → jevify-0.2.0}/tests/test_encoder_adapter.py +0 -0
  156. {jevify-0.1.0 → jevify-0.2.0}/tests/test_engine.py +0 -0
  157. {jevify-0.1.0 → jevify-0.2.0}/tests/test_evaluation.py +0 -0
  158. {jevify-0.1.0 → jevify-0.2.0}/tests/test_llamacpp_dialect.py +0 -0
  159. {jevify-0.1.0 → jevify-0.2.0}/tests/test_local_embedding.py +0 -0
  160. {jevify-0.1.0 → jevify-0.2.0}/tests/test_multimodal.py +0 -0
  161. {jevify-0.1.0 → jevify-0.2.0}/tests/test_probe.py +0 -0
  162. {jevify-0.1.0 → jevify-0.2.0}/tests/test_readout_ladder.py +0 -0
  163. {jevify-0.1.0 → jevify-0.2.0}/tests/test_rerank_adapter.py +0 -0
  164. {jevify-0.1.0 → jevify-0.2.0}/tests/test_sdk_acceptance.py +0 -0
  165. {jevify-0.1.0 → jevify-0.2.0}/tests/test_translate.py +0 -0
  166. {jevify-0.1.0 → jevify-0.2.0}/tests/test_warm.py +0 -0
  167. {jevify-0.1.0 → jevify-0.2.0}/tools/author_long_state.py +0 -0
  168. {jevify-0.1.0 → jevify-0.2.0}/tools/author_policy_hard.py +0 -0
jevify-0.2.0/Makefile ADDED
@@ -0,0 +1,50 @@
1
+ # Shortcuts for the two everyday jevify commands: `make ask` asks typed questions
2
+ # about one state and prints the answer as JSON; `make serve` serves Jev's HTTP API
3
+ # for one recipe. Values reach jevify through the environment, so a state may hold
4
+ # spaces, quotes and apostrophes. `make help` lists the variables and the recipes.
5
+
6
+ JEVIFY ?= uv run jevify
7
+ HOST ?= 127.0.0.1
8
+ PORT ?= 8600
9
+
10
+ export RECIPE STATE CHOICE SCORE NOUL IMAGE RUNG HOST PORT
11
+
12
+ .DEFAULT_GOAL := help
13
+ .PHONY: help ask serve
14
+
15
+ help:
16
+ @echo "make ask RECIPE=<file> STATE=<text|@file> [CHOICE=...] [SCORE=...] [NOUL=...] [IMAGE=<png|jpg|webp>] [RUNG=<rung>]"
17
+ @echo "make serve RECIPE=<file> [HOST=$(HOST)] [PORT=$(PORT)]"
18
+ @echo ""
19
+ @echo " CHOICE id:question=option1,option2,... one option from a menu"
20
+ @echo " SCORE id:question=level0,level1,... a position on ordered levels, low to high"
21
+ @echo " NOUL id:statement the probability that the statement is true"
22
+ @echo " Give at least one of CHOICE, SCORE, NOUL. For several of one type, call jevify ask directly."
23
+ @echo " make reads \$$ in a value: write \$$\$$49.99 for \$$49.99, or put the text in a file and pass STATE=@path."
24
+ @echo " HOST other than 127.0.0.1 needs JEVIFY_API_KEY set: clients must then send it as a bearer key."
25
+ @echo ""
26
+ @echo "Example:"
27
+ @echo " make ask RECIPE=recipes/gemma-4-e4b-it.llamacpp.yaml \\"
28
+ @echo " STATE=\"Customer: I was charged twice. Fix it today or I cancel.\" \\"
29
+ @echo " CHOICE=\"dept:Which team should handle this?=billing,support,sales\" \\"
30
+ @echo " SCORE=\"urgency:How urgent is this?=low,medium,high\" \\"
31
+ @echo " NOUL=\"cancel:The customer threatens to cancel.\""
32
+ @echo ""
33
+ @echo "Recipes:"
34
+ @ls recipes/*.yaml | sed 's/^/ /'
35
+
36
+ ask:
37
+ @test -n "$$RECIPE" || { echo "make ask: set RECIPE=recipes/<name>.yaml (make help lists them)" >&2; exit 2; }
38
+ @test -n "$$STATE" || { echo "make ask: set STATE=\"...\" (the text, or @path to a file)" >&2; exit 2; }
39
+ @set -- --state "$$STATE"; \
40
+ [ -z "$$CHOICE" ] || set -- "$$@" --choice "$$CHOICE"; \
41
+ [ -z "$$SCORE" ] || set -- "$$@" --score "$$SCORE"; \
42
+ [ -z "$$NOUL" ] || set -- "$$@" --noul "$$NOUL"; \
43
+ [ -z "$$IMAGE" ] || set -- "$$@" --image "$$IMAGE"; \
44
+ [ -z "$$RUNG" ] || set -- "$$@" --rung "$$RUNG"; \
45
+ $(JEVIFY) ask "$$RECIPE" "$$@"
46
+
47
+ serve:
48
+ @test -n "$$RECIPE" || { echo "make serve: set RECIPE=recipes/<name>.yaml (make help lists them)" >&2; exit 2; }
49
+ @case "$$HOST" in 127.0.0.1|localhost|::1) ;; *) test -n "$$JEVIFY_API_KEY" || { echo "make serve: HOST=$$HOST opens the API to the network; export JEVIFY_API_KEY first" >&2; exit 2; } ;; esac
50
+ $(JEVIFY) serve "$$RECIPE" --host "$$HOST" --port "$$PORT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: jevify
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Make a pretrained model you already have answer typed questions in one pass, and measure how well it does.
5
5
  Project-URL: Homepage, https://github.com/fidecastro/jevify
6
6
  Author: Felipe Infante de Castro
@@ -39,7 +39,8 @@ Jev does, and measure how well it does.**
39
39
  [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/)
40
40
 
41
41
  You bring a model behind an OpenAI-compatible endpoint (vLLM, llama.cpp,
42
- anything with logprobs) or a small model that runs in-process. jevify sends it
42
+ TensorFold, anything that returns logprobs for a reply token or for the
43
+ prompt) or a small model that runs in-process. jevify sends it
43
44
  a **state** once, then asks **typed questions** about it: a choice among
44
45
  options, a score on ordered levels, a yes/no with a probability. Each answer
45
46
  is read straight off the model's next-token distribution, no generation, no
@@ -81,10 +82,34 @@ jevify ask recipes/gemma-4-e4b-it.llamacpp.yaml \
81
82
  jevify serve recipes/gemma-4-e4b-it.llamacpp.yaml --port 8600
82
83
  ```
83
84
 
85
+ Any model behind an OpenAI-compatible server works, not only the ones under
86
+ `recipes/`: copy the nearest recipe, set `endpoint.base_url` and
87
+ `model.name`, and run the probe, which re-verifies the answer token ids for
88
+ that model and writes what it found into the file. vLLM and llama.cpp get
89
+ their dialect extras; any other server is driven through the plain OpenAI
90
+ surface. A server that refuses logprobs on the reply token but returns
91
+ vLLM's `prompt_logprobs`, such as TensorFold's native server with
92
+ GLM-5.3-Flash, is read through the `prompt_logprobs` rung, which needs a
93
+ raw-mode recipe (the template written out with the model's own control
94
+ tokens, served on `/v1/completions`).
95
+
96
+ `serve` binds to `127.0.0.1`. To reach it from other machines pass
97
+ `--host 0.0.0.0`, and set `JEVIFY_API_KEY` first so that every request must
98
+ carry that bearer key; to leave it running, detach it and keep its log:
99
+
100
+ ```sh
101
+ jevify serve recipes/<name>.yaml --host 0.0.0.0 --port 8600 > ~/jevify.log 2>&1 &
102
+ curl -s http://<this-host>:8600/health
103
+ ```
104
+
84
105
  Every answer carries Jev's fields (`choice`, `confidence`, `probabilities`,
85
106
  `score`, `legend`, `noul`) plus an `x_jevify` block naming the readout method,
86
107
  the cached tokens, the latency and the recipe hash that produced it.
87
108
 
109
+ From a clone, `make ask` and `make serve` wrap the two commands;
110
+ [`docs/playbook.md`](https://github.com/fidecastro/jevify/blob/main/docs/playbook.md)
111
+ walks through them and explains every answer field.
112
+
88
113
  ## How it works
89
114
 
90
115
  1. **Recipe.** A hashed YAML file: the endpoint, the prompt template (chat
@@ -99,7 +124,9 @@ the cached tokens, the latency and the recipe hash that produced it.
99
124
  that reuses the server's prefix cache. A 32k-token state costs one warm and
100
125
  then a few hundred milliseconds per question.
101
126
  4. **Readout ladder.** Named-token logprobs, grammar with post-sampling
102
- probabilities, top-k, equal bias, and a floored top-k as the last resort.
127
+ probabilities, top-k, prompt logprobs (each answer token appended to the
128
+ prompt and its logprob read back, for servers that give none on the reply
129
+ token), equal bias, and a floored top-k as the last resort.
103
130
  Auto walks down the proven rungs; the answer names the rung that read it
104
131
  and whether it was degraded.
105
132
  5. **Evidence.** `jevify eval` runs a frozen suite with controls (shuffled
@@ -110,7 +137,7 @@ the cached tokens, the latency and the recipe hash that produced it.
110
137
 
111
138
  | Kind | Talks to | Proven live with |
112
139
  |---|---|---|
113
- | `endpoint` | Any OpenAI-compatible server; dialect extras for vLLM and llama.cpp | DeepSeek-V4-Flash on vLLM; Ternary Bonsai 2 27B, Gemma 4 E4B and Qwen3-VL-Reranker 8B/2B on llama.cpp; text and images |
140
+ | `endpoint` | Any OpenAI-compatible server that returns reply-token or prompt logprobs; dialect extras for vLLM and llama.cpp | DeepSeek-V4-Flash on vLLM; Ternary Bonsai 2 27B, Gemma 4 E4B and Qwen3-VL-Reranker 8B/2B on llama.cpp; text and images |
114
141
  | `embedding` | A `/v1/embeddings` route, or a model in-process | Qwen3-Embedding-0.6B in-process |
115
142
  | `encoder` | An NLI sequence-classification head in-process | facebook/bart-large-mnli in-process |
116
143
  | `rerank` | A `/v1/rerank` route | Fakes only |
@@ -123,6 +150,28 @@ Four frozen suites, every recipe on each, one run each, latencies per
123
150
  question on one machine on one day. Every row links to the scorecard that
124
151
  records the command, the recipe hash, the suite hash and the raw file digest.
125
152
 
153
+ ### Latency per question
154
+
155
+ The median of each suite, combined across the suites a model ran; one run
156
+ each. A question is one request that reads the answer off the next-token
157
+ probabilities, after the state was sent once and cached.
158
+
159
+ | Model | How it runs | Hardware | Latency per question |
160
+ |---|---|---|---|
161
+ | facebook/bart-large-mnli | in-process NLI classifier | RTX 4090 | 7–9 ms, avg 8 |
162
+ | Qwen3-Embedding-0.6B | in-process embedding, cosine | RTX 4090 | 8–10 ms, avg 9 |
163
+ | Gemma 4 E4B (Q8_0) | llama.cpp, chat | RTX 4090 | 28–37 ms, avg 32 |
164
+ | Qwen3-VL-Reranker-2B (Q8_0) | llama.cpp, yes/no readout | RTX 4090 | 47–64 ms, avg 53 |
165
+ | Qwen3-VL-Reranker-8B (Q8_0) | llama.cpp, yes/no readout | RTX 4090 | 68 ms (one suite) |
166
+ | Ternary Bonsai 2 27B (PQ2_0) | llama.cpp (PrismML fork), raw prompt | RTX 4090 | 97–113 ms, avg 102 |
167
+ | DeepSeek-V4-Flash | vLLM, chat | 2× DGX Spark, over LAN | 359–672 ms, avg 483 |
168
+
169
+ Image questions cost the llama.cpp models nothing extra, since the frame is
170
+ paid once in the warm; DeepSeek pays about half again on images. The two
171
+ in-process models are the fastest and the least accurate.
172
+
173
+ ### Accuracy by suite
174
+
126
175
  **policy-29**, the diagnostic suite carried over from the author's earlier experiments (choice only):
127
176
 
128
177
  | Recipe | Kind | Accuracy | Median latency | Scorecard |
@@ -221,6 +270,8 @@ jevify is written to be operated through a coding agent. Point yours at
221
270
  | [`tools/`](https://github.com/fidecastro/jevify/blob/main/tools) | Suite authoring, the Doom recorder and player |
222
271
  | [`AGENTS.md`](https://github.com/fidecastro/jevify/blob/main/AGENTS.md) | The engineering principles that bind every change |
223
272
  | [`SKILL.md`](https://github.com/fidecastro/jevify/blob/main/SKILL.md) | How an agent operates jevify |
273
+ | [`docs/playbook.md`](https://github.com/fidecastro/jevify/blob/main/docs/playbook.md) | How a person operates jevify: ask, read an answer, serve, add a model, fix common problems |
274
+ | [`Makefile`](https://github.com/fidecastro/jevify/blob/main/Makefile) | `make ask` and `make serve` shortcuts; `make help` lists the variables and recipes |
224
275
 
225
276
  To work on jevify: `uv sync --group dev` (add `--extra encoder --extra
226
277
  games` as needed) and `uv run pytest`. Live and in-process tests skip unless
@@ -229,14 +280,21 @@ you opt in.
229
280
  ## Not yet
230
281
 
231
282
  Named so that nobody has to discover it: a direct-call control, so no
232
- "faster than generation" claim exists yet; a Doom player that survives (both
233
- models die within seconds of `deadly_corridor`); the rerank kind and the
283
+ "faster than generation" claim exists yet; clearing Doom's `deadly_corridor`
284
+ (in `defend_the_center` Bonsai's perception plus a fixed rule scores 10 kills
285
+ in 16 seconds, but in the corridor even the rule fed the engine's own labels
286
+ dies before the armour, so the corridor needs a policy with memory, not
287
+ better perception); the rerank kind and the
234
288
  embedding route against a live server; a marker-slot encoder layout; the
235
289
  grammar rung on the PrismML llama.cpp fork, which accepts a grammar without
236
- constraining the reported probabilities; Windows. Choice menus on the
237
- endpoint kind are bounded by the single-token identifier alphabet the probe
238
- verifies (26 on the DeepSeek recipe); Jev's 255-option ceiling is not
239
- reachable there.
290
+ constraining the reported probabilities; the `prompt_logprobs` rung against a
291
+ live server (written for TensorFold's native server with GLM-5.3-Flash, run
292
+ only against the fake server in `tests/` so far; a server that skips its
293
+ prefix cache for such requests, as TensorFold's source shows it does, makes
294
+ every question read the whole state again, which the probe measures and
295
+ notes); Windows. Choice menus on the endpoint kind are bounded by the
296
+ single-token identifier alphabet the probe verifies (26 on the DeepSeek
297
+ recipe); Jev's 255-option ceiling is not reachable there.
240
298
 
241
299
  ## Licence
242
300
 
@@ -9,7 +9,8 @@ Jev does, and measure how well it does.**
9
9
  [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/)
10
10
 
11
11
  You bring a model behind an OpenAI-compatible endpoint (vLLM, llama.cpp,
12
- anything with logprobs) or a small model that runs in-process. jevify sends it
12
+ TensorFold, anything that returns logprobs for a reply token or for the
13
+ prompt) or a small model that runs in-process. jevify sends it
13
14
  a **state** once, then asks **typed questions** about it: a choice among
14
15
  options, a score on ordered levels, a yes/no with a probability. Each answer
15
16
  is read straight off the model's next-token distribution, no generation, no
@@ -51,10 +52,34 @@ jevify ask recipes/gemma-4-e4b-it.llamacpp.yaml \
51
52
  jevify serve recipes/gemma-4-e4b-it.llamacpp.yaml --port 8600
52
53
  ```
53
54
 
55
+ Any model behind an OpenAI-compatible server works, not only the ones under
56
+ `recipes/`: copy the nearest recipe, set `endpoint.base_url` and
57
+ `model.name`, and run the probe, which re-verifies the answer token ids for
58
+ that model and writes what it found into the file. vLLM and llama.cpp get
59
+ their dialect extras; any other server is driven through the plain OpenAI
60
+ surface. A server that refuses logprobs on the reply token but returns
61
+ vLLM's `prompt_logprobs`, such as TensorFold's native server with
62
+ GLM-5.3-Flash, is read through the `prompt_logprobs` rung, which needs a
63
+ raw-mode recipe (the template written out with the model's own control
64
+ tokens, served on `/v1/completions`).
65
+
66
+ `serve` binds to `127.0.0.1`. To reach it from other machines pass
67
+ `--host 0.0.0.0`, and set `JEVIFY_API_KEY` first so that every request must
68
+ carry that bearer key; to leave it running, detach it and keep its log:
69
+
70
+ ```sh
71
+ jevify serve recipes/<name>.yaml --host 0.0.0.0 --port 8600 > ~/jevify.log 2>&1 &
72
+ curl -s http://<this-host>:8600/health
73
+ ```
74
+
54
75
  Every answer carries Jev's fields (`choice`, `confidence`, `probabilities`,
55
76
  `score`, `legend`, `noul`) plus an `x_jevify` block naming the readout method,
56
77
  the cached tokens, the latency and the recipe hash that produced it.
57
78
 
79
+ From a clone, `make ask` and `make serve` wrap the two commands;
80
+ [`docs/playbook.md`](https://github.com/fidecastro/jevify/blob/main/docs/playbook.md)
81
+ walks through them and explains every answer field.
82
+
58
83
  ## How it works
59
84
 
60
85
  1. **Recipe.** A hashed YAML file: the endpoint, the prompt template (chat
@@ -69,7 +94,9 @@ the cached tokens, the latency and the recipe hash that produced it.
69
94
  that reuses the server's prefix cache. A 32k-token state costs one warm and
70
95
  then a few hundred milliseconds per question.
71
96
  4. **Readout ladder.** Named-token logprobs, grammar with post-sampling
72
- probabilities, top-k, equal bias, and a floored top-k as the last resort.
97
+ probabilities, top-k, prompt logprobs (each answer token appended to the
98
+ prompt and its logprob read back, for servers that give none on the reply
99
+ token), equal bias, and a floored top-k as the last resort.
73
100
  Auto walks down the proven rungs; the answer names the rung that read it
74
101
  and whether it was degraded.
75
102
  5. **Evidence.** `jevify eval` runs a frozen suite with controls (shuffled
@@ -80,7 +107,7 @@ the cached tokens, the latency and the recipe hash that produced it.
80
107
 
81
108
  | Kind | Talks to | Proven live with |
82
109
  |---|---|---|
83
- | `endpoint` | Any OpenAI-compatible server; dialect extras for vLLM and llama.cpp | DeepSeek-V4-Flash on vLLM; Ternary Bonsai 2 27B, Gemma 4 E4B and Qwen3-VL-Reranker 8B/2B on llama.cpp; text and images |
110
+ | `endpoint` | Any OpenAI-compatible server that returns reply-token or prompt logprobs; dialect extras for vLLM and llama.cpp | DeepSeek-V4-Flash on vLLM; Ternary Bonsai 2 27B, Gemma 4 E4B and Qwen3-VL-Reranker 8B/2B on llama.cpp; text and images |
84
111
  | `embedding` | A `/v1/embeddings` route, or a model in-process | Qwen3-Embedding-0.6B in-process |
85
112
  | `encoder` | An NLI sequence-classification head in-process | facebook/bart-large-mnli in-process |
86
113
  | `rerank` | A `/v1/rerank` route | Fakes only |
@@ -93,6 +120,28 @@ Four frozen suites, every recipe on each, one run each, latencies per
93
120
  question on one machine on one day. Every row links to the scorecard that
94
121
  records the command, the recipe hash, the suite hash and the raw file digest.
95
122
 
123
+ ### Latency per question
124
+
125
+ The median of each suite, combined across the suites a model ran; one run
126
+ each. A question is one request that reads the answer off the next-token
127
+ probabilities, after the state was sent once and cached.
128
+
129
+ | Model | How it runs | Hardware | Latency per question |
130
+ |---|---|---|---|
131
+ | facebook/bart-large-mnli | in-process NLI classifier | RTX 4090 | 7–9 ms, avg 8 |
132
+ | Qwen3-Embedding-0.6B | in-process embedding, cosine | RTX 4090 | 8–10 ms, avg 9 |
133
+ | Gemma 4 E4B (Q8_0) | llama.cpp, chat | RTX 4090 | 28–37 ms, avg 32 |
134
+ | Qwen3-VL-Reranker-2B (Q8_0) | llama.cpp, yes/no readout | RTX 4090 | 47–64 ms, avg 53 |
135
+ | Qwen3-VL-Reranker-8B (Q8_0) | llama.cpp, yes/no readout | RTX 4090 | 68 ms (one suite) |
136
+ | Ternary Bonsai 2 27B (PQ2_0) | llama.cpp (PrismML fork), raw prompt | RTX 4090 | 97–113 ms, avg 102 |
137
+ | DeepSeek-V4-Flash | vLLM, chat | 2× DGX Spark, over LAN | 359–672 ms, avg 483 |
138
+
139
+ Image questions cost the llama.cpp models nothing extra, since the frame is
140
+ paid once in the warm; DeepSeek pays about half again on images. The two
141
+ in-process models are the fastest and the least accurate.
142
+
143
+ ### Accuracy by suite
144
+
96
145
  **policy-29**, the diagnostic suite carried over from the author's earlier experiments (choice only):
97
146
 
98
147
  | Recipe | Kind | Accuracy | Median latency | Scorecard |
@@ -191,6 +240,8 @@ jevify is written to be operated through a coding agent. Point yours at
191
240
  | [`tools/`](https://github.com/fidecastro/jevify/blob/main/tools) | Suite authoring, the Doom recorder and player |
192
241
  | [`AGENTS.md`](https://github.com/fidecastro/jevify/blob/main/AGENTS.md) | The engineering principles that bind every change |
193
242
  | [`SKILL.md`](https://github.com/fidecastro/jevify/blob/main/SKILL.md) | How an agent operates jevify |
243
+ | [`docs/playbook.md`](https://github.com/fidecastro/jevify/blob/main/docs/playbook.md) | How a person operates jevify: ask, read an answer, serve, add a model, fix common problems |
244
+ | [`Makefile`](https://github.com/fidecastro/jevify/blob/main/Makefile) | `make ask` and `make serve` shortcuts; `make help` lists the variables and recipes |
194
245
 
195
246
  To work on jevify: `uv sync --group dev` (add `--extra encoder --extra
196
247
  games` as needed) and `uv run pytest`. Live and in-process tests skip unless
@@ -199,14 +250,21 @@ you opt in.
199
250
  ## Not yet
200
251
 
201
252
  Named so that nobody has to discover it: a direct-call control, so no
202
- "faster than generation" claim exists yet; a Doom player that survives (both
203
- models die within seconds of `deadly_corridor`); the rerank kind and the
253
+ "faster than generation" claim exists yet; clearing Doom's `deadly_corridor`
254
+ (in `defend_the_center` Bonsai's perception plus a fixed rule scores 10 kills
255
+ in 16 seconds, but in the corridor even the rule fed the engine's own labels
256
+ dies before the armour, so the corridor needs a policy with memory, not
257
+ better perception); the rerank kind and the
204
258
  embedding route against a live server; a marker-slot encoder layout; the
205
259
  grammar rung on the PrismML llama.cpp fork, which accepts a grammar without
206
- constraining the reported probabilities; Windows. Choice menus on the
207
- endpoint kind are bounded by the single-token identifier alphabet the probe
208
- verifies (26 on the DeepSeek recipe); Jev's 255-option ceiling is not
209
- reachable there.
260
+ constraining the reported probabilities; the `prompt_logprobs` rung against a
261
+ live server (written for TensorFold's native server with GLM-5.3-Flash, run
262
+ only against the fake server in `tests/` so far; a server that skips its
263
+ prefix cache for such requests, as TensorFold's source shows it does, makes
264
+ every question read the whole state again, which the probe measures and
265
+ notes); Windows. Choice menus on the endpoint kind are bounded by the
266
+ single-token identifier alphabet the probe verifies (26 on the DeepSeek
267
+ recipe); Jev's 255-option ceiling is not reachable there.
210
268
 
211
269
  ## Licence
212
270
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jevify
3
- description: Operate jevify, the bring-your-own-model harness that makes a pretrained model answer Jev-shaped typed questions (choice, score, noul) over a shared state and measures how well it does. Use when asked to connect a model behind an OpenAI-compatible endpoint (vLLM, llama.cpp, others) or an in-process encoder or embedding model, write or probe a recipe, ask questions, run a suite, read a scorecard, calibrate, or serve Jev's API.
3
+ description: Operate jevify, the bring-your-own-model harness that makes a pretrained model answer Jev-shaped typed questions (choice, score, noul) over a shared state and measures how well it does. Use when asked to connect a model behind an OpenAI-compatible endpoint (vLLM, llama.cpp, TensorFold, others) or an in-process encoder or embedding model, write or probe a recipe, ask questions, run a suite, read a scorecard, calibrate, or serve Jev's API.
4
4
  ---
5
5
 
6
6
  # Operating jevify
@@ -31,7 +31,9 @@ result.
31
31
  rows carry `images`, regenerate the frames with
32
32
  `uv run python tools/record_doom_suite.py` after `uv sync --extra games`),
33
33
  `long-state-24` (2k to 32k tokens). `tools/play_doom.py <recipe> --scenario
34
- defend_the_center --policy perception` records a video of a recipe playing.
34
+ defend_the_center --policy perception` records a video of a recipe playing;
35
+ `--policy expert` replays the same rule from the engine's labels, the
36
+ ceiling any perception can reach with that rule.
35
37
  Raw decisions land in `runs/` (git-ignored); the summary in `docs/evidence/`
36
38
  (committed). Read the Markdown summary, then the `wrong` list in the JSON.
37
39
  5. **Serve**: `jevify serve recipes/<name>.yaml --port 8600`. Jev's SDK works
@@ -51,15 +53,35 @@ Every recipe has `schema_version: 1`, `model: {name, kind, revision}` and
51
53
  the rest.
52
54
 
53
55
  **`endpoint`** (any OpenAI-compatible server): `endpoint: {base_url,
54
- api_key_env, dialect: auto|vllm|llamacpp|generic, timeout_s}`, `template`
55
- and `answers`.
56
+ api_key_env, dialect: auto|vllm|llamacpp|generic, timeout_s, extra_body,
57
+ headers, headers_env}`, `template` and `answers`.
56
58
  - `template.mode: messages` sends chat messages; put thinking off in
57
59
  `template.kwargs` (`{thinking: false}` for DeepSeek, `{enable_thinking:
58
60
  false}` for Qwen3-class models; names are model-specific, the probe checks
59
61
  that no `<think>` token opens the answer).
62
+ - `endpoint.extra_body` is merged into the top level of every request, for
63
+ servers that take a switch there rather than as a template argument. The
64
+ `generic` dialect sends no template arguments, so on Ollama's `/v1` route
65
+ a thinking model needs `extra_body: {reasoning_effort: none}` (`think:
66
+ false` is ignored there). A key jevify itself sets (`max_tokens`,
67
+ `logprobs`, `logit_bias`, ...) is refused when the backend is built, before
68
+ any request, so probe, ask, eval and serve all fail the same way. The
69
+ fields also reach the tokenize and template routes the probe uses.
70
+ - `endpoint.headers` adds static HTTP headers to every request, for a router
71
+ or gateway that requires one (`{x-client-id: agenticApp}`). The values are
72
+ written in the recipe and hashed with it, so never put a secret there: a
73
+ bearer key goes in the env var that `api_key_env` names, and any other
74
+ secret header in `endpoint.headers_env` (header name to env var name, read
75
+ when the backend is built; an unset variable fails then). A static header
76
+ whose name looks like a credential (`key`, `token`, `secret`, `auth`,
77
+ `cookie`, ...) is refused when the recipe loads; `Content-Type` and
78
+ `Authorization` are refused when the backend is built. Every
79
+ endpoint-backed kind sends them (endpoint, rerank, embedding).
60
80
  - `template.mode: raw` sends a completion prompt you write with the model's
61
81
  own control tokens; use it when the chat template would not put the model
62
- in its trained regime (the Qwen3-VL reranker recipe is the example).
82
+ in its trained regime (the Qwen3-VL reranker recipe is the example), and
83
+ for a server that refuses reply-token logprobs (TensorFold's native server
84
+ with GLM-5.3-Flash): only a raw prompt can carry the `prompt_logprobs` rung.
63
85
  - `template.questions.choice_strategy`: `identifiers` (one request, options
64
86
  labelled A, B, C) or `per_option` (one yes/no request per option; needed
65
87
  for a model that never emits letters, such as a reranker).
@@ -67,8 +89,11 @@ and `answers`.
67
89
  the probe fills their ids.
68
90
  - `readout.rung: auto` walks the rungs the probe proved, strongest first:
69
91
  `named_token_logprobs` (vLLM with `logprob_token_ids`), `grammar`
70
- (llama.cpp), `top_k`, `equal_bias`, `top_k_floor` (degraded, missing
71
- labels floored and listed). A rung that cannot see every label hands the
92
+ (llama.cpp), `top_k`, `prompt_logprobs` (each answer spelling appended to
93
+ a raw prompt and read back from vLLM-style `prompt_logprobs` rows, one
94
+ request more for every spelling the rows did not show; exact, off-menu mass
95
+ visible), `equal_bias`, `top_k_floor` (degraded, missing labels floored and
96
+ listed). A rung that cannot see every label hands the
72
97
  question to the next; the answer names the rung that read it. A pinned rung
73
98
  never walks: it fails loudly instead.
74
99
 
@@ -88,7 +113,10 @@ sequence-classification head; all three question types; semantics `readout`.
88
113
  ## Reading an answer
89
114
 
90
115
  Jev's fields come first (`choice`, `confidence`, `probabilities`; `score`,
91
- `legend`; `noul`). Everything jevify adds is under `x_jevify`:
116
+ `legend`; `noul`). `confidence` is Jev's statistic, `(n × peak − 1) / (n − 1)`
117
+ over the options or levels: 0 when uniform, 1 when certain, and the number
118
+ TypeSafe's published thresholds refer to. Everything jevify adds is under
119
+ `x_jevify`:
92
120
 
93
121
  | Field | Meaning |
94
122
  |---|---|
@@ -138,6 +166,16 @@ Jev's fields come first (`choice`, `confidence`, `probabilities`; `score`,
138
166
  checkpoint at the end of an earlier prompt, and a chat template closes the
139
167
  user turn right after the state. Use `template.mode: raw` so the warm prompt
140
168
  ends at the state boundary; the Bonsai recipe is the example.
169
+ - **Probe notes "reply-token logprobs refused"**: the server gives no
170
+ logprobs on a generated token (TensorFold with GLM-5.3-Flash). Write a raw
171
+ recipe so the probe can prove `prompt_logprobs`; with a chat recipe the
172
+ probe ends in "no readout is possible".
173
+ - **"did not come back as its own prompt token"**: the appended answer fuses
174
+ with the end of the raw template; end the template where the answer starts
175
+ a token of its own (after a newline, or before a leading-space spelling).
176
+ - **Probe notes "prompt_logprobs requests reuse no cached prefix"**: the
177
+ server reads the whole state again for every question on that rung; keep
178
+ states short there, or serve the model where reply-token logprobs exist.
141
179
  - **Missing encoder extra**: the error names the install command.
142
180
 
143
181
  ## House rules that bind you here
@@ -126,7 +126,7 @@ API response may claim more than it permits.
126
126
 
127
127
  Rules that follow:
128
128
 
129
- - **Confidence** is a statistic of the returned distribution: one minus its normalized entropy for choice and score, distance from one half for noul. It is not an independent estimate that the decision is right, and copy may not describe it as one.
129
+ - **Confidence** is Jev's statistic of the returned distribution: `(n × peak − 1) / (n − 1)` over the n options or levels for choice and score, so a uniform distribution scores 0 and a certain one scores 1 (TypeSafe's published definition). Jev's noul answers carry no confidence; jevify reports the distance of P(true) from one half, scaled to [0, 1], under `x_jevify` only. It is not an independent estimate that the decision is right, and copy may not describe it as one.
130
130
  - **"Faster"** may be claimed only against a measured direct call on the same backend, same model, same state, with the measurement in a scorecard.
131
131
  - **"Accuracy"** names its suite and the suite's hash. A suite whose expected answers came from a teacher model measures agreement with that teacher, and copy says so.
132
132
  - **A fine-tuned result is never compared with a zero-shot result in one table** without the words fine-tuned and zero-shot in the row.
@@ -150,6 +150,7 @@ number and the previous wording. The edit and the ADR land in the same commit.
150
150
  | Date | Invariant | ADR | Change |
151
151
  |---|---|---|---|
152
152
  | 2026-09-19 | all | ADR-0001 | Adopted. |
153
+ | 2026-09-23 | §5, confidence rule | ADR-0003 D8 | Confidence is Jev's peak statistic, `(n × peak − 1) / (n − 1)`. Previous wording: "a statistic of the returned distribution: one minus its normalized entropy for choice and score, distance from one half for noul". The entropy statistic disagreed with Jev's published definition, so the SDK-facing `confidence` field did not carry Jev's meaning. |
153
154
 
154
155
  ## 8. Document map
155
156
 
@@ -66,7 +66,7 @@ on an adapter module; a structure guard enforces the import ban.
66
66
 
67
67
  | Kind | Talks to | Readout | Semantics |
68
68
  |---|---|---|---|
69
- | `endpoint` | Any OpenAI-compatible HTTP server | One generated token; distribution from logprobs at the answer position | `readout` |
69
+ | `endpoint` | Any OpenAI-compatible HTTP server | One generated token, or an answer token appended to the prompt; distribution from logprobs at the answer position | `readout` |
70
70
  | `encoder` | An in-process encoder classifier loaded through Transformers | The head's scores at option marker slots (Laya's layout) or label logits (GLiClass, NLI) | `readout` |
71
71
  | `rerank` | A rerank or score endpoint, or a reranker model in-process | One independent relevance score per option | `relevance` |
72
72
  | `embedding` | An embeddings endpoint or model | Cosine between instruction-conditioned state vector and cached option vectors | `similarity` |
@@ -91,7 +91,7 @@ requests and records:
91
91
  - whether each answer token under the recipe's template is a single token
92
92
  (via the tokenizer or the server's tokenize endpoint);
93
93
  - whether all answer tokens come back in the logprobs unbiased, biased, or
94
- only through named-token logprobs or a grammar;
94
+ only through named-token logprobs, a grammar or prompt logprobs;
95
95
  - whether assistant prefill and template arguments (thinking off) are honored;
96
96
  - whether a second request over the same state is measurably faster than the
97
97
  first, which is the only proof that the prefix is cached;
@@ -115,8 +115,11 @@ allows, in this order:
115
115
  (exact over the answer set, off-menu mass hidden);
116
116
  3. an unbiased top-k request when every answer token is present (exact, and
117
117
  off-menu mass visible);
118
- 4. equal bias on all answer tokens (exact ratios, off-menu mass hidden);
119
- 5. top-k with a floor for missing tokens, marked degraded in the answer.
118
+ 4. prompt logprobs at an answer token appended to a raw prompt, one request
119
+ more for each answer spelling the rows did not show (exact, off-menu mass
120
+ visible; added 2026-11-04, below);
121
+ 5. equal bias on all answer tokens (exact ratios, off-menu mass hidden);
122
+ 6. top-k with a floor for missing tokens, marked degraded in the answer.
120
123
 
121
124
  The method used is recorded on every answer. A recipe may pin a rung; it may
122
125
  not skip the ladder's recording.
@@ -131,6 +134,30 @@ also proves two things it used to assume: that an image adds prompt tokens
131
134
  question request reuses (a recurrent model resumes only from a checkpoint at
132
135
  the end of an earlier prompt, so a chat-template warm can buy nothing).
133
136
 
137
+ Amended 2026-11-04, for a server that refuses reply-token logprobs:
138
+ TensorFold's native server (pin `f350cd3e`) serves logprobs on a generated
139
+ token only for a family that gives the first token's row, and GLM-5.3-Flash
140
+ does not, so every rung above that reads a generated token is refused with a
141
+ 400. The same server, like vLLM, returns `prompt_logprobs` on
142
+ `/v1/completions`: null, then one `{id: {logprob, rank, decoded_token}}` row
143
+ for each prompt token, the token the prompt holds listed first. The
144
+ `prompt_logprobs` rung appends an answer spelling to the raw prompt and reads
145
+ the last row: the appended token's exact logprob and the top-k beside it, all
146
+ from the one distribution at the answer slot. A spelling the rows did not
147
+ show is appended in a request of its own, so every label is exact and the
148
+ off-menu mass visible. The rung refuses a row whose prompt token is not the
149
+ appended one: an answer that fuses with the template's tail would be read at
150
+ the wrong position. It reads raw prompts only (TensorFold serves
151
+ `prompt_logprobs` on the completions route alone). It sits below `top_k`,
152
+ which needs one request, and above `equal_bias`, which hides the off-menu
153
+ mass. The probe no longer assumes reply-token logprobs: a 400 on its first
154
+ readout is recorded, the rungs that need them stay unproven, its own usage
155
+ and timing requests stop asking for them, and the probe proves
156
+ `prompt_logprobs` by appending an answer and checking the row. It also sends
157
+ that request twice and records when the repeat reuses no cached prefix,
158
+ since TensorFold skips its prefix cache for any request with
159
+ `prompt_logprobs` and each question then reads the whole state again.
160
+
134
161
  ### D5 — Choice questions on decoders
135
162
 
136
163
  Options are labeled with single-token identifiers and the distribution is
@@ -1,6 +1,7 @@
1
1
  # ADR-0003 — Jev's evaluate call is the API; extensions are namespaced; the SDK is the acceptance test
2
2
 
3
- - **Status:** accepted (2026-09-19).
3
+ - **Status:** accepted (2026-09-19); D8 added 2026-09-23: `confidence` is
4
+ Jev's peak statistic, amending `docs/00-invariants.md` §5.
4
5
  - **Extends:** ADR-0001 D2. Implements INV-8; constrained by INV-4, INV-9.
5
6
 
6
7
  ## Context
@@ -77,6 +78,31 @@ implementation, and a structure guard forbids it from touching the port.
77
78
  The library is the product; the server is one caller of it, and a program
78
79
  that imports jevify gets identical behaviour without HTTP.
79
80
 
81
+ ### D8 — `confidence` is Jev's statistic, computed Jev's way
82
+
83
+ Jev's `confidence` on a `choice` or `score` answer is
84
+ `(n × peak − 1) / (n − 1)`, where `n` is the number of options or levels and
85
+ `peak` is the largest probability: 0 for a uniform distribution, 1 for a
86
+ certain one. This is TypeSafe's published definition
87
+ (<https://docs.typesafe.ai/confidence>, worked example
88
+ `[0.90, 0.06, 0.04] → 0.85`), and it is what the thresholds TypeSafe
89
+ recommends to clients (act above 0.9, review between 0.5 and 0.9, route to
90
+ a human below 0.5) are calibrated against. Jev's `noul` answers carry no
91
+ confidence.
92
+
93
+ jevify computes exactly this in the one statistics chokepoint
94
+ (`jevify/domain/distribution.py`) and nowhere else. Until 2026-09-23 it
95
+ served one minus the normalized entropy under Jev's field name, which is
96
+ systematically lower (`[0.90, 0.06, 0.04]` gave 0.64) and would have moved
97
+ a client's threshold decisions; a reader of the NVIDIA forum thread caught
98
+ it. For `noul`, jevify keeps its own statistic (distance of P(true) from one
99
+ half, scaled to [0, 1]) under `x_jevify.confidence`, never on Jev's field.
100
+
101
+ Scorecards under `docs/evidence/` whose `run_at` precedes 2026-09-23 record
102
+ the entropy statistic in their wrong-case column; the reliability table bins
103
+ on the top probability and is unaffected. They are not rewritten, because a
104
+ number in a document comes from a run.
105
+
80
106
  ## Consequences
81
107
 
82
108
  - Compatibility is tested, not asserted. A client written for Jev runs
@@ -84,8 +110,9 @@ that imports jevify gets identical behaviour without HTTP.
84
110
  - The `x_jevify` namespace grows without ever touching Jev's fields.
85
111
  - The explicit ingest introduces server-side state and its expiry; the
86
112
  stateless route remains the default and the ingest is opt-in.
87
- - Jev's `confidence` is served with Jev's meaning, a statistic of the
88
- distribution, and the claims contract forbids describing it otherwise.
113
+ - Jev's `confidence` is served with Jev's meaning and Jev's formula (D8), a
114
+ statistic of the distribution, and the claims contract forbids describing
115
+ it otherwise.
89
116
 
90
117
  ## Rejected alternatives
91
118
 
@@ -32,6 +32,7 @@ hash is the recipe's identity in every answer and every scorecard.
32
32
  | Section | Holds |
33
33
  |---|---|
34
34
  | `model` | Identifier as the backend knows it, revision when known, the adapter kind, and for endpoints the dialect the probe detected. |
35
+ | `endpoint` | For endpoint-backed kinds: base URL, the environment variable holding an API key, the dialect, the timeout, and `extra_body`, server-specific fields merged into the top level of every request (a field jevify itself sets is refused when the backend is built, from one registry in the endpoint client; omitted from the hash when empty, so older recipes keep theirs), `headers`, static HTTP headers sent on every request (`Content-Type` and `Authorization` are refused when the backend is built; never a secret, since the recipe is hashed and shared, so a name that looks like a credential is refused when the recipe loads; omitted from the hash when empty), and `headers_env`, header names mapped to environment variables whose values are read when the backend is built and never written to the recipe (an unset variable fails then; omitted from the hash when empty). |
35
36
  | `template` | For endpoints: how state, instructions and options are rendered into messages or a raw prompt; where the question goes; the answer-slot suffix; template arguments such as thinking off; whether assistant prefill is used. For encoders: the sequence layout and marker scheme. |
36
37
  | `answers` | The answer tokens for `noul`, the identifier scheme for `choice`, the level tokens for `score`, each with the token ids the probe verified. |
37
38
  | `readout` | The rung of the ladder to use or `auto`; the permutation policy and call count for `choice`. |
@@ -14,7 +14,7 @@ later documents can cite "ADR-0002 D3".
14
14
  |---|---|---|
15
15
  | [0001](0001-founding-choices.md) | Founding choices: name, scope, licence, toolchain, conventions | Accepted (2026-09-19); D3 amended 2026-09-20: GPL-3.0 to MIT |
16
16
  | [0002](0002-backend-port-and-adapter-kinds.md) | One backend port, four adapter kinds, a capability probe, a readout ladder | Accepted (2026-09-19) |
17
- | [0003](0003-jev-compatible-api.md) | Jev's evaluate call is the API; extensions are namespaced; the SDK is the acceptance test | Accepted (2026-09-19) |
17
+ | [0003](0003-jev-compatible-api.md) | Jev's evaluate call is the API; extensions are namespaced; the SDK is the acceptance test | Accepted (2026-09-19); D8 added 2026-09-23: `confidence` is Jev's peak statistic, amends invariants §5 |
18
18
  | [0004](0004-recipes.md) | Recipes: the hashed file that is the only way a model is asked | Accepted (2026-09-19) |
19
19
  | [0005](0005-evaluation-evidence-and-calibration.md) | Suites, scorecards, committed evidence, post-hoc calibration | Accepted (2026-09-19) |
20
20
  | [0006](0006-toolchain-dependency-budget-and-portability.md) | Toolchain, dependency budget and portability: five light core packages, extras, argparse, uv/pipx, Python 3.12–3.14, no lock-in | Accepted (2026-09-19); makes ADR-0001 D4 precise |