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(())
}