anthropic-haystack 6.3.0__tar.gz → 7.0.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.
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/CHANGELOG.md +7 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/PKG-INFO +1 -1
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/src/haystack_integrations/components/generators/anthropic/chat/chat_generator.py +14 -18
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/src/haystack_integrations/components/generators/anthropic/chat/foundry_chat_generator.py +3 -4
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/src/haystack_integrations/components/generators/anthropic/chat/utils.py +16 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/src/haystack_integrations/components/generators/anthropic/chat/vertex_chat_generator.py +3 -2
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/src/haystack_integrations/token_counters/anthropic/token_counter.py +1 -2
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/tests/test_chat_generator.py +17 -12
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/tests/test_chat_generator_async.py +4 -2
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/tests/test_utils.py +47 -4
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/.gitignore +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/LICENSE.txt +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/README.md +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/example/documentation_rag_with_claude.py +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/example/prompt_caching.py +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/pydoc/config_docusaurus.yml +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/pyproject.toml +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/src/haystack_integrations/components/generators/anthropic/__init__.py +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/src/haystack_integrations/components/generators/anthropic/chat/__init__.py +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/src/haystack_integrations/components/generators/py.typed +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/src/haystack_integrations/token_counters/anthropic/__init__.py +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/src/haystack_integrations/token_counters/py.typed +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/tests/__init__.py +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/tests/conftest.py +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/tests/test_files/apple.jpg +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/tests/test_files/sample_pdf_3.pdf +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/tests/test_foundry_chat_generator.py +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/tests/test_token_counter.py +0 -0
- {anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/tests/test_vertex_chat_generator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: anthropic-haystack
|
|
3
|
-
Version:
|
|
3
|
+
Version: 7.0.0
|
|
4
4
|
Summary: An integration of Anthropic Claude models into the Haystack framework.
|
|
5
5
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/anthropic#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
|
|
@@ -19,7 +19,7 @@ from haystack.tools import (
|
|
|
19
19
|
flatten_tools_or_toolsets,
|
|
20
20
|
serialize_tools_or_toolset,
|
|
21
21
|
)
|
|
22
|
-
from haystack.utils.auth import Secret
|
|
22
|
+
from haystack.utils.auth import Secret
|
|
23
23
|
from haystack.utils.callable_serialization import deserialize_callable, serialize_callable
|
|
24
24
|
|
|
25
25
|
from anthropic import Anthropic, AsyncAnthropic
|
|
@@ -37,6 +37,7 @@ from .utils import (
|
|
|
37
37
|
_convert_messages_to_anthropic_format,
|
|
38
38
|
_extract_citations,
|
|
39
39
|
_has_server_tool_blocks,
|
|
40
|
+
_merge_streaming_usage,
|
|
40
41
|
_process_reasoning_contents,
|
|
41
42
|
)
|
|
42
43
|
|
|
@@ -278,7 +279,6 @@ class AnthropicChatGenerator:
|
|
|
278
279
|
:returns:
|
|
279
280
|
The deserialized component instance.
|
|
280
281
|
"""
|
|
281
|
-
deserialize_secrets_inplace(data["init_parameters"], keys=["api_key"])
|
|
282
282
|
deserialize_tools_or_toolset_inplace(data["init_parameters"], key="tools")
|
|
283
283
|
init_params = data.get("init_parameters", {})
|
|
284
284
|
serialized_callback_handler = init_params.get("streaming_callback")
|
|
@@ -415,15 +415,14 @@ class AnthropicChatGenerator:
|
|
|
415
415
|
model: str | None = None
|
|
416
416
|
tool_call_index = -1
|
|
417
417
|
in_server_tool_block = False
|
|
418
|
-
|
|
418
|
+
start_usage: dict[str, Any] = {}
|
|
419
419
|
component_info = ComponentInfo.from_component(self)
|
|
420
420
|
for chunk in response:
|
|
421
421
|
if chunk.type in ["message_start", "content_block_start", "content_block_delta", "message_delta"]:
|
|
422
|
-
# Extract model from message_start chunks
|
|
422
|
+
# Extract model and full usage from message_start chunks
|
|
423
423
|
if chunk.type == "message_start":
|
|
424
424
|
model = chunk.message.model
|
|
425
|
-
|
|
426
|
-
input_tokens = chunk.message.usage.input_tokens
|
|
425
|
+
start_usage = chunk.message.usage.model_dump()
|
|
427
426
|
|
|
428
427
|
if chunk.type == "content_block_start":
|
|
429
428
|
if chunk.content_block.type == "tool_use":
|
|
@@ -445,10 +444,9 @@ class AnthropicChatGenerator:
|
|
|
445
444
|
{"received_at": datetime.now(timezone.utc).isoformat(), "model": model},
|
|
446
445
|
)
|
|
447
446
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
completion.meta["usage"]["input_tokens"] = input_tokens
|
|
447
|
+
# `completion.meta["usage"]` is the usage of the last message_delta chunk
|
|
448
|
+
if usage := _merge_streaming_usage(start_usage=start_usage, delta_usage=completion.meta.get("usage")):
|
|
449
|
+
completion.meta["usage"] = usage
|
|
452
450
|
|
|
453
451
|
# keep the raw content so server-tool blocks can be replayed on later turns
|
|
454
452
|
if self.anthropic_server_tools:
|
|
@@ -495,7 +493,7 @@ class AnthropicChatGenerator:
|
|
|
495
493
|
model: str | None = None
|
|
496
494
|
tool_call_index = -1
|
|
497
495
|
in_server_tool_block = False
|
|
498
|
-
|
|
496
|
+
start_usage: dict[str, Any] = {}
|
|
499
497
|
component_info = ComponentInfo.from_component(self)
|
|
500
498
|
async for chunk in response:
|
|
501
499
|
if chunk.type in [
|
|
@@ -504,11 +502,10 @@ class AnthropicChatGenerator:
|
|
|
504
502
|
"content_block_delta",
|
|
505
503
|
"message_delta",
|
|
506
504
|
]:
|
|
507
|
-
# Extract model from message_start chunks
|
|
505
|
+
# Extract model and full usage from message_start chunks
|
|
508
506
|
if chunk.type == "message_start":
|
|
509
507
|
model = chunk.message.model
|
|
510
|
-
|
|
511
|
-
input_tokens = chunk.message.usage.input_tokens
|
|
508
|
+
start_usage = chunk.message.usage.model_dump()
|
|
512
509
|
|
|
513
510
|
if chunk.type == "content_block_start":
|
|
514
511
|
if chunk.content_block.type == "tool_use":
|
|
@@ -533,10 +530,9 @@ class AnthropicChatGenerator:
|
|
|
533
530
|
{"received_at": datetime.now(timezone.utc).isoformat(), "model": model},
|
|
534
531
|
)
|
|
535
532
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
completion.meta["usage"]["input_tokens"] = input_tokens
|
|
533
|
+
# `completion.meta["usage"]` is the usage of the last message_delta chunk
|
|
534
|
+
if usage := _merge_streaming_usage(start_usage=start_usage, delta_usage=completion.meta.get("usage")):
|
|
535
|
+
completion.meta["usage"] = usage
|
|
540
536
|
|
|
541
537
|
# keep the raw content so server-tool blocks can be replayed on later turns
|
|
542
538
|
if self.anthropic_server_tools:
|
|
@@ -18,7 +18,6 @@ from haystack.tools import (
|
|
|
18
18
|
from haystack.utils import (
|
|
19
19
|
Secret,
|
|
20
20
|
deserialize_callable,
|
|
21
|
-
deserialize_secrets_inplace,
|
|
22
21
|
serialize_callable,
|
|
23
22
|
)
|
|
24
23
|
|
|
@@ -68,8 +67,9 @@ class AnthropicFoundryChatGenerator(AnthropicChatGenerator):
|
|
|
68
67
|
>> focuses on enabling computers to understand, interpret, and generate human language. It involves developing
|
|
69
68
|
>> techniques and algorithms to analyze and process text or speech data, allowing machines to comprehend and
|
|
70
69
|
>> communicate in natural languages like English, Spanish, or Chinese.")],
|
|
71
|
-
>> _name=None, _meta={'model': 'claude-sonnet-4-5', 'index': 0, 'finish_reason': '
|
|
72
|
-
>> 'usage': {'
|
|
70
|
+
>> _name=None, _meta={'model': 'claude-sonnet-4-5', 'index': 0, 'finish_reason': 'stop',
|
|
71
|
+
>> 'usage': {'prompt_tokens': 15, 'completion_tokens': 64, 'cache_read_input_tokens': 0,
|
|
72
|
+
>> 'cache_creation_input_tokens': 0, ...}})]}
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
For more details on supported models and their capabilities, refer to the Anthropic
|
|
@@ -258,7 +258,6 @@ class AnthropicFoundryChatGenerator(AnthropicChatGenerator):
|
|
|
258
258
|
The deserialized component instance.
|
|
259
259
|
"""
|
|
260
260
|
deserialize_tools_or_toolset_inplace(data["init_parameters"], key="tools")
|
|
261
|
-
deserialize_secrets_inplace(data["init_parameters"], keys=["api_key"])
|
|
262
261
|
|
|
263
262
|
init_params = data.get("init_parameters", {})
|
|
264
263
|
if serialized_callback := init_params.get("streaming_callback"):
|
|
@@ -387,6 +387,22 @@ def _get_openai_compatible_usage(response_dict: dict) -> dict:
|
|
|
387
387
|
return usage
|
|
388
388
|
|
|
389
389
|
|
|
390
|
+
def _merge_streaming_usage(start_usage: dict[str, Any], delta_usage: dict[str, Any] | None) -> dict[str, Any]:
|
|
391
|
+
"""
|
|
392
|
+
Combines the usage of a streamed response into the format a non-streamed response reports.
|
|
393
|
+
|
|
394
|
+
The `message_start` event carries the full `Usage`, including the `cache_creation` split by TTL and the
|
|
395
|
+
`service_tier`. The `message_delta` event carries the final cumulative counts, but only a subset of the fields.
|
|
396
|
+
|
|
397
|
+
:param start_usage: The usage of the `message_start` event.
|
|
398
|
+
:param delta_usage: The usage of the last `message_delta` event, or `None` if the stream had none.
|
|
399
|
+
:returns: The `message_start` usage with every non-null `message_delta` count written over it, converted with
|
|
400
|
+
`_get_openai_compatible_usage`.
|
|
401
|
+
"""
|
|
402
|
+
usage = {**start_usage, **{key: value for key, value in (delta_usage or {}).items() if value is not None}}
|
|
403
|
+
return _get_openai_compatible_usage({"usage": usage})
|
|
404
|
+
|
|
405
|
+
|
|
390
406
|
def _convert_chat_completion_to_chat_message(
|
|
391
407
|
anthropic_response: Any, ignore_tools_thinking_messages: bool
|
|
392
408
|
) -> ChatMessage:
|
|
@@ -57,8 +57,9 @@ class AnthropicVertexChatGenerator(AnthropicChatGenerator):
|
|
|
57
57
|
>> focuses on enabling computers to understand, interpret, and generate human language. It involves developing
|
|
58
58
|
>> techniques and algorithms to analyze and process text or speech data, allowing machines to comprehend and
|
|
59
59
|
>> communicate in natural languages like English, Spanish, or Chinese.")],
|
|
60
|
-
>> _name=None, _meta={'model': 'claude-sonnet-4-5@20250929', 'index': 0, 'finish_reason': '
|
|
61
|
-
>> 'usage': {'
|
|
60
|
+
>> _name=None, _meta={'model': 'claude-sonnet-4-5@20250929', 'index': 0, 'finish_reason': 'stop',
|
|
61
|
+
>> 'usage': {'prompt_tokens': 15, 'completion_tokens': 64, 'cache_read_input_tokens': 0,
|
|
62
|
+
>> 'cache_creation_input_tokens': 0, ...}})]}
|
|
62
63
|
```
|
|
63
64
|
|
|
64
65
|
For more details on supported models and their capabilities, refer to the Anthropic
|
|
@@ -7,7 +7,7 @@ from typing import Any
|
|
|
7
7
|
from haystack import default_from_dict, default_to_dict
|
|
8
8
|
from haystack.dataclasses import ChatMessage
|
|
9
9
|
from haystack.tools import ToolsType, flatten_tools_or_toolsets
|
|
10
|
-
from haystack.utils.auth import Secret
|
|
10
|
+
from haystack.utils.auth import Secret
|
|
11
11
|
|
|
12
12
|
from anthropic import Anthropic
|
|
13
13
|
from anthropic.types import ToolParam
|
|
@@ -132,5 +132,4 @@ class AnthropicTokenCounter:
|
|
|
132
132
|
:param data: The dictionary to deserialize from.
|
|
133
133
|
:returns: The deserialized token counter.
|
|
134
134
|
"""
|
|
135
|
-
deserialize_secrets_inplace(data["init_parameters"], keys=["api_key"])
|
|
136
135
|
return default_from_dict(cls, data)
|
|
@@ -901,12 +901,14 @@ class TestIntegration:
|
|
|
901
901
|
assert "Paris" in message.text
|
|
902
902
|
assert "claude-sonnet-4-5" in message.meta["model"]
|
|
903
903
|
assert message.meta["finish_reason"] == "stop"
|
|
904
|
+
assert "prompt_tokens" in message.meta["usage"]
|
|
905
|
+
assert "completion_tokens" in message.meta["usage"]
|
|
906
|
+
# only the message_start event carries `service_tier` when streaming
|
|
907
|
+
assert message.meta["usage"]["service_tier"] is not None
|
|
904
908
|
|
|
905
909
|
if streaming:
|
|
906
910
|
assert callback.counter > 1
|
|
907
911
|
assert "Paris" in callback.responses
|
|
908
|
-
assert "input_tokens" in message.meta["usage"]
|
|
909
|
-
assert "output_tokens" in message.meta["usage"]
|
|
910
912
|
|
|
911
913
|
def test_live_run_wrong_model(self, chat_messages):
|
|
912
914
|
component = AnthropicChatGenerator(model="something-obviously-wrong")
|
|
@@ -938,11 +940,8 @@ class TestIntegration:
|
|
|
938
940
|
assert tool_call.tool_name == "weather"
|
|
939
941
|
assert tool_call.arguments == {"city": "Paris"}
|
|
940
942
|
assert message.meta["finish_reason"] == "tool_calls"
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
assert "input_tokens" in message.meta["usage"]
|
|
944
|
-
else:
|
|
945
|
-
assert "completion_tokens" in message.meta["usage"]
|
|
943
|
+
assert "prompt_tokens" in message.meta["usage"]
|
|
944
|
+
assert "completion_tokens" in message.meta["usage"]
|
|
946
945
|
|
|
947
946
|
new_messages = [
|
|
948
947
|
*initial_messages,
|
|
@@ -1195,8 +1194,8 @@ class TestIntegration:
|
|
|
1195
1194
|
assert tool_call.tool_name == "weather"
|
|
1196
1195
|
assert tool_call.arguments == {"city": "Paris"}
|
|
1197
1196
|
assert message.meta["finish_reason"] == "tool_calls"
|
|
1198
|
-
assert "
|
|
1199
|
-
assert "
|
|
1197
|
+
assert "completion_tokens" in message.meta["usage"]
|
|
1198
|
+
assert "prompt_tokens" in message.meta["usage"]
|
|
1200
1199
|
|
|
1201
1200
|
new_messages = [
|
|
1202
1201
|
*initial_messages,
|
|
@@ -1210,16 +1209,20 @@ class TestIntegration:
|
|
|
1210
1209
|
assert len(final_message.text) > 0
|
|
1211
1210
|
assert "paris" in final_message.text.lower()
|
|
1212
1211
|
|
|
1212
|
+
@pytest.mark.parametrize("streaming", [False, True])
|
|
1213
1213
|
@pytest.mark.parametrize("cache_enabled", [True, False])
|
|
1214
1214
|
@pytest.mark.parametrize("cache_location", ["system", "user"])
|
|
1215
|
-
def test_prompt_caching_live_run(self, cache_enabled, cache_location):
|
|
1215
|
+
def test_prompt_caching_live_run(self, cache_enabled, cache_location, streaming):
|
|
1216
1216
|
"""
|
|
1217
|
-
Prompt caching works whether the cache_control marker sits on the system or the user message
|
|
1217
|
+
Prompt caching works whether the cache_control marker sits on the system or the user message, with and
|
|
1218
|
+
without streaming.
|
|
1218
1219
|
"""
|
|
1219
1220
|
generation_kwargs = {"extra_headers": {"anthropic-beta": "prompt-caching-2024-07-31"}} if cache_enabled else {}
|
|
1220
1221
|
|
|
1221
1222
|
claude_llm = AnthropicChatGenerator(
|
|
1222
|
-
api_key=Secret.from_env_var("ANTHROPIC_API_KEY"),
|
|
1223
|
+
api_key=Secret.from_env_var("ANTHROPIC_API_KEY"),
|
|
1224
|
+
generation_kwargs=generation_kwargs,
|
|
1225
|
+
streaming_callback=print_streaming_chunk if streaming else None,
|
|
1223
1226
|
)
|
|
1224
1227
|
|
|
1225
1228
|
# see https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching#cache-limitations
|
|
@@ -1247,6 +1250,8 @@ class TestIntegration:
|
|
|
1247
1250
|
token_usage.get("cache_creation_input_tokens", 0) > 1024
|
|
1248
1251
|
or token_usage.get("cache_read_input_tokens", 0) > 1024
|
|
1249
1252
|
), f"Unexpected usage stats: {token_usage}"
|
|
1253
|
+
# the split of cache writes by TTL, which only the message_start event carries when streaming
|
|
1254
|
+
assert token_usage["cache_creation"] is not None
|
|
1250
1255
|
else:
|
|
1251
1256
|
assert token_usage.get("cache_creation_input_tokens", 0) == 0
|
|
1252
1257
|
assert token_usage.get("cache_read_input_tokens", 0) == 0
|
|
@@ -132,8 +132,10 @@ class TestIntegration:
|
|
|
132
132
|
assert "paris" in message.text.lower()
|
|
133
133
|
assert "claude-sonnet-4-5" in message.meta["model"]
|
|
134
134
|
assert message.meta["finish_reason"] == "stop"
|
|
135
|
-
assert "
|
|
136
|
-
assert "
|
|
135
|
+
assert "prompt_tokens" in message.meta["usage"]
|
|
136
|
+
assert "completion_tokens" in message.meta["usage"]
|
|
137
|
+
# only the message_start event carries `service_tier` when streaming
|
|
138
|
+
assert message.meta["usage"]["service_tier"] is not None
|
|
137
139
|
|
|
138
140
|
# Verify streaming behavior
|
|
139
141
|
assert counter > 1 # Should have received multiple chunks
|
|
@@ -4,6 +4,7 @@ from dataclasses import replace
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
from anthropic.types import (
|
|
7
|
+
CacheCreation,
|
|
7
8
|
InputJSONDelta,
|
|
8
9
|
Message,
|
|
9
10
|
MessageDeltaUsage,
|
|
@@ -52,6 +53,7 @@ from haystack_integrations.components.generators.anthropic.chat.utils import (
|
|
|
52
53
|
_finalize_reasoning_group,
|
|
53
54
|
_get_openai_compatible_usage,
|
|
54
55
|
_has_server_tool_blocks,
|
|
56
|
+
_merge_streaming_usage,
|
|
55
57
|
)
|
|
56
58
|
|
|
57
59
|
CITATION = {
|
|
@@ -627,11 +629,14 @@ class TestConvertAnthropicChunkToStreamingChunk:
|
|
|
627
629
|
message = generator._process_response(raw_chunks)
|
|
628
630
|
usage = message["replies"][0].meta["usage"]
|
|
629
631
|
assert isinstance(usage, dict)
|
|
630
|
-
assert usage["cache_creation_input_tokens"]
|
|
631
|
-
assert usage["cache_read_input_tokens"]
|
|
632
|
-
assert usage["
|
|
633
|
-
assert usage["
|
|
632
|
+
assert usage["cache_creation_input_tokens"] == 0
|
|
633
|
+
assert usage["cache_read_input_tokens"] == 0
|
|
634
|
+
assert usage["prompt_tokens"] == 393
|
|
635
|
+
assert usage["completion_tokens"] == 77
|
|
634
636
|
assert usage["server_tool_use"] is None
|
|
637
|
+
assert usage["service_tier"] == "standard"
|
|
638
|
+
assert "input_tokens" not in usage
|
|
639
|
+
assert "output_tokens" not in usage
|
|
635
640
|
|
|
636
641
|
def test_accumulate_raw_content_blocks_from_stream(self):
|
|
637
642
|
component_info = ComponentInfo(name="test", type="test")
|
|
@@ -1568,3 +1573,41 @@ class TestOpenAICompatibleUsage:
|
|
|
1568
1573
|
@pytest.mark.parametrize("response_dict", [{}, {"usage": {}}], ids=["no_usage_key", "empty_usage"])
|
|
1569
1574
|
def test_empty_usage_is_unchanged(self, response_dict):
|
|
1570
1575
|
assert _get_openai_compatible_usage(response_dict) == {}
|
|
1576
|
+
|
|
1577
|
+
|
|
1578
|
+
class TestMergeStreamingUsage:
|
|
1579
|
+
def test_cache_creation_split_is_kept(self):
|
|
1580
|
+
"""`message_delta` usage has no `cache_creation`, so the split must come from `message_start`."""
|
|
1581
|
+
start_usage = Usage(
|
|
1582
|
+
input_tokens=10,
|
|
1583
|
+
output_tokens=1,
|
|
1584
|
+
cache_read_input_tokens=2000,
|
|
1585
|
+
cache_creation_input_tokens=1500,
|
|
1586
|
+
cache_creation=CacheCreation(ephemeral_5m_input_tokens=500, ephemeral_1h_input_tokens=1000),
|
|
1587
|
+
service_tier="standard",
|
|
1588
|
+
).model_dump()
|
|
1589
|
+
delta_usage = MessageDeltaUsage(
|
|
1590
|
+
input_tokens=10, output_tokens=77, cache_read_input_tokens=2000, cache_creation_input_tokens=1500
|
|
1591
|
+
).model_dump()
|
|
1592
|
+
|
|
1593
|
+
usage = _merge_streaming_usage(start_usage=start_usage, delta_usage=delta_usage)
|
|
1594
|
+
|
|
1595
|
+
assert usage["prompt_tokens"] == 3510
|
|
1596
|
+
assert usage["completion_tokens"] == 77
|
|
1597
|
+
assert usage["cache_read_input_tokens"] == 2000
|
|
1598
|
+
assert usage["cache_creation_input_tokens"] == 1500
|
|
1599
|
+
assert usage["cache_creation"] == {"ephemeral_5m_input_tokens": 500, "ephemeral_1h_input_tokens": 1000}
|
|
1600
|
+
assert usage["service_tier"] == "standard"
|
|
1601
|
+
assert "input_tokens" not in usage
|
|
1602
|
+
assert "output_tokens" not in usage
|
|
1603
|
+
|
|
1604
|
+
def test_null_delta_counts_keep_start_counts(self):
|
|
1605
|
+
start_usage = {"input_tokens": 393, "output_tokens": 3, "cache_read_input_tokens": 0}
|
|
1606
|
+
delta_usage = {"input_tokens": None, "output_tokens": 77, "cache_read_input_tokens": None}
|
|
1607
|
+
|
|
1608
|
+
usage = _merge_streaming_usage(start_usage=start_usage, delta_usage=delta_usage)
|
|
1609
|
+
|
|
1610
|
+
assert usage == {"prompt_tokens": 393, "completion_tokens": 77, "cache_read_input_tokens": 0}
|
|
1611
|
+
|
|
1612
|
+
def test_no_usage_returns_empty_dict(self):
|
|
1613
|
+
assert _merge_streaming_usage(start_usage={}, delta_usage=None) == {}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anthropic_haystack-6.3.0 → anthropic_haystack-7.0.0}/example/documentation_rag_with_claude.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|