Skip to content

Commit 4679dc7

Browse files
authored
Merge branch 'main' into github_repo_forker_tool_integration
2 parents 391fb50 + e6a13ef commit 4679dc7

File tree

39 files changed

+665
-305
lines changed

39 files changed

+665
-305
lines changed

.github/workflows/cohere.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,9 @@ jobs:
5050
- name: Install Hatch
5151
run: pip install --upgrade hatch
5252

53-
# TODO: Once this integration is properly typed, use hatch run test:types
54-
# https://github.com/deepset-ai/haystack-core-integrations/issues/1771
5553
- name: Lint
5654
if: matrix.python-version == '3.9' && runner.os == 'Linux'
57-
run: hatch run fmt-check && hatch run lint:typing
55+
run: hatch run fmt-check && hatch run test:types
5856

5957
- name: Generate docs
6058
if: matrix.python-version == '3.9' && runner.os == 'Linux'

.github/workflows/fastembed.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,9 @@ jobs:
3333

3434
- name: Install Hatch
3535
run: pip install --upgrade hatch
36-
37-
# TODO: Once this integration is properly typed, use hatch run test:types
38-
# https://github.com/deepset-ai/haystack-core-integrations/issues/1771
3936
- name: Lint
4037
if: matrix.python-version == '3.9' && runner.os == 'Linux'
41-
run: hatch run fmt-check && hatch run lint:typing
38+
run: hatch run fmt-check && hatch run test:types
4239

4340
- name: Generate docs
4441
if: matrix.python-version == '3.9' && runner.os == 'Linux'

.github/workflows/unstructured.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,9 @@ jobs:
6161
- name: Install Hatch
6262
run: pip install --upgrade hatch
6363

64-
# TODO: Once this integration is properly typed, use hatch run test:types
65-
# https://github.com/deepset-ai/haystack-core-integrations/issues/1771
6664
- name: Lint
6765
if: matrix.python-version == '3.9' && runner.os == 'Linux'
68-
run: hatch run fmt-check && hatch run lint:typing
66+
run: hatch run fmt-check && hatch run test:types
6967

7068
- name: Generate docs
7169
if: matrix.python-version == '3.9' && runner.os == 'Linux'

integrations/anthropic/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [integrations/anthropic-v2.6.1] - 2025-06-16
4+
5+
### 🌀 Miscellaneous
6+
7+
- Fix: `AnthropicChatGenerator` now properly can call tools that have no arguments when streaming is enabled (#1950)
8+
39
## [integrations/anthropic-v2.6.0] - 2025-06-13
410

511
### 🐛 Bug Fixes

integrations/anthropic/src/haystack_integrations/components/generators/anthropic/chat/chat_generator.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,12 +367,18 @@ def _convert_streaming_chunks_to_chat_message(
367367
elif chunk_type == "message_delta":
368368
if chunk.meta.get("delta", {}).get("stop_reason") == "tool_use" and current_tool_call:
369369
try:
370-
# arguments is a string, convert to json
370+
# When calling a tool with no arguments, the `arguments` field is an empty string.
371+
# We handle this by checking if `arguments` is empty and setting it to an empty dict.
372+
arguments = (
373+
json.loads(current_tool_call.get("arguments", "{}"))
374+
if current_tool_call.get("arguments")
375+
else {}
376+
)
371377
tool_calls.append(
372378
ToolCall(
373379
id=current_tool_call.get("id"),
374380
tool_name=str(current_tool_call.get("name")),
375-
arguments=json.loads(current_tool_call.get("arguments", {})),
381+
arguments=arguments,
376382
)
377383
)
378384
except json.JSONDecodeError:

integrations/anthropic/tests/test_chat_generator.py

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@
3030
)
3131

3232

33+
def hello_world():
34+
return "Hello, World!"
35+
36+
37+
@pytest.fixture
38+
def tool_with_no_parameters():
39+
tool = Tool(
40+
name="hello_world",
41+
description="This prints hello world",
42+
parameters={"properties": {}, "type": "object"},
43+
function=hello_world,
44+
)
45+
return tool
46+
47+
3348
@pytest.fixture
3449
def tools():
3550
tool_parameters = {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
@@ -39,7 +54,6 @@ def tools():
3954
parameters=tool_parameters,
4055
function=lambda x: x,
4156
)
42-
4357
return [tool]
4458

4559

@@ -533,6 +547,116 @@ def test_convert_streaming_chunks_to_chat_message_malformed_json(self, caplog):
533547
with caplog.at_level(logging.WARNING):
534548
assert "Anthropic returned a malformed JSON string" in caplog.text
535549

550+
def test_convert_streaming_chunks_to_chat_message_tool_call_with_empty_arguments(self):
551+
"""
552+
Test converting streaming chunks with an empty tool call arguments
553+
"""
554+
chunks = [
555+
StreamingChunk(
556+
content="",
557+
meta={
558+
"content_block": {"citations": None, "text": "", "type": "text"},
559+
"index": 0,
560+
"type": "content_block_start",
561+
},
562+
),
563+
StreamingChunk(
564+
content="Certainly! I can",
565+
meta={
566+
"delta": {"text": "Certainly! I can", "type": "text_delta"},
567+
"index": 0,
568+
"type": "content_block_delta",
569+
},
570+
),
571+
StreamingChunk(
572+
content=' help you print "Hello World" using the available',
573+
meta={
574+
"delta": {"text": ' help you print "Hello World" using the available', "type": "text_delta"},
575+
"index": 0,
576+
"type": "content_block_delta",
577+
},
578+
),
579+
StreamingChunk(
580+
content=" tool. Let's use the \"",
581+
meta={
582+
"delta": {"text": " tool. Let's use the \"", "type": "text_delta"},
583+
"index": 0,
584+
"type": "content_block_delta",
585+
},
586+
),
587+
StreamingChunk(
588+
content='hello_world" function to accomplish this task.',
589+
meta={
590+
"delta": {"text": 'hello_world" function to accomplish this task.', "type": "text_delta"},
591+
"index": 0,
592+
"type": "content_block_delta",
593+
},
594+
),
595+
StreamingChunk(
596+
content="",
597+
meta={
598+
"content_block": {
599+
"id": "toolu_014yzmmeNPAuTuiN92qV6LKr",
600+
"input": {},
601+
"name": "hello_world",
602+
"type": "tool_use",
603+
},
604+
"index": 1,
605+
"type": "content_block_start",
606+
},
607+
),
608+
StreamingChunk(
609+
content="",
610+
meta={
611+
"delta": {"partial_json": "", "type": "input_json_delta"},
612+
"index": 1,
613+
"type": "content_block_delta",
614+
},
615+
),
616+
StreamingChunk(
617+
content="",
618+
meta={
619+
"delta": {"stop_reason": "tool_use", "stop_sequence": None},
620+
"type": "message_delta",
621+
"usage": {
622+
"cache_creation_input_tokens": None,
623+
"cache_read_input_tokens": None,
624+
"input_tokens": None,
625+
"output_tokens": 69,
626+
"server_tool_use": None,
627+
},
628+
},
629+
),
630+
]
631+
632+
component = AnthropicChatGenerator(api_key=Secret.from_token("test-api-key"))
633+
message = component._convert_streaming_chunks_to_chat_message(chunks, model="claude-3-sonnet")
634+
635+
# Verify the message content
636+
assert message.text == (
637+
'Certainly! I can help you print "Hello World" using the available tool. Let\'s use the "hello_world" '
638+
"function to accomplish this task."
639+
)
640+
641+
# Verify tool calls
642+
assert len(message.tool_calls) == 1
643+
tool_call = message.tool_calls[0]
644+
assert tool_call.id == "toolu_014yzmmeNPAuTuiN92qV6LKr"
645+
assert tool_call.tool_name == "hello_world"
646+
assert tool_call.arguments == {}
647+
648+
# Verify meta information
649+
assert message._meta["model"] == "claude-3-sonnet"
650+
assert message._meta["index"] == 0
651+
assert message._meta["finish_reason"] == "tool_use"
652+
assert message._meta["usage"] == {
653+
"cache_creation_input_tokens": None,
654+
"cache_read_input_tokens": None,
655+
"completion_tokens": 69,
656+
"prompt_tokens": None,
657+
"server_tool_use": None,
658+
}
659+
536660
def test_serde_in_pipeline(self):
537661
tool = Tool(name="name", description="description", parameters={"x": {"type": "string"}}, function=print)
538662

@@ -970,6 +1094,47 @@ def test_live_run_with_tools_streaming(self, tools):
9701094
assert len(final_message.text) > 0
9711095
assert "paris" in final_message.text.lower()
9721096

1097+
@pytest.mark.skipif(
1098+
not os.environ.get("ANTHROPIC_API_KEY", None),
1099+
reason="Export an env var called ANTHROPIC_API_KEY containing the Anthropic API key to run this test.",
1100+
)
1101+
@pytest.mark.integration
1102+
def test_live_run_with_tool_with_no_args_streaming(self, tool_with_no_parameters):
1103+
"""
1104+
Integration test that the AnthropicChatGenerator component can run with a tool that has no arguments and
1105+
streaming.
1106+
"""
1107+
initial_messages = [ChatMessage.from_user("Print Hello World using the print hello world tool.")]
1108+
component = AnthropicChatGenerator(tools=[tool_with_no_parameters], streaming_callback=print_streaming_chunk)
1109+
results = component.run(messages=initial_messages)
1110+
1111+
assert len(results["replies"]) == 1
1112+
message = results["replies"][0]
1113+
1114+
# this is Anthropic thinking message prior to tool call
1115+
assert message.text is not None
1116+
1117+
# now we have the tool call
1118+
assert message.tool_calls
1119+
tool_call = message.tool_call
1120+
assert isinstance(tool_call, ToolCall)
1121+
assert tool_call.id is not None
1122+
assert tool_call.tool_name == "hello_world"
1123+
assert tool_call.arguments == {}
1124+
assert message.meta["finish_reason"] == "tool_use"
1125+
1126+
new_messages = [
1127+
*initial_messages,
1128+
message,
1129+
ChatMessage.from_tool(tool_result="Hello World!", origin=tool_call),
1130+
]
1131+
results = component.run(new_messages)
1132+
assert len(results["replies"]) == 1
1133+
final_message = results["replies"][0]
1134+
assert not final_message.tool_calls
1135+
assert len(final_message.text) > 0
1136+
assert "hello" in final_message.text.lower()
1137+
9731138
@pytest.mark.skipif(
9741139
not os.environ.get("ANTHROPIC_API_KEY", None),
9751140
reason="Export an env var called ANTHROPIC_API_KEY containing the Anthropic API key to run this test.",

integrations/cohere/CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# Changelog
22

3+
## [integrations/cohere-v5.0.0] - 2025-06-17
4+
5+
### 🚀 Features
6+
7+
- Add run_async support for CohereTextEmbedder (#1873)
8+
- Add async support for CohereDocumentEmbedder (#1876)
9+
10+
### 🚜 Refactor
11+
12+
- [**breaking**] Cohere: remove `use_async_client` parameter; fix types and add py.typed (#1946)
13+
14+
### 🧹 Chores
15+
16+
- Align core-integrations Hatch scripts (#1898)
17+
- Update md files for new hatch scripts (#1911)
18+
19+
320
## [integrations/cohere-v4.2.1] - 2025-05-27
421

522
### 🌀 Miscellaneous

integrations/cohere/pyproject.toml

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,15 @@ integration = 'pytest -m "integration" {args:tests}'
6666
all = 'pytest {args:tests}'
6767
cov-retry = 'all --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x'
6868

69-
types = "mypy --install-types --non-interactive --explicit-package-bases {args:src/ tests}"
69+
types = """mypy -p haystack_integrations.components.embedders.cohere \
70+
-p haystack_integrations.components.generators.cohere \
71+
-p haystack_integrations.components.rankers.cohere {args}"""
7072

71-
# TODO: remove lint environment once this integration is properly typed
72-
# test environment should be used instead
73-
# https://github.com/deepset-ai/haystack-core-integrations/issues/1771
74-
[tool.hatch.envs.lint]
75-
installer = "uv"
76-
detached = true
77-
dependencies = ["pip", "black>=23.1.0", "mypy>=1.0.0", "ruff>=0.0.243"]
78-
79-
[tool.hatch.envs.lint.scripts]
80-
typing = "mypy --install-types --non-interactive --explicit-package-bases {args:src/ tests}"
73+
[tool.mypy]
74+
install_types = true
75+
non_interactive = true
76+
check_untyped_defs = true
77+
disallow_incomplete_defs = true
8178

8279
[tool.black]
8380
target-version = ["py38"]
@@ -159,16 +156,6 @@ show_missing = true
159156
exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]
160157

161158

162-
[[tool.mypy.overrides]]
163-
module = [
164-
"cohere.*",
165-
"haystack.*",
166-
"haystack_integrations.*",
167-
"pytest.*",
168-
"numpy.*",
169-
]
170-
ignore_missing_imports = true
171-
172159
[tool.pytest.ini_options]
173160
addopts = "--strict-markers"
174161
markers = [

0 commit comments

Comments
 (0)