rig-core 0.45.0

An opinionated library for building LLM powered applications.
Documentation
use super::*;
use crate::message::ToolFunction;

fn tool_call(id: &str) -> ToolCall {
    ToolCall {
        id: CallId::from_wire(id),
        function: ToolFunction::new(
            ToolName::new("add").expect("tool name"),
            serde_json::json!({}),
        ),
        native: None,
    }
}

#[test]
fn arguments_the_schema_does_not_declare_are_refused() {
    let schema = serde_json::json!({"properties": {"a": {}, "b": {}}});
    let mut extra = tool_call("c1");
    extra.function = ToolFunction::new(
        crate::message::ToolName::new("add").expect("tool name"),
        serde_json::json!({"a": 1, "c": 2}),
    );
    assert_eq!(
        arguments_refusal(&schema, &extra).as_deref(),
        Some(
            "`add` has no argument `c`. Its arguments are: `a`, `b`. Call it again with only those."
        )
    );
    assert_eq!(arguments_refusal(&schema, &tool_call("c2")), None);
}

#[test]
fn ill_typed_arguments_are_refused() {
    let schema = serde_json::json!({
        "type": "object",
        "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}},
        "required": ["path"],
    });
    let mut ill_typed = tool_call("c1");
    ill_typed.function = ToolFunction::new(
        crate::message::ToolName::new("read").expect("tool name"),
        serde_json::json!({"limit": "ten"}),
    );
    assert_eq!(
        arguments_refusal(&schema, &ill_typed).as_deref(),
        Some(
            "`read`'s arguments do not fit its schema: /limit: expected an integer, got a \
             string; /path: missing. Call it again with arguments that fit."
        )
    );
}

#[test]
fn a_refusal_names_five_problems_and_counts_the_rest() {
    let schema = serde_json::json!({
        "type": "object",
        "properties": {},
        "required": ["a", "b", "c", "d", "e", "f", "g"],
    });
    assert_eq!(
        arguments_refusal(&schema, &tool_call("c1")).as_deref(),
        Some(
            "`add`'s arguments do not fit its schema: /a: missing; /b: missing; /c: missing; \
             /d: missing; /e: missing; and 2 more. Call it again with arguments that fit."
        )
    );
}

#[test]
fn arguments_that_are_not_an_object_are_refused_before_the_schema() {
    let mut call = tool_call("c1");
    call.function = ToolFunction::parse(
        crate::message::ToolName::new("add").expect("tool name"),
        "[1, 2]",
    );
    let schema = serde_json::json!({"properties": {}, "required": ["a"]});
    assert_eq!(
        arguments_refusal(&schema, &call),
        Some(invalid_arguments_feedback("add", "[1, 2]"))
    );
}

#[test]
fn a_schema_that_names_no_arguments_or_allows_more_accepts_any_argument() {
    let mut call = tool_call("c1");
    call.function = ToolFunction::new(
        crate::message::ToolName::new("open").expect("tool name"),
        serde_json::json!({"path": "x"}),
    );
    for open in [
        serde_json::json!({}),
        serde_json::json!({"type": "object"}),
        serde_json::json!({"type": "object", "properties": {}, "additionalProperties": true}),
        serde_json::json!({"properties": {"a": {}}, "additionalProperties": {"type": "string"}}),
        serde_json::json!({"properties": {"a": {}}, "patternProperties": {"^p": {}}}),
    ] {
        assert_eq!(arguments_refusal(&open, &call), None, "{open}");
    }
    let closed = serde_json::json!({"properties": {}, "additionalProperties": false});
    assert!(arguments_refusal(&closed, &call).is_some());
}

#[test]
fn a_call_cut_off_by_the_output_limit_is_told_so_with_its_start_echoed() {
    let raw = format!(
        "{{\"path\": \"big.rs\", \"content\": \"{}",
        "x".repeat(50_000)
    );
    let text = output_limit_feedback("write", &raw);
    assert!(text.starts_with("Not run: your reply reached the output limit before `write`"));
    assert!(text.contains("big.rs"), "{text}");
    assert!(text.len() < 1_000, "{}", text.len());
    assert!(output_limit_feedback("write", "{}").ends_with("began: {}"));
}

#[test]
fn a_call_that_did_not_finish_says_whether_it_ran_and_what_it_may_have_changed() {
    use crate::tool::{Scope, Touches};
    let why = "the user stopped the turn.";
    let reads = Touches::Reads(Scope::Anything);
    let writes = Touches::Writes(Scope::Anything);
    let said = [
        not_finished(why, false, &writes),
        not_finished(why, true, &reads),
        not_finished(why, true, &writes),
        not_finished(why, true, &Touches::Independent),
    ];
    let partly = "Stopped: the user stopped the turn. It may have partly run; check before \
                  running it again.";
    assert_eq!(
        said,
        [
            "Not run: the user stopped the turn.",
            "Stopped: the user stopped the turn. It only read; run it again if you still need it.",
            partly,
            partly,
        ]
    );
}