Toilを無くして徒然なるままに日暮し硯に向かひたい

生成AIアプリケーション開発などを行うエンジニアのブログです。

RAGアプリ開発ハンズオン(前編:バックエンド編)

genai-users.connpass.com

上記ハンズオン勉強会の資料になります。

ソースコード

github.com

FastAPIの準備

python -m venv fastapi-env
source fastapi-env/bin/activate
fastapi-env/Scripts/activate
pip install fastapi uvicorn
touch main.py
from fastapi import FastAPI

app = FastAPI()

@app.get('/')
def index():
  return 'hello'
  • 実行
uvicorn main:app --reload
  • 別ターミナルにて
curl -s http://localhost:8000/
  • POSTも追加
from pydantic import BaseModel
class User(BaseModel):
    name: str

@app.post('/api/hello')
def hello_service(user: User):
    resp = { 'message': 'Hello, {}!'.format(user.name) }
    return resp
USER='{"name":"平賀源内"}'
curl -X POST -H "Content-Type: application/json" -d "$USER" -s http://localhost:8000/api/hello | jq .

Google Cloudでサービスアカウントの準備

Geminiマルチモーダルプログラミングハンズオン - Toilを無くして徒然なるままに日暮し硯に向かひたいの記事を参考に、ロールへ

を追加し、環境変数の設定

Geminiを呼び出すコードを記載

  • main.pyの上に以下を追加
import vertexai
from vertexai.generative_models import GenerativeModel
  • main.pyの下に以下を追加
class Question(BaseModel):
    query: str

@app.post('/api/llm')
def llm_service(question: Question):
    prompt = question.query

    vertexai.init(location="us-west1") # vertexaiの初期化で、ロケーションを設定

    model = GenerativeModel("gemini-2.0-flash-001") # モデルを設定

    response = model.generate_content( # プロンプトをモデルに入れて出力(レスポンスを得る)
        prompt
    )

    print(response.text) # コンソールログにresponseのテキストを表示
    resp = { 'answer': response.text } # responseを形作る
    return resp

ライブラリのインストール

  • requirements.txtに以下を記載
google-cloud-aiplatform==1.83.0
vertexai==1.43.0
langchain_core==0.3.33
langchain_google_vertexai==2.0.12
google===3.0.0
google-cloud-discoveryengine==0.13.6
pip install -r requirements.txt

--break-system-packagesをつけよ、とエラーが出たら以下

pip install --user -r requirements.txt --break-system-packages

実行方法

uvicorn main:app --reload
  • 別ターミナルにて
QUESTION='{"query":"プロンプトエンジニアリングとは何ですか?"}'
curl -X POST -H "Content-Type: application/json" -d "$QUESTION" -s http://localhost:8000/api/llm | jq .

LangChainを用いる

import vertexai # 削除
from vertexai.generative_models import GenerativeModel # 削除

from langchain_google_vertexai import VertexAI # 追記
from langchain_core.prompts import PromptTemplate # 追記
@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

RAG構築

Google Cloud Vertex AI Agent Builderの使い方 - Toilを無くして徒然なるままに日暮し硯に向かひたいの記事を参考に、Google Cloud Storageにドキュメントを格納し、Agent Builderで検索アプリを作ります。

  • main.pyの上に追記
from google.api_core.client_options import ClientOptions
from google.cloud import discoveryengine_v1 as discoveryengine
import os
import google.auth

credentials, project_id = google.auth.default()
  • main.pyの下に追記

  • 'DISCOVERY_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' # AI Applicationsで作成したアプリケーションのIDに変更する
    def search(
        project_id: str,
        location: str,
        engine_id: str,
        search_query: str,
    ) -> discoveryengine.services.search_service.pagers.SearchPager:
        client_options = (
            ClientOptions(api_endpoint=f"{location}-discoveryengine.googleapis.com")
            if location != "global"
            else None
        )

        client = discoveryengine.SearchServiceClient(client_options=client_options)

        serving_config = f"projects/{project_id}/locations/{location}/collections/default_collection/engines/{engine_id}/servingConfigs/default_config"

        content_search_spec = discoveryengine.SearchRequest.ContentSearchSpec(
            snippet_spec=discoveryengine.SearchRequest.ContentSearchSpec.SnippetSpec(
                return_snippet=True
            ),
            summary_spec=discoveryengine.SearchRequest.ContentSearchSpec.SummarySpec(
                summary_result_count=3,
                include_citations=True,
                ignore_adversarial_query=True,
                ignore_non_summary_seeking_query=True,
                model_prompt_spec=discoveryengine.SearchRequest.ContentSearchSpec.SummarySpec.ModelPromptSpec(
                    preamble="文献の検索結果を要約してください"
                ),
                model_spec=discoveryengine.SearchRequest.ContentSearchSpec.SummarySpec.ModelSpec(
                    version="stable",
                ),
            ),
        )
        request = discoveryengine.SearchRequest(
            serving_config=serving_config,
            query=search_query,
            page_size=3,
            content_search_spec=content_search_spec,
            query_expansion_spec=discoveryengine.SearchRequest.QueryExpansionSpec(
                condition=discoveryengine.SearchRequest.QueryExpansionSpec.Condition.AUTO,
            ),
            spell_correction_spec=discoveryengine.SearchRequest.SpellCorrectionSpec(
                mode=discoveryengine.SearchRequest.SpellCorrectionSpec.Mode.AUTO
            ),
        )
        page_result = client.search(request)

        return page_result

    response = search(project_id, location, engine_id, search_query)
    resp = { 'search_result': response.summary.summary_text }
    print(resp)
    return resp
QUESTION='{"query":"情報セキュリティにおいて気をつけるべきことを教えてください"}'
curl -X POST -H "Content-Type: application/json" -d "$QUESTION" -s http://localhost:8000/api/retriever | jq .

課題

retriever_service を定義しましたが、検索結果をcontextとして、LLMへの問い合わせを行なってください。

次回、5月の回(日程未定)で解説します。