
はじめに
本記事は、2025年5月21日(火)に開催するSnow WEST User Group ハンズオン「関西弁で答えるチャットボットをSnowflake × Streamlitで作ろう」で利用するコードをまとめたものです。
★★★資料はこちら★★★
ハンズオン コピー&ペースト用コード
1-3・4.環境設定 | データベース・スキーマ作成・スキーマ作成
-- 1.環境設定 | 3.データベース・スキーマ作成
CREATE DATABASE SNOWVILLAGE_WEST_DEMO;
CREATE SCHEMA SNOWVILLAGE_WEST_DEMO.KANSAI_OBACHAN_APP;
-- 1.環境設定 | 4.ステージ作成
CREATE STAGE SNOWVILLAGE_WEST_DEMO.KANSAI_OBACHAN_APP.SEMANTIC_MODEL;
3-8.セマンティックモデルを作成 | コードを張り付ける
name: osaka_restaurant_info
tables:
- name: FOOD_SAMPLE_OSAKA_CHUO
base_table:
database: FOOD_ESTABLISHMENTS_DATA_SET_IN_JAPAN
schema: MART
table: FOOD_SAMPLE_OSAKA_CHUO
dimensions:
- name: UUID
expr: UUID
data_type: VARCHAR(16777216)
description: 店舗ID
sample_values:
- 71b87932-e132-43e4-9583-96bfdd3d2b8e
- ab7abad2-390c-4eac-8004-231d427ca34e
- d0f9e27d-8ccf-4175-9312-cbf8ff3f9585
- name: STORE_NAME
expr: STORE_NAME
data_type: VARCHAR(16777216)
description: 店舗名
sample_values:
- 神戸牛牛・本町店
- Bar Reiche
- DOPE
- name: STORE_PHONE_NUMBER
expr: STORE_PHONE_NUMBER
data_type: VARCHAR(16777216)
description: 店舗電話番号
sample_values:
- 080-5696-6988
- 06-6121-6588
- name: STORE_PREF_NAME
expr: STORE_PREF_NAME
data_type: VARCHAR(16777216)
description: 店舗都道府県
sample_values:
- 大阪府
- name: STORE_CITY_NAME
expr: STORE_CITY_NAME
data_type: VARCHAR(16777216)
description: 店舗市町村
sample_values:
- 大阪市中央区
- name: STORE_ZIPCODE
expr: STORE_ZIPCODE
data_type: VARCHAR(16777216)
description: 店舗郵便番号
sample_values:
- '5420076'
- '5410041'
- '5420083'
- name: STORE_ADDRESS
expr: STORE_ADDRESS
data_type: VARCHAR(16777216)
description: 店舗住所
sample_values:
- 大阪府大阪市中央区高麗橋4ー2ー7
- 大阪府大阪市中央区東心斎橋2丁目5ー22
- 大阪府大阪市中央区心斎橋筋2ー2ー3
- name: STORE_BUILDING_NAME
expr: STORE_BUILDING_NAME
data_type: VARCHAR(16777216)
description: 店舗ビル名
sample_values:
- ヴィラ日本橋ビル 1F
- 中西ビル 1F
- name: GENRE_MAJOR_NAME
expr: GENRE_MAJOR_NAME
data_type: VARCHAR(16777216)
description: ジャンル大分類
sample_values:
- 飲食業
- name: GENRE_MEDIUM_NAME
expr: GENRE_MEDIUM_NAME
data_type: VARCHAR(16777216)
description: ジャンル中分類
sample_values:
- 配達・持ち帰り
- 飲食店
- name: GENRE_SUB_NAME
expr: GENRE_SUB_NAME
data_type: VARCHAR(16777216)
description: ジャンル小分類
sample_values:
- 飲み屋・居酒屋
- 専門料理店
- 寿司店
- ステーキ
- その他配達・持ち帰り
- 各国料理
- 肉調理店(焼肉・焼鳥・ステーキ除く)
- バー
- 焼肉
- キャバレー・ナイトクラブ・ラウンジ
- カレー店
- 持ち帰り飲食サービス業
- 料亭
- 麺類(そば)
- 分類できない飲食店
- 丼・定食
- 鍋料理
- 和食・郷土料理
- ファーストフード
- カフェ・喫茶店
- ラーメン
- 焼き鳥
- 中華料理
- 麺類(うどん)
- 一般レストラン
- お菓子・スイーツ
- 配達飲食サービス業
- その他飲食店
- お好み焼き・たこ焼き・焼きそば等
- name: STORE_URL
expr: STORE_URL
data_type: VARCHAR(16777216)
description: 店舗URL
sample_values:
- https://tabelog.com/osaka/A2701/A270204/27112287/
- https://s.tabelog.com/osaka/A2701/A270201/27078266/
- name: STORE_FACEBOOK
expr: STORE_FACEBOOK
data_type: VARCHAR(16777216)
description: 店舗Facebook
sample_values:
- https://www.facebook.com/ebisuhub/
- https://www.facebook.com/people/BISTROT-LE-CANON/100054318979844/
- name: STORE_X
expr: STORE_X
data_type: VARCHAR(16777216)
description: 店舗X
sample_values:
- https://twitter.com/MixBarSLY
- https://twitter.com/miyazakikan
- name: STORE_INSTAGRAM
expr: STORE_INSTAGRAM
data_type: VARCHAR(16777216)
description: 店舗instagram
sample_values:
- https://www.instagram.com/teppan_osaka/
- https://www.instagram.com/cafegarb/
- name: STORE_OTHER_SITE
expr: STORE_OTHER_SITE
data_type: VARCHAR(16777216)
description: 店舗_他site
sample_values:
- '[''https://tabelog.com/osaka/A2701/A270107/27075875/'', ''https://www.ekiten.jp/shop_6851905/'']'
- '[''https://tabelog.com/osaka/A2701/A270201/27074686/'', ''https://www.ekiten.jp/shop_6525173/'', ''https://place.line.me/businesses/35063080'']'
- name: BRAND_ID
expr: BRAND_ID
data_type: VARCHAR(16777216)
description: ブランドコード
sample_values:
- '797'
- '1861'
- name: BRAND_NAME
expr: BRAND_NAME
data_type: VARCHAR(16777216)
description: ブランド名
sample_values:
- 白木屋
- 大阪王将
- ミスタードーナツ
- name: BRAND_OWNER
expr: BRAND_OWNER
data_type: VARCHAR(16777216)
description: オーナー会社の法人番号
sample_values:
- '4120001168969'
- '2120001168995'
- name: UPDATE_DATE
expr: UPDATE_DATE
data_type: VARCHAR(16777216)
description: データ取得日
sample_values:
- '20250408'
time_dimensions:
- name: OPEN_DATE
expr: OPEN_DATE
data_type: DATE
description: 開業推定日
sample_values:
- '2016-07-08'
- '2017-02-01'
- '2006-04-01'
- name: CLOSED_DATE
expr: CLOSED_DATE
data_type: DATE
description: 閉店推定日
sample_values:
- '2024-05-27'
- '2024-02-09'
facts:
- name: STORE_LAT
expr: STORE_LAT
data_type: NUMBER(10,7)
description: 緯度
sample_values:
- '34.6675624'
- '34.6897922'
- '34.6732392'
- name: STORE_LON
expr: STORE_LON
data_type: NUMBER(10,7)
description: 経度
sample_values:
- '135.5007200'
- '135.5053260'
- '135.5006608'
- name: SEATS
expr: SEATS
data_type: NUMBER(38,0)
description: 店舗座席数
- name: CLOSED_FLAG
expr: CLOSED_FLAG
data_type: NUMBER(38,0)
description: 閉店フラグ
sample_values:
- '1'
- '0'
- '2'
verified_queries:
- name: ラーメン
question: ラーメン食べたい。一番新しいお店の名前と電話番号教えて
use_as_onboarding_question: true
sql: SELECT store_name, store_phone_number FROM food_sample_osaka_chuo WHERE genre_sub_name = 'ラーメン' ORDER BY open_date DESC NULLS LAST LIMIT 1 /* Generated by Cortex Analyst */
verified_by: 〇〇〇
verified_at: 1747702367
4-4. Streamlitで画面を作成 | Pythonコードを張り付け
from typing import Dict, List, Optional
import _snowflake
import json
import streamlit as st
import time
from snowflake.snowpark.context import get_active_session
from snowflake.cortex import Complete
import pandas as pd
######################### セマンティックモデルの設定 #########################
DATABASE = "SNOWVILLAGE_WEST_DEMO"
SCHEMA = "KANSAI_OBACHAN_APP"
STAGE = "SEMANTIC_MODEL"
FILE = "osaka_restaurant_info.yaml"
#おばちゃんの画像
obachan_pic ="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcvOAwqo-YodHiO-K66kjb4OkhFMZ5ZtqiojbDWXNcwso-8HuWjVD3Qb-otpKY6e9zRBpPlAaNRut39BaFSI8ckI9EVBsyEfPUtSjaD99vMnxnlxJsr4CiA2Y33Znm6GZ5TDis0SSI5q10/s800/fashion_oosaka_hyougara.png"
######################### 関数を定義 #########################
def send_message(prompt: str) -> dict:
"""Calls the REST API and returns the response."""
request_body = {
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": prompt
}
]
}
],
"semantic_model_file": f"@{DATABASE}.{SCHEMA}.{STAGE}/{FILE}",
}
resp = _snowflake.send_snow_api_request(
"POST",
f"/api/v2/cortex/analyst/message",
{},
{},
request_body,
{},
30000,
)
if resp["status"] < 400:
return json.loads(resp["content"])
else:
st.session_state.messages.pop()
raise Exception(
f"Failed request with status {resp['status']}: {resp}"
)
def process_message(prompt: str,user_input: str,obachan_pic) -> None:
"""Processes a message and adds the response to the chat."""
st.session_state.messages.append(
{"role": "user", "content": [{"type": "text", "text": prompt}]}
)
with st.chat_message("user"):
st.markdown(user_input)
with st.chat_message("assistant",avatar=obachan_pic):
with st.spinner("ちょいまちや!"):
response = send_message(prompt=prompt)
request_id = response["request_id"]
content = response["message"]["content"]
#st.write(content) #DEBUG
#####ここでおばちゃん化#######
answer,sql,df_for_answer = obachan_answer(user_input,content)
st.session_state.messages.append(
{**response['message'], "request_id": request_id, "answer":answer, "sql":sql}
)
display_content(content=content, request_id=request_id, answer=answer, sql=sql,df_for_answer=df_for_answer) # type: ignore[arg-type]
def display_content(
content: List[Dict[str, str]],
request_id: Optional[str] = None,
message_index: Optional[int] = None,
answer: str = "",
sql: str = "",
df_for_answer: str =None) -> None:
"""Displays a content item for a message."""
message_index = message_index or len(st.session_state.messages)
st.markdown(answer)
st.write(df_for_answer)
with st.expander("SQL Query", expanded=False):
st.code(sql, language="sql")
def obachan_answer(user_input,content):
"""
関西弁の回答を作成
"""
for item in content:
if item["type"] == "sql":
sql = item["statement"]
session = get_active_session()
df_for_answer = session.sql(sql).to_pandas()
kansaiben_prompt = f"""
あたなは、とても元気なやかましい大阪のおばちゃんです。
以下の質問と、質問で参照するデータフレームに基づいて、回答してください。
質問: {user_input}
データフレーム: {df_for_answer}
注意:
- 分析結果は、下記の「関西弁説明」を学習し、関西弁で回答してください。
- 敬語は使わないでください。
- 質問に関連する面白い話を加えてください。
- 最後に「知らんけど」か「飴ちゃんやるわ」と言ってください。
-関西弁説明
-語彙
-ありがとう:標準語では「ありがとう」ですが、関西弁では「おおきに」と言います。
-とても:標準語の「とても」は、関西弁では「めっちゃ」や「ほんまに」と言います。
-疲れた:標準語の「疲れた」は、関西弁では「しんどい」と言います。
-だめ:標準語の「だめ」は、関西弁では「あかん」と言います。
-すごい:標準語の「すごい」は、関西弁では「えらい」と言います。
-文法
-標準語の「〜している」は、関西弁では「〜しとる」と言います。
-標準語の「〜していない」は、関西弁では「〜してへん」と言います。
-標準語の「〜だ」は、関西弁では「〜や」と言います。例えば、「これは本だ」は「これは本や」となります。
-標準語の「〜でしょう」は、関西弁では「〜やろ」と言います。例えば、「明日は雨でしょう」は「明日は雨やろ」となります。
-標準語の「〜ない」は、関西弁では「〜へん」と言います。例えば、「行かない」は「行かへん」となります。
"""
result = Complete("mistral-large2",kansaiben_prompt)
return result,sql,df_for_answer
def read_osaka_restaurants_df():
"""
大阪の飲食店データを取得
"""
target_table = 'FOOD_ESTABLISHMENTS_DATA_SET_IN_JAPAN.MART.FOOD_SAMPLE_OSAKA_CHUO'
#データを取得
query = f"SELECT * FROM {target_table} limit 10"
osaka_restaurants_data = session.sql(query).collect()
osaka_restaurants_df = pd.DataFrame(osaka_restaurants_data)
st.write("🔵大阪市中央区の飲食店データの中身を確認")
st.write(osaka_restaurants_df) #debug
def add_prompt_request(user_input: str) -> str:
added_prompt = f"""
質問に対して、日本語で回答してください。
質問: {user_input}
"""
return added_prompt
def reset() -> None:
st.session_state.messages = []
st.session_state.suggestions = []
st.session_state.active_suggestion = None
######################### メイン処理 #########################
st.title("飲食店に詳しい大阪のおばちゃんチャットボット🐯🐙🍬")
st.markdown(f"Semantic Model: {FILE}
")
if "messages" not in st.session_state:
reset()
# Get the current credentials
session = get_active_session()
read_osaka_restaurants_df()
if user_input := st.chat_input("何食いたいんや?言うてみ!"):
prompt = add_prompt_request(user_input)
process_message(prompt, user_input, obachan_pic)
if st.session_state.active_suggestion:
process_message(prompt=st.session_state.active_suggestion)
st.session_state.active_suggestion = None