Skip to main content

rig_core/providers/cohere/
streaming.rs

1use crate::completion::{CompletionError, CompletionRequest, GetTokenUsage};
2use crate::http_client::HttpClientExt;
3use crate::http_client::sse::{Event, GenericEventSource};
4use crate::providers::cohere::CompletionModel;
5use crate::providers::cohere::completion::{CohereCompletionRequest, Usage};
6use crate::streaming::{RawStreamingChoice, RawStreamingToolCall, ToolCallDeltaContent};
7use crate::telemetry::{CompletionOperation, CompletionSpanBuilder, SpanCombinator};
8use crate::{json_utils, streaming};
9use async_stream::stream;
10use futures::StreamExt;
11use serde::{Deserialize, Serialize};
12use tracing::{Level, enabled};
13use tracing_futures::Instrument;
14
15#[derive(Debug, Deserialize)]
16#[serde(rename_all = "kebab-case", tag = "type")]
17enum StreamingEvent {
18    MessageStart,
19    ContentStart,
20    ContentDelta { delta: Option<Delta> },
21    ContentEnd,
22    ToolPlan,
23    ToolCallStart { delta: Option<Delta> },
24    ToolCallDelta { delta: Option<Delta> },
25    ToolCallEnd,
26    MessageEnd { delta: Option<MessageEndDelta> },
27}
28
29#[derive(Debug, Deserialize)]
30struct MessageContentDelta {
31    text: Option<String>,
32}
33
34#[derive(Debug, Deserialize)]
35struct MessageToolFunctionDelta {
36    name: Option<String>,
37    arguments: Option<String>,
38}
39
40#[derive(Debug, Deserialize)]
41struct MessageToolCallDelta {
42    id: Option<String>,
43    function: Option<MessageToolFunctionDelta>,
44}
45
46#[derive(Debug, Deserialize)]
47struct MessageDelta {
48    content: Option<MessageContentDelta>,
49    tool_calls: Option<MessageToolCallDelta>,
50}
51
52#[derive(Debug, Deserialize)]
53struct Delta {
54    message: Option<MessageDelta>,
55}
56
57#[derive(Debug, Deserialize)]
58struct MessageEndDelta {
59    usage: Option<Usage>,
60}
61
62#[derive(Clone, Serialize, Deserialize)]
63pub struct StreamingCompletionResponse {
64    pub usage: Option<Usage>,
65}
66
67impl GetTokenUsage for StreamingCompletionResponse {
68    fn token_usage(&self) -> crate::completion::Usage {
69        let tokens = self
70            .usage
71            .clone()
72            .and_then(|response| response.tokens)
73            .map(|tokens| {
74                (
75                    tokens.input_tokens.map(|x| x as u64),
76                    tokens.output_tokens.map(|y| y as u64),
77                )
78            });
79        let Some((Some(input), Some(output))) = tokens else {
80            return crate::completion::Usage::new();
81        };
82        let mut usage = crate::completion::Usage::new();
83        usage.input_tokens = input;
84        usage.output_tokens = output;
85        usage.total_tokens = input + output;
86
87        usage
88    }
89}
90
91impl<T> CompletionModel<T>
92where
93    T: HttpClientExt + Clone + 'static,
94{
95    pub(crate) async fn stream(
96        &self,
97        request: CompletionRequest,
98    ) -> Result<streaming::StreamingCompletionResponse<StreamingCompletionResponse>, CompletionError>
99    {
100        let system_instructions = request.preamble.clone();
101        let record_telemetry_content = request.record_telemetry_content;
102        let mut request = CohereCompletionRequest::try_from((self.model.as_ref(), request))?;
103        let span = CompletionSpanBuilder::new(
104            "cohere",
105            &request.model,
106            CompletionOperation::ChatStreaming,
107        )
108        .system_instructions(system_instructions.as_deref(), record_telemetry_content)
109        .build();
110
111        let params = json_utils::merge(
112            request.additional_params.unwrap_or(serde_json::json!({})),
113            serde_json::json!({"stream": true}),
114        );
115
116        request.additional_params = Some(params);
117
118        if enabled!(Level::TRACE) {
119            tracing::trace!(
120                target: "rig::streaming",
121                "Cohere streaming completion input: {}",
122                serde_json::to_string_pretty(&request)?
123            );
124        }
125
126        let body = serde_json::to_vec(&request)?;
127
128        let req = self
129            .client
130            .post("/v2/chat")?
131            .body(body)
132            .map_err(|e| CompletionError::HttpError(e.into()))?;
133
134        let mut event_source = GenericEventSource::new(self.client.clone(), req);
135
136        let stream = stream! {
137            let mut current_tool_call: Option<(String, String, String, String)> = None;
138            let mut final_usage = None;
139
140            while let Some(event_result) = event_source.next().await {
141                match event_result {
142                    Ok(Event::Open) => {
143                        tracing::trace!("SSE connection opened");
144                        continue;
145                    }
146
147                    Ok(Event::Message(message)) => {
148                        let data_str = message.data.trim();
149                        if data_str.is_empty() || data_str == "[DONE]" {
150                            continue;
151                        }
152
153                        let event: StreamingEvent = match serde_json::from_str(data_str) {
154                            Ok(ev) => ev,
155                            Err(_) => {
156                                tracing::debug!("Couldn't parse SSE payload as StreamingEvent");
157                                continue;
158                            }
159                        };
160
161                        match event {
162                            StreamingEvent::ContentDelta { delta: Some(delta) } => {
163                                let Some(message) = &delta.message else { continue; };
164                                let Some(content) = &message.content else { continue; };
165                                let Some(text) = &content.text else { continue; };
166
167                                yield Ok(RawStreamingChoice::Message(text.clone()));
168                            },
169
170                            StreamingEvent::MessageEnd { delta: Some(delta) } => {
171                                let span = tracing::Span::current();
172                                span.record_token_usage(&delta.usage);
173                                final_usage = Some(delta.usage.clone());
174                                break;
175                            },
176
177                            StreamingEvent::ToolCallStart { delta: Some(delta) } => {
178                                let Some(message) = &delta.message else { continue; };
179                                let Some(tool_calls) = &message.tool_calls else { continue; };
180                                let Some(id) = tool_calls.id.clone() else { continue; };
181                                let Some(function) = &tool_calls.function else { continue; };
182                                let Some(name) = function.name.clone() else { continue; };
183                                let Some(arguments) = function.arguments.clone() else { continue; };
184
185                                let internal_call_id = crate::id::generate();
186                                current_tool_call = Some((id.clone(), internal_call_id.clone(), name.clone(), arguments));
187
188                                yield Ok(RawStreamingChoice::ToolCallDelta {
189                                    id,
190                                    internal_call_id,
191                                    content: ToolCallDeltaContent::Name(name),
192                                });
193                            },
194
195                            StreamingEvent::ToolCallDelta { delta: Some(delta) } => {
196                                let Some(message) = &delta.message else { continue; };
197                                let Some(tool_calls) = &message.tool_calls else { continue; };
198                                let Some(function) = &tool_calls.function else { continue; };
199                                let Some(arguments) = function.arguments.clone() else { continue; };
200
201                                let Some(tc) = current_tool_call.clone() else { continue; };
202                                current_tool_call = Some((tc.0.clone(), tc.1.clone(), tc.2, format!("{}{}", tc.3, arguments)));
203
204                                // Emit the delta so UI can show progress
205                                yield Ok(RawStreamingChoice::ToolCallDelta {
206                                    id: tc.0,
207                                    internal_call_id: tc.1,
208                                    content: ToolCallDeltaContent::Delta(arguments),
209                                });
210                            },
211
212                            StreamingEvent::ToolCallEnd => {
213                                let Some(tc) = current_tool_call.clone() else { continue; };
214                                let Ok(args) = json_utils::parse_tool_arguments(&tc.3) else { continue; };
215
216                                let raw_tool_call = RawStreamingToolCall::new(tc.0, tc.2, args)
217                                    .with_internal_call_id(tc.1);
218                                yield Ok(RawStreamingChoice::ToolCall(raw_tool_call));
219
220                                current_tool_call = None;
221                            },
222
223                            _ => {}
224                        }
225                    },
226                    Err(crate::http_client::Error::StreamEnded) => {
227                        break;
228                    }
229                    Err(err) => {
230                        tracing::error!(?err, "SSE error");
231                        yield Err(CompletionError::from_stream_transport(err));
232                        break;
233                    }
234                }
235            }
236
237            // Ensure event source is closed when stream ends
238            event_source.close();
239
240            yield Ok(RawStreamingChoice::FinalResponse(StreamingCompletionResponse {
241                usage: final_usage.unwrap_or_default()
242            }))
243        }.instrument(span);
244
245        Ok(streaming::StreamingCompletionResponse::stream(Box::pin(
246            stream,
247        )))
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254    use serde_json::json;
255
256    #[test]
257    fn test_message_content_delta_deserialization() {
258        let json = json!({
259            "type": "content-delta",
260            "delta": {
261                "message": {
262                    "content": {
263                        "text": "Hello world"
264                    }
265                }
266            }
267        });
268
269        let event: StreamingEvent = serde_json::from_value(json).unwrap();
270        match event {
271            StreamingEvent::ContentDelta { delta } => {
272                assert!(delta.is_some());
273                let message = delta.unwrap().message.unwrap();
274                let content = message.content.unwrap();
275                assert_eq!(content.text, Some("Hello world".to_string()));
276            }
277            _ => panic!("Expected ContentDelta"),
278        }
279    }
280
281    #[test]
282    fn test_tool_call_start_deserialization() {
283        let json = json!({
284            "type": "tool-call-start",
285            "delta": {
286                "message": {
287                    "tool_calls": {
288                        "id": "call_123",
289                        "function": {
290                            "name": "get_weather",
291                            "arguments": "{"
292                        }
293                    }
294                }
295            }
296        });
297
298        let event: StreamingEvent = serde_json::from_value(json).unwrap();
299        match event {
300            StreamingEvent::ToolCallStart { delta } => {
301                assert!(delta.is_some());
302                let tool_call = delta.unwrap().message.unwrap().tool_calls.unwrap();
303                assert_eq!(tool_call.id, Some("call_123".to_string()));
304                assert_eq!(
305                    tool_call.function.unwrap().name,
306                    Some("get_weather".to_string())
307                );
308            }
309            _ => panic!("Expected ToolCallStart"),
310        }
311    }
312
313    #[test]
314    fn test_tool_call_delta_deserialization() {
315        let json = json!({
316            "type": "tool-call-delta",
317            "delta": {
318                "message": {
319                    "tool_calls": {
320                        "function": {
321                            "arguments": "\"location\""
322                        }
323                    }
324                }
325            }
326        });
327
328        let event: StreamingEvent = serde_json::from_value(json).unwrap();
329        match event {
330            StreamingEvent::ToolCallDelta { delta } => {
331                assert!(delta.is_some());
332                let tool_call = delta.unwrap().message.unwrap().tool_calls.unwrap();
333                let function = tool_call.function.unwrap();
334                assert_eq!(function.arguments, Some("\"location\"".to_string()));
335            }
336            _ => panic!("Expected ToolCallDelta"),
337        }
338    }
339
340    #[test]
341    fn test_tool_call_end_deserialization() {
342        let json = json!({
343            "type": "tool-call-end"
344        });
345
346        let event: StreamingEvent = serde_json::from_value(json).unwrap();
347        match event {
348            StreamingEvent::ToolCallEnd => {
349                // Success
350            }
351            _ => panic!("Expected ToolCallEnd"),
352        }
353    }
354
355    #[test]
356    fn test_message_end_with_usage_deserialization() {
357        let json = json!({
358            "type": "message-end",
359            "delta": {
360                "usage": {
361                    "tokens": {
362                        "input_tokens": 100,
363                        "output_tokens": 50
364                    }
365                }
366            }
367        });
368
369        let event: StreamingEvent = serde_json::from_value(json).unwrap();
370        match event {
371            StreamingEvent::MessageEnd { delta } => {
372                assert!(delta.is_some());
373                let usage = delta.unwrap().usage.unwrap();
374                let tokens = usage.tokens.unwrap();
375                assert_eq!(tokens.input_tokens, Some(100.0));
376                assert_eq!(tokens.output_tokens, Some(50.0));
377            }
378            _ => panic!("Expected MessageEnd"),
379        }
380    }
381
382    #[test]
383    fn test_streaming_event_order() {
384        // Test that a typical sequence of events deserializes correctly
385        let events = vec![
386            json!({"type": "message-start"}),
387            json!({"type": "content-start"}),
388            json!({
389                "type": "content-delta",
390                "delta": {
391                    "message": {
392                        "content": {
393                            "text": "Sure, "
394                        }
395                    }
396                }
397            }),
398            json!({
399                "type": "content-delta",
400                "delta": {
401                    "message": {
402                        "content": {
403                            "text": "I can help with that."
404                        }
405                    }
406                }
407            }),
408            json!({"type": "content-end"}),
409            json!({"type": "tool-plan"}),
410            json!({
411                "type": "tool-call-start",
412                "delta": {
413                    "message": {
414                        "tool_calls": {
415                            "id": "call_abc",
416                            "function": {
417                                "name": "search",
418                                "arguments": ""
419                            }
420                        }
421                    }
422                }
423            }),
424            json!({
425                "type": "tool-call-delta",
426                "delta": {
427                    "message": {
428                        "tool_calls": {
429                            "function": {
430                                "arguments": "{\"query\":"
431                            }
432                        }
433                    }
434                }
435            }),
436            json!({
437                "type": "tool-call-delta",
438                "delta": {
439                    "message": {
440                        "tool_calls": {
441                            "function": {
442                                "arguments": "\"Rust\"}"
443                            }
444                        }
445                    }
446                }
447            }),
448            json!({"type": "tool-call-end"}),
449            json!({
450                "type": "message-end",
451                "delta": {
452                    "usage": {
453                        "tokens": {
454                            "input_tokens": 50,
455                            "output_tokens": 25
456                        }
457                    }
458                }
459            }),
460        ];
461
462        for (i, event_json) in events.iter().enumerate() {
463            let result = serde_json::from_value::<StreamingEvent>(event_json.clone());
464            assert!(
465                result.is_ok(),
466                "Failed to deserialize event at index {}: {:?}",
467                i,
468                result.err()
469            );
470        }
471    }
472}