Skip to content

Commit fd7e02e

Browse files
authored
Fix MistralAIMessage to handle "Tool" Output (#850)
* Content as a String and not an array for Assistants/MistralAI Chat Completion * break up hashes / handle images correctly * clean up assistant spec * update style * fix linter
1 parent 9e93084 commit fd7e02e

File tree

3 files changed

+76
-31
lines changed

3 files changed

+76
-31
lines changed

lib/langchain/assistant/messages/mistral_ai_message.rb

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,30 +45,14 @@ def llm?
4545
#
4646
# @return [Hash] The message as an MistralAI API-compatible hash
4747
def to_hash
48-
{}.tap do |h|
49-
h[:role] = role
50-
51-
if tool_calls.any?
52-
h[:tool_calls] = tool_calls
53-
else
54-
h[:tool_call_id] = tool_call_id if tool_call_id
55-
56-
h[:content] = []
57-
58-
if content && !content.empty?
59-
h[:content] << {
60-
type: "text",
61-
text: content
62-
}
63-
end
64-
65-
if image_url
66-
h[:content] << {
67-
type: "image_url",
68-
image_url: image_url
69-
}
70-
end
71-
end
48+
if assistant?
49+
assistant_hash
50+
elsif system?
51+
system_hash
52+
elsif tool?
53+
tool_hash
54+
elsif user?
55+
user_hash
7256
end
7357
end
7458

@@ -92,6 +76,67 @@ def system?
9276
def tool?
9377
role == "tool"
9478
end
79+
80+
# Convert the message to an MistralAI API-compatible hash
81+
# @return [Hash] The message as an MistralAI API-compatible hash, with the role as "assistant"
82+
def assistant_hash
83+
{
84+
role: "assistant",
85+
content: content,
86+
tool_calls: tool_calls,
87+
prefix: false
88+
}
89+
end
90+
91+
# Convert the message to an MistralAI API-compatible hash
92+
# @return [Hash] The message as an MistralAI API-compatible hash, with the role as "system"
93+
def system_hash
94+
{
95+
role: "system",
96+
content: build_content_array
97+
}
98+
end
99+
100+
# Convert the message to an MistralAI API-compatible hash
101+
# @return [Hash] The message as an MistralAI API-compatible hash, with the role as "tool"
102+
def tool_hash
103+
{
104+
role: "tool",
105+
content: content,
106+
tool_call_id: tool_call_id
107+
}
108+
end
109+
110+
# Convert the message to an MistralAI API-compatible hash
111+
# @return [Hash] The message as an MistralAI API-compatible hash, with the role as "user"
112+
def user_hash
113+
{
114+
role: "user",
115+
content: build_content_array
116+
}
117+
end
118+
119+
# Builds the content value for the message hash
120+
# @return [Array<Hash>] An array of content hashes, with keys :type and :text or :image_url.
121+
def build_content_array
122+
content_details = []
123+
124+
if content && !content.empty?
125+
content_details << {
126+
type: "text",
127+
text: content
128+
}
129+
end
130+
131+
if image_url
132+
content_details << {
133+
type: "image_url",
134+
image_url: image_url
135+
}
136+
end
137+
138+
content_details
139+
end
95140
end
96141
end
97142
end

spec/langchain/assistant/assistant_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -641,14 +641,14 @@
641641
messages: [
642642
{role: "system", content: [{type: "text", text: instructions}]},
643643
{role: "user", content: [{type: "text", text: "Please calculate 2+2"}]},
644-
{role: "assistant", tool_calls: [
644+
{role: "assistant", prefix: false, content: "", tool_calls: [
645645
{
646646
"function" => {"arguments" => "{\"input\":\"2+2\"}", "name" => "langchain_tool_calculator__execute"},
647647
"id" => "call_9TewGANaaIjzY31UCpAAGLeV",
648648
"type" => "function"
649649
}
650650
]},
651-
{content: [{type: "text", text: "4.0"}], role: "tool", tool_call_id: "call_9TewGANaaIjzY31UCpAAGLeV"}
651+
{content: "4.0", role: "tool", tool_call_id: "call_9TewGANaaIjzY31UCpAAGLeV"}
652652
],
653653
tools: calculator.class.function_schemas.to_openai_format,
654654
tool_choice: "auto"

spec/langchain/assistant/messages/mistral_ai_message_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
1010
let(:message) { described_class.new(role: "user", content: "Hello, world!", tool_calls: [], tool_call_id: nil) }
1111

1212
it "returns a hash with the role and content key" do
13-
expect(message.to_hash).to eq({role: "user", content: [{type: "text", text: "Hello, world!"}]})
13+
expect(message.to_hash).to eq({role: "user", content: [{text: "Hello, world!", type: "text"}]})
1414
end
1515
end
1616

1717
context "when tool_call_id is not nil" do
1818
let(:message) { described_class.new(role: "tool", content: "Hello, world!", tool_calls: [], tool_call_id: "123") }
1919

2020
it "returns a hash with the tool_call_id key" do
21-
expect(message.to_hash).to eq({role: "tool", content: [{type: "text", text: "Hello, world!"}], tool_call_id: "123"})
21+
expect(message.to_hash).to eq({role: "tool", content: "Hello, world!", tool_call_id: "123"})
2222
end
2323
end
2424

@@ -32,7 +32,7 @@
3232
let(:message) { described_class.new(role: "assistant", tool_calls: [tool_call], tool_call_id: nil) }
3333

3434
it "returns a hash with the tool_calls key" do
35-
expect(message.to_hash).to eq({role: "assistant", tool_calls: [tool_call]})
35+
expect(message.to_hash).to eq({role: "assistant", tool_calls: [tool_call], content: "", prefix: false})
3636
end
3737
end
3838

@@ -43,8 +43,8 @@
4343
expect(message.to_hash).to eq({
4444
role: "user",
4545
content: [
46-
{type: "text", text: "Please describe this image"},
47-
{type: "image_url", image_url: "https://example.com/image.jpg"}
46+
{text: "Please describe this image", type: "text"},
47+
{image_url: "https://example.com/image.jpg", type: "image_url"}
4848
]
4949
})
5050
end

0 commit comments

Comments
 (0)