mirror of
https://github.com/whit3rabbit/anyllm-proxy.git
synced 2026-09-22 00:00:50 +00:00
- 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>
100 lines
3.5 KiB
Rust
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")
|
|
}
|