#![cfg(feature = "openai")]
mod support;
use std::collections::BTreeMap;
use std::time::{Duration, Instant};
use ingot_runtime::catalogue::{ModelConfig, ProviderConfig, ProviderKind, DEFAULT_TIMEOUT};
use ingot_runtime::http;
use ingot_runtime::provider::{CompletionRequest, ModelProvider, ModelSelection, ProviderError};
use ingot_runtime::schema;
use serde_json::json;
use support::{serve_once, serve_silently};
const BRIEF: Duration = Duration::from_millis(250);
fn request() -> CompletionRequest {
CompletionRequest {
node: "n0".into(),
model: ModelSelection::Exact("local/any".into()),
system: None,
prompt: "anything".into(),
context: Vec::new(),
response_type: "markdown".into(),
shape: schema::response_shape("markdown", &BTreeMap::new()).unwrap(),
max_tokens: 256,
}
}
#[test]
fn a_stated_ceiling_is_the_one_the_transport_waits_for() {
let url = serve_silently("/v1/chat/completions");
let started = Instant::now();
let error = http::post_json(&url, &[], &json!({ "model": "m" }), Some(BRIEF), 0)
.expect_err("nobody is going to answer");
let waited = started.elapsed();
assert!(
matches!(error, ProviderError::Transport(_)),
"a silent endpoint is a transport failure, not a rejection: {error}"
);
assert!(
waited < DEFAULT_TIMEOUT,
"waited {waited:?}, which is the default rather than the ceiling asked for"
);
}
#[test]
fn no_ceiling_is_not_a_ceiling_of_zero() {
let (url, _captured) = serve_once(
"/v1/chat/completions",
200,
json!({ "id": "x", "model": "m", "choices": [] }),
);
let answer = http::post_json(&url, &[], &json!({ "model": "m" }), None, 0)
.expect("an unbounded wait still returns when the answer arrives");
assert_eq!(answer["id"], "x");
}
#[test]
fn a_request_that_ran_out_of_time_is_not_asked_again() {
let url = serve_silently("/v1/chat/completions");
let started = Instant::now();
let _ = http::post_json(&url, &[], &json!({ "model": "m" }), Some(BRIEF), 3)
.expect_err("nobody is going to answer");
let waited = started.elapsed();
assert!(
waited < BRIEF * 2,
"waited {waited:?} for a ceiling of {BRIEF:?}: the attempt was repeated"
);
}
#[test]
fn a_declared_wait_reaches_the_provider_built_from_it() {
let url = serve_silently("/v1/chat/completions");
let declaration = ProviderConfig {
name: "local".to_string(),
kind: ProviderKind::Openai,
base_url: url,
api_key_env: None,
timeout_seconds: Some(1),
};
let mut provider =
ingot_runtime::catalogue::build(&declaration, &ModelConfig::default(), None, None)
.expect("an openai-compatible endpoint needs no key");
let started = Instant::now();
let error = provider
.complete(&request())
.expect_err("nobody is going to answer");
let waited = started.elapsed();
assert!(
matches!(error, ProviderError::Transport(_)),
"a silent endpoint is a transport failure: {error}"
);
assert!(
waited < DEFAULT_TIMEOUT,
"waited {waited:?}: `timeout-seconds` never reached the transport"
);
}
#[test]
fn a_declaration_that_says_nothing_still_reaches_a_service_that_answers() {
let (url, _captured) = serve_once(
"/v1/chat/completions",
200,
json!({
"id": "chatcmpl-test",
"model": "m",
"choices": [{
"index": 0,
"message": { "role": "assistant", "content": "# Fine" },
"finish_reason": "stop",
}],
"usage": { "prompt_tokens": 1, "completion_tokens": 1 },
}),
);
let declaration = ProviderConfig {
name: "local".to_string(),
kind: ProviderKind::Openai,
base_url: url,
api_key_env: None,
timeout_seconds: None,
};
let mut provider =
ingot_runtime::catalogue::build(&declaration, &ModelConfig::default(), None, None)
.expect("an openai-compatible endpoint needs no key");
let response = provider.complete(&request()).expect("the stub answers");
assert_eq!(response.value, json!("# Fine"));
}