use std::str::FromStr;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum Kind {
Call,
Respond,
#[default]
#[serde(other)]
Unknown,
}
impl Kind {
#[inline]
#[must_use]
pub const fn is_call(&self) -> bool {
matches!(self, Kind::Call)
}
#[inline]
#[must_use]
pub const fn is_respond(&self) -> bool {
matches!(self, Kind::Respond)
}
#[inline]
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(self, Kind::Unknown)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Call {
#[serde(default)]
name: String,
#[serde(default)]
arguments: Value,
}
impl Call {
#[inline]
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
#[must_use]
pub fn arguments(&self) -> &Value {
&self.arguments
}
#[must_use]
pub fn arguments_json(&self) -> String {
serde_json::to_string(&self.arguments).unwrap_or_default()
}
pub fn parse<T>(&self) -> Result<T>
where
T: DeserializeOwned,
{
serde_json::from_value(self.arguments.clone()).map_err(|source| Error::Arguments { source })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Stats {
#[serde(default)]
prefill_tps: f32,
#[serde(default)]
decode_tps: f32,
#[serde(default)]
peak_ram_mb: f32,
}
impl Stats {
#[inline]
#[must_use]
pub const fn prefill_tps(&self) -> f32 {
self.prefill_tps
}
#[inline]
#[must_use]
pub const fn decode_tps(&self) -> f32 {
self.decode_tps
}
#[inline]
#[must_use]
pub const fn peak_ram_mb(&self) -> f32 {
self.peak_ram_mb
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Validation {
#[serde(default)]
ungrounded: Vec<String>,
#[serde(default)]
negation: bool,
}
impl Validation {
#[inline]
#[must_use]
pub fn ungrounded(&self) -> &[String] {
&self.ungrounded
}
#[inline]
#[must_use]
pub const fn negation(&self) -> bool {
self.negation
}
#[inline]
#[must_use]
pub fn is_grounded(&self) -> bool {
self.ungrounded.is_empty() && !self.negation
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[non_exhaustive]
pub struct Completion {
#[serde(rename = "type", default)]
kind: Kind,
#[serde(default = "succeeded")]
success: bool,
#[serde(default)]
error: Option<String>,
#[serde(default)]
error_code: Option<String>,
#[serde(rename = "function_calls", default)]
calls: Vec<Call>,
#[serde(default)]
suppressed_calls: Vec<Call>,
#[serde(default)]
reasoning: Option<String>,
#[serde(default)]
reason: Option<String>,
#[serde(default)]
confidence: f32,
#[serde(flatten)]
stats: Stats,
#[serde(default)]
validation: Validation,
}
fn succeeded() -> bool {
true
}
impl Completion {
#[inline]
#[must_use]
pub const fn kind(&self) -> Kind {
self.kind
}
#[inline]
#[must_use]
pub fn calls(&self) -> &[Call] {
&self.calls
}
#[inline]
#[must_use]
pub fn grounded_calls(&self) -> &[Call] {
if self.validation.is_grounded() {
&self.calls
} else {
&[]
}
}
#[inline]
#[must_use]
pub fn suppressed_calls(&self) -> &[Call] {
&self.suppressed_calls
}
#[inline]
#[must_use]
pub fn reasoning(&self) -> Option<&str> {
self.reasoning.as_deref()
}
#[inline]
#[must_use]
pub fn reason(&self) -> Option<&str> {
self.reason.as_deref()
}
#[inline]
#[must_use]
pub const fn confidence(&self) -> f32 {
self.confidence
}
#[inline]
#[must_use]
pub const fn stats(&self) -> &Stats {
&self.stats
}
#[inline]
#[must_use]
pub const fn validation(&self) -> &Validation {
&self.validation
}
}
impl FromStr for Completion {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let completion: Completion =
serde_json::from_str(s).map_err(|error| Error::envelope(s, error))?;
if completion.success && completion.error.is_none() {
return Ok(completion);
}
let detail = match (&completion.error, &completion.error_code) {
(Some(error), Some(code)) => format!("{error} ({code})"),
(Some(error), None) => error.clone(),
(None, Some(code)) => format!("the engine reported failure {code}"),
(None, None) => "the engine reported a failure without saying why".to_owned(),
};
Err(Error::Complete { detail })
}
}
#[cfg(test)]
mod tests {
use super::*;
const CALL: &str = concat!(
r#"{"type":"call","success":true,"error":null,"error_code":null,"reason":null,"#,
r#""function_calls":[{"name":"set_light","arguments":{"room":"kitchen","on":true}}],"#,
r#""suppressed_calls":[],"#,
r#""reasoning":"room 'kitchen' from query; on true from 'on'","confidence":1.0000,"#,
r#""prefill_tps":1966.8,"decode_tps":809.7,"peak_ram_mb":90.9,"#,
r#""validation":{"ungrounded":[],"negation":false}}"#,
);
const RESPOND: &str = r#"{"type":"respond","success":true,"error":null,"error_code":null,
"reason":null,"function_calls":[],"suppressed_calls":[],"reasoning":null,
"confidence":0.0,"prefill_tps":1902.1,"decode_tps":794.3,"peak_ram_mb":90.9,
"validation":{"ungrounded":[],"negation":false}}"#;
#[test]
fn a_tool_call_envelope_parses() {
let completion: Completion = CALL.parse().expect("the sample envelope parses");
assert!(completion.kind().is_call());
assert_eq!(completion.calls().len(), 1);
assert_eq!(completion.calls()[0].name(), "set_light");
assert_eq!(
completion.calls()[0].arguments_json(),
r#"{"room":"kitchen","on":true}"#
);
assert_eq!(completion.confidence(), 1.0);
assert_eq!(completion.stats().decode_tps(), 809.7);
assert_eq!(completion.stats().prefill_tps(), 1966.8);
assert_eq!(completion.stats().peak_ram_mb(), 90.9);
assert!(completion.validation().is_grounded());
assert_eq!(completion.grounded_calls().len(), 1);
assert!(completion.suppressed_calls().is_empty());
assert!(completion.reasoning().is_some());
assert_eq!(completion.reason(), None);
}
#[test]
fn a_reply_envelope_parses() {
let completion: Completion = RESPOND.parse().expect("the sample envelope parses");
assert!(completion.kind().is_respond());
assert!(completion.calls().is_empty());
assert_eq!(completion.reasoning(), None);
assert_eq!(completion.confidence(), 0.0);
}
#[test]
fn an_error_envelope_becomes_a_complete_error() {
let envelope = r#"{"type":"error","error":"needle_init not called"}"#;
let error = envelope
.parse::<Completion>()
.expect_err("an error envelope is not a completion");
let Error::Complete { detail } = error else {
panic!("expected Error::Complete");
};
assert_eq!(detail, "needle_init not called");
}
#[test]
fn success_false_becomes_a_complete_error() {
let envelope = r#"{"type":"call","success":false,"error":"nope","error_code":"E7"}"#;
let error = envelope
.parse::<Completion>()
.expect_err("success:false is not a completion");
let Error::Complete { detail } = error else {
panic!("expected Error::Complete");
};
assert_eq!(detail, "nope (E7)");
}
#[test]
fn a_new_engine_tag_parses_as_unknown() {
let completion: Completion = r#"{"type":"reflect","success":true}"#
.parse()
.expect("an unknown tag is not a parse failure");
assert!(completion.kind().is_unknown());
}
#[test]
fn a_missing_field_is_not_a_parse_failure() {
let completion: Completion = r#"{"type":"respond"}"#.parse().expect("defaults fill in");
assert_eq!(completion.confidence(), 0.0);
assert_eq!(completion.stats().decode_tps(), 0.0);
assert!(completion.validation().is_grounded());
}
#[test]
fn junk_is_reported_with_its_text() {
let error = "not json"
.parse::<Completion>()
.expect_err("junk is not a completion");
let Error::Envelope { text, .. } = &error else {
panic!("expected Error::Envelope");
};
assert_eq!(text, "not json");
}
#[test]
fn a_negated_turn_has_no_grounded_calls() {
let completion: Completion = r#"{"type":"call",
"function_calls":[{"name":"ping","arguments":{}}],
"validation":{"ungrounded":[],"negation":true}}"#
.parse()
.expect("the envelope parses");
assert_eq!(completion.calls().len(), 1);
assert!(completion.grounded_calls().is_empty());
}
}