flightdeck-sensor 0.3.0__tar.gz → 0.5.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 (80) hide show
  1. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/Makefile +9 -5
  2. flightdeck_sensor-0.5.0/PKG-INFO +190 -0
  3. flightdeck_sensor-0.5.0/README.md +141 -0
  4. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/__init__.py +276 -40
  5. flightdeck_sensor-0.5.0/flightdeck_sensor/compat/__init__.py +16 -0
  6. flightdeck_sensor-0.5.0/flightdeck_sensor/compat/crewai_mcp.py +222 -0
  7. flightdeck_sensor-0.5.0/flightdeck_sensor/config.py +59 -0
  8. flightdeck_sensor-0.5.0/flightdeck_sensor/core/agent_id.py +85 -0
  9. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/core/context.py +39 -9
  10. flightdeck_sensor-0.5.0/flightdeck_sensor/core/errors.py +448 -0
  11. flightdeck_sensor-0.5.0/flightdeck_sensor/core/exceptions.py +79 -0
  12. flightdeck_sensor-0.5.0/flightdeck_sensor/core/mcp_policy.py +428 -0
  13. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/core/policy.py +68 -9
  14. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/core/schemas.py +14 -2
  15. flightdeck_sensor-0.5.0/flightdeck_sensor/core/session.py +1515 -0
  16. flightdeck_sensor-0.5.0/flightdeck_sensor/core/types.py +374 -0
  17. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/interceptor/anthropic.py +11 -16
  18. flightdeck_sensor-0.5.0/flightdeck_sensor/interceptor/base.py +1190 -0
  19. flightdeck_sensor-0.5.0/flightdeck_sensor/interceptor/crewai.py +335 -0
  20. flightdeck_sensor-0.5.0/flightdeck_sensor/interceptor/langgraph.py +331 -0
  21. flightdeck_sensor-0.5.0/flightdeck_sensor/interceptor/litellm.py +365 -0
  22. flightdeck_sensor-0.5.0/flightdeck_sensor/interceptor/mcp.py +1667 -0
  23. flightdeck_sensor-0.5.0/flightdeck_sensor/interceptor/mcp_identity.py +176 -0
  24. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/interceptor/openai.py +37 -17
  25. flightdeck_sensor-0.5.0/flightdeck_sensor/provider.py +56 -0
  26. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/providers/anthropic.py +104 -11
  27. flightdeck_sensor-0.5.0/flightdeck_sensor/providers/litellm.py +290 -0
  28. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/providers/openai.py +157 -12
  29. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/providers/protocol.py +51 -3
  30. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/transport/client.py +19 -6
  31. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/transport/retry.py +15 -4
  32. flightdeck_sensor-0.5.0/pyproject.toml +142 -0
  33. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/tests/conftest.py +1 -1
  34. flightdeck_sensor-0.5.0/tests/unit/test_agent_id.py +212 -0
  35. flightdeck_sensor-0.5.0/tests/unit/test_agent_type_validation.py +123 -0
  36. flightdeck_sensor-0.5.0/tests/unit/test_compat_crewai_mcp.py +307 -0
  37. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/tests/unit/test_context.py +4 -0
  38. flightdeck_sensor-0.5.0/tests/unit/test_cross_agent_messages.py +425 -0
  39. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/tests/unit/test_custom_directives.py +1 -1
  40. flightdeck_sensor-0.5.0/tests/unit/test_d150_tool_capture_destination.py +100 -0
  41. flightdeck_sensor-0.5.0/tests/unit/test_errors.py +411 -0
  42. flightdeck_sensor-0.5.0/tests/unit/test_framework_attribution.py +188 -0
  43. flightdeck_sensor-0.5.0/tests/unit/test_interceptor.py +571 -0
  44. flightdeck_sensor-0.5.0/tests/unit/test_interceptor_crewai.py +385 -0
  45. flightdeck_sensor-0.5.0/tests/unit/test_interceptor_langgraph.py +252 -0
  46. flightdeck_sensor-0.5.0/tests/unit/test_llm_event_enrichment.py +401 -0
  47. flightdeck_sensor-0.5.0/tests/unit/test_mcp_identity.py +153 -0
  48. flightdeck_sensor-0.5.0/tests/unit/test_mcp_interceptor.py +1118 -0
  49. flightdeck_sensor-0.5.0/tests/unit/test_mcp_policy_emission_enriched.py +165 -0
  50. flightdeck_sensor-0.5.0/tests/unit/test_mcp_policy_enforcement.py +717 -0
  51. flightdeck_sensor-0.5.0/tests/unit/test_mcp_policy_enforcement_all_paths.py +138 -0
  52. flightdeck_sensor-0.5.0/tests/unit/test_originating_event_id.py +123 -0
  53. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/tests/unit/test_patch.py +341 -16
  54. flightdeck_sensor-0.5.0/tests/unit/test_policy_decision_summary.py +106 -0
  55. flightdeck_sensor-0.5.0/tests/unit/test_policy_emission_enriched.py +159 -0
  56. flightdeck_sensor-0.5.0/tests/unit/test_policy_events.py +397 -0
  57. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/tests/unit/test_prompt_capture.py +125 -1
  58. flightdeck_sensor-0.5.0/tests/unit/test_provider_enum.py +251 -0
  59. flightdeck_sensor-0.5.0/tests/unit/test_providers.py +200 -0
  60. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/tests/unit/test_session.py +117 -24
  61. flightdeck_sensor-0.5.0/tests/unit/test_session_lifecycle_enrichment.py +130 -0
  62. flightdeck_sensor-0.3.0/PKG-INFO +0 -95
  63. flightdeck_sensor-0.3.0/README.md +0 -56
  64. flightdeck_sensor-0.3.0/flightdeck_sensor/core/exceptions.py +0 -34
  65. flightdeck_sensor-0.3.0/flightdeck_sensor/core/session.py +0 -749
  66. flightdeck_sensor-0.3.0/flightdeck_sensor/core/types.py +0 -156
  67. flightdeck_sensor-0.3.0/flightdeck_sensor/interceptor/base.py +0 -327
  68. flightdeck_sensor-0.3.0/pyproject.toml +0 -89
  69. flightdeck_sensor-0.3.0/tests/unit/test_interceptor.py +0 -179
  70. flightdeck_sensor-0.3.0/tests/unit/test_providers.py +0 -70
  71. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/.gitignore +0 -0
  72. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/core/__init__.py +0 -0
  73. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/interceptor/__init__.py +0 -0
  74. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/providers/__init__.py +0 -0
  75. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/py.typed +0 -0
  76. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/flightdeck_sensor/transport/__init__.py +0 -0
  77. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/tests/__init__.py +0 -0
  78. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/tests/unit/__init__.py +0 -0
  79. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/tests/unit/test_policy.py +0 -0
  80. {flightdeck_sensor-0.3.0 → flightdeck_sensor-0.5.0}/tests/unit/test_transport.py +0 -0
@@ -1,20 +1,24 @@
1
1
  .PHONY: help install build test lint clean
2
2
 
3
+ # Resolves through the project's pinned 3.12 venv by default. CI
4
+ # overrides via env: ``PYTHON=python make -C sensor test``.
5
+ PYTHON ?= ./.venv/bin/python
6
+
3
7
  help: ## Show this help
4
8
  @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-12s %s\n", $$1, $$2}'
5
9
 
6
10
  install: ## Install package in editable mode with dev dependencies
7
- pip install -e ".[dev,anthropic,openai]"
11
+ $(PYTHON) -m pip install -e ".[dev,anthropic,openai]"
8
12
 
9
13
  build: ## Build wheel and sdist
10
- python -m build
14
+ $(PYTHON) -m build
11
15
 
12
16
  test: ## Run unit tests
13
- pytest tests/unit/ -v
17
+ $(PYTHON) -m pytest tests/unit/ -v
14
18
 
15
19
  lint: ## Run mypy and ruff
16
- mypy flightdeck_sensor/ --strict
17
- ruff check flightdeck_sensor/
20
+ $(PYTHON) -m mypy flightdeck_sensor/ --strict
21
+ $(PYTHON) -m ruff check flightdeck_sensor/
18
22
 
19
23
  clean: ## Remove build artifacts
20
24
  rm -rf dist/ build/ *.egg-info
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.4
2
+ Name: flightdeck-sensor
3
+ Version: 0.5.0
4
+ Summary: In-process agent observability sensor for Flightdeck
5
+ License-Expression: Apache-2.0
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: License :: OSI Approved :: Apache Software License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: <3.14,>=3.10
16
+ Requires-Dist: pydantic>=2.0
17
+ Provides-Extra: anthropic
18
+ Requires-Dist: anthropic>=0.20; extra == 'anthropic'
19
+ Provides-Extra: dev
20
+ Requires-Dist: anthropic>=0.20; extra == 'dev'
21
+ Requires-Dist: crewai>=1.14; extra == 'dev'
22
+ Requires-Dist: httpx>=0.25; extra == 'dev'
23
+ Requires-Dist: langchain-anthropic<1,>=0.1; extra == 'dev'
24
+ Requires-Dist: langchain-mcp-adapters<0.2; extra == 'dev'
25
+ Requires-Dist: langchain-openai<1,>=0.1; extra == 'dev'
26
+ Requires-Dist: langchain<1,>=0.3; extra == 'dev'
27
+ Requires-Dist: langgraph<1; extra == 'dev'
28
+ Requires-Dist: litellm>=1.0; extra == 'dev'
29
+ Requires-Dist: llama-index-llms-anthropic>=0.1; extra == 'dev'
30
+ Requires-Dist: llama-index-llms-openai>=0.1; extra == 'dev'
31
+ Requires-Dist: llama-index-tools-mcp>=0.1; extra == 'dev'
32
+ Requires-Dist: mcp>=1.20; extra == 'dev'
33
+ Requires-Dist: mcpadapt>=0.0.6; extra == 'dev'
34
+ Requires-Dist: mypy>=1.8; extra == 'dev'
35
+ Requires-Dist: openai>=1.0; extra == 'dev'
36
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
38
+ Requires-Dist: pytest>=7.0; extra == 'dev'
39
+ Requires-Dist: respx>=0.20; extra == 'dev'
40
+ Requires-Dist: ruff>=0.3; extra == 'dev'
41
+ Provides-Extra: litellm
42
+ Requires-Dist: litellm>=1.0; extra == 'litellm'
43
+ Provides-Extra: mcp
44
+ Requires-Dist: mcp>=1.20; extra == 'mcp'
45
+ Provides-Extra: openai
46
+ Requires-Dist: openai>=1.0; extra == 'openai'
47
+ Requires-Dist: tiktoken>=0.5; extra == 'openai'
48
+ Description-Content-Type: text/markdown
49
+
50
+ # flightdeck-sensor
51
+
52
+ In-process agent observability sensor for [Flightdeck](https://github.com/flightdeckhq/flightdeck).
53
+
54
+ ## Local development venv
55
+
56
+ Flightdeck pins **Python 3.12** for local development. The project
57
+ bound is `3.10 ≤ x < 3.14` (pyproject `requires-python`); `sensor/.venv`
58
+ is the canonical interpreter for every Make target that runs Python.
59
+
60
+ Recreate from a fresh clone or after a `rm -rf sensor/.venv`:
61
+
62
+ ```bash
63
+ python3.12 -m venv sensor/.venv
64
+ ./sensor/.venv/bin/python -m pip install -e ".[dev,anthropic,openai]"
65
+ ```
66
+
67
+ `make -C sensor install` does the same once the venv exists. CI
68
+ overrides via env (`PYTHON=python make -C sensor test`) where
69
+ `actions/setup-python` already pinned the interpreter.
70
+
71
+ ## Optional `session_id` hint (D094)
72
+
73
+ By default `init()` auto-generates a fresh UUID every time the process
74
+ starts. Orchestrators that re-run the same logical workflow (Temporal,
75
+ Airflow, cron) can instead pass a stable identifier; if the backend
76
+ already has a row for that session, the new execution is attached to it
77
+ and appears as a continuation of the prior run in the fleet view.
78
+
79
+ Supply the hint via either the `session_id=` kwarg or the
80
+ `FLIGHTDECK_SESSION_ID` environment variable. The env var takes
81
+ precedence.
82
+
83
+ The value MUST parse as a canonical UUID (any version) -- the
84
+ sessions table column is UUID-typed. If you pass a non-UUID the
85
+ sensor logs a warning and falls back to auto-generating one.
86
+ Orchestrators that use string identifiers (Temporal workflow_id,
87
+ Airflow dag_run_id) should hash the identifier into a deterministic
88
+ UUID with `uuid.uuid5`.
89
+
90
+ ### Temporal workflow example
91
+
92
+ ```python
93
+ import uuid
94
+ import flightdeck_sensor as fd
95
+ from temporalio import workflow
96
+
97
+ # Pick any fixed namespace UUID for your deployment. The same
98
+ # workflow_id + namespace always produces the same session UUID,
99
+ # so re-runs of the same workflow all map to the same sessions row.
100
+ FLIGHTDECK_NS = uuid.UUID("00000000-0000-0000-0000-000000000001")
101
+
102
+ @workflow.defn
103
+ class MyWorkflow:
104
+ @workflow.run
105
+ async def run(self, input):
106
+ ctx = workflow.info()
107
+ fd.init(
108
+ server="http://flightdeck.internal/ingest",
109
+ token="ftd_...",
110
+ session_id=str(uuid.uuid5(FLIGHTDECK_NS, ctx.workflow_id)),
111
+ )
112
+ # If this workflow_id has run before, the backend attaches
113
+ # this execution to the existing session automatically; the
114
+ # sensor logs INFO on the first response that confirms it.
115
+ ...
116
+ ```
117
+
118
+ The sensor logs a single WARNING at `init()` time whenever a custom
119
+ `session_id` is in play so the behaviour is visible in operational
120
+ logs, and an INFO line on the first response where the backend
121
+ confirms attachment. See DECISIONS.md D094 and ARCHITECTURE.md
122
+ ("Session attachment flow") for the full protocol.
123
+
124
+ ## Framework support
125
+
126
+ `flightdeck_sensor.patch()` installs three class-level (or module-
127
+ level) interceptors process-wide. Every LLM call that flows through
128
+ a patched entry point emits a `pre_call` / `post_call` event pair
129
+ without any framework-specific wiring.
130
+
131
+ | Framework | Interceptor | Entry points |
132
+ | --- | --- | --- |
133
+ | Anthropic SDK (direct or via a framework that constructs `Anthropic()` internally — LangChain, LlamaIndex, CrewAI native) | `patch_anthropic_classes` | `Anthropic.messages.create` / `.stream`, async + sync, beta resources |
134
+ | OpenAI SDK (direct or via a framework that constructs `OpenAI()` internally — LangChain, LlamaIndex, CrewAI native) | `patch_openai_classes` | `OpenAI.chat.completions.create`, `.responses.create`, `.embeddings.create`, async + sync |
135
+ | litellm (router that aggregates many providers behind one function surface) | `patch_litellm_functions` | `litellm.completion`, `litellm.acompletion` |
136
+
137
+ ### litellm example (KI21)
138
+
139
+ litellm's Anthropic route uses raw httpx instead of the Anthropic
140
+ SDK, so it bypasses the SDK-class patches. The litellm interceptor
141
+ patches `litellm.completion` / `litellm.acompletion` directly to
142
+ close this gap. An Anthropic model string via litellm exercises the
143
+ bypass case:
144
+
145
+ ```python
146
+ import flightdeck_sensor
147
+ import litellm
148
+
149
+ flightdeck_sensor.init(
150
+ server="http://flightdeck.internal/ingest", token="ftd_...",
151
+ )
152
+ flightdeck_sensor.patch()
153
+
154
+ # After patch(), this call routes through the sensor's pre/post-call
155
+ # plumbing regardless of which underlying provider litellm picks for
156
+ # the model string.
157
+ response = litellm.completion(
158
+ model="claude-haiku-4-5-20251001",
159
+ messages=[{"role": "user", "content": "hi"}],
160
+ max_tokens=5,
161
+ )
162
+ ```
163
+
164
+ Install the optional dependency: `pip install flightdeck-sensor[litellm]`.
165
+
166
+ ### What the litellm interceptor catches, and what it doesn't
167
+
168
+ **Catches.** Direct callers of `litellm.completion(**kwargs)` and
169
+ `litellm.acompletion(**kwargs)` — the vast majority of litellm
170
+ integrations, including the default chat-completion surface exposed
171
+ by user code, Router, and frameworks that route through the public
172
+ API (CrewAI non-native flavors, langchain-community's litellm
173
+ adapter, etc.).
174
+
175
+ **Does NOT catch.**
176
+ - **Streaming.** `stream=True` raises `NotImplementedError` in v1
177
+ with a pointer to KI26 (the tracked follow-up). Use
178
+ `stream=False` or reconstruct the stream downstream of the call.
179
+ - **Lower-level litellm entry points.** Some integrations reach
180
+ past `completion` into `litellm.llms.custom_httpx.http_handler`
181
+ or other internal helpers directly. Those calls bypass the
182
+ module-level wrapper. A broader httpx-level interceptor was
183
+ considered during the KI21 scoping and deferred — if framework
184
+ reports surface calls that slip past the current coverage, file
185
+ a new issue.
186
+ - **Embeddings (`litellm.embedding` / `aembedding`).** Not wrapped
187
+ in v1. Out of scope; embeddings have no policy/budget surface
188
+ in the current session layer.
189
+ - **`litellm.text_completion`.** Legacy completion API, not
190
+ wrapped.
@@ -0,0 +1,141 @@
1
+ # flightdeck-sensor
2
+
3
+ In-process agent observability sensor for [Flightdeck](https://github.com/flightdeckhq/flightdeck).
4
+
5
+ ## Local development venv
6
+
7
+ Flightdeck pins **Python 3.12** for local development. The project
8
+ bound is `3.10 ≤ x < 3.14` (pyproject `requires-python`); `sensor/.venv`
9
+ is the canonical interpreter for every Make target that runs Python.
10
+
11
+ Recreate from a fresh clone or after a `rm -rf sensor/.venv`:
12
+
13
+ ```bash
14
+ python3.12 -m venv sensor/.venv
15
+ ./sensor/.venv/bin/python -m pip install -e ".[dev,anthropic,openai]"
16
+ ```
17
+
18
+ `make -C sensor install` does the same once the venv exists. CI
19
+ overrides via env (`PYTHON=python make -C sensor test`) where
20
+ `actions/setup-python` already pinned the interpreter.
21
+
22
+ ## Optional `session_id` hint (D094)
23
+
24
+ By default `init()` auto-generates a fresh UUID every time the process
25
+ starts. Orchestrators that re-run the same logical workflow (Temporal,
26
+ Airflow, cron) can instead pass a stable identifier; if the backend
27
+ already has a row for that session, the new execution is attached to it
28
+ and appears as a continuation of the prior run in the fleet view.
29
+
30
+ Supply the hint via either the `session_id=` kwarg or the
31
+ `FLIGHTDECK_SESSION_ID` environment variable. The env var takes
32
+ precedence.
33
+
34
+ The value MUST parse as a canonical UUID (any version) -- the
35
+ sessions table column is UUID-typed. If you pass a non-UUID the
36
+ sensor logs a warning and falls back to auto-generating one.
37
+ Orchestrators that use string identifiers (Temporal workflow_id,
38
+ Airflow dag_run_id) should hash the identifier into a deterministic
39
+ UUID with `uuid.uuid5`.
40
+
41
+ ### Temporal workflow example
42
+
43
+ ```python
44
+ import uuid
45
+ import flightdeck_sensor as fd
46
+ from temporalio import workflow
47
+
48
+ # Pick any fixed namespace UUID for your deployment. The same
49
+ # workflow_id + namespace always produces the same session UUID,
50
+ # so re-runs of the same workflow all map to the same sessions row.
51
+ FLIGHTDECK_NS = uuid.UUID("00000000-0000-0000-0000-000000000001")
52
+
53
+ @workflow.defn
54
+ class MyWorkflow:
55
+ @workflow.run
56
+ async def run(self, input):
57
+ ctx = workflow.info()
58
+ fd.init(
59
+ server="http://flightdeck.internal/ingest",
60
+ token="ftd_...",
61
+ session_id=str(uuid.uuid5(FLIGHTDECK_NS, ctx.workflow_id)),
62
+ )
63
+ # If this workflow_id has run before, the backend attaches
64
+ # this execution to the existing session automatically; the
65
+ # sensor logs INFO on the first response that confirms it.
66
+ ...
67
+ ```
68
+
69
+ The sensor logs a single WARNING at `init()` time whenever a custom
70
+ `session_id` is in play so the behaviour is visible in operational
71
+ logs, and an INFO line on the first response where the backend
72
+ confirms attachment. See DECISIONS.md D094 and ARCHITECTURE.md
73
+ ("Session attachment flow") for the full protocol.
74
+
75
+ ## Framework support
76
+
77
+ `flightdeck_sensor.patch()` installs three class-level (or module-
78
+ level) interceptors process-wide. Every LLM call that flows through
79
+ a patched entry point emits a `pre_call` / `post_call` event pair
80
+ without any framework-specific wiring.
81
+
82
+ | Framework | Interceptor | Entry points |
83
+ | --- | --- | --- |
84
+ | Anthropic SDK (direct or via a framework that constructs `Anthropic()` internally — LangChain, LlamaIndex, CrewAI native) | `patch_anthropic_classes` | `Anthropic.messages.create` / `.stream`, async + sync, beta resources |
85
+ | OpenAI SDK (direct or via a framework that constructs `OpenAI()` internally — LangChain, LlamaIndex, CrewAI native) | `patch_openai_classes` | `OpenAI.chat.completions.create`, `.responses.create`, `.embeddings.create`, async + sync |
86
+ | litellm (router that aggregates many providers behind one function surface) | `patch_litellm_functions` | `litellm.completion`, `litellm.acompletion` |
87
+
88
+ ### litellm example (KI21)
89
+
90
+ litellm's Anthropic route uses raw httpx instead of the Anthropic
91
+ SDK, so it bypasses the SDK-class patches. The litellm interceptor
92
+ patches `litellm.completion` / `litellm.acompletion` directly to
93
+ close this gap. An Anthropic model string via litellm exercises the
94
+ bypass case:
95
+
96
+ ```python
97
+ import flightdeck_sensor
98
+ import litellm
99
+
100
+ flightdeck_sensor.init(
101
+ server="http://flightdeck.internal/ingest", token="ftd_...",
102
+ )
103
+ flightdeck_sensor.patch()
104
+
105
+ # After patch(), this call routes through the sensor's pre/post-call
106
+ # plumbing regardless of which underlying provider litellm picks for
107
+ # the model string.
108
+ response = litellm.completion(
109
+ model="claude-haiku-4-5-20251001",
110
+ messages=[{"role": "user", "content": "hi"}],
111
+ max_tokens=5,
112
+ )
113
+ ```
114
+
115
+ Install the optional dependency: `pip install flightdeck-sensor[litellm]`.
116
+
117
+ ### What the litellm interceptor catches, and what it doesn't
118
+
119
+ **Catches.** Direct callers of `litellm.completion(**kwargs)` and
120
+ `litellm.acompletion(**kwargs)` — the vast majority of litellm
121
+ integrations, including the default chat-completion surface exposed
122
+ by user code, Router, and frameworks that route through the public
123
+ API (CrewAI non-native flavors, langchain-community's litellm
124
+ adapter, etc.).
125
+
126
+ **Does NOT catch.**
127
+ - **Streaming.** `stream=True` raises `NotImplementedError` in v1
128
+ with a pointer to KI26 (the tracked follow-up). Use
129
+ `stream=False` or reconstruct the stream downstream of the call.
130
+ - **Lower-level litellm entry points.** Some integrations reach
131
+ past `completion` into `litellm.llms.custom_httpx.http_handler`
132
+ or other internal helpers directly. Those calls bypass the
133
+ module-level wrapper. A broader httpx-level interceptor was
134
+ considered during the KI21 scoping and deferred — if framework
135
+ reports surface calls that slip past the current coverage, file
136
+ a new issue.
137
+ - **Embeddings (`litellm.embedding` / `aembedding`).** Not wrapped
138
+ in v1. Out of scope; embeddings have no policy/budget surface
139
+ in the current session layer.
140
+ - **`litellm.text_completion`.** Legacy completion API, not
141
+ wrapped.