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::SpanCombinator;
8use crate::{json_utils, streaming};
9use async_stream::stream;
10use futures::StreamExt;
11use serde::{Deserialize, Serialize};
12use tracing::{Level, enabled, info_span};
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 mut request = CohereCompletionRequest::try_from((self.model.as_ref(), request))?;
101        let span = if tracing::Span::current().is_disabled() {
102            info_span!(
103                target: "rig::completions",
104                "chat_streaming",
105                gen_ai.operation.name = "chat_streaming",
106                gen_ai.provider.name = "cohere",
107                gen_ai.request.model = self.model,
108                gen_ai.response.id = tracing::field::Empty,
109                gen_ai.response.model = self.model,
110                gen_ai.usage.output_tokens = tracing::field::Empty,
111                gen_ai.usage.input_tokens = tracing::field::Empty,
112                gen_ai.usage.cache_read.input_tokens = tracing::field::Empty,
113            )
114        } else {
115            tracing::Span::current()
116        };
117
118        let params = json_utils::merge(
119            request.additional_params.unwrap_or(serde_json::json!({})),
120            serde_json::json!({"stream": true}),
121        );
122
123        request.additional_params = Some(params);
124
125        if enabled!(Level::TRACE) {
126            tracing::trace!(
127                target: "rig::streaming",
128                "Cohere streaming completion input: {}",
129                serde_json::to_string_pretty(&request)?
130            );
131        }
132
133        let body = serde_json::to_vec(&request)?;
134
135        let req = self
136            .client
137            .post("/v2/chat")?
138            .body(body)
139            .map_err(|e| CompletionError::HttpError(e.into()))?;
140
141        let mut event_source = GenericEventSource::new(self.client.clone(), req);
142
143        let stream = stream! {
144            let mut current_tool_call: Option<(String, String, String, String)> = None;
145            let mut final_usage = None;
146
147            while let Some(event_result) = event_source.next().await {
148                match event_result {
149                    Ok(Event::Open) => {
150                        tracing::trace!("SSE connection opened");
151                        continue;
152                    }
153
154                    Ok(Event::Message(message)) => {
155                        let data_str = message.data.trim();
156                        if data_str.is_empty() || data_str == "[DONE]" {
157                            continue;
158                        }
159
160                        let event: StreamingEvent = match serde_json::from_str(data_str) {
161                            Ok(ev) => ev,
162                            Err(_) => {
163                                tracing::debug!("Couldn't parse SSE payload as StreamingEvent");
164                                continue;
165                            }
166                        };
167
168                        match event {
169                            StreamingEvent::ContentDelta { delta: Some(delta) } => {
170                                let Some(message) = &delta.message else { continue; };
171                                let Some(content) = &message.content else { continue; };
172                                let Some(text) = &content.text else { continue; };
173
174                                yield Ok(RawStreamingChoice::Message(text.clone()));
175                            },
176
177                            StreamingEvent::MessageEnd { delta: Some(delta) } => {
178                                let span = tracing::Span::current();
179                                span.record_token_usage(&delta.usage);
180
181                                final_usage = Some(delta.usage.clone());
182                                break;
183                            },
184
185                            StreamingEvent::ToolCallStart { delta: Some(delta) } => {
186                                let Some(message) = &delta.message else { continue; };
187                                let Some(tool_calls) = &message.tool_calls else { continue; };
188                                let Some(id) = tool_calls.id.clone() else { continue; };
189                                let Some(function) = &tool_calls.function else { continue; };
190                                let Some(name) = function.name.clone() else { continue; };
191                                let Some(arguments) = function.arguments.clone() else { continue; };
192
193                                let internal_call_id = crate::id::generate();
194                                current_tool_call = Some((id.clone(), internal_call_id.clone(), name.clone(), arguments));
195
196                                yield Ok(RawStreamingChoice::ToolCallDelta {
197                                    id,
198                                    internal_call_id,
199                                    content: ToolCallDeltaContent::Name(name),
200                                });
201                            },
202
203                            StreamingEvent::ToolCallDelta { delta: Some(delta) } => {
204                                let Some(message) = &delta.message else { continue; };
205                                let Some(tool_calls) = &message.tool_calls else { continue; };
206                                let Some(function) = &tool_calls.function else { continue; };
207                                let Some(arguments) = function.arguments.clone() else { continue; };
208
209                                let Some(tc) = current_tool_call.clone() else { continue; };
210                                current_tool_call = Some((tc.0.clone(), tc.1.clone(), tc.2, format!("{}{}", tc.3, arguments)));
211
212                                // Emit the delta so UI can show progress
213                                yield Ok(RawStreamingChoice::ToolCallDelta {
214                                    id: tc.0,
215                                    internal_call_id: tc.1,
216                                    content: ToolCallDeltaContent::Delta(arguments),
217                                });
218                            },
219
220                            StreamingEvent::ToolCallEnd => {
221                                let Some(tc) = current_tool_call.clone() else { continue; };
222                                let Ok(args) = json_utils::parse_tool_arguments(&tc.3) else { continue; };
223
224                                let raw_tool_call = RawStreamingToolCall::new(tc.0, tc.2, args)
225                                    .with_internal_call_id(tc.1);
226                                yield Ok(RawStreamingChoice::ToolCall(raw_tool_call));
227
228                                current_tool_call = None;
229                            },
230
231                            _ => {}
232                        }
233                    },
234                    Err(crate::http_client::Error::StreamEnded) => {
235                        break;
236                    }
237                    Err(err) => {
238                        tracing::error!(?err, "SSE error");
239                        yield Err(CompletionError::from_stream_transport(err));
240                        break;
241                    }
242                }
243            }
244
245            // Ensure event source is closed when stream ends
246            event_source.close();
247
248            yield Ok(RawStreamingChoice::FinalResponse(StreamingCompletionResponse {
249                usage: final_usage.unwrap_or_default()
250            }))
251        }.instrument(span);
252
253        Ok(streaming::StreamingCompletionResponse::stream(Box::pin(
254            stream,
255        )))
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262    use serde_json::json;
263
264    #[test]
265    fn test_message_content_delta_deserialization() {
266        let json = json!({
267            "type": "content-delta",
268            "delta": {
269                "message": {
270                    "content": {
271                        "text": "Hello world"
272                    }
273                }
274            }
275        });
276
277        let event: StreamingEvent = serde_json::from_value(json).unwrap();
278        match event {
279            StreamingEvent::ContentDelta { delta } => {
280                assert!(delta.is_some());
281                let message = delta.unwrap().message.unwrap();
282                let content = message.content.unwrap();
283                assert_eq!(content.text, Some("Hello world".to_string()));
284            }
285            _ => panic!("Expected ContentDelta"),
286        }
287    }
288
289    #[test]
290    fn test_tool_call_start_deserialization() {
291        let json = json!({
292            "type": "tool-call-start",
293            "delta": {
294                "message": {
295                    "tool_calls": {
296                        "id": "call_123",
297                        "function": {
298                            "name": "get_weather",
299                            "arguments": "{"
300                        }
301                    }
302                }
303            }
304        });
305
306        let event: StreamingEvent = serde_json::from_value(json).unwrap();
307        match event {
308            StreamingEvent::ToolCallStart { delta } => {
309                assert!(delta.is_some());
310                let tool_call = delta.unwrap().message.unwrap().tool_calls.unwrap();
311                assert_eq!(tool_call.id, Some("call_123".to_string()));
312                assert_eq!(
313                    tool_call.function.unwrap().name,
314                    Some("get_weather".to_string())
315                );
316            }
317            _ => panic!("Expected ToolCallStart"),
318        }
319    }
320
321    #[test]
322    fn test_tool_call_delta_deserialization() {
323        let json = json!({
324            "type": "tool-call-delta",
325            "delta": {
326                "message": {
327                    "tool_calls": {
328                        "function": {
329                            "arguments": "\"location\""
330                        }
331                    }
332                }
333            }
334        });
335
336        let event: StreamingEvent = serde_json::from_value(json).unwrap();
337        match event {
338            StreamingEvent::ToolCallDelta { delta } => {
339                assert!(delta.is_some());
340                let tool_call = delta.unwrap().message.unwrap().tool_calls.unwrap();
341                let function = tool_call.function.unwrap();
342                assert_eq!(function.arguments, Some("\"location\"".to_string()));
343            }
344            _ => panic!("Expected ToolCallDelta"),
345        }
346    }
347
348    #[test]
349    fn test_tool_call_end_deserialization() {
350        let json = json!({
351            "type": "tool-call-end"
352        });
353
354        let event: StreamingEvent = serde_json::from_value(json).unwrap();
355        match event {
356            StreamingEvent::ToolCallEnd => {
357                // Success
358            }
359            _ => panic!("Expected ToolCallEnd"),
360        }
361    }
362
363    #[test]
364    fn test_message_end_with_usage_deserialization() {
365        let json = json!({
366            "type": "message-end",
367            "delta": {
368                "usage": {
369                    "tokens": {
370                        "input_tokens": 100,
371                        "output_tokens": 50
372                    }
373                }
374            }
375        });
376
377        let event: StreamingEvent = serde_json::from_value(json).unwrap();
378        match event {
379            StreamingEvent::MessageEnd { delta } => {
380                assert!(delta.is_some());
381                let usage = delta.unwrap().usage.unwrap();
382                let tokens = usage.tokens.unwrap();
383                assert_eq!(tokens.input_tokens, Some(100.0));
384                assert_eq!(tokens.output_tokens, Some(50.0));
385            }
386            _ => panic!("Expected MessageEnd"),
387        }
388    }
389
390    #[test]
391    fn test_streaming_event_order() {
392        // Test that a typical sequence of events deserializes correctly
393        let events = vec![
394            json!({"type": "message-start"}),
395            json!({"type": "content-start"}),
396            json!({
397                "type": "content-delta",
398                "delta": {
399                    "message": {
400                        "content": {
401                            "text": "Sure, "
402                        }
403                    }
404                }
405            }),
406            json!({
407                "type": "content-delta",
408                "delta": {
409                    "message": {
410                        "content": {
411                            "text": "I can help with that."
412                        }
413                    }
414                }
415            }),
416            json!({"type": "content-end"}),
417            json!({"type": "tool-plan"}),
418            json!({
419                "type": "tool-call-start",
420                "delta": {
421                    "message": {
422                        "tool_calls": {
423                            "id": "call_abc",
424                            "function": {
425                                "name": "search",
426                                "arguments": ""
427                            }
428                        }
429                    }
430                }
431            }),
432            json!({
433                "type": "tool-call-delta",
434                "delta": {
435                    "message": {
436                        "tool_calls": {
437                            "function": {
438                                "arguments": "{\"query\":"
439                            }
440                        }
441                    }
442                }
443            }),
444            json!({
445                "type": "tool-call-delta",
446                "delta": {
447                    "message": {
448                        "tool_calls": {
449                            "function": {
450                                "arguments": "\"Rust\"}"
451                            }
452                        }
453                    }
454                }
455            }),
456            json!({"type": "tool-call-end"}),
457            json!({
458                "type": "message-end",
459                "delta": {
460                    "usage": {
461                        "tokens": {
462                            "input_tokens": 50,
463                            "output_tokens": 25
464                        }
465                    }
466                }
467            }),
468        ];
469
470        for (i, event_json) in events.iter().enumerate() {
471            let result = serde_json::from_value::<StreamingEvent>(event_json.clone());
472            assert!(
473                result.is_ok(),
474                "Failed to deserialize event at index {}: {:?}",
475                i,
476                result.err()
477            );
478        }
479    }
480}