# Querying Structured Data (Tables)

> Build an agentic RAG system that can query structured data (tables).

<a target="_blank" href="https://colab.research.google.com/github/cohere-ai/cohere-developer-experience/blob/main/notebooks/guides/agentic-rag/agentic_rag_pt5_structured_data_tables.ipynb">
  Open in Colab
</a>

In the previous tutorials, we explored how to build agentic RAG applications over unstructured and semi-structured data. In this tutorial and the next, we'll turn our focus to structured data.

This tutorial focuses on querying tables, and the next tutorial will be about querying SQL databases.

Consider a scenario where you have a CSV file containing evaluation results for an LLM application.

A user might ask questions like "What's the average score for a specific use case?" or "Which configuration has the lowest latency?". These queries require not just retrieval, but also data analysis and interpretation.

In this tutorial, we'll cover:

* Creating a function to execute Python code
* Setting up a tool to interact with tabular data
* Building an agent for querying tabular data
* Running the agent

Let's get started by setting up our environment and defining the necessary tools for our agent.

## Setup

To get started, first we need to install the `cohere` library and create a Cohere client.

```python PYTHON
! pip install cohere pandas -qq
```

```python PYTHON
import json
import os
import cohere

co = cohere.ClientV2(
    "COHERE_API_KEY"
)  # Get your free API key: https://dashboard.cohere.com/api-keys
```

And here's the data we'll be working with. `evaluation_results.csv` is a CSV file containing evaluation results for a set of LLM applications - name extraction, email drafting, and article summarization.

The file has the following columns:

* `usecase`: The use case.
* `run`: The run ID.
* `score`: The evaluation score for a particular run.
* `temperature`: The temperature setting of the model for a particular run.
* `tokens`: The number of tokens generated by the model for a particular run.
* `latency`: The latency of the model's response for a particular run.

<Note>
   Important: the data can be 

  [found here](https://github.com/cohere-ai/cohere-developer-experience/blob/main/notebooks/guides/agentic-rag/evaluation_results.csv)

  . Make sure to have the file in the same directory as this notebook for the imports to work correctly. 
</Note>

```python PYTHON
import pandas as pd

df = pd.read_csv("evaluation_results.csv")

df.head()
```

<div>
  <table border="1" class="dataframe fern-table">
    <thead>
      <tr>
        <th />

        <th>
          usecase
        </th>

        <th>
          run
        </th>

        <th>
          score
        </th>

        <th>
          temperature
        </th>

        <th>
          tokens
        </th>

        <th>
          latency
        </th>
      </tr>
    </thead>

    <tbody>
      <tr>
        <th>
          0
        </th>

        <td>
          extract_names
        </td>

        <td>
          A
        </td>

        <td>
          0.5
        </td>

        <td>
          0.3
        </td>

        <td>
          103
        </td>

        <td>
          1.12
        </td>
      </tr>

      <tr>
        <th>
          1
        </th>

        <td>
          draft_email
        </td>

        <td>
          A
        </td>

        <td>
          0.6
        </td>

        <td>
          0.3
        </td>

        <td>
          252
        </td>

        <td>
          2.50
        </td>
      </tr>

      <tr>
        <th>
          2
        </th>

        <td>
          summarize_article
        </td>

        <td>
          A
        </td>

        <td>
          0.8
        </td>

        <td>
          0.3
        </td>

        <td>
          350
        </td>

        <td>
          4.20
        </td>
      </tr>

      <tr>
        <th>
          3
        </th>

        <td>
          extract_names
        </td>

        <td>
          B
        </td>

        <td>
          0.2
        </td>

        <td>
          0.3
        </td>

        <td>
          101
        </td>

        <td>
          2.85
        </td>
      </tr>

      <tr>
        <th>
          4
        </th>

        <td>
          draft_email
        </td>

        <td>
          B
        </td>

        <td>
          0.4
        </td>

        <td>
          0.3
        </td>

        <td>
          230
        </td>

        <td>
          3.20
        </td>
      </tr>
    </tbody>
  </table>
</div>

## Creating a function to execute Python code

Here, we introduce a new tool that allows the agent to execute Python code and return the result. The agent will use this tool to generate pandas code for querying the data.

To create this tool, we'll use the `PythonREPL` class from the `langchain_experimental.utilities` module. This class provides a sandboxed environment for executing Python code and returns the result.

First, we define a `python_tool` that uses the `PythonREPL` class to execute Python code and return the result.

Next, we define a `ToolInput` class to handle the input for the `python_tool`.

Finally, we create a function `analyze_evaluation_results` that takes a string of Python code as input, executes the code using the Python tool we created, and returns the result.

**IMPORTANT:**

The source code for tool definitions can be [found here](https://github.com/cohere-ai/cohere-developer-experience/blob/main/notebooks/guides/agentic-rag/tool_def.py). Make sure to have the `tool_def.py` file in the same directory as this notebook for the imports to work correctly.

```python PYTHON
from tool_def import (
    analyze_evaluation_results,
    analyze_evaluation_results_tool,
)
```

```python PYTHON
functions_map = {
    "analyze_evaluation_results": analyze_evaluation_results
}
```

## Setting up a tool to interact with tabular data

Next, we define the `analyze_evaluation_results` tool. There are many ways we can set up a tool to work with CSV data, and in this example, we are using the tool description to provide the agent with the necessary context for working with the CSV file, such as:

* the name of the CSV file to load
* the columns of the CSV file
* additional instructions on what libraries to use (in this case, `pandas`)

The parameter of this tool is the `code` string containing the Python code that the agent writes to analyze the data.

```python PYTHON
analyze_evaluation_results_tool = {
    "type": "function",
    "function": {
        "name": "analyze_evaluation_results",
        "description": "Generate Python code using the pandas library to analyze evaluation results from a dataframe called `evaluation_results`. The dataframe has columns 'usecase','run','score','temperature','tokens', and 'latency'. You must start with `import pandas as pd` and read a CSV file called `evaluation_results.csv` into the `evaluation_results` dataframe.",
        "parameters": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "string",
                    "description": "Executable Python code",
                }
            },
            "required": ["code"],
        },
    },
}
```

```python PYTHON
tools = [analyze_evaluation_results_tool]
```

## Building an agent for querying tabular data

Next, let's create a `run_agent` function to run the agentic RAG workflow, the same as in Part 1.

The only change we are making here is to make the system message simpler and more specific since the agent now only has one tool.

```python PYTHON
system_message = """## Task and Context
ou are an assistant who helps developers analyze LLM application evaluation results from a CSV files."""
```

```python PYTHON
model = "command-a-03-2025"


def run_agent(query, messages=None):
    if messages is None:
        messages = []

    if "system" not in {m.get("role") for m in messages}:
        messages.append({"role": "system", "content": system_message})

    # Step 1: get user message
    print(f"Question:\n{query}")
    print("=" * 50)

    messages.append({"role": "user", "content": query})

    # Step 2: Generate tool calls (if any)
    response = co.chat(
        model=model, messages=messages, tools=tools, temperature=0.3
    )

    while response.message.tool_calls:

        print("TOOL PLAN:")
        print(response.message.tool_plan, "\n")
        print("TOOL CALLS:")
        for tc in response.message.tool_calls:
            if tc.function.name == "analyze_evaluation_results":
                print(f"Tool name: {tc.function.name}")
                tool_call_prettified = print(
                    "\n".join(
                        f"  {line}"
                        for line_num, line in enumerate(
                            json.loads(tc.function.arguments)[
                                "code"
                            ].splitlines()
                        )
                    )
                )
                print(tool_call_prettified)
            else:
                print(
                    f"Tool name: {tc.function.name} | Parameters: {tc.function.arguments}"
                )
        print("=" * 50)

        messages.append(response.message)

        # Step 3: Get tool results
        for tc in response.message.tool_calls:
            tool_result = functions_map[tc.function.name](
                **json.loads(tc.function.arguments)
            )
            tool_content = [
                {
                    "type": "document",
                    "document": {"data": json.dumps(tool_result)},
                }
            ]

            messages.append(
                {
                    "role": "tool",
                    "tool_call_id": tc.id,
                    "content": tool_content,
                }
            )

        # Step 4: Generate response and citations
        response = co.chat(
            model=model,
            messages=messages,
            tools=tools,
            temperature=0.3,
        )

    messages.append(
        {
            "role": "assistant",
            "content": response.message.content[0].text,
        }
    )

    # Print final response
    print("RESPONSE:")
    print(response.message.content[0].text)
    print("=" * 50)

    # Print citations (if any)
    verbose_source = (
        False  # Change to True to display the contents of a source
    )
    if response.message.citations:
        print("CITATIONS:\n")
        for citation in response.message.citations:
            print(
                f"Start: {citation.start}| End:{citation.end}| Text:'{citation.text}' "
            )
            print("Sources:")
            for idx, source in enumerate(citation.sources):
                print(f"{idx+1}. {source.id}")
                if verbose_source:
                    print(f"{source.tool_output}")
            print("\n")

    return messages
```

## Running the agent

Let's ask the agent a few questions, starting with this query about the average evaluation score in run A.

To answer this query, the agent needs to write Python code that uses the pandas library to calculate the average evaluation score in run A. And it gets the answer right.

```python PYTHON
messages = run_agent("What's the average evaluation score in run A")