rig_core/providers/cohere/
streaming.rs1use 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 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 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 }
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 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}