Files
whit3rabbitandClaude Opus 4.6 b6277a8b86 feat: operator observability dashboard with error classification
- Admin UI: operator view with request volume, token usage, latency,
  cost charts, failure table, and request timeline
- Backend: add error_kind() method and infer_error_kind() for stable
  error classification (rate_limit, timeout, backend_error, client_error)
- Admin DB: error_kind column in request_log, observability aggregate
  queries (bucketed timeseries, failure breakdown, timeline)
- Gemini: improved streaming translation, thinking block support,
  grounding metadata passthrough
- Request timeout: configurable REQUEST_TIMEOUT_SECS with streaming
  watchdog
- Model pricing: MODEL_PRICING_FILE for external pricing overrides
- Degradation header: ANYLLM_DEGRADATION_WARNINGS env var control

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 19:12:03 -05:00

100 lines
3.5 KiB
Rust

//! Tool calling example: define a tool, let the model call it, handle the result.
//!
//! ```bash
//! CHAT_COMPLETIONS_URL=https://api.openai.com/v1/chat/completions \
//! OPENAI_API_KEY=sk-... \
//! cargo run --example tools -p anyllm_client
//! ```
use anyllm_client::{Client, ClientError, ToolBuilder, ToolChoiceBuilder};
use anyllm_translate::anthropic::{ContentBlock, MessageCreateRequest, StopReason};
use serde_json::json;
#[tokio::main]
async fn main() {
if let Err(e) = run().await {
eprintln!("Error: {e}");
std::process::exit(1);
}
}
async fn run() -> Result<(), ClientError> {
let url = std::env::var("CHAT_COMPLETIONS_URL")
.unwrap_or_else(|_| "https://api.openai.com/v1/chat/completions".to_string());
let api_key = std::env::var("OPENAI_API_KEY").unwrap_or_default();
let client = Client::builder().base_url(&url).api_key(&api_key).build()?;
// Define a tool with ToolBuilder. The input_schema is standard JSON Schema.
let weather_tool = ToolBuilder::new("get_weather")
.description("Get the current weather for a location")
.input_schema(json!({
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and country, e.g. 'Paris, France'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}))
.build();
// Construct the request with the tool attached.
// tool_choice: auto lets the model decide; use ToolChoiceBuilder::specific("get_weather")
// to force it to call a particular tool.
let req: MessageCreateRequest = serde_json::from_value(json!({
"model": "claude-sonnet-4-6",
"max_tokens": 512,
"messages": [
{"role": "user", "content": "What is the weather like in Tokyo right now?"}
],
"tools": [serde_json::to_value(&weather_tool).unwrap()],
"tool_choice": serde_json::to_value(ToolChoiceBuilder::auto()).unwrap()
}))
.expect("request JSON is valid");
let response = client.messages(&req).await?;
println!("stop_reason: {:?}", response.stop_reason);
for block in &response.content {
match block {
ContentBlock::Text { text } => {
println!("text: {text}");
}
ContentBlock::ToolUse { id, name, input } => {
println!("tool_use: name={name} id={id}");
println!(" input: {}", serde_json::to_string_pretty(input).unwrap());
// In a real application: execute the tool here, then send the result
// back in a follow-up request as a ContentBlock::ToolResult.
let _tool_result = call_weather_tool(input);
println!(" (tool execution would happen here)");
}
_ => {}
}
}
// If stop_reason is ToolUse, send the tool result in a follow-up turn.
if matches!(response.stop_reason, Some(StopReason::ToolUse)) {
println!("\nNext step: send tool result back in a follow-up messages() call.");
}
Ok(())
}
// Stub simulating a real tool execution.
fn call_weather_tool(input: &serde_json::Value) -> String {
let location = input
.get("location")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
format!("Weather in {location}: 22°C, partly cloudy")
}