---
title: "Tool calling"
subtitle: "Save tools on a prompt and run the tool loop from your code."
---

Add tools to a prompt, and the model can ask your code to run a function before it answers. Respan returns the tool call; your code runs the function and sends the result back. The functions live in your code, so they can query a database, call an internal API, or anything else.

## Add a tool in the editor

<Steps>
<Step title="Add the tool">

Go to **Gateway** › **Prompts** and open your prompt. In the side panel's **Settings** tab, click **+** next to **Tools**. Paste the function definition and click **Save**. **Examples** loads `get_weather()` or `get_stock_price()` as a starting point.

```json
{
  "name": "check_stock",
  "description": "Look up how many units of a product are in stock.",
  "parameters": {
    "type": "object",
    "properties": {
      "sku": { "type": "string", "description": "Product SKU, e.g. SKU-1001" }
    },
    "required": ["sku"],
    "additionalProperties": false
  }
}
```

Paste the function itself (`name`, `description`, `parameters`), not the `{"type": "function", ...}` wrapper. Each tool on a prompt needs a unique name.

</Step>
<Step title="Set the tool choice">

**Tool choice** decides whether the model calls a tool:

| Option | Behavior |
| --- | --- |
| **Auto** | The model decides. |
| A tool name | The model must call that tool. |
| **None** | No `tool_choice` is sent, so the provider's default applies. |

<img src={"/docs/images/prompts-advanced/tools-section-light.png"} width="420" className="block dark:hidden" alt="The Tools section of the prompt Settings tab with a check_stock tool and Tool choice set to Auto" />
<img src={"/docs/images/prompts-advanced/tools-section-dark.png"} width="420" className="hidden dark:block" alt="The Tools section of the prompt Settings tab with a check_stock tool and Tool choice set to Auto" />

To change or remove a tool, click the pencil or the X next to its name.

</Step>
<Step title="Commit and deploy">

Commit the version and deploy it, as in the [Quickstart](/docs/documentation/features/prompt-management/prompt-management-quickstart).

</Step>
</Steps>

**Run** in the editor shows the model's tool call in the **Test run** tab, but it can't run your function.

## Run the tool loop in code

A tool call takes two requests, both to the same prompt:

1. Call the prompt. If the model needs data, the response has `tool_calls` instead of an answer.
2. Run the function. Call the prompt again with the assistant's message and the tool result in `messages`. With `schema_version: 2`, Respan puts the prompt's messages first and yours after, so the model sees the whole exchange.

<Tabs>
<Tab title="Python">
```python
import json
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.respan.ai/api/",
    api_key=os.environ["RESPAN_API_KEY"],
)

PROMPT = {
    "prompt_id": "YOUR_PROMPT_ID",
    "schema_version": 2,
    "variables": {"question": "Do you have SKU-1001 in stock?"},
}

def check_stock(sku):
    # Your code. Swap in a real inventory lookup.
    units = {"SKU-1001": 42}.get(sku, 0)
    return {"sku": sku, "in_stock": units > 0, "units": units}

# 1. The model asks for check_stock.
first = client.chat.completions.create(
    model="gpt-5.5", messages=[], extra_body={"prompt": PROMPT}
)
assistant_message = first.choices[0].message
call = assistant_message.tool_calls[0]

# 2. Run it and send the result back to the same prompt.
result = check_stock(**json.loads(call.function.arguments))
second = client.chat.completions.create(
    model="gpt-5.5",
    messages=[
        assistant_message,
        {"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)},
    ],
    extra_body={"prompt": PROMPT},
)
print(second.choices[0].message.content)
```
</Tab>
<Tab title="TypeScript">
```typescript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.respan.ai/api/",
  apiKey: process.env.RESPAN_API_KEY,
});

const prompt = {
  prompt_id: "YOUR_PROMPT_ID",
  schema_version: 2,
  variables: { question: "Do you have SKU-1001 in stock?" },
};

function checkStock(sku: string) {
  // Your code. Swap in a real inventory lookup.
  const units = sku === "SKU-1001" ? 42 : 0;
  return { sku, in_stock: units > 0, units };
}

// 1. The model asks for check_stock.
const first = await client.chat.completions.create({
  model: "gpt-5.5",
  messages: [],
  // @ts-expect-error `prompt` is a Respan field
  prompt,
});
const assistantMessage = first.choices[0].message;
const call = assistantMessage.tool_calls?.[0];
if (call?.type !== "function") throw new Error("Expected a function call");

// 2. Run it and send the result back to the same prompt.
const result = checkStock(JSON.parse(call.function.arguments).sku);
const second = await client.chat.completions.create({
  model: "gpt-5.5",
  messages: [
    assistantMessage,
    { role: "tool", tool_call_id: call.id, content: JSON.stringify(result) },
  ],
  // @ts-expect-error `prompt` is a Respan field
  prompt,
});
console.log(second.choices[0].message.content);
```
</Tab>
</Tabs>

<Note>
The prompt's tools and **Tool choice** apply to both requests. Keep **Tool choice** on **Auto** for this loop: a tool you force on the first request is forced again on the second.
</Note>

For how the prompt and your request merge, see [Prompt schema](/docs/documentation/features/prompt-management/advanced#prompt-schema).
