undercurrent 0.0.0a0__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 (273) hide show
  1. undercurrent-0.2.0/.gitignore +58 -0
  2. undercurrent-0.2.0/CHANGELOG.md +254 -0
  3. undercurrent-0.2.0/NOTICE +4 -0
  4. undercurrent-0.2.0/PKG-INFO +229 -0
  5. undercurrent-0.2.0/README.md +172 -0
  6. undercurrent-0.2.0/benchmarks/README.md +390 -0
  7. undercurrent-0.2.0/benchmarks/run.py +1413 -0
  8. undercurrent-0.2.0/docs/README.md +50 -0
  9. undercurrent-0.2.0/docs/_legacy/probing_adapter_hf.md +68 -0
  10. undercurrent-0.2.0/docs/_legacy/probing_adapter_vllm.md +389 -0
  11. undercurrent-0.2.0/docs/_legacy/probing_content_safety.md +394 -0
  12. undercurrent-0.2.0/docs/_legacy/probing_core.md +136 -0
  13. undercurrent-0.2.0/docs/_legacy/probing_logging.md +177 -0
  14. undercurrent-0.2.0/docs/_legacy/probing_router.md +247 -0
  15. undercurrent-0.2.0/docs/_legacy/probing_spec.md +227 -0
  16. undercurrent-0.2.0/docs/_legacy/vllm_tp_pp_activation_extraction_notes.md +443 -0
  17. undercurrent-0.2.0/docs/about/changelog.md +6 -0
  18. undercurrent-0.2.0/docs/about/contributing.md +12 -0
  19. undercurrent-0.2.0/docs/about/intended-use.md +136 -0
  20. undercurrent-0.2.0/docs/about/privacy.md +203 -0
  21. undercurrent-0.2.0/docs/api-stability.md +156 -0
  22. undercurrent-0.2.0/docs/assets/brand/social-preview.png +0 -0
  23. undercurrent-0.2.0/docs/assets/brand/undercurrent-icon.svg +8 -0
  24. undercurrent-0.2.0/docs/assets/brand/undercurrent-logo-dark.png +0 -0
  25. undercurrent-0.2.0/docs/assets/brand/undercurrent-logo-dark.svg +8 -0
  26. undercurrent-0.2.0/docs/assets/brand/undercurrent-logo.png +0 -0
  27. undercurrent-0.2.0/docs/assets/brand/undercurrent-logo.svg +8 -0
  28. undercurrent-0.2.0/docs/assets/brand/undercurrent-mark-dark.svg +8 -0
  29. undercurrent-0.2.0/docs/assets/brand/undercurrent-mark.svg +8 -0
  30. undercurrent-0.2.0/docs/benchmarks.md +6 -0
  31. undercurrent-0.2.0/docs/comparison.md +264 -0
  32. undercurrent-0.2.0/docs/compatibility.md +183 -0
  33. undercurrent-0.2.0/docs/concepts/architecture.md +233 -0
  34. undercurrent-0.2.0/docs/concepts/execution-modes.md +194 -0
  35. undercurrent-0.2.0/docs/concepts/extraction-points.md +256 -0
  36. undercurrent-0.2.0/docs/concepts/interventions.md +180 -0
  37. undercurrent-0.2.0/docs/concepts/position-selectors.md +142 -0
  38. undercurrent-0.2.0/docs/concepts/probes.md +276 -0
  39. undercurrent-0.2.0/docs/examples.md +61 -0
  40. undercurrent-0.2.0/docs/getting-started/installation.md +89 -0
  41. undercurrent-0.2.0/docs/getting-started/quickstart.md +271 -0
  42. undercurrent-0.2.0/docs/guides/custom-probe.md +813 -0
  43. undercurrent-0.2.0/docs/guides/observation-sinks.md +714 -0
  44. undercurrent-0.2.0/docs/guides/vllm-deployment.md +538 -0
  45. undercurrent-0.2.0/docs/index.md +88 -0
  46. undercurrent-0.2.0/docs/internals/vllm-adapter.md +489 -0
  47. undercurrent-0.2.0/docs/internals/vllm-parallelism.md +440 -0
  48. undercurrent-0.2.0/docs/production/async-execution.md +314 -0
  49. undercurrent-0.2.0/docs/production/embedding.md +447 -0
  50. undercurrent-0.2.0/docs/production/intervention-policies.md +308 -0
  51. undercurrent-0.2.0/docs/production/metrics.md +299 -0
  52. undercurrent-0.2.0/docs/reference/adapters.md +70 -0
  53. undercurrent-0.2.0/docs/reference/cli.md +120 -0
  54. undercurrent-0.2.0/docs/reference/core.md +140 -0
  55. undercurrent-0.2.0/docs/reference/index.md +270 -0
  56. undercurrent-0.2.0/docs/reference/model.md +52 -0
  57. undercurrent-0.2.0/docs/reference/router.md +102 -0
  58. undercurrent-0.2.0/docs/reference/sinks.md +86 -0
  59. undercurrent-0.2.0/docs/reference/spec.md +202 -0
  60. undercurrent-0.2.0/docs/stylesheets/brand.css +31 -0
  61. undercurrent-0.2.0/examples/content_safety/Dockerfile +73 -0
  62. undercurrent-0.2.0/examples/content_safety/README.md +36 -0
  63. undercurrent-0.2.0/examples/content_safety/content_safety_demo/__init__.py +45 -0
  64. undercurrent-0.2.0/examples/content_safety/content_safety_demo/custom_mlp.py +296 -0
  65. undercurrent-0.2.0/examples/content_safety/content_safety_demo/models.py +123 -0
  66. undercurrent-0.2.0/examples/content_safety/content_safety_demo/single_token.py +136 -0
  67. undercurrent-0.2.0/examples/content_safety/content_safety_demo/spec_binding.py +61 -0
  68. undercurrent-0.2.0/examples/content_safety/content_safety_demo/tensors.py +25 -0
  69. undercurrent-0.2.0/examples/content_safety/content_safety_demo/trajectory.py +144 -0
  70. undercurrent-0.2.0/examples/content_safety/content_safety_gpt2.yaml +28 -0
  71. undercurrent-0.2.0/examples/content_safety/content_safety_llama.yaml +31 -0
  72. undercurrent-0.2.0/examples/content_safety/custom_mlp_llama31_8b.yaml +26 -0
  73. undercurrent-0.2.0/examples/content_safety/make_dummy_mlp_checkpoint.py +65 -0
  74. undercurrent-0.2.0/examples/content_safety/run_vllm_pipeline.py +166 -0
  75. undercurrent-0.2.0/examples/function_probe.py +18 -0
  76. undercurrent-0.2.0/examples/openai_server/README.md +5 -0
  77. undercurrent-0.2.0/examples/openai_server/response_schema.py +404 -0
  78. undercurrent-0.2.0/examples/openai_server/serve_llama_mlp_pipeline.py +339 -0
  79. undercurrent-0.2.0/examples/quickstart.py +24 -0
  80. undercurrent-0.2.0/examples/specs/classic_single_token.yaml +13 -0
  81. undercurrent-0.2.0/examples/specs/continuous_trajectory.yaml +16 -0
  82. undercurrent-0.2.0/examples/specs/offset_dual_position.yaml +14 -0
  83. undercurrent-0.2.0/examples/specs/sliced_range.yaml +13 -0
  84. undercurrent-0.2.0/examples/train_probe/README.md +188 -0
  85. undercurrent-0.2.0/examples/train_probe/collector.py +79 -0
  86. undercurrent-0.2.0/examples/train_probe/data_sources.py +132 -0
  87. undercurrent-0.2.0/examples/train_probe/linear_probe.py +262 -0
  88. undercurrent-0.2.0/examples/train_probe/requirements.txt +3 -0
  89. undercurrent-0.2.0/examples/train_probe/train_probe.py +286 -0
  90. undercurrent-0.2.0/examples/train_probe/use_probe.py +19 -0
  91. undercurrent-0.2.0/notebooks/quickstart.ipynb +357 -0
  92. undercurrent-0.2.0/notebooks/train_probe.ipynb +415 -0
  93. undercurrent-0.2.0/pyproject.toml +297 -0
  94. undercurrent-0.2.0/schema/probe-spec.schema.json +481 -0
  95. undercurrent-0.2.0/scripts/generate_schema.py +34 -0
  96. undercurrent-0.2.0/scripts/gpu_check.sh +203 -0
  97. undercurrent-0.2.0/scripts/smoke_test_wheel.sh +452 -0
  98. undercurrent-0.2.0/src/undercurrent/__init__.py +191 -0
  99. undercurrent-0.2.0/src/undercurrent/__main__.py +8 -0
  100. undercurrent-0.2.0/src/undercurrent/_version.py +24 -0
  101. undercurrent-0.2.0/src/undercurrent/adapters/__init__.py +26 -0
  102. undercurrent-0.2.0/src/undercurrent/adapters/_optional.py +89 -0
  103. undercurrent-0.2.0/src/undercurrent/adapters/base.py +141 -0
  104. undercurrent-0.2.0/src/undercurrent/adapters/hf/__init__.py +38 -0
  105. undercurrent-0.2.0/src/undercurrent/adapters/hf/adapter.py +459 -0
  106. undercurrent-0.2.0/src/undercurrent/adapters/hf/errors.py +15 -0
  107. undercurrent-0.2.0/src/undercurrent/adapters/hf/introspect.py +107 -0
  108. undercurrent-0.2.0/src/undercurrent/adapters/hf/stopping_criteria.py +72 -0
  109. undercurrent-0.2.0/src/undercurrent/adapters/vllm/__init__.py +46 -0
  110. undercurrent-0.2.0/src/undercurrent/adapters/vllm/activation_batch.py +90 -0
  111. undercurrent-0.2.0/src/undercurrent/adapters/vllm/adapter.py +794 -0
  112. undercurrent-0.2.0/src/undercurrent/adapters/vllm/graph_capture.py +133 -0
  113. undercurrent-0.2.0/src/undercurrent/adapters/vllm/introspection.py +153 -0
  114. undercurrent-0.2.0/src/undercurrent/adapters/vllm/plugin.py +181 -0
  115. undercurrent-0.2.0/src/undercurrent/adapters/vllm/seq_mapper.py +236 -0
  116. undercurrent-0.2.0/src/undercurrent/adapters/vllm/support.py +89 -0
  117. undercurrent-0.2.0/src/undercurrent/adapters/vllm/version_check.py +100 -0
  118. undercurrent-0.2.0/src/undercurrent/adapters/vllm/worker_extension.py +998 -0
  119. undercurrent-0.2.0/src/undercurrent/cli/__init__.py +115 -0
  120. undercurrent-0.2.0/src/undercurrent/cli/_errors.py +13 -0
  121. undercurrent-0.2.0/src/undercurrent/cli/inspect_model.py +308 -0
  122. undercurrent-0.2.0/src/undercurrent/cli/schema.py +40 -0
  123. undercurrent-0.2.0/src/undercurrent/cli/validate.py +164 -0
  124. undercurrent-0.2.0/src/undercurrent/core/__init__.py +69 -0
  125. undercurrent-0.2.0/src/undercurrent/core/activation.py +11 -0
  126. undercurrent-0.2.0/src/undercurrent/core/context.py +29 -0
  127. undercurrent-0.2.0/src/undercurrent/core/examples/__init__.py +15 -0
  128. undercurrent-0.2.0/src/undercurrent/core/examples/_util.py +23 -0
  129. undercurrent-0.2.0/src/undercurrent/core/examples/mlp_classifier.py +73 -0
  130. undercurrent-0.2.0/src/undercurrent/core/examples/trajectory_score.py +102 -0
  131. undercurrent-0.2.0/src/undercurrent/core/function_probe.py +364 -0
  132. undercurrent-0.2.0/src/undercurrent/core/probe.py +207 -0
  133. undercurrent-0.2.0/src/undercurrent/core/registry.py +393 -0
  134. undercurrent-0.2.0/src/undercurrent/core/result.py +33 -0
  135. undercurrent-0.2.0/src/undercurrent/core/signal.py +41 -0
  136. undercurrent-0.2.0/src/undercurrent/errors.py +97 -0
  137. undercurrent-0.2.0/src/undercurrent/model.py +955 -0
  138. undercurrent-0.2.0/src/undercurrent/py.typed +0 -0
  139. undercurrent-0.2.0/src/undercurrent/router/__init__.py +85 -0
  140. undercurrent-0.2.0/src/undercurrent/router/binding.py +246 -0
  141. undercurrent-0.2.0/src/undercurrent/router/errors.py +14 -0
  142. undercurrent-0.2.0/src/undercurrent/router/metrics.py +233 -0
  143. undercurrent-0.2.0/src/undercurrent/router/overflow.py +165 -0
  144. undercurrent-0.2.0/src/undercurrent/router/request.py +134 -0
  145. undercurrent-0.2.0/src/undercurrent/router/router.py +888 -0
  146. undercurrent-0.2.0/src/undercurrent/sinks/__init__.py +55 -0
  147. undercurrent-0.2.0/src/undercurrent/sinks/file_sink.py +69 -0
  148. undercurrent-0.2.0/src/undercurrent/sinks/records.py +32 -0
  149. undercurrent-0.2.0/src/undercurrent/sinks/redaction.py +179 -0
  150. undercurrent-0.2.0/src/undercurrent/sinks/serialize.py +70 -0
  151. undercurrent-0.2.0/src/undercurrent/sinks/sink.py +97 -0
  152. undercurrent-0.2.0/src/undercurrent/sinks/webhook_sink.py +217 -0
  153. undercurrent-0.2.0/src/undercurrent/sinks/wiring.py +16 -0
  154. undercurrent-0.2.0/src/undercurrent/spec/__init__.py +80 -0
  155. undercurrent-0.2.0/src/undercurrent/spec/activation_record.py +86 -0
  156. undercurrent-0.2.0/src/undercurrent/spec/errors.py +41 -0
  157. undercurrent-0.2.0/src/undercurrent/spec/json_schema.py +248 -0
  158. undercurrent-0.2.0/src/undercurrent/spec/parser.py +476 -0
  159. undercurrent-0.2.0/src/undercurrent/spec/position.py +285 -0
  160. undercurrent-0.2.0/src/undercurrent/spec/resolved.py +223 -0
  161. undercurrent-0.2.0/src/undercurrent/spec/schema.py +379 -0
  162. undercurrent-0.2.0/src/undercurrent/spec/serialize.py +58 -0
  163. undercurrent-0.2.0/tests/__init__.py +0 -0
  164. undercurrent-0.2.0/tests/adapters/__init__.py +0 -0
  165. undercurrent-0.2.0/tests/adapters/hf/__init__.py +0 -0
  166. undercurrent-0.2.0/tests/adapters/hf/_helpers.py +86 -0
  167. undercurrent-0.2.0/tests/adapters/hf/conftest.py +55 -0
  168. undercurrent-0.2.0/tests/adapters/hf/test_adapter_contract.py +92 -0
  169. undercurrent-0.2.0/tests/adapters/hf/test_intervention_e2e.py +127 -0
  170. undercurrent-0.2.0/tests/adapters/hf/test_introspect.py +69 -0
  171. undercurrent-0.2.0/tests/adapters/test_optional.py +42 -0
  172. undercurrent-0.2.0/tests/adapters/vllm/__init__.py +0 -0
  173. undercurrent-0.2.0/tests/adapters/vllm/_helpers.py +88 -0
  174. undercurrent-0.2.0/tests/adapters/vllm/conftest.py +65 -0
  175. undercurrent-0.2.0/tests/adapters/vllm/test_activation_batch.py +92 -0
  176. undercurrent-0.2.0/tests/adapters/vllm/test_adapter_contract.py +795 -0
  177. undercurrent-0.2.0/tests/adapters/vllm/test_adapter_shutdown.py +90 -0
  178. undercurrent-0.2.0/tests/adapters/vllm/test_graph_capture.py +216 -0
  179. undercurrent-0.2.0/tests/adapters/vllm/test_integration_vllm.py +213 -0
  180. undercurrent-0.2.0/tests/adapters/vllm/test_plugin.py +90 -0
  181. undercurrent-0.2.0/tests/adapters/vllm/test_residual_stream_capture.py +354 -0
  182. undercurrent-0.2.0/tests/adapters/vllm/test_residual_stream_gpu.py +199 -0
  183. undercurrent-0.2.0/tests/adapters/vllm/test_seq_mapper.py +225 -0
  184. undercurrent-0.2.0/tests/adapters/vllm/test_support.py +54 -0
  185. undercurrent-0.2.0/tests/adapters/vllm/test_version_check.py +111 -0
  186. undercurrent-0.2.0/tests/adapters/vllm/test_worker_extension_bookkeeping.py +237 -0
  187. undercurrent-0.2.0/tests/adapters/vllm/test_worker_extension_capture.py +221 -0
  188. undercurrent-0.2.0/tests/adapters/vllm/test_worker_extension_staging.py +279 -0
  189. undercurrent-0.2.0/tests/benchmarks/__init__.py +0 -0
  190. undercurrent-0.2.0/tests/benchmarks/test_harness.py +353 -0
  191. undercurrent-0.2.0/tests/cli/__init__.py +0 -0
  192. undercurrent-0.2.0/tests/cli/conftest.py +35 -0
  193. undercurrent-0.2.0/tests/cli/test_inspect_model.py +162 -0
  194. undercurrent-0.2.0/tests/cli/test_main.py +76 -0
  195. undercurrent-0.2.0/tests/cli/test_schema.py +34 -0
  196. undercurrent-0.2.0/tests/cli/test_validate.py +238 -0
  197. undercurrent-0.2.0/tests/conftest.py +72 -0
  198. undercurrent-0.2.0/tests/core/__init__.py +0 -0
  199. undercurrent-0.2.0/tests/core/conftest.py +27 -0
  200. undercurrent-0.2.0/tests/core/test_function_probe.py +515 -0
  201. undercurrent-0.2.0/tests/core/test_probe_isolation.py +100 -0
  202. undercurrent-0.2.0/tests/core/test_probe_result_always_returned.py +38 -0
  203. undercurrent-0.2.0/tests/core/test_registry.py +347 -0
  204. undercurrent-0.2.0/tests/core/test_trajectory_score_probe.py +59 -0
  205. undercurrent-0.2.0/tests/docs/__init__.py +0 -0
  206. undercurrent-0.2.0/tests/docs/test_doc_snippets.py +40 -0
  207. undercurrent-0.2.0/tests/e2e/__init__.py +0 -0
  208. undercurrent-0.2.0/tests/e2e/test_hf_pipeline_cpu.py +199 -0
  209. undercurrent-0.2.0/tests/examples/__init__.py +0 -0
  210. undercurrent-0.2.0/tests/examples/content_safety/__init__.py +0 -0
  211. undercurrent-0.2.0/tests/examples/content_safety/conftest.py +37 -0
  212. undercurrent-0.2.0/tests/examples/content_safety/test_custom_mlp_probe.py +200 -0
  213. undercurrent-0.2.0/tests/examples/content_safety/test_isolation.py +62 -0
  214. undercurrent-0.2.0/tests/examples/content_safety/test_live_llama_integration.py +98 -0
  215. undercurrent-0.2.0/tests/examples/content_safety/test_single_token_probe.py +83 -0
  216. undercurrent-0.2.0/tests/examples/content_safety/test_spec_binding.py +112 -0
  217. undercurrent-0.2.0/tests/examples/content_safety/test_trajectory_probe.py +118 -0
  218. undercurrent-0.2.0/tests/examples/openai_server/__init__.py +0 -0
  219. undercurrent-0.2.0/tests/examples/openai_server/conftest.py +6 -0
  220. undercurrent-0.2.0/tests/examples/openai_server/test_response_schema.py +324 -0
  221. undercurrent-0.2.0/tests/examples/openai_server/test_server_cli.py +13 -0
  222. undercurrent-0.2.0/tests/examples/test_function_probe_example.py +24 -0
  223. undercurrent-0.2.0/tests/examples/train_probe/__init__.py +0 -0
  224. undercurrent-0.2.0/tests/examples/train_probe/conftest.py +6 -0
  225. undercurrent-0.2.0/tests/examples/train_probe/test_train_probe.py +331 -0
  226. undercurrent-0.2.0/tests/notebooks/__init__.py +0 -0
  227. undercurrent-0.2.0/tests/notebooks/test_notebooks.py +77 -0
  228. undercurrent-0.2.0/tests/router/__init__.py +0 -0
  229. undercurrent-0.2.0/tests/router/_helpers.py +210 -0
  230. undercurrent-0.2.0/tests/router/conftest.py +47 -0
  231. undercurrent-0.2.0/tests/router/test_context_managers.py +268 -0
  232. undercurrent-0.2.0/tests/router/test_dispatch.py +118 -0
  233. undercurrent-0.2.0/tests/router/test_end_request.py +95 -0
  234. undercurrent-0.2.0/tests/router/test_intervention.py +288 -0
  235. undercurrent-0.2.0/tests/router/test_isolation.py +82 -0
  236. undercurrent-0.2.0/tests/router/test_load.py +129 -0
  237. undercurrent-0.2.0/tests/router/test_metrics.py +294 -0
  238. undercurrent-0.2.0/tests/router/test_overflow.py +110 -0
  239. undercurrent-0.2.0/tests/router/test_probe_args.py +48 -0
  240. undercurrent-0.2.0/tests/router/test_probe_registry.py +122 -0
  241. undercurrent-0.2.0/tests/router/test_registration.py +111 -0
  242. undercurrent-0.2.0/tests/router/test_shutdown.py +129 -0
  243. undercurrent-0.2.0/tests/router/test_stress_overflow.py +177 -0
  244. undercurrent-0.2.0/tests/sinks/__init__.py +0 -0
  245. undercurrent-0.2.0/tests/sinks/_helpers.py +125 -0
  246. undercurrent-0.2.0/tests/sinks/conftest.py +28 -0
  247. undercurrent-0.2.0/tests/sinks/test_file_sink.py +105 -0
  248. undercurrent-0.2.0/tests/sinks/test_redaction.py +455 -0
  249. undercurrent-0.2.0/tests/sinks/test_router_integration.py +167 -0
  250. undercurrent-0.2.0/tests/sinks/test_webhook_sink.py +178 -0
  251. undercurrent-0.2.0/tests/spec/__init__.py +0 -0
  252. undercurrent-0.2.0/tests/spec/test_activation_record.py +116 -0
  253. undercurrent-0.2.0/tests/spec/test_intervention.py +117 -0
  254. undercurrent-0.2.0/tests/spec/test_json_schema.py +323 -0
  255. undercurrent-0.2.0/tests/spec/test_parser.py +171 -0
  256. undercurrent-0.2.0/tests/spec/test_position.py +140 -0
  257. undercurrent-0.2.0/tests/spec/test_roundtrip.py +100 -0
  258. undercurrent-0.2.0/tests/spec/test_vocab.py +250 -0
  259. undercurrent-0.2.0/tests/test_errors.py +571 -0
  260. undercurrent-0.2.0/tests/test_gpu_check.py +91 -0
  261. undercurrent-0.2.0/tests/test_import_light.py +17 -0
  262. undercurrent-0.2.0/tests/test_markers.py +18 -0
  263. undercurrent-0.2.0/tests/test_metadata.py +20 -0
  264. undercurrent-0.2.0/tests/test_model.py +642 -0
  265. undercurrent-0.2.0/tests/test_model_vllm.py +478 -0
  266. undercurrent-0.2.0/tests/test_public_api.py +397 -0
  267. undercurrent-0.2.0/tests/test_release_workflow.py +67 -0
  268. undercurrent-0.2.0/tests/test_version.py +11 -0
  269. undercurrent-0.0.0a0/PKG-INFO +0 -28
  270. undercurrent-0.0.0a0/README.md +0 -14
  271. undercurrent-0.0.0a0/pyproject.toml +0 -24
  272. undercurrent-0.0.0a0/src/undercurrent/__init__.py +0 -15
  273. {undercurrent-0.0.0a0 → undercurrent-0.2.0}/LICENSE +0 -0
@@ -0,0 +1,58 @@
1
+ # Root ignore rules for the whole repository. Keep .dockerignore in sync:
2
+ # anything that must never be committed should also stay out of image
3
+ # build contexts.
4
+
5
+ # --- Python bytecode and caches ---------------------------------------------
6
+ __pycache__/
7
+ *.py[cod]
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .mypy_cache/
11
+
12
+ # --- Build output ------------------------------------------------------------
13
+ build/
14
+ dist/
15
+ *.egg-info/
16
+ # Generated by hatch-vcs at build/install time
17
+ src/undercurrent/_version.py
18
+ # mkdocs build output
19
+ site/
20
+
21
+ # --- Environments ------------------------------------------------------------
22
+ .venv/
23
+ venv/
24
+ .env
25
+ .env.*
26
+ !.env.example
27
+
28
+ # --- Coverage ----------------------------------------------------------------
29
+ .coverage*
30
+ htmlcov/
31
+ coverage.xml
32
+
33
+ # --- Model weights and checkpoints -------------------------------------------
34
+ # Trained checkpoints are private, per-user artefacts and can be tens of MB.
35
+ # Never commit them; publish weights through the Hugging Face Hub instead.
36
+ *.pt
37
+ *.pth
38
+ *.ckpt
39
+ *.safetensors
40
+ *.bin
41
+ *.gguf
42
+
43
+ # --- Notebooks ---------------------------------------------------------------
44
+ .ipynb_checkpoints/
45
+
46
+ # --- Local tooling -----------------------------------------------------------
47
+ .claude/worktrees/
48
+ .claude/settings.local.json
49
+ .idea/
50
+ .vscode/
51
+ .DS_Store
52
+
53
+ # --- Run artefacts -----------------------------------------------------------
54
+ # Observation / metrics sink output
55
+ *.ndjson
56
+ benchmarks/results/
57
+ # examples/train_probe output (probe weights, metrics), wherever it's run from
58
+ **/outputs/train_probe/
@@ -0,0 +1,254 @@
1
+ # Changelog
2
+
3
+ All notable changes to Undercurrent are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ While Undercurrent is on 0.x:
9
+
10
+ - A **minor** release (0.1 → 0.2) may contain breaking changes. Each one is
11
+ listed under **Changed** or **Removed** with what to do about it.
12
+ - A **patch** release (0.1.0 → 0.1.1) contains only backwards-compatible fixes.
13
+ - A deprecated API keeps working and emits a `DeprecationWarning` for at least
14
+ one minor release before it is removed.
15
+
16
+ Versions come from git tags (`vX.Y.Z`). See [RELEASING.md](RELEASING.md) for
17
+ how a release is cut.
18
+
19
+ ## [Unreleased]
20
+
21
+ ## [0.2.0] - 2026-11-01
22
+
23
+ Performance and correctness work on the vLLM adapter, and experimental
24
+ graph-mode `residual_stream` capture that keeps CUDA graphs and
25
+ `torch.compile`. There are no breaking changes: code written for 0.1.0 runs
26
+ unchanged.
27
+
28
+ ### Added
29
+
30
+ - vLLM adapter (experimental): graph-mode `residual_stream` capture.
31
+ `load_model(graph_capture_layers=[...])` (also accepted by
32
+ `ProbedModel(..., backend="vllm")`) captures the residual stream at the given
33
+ decoder layers without `enforce_eager=True`, so the engine keeps CUDA graphs
34
+ and `torch.compile`. It reuses vLLM's EAGLE-3 auxiliary hidden-state outputs,
35
+ enabled by the `vllm.general_plugins` entry point before compilation. Other
36
+ tensor types, other layers, models without that interface, and speculative
37
+ decoding that uses aux outputs are rejected with an error. The benchmark
38
+ harness gains `--graph-capture`.
39
+
40
+ ### Changed
41
+
42
+ - vLLM adapter: activation capture on CUDA no longer blocks the host once per
43
+ captured row. Each forward hook gathers its matched rows on the GPU and
44
+ starts one non-blocking copy into pinned host memory; records are built after
45
+ one wait per step (at the end of the step in-process, or when the driver
46
+ polls on the cross-process path). Probes see the same records, in the same
47
+ order, with inline aborts taking effect at the same step as before. See
48
+ "Copying captures to the host" in the vLLM adapter internals docs.
49
+ - vLLM adapter: on the cross-process polling path (vLLM 0.28's default), the
50
+ worker sends each poll's activations as one packed batch (float32 `bytes`
51
+ plus per-field lists) instead of one dict per record with the tensor as a
52
+ nested Python list, and the driver routes each drain in one executor call
53
+ instead of one per record. In a CPU microbenchmark at a 3,584-wide hidden
54
+ size, transport fell from about 350 µs to under 15 µs per record. Probes see
55
+ the same records in the same order; aborts are sent after the drain is
56
+ routed.
57
+ - vLLM adapter: forward hooks skip (layer, tensor type) pairs no request
58
+ captures with one set lookup, and position matching runs once per step
59
+ instead of once per hooked layer; the captured tensor is only extracted when
60
+ something matched. Hook-path CPU time per step fell by about a third in a
61
+ microbenchmark (16 requests x 8 probes, 28 layers).
62
+
63
+ ### Fixed
64
+
65
+ - vLLM adapter: `ActivationRecord.timestamp` is now the capture time, as
66
+ documented, on every path. On the cross-process polling path the record was
67
+ rebuilt in the driver with a fresh timestamp, so a probe (or a latency
68
+ measurement) saw the time of routing, not of capture, and couldn't tell how
69
+ stale an activation was. The worker now stamps each capture when its hook
70
+ runs and the packed batch carries it to the driver.
71
+ - vLLM adapter: with `OverflowPolicy.BLOCK` on the cross-process polling path,
72
+ async probes ran one at a time instead of in parallel. Each drained backlog
73
+ was routed one request at a time, so `route()` waited on one request's full
74
+ queue while every other request's worker sat idle. Drained records are now
75
+ interleaved round-robin across requests, keeping each request's forward-pass
76
+ order. On an A100 (Qwen2.5-7B, 16 requests, 76 ms probe) the old order made
77
+ `block` about 13x slower than the probe's own pace. The docs now also say
78
+ that on this path `block` delays results rather than slowing the engine.
79
+ - vLLM adapter: activations could be silently dropped under concurrency on the
80
+ polling path (engine core in a subprocess, the vLLM 0.28 default). One
81
+ request's drain of the shared worker buffer could still be routing another
82
+ request's records when that request received its final output and ended, so
83
+ those records were discarded as late. A request that finishes in the step that
84
+ captured its only extraction point (e.g. `prompt[-1]` with one new token) lost
85
+ its capture entirely; longer generations could lose their last activations.
86
+ Drains are now serialised, so every record captured up to a request's final
87
+ step is routed before the request ends.
88
+
89
+ ## [0.1.0] - 2026-10-02
90
+
91
+ The first public release of **Undercurrent**, Wrynx's activation-probing
92
+ platform for LLM inference. Earlier versions were internal development builds
93
+ and were never published.
94
+
95
+ Planned work that is not part of this release (an OpenAI-compatible
96
+ `undercurrent serve` server, Hugging Face Hub integration, a published
97
+ container image, and more) is tracked in the
98
+ [roadmap](https://github.com/wrynx/undercurrent/blob/main/ROADMAP.md).
99
+
100
+ ### Added
101
+
102
+ - **One installable package.** `pip install undercurrent` installs the
103
+ `undercurrent` package for Python 3.10+, with the Hugging Face
104
+ `transformers` backend in the base install. vLLM is never installed by
105
+ default. Tested on Python 3.10–3.13 with torch 2.1+ and transformers
106
+ 4.40–5.x; see [Compatibility](https://wrynx.github.io/undercurrent/compatibility/).
107
+ - **`ProbedModel`**, the high-level API (`undercurrent.ProbedModel`):
108
+ `ProbedModel.from_pretrained(model, spec=..., probes=...)`, then
109
+ `generate()`, which returns a `GenerationOutput` with the text and each
110
+ extraction point's `ProbeResult`. Backends `"hf"` (default) and `"vllm"`.
111
+ Pass `on_result=callback` for a simple result hook alongside the sink API.
112
+ - **Extraction-point specs** (`undercurrent.spec`). Declare what to capture
113
+ in YAML (`load_yaml_file`, `parse_yaml`, `parse_dict`) or construct
114
+ `ExtractionPoint`s in Python. Specs are validated by pydantic models and
115
+ round-trip back to YAML (`to_yaml`).
116
+ - Tensor types `residual_stream`, `attn_out`, `mlp_out` and `final_norm`,
117
+ selected per layer.
118
+ - A position-selector grammar: absolute indices (`5`), prompt and generated
119
+ indices (`prompt[-1]`, `generated[0]`), every generated token
120
+ (`generated[*]`), generated slices (`generated[5:]`, `generated[2:8]`),
121
+ and offsets (`prompt[-1]+1`). Malformed selectors raise
122
+ `PositionSyntaxError` with the accepted forms.
123
+ - `until` conditions for continuous selectors (`generation_end`,
124
+ `fixed_count`, `stop_token`).
125
+ - Example specs in `examples/specs/`.
126
+ - **Probe interface** (`undercurrent.core`). Probes implement the lifecycle
127
+ `spawn` → `on_start` → `on_activation`* → `on_end` and return
128
+ `ProbeSignal`s and a final `ProbeResult`. There are two probe kinds,
129
+ `single_shot` and `trajectory`. A fresh instance is spawned for each
130
+ request and extraction point, and probe classes that declare mutable
131
+ class-level attributes are rejected when the class is defined, so instances
132
+ can't share state across requests. Reference probes are in
133
+ `undercurrent.core.examples`: `MLPClassifierProbe` (single-shot) and
134
+ `TrajectoryScoreProbe` (trajectory).
135
+ - **Function probes and a probe registry.** Decorate a plain function
136
+ `(record) -> float | bool | ProbeSignal | None` with `@probe("name", ...)`
137
+ for stateless single-shot probes, and register probe classes by name with
138
+ `@register_probe("name")`. Third-party packages can expose probes through
139
+ the `undercurrent.probes` entry-point group.
140
+ - **Router** (`undercurrent.router`). Dispatches activation records to
141
+ per-request probe instances. Each extraction point picks an execution mode:
142
+ - `inline`: the probe runs on the generation path and its signal is
143
+ returned to the adapter, so it can abort generation.
144
+ - `async`: the probe runs out of band and doesn't block the response. Each
145
+ binding has a bounded queue (`DEFAULT_QUEUE_DEPTH`), served by a shared
146
+ worker pool, with a configurable `OverflowPolicy` (`drop_oldest`,
147
+ `drop_newest` or `block`).
148
+ - **Interventions.** An inline probe can return `ProbeAction.ABORT` to stop
149
+ generation. With the `block_until_signal` intervention policy, the router
150
+ waits up to `timeout_ms` for the probe, then applies `on_timeout`
151
+ (`continue` or `abort`). A circuit breaker downgrades an extraction point
152
+ to observe-only after `circuit_breaker_threshold` consecutive timeouts or
153
+ probe errors, so a failing probe can't keep stalling generation.
154
+ - **Metrics.** Per-binding queue depth, drops, activation latency and probe
155
+ errors go to a pluggable `MetricsSink`. `InMemoryMetricsRegistry` provides
156
+ snapshots.
157
+ - **Observation sinks** (`undercurrent.sinks`) for async probe output:
158
+ - `FileLogSink` writes NDJSON.
159
+ - `WebhookLogSink` POSTs JSON from a background thread, so a slow endpoint
160
+ never adds latency to the caller. Failed deliveries are retried with
161
+ exponential backoff, records that exhaust their retries are
162
+ dead-lettered to a file, and the send queue is bounded.
163
+ - A `LogSink` base class for writing your own sink.
164
+ - **Sink redaction.** `redact_keys()`, `drop_keys()` and `chain()` control
165
+ what a sink writes. `WebhookLogSink` redacts prompt text by default.
166
+ - **Router context managers.** `Router` and `router.request(...)` can be used
167
+ in `with` blocks so requests end and workers shut down cleanly.
168
+ - **Hugging Face adapter** (`undercurrent.adapters.hf`, in the base install).
169
+ `HFEngineAdapter` captures activations from `transformers` models with
170
+ forward hooks during `generate()`, and aborts generation through a
171
+ `StoppingCriteria` (`ProbingStoppingCriteria`).
172
+ - **vLLM adapter** (`undercurrent.adapters.vllm`). Install Undercurrent into
173
+ your existing vLLM environment or image (the recommended production path),
174
+ or use the `undercurrent[vllm]` extra for a vLLM from the tested range.
175
+ - Capture is aware of continuous batching: a worker extension installs the
176
+ hooks, and `SeqIdMapper` translates batch rows to
177
+ `(request_id, token_pos)`. Abort signals are wired back to the engine.
178
+ - A `vllm.general_plugins` entry point registers the worker extension.
179
+ - Constructing the adapter checks the installed vLLM version against the
180
+ supported range and raises `VLLMAdapterLimitationError` with the
181
+ installed version and the supported range. Set
182
+ `UNDERCURRENT_ALLOW_UNSUPPORTED_VLLM=1` to downgrade the error to a
183
+ warning. See
184
+ [docs/compatibility.md](https://github.com/wrynx/undercurrent/blob/main/docs/compatibility.md).
185
+ - Tensor and pipeline parallelism: results from every worker are merged,
186
+ and events that tensor-parallel ranks duplicate are deduplicated.
187
+ Multi-worker topologies are refused unless you pass
188
+ `allow_unsupported_executor=True`, because cross-rank ordering under
189
+ pipeline parallelism and Ray/distributed executors haven't been
190
+ validated. See the
191
+ [tensor and pipeline parallelism notes](https://wrynx.github.io/undercurrent/internals/vllm-parallelism/).
192
+ - **Command-line tool** `undercurrent`: `inspect-model` lists a model's
193
+ hookable layers and supported tensor types, `validate` checks spec files,
194
+ and `schema` prints the spec JSON Schema (also in
195
+ `schema/probe-spec.schema.json` and `undercurrent.spec.json_schema()`).
196
+ - **Curated top-level API.** The common names import from `undercurrent`
197
+ directly; see [API stability](https://wrynx.github.io/undercurrent/api-stability/)
198
+ for what is public. `load_spec()`
199
+ loads a spec from a path, YAML string or dict. The package ships `py.typed`.
200
+ - **Error hierarchy.** Errors Undercurrent raises on purpose derive from
201
+ `ProbingError` (and the matching built-in type), with messages that say
202
+ how to fix the problem.
203
+ - **Clear errors for missing backends.** Using an adapter whose backend isn't
204
+ installed raises `MissingDependencyError`, which says how to install it.
205
+ - **Examples** (in the repository, not installed with the package, and covered
206
+ by the test suite):
207
+ - [`examples/content_safety/`](https://github.com/wrynx/undercurrent/tree/main/examples/content_safety):
208
+ a **demo** content-safety probe in both `single_shot` and `trajectory`
209
+ variants, with YAML specs, a dummy-checkpoint maker, a vLLM pipeline
210
+ script and a Dockerfile. It is a demonstration of the probe API, not a
211
+ trained safety classifier.
212
+ - [`examples/openai_server/`](https://github.com/wrynx/undercurrent/tree/main/examples/openai_server):
213
+ a reference OpenAI-compatible wire format for probe verdicts (completion
214
+ bodies and SSE chunks) and a reference HTTP server built on it.
215
+ - **Probe-training example** (`examples/train_probe/`) and Colab notebooks
216
+ (`notebooks/quickstart.ipynb`, `notebooks/train_probe.ipynb`).
217
+
218
+ ### Fixed
219
+
220
+ - **vLLM `residual_stream` on fused-residual layers.** On Llama-family models
221
+ (and the other vLLM architectures whose decoder layer returns
222
+ `(hidden_states, residual)`), a `residual_stream` extraction point captured
223
+ the layer's MLP output. It now captures `hidden_states + residual`, the
224
+ residual stream after the layer, matching the HF backend. Decoder-layer
225
+ outputs the adapter can't classify raise `VLLMAdapterLimitationError`
226
+ instead of being captured silently. Verified on GPU (NVIDIA L4, vLLM 0.28.0)
227
+ by `tests/adapters/vllm/test_residual_stream_gpu.py`, which compares the
228
+ vLLM and HF captures token by token.
229
+
230
+ ### Known issues
231
+
232
+ See [Known issues (v0.1)](https://github.com/wrynx/undercurrent/blob/main/docs/compatibility.md#known-issues-v01)
233
+ for details and workarounds.
234
+
235
+ - **No GPU CI.** The GPU tests run by hand with `scripts/gpu_check.sh` before
236
+ each release. 0.1.0 passed on an NVIDIA L4 with vLLM 0.28.0, torch 2.13.0
237
+ and CUDA 13.0; other GPUs and multi-GPU topologies haven't been run.
238
+ - **Only vLLM 0.28 is supported** (`vllm>=0.28,<0.29`), and vLLM 0.30 is
239
+ already out. `UNDERCURRENT_ALLOW_UNSUPPORTED_VLLM=1` lets you try a newer
240
+ vLLM; the range widens only after a GPU validation run.
241
+
242
+ ### Security
243
+
244
+ - Vulnerabilities can be reported privately to security@wrynx.com or through
245
+ GitHub private vulnerability reporting. See
246
+ [SECURITY.md](https://github.com/wrynx/undercurrent/blob/main/SECURITY.md).
247
+ - The content-safety example loads classifier checkpoints only with
248
+ `torch.load(..., weights_only=True)`, accepts only plain tensor
249
+ `state_dict`s, and refuses to load on torch older than 2.6 (the first
250
+ release with a fix for CVE-2025-32434).
251
+
252
+ [Unreleased]: https://github.com/wrynx/undercurrent/compare/v0.2.0...HEAD
253
+ [0.2.0]: https://github.com/wrynx/undercurrent/compare/v0.1.0...v0.2.0
254
+ [0.1.0]: https://github.com/wrynx/undercurrent/releases/tag/v0.1.0
@@ -0,0 +1,4 @@
1
+ Undercurrent, Wrynx's activation-probing platform
2
+ Copyright 2026 Wrynx
3
+
4
+ This product includes software developed at Wrynx.
@@ -0,0 +1,229 @@
1
+ Metadata-Version: 2.5
2
+ Name: undercurrent
3
+ Version: 0.2.0
4
+ Summary: Undercurrent: production-grade LLM activation probing and intervention. Works with Hugging Face Transformers and vLLM.
5
+ Project-URL: Homepage, https://github.com/wrynx/undercurrent
6
+ Project-URL: Source, https://github.com/wrynx/undercurrent
7
+ Project-URL: Issues, https://github.com/wrynx/undercurrent/issues
8
+ Project-URL: Changelog, https://github.com/wrynx/undercurrent/blob/main/CHANGELOG.md
9
+ Project-URL: Documentation, https://wrynx.github.io/undercurrent/
10
+ Author: Wrynx
11
+ Maintainer: Wrynx
12
+ License-Expression: Apache-2.0
13
+ License-File: LICENSE
14
+ License-File: NOTICE
15
+ Keywords: activation-probing,ai-safety,guardrails,interpretability,llm,llm-inference,mechanistic-interpretability,probes,transformers,undercurrent,vllm
16
+ Classifier: Development Status :: 3 - Alpha
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Intended Audience :: Science/Research
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
27
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
+ Classifier: Typing :: Typed
29
+ Requires-Python: >=3.10
30
+ Requires-Dist: numpy>=1.23
31
+ Requires-Dist: packaging>=22
32
+ Requires-Dist: pydantic<3,>=2
33
+ Requires-Dist: pyyaml<7,>=6
34
+ Requires-Dist: torch>=2.1
35
+ Requires-Dist: transformers<6,>=4.40
36
+ Provides-Extra: dev
37
+ Requires-Dist: ipykernel>=6; extra == 'dev'
38
+ Requires-Dist: jsonschema<5,>=4.18; extra == 'dev'
39
+ Requires-Dist: mypy>=1.11; extra == 'dev'
40
+ Requires-Dist: nbclient>=0.10; extra == 'dev'
41
+ Requires-Dist: nbformat>=5.10; extra == 'dev'
42
+ Requires-Dist: pre-commit>=4; extra == 'dev'
43
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
44
+ Requires-Dist: pytest-timeout>=2.3; extra == 'dev'
45
+ Requires-Dist: pytest>=7; extra == 'dev'
46
+ Requires-Dist: ruff<0.18,>=0.16; extra == 'dev'
47
+ Requires-Dist: types-pyyaml; extra == 'dev'
48
+ Provides-Extra: docs
49
+ Requires-Dist: mkdocs-material; extra == 'docs'
50
+ Requires-Dist: mkdocs<2,>=1.6; extra == 'docs'
51
+ Requires-Dist: mkdocstrings[python]; extra == 'docs'
52
+ Requires-Dist: mktestdocs; extra == 'docs'
53
+ Requires-Dist: pymdown-extensions; extra == 'docs'
54
+ Provides-Extra: vllm
55
+ Requires-Dist: vllm<0.29,>=0.28; extra == 'vllm'
56
+ Description-Content-Type: text/markdown
57
+
58
+ <p align="center">
59
+ <picture>
60
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/wrynx/undercurrent/main/docs/assets/brand/undercurrent-logo-dark.png">
61
+ <img alt="Undercurrent" src="https://raw.githubusercontent.com/wrynx/undercurrent/main/docs/assets/brand/undercurrent-logo.png" width="440">
62
+ </picture>
63
+ </p>
64
+
65
+ <p align="center"><em>See the undercurrent before it surfaces.</em></p>
66
+
67
+ [![PyPI](https://img.shields.io/pypi/v/undercurrent)](https://pypi.org/project/undercurrent/)
68
+ [![Python versions](https://img.shields.io/pypi/pyversions/undercurrent)](https://pypi.org/project/undercurrent/)
69
+ [![CI](https://github.com/wrynx/undercurrent/actions/workflows/ci.yml/badge.svg)](https://github.com/wrynx/undercurrent/actions/workflows/ci.yml)
70
+ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/wrynx/undercurrent/blob/main/LICENSE)
71
+ [![Docs](https://img.shields.io/badge/docs-wrynx.github.io%2Fundercurrent-blue)](https://wrynx.github.io/undercurrent/)
72
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/wrynx/undercurrent/blob/main/notebooks/quickstart.ipynb)
73
+
74
+ <sub>An open-source project by [Wrynx](https://github.com/wrynx).</sub>
75
+
76
+ Undercurrent is production-grade activation probing for live LLM inference.
77
+ It runs your probes on a model's internal activations *while it generates*,
78
+ so a probe can stop a generation mid-flight, or observe asynchronously
79
+ without adding latency to the response.
80
+
81
+ ## Why Undercurrent
82
+
83
+ - **Declarative YAML specs.** A few lines say which tensor to capture, at
84
+ which layers and token positions, and which probe receives it. No model
85
+ surgery, no forked serving code.
86
+ - **Inline abort mid-generation.** An inline probe runs on the generation path
87
+ and can stop a request before the next token is produced.
88
+ - **Production-ready async execution.** Observe-only probes run off the hot
89
+ path on bounded per-binding queues with overflow policies, and report to
90
+ metrics and to file or webhook sinks (with retries, dead-lettering and
91
+ redaction).
92
+ - **Runs where your model runs.** Inside your existing vLLM deployment, with
93
+ continuous batching (the vLLM adapter is experimental, see
94
+ [Deploy with vLLM](https://wrynx.github.io/undercurrent/guides/vllm-deployment/)),
95
+ and with Hugging Face Transformers on a laptop CPU.
96
+
97
+ > **Known issues (v0.2):** the vLLM adapter is experimental: its GPU tests
98
+ > pass on an NVIDIA L4 with vLLM 0.28.0, run by hand (there's no GPU CI yet),
99
+ > and only vLLM 0.28 is supported. Fixed in 0.1.0: on vLLM, `residual_stream`
100
+ > on Llama-family (fused-residual) models is captured as
101
+ > `hidden_states + residual`, the same residual stream as the HF backend
102
+ > (GPU-verified). See
103
+ > [Known issues](https://wrynx.github.io/undercurrent/compatibility/#known-issues-v02).
104
+
105
+ ## How it works
106
+
107
+ In short: an engine adapter (Hugging Face or vLLM) captures the activations your
108
+ YAML spec asks for, and the router hands them to your probes, which either
109
+ abort the generation (inline) or report to sinks and metrics (async).
110
+
111
+ ```mermaid
112
+ flowchart LR
113
+ spec["YAML spec"] --> adapter
114
+ subgraph engine["Inference engine (HF Transformers or vLLM)"]
115
+ model["LLM generating tokens"] -- forward hooks --> adapter["Engine adapter"]
116
+ end
117
+ adapter -- ActivationRecords --> router["Router"]
118
+ router -- inline --> inline["Inline probes"]
119
+ inline -- ABORT --> model
120
+ router -- async, bounded queues --> async["Async probes"]
121
+ async --> sinks["Sinks: file / webhook / metrics"]
122
+ ```
123
+
124
+ ## Install
125
+
126
+ ```bash
127
+ pip install undercurrent
128
+ ```
129
+
130
+ Python 3.10+. The Hugging Face Transformers backend is included; there are no
131
+ extras to choose. For vLLM, install Undercurrent into your existing vLLM
132
+ environment or image, or use `pip install "undercurrent[vllm]"` to get a vLLM
133
+ from the tested range (currently `vllm>=0.28,<0.29`). The adapter checks the
134
+ installed vLLM version at runtime; set `UNDERCURRENT_ALLOW_UNSUPPORTED_VLLM=1`
135
+ to try an untested vLLM anyway.
136
+
137
+ Supported versions, tested combinations and known issues are on the
138
+ [Compatibility](https://wrynx.github.io/undercurrent/compatibility/) page.
139
+
140
+ ## Quickstart
141
+
142
+ Attach a probe to GPT-2 and read its result. This runs on CPU:
143
+
144
+ ```python
145
+ from undercurrent.core import ActivationRecord, probe
146
+ from undercurrent.model import ProbedModel
147
+
148
+ SPEC = """
149
+ version: "1"
150
+ extraction_points:
151
+ - name: last_prompt_token
152
+ layers: 6
153
+ tensor_type: residual_stream
154
+ position: "prompt[-1]"
155
+ probe_type: norm_gate
156
+ probe_kind: single_shot
157
+ execution_mode: inline
158
+ """
159
+
160
+
161
+ @probe("norm_gate", threshold=500.0)
162
+ def norm_gate(record: ActivationRecord) -> float:
163
+ """L2 norm of the residual stream at this token."""
164
+ return float(record.tensor.norm())
165
+
166
+
167
+ with ProbedModel.from_pretrained("openai-community/gpt2", spec=SPEC, probes={"norm_gate": norm_gate}) as model:
168
+ out = model.generate("The quick brown fox", max_new_tokens=20, temperature=0)
169
+
170
+ print(repr(out.text))
171
+ print(out.probe_results["last_prompt_token"].verdict)
172
+ ```
173
+
174
+ A score at or above `threshold` would abort the generation. The
175
+ [Quickstart](https://wrynx.github.io/undercurrent/getting-started/quickstart/)
176
+ continues with a trajectory probe that stops a generation part-way through,
177
+ and an async probe that logs to a file without touching the response.
178
+
179
+ ## How it compares
180
+
181
+ | | Undercurrent | TransformerLens / pyvene / baukit | nnsight (+ NDIF) | Llama Guard / NeMo Guardrails |
182
+ |---|---|---|---|---|
183
+ | Purpose | Production activation probing | Interpretability research | Interpretability research, local or remote | Text-level safety classification and rails |
184
+ | Runs inside vLLM serving | partial (single worker validated) | ✗ | ✓ | n/a (separate model / wraps the LLM call) |
185
+ | Abort a request mid-generation from a probe | ✓ | partial (edit activations; no built-in abort) | partial (edit activations; no built-in abort) | Llama Guard: n/a (application decides); NeMo: partial (streaming rails end the stream, in text chunks) |
186
+ | Declarative spec | ✓ YAML | pyvene: partial (dict configs); others ✗ | ✗ | NeMo: ✓ (YAML + Colang) |
187
+ | License | Apache-2.0 | MIT / Apache-2.0 / MIT | MIT | Llama 4 Community License / Apache-2.0 |
188
+
189
+ Last reviewed: 2026-10-01. Footnotes, sources and when to pick something else:
190
+ [full comparison](https://wrynx.github.io/undercurrent/comparison/).
191
+
192
+ ## Documentation
193
+
194
+ - [Quickstart](https://wrynx.github.io/undercurrent/getting-started/quickstart/): your first probe on GPT-2, in five minutes.
195
+ - [Concepts](https://wrynx.github.io/undercurrent/concepts/architecture/): extraction points, position selectors, probes, inline vs async.
196
+ - [Guides](https://wrynx.github.io/undercurrent/guides/custom-probe/): write, test and package your own probe.
197
+ - [Production](https://wrynx.github.io/undercurrent/guides/vllm-deployment/): vLLM deployment, embedding the router, backpressure, sinks and metrics.
198
+ - [Examples](https://wrynx.github.io/undercurrent/examples/): complete, runnable projects.
199
+ - [API reference](https://wrynx.github.io/undercurrent/reference/): every public class and function, and the CLI.
200
+ - [Intended use and limitations](https://wrynx.github.io/undercurrent/about/intended-use/) and [privacy and data handling](https://wrynx.github.io/undercurrent/about/privacy/): what Undercurrent is and isn't for, and what data it captures and writes.
201
+ - [Colab notebook](https://colab.research.google.com/github/wrynx/undercurrent/blob/main/notebooks/quickstart.ipynb): the quickstart in your browser.
202
+
203
+ [`examples/content_safety/`](https://github.com/wrynx/undercurrent/tree/main/examples/content_safety)
204
+ is a demo of the wiring (probes, specs and a vLLM pipeline), not a safety
205
+ product. Its probes use random-initialised or dummy weights.
206
+
207
+ ## Project status
208
+
209
+ Undercurrent is on 0.x: the API may change between minor releases. The
210
+ [API stability policy](https://github.com/wrynx/undercurrent/blob/main/docs/api-stability.md)
211
+ says what is public, what is experimental, and how deprecations work. Planned
212
+ work is in the [roadmap](https://github.com/wrynx/undercurrent/blob/main/ROADMAP.md).
213
+
214
+ ## Contributing and community
215
+
216
+ - [Contributing guide](https://github.com/wrynx/undercurrent/blob/main/CONTRIBUTING.md)
217
+ - [Security policy](https://github.com/wrynx/undercurrent/blob/main/SECURITY.md): report vulnerabilities privately to security@wrynx.com.
218
+ - [Code of Conduct](https://github.com/wrynx/undercurrent/blob/main/CODE_OF_CONDUCT.md)
219
+
220
+ ## Citation
221
+
222
+ If you use Undercurrent in research, please cite it using
223
+ [`CITATION.cff`](https://github.com/wrynx/undercurrent/blob/main/CITATION.cff)
224
+ (GitHub's "Cite this repository" button reads it).
225
+
226
+ ## License
227
+
228
+ Apache-2.0. See [LICENSE](https://github.com/wrynx/undercurrent/blob/main/LICENSE)
229
+ and [NOTICE](https://github.com/wrynx/undercurrent/blob/main/NOTICE). Copyright Wrynx.