0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

転機となった一年の出来事を振り返るAdvent Calendar 2024

Day 6

【RAG】Slackからスプレッドシート経由でChromaDBにデータを登録したい

Last updated at Posted at 2024-12-05

こちらの記事の続きです!

前回の記事ではデータベースの自動更新を行い、Geminiからの回答結果が変わることを同じ質問で試しました。
更新するデータはスクリプト内に記述していましたが、今回はSlackで回答を受け取った際にGoogleスプレッドシート経由で更新できるように改良してみました。

スプレッドシート更新前と更新後の回答の変化

  • あなたはだれ

スクリーンショット 2024-12-02 23.28.40.png

  • ジャイアンはどんな人ですか

スクリーンショット 2024-12-02 23.33.02.png

データベースが更新されて、質問に関連する情報を回答してくれていることが確認できました。
これで、質問に対する回答精度がいまいちだった際に、求める回答をシートに記載するだけで次に活きます。

コード

sample.py
import os
import time
import chromadb
import google.generativeai as genai
import schedule
from dotenv import load_dotenv
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
from threading import Thread

load_dotenv()

genai.configure(api_key=os.getenv("GEMINI_API_KEY"))

chroma_client = chromadb.Client()
collection = chroma_client.create_collection(name="japanese_documents")

SLACK_BOT_TOKEN = os.getenv('SLACK_BOT_TOKEN')
SLACK_APP_TOKEN = os.getenv('SLACK_APP_TOKEN')

SHEET_ID = os.getenv("SAMPLE_SPREADSHEET_ID")
SAMPLE_RANGE_NAME = "シート1!A2:A4"

app = App(token=SLACK_BOT_TOKEN)

def get_sheets_data():
    creds = None
    if os.path.exists("token.json"):
        creds = Credentials.from_authorized_user_file("token.json", ["https://www.googleapis.com/auth/spreadsheets.readonly"])
    
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file("credentials.json", ["https://www.googleapis.com/auth/spreadsheets.readonly"])
            creds = flow.run_local_server(port=0)
        with open("token.json", "w") as token:
            token.write(creds.to_json())

    try:
        service = build("sheets", "v4", credentials=creds)
        sheet = service.spreadsheets()
        result = sheet.values().get(spreadsheetId=SHEET_ID, range=SAMPLE_RANGE_NAME).execute()
        values = result.get("values", [])

        if not values:
            print("No data found.")
            return []

        documents = [row[0] for row in values]
        return documents

    except Exception as err:
        print(f"Error retrieving data from Google Sheets: {err}")
        return []

def add_documents_to_chroma(documents, ids):
    try:
        collection.add(documents=documents, ids=ids)
        print("データがChromaDBに追加されました。")
    except Exception as e:
        print(f"エラーが発生しました: {e}")

def update_chroma_from_sheets():
    documents = get_sheets_data()
    if documents:
        new_ids = [f"id{index+1}" for index in range(len(documents))]
        add_documents_to_chroma(documents, new_ids)

def search_similar_documents(query_text):
    try:
        results = collection.query(query_texts=[query_text], n_results=2)
        return results
    except Exception as e:
        print(f"Error in searching documents: {e}")
        return None

def get_gemini_response(question, context):
    try:
        model = genai.GenerativeModel("gemini-1.5-flash")
        response = model.generate_content(f"以下の情報をもとに質問に答えてください:\n{context}\n質問: {question}")
        return response.text
    except Exception as e:
        print(f"Error in generating Gemini response: {e}")
        return "Sorry, I couldn't fetch an answer at the moment."

@app.message("")
def handle_message(message, say):
    user_message = message.get('text', '')

    search_results = search_similar_documents(user_message)
    
    spreadsheet_link = f"https://docs.google.com/spreadsheets/d/{SHEET_ID}/edit"

    if search_results and 'documents' in search_results:
        # search_results['documents'] がリストのリストである場合があるためフラット化する
        flat_results = [item for sublist in search_results['documents'] for item in (sublist if isinstance(sublist, list) else [sublist])]
        
        # 結果を文字列として結合
        context = "\n".join(flat_results)
        
        gemini_response = get_gemini_response(user_message, context)
        
        say(f"質問: {user_message}\n回答: {gemini_response}\nスプレッドシートリンク: {spreadsheet_link}")
    else:
        say(f"関連する情報が見つかりませんでした。\nスプレッドシートリンク: {spreadsheet_link}")

schedule.every(1).minute.do(update_chroma_from_sheets)

def run_scheduled_tasks():
    while True:
        schedule.run_pending()
        time.sleep(1)

threading_thread = Thread(target=run_scheduled_tasks)
threading_thread.daemon = True
threading_thread.start()

if __name__ == "__main__":
    SocketModeHandler(app, SLACK_APP_TOKEN).start()

注意点

  • SAMPLE_RANGE_NAMEはファイル名ではなく、シート名とセル範囲を指定
    ファイル名を指定して沼りました

  • シートのアクセス権限許可があることを確認

  • credentials.jsonをsample.pyと同じ階層に置く

おわりに

無事スプレッドシート経由でデータベースを更新することに成功しました!
次は登録するデータをQA形式にしてみたいです:relieved:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?