llm_providers 0.4.0 → 0.4.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/llm_providers/providers/google.rb +36 -34
- data/lib/llm_providers/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 65eb7fcee6973ec4cd31c06f1c9b0a04f82682cab0f2799d711dda78d4d09f0d
|
|
4
|
+
data.tar.gz: 9c5ea371c80193690d08e5b412ae1adb9872b2b6b39cb845ef09364dd9abc272
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6c5bd8f0e9eaa26830433d938322eee4cf4292a68a16031a6c088f819b6b311433ebfe5780dd9f95dbbbc7e7ca9eb4dfd08bc43a88f02e8c3552ade590984019
|
|
7
|
+
data.tar.gz: 10fed167a8f5835f60d3a4d475ec46ee81a80eab2e7a774b6fc3a864b016d0a7a823a5101d986ec20dfeab1d49fab985e877a18998d2b52fab237479f481bf9e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.1] - 2026-10-11
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Gemini tool loops failed on the second round with `Role 'function' is not supported`. Tool results are now sent as a `user` turn of `functionResponse` parts, and results for parallel calls share one turn.
|
|
8
|
+
- When a tool message has no `tool_name`, the function response is named after the call it answers (looked up by `tool_call_id`). It used to fall back to `"function"`.
|
|
9
|
+
- Gemini 3 returns a `thoughtSignature` on function calls and rejects the next request without it. Tool calls now carry it as `thought_signature`, and it goes back on the matching `functionCall` part.
|
|
10
|
+
|
|
3
11
|
## [0.4.0] - 2026-10-10
|
|
4
12
|
|
|
5
13
|
### Added
|
|
@@ -65,40 +65,54 @@ module LlmProviders
|
|
|
65
65
|
payload
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
# Gemini only accepts user/model roles. Tool results go back as a user turn of
|
|
69
|
+
# functionResponse parts (one turn per batch of calls), named after the call they
|
|
70
|
+
# answer. Gemini 3 also rejects a functionCall replayed without its thoughtSignature.
|
|
68
71
|
def format_messages(messages)
|
|
69
|
-
|
|
72
|
+
call_names = {}
|
|
73
|
+
|
|
74
|
+
messages.each_with_object([]) do |msg, contents|
|
|
70
75
|
msg = stringify_keys(msg)
|
|
71
76
|
next if msg["role"] == "system"
|
|
72
77
|
|
|
73
|
-
role = msg["role"] == "assistant" ? "model" : msg["role"]
|
|
74
|
-
|
|
75
78
|
if msg["role"] == "tool"
|
|
76
|
-
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
response: { content: msg["content"] }
|
|
82
|
-
}
|
|
83
|
-
}]
|
|
79
|
+
part = {
|
|
80
|
+
functionResponse: {
|
|
81
|
+
name: msg["tool_name"] || call_names[msg["tool_call_id"]] || "function",
|
|
82
|
+
response: { content: msg["content"] }
|
|
83
|
+
}
|
|
84
84
|
}
|
|
85
|
+
if contents.last && contents.last[:function_responses]
|
|
86
|
+
contents.last[:parts] << part
|
|
87
|
+
else
|
|
88
|
+
contents << { role: "user", parts: [part], function_responses: true }
|
|
89
|
+
end
|
|
85
90
|
elsif msg["tool_calls"] && !msg["tool_calls"].empty?
|
|
86
91
|
parts = []
|
|
87
92
|
parts << { text: msg["content"] } if msg["content"] && !msg["content"].empty?
|
|
88
93
|
msg["tool_calls"].each do |tc|
|
|
89
94
|
tc = stringify_keys(tc)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
}
|
|
95
|
+
call_names[tc["id"]] = tc["name"] if tc["id"]
|
|
96
|
+
part = { functionCall: { name: tc["name"], args: tc["input"] || {} } }
|
|
97
|
+
part[:thoughtSignature] = tc["thought_signature"] if tc["thought_signature"]
|
|
98
|
+
parts << part
|
|
96
99
|
end
|
|
97
|
-
{ role:
|
|
100
|
+
contents << { role: "model", parts: parts }
|
|
98
101
|
else
|
|
99
|
-
|
|
102
|
+
role = msg["role"] == "assistant" ? "model" : msg["role"]
|
|
103
|
+
contents << { role: role, parts: [{ text: msg["content"] }] }
|
|
100
104
|
end
|
|
101
|
-
end
|
|
105
|
+
end.map { |content| content.except(:function_responses) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def tool_call_from(part)
|
|
109
|
+
call = {
|
|
110
|
+
id: SecureRandom.hex(12),
|
|
111
|
+
name: part.dig("functionCall", "name"),
|
|
112
|
+
input: part.dig("functionCall", "args") || {}
|
|
113
|
+
}
|
|
114
|
+
call[:thought_signature] = part["thoughtSignature"] if part["thoughtSignature"]
|
|
115
|
+
call
|
|
102
116
|
end
|
|
103
117
|
|
|
104
118
|
def format_tools(tools)
|
|
@@ -205,11 +219,7 @@ module LlmProviders
|
|
|
205
219
|
yield(content: part["text"]) if part["text"] && !part["thought"]
|
|
206
220
|
next unless part["functionCall"]
|
|
207
221
|
|
|
208
|
-
tool_calls <<
|
|
209
|
-
id: SecureRandom.hex(12),
|
|
210
|
-
name: part.dig("functionCall", "name"),
|
|
211
|
-
input: part.dig("functionCall", "args") || {}
|
|
212
|
-
}
|
|
222
|
+
tool_calls << tool_call_from(part)
|
|
213
223
|
end
|
|
214
224
|
end
|
|
215
225
|
|
|
@@ -249,15 +259,7 @@ module LlmProviders
|
|
|
249
259
|
parts = candidate&.dig("content", "parts") || []
|
|
250
260
|
|
|
251
261
|
content = parts.filter_map { |p| p["text"] unless p["thought"] }.join
|
|
252
|
-
tool_calls = parts.filter_map
|
|
253
|
-
next unless p["functionCall"]
|
|
254
|
-
|
|
255
|
-
{
|
|
256
|
-
id: SecureRandom.hex(12),
|
|
257
|
-
name: p.dig("functionCall", "name"),
|
|
258
|
-
input: p.dig("functionCall", "args") || {}
|
|
259
|
-
}
|
|
260
|
-
end
|
|
262
|
+
tool_calls = parts.filter_map { |p| tool_call_from(p) if p["functionCall"] }
|
|
261
263
|
|
|
262
264
|
{
|
|
263
265
|
content: content,
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llm_providers
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kaba
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|