Skip to content

Commit d8a1e91

Browse files
committed
feat: Tool call response
1 parent d96ff94 commit d8a1e91

3 files changed

Lines changed: 162 additions & 31 deletions

File tree

aleph_alpha_client/chat.py

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,7 @@ class Role(str, Enum):
2626
User = "user"
2727
Assistant = "assistant"
2828
System = "system"
29-
30-
31-
@dataclass(frozen=True)
32-
class Message:
33-
"""
34-
Describes a message in a chat.
35-
36-
Parameters:
37-
role (Role, required):
38-
The role of the message.
39-
40-
content (str | List[Union[str | Image]], required):
41-
The content of the message.
42-
"""
43-
44-
role: Role
45-
content: Union[str, List[Union[str, Image]]]
46-
47-
def to_json(self) -> Mapping[str, Any]:
48-
result = {
49-
"role": self.role.value,
50-
"content": _message_content_to_json(self.content),
51-
}
52-
return result
29+
Tool = "tool"
5330

5431

5532
@dataclass(frozen=True)
@@ -86,6 +63,36 @@ def to_json(self) -> Mapping[str, Any]:
8663
}
8764

8865

66+
@dataclass(frozen=True)
67+
class Message:
68+
"""
69+
Describes a message in a chat.
70+
71+
Parameters:
72+
role (Role, required):
73+
The role of the message.
74+
75+
content (str | List[Union[str | Image]], required):
76+
The content of the message.
77+
"""
78+
79+
role: Role
80+
content: Union[str, List[Union[str, Image]]]
81+
tool_call_id: Optional[str] = None
82+
tool_calls: Optional[List[ToolCall]] = None
83+
84+
def to_json(self) -> Mapping[str, Any]:
85+
result = {
86+
"role": self.role.value,
87+
"content": _message_content_to_json(self.content),
88+
}
89+
if self.tool_calls:
90+
result["tool_calls"] = [t.to_json() for t in self.tool_calls]
91+
if self.tool_call_id:
92+
result["tool_call_id"] = self.tool_call_id
93+
return result
94+
95+
8996
# We introduce a more specific message type because chat responses can only
9097
# contain text at the moment. This enables static type checking to proof that
9198
# `content` is always a string.

tests/cassettes/test_chat/test_can_chat_with_tools.yaml

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ interactions:
44
messages:
55
- content: You are a helpful assistant.
66
role: system
7+
tool_call_id: null
8+
tool_calls: null
79
- content: What is the weather like in Paris today?
810
role: user
11+
tool_call_id: null
12+
tool_calls: null
913
model: qwen3-32b-tool
1014
tools:
1115
- function:
@@ -27,14 +31,16 @@ interactions:
2731
uri: https://inference-api.stage.product.pharia.com/chat/completions
2832
response:
2933
body:
30-
string: '{"id":"chatcmpl-11b3f640-841a-478a-93cb-0c7ac98fc3da","choices":[{"finish_reason":"tool_calls","index":0,"message":{"role":"assistant","content":"\n\n","reasoning_content":"\nOkay,
34+
string: '{"id":"chatcmpl-94e105fc-b16b-41dc-8def-5b613df69d3e","choices":[{"finish_reason":"tool_calls","index":0,"message":{"role":"assistant","content":"<think>\nOkay,
3135
the user is asking about the weather in Paris today. I need to figure out
3236
which function to use. The available tool is get_weather, which requires a
3337
location parameter. Paris is the city mentioned, and the country is France.
34-
So I should format the location as \"Paris, France\". Let me make sure there
35-
are no other parameters needed. The function only needs the location, so I''ll
36-
construct the tool call with that.\n","tool_calls":[{"id":"chatcmpl-tool-2370633f184e43d8a700b78806cb1083","type":"function","function":{"name":"get_weather","arguments":"{\"location\":
37-
\"Paris, France\"}"}}]},"logprobs":null}],"created":1755691940,"model":"qwen3-32b-tool","system_fingerprint":null,"object":"chat.completion","usage":{"prompt_tokens":188,"completion_tokens":114,"total_tokens":302}}'
38+
So I should format the location as \"Paris, France\". Let me check the function''s
39+
parameters again to make sure. The function needs a location string with city
40+
and country. I''ll call get_weather with Paris, France as the argument. No
41+
other parameters are needed. Alright, that should get the current temperature
42+
for them.\n</think>\n\n","tool_calls":[{"id":"chatcmpl-tool-0b07f5cbf0b44e9e8eeb9e1f821ae1b5","type":"function","function":{"name":"get_weather","arguments":"{\"location\":
43+
\"Paris, France\"}"}}]},"logprobs":null}],"created":1756904242,"model":"qwen3-32b-tool","system_fingerprint":null,"object":"chat.completion","usage":{"prompt_tokens":188,"completion_tokens":139,"total_tokens":327}}'
3844
headers:
3945
Access-Control-Allow-Credentials:
4046
- 'true'
@@ -47,7 +53,104 @@ interactions:
4753
Content-Type:
4854
- application/json
4955
Date:
50-
- Wed, 20 Aug 2025 12:12:23 GMT
56+
- Wed, 03 Sep 2025 12:57:25 GMT
57+
Strict-Transport-Security:
58+
- max-age=31536000; includeSubDomains
59+
Transfer-Encoding:
60+
- chunked
61+
Vary:
62+
- Origin, Access-Control-Request-Method, Access-Control-Request-Headers
63+
- accept-encoding
64+
status:
65+
code: 200
66+
message: OK
67+
- request:
68+
body:
69+
messages:
70+
- content: You are a helpful assistant.
71+
role: system
72+
tool_call_id: null
73+
tool_calls: null
74+
- content: '<think>
75+
76+
Okay, the user is asking about the weather in Paris today. I need to figure
77+
out which function to use. The available tool is get_weather, which requires
78+
a location parameter. Paris is the city mentioned, and the country is France.
79+
So I should format the location as "Paris, France". Let me check the function''s
80+
parameters again to make sure. The function needs a location string with
81+
city and country. I''ll call get_weather with Paris, France as the argument.
82+
No other parameters are needed. Alright, that should get the current temperature
83+
for them.
84+
85+
</think>
86+
87+
88+
'
89+
role: assistant
90+
tool_call_id: null
91+
tool_calls:
92+
- function:
93+
arguments: '{"location": "Paris, France"}'
94+
name: get_weather
95+
id: chatcmpl-tool-0b07f5cbf0b44e9e8eeb9e1f821ae1b5
96+
type: function
97+
- content: Cloudy with a bit of rain.
98+
role: assistant
99+
tool_call_id: chatcmpl-tool-0b07f5cbf0b44e9e8eeb9e1f821ae1b5
100+
tool_calls: null
101+
- content: What is the weather like in Paris today?
102+
role: user
103+
tool_call_id: null
104+
tool_calls: null
105+
model: qwen3-32b-tool
106+
tools:
107+
- function:
108+
description: Get current temperature for a given location.
109+
name: get_weather
110+
parameters:
111+
additionalProperties: false
112+
properties:
113+
location:
114+
description: "City and country e.g. Bogot\xE1, Colombia"
115+
type: string
116+
required:
117+
- location
118+
type: object
119+
strict: true
120+
type: function
121+
headers: {}
122+
method: POST
123+
uri: https://inference-api.stage.product.pharia.com/chat/completions
124+
response:
125+
body:
126+
string: "{\"id\":\"chatcmpl-530cf6e2-6891-4664-9198-43ef0d4faaa1\",\"choices\":[{\"finish_reason\":\"tool_calls\",\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"<think>\\nOkay,
127+
the user is asking about the weather in Paris today. I need to check if I
128+
have the right tool for that.\\n\\nLooking at the tools provided, there's
129+
a function called get_weather that takes a location parameter. The example
130+
uses Bogot\xE1, Colombia, so I should format the location as \\\"Paris, France\\\".\\n\\nI
131+
should call get_weather with location set to Paris, France. The previous response
132+
from the assistant was \\\"Cloudy with a bit of rain.\\\" So maybe the user
133+
wants more details or confirmation. But according to the tool's description,
134+
it returns the current temperature. Wait, the user asked about the weather
135+
condition, not just temperature. Hmm, maybe the function also includes weather
136+
details beyond temperature. But the example response only mentions temperature.
137+
I should stick to the function's parameters and call it correctly. The user's
138+
question is about the weather, so the function should handle it. I'll proceed
139+
to call get_weather for Paris, France.\\n</think>\\n\\n\",\"tool_calls\":[{\"id\":\"chatcmpl-tool-0e1a89fc961042a39da45526bcffdec9\",\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"{\\\"location\\\":
140+
\\\"Paris, France\\\"}\"}}]},\"logprobs\":null}],\"created\":1756904245,\"model\":\"qwen3-32b-tool\",\"system_fingerprint\":null,\"object\":\"chat.completion\",\"usage\":{\"prompt_tokens\":227,\"completion_tokens\":220,\"total_tokens\":447}}"
141+
headers:
142+
Access-Control-Allow-Credentials:
143+
- 'true'
144+
Access-Control-Expose-Headers:
145+
- content-type
146+
Connection:
147+
- keep-alive
148+
Content-Encoding:
149+
- gzip
150+
Content-Type:
151+
- application/json
152+
Date:
153+
- Wed, 03 Sep 2025 12:57:31 GMT
51154
Strict-Transport-Security:
52155
- max-age=31536000; includeSubDomains
53156
Transfer-Encoding:

tests/test_chat.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,28 @@ async def test_can_chat_with_tools(
113113
assert calls[0].type == "function"
114114
assert calls[0].function.name == "get_weather"
115115

116+
request = ChatRequest(
117+
messages=[
118+
system_msg,
119+
Message(
120+
role=response.message.role,
121+
content=response.message.content,
122+
tool_calls=response.message.tool_calls,
123+
),
124+
Message(
125+
role=Role.Assistant,
126+
content="Cloudy with a bit of rain.",
127+
tool_call_id=response.message.tool_calls[0].id,
128+
),
129+
user_msg,
130+
],
131+
model=tool_calling_model_name,
132+
tools=TOOLS,
133+
)
134+
135+
response = await async_client.chat(request, model=tool_calling_model_name)
136+
assert "cloudy" in response.message.content.lower()
137+
116138

117139
@pytest.mark.vcr
118140
async def test_can_chat_with_streaming_support(
@@ -352,7 +374,6 @@ def test_response_format_json_schema(
352374

353375
response = sync_client.chat(request, model=structured_output_model_name)
354376
json_response = json.loads(remove_thinking_content(response.message.content))
355-
356377
# Validate all required fields are present
357378
required_fields = ["nemo", "species", "color", "size_cm"]
358379
for field in required_fields:

0 commit comments

Comments
 (0)