Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Inference API

myco-model provides backend-independent messages and streaming drivers for Anthropic Messages, OpenAI Responses, and OpenAI Chat Completions. It has no dependency on Myco's server, profile configuration, tool runtime, or session store.

Start with GenerativeModel, GenerativeModelConfig, and Message.

Construct a driver

myco_model::new takes a ModelSpec, matching BackendConfig, system prompt, and tool catalog. ModelSpec::key is your application's name for a model; api_id is what the endpoint receives. The backend variant must match the spec's protocol. Credentials and endpoint settings are supplied directly; the library does not read ~/.myco or load .env for you.

This complete example sends one prompt to a Chat Completions compatible endpoint, streams text, and validates the completed response:

use std::io::{self, Write};

use futures::StreamExt;
use myco_model::{
    BackendConfig, Content, ContentDelta, GenerativeModelConfig, Message, MessageAccumulator,
    MessagePart, ModelSpec, OpenAIBackendConfig, Protocol, ThinkingMode,
};

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let model = myco_model::new(GenerativeModelConfig {
        model: ModelSpec {
            key: "example".into(),
            api_id: std::env::var("MYCO_EXAMPLE_MODEL")?,
            protocol: Protocol::OpenAICompletions,
            thinking: ThinkingMode::None,
            context_window_tokens: 32768,
            max_image_base64_bytes: 5 * 1024 * 1024,
            max_truncated_resumes: 0,
            auto_compact_at_tokens: None,
        },
        tools: vec![],
        system_prompt: "Give a brief, helpful answer.".into(),
        backend_config: BackendConfig::OpenAICompletions(OpenAIBackendConfig {
            base_url: std::env::var("MYCO_EXAMPLE_BASE_URL")?,
            auth_token: std::env::var("MYCO_EXAMPLE_API_KEY").unwrap_or_default(),
            effort: None,
            max_output_tokens: Some(512),
            ..Default::default()
        }),
    })?;
    let history = vec![Message::UserMessage {
        content: vec![Content::Text {
            text: "What makes a good library interface?".into(),
        }],
    }];

    let mut stream = model.generate(&history);
    let mut response = MessageAccumulator::default();
    while let Some(event) = stream.next().await {
        // This example performs one attempt; errors propagate to the caller.
        let part = event.into_result()?;
        response.push(&part)?;
        if let MessagePart::ContentDelta(ContentDelta::Text { delta, .. }) = part {
            print!("{delta}");
            io::stdout().flush()?;
        }
    }
    let output = response.finish()?;
    println!();
    eprintln!(
        "stop: {:?}; usage: {:?}",
        output.turn_end_reason, output.usage
    );
    Ok(())
}

Run it from the repository with a server and model you have configured:

MYCO_EXAMPLE_BASE_URL=http://localhost:11434/v1 \
MYCO_EXAMPLE_MODEL=YOUR_SERVED_MODEL_ID \
cargo run --locked -p myco-model --example inference

For endpoints requiring authentication, set MYCO_EXAMPLE_API_KEY in the environment. This example makes a real model request. Its model window and output budget are illustrative; adjust them for your endpoint.

Consume the stream

Each generate(&history) call is one attempt. Its stream yields GenerationEvent::Part(MessagePart) or terminates with a GenerationEvent::Failure(GenerationFailure). Dropping the stream cancels the in-flight request.

MessagePart carries message start, content starts/deltas, tool starts/JSON deltas, token usage, and a stop reason. Feed all parts to MessageAccumulator, then call finish() to validate and obtain GenerateOutput. Text shown during streaming is provisional until the whole attempt succeeds.

When incremental output is unnecessary, GenerateOutput::from_generation(model.generate(&history)).await performs that accumulation for you. It returns the error cause but discards retry metadata; inspect GenerationEvent::Failure yourself if you need that metadata.

Own policy above the driver

Drivers do not retry. A failure's retryable flag and optional retry_after are inputs to caller policy. Retry only if no response parts were emitted, with an attempt limit and bounded delay. myco-agent implements that policy; passing retry settings to a backend alone does not create a retry loop.

GenerateError::recovery() distinguishes ordinary retry eligibility at the history level from Recovery::OmitLastMessage, used when input must shrink. It is not a promise that retrying will succeed or an instruction to retry all errors indefinitely.

Preserve message structure

Messages contain user input, assistant output with optional tool calls, or tool results. Results pair positionally: result j answers call j of the immediately preceding assistant message. Keep the ordering and counts when storing, slicing, or replaying history. Drivers generate protocol-specific wire IDs; callers do not need to store provider call IDs.

A tool specification advertises a name, description, and JSON input schema. The model crate never executes the tool. Use the agent crate, or implement your own dispatch loop that preserves the message contract.

Content supports text, images, and thinking blocks. answer_content selects text and image blocks for an answer. For images, provide a URL or a typed data URL; raw base64 is treated as PNG. Token usage may be absent. Cached input tokens are a subset of input tokens, not an extra quantity to add.