switchyard-protocol 0.2.0

Provider-neutral LLM protocol types for Switchyard
Documentation
# switchyard-protocol

Provider-neutral request, response, streaming, routing, and metadata types
shared by Switchyard algorithms, clients, and translation codecs. This crate
defines contracts; it does not route, translate, or perform network calls.

## Setup

```toml
[dependencies]
switchyard-protocol = { git = "https://github.com/NVIDIA-NeMo/Switchyard.git" }
serde_json = "1"
```

## Main types

| Area | Types |
|---|---|
| Conversation | [`LlmRequest`], [`Message`], [`InstructionBlock`], [`ContentBlock`] |
| Tools | [`ToolDefinition`], [`ToolChoice`], [`ToolCall`], [`ToolResult`] |
| Response | [`AggLlmResponse`], [`ResponseOutput`], [`Usage`], [`StopReason`] |
| Streaming | [`LlmResponse`], [`LlmResponseStream`], [`LlmResponseStreamEvent`], [`LlmResponseChunk`], [`ProviderStreamEvent`] |
| Envelope | [`Context`], [`Request`], [`Response`], [`Metadata`] |
| Routing I/O | [`Decision`], [`RoutedLlmClient`], [`LlmClientError`] |
| Wire identity | [`WireFormat`], [`FormatId`] |

## Simple request

```rust
use switchyard_protocol::{ContentBlock, LlmRequest, Message, Role};

let request = LlmRequest {
    model: Some("provider/model".into()),
    messages: vec![Message {
        role: Role::User,
        content: vec![ContentBlock::Text {
            text: "Explain tail latency".into(),
        }],
    }],
    ..LlmRequest::default()
};

assert_eq!(request.model.as_deref(), Some("provider/model"));
assert_eq!(request.messages.len(), 1);
```

## Detailed request

Construct the normalized [`Request`] directly when routing needs instructions,
tools, generation controls, and correlation metadata:

```rust
use serde_json::json;
use switchyard_protocol::{
    ContentBlock, InstructionBlock, LlmRequest, Message, Metadata, OutputParams,
    Request, Role, SamplingParams, ToolChoice, ToolDefinition,
};

let request = Request {
    llm_request: LlmRequest {
        model: Some("provider/model".into()),
        instructions: vec![InstructionBlock {
            role: Role::System,
            content: vec![ContentBlock::Text {
                text: "Answer with concise operational guidance.".into(),
            }],
        }],
        messages: vec![Message::text(Role::User, "Why is p99 latency rising?")],
        tools: vec![ToolDefinition {
            name: "lookup_metric".into(),
            description: Some("Read one service metric".into()),
            parameters: json!({
                "type": "object",
                "properties": { "name": { "type": "string" } },
                "required": ["name"]
            }),
            strict: Some(true),
        }],
        tool_choice: Some(ToolChoice::Auto),
        sampling: SamplingParams {
            temperature: Some(0.2),
            ..SamplingParams::default()
        },
        output: OutputParams {
            max_output_tokens: Some(512),
            ..OutputParams::default()
        },
        stream: true,
        ..LlmRequest::default()
    },
    metadata: Some(Metadata {
        session_id: Some("session-42".into()),
        correlation_id: Some("request-7".into()),
        ..Metadata::default()
    }),
    ..Request::default()
};

assert_eq!(request.llm_request.tools[0].name, "lookup_metric");
```

## Response forms

[`LlmResponse`] contains either a completed [`AggLlmResponse`] or a
single-consumption [`LlmResponseStream`] of [`LlmResponseStreamEvent`] values.
Each event carries provider-neutral [`LlmResponseChunk`] values and may retain
one opaque [`ProviderStreamEvent`] for same-format parsed-JSON-value replay. See
[`LlmResponse::into_agg`] for aggregation and [`PreservationMetadata`],
[`Usage`], and [`Metadata`] for the data retained around a response.

## License

Licensed under the Apache License, Version 2.0.