12
12
from openai import Stream
13
13
from openai .types .chat import ChatCompletionChunk , chat_completion_chunk
14
14
15
- from haystack import Pipeline , tracing
15
+ from haystack import Pipeline , component , tracing
16
16
from haystack .components .agents import Agent
17
17
from haystack .components .agents .state import merge_lists
18
18
from haystack .components .builders .chat_prompt_builder import ChatPromptBuilder
19
19
from haystack .components .builders .prompt_builder import PromptBuilder
20
20
from haystack .components .generators .chat .openai import OpenAIChatGenerator
21
- from haystack .components .generators .chat .types import ChatGenerator
22
21
from haystack .core .component .types import OutputSocket
23
22
from haystack .dataclasses import ChatMessage , ToolCall
24
23
from haystack .dataclasses .chat_message import ChatRole , TextContent
@@ -101,59 +100,56 @@ def openai_mock_chat_completion_chunk():
101
100
yield mock_chat_completion_create
102
101
103
102
104
- class MockChatGeneratorWithoutTools (ChatGenerator ):
103
+ @component
104
+ class MockChatGeneratorWithoutTools :
105
105
"""A mock chat generator that implements ChatGenerator protocol but doesn't support tools."""
106
106
107
- __haystack_input__ = MagicMock (_sockets_dict = {})
108
- __haystack_output__ = MagicMock (_sockets_dict = {})
109
-
110
107
def to_dict (self ) -> dict [str , Any ]:
111
108
return {"type" : "MockChatGeneratorWithoutTools" , "data" : {}}
112
109
113
110
@classmethod
114
111
def from_dict (cls , data : dict [str , Any ]) -> "MockChatGeneratorWithoutTools" :
115
112
return cls ()
116
113
114
+ @component .output_types (replies = list [ChatMessage ])
117
115
def run (self , messages : list [ChatMessage ]) -> dict [str , Any ]:
118
116
return {"replies" : [ChatMessage .from_assistant ("Hello" )]}
119
117
120
118
121
- class MockChatGeneratorWithoutRunAsync (ChatGenerator ):
119
+ @component
120
+ class MockChatGeneratorWithoutRunAsync :
122
121
"""A mock chat generator that implements ChatGenerator protocol but doesn't have run_async method."""
123
122
124
- __haystack_input__ = MagicMock (_sockets_dict = {})
125
- __haystack_output__ = MagicMock (_sockets_dict = {})
126
-
127
123
def to_dict (self ) -> dict [str , Any ]:
128
124
return {"type" : "MockChatGeneratorWithoutRunAsync" , "data" : {}}
129
125
130
126
@classmethod
131
127
def from_dict (cls , data : dict [str , Any ]) -> "MockChatGeneratorWithoutRunAsync" :
132
128
return cls ()
133
129
130
+ @component .output_types (replies = list [ChatMessage ])
134
131
def run (
135
132
self , messages : list [ChatMessage ], tools : Optional [Union [list [Tool ], Toolset ]] = None , ** kwargs
136
133
) -> dict [str , Any ]:
137
134
return {"replies" : [ChatMessage .from_assistant ("Hello" )]}
138
135
139
136
140
- class MockChatGeneratorWithRunAsync (ChatGenerator ):
141
- __haystack_supports_async__ = True
142
- __haystack_input__ = MagicMock (_sockets_dict = {})
143
- __haystack_output__ = MagicMock (_sockets_dict = {})
144
-
137
+ @component
138
+ class MockChatGenerator :
145
139
def to_dict (self ) -> dict [str , Any ]:
146
140
return {"type" : "MockChatGeneratorWithoutRunAsync" , "data" : {}}
147
141
148
142
@classmethod
149
143
def from_dict (cls , data : dict [str , Any ]) -> "MockChatGeneratorWithoutRunAsync" :
150
144
return cls ()
151
145
146
+ @component .output_types (replies = list [ChatMessage ])
152
147
def run (
153
148
self , messages : list [ChatMessage ], tools : Optional [Union [list [Tool ], Toolset ]] = None , ** kwargs
154
149
) -> dict [str , Any ]:
155
150
return {"replies" : [ChatMessage .from_assistant ("Hello" )]}
156
151
152
+ @component .output_types (replies = list [ChatMessage ])
157
153
async def run_async (
158
154
self , messages : list [ChatMessage ], tools : Optional [Union [list [Tool ], Toolset ]] = None , ** kwargs
159
155
) -> dict [str , Any ]:
@@ -818,7 +814,7 @@ async def test_run_async_falls_back_to_run_when_chat_generator_has_no_run_async(
818
814
819
815
@pytest .mark .asyncio
820
816
async def test_run_async_uses_chat_generator_run_async_when_available (self , weather_tool ):
821
- chat_generator = MockChatGeneratorWithRunAsync ()
817
+ chat_generator = MockChatGenerator ()
822
818
agent = Agent (chat_generator = chat_generator , tools = [weather_tool ])
823
819
agent .warm_up ()
824
820
@@ -904,16 +900,16 @@ def test_agent_tracing_span_run(self, caplog, monkeypatch, weather_tool):
904
900
"chat_generator" ,
905
901
"MockChatGeneratorWithoutRunAsync" ,
906
902
'{"messages": "list", "tools": "list"}' ,
907
- "{}" ,
908
- "{}" ,
903
+ '{"messages": {"type": "list", "senders": []}, "tools": {"type": "typing.Union[list[haystack.tools.tool.Tool], haystack.tools.toolset.Toolset, NoneType]", "senders": []}}' , # noqa: E501
904
+ '{"replies": {"type": "list", "receivers": []}}' ,
909
905
'{"messages": [{"role": "user", "meta": {}, "name": null, "content": [{"text": "What\' s the weather in Paris?"}]}], "tools": [{"type": "haystack.tools.tool.Tool", "data": {"name": "weather_tool", "description": "Provides weather information for a given location.", "parameters": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]}, "function": "test_agent.weather_function", "outputs_to_string": null, "inputs_from_state": null, "outputs_to_state": null}}]}' , # noqa: E501
910
906
1 ,
911
907
'{"replies": [{"role": "assistant", "meta": {}, "name": null, "content": [{"text": "Hello"}]}]}' ,
912
908
100 ,
913
909
'[{"type": "haystack.tools.tool.Tool", "data": {"name": "weather_tool", "description": "Provides weather information for a given location.", "parameters": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]}, "function": "test_agent.weather_function", "outputs_to_string": null, "inputs_from_state": null, "outputs_to_state": null}}]' , # noqa: E501
914
910
'["text"]' ,
915
911
'{"messages": {"type": "list[haystack.dataclasses.chat_message.ChatMessage]", "handler": "haystack.components.agents.state.state_utils.merge_lists"}}' , # noqa: E501
916
- '{"messages": [{"role": "user", "meta": {}, "name": null, "content": [{"text": "What\' s the weather in Paris?"}]}], "streaming_callback": null}' , # noqa: E501
912
+ '{"messages": [{"role": "user", "meta": {}, "name": null, "content": [{"text": "What\' s the weather in Paris?"}]}], "streaming_callback": null, "break_point": null, "snapshot": null }' , # noqa: E501
917
913
'{"messages": [{"role": "user", "meta": {}, "name": null, "content": [{"text": "What\' s the weather in Paris?"}]}, {"role": "assistant", "meta": {}, "name": null, "content": [{"text": "Hello"}]}]}' , # noqa: E501
918
914
1 ,
919
915
]
@@ -927,7 +923,7 @@ def test_agent_tracing_span_run(self, caplog, monkeypatch, weather_tool):
927
923
928
924
@pytest .mark .asyncio
929
925
async def test_agent_tracing_span_async_run (self , caplog , monkeypatch , weather_tool ):
930
- chat_generator = MockChatGeneratorWithRunAsync ()
926
+ chat_generator = MockChatGenerator ()
931
927
agent = Agent (chat_generator = chat_generator , tools = [weather_tool ])
932
928
933
929
tracing .tracer .is_content_tracing_enabled = True
@@ -962,18 +958,18 @@ async def test_agent_tracing_span_async_run(self, caplog, monkeypatch, weather_t
962
958
963
959
expected_tag_values = [
964
960
"chat_generator" ,
965
- "MockChatGeneratorWithRunAsync " ,
961
+ "MockChatGenerator " ,
966
962
'{"messages": "list", "tools": "list"}' ,
967
- "{}" ,
968
- "{}" ,
963
+ '{"messages": {"type": "list", "senders": []}, "tools": {"type": "typing.Union[list[haystack.tools.tool.Tool], haystack.tools.toolset.Toolset, NoneType]", "senders": []}}' , # noqa: E501
964
+ '{"replies": {"type": "list", "receivers": []}}' ,
969
965
'{"messages": [{"role": "user", "meta": {}, "name": null, "content": [{"text": "What\' s the weather in Paris?"}]}], "tools": [{"type": "haystack.tools.tool.Tool", "data": {"name": "weather_tool", "description": "Provides weather information for a given location.", "parameters": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]}, "function": "test_agent.weather_function", "outputs_to_string": null, "inputs_from_state": null, "outputs_to_state": null}}]}' , # noqa: E501
970
966
1 ,
971
967
'{"replies": [{"role": "assistant", "meta": {}, "name": null, "content": [{"text": "Hello from run_async"}]}]}' , # noqa: E501
972
968
100 ,
973
969
'[{"type": "haystack.tools.tool.Tool", "data": {"name": "weather_tool", "description": "Provides weather information for a given location.", "parameters": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]}, "function": "test_agent.weather_function", "outputs_to_string": null, "inputs_from_state": null, "outputs_to_state": null}}]' , # noqa: E501
974
970
'["text"]' ,
975
971
'{"messages": {"type": "list[haystack.dataclasses.chat_message.ChatMessage]", "handler": "haystack.components.agents.state.state_utils.merge_lists"}}' , # noqa: E501
976
- '{"messages": [{"role": "user", "meta": {}, "name": null, "content": [{"text": "What\' s the weather in Paris?"}]}], "streaming_callback": null}' , # noqa: E501
972
+ '{"messages": [{"role": "user", "meta": {}, "name": null, "content": [{"text": "What\' s the weather in Paris?"}]}], "streaming_callback": null, "break_point": null, "snapshot": null }' , # noqa: E501
977
973
'{"messages": [{"role": "user", "meta": {}, "name": null, "content": [{"text": "What\' s the weather in Paris?"}]}, {"role": "assistant", "meta": {}, "name": null, "content": [{"text": "Hello from run_async"}]}]}' , # noqa: E501
978
974
1 ,
979
975
]
0 commit comments