llmoxide-tools 0.1.0

Tool-calling runner for llmoxide (schemas, dispatch, streaming callbacks)
Documentation
use llmoxide::Event;
use llmoxide_tools::{
    RunConfig, ToolMeta, ToolRegistry, ToolRunnerStreamText, tools_stream_debug_enabled,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::io::{self, Write};

fn env(name: &str) -> String {
    std::env::var(name).unwrap_or_default()
}

#[derive(Debug, Clone, Deserialize, JsonSchema)]
struct AddArgs {
    a: i64,
    b: i64,
}

#[derive(Debug, Clone, Serialize, JsonSchema)]
struct AddResult {
    sum: i64,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let key = env("OPENAI_API_KEY");
    if key.is_empty() {
        eprintln!("Set OPENAI_API_KEY.");
        return Ok(());
    }

    if tools_stream_debug_enabled() {
        eprintln!("[add_tool_stream] LLMOXIDE_DEBUG_TOOLS_STREAM=1 — logging all events to stderr");
    }

    let mut tools = ToolRegistry::new();
    tools.register::<AddArgs, AddResult, _, _>(
        ToolMeta::new("add").description("Add two integers."),
        |args| async move {
            Ok(AddResult {
                sum: args.a + args.b,
            })
        },
    );

    let mut on_event = |ev: Event| {
        if tools_stream_debug_enabled() {
            match &ev {
                Event::TextDelta(t) => {
                    let preview: String = t.chars().take(120).collect();
                    let suffix = if t.len() > 120 { "" } else { "" };
                    eprintln!(
                        "[add_tool_stream] Event::TextDelta({preview}{suffix}) len={}",
                        t.len()
                    );
                }
                Event::ToolCall(tc) => {
                    eprintln!("[add_tool_stream] Event::ToolCall {:?}", tc);
                }
                Event::Completed(resp) => {
                    eprintln!(
                        "[add_tool_stream] Event::Completed tool_calls={} text_len={:?}",
                        resp.tool_calls.len(),
                        resp.text().map(|s| s.len())
                    );
                }
                _ => {}
            }
        }
        match ev {
            Event::TextDelta(t) => {
                print!("{t}");
                let _ = io::stdout().flush();
            }
            Event::ToolCall(tc) => eprintln!("[tool] {} {:?}", tc.name, tc.arguments),
            Event::Completed(_) => println!(),
            _ => {}
        }
    };

    let resp = llmoxide::openai(key)
        .run_with_tools_stream_text(
            "Use the add tool to compute 19 + 23. Reply with the number only.",
            &tools,
            RunConfig { max_rounds: 4 },
            &mut on_event,
        )
        .await?;

    if let Some(t) = resp.text() {
        eprintln!("\n---\nfinal:\n{t}");
    }

    Ok(())
}