lex-llm-vllm 0.3.13 → 0.3.14

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: a62d4fc4e5b1020ccf9a4bcb5ed9c03096923075c7111357648cc1b360dd804b
4
- data.tar.gz: 1cb2fe7f84f9fcd44b0c04407e62310e5fa4fad59bdfd2bc65889bfa8b9bd213
3
+ metadata.gz: '0459c715b28893afc32019e6f2f831ee08824cd3a0224c17fa25164f5f14a9fc'
4
+ data.tar.gz: 72629182ec192e73316f29d1b491b471a1c8cfe6d87e348f2ce00a16f9df8a1b
5
5
  SHA512:
6
- metadata.gz: 0a68362c7660f1474b943d8d67948e71b3d2b23840b39835937839bf5d2542d0baaa05b569764f77e5268cde1848dfca20e2e16274de6f112756a8eaa1b79ccc
7
- data.tar.gz: de8011c0d1468c8d3f35215f01b7ee3ffa182d47c56dc89ac5fd983dcbf6f9de52ef167fc04d7514b984423081b65f4a8a6f5b463dc6d4372bbcbcdd90d7aa75
6
+ metadata.gz: 60b334c314c6f29257ae7c9a073dfc034536f50f4c58e620137d49d88fb682d12f1abb5882ae792e7a99faffabd3cb01ba990f053f341d10b5478952f527e6bd
7
+ data.tar.gz: 3b602d7548c15a76068645d27da5a94619d7ca1954a2620c8291b2eb4af93384b2a4fe5f241b15487b3b8f7bb99a8294cd474ddac7f94ee1439d9e2d5f76ee08
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.14] - 2026-07-24
4
+
5
+ ### Fixed
6
+ - **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.
7
+ - **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.
8
+
3
9
  ## [0.3.13] - 2026-07-14
4
10
 
5
11
  ### Fixed
@@ -471,10 +471,14 @@ module Legion
471
471
 
472
472
  # Override: delegate SSE chunk parsing to the canonical translator.
473
473
  def build_chunk(data)
474
- canonical_chunk = translator.parse_chunk(data)
475
- return nil if canonical_chunk.nil?
474
+ result = translator.parse_chunk(data)
475
+ return nil if result.nil?
476
476
 
477
- to_legacy_chunk(canonical_chunk, data)
477
+ if result.is_a?(Array)
478
+ result.map { |c| to_legacy_chunk(c, data) }
479
+ else
480
+ to_legacy_chunk(result, data)
481
+ end
478
482
  end
479
483
 
480
484
  def parse_list_models_response(response, provider, capabilities)
@@ -163,23 +163,43 @@ 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
- return build_tool_call_delta_chunk(tool_calls.first, request_id) unless tool_calls.empty?
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
+ return build_tool_call_delta_chunk(tool_calls.first, request_id,
176
+ stop_reason: chunk_stop_reason, usage: chunk_usage)
177
+ end
168
178
 
169
179
  # Thinking delta from reasoning_content
170
180
  reasoning_content = delta['reasoning_content'] || delta['reasoning']
181
+ content = delta['content']
171
182
  unless reasoning_content.to_s.empty?
172
- return Canonical::Chunk.thinking_delta(
183
+ thinking_chunk = Canonical::Chunk.thinking_delta(
173
184
  delta: reasoning_content,
174
185
  request_id: request_id,
175
186
  block_index: delta.dig('content_block', 'index'),
176
- item_id: delta['content_block_start']&.dig('id')
187
+ item_id: delta['content_block_start']&.dig('id'),
188
+ stop_reason: content.to_s.empty? ? chunk_stop_reason : nil,
189
+ usage: content.to_s.empty? ? chunk_usage : nil
177
190
  )
191
+ return thinking_chunk if content.to_s.empty?
192
+
193
+ content_chunk = parse_text_delta_with_thinking(content, request_id, data,
194
+ stop_reason: chunk_stop_reason, usage: chunk_usage)
195
+ return [thinking_chunk, content_chunk]
178
196
  end
179
197
 
180
198
  # Text delta — check for embedded think tags
181
- content = delta['content']
182
- return parse_text_delta_with_thinking(content, request_id, data) unless content.to_s.empty?
199
+ unless content.to_s.empty?
200
+ return parse_text_delta_with_thinking(content, request_id, data,
201
+ stop_reason: chunk_stop_reason, usage: chunk_usage)
202
+ end
183
203
 
184
204
  nil
185
205
  rescue Legion::JSON::ParseError => e
@@ -635,7 +655,7 @@ module Legion
635
655
  # fragments carry id: nil and a raw partial-JSON arguments string.
636
656
  # The StreamAccumulator keys off a nil id to append fragments to the
637
657
  # current tool call, so the id must NOT be synthesized here.
638
- def build_tool_call_delta_chunk(first_call, request_id)
658
+ def build_tool_call_delta_chunk(first_call, request_id, stop_reason: nil, usage: nil)
639
659
  function = first_call.fetch('function', {})
640
660
 
641
661
  tc = Canonical::ToolCall.new(
@@ -649,7 +669,9 @@ module Legion
649
669
  Canonical::Chunk.tool_call_delta(
650
670
  tool_call: tc,
651
671
  request_id: request_id,
652
- block_index: first_call['index']
672
+ block_index: first_call['index'],
673
+ stop_reason: stop_reason,
674
+ usage: usage
653
675
  )
654
676
  end
655
677
 
@@ -667,11 +689,13 @@ module Legion
667
689
  # (Previously called ThinkingExtractor.extract_from_content, which is
668
690
  # private_class_method in lex-llm >= 0.5.0 and raised NoMethodError on
669
691
  # every streamed text delta, silently killing all vLLM streaming.)
670
- def parse_text_delta_with_thinking(content, request_id, data)
692
+ def parse_text_delta_with_thinking(content, request_id, data, stop_reason: nil, usage: nil)
671
693
  Canonical::Chunk.text_delta(
672
694
  delta: content,
673
695
  request_id: request_id,
674
- index: data['index']
696
+ index: data['index'],
697
+ stop_reason: stop_reason,
698
+ usage: usage
675
699
  )
676
700
  end
677
701
 
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Vllm
7
- VERSION = '0.3.13'
7
+ VERSION = '0.3.14'
8
8
  end
9
9
  end
10
10
  end
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.13
4
+ version: 0.3.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO