lex-llm-vllm 0.3.13 → 0.3.16
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a53b31d5fe4efcc8b9451b0934c52bcbf71a4af70e493cfe82d7ba75f40ce68
|
|
4
|
+
data.tar.gz: a567a34e2f14e2cf0497b98926527fa9895040981e19e0340006bdf143640ef4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0a64470ff262c97f5cd99b02c066cf24ffb2872ae7dcde10597e9869f3cfc2be89ffc4d07bf9bb43bd83b83d161cfc8d987f25121f87e9cf4f8770abe66fb358
|
|
7
|
+
data.tar.gz: cd4b89c6a0297f70232cf882480b24f903517caec5ed9f7129a61e0dc0d65bb599126cc1fa7cceccd388c8411035db74d737ff15351d7c3ad4c9433823fd5e37
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.16] - 2026-07-31
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **`parse_chunk` now handles multiple tool_calls batched in a single SSE delta.** vLLM can batch several parallel tool_calls into one `choices[0].delta.tool_calls` array. Previously only `tool_calls.first` was processed — the 2nd+ tool calls were silently dropped, causing parallel tool invocations to lose calls. Now returns an array of `tool_call_delta` chunks (one per tool_call), which the streaming handler already iterates (per lex-llm 0.6.13). Single tool_call deltas still return a single chunk (not wrapped in an array) for backward compatibility.
|
|
7
|
+
|
|
8
|
+
## [0.3.15] - 2026-07-31
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **`to_legacy_chunk` now propagates `stop_reason` from canonical chunks.** The canonical translator correctly parsed vLLM's `finish_reason` into `Canonical::Chunk.stop_reason`, but `to_legacy_chunk` never passed it into the legacy `Legion::Extensions::Llm::Chunk` constructor. The `StreamAccumulator` already reads `chunk.stop_reason` (line 40), so the field was silently nil on every streamed chunk — downstream always defaulted to `:end_turn` regardless of what the provider actually said. Truncated responses (`:max_tokens`) and content-filtered responses (`:content_filter`) were indistinguishable from clean completions. Now the real finish_reason flows through: canonical chunk → legacy chunk → accumulator → assembled Message.
|
|
12
|
+
|
|
13
|
+
## [0.3.14] - 2026-07-24
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- **Translator passes `finish_reason` and `usage` through on all streaming chunk types.** vLLM sends `finish_reason` on the same chunk as the last content token. Previously the translator dropped it because the early-return pattern only emitted finish_reason when the delta was empty. Now `text_delta`, `tool_call_delta`, and `thinking_delta` chunks carry `stop_reason` and `usage` when present on the SSE event, enabling the accumulator to capture the real provider signal.
|
|
17
|
+
- **Translator returns both thinking and content when vLLM sends both on the same SSE chunk.** vLLM emits `reasoning` and `content` simultaneously on the boundary between thinking and visible output. Previously the first-branch-wins pattern dropped content when reasoning was present, causing visible characters to disappear from responses (e.g. numbered list items losing their leading digit). `parse_chunk` now returns an array `[thinking_delta, text_delta]` when both fields are present, and the streaming handler iterates them.
|
|
18
|
+
|
|
3
19
|
## [0.3.13] - 2026-07-14
|
|
4
20
|
|
|
5
21
|
### Fixed
|
data/lex-llm-vllm.gemspec
CHANGED
|
@@ -27,5 +27,5 @@ Gem::Specification.new do |spec|
|
|
|
27
27
|
spec.add_dependency 'legion-logging', '>= 1.3.2'
|
|
28
28
|
spec.add_dependency 'legion-settings', '>= 1.3.14'
|
|
29
29
|
spec.add_dependency 'legion-transport', '>= 1.4.14'
|
|
30
|
-
spec.add_dependency 'lex-llm', '>= 0.6.
|
|
30
|
+
spec.add_dependency 'lex-llm', '>= 0.6.13'
|
|
31
31
|
end
|
|
@@ -363,6 +363,7 @@ module Legion
|
|
|
363
363
|
thinking: thinking,
|
|
364
364
|
input_tokens: usage.respond_to?(:input_tokens) ? usage.input_tokens : nil,
|
|
365
365
|
output_tokens: usage.respond_to?(:output_tokens) ? usage.output_tokens : nil,
|
|
366
|
+
stop_reason: canonical.stop_reason,
|
|
366
367
|
raw: raw_data
|
|
367
368
|
)
|
|
368
369
|
end
|
|
@@ -471,10 +472,14 @@ module Legion
|
|
|
471
472
|
|
|
472
473
|
# Override: delegate SSE chunk parsing to the canonical translator.
|
|
473
474
|
def build_chunk(data)
|
|
474
|
-
|
|
475
|
-
return nil if
|
|
475
|
+
result = translator.parse_chunk(data)
|
|
476
|
+
return nil if result.nil?
|
|
476
477
|
|
|
477
|
-
|
|
478
|
+
if result.is_a?(Array)
|
|
479
|
+
result.map { |c| to_legacy_chunk(c, data) }
|
|
480
|
+
else
|
|
481
|
+
to_legacy_chunk(result, data)
|
|
482
|
+
end
|
|
478
483
|
end
|
|
479
484
|
|
|
480
485
|
def parse_list_models_response(response, provider, capabilities)
|
|
@@ -163,23 +163,46 @@ module Legion
|
|
|
163
163
|
)
|
|
164
164
|
end
|
|
165
165
|
|
|
166
|
+
chunk_stop_reason = finish_reason ? map_stop_reason(finish_reason) : nil
|
|
167
|
+
chunk_usage = finish_reason && data['usage'] ? Canonical::Usage.from_hash(data['usage']) : nil
|
|
168
|
+
|
|
166
169
|
tool_calls = Array(delta['tool_calls'])
|
|
167
|
-
|
|
170
|
+
unless tool_calls.empty?
|
|
171
|
+
if delta['content'] && !delta['content'].to_s.empty?
|
|
172
|
+
log.debug '[vllm][translator] action=content_dropped_with_tool_call ' \
|
|
173
|
+
"content=#{delta['content'][0, 100].inspect} request_id=#{request_id}"
|
|
174
|
+
end
|
|
175
|
+
chunks = tool_calls.map do |tc|
|
|
176
|
+
build_tool_call_delta_chunk(tc, request_id,
|
|
177
|
+
stop_reason: chunk_stop_reason, usage: chunk_usage)
|
|
178
|
+
end
|
|
179
|
+
return chunks.size == 1 ? chunks.first : chunks
|
|
180
|
+
end
|
|
168
181
|
|
|
169
182
|
# Thinking delta from reasoning_content
|
|
170
183
|
reasoning_content = delta['reasoning_content'] || delta['reasoning']
|
|
184
|
+
content = delta['content']
|
|
171
185
|
unless reasoning_content.to_s.empty?
|
|
172
|
-
|
|
186
|
+
thinking_chunk = Canonical::Chunk.thinking_delta(
|
|
173
187
|
delta: reasoning_content,
|
|
174
188
|
request_id: request_id,
|
|
175
189
|
block_index: delta.dig('content_block', 'index'),
|
|
176
|
-
item_id: delta['content_block_start']&.dig('id')
|
|
190
|
+
item_id: delta['content_block_start']&.dig('id'),
|
|
191
|
+
stop_reason: content.to_s.empty? ? chunk_stop_reason : nil,
|
|
192
|
+
usage: content.to_s.empty? ? chunk_usage : nil
|
|
177
193
|
)
|
|
194
|
+
return thinking_chunk if content.to_s.empty?
|
|
195
|
+
|
|
196
|
+
content_chunk = parse_text_delta_with_thinking(content, request_id, data,
|
|
197
|
+
stop_reason: chunk_stop_reason, usage: chunk_usage)
|
|
198
|
+
return [thinking_chunk, content_chunk]
|
|
178
199
|
end
|
|
179
200
|
|
|
180
201
|
# Text delta — check for embedded think tags
|
|
181
|
-
|
|
182
|
-
|
|
202
|
+
unless content.to_s.empty?
|
|
203
|
+
return parse_text_delta_with_thinking(content, request_id, data,
|
|
204
|
+
stop_reason: chunk_stop_reason, usage: chunk_usage)
|
|
205
|
+
end
|
|
183
206
|
|
|
184
207
|
nil
|
|
185
208
|
rescue Legion::JSON::ParseError => e
|
|
@@ -635,7 +658,7 @@ module Legion
|
|
|
635
658
|
# fragments carry id: nil and a raw partial-JSON arguments string.
|
|
636
659
|
# The StreamAccumulator keys off a nil id to append fragments to the
|
|
637
660
|
# current tool call, so the id must NOT be synthesized here.
|
|
638
|
-
def build_tool_call_delta_chunk(first_call, request_id)
|
|
661
|
+
def build_tool_call_delta_chunk(first_call, request_id, stop_reason: nil, usage: nil)
|
|
639
662
|
function = first_call.fetch('function', {})
|
|
640
663
|
|
|
641
664
|
tc = Canonical::ToolCall.new(
|
|
@@ -649,7 +672,9 @@ module Legion
|
|
|
649
672
|
Canonical::Chunk.tool_call_delta(
|
|
650
673
|
tool_call: tc,
|
|
651
674
|
request_id: request_id,
|
|
652
|
-
block_index: first_call['index']
|
|
675
|
+
block_index: first_call['index'],
|
|
676
|
+
stop_reason: stop_reason,
|
|
677
|
+
usage: usage
|
|
653
678
|
)
|
|
654
679
|
end
|
|
655
680
|
|
|
@@ -667,11 +692,13 @@ module Legion
|
|
|
667
692
|
# (Previously called ThinkingExtractor.extract_from_content, which is
|
|
668
693
|
# private_class_method in lex-llm >= 0.5.0 and raised NoMethodError on
|
|
669
694
|
# every streamed text delta, silently killing all vLLM streaming.)
|
|
670
|
-
def parse_text_delta_with_thinking(content, request_id, data)
|
|
695
|
+
def parse_text_delta_with_thinking(content, request_id, data, stop_reason: nil, usage: nil)
|
|
671
696
|
Canonical::Chunk.text_delta(
|
|
672
697
|
delta: content,
|
|
673
698
|
request_id: request_id,
|
|
674
|
-
index: data['index']
|
|
699
|
+
index: data['index'],
|
|
700
|
+
stop_reason: stop_reason,
|
|
701
|
+
usage: usage
|
|
675
702
|
)
|
|
676
703
|
end
|
|
677
704
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-llm-vllm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.16
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LegionIO
|
|
@@ -71,14 +71,14 @@ dependencies:
|
|
|
71
71
|
requirements:
|
|
72
72
|
- - ">="
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: 0.6.
|
|
74
|
+
version: 0.6.13
|
|
75
75
|
type: :runtime
|
|
76
76
|
prerelease: false
|
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: 0.6.
|
|
81
|
+
version: 0.6.13
|
|
82
82
|
description: vLLM provider integration for the LegionIO LLM routing framework.
|
|
83
83
|
email:
|
|
84
84
|
- matthewdiverson@gmail.com
|