🗂
TiDB Starter Data Service V3 (2) Chat2Query テスト用ウェブツール
前回の記事ではTiDB Starter のData App / Chat2Queryモードでv3のエンドポイントをテストしました。
おさらい
Chat2Queryは動的に自然言語ともとにSQLを作成し実行してくれるツールです。Data App で Chat2Queryモードを使うと動的にSQLが生成されじっこいうされます。
Standardモードだとあらかじめ保存しておいたSQLのみが実行されるため、Chat2Queryの方が多様なワークロードに対応しています。どういうSQLが生成されるかは実行前に事前確認が行えないため安全用にデータ操作系SQLは実行されないようになっています。
実行までの手順は以下です。
- Data Summary の取得 (スキーマが変更する都度実行が必要です)
- 自然言語でクエリの実行リクエス → クエリ生成/ジョブ生成
- クエリ生成ジョブステータス確認(ポーリング)
- ジョブの実行
Chat2Queryを実行するには3回curlコマンドを実行しないといけないため少し不便です。このため一気通貫で1,2,3を行い生成されたSQLやその実行結果を確認できるウェブツールを作りました
出力される内容
このウェブツールで出力される内容は生成されたSQLとその実行結果です。
実行結果は大きく3つに分かれます。
1.SQLの実行結果の出力
2.SQLは生成されたが実行が禁止された場合(データ操作系SQL)のエラー
3.その他汎用エラー / SQLの生成失敗
さっそくやってみる
1. 必要ファイルの作成
まずはserver.cjs
,.env
,index.html
3つのファイルを作成し以下の内容をコピペします。
server.cjs
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const dotenv = require('dotenv');
const DigestFetch = require('digest-fetch').default;
dotenv.config();
const app = express();
const port = 3000;
const BASE_URL = 'https://us-west-2.data.tidbcloud.com/api/v1beta/app/chat2query-UfpVDXbp';
const CLUSTER_ID = '10080985057875672215';
const DATABASE = 'test';
const client = new DigestFetch(process.env.PUBLIC_KEY, process.env.PRIVATE_KEY, {
algorithm: 'MD5',
basic: false,
});
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
// 🔁 ポーリング処理:ジョブが完了するまで待機
async function waitForJob(jobId, maxRetries = 10, interval = 1500) {
const jobUrl = `${BASE_URL}/endpoint/v2/jobs/${jobId}`;
for (let i = 0; i < maxRetries; i++) {
const resp = await client.fetch(jobUrl, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});
const text = await resp.text();
console.log(`📨 GETレスポンス try=${i + 1}:`, text);
const data = JSON.parse(text);
const status = data?.result?.status;
if (status === 'done') {
return data;
}
if (status === 'failed') {
throw new Error('❌ ジョブ失敗: ' + JSON.stringify(data, null, 2));
}
// running/init の場合は待つ
await new Promise(r => setTimeout(r, interval));
}
throw new Error(`❌ ジョブが完了しませんでした(${maxRetries}回試行後)`);
}
// フォーム送信処理
app.post('/ask', async (req, res) => {
const question = req.body.question;
try {
console.log('📤 質問:', question);
// Step 1: Chat2Query 実行(POST)
const postBody = { cluster_id: CLUSTER_ID, database: DATABASE, question };
console.log('📦 POSTボディ:', JSON.stringify(postBody, null, 2));
const postResponse = await client.fetch(`${BASE_URL}/endpoint/v3/chat2data`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(postBody),
});
const postData = await postResponse.json();
const jobId = postData?.result?.job_id;
if (!jobId) throw new Error('❌ job_id が取得できません: ' + JSON.stringify(postData, null, 2));
console.log('✅ job_id:', jobId);
// Step 2: job 完了まで待つ
const getData = await waitForJob(jobId);
const sql = getData?.result?.result?.sql || '(SQLなし)';
const sqlError = getData?.result?.result?.sql_error || null;
const rows = getData?.result?.result?.data?.rows || [];
const columns = getData?.result?.result?.data?.columns || [];
console.log('🧠 生成されたSQL:\n', sql);
if (sqlError) console.error('🚨 SQLエラー:', sqlError);
console.log('📊 行数:', rows.length, '列数:', columns.length);
// テーブルHTML生成
const table = rows.length > 0 ? `
<table border="1" cellpadding="8" style="border-collapse: collapse;">
<thead><tr>${columns.map(c => `<th>${c.col}</th>`).join('')}</tr></thead>
<tbody>
${rows.map(row => `<tr>${row.map(cell => `<td>${cell}</td>`).join('')}</tr>`).join('')}
</tbody>
</table>
` : '<p>結果なし</p>';
// HTMLレスポンス生成
let content = `
<h2>質問:</h2>
<pre>${question}</pre>
<h2>生成されたSQL:</h2>
<pre>${sql}</pre>
`;
if (sqlError) {
content += `<h2 style="color:red;">SQLエラー:</h2><pre>${sqlError}</pre>`;
} else {
content += `<h2>実行結果:</h2>${table}`;
}
res.send(content + '<br><a href="/">← 戻る</a>');
} catch (err) {
console.error('🚨 エラー内容:', err);
res.send(`<h2>エラーが発生しました:</h2><pre>${err.message}</pre><a href="/">← 戻る</a>`);
}
});
app.listen(port, () => {
console.log(`✅ Server is running: http://localhost:${port}`);
});
.env
PUBLIC_KEY=xxJ7PJD0
PRIVATE_KEY=xxe13e7a-8b9b-4f98-a886-3d9ca5a58c6e
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>TiDB Chat2Query フォーム</title>
</head>
<body>
<h1>TiDB Chat2Query 質問フォーム</h1>
<form action="/ask" method="post">
<label for="question">質問内容:</label><br>
<input type="text" id="question" name="question" size="60" required><br><br>
<input type="submit" value="送信">
</form>
</body>
</html>
2.起動とテスト実行
node server.cjs
で起動します。
node server.cjs
[dotenv@17.2.3] injecting env (2) from .env -- tip: 🔐 encrypt with Dotenvx: https://dotenvx.com
✅ Server is running: http://localhost:3000
http://localhost:3000
にアクセスしてデータ分析指示を出します。
実行の許可がされないSQLが生成された場合は以下の通りエラーとなります。
Discussion