Tableau Langchainのハンズオントレーニングメモ

# はじめに

Tableau Conferencde 2025のハンズオンセッションである「[Tableau for AI Developer](https://reg.salesforce.com/flow/plus/tc25/sessioncatalog/page/catalog/session/1738267142494001uRev)」を途中までやってみたメモです。Tableau Langchainを使ってTableau Cloud上にあるサンプルスーパーストアに関する質問を受け付けるエージェントを作るハンズオンです。

# 前提条件

私自身LangchainやLanggraphはド素人。なので解釈ミスもあるかもしれませんし、分からなかったことのメモも残しています。あと一部手書きで写経したコードを載せているので誤字があるかもしれません。後述するtableau-langchainのgithubに正しいコードがあるのでそちらを確認してください。

# まずはお天気エージェントを作るハンズオン
まずはTableauはおいておいて、お天気エージェントを作ってLangchainとLanggraphの基本を理解するハンズオンからスタート。

## 環境のセットアップ
ハンズオン環境ではセットアップが完了していたので割愛。ただしopenaiのAPIキーを.envから読み込み動作は実施していた。ちなみにopenai_keyはこの後一度も使っていないので暗黙的に利用されているのかもしれない。

```py
load_dotenv()
openai_key = os.getenv("OPENAI_API_KEY")
```

## 生成AIモデルの指定
4o-miniが性能とコストのバランスが良いとのことでハンズオンではこちらを使用するとのこと。以下の通りOpenAIの4o-miniを使うことを指定する。ここで先ほど設定したopenai_keyを使うのかと思いきや使わない。不思議である。langchain_openaiの使い方を調査しないと分からなそう。

```py
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
```

## エージェントのための「ツール」を定義
エージェントが利用できるツールの定義をする。langchain_coreのtoolというモジュールを使って特定の関数をツールとして定義し、`tools`という配列にツールを入れることでlangchainに利用できるツール群を渡すことができる模様。ちなみにLiteralというモジュールを使うことで引数として指定できる文字列の種類を限定することができる模様。今回は引数の`nyc`か`sf`しか受け付けない`get_weather`という関数をツールとして定義。それぞれ固定された返答が返ってくるようなシンプルなツールです。

```py
from typing import Literal
from langchain_core.tools import tool

@tool
def get_weather(city: Literal["nyc", "sf"]):
  if city == "nyc":
    return "It might be cloudy in nyc"
  elif city == "sf":
    return "It's always sunny in sf"
  else:
    raise AssertionError("Unknown city")

tools = [get_weather]
```

## Reactエージェントを定義する
ReactはReason+Actの意味で、推論とアクションをできるエージェントを作ることを意味する。ただアクションを実行するだけでなくどのアクションをなぜ実行すべきかを考えることができる。

```py
from langgraph.prebuilt import create_react_agent
graph = create_react_agent(model, tools=tools)
```

langgraphは作成したgraphを可視化する機能も持っている。以下のコードで画像を生成できる。

```py
from IPython.display import Image, display
display(Image(graph.get_traph().draw_mermade_png()))
```

すると以下のような画像が出てくる。おそらく「処理が始まったらagentが推論をはじめて時にtoolsを利用して何らかのタイミングで終わる」ということを表しているのではないか。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200471/c9630c93-a861-43fa-a80e-2dd0775a5c8d.png)

## print_streamを使ってエージェントのプロセスを可視化
prinht_streamを使うことでエージェントが何を考えてどうプロセスで動いているのかをリアルタイムに見ることできる。

```py
def print_stream(stream):
  for s in stream:
    message = s["messages"][-1]
    if isinstance(message, tuple):
      print(message)
    else:
      message.pretty_print()
```

## Agentに聞いてみよう！（お待ちかね）
以下の通りAgentに質問をしてどんな動作をするのか確かめます。今回はサンフランシスコは雨が降っていますか？と聞いてみました。

```py
your_prompt = "Is it raining in San Francisco?"

inputs = {"messages": {("user", your_prompt)}}
print_stream(graph.stream(inputs, stream_mode="values"))
```

するとすぐに「get_weather」ツールを使って返り値を得て、その返り値をもとに回答をしてくれました。ツールを使うかどうかは関数名から判断しているんですかね？すごい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200471/2de493a3-f417-456c-a808-611bdce5025f.png)

アメリカ最大の都市の天気を聞いてみるとNew Yorkの天気を返してくれました。これはさすが生成AIって感じですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200471/e4d68e22-76a4-41f4-b502-642e85f8de41.png)

# Tableauとエージェントをつなごう！
とうとうお待ちかね、エージェントにTableau上のデータを読み込んでもらいます。この実装のためには以下の二つのTableauの機能を使います。

- VizQL Data Service API: Tableauにパブリッシュされたデータソースにクエリを投げるAPI
- Metadata API: パブリッシュデータソースのメタデータを読み込むAPI

今回のハンズオンではサンプルスーパーストアのデータを読み込んだSuperstore Agentを使います。

Tableau Developerサイトで試すこともできるのでDeveloperサイトの取得方法の記事も紹介されていました。

https://cristiansaavedra.medium.com/how-to-get-your-tableau-online-sandbox-61d6a1f8b697

## Connected Appとして許可する
ここはちゃんと勉強しないといけなそうなので、とりあえず雰囲気理解だけにとどめる。詳細は以下の記事を観れば良さそう。

https://www.tableau.com/blog/unlock-power-personalized-analytics-user-attribute-functions

Tableauにエージェントが接続するために以下の情報を与えている模様。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200471/2a9d197c-d537-47b5-9b50-be249e39ed13.png)

## シンプルなQAエージェントを作る
作り方はシンプルで`langchain_tableau`に含まれている`simple_datasource_qa`というツールを使って、エージェント用のツールを定義していました。`simple_datasource_qa`の引数は以下の通りですが、データソースのLUIDを指定しているので、質問に応じてデータソースを切り替えるような機能は持っていない本当にシンプルなQAツールのようです。

```py
# langchain_tableau packages
from langchain_tableau.tools.simple_datasource_qa import initialize_simple_datasource_qa

# Large Language Model for writing VDS queries from natural language within the tool
tooling_model = 'gpt-4o-mini'

# Initalize the tool for querying Tableau Datasources through VDS
analyze_datasource = initialize_simple_datasource_qa(
    domain=tableau_server,
    site=tableau_site,
    jwt_client_id=tableau_jwt_client_id,
    jwt_secret_id=tableau_jwt_secret_id,
    jwt_secret=tableau_jwt_secret,
    tableau_api_version=tableau_api_version,
    tableau_user=tableau_user,
    datasource_luid=datasource_luid,
    tooling_llm_model=tooling_model
)

# add the tool to a List to give to the agent
tools = [ analyze_datasource ]
```

加えて、以下の通りかなり事細かにエージェントのアイデンティティ、ツールの使い方、回答の仕方を書いているのが印象的でした。特にデータから読み解けることだけを回答する旨をTool UsageにもReponse Guidelineにも書いてあるのは春市ネーション防止のために重要なのでしょうね。

```py
# Agent Identity Definition
identity = """
You are **Agent Superstore**, the veteran AI analyst who has spent years exploring the aisles of the legendary Superstore dataset.
A dataset many Tableau users know and love! 
You live and breathe Superstore data: sales, profits, regions, categories, customer segments, shipping modes, you name it.

Your special mission **today at Tableau Conference 2025** is to help attendees experience the power of Tableau for Langchain Agents. 
You'll be their guide, using this new tool to query the Superstore dataset directly and uncover insights in real-time.

**When you first introduce yourself:**
1.  Greet the attendees and welcome them to the Tableau Conference 2025 hands-on session.
2.  Introduce yourself as Agent Superstore, the AI expert on the classic Superstore dataset.
3.  Briefly explain your purpose: to demonstrate Tableau analytics via agents
"""
# Main System Prompt
system_prompt = f"""**Agent Identity:**
{identity}

**Core Instructions:**

You are an AI Analyst specifically designed to generate data-driven insights from datasets using the tools provided. 
Your goal is to provide answers, guidance, and analysis based on the data accessed via your tools. 
Remember your audience: Tableau users at a conference session, likely familiar with Superstore aka the best dataset ever created.

**Tool Usage Strategy:**

You have access to the following tool:

1.  **`tableau_query_tool` (Data Source Query):** This is your primary tool for interacting with data.
    * **Prioritize this tool** for nearly all user requests asking for specific data points, aggregations, comparisons, trends, or filtered information from datasets.
    * Use it to find specific values (e.g., sales for 'Technology' in 'West' region), calculate aggregates (e.g., `SUM(Sales)`, `AVG(Profit Ratio)`), filter data (e.g., orders in 2023), group data (e.g., sales `BY Category`), and find rankings (e.g., top 5 products by quantity).
    * Be precise in formulating the queries based on the user's request.

**Response Guidelines:**

* **Grounding:** Base ALL your answers strictly on the information retrieved from your available tools.
* **Clarity:** Always answer the user's core question directly first.
* **Source Attribution:** Clearly state that the information comes from the **dataset** accessed via the Tableau tool (e.g., "According to the data...", "Querying the datasource reveals...").
* **Structure:** Present findings clearly. Use lists or summaries for complex results like rankings or multiple data points. Think like a mini-report derived *directly* from the data query.
* **Tone:** Maintain a helpful, and knowledgeable, befitting your Tableau Superstore expert persona.

**Crucial Restrictions:**
* **DO NOT HALLUCINATE:** Never invent data, categories, regions, or metrics that are not present in the output of your tools. If the tool doesn't provide the answer, state that the information isn't available in the queried data.
"""
```


これらの情報をもとに以下の通りsuperstore_agentを定義します。天気のエージェントを作った時と同様にmodelとツールを指定するのに加えて、`prompt`引数に先ほど定義した`system_prompt`を与えています。これでエージェントの立ち位置や何をすべきか？を指定しているようです。

```py
# initialize a Large Language Model to be the "brains" of the Agent
model = ChatOpenAI(model='gpt-4o-mini', temperature=0)

superstore_agent = create_react_agent(model=model, tools=tools, prompt=system_prompt)
```

## 質問してみよう！
天気エージェントの時と同様にprint_streamを使って質問を投げてみます。質問は「リージョン別の売上合計はいくら？」です。

```py
your_prompt = 'What are the total sales for each region?'
# Run the agentmessages = {"messages": [("user", your_prompt)]}print_stream(superstore_agent.stream(messages, stream_mode="values"))
```

すると以下の結果を得られました。`simple_datasource_qa`ツールに`total sales for each region`とだけ与えているのはツールの使い方を理解しているからなのでしょうか？そして`simple_datasource_qa`の回答は自然言語で与えられているようです。ただ数値を返しているだけではなく、デーソースの名前や管理者の情報、ユーザーに返答するときに気を付けるべき点なども記載されています。エージェント作成時に指定したプロンプトだけでなく、ツールの回答においても細かく指示を出しているのですね。

```
================================ Human Message =================================

What are the total sales for each region?
================================== Ai Message ==================================
Tool Calls:
  simple_datasource_qa (call_FiU5JnT0m9IFLti7mNYJr84W)
 Call ID: call_FiU5JnT0m9IFLti7mNYJr84W
  Args:
    user_input: total sales for each region
================================= Tool Message =================================
Name: simple_datasource_qa

text='\nThis is the output of a data query tool used to fetch information via Tableau\'s VizQL API\nYour task is to synthesize all of this information to provide a clear, concise answer to the end user.\n\nData Source Name: Superstore Datasource\nDescription: \nMaintainer: zhao.y@tableau.com\n\nThis is the query written to Tableau\'s VizQL API:\n{"fields":[{"fieldCaption":"Region"},{"fieldCaption":"Sales","function":"SUM","sortDirection":"DESC","sortPriority":1}]}\n\nThis is the resulting data from the query:\n| Region | SUM(Sales) |\n| --- | --- |\n| West | 739813.6085 |\n| East | 691828.1679999988 |\n| Central | 503170.6728000007 |\n| South | 391721.90500000055 |\n\n\nThis was the user_input (question or task):\ntotal sales for each region\n\nBased on the provided context, formulate a comprehensive and informative response to the user\'s query.\nYour response should:\n1. Describe the data source name, description and maintainer if this is the first interaction the user has with it\n2. Use the resulting data to answer the user\'s question or task\n3. Be short and concise, if the data table is too long only return the relevant rows or a small sample\n\nYour synthesized response:\n'
================================== Ai Message ==================================

According to the data retrieved from the Superstore dataset, here are the total sales for each region:

| Region  | Total Sales      |
|---------|------------------|
| West    | $739,813.61      |
| East    | $691,828.17      |
| Central | $503,170.67      |
| South   | $391,721.91      |

This information is derived from the Superstore dataset accessed via Tableau. If you have any further questions or need more insights, feel free to ask!
```


## 試しに日本語できいてみた

同じ質問を日本語で投げてみました。するとちゃんと日本語で返してくれました！ただし金額に「$」マークがついていませんでした。逆にさっきは$が勝手にっていたのは面白いですね。

```
================================ Human Message =================================

地域別の合計売上を教えてください。
================================== Ai Message ==================================
Tool Calls:
  simple_datasource_qa (call_wZDJUERtIcTeoPHKVbSFS1Wr)
 Call ID: call_wZDJUERtIcTeoPHKVbSFS1Wr
  Args:
    user_input: 地域別の合計売上
================================= Tool Message =================================
Name: simple_datasource_qa

text='\nThis is the output of a data query tool used to fetch information via Tableau\'s VizQL API\nYour task is to synthesize all of this information to provide a clear, concise answer to the end user.\n\nData Source Name: Superstore Datasource\nDescription: \nMaintainer: zhao.y@tableau.com\n\nThis is the query written to Tableau\'s VizQL API:\n{\n  "fields": [\n    {\n      "fieldCaption": "Region"\n    },\n    {\n      "fieldCaption": "Sales",\n      "function": "SUM",\n      "sortDirection": "DESC",\n      "sortPriority": 1\n    }\n  ]\n}\n\nThis is the resulting data from the query:\n| Region | SUM(Sales) |\n| --- | --- |\n| West | 739813.6085 |\n| East | 691828.1679999988 |\n| Central | 503170.6728000007 |\n| South | 391721.90500000055 |\n\n\nThis was the user_input (question or task):\n地域別の合計売上\n\nBased on the provided context, formulate a comprehensive and informative response to the user\'s query.\nYour response should:\n1. Describe the data source name, description and maintainer if this is the first interaction the user has with it\n2. Use the resulting data to answer the user\'s question or task\n3. Be short and concise, if the data table is too long only return the relevant rows or a small sample\n\nYour synthesized response:\n'
================================== Ai Message ==================================

地域別の合計売上は以下の通りです：

| 地域   | 合計売上       |
|--------|----------------|
| West   | 739,813.61     |
| East   | 691,828.17     |
| Central| 503,170.67     |
| South  | 391,721.91     |

このデータはSuperstoreデータソースから取得したものです。
```

もう少し複雑な質問を日本語でやってみましたが、こちらは回答を得られませんでした。「西」というデータがRegionに存在しないので回答を得られなかったのかもしれません。

```
================================[1m Human Message [0m=================================

西地域で最も売り上げの大きいカテゴリーは何ですか？
==================================[1m Ai Message [0m==================================
Tool Calls:
  simple_datasource_qa (call_EfLOhrid0gLdh2WJZ9nhSu3t)
 Call ID: call_EfLOhrid0gLdh2WJZ9nhSu3t
  Args:
    user_input: 西地域での売上が最も大きいカテゴリー
    previous_call_error: None
    previous_vds_payload: None
ERROR:root:Value error in get_headlessbi_data: Invalid JSON data, you may have an error or if the array is empty then it was not possible to resolve the query your wrote: []
=================================[1m Tool Message [0m=================================
Name: simple_datasource_qa

Error: ToolException('\n                Tableau\'s VizQL Data Service return an error for the generated query:\n\n                {\n  "fields": [\n    {\n      "fieldCaption": "Category"\n    },\n    {\n      "fieldCaption": "Sales",\n      "function": "SUM",\n      "sortDirection": "DESC",\n      "sortPriority": 1\n    }\n  ],\n  "filters": [\n    {\n      "field": {\n        "fieldCaption": "Region"\n      },\n      "filterType": "MATCH",\n      "contains": "西",\n      "exclude": false\n    }\n  ]\n}\n\n                The user_input used to write this query was:\n\n                西地域での売上が最も大きいカテゴリー\n\n                This was the error:\n\n                Invalid JSON data, you may have an error or if the array is empty then it was not possible to resolve the query your wrote: []\n\n                Consider retrying this tool with the same inputs but include the previous query\n                causing the error and the error itself for the tool to correct itself on a retry.\n                If the error was an empty array, this usually indicates an incorrect filter value\n                was applied, thus returning no data\n                ')
 Please fix your mistakes.
==================================[1m Ai Message [0m==================================
Tool Calls:
  simple_datasource_qa (call_YjNBlPM2kv0xT95v0UgtsUA2)
 Call ID: call_YjNBlPM2kv0xT95v0UgtsUA2
  Args:
    user_input: 西地域での売上が最も大きいカテゴリーを特定するために、カテゴリーごとの売上を合計し、地域を西にフィルタリングする
    previous_call_error: Invalid JSON data, you may have an error or if the array is empty then it was not possible to resolve the query your wrote: []
    previous_vds_payload: {"fields":[{"fieldCaption":"Category"},{"fieldCaption":"Sales","function":"SUM","sortDirection":"DESC","sortPriority":1}],"filters":[{"field":{"fieldCaption":"Region"},"filterType":"MATCH","contains":"西","exclude":false}]}
=================================[1m Tool Message [0m=================================
Name: simple_datasource_qa

Error: ToolException('\n                Tableau\'s VizQL Data Service return an error for the generated query:\n\n                ```json\n{"fields":[{"fieldCaption":"Category"},{"fieldCaption":"Sales","function":"SUM","sortDirection":"DESC","sortPriority":1}],"filters":[{"field":{"fieldCaption":"Region"},"filterType":"MATCH","contains":"西","exclude":false}]}\n```\n\n                The user_input used to write this query was:\n\n                西地域での売上が最も大きいカテゴリーを特定するために、カテゴリーごとの売上を合計し、地域を西にフィルタリングする\n\n                This was the error:\n\n                Expecting value: line 1 column 1 (char 0)\n\n                Consider retrying this tool with the same inputs but include the previous query\n                causing the error and the error itself for the tool to correct itself on a retry.\n                If the error was an empty array, this usually indicates an incorrect filter value\n                was applied, thus returning no data\n                ')
 Please fix your mistakes.
==================================[1m Ai Message [0m==================================
Tool Calls:
  simple_datasource_qa (call_oxG9gMMg7mh5uwtbWPv893ck)
 Call ID: call_oxG9gMMg7mh5uwtbWPv893ck
  Args:
    user_input: 西地域のカテゴリーごとの売上合計を取得し、最も売上が大きいカテゴリーを特定する
    previous_call_error: Invalid JSON data, you may have an error or if the array is empty then it was not possible to resolve the query your wrote: []
    previous_vds_payload: {"fields":[{"fieldCaption":"Category"},{"fieldCaption":"Sales","function":"SUM","sortDirection":"DESC","sortPriority":1}],"filters":[{"field":{"fieldCaption":"Region"},"filterType":"MATCH","contains":"西","exclude":false}]}
ERROR:root:Value error in get_headlessbi_data: Invalid JSON data, you may have an error or if the array is empty then it was not possible to resolve the query your wrote: []
=================================[1m Tool Message [0m=================================
Name: simple_datasource_qa

Error: ToolException('\n                Tableau\'s VizQL Data Service return an error for the generated query:\n\n                {"fields":[{"fieldCaption":"Category"},{"fieldCaption":"Sales","function":"SUM","sortDirection":"DESC","sortPriority":1}],"filters":[{"field":{"fieldCaption":"Region"},"filterType":"MATCH","contains":"西","exclude":false}]}\n\n                The user_input used to write this query was:\n\n                西地域のカテゴリーごとの売上合計を取得し、最も売上が大きいカテゴリーを特定する\n\n                This was the error:\n\n                Invalid JSON data, you may have an error or if the array is empty then it was not possible to resolve the query your wrote: []\n\n                Consider retrying this tool with the same inputs but include the previous query\n                causing the error and the error itself for the tool to correct itself on a retry.\n                If the error was an empty array, this usually indicates an incorrect filter value\n                was applied, thus returning no data\n                ')
 Please fix your mistakes.
==================================[1m Ai Message [0m==================================
Tool Calls:
  simple_datasource_qa (call_r8midLSd7E1eMWmrsZs3Wcmt)
 Call ID: call_r8midLSd7E1eMWmrsZs3Wcmt
  Args:
    user_input: 西地域の売上をカテゴリーごとに合計し、最も売上が大きいカテゴリーを特定する
    previous_call_error: Invalid JSON data, you may have an error or if the array is empty then it was not possible to resolve the query your wrote: []
    previous_vds_payload: {"fields":[{"fieldCaption":"Category"},{"fieldCaption":"Sales","function":"SUM","sortDirection":"DESC","sortPriority":1}],"filters":[{"field":{"fieldCaption":"Region"},"filterType":"MATCH","contains":"西","exclude":false}]}
=================================[1m Tool Message [0m=================================
Name: simple_datasource_qa

Error: ToolException('\n                Tableau\'s VizQL Data Service return an error for the generated query:\n\n                ```json\n{"fields":[{"fieldCaption":"Category"},{"fieldCaption":"Sales","function":"SUM","sortDirection":"DESC","sortPriority":1}],"filters":[{"field":{"fieldCaption":"Region"},"filterType":"MATCH","contains":"西","exclude":false}]}\n```\n\n                The user_input used to write this query was:\n\n                西地域の売上をカテゴリーごとに合計し、最も売上が大きいカテゴリーを特定する\n\n                This was the error:\n\n                Expecting value: line 1 column 1 (char 0)\n\n                Consider retrying this tool with the same inputs but include the previous query\n                causing the error and the error itself for the tool to correct itself on a retry.\n                If the error was an empty array, this usually indicates an incorrect filter value\n                was applied, thus returning no data\n                ')
 Please fix your mistakes.
==================================[1m Ai Message [0m==================================
Tool Calls:
  simple_datasource_qa (call_pzb0MI6YrlBfM9tJ5OgpjvVU)
 Call ID: call_pzb0MI6YrlBfM9tJ5OgpjvVU
  Args:
    user_input: 西地域の売上をカテゴリーごとに合計し、最も売上が大きいカテゴリーを見つける
    previous_call_error: Invalid JSON data, you may have an error or if the array is empty then it was not possible to resolve the query your wrote: []
    previous_vds_payload: {"fields":[{"fieldCaption":"Category"},{"fieldCaption":"Sales","function":"SUM","sortDirection":"DESC","sortPriority":1}],"filters":[{"field":{"fieldCaption":"Region"},"filterType":"MATCH","contains":"西","exclude":false}]}
ERROR:root:Value error in get_headlessbi_data: Invalid JSON data, you may have an error or if the array is empty then it was not possible to resolve the query your wrote: []
=================================[1m Tool Message [0m=================================
Name: simple_datasource_qa

Error: ToolException('\n                Tableau\'s VizQL Data Service return an error for the generated query:\n\n                {"fields":[{"fieldCaption":"Category"},{"fieldCaption":"Sales","function":"SUM","sortDirection":"DESC","sortPriority":1}],"filters":[{"field":{"fieldCaption":"Region"},"filterType":"MATCH","contains":"西","exclude":false}]}\n\n                The user_input used to write this query was:\n\n                西地域の売上をカテゴリーごとに合計し、最も売上が大きいカテゴリーを見つける\n\n                This was the error:\n\n                Invalid JSON data, you may have an error or if the array is empty then it was not possible to resolve the query your wrote: []\n\n                Consider retrying this tool with the same inputs but include the previous query\n                causing the error and the error itself for the tool to correct itself on a retry.\n                If the error was an empty array, this usually indicates an incorrect filter value\n                was applied, thus returning no data\n                ')
 Please fix your mistakes.
==================================[1m Ai Message [0m==================================
Tool Calls:
  simple_datasource_qa (call_ssDxJVhpYztHbvx5Mg57celG)
 Call ID: call_ssDxJVhpYztHbvx5Mg57celG
  Args:
    user_input: 西地域の売上をカテゴリーごとに合計し、最も売上が大きいカテゴリーを特定するためのクエリ
    previous_call_error: Invalid JSON data, you may have an error or if the array is empty then it was not possible to resolve the query your wrote: []
    previous_vds_payload: {"fields":[{"fieldCaption":"Category"},{"fieldCaption":"Sales","function":"SUM","sortDirection":"DESC","sortPriority":1}],"filters":[{"field":{"fieldCaption":"Region"},"filterType":"MATCH","contains":"西","exclude":false}]}
ERROR:root:Value error in get_headlessbi_data: Invalid JSON data, you may have an error or if the array is empty then it was not possible to resolve the query your wrote: []
=================================[1m Tool Message [0m=================================
Name: simple_datasource_qa

Error: ToolException('\n                Tableau\'s VizQL Data Service return an error for the generated query:\n\n                {\n  "fields": [\n    {\n      "fieldCaption": "Category"\n    },\n    {\n      "fieldCaption": "Sales",\n      "function": "SUM",\n      "sortDirection": "DESC",\n      "sortPriority": 1\n    }\n  ],\n  "filters": [\n    {\n      "field": {\n        "fieldCaption": "Region"\n      },\n      "filterType": "MATCH",\n      "contains": "西",\n      "exclude": false\n    }\n  ]\n}\n\n                The user_input used to write this query was:\n\n                西地域の売上をカテゴリーごとに合計し、最も売上が大きいカテゴリーを特定するためのクエリ\n\n                This was the error:\n\n                Invalid JSON data, you may have an error or if the array is empty then it was not possible to resolve the query your wrote: []\n\n                Consider retrying this tool with the same inputs but include the previous query\n                causing the error and the error itself for the tool to correct itself on a retry.\n                If the error was an empty array, this usually indicates an incorrect filter value\n                was applied, thus returning no data\n                ')
 Please fix your mistakes.
==================================[1m Ai Message [0m==================================

申し訳ありませんが、西地域に関するデータを取得する際にエラーが発生しています。具体的には、フィルタリング条件に問題があるようです。このため、正確な結果を取得できませんでした。

別の方法でお手伝いできることがあれば教えてください。たとえば、他の地域や異なるカテゴリーについての情報を取得することができます。どのような情報が必要かお知らせください。
```

あらためて「WEST地域」という言い方をしてみると正しく答えを返してきてくれました！日本語でもうまく対応してくれそうです。ただしデータの中身と同じ文言でないと検索がうまく働かなそうなのでそのあたりは注意が必要そうです。

```
================================ Human Message =================================

WEST地域で最も売り上げの大きいカテゴリーは何ですか？
================================== Ai Message ==================================
Tool Calls:
  simple_datasource_qa (call_VlZGVdJDZ7b7fhtvx0FMDzkQ)
 Call ID: call_VlZGVdJDZ7b7fhtvx0FMDzkQ
  Args:
    user_input: 最も売り上げの大きいカテゴリーをWEST地域で特定する
================================= Tool Message =================================
Name: simple_datasource_qa

text='\nThis is the output of a data query tool used to fetch information via Tableau\'s VizQL API\nYour task is to synthesize all of this information to provide a clear, concise answer to the end user.\n\nData Source Name: Superstore Datasource\nDescription: \nMaintainer: zhao.y@tableau.com\n\nThis is the query written to Tableau\'s VizQL API:\n{\n  "fields": [\n    {\n      "fieldCaption": "Category"\n    },\n    {\n      "fieldCaption": "Sales",\n      "function": "SUM",\n      "sortDirection": "DESC",\n      "sortPriority": 1\n    }\n  ],\n  "filters": [\n    {\n      "field": {\n        "fieldCaption": "Region"\n      },\n      "filterType": "MATCH",\n      "contains": "WEST",\n      "exclude": false\n    }\n  ]\n}\n\nThis is the resulting data from the query:\n| Category | SUM(Sales) |\n| --- | --- |\n| Furniture | 260679.72950000022 |\n| Technology | 252766.988 |\n| Office Supplies | 226366.89099999997 |\n\n\nThis was the user_input (question or task):\n最も売り上げの大きいカテゴリーをWEST地域で特定する\n\nBased on the provided context, formulate a comprehensive and informative response to the user\'s query.\nYour response should:\n1. Describe the data source name, description and maintainer if this is the first interaction the user has with it\n2. Use the resulting data to answer the user\'s question or task\n3. Be short and concise, if the data table is too long only return the relevant rows or a small sample\n\nYour synthesized response:\n'
================================== Ai Message ==================================

WEST地域で最も売り上げの大きいカテゴリーは「Furniture」で、売上は約260,679.73ドルです。次に「Technology」が約252,766.99ドル、そして「Office Supplies」が約226,366.89ドルとなっています。

この情報はSuperstoreデータセットから取得したものです。
```

# 感想

このあと出力結果をcsvやmarkdownとしてファイル出力するチュートリアルや、Slackに投稿するチュートリアルもありましたが、力尽きたのでとりあえず斜め読みして終えました。

やってみての感想ですが、すごく可能性を感じました。独自にAI機能を構築できるのは自由度が高いですし、Tableau ServerにはAI機能が全くなかったのでこういった形で実装できるのは良いと思います。

ただしまだTableau Langchainには`simple_datasource_qa`というシンプルな機能しかありません。おそらくmetadata apiも活用して質問に応じて必要なデータソースを探すところからエージェントが実施することも開発すればできると思います。

LangchainのTableauページでは「Follow the tableau-langchain project for more tools coming soon!」とあり、今後も新たなツールを開発していく予定の様なので要チェックですね！

https://python.langchain.com/docs/integrations/tools/tableau/

まだ力不足ですが可能性を感じる機能でした。

# おまけ

ちなみにハンズオンの内容は以下のgithubリポジトリにてすべて公開されていました。ただしハンズオントレーニング環境ではOpenAIのAPIキーやTableau Cloudの情報などが提供されていたので余計な費用も手間もかからないようになっていました。一から構築するとなると、Python環境の準備もいるし、OpenAIの費用もかかるし、Tableau Server/Cloud側の設定もややこしそうです。

https://github.com/Tab-SE/tableau_langchain/tree/main/experimental/notebooks