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

# Grouped Results tool

> Let your agent curate search results into themed groups instead of one flat list of hits.

export const Index = () => <Tooltip tip="An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.">
    index
  </Tooltip>;

The Grouped Results tool lets your agent curate which search results to show, group them by theme, and explain each choice.

Enable it for broad or subjective questions that individual search queries can't answer well:

* **Queries that span several categories**: "show me summer outfits" needs separate searches for t-shirts, shorts, dresses, and accessories. A single result list mixes them together.
* **Queries that don't map to your facet filters**: "dresses for goths" might not map to the facets defined on your <Index />. The agent has to search broadly, then shortlist.

For "show me summer outfits", the agent runs several searches, picks the strongest hits, and returns something like "Beach days", "City heat", and "Evening cover-ups", each with its own carousel and a short reason for the grouping.

<Accordion title="Key capabilities">
  * **Multi-search curation**: the agent searches as many times as it needs, then selects across all the results it has seen
  * **Themed groups**: results are split into cohesive groups, each with a title and a short reason
  * **Streaming**: users can see results immediately as they come in without waiting for the full generation
</Accordion>

## How the tool works

The agent searches as many times as it needs, then calls the Grouped Results tool with its curated selection.
Your frontend renders groups as they arrive (as long as your LLM provider supports tool input streaming).

```mermaid theme={"system"}
sequenceDiagram
    participant User as user
    participant App as Your app
    participant Agent as Algolia Agent
    participant Search as Algolia Search tool
    participant Display as Grouped Results tool

    User->>App: "Show me summer outfits"
    App->>Agent: Send the message
    Agent-->>App: Send the rendering hint
    loop One or more searches
        Agent->>Search: Run a search
        Search->>Agent: Return hits
    end
    Agent->>Display: Call the tool with intro and groups
    Agent-->>App: Stream the curated payload
    App->>App: Match each objectID to a record (Hydration)
    App->>User: Render the groups as they arrive
    Display->>Agent: Return a status
    opt Call isn't terminal
        Agent-->>App: Add a closing message
    end
```

### Input

The agent generates the curated payload and sends it to the Grouped Results tool as the tool's input.
For example, if the user asks for "summer outfits", the agent might generate a payload like this:

```json JSON icon=braces expandable theme={"system"}
{
  // Introduces the whole selection
  "intro": "Here are a few summer outfit ideas:",
  "groups": [
    {
      // The group's heading
      "title": "Beach days",
      // Why these results belong together
      "why": "light, breathable pieces",
      "results": [
        // Each result pairs an objectID from an earlier search
        // with the reason the agent picked it
        { "objectID": "PROD-1042", "why": "linen, packs small" },
        { "objectID": "PROD-2287", "why": "quick-dry fabric" },
        { "objectID": "PROD-3391", "why": "wide brim, UV weave" }
      ]
    },
    {
      "title": "City heat",
      "why": "smart pieces that stay cool",
      "results": [
        { "objectID": "PROD-5510", "why": "breathable cotton weave" },
        { "objectID": "PROD-6624", "why": "loose cut, no lining" },
        { "objectID": "PROD-7702", "why": "leather, stays cool" }
      ]
    }
  ]
}
```

Read the `why` fields to understand how the agent grouped and picked.

<AccordionGroup>
  <Accordion title="Why the payload contains object IDs">
    * **Fewer hallucinations**: the agent can't invent a name, price, or image for a record it already retrieved through search. It can still reference an `objectID` that doesn't exist, which hydration catches at render time
    * **Fewer tokens**: the payload stays small whatever your records weigh, on both the input and the output side
    * **Quicker first paint**: fewer tokens to write means the first group arrives sooner
  </Accordion>

  <Accordion title="How hydration works">
    A reference resolves against the hits an earlier search already returned in the same conversation.
    Your frontend keeps those hits, then looks up each `objectID` as the payload streams in.
    Two consequences are worth planning for:

    * **A curated card only shows attributes the search tool retrieved.** If a card needs an image or a price, make sure `attributesToRetrieve` on the search tool includes it. For more information, see [Search parameters](/doc/guides/algolia-ai/agent-studio/how-to/tools/algolia-search#search-parameters).
    * **Unresolved `objectID` values disappear.** If an `objectID` doesn't match a record from an earlier search, that entry drops out, and a group left with no entries isn't rendered.
  </Accordion>

  <Accordion title="How the rendering hint works">
    Agent Studio adds the hint at the start of each assistant message when the tool is enabled, before the agent has called anything.
    Your frontend can then decide how to render the whole message without waiting to find out whether curation follows.
    InstantSearch reads it for you and renders curated groups in place of the search tool's own result list.

    The hint travels as assistant message metadata, once per message.
    `ai-sdk` v5 carries it on the `start` event that opens the stream, and v4 sends a `message-metadata` data part right after the first step:

    <CodeGroup>
      ```txt ai-sdk v5 theme={"system"}
      data: {"type": "start", "messageId": "msg_1", "messageMetadata": {"groupedResultsEnabled": true}}

      data: {"type": "start-step"}

      ```

      ```txt ai-sdk v4 theme={"system"}
      f:{"messageId": "msg_1"}
      2:[{"type": "message-metadata", "messageId": "msg_1", "messageMetadata": {"groupedResultsEnabled": true}}]
      ```
    </CodeGroup>

    The hint is absent when the tool is off, so treat a missing value as `false`.
    It comes from the agent's configuration, so it holds for every query that agent handles, including the turns where the agent decides not to curate.
  </Accordion>

  <Accordion title="Why the agent can skip the tool call">
    Enabling the tool makes it available to the agent, which still decides turn by turn whether to curate.
    Skipping the call can be the right choice, for example when a search returns nothing worth showing.

    On a turn where it searches without curating, users see the answer text without any product cards, because the search tool's own result list stays hidden.

    If that happens more often than you want, tell the agent when to group in its instructions.
    For more information, see [Tips for writing efficient prompts](/doc/guides/algolia-ai/agent-studio/how-to/prompting).
  </Accordion>
</AccordionGroup>

### Output

The tool output is only an execution artifact that carries diagnostic information.

On the normal path it reports success:

```json JSON icon=braces theme={"system"}
{
  "status": "success"
}
```

When the payload referenced `objectID` values that no earlier search returned, the output lists them:

```json JSON icon=braces theme={"system"}
{
  "status": "warning",
  "unknownObjectIds": ["PROD-4021", "PROD-4022"]
}
```

A warning status is informational. The widget still renders without those entries.
Treat repeated warnings as a signal that the agent is inventing `objectID` values, and give it a clearer instruction to select only from results it has seen.

To inspect the input and output of a call, expand the tool indicator in the **Live preview** panel.
For more information, see [Test tools in live preview](/doc/guides/algolia-ai/agent-studio/how-to/tools/overview#test-tools-in-live-preview).

To review calls from real users, open the **Conversations** tab.

## Configuration

<Note>
  An agent accepts one Grouped Results tool at most, and only when it also has
  an [Algolia Search
  tool](/doc/guides/algolia-ai/agent-studio/how-to/tools/algolia-search).
</Note>

<Tabs sync={false}>
  <Tab title="From the dashboard">
    <img src="https://mintcdn.com/algolia/C7l77FbvfKf-jouX/doc/guides/algolia-ai/agent-studio/how-to/tools/agent-studio-configure-grouped-results.png?fit=max&auto=format&n=C7l77FbvfKf-jouX&q=85&s=a2e1d7a836df9209c6fe291479b49dbd" alt="Screenshot of the &#x22;Configure Algolia Search&#x22; dialog with the &#x22;Grouped Results&#x22; switch turned on and the &#x22;Advanced settings&#x22; accordion expanded." width="3600" height="2102" data-path="doc/guides/algolia-ai/agent-studio/how-to/tools/agent-studio-configure-grouped-results.png" />

    From the [Agent Studio agent edit view](https://dashboard.algolia.com/generativeAi/agent-studio/agents) in the Algolia dashboard:

    1. On your Algolia Search tool, click **Configure**
    2. Turn on **Grouped Results**
    3. Optional: to change how many groups the agent creates, or how many results go in each, open **Advanced settings**
    4. Click **Save**
  </Tab>

  <Tab title="With the API">
    Add the tool to the agent's `tools` array, alongside your search tool:

    ```json JSON icon=braces theme={"system"}
    {
      "type": "algolia_grouped_results",
      "name": "algolia_grouped_results",
      "minGroups": 1,
      "maxGroups": 3,
      "minResultsPerGroup": 3,
      "maxResultsPerGroup": 6,
      "isTerminal": false
    }
    ```

    **Fields**

    | Field                | Default | Notes                               |
    | -------------------- | ------- | ----------------------------------- |
    | `type`               | -       | Must be `algolia_grouped_results`   |
    | `name`               | -       | Must be `algolia_grouped_results`   |
    | `minGroups`          | `1`     | From 1 to 6                         |
    | `maxGroups`          | `3`     | From 1 to 6                         |
    | `minResultsPerGroup` | `3`     | From 1 to 15                        |
    | `maxResultsPerGroup` | `6`     | From 1 to 15                        |
    | `isTerminal`         | `false` | Ends the run once the tool succeeds |

    For more information,
    see the [Agent Studio API reference](/doc/rest-api/agent-studio).
  </Tab>
</Tabs>

### Group and result constraints

The constraints you set go into the tool's schema, so the agent has to respect them.
We recommend that you start with the defaults.

The minimums are floors the agent has to fill.
A high `minResultsPerGroup` on a thin catalog pushes it toward weaker matches, and a high `minGroups` makes it invent themes it wouldn't have found on its own.
Lower them when that happens.

### Terminal tool calls

By default the agent writes a closing message after the tool runs.
Set `isTerminal` to `true`, or turn on **Terminate run after display** in the dashboard, to end the run as soon as the call succeeds.

Use it when the curated groups are the answer, and the closing sentence adds latency without adding meaning.
Keep it off when the agent should comment on what it selected, or ask a follow-up question.

## Implementation

### With InstantSearch (Recommended)

The `Chat` widget does all the heavy lifting for you:

* rendering the curated groups as they stream in
* hydrating every `objectID` reference from previous search results
* reading the rendering hint

For the base setup, see [Integrate Agent Studio](/doc/guides/algolia-ai/agent-studio/how-to/integration).

Here are the minimum supported versions for handling the Grouped Results tool:

| Package                                               | Minimum supported version |
| ----------------------------------------------------- | ------------------------- |
| `instantsearch.js`                                    | `4.118.0`                 |
| `react-instantsearch` and `react-instantsearch-core`  | `7.50.0`                  |
| `instantsearch.css`                                   | `8.23.0`                  |
| `instantsearch-ui-components`, if you use it directly | `0.41.0`                  |

Two rendering details are worth knowing:

* **Replacing the search tool's rendering keeps the curated behavior.** Pass your own layout for the search tool and it still stays hidden during curated turns, unless you set your own `shouldRender`.
* **Recommend carousels aren't affected.** Only the search tool defers to curated groups.

The widget renders `intro`, each group `title`, and the group `why` under its title.
The per-result `why` is not rendered by default but is available through `__groupedResult` if you want to display it.

Curated cards use the same item template as the rest of the chat, so styling them once covers both.

<CodeGroup>
  ```jsx React theme={"system"}
  <Chat
    agentId={agentId}
    itemComponent={({ item }) => (
      <div>
        <h3>{item.title}</h3>
        <p>{item.price}</p>
        {item.__groupedResult?.why && <small>{item.__groupedResult.why}</small>}
      </div>
    )}
  />
  ```

  ```js JavaScript theme={"system"}
  chat({
    container: "#chat",
    agentId,
    templates: {
      item: ({ item }) => `
        <h3>${item.title}</h3>
        <p>${item.price}</p>
        ${item.__groupedResult?.why ? `<small>${item.__groupedResult.why}</small>` : ""}
      `,
    },
  });
  ```
</CodeGroup>

To restyle the groups themselves, target these classes:

| Class                                   | Element                                         |
| --------------------------------------- | ----------------------------------------------- |
| `ais-ChatToolGroupedResults`            | The whole curated block                         |
| `ais-ChatToolGroupedResults-intro`      | The agent's introductory sentence               |
| `ais-ChatToolGroupedResults-group`      | One group, including its title and its carousel |
| `ais-ChatToolGroupedResults-groupTitle` | A group title                                   |
| `ais-ChatToolGroupedResults-groupWhy`   | The reason a group exists                       |
| `ais-ChatToolGroupedResults-streaming`  | The caption shown while the payload streams     |

For full control over the layout, pass your own component for the `algolia_grouped_results` tool in the `Chat` widget's `tools` option.
Build it with `createGroupedResultsToolComponent` from `instantsearch-ui-components` to keep the streaming and hydration behavior, and supply your own group and carousel markup.
That component also accepts a `translations.streamingLabel` option, which replaces the default "Curating results…" caption.

### With custom code

A custom chat UI has to cover what the widget does for you.

* **Read the rendering hint** at the start of each assistant message, and use it to decide whether to render the search tool's results yourself.
* **Build a lookup** of the hits from every completed search tool call in the conversation, keyed by `objectID`, and resolve each reference against it.
* **Drop entries you can't resolve**, along with any group they leave empty.
* **Parse the streaming input with a tolerant JSON parser** to render groups before the call completes. Skip this and the groups appear together at the end.
* **Render the last result of the last group only once its `objectID` is complete**, since a value still mid-delta can resolve to the wrong record.

## See also

* [Tools overview](/doc/guides/algolia-ai/agent-studio/how-to/tools/overview)
* [Algolia Search tool](/doc/guides/algolia-ai/agent-studio/how-to/tools/algolia-search)
* [Integrate Agent Studio](/doc/guides/algolia-ai/agent-studio/how-to/integration)
