Function calling fine-tuning teaches models to reliably invoke tools and structured functions in response to user queries. This is essential for building agents that can interact with APIs, query databases, and perform actions in the real world.
This notebook provides a step-by-step guide to fine-tuning a model for function calling using the Together AI platform. We'll use a subset of the glaive-function-calling-v2 dataset to train a model that can accurately select and call the right tools with correct arguments.
We will cover:
- Dataset Exploration: Loading and understanding the function calling dataset.
- Data Transformation: Converting the dataset to Together AI's function calling fine-tuning format.
- Data Upload: Validating and uploading the prepared dataset to Together AI.
- Fine-tuning Job Launch: Configuring and starting a LoRA fine-tuning job.
- Job Monitoring: Checking the status and progress of your fine-tuning job.
- Inference: Comparing the base model vs. fine-tuned model on function calling tasks.
By following this guide, you'll learn how to create models that reliably call the right tools with correct arguments using Together AI.
Setup and Installation
First, install the necessary Python libraries:
together: The official Together AI Python client for interacting with the API.datasets: A library from Hugging Face for easily downloading and manipulating datasets.tqdm: For progress bars.
1. Dataset Exploration
We'll use the glaive-function-calling-v2-formatted dataset, a curated collection of function calling conversations. The dataset contains:
- 112,000 training examples of conversations that include function calling
- 1,000 test examples
Each example includes a conversation with a system prompt, user query, and assistant responses that invoke functions, along with the available tool definitions.
README.md: 0%| | 0.00/662 [00:00<?, ?B/s]
data/train-00000-of-00002.parquet: 0%| | 0.00/99.6M [00:00<?, ?B/s]
data/train-00001-of-00002.parquet: 0%| | 0.00/99.3M [00:00<?, ?B/s]
data/test-00000-of-00001.parquet: 0%| | 0.00/1.66M [00:00<?, ?B/s]
Generating train split: 0%| | 0/111944 [00:00<?, ? examples/s]
Generating test split: 0%| | 0/1000 [00:00<?, ? examples/s]
DatasetDict({
train: Dataset({
features: ['text', 'messages', 'tools'],
num_rows: 111944
})
test: Dataset({
features: ['text', 'messages', 'tools'],
num_rows: 1000
})
})
Keys in each sample: ['text', 'messages', 'tools'] Number of messages: 5 Number of tools: 1
[system]: You are a helpful assistant with access to the following functions. Use them if required -
[
{
"type": "function",
"function": {
"name": "send_email",
"description": "Send an ema...
[user]: I need to send an email to my boss. Can you help me with that?
[assistant]: Of course, I can help you with that. Could you please provide me with the recipient's email address, the subject of the email, and the message you want to send?
[user]: Sure, the recipient's email is boss@company.com. The subject is "Project Update" and the message is "Dear Boss, I have completed the project as per the given deadline. I have attached the final report for your review. Regards, [User's Name]".
[assistant]: [
{
"name": "send_email",
"arguments": {
"recipient": "boss@company.com",
"subject": "Project Update",
"message": "Dear Boss, I have completed the project as per the given deadline. I have attached the final report for your review. Regards, [User's Name]"
}
}
]
Tool definitions:
[
{
"type": "function",
"function": {
"name": "send_email",
"description": "Send an email to a recipient",
"parameters": {
"type": "object",
"properties": {
"recipient": {
"type": "string",
"description": "The email address of the recipient"
},
"subject": {
"type": "string",
"description": "The subject of the email"
},
"message": {
"type": "string",
"description": "The body of the email message"
}
},
"required": [
"recipient",
"subject",
"message"
]
}
}
}
]
Notice the current format of this dataset:
- The system message contains the tool definitions embedded in the text content
- The assistant messages contain function calls as JSON strings in the
contentfield - The tools are stored as a separate column with proper OpenAI-style schemas
For Together AI's function calling fine-tuning format, we need to restructure this so that:
- Assistant messages use the
tool_callsfield (notcontent) for function invocations - Tool results use the
toolrole - The
toolsfield is at the top level of each example alongsidemessages
2. Data Transformation to Function Calling Format
Together AI's function calling fine-tuning requires a specific format where tool invocations use the tool_calls field in assistant messages.
Required Format
{
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What's the weather in SF?"},
{"role": "assistant", "tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "getCurrentWeather",
"arguments": "{\"location\": \"San Francisco, CA\"}"
}
}
]},
{"role": "tool", "content": "{\"temperature\": \"65\", \"unit\": \"fahrenheit\"}"}
],
"tools": [
{
"type": "function",
"function": {
"name": "getCurrentWeather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "The city and state"}
},
"required": ["location"]
}
}
}
]
}
Key Requirements:
- Assistant messages with function calls use
tool_calls(notcontent) - Each tool call needs an
id,type: "function", andfunctionwithnameandarguments(as a JSON string) - Tool responses use the
toolrole with the result incontent
We'll also subsample 200 training and 50 validation examples since this is a demo. For production use cases, you'd use significantly more data.
Converted format:
{
"messages": [
{
"role": "system",
"content": "You are a helpful assistant with access to tools. Use them when appropriate to answer user questions."
},
{
"role": "user",
"content": "I need to send an email to my boss. Can you help me with that?"
},
{
"role": "assistant",
"content": "Of course, I can help you with that. Could you please provide me with the recipient's email address, the subject of the email, and the message you want to send?"
},
{
"role": "user",
"content": "Sure, the recipient's email is boss@company.com. The subject is \"Project Update\" and the message is \"Dear Boss, I have completed the project as per the given deadline. I have attached the final report for your review. Regards, [User's Name]\"."
},
{
"role": "assistant",
"tool_calls": [
{
"id": "call_09c5c9e4",
"type": "function",
"function": {
"name": "send_email",
"arguments": "{\"recipient\": \"boss@company.com\", \"subject\": \"Project Update\", \"message\": \"Dear Boss, I have completed the project as per the given deadline. I have attached the final report for your review. Regards, [User's Name]\"}"
}
}
]
}
],
"tools": [
{
"type": "function",
"function": {
"name": "send_email",
"description": "Send an email to a recipient",
"parameters": {
"type": "object",
"properties": {
"recipient": {
"type": "string",
"description": "The email address of the recipient"
},
"subject": {
"type": "string",
"description": "The subject of the email"
},
"message": {
"type": "string",
"description": "The body of the email message"
}
},
"required": [
"recipient",
"subject",
"message"
]
}
}
}
]
}
Converting training examples...
0%| | 0/111944 [00:00<?, ?it/s]
Converted 200 training examples Converting validation examples...
0%| | 0/1000 [00:00<?, ?it/s]
Converted 50 validation examples
Saved 200 examples to fc_train.jsonl Saved 50 examples to fc_val.jsonl
3. Upload Data to Together AI
Now we'll validate and upload our prepared datasets to Together AI. The check=True parameter validates the file format before uploading.
Uploading training file...
Validating file: 200 lines [00:00, 69713.35 lines/s] Uploading file fc_train.jsonl: 100%|██████████| 248k/248k [00:00<00:00, 814kB/s]
Training file ID: file-f253dccc-9d68-4d01-a9e3-efb02de74206
Uploading validation file...
Validating file: 50 lines [00:00, 63859.68 lines/s] Uploading file fc_val.jsonl: 100%|██████████| 58.6k/58.6k [00:00<00:00, 281kB/s]
Validation file ID: file-655272d8-0ee4-4ece-9a3f-25a4776baf41
4. Launch Fine-tuning Job
Now we'll launch a LoRA fine-tuning job for function calling.
Key Parameters
| Parameter | Description | Our Value |
|---|---|---|
model | Base model to fine-tune | Qwen/Qwen3-8B |
lora | Use LoRA (recommended) | True |
n_epochs | Training epochs | 3 |
learning_rate | Weight update rate | 1e-5 |
n_checkpoints | Checkpoints to save | 1 |
suffix | Custom model name suffix | fc-demo |
Note: These hyperparameters are tuned for this small demo dataset. For production, you'd use more data and may want to adjust epochs and learning rate accordingly.
🔗 See the Function Calling Fine-tuning documentation for the full list of supported models and parameters.
message="train_on_inputs is not set for SFT training, it will be set to 'auto'" message="train_on_inputs is not set for SFT training, it will be set to 'auto'"
Fine-tuning job created! Job ID: ft-05f3fbe3-bd07
5. Monitor Fine-tuning Job
You can monitor your fine-tuning job's progress using the Together AI API or the dashboard.
Your job will progress through several states: Pending → Queued → Running → Uploading → Completed.
Available methods:
client.fine_tuning.retrieve(id)— Get job statusclient.fine_tuning.list_events(id=job_id)— Get job logsclient.fine_tuning.cancel(id=job_id)— Cancel a jobclient.fine_tuning.list()— List all jobs
Job Status: pending
Fine-tuning request created
Status: completed Final status: completed
🔗 You can also monitor your job on the Together AI Fine-tuning Dashboard and view WandB logs if you provided an API key.
6. Inference — Base Model vs. Fine-tuned Model
Once training is complete, let's compare the base model against our fine-tuned model on function calling tasks. We'll define a set of test tools and queries to see how each model performs.
Fine-tuned model: zainhas/Qwen3-8B-fc-demo-792ac632
Below we compare the fine-tuned model against the base model on our test queries. We expect the fine-tuned model to more reliably select the correct tool and provide well-formed arguments.
Summary
In this notebook, we demonstrated how to:
- Load and explore a function calling dataset from Hugging Face
- Transform data into Together AI's function calling format with
tool_calls,toolroles, and top-leveltools - Upload and validate the dataset using the Together AI Python client
- Launch a LoRA fine-tuning job on Qwen3-8B for function calling
- Monitor training progress via the API and dashboard
- Compare inference between the base model and fine-tuned model
Key Takeaways
- Function calling fine-tuning requires assistant messages to use the
tool_callsfield withid,type, andfunction(name + arguments as JSON string) - LoRA fine-tuning is recommended for function calling — it's faster, cheaper, and enables serverless inference
- Even a small subset of training data (200 examples) can improve a model's tool selection and argument formatting
- The
toolsfield at the top level of each example defines the available functions for that conversation
Next Steps
- Scale up to more training data for improved reliability
- Try DPO preference tuning to teach the model when not to call tools
- Experiment with different base models (Qwen3-8B, Qwen2.5-72B-Instruct, etc.)
- Deploy your fine-tuned model on a dedicated endpoint for production use
🔗 For more details, see the Function Calling Fine-tuning documentation.