undercurrent 0.0.0a0__tar.gz → 0.1.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 (268) hide show
  1. undercurrent-0.1.0/.gitignore +58 -0
  2. undercurrent-0.1.0/CHANGELOG.md +185 -0
  3. undercurrent-0.1.0/NOTICE +4 -0
  4. undercurrent-0.1.0/PKG-INFO +229 -0
  5. undercurrent-0.1.0/README.md +172 -0
  6. undercurrent-0.1.0/benchmarks/README.md +389 -0
  7. undercurrent-0.1.0/benchmarks/run.py +1391 -0
  8. undercurrent-0.1.0/docs/README.md +50 -0
  9. undercurrent-0.1.0/docs/_legacy/probing_adapter_hf.md +68 -0
  10. undercurrent-0.1.0/docs/_legacy/probing_adapter_vllm.md +389 -0
  11. undercurrent-0.1.0/docs/_legacy/probing_content_safety.md +394 -0
  12. undercurrent-0.1.0/docs/_legacy/probing_core.md +136 -0
  13. undercurrent-0.1.0/docs/_legacy/probing_logging.md +177 -0
  14. undercurrent-0.1.0/docs/_legacy/probing_router.md +247 -0
  15. undercurrent-0.1.0/docs/_legacy/probing_spec.md +227 -0
  16. undercurrent-0.1.0/docs/_legacy/vllm_tp_pp_activation_extraction_notes.md +443 -0
  17. undercurrent-0.1.0/docs/about/changelog.md +6 -0
  18. undercurrent-0.1.0/docs/about/contributing.md +12 -0
  19. undercurrent-0.1.0/docs/about/intended-use.md +136 -0
  20. undercurrent-0.1.0/docs/about/privacy.md +203 -0
  21. undercurrent-0.1.0/docs/api-stability.md +156 -0
  22. undercurrent-0.1.0/docs/assets/brand/social-preview.png +0 -0
  23. undercurrent-0.1.0/docs/assets/brand/undercurrent-icon.svg +8 -0
  24. undercurrent-0.1.0/docs/assets/brand/undercurrent-logo-dark.png +0 -0
  25. undercurrent-0.1.0/docs/assets/brand/undercurrent-logo-dark.svg +8 -0
  26. undercurrent-0.1.0/docs/assets/brand/undercurrent-logo.png +0 -0
  27. undercurrent-0.1.0/docs/assets/brand/undercurrent-logo.svg +8 -0
  28. undercurrent-0.1.0/docs/assets/brand/undercurrent-mark-dark.svg +8 -0
  29. undercurrent-0.1.0/docs/assets/brand/undercurrent-mark.svg +8 -0
  30. undercurrent-0.1.0/docs/benchmarks.md +6 -0
  31. undercurrent-0.1.0/docs/comparison.md +264 -0
  32. undercurrent-0.1.0/docs/compatibility.md +172 -0
  33. undercurrent-0.1.0/docs/concepts/architecture.md +233 -0
  34. undercurrent-0.1.0/docs/concepts/execution-modes.md +194 -0
  35. undercurrent-0.1.0/docs/concepts/extraction-points.md +256 -0
  36. undercurrent-0.1.0/docs/concepts/interventions.md +180 -0
  37. undercurrent-0.1.0/docs/concepts/position-selectors.md +142 -0
  38. undercurrent-0.1.0/docs/concepts/probes.md +276 -0
  39. undercurrent-0.1.0/docs/examples.md +61 -0
  40. undercurrent-0.1.0/docs/getting-started/installation.md +89 -0
  41. undercurrent-0.1.0/docs/getting-started/quickstart.md +271 -0
  42. undercurrent-0.1.0/docs/guides/custom-probe.md +813 -0
  43. undercurrent-0.1.0/docs/guides/observation-sinks.md +714 -0
  44. undercurrent-0.1.0/docs/guides/vllm-deployment.md +496 -0
  45. undercurrent-0.1.0/docs/index.md +88 -0
  46. undercurrent-0.1.0/docs/internals/vllm-adapter.md +424 -0
  47. undercurrent-0.1.0/docs/internals/vllm-parallelism.md +440 -0
  48. undercurrent-0.1.0/docs/production/async-execution.md +300 -0
  49. undercurrent-0.1.0/docs/production/embedding.md +447 -0
  50. undercurrent-0.1.0/docs/production/intervention-policies.md +308 -0
  51. undercurrent-0.1.0/docs/production/metrics.md +299 -0
  52. undercurrent-0.1.0/docs/reference/adapters.md +70 -0
  53. undercurrent-0.1.0/docs/reference/cli.md +120 -0
  54. undercurrent-0.1.0/docs/reference/core.md +140 -0
  55. undercurrent-0.1.0/docs/reference/index.md +270 -0
  56. undercurrent-0.1.0/docs/reference/model.md +52 -0
  57. undercurrent-0.1.0/docs/reference/router.md +102 -0
  58. undercurrent-0.1.0/docs/reference/sinks.md +86 -0
  59. undercurrent-0.1.0/docs/reference/spec.md +202 -0
  60. undercurrent-0.1.0/docs/stylesheets/brand.css +31 -0
  61. undercurrent-0.1.0/examples/content_safety/Dockerfile +73 -0
  62. undercurrent-0.1.0/examples/content_safety/README.md +36 -0
  63. undercurrent-0.1.0/examples/content_safety/content_safety_demo/__init__.py +45 -0
  64. undercurrent-0.1.0/examples/content_safety/content_safety_demo/custom_mlp.py +296 -0
  65. undercurrent-0.1.0/examples/content_safety/content_safety_demo/models.py +123 -0
  66. undercurrent-0.1.0/examples/content_safety/content_safety_demo/single_token.py +136 -0
  67. undercurrent-0.1.0/examples/content_safety/content_safety_demo/spec_binding.py +61 -0
  68. undercurrent-0.1.0/examples/content_safety/content_safety_demo/tensors.py +25 -0
  69. undercurrent-0.1.0/examples/content_safety/content_safety_demo/trajectory.py +144 -0
  70. undercurrent-0.1.0/examples/content_safety/content_safety_gpt2.yaml +28 -0
  71. undercurrent-0.1.0/examples/content_safety/content_safety_llama.yaml +31 -0
  72. undercurrent-0.1.0/examples/content_safety/custom_mlp_llama31_8b.yaml +26 -0
  73. undercurrent-0.1.0/examples/content_safety/make_dummy_mlp_checkpoint.py +65 -0
  74. undercurrent-0.1.0/examples/content_safety/run_vllm_pipeline.py +166 -0
  75. undercurrent-0.1.0/examples/function_probe.py +18 -0
  76. undercurrent-0.1.0/examples/openai_server/README.md +5 -0
  77. undercurrent-0.1.0/examples/openai_server/response_schema.py +404 -0
  78. undercurrent-0.1.0/examples/openai_server/serve_llama_mlp_pipeline.py +339 -0
  79. undercurrent-0.1.0/examples/quickstart.py +24 -0
  80. undercurrent-0.1.0/examples/specs/classic_single_token.yaml +13 -0
  81. undercurrent-0.1.0/examples/specs/continuous_trajectory.yaml +16 -0
  82. undercurrent-0.1.0/examples/specs/offset_dual_position.yaml +14 -0
  83. undercurrent-0.1.0/examples/specs/sliced_range.yaml +13 -0
  84. undercurrent-0.1.0/examples/train_probe/README.md +188 -0
  85. undercurrent-0.1.0/examples/train_probe/collector.py +79 -0
  86. undercurrent-0.1.0/examples/train_probe/data_sources.py +132 -0
  87. undercurrent-0.1.0/examples/train_probe/linear_probe.py +262 -0
  88. undercurrent-0.1.0/examples/train_probe/requirements.txt +3 -0
  89. undercurrent-0.1.0/examples/train_probe/train_probe.py +286 -0
  90. undercurrent-0.1.0/examples/train_probe/use_probe.py +19 -0
  91. undercurrent-0.1.0/notebooks/quickstart.ipynb +357 -0
  92. undercurrent-0.1.0/notebooks/train_probe.ipynb +415 -0
  93. undercurrent-0.1.0/pyproject.toml +297 -0
  94. undercurrent-0.1.0/schema/probe-spec.schema.json +481 -0
  95. undercurrent-0.1.0/scripts/generate_schema.py +34 -0
  96. undercurrent-0.1.0/scripts/gpu_check.sh +203 -0
  97. undercurrent-0.1.0/scripts/smoke_test_wheel.sh +452 -0
  98. undercurrent-0.1.0/src/undercurrent/__init__.py +191 -0
  99. undercurrent-0.1.0/src/undercurrent/__main__.py +8 -0
  100. undercurrent-0.1.0/src/undercurrent/_version.py +24 -0
  101. undercurrent-0.1.0/src/undercurrent/adapters/__init__.py +26 -0
  102. undercurrent-0.1.0/src/undercurrent/adapters/_optional.py +89 -0
  103. undercurrent-0.1.0/src/undercurrent/adapters/base.py +141 -0
  104. undercurrent-0.1.0/src/undercurrent/adapters/hf/__init__.py +38 -0
  105. undercurrent-0.1.0/src/undercurrent/adapters/hf/adapter.py +459 -0
  106. undercurrent-0.1.0/src/undercurrent/adapters/hf/errors.py +15 -0
  107. undercurrent-0.1.0/src/undercurrent/adapters/hf/introspect.py +107 -0
  108. undercurrent-0.1.0/src/undercurrent/adapters/hf/stopping_criteria.py +72 -0
  109. undercurrent-0.1.0/src/undercurrent/adapters/vllm/__init__.py +46 -0
  110. undercurrent-0.1.0/src/undercurrent/adapters/vllm/adapter.py +702 -0
  111. undercurrent-0.1.0/src/undercurrent/adapters/vllm/introspection.py +153 -0
  112. undercurrent-0.1.0/src/undercurrent/adapters/vllm/plugin.py +154 -0
  113. undercurrent-0.1.0/src/undercurrent/adapters/vllm/seq_mapper.py +236 -0
  114. undercurrent-0.1.0/src/undercurrent/adapters/vllm/support.py +89 -0
  115. undercurrent-0.1.0/src/undercurrent/adapters/vllm/version_check.py +100 -0
  116. undercurrent-0.1.0/src/undercurrent/adapters/vllm/worker_extension.py +773 -0
  117. undercurrent-0.1.0/src/undercurrent/cli/__init__.py +115 -0
  118. undercurrent-0.1.0/src/undercurrent/cli/_errors.py +13 -0
  119. undercurrent-0.1.0/src/undercurrent/cli/inspect_model.py +308 -0
  120. undercurrent-0.1.0/src/undercurrent/cli/schema.py +40 -0
  121. undercurrent-0.1.0/src/undercurrent/cli/validate.py +164 -0
  122. undercurrent-0.1.0/src/undercurrent/core/__init__.py +69 -0
  123. undercurrent-0.1.0/src/undercurrent/core/activation.py +11 -0
  124. undercurrent-0.1.0/src/undercurrent/core/context.py +29 -0
  125. undercurrent-0.1.0/src/undercurrent/core/examples/__init__.py +15 -0
  126. undercurrent-0.1.0/src/undercurrent/core/examples/_util.py +23 -0
  127. undercurrent-0.1.0/src/undercurrent/core/examples/mlp_classifier.py +73 -0
  128. undercurrent-0.1.0/src/undercurrent/core/examples/trajectory_score.py +102 -0
  129. undercurrent-0.1.0/src/undercurrent/core/function_probe.py +364 -0
  130. undercurrent-0.1.0/src/undercurrent/core/probe.py +207 -0
  131. undercurrent-0.1.0/src/undercurrent/core/registry.py +393 -0
  132. undercurrent-0.1.0/src/undercurrent/core/result.py +33 -0
  133. undercurrent-0.1.0/src/undercurrent/core/signal.py +41 -0
  134. undercurrent-0.1.0/src/undercurrent/errors.py +97 -0
  135. undercurrent-0.1.0/src/undercurrent/model.py +955 -0
  136. undercurrent-0.1.0/src/undercurrent/py.typed +0 -0
  137. undercurrent-0.1.0/src/undercurrent/router/__init__.py +85 -0
  138. undercurrent-0.1.0/src/undercurrent/router/binding.py +246 -0
  139. undercurrent-0.1.0/src/undercurrent/router/errors.py +14 -0
  140. undercurrent-0.1.0/src/undercurrent/router/metrics.py +233 -0
  141. undercurrent-0.1.0/src/undercurrent/router/overflow.py +163 -0
  142. undercurrent-0.1.0/src/undercurrent/router/request.py +134 -0
  143. undercurrent-0.1.0/src/undercurrent/router/router.py +888 -0
  144. undercurrent-0.1.0/src/undercurrent/sinks/__init__.py +55 -0
  145. undercurrent-0.1.0/src/undercurrent/sinks/file_sink.py +69 -0
  146. undercurrent-0.1.0/src/undercurrent/sinks/records.py +32 -0
  147. undercurrent-0.1.0/src/undercurrent/sinks/redaction.py +179 -0
  148. undercurrent-0.1.0/src/undercurrent/sinks/serialize.py +70 -0
  149. undercurrent-0.1.0/src/undercurrent/sinks/sink.py +97 -0
  150. undercurrent-0.1.0/src/undercurrent/sinks/webhook_sink.py +217 -0
  151. undercurrent-0.1.0/src/undercurrent/sinks/wiring.py +16 -0
  152. undercurrent-0.1.0/src/undercurrent/spec/__init__.py +80 -0
  153. undercurrent-0.1.0/src/undercurrent/spec/activation_record.py +86 -0
  154. undercurrent-0.1.0/src/undercurrent/spec/errors.py +41 -0
  155. undercurrent-0.1.0/src/undercurrent/spec/json_schema.py +248 -0
  156. undercurrent-0.1.0/src/undercurrent/spec/parser.py +476 -0
  157. undercurrent-0.1.0/src/undercurrent/spec/position.py +285 -0
  158. undercurrent-0.1.0/src/undercurrent/spec/resolved.py +223 -0
  159. undercurrent-0.1.0/src/undercurrent/spec/schema.py +379 -0
  160. undercurrent-0.1.0/src/undercurrent/spec/serialize.py +58 -0
  161. undercurrent-0.1.0/tests/__init__.py +0 -0
  162. undercurrent-0.1.0/tests/adapters/__init__.py +0 -0
  163. undercurrent-0.1.0/tests/adapters/hf/__init__.py +0 -0
  164. undercurrent-0.1.0/tests/adapters/hf/_helpers.py +86 -0
  165. undercurrent-0.1.0/tests/adapters/hf/conftest.py +55 -0
  166. undercurrent-0.1.0/tests/adapters/hf/test_adapter_contract.py +92 -0
  167. undercurrent-0.1.0/tests/adapters/hf/test_intervention_e2e.py +127 -0
  168. undercurrent-0.1.0/tests/adapters/hf/test_introspect.py +69 -0
  169. undercurrent-0.1.0/tests/adapters/test_optional.py +42 -0
  170. undercurrent-0.1.0/tests/adapters/vllm/__init__.py +0 -0
  171. undercurrent-0.1.0/tests/adapters/vllm/_helpers.py +88 -0
  172. undercurrent-0.1.0/tests/adapters/vllm/conftest.py +65 -0
  173. undercurrent-0.1.0/tests/adapters/vllm/test_adapter_contract.py +540 -0
  174. undercurrent-0.1.0/tests/adapters/vllm/test_adapter_shutdown.py +90 -0
  175. undercurrent-0.1.0/tests/adapters/vllm/test_integration_vllm.py +213 -0
  176. undercurrent-0.1.0/tests/adapters/vllm/test_plugin.py +90 -0
  177. undercurrent-0.1.0/tests/adapters/vllm/test_residual_stream_capture.py +354 -0
  178. undercurrent-0.1.0/tests/adapters/vllm/test_residual_stream_gpu.py +199 -0
  179. undercurrent-0.1.0/tests/adapters/vllm/test_seq_mapper.py +225 -0
  180. undercurrent-0.1.0/tests/adapters/vllm/test_support.py +54 -0
  181. undercurrent-0.1.0/tests/adapters/vllm/test_version_check.py +111 -0
  182. undercurrent-0.1.0/tests/adapters/vllm/test_worker_extension_bookkeeping.py +235 -0
  183. undercurrent-0.1.0/tests/adapters/vllm/test_worker_extension_capture.py +224 -0
  184. undercurrent-0.1.0/tests/benchmarks/__init__.py +0 -0
  185. undercurrent-0.1.0/tests/benchmarks/test_harness.py +353 -0
  186. undercurrent-0.1.0/tests/cli/__init__.py +0 -0
  187. undercurrent-0.1.0/tests/cli/conftest.py +35 -0
  188. undercurrent-0.1.0/tests/cli/test_inspect_model.py +162 -0
  189. undercurrent-0.1.0/tests/cli/test_main.py +76 -0
  190. undercurrent-0.1.0/tests/cli/test_schema.py +34 -0
  191. undercurrent-0.1.0/tests/cli/test_validate.py +238 -0
  192. undercurrent-0.1.0/tests/conftest.py +72 -0
  193. undercurrent-0.1.0/tests/core/__init__.py +0 -0
  194. undercurrent-0.1.0/tests/core/conftest.py +27 -0
  195. undercurrent-0.1.0/tests/core/test_function_probe.py +515 -0
  196. undercurrent-0.1.0/tests/core/test_probe_isolation.py +100 -0
  197. undercurrent-0.1.0/tests/core/test_probe_result_always_returned.py +38 -0
  198. undercurrent-0.1.0/tests/core/test_registry.py +347 -0
  199. undercurrent-0.1.0/tests/core/test_trajectory_score_probe.py +59 -0
  200. undercurrent-0.1.0/tests/docs/__init__.py +0 -0
  201. undercurrent-0.1.0/tests/docs/test_doc_snippets.py +40 -0
  202. undercurrent-0.1.0/tests/e2e/__init__.py +0 -0
  203. undercurrent-0.1.0/tests/e2e/test_hf_pipeline_cpu.py +199 -0
  204. undercurrent-0.1.0/tests/examples/__init__.py +0 -0
  205. undercurrent-0.1.0/tests/examples/content_safety/__init__.py +0 -0
  206. undercurrent-0.1.0/tests/examples/content_safety/conftest.py +37 -0
  207. undercurrent-0.1.0/tests/examples/content_safety/test_custom_mlp_probe.py +200 -0
  208. undercurrent-0.1.0/tests/examples/content_safety/test_isolation.py +62 -0
  209. undercurrent-0.1.0/tests/examples/content_safety/test_live_llama_integration.py +98 -0
  210. undercurrent-0.1.0/tests/examples/content_safety/test_single_token_probe.py +83 -0
  211. undercurrent-0.1.0/tests/examples/content_safety/test_spec_binding.py +112 -0
  212. undercurrent-0.1.0/tests/examples/content_safety/test_trajectory_probe.py +118 -0
  213. undercurrent-0.1.0/tests/examples/openai_server/__init__.py +0 -0
  214. undercurrent-0.1.0/tests/examples/openai_server/conftest.py +6 -0
  215. undercurrent-0.1.0/tests/examples/openai_server/test_response_schema.py +324 -0
  216. undercurrent-0.1.0/tests/examples/openai_server/test_server_cli.py +13 -0
  217. undercurrent-0.1.0/tests/examples/test_function_probe_example.py +24 -0
  218. undercurrent-0.1.0/tests/examples/train_probe/__init__.py +0 -0
  219. undercurrent-0.1.0/tests/examples/train_probe/conftest.py +6 -0
  220. undercurrent-0.1.0/tests/examples/train_probe/test_train_probe.py +331 -0
  221. undercurrent-0.1.0/tests/notebooks/__init__.py +0 -0
  222. undercurrent-0.1.0/tests/notebooks/test_notebooks.py +77 -0
  223. undercurrent-0.1.0/tests/router/__init__.py +0 -0
  224. undercurrent-0.1.0/tests/router/_helpers.py +210 -0
  225. undercurrent-0.1.0/tests/router/conftest.py +47 -0
  226. undercurrent-0.1.0/tests/router/test_context_managers.py +268 -0
  227. undercurrent-0.1.0/tests/router/test_dispatch.py +118 -0
  228. undercurrent-0.1.0/tests/router/test_end_request.py +95 -0
  229. undercurrent-0.1.0/tests/router/test_intervention.py +288 -0
  230. undercurrent-0.1.0/tests/router/test_isolation.py +82 -0
  231. undercurrent-0.1.0/tests/router/test_load.py +129 -0
  232. undercurrent-0.1.0/tests/router/test_metrics.py +294 -0
  233. undercurrent-0.1.0/tests/router/test_overflow.py +110 -0
  234. undercurrent-0.1.0/tests/router/test_probe_args.py +48 -0
  235. undercurrent-0.1.0/tests/router/test_probe_registry.py +122 -0
  236. undercurrent-0.1.0/tests/router/test_registration.py +111 -0
  237. undercurrent-0.1.0/tests/router/test_shutdown.py +129 -0
  238. undercurrent-0.1.0/tests/router/test_stress_overflow.py +177 -0
  239. undercurrent-0.1.0/tests/sinks/__init__.py +0 -0
  240. undercurrent-0.1.0/tests/sinks/_helpers.py +125 -0
  241. undercurrent-0.1.0/tests/sinks/conftest.py +28 -0
  242. undercurrent-0.1.0/tests/sinks/test_file_sink.py +105 -0
  243. undercurrent-0.1.0/tests/sinks/test_redaction.py +455 -0
  244. undercurrent-0.1.0/tests/sinks/test_router_integration.py +167 -0
  245. undercurrent-0.1.0/tests/sinks/test_webhook_sink.py +178 -0
  246. undercurrent-0.1.0/tests/spec/__init__.py +0 -0
  247. undercurrent-0.1.0/tests/spec/test_activation_record.py +116 -0
  248. undercurrent-0.1.0/tests/spec/test_intervention.py +117 -0
  249. undercurrent-0.1.0/tests/spec/test_json_schema.py +323 -0
  250. undercurrent-0.1.0/tests/spec/test_parser.py +171 -0
  251. undercurrent-0.1.0/tests/spec/test_position.py +140 -0
  252. undercurrent-0.1.0/tests/spec/test_roundtrip.py +100 -0
  253. undercurrent-0.1.0/tests/spec/test_vocab.py +250 -0
  254. undercurrent-0.1.0/tests/test_errors.py +571 -0
  255. undercurrent-0.1.0/tests/test_gpu_check.py +91 -0
  256. undercurrent-0.1.0/tests/test_import_light.py +17 -0
  257. undercurrent-0.1.0/tests/test_markers.py +18 -0
  258. undercurrent-0.1.0/tests/test_metadata.py +20 -0
  259. undercurrent-0.1.0/tests/test_model.py +642 -0
  260. undercurrent-0.1.0/tests/test_model_vllm.py +478 -0
  261. undercurrent-0.1.0/tests/test_public_api.py +397 -0
  262. undercurrent-0.1.0/tests/test_release_workflow.py +67 -0
  263. undercurrent-0.1.0/tests/test_version.py +11 -0
  264. undercurrent-0.0.0a0/PKG-INFO +0 -28
  265. undercurrent-0.0.0a0/README.md +0 -14
  266. undercurrent-0.0.0a0/pyproject.toml +0 -24
  267. undercurrent-0.0.0a0/src/undercurrent/__init__.py +0 -15
  268. {undercurrent-0.0.0a0 → undercurrent-0.1.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,185 @@
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.1.0] - 2026-10-02
22
+
23
+ The first public release of **Undercurrent**, Wrynx's activation-probing
24
+ platform for LLM inference. Earlier versions were internal development builds
25
+ and were never published.
26
+
27
+ Planned work that is not part of this release (an OpenAI-compatible
28
+ `undercurrent serve` server, Hugging Face Hub integration, a published
29
+ container image, and more) is tracked in the
30
+ [roadmap](https://github.com/wrynx/undercurrent/blob/main/ROADMAP.md).
31
+
32
+ ### Added
33
+
34
+ - **One installable package.** `pip install undercurrent` installs the
35
+ `undercurrent` package for Python 3.10+, with the Hugging Face
36
+ `transformers` backend in the base install. vLLM is never installed by
37
+ default. Tested on Python 3.10–3.13 with torch 2.1+ and transformers
38
+ 4.40–5.x; see [Compatibility](https://wrynx.github.io/undercurrent/compatibility/).
39
+ - **`ProbedModel`**, the high-level API (`undercurrent.ProbedModel`):
40
+ `ProbedModel.from_pretrained(model, spec=..., probes=...)`, then
41
+ `generate()`, which returns a `GenerationOutput` with the text and each
42
+ extraction point's `ProbeResult`. Backends `"hf"` (default) and `"vllm"`.
43
+ Pass `on_result=callback` for a simple result hook alongside the sink API.
44
+ - **Extraction-point specs** (`undercurrent.spec`). Declare what to capture
45
+ in YAML (`load_yaml_file`, `parse_yaml`, `parse_dict`) or construct
46
+ `ExtractionPoint`s in Python. Specs are validated by pydantic models and
47
+ round-trip back to YAML (`to_yaml`).
48
+ - Tensor types `residual_stream`, `attn_out`, `mlp_out` and `final_norm`,
49
+ selected per layer.
50
+ - A position-selector grammar: absolute indices (`5`), prompt and generated
51
+ indices (`prompt[-1]`, `generated[0]`), every generated token
52
+ (`generated[*]`), generated slices (`generated[5:]`, `generated[2:8]`),
53
+ and offsets (`prompt[-1]+1`). Malformed selectors raise
54
+ `PositionSyntaxError` with the accepted forms.
55
+ - `until` conditions for continuous selectors (`generation_end`,
56
+ `fixed_count`, `stop_token`).
57
+ - Example specs in `examples/specs/`.
58
+ - **Probe interface** (`undercurrent.core`). Probes implement the lifecycle
59
+ `spawn` → `on_start` → `on_activation`* → `on_end` and return
60
+ `ProbeSignal`s and a final `ProbeResult`. There are two probe kinds,
61
+ `single_shot` and `trajectory`. A fresh instance is spawned for each
62
+ request and extraction point, and probe classes that declare mutable
63
+ class-level attributes are rejected when the class is defined, so instances
64
+ can't share state across requests. Reference probes are in
65
+ `undercurrent.core.examples`: `MLPClassifierProbe` (single-shot) and
66
+ `TrajectoryScoreProbe` (trajectory).
67
+ - **Function probes and a probe registry.** Decorate a plain function
68
+ `(record) -> float | bool | ProbeSignal | None` with `@probe("name", ...)`
69
+ for stateless single-shot probes, and register probe classes by name with
70
+ `@register_probe("name")`. Third-party packages can expose probes through
71
+ the `undercurrent.probes` entry-point group.
72
+ - **Router** (`undercurrent.router`). Dispatches activation records to
73
+ per-request probe instances. Each extraction point picks an execution mode:
74
+ - `inline`: the probe runs on the generation path and its signal is
75
+ returned to the adapter, so it can abort generation.
76
+ - `async`: the probe runs out of band and doesn't block the response. Each
77
+ binding has a bounded queue (`DEFAULT_QUEUE_DEPTH`), served by a shared
78
+ worker pool, with a configurable `OverflowPolicy` (`drop_oldest`,
79
+ `drop_newest` or `block`).
80
+ - **Interventions.** An inline probe can return `ProbeAction.ABORT` to stop
81
+ generation. With the `block_until_signal` intervention policy, the router
82
+ waits up to `timeout_ms` for the probe, then applies `on_timeout`
83
+ (`continue` or `abort`). A circuit breaker downgrades an extraction point
84
+ to observe-only after `circuit_breaker_threshold` consecutive timeouts or
85
+ probe errors, so a failing probe can't keep stalling generation.
86
+ - **Metrics.** Per-binding queue depth, drops, activation latency and probe
87
+ errors go to a pluggable `MetricsSink`. `InMemoryMetricsRegistry` provides
88
+ snapshots.
89
+ - **Observation sinks** (`undercurrent.sinks`) for async probe output:
90
+ - `FileLogSink` writes NDJSON.
91
+ - `WebhookLogSink` POSTs JSON from a background thread, so a slow endpoint
92
+ never adds latency to the caller. Failed deliveries are retried with
93
+ exponential backoff, records that exhaust their retries are
94
+ dead-lettered to a file, and the send queue is bounded.
95
+ - A `LogSink` base class for writing your own sink.
96
+ - **Sink redaction.** `redact_keys()`, `drop_keys()` and `chain()` control
97
+ what a sink writes. `WebhookLogSink` redacts prompt text by default.
98
+ - **Router context managers.** `Router` and `router.request(...)` can be used
99
+ in `with` blocks so requests end and workers shut down cleanly.
100
+ - **Hugging Face adapter** (`undercurrent.adapters.hf`, in the base install).
101
+ `HFEngineAdapter` captures activations from `transformers` models with
102
+ forward hooks during `generate()`, and aborts generation through a
103
+ `StoppingCriteria` (`ProbingStoppingCriteria`).
104
+ - **vLLM adapter** (`undercurrent.adapters.vllm`). Install Undercurrent into
105
+ your existing vLLM environment or image (the recommended production path),
106
+ or use the `undercurrent[vllm]` extra for a vLLM from the tested range.
107
+ - Capture is aware of continuous batching: a worker extension installs the
108
+ hooks, and `SeqIdMapper` translates batch rows to
109
+ `(request_id, token_pos)`. Abort signals are wired back to the engine.
110
+ - A `vllm.general_plugins` entry point registers the worker extension.
111
+ - Constructing the adapter checks the installed vLLM version against the
112
+ supported range and raises `VLLMAdapterLimitationError` with the
113
+ installed version and the supported range. Set
114
+ `UNDERCURRENT_ALLOW_UNSUPPORTED_VLLM=1` to downgrade the error to a
115
+ warning. See
116
+ [docs/compatibility.md](https://github.com/wrynx/undercurrent/blob/main/docs/compatibility.md).
117
+ - Tensor and pipeline parallelism: results from every worker are merged,
118
+ and events that tensor-parallel ranks duplicate are deduplicated.
119
+ Multi-worker topologies are refused unless you pass
120
+ `allow_unsupported_executor=True`, because cross-rank ordering under
121
+ pipeline parallelism and Ray/distributed executors haven't been
122
+ validated. See the
123
+ [tensor and pipeline parallelism notes](https://wrynx.github.io/undercurrent/internals/vllm-parallelism/).
124
+ - **Command-line tool** `undercurrent`: `inspect-model` lists a model's
125
+ hookable layers and supported tensor types, `validate` checks spec files,
126
+ and `schema` prints the spec JSON Schema (also in
127
+ `schema/probe-spec.schema.json` and `undercurrent.spec.json_schema()`).
128
+ - **Curated top-level API.** The common names import from `undercurrent`
129
+ directly; see [API stability](https://wrynx.github.io/undercurrent/api-stability/)
130
+ for what is public. `load_spec()`
131
+ loads a spec from a path, YAML string or dict. The package ships `py.typed`.
132
+ - **Error hierarchy.** Errors Undercurrent raises on purpose derive from
133
+ `ProbingError` (and the matching built-in type), with messages that say
134
+ how to fix the problem.
135
+ - **Clear errors for missing backends.** Using an adapter whose backend isn't
136
+ installed raises `MissingDependencyError`, which says how to install it.
137
+ - **Examples** (in the repository, not installed with the package, and covered
138
+ by the test suite):
139
+ - [`examples/content_safety/`](https://github.com/wrynx/undercurrent/tree/main/examples/content_safety):
140
+ a **demo** content-safety probe in both `single_shot` and `trajectory`
141
+ variants, with YAML specs, a dummy-checkpoint maker, a vLLM pipeline
142
+ script and a Dockerfile. It is a demonstration of the probe API, not a
143
+ trained safety classifier.
144
+ - [`examples/openai_server/`](https://github.com/wrynx/undercurrent/tree/main/examples/openai_server):
145
+ a reference OpenAI-compatible wire format for probe verdicts (completion
146
+ bodies and SSE chunks) and a reference HTTP server built on it.
147
+ - **Probe-training example** (`examples/train_probe/`) and Colab notebooks
148
+ (`notebooks/quickstart.ipynb`, `notebooks/train_probe.ipynb`).
149
+
150
+ ### Fixed
151
+
152
+ - **vLLM `residual_stream` on fused-residual layers.** On Llama-family models
153
+ (and the other vLLM architectures whose decoder layer returns
154
+ `(hidden_states, residual)`), a `residual_stream` extraction point captured
155
+ the layer's MLP output. It now captures `hidden_states + residual`, the
156
+ residual stream after the layer, matching the HF backend. Decoder-layer
157
+ outputs the adapter can't classify raise `VLLMAdapterLimitationError`
158
+ instead of being captured silently. Verified on GPU (NVIDIA L4, vLLM 0.28.0)
159
+ by `tests/adapters/vllm/test_residual_stream_gpu.py`, which compares the
160
+ vLLM and HF captures token by token.
161
+
162
+ ### Known issues
163
+
164
+ See [Known issues (v0.1)](https://github.com/wrynx/undercurrent/blob/main/docs/compatibility.md#known-issues-v01)
165
+ for details and workarounds.
166
+
167
+ - **No GPU CI.** The GPU tests run by hand with `scripts/gpu_check.sh` before
168
+ each release. 0.1.0 passed on an NVIDIA L4 with vLLM 0.28.0, torch 2.13.0
169
+ and CUDA 13.0; other GPUs and multi-GPU topologies haven't been run.
170
+ - **Only vLLM 0.28 is supported** (`vllm>=0.28,<0.29`), and vLLM 0.30 is
171
+ already out. `UNDERCURRENT_ALLOW_UNSUPPORTED_VLLM=1` lets you try a newer
172
+ vLLM; the range widens only after a GPU validation run.
173
+
174
+ ### Security
175
+
176
+ - Vulnerabilities can be reported privately to security@wrynx.com or through
177
+ GitHub private vulnerability reporting. See
178
+ [SECURITY.md](https://github.com/wrynx/undercurrent/blob/main/SECURITY.md).
179
+ - The content-safety example loads classifier checkpoints only with
180
+ `torch.load(..., weights_only=True)`, accepts only plain tensor
181
+ `state_dict`s, and refuses to load on torch older than 2.6 (the first
182
+ release with a fix for CVE-2025-32434).
183
+
184
+ [Unreleased]: https://github.com/wrynx/undercurrent/compare/v0.1.0...HEAD
185
+ [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.1.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.17,>=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.1):** 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-v01).
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.
@@ -0,0 +1,172 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/wrynx/undercurrent/main/docs/assets/brand/undercurrent-logo-dark.png">
4
+ <img alt="Undercurrent" src="https://raw.githubusercontent.com/wrynx/undercurrent/main/docs/assets/brand/undercurrent-logo.png" width="440">
5
+ </picture>
6
+ </p>
7
+
8
+ <p align="center"><em>See the undercurrent before it surfaces.</em></p>
9
+
10
+ [![PyPI](https://img.shields.io/pypi/v/undercurrent)](https://pypi.org/project/undercurrent/)
11
+ [![Python versions](https://img.shields.io/pypi/pyversions/undercurrent)](https://pypi.org/project/undercurrent/)
12
+ [![CI](https://github.com/wrynx/undercurrent/actions/workflows/ci.yml/badge.svg)](https://github.com/wrynx/undercurrent/actions/workflows/ci.yml)
13
+ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/wrynx/undercurrent/blob/main/LICENSE)
14
+ [![Docs](https://img.shields.io/badge/docs-wrynx.github.io%2Fundercurrent-blue)](https://wrynx.github.io/undercurrent/)
15
+ [![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)
16
+
17
+ <sub>An open-source project by [Wrynx](https://github.com/wrynx).</sub>
18
+
19
+ Undercurrent is production-grade activation probing for live LLM inference.
20
+ It runs your probes on a model's internal activations *while it generates*,
21
+ so a probe can stop a generation mid-flight, or observe asynchronously
22
+ without adding latency to the response.
23
+
24
+ ## Why Undercurrent
25
+
26
+ - **Declarative YAML specs.** A few lines say which tensor to capture, at
27
+ which layers and token positions, and which probe receives it. No model
28
+ surgery, no forked serving code.
29
+ - **Inline abort mid-generation.** An inline probe runs on the generation path
30
+ and can stop a request before the next token is produced.
31
+ - **Production-ready async execution.** Observe-only probes run off the hot
32
+ path on bounded per-binding queues with overflow policies, and report to
33
+ metrics and to file or webhook sinks (with retries, dead-lettering and
34
+ redaction).
35
+ - **Runs where your model runs.** Inside your existing vLLM deployment, with
36
+ continuous batching (the vLLM adapter is experimental, see
37
+ [Deploy with vLLM](https://wrynx.github.io/undercurrent/guides/vllm-deployment/)),
38
+ and with Hugging Face Transformers on a laptop CPU.
39
+
40
+ > **Known issues (v0.1):** the vLLM adapter is experimental: its GPU tests
41
+ > pass on an NVIDIA L4 with vLLM 0.28.0, run by hand (there's no GPU CI yet),
42
+ > and only vLLM 0.28 is supported. Fixed in 0.1.0: on vLLM, `residual_stream`
43
+ > on Llama-family (fused-residual) models is captured as
44
+ > `hidden_states + residual`, the same residual stream as the HF backend
45
+ > (GPU-verified). See
46
+ > [Known issues](https://wrynx.github.io/undercurrent/compatibility/#known-issues-v01).
47
+
48
+ ## How it works
49
+
50
+ In short: an engine adapter (Hugging Face or vLLM) captures the activations your
51
+ YAML spec asks for, and the router hands them to your probes, which either
52
+ abort the generation (inline) or report to sinks and metrics (async).
53
+
54
+ ```mermaid
55
+ flowchart LR
56
+ spec["YAML spec"] --> adapter
57
+ subgraph engine["Inference engine (HF Transformers or vLLM)"]
58
+ model["LLM generating tokens"] -- forward hooks --> adapter["Engine adapter"]
59
+ end
60
+ adapter -- ActivationRecords --> router["Router"]
61
+ router -- inline --> inline["Inline probes"]
62
+ inline -- ABORT --> model
63
+ router -- async, bounded queues --> async["Async probes"]
64
+ async --> sinks["Sinks: file / webhook / metrics"]
65
+ ```
66
+
67
+ ## Install
68
+
69
+ ```bash
70
+ pip install undercurrent
71
+ ```
72
+
73
+ Python 3.10+. The Hugging Face Transformers backend is included; there are no
74
+ extras to choose. For vLLM, install Undercurrent into your existing vLLM
75
+ environment or image, or use `pip install "undercurrent[vllm]"` to get a vLLM
76
+ from the tested range (currently `vllm>=0.28,<0.29`). The adapter checks the
77
+ installed vLLM version at runtime; set `UNDERCURRENT_ALLOW_UNSUPPORTED_VLLM=1`
78
+ to try an untested vLLM anyway.
79
+
80
+ Supported versions, tested combinations and known issues are on the
81
+ [Compatibility](https://wrynx.github.io/undercurrent/compatibility/) page.
82
+
83
+ ## Quickstart
84
+
85
+ Attach a probe to GPT-2 and read its result. This runs on CPU:
86
+
87
+ ```python
88
+ from undercurrent.core import ActivationRecord, probe
89
+ from undercurrent.model import ProbedModel
90
+
91
+ SPEC = """
92
+ version: "1"
93
+ extraction_points:
94
+ - name: last_prompt_token
95
+ layers: 6
96
+ tensor_type: residual_stream
97
+ position: "prompt[-1]"
98
+ probe_type: norm_gate
99
+ probe_kind: single_shot
100
+ execution_mode: inline
101
+ """
102
+
103
+
104
+ @probe("norm_gate", threshold=500.0)
105
+ def norm_gate(record: ActivationRecord) -> float:
106
+ """L2 norm of the residual stream at this token."""
107
+ return float(record.tensor.norm())
108
+
109
+
110
+ with ProbedModel.from_pretrained("openai-community/gpt2", spec=SPEC, probes={"norm_gate": norm_gate}) as model:
111
+ out = model.generate("The quick brown fox", max_new_tokens=20, temperature=0)
112
+
113
+ print(repr(out.text))
114
+ print(out.probe_results["last_prompt_token"].verdict)
115
+ ```
116
+
117
+ A score at or above `threshold` would abort the generation. The
118
+ [Quickstart](https://wrynx.github.io/undercurrent/getting-started/quickstart/)
119
+ continues with a trajectory probe that stops a generation part-way through,
120
+ and an async probe that logs to a file without touching the response.
121
+
122
+ ## How it compares
123
+
124
+ | | Undercurrent | TransformerLens / pyvene / baukit | nnsight (+ NDIF) | Llama Guard / NeMo Guardrails |
125
+ |---|---|---|---|---|
126
+ | Purpose | Production activation probing | Interpretability research | Interpretability research, local or remote | Text-level safety classification and rails |
127
+ | Runs inside vLLM serving | partial (single worker validated) | ✗ | ✓ | n/a (separate model / wraps the LLM call) |
128
+ | 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) |
129
+ | Declarative spec | ✓ YAML | pyvene: partial (dict configs); others ✗ | ✗ | NeMo: ✓ (YAML + Colang) |
130
+ | License | Apache-2.0 | MIT / Apache-2.0 / MIT | MIT | Llama 4 Community License / Apache-2.0 |
131
+
132
+ Last reviewed: 2026-10-01. Footnotes, sources and when to pick something else:
133
+ [full comparison](https://wrynx.github.io/undercurrent/comparison/).
134
+
135
+ ## Documentation
136
+
137
+ - [Quickstart](https://wrynx.github.io/undercurrent/getting-started/quickstart/): your first probe on GPT-2, in five minutes.
138
+ - [Concepts](https://wrynx.github.io/undercurrent/concepts/architecture/): extraction points, position selectors, probes, inline vs async.
139
+ - [Guides](https://wrynx.github.io/undercurrent/guides/custom-probe/): write, test and package your own probe.
140
+ - [Production](https://wrynx.github.io/undercurrent/guides/vllm-deployment/): vLLM deployment, embedding the router, backpressure, sinks and metrics.
141
+ - [Examples](https://wrynx.github.io/undercurrent/examples/): complete, runnable projects.
142
+ - [API reference](https://wrynx.github.io/undercurrent/reference/): every public class and function, and the CLI.
143
+ - [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.
144
+ - [Colab notebook](https://colab.research.google.com/github/wrynx/undercurrent/blob/main/notebooks/quickstart.ipynb): the quickstart in your browser.
145
+
146
+ [`examples/content_safety/`](https://github.com/wrynx/undercurrent/tree/main/examples/content_safety)
147
+ is a demo of the wiring (probes, specs and a vLLM pipeline), not a safety
148
+ product. Its probes use random-initialised or dummy weights.
149
+
150
+ ## Project status
151
+
152
+ Undercurrent is on 0.x: the API may change between minor releases. The
153
+ [API stability policy](https://github.com/wrynx/undercurrent/blob/main/docs/api-stability.md)
154
+ says what is public, what is experimental, and how deprecations work. Planned
155
+ work is in the [roadmap](https://github.com/wrynx/undercurrent/blob/main/ROADMAP.md).
156
+
157
+ ## Contributing and community
158
+
159
+ - [Contributing guide](https://github.com/wrynx/undercurrent/blob/main/CONTRIBUTING.md)
160
+ - [Security policy](https://github.com/wrynx/undercurrent/blob/main/SECURITY.md): report vulnerabilities privately to security@wrynx.com.
161
+ - [Code of Conduct](https://github.com/wrynx/undercurrent/blob/main/CODE_OF_CONDUCT.md)
162
+
163
+ ## Citation
164
+
165
+ If you use Undercurrent in research, please cite it using
166
+ [`CITATION.cff`](https://github.com/wrynx/undercurrent/blob/main/CITATION.cff)
167
+ (GitHub's "Cite this repository" button reads it).
168
+
169
+ ## License
170
+
171
+ Apache-2.0. See [LICENSE](https://github.com/wrynx/undercurrent/blob/main/LICENSE)
172
+ and [NOTICE](https://github.com/wrynx/undercurrent/blob/main/NOTICE). Copyright Wrynx.