use std::collections::BTreeMap;
use serde_json::{json, Map, Value};
use super::interactions::{STEP_FUNCTION_CALL, STEP_MODEL_OUTPUT, STEP_THOUGHT};
pub(crate) const DONE_SENTINEL: &str = "[DONE]";
#[derive(Debug, Default)]
pub(crate) struct InteractionStream {
interaction: Map<String, Value>,
steps: BTreeMap<u64, StepBuilder>,
error: Option<Value>,
}
#[derive(Debug, Default)]
struct StepBuilder {
step: Map<String, Value>,
text: String,
signature: String,
arguments: String,
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum StreamAction {
None,
Text(String),
Done,
}
impl InteractionStream {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn push(&mut self, event: &Value) -> StreamAction {
match event
.get("event_type")
.and_then(Value::as_str)
.unwrap_or_default()
{
"interaction.created" | "interaction.completed" => {
if let Some(interaction) = event.get("interaction").and_then(Value::as_object) {
for (key, value) in interaction {
if key == "steps" || is_blank(value) {
continue;
}
self.interaction.insert(key.clone(), value.clone());
}
}
if event.get("event_type").and_then(Value::as_str) == Some("interaction.completed")
{
return StreamAction::Done;
}
StreamAction::None
}
"interaction.status_update" => {
if let Some(status) = event.get("status").filter(|value| !is_blank(value)) {
self.interaction
.insert("status".to_string(), status.clone());
}
StreamAction::None
}
"step.start" => {
let Some(index) = step_index(event) else {
return StreamAction::None;
};
let builder = self.steps.entry(index).or_default();
if let Some(step) = event.get("step").and_then(Value::as_object) {
for (key, value) in step {
builder.step.insert(key.clone(), value.clone());
}
}
StreamAction::None
}
"step.delta" => self.push_delta(event),
"step.stop" => {
if let Some(index) = step_index(event) {
if let Some(builder) = self.steps.get_mut(&index) {
builder.close();
}
}
StreamAction::None
}
"error" => {
self.error = event.get("error").cloned();
StreamAction::Done
}
_ => StreamAction::None,
}
}
fn push_delta(&mut self, event: &Value) -> StreamAction {
let Some(index) = step_index(event) else {
return StreamAction::None;
};
let Some(delta) = event.get("delta") else {
return StreamAction::None;
};
let builder = self.steps.entry(index).or_default();
if let Some(signature) = delta.get("signature").and_then(Value::as_str) {
builder.signature.push_str(signature);
return StreamAction::None;
}
if let Some(arguments) = delta.get("arguments").and_then(Value::as_str) {
builder.arguments.push_str(arguments);
return StreamAction::None;
}
if let Some(text) = delta.get("text").and_then(Value::as_str) {
builder.text.push_str(text);
if builder.step_type() == STEP_MODEL_OUTPUT && !text.is_empty() {
return StreamAction::Text(text.to_string());
}
}
StreamAction::None
}
pub(crate) fn finish(mut self) -> Value {
if let Some(error) = self.error {
return json!({"error": error});
}
let steps: Vec<Value> = self
.steps
.into_values()
.map(|mut builder| {
builder.close();
Value::Object(builder.step)
})
.collect();
self.interaction.insert("steps".to_string(), json!(steps));
Value::Object(self.interaction)
}
}
impl StepBuilder {
fn step_type(&self) -> &str {
self.step
.get("type")
.and_then(Value::as_str)
.unwrap_or_default()
}
fn close(&mut self) {
match self.step_type() {
STEP_THOUGHT => {
if !self.signature.is_empty() {
self.step
.insert("signature".to_string(), json!(self.signature));
}
if !self.text.is_empty() {
self.step.insert(
"summary".to_string(),
json!([{"type": "text", "text": self.text}]),
);
}
}
STEP_MODEL_OUTPUT if !self.text.is_empty() => {
self.step.insert(
"content".to_string(),
json!([{"type": "text", "text": self.text}]),
);
}
STEP_FUNCTION_CALL => {
if let Ok(arguments) = serde_json::from_str::<Value>(&self.arguments) {
self.step.insert("arguments".to_string(), arguments);
}
}
_ => {}
}
}
}
fn step_index(event: &Value) -> Option<u64> {
event.get("index").and_then(Value::as_u64)
}
fn is_blank(value: &Value) -> bool {
match value {
Value::Null => true,
Value::String(text) => text.is_empty(),
_ => false,
}
}