zai-rs 0.6.0

Type-safe async Rust SDK for Zhipu AI (BigModel) APIs
Documentation
//! Complete one typed function-call round trip with a deterministic local tool.

use zai_rs::{
    client::ZaiClient,
    model::{
        Function, FunctionParams, GLM4_5_flash, TextMessage, ThinkingType, ToolCall, ToolChoice,
        Tools, chat::ChatCompletion, chat_base_response::ChatCompletionResponse,
    },
};

#[derive(serde::Deserialize)]
struct WeatherArgs {
    city: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let weather = Function::new(
        "get_weather",
        "Return the example weather observation for a city",
        serde_json::json!({
            "type": "object",
            "properties": { "city": { "type": "string" } },
            "required": ["city"],
            "additionalProperties": false
        }),
    );

    let client = ZaiClient::from_env()?;
    let mut request =
        ChatCompletion::new(GLM4_5_flash {}, TextMessage::user("深圳现在的天气怎么样?"))
            .with_thinking(ThinkingType::disabled())
            .add_tool(Tools::Function { function: weather })
            .with_tool_choice(ToolChoice::auto());

    let first: ChatCompletionResponse = request.send_via(&client).await?;
    let (id, name, arguments, assistant_content) = {
        let message = first
            .choices()
            .and_then(|choices| choices.first())
            .ok_or("chat response did not contain a choice")?
            .message()
            .ok_or("chat choice omitted its message")?;
        let call = message
            .tool_calls()
            .and_then(|calls| calls.first())
            .ok_or("model did not return a function call")?;
        let function = call.function().ok_or("tool call omitted its function")?;

        (
            call.id().ok_or("tool call omitted its id")?.to_owned(),
            function.name().to_owned(),
            function.arguments().to_owned(),
            message.content_str().map(str::to_owned),
        )
    };

    if name != "get_weather" {
        return Err(format!("unexpected function call: {name}").into());
    }
    let args: WeatherArgs = serde_json::from_str(&arguments)?;
    let result = serde_json::json!({
        "city": args.city,
        "condition": "",
        "temperature_c": 28,
        "source": "example fixture"
    });

    let assistant_call = ToolCall::new_function(&id, FunctionParams::new(name, arguments));
    request = request
        .add_message(TextMessage::assistant_with_tools(
            assistant_content,
            vec![assistant_call],
        ))
        .add_message(TextMessage::tool_with_id(result.to_string(), id))
        .clear_tools();

    let final_response: ChatCompletionResponse = request.send_via(&client).await?;
    let answer = final_response
        .choices()
        .and_then(|choices| choices.first())
        .and_then(|choice| choice.message())
        .and_then(|message| message.content_str())
        .ok_or("final response did not contain text")?;
    println!("{answer}");

    Ok(())
}