Skip to main content

switchyard_protocol/
lib.rs

1// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4#![warn(missing_docs)]
5#![doc = include_str!("../README.md")]
6
7pub mod client;
8pub mod envelope;
9pub mod format;
10pub mod llm;
11pub mod metadata;
12pub mod model_id;
13pub mod stream;
14
15pub use client::*;
16pub use envelope::*;
17pub use format::*;
18pub use llm::*;
19pub use metadata::*;
20pub use model_id::*;
21pub use stream::*;
22
23/// Builds a single-turn request: one user message carrying `prompt`, for `model`.
24///
25/// This deliberately creates only the common text shape. Construct [`LlmRequest`]
26/// directly for instructions, tools, multimodal content, or sampling controls.
27pub fn text_request(model: Option<String>, prompt: impl Into<String>) -> LlmRequest {
28    LlmRequest {
29        model,
30        messages: vec![Message::text(Role::User, prompt)],
31        ..LlmRequest::default()
32    }
33}
34
35/// Returns a lossy text view of all user messages, joined by newlines.
36///
37/// Only text and refusal blocks are included. Instructions, tool content,
38/// reasoning, and media are omitted. Returns an empty string when no user text exists.
39pub fn prompt_text(request: &LlmRequest) -> String {
40    request
41        .messages
42        .iter()
43        .filter(|message| message.role == Role::User)
44        .filter_map(|message| message.text_content("\n"))
45        .collect::<Vec<_>>()
46        .join("\n")
47}
48
49/// Builds a single-turn response: one assistant message carrying `completion`, for `model`.
50///
51/// Construct [`AggLlmResponse`] directly when usage, tools, reasoning, or multiple
52/// output items must be represented.
53pub fn text_response(model: Option<String>, completion: impl Into<String>) -> AggLlmResponse {
54    AggLlmResponse {
55        model,
56        outputs: vec![ResponseOutput {
57            role: Role::Assistant,
58            content: vec![ContentBlock::Text {
59                text: completion.into(),
60            }],
61            stop_reason: None,
62        }],
63        ..AggLlmResponse::default()
64    }
65}
66
67/// Returns a lossy text view of the first assistant output.
68///
69/// Only text blocks from the first output are concatenated. Refusals, reasoning,
70/// tools, media, and additional outputs are omitted. Returns an empty string when
71/// no such text exists.
72pub fn completion_text(response: &AggLlmResponse) -> String {
73    response
74        .outputs
75        .first()
76        .map(|output| {
77            output
78                .content
79                .iter()
80                .filter_map(|block| match block {
81                    ContentBlock::Text { text } => Some(text.as_str()),
82                    _ => None,
83                })
84                .collect::<String>()
85        })
86        .unwrap_or_default()
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn request_round_trips_prompt_text() {
95        let req = text_request(Some("m".to_string()), "hello world");
96        assert_eq!(req.model.as_deref(), Some("m"));
97        assert_eq!(prompt_text(&req), "hello world");
98    }
99
100    #[test]
101    fn response_round_trips_completion_text() {
102        let resp = text_response(None, "the answer");
103        assert_eq!(completion_text(&resp), "the answer");
104    }
105
106    #[test]
107    fn empty_text_helpers_are_empty_strings() {
108        assert_eq!(prompt_text(&LlmRequest::default()), "");
109        assert_eq!(completion_text(&AggLlmResponse::default()), "");
110    }
111}