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