> ## Documentation Index
> Fetch the complete documentation index at: https://www.edgee.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Rust SDK - Tools (Function Calling)

> Complete guide to function calling with the Rust SDK.

The Edgee Rust SDK supports OpenAI-compatible function calling (tools), allowing models to request execution of functions you define. This enables models to interact with external APIs, databases, and your application logic.

## Overview

Function calling works in two steps:

1. **Request**: Send a request with tool definitions. The model may request to call one or more tools.
2. **Execute & Respond**: Execute the requested functions and send the results back to the model.

## Tool Definition

A tool is defined using the `Tool` struct:

```rust theme={"dark"}
use edgee::{Tool, FunctionDefinition, JsonSchema};
use std::collections::HashMap;

let tool = Tool::function(FunctionDefinition {
    name: "function_name".to_string(),
    description: Some("Function description".to_string()),
    parameters: JsonSchema {
        schema_type: "object".to_string(),
        properties: Some(HashMap::new()),
        required: Some(vec![]),
        description: None,
    },
});
```

### FunctionDefinition

| Property                                                                                                                      | Type             | Description                                                                                               |
| ----------------------------------------------------------------------------------------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------- |
| `name` <Tooltip headline="Required" tip="The field is required."><Icon icon="asterisk" size={15} color="#8924A6" /></Tooltip> | `String`         | The name of the function (must be unique, a-z, A-Z, 0-9, \_, -)                                           |
| `description`                                                                                                                 | `Option<String>` | Description of what the function does. **Highly recommended** - helps the model understand when to use it |
| `parameters`                                                                                                                  | `JsonSchema`     | JSON Schema object describing the function parameters                                                     |

### Parameters Schema

The `parameters` field uses JSON Schema format via the `JsonSchema` struct:

```rust theme={"dark"}
use edgee::JsonSchema;
use std::collections::HashMap;

let parameters = JsonSchema {
    schema_type: "object".to_string(),
    properties: Some({
        let mut props = HashMap::new();
        props.insert("paramName".to_string(), serde_json::json!({
            "type": "string",
            "description": "Parameter description"
        }));
        props
    }),
    required: Some(vec!["paramName".to_string()]),
    description: None,
};
```

**Example - Defining a Tool:**

```rust theme={"dark"}
use edgee::{Edgee, Message, InputObject, Tool, FunctionDefinition, JsonSchema};
use std::collections::HashMap;

let client = Edgee::from_env()?;

let function = FunctionDefinition {
    name: "get_weather".to_string(),
    description: Some("Get the current weather for a location".to_string()),
    parameters: JsonSchema {
        schema_type: "object".to_string(),
        properties: Some({
            let mut props = HashMap::new();
            props.insert("location".to_string(), serde_json::json!({
                "type": "string",
                "description": "The city and state, e.g. San Francisco, CA"
            }));
            props.insert("unit".to_string(), serde_json::json!({
                "type": "string",
                "enum": ["celsius", "fahrenheit"],
                "description": "Temperature unit"
            }));
            props
        }),
        required: Some(vec!["location".to_string()]),
        description: None,
    },
};

let input = InputObject::new(vec![
    Message::user("What is the weather in Paris?")
])
.with_tools(vec![Tool::function(function)]);

let response = client.send("gpt-5.2", input).await?;
```

## Tool Choice

The `tool_choice` parameter controls when and which tools the model should call. In Rust, this is set using `serde_json::Value`:

| Value                                                         | Type                | Description                                          |
| ------------------------------------------------------------- | ------------------- | ---------------------------------------------------- |
| `"auto"`                                                      | `serde_json::Value` | Let the model decide whether to call tools (default) |
| `"none"`                                                      | `serde_json::Value` | Don't call any tools, even if provided               |
| `{"type": "function", "function": {"name": "function_name"}}` | `serde_json::Value` | Force the model to call a specific function          |

**Example - Force a Specific Tool:**

```rust theme={"dark"}
use serde_json::json;

let input = InputObject::new(vec![
    Message::user("What is the weather?")
])
.with_tools(vec![Tool::function(function)])
.with_tool_choice(json!({
    "type": "function",
    "function": {"name": "get_weather"}
}));

let response = client.send("gpt-5.2", input).await?;
// Model will always call get_weather
```

**Example - Disable Tool Calls:**

```rust theme={"dark"}
use serde_json::json;

let input = InputObject::new(vec![
    Message::user("What is the weather?")
])
.with_tools(vec![Tool::function(function)])
.with_tool_choice(json!("none"));

let response = client.send("gpt-5.2", input).await?;
// Model will not call tools, even though they're available
```

## Tool Call Object Structure

When the model requests a tool call, you receive a `ToolCall` object in the response:

| Property             | Type           | Description                                   |
| -------------------- | -------------- | --------------------------------------------- |
| `id`                 | `String`       | Unique identifier for this tool call          |
| `call_type`          | `String`       | Type of tool call (typically `"function"`)    |
| `function`           | `FunctionCall` | Function call details                         |
| `function.name`      | `String`       | Name of the function to call                  |
| `function.arguments` | `String`       | JSON string containing the function arguments |

### Parsing Arguments

```rust theme={"dark"}
use serde_json;

if let Some(tool_calls) = response.tool_calls() {
    let tool_call = &tool_calls[0];
    let args: serde_json::Value = serde_json::from_str(&tool_call.function.arguments)?;
    // args is now a serde_json::Value
    println!("Location: {}", args["location"]);
}
```

## Complete Example

Here's a complete end-to-end example with error handling:

```rust theme={"dark"}
use edgee::{Edgee, Message, InputObject, Tool, FunctionDefinition, JsonSchema};
use std::collections::HashMap;
use serde_json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Edgee::from_env()?;

    // Define the weather function
    let function = FunctionDefinition {
        name: "get_weather".to_string(),
        description: Some("Get the current weather for a location".to_string()),
        parameters: JsonSchema {
            schema_type: "object".to_string(),
            properties: Some({
                let mut props = HashMap::new();
                props.insert("location".to_string(), serde_json::json!({
                    "type": "string",
                    "description": "The city name"
                }));
                props.insert("unit".to_string(), serde_json::json!({
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature unit"
                }));
                props
            }),
            required: Some(vec!["location".to_string()]),
            description: None,
        },
    };

    // Step 1: Initial request with tools
    let input = InputObject::new(vec![
        Message::user("What is the weather in Paris and Tokyo?")
    ])
    .with_tools(vec![Tool::function(function)]);

    let response1 = client.send("gpt-5.2", input).await?;

    // Step 2: Execute all tool calls
    let mut messages = vec![
        Message::user("What is the weather in Paris and Tokyo?")
    ];

    // Add assistant's message
    if let Some(message) = response1.message() {
        messages.push(message.clone());
    }

    if let Some(tool_calls) = response1.tool_calls() {
        for tool_call in tool_calls {
            let args: serde_json::Value = serde_json::from_str(&tool_call.function.arguments)?;
            let result = get_weather(
                args["location"].as_str().unwrap(),
                args.get("unit").and_then(|v| v.as_str())
            );
            
            messages.push(Message::tool(
                tool_call.id.clone(),
                serde_json::to_string(&result)?
            ));
        }
    }

    // Step 3: Send results back
    let function2 = FunctionDefinition {
        name: "get_weather".to_string(),
        description: Some("Get the current weather for a location".to_string()),
        parameters: JsonSchema {
            schema_type: "object".to_string(),
            properties: Some({
                let mut props = HashMap::new();
                props.insert("location".to_string(), serde_json::json!({
                    "type": "string",
                    "description": "The city name"
                }));
                props.insert("unit".to_string(), serde_json::json!({
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"]
                }));
                props
            }),
            required: Some(vec!["location".to_string()]),
            description: None,
        },
    };

    let input2 = InputObject::new(messages)
        .with_tools(vec![Tool::function(function2)]);

    let response2 = client.send("gpt-5.2", input2).await?;
    println!("{}", response2.text().unwrap_or(""));

    Ok(())
}

fn get_weather(location: &str, unit: Option<&str>) -> serde_json::Value {
    serde_json::json!({
        "location": location,
        "temperature": 15,
        "unit": unit.unwrap_or("celsius"),
        "condition": "sunny"
    })
}
```

**Example - Multiple Tools:**

You can provide multiple tools and let the model choose which ones to call:

```rust theme={"dark"}
let get_weather_tool = Tool::function(get_weather_function);
let send_email_tool = Tool::function(send_email_function);

let input = InputObject::new(vec![
    Message::user("Get the weather in Paris and send an email about it")
])
.with_tools(vec![get_weather_tool, send_email_tool]);

let response = client.send("gpt-5.2", input).await?;
```

## Streaming with Tools

The `stream()` method also supports tools. For details about streaming, see the [Stream Method documentation](/sdk/rust/stream).

```rust theme={"dark"}
use tokio_stream::StreamExt;

let input = InputObject::new(vec![
    Message::user("What is the weather in Paris?")
])
.with_tools(vec![Tool::function(function)]);

let mut stream = client.stream("gpt-5.2", input).await?;

while let Some(result) = stream.next().await {
    match result {
        Ok(chunk) => {
            if let Some(text) = chunk.text() {
                print!("{}", text);
            }
            
            // Check for tool calls in the delta
            if let Some(choice) = chunk.choices.first() {
                if let Some(tool_calls) = &choice.delta.tool_calls {
                    println!("\nTool calls detected: {:?}", tool_calls);
                }
            }
            
            if chunk.finish_reason() == Some("tool_calls") {
                println!("\nModel requested tool calls");
            }
        }
        Err(e) => eprintln!("Stream error: {}", e),
    }
}
```

## Best Practices

### 1. Always Provide Descriptions

Descriptions help the model understand when to use each function:

```rust theme={"dark"}
// ✅ Good
let function = FunctionDefinition {
    name: "get_weather".to_string(),
    description: Some("Get the current weather conditions for a specific location".to_string()),
    parameters: JsonSchema { /* ... */ },
};

// ❌ Bad
let function = FunctionDefinition {
    name: "get_weather".to_string(),
    description: None,  // Missing description
    parameters: JsonSchema { /* ... */ },
};
```

### 2. Use Clear Parameter Names

```rust theme={"dark"}
// ✅ Good
properties.insert("location".to_string(), serde_json::json!({
    "type": "string",
    "description": "The city name"
}));

// ❌ Bad
properties.insert("loc".to_string(), serde_json::json!({
    "type": "string"
    // Unclear name, no description
}));
```

### 3. Mark Required Parameters

```rust theme={"dark"}
let parameters = JsonSchema {
    schema_type: "object".to_string(),
    properties: Some({
        let mut props = HashMap::new();
        props.insert("location".to_string(), serde_json::json!({
            "type": "string",
            "description": "City name"
        }));
        props.insert("unit".to_string(), serde_json::json!({
            "type": "string",
            "description": "Temperature unit"
        }));
        props
    }),
    required: Some(vec!["location".to_string()]),  // location is required, unit is optional
    description: None,
};
```

### 4. Handle Multiple Tool Calls

Models can request multiple tool calls in a single response. Use parallel execution when possible:

```rust theme={"dark"}
use futures::future;

if let Some(tool_calls) = response.tool_calls() {
    // Execute all tool calls in parallel
    let results: Vec<_> = future::join_all(
        tool_calls.iter().map(|tool_call| {
            let args: serde_json::Value = serde_json::from_str(&tool_call.function.arguments)?;
            let result = execute_function(&tool_call.function.name, &args)?;
            Ok((tool_call.id.clone(), result))
        })
    ).await;

    // Add all tool results to messages
    for (tool_call_id, result) in results {
        messages.push(Message::tool(
            tool_call_id,
            serde_json::to_string(&result)?
        ));
    }
}
```

**Example - Handling Multiple Tool Calls:**

```rust theme={"dark"}
// Step 2: Execute all tool calls
let mut messages = vec![
    Message::user("What is the weather in Paris and Tokyo?"),
];

if let Some(message) = response1.message() {
    messages.push(message.clone());
}

if let Some(tool_calls) = response1.tool_calls() {
    for tool_call in tool_calls {
        let args: serde_json::Value = serde_json::from_str(&tool_call.function.arguments)?;
        let result = get_weather(
            args["location"].as_str().unwrap(),
            args.get("unit").and_then(|v| v.as_str())
        );
        
        messages.push(Message::tool(
            tool_call.id.clone(),
            serde_json::to_string(&result)?
        ));
    }
}
```

### 5. Error Handling in Tool Execution

```rust theme={"dark"}
if let Some(tool_calls) = response.tool_calls() {
    for tool_call in tool_calls {
        match serde_json::from_str::<serde_json::Value>(&tool_call.function.arguments) {
            Ok(args) => {
                match execute_function(&tool_call.function.name, &args) {
                    Ok(result) => {
                        messages.push(Message::tool(
                            tool_call.id.clone(),
                            serde_json::to_string(&result)?
                        ));
                    }
                    Err(e) => {
                        // Send error back to model
                        messages.push(Message::tool(
                            tool_call.id.clone(),
                            serde_json::to_string(&serde_json::json!({
                                "error": e.to_string()
                            }))?
                        ));
                    }
                }
            }
            Err(e) => {
                eprintln!("Failed to parse arguments: {}", e);
            }
        }
    }
}
```

### 6. Keep Tools Available

Include tools in follow-up requests so the model can call them again if needed:

```rust theme={"dark"}
let input2 = InputObject::new(messages_with_tool_results)
    .with_tools(vec![
        // Keep the same tools available
        Tool::function(function)
    ]);

let response2 = client.send("gpt-5.2", input2).await?;
```

**Example - Checking for Tool Calls:**

```rust theme={"dark"}
if let Some(tool_calls) = response.tool_calls() {
    // Model wants to call a function
    for tool_call in tool_calls {
        println!("Function: {}", tool_call.function.name);
        println!("Arguments: {}", tool_call.function.arguments);
    }
}
```

**Example - Executing Functions and Sending Results:**

```rust theme={"dark"}
// Execute the function
if let Some(tool_calls) = response.tool_calls() {
    let tool_call = &tool_calls[0];
    let args: serde_json::Value = serde_json::from_str(&tool_call.function.arguments)?;
    let weather_result = get_weather(
        args["location"].as_str().unwrap(),
        args.get("unit").and_then(|v| v.as_str())
    );

    // Send the result back
    let mut messages = vec![
        Message::user("What is the weather in Paris?"),
    ];
    
    // Include assistant's message with tool_calls
    if let Some(message) = response.message() {
        messages.push(message.clone());
    }
    
    messages.push(Message::tool(
        tool_call.id.clone(),
        serde_json::to_string(&weather_result)?
    ));

    let input2 = InputObject::new(messages)
        .with_tools(vec![Tool::function(function)]);

    let response2 = client.send("gpt-5.2", input2).await?;
    println!("{}", response2.text().unwrap_or(""));
    // "The weather in Paris is 15°C and sunny."
}
```
