sim-lib-openai-server 0.1.1

OpenAI-compatible gateway skeleton for SIM.
Documentation
use std::sync::Arc;

use serde_json::Value;
use sim_kernel::{EvalFabricRef, Expr, Symbol};
use sim_lib_agent_runner_core::ModelResponse;

use crate::{
    GatewayEvent, OpenAiFederation, OpenAiGatewayFabric, eval_plan_report_with_federation,
    openai_gateway_federate_capability, openai_gateway_plan_capability, parse_plan,
};

#[test]
fn race_calls_two_in_process_gateway_fabrics_and_returns_first_answer() {
    let (federation, gateway_a, gateway_b) = federation_with_two_gateways();
    let mut cx = plan_federation_cx();
    let plan = parse_plan("race(gateway/a, gateway/b)").unwrap();
    let request = model_request("federate me", Vec::new());

    let report = eval_plan_report_with_federation(&mut cx, &plan, &request, &federation).unwrap();
    let response = ModelResponse::try_from(report.response).unwrap();

    assert_eq!(response.model, "gateway/a");
    assert!(format!("{:?}", response.content).contains("fixture a transcript"));
    assert!(gateway_a.last_execution().unwrap().is_some());
    assert!(gateway_b.last_execution().unwrap().is_some());
}

#[test]
fn federation_requires_federate_capability() {
    let (federation, _, _) = federation_with_two_gateways();
    let mut cx = super::cx();
    cx.grant(openai_gateway_plan_capability());
    let plan = parse_plan("race(gateway/a, gateway/b)").unwrap();
    let request = model_request("federate me", Vec::new());

    let err = eval_plan_report_with_federation(&mut cx, &plan, &request, &federation).unwrap_err();

    assert!(format!("{err}").contains("openai-gateway.federate"));
}

#[test]
fn local_only_request_refuses_federation_boundary() {
    let (federation, gateway_a, gateway_b) = federation_with_two_gateways();
    let mut cx = plan_federation_cx();
    let plan = parse_plan("race(gateway/a, gateway/b)").unwrap();
    let request = model_request(
        "stay local",
        vec![(
            Expr::Symbol(Symbol::new("privacy")),
            Expr::String("local-only".to_owned()),
        )],
    );

    let err = eval_plan_report_with_federation(&mut cx, &plan, &request, &federation).unwrap_err();

    assert!(format!("{err}").contains("local-only privacy"));
    assert!(gateway_a.last_execution().unwrap().is_none());
    assert!(gateway_b.last_execution().unwrap().is_none());
}

#[test]
fn federation_carries_privacy_and_budget_policy_to_target_request() {
    let (federation, gateway_a, _) = federation_with_two_gateways();
    let mut cx = plan_federation_cx();
    let plan = parse_plan("budget(gateway/a, max-tokens: 7)").unwrap();
    let request = model_request(
        "federate with policy",
        vec![(
            Expr::Symbol(Symbol::new("privacy")),
            Expr::String("metadata-only".to_owned()),
        )],
    );

    eval_plan_report_with_federation(&mut cx, &plan, &request, &federation).unwrap();
    let body = target_request_json(&gateway_a);

    assert_eq!(body["model"], "fixture/a");
    assert_eq!(body["input"], "federate with policy");
    assert_eq!(body["privacy"], "metadata-only");
    assert_eq!(body["budget"]["max-tokens"], 7);
}

fn federation_with_two_gateways() -> (
    OpenAiFederation,
    Arc<OpenAiGatewayFabric>,
    Arc<OpenAiGatewayFabric>,
) {
    let federation = OpenAiFederation::new();
    let gateway_a = Arc::new(OpenAiGatewayFabric::deterministic(101, 10_000, 10));
    let gateway_b = Arc::new(OpenAiGatewayFabric::deterministic(201, 20_000, 10));
    let target_a: EvalFabricRef = gateway_a.clone();
    let target_b: EvalFabricRef = gateway_b.clone();
    federation
        .insert_gateway("gateway/a", "fixture/a", target_a)
        .unwrap();
    federation
        .insert_gateway("gateway/b", "fixture/b", target_b)
        .unwrap();
    (federation, gateway_a, gateway_b)
}

fn plan_federation_cx() -> sim_kernel::Cx {
    let mut cx = super::cx();
    cx.grant(openai_gateway_plan_capability());
    cx.grant(openai_gateway_federate_capability());
    cx
}

fn model_request(text: &str, extra: Vec<(Expr, Expr)>) -> Expr {
    let mut entries = vec![
        (Expr::Symbol(Symbol::new("model-request")), Expr::Bool(true)),
        (
            Expr::Symbol(Symbol::new("task")),
            Expr::String(text.to_owned()),
        ),
        (
            Expr::Symbol(Symbol::new("messages")),
            Expr::List(Vec::new()),
        ),
    ];
    entries.extend(extra);
    Expr::Map(entries)
}

fn target_request_json(gateway: &OpenAiGatewayFabric) -> Value {
    let execution = gateway.last_execution().unwrap().unwrap();
    let event = execution
        .events()
        .iter()
        .find(|event| event.kind().name.as_ref() == "request-start")
        .unwrap();
    let body = request_body(event);
    serde_json::from_slice(body).unwrap()
}

fn request_body(event: &GatewayEvent) -> &[u8] {
    let Expr::Map(entries) = event.payload() else {
        panic!("request-start payload must be a map");
    };
    entries
        .iter()
        .find_map(|(key, value)| match (key, value) {
            (Expr::Symbol(key), Expr::Bytes(body))
                if key.namespace.is_none() && key.name.as_ref() == "body" =>
            {
                Some(body.as_slice())
            }
            _ => None,
        })
        .unwrap()
}