anthropic-haystack 6.0.1__tar.gz → 6.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/CHANGELOG.md +19 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/PKG-INFO +1 -1
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/src/haystack_integrations/components/generators/anthropic/chat/chat_generator.py +31 -10
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/src/haystack_integrations/components/generators/anthropic/chat/foundry_chat_generator.py +12 -3
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/src/haystack_integrations/components/generators/anthropic/chat/utils.py +38 -20
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/src/haystack_integrations/components/generators/anthropic/chat/vertex_chat_generator.py +15 -8
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/tests/test_chat_generator.py +91 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/tests/test_utils.py +129 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/tests/test_vertex_chat_generator.py +9 -9
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/.gitignore +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/LICENSE.txt +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/README.md +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/example/documentation_rag_with_claude.py +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/example/prompt_caching.py +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/pydoc/config_docusaurus.yml +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/pyproject.toml +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/src/haystack_integrations/components/generators/anthropic/__init__.py +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/src/haystack_integrations/components/generators/anthropic/chat/__init__.py +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/src/haystack_integrations/components/generators/py.typed +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/src/haystack_integrations/token_counters/anthropic/__init__.py +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/src/haystack_integrations/token_counters/anthropic/token_counter.py +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/src/haystack_integrations/token_counters/py.typed +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/tests/__init__.py +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/tests/conftest.py +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/tests/test_chat_generator_async.py +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/tests/test_files/apple.jpg +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/tests/test_files/sample_pdf_3.pdf +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/tests/test_foundry_chat_generator.py +0 -0
- {anthropic_haystack-6.0.1 → anthropic_haystack-6.2.0}/tests/test_token_counter.py +0 -0
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [integrations/anthropic-v6.1.0] - 2026-09-22
|
|
4
|
+
|
|
5
|
+
### 🐛 Bug Fixes
|
|
6
|
+
|
|
7
|
+
- Anthropic - respect an empty runtime tools list (#3977)
|
|
8
|
+
- Drop Anthropic tool calls truncated at max_tokens and raise the default limit (#3982)
|
|
9
|
+
|
|
10
|
+
### 🧹 Chores
|
|
11
|
+
|
|
12
|
+
- Refresh Anthropic model lists and expose service_tierupdates (#3983)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## [integrations/anthropic-v6.0.1] - 2026-09-14
|
|
16
|
+
|
|
17
|
+
### 🐛 Bug Fixes
|
|
18
|
+
|
|
19
|
+
- Anthropic - allow empty assistant messages (#3952)
|
|
20
|
+
|
|
21
|
+
|
|
3
22
|
## [integrations/anthropic-v6.0.0] - 2026-09-03
|
|
4
23
|
|
|
5
24
|
### 🚀 Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: anthropic-haystack
|
|
3
|
-
Version: 6.0
|
|
3
|
+
Version: 6.2.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
|
|
@@ -42,6 +42,8 @@ from .utils import (
|
|
|
42
42
|
|
|
43
43
|
logger = logging.getLogger(__name__)
|
|
44
44
|
|
|
45
|
+
_DEFAULT_MAX_TOKENS = 8192
|
|
46
|
+
|
|
45
47
|
|
|
46
48
|
@component
|
|
47
49
|
class AnthropicChatGenerator:
|
|
@@ -104,6 +106,7 @@ class AnthropicChatGenerator:
|
|
|
104
106
|
"tool_choice",
|
|
105
107
|
"max_tokens",
|
|
106
108
|
"metadata",
|
|
109
|
+
"service_tier",
|
|
107
110
|
"stop_sequences",
|
|
108
111
|
"temperature",
|
|
109
112
|
"top_p",
|
|
@@ -114,15 +117,17 @@ class AnthropicChatGenerator:
|
|
|
114
117
|
]
|
|
115
118
|
|
|
116
119
|
SUPPORTED_MODELS: ClassVar[list[str]] = [
|
|
120
|
+
"claude-fable-5-1",
|
|
121
|
+
"claude-fable-5",
|
|
122
|
+
"claude-opus-5",
|
|
123
|
+
"claude-opus-4-8",
|
|
124
|
+
"claude-opus-4-7",
|
|
117
125
|
"claude-opus-4-6",
|
|
126
|
+
"claude-opus-4-5-20251101",
|
|
127
|
+
"claude-sonnet-5",
|
|
118
128
|
"claude-sonnet-4-6",
|
|
119
|
-
"claude-haiku-4-5-20251001",
|
|
120
129
|
"claude-sonnet-4-5-20250929",
|
|
121
|
-
"claude-
|
|
122
|
-
"claude-opus-4-1-20250805",
|
|
123
|
-
"claude-sonnet-4-20250514",
|
|
124
|
-
"claude-opus-4-20250514",
|
|
125
|
-
"claude-3-haiku-20240307",
|
|
130
|
+
"claude-haiku-4-5-20251001",
|
|
126
131
|
]
|
|
127
132
|
"""A non-exhaustive list of chat models supported by this component. See
|
|
128
133
|
https://platform.claude.com/docs/en/about-claude/models/overview for the full list."""
|
|
@@ -153,8 +158,12 @@ class AnthropicChatGenerator:
|
|
|
153
158
|
|
|
154
159
|
Supported generation_kwargs parameters are:
|
|
155
160
|
- `system`: The system message to be passed to the model.
|
|
156
|
-
- `max_tokens`: The maximum number of tokens to generate.
|
|
161
|
+
- `max_tokens`: The maximum number of tokens to generate. Defaults to 8192. A response that hits
|
|
162
|
+
this limit is cut off; if the model was writing a tool call at the time, that call is dropped
|
|
163
|
+
and the reply carries a `length` finish reason.
|
|
157
164
|
- `metadata`: A dictionary of metadata to be passed to the model.
|
|
165
|
+
- `service_tier`: Whether the request may use priority capacity (`auto`) or standard capacity only
|
|
166
|
+
(`standard_only`). See [service tiers](https://platform.claude.com/docs/en/api/service-tiers).
|
|
158
167
|
- `stop_sequences`: A list of strings that the model should stop generating at.
|
|
159
168
|
- `temperature`: The temperature to use for sampling.
|
|
160
169
|
- `top_p`: The top_p value to use for nucleus sampling.
|
|
@@ -316,7 +325,7 @@ class AnthropicChatGenerator:
|
|
|
316
325
|
# prompt caching
|
|
317
326
|
|
|
318
327
|
# tools management
|
|
319
|
-
tools = tools
|
|
328
|
+
tools = tools if tools is not None else self.tools
|
|
320
329
|
flattened_tools = flatten_tools_or_toolsets(tools)
|
|
321
330
|
_check_duplicate_tool_names(flattened_tools)
|
|
322
331
|
|
|
@@ -340,6 +349,11 @@ class AnthropicChatGenerator:
|
|
|
340
349
|
|
|
341
350
|
def _resolve_flattened_generation_kwargs(self, generation_kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
342
351
|
generation_kwargs = generation_kwargs.copy()
|
|
352
|
+
# Copy the nested dicts the flattened kwargs write into; a shallow copy would share them with the
|
|
353
|
+
# component's init-time generation_kwargs, so a per-run flattened kwarg would persist across runs.
|
|
354
|
+
for key in ("tool_choice", "thinking", "output_config"):
|
|
355
|
+
if isinstance(generation_kwargs.get(key), dict):
|
|
356
|
+
generation_kwargs[key] = dict(generation_kwargs[key])
|
|
343
357
|
|
|
344
358
|
disable_parallel_tool_use = generation_kwargs.pop("disable_parallel_tool_use", None)
|
|
345
359
|
parallel_tool_use = generation_kwargs.pop("parallel_tool_use", None)
|
|
@@ -376,6 +390,13 @@ class AnthropicChatGenerator:
|
|
|
376
390
|
output_config = generation_kwargs.setdefault("output_config", {})
|
|
377
391
|
output_config["effort"] = adaptive_thinking_effort
|
|
378
392
|
|
|
393
|
+
thinking_display = generation_kwargs.pop("thinking_display", None)
|
|
394
|
+
if thinking_display is not None:
|
|
395
|
+
thinking = generation_kwargs.setdefault("thinking", {})
|
|
396
|
+
# `display` is only accepted with enabled or adaptive thinking, so it's dropped when thinking is disabled
|
|
397
|
+
if thinking.get("type") != "disabled":
|
|
398
|
+
thinking["display"] = thinking_display
|
|
399
|
+
|
|
379
400
|
return generation_kwargs
|
|
380
401
|
|
|
381
402
|
def _process_response(
|
|
@@ -587,7 +608,7 @@ class AnthropicChatGenerator:
|
|
|
587
608
|
system=system_messages,
|
|
588
609
|
tools=anthropic_tools,
|
|
589
610
|
stream=streaming_callback is not None,
|
|
590
|
-
max_tokens=generation_kwargs.pop("max_tokens",
|
|
611
|
+
max_tokens=generation_kwargs.pop("max_tokens", _DEFAULT_MAX_TOKENS),
|
|
591
612
|
**generation_kwargs,
|
|
592
613
|
)
|
|
593
614
|
|
|
@@ -636,7 +657,7 @@ class AnthropicChatGenerator:
|
|
|
636
657
|
system=system_messages,
|
|
637
658
|
tools=anthropic_tools,
|
|
638
659
|
stream=streaming_callback is not None,
|
|
639
|
-
max_tokens=generation_kwargs.pop("max_tokens",
|
|
660
|
+
max_tokens=generation_kwargs.pop("max_tokens", _DEFAULT_MAX_TOKENS),
|
|
640
661
|
**generation_kwargs,
|
|
641
662
|
)
|
|
642
663
|
|
|
@@ -77,11 +77,16 @@ class AnthropicFoundryChatGenerator(AnthropicChatGenerator):
|
|
|
77
77
|
"""
|
|
78
78
|
|
|
79
79
|
SUPPORTED_MODELS: ClassVar[list[str]] = [
|
|
80
|
+
"claude-fable-5-1",
|
|
81
|
+
"claude-fable-5",
|
|
82
|
+
"claude-opus-5",
|
|
83
|
+
"claude-opus-4-8",
|
|
84
|
+
"claude-opus-4-7",
|
|
80
85
|
"claude-opus-4-6",
|
|
86
|
+
"claude-opus-4-5",
|
|
87
|
+
"claude-sonnet-5",
|
|
81
88
|
"claude-sonnet-4-6",
|
|
82
89
|
"claude-sonnet-4-5",
|
|
83
|
-
"claude-opus-4-5",
|
|
84
|
-
"claude-opus-4-1",
|
|
85
90
|
"claude-haiku-4-5",
|
|
86
91
|
]
|
|
87
92
|
"""A non-exhaustive list of chat models supported by this component.
|
|
@@ -122,8 +127,12 @@ class AnthropicFoundryChatGenerator(AnthropicChatGenerator):
|
|
|
122
127
|
for more details.
|
|
123
128
|
Supported generation_kwargs parameters are:
|
|
124
129
|
- `system`: The system message to be passed to the model.
|
|
125
|
-
- `max_tokens`: The maximum number of tokens to generate.
|
|
130
|
+
- `max_tokens`: The maximum number of tokens to generate. Defaults to 8192. A response that hits
|
|
131
|
+
this limit is cut off; if the model was writing a tool call at the time, that call is dropped
|
|
132
|
+
and the reply carries a `length` finish reason.
|
|
126
133
|
- `metadata`: A dictionary of metadata to be passed to the model.
|
|
134
|
+
- `service_tier`: Whether the request may use priority capacity (`auto`) or standard capacity only
|
|
135
|
+
(`standard_only`). See [service tiers](https://platform.claude.com/docs/en/api/service-tiers).
|
|
127
136
|
- `stop_sequences`: A list of strings that the model should stop generating at.
|
|
128
137
|
- `temperature`: The temperature to use for sampling.
|
|
129
138
|
- `top_p`: The top_p value to use for nucleus sampling.
|
|
@@ -2,6 +2,7 @@ import json
|
|
|
2
2
|
from copy import deepcopy
|
|
3
3
|
from typing import Any, Literal, TypeAlias, cast, get_args
|
|
4
4
|
|
|
5
|
+
from haystack import logging
|
|
5
6
|
from haystack.dataclasses.chat_message import (
|
|
6
7
|
ChatMessage,
|
|
7
8
|
ChatRole,
|
|
@@ -32,6 +33,8 @@ from anthropic.types import (
|
|
|
32
33
|
ToolUseBlockParam,
|
|
33
34
|
)
|
|
34
35
|
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
35
38
|
# See https://docs.anthropic.com/en/api/messages for supported formats
|
|
36
39
|
ImageFormat = Literal["image/jpeg", "image/png", "image/gif", "image/webp"]
|
|
37
40
|
IMAGE_SUPPORTED_FORMATS: list[ImageFormat] = list(get_args(ImageFormat))
|
|
@@ -392,9 +395,29 @@ def _convert_chat_completion_to_chat_message(
|
|
|
392
395
|
"""
|
|
393
396
|
tool_calls = []
|
|
394
397
|
reasoning_contents = []
|
|
395
|
-
reasoning_text = ""
|
|
396
398
|
|
|
397
|
-
|
|
399
|
+
# A `max_tokens` stop reason cuts the response off mid-block. If that block was a tool call, its arguments are
|
|
400
|
+
# incomplete: Anthropic still returns a `tool_use` block, but only the keys it managed to finish. Running the
|
|
401
|
+
# tool with those arguments would act on the wrong input, so the call is dropped here. Instead, the reply carries
|
|
402
|
+
# the `length` finish reason which mirrors the streaming path, where the truncated JSON fails to parse and the tool
|
|
403
|
+
# call is dropped.
|
|
404
|
+
blocks = anthropic_response.content
|
|
405
|
+
truncated_tool_call = (
|
|
406
|
+
anthropic_response.stop_reason == "max_tokens" and bool(blocks) and blocks[-1].type == "tool_use"
|
|
407
|
+
)
|
|
408
|
+
if truncated_tool_call:
|
|
409
|
+
truncated_block = blocks[-1]
|
|
410
|
+
logger.warning(
|
|
411
|
+
"The model hit the `max_tokens` limit while writing the arguments of tool call `{tool_name}`, so they "
|
|
412
|
+
"are incomplete and the tool call will be skipped. Raise `max_tokens` in `generation_kwargs` to give "
|
|
413
|
+
"the model room to finish. Tool call ID: {tool_call_id}, partial arguments: {arguments}",
|
|
414
|
+
tool_name=truncated_block.name,
|
|
415
|
+
tool_call_id=truncated_block.id,
|
|
416
|
+
arguments=truncated_block.input,
|
|
417
|
+
)
|
|
418
|
+
blocks = blocks[:-1]
|
|
419
|
+
|
|
420
|
+
for block in blocks:
|
|
398
421
|
reasoning_content: dict[str, Any] = {}
|
|
399
422
|
if block.type == "tool_use":
|
|
400
423
|
tool_calls.append(ToolCall(tool_name=block.name, arguments=block.input, id=block.id))
|
|
@@ -420,30 +443,25 @@ def _convert_chat_completion_to_chat_message(
|
|
|
420
443
|
text = ""
|
|
421
444
|
|
|
422
445
|
if not (ignore_tools_thinking_messages and tool_calls):
|
|
423
|
-
text = " ".join(block.text for block in
|
|
424
|
-
|
|
425
|
-
message = ChatMessage.from_assistant(text=text, tool_calls=tool_calls, reasoning=reasoning)
|
|
446
|
+
text = " ".join(block.text for block in blocks if block.type == "text")
|
|
426
447
|
|
|
427
448
|
# Dump the chat completion to a dict
|
|
428
449
|
response_dict = anthropic_response.model_dump()
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
"usage": usage,
|
|
436
|
-
}
|
|
437
|
-
)
|
|
450
|
+
meta: dict[str, Any] = {
|
|
451
|
+
"model": response_dict.get("model", None),
|
|
452
|
+
"index": 0,
|
|
453
|
+
"finish_reason": FINISH_REASON_MAPPING.get(response_dict.get("stop_reason")),
|
|
454
|
+
"usage": _get_openai_compatible_usage(response_dict),
|
|
455
|
+
}
|
|
438
456
|
|
|
439
457
|
# keep raw content so server-tool blocks can be replayed on later turns
|
|
440
|
-
|
|
441
|
-
if _has_server_tool_blocks(
|
|
442
|
-
|
|
443
|
-
if citations := _extract_citations(
|
|
444
|
-
|
|
458
|
+
raw_content_blocks = response_dict.get("content") or []
|
|
459
|
+
if _has_server_tool_blocks(raw_content_blocks):
|
|
460
|
+
meta["raw_content_for_server_tools"] = raw_content_blocks
|
|
461
|
+
if citations := _extract_citations(raw_content_blocks):
|
|
462
|
+
meta["citations"] = citations
|
|
445
463
|
|
|
446
|
-
return
|
|
464
|
+
return ChatMessage.from_assistant(text=text, tool_calls=tool_calls, reasoning=reasoning, meta=meta)
|
|
447
465
|
|
|
448
466
|
|
|
449
467
|
def _convert_anthropic_chunk_to_streaming_chunk(
|
|
@@ -46,7 +46,7 @@ class AnthropicVertexChatGenerator(AnthropicChatGenerator):
|
|
|
46
46
|
|
|
47
47
|
messages = [ChatMessage.from_user("What's Natural Language Processing?")]
|
|
48
48
|
client = AnthropicVertexChatGenerator(
|
|
49
|
-
model="claude-sonnet-4@
|
|
49
|
+
model="claude-sonnet-4-5@20250929",
|
|
50
50
|
project_id="your-project-id", region="your-region"
|
|
51
51
|
)
|
|
52
52
|
response = client.run(messages)
|
|
@@ -57,7 +57,7 @@ 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@
|
|
60
|
+
>> _name=None, _meta={'model': 'claude-sonnet-4-5@20250929', 'index': 0, 'finish_reason': 'end_turn',
|
|
61
61
|
>> 'usage': {'input_tokens': 15, 'output_tokens': 64}})]}
|
|
62
62
|
```
|
|
63
63
|
|
|
@@ -69,13 +69,16 @@ class AnthropicVertexChatGenerator(AnthropicChatGenerator):
|
|
|
69
69
|
"""
|
|
70
70
|
|
|
71
71
|
SUPPORTED_MODELS: ClassVar[list[str]] = [
|
|
72
|
+
"claude-fable-5-1",
|
|
73
|
+
"claude-fable-5",
|
|
74
|
+
"claude-opus-5",
|
|
75
|
+
"claude-opus-4-8",
|
|
76
|
+
"claude-opus-4-7",
|
|
72
77
|
"claude-opus-4-6",
|
|
78
|
+
"claude-opus-4-5@20251101",
|
|
79
|
+
"claude-sonnet-5",
|
|
73
80
|
"claude-sonnet-4-6",
|
|
74
81
|
"claude-sonnet-4-5@20250929",
|
|
75
|
-
"claude-sonnet-4@20250514",
|
|
76
|
-
"claude-opus-4-5@20251101",
|
|
77
|
-
"claude-opus-4-1@20250805",
|
|
78
|
-
"claude-opus-4@20250514",
|
|
79
82
|
"claude-haiku-4-5@20251001",
|
|
80
83
|
]
|
|
81
84
|
"""A non-exhaustive list of chat models supported by this component. See
|
|
@@ -85,7 +88,7 @@ class AnthropicVertexChatGenerator(AnthropicChatGenerator):
|
|
|
85
88
|
self,
|
|
86
89
|
region: str,
|
|
87
90
|
project_id: str,
|
|
88
|
-
model: str = "claude-sonnet-4@
|
|
91
|
+
model: str = "claude-sonnet-4-5@20250929",
|
|
89
92
|
streaming_callback: Callable[[StreamingChunk], None] | None = None,
|
|
90
93
|
generation_kwargs: dict[str, Any] | None = None,
|
|
91
94
|
ignore_tools_thinking_messages: bool = True,
|
|
@@ -109,8 +112,12 @@ class AnthropicVertexChatGenerator(AnthropicChatGenerator):
|
|
|
109
112
|
|
|
110
113
|
Supported generation_kwargs parameters are:
|
|
111
114
|
- `system`: The system message to be passed to the model.
|
|
112
|
-
- `max_tokens`: The maximum number of tokens to generate.
|
|
115
|
+
- `max_tokens`: The maximum number of tokens to generate. Defaults to 8192. A response that hits
|
|
116
|
+
this limit is cut off; if the model was writing a tool call at the time, that call is dropped
|
|
117
|
+
and the reply carries a `length` finish reason.
|
|
113
118
|
- `metadata`: A dictionary of metadata to be passed to the model.
|
|
119
|
+
- `service_tier`: Whether the request may use priority capacity (`auto`) or standard capacity only
|
|
120
|
+
(`standard_only`). See [service tiers](https://platform.claude.com/docs/en/api/service-tiers).
|
|
114
121
|
- `stop_sequences`: A list of strings that the model should stop generating at.
|
|
115
122
|
- `temperature`: The temperature to use for sampling.
|
|
116
123
|
- `top_p`: The top_p value to use for nucleus sampling.
|
|
@@ -481,6 +481,7 @@ class TestRun:
|
|
|
481
481
|
"parallel_tool_use": None,
|
|
482
482
|
"tool_choice_type": None,
|
|
483
483
|
"adaptive_thinking_effort": None,
|
|
484
|
+
"thinking_display": None,
|
|
484
485
|
},
|
|
485
486
|
{},
|
|
486
487
|
),
|
|
@@ -509,6 +510,54 @@ class TestRun:
|
|
|
509
510
|
"thinking": {"type": "disabled"},
|
|
510
511
|
},
|
|
511
512
|
),
|
|
513
|
+
(
|
|
514
|
+
{
|
|
515
|
+
"service_tier": "standard_only",
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
"service_tier": "standard_only",
|
|
519
|
+
},
|
|
520
|
+
),
|
|
521
|
+
(
|
|
522
|
+
{
|
|
523
|
+
"adaptive_thinking_effort": "high",
|
|
524
|
+
"thinking_display": "summarized",
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
"thinking": {"type": "adaptive", "display": "summarized"},
|
|
528
|
+
"output_config": {"effort": "high"},
|
|
529
|
+
},
|
|
530
|
+
),
|
|
531
|
+
(
|
|
532
|
+
# thinking_display is merged into an explicitly passed thinking dict
|
|
533
|
+
{
|
|
534
|
+
"thinking": {"type": "adaptive"},
|
|
535
|
+
"thinking_display": "omitted",
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
"thinking": {"type": "adaptive", "display": "omitted"},
|
|
539
|
+
},
|
|
540
|
+
),
|
|
541
|
+
(
|
|
542
|
+
# thinking_display is dropped when thinking is disabled through a flattened kwarg
|
|
543
|
+
{
|
|
544
|
+
"adaptive_thinking_effort": "none",
|
|
545
|
+
"thinking_display": "summarized",
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
"thinking": {"type": "disabled"},
|
|
549
|
+
},
|
|
550
|
+
),
|
|
551
|
+
(
|
|
552
|
+
# thinking_display is dropped when thinking is disabled through an explicit thinking dict
|
|
553
|
+
{
|
|
554
|
+
"thinking": {"type": "disabled"},
|
|
555
|
+
"thinking_display": "summarized",
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
"thinking": {"type": "disabled"},
|
|
559
|
+
},
|
|
560
|
+
),
|
|
512
561
|
],
|
|
513
562
|
)
|
|
514
563
|
def test_run_with_flattened_generation_kwargs(
|
|
@@ -528,6 +577,42 @@ class TestRun:
|
|
|
528
577
|
assert actual_kwargs.get("tool_choice") == expected_kwargs.get("tool_choice")
|
|
529
578
|
assert actual_kwargs.get("thinking") == expected_kwargs.get("thinking")
|
|
530
579
|
assert actual_kwargs.get("output_config") == expected_kwargs.get("output_config")
|
|
580
|
+
assert actual_kwargs.get("service_tier") == expected_kwargs.get("service_tier")
|
|
581
|
+
|
|
582
|
+
def test_run_with_flattened_generation_kwargs_does_not_leak_across_runs(self, chat_messages, mock_chat_completion):
|
|
583
|
+
init_generation_kwargs = {
|
|
584
|
+
"tool_choice": {"type": "auto"},
|
|
585
|
+
"thinking": {"type": "adaptive"},
|
|
586
|
+
"output_config": {"effort": "low"},
|
|
587
|
+
}
|
|
588
|
+
component = AnthropicChatGenerator(
|
|
589
|
+
api_key=Secret.from_token("test-api-key"),
|
|
590
|
+
generation_kwargs=init_generation_kwargs,
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
component.run(
|
|
594
|
+
chat_messages,
|
|
595
|
+
generation_kwargs={
|
|
596
|
+
"parallel_tool_use": False,
|
|
597
|
+
"thinking_display": "summarized",
|
|
598
|
+
"adaptive_thinking_effort": "high",
|
|
599
|
+
},
|
|
600
|
+
)
|
|
601
|
+
first_kwargs = mock_chat_completion.call_args.kwargs
|
|
602
|
+
assert first_kwargs["tool_choice"] == {"type": "auto", "disable_parallel_tool_use": True}
|
|
603
|
+
assert first_kwargs["thinking"] == {"type": "adaptive", "display": "summarized"}
|
|
604
|
+
assert first_kwargs["output_config"] == {"effort": "high"}
|
|
605
|
+
|
|
606
|
+
component.run(chat_messages)
|
|
607
|
+
second_kwargs = mock_chat_completion.call_args.kwargs
|
|
608
|
+
assert second_kwargs["tool_choice"] == {"type": "auto"}
|
|
609
|
+
assert second_kwargs["thinking"] == {"type": "adaptive"}
|
|
610
|
+
assert second_kwargs["output_config"] == {"effort": "low"}
|
|
611
|
+
assert component.generation_kwargs == {
|
|
612
|
+
"tool_choice": {"type": "auto"},
|
|
613
|
+
"thinking": {"type": "adaptive"},
|
|
614
|
+
"output_config": {"effort": "low"},
|
|
615
|
+
}
|
|
531
616
|
|
|
532
617
|
|
|
533
618
|
class TestAnthropicServerTools:
|
|
@@ -569,6 +654,12 @@ class TestAnthropicServerTools:
|
|
|
569
654
|
assert len(kwargs["tools"]) == 2
|
|
570
655
|
assert kwargs["tools"][-1] == {"type": "web_search_20250305"}
|
|
571
656
|
|
|
657
|
+
def test_run_with_empty_tools_override(self, chat_messages, mock_chat_completion, tools):
|
|
658
|
+
component = AnthropicChatGenerator(api_key=Secret.from_token("test-api-key"), tools=tools)
|
|
659
|
+
component.run(messages=chat_messages, tools=[])
|
|
660
|
+
_, kwargs = mock_chat_completion.call_args
|
|
661
|
+
assert kwargs["tools"] == []
|
|
662
|
+
|
|
572
663
|
|
|
573
664
|
class TestMixedToolsAndToolsets:
|
|
574
665
|
def test_init_with_mixed_tools_and_toolsets(self, monkeypatch):
|
|
@@ -182,6 +182,135 @@ class TestConvertChatCompletionToChatMessage:
|
|
|
182
182
|
assert message.meta["raw_content_for_server_tools"][2]["citations"][0]["encrypted_index"] == "ENCRYPTED_INDEX"
|
|
183
183
|
|
|
184
184
|
|
|
185
|
+
class TestTruncatedToolCall:
|
|
186
|
+
def test_truncated_tool_call_is_dropped(self, caplog):
|
|
187
|
+
chat_completion = Message(
|
|
188
|
+
id="msg_01MZF",
|
|
189
|
+
content=[
|
|
190
|
+
ToolUseBlock(
|
|
191
|
+
type="tool_use",
|
|
192
|
+
id="toolu_01",
|
|
193
|
+
name="update_description",
|
|
194
|
+
# the model never got to write `description`
|
|
195
|
+
input={"issue_id": "AGE-144"},
|
|
196
|
+
)
|
|
197
|
+
],
|
|
198
|
+
model="claude-sonnet-4-5",
|
|
199
|
+
role="assistant",
|
|
200
|
+
stop_reason="max_tokens",
|
|
201
|
+
type="message",
|
|
202
|
+
usage=Usage(input_tokens=57, output_tokens=8192),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
chat_message = _convert_chat_completion_to_chat_message(chat_completion, ignore_tools_thinking_messages=True)
|
|
206
|
+
|
|
207
|
+
assert chat_message.tool_calls == []
|
|
208
|
+
assert chat_message.meta["finish_reason"] == "length"
|
|
209
|
+
assert "max_tokens" in caplog.text
|
|
210
|
+
assert "update_description" in caplog.text
|
|
211
|
+
|
|
212
|
+
def test_completed_tool_calls_before_the_truncated_one_are_kept(self):
|
|
213
|
+
"""Only the final block is cut off, so earlier tool calls in the same reply are still usable."""
|
|
214
|
+
chat_completion = Message(
|
|
215
|
+
id="msg_01MZF",
|
|
216
|
+
content=[
|
|
217
|
+
ToolUseBlock(type="tool_use", id="toolu_01", name="weather", input={"city": "Paris"}),
|
|
218
|
+
ToolUseBlock(type="tool_use", id="toolu_02", name="update_description", input={}),
|
|
219
|
+
],
|
|
220
|
+
model="claude-sonnet-4-5",
|
|
221
|
+
role="assistant",
|
|
222
|
+
stop_reason="max_tokens",
|
|
223
|
+
type="message",
|
|
224
|
+
usage=Usage(input_tokens=57, output_tokens=8192),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
chat_message = _convert_chat_completion_to_chat_message(chat_completion, ignore_tools_thinking_messages=True)
|
|
228
|
+
|
|
229
|
+
assert [tool_call.tool_name for tool_call in chat_message.tool_calls] == ["weather"]
|
|
230
|
+
|
|
231
|
+
def test_tool_call_is_kept_when_the_response_ended_normally(self):
|
|
232
|
+
chat_completion = Message(
|
|
233
|
+
id="msg_01MZF",
|
|
234
|
+
content=[ToolUseBlock(type="tool_use", id="toolu_01", name="weather", input={"city": "Paris"})],
|
|
235
|
+
model="claude-sonnet-4-5",
|
|
236
|
+
role="assistant",
|
|
237
|
+
stop_reason="tool_use",
|
|
238
|
+
type="message",
|
|
239
|
+
usage=Usage(input_tokens=57, output_tokens=40),
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
chat_message = _convert_chat_completion_to_chat_message(chat_completion, ignore_tools_thinking_messages=True)
|
|
243
|
+
|
|
244
|
+
assert [tool_call.tool_name for tool_call in chat_message.tool_calls] == ["weather"]
|
|
245
|
+
assert chat_message.meta["finish_reason"] == "tool_calls"
|
|
246
|
+
|
|
247
|
+
def test_text_truncated_at_max_tokens_is_kept(self):
|
|
248
|
+
"""Only tool calls are dropped: truncated text is still the model's answer, just a shorter one."""
|
|
249
|
+
chat_completion = Message(
|
|
250
|
+
id="msg_01MZF",
|
|
251
|
+
content=[TextBlock(type="text", text="The capital of France is", citations=None)],
|
|
252
|
+
model="claude-sonnet-4-5",
|
|
253
|
+
role="assistant",
|
|
254
|
+
stop_reason="max_tokens",
|
|
255
|
+
type="message",
|
|
256
|
+
usage=Usage(input_tokens=57, output_tokens=8192),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
chat_message = _convert_chat_completion_to_chat_message(chat_completion, ignore_tools_thinking_messages=True)
|
|
260
|
+
|
|
261
|
+
assert chat_message.text == "The capital of France is"
|
|
262
|
+
assert chat_message.meta["finish_reason"] == "length"
|
|
263
|
+
|
|
264
|
+
@pytest.mark.parametrize("is_async", [False, True])
|
|
265
|
+
async def test_streaming_truncated_tool_call_is_dropped(self, is_async):
|
|
266
|
+
generator = AnthropicChatGenerator(api_key=Secret.from_token("test-api-key"))
|
|
267
|
+
raw_chunks = [
|
|
268
|
+
RawMessageStartEvent(
|
|
269
|
+
message=Message(
|
|
270
|
+
id="msg_123",
|
|
271
|
+
content=[],
|
|
272
|
+
model="claude-sonnet-4-5",
|
|
273
|
+
role="assistant",
|
|
274
|
+
stop_reason=None,
|
|
275
|
+
stop_sequence=None,
|
|
276
|
+
type="message",
|
|
277
|
+
usage=Usage(input_tokens=100, output_tokens=1),
|
|
278
|
+
),
|
|
279
|
+
type="message_start",
|
|
280
|
+
),
|
|
281
|
+
RawContentBlockStartEvent(
|
|
282
|
+
content_block=ToolUseBlock(id="toolu_01", input={}, name="update_description", type="tool_use"),
|
|
283
|
+
index=0,
|
|
284
|
+
type="content_block_start",
|
|
285
|
+
),
|
|
286
|
+
RawContentBlockDeltaEvent(
|
|
287
|
+
delta=InputJSONDelta(
|
|
288
|
+
partial_json='{"issue_id": "AGE-144", "description": "Lorem ip', type="input_json_delta"
|
|
289
|
+
),
|
|
290
|
+
index=0,
|
|
291
|
+
type="content_block_delta",
|
|
292
|
+
),
|
|
293
|
+
RawMessageDeltaEvent(
|
|
294
|
+
delta=Delta(stop_reason="max_tokens", stop_sequence=None),
|
|
295
|
+
type="message_delta",
|
|
296
|
+
usage=MessageDeltaUsage(output_tokens=8192),
|
|
297
|
+
),
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
if is_async:
|
|
301
|
+
|
|
302
|
+
async def _astream():
|
|
303
|
+
for chunk in raw_chunks:
|
|
304
|
+
yield chunk
|
|
305
|
+
|
|
306
|
+
message = (await generator._process_response_async(_astream()))["replies"][0]
|
|
307
|
+
else:
|
|
308
|
+
message = generator._process_response(raw_chunks)["replies"][0]
|
|
309
|
+
|
|
310
|
+
assert message.tool_calls == []
|
|
311
|
+
assert message.meta["finish_reason"] == "length"
|
|
312
|
+
|
|
313
|
+
|
|
185
314
|
class TestConvertAnthropicChunkToStreamingChunk:
|
|
186
315
|
def test_convert_anthropic_completion_chunks_with_multiple_tool_calls_and_reasoning_to_streaming_chunks(self):
|
|
187
316
|
"""
|
|
@@ -20,7 +20,7 @@ class TestUnit:
|
|
|
20
20
|
component = AnthropicVertexChatGenerator(region="us-central1", project_id="test-project-id")
|
|
21
21
|
assert component.region == "us-central1"
|
|
22
22
|
assert component.project_id == "test-project-id"
|
|
23
|
-
assert component.model == "claude-sonnet-4@
|
|
23
|
+
assert component.model == "claude-sonnet-4-5@20250929"
|
|
24
24
|
assert component.streaming_callback is None
|
|
25
25
|
assert not component.generation_kwargs
|
|
26
26
|
assert component.ignore_tools_thinking_messages
|
|
@@ -31,14 +31,14 @@ class TestUnit:
|
|
|
31
31
|
component = AnthropicVertexChatGenerator(
|
|
32
32
|
region="us-central1",
|
|
33
33
|
project_id="test-project-id",
|
|
34
|
-
model="claude-sonnet-4@
|
|
34
|
+
model="claude-sonnet-4-5@20250929",
|
|
35
35
|
streaming_callback=print_streaming_chunk,
|
|
36
36
|
generation_kwargs={"max_tokens": 10, "some_test_param": "test-params"},
|
|
37
37
|
ignore_tools_thinking_messages=False,
|
|
38
38
|
)
|
|
39
39
|
assert component.region == "us-central1"
|
|
40
40
|
assert component.project_id == "test-project-id"
|
|
41
|
-
assert component.model == "claude-sonnet-4@
|
|
41
|
+
assert component.model == "claude-sonnet-4-5@20250929"
|
|
42
42
|
assert component.streaming_callback is print_streaming_chunk
|
|
43
43
|
assert component.generation_kwargs == {"max_tokens": 10, "some_test_param": "test-params"}
|
|
44
44
|
assert component.ignore_tools_thinking_messages is False
|
|
@@ -54,7 +54,7 @@ class TestUnit:
|
|
|
54
54
|
"init_parameters": {
|
|
55
55
|
"region": "us-central1",
|
|
56
56
|
"project_id": "test-project-id",
|
|
57
|
-
"model": "claude-sonnet-4@
|
|
57
|
+
"model": "claude-sonnet-4-5@20250929",
|
|
58
58
|
"streaming_callback": None,
|
|
59
59
|
"generation_kwargs": {},
|
|
60
60
|
"ignore_tools_thinking_messages": True,
|
|
@@ -85,7 +85,7 @@ class TestUnit:
|
|
|
85
85
|
"init_parameters": {
|
|
86
86
|
"region": "us-central1",
|
|
87
87
|
"project_id": "test-project-id",
|
|
88
|
-
"model": "claude-sonnet-4@
|
|
88
|
+
"model": "claude-sonnet-4-5@20250929",
|
|
89
89
|
"streaming_callback": "haystack.components.generators.utils.print_streaming_chunk",
|
|
90
90
|
"generation_kwargs": {"max_tokens": 10, "some_test_param": "test-params"},
|
|
91
91
|
"ignore_tools_thinking_messages": False,
|
|
@@ -105,7 +105,7 @@ class TestUnit:
|
|
|
105
105
|
"init_parameters": {
|
|
106
106
|
"region": "us-central1",
|
|
107
107
|
"project_id": "test-project-id",
|
|
108
|
-
"model": "claude-sonnet-4@
|
|
108
|
+
"model": "claude-sonnet-4-5@20250929",
|
|
109
109
|
"streaming_callback": "haystack.components.generators.utils.print_streaming_chunk",
|
|
110
110
|
"generation_kwargs": {"max_tokens": 10, "some_test_param": "test-params"},
|
|
111
111
|
"ignore_tools_thinking_messages": True,
|
|
@@ -116,7 +116,7 @@ class TestUnit:
|
|
|
116
116
|
},
|
|
117
117
|
}
|
|
118
118
|
component = AnthropicVertexChatGenerator.from_dict(data)
|
|
119
|
-
assert component.model == "claude-sonnet-4@
|
|
119
|
+
assert component.model == "claude-sonnet-4-5@20250929"
|
|
120
120
|
assert component.region == "us-central1"
|
|
121
121
|
assert component.project_id == "test-project-id"
|
|
122
122
|
assert component.streaming_callback is print_streaming_chunk
|
|
@@ -220,7 +220,7 @@ class TestIntegration:
|
|
|
220
220
|
|
|
221
221
|
def test_default_inference_params(self, chat_messages):
|
|
222
222
|
client = AnthropicVertexChatGenerator(
|
|
223
|
-
region=os.environ.get("REGION"), project_id=os.environ.get("PROJECT_ID"), model="claude-sonnet-4@
|
|
223
|
+
region=os.environ.get("REGION"), project_id=os.environ.get("PROJECT_ID"), model="claude-sonnet-4-5@20250929"
|
|
224
224
|
)
|
|
225
225
|
response = client.run(chat_messages)
|
|
226
226
|
|
|
@@ -243,7 +243,7 @@ class TestIntegration:
|
|
|
243
243
|
component = AnthropicVertexChatGenerator(
|
|
244
244
|
region=os.environ.get("REGION"),
|
|
245
245
|
project_id=os.environ.get("PROJECT_ID"),
|
|
246
|
-
model="claude-sonnet-4@
|
|
246
|
+
model="claude-sonnet-4-5@20250929",
|
|
247
247
|
)
|
|
248
248
|
results = await component.run_async(messages=[ChatMessage.from_user("What's the capital of France?")])
|
|
249
249
|
assert len(results["replies"]) == 1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anthropic_haystack-6.0.1 → anthropic_haystack-6.2.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
|
|
File without changes
|