上記ハンズオン勉強会の資料になります。
前回資料
前回の課題
retriever_serviceを定義しましたが、検索結果をcontextとして、LLMへの問い合わせを行なってください。llm_serviceでretriever_serviceを使うようにします。
@app.post('/api/llm')
def llm_service(question: Question):
human_question = question.query
model = VertexAI(model_name="gemini-2.0-flash-001", location="us-west1")
template = """質問: {question}
ステップバイステップで考えてください。"""
prompt_template = PromptTemplate.from_template(template)
chain = prompt_template | model # prompt_templateをmodelに引き渡す処理を"|"を用いて簡単に実現
response = chain.invoke({"question": human_question}) # invokeは全ての処理が終わってから値を返す。他にはstreamなど
print(response)
resp = { 'answer': response }
return resp
↓
@app.post('/api/llm')
def llm_service(question: Question):
human_question = question.query
model = VertexAI(model_name="gemini-2.0-flash-001", location="us-west1")
context_resp = retriever_service(question)
context = context_resp['search_result']
print(context)
template = """質問: {question}
以下の情報を参考にして、質問に答えてください。
{context}
"""
prompt_template = PromptTemplate.from_template(template)
chain = prompt_template | model # prompt_templateをmodelに引き渡す処理を"|"を用いて簡単に実現
response = chain.invoke({"question": human_question, "context": context}) # invokeは全ての処理が終わってから値を返す。他にはstreamなど
print(response)
resp = { 'answer': response }
return resp
以下も行っておくと便利です。
.envを作成
DISCOVERY_ENGINE_ID=XXXXXXXXXXXXX
- 以下の行を
main.pyに追記
from dotenv import load_dotenv load_dotenv()
engine_idの行を変更
@app.post('/api/retriever')
def retriever_service(question: Question):
search_query = question.query
project_id
location: str = "global"
engine_id: str = 'DISCOVERY_ENGINE_ID'
↓
@app.post('/api/retriever')
def retriever_service(question: Question):
search_query = question.query
project_id
location: str = "global"
engine_id: str = os.environ['DISCOVERY_ENGINE_ID']
- 動作確認
QUESTION='{"query":"情報セキュリティにおいて気をつけるべきことを教えてください"}'
curl -X POST -H "Content-Type: application/json" -d "$QUESTION" -s http://localhost:8000/api/llm | jq .
参考)ソースコード差分
retriever_serviceで得た検索結果をcontextに by shu-kob · Pull Request #4 · shu-kob/rag-app-handson · GitHub
フロントエンドの実装
フォルダ整理
これまでバックエンドを追加してきたのと同じリポジトリでフロントエンドも管理いたします。
そのためにこれまで追加してきたファイルをバックエンド用のフォルダに移動させます。
mkdir backend # 下記以外にも必要なファイル、フォルダはbackendに移動してください。 # - __pycache__とfastapi-envは削除してください。 # - .gitがある場合は移動も削除もしないでください。 mv *.md *.py *.txt .env backend
アプリ作成
アプリの雛形を作成し、起動を確認します。
npx --yes create-react-router@latest --install --no-git-init frontend cd frontend npm run dev
ブラウザでhttp://localhost:5173/を開いてReact Routerの画面が表示されればOKです。
画面を変更してみる
見た目を定義しているコンポーネントはfrontend/app/welcome/welcome.tsxです。
Welcomeコンポーネントを以下のように変更します。
export function Welcome() { return ( <main className="flex items-center justify-center pt-16 pb-4"> <div className="flex-1 flex flex-col items-center gap-16 min-h-0"> <div> <div> <label htmlFor="message">メッセージ</label> </div> <div> <textarea id="message" rows={4} cols={50} style={{ padding: "0.5rem", border: "1px solid #ccc", outline: "none", boxShadow: "none", }} /> </div> <div> <button type="button" style={{ border: "1px solid #ccc", padding: "0.5rem 1rem", }} > 送信 </button> </div> </div> </div> </main> ); }
画面に入力欄とボタンが表示されればOKです。
入力をコントロールする
上記で入力欄に文字を入力することはできますが、その値はブラウザ側で管理されており、Reactアプリ側では取得できません。
そこでstateを用いてアプリ側で入力を制御します。
import { useState } from "react"; export function Welcome() { const [input, setInput] = useState(""); const onSend = () => { console.log(input) } return ( <main className="flex items-center justify-center pt-16 pb-4"> <div className="flex-1 flex flex-col items-center gap-16 min-h-0"> <div> <div> <label htmlFor="message">メッセージ</label> </div> <div> <textarea id="message" rows={4} cols={50} style={{ padding: "0.5rem", border: "1px solid #ccc", outline: "none", boxShadow: "none", }} value={input} onChange={(e) => setInput(e.target.value)} /> </div> <div> <button type="button" style={{ border: "1px solid #ccc", padding: "0.5rem 1rem", }} onClick={onSend} > 送信 </button> </div> </div> </div> </main> ); }
テキストを入力して送信ボタンをクリックするとログにテキストの内容が表示されるようになります。
ログの確認はブラウザの開発者ツールで行います。
バックエンドとの接続
フロントエンドはバックエンドと異なるオリジンで動かしているため、CORSエラーにならないようバックエンドを修正します。
backend/main.pyに以下を追加してください。
# CORSミドルウェアの設定 from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=["*"], # すべてのオリジンを許可 allow_credentials=True, allow_methods=["*"], # すべてのメソッドを許可 allow_headers=["*"], # すべてのヘッダーを許可 expose_headers=["*"] # すべてのヘッダーを公開 )
変更後、バックエンドを起動します。
python -m venv fastapi-env
source fastapi-env/bin/activate
fastapi-env/Scripts/activate
uvicorn main:app --reload
送信ボタンが押された際に入力されたテキストをバックエンドに送信し、生成AIの回答を取得できるようにします。
レスポンスの確認はブラウザの開発者ツールで行います。
const onSend = () => {
fetch("http://localhost:8000/api/llm", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ query: input }),
})
}
演習
バックエンドのResponseを画面に表示させましょう
例
バックエンドからのresponseをフロントエンドに表示 by shu-kob · Pull Request #6 · shu-kob/rag-app-handson · GitHub