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