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

# Tutorial: Using tools

> PolyAcademy Level 2 – How the LLM calls Python functions (tools) to integrate with external systems.

export const LessonMeta = ({level, difficulty, time}) => {
  const levelConfig = {
    1: {
      badge: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
      label: 'Level 1'
    },
    2: {
      badge: 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200',
      label: 'Level 2'
    },
    3: {
      badge: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
      label: 'Level 3'
    }
  };
  const difficultyConfig = {
    Beginner: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
    Intermediate: 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200',
    Advanced: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
  };
  const lvl = levelConfig[level] || levelConfig[1];
  const diffColor = difficultyConfig[difficulty] || difficultyConfig['Beginner'];
  return <div className="flex flex-wrap items-center gap-2 my-4 not-prose">
      <span className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ${lvl.badge}`}>
        {lvl.label}
      </span>
      <span className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ${diffColor}`}>
        {difficulty}
      </span>
      {time && <span className="inline-flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
          <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          {time}
        </span>}
    </div>;
};

export const ProgressTracker = ({lessonNum, totalLessons, level}) => {
  const [checked, setChecked] = useState(false);
  return <div onClick={() => setChecked(prev => !prev)} className={checked ? 'flex items-center gap-3 p-4 rounded-lg border-2 border-green-600 bg-green-50 dark:bg-green-950 cursor-pointer select-none transition-all' : 'flex items-center gap-3 p-4 rounded-lg border-2 border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 cursor-pointer select-none transition-all'}>
      <div className={checked ? 'w-5 h-5 rounded border-2 border-green-600 bg-green-600 flex items-center justify-center shrink-0 transition-all' : 'w-5 h-5 rounded border-2 border-gray-400 dark:border-gray-500 bg-white dark:bg-gray-800 flex items-center justify-center shrink-0 transition-all'}>
        {checked ? <svg width="10" height="8" viewBox="0 0 10 8" fill="none">
            <path d="M1 4L3.5 6.5L9 1" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
          </svg> : null}
      </div>
      <div>
        <div className={checked ? 'font-semibold text-sm text-green-700 dark:text-green-300' : 'font-semibold text-sm text-gray-700 dark:text-gray-200'}>
          {checked ? 'Lesson complete' : 'Mark lesson complete'}
        </div>
        {lessonNum && totalLessons ? <div className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
            {level ? level + ' - ' : ''}Lesson {lessonNum} of {totalLessons}
          </div> : null}
      </div>
    </div>;
};

export const FillBlank = ({prompt, answer, hint, explanation}) => {
  const [value, setValue] = useState('');
  const [submitted, setSubmitted] = useState(false);
  const normalize = s => s.trim().toLowerCase().replace(/[^a-z0-9_]/g, '');
  const answers = Array.isArray(answer) ? answer : [answer];
  const isCorrect = answers.some(a => normalize(value) === normalize(a));
  const handleSubmit = e => {
    e.preventDefault();
    if (value.trim()) setSubmitted(true);
  };
  const handleReset = () => {
    setValue('');
    setSubmitted(false);
  };
  return <div className="my-6">
      <p className="mt-0 mb-3 text-sm font-semibold leading-relaxed text-gray-900 dark:text-gray-100">
        {prompt}
      </p>
      <form onSubmit={handleSubmit} className="flex flex-col gap-2.5">
        <div className="flex gap-2">
          <input type="text" value={value} onChange={e => {
    setValue(e.target.value);
    setSubmitted(false);
  }} placeholder={hint || "Type your answer…"} className="flex-1 rounded-xl border py-2.5 px-4 text-sm font-mono border-gray-200 bg-white text-gray-900 placeholder-gray-400 outline-none focus:border-gray-400 focus:ring-2 focus:ring-gray-200 transition-all duration-150 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-600 dark:focus:border-gray-500 dark:focus:ring-gray-700" />
          <button type="submit" className="rounded-xl border py-2.5 px-5 text-sm font-medium transition-all duration-150 border-gray-800 bg-gray-800 text-white hover:bg-gray-700 hover:border-gray-700 dark:border-gray-200 dark:bg-gray-200 dark:text-gray-900 dark:hover:bg-white">
            Check
          </button>
        </div>
        {submitted ? <div className={`py-3 pl-4 pr-3.5 rounded-r-xl text-sm leading-relaxed border-l-4 ${isCorrect ? 'border-green-500 bg-green-50 dark:bg-green-900 dark:border-green-500' : 'border-red-500 bg-red-50 dark:bg-red-900 dark:border-red-500'}`}>
            {isCorrect ? <>
                <span className={`font-semibold !text-green-800 dark:!text-green-200`}>Correct.</span>{' '}
                <span className="!text-gray-700 dark:!text-gray-300">{explanation}</span>
              </> : <>
                <span className="font-semibold !text-red-800 dark:!text-red-200">Not quite.</span>{' '}
                <span className="!text-gray-700 dark:!text-gray-300">The answer is <code className="!text-gray-800 dark:!text-gray-200">{answers[0]}</code>. {explanation}</span>
              </>}
          </div> : null}
      </form>
      {submitted ? <button type="button" onClick={handleReset} className="mt-2 text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 underline underline-offset-2 cursor-pointer transition-colors duration-150">
          Try again
        </button> : null}
    </div>;
};

export const Quiz = ({questions = []}) => {
  const [selected, setSelected] = useState({});
  const [resetCount, setResetCount] = useState(0);
  const letters = ['A', 'B', 'C', 'D'];
  const handleSelect = (qIdx, optIdx) => {
    if (selected[qIdx] !== undefined) return;
    setSelected(prev => ({
      ...prev,
      [qIdx]: optIdx
    }));
  };
  const handleReset = () => {
    setSelected({});
    setResetCount(c => c + 1);
  };
  if (!questions?.length) return null;
  const getOptionClasses = ({hasAnswered, isThisCorrect, isThisSelected}) => {
    if (!hasAnswered) {
      return {
        btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-pointer border-gray-200 bg-white text-gray-700 hover:border-gray-300 hover:bg-gray-50 hover:shadow-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 dark:hover:border-gray-500 dark:hover:bg-gray-700',
        badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-300',
        icon: null
      };
    }
    if (isThisCorrect) {
      return {
        btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-default border-green-400 bg-green-50 text-green-900 font-medium dark:border-green-500 dark:bg-green-950 dark:text-green-100',
        badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-green-500 text-white dark:bg-green-500',
        icon: <svg className="shrink-0 w-4 h-4 text-green-500 dark:text-green-400 ml-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
          </svg>
      };
    }
    if (isThisSelected) {
      return {
        btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-default border-red-400 bg-red-50 text-red-900 dark:border-red-500 dark:bg-red-950 dark:text-red-100',
        badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-red-500 text-white dark:bg-red-500',
        icon: <svg className="shrink-0 w-4 h-4 text-red-400 dark:text-red-400 ml-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
          </svg>
      };
    }
    return {
      btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-default border-gray-100 bg-white text-gray-400 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-500',
      badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-500',
      icon: null
    };
  };
  return <div key={resetCount} className="my-6">
      {questions.map((q, qIdx) => {
    const answer = selected[qIdx];
    const hasAnswered = answer !== undefined;
    const isCorrect = answer === q.correct;
    return <div key={String(qIdx)} className="mb-8">
            <p className="flex items-start gap-2.5 font-semibold text-sm mb-3 mt-0 leading-relaxed text-gray-900 dark:text-gray-100">
              <span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-gray-800 dark:bg-gray-200 text-white dark:text-gray-900 text-xs font-bold shrink-0 mt-px leading-none">
                {qIdx + 1}
              </span>
              {q.q}
            </p>

            <div className="flex flex-col gap-2">
              {q.options.map((opt, i) => {
      const isThisCorrect = i === q.correct;
      const isThisSelected = i === answer;
      const {btn, badge, icon} = getOptionClasses({
        hasAnswered,
        isThisCorrect,
        isThisSelected
      });
      return <button key={String(i)} type="button" onClick={() => handleSelect(qIdx, i)} className={btn}>
                    <span className={badge}>{letters[i]}</span>
                    <span className="flex-1">{opt}</span>
                    {icon}
                  </button>;
    })}
            </div>

            {hasAnswered ? <div className={`mt-3 py-3 pl-4 pr-3.5 rounded-r-xl text-sm leading-relaxed border-l-4 ${isCorrect ? 'border-green-500 bg-green-50 dark:bg-green-950 dark:border-green-500' : 'border-red-500 bg-red-50 dark:bg-red-950 dark:border-red-500'}`}>
                <span className={`font-semibold ${isCorrect ? '!text-green-800 dark:!text-green-200' : '!text-red-800 dark:!text-red-200'}`}>
                  {isCorrect ? 'Correct.' : 'Not quite.'}
                </span>{' '}
                <span className="!text-gray-700 dark:!text-gray-300">{q.explanation}</span>
              </div> : null}
          </div>;
  })}

      <button type="button" onClick={handleReset} className="mt-1 text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 underline underline-offset-2 cursor-pointer transition-colors duration-150">
        Reset quiz
      </button>
    </div>;
};

**Level 2 – Lesson 2 of 8** – This lesson explains what [tools](/tools/introduction) are, why they exist, and how the LLM uses them.

<LessonMeta level={2} difficulty="Intermediate" time="20 min" /> By the end you will understand the full request-response loop and be able to create and reference a tool correctly.

<Note>In Agent Studio, tools are defined as Python `function`s – "tool" is the UI name and "function" is the code identifier. This lesson uses both interchangeably.</Note>

## Why tools exist

The Agent tab (personality, role, rules) only gets you so far. Without tools, the agent can't retrieve user data, execute actions, save state, or integrate with external systems.

Prompt engineering also doesn't scale – putting dozens of user journeys into one rules box gives the LLM too much context and makes it harder to reason about any single scenario.

Tools solve both problems: external integration **and** fine-grained control over what the LLM knows and does at each step.

## How LLMs use tools

Before looking at Agent Studio specifically, it helps to understand how LLMs use tools in general – because this pattern is the same across all modern AI systems.

An LLM can produce two kinds of output:

<CardGroup cols={2}>
  <Card title="Text output" icon="comment">
    The model speaks to the **user** using natural language.

    *"The weather in Paris is usually mild in October."*
  </Card>

  <Card title="Tool call" icon="code">
    The model communicates with a **system** – a function, API, or piece of code – to fetch data or trigger an action.

    *call: `get_weather` with `{city: "Paris"}`*
  </Card>
</CardGroup>

The LLM **mediates** between the user and the system. It takes a user request, decides whether it needs to call a tool to answer it, calls that tool, receives a result, and then reports the result back to the user as text.

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}}
sequenceDiagram
    actor User
    participant LLM
    participant Fn as Python Function

    User->>LLM: "What are your opening hours?"
    Note over LLM: Request 1 – decides to call a tool
    LLM->>Fn: call get_store_hours()
    Fn-->>LLM: "Open Mon–Fri, 9am–6pm"
    Note over LLM: Request 2 – formulates response
    LLM->>User: "We're open Monday to Friday, 9am to 6pm."
```

This is the core loop that powers almost everything beyond basic FAQ responses.

<Quiz
  questions={[
{
q: "What is the difference between a text output and a tool call from an LLM?",
options: [
  "Text outputs go to the user; tool calls go to a system",
  "Tool calls are more reliable than text outputs",
  "Text outputs are only used for greetings",
  "There is no practical difference",
],
correct: 0,
explanation: "Text outputs are user-facing – the agent speaking. Tool calls are system-facing – the agent communicating with Python code or an external API to fetch data or trigger an action.",
}
]}
/>

## Creating a function in Agent Studio

In Agent Studio, tools are called **functions**. You write them in Python and they are available for the LLM to call during a conversation.

To create a function, go to **Tools** and click the **+** button.

Every function has:

* **Name** – how the function is identified (used by the LLM to decide when to call it)
* **Description** – what the function does (also read by the LLM when deciding whether to call it)
* **Parameters** – the inputs the function needs, each with a name, description, and type
* **Python code** – what the function actually does when called

The LLM reads the function name, description, and parameter descriptions when deciding whether to call the function and what to pass as arguments. **Name and describe everything clearly** — this directly affects whether the LLM uses your function correctly.

### Example: a simple addition function

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
def add_two_numbers(conv, first_number: float, second_number: float) -> str:
    total = first_number + second_number
    return f"The total is {total}"
```

Parameters:

* `first_number` – *The first number the user wants to add* (type: number)
* `second_number` – *The second number the user wants to add* (type: number)

<Warning>
  Functions called by the LLM must return either a **string** or a **dictionary** with specific keys. Returning an integer, list, or other type will cause an error that the LLM will encounter and retry up to three times before giving up.
</Warning>

## Making a function visible to the LLM

Creating a function is not enough. **The LLM will not call a function it does not know exists.**

To make a function available to the LLM, you must reference it somewhere – in a topic action, a flow step, or directly in the Behavior field using the `@function_name` syntax.

When you reference a function, Agent Studio highlights it and registers it in the LLM request. The LLM will then see the function's full definition (name, description, parameters) and can choose to call it.

<Warning>
  A common mistake is creating a function and testing the agent, only to find the LLM never calls it. If the function was not triggered, confirm you have referenced it with `@function_name` in a topic action, a flow step, or the Behavior field. Use the **Tool calls** layer in [Conversation Diagnosis](/analytics/conversations/diagnosis) to verify what the agent actually did.
</Warning>

## What the LLM actually sees

When you reference a function, it is added to the prompt sent to the LLM. The prompt is structured roughly like this:

| Section                           | What it contains                                                               |
| --------------------------------- | ------------------------------------------------------------------------------ |
| **Base system prompt – intro**    | Your Personality and Role fields concatenated                                  |
| **Base system prompt – behavior** | Everything in the Behavior field                                               |
| **Context information**           | Knowledge content retrieved for this turn (empty if no relevant topic matched) |
| **Conversation history**          | The full transcript so far, alternating `assistant` / `user` roles             |
| **Functions**                     | Definitions of any functions that have been referenced                         |

The LLM does **not** see the Python code inside the function. It only sees the function's name, description, and parameters.

The **Greeting** is different – it is hard-coded text played at the start of every call and is **not** generated by the LLM. It does appear in conversation history so the LLM knows how the call opened, but no LLM request is made to produce it.

This means your function name, description, and parameter descriptions are all part of the prompt. Write them with the same care as any other prompt text.

<Quiz
  questions={[
{
q: "You create a function called `check_order_status` but the LLM never calls it. What is the most likely cause?",
options: [
  "The function has a bug in the Python code",
  "The function has not been referenced anywhere in a topic, flow, or behavior",
  "The LLM does not support functions",
  "The function name contains underscores",
],
correct: 1,
explanation: "Creating a function does not make it visible to the LLM. You must reference it – in a topic action, a flow step, or the Behavior field. If the function does not appear in the LLM request's functions list, it has not been registered.",
}
]}
/>

<FillBlank prompt="To make a function visible to the LLM, you reference it in a topic, flow, or Behavior field using the syntax: @________" answer={["function_name", "@function_name"]} hint="What prefix makes a function reference?" explanation="Use @function_name in Behavior, a topic action, or a flow step. Agent Studio highlights it and registers the function in the LLM request." />

## The two-request pattern

When the LLM calls a function, it takes **two LLM requests** to produce the final response to the user.

<Steps>
  <Step title="Request 1: the LLM decides to call a function">
    The LLM receives the user input, sees the available function definitions, and outputs a **tool call** rather than text.

    The response content is empty. The tool call object contains the function name and the parameter values the LLM extracted from the conversation.
  </Step>

  <Step title="The function runs">
    Agent Studio executes the Python function with the provided parameters and gets back a result.
  </Step>

  <Step title="Request 2: the LLM reports the result">
    The function result is inserted into the conversation history under a `function` role. The LLM sees this result and produces a **text response** to communicate the result to the user.
  </Step>
</Steps>

Enable the **Tool calls** toggle in [Conversation Diagnosis](/analytics/conversations/diagnosis) to see what parameters the agent passed to the function and what was returned. The function call and its result both appear in the turn timeline.

### What the conversation history looks like after a tool call

```
assistant: "Hi, thanks for calling. How can I help?"
user: "What's 2 plus 3?"
assistant: [calls add_two_numbers with first_number=2, second_number=3]
function: "The total is 5"
assistant: "2 plus 3 equals 5."
```

The `function` role is the third role alongside `user` and `assistant`. This is how function results are fed back into the LLM's context.

<Quiz
  questions={[
{
q: "Why are there two LLM requests when a function is called?",
options: [
  "The first request checks permissions, the second runs the function",
  "The first request produces the tool call, the second reads the function result and generates the user response",
  "Both requests are identical – it is a retry mechanism",
  "The first request is for voice, the second for chat",
],
correct: 1,
explanation: "Request 1 produces the tool call (the LLM's decision to call the function). After the function runs, its result is inserted into conversation history. Request 2 lets the LLM read that result and produce the text response for the user.",
}
]}
/>

## Try it yourself

<Steps>
  <Step title="Create a function">
    In **Tools**, create a function called `get_store_hours` with no parameters. Return a string like `"The store is open Monday–Friday, 9am to 6pm."`.
  </Step>

  <Step title="Reference it">
    In **Behavior > General > Rules**, add a reference to `@get_store_hours`. Confirm it highlights.
  </Step>

  <Step title="Test it">
    Open **Chat** and ask "What are your opening hours?" Enable the **Tool calls** layer in [Conversation Diagnosis](/analytics/conversations/diagnosis) to confirm:

    * The `get_store_hours` tool call appears with its parameters
    * The returned value is shown in the turn timeline
    * The agent's spoken response incorporates the returned value
  </Step>
</Steps>

<CardGroup cols={2}>
  <Card title="← Previous: Complex topics" icon="arrow-left" href="/learn/guides/advanced/add-complex-kb-topic">
    Lesson 1 of 8
  </Card>

  <Card title="Next: Return values →" icon="arrow-right" href="/learn/guides/advanced/tool-return-values">
    Lesson 3 of 8
  </Card>
</CardGroup>

<ProgressTracker lessonKey="l2-2-using-functions" lessonNum={2} totalLessons={8} level="Level 2" />
