NTTドコモR&Dの技術ブログです。

生成AI と Wikipedia記事 で 子供向けお仕事提案bot を作ってみよう(Azure OpenAI + RAG)

NTT コノキューに出向中の澤山です。 今年の7月にドコモから、コノキューにやってきました。

この記事は、NTTドコモ アドベントカレンダー2023 21日目の記事です。 この記事では、Wikipedia記事Azure OpenAI API既存のモデルの3つを用い、RAG(Retrieval-Augmented Generation)のためのデータ作成と、RAGを活用した子ども向けお仕事提案botを作ります。

(記事の情報は2023/11月のものです。)

生成AI / ChatGPT の大流行

今年に入ってから、生成AI / ChatGPT の大衆利用が急速に進み、流行語大賞候補になるまで一般に浸透しました。

生成AIは、質問応答や翻訳、要約にプログラミングなど、さまざまな用途で使われ、最近では、AIに詳しくない方ですら、使いこなしている様子が散見されるようになりました。

子供のための、生成AI活用方法、ってある?

話題になっている、生成AIを活用したアプリやサービスの多くが、大人向けのアプリであったり、ある程度、ITリテラシーのある方向けであることが多いです。 せっかくなので、子供向けにも何か作ってみよう、というのが、本記事の発端です。

では、子供向けのアプリやサービスは、どんなものが作れそうでしょうか?

わかりやすいものだと、外国語教育や、読書感想文作成といった、教育応用が考えつくかなと思います。

今回、私が着目したのは、子供の世界を広げること、です。

みなさんは子供の頃、どうやって将来の夢を見つけましたか?

おそらく、子供の頃の将来の夢や、なりたい(なりたかった)仕事は、身の回りで出会う大人がどんな仕事をしていたかや、日常生活によって左右され、 本当になりたかった仕事を知ったのは、もっと大きくなってからだった、という方も少なくはないと思います。

一方で、自分の好きなことや、得意なことから、仕事を見つけることができた、という方もいらっしゃるかなと思ったので、自分の興味や好きなこと、得意なことから、いろんな(新しい)仕事を知るきっかけを生成AIで得られるようにする というのは、子供にとって、いい機会になるのではないでしょうか。

そんな思いから、今回は、お仕事提案チャットボット を作ってみることにしました。

子供向けお仕事提案チャットボットを作ってみる

今回は、子供の興味や好きなことや得意なこと、子供の性格から、お仕事を提案するチャットボットを作っていきます。

全体像

今回の、お仕事提案チャットボット の入出力のイメージは以下です。

入力: 子供の興味や好きなこと、得意なことや性格

出力: その子供に合う、お仕事の提案

今回のチャットボット構築では、Wikipedia記事として存在するお仕事(職業)を対象に、各お仕事の情報を、「必要な資格やスキル、向いている性格」等の観点で、お仕事内容整理をし、ユーザの得意なこと、できること、性格等から、向いているお仕事を提案します。 このチャットボットを実現するために、以下の2つのステップの内容で準備を進めます。

ステップ1: Wikipedia + Azure OpenAI service でお仕事情報をまとめよう

ステップ2: お仕事提案チャットボットを作ろう

なお、今回の実行環境は以下です。ソースコード等を記載していますが、あくまでお試し目的で作っているので、参考としてご覧ください。

Python 3.11.4
wikipedia==1.4.0
openai==0.27.8
streamlit==1.26.0
langchain==0.0.301

ステップ1 Wikipedia + Azure OpenAI service でお仕事情報をまとめよう

ステップ1 では、Wikipedia の「職業名一覧」記事から、各お仕事の記事の概要を抽出し、 その概要をプロンプトとともにAzure OpenAI service APIに渡すことで、後述する観点で整理を整理します。

今回は、Wikipediaの各お仕事の記事をそのまま、ユーザの回答と(ベクトル間類似度等で)比較するのではなく、各お仕事記事に2つの加工を加えた状態で利用します。

一つは、共通の項目に基づく、お仕事内容の書き換えです。 これは、各お仕事のWikipedia記事ごとに、記載されている内容や項目、記述方法が異なっており、書き換えによって、同一観点でお仕事を比較できるようにするためです。 各お仕事の情報を、共通の観点で内容を記述するよう、取得した記事概要を、生成AIによって加工します。

もう一つは、各お仕事で必要となるスキルや適正情報の付与です。 これは、今回の比較の観点が、仕事の類似度だけでなく、スキルや個人の性格の観点でも比較できるようにするためです。 生成AIによって加工する際の共通項目に、後述する、必要とするスキルや資格、仕事に向いている性格などの適正条件の項目も出力させるようにします。 これにより、単純なWikipedia記事とユーザ回答の比較では引っ掛からなかった、ユーザ適正の観点でもお仕事提案が可能となります。

Wikipedia 記事からのお仕事情報・概要の抽出

では、はじめに、Wikipedia記事から、各お仕事の概要を取得しましょう。

Wikipediaの職業一覧ページから、職業名を抽出し、各記事のサマリ部分を抽出します。

import os
import wikipedia

# Wikipediaの「職業一覧」記事から、職業名一覧を取得
wikipedia.set_lang("jp")
keyword = "職業一覧"
result = wikipedia.search(keyword)
job_names = str(wikipedia.summary(result))

# 職業名一覧を前処理し、職業名を抽出
## 各行には、ひらがな読みごとの職業名が含まれる。
## (例「あ」) アイドル - アーキビスト - アクチュアリー - アシスタントディレクター - アスレティックトレーナー - アーティスト - アートディレクター - アナウンサー - アニメーター - 海人 - アメリカンフットボール選手* - アレンジャー - あん摩マッサージ指圧師
job_name_list  = []
for line in job_names:
    # 注釈記号/改行記号/ハイフン/空白を取り除く
    line = line.replace("*", "")
    line = line.replace("\n", "")
    jobs = line.split("-")
    for job in jobs:
        job_name_list.append(job.replace(" ", ""))

# 各職業名の記事から、概要部分を抽出する
job_and_summary_path ="./job_and_summary"
if os.path.exists(job_and_summary_path):
    pass
else:
    os.mkdir(job_and_summary_path)
        
for job in job_name_list:
    result = wikipedia.search(job)
    try:
        summary = str(wikipedia.summary(result[0]))
        with open("{}/{}.txt".format(job_and_summary_path,job), "w") as fw:
            fw.write(summary)
    except wikipedia.exceptions.DisambiguationError as e:
        # 語義曖昧性ある職業名・ワードを抽出し、個別で修正した。
        print(e)

これで、各記事から、お仕事の概要を抽出できました。

お仕事情報・概要に基づく、情報の整理

続いて、抽出した各お仕事の概要を、生成AIに入力し、共通項目に整理して出力させましょう。 これは、単純に得られた概要記事だけでは、お仕事の情報のみが記述されており、お仕事ごとに記述内容・方法がバラバラであること、お仕事への適性や必要となるスキル情報が抜け落ちている部分を補完することに加え、明示的に共通項目で整理した情報に加工することで、よりお仕事との比較をしやすくするためです。

今回は、以下の観点で記述するようプロンプトを記述しました。

  • 小学生向けにわかりやすく説明した仕事内容(content)

  • 必要な資格やスキル(skill)

  • 働くために、勉強すべきことや、小学生の時から知っておくと良い知識(knowledge)

  • 性格や、個人の特性を踏まえて、どんな人がその仕事に向いているか(character)

  • そのお仕事に関連する個人のスキル・知識タグ(tag)

  • そのお仕事に関連する個人の特性タグ(character-tag)

プロンプト

# この仕事について、以下の観点でまとめたのち、json形式で出力してください。
・小学生向けにわかりやすく説明した仕事内容(content)
・必要な資格やスキル(skill)
・働くために、勉強すべきことや、小学生の時から知っておくと良い知識(knowledge)
・性格や、個人の特性を踏まえて、どんな人がその仕事に向いているか(character)
・その職業に関連する個人のスキル・知識タグ(tag)
・その職業に関連する個人の特性タグ(character-tag)


# 制約条件
・わかりやすい用語を使ってください。
・小学生向けの説明であるため、公序良俗に反する内容や単語は控えること。
・出力されるjson形式は、読み込みが正しく行えるような形式であるようにする。
・出力形式のjsonの内容には、改行コード(\n)など、読み込みの妨げになる特殊記号は含まないようにする。
・最後のcharacter-tagの一番後ろには、カンマ(,)を使わないこと。

# 出力の例(通訳)
{
    "content": "通訳とは、異なる言語を話す人たちの間に入り、双方の言語を相手方の言語へと変換し伝える仕事です。",
    "skill": "通訳のためには、複数の言語を流暢に話す能力が必要です。また、高度な言語理解力とコミュニケーション能力も求められます。",
    "knowledge": "通訳になるためには、異なる言語の文法や表現方法を学ぶ必要があります。また、各国の文化や習慣にも詳しくなることが重要です。",
    "character": "通訳には、聞き取りの正確さや集中力が求められます。また、柔軟性や冷静な判断力も必要です。異なる文化に対する理解と尊重の姿勢も重要な特性です。",
    "tag": "語学力・コミュニケーション能力・異文文化理解",
    "character-tag": "聞き取りの正確さ・集中力・柔軟性・冷静な理解"
}

# 出力

上記プロンプトを実行しつつ、生成AI に出力させる

import os
import json
import argparse
import openai

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("-conf", type=str, help="API config file path", default="./conf.json")
    return parser.parse_args()

def load_json_file(path: str) -> dict:
    with open(path, "r", encoding="utf-8") as f:
        json_dict = json.load(f)
    return json_dict

def generate(api_conf: dict, prompt: str) -> str:
    # APIキーなどの読み込み。
    openai.api_type    = api_conf["api_type"]
    openai.api_version = api_conf["api_version"]
    openai.api_base    = api_conf["api_base"]
    openai.api_key     = api_conf["api_key"]
    deployment_name ='your_model_name'

    # APIによる生成。温度パラメータなどは各自お好みで。
    response = openai.ChatCompletion.create(engine=deployment_name,
                                        messages = [{"role":"system","content":prompt},
                                                    {"role":"user","content":"では、上記の指示に基づき、出力してください。"}],
                                        temperature=0.7,
                                        top_p=0.95,
                                        frequency_penalty=0,
                                        presence_penalty=0,
                                        stop=None)
    text  = str(response['choices'][0]["message"]["content"])

    return text

def get_job_description(job_name: str, job_description: str) -> str:
    # 各お仕事ごとにプロンプトを作成
    line = "「{}」の仕事は、以下の内容に基づく仕事をしている。".format(job_name)
    
    with open("./job_and_summary/{}.txt".format(job_name), "r") as f:
        job_content = f.readlines()
    
    job = line + "# 仕事の説明\n" + str(job_description) +"\n"

    with open("./prompt.txt", "r") as f:
        condition = f.readlines()
    
    return job + str(condition)

def main():
    args = parse_args()
    api_conf = load_json_file(args.conf)

    # 指定フォルダ以下のファイル名を取得。
    file_path = "./job_and_summary/"
    job_list = os.listdir(file_path)
    job_names = {}

    for job_filename in job_list:
        if ".txt" in job_filename:
            # ファイル名から、拡張子を取り除く
            job_name = os.path.splitext(job_filename)[0]
            with open(file_path+job_filename, "r") as f:
                descripription = f.readlines()
            job_names[job_name] = descripription

    # 得られたテキストを保存しておく。
    file_path = "./job_detail/"
    if os.path.exists(file_path):
        pass
    else:
        os.mkdir(file_path)

    file_path_tag = "./job_tag/" # タグ情報も使う場合は、お仕事情報のテキストに追加しておく。
    if os.path.exists(file_path_tag):
        pass
    else:
        os.mkdir(file_path_tag)
        
    for job_name in job_names:
        if os.path.exists("{}{}.txt".format(file_path, job_name)):
            pass
        else:
            try:
                prompt = get_job_description(job_name, job_names[job_name])

                json_text = generate(api_conf, prompt)

                # APIの生成結果(json形式)を読み込み、1つの文書とて保存
                job_dict = json.loads(json_text)
                job_detail = job_dict["content"] + job_dict["skill"] + job_dict["knowledge"] + job_dict["character"]
                job_details[job_name] = job_detail

                # 職業説明の書き出し
                with open("{}{}.txt".format(file_path, job_name), "w") as fw:
                    fw.write(job_details[job_name])
                
                # 職業に関連するタグの書き出し
                with open("{}{}.txt".format(file_path_tag, job_name), "w") as fw:
                                    fw.write(job_dict["tag"]+job_dict["character-tag"])
            
            except SyntaxError as e:
                # プロンプトのパラメータ次第では、json形式での生成が失敗する場合があることに注意。
                print(job_name)

if __name__ == '__main__':
    main()

プロンプトに記載があるように、お仕事情報の出力形式は、jsonにしています。 これは、各項目を明示的に出力させることで、項目の抜け漏れを防ぐためです。 上記処理の入出力例は以下のようになります。

入力の例(Wikipedia の各お仕事記事の概要部分)

ファッションデザイナー(en:fashion designer、fr:styliste)とは、デザイナーの中で、服飾・ファッション分野のデザインを専門とする者を指す。特に、ファッションショー等に出展する者の職業名ではなく、実際にはこれらに無関係の、企業専属のアパレルデザイナーや、インディーズのデザイナー、フリーのデザイナーなど、多岐にわたる。彼らは一貫してデザイン画を描いたり、実際に裁縫をすることも多い。なお、服の完成や、商品の売上について責任の一端を負う場合も多い。映画や舞台などにおける衣裳をてがけるデザイナーは、衣裳デザイナーと呼ばれる。

画家(がか)は、絵画を制作する者の総称である。日本画や洋画など、画風や画材・作成スタイルなどによって、様々なタイプの画家が存在する。画家たちで形成されるコミュニティー(社会)を画壇(がだん)という。

出力内容の例(json形式)

{
    "content": "ファッションデザイナーとは、服飾・ファッション分野のデザインを専門とするデザイナーのことです。彼らはデザイン画を描いたり、裁縫をすることもあります。また、商品の売上にも責任を持つことがあります。",
    "skill": "ファッションデザイナーになるためには、創造力やデザインセンスが必要です。また、服飾やファッションのトレンドに敏感であることが求められます。デザイン画やパターン作成、裁縫などの技術も重要です。",
    "knowledge": "ファッションデザイナーになるためには、服飾やファッションに関する知識が必要です。素材や色彩、デザインの歴史やトレンドなどについての知識が重要です。また、市場や消費者のニーズについても理解しておくことが大切です。",
    "character": "ファッションデザイナーには、創造性や独自性が求められます。また、忍耐力や努力を持ち、デッサンや裁縫などの作業に集中できることも重要です。ファッションに対する情熱や好奇心も必要な特性です。",
    "tag": "創造力・デザインセンス・トレンド感・技術力・知識",
    "character-tag": "創造性・忍耐力・集中力・情熱・好奇心"
}

{
    "content": "画家は、絵画を制作する者の総称であり、画風や画材・作成スタイルなどによって様々なタイプの画家が存在します。画家たちで形成されるコミュニティーを画壇といいます。",
    "skill": "画家になるためには、絵画の技術や表現力が必要です。また、創造性や想像力も求められます。",
    "knowledge": "画家になるためには、絵画の歴史や様々な画風・画材についての知識が重要です。また、美術の基本的な概念や色彩理論なども学ぶ必要があります。",
    "character": "画家には、繊細さや感性が求められます。また、忍耐力や粘り強さも必要です。自己表現や創造力に興味・関心を持っている人に向いています。",
    "tag": "絵画技術・創造性・美術史・色彩理論",
    "character-tag": "繊細さ・感性・忍耐力・粘り強さ"
}

最終的に、Wikipediaの職業一覧ページに記載のある、約600種のお仕事の情報を、各項目を含むように生成できました。 多少、お仕事内容のかぶりはありますが、比較的良さそうな出力結果が得られました。 ※生成内容については、シードや、生成の際の各パラメータ設定によって変化します。

ステップ2 お仕事提案チャットボットを作ろう

ステップ2 では、ステップ1で作成したお仕事情報をもとに、お仕事提案チャットボットを作成します。

まず、チャットで、ユーザに興味や得意なこと、性格といった項目を入力させます。

ユーザの必要項目が入力されると、ユーザの入力(のベクトル表現/embedding)と、あらかじめ共通項目で整理された各お仕事情報(のベクトル表現/embeddings)の、類似度を比較します。

最後に、類似する上位のお仕事情報とユーザの各項目、プロンプトを生成AIに入力し、お仕事の提案を行います。

今回は、3回の対話のやり取りの結果から、ユーザの興味やスキル、性格を一つのベクトル(embedding)とし、 各お仕事のベクトル(embeddings)とコサイン類似度を取り、上位のお仕事を取得します。

ベクトル(embeding)を取得するモデルについて、せっかくなので、こちらは Azure の Embedding モデルのAPIではなく、HuggingFace で公開されている Embedding モデル(LUKE Japanese base をもとにしたモデル)を使ってみました。

このチャットボットの実装の注意点として、チャットでユーザの興味や得意なこと、性格を尋ねるプロンプトと、ユーザへのお仕事提案をするプロンプト(こちらはソースコード中で作成)の2つを実行しています。

デモのWebアプリの作成には、Streamlitを用いました。

import argparse
import json
import os

import numpy as np
import openai
from langchain.schema import (SystemMessage, HumanMessage, AIMessage)
from langchain.embeddings import HuggingFaceEmbeddings
import streamlit as st

bot_name = "お仕事提案chatbot"
icon = "👶"
embedding = HuggingFaceEmbeddings(model_name="oshizo/sbert-jsnli-luke-japanese-base-lite")


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("-prompt", type=str, help="prompt file path", default="./chat_prompt.json")
    parser.add_argument("-conf", type=str, help="config file path", default="./conf.json")
    return parser.parse_args()

def load_json_file(path: str) -> dict:
    with open(path, "r", encoding="utf-8") as f:
        json_dict = json.load(f)
    return json_dict

    
def read_docs(path: str) -> dict:
    job_list = os.listdir(path)
    docs = {}
    for job_filename in job_list:
        if ".txt" in job_filename:
            # ファイル名から、拡張子を取り除く
            job_name = os.path.splitext(job_filename)[0]
            with open(path+job_filename, "r") as f:
                doc = f.readlines()
            docs[job_name] = doc
    return docs

def get_doc_embeddings(docs: list) -> (dict, dict, dict):
    text_dict = {}
    emb_dict  = {}
    doc_name_dict = {}
    
    for i, doc_name in enumerate(docs):
        doc = "".join(docs[doc_name])
        doc_result = embedding.embed_query(doc)
        text_dict[i] = "".join(doc)
        emb_dict[i]  = doc_result
        doc_name_dict[i] = doc_name
    return text_dict, emb_dict, doc_name_dict

def calc_all_similarity(question_emb, text_dict, emb_dict, doc_name_dict) -> (str, list, list):
    sim = 0
    sim_id = int()
    similarity = []
    for n, (t, i) in enumerate(zip(text_dict, emb_dict)):
        cos_n = cos_sim(question_emb, emb_dict[i])
        if cos_n > sim:
            sim = round(float(cos_n),3)
            sim_id = n
        similarity.append([round(float(cos_n),3) , doc_name_dict[n], "".join(text_dict[t])])
    most_matched_text = text_dict[sim_id]

    # コサイン類似度が高い順にソートし、上位3つを返却
    similarity_reverse = sorted(similarity, reverse=True)
    return most_matched_text, similarity, similarity_reverse[:3]

def cos_sim(v1, v2):
    return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))

def make_prompt(user_answer: str, similarity_reverse_top_three: list) -> str:
    p = "# 命令書 あなたは、プロの提案者です。以下のユーザ文書と、3つの仕事内容から、ユーザの文書を参照しつつ、提示されている3つの仕事の推薦コメントを作成してください。"
    p += "# ユーザ回答" + user_answer
    p += "# 仕事内容"
    for i, j in enumerate(similarity_reverse_top_three):
        p += "## 仕事({}):{}".format(i, j[2])
    p += "# 推薦コメント"
    return p

def start_app(api_conf, prompt):
    # APIキーなどの読み込み。
    openai.api_type    = api_conf["api_type"]
    openai.api_version = api_conf["api_version"]
    openai.api_base    = api_conf["api_base"]
    openai.api_key     = api_conf["api_key"]
    deployment_name ='your_model_name'

    print("setting chat")
    st.set_page_config(
        page_title=bot_name,
        page_icon=icon
    )
    st.header("{}{}".format(bot_name, icon))
    
    conversation=[{"role": "system", "content": prompt["prompt"]}]
    
    # 参照テキストのベクトル化 または ロード
    first_question = "あなたの好きなことや興味、スキルや性格からお仕事を推薦します。あなたの情報を教えてください。"

    # チャット履歴の初期化
    if "messages" not in st.session_state:
        st.session_state.messages = [
            SystemMessage(content=prompt["prompt"])
        ]
        st.session_state.messages.append(AIMessage(content=first_question))

    # ユーザーの入力を監視
    if user_input := st.chat_input("あなたの好きなことや興味、スキルや性格からお仕事を推薦します。あなたの情報を教えてください。"):
        st.session_state.messages.append(HumanMessage(content=user_input))
        conversation.append({"role": "user", "content": user_input})
        with st.spinner("回答を生成中"):
            response = openai.ChatCompletion.create(engine=deployment_name,
                                    messages = conversation,
                                    temperature=0.7,
                                    top_p=0.95,
                                    frequency_penalty=0,
                                    presence_penalty=0,
                                    stop=None)
            conversation.append({"role": "assistant", "content": response["choices"][0]["message"]["content"]})
        st.session_state.messages.append(AIMessage(content=response["choices"][0]["message"]["content"]))

    # チャット履歴の表示
    messages = st.session_state.get('messages', [])
    user_messages = []
    for message in messages:
        if isinstance(message, AIMessage):
            if len(user_messages) == 3:
                # 3回目の結果を二重に表示させないため、生成結果をスキップ
                pass
            else:
                with st.chat_message('assistant'):
                    st.markdown(message.content)

        elif isinstance(message, HumanMessage):
            with st.chat_message('user'):
                st.markdown(message.content)
            user_messages.append(message.content)

        else:
            pass

    # ユーザから3回質問の結果をもらったら、お仕事を検索し、提案を生成
    # 今回は、ユーザに尋ねる回数を3回に設定しているので、そのまま記載
    if len(user_messages) == 3:
        print("loading emb")    
        file_path = "./job_detail/"
        docs = read_docs(file_path)

        # 各お仕事のembedingを作成(今回は読み込みではなく、この時に作成)
        text_dict, emb_dict, doc_name_dict = get_doc_embeddings(docs)
        
        # ユーザ入力を変換
        user_answer = "".join(user_messages)
        question_emb = embedding.embed_query(user_answer)
        
        # ユーザ入力とお仕事の類似度を算出し、類似するお仕事を取得
        most_matched_text, similarity, similarity_reverse_top_three = calc_all_similarity(question_emb, text_dict, emb_dict, doc_name_dict)
        
        ## ユーザ入力とお仕事の共通点を引用しながら、提案
        user_input_and_recommended_jobs = make_prompt(user_answer, similarity_reverse_top_three)
        recommend=[{"role": "system", "content": user_input_and_recommended_jobs}]

        response = openai.ChatCompletion.create(engine=deployment_name,
                            messages = recommend,
                            temperature=0.7,
                            top_p=0.95,
                            frequency_penalty=0,
                            presence_penalty=0,
                            stop=None)
        st.session_state.messages.append(AIMessage(content=response["choices"][0]["message"]["content"]))
        with st.chat_message('assistant'):
            st.markdown(response["choices"][0]["message"]["content"])
    return

def main():
    args = parse_args()
    api_conf = load_json_file(args.conf)
    prompt   = load_json_file(args.prompt)
    start_app(api_conf, prompt)

if __name__ == '__main__':
    main()

プロンプト(ユーザからの情報取得)

# 命令書
あなたはプロのインタビュアーです。 
以下の制約条件に沿って、ユーザから必要な情報の回答を得てください。

# 制約条件
・ユーザから、スキル・知識・性格の観点で情報を聞き出してください。
・スキル:ユーザが持っている資格や能力、得意なこと。
・知識:ユーザが持っている知識や興味、好きなこと。
・性格:ユーザの性格。
・ユーザが質問に答えられていない場合には、以下のユーザ情報の例を参考に、ユーザにも例を提示してください。
・一度の質問は1つまでです。
・各項目について、1つ以上聞ければ、情報の聞き出しを完了としてください。
・各項目を詳しく深掘りする必要はありません。
・過去の質問と類似する質問はしないでください。

# ユーザ情報の例1
・スキル:コミュニケーション能力がある
・知識:デザインの歴史に詳しい
・性格:優しい

# ユーザ情報の例2
・スキル:英語が得意
・知識:運動が好き
・性格:責任感が強い

# ユーザ情報の例3
・スキル:色彩検定資格
・知識:プログラミング言語
・性格:創造性

デモと結果の例

対話の結果、以下のようなお仕事提案ができました。 対話履歴を見ていただくと分かる通り、提案されるお仕事は、ユーザの得意なことや興味に沿ったもの が提案できています。 これによって、自分の好きなことに近しい、知らない仕事を新しく提案できるようになりました。

参考までに、同様の結果を、ChatGPTで出力した場合には、以下のような仕事が提案されました。ChatGPTの方が、一般的に認知度の高い仕事が出ているように思えます。 いろんな仕事を知る、という意味では、生成AIのAPI単体で出力した結果と、RAGで得られた結果の両方を利用することで、さまざまな観点で(事実に基づいた)仕事を知るきっかけを提供できるのではないかなと思います。

課題

今回作成したお仕事提案のデモについての課題は以下です。

  • チャット返答・お仕事比較部分の高速化: 今回は、比較するお仕事の数はせいぜい600ほどですので、とりあえず動くところまでにしました(なので提案部分は遅いです)。数万・数十万以上の文書との比較が必要であれば、高速化を検討する必要があります。お仕事の embedding を事前に作成しておくことや、Faissなどを検討してみる感じですかね。

  • 出力内容の変更: 今回は、プロンプトの条件に、わかりやすく書くように出力しましたが、子供(とりわけ小学生以下)に利用してもらって検討すべき部分は、漢字表記かなと思います。実際に子供に使ってもらう場合には、読める漢字への書き換えは必要になると思います。 「常用漢字・小学生向け漢字」を指定する形で読み込ませるには、プロンプトに「ここにある漢字以外は、ひらがなで回答するか、読み方(よみかた)をつけてください」のように記述するか、出力に小学生が習わない漢字を含む場合に、MeCab等で漢字の読みを取得して、変換するような処理をすることになるかもしれません。

  • 子供向けのUIUX設計: 上記と合わせて、音声認識 で入力できるようにしたり、子供が使いやすいインターフェースにすることで利用ハードルを下げることも必要ですね。

  • 入力内容の例示: 今回は興味のあることや好きなこと、得意なことに性格を入力してもらうことを想定しましたが、そんな簡単に出てこないかもしれません。特に、自分の性格を、自分で答えることは難しいと思います。 ですので、そういったものの例示も必要になるかなと思います。例えば、事前に性格診断をおこない、その結果を入力する、などはありかなと思います。

  • 提案結果の修正: 今回は1度提案するところまでとしましたが、「もう少しこういう感じの仕事はある?」 みたいな形で、出力結果に対する修正希望や、詳細についての追加質問回答も必要になると思います。

  • 職業体験への導線: 今回は、お仕事の提案まででしたが、自分に合う、新しい職業を知るだけではなく、それを実際に体験できるところまで繋ぐことができれば、さらに良いチャットボットになると思います。一つの方法としては、地図API等と組み合わせて、関連する場所(例えば、消防署)を紹介したり、子供向けの職業見学・体験サービスに繋ぐ、といったことができると、さらに良いですね。

まとめ

  • 今回は、子供向け生成AIの活用として、お仕事提案チャットボット のデモを実装してみました。

  • 子供が知らない職業と出会う、一つの方法として、活用してみるのは良いかもしれません。

  • さらにチャットボットを価値あるものにするために、職業見学・体験に繋げる仕組みを増やすと良いかもしれません。

  • 生成AIのいろんなテクニックを使ってみる、を裏テーマとして記事を書いているので、プロンプトの中身や、各項目で記載している方法が、何かの参考になれば幸いです。

参考文献・記事

以前書いた記事