llm.rb 16.0.0.pre.4 → 16.0.0.pre.6

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.
data/data/openrouter.json CHANGED
@@ -518,9 +518,9 @@
518
518
  "output": 943718
519
519
  },
520
520
  "cost": {
521
- "input": 0.4,
522
- "output": 10,
523
- "cache_read": 0.4
521
+ "input": 0.29,
522
+ "output": 9,
523
+ "cache_read": 0.28
524
524
  }
525
525
  },
526
526
  "poolside/laguna-s-2.1:free": {
@@ -1859,13 +1859,13 @@
1859
1859
  },
1860
1860
  "open_weights": true,
1861
1861
  "limit": {
1862
- "context": 1310720,
1862
+ "context": 1048576,
1863
1863
  "output": 943718
1864
1864
  },
1865
1865
  "cost": {
1866
- "input": 0.018,
1867
- "output": 0.32,
1868
- "cache_read": 0.018
1866
+ "input": 0.01,
1867
+ "output": 1.28,
1868
+ "cache_read": 0.01
1869
1869
  },
1870
1870
  "canonical_model_id": "deepseek/deepseek-v4-flash-0731"
1871
1871
  },
@@ -1910,9 +1910,9 @@
1910
1910
  "output": 943718
1911
1911
  },
1912
1912
  "cost": {
1913
- "input": 0.3,
1914
- "output": 1.2,
1915
- "cache_read": 0.006
1913
+ "input": 0.0198,
1914
+ "output": 0.396,
1915
+ "cache_read": 0.00291
1916
1916
  },
1917
1917
  "canonical_model_id": "deepseek/deepseek-v4.1-flash"
1918
1918
  },
@@ -2067,12 +2067,12 @@
2067
2067
  "open_weights": true,
2068
2068
  "limit": {
2069
2069
  "context": 1048576,
2070
- "output": 943718
2070
+ "output": 393216
2071
2071
  },
2072
2072
  "cost": {
2073
- "input": 0.4,
2074
- "output": 4.2,
2075
- "cache_read": 0.32
2073
+ "input": 0.66,
2074
+ "output": 1.98,
2075
+ "cache_read": 0.022
2076
2076
  },
2077
2077
  "canonical_model_id": "deepseek/deepseek-v4-pro-0813"
2078
2078
  },
@@ -2219,9 +2219,9 @@
2219
2219
  "output": 384000
2220
2220
  },
2221
2221
  "cost": {
2222
- "input": 0.95526,
2223
- "output": 1.91052,
2224
- "cache_read": 0.079605
2222
+ "input": 0.783,
2223
+ "output": 1.566,
2224
+ "cache_read": 0.06525
2225
2225
  },
2226
2226
  "canonical_model_id": "deepseek/deepseek-v4-pro"
2227
2227
  },
@@ -2335,12 +2335,12 @@
2335
2335
  "open_weights": true,
2336
2336
  "limit": {
2337
2337
  "context": 1048576,
2338
- "output": 131072
2338
+ "output": 384000
2339
2339
  },
2340
2340
  "cost": {
2341
- "input": 0.14,
2342
- "output": 0.28,
2343
- "cache_read": 0.028
2341
+ "input": 0.07854,
2342
+ "output": 0.15708,
2343
+ "cache_read": 0.015708
2344
2344
  },
2345
2345
  "canonical_model_id": "deepseek/deepseek-v4-flash"
2346
2346
  },
@@ -2551,9 +2551,9 @@
2551
2551
  "output": 128000
2552
2552
  },
2553
2553
  "cost": {
2554
- "input": 0.132,
2555
- "output": 0.528,
2556
- "cache_read": 0.033
2554
+ "input": 0.0825,
2555
+ "output": 0.33,
2556
+ "cache_read": 0.020625
2557
2557
  },
2558
2558
  "canonical_model_id": "tencent/hy3"
2559
2559
  },
@@ -2623,9 +2623,9 @@
2623
2623
  "output": 64000
2624
2624
  },
2625
2625
  "cost": {
2626
- "input": 0.834,
2627
- "output": 2.501,
2628
- "cache_read": 0.042
2626
+ "input": 0.7506,
2627
+ "output": 2.2509,
2628
+ "cache_read": 0.0378
2629
2629
  },
2630
2630
  "canonical_model_id": "tencent/hy4-preview"
2631
2631
  },
@@ -3142,7 +3142,7 @@
3142
3142
  "cost": {
3143
3143
  "input": 0.3,
3144
3144
  "output": 0.9,
3145
- "cache_read": 0.05
3145
+ "cache_read": 0.055
3146
3146
  },
3147
3147
  "canonical_model_id": "zhipuai/glm-4.6v"
3148
3148
  },
@@ -3298,7 +3298,7 @@
3298
3298
  },
3299
3299
  "open_weights": true,
3300
3300
  "limit": {
3301
- "context": 1310720,
3301
+ "context": 1048576,
3302
3302
  "output": 943717
3303
3303
  },
3304
3304
  "cost": {
@@ -3590,9 +3590,9 @@
3590
3590
  "output": 943718
3591
3591
  },
3592
3592
  "cost": {
3593
- "input": 0.3249,
3594
- "output": 4.4,
3595
- "cache_read": 0.2599
3593
+ "input": 0.432,
3594
+ "output": 3.99,
3595
+ "cache_read": 0.26
3596
3596
  },
3597
3597
  "canonical_model_id": "zhipuai/glm-5.2"
3598
3598
  },
@@ -3630,9 +3630,9 @@
3630
3630
  "output": 131072
3631
3631
  },
3632
3632
  "cost": {
3633
- "input": 1.4,
3634
- "output": 4.4,
3635
- "cache_read": 0.26
3633
+ "input": 0.9646,
3634
+ "output": 3.0316,
3635
+ "cache_read": 0.17914
3636
3636
  },
3637
3637
  "canonical_model_id": "zhipuai/glm-5.1"
3638
3638
  },
@@ -3746,7 +3746,7 @@
3746
3746
  },
3747
3747
  "open_weights": true,
3748
3748
  "limit": {
3749
- "context": 1310720,
3749
+ "context": 1048576,
3750
3750
  "output": 943717
3751
3751
  },
3752
3752
  "cost": {
@@ -4430,11 +4430,11 @@
4430
4430
  "open_weights": true,
4431
4431
  "limit": {
4432
4432
  "context": 131072,
4433
- "output": 16384
4433
+ "output": 117964
4434
4434
  },
4435
4435
  "cost": {
4436
- "input": 0.3,
4437
- "output": 1.2,
4436
+ "input": 0.35,
4437
+ "output": 1.5,
4438
4438
  "cache_read": 0.04
4439
4439
  },
4440
4440
  "canonical_model_id": "meta/muse-glimmer-30b"
@@ -4758,12 +4758,12 @@
4758
4758
  },
4759
4759
  "open_weights": false,
4760
4760
  "limit": {
4761
- "context": 1310720,
4761
+ "context": 1048576,
4762
4762
  "output": 943718
4763
4763
  },
4764
4764
  "cost": {
4765
4765
  "input": 0.02,
4766
- "output": 0.3,
4766
+ "output": 0.2475,
4767
4767
  "cache_read": 0.01
4768
4768
  }
4769
4769
  },
@@ -4799,13 +4799,13 @@
4799
4799
  },
4800
4800
  "open_weights": false,
4801
4801
  "limit": {
4802
- "context": 1310720,
4802
+ "context": 1048576,
4803
4803
  "output": 131072
4804
4804
  },
4805
4805
  "cost": {
4806
- "input": 0.19,
4806
+ "input": 0.13,
4807
4807
  "output": 4,
4808
- "cache_read": 0.19
4808
+ "cache_read": 0.13
4809
4809
  }
4810
4810
  },
4811
4811
  "arcee-ai/trinity-large-thinking": {
@@ -4984,7 +4984,6 @@
4984
4984
  {
4985
4985
  "type": "effort",
4986
4986
  "values": [
4987
- "none",
4988
4987
  "low",
4989
4988
  "medium",
4990
4989
  "high",
@@ -5017,13 +5016,13 @@
5017
5016
  "cost": {
5018
5017
  "input": 2,
5019
5018
  "output": 10,
5020
- "cache_read": 0.2,
5019
+ "cache_read": 0.1,
5021
5020
  "cache_write": 2.5,
5022
5021
  "tiers": [
5023
5022
  {
5024
5023
  "input": 4,
5025
5024
  "output": 15,
5026
- "cache_read": 0.4,
5025
+ "cache_read": 0.2,
5027
5026
  "cache_write": 5,
5028
5027
  "tier": {
5029
5028
  "type": "context",
@@ -5034,7 +5033,7 @@
5034
5033
  "context_over_200k": {
5035
5034
  "input": 4,
5036
5035
  "output": 15,
5037
- "cache_read": 0.4,
5036
+ "cache_read": 0.2,
5038
5037
  "cache_write": 5
5039
5038
  }
5040
5039
  }
@@ -6072,9 +6071,9 @@
6072
6071
  "output": 943718
6073
6072
  },
6074
6073
  "cost": {
6075
- "input": 0.02,
6076
- "output": 0.6,
6077
- "cache_read": 0.02
6074
+ "input": 0.0198,
6075
+ "output": 0.396,
6076
+ "cache_read": 0.00291
6078
6077
  }
6079
6078
  },
6080
6079
  "~deepseek/deepseek-pro-latest": {
@@ -6113,12 +6112,12 @@
6113
6112
  "open_weights": false,
6114
6113
  "limit": {
6115
6114
  "context": 1048576,
6116
- "output": 943718
6115
+ "output": 393216
6117
6116
  },
6118
6117
  "cost": {
6119
- "input": 0.15808,
6120
- "output": 1.9584,
6121
- "cache_read": 0.07488
6118
+ "input": 0.123,
6119
+ "output": 3.5,
6120
+ "cache_read": 0.123
6122
6121
  }
6123
6122
  },
6124
6123
  "~deepseek/deepseek-v4-flash-latest": {
@@ -6156,13 +6155,13 @@
6156
6155
  },
6157
6156
  "open_weights": false,
6158
6157
  "limit": {
6159
- "context": 1310720,
6158
+ "context": 1048576,
6160
6159
  "output": 943718
6161
6160
  },
6162
6161
  "cost": {
6163
- "input": 0.012,
6164
- "output": 0.7,
6165
- "cache_read": 0.01
6162
+ "input": 0.0099,
6163
+ "output": 0.13068,
6164
+ "cache_read": 0.001386
6166
6165
  }
6167
6166
  },
6168
6167
  "~x-ai/grok-latest": {
@@ -6666,9 +6665,9 @@
6666
6665
  "output": 235929
6667
6666
  },
6668
6667
  "cost": {
6669
- "input": 0.0765,
6670
- "output": 0.255,
6671
- "cache_read": 0.0425
6668
+ "input": 0.09,
6669
+ "output": 0.3,
6670
+ "cache_read": 0.05
6672
6671
  },
6673
6672
  "canonical_model_id": "google/gemma-4-26b-a4b-it"
6674
6673
  },
@@ -9041,8 +9040,8 @@
9041
9040
  "output": 235929
9042
9041
  },
9043
9042
  "cost": {
9044
- "input": 0.6562,
9045
- "output": 3.3,
9043
+ "input": 0.6712,
9044
+ "output": 3.35,
9046
9045
  "cache_read": 0.18
9047
9046
  },
9048
9047
  "canonical_model_id": "moonshotai/kimi-k2.7-code"
@@ -9212,7 +9211,7 @@
9212
9211
  },
9213
9212
  "open_weights": true,
9214
9213
  "limit": {
9215
- "context": 1000000,
9214
+ "context": 262144,
9216
9215
  "output": 32768
9217
9216
  },
9218
9217
  "cost": {
@@ -9717,7 +9716,7 @@
9717
9716
  },
9718
9717
  "open_weights": true,
9719
9718
  "limit": {
9720
- "context": 1048576,
9719
+ "context": 1050000,
9721
9720
  "output": 131072
9722
9721
  },
9723
9722
  "cost": {
@@ -10865,9 +10864,9 @@
10865
10864
  "output": 235929
10866
10865
  },
10867
10866
  "cost": {
10868
- "input": 0.0647,
10869
- "output": 4.4,
10870
- "cache_read": 0.0517
10867
+ "input": 0.0249,
10868
+ "output": 4.35,
10869
+ "cache_read": 0.0199
10871
10870
  },
10872
10871
  "canonical_model_id": "alibaba/qwen3.8-27b"
10873
10872
  },
@@ -11500,7 +11499,7 @@
11500
11499
  "open_weights": true,
11501
11500
  "limit": {
11502
11501
  "context": 262144,
11503
- "output": 235929
11502
+ "output": 65536
11504
11503
  },
11505
11504
  "cost": {
11506
11505
  "input": 0.26,
@@ -12242,11 +12241,11 @@
12242
12241
  "open_weights": true,
12243
12242
  "limit": {
12244
12243
  "context": 131072,
12245
- "output": 8192
12244
+ "output": 16384
12246
12245
  },
12247
12246
  "cost": {
12248
- "input": 0.13,
12249
- "output": 0.52
12247
+ "input": 0.12,
12248
+ "output": 0.5
12250
12249
  },
12251
12250
  "canonical_model_id": "alibaba/qwen3-30b-a3b"
12252
12251
  },
@@ -14148,6 +14147,73 @@
14148
14147
  "output": 2
14149
14148
  }
14150
14149
  },
14150
+ "openai/gpt-6.1-sol": {
14151
+ "id": "openai/gpt-6.1-sol",
14152
+ "name": "GPT-6.1 Sol",
14153
+ "description": "Near-Astra performance for complex coding, computer use, and professional work at a lower cost.",
14154
+ "family": "gpt-sol",
14155
+ "attachment": true,
14156
+ "reasoning": true,
14157
+ "reasoning_options": [
14158
+ {
14159
+ "type": "effort",
14160
+ "values": [
14161
+ "low",
14162
+ "medium",
14163
+ "high",
14164
+ "xhigh",
14165
+ "max"
14166
+ ]
14167
+ }
14168
+ ],
14169
+ "tool_call": true,
14170
+ "structured_output": true,
14171
+ "temperature": false,
14172
+ "knowledge": "2026-04-30",
14173
+ "release_date": "2026-09-29",
14174
+ "last_updated": "2026-09-29",
14175
+ "modalities": {
14176
+ "input": [
14177
+ "text",
14178
+ "image",
14179
+ "pdf"
14180
+ ],
14181
+ "output": [
14182
+ "text"
14183
+ ]
14184
+ },
14185
+ "open_weights": false,
14186
+ "limit": {
14187
+ "context": 1050000,
14188
+ "input": 922000,
14189
+ "output": 128000
14190
+ },
14191
+ "cost": {
14192
+ "input": 2,
14193
+ "output": 10,
14194
+ "cache_read": 0.1,
14195
+ "cache_write": 2.5,
14196
+ "tiers": [
14197
+ {
14198
+ "input": 4,
14199
+ "output": 15,
14200
+ "cache_read": 0.2,
14201
+ "cache_write": 5,
14202
+ "tier": {
14203
+ "type": "context",
14204
+ "size": 272000
14205
+ }
14206
+ }
14207
+ ],
14208
+ "context_over_200k": {
14209
+ "input": 4,
14210
+ "output": 15,
14211
+ "cache_read": 0.2,
14212
+ "cache_write": 5
14213
+ }
14214
+ },
14215
+ "canonical_model_id": "openai/gpt-6.1-sol"
14216
+ },
14151
14217
  "openai/gpt-4o-2024-08-06": {
14152
14218
  "id": "openai/gpt-4o-2024-08-06",
14153
14219
  "name": "GPT-4o (2024-08-06)",
@@ -15311,6 +15377,70 @@
15311
15377
  },
15312
15378
  "canonical_model_id": "openai/gpt-6-luna"
15313
15379
  },
15380
+ "openai/gpt-6.1-sol-pro": {
15381
+ "id": "openai/gpt-6.1-sol-pro",
15382
+ "name": "GPT-6.1 Sol Pro",
15383
+ "description": "Frontier GPT model for professional reasoning, coding, and multimodal work",
15384
+ "family": "gpt",
15385
+ "attachment": true,
15386
+ "reasoning": true,
15387
+ "reasoning_options": [
15388
+ {
15389
+ "type": "effort",
15390
+ "values": [
15391
+ "low",
15392
+ "medium",
15393
+ "high",
15394
+ "xhigh",
15395
+ "max"
15396
+ ]
15397
+ }
15398
+ ],
15399
+ "tool_call": true,
15400
+ "structured_output": true,
15401
+ "temperature": false,
15402
+ "release_date": "2026-09-29",
15403
+ "last_updated": "2026-09-29",
15404
+ "modalities": {
15405
+ "input": [
15406
+ "pdf",
15407
+ "image",
15408
+ "text"
15409
+ ],
15410
+ "output": [
15411
+ "text"
15412
+ ]
15413
+ },
15414
+ "open_weights": false,
15415
+ "limit": {
15416
+ "context": 1050000,
15417
+ "output": 128000
15418
+ },
15419
+ "cost": {
15420
+ "input": 2,
15421
+ "output": 10,
15422
+ "cache_read": 0.1,
15423
+ "cache_write": 2.5,
15424
+ "tiers": [
15425
+ {
15426
+ "input": 4,
15427
+ "output": 15,
15428
+ "cache_read": 0.2,
15429
+ "cache_write": 5,
15430
+ "tier": {
15431
+ "type": "context",
15432
+ "size": 272000
15433
+ }
15434
+ }
15435
+ ],
15436
+ "context_over_200k": {
15437
+ "input": 4,
15438
+ "output": 15,
15439
+ "cache_read": 0.2,
15440
+ "cache_write": 5
15441
+ }
15442
+ }
15443
+ },
15314
15444
  "openai/o4-mini-high": {
15315
15445
  "id": "openai/o4-mini-high",
15316
15446
  "name": "o4 Mini High",
@@ -15552,12 +15682,11 @@
15552
15682
  "open_weights": true,
15553
15683
  "limit": {
15554
15684
  "context": 131072,
15555
- "output": 65536
15685
+ "output": 117964
15556
15686
  },
15557
15687
  "cost": {
15558
- "input": 0.15,
15559
- "output": 0.6,
15560
- "cache_read": 0.075
15688
+ "input": 0.037,
15689
+ "output": 0.17
15561
15690
  },
15562
15691
  "canonical_model_id": "openai/gpt-oss-120b"
15563
15692
  },
@@ -424,7 +424,9 @@ The block receives the prompt object when it takes an argument, and
424
424
  otherwise runs in the prompt's context:
425
425
  [`LLM::Prompt#to_a`](https://r.uby.dev/api-docs/llm.rb/LLM/Prompt.html#to_a)
426
426
  returns the messages in order, and two prompts are equal when their
427
- messages match.
427
+ messages match. Each of `system`, `user`, `developer`, and `talk`
428
+ accepts an `extra:` hash, which is attached to the message it builds,
429
+ so a caller can carry a field of its own alongside the content.
428
430
 
429
431
  #### Why would I use it?
430
432
 
@@ -66,9 +66,9 @@ should use the `console` names.
66
66
  When `bin/llm.rb` starts, it looks for an `AGENTS.md` file in the
67
67
  current working directory. If one exists, its contents become the
68
68
  agent's instructions for the session, so a project can describe how it
69
- wants the agent to work without passing flags. The instructions are
70
- injected once, so a resumed session that already has a system message
71
- keeps the one it has.
69
+ wants the agent to work without passing flags. The agent brings its
70
+ instructions up to date before each request, so a resumed session runs
71
+ on the current file rather than the one it was saved with.
72
72
 
73
73
  The user-message label is exposed through
74
74
  [`LLM::Console#sender`](https://r.uby.dev/api-docs/llm.rb/LLM/Console.html#sender-instance_method),
@@ -34,13 +34,23 @@ and
34
34
  for serialization and
35
35
  [`LLM::Context#restore`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#restore)
36
36
  for deserialization. Save writes the current state, restore loads
37
- it back and picks up where the conversation left off. The ORM
38
- wrappers automate this; each
39
- [`LLM::Agent#talk`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#talk)
40
- or
41
- [`LLM::Agent#ask`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#ask)
42
- call persists the
43
- updated state back to the column automatically.
37
+ it back and picks up where the conversation left off.
38
+
39
+ The ORM wrappers write on a different boundary than the file does, and
40
+ the two are worth telling apart.
41
+ [`LLM::Step`](https://r.uby.dev/api-docs/llm.rb/LLM/Step.html) is
42
+ prepended onto the stream, and it saves the conversation through the
43
+ record the context is bound to each time
44
+ [`LLM::Stream#on_step`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_step-instance_method)
45
+ fires - after a response is in the conversation and before the tools it
46
+ asked for run. A turn that runs tools before it answers is therefore
47
+ saved more than once, and an application that writes it down can
48
+ continue an interrupted turn instead of redoing it.
49
+
50
+ A `path:` has no record behind it to write to, so that one saves once
51
+ per turn. The difference is what each is for: the column is a
52
+ checkpoint after every completed request, and the file is a session at
53
+ the end of one.
44
54
 
45
55
  #### Why would I use it?
46
56
 
@@ -242,12 +252,10 @@ first call, a fresh agent is created and the conversation starts
242
252
  from scratch.
243
253
 
244
254
  On subsequent calls, the stored state is restored and the
245
- conversation continues. Every
246
- [`LLM::Agent#talk`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#talk)
247
- or
248
- [`LLM::Agent#ask`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#ask)
249
- call persists the
250
- updated state back to the column automatically.
255
+ conversation continues. The column is written after each completed
256
+ request rather than at the end of a turn, so a turn that runs tools
257
+ before it answers is saved more than once - and a turn that is
258
+ interrupted can be resumed from its last completed request.
251
259
 
252
260
  The `data_column:` option lets you use a different column name.
253
261
  The `format:` option controls the storage type. Use `:string` for
@@ -327,7 +335,7 @@ agent.talk "perform research"
327
335
  #### Why would I use it?
328
336
 
329
337
  The ActiveRecord wrapper integrates with your existing models.
330
- Agent state is automatically persisted after each conversation turn
338
+ Agent state is automatically persisted after each completed request,
331
339
  using the same `create!`, `save!`, and query methods you already
332
340
  use.
333
341
 
@@ -447,11 +455,8 @@ the plugin in the model class. The `data` column stores
447
455
  the full agent state as JSON, same structure as ActiveRecord.
448
456
  On first call, a fresh agent is created. On subsequent calls,
449
457
  the stored state is restored and the conversation continues.
450
- Every
451
- [`LLM::Agent#talk`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#talk)
452
- or
453
- [`LLM::Agent#ask`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#ask)
454
- call persists the updated state.
458
+ The state is written after each completed request, as it is under
459
+ ActiveRecord.
455
460
 
456
461
  The `data_column:` and `format:` options work identically to
457
462
  ActiveRecord. For `:jsonb`, Sequel loads the `pg_json` extension
@@ -21,8 +21,9 @@ the model responds, and if it requests tools the agent
21
21
  executes them automatically and feeds the results back. The tool
22
22
  loop can be bounded with
23
23
  [`LLM::Agent.tool_budget`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#tool_budget-class_method)
24
- (see the Tool budget section). Instructions are injected once
25
- unless a system message is already present.
24
+ (see the Tool budget section). Instructions are injected once and
25
+ brought up to date from the agent before each request, unless the
26
+ prompt already carries a system message of the caller's own.
26
27
 
27
28
  #### Why would I use it?
28
29
 
@@ -67,6 +67,17 @@ receives tokens as they arrive.
67
67
  fires when the model requests a tool.
68
68
  [`LLM::Stream#on_tool_return`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_tool_return)
69
69
  fires when the tool completes.
70
+ [`LLM::Stream#on_step`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_step-instance_method)
71
+ fires when a request completes, which is the boundary between one
72
+ request and the next: the response is in the conversation, and a
73
+ turn that asked for tools runs them after this while a turn that is
74
+ finished ends here. That boundary is where a conversation is whole,
75
+ and the runtime's
76
+ [`LLM::Step`](https://r.uby.dev/api-docs/llm.rb/LLM/Step.html) is
77
+ written against it - it is prepended onto a stream, and saves the
78
+ conversation through the record the context is bound to, so a turn
79
+ that is interrupted can be continued from its last completed request
80
+ rather than started over.
70
81
  [`LLM::Stream#on_retry`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_retry)
71
82
  fires each time a failed request is retried. Compaction hooks
72
83
  let you show progress or log what was trimmed. Skill hooks bracket a
@@ -96,6 +107,11 @@ class MyStream < LLM::Stream
96
107
  def on_tool_return(tool, result)
97
108
  end
98
109
 
110
+ # A request has completed: the response is in the conversation, and
111
+ # any tools it asked for run after this.
112
+ def on_step(ctx, res)
113
+ end
114
+
99
115
  # Before a transformer rewrites an outgoing message.
100
116
  def on_transform(transformer)
101
117
  end
@@ -135,9 +151,3 @@ agent.talk "Explain Ruby fibers."
135
151
  A stream subclass gives you visibility into more than just content chunks. React to tool
136
152
  calls as they happen, show compaction progress, or integrate with
137
153
  an existing observability stack.
138
-
139
- #### Notes
140
-
141
- The IO-like form is equivalent to
142
- [`LLM::Stream#on_content`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_content)
143
- and does not include the other hooks.