vantage-litellm-callback 0.0.3__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 (36) hide show
  1. vantage_litellm_callback-0.0.3/PKG-INFO +100 -0
  2. vantage_litellm_callback-0.0.3/README.md +91 -0
  3. vantage_litellm_callback-0.0.3/pyproject.toml +25 -0
  4. vantage_litellm_callback-0.0.3/setup.cfg +4 -0
  5. vantage_litellm_callback-0.0.3/tests/fixtures/metadata_tags.json +52 -0
  6. vantage_litellm_callback-0.0.3/tests/fixtures/nonstreaming/anthropic_calculate_usage.json +25 -0
  7. vantage_litellm_callback-0.0.3/tests/fixtures/nonstreaming/bedrock_converse.json +25 -0
  8. vantage_litellm_callback-0.0.3/tests/fixtures/nonstreaming/ocr_non_token.json +20 -0
  9. vantage_litellm_callback-0.0.3/tests/fixtures/nonstreaming/openai_chat_cache.json +19 -0
  10. vantage_litellm_callback-0.0.3/tests/fixtures/nonstreaming/openai_multimodal.json +18 -0
  11. vantage_litellm_callback-0.0.3/tests/fixtures/nonstreaming/openai_multimodal_cache.json +18 -0
  12. vantage_litellm_callback-0.0.3/tests/fixtures/nonstreaming/openai_zero_modality_cache.json +19 -0
  13. vantage_litellm_callback-0.0.3/tests/fixtures/nonstreaming/responses_api_cache.json +19 -0
  14. vantage_litellm_callback-0.0.3/tests/fixtures/nonstreaming/responses_api_cache_no_text_detail.json +18 -0
  15. vantage_litellm_callback-0.0.3/tests/fixtures/streaming/anthropic_messages.json +16 -0
  16. vantage_litellm_callback-0.0.3/tests/fixtures/streaming/bedrock_converse.json +32 -0
  17. vantage_litellm_callback-0.0.3/tests/fixtures/streaming/openai_chat.json +32 -0
  18. vantage_litellm_callback-0.0.3/tests/fixtures/streaming/responses_api.json +24 -0
  19. vantage_litellm_callback-0.0.3/tests/test_callback.py +1135 -0
  20. vantage_litellm_callback-0.0.3/tests/test_provider_matrix.py +247 -0
  21. vantage_litellm_callback-0.0.3/tests/test_provider_metadata.py +157 -0
  22. vantage_litellm_callback-0.0.3/vantage_callback.py +3 -0
  23. vantage_litellm_callback-0.0.3/vantage_litellm_callback/__init__.py +11 -0
  24. vantage_litellm_callback-0.0.3/vantage_litellm_callback/callback.py +361 -0
  25. vantage_litellm_callback-0.0.3/vantage_litellm_callback/client.py +185 -0
  26. vantage_litellm_callback-0.0.3/vantage_litellm_callback/delivery.py +357 -0
  27. vantage_litellm_callback-0.0.3/vantage_litellm_callback/projection.py +598 -0
  28. vantage_litellm_callback-0.0.3/vantage_litellm_callback/provider_metadata.py +173 -0
  29. vantage_litellm_callback-0.0.3/vantage_litellm_callback/schema.py +104 -0
  30. vantage_litellm_callback-0.0.3/vantage_litellm_callback.egg-info/PKG-INFO +100 -0
  31. vantage_litellm_callback-0.0.3/vantage_litellm_callback.egg-info/SOURCES.txt +34 -0
  32. vantage_litellm_callback-0.0.3/vantage_litellm_callback.egg-info/dependency_links.txt +1 -0
  33. vantage_litellm_callback-0.0.3/vantage_litellm_callback.egg-info/requires.txt +2 -0
  34. vantage_litellm_callback-0.0.3/vantage_litellm_callback.egg-info/scm_file_list.json +31 -0
  35. vantage_litellm_callback-0.0.3/vantage_litellm_callback.egg-info/scm_version.json +8 -0
  36. vantage_litellm_callback-0.0.3/vantage_litellm_callback.egg-info/top_level.txt +2 -0
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.4
2
+ Name: vantage-litellm-callback
3
+ Version: 0.0.3
4
+ Summary: Durable Vantage lifecycle callback for LiteLLM
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: litellm
8
+ Requires-Dist: pydantic>=2
9
+
10
+ # Vantage LiteLLM Callback
11
+
12
+ Install a released version with `pip install vantage-litellm-callback`. To work
13
+ from a source checkout, install this directory with `pip install .`. Configure
14
+ LiteLLM with:
15
+
16
+ ```yaml
17
+ litellm_settings:
18
+ callbacks:
19
+ - vantage_callback.callback_instance
20
+ ```
21
+
22
+ See the repository README for collector setup and lifecycle billing semantics.
23
+
24
+ The callback is fail-open: collector outages, acknowledgement failures, malformed
25
+ responses, queue pressure, and projection errors never block or fail a provider
26
+ request. Usage events are placed into a bounded in-process queue and delivered by
27
+ a background worker. When delivery is unhealthy, the worker opens a circuit,
28
+ aggregates bounded loss information, and periodically probes the collector with a
29
+ versioned `delivery_gap` control frame. Normal delivery resumes only after that
30
+ frame receives a durable acknowledgement.
31
+
32
+ Configuration:
33
+
34
+ - `VANTAGE_COLLECTOR_DELIVERY_QUEUE_SIZE` (default `1024`)
35
+ - `VANTAGE_COLLECTOR_RECOVERY_PROBE_SECONDS` (default `5`)
36
+ - `VANTAGE_COLLECTOR_GAP_EVENT_ID_SAMPLE_SIZE` (default `32`)
37
+ - `VANTAGE_COLLECTOR_GAP_SUMMARY_INTERVAL_SECONDS` (default `60`)
38
+ - `VANTAGE_COLLECTOR_SOCKET_PATH` (default
39
+ `/var/run/vantage-collector/collector.sock`)
40
+ - `VANTAGE_COLLECTOR_ACK_TIMEOUT_SECONDS` (default `5`)
41
+ - `VANTAGE_COLLECTOR_CONNECTION_POOL_SIZE` (default `16`)
42
+
43
+ The queue and gap aggregate are intentionally process-local and bounded. A
44
+ process exit may therefore lose queued events; the gap frame reports losses
45
+ observed while the process remains alive.
46
+
47
+ ## Running the provider matrix locally
48
+
49
+ `tests/test_provider_matrix.py` is a regression matrix over the provider and
50
+ mode combinations whose usage shapes disagree with one another. Every cell is a
51
+ recorded fixture, so the matrix needs no API keys and runs on every pull
52
+ request:
53
+
54
+ ```sh
55
+ pip install --editable . pytest pytest-asyncio
56
+ PYTHONPATH=. pytest tests/test_provider_matrix.py -v
57
+ ```
58
+
59
+ Run a single cell while iterating on a provider:
60
+
61
+ ```sh
62
+ PYTHONPATH=. pytest tests/test_provider_matrix.py -k anthropic
63
+ ```
64
+
65
+ Fixtures live in `tests/fixtures/`, one JSON file per cell:
66
+
67
+ | Directory | Cells |
68
+ | --- | --- |
69
+ | `nonstreaming/` | OpenAI chat + cache, OpenAI multimodal, Anthropic post-`calculate_usage()`, Bedrock converse, Responses API + cache (with and without a `text_tokens` detail), OCR / non-token |
70
+ | `streaming/` | OpenAI SSE, Anthropic `/v1/messages` SSE, Bedrock converse, Responses API `response.completed` |
71
+
72
+ Each file carries its `usage` payload, the `expected_usage` projection, the
73
+ `expected_billable_total`, and a `note` citing the LiteLLM transform the shape
74
+ was taken from. Adding a provider means adding a JSON file — the tests
75
+ parametrize over the directory, so a new file becomes a new cell with no test
76
+ changes.
77
+
78
+ The matrix asserts three things per cell: the four projected `Usage` fields,
79
+ that those fields are mutually exclusive (they must sum to the billable total,
80
+ so no token is counted twice), and that the callback emits exactly one `started`
81
+ and one terminal event. Two further tests assert that nothing the callback
82
+ attaches to a request reaches the outbound provider body.
83
+
84
+ Two token-semantics rules are what the matrix exists to protect, both of which
85
+ have regressed before:
86
+
87
+ - **The input count is gross wherever the cache is reported nested.** LiteLLM's
88
+ transforms fold cache reads and writes into `prompt_tokens`, and the Responses
89
+ API counts `input_tokens_details.cached_tokens` inside `input_tokens`. Those
90
+ buckets have to be subtracted or the cached tokens bill twice. Raw Anthropic
91
+ usage is the exception: it puts `cache_*_input_tokens` beside a *net*
92
+ `input_tokens`, so nothing is subtracted there.
93
+ - **`text_tokens` is not "uncached input".** It means the text modality for
94
+ OpenAI but the net raw input for Anthropic. Reading it verbatim silently drops
95
+ image, video, and audio input tokens, so uncached input is derived from the
96
+ gross count instead.
97
+
98
+ For an end-to-end check against a live proxy on `:4000`, `demo/send_requests.sh`
99
+ sends tagged requests through LiteLLM; see `demo/README.md`. That path needs a
100
+ running proxy and collector and is a manual smoke test, not part of CI.
@@ -0,0 +1,91 @@
1
+ # Vantage LiteLLM Callback
2
+
3
+ Install a released version with `pip install vantage-litellm-callback`. To work
4
+ from a source checkout, install this directory with `pip install .`. Configure
5
+ LiteLLM with:
6
+
7
+ ```yaml
8
+ litellm_settings:
9
+ callbacks:
10
+ - vantage_callback.callback_instance
11
+ ```
12
+
13
+ See the repository README for collector setup and lifecycle billing semantics.
14
+
15
+ The callback is fail-open: collector outages, acknowledgement failures, malformed
16
+ responses, queue pressure, and projection errors never block or fail a provider
17
+ request. Usage events are placed into a bounded in-process queue and delivered by
18
+ a background worker. When delivery is unhealthy, the worker opens a circuit,
19
+ aggregates bounded loss information, and periodically probes the collector with a
20
+ versioned `delivery_gap` control frame. Normal delivery resumes only after that
21
+ frame receives a durable acknowledgement.
22
+
23
+ Configuration:
24
+
25
+ - `VANTAGE_COLLECTOR_DELIVERY_QUEUE_SIZE` (default `1024`)
26
+ - `VANTAGE_COLLECTOR_RECOVERY_PROBE_SECONDS` (default `5`)
27
+ - `VANTAGE_COLLECTOR_GAP_EVENT_ID_SAMPLE_SIZE` (default `32`)
28
+ - `VANTAGE_COLLECTOR_GAP_SUMMARY_INTERVAL_SECONDS` (default `60`)
29
+ - `VANTAGE_COLLECTOR_SOCKET_PATH` (default
30
+ `/var/run/vantage-collector/collector.sock`)
31
+ - `VANTAGE_COLLECTOR_ACK_TIMEOUT_SECONDS` (default `5`)
32
+ - `VANTAGE_COLLECTOR_CONNECTION_POOL_SIZE` (default `16`)
33
+
34
+ The queue and gap aggregate are intentionally process-local and bounded. A
35
+ process exit may therefore lose queued events; the gap frame reports losses
36
+ observed while the process remains alive.
37
+
38
+ ## Running the provider matrix locally
39
+
40
+ `tests/test_provider_matrix.py` is a regression matrix over the provider and
41
+ mode combinations whose usage shapes disagree with one another. Every cell is a
42
+ recorded fixture, so the matrix needs no API keys and runs on every pull
43
+ request:
44
+
45
+ ```sh
46
+ pip install --editable . pytest pytest-asyncio
47
+ PYTHONPATH=. pytest tests/test_provider_matrix.py -v
48
+ ```
49
+
50
+ Run a single cell while iterating on a provider:
51
+
52
+ ```sh
53
+ PYTHONPATH=. pytest tests/test_provider_matrix.py -k anthropic
54
+ ```
55
+
56
+ Fixtures live in `tests/fixtures/`, one JSON file per cell:
57
+
58
+ | Directory | Cells |
59
+ | --- | --- |
60
+ | `nonstreaming/` | OpenAI chat + cache, OpenAI multimodal, Anthropic post-`calculate_usage()`, Bedrock converse, Responses API + cache (with and without a `text_tokens` detail), OCR / non-token |
61
+ | `streaming/` | OpenAI SSE, Anthropic `/v1/messages` SSE, Bedrock converse, Responses API `response.completed` |
62
+
63
+ Each file carries its `usage` payload, the `expected_usage` projection, the
64
+ `expected_billable_total`, and a `note` citing the LiteLLM transform the shape
65
+ was taken from. Adding a provider means adding a JSON file — the tests
66
+ parametrize over the directory, so a new file becomes a new cell with no test
67
+ changes.
68
+
69
+ The matrix asserts three things per cell: the four projected `Usage` fields,
70
+ that those fields are mutually exclusive (they must sum to the billable total,
71
+ so no token is counted twice), and that the callback emits exactly one `started`
72
+ and one terminal event. Two further tests assert that nothing the callback
73
+ attaches to a request reaches the outbound provider body.
74
+
75
+ Two token-semantics rules are what the matrix exists to protect, both of which
76
+ have regressed before:
77
+
78
+ - **The input count is gross wherever the cache is reported nested.** LiteLLM's
79
+ transforms fold cache reads and writes into `prompt_tokens`, and the Responses
80
+ API counts `input_tokens_details.cached_tokens` inside `input_tokens`. Those
81
+ buckets have to be subtracted or the cached tokens bill twice. Raw Anthropic
82
+ usage is the exception: it puts `cache_*_input_tokens` beside a *net*
83
+ `input_tokens`, so nothing is subtracted there.
84
+ - **`text_tokens` is not "uncached input".** It means the text modality for
85
+ OpenAI but the net raw input for Anthropic. Reading it verbatim silently drops
86
+ image, video, and audio input tokens, so uncached input is derived from the
87
+ gross count instead.
88
+
89
+ For an end-to-end check against a live proxy on `:4000`, `demo/send_requests.sh`
90
+ sends tagged requests through LiteLLM; see `demo/README.md`. That path needs a
91
+ running proxy and collector and is a manual smoke test, not part of CI.
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "setuptools-scm[simple]>=8"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "vantage-litellm-callback"
7
+ dynamic = ["version"]
8
+ description = "Durable Vantage lifecycle callback for LiteLLM"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "litellm",
13
+ "pydantic>=2",
14
+ ]
15
+
16
+ [tool.setuptools]
17
+ packages = ["vantage_litellm_callback"]
18
+ py-modules = ["vantage_callback"]
19
+
20
+ [tool.setuptools_scm]
21
+ root = ".."
22
+ local_scheme = "no-local-version"
23
+
24
+ [tool.pytest.ini_options]
25
+ asyncio_mode = "auto"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,52 @@
1
+ {
2
+ "request_data": {
3
+ "model": "logical-model",
4
+ "litellm_call_id": "request-metadata-tags",
5
+ "litellm_params": {
6
+ "model": "openai/gpt-4.1",
7
+ "custom_llm_provider": "openai",
8
+ "metadata": {
9
+ "tags": {
10
+ "team": "search",
11
+ "collision": "explicit-tag",
12
+ "enabled": true,
13
+ "weight": 1.5,
14
+ "nested_tag": {
15
+ "drop": "this"
16
+ }
17
+ },
18
+ "spend_logs_metadata": {
19
+ "environment": "production",
20
+ "collision": "spend-log",
21
+ "retries": 2,
22
+ "nested_spend_metadata": [
23
+ "drop",
24
+ "this"
25
+ ]
26
+ }
27
+ }
28
+ }
29
+ },
30
+ "collector_event": {
31
+ "event_id": "event-metadata-tags",
32
+ "request_id": "request-metadata-tags",
33
+ "timestamp": "2026-09-01T17:30:00Z",
34
+ "provider": "openai",
35
+ "model": "gpt-4.1",
36
+ "status": "success",
37
+ "usage": {
38
+ "non_cache_input_tokens": 0,
39
+ "output_tokens": 0,
40
+ "cache_read_input_tokens": 0,
41
+ "cache_write_input_tokens": 0
42
+ },
43
+ "tags": {
44
+ "team": "search",
45
+ "collision": "explicit-tag",
46
+ "enabled": true,
47
+ "weight": 1.5,
48
+ "environment": "production",
49
+ "retries": 2
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "provider": "anthropic",
3
+ "mode": "non-stream + cache",
4
+ "note": "Post-calculate_usage() Usage. LiteLLM inflates prompt_tokens to gross (raw input + cache read + cache creation); prompt_tokens_details.text_tokens is the net raw input. See litellm/llms/anthropic/chat/transformation.py:2189-2223.",
5
+ "usage": {
6
+ "prompt_tokens": 2000,
7
+ "completion_tokens": 150,
8
+ "total_tokens": 2150,
9
+ "prompt_tokens_details": {
10
+ "cached_tokens": 1500,
11
+ "text_tokens": 200,
12
+ "cache_write_tokens": 300,
13
+ "cache_creation_tokens": 300
14
+ },
15
+ "cache_creation_input_tokens": 300,
16
+ "cache_read_input_tokens": 1500
17
+ },
18
+ "expected_usage": {
19
+ "non_cache_input_tokens": 200,
20
+ "output_tokens": 150,
21
+ "cache_read_input_tokens": 1500,
22
+ "cache_write_input_tokens": 300
23
+ },
24
+ "expected_billable_total": 2150
25
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "provider": "bedrock",
3
+ "mode": "converse",
4
+ "note": "Post-transform Bedrock converse usage. Bedrock passes totalTokens through unchanged while prompt_tokens is inflated, so total_tokens (350) intentionally disagrees with prompt+completion. See litellm/llms/bedrock/chat/converse_transformation.py:1780-1790.",
5
+ "usage": {
6
+ "prompt_tokens": 2000,
7
+ "completion_tokens": 150,
8
+ "total_tokens": 350,
9
+ "prompt_tokens_details": {
10
+ "cached_tokens": 1500,
11
+ "text_tokens": 200,
12
+ "cache_write_tokens": 300,
13
+ "cache_creation_tokens": 300
14
+ },
15
+ "cache_creation_input_tokens": 300,
16
+ "cache_read_input_tokens": 1500
17
+ },
18
+ "expected_usage": {
19
+ "non_cache_input_tokens": 200,
20
+ "output_tokens": 150,
21
+ "cache_read_input_tokens": 1500,
22
+ "cache_write_input_tokens": 300
23
+ },
24
+ "expected_billable_total": 2150
25
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "provider": "mistral",
3
+ "mode": "ocr / non-token",
4
+ "note": "OCR spend-tracking usage carries page meters with zeroed token counts. The collector bills tokens only, so this must project to zeros rather than inventing usage. See litellm/proxy/spend_tracking/spend_tracking_utils.py:164.",
5
+ "usage": {
6
+ "prompt_tokens": 0,
7
+ "completion_tokens": 0,
8
+ "total_tokens": 0,
9
+ "pages_processed": 7,
10
+ "credits": null,
11
+ "doc_size_bytes": 284729
12
+ },
13
+ "expected_usage": {
14
+ "non_cache_input_tokens": 0,
15
+ "output_tokens": 0,
16
+ "cache_read_input_tokens": 0,
17
+ "cache_write_input_tokens": 0
18
+ },
19
+ "expected_billable_total": 0
20
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "provider": "openai",
3
+ "mode": "non-stream + cache",
4
+ "note": "OpenAI chat completions. prompt_tokens is gross; cached_tokens is a subset of it.",
5
+ "usage": {
6
+ "prompt_tokens": 100,
7
+ "completion_tokens": 20,
8
+ "total_tokens": 120,
9
+ "prompt_tokens_details": {"cached_tokens": 30},
10
+ "cache_creation_input_tokens": 10
11
+ },
12
+ "expected_usage": {
13
+ "non_cache_input_tokens": 60,
14
+ "output_tokens": 20,
15
+ "cache_read_input_tokens": 30,
16
+ "cache_write_input_tokens": 10
17
+ },
18
+ "expected_billable_total": 120
19
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "provider": "openai",
3
+ "mode": "non-stream multimodal",
4
+ "note": "Image input. prompt_tokens_details.text_tokens covers only the text modality, so billable input must include image_tokens rather than trusting text_tokens alone.",
5
+ "usage": {
6
+ "prompt_tokens": 100,
7
+ "completion_tokens": 20,
8
+ "total_tokens": 120,
9
+ "prompt_tokens_details": {"text_tokens": 40, "image_tokens": 60}
10
+ },
11
+ "expected_usage": {
12
+ "non_cache_input_tokens": 100,
13
+ "output_tokens": 20,
14
+ "cache_read_input_tokens": 0,
15
+ "cache_write_input_tokens": 0
16
+ },
17
+ "expected_billable_total": 120
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "provider": "openai",
3
+ "mode": "non-stream multimodal + cache",
4
+ "note": "Image input on a cached request. LiteLLM bills the prompt detail buckets additively (text + cache_hit + audio + image + video, see litellm/litellm_core_utils/llm_cost_calc/utils.py:652-676), so text_tokens is already net of cached_tokens and the modality counts must not have the cache subtracted from them again.",
5
+ "usage": {
6
+ "prompt_tokens": 100,
7
+ "completion_tokens": 20,
8
+ "total_tokens": 120,
9
+ "prompt_tokens_details": {"text_tokens": 40, "image_tokens": 30, "cached_tokens": 30}
10
+ },
11
+ "expected_usage": {
12
+ "non_cache_input_tokens": 70,
13
+ "output_tokens": 20,
14
+ "cache_read_input_tokens": 30,
15
+ "cache_write_input_tokens": 0
16
+ },
17
+ "expected_billable_total": 120
18
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "provider": "openai",
3
+ "mode": "non-stream text-only, zero modality detail",
4
+ "note": "A text-only cached request whose detail block still carries image_tokens: 0. Treating any present modality key as a multimodal breakdown projects the input as zero and under-bills by the whole uncached remainder, so only a positive modality count may select that path.",
5
+ "usage": {
6
+ "prompt_tokens": 100,
7
+ "completion_tokens": 20,
8
+ "total_tokens": 120,
9
+ "prompt_tokens_details": {"image_tokens": 0, "cached_tokens": 30},
10
+ "cache_creation_input_tokens": 10
11
+ },
12
+ "expected_usage": {
13
+ "non_cache_input_tokens": 60,
14
+ "output_tokens": 20,
15
+ "cache_read_input_tokens": 30,
16
+ "cache_write_input_tokens": 10
17
+ },
18
+ "expected_billable_total": 120
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "provider": "openai",
3
+ "mode": "responses api + cache",
4
+ "note": "Responses API usage. input_tokens is gross and cached_tokens is a subset of it, so cache must be subtracted or the same tokens bill twice. See litellm/types/llms/openai.py:1173-1186.",
5
+ "usage": {
6
+ "input_tokens": 2000,
7
+ "input_tokens_details": {"cached_tokens": 1792, "text_tokens": 208},
8
+ "output_tokens": 350,
9
+ "output_tokens_details": {"reasoning_tokens": 256, "text_tokens": 94},
10
+ "total_tokens": 2350
11
+ },
12
+ "expected_usage": {
13
+ "non_cache_input_tokens": 208,
14
+ "output_tokens": 350,
15
+ "cache_read_input_tokens": 1792,
16
+ "cache_write_input_tokens": 0
17
+ },
18
+ "expected_billable_total": 2350
19
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "provider": "openai",
3
+ "mode": "responses api + cache, no text detail",
4
+ "note": "InputTokensDetails.text_tokens is optional while cached_tokens defaults to 0, so a cached Responses API call can report only the cache subset. input_tokens stays gross, so the cache must still be subtracted; counting it on both sides billed 4142 tokens for this 2350-token request. See litellm/types/llms/openai.py:1173.",
5
+ "usage": {
6
+ "input_tokens": 2000,
7
+ "input_tokens_details": {"cached_tokens": 1792},
8
+ "output_tokens": 350,
9
+ "total_tokens": 2350
10
+ },
11
+ "expected_usage": {
12
+ "non_cache_input_tokens": 208,
13
+ "output_tokens": 350,
14
+ "cache_read_input_tokens": 1792,
15
+ "cache_write_input_tokens": 0
16
+ },
17
+ "expected_billable_total": 2350
18
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "chunks": [
3
+ "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"msg_01XYZ\",\"model\":\"claude-sonnet-4-20250514\",\"usage\":{\"input_tokens\":60,\"cache_read_input_tokens\":30,\"cache_creation_input_tokens\":10,\"output_tokens\":2}}}\n\n",
4
+ "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\n",
5
+ "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Hello\"}}\n\n",
6
+ "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\n",
7
+ "event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null},\"usage\":{\"output_tokens\":20}}\n\n",
8
+ "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
9
+ ],
10
+ "expected_usage": {
11
+ "non_cache_input_tokens": 60,
12
+ "output_tokens": 20,
13
+ "cache_read_input_tokens": 30,
14
+ "cache_write_input_tokens": 10
15
+ }
16
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "chunks": [
3
+ {
4
+ "id": "chatcmpl-bedrock-1",
5
+ "model": "anthropic.claude-sonnet-4-20250514-v1:0",
6
+ "choices": [{"delta": {"content": "Hello"}, "finish_reason": null}]
7
+ },
8
+ {
9
+ "id": "chatcmpl-bedrock-1",
10
+ "model": "anthropic.claude-sonnet-4-20250514-v1:0",
11
+ "choices": [{"delta": {}, "finish_reason": "stop"}],
12
+ "usage": {
13
+ "prompt_tokens": 100,
14
+ "completion_tokens": 20,
15
+ "total_tokens": 120,
16
+ "prompt_tokens_details": {
17
+ "text_tokens": 60,
18
+ "cached_tokens": 30,
19
+ "cache_creation_tokens": 10
20
+ },
21
+ "cache_read_input_tokens": 30,
22
+ "cache_creation_input_tokens": 10
23
+ }
24
+ }
25
+ ],
26
+ "expected_usage": {
27
+ "non_cache_input_tokens": 60,
28
+ "output_tokens": 20,
29
+ "cache_read_input_tokens": 30,
30
+ "cache_write_input_tokens": 10
31
+ }
32
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "chunks": [
3
+ {
4
+ "id": "chatcmpl-oai-1",
5
+ "model": "gpt-4.1",
6
+ "choices": [{"delta": {"content": "Hello"}, "finish_reason": null}]
7
+ },
8
+ {
9
+ "id": "chatcmpl-oai-1",
10
+ "model": "gpt-4.1",
11
+ "choices": [{"delta": {"content": " world"}, "finish_reason": null}]
12
+ },
13
+ {
14
+ "id": "chatcmpl-oai-1",
15
+ "model": "gpt-4.1",
16
+ "choices": [{"delta": {}, "finish_reason": "stop"}],
17
+ "usage": {
18
+ "prompt_tokens": 100,
19
+ "completion_tokens": 20,
20
+ "total_tokens": 120,
21
+ "prompt_tokens_details": {"cached_tokens": 30},
22
+ "cache_creation_input_tokens": 10
23
+ }
24
+ }
25
+ ],
26
+ "expected_usage": {
27
+ "non_cache_input_tokens": 60,
28
+ "output_tokens": 20,
29
+ "cache_read_input_tokens": 30,
30
+ "cache_write_input_tokens": 10
31
+ }
32
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "chunks": [
3
+ {
4
+ "type": "response.created",
5
+ "response": {"id": "resp_1", "model": "gpt-4.1", "status": "in_progress"}
6
+ },
7
+ {"type": "response.output_text.delta", "delta": "Hello"},
8
+ {
9
+ "type": "response.completed",
10
+ "response": {
11
+ "id": "resp_1",
12
+ "model": "gpt-4.1",
13
+ "status": "completed",
14
+ "usage": {"input_tokens": 9, "output_tokens": 4, "total_tokens": 13}
15
+ }
16
+ }
17
+ ],
18
+ "expected_usage": {
19
+ "non_cache_input_tokens": 9,
20
+ "output_tokens": 4,
21
+ "cache_read_input_tokens": 0,
22
+ "cache_write_input_tokens": 0
23
+ }
24
+ }