llm.rb 4.9.0 → 4.11.0
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 +152 -0
- data/README.md +178 -31
- data/data/anthropic.json +209 -242
- data/data/deepseek.json +15 -15
- data/data/google.json +553 -403
- data/data/openai.json +740 -535
- data/data/xai.json +250 -253
- data/data/zai.json +157 -90
- data/lib/llm/context/deserializer.rb +2 -1
- data/lib/llm/context.rb +58 -2
- data/lib/llm/contract/completion.rb +7 -0
- data/lib/llm/error.rb +4 -0
- data/lib/llm/eventhandler.rb +7 -0
- data/lib/llm/function/registry.rb +106 -0
- data/lib/llm/function/task.rb +39 -0
- data/lib/llm/function.rb +12 -7
- data/lib/llm/mcp/transport/http/event_handler.rb +66 -0
- data/lib/llm/mcp/transport/http.rb +156 -0
- data/lib/llm/mcp/transport/stdio.rb +7 -0
- data/lib/llm/mcp.rb +74 -30
- data/lib/llm/message.rb +9 -2
- data/lib/llm/provider.rb +10 -0
- data/lib/llm/providers/anthropic/response_adapter/completion.rb +6 -0
- data/lib/llm/providers/anthropic/stream_parser.rb +37 -4
- data/lib/llm/providers/anthropic.rb +1 -1
- data/lib/llm/providers/google/response_adapter/completion.rb +12 -5
- data/lib/llm/providers/google/stream_parser.rb +54 -11
- data/lib/llm/providers/google/utils.rb +30 -0
- data/lib/llm/providers/google.rb +2 -0
- data/lib/llm/providers/ollama/response_adapter/completion.rb +6 -0
- data/lib/llm/providers/ollama/stream_parser.rb +10 -4
- data/lib/llm/providers/ollama.rb +1 -1
- data/lib/llm/providers/openai/response_adapter/completion.rb +7 -0
- data/lib/llm/providers/openai/response_adapter/responds.rb +84 -10
- data/lib/llm/providers/openai/responses/stream_parser.rb +63 -4
- data/lib/llm/providers/openai/responses.rb +1 -1
- data/lib/llm/providers/openai/stream_parser.rb +68 -4
- data/lib/llm/providers/openai.rb +1 -1
- data/lib/llm/schema/all_of.rb +31 -0
- data/lib/llm/schema/any_of.rb +31 -0
- data/lib/llm/schema/one_of.rb +31 -0
- data/lib/llm/schema/parser.rb +36 -0
- data/lib/llm/schema.rb +45 -8
- data/lib/llm/stream/queue.rb +51 -0
- data/lib/llm/stream.rb +102 -0
- data/lib/llm/tool.rb +53 -47
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +3 -2
- data/llm.gemspec +2 -2
- metadata +12 -1
data/data/zai.json
CHANGED
|
@@ -8,19 +8,17 @@
|
|
|
8
8
|
"name": "Z.AI",
|
|
9
9
|
"doc": "https://docs.z.ai/guides/overview/pricing",
|
|
10
10
|
"models": {
|
|
11
|
-
"glm-
|
|
12
|
-
"id": "glm-
|
|
13
|
-
"name": "GLM-
|
|
14
|
-
"family": "glm",
|
|
11
|
+
"glm-4.7-flash": {
|
|
12
|
+
"id": "glm-4.7-flash",
|
|
13
|
+
"name": "GLM-4.7-Flash",
|
|
14
|
+
"family": "glm-flash",
|
|
15
15
|
"attachment": false,
|
|
16
16
|
"reasoning": true,
|
|
17
17
|
"tool_call": true,
|
|
18
|
-
"interleaved": {
|
|
19
|
-
"field": "reasoning_content"
|
|
20
|
-
},
|
|
21
18
|
"temperature": true,
|
|
22
|
-
"
|
|
23
|
-
"
|
|
19
|
+
"knowledge": "2025-04",
|
|
20
|
+
"release_date": "2026-01-19",
|
|
21
|
+
"last_updated": "2026-01-19",
|
|
24
22
|
"modalities": {
|
|
25
23
|
"input": [
|
|
26
24
|
"text"
|
|
@@ -31,58 +29,66 @@
|
|
|
31
29
|
},
|
|
32
30
|
"open_weights": true,
|
|
33
31
|
"cost": {
|
|
34
|
-
"input":
|
|
35
|
-
"output":
|
|
36
|
-
"cache_read": 0
|
|
32
|
+
"input": 0,
|
|
33
|
+
"output": 0,
|
|
34
|
+
"cache_read": 0,
|
|
37
35
|
"cache_write": 0
|
|
38
36
|
},
|
|
39
37
|
"limit": {
|
|
40
|
-
"context":
|
|
38
|
+
"context": 200000,
|
|
41
39
|
"output": 131072
|
|
42
40
|
}
|
|
43
41
|
},
|
|
44
|
-
"glm-
|
|
45
|
-
"id": "glm-
|
|
46
|
-
"name": "
|
|
47
|
-
"family": "glm
|
|
48
|
-
"attachment":
|
|
42
|
+
"glm-5v-turbo": {
|
|
43
|
+
"id": "glm-5v-turbo",
|
|
44
|
+
"name": "glm-5v-turbo",
|
|
45
|
+
"family": "glm",
|
|
46
|
+
"attachment": true,
|
|
49
47
|
"reasoning": true,
|
|
50
48
|
"tool_call": true,
|
|
49
|
+
"interleaved": {
|
|
50
|
+
"field": "reasoning_content"
|
|
51
|
+
},
|
|
51
52
|
"temperature": true,
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"last_updated": "2025-07-28",
|
|
53
|
+
"release_date": "2026-04-01",
|
|
54
|
+
"last_updated": "2026-04-01",
|
|
55
55
|
"modalities": {
|
|
56
56
|
"input": [
|
|
57
|
-
"text"
|
|
57
|
+
"text",
|
|
58
|
+
"image",
|
|
59
|
+
"video",
|
|
60
|
+
"pdf"
|
|
58
61
|
],
|
|
59
62
|
"output": [
|
|
60
63
|
"text"
|
|
61
64
|
]
|
|
62
65
|
},
|
|
63
|
-
"open_weights":
|
|
66
|
+
"open_weights": false,
|
|
64
67
|
"cost": {
|
|
65
|
-
"input":
|
|
66
|
-
"output":
|
|
67
|
-
"cache_read": 0.
|
|
68
|
+
"input": 1.2,
|
|
69
|
+
"output": 4,
|
|
70
|
+
"cache_read": 0.24,
|
|
68
71
|
"cache_write": 0
|
|
69
72
|
},
|
|
70
73
|
"limit": {
|
|
71
|
-
"context":
|
|
72
|
-
"output":
|
|
74
|
+
"context": 200000,
|
|
75
|
+
"output": 131072
|
|
73
76
|
}
|
|
74
77
|
},
|
|
75
|
-
"glm-
|
|
76
|
-
"id": "glm-
|
|
77
|
-
"name": "GLM-
|
|
78
|
+
"glm-5-turbo": {
|
|
79
|
+
"id": "glm-5-turbo",
|
|
80
|
+
"name": "GLM-5-Turbo",
|
|
78
81
|
"family": "glm",
|
|
79
82
|
"attachment": false,
|
|
80
83
|
"reasoning": true,
|
|
81
84
|
"tool_call": true,
|
|
85
|
+
"interleaved": {
|
|
86
|
+
"field": "reasoning_content"
|
|
87
|
+
},
|
|
88
|
+
"structured_output": true,
|
|
82
89
|
"temperature": true,
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"last_updated": "2025-07-28",
|
|
90
|
+
"release_date": "2026-03-16",
|
|
91
|
+
"last_updated": "2026-03-16",
|
|
86
92
|
"modalities": {
|
|
87
93
|
"input": [
|
|
88
94
|
"text"
|
|
@@ -91,22 +97,22 @@
|
|
|
91
97
|
"text"
|
|
92
98
|
]
|
|
93
99
|
},
|
|
94
|
-
"open_weights":
|
|
100
|
+
"open_weights": false,
|
|
95
101
|
"cost": {
|
|
96
|
-
"input":
|
|
97
|
-
"output":
|
|
98
|
-
"cache_read": 0.
|
|
102
|
+
"input": 1.2,
|
|
103
|
+
"output": 4,
|
|
104
|
+
"cache_read": 0.24,
|
|
99
105
|
"cache_write": 0
|
|
100
106
|
},
|
|
101
107
|
"limit": {
|
|
102
|
-
"context":
|
|
103
|
-
"output":
|
|
108
|
+
"context": 200000,
|
|
109
|
+
"output": 131072
|
|
104
110
|
}
|
|
105
111
|
},
|
|
106
|
-
"glm-4.5
|
|
107
|
-
"id": "glm-4.5
|
|
108
|
-
"name": "GLM-4.5
|
|
109
|
-
"family": "glm
|
|
112
|
+
"glm-4.5": {
|
|
113
|
+
"id": "glm-4.5",
|
|
114
|
+
"name": "GLM-4.5",
|
|
115
|
+
"family": "glm",
|
|
110
116
|
"attachment": false,
|
|
111
117
|
"reasoning": true,
|
|
112
118
|
"tool_call": true,
|
|
@@ -124,9 +130,9 @@
|
|
|
124
130
|
},
|
|
125
131
|
"open_weights": true,
|
|
126
132
|
"cost": {
|
|
127
|
-
"input": 0,
|
|
128
|
-
"output":
|
|
129
|
-
"cache_read": 0,
|
|
133
|
+
"input": 0.6,
|
|
134
|
+
"output": 2.2,
|
|
135
|
+
"cache_read": 0.11,
|
|
130
136
|
"cache_write": 0
|
|
131
137
|
},
|
|
132
138
|
"limit": {
|
|
@@ -134,9 +140,9 @@
|
|
|
134
140
|
"output": 98304
|
|
135
141
|
}
|
|
136
142
|
},
|
|
137
|
-
"glm-4.7-
|
|
138
|
-
"id": "glm-4.7-
|
|
139
|
-
"name": "GLM-4.7-
|
|
143
|
+
"glm-4.7-flashx": {
|
|
144
|
+
"id": "glm-4.7-flashx",
|
|
145
|
+
"name": "GLM-4.7-FlashX",
|
|
140
146
|
"family": "glm-flash",
|
|
141
147
|
"attachment": false,
|
|
142
148
|
"reasoning": true,
|
|
@@ -155,9 +161,9 @@
|
|
|
155
161
|
},
|
|
156
162
|
"open_weights": true,
|
|
157
163
|
"cost": {
|
|
158
|
-
"input": 0,
|
|
159
|
-
"output": 0,
|
|
160
|
-
"cache_read": 0,
|
|
164
|
+
"input": 0.07,
|
|
165
|
+
"output": 0.4,
|
|
166
|
+
"cache_read": 0.01,
|
|
161
167
|
"cache_write": 0
|
|
162
168
|
},
|
|
163
169
|
"limit": {
|
|
@@ -196,20 +202,48 @@
|
|
|
196
202
|
"output": 131072
|
|
197
203
|
}
|
|
198
204
|
},
|
|
199
|
-
"glm-4.
|
|
200
|
-
"id": "glm-4.
|
|
201
|
-
"name": "GLM-4.
|
|
205
|
+
"glm-4.6v": {
|
|
206
|
+
"id": "glm-4.6v",
|
|
207
|
+
"name": "GLM-4.6V",
|
|
202
208
|
"family": "glm",
|
|
203
|
-
"attachment":
|
|
209
|
+
"attachment": true,
|
|
204
210
|
"reasoning": true,
|
|
205
211
|
"tool_call": true,
|
|
206
|
-
"
|
|
207
|
-
|
|
212
|
+
"temperature": true,
|
|
213
|
+
"knowledge": "2025-04",
|
|
214
|
+
"release_date": "2025-12-08",
|
|
215
|
+
"last_updated": "2025-12-08",
|
|
216
|
+
"modalities": {
|
|
217
|
+
"input": [
|
|
218
|
+
"text",
|
|
219
|
+
"image",
|
|
220
|
+
"video"
|
|
221
|
+
],
|
|
222
|
+
"output": [
|
|
223
|
+
"text"
|
|
224
|
+
]
|
|
208
225
|
},
|
|
226
|
+
"open_weights": true,
|
|
227
|
+
"cost": {
|
|
228
|
+
"input": 0.3,
|
|
229
|
+
"output": 0.9
|
|
230
|
+
},
|
|
231
|
+
"limit": {
|
|
232
|
+
"context": 128000,
|
|
233
|
+
"output": 32768
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
"glm-4.5-flash": {
|
|
237
|
+
"id": "glm-4.5-flash",
|
|
238
|
+
"name": "GLM-4.5-Flash",
|
|
239
|
+
"family": "glm-flash",
|
|
240
|
+
"attachment": false,
|
|
241
|
+
"reasoning": true,
|
|
242
|
+
"tool_call": true,
|
|
209
243
|
"temperature": true,
|
|
210
244
|
"knowledge": "2025-04",
|
|
211
|
-
"release_date": "2025-
|
|
212
|
-
"last_updated": "2025-
|
|
245
|
+
"release_date": "2025-07-28",
|
|
246
|
+
"last_updated": "2025-07-28",
|
|
213
247
|
"modalities": {
|
|
214
248
|
"input": [
|
|
215
249
|
"text"
|
|
@@ -220,19 +254,19 @@
|
|
|
220
254
|
},
|
|
221
255
|
"open_weights": true,
|
|
222
256
|
"cost": {
|
|
223
|
-
"input": 0
|
|
224
|
-
"output":
|
|
225
|
-
"cache_read": 0
|
|
257
|
+
"input": 0,
|
|
258
|
+
"output": 0,
|
|
259
|
+
"cache_read": 0,
|
|
226
260
|
"cache_write": 0
|
|
227
261
|
},
|
|
228
262
|
"limit": {
|
|
229
|
-
"context":
|
|
230
|
-
"output":
|
|
263
|
+
"context": 131072,
|
|
264
|
+
"output": 98304
|
|
231
265
|
}
|
|
232
266
|
},
|
|
233
|
-
"glm-5
|
|
234
|
-
"id": "glm-5
|
|
235
|
-
"name": "GLM-5
|
|
267
|
+
"glm-5": {
|
|
268
|
+
"id": "glm-5",
|
|
269
|
+
"name": "GLM-5",
|
|
236
270
|
"family": "glm",
|
|
237
271
|
"attachment": false,
|
|
238
272
|
"reasoning": true,
|
|
@@ -240,10 +274,9 @@
|
|
|
240
274
|
"interleaved": {
|
|
241
275
|
"field": "reasoning_content"
|
|
242
276
|
},
|
|
243
|
-
"structured_output": true,
|
|
244
277
|
"temperature": true,
|
|
245
|
-
"release_date": "2026-
|
|
246
|
-
"last_updated": "2026-
|
|
278
|
+
"release_date": "2026-02-11",
|
|
279
|
+
"last_updated": "2026-02-11",
|
|
247
280
|
"modalities": {
|
|
248
281
|
"input": [
|
|
249
282
|
"text"
|
|
@@ -252,18 +285,49 @@
|
|
|
252
285
|
"text"
|
|
253
286
|
]
|
|
254
287
|
},
|
|
255
|
-
"open_weights":
|
|
288
|
+
"open_weights": true,
|
|
256
289
|
"cost": {
|
|
257
|
-
"input": 1
|
|
258
|
-
"output":
|
|
259
|
-
"cache_read": 0.
|
|
290
|
+
"input": 1,
|
|
291
|
+
"output": 3.2,
|
|
292
|
+
"cache_read": 0.2,
|
|
260
293
|
"cache_write": 0
|
|
261
294
|
},
|
|
262
295
|
"limit": {
|
|
263
|
-
"context":
|
|
296
|
+
"context": 204800,
|
|
264
297
|
"output": 131072
|
|
265
298
|
}
|
|
266
299
|
},
|
|
300
|
+
"glm-4.5-air": {
|
|
301
|
+
"id": "glm-4.5-air",
|
|
302
|
+
"name": "GLM-4.5-Air",
|
|
303
|
+
"family": "glm-air",
|
|
304
|
+
"attachment": false,
|
|
305
|
+
"reasoning": true,
|
|
306
|
+
"tool_call": true,
|
|
307
|
+
"temperature": true,
|
|
308
|
+
"knowledge": "2025-04",
|
|
309
|
+
"release_date": "2025-07-28",
|
|
310
|
+
"last_updated": "2025-07-28",
|
|
311
|
+
"modalities": {
|
|
312
|
+
"input": [
|
|
313
|
+
"text"
|
|
314
|
+
],
|
|
315
|
+
"output": [
|
|
316
|
+
"text"
|
|
317
|
+
]
|
|
318
|
+
},
|
|
319
|
+
"open_weights": true,
|
|
320
|
+
"cost": {
|
|
321
|
+
"input": 0.2,
|
|
322
|
+
"output": 1.1,
|
|
323
|
+
"cache_read": 0.03,
|
|
324
|
+
"cache_write": 0
|
|
325
|
+
},
|
|
326
|
+
"limit": {
|
|
327
|
+
"context": 131072,
|
|
328
|
+
"output": 98304
|
|
329
|
+
}
|
|
330
|
+
},
|
|
267
331
|
"glm-4.5v": {
|
|
268
332
|
"id": "glm-4.5v",
|
|
269
333
|
"name": "GLM-4.5V",
|
|
@@ -295,22 +359,23 @@
|
|
|
295
359
|
"output": 16384
|
|
296
360
|
}
|
|
297
361
|
},
|
|
298
|
-
"glm-4.
|
|
299
|
-
"id": "glm-4.
|
|
300
|
-
"name": "GLM-4.
|
|
362
|
+
"glm-4.7": {
|
|
363
|
+
"id": "glm-4.7",
|
|
364
|
+
"name": "GLM-4.7",
|
|
301
365
|
"family": "glm",
|
|
302
|
-
"attachment":
|
|
366
|
+
"attachment": false,
|
|
303
367
|
"reasoning": true,
|
|
304
368
|
"tool_call": true,
|
|
369
|
+
"interleaved": {
|
|
370
|
+
"field": "reasoning_content"
|
|
371
|
+
},
|
|
305
372
|
"temperature": true,
|
|
306
373
|
"knowledge": "2025-04",
|
|
307
|
-
"release_date": "2025-12-
|
|
308
|
-
"last_updated": "2025-12-
|
|
374
|
+
"release_date": "2025-12-22",
|
|
375
|
+
"last_updated": "2025-12-22",
|
|
309
376
|
"modalities": {
|
|
310
377
|
"input": [
|
|
311
|
-
"text"
|
|
312
|
-
"image",
|
|
313
|
-
"video"
|
|
378
|
+
"text"
|
|
314
379
|
],
|
|
315
380
|
"output": [
|
|
316
381
|
"text"
|
|
@@ -318,12 +383,14 @@
|
|
|
318
383
|
},
|
|
319
384
|
"open_weights": true,
|
|
320
385
|
"cost": {
|
|
321
|
-
"input": 0.
|
|
322
|
-
"output":
|
|
386
|
+
"input": 0.6,
|
|
387
|
+
"output": 2.2,
|
|
388
|
+
"cache_read": 0.11,
|
|
389
|
+
"cache_write": 0
|
|
323
390
|
},
|
|
324
391
|
"limit": {
|
|
325
|
-
"context":
|
|
326
|
-
"output":
|
|
392
|
+
"context": 204800,
|
|
393
|
+
"output": 131072
|
|
327
394
|
}
|
|
328
395
|
}
|
|
329
396
|
}
|
|
@@ -12,7 +12,8 @@ class LLM::Context
|
|
|
12
12
|
returns = deserialize_returns(payload["content"]) if returns.nil?
|
|
13
13
|
original_tool_calls = payload["original_tool_calls"]
|
|
14
14
|
usage = payload["usage"]
|
|
15
|
-
|
|
15
|
+
reasoning_content = payload["reasoning_content"]
|
|
16
|
+
extra = {tool_calls:, original_tool_calls:, tools: @params[:tools], usage:, reasoning_content:}.compact
|
|
16
17
|
content = returns.nil? ? payload["content"] : returns
|
|
17
18
|
LLM::Message.new(payload["role"], content, extra)
|
|
18
19
|
end
|
data/lib/llm/context.rb
CHANGED
|
@@ -42,6 +42,11 @@ module LLM
|
|
|
42
42
|
# @return [LLM::Provider]
|
|
43
43
|
attr_reader :llm
|
|
44
44
|
|
|
45
|
+
##
|
|
46
|
+
# Returns the context mode
|
|
47
|
+
# @return [Symbol]
|
|
48
|
+
attr_reader :mode
|
|
49
|
+
|
|
45
50
|
##
|
|
46
51
|
# @param [LLM::Provider] llm
|
|
47
52
|
# A provider
|
|
@@ -49,10 +54,12 @@ module LLM
|
|
|
49
54
|
# The parameters to maintain throughout the conversation.
|
|
50
55
|
# Any parameter the provider supports can be included and
|
|
51
56
|
# not only those listed here.
|
|
57
|
+
# @option params [Symbol] :mode Defaults to :completions
|
|
52
58
|
# @option params [String] :model Defaults to the provider's default model
|
|
53
59
|
# @option params [Array<LLM::Function>, nil] :tools Defaults to nil
|
|
54
60
|
def initialize(llm, params = {})
|
|
55
61
|
@llm = llm
|
|
62
|
+
@mode = params.delete(:mode) || :completions
|
|
56
63
|
@params = {model: llm.default_model, schema: nil}.compact.merge!(params)
|
|
57
64
|
@messages = LLM::Buffer.new(llm)
|
|
58
65
|
end
|
|
@@ -70,6 +77,7 @@ module LLM
|
|
|
70
77
|
# res = ctx.talk("Hello, what is your name?")
|
|
71
78
|
# puts res.messages[0].content
|
|
72
79
|
def talk(prompt, params = {})
|
|
80
|
+
return respond(prompt, params) if mode == :responses
|
|
73
81
|
params = params.merge(messages: @messages.to_a)
|
|
74
82
|
params = @params.merge(params)
|
|
75
83
|
res = @llm.complete(prompt, params)
|
|
@@ -109,7 +117,7 @@ module LLM
|
|
|
109
117
|
# @return [String]
|
|
110
118
|
def inspect
|
|
111
119
|
"#<#{self.class.name}:0x#{object_id.to_s(16)} " \
|
|
112
|
-
"@llm=#{@llm.class}, @params=#{@params.inspect}, " \
|
|
120
|
+
"@llm=#{@llm.class}, @mode=#{@mode.inspect}, @params=#{@params.inspect}, " \
|
|
113
121
|
"@messages=#{@messages.inspect}>"
|
|
114
122
|
end
|
|
115
123
|
|
|
@@ -117,10 +125,11 @@ module LLM
|
|
|
117
125
|
# Returns an array of functions that can be called
|
|
118
126
|
# @return [Array<LLM::Function>]
|
|
119
127
|
def functions
|
|
128
|
+
return_ids = returns.map(&:id)
|
|
120
129
|
@messages
|
|
121
130
|
.select(&:assistant?)
|
|
122
131
|
.flat_map do |msg|
|
|
123
|
-
fns = msg.functions.select
|
|
132
|
+
fns = msg.functions.select { _1.pending? && !return_ids.include?(_1.id) }
|
|
124
133
|
fns.each do |fn|
|
|
125
134
|
fn.tracer = tracer
|
|
126
135
|
fn.model = msg.model
|
|
@@ -128,6 +137,53 @@ module LLM
|
|
|
128
137
|
end.extend(LLM::Function::Array)
|
|
129
138
|
end
|
|
130
139
|
|
|
140
|
+
##
|
|
141
|
+
# Calls a named collection of work through the context.
|
|
142
|
+
#
|
|
143
|
+
# This currently supports `:functions`, forwarding to `functions.call`.
|
|
144
|
+
#
|
|
145
|
+
# @param [Symbol] target
|
|
146
|
+
# The work collection to call
|
|
147
|
+
# @return [Array<LLM::Function::Return>]
|
|
148
|
+
def call(target)
|
|
149
|
+
case target
|
|
150
|
+
when :functions then functions.call
|
|
151
|
+
else raise ArgumentError, "Unknown target: #{target.inspect}. Expected :functions"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
##
|
|
156
|
+
# Returns tool returns accumulated in this context
|
|
157
|
+
# @return [Array<LLM::Function::Return>]
|
|
158
|
+
def returns
|
|
159
|
+
@messages
|
|
160
|
+
.select(&:tool_return?)
|
|
161
|
+
.flat_map do |msg|
|
|
162
|
+
LLM::Function::Return === msg.content ?
|
|
163
|
+
[msg.content] :
|
|
164
|
+
[*msg.content].grep(LLM::Function::Return)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
##
|
|
169
|
+
# Waits for queued tool work to finish.
|
|
170
|
+
#
|
|
171
|
+
# This prefers queued streamed tool work when the configured stream
|
|
172
|
+
# exposes a non-empty queue. Otherwise it falls back to waiting on
|
|
173
|
+
# the context's pending functions directly.
|
|
174
|
+
#
|
|
175
|
+
# @param [Symbol] strategy
|
|
176
|
+
# The concurrency strategy to use
|
|
177
|
+
# @return [Array<LLM::Function::Return>]
|
|
178
|
+
def wait(strategy)
|
|
179
|
+
stream = @params[:stream]
|
|
180
|
+
if LLM::Stream === stream && !stream.queue.empty?
|
|
181
|
+
stream.wait(strategy)
|
|
182
|
+
else
|
|
183
|
+
functions.wait(strategy)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
131
187
|
##
|
|
132
188
|
# Returns token usage accumulated in this context
|
|
133
189
|
# @note
|
|
@@ -50,6 +50,13 @@ module LLM::Contract
|
|
|
50
50
|
messages.find(&:assistant?).content
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
##
|
|
54
|
+
# @return [String, nil]
|
|
55
|
+
# Returns the reasoning content when the provider exposes it
|
|
56
|
+
def reasoning_content
|
|
57
|
+
messages.find(&:assistant?)&.reasoning_content
|
|
58
|
+
end
|
|
59
|
+
|
|
53
60
|
##
|
|
54
61
|
# @return [Hash]
|
|
55
62
|
# Returns the LLM response after parsing it as JSON
|
data/lib/llm/error.rb
CHANGED
|
@@ -55,6 +55,10 @@ module LLM
|
|
|
55
55
|
# When stuck in a tool call loop
|
|
56
56
|
ToolLoopError = Class.new(Error)
|
|
57
57
|
|
|
58
|
+
##
|
|
59
|
+
# When a tool call cannot be mapped to a local tool
|
|
60
|
+
NoSuchToolError = Class.new(Error)
|
|
61
|
+
|
|
58
62
|
##
|
|
59
63
|
# When {LLM::Registry} can't map a model
|
|
60
64
|
NoSuchModelError = Class.new(Error)
|
data/lib/llm/eventhandler.rb
CHANGED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Function
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Function::Registry LLM::Function::Registry} module provides
|
|
6
|
+
# shared registry behavior for functions and tools. {LLM::Tool.registry}
|
|
7
|
+
# stores {LLM::Tool LLM::Tool} subclasses, including dynamically created MCP
|
|
8
|
+
# tool subclasses, while {LLM::Function.registry} stores the functions
|
|
9
|
+
# derived from those tools.
|
|
10
|
+
#
|
|
11
|
+
# The registry overwrites older tool definitions with newer ones when they
|
|
12
|
+
# share the same tool name. In practice, tool identity is resolved by name,
|
|
13
|
+
# and LLMs generally do not allow two tools with the same name.
|
|
14
|
+
#
|
|
15
|
+
# Functions defined with {LLM.function} are not added to the function
|
|
16
|
+
# registry, since they may be closures bound to local state. Each registry
|
|
17
|
+
# decides how entries are keyed via {#registry_key}.
|
|
18
|
+
module Registry
|
|
19
|
+
##
|
|
20
|
+
# @api private
|
|
21
|
+
def self.extended(klass)
|
|
22
|
+
klass.instance_variable_set(:@__registry, {})
|
|
23
|
+
klass.instance_variable_set(:@__names, {})
|
|
24
|
+
klass.instance_variable_set(:@__monitor, Monitor.new)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Returns all registered entries.
|
|
29
|
+
# @return [Array<LLM::Function, LLM::Tool>]
|
|
30
|
+
def registry
|
|
31
|
+
lock do
|
|
32
|
+
@__registry.values
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# Finds a registered entry by name.
|
|
38
|
+
# @param [String] name
|
|
39
|
+
# @return [LLM::Function, LLM::Tool, nil]
|
|
40
|
+
def find_by_name(name)
|
|
41
|
+
lock do
|
|
42
|
+
@__names[name.to_s] ||= @__registry.each_value.find do
|
|
43
|
+
tool_name(_1).to_s == name.to_s
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Clears the registry.
|
|
50
|
+
# @return [void]
|
|
51
|
+
def clear_registry!
|
|
52
|
+
lock do
|
|
53
|
+
@__registry.clear
|
|
54
|
+
@__names.clear
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Registers an entry.
|
|
61
|
+
# @param [LLM::Function, LLM::Tool] entry
|
|
62
|
+
# @api private
|
|
63
|
+
def register(entry)
|
|
64
|
+
lock do
|
|
65
|
+
@__registry[registry_key(entry)] = entry
|
|
66
|
+
@__names[tool_name(entry).to_s] = entry if tool_name(entry)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# Unregisters an entry.
|
|
72
|
+
# @param [LLM::Function, LLM::Tool] entry
|
|
73
|
+
# @api private
|
|
74
|
+
def unregister(entry)
|
|
75
|
+
lock do
|
|
76
|
+
@__registry.delete(registry_key(entry))
|
|
77
|
+
@__registry.delete(entry)
|
|
78
|
+
@__names.delete(tool_name(entry).to_s) if tool_name(entry)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
##
|
|
83
|
+
# Returns the storage key for an entry.
|
|
84
|
+
# @param [LLM::Function, LLM::Tool] entry
|
|
85
|
+
# @return [Class<LLM::Tool>, String, nil]
|
|
86
|
+
# @api private
|
|
87
|
+
def registry_key(entry)
|
|
88
|
+
tool_name(entry) ? entry.name : entry
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
# Returns the tool name, or nil for tools that are not fully initialized.
|
|
93
|
+
# @param [LLM::Function, LLM::Tool] entry
|
|
94
|
+
# @return [String, nil]
|
|
95
|
+
# @api private
|
|
96
|
+
def tool_name(entry)
|
|
97
|
+
entry.respond_to?(:name) ? entry.name : nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
# @api private
|
|
102
|
+
def lock(&)
|
|
103
|
+
@__monitor.synchronize(&)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|