gproxy-protocol 4.1.14

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use crate::{
    transform::{Report, TransformError},
    wire::openai::{chat::response as c, responses::input as r},
};

pub(crate) fn to_responses(
    input: Option<Vec<c::Annotation>>,
) -> Result<Vec<r::OutputAnnotation>, TransformError> {
    input
        .unwrap_or_default()
        .into_iter()
        .map(|item| {
            if item.url_citation.start_index < 0
                || item.url_citation.end_index < item.url_citation.start_index
            {
                return Err(TransformError::invalid_result(
                    "annotations",
                    "invalid citation range",
                ));
            }
            Ok(r::OutputAnnotation::Url(r::UrlCitation {
                start_index: item.url_citation.start_index,
                end_index: item.url_citation.end_index,
                title: item.url_citation.title,
                url: item.url_citation.url,
                type_: r::UrlCitationType::UrlCitation,
                rest: Default::default(),
            }))
        })
        .collect()
}

pub(crate) fn to_chat(
    input: Vec<r::OutputAnnotation>,
    offset: i64,
    report: &mut Report,
) -> Result<Vec<c::Annotation>, TransformError> {
    let mut result = Vec::new();
    for item in input {
        match item {
            r::OutputAnnotation::Url(item) => {
                let start = item.start_index.checked_add(offset).ok_or_else(|| {
                    TransformError::invalid_result("annotations", "index overflow")
                })?;
                let end = item.end_index.checked_add(offset).ok_or_else(|| {
                    TransformError::invalid_result("annotations", "index overflow")
                })?;
                if item.start_index < 0 || item.end_index < item.start_index {
                    return Err(TransformError::invalid_result(
                        "annotations",
                        "invalid citation range",
                    ));
                }
                result.push(c::Annotation {
                    type_: c::AnnotationType::UrlCitation,
                    url_citation: c::UrlCitation {
                        start_index: start,
                        end_index: end,
                        title: item.title,
                        url: item.url,
                        rest: Default::default(),
                    },
                    rest: Default::default(),
                });
            }
            r::OutputAnnotation::File(_)
            | r::OutputAnnotation::ContainerFile(_)
            | r::OutputAnnotation::Path(_) => report.omitted(
                "output.content.annotations",
                "Chat annotations cannot represent file citations",
            ),
        }
    }
    Ok(result)
}

fn number(value: f64) -> Result<serde_json::Number, TransformError> {
    serde_json::Number::from_f64(value)
        .ok_or_else(|| TransformError::invalid_result("logprobs", "non-finite log probability"))
}

pub(crate) fn logs_to_responses(
    input: Option<Vec<c::TokenLogprob>>,
    report: &mut Report,
) -> Result<Vec<r::OutputLogprob>, TransformError> {
    let Some(input) = input else {
        return Ok(Vec::new());
    };
    if input
        .iter()
        .any(|p| p.bytes.is_none() || p.top_logprobs.iter().any(|q| q.bytes.is_none()))
    {
        report.omitted(
            "logprobs.content",
            "Responses requires token bytes absent from this Chat logprob record",
        );
        return Ok(Vec::new());
    }
    input
        .into_iter()
        .map(|p| {
            Ok(r::OutputLogprob {
                token: p.token,
                bytes: p.bytes.expect("checked"),
                logprob: number(p.logprob)?,
                top_logprobs: p
                    .top_logprobs
                    .into_iter()
                    .map(|q| {
                        Ok(r::TopLogprob {
                            token: q.token,
                            bytes: q.bytes.expect("checked"),
                            logprob: number(q.logprob)?,
                            rest: Default::default(),
                        })
                    })
                    .collect::<Result<_, TransformError>>()?,
                rest: Default::default(),
            })
        })
        .collect()
}

fn float(value: serde_json::Number) -> Result<f64, TransformError> {
    value.as_f64().filter(|n| n.is_finite()).ok_or_else(|| {
        TransformError::invalid_result("logprobs", "log probability exceeds target numeric range")
    })
}

pub(crate) fn logs_to_chat(
    input: Vec<r::OutputLogprob>,
) -> Result<Vec<c::TokenLogprob>, TransformError> {
    input
        .into_iter()
        .map(|p| {
            Ok(c::TokenLogprob {
                token: p.token,
                bytes: Some(p.bytes),
                logprob: float(p.logprob)?,
                top_logprobs: p
                    .top_logprobs
                    .into_iter()
                    .map(|q| {
                        Ok(c::TokenLogprobTop {
                            token: q.token,
                            bytes: Some(q.bytes),
                            logprob: float(q.logprob)?,
                            rest: Default::default(),
                        })
                    })
                    .collect::<Result<_, TransformError>>()?,
                rest: Default::default(),
            })
        })
        .collect()
}