Skip to content

Latest commit

 

History

History
132 lines (104 loc) · 8.49 KB

decisions.md

File metadata and controls

132 lines (104 loc) · 8.49 KB

Decision Model-Related Protocols

A decision model evaluates a context against named questions and returns typed answers. An algorithm can use those answers to choose a model or decide what work to request next.

Switchyard represents this exchange with DecisionRequest and DecisionResponse. These are the types exchanged through Step::CallDecision.

System One is a common API for open decision models, including Jev. Switchyard supports it alongside OpenAI Decisions through the same protocol.

!!! info "Question fields, answer types, and API mappings"

- **[Question reference](../reference/protocols/decision_questions.md):** shared context, question IDs, Boolean/choice/score questions, and provider request mappings.
- **[Answer reference](../reference/protocols/decision_answers.md):** probabilities, rubric scores, confidence, usage, provider response mappings, and errors.

Describe the questions

One request carries shared context and independent questions keyed by ID. Each DecisionQuestion defines what to evaluate and which answer kind to return. The response uses the same question IDs, so the algorithm can match each answer to its question.

Here is a request asking whether a task contains private information. This is request construction only; it does not call a provider.

use std::collections::BTreeMap;
use serde_json::json;
use switchyard_protocol::{DecisionKind, DecisionQuestion, DecisionRequest};

let request = DecisionRequest {
    model: Some("privacy-judge".into()),
    context: json!({"task": "Summarize this customer's account history."}),
    questions: BTreeMap::from([(
        "contains_private_data".into(),
        DecisionQuestion {
            instructions: json!("Does the task contain private customer data?"),
            kind: DecisionKind::Boolean {
                true_description: Some(json!("Contains private customer data")),
                false_description: Some(json!("Contains only public information")),
            },
        },
    )]),
};

For required option IDs, rubric levels, and validation responsibilities, see question construction and validation.

Interpret the answers

DecisionResponse::answers maps the same question IDs to DecisionAnswer values. Each answer separates its typed value from optional provider_confidence. For the privacy question, ProbabilityTrue(Probability(0.8)) means an estimated 0.8 probability that the answer is true. It is not a routing choice until the algorithm applies its policy. Choice questions can name any supported set of candidates; their shape is not limited to two models.

The answer value reference explains rubric scores, distributions, and the difference between a Boolean judgment and an answer probability.

Understand conversion

For supported questions and answers, conversion is a direct, lossless mapping of decision data. IDs, answer probabilities, rubric positions, and confidence retain their meanings. Structured values become JSON text where OpenAI requires strings. This retains their content, rather than making the two wire formats identical or providing an arbitrary JSON round-trip guarantee.

Both adapters return probabilities for Boolean answers. A custom backend that returns only true or false can use BooleanEstimate::Value without inventing certainty. Optional distributions remain None when absent. The OpenAI adapter reports refusals as errors because DecisionValue has no refusal variant.

Own the conversion

When handling Step::CallDecision, use your existing client or a function to convert call.request into the backend's format, send it, and convert the answer into DecisionResponse. Pass that work to call.respond as a future returning libsy::Result<DecisionResponse>.

Match question IDs and kinds, and retain available distributions and confidence separately. The question reference links the System One and OpenAI adapter implementations. Decision conversions live in switchyard-llm-client; switchyard-translation handles LLM formats.

RoutedDecisionClient is an optional interface that lets Switchyard's clients and execution helpers work together. Direct run_stream handling does not require implementing it. See Mechanics of Libsy for the call-handling loop and cancellation behavior.

Use the supplied clients

You can instead use the public SystemOneClient or OpenAiDecisionClient from switchyard-llm-client. Use dependencies at the same API revision as libsy and the protocol crate.

This async excerpt sends the earlier request to System One. Set its model to an ID served by your endpoint. SYSTEM_ONE_ENDPOINT must include /v1/systemone. Running it makes a provider call and may incur a charge. The trait import makes the supplied client's call method available; you do not implement a client in this example.

use std::time::Duration;
use switchyard_llm_client::SystemOneClient;
use switchyard_protocol::RoutedDecisionClient;

let client = SystemOneClient::new(
    std::env::var("SYSTEM_ONE_ENDPOINT")?.parse()?,
    std::env::var("SYSTEM_ONE_API_KEY")?,
    Duration::from_secs(30),
)?;
let response = client.call(request).await?;

OpenAiDecisionClient::new takes the same arguments, with an endpoint ending in /v1/decisions and that provider's key. Each adapter makes one attempt per call; its timeout covers the request and reading the response body.

For the built-in execution path, register decision clients by model ID with ClientRouter::single_with_decision_clients or new_with_decision_clients, then use switchyard_llm_client::run. The algorithm still receives the same typed DecisionResponse whichever client performs the call.