Skip to content

Commit 076ce9d

Browse files
authored
fix(toolcalling): synthesize tool_calls finish_reason when stream lacks one (#11045)
Signed-off-by: Matej Kosec <mkosec@nvidia.com>
1 parent 5245c0f commit 076ce9d

7 files changed

Lines changed: 791 additions & 150 deletions

File tree

lib/llm/src/protocols/openai/chat_completions.rs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use std::collections::HashMap;
55

6-
use dynamo_runtime::protocols::annotated::AnnotationsProvider;
6+
use dynamo_runtime::protocols::annotated::{Annotated, AnnotationsProvider};
77
use serde::{Deserialize, Serialize};
88
use utoipa::ToSchema;
99
use validator::Validate;
@@ -30,8 +30,9 @@ pub use delta::DeltaGenerator;
3030

3131
use dynamo_parsers::tool_calling::{ToolCallResponse, ToolCallResponseChunk};
3232
use dynamo_protocols::types::{
33-
ChatCompletionMessageToolCall, ChatCompletionMessageToolCallChunk, FunctionCall,
34-
FunctionCallStream, FunctionType,
33+
ChatChoiceStream, ChatCompletionMessageContent, ChatCompletionMessageToolCall,
34+
ChatCompletionMessageToolCallChunk, ChatCompletionStreamResponseDelta, FinishReason,
35+
FunctionCall, FunctionCallStream, FunctionType,
3536
};
3637

3738
/// Map a parser-native [`ToolCallResponse`] onto the protocol/wire
@@ -239,6 +240,45 @@ pub struct NvCreateChatCompletionStreamResponse {
239240
pub llm_metrics: Option<crate::protocols::common::metrics::LLMMetricAnnotation>,
240241
}
241242

243+
/// Build one synthetic stream choice from an existing response template.
244+
///
245+
/// Both streaming tool-call paths use this constructor when an engine omits a
246+
/// terminal choice. Accounting data belongs only on the usage chunk and must
247+
/// not be copied onto the synthetic choice.
248+
pub(super) fn stream_choice_chunk_from_template(
249+
template: &NvCreateChatCompletionStreamResponse,
250+
index: u32,
251+
content: Option<ChatCompletionMessageContent>,
252+
tool_calls: Option<Vec<ChatCompletionMessageToolCallChunk>>,
253+
finish_reason: Option<FinishReason>,
254+
) -> Annotated<NvCreateChatCompletionStreamResponse> {
255+
let mut response = template.clone();
256+
response.inner.usage = None;
257+
response.llm_metrics = None;
258+
#[allow(deprecated)]
259+
let choice = ChatChoiceStream {
260+
index,
261+
delta: ChatCompletionStreamResponseDelta {
262+
role: None,
263+
content,
264+
tool_calls,
265+
function_call: None,
266+
refusal: None,
267+
reasoning_content: None,
268+
},
269+
finish_reason,
270+
logprobs: None,
271+
};
272+
response.inner.choices = vec![choice];
273+
Annotated {
274+
data: Some(response),
275+
id: None,
276+
event: None,
277+
comment: None,
278+
error: None,
279+
}
280+
}
281+
242282
/// Implements `NvExtProvider` for `NvCreateChatCompletionRequest`,
243283
/// providing access to NVIDIA-specific extensions.
244284
impl NvExtProvider for NvCreateChatCompletionRequest {

0 commit comments

Comments
 (0)