rig-core 0.46.0

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

#[test]
fn argument_feedback_obeys_the_schema_and_reports_bounded_problems() {
    let name = ToolName::new("add");
    assert!(name.is_ok());
    let Ok(name) = name else { return };
    let extra =
        "`add` has no argument `c`. Its arguments are: `a`, `b`. Call it again with only those.";
    let typed = "`add`'s arguments do not fit its schema: /limit: expected an integer, got a string; /path: missing. Call it again with arguments that fit.";
    let many = "`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.";
    let invalid = invalid_arguments_feedback("add", "[1, 2]");
    for (schema, raw, expected) in [
        (
            serde_json::json!({"properties": {"a": {}, "b": {}}}),
            r#"{"a":1,"c":2}"#,
            Some(extra),
        ),
        (
            serde_json::json!({"properties": {"a": {}, "b": {}}}),
            "{}",
            None,
        ),
        (
            serde_json::json!({"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}),
            r#"{"limit":"ten"}"#,
            Some(typed),
        ),
        (
            serde_json::json!({"type": "object", "properties": {}, "required": ["a", "b", "c", "d", "e", "f", "g"]}),
            "{}",
            Some(many),
        ),
        (
            serde_json::json!({"properties": {}, "required": ["a"]}),
            "[1, 2]",
            Some(invalid.as_str()),
        ),
    ] {
        let call = ToolCall::from_wire("call", ToolFunction::parse(name.clone(), raw));
        assert_eq!(
            arguments_refusal(&schema, &call).as_deref(),
            expected,
            "{schema}, {raw}"
        );
    }
    let call = ToolCall::from_wire(
        "call",
        ToolFunction::new(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,
        ]
    );
}

#[test]
fn unknown_tool_feedback_lists_exact_names_or_no_tools() {
    assert_eq!(
        unknown_tool_feedback("bash", ["write", "read", "edit", "shell"]),
        "There is no tool named `bash`. Your tools are: `edit`, `read`, `shell`, `write`. Call one by its exact name."
    );
    assert_eq!(
        unknown_tool_feedback("bash", []),
        "There is no tool named `bash`. You have no tools available. Answer without calling tools."
    );
}