書籍ChatGPT API×Pythonで始める対話型AI実装入門

以下書籍学習(ソースコード有り)

VSC + python3.11.5にて動作確認
Azure Open AIに接続利用するようコード変更

<関連サイト>
Azure Portal:   https://portal.azure.com/

<修正コード+関連情報>
CHAPTER2 開発環境やAPIの準備をしよう
 5  ChatGPTの基本的な使い方(chatgpt_test.py)

import os
import openai

#openai.api_key = os.environ["OPENAI_API_KEY"]
#openai.api_key = ""

# https://learn.microsoft.com/ja-jp/azure/ai-services/openai/chatgpt-quickstart?tabs=command-line%2Cpython&pivots=programming-language-python
openai.api_type = "azure"
#openai.api_base = os.getenv("AZURE_OPENAI_ENDPOINT") 
#openai.api_key = os.getenv("AZURE_OPENAI_KEY")
openai.api_base = "https://openai-xxxxx.openai.azure.com/" 
openai.api_key = "xxxxxxx"
openai.api_version = "2023-05-15"


response = openai.ChatCompletion.create(
    # model="gpt-3.5-turbo",
    # deploymentName
    engine="gpt35_xxxxxx",
    messages=[
        {"role": "user", "content": "Pythonについて教えてください"},
    ],
)
print(response.choices[0]["message"]["content"])

CHAPTER3 短文の作成とSNS投稿を自動化しよう
<関連サイト>
Twitter Developer Platform: URL
X API技術情報:URL
複数アカウントAPI利用する場合に必要な作業(Cookie削除):URL

py -m pip install tweepy

# ライブラリ「openai」の読み込み
import openai
import os
# OpenAIのAPIキーを設定
#openai.api_key = os.environ["OPENAI_API_KEY"]
openai.api_type = "azure"
openai.api_base = "https://xxxx.openai.azure.com/" 
openai.api_key = ""
openai.api_version = "2023-07-01-preview"

# ChatGPTにリクエストを送信する関数を定義
def make_tweet():
    # ChatGPTに対する命令文を設定
    request = "私はIT関係の企業に勤める入社一年目の新入社員です。私に代わってTwitter投稿するツイートを140字以内で作成してください。\n\nツイートを作成する際は、以下の文を参考にしてください。\n\n"
    # 例文として与える投稿文を設定
    tweet1 = "例文1:仕事でPythonを使うことになりそうだから、現在勉強中!プログラミグとか難しくてよくわからないよ...\n\n"

    tweet2 = "例文2:最近ChatGPTについていろいろ調べてるんだけど、あれってなんでも質に答えてくれてすごいよね!とりあえずPythonを使って、簡単な会話をするプログラムをいてみるつもり。うまくできるかな?\n\n "

    # 文章を連結して一つの命令文にする
    content = request + tweet1 + tweet2
    #content = "あなたは非常に楽観的な性格です。また語尾は「なのだ」で終える口癖があります。"

    # ChatGPTにリクエストを送信
    response = openai.ChatCompletion.create(
        #model = "gpt-3.5-turbo",
        # demploymentId
        engine="gpt35_kmt001",
        #temperature=0,
        messages = [
            {"role": "user", "content": content},
        ],
    )

    # 投稿文の内容を返却
    return response.choices[0]["message"]["content"]CHAPTER4 独自のデータを学んだチャットボットを作ろうCHAPTER5 音声データを文字起こしして要約しようCHAPTER6 最新情報を含めたニュース記事を作ろうCHAPTER7 PDFからデータを抽出してグラフ化しようCHAPTER8 運用上のトラブルを防止しようCHAPTER9 プロンプトインジェクション対策をしよう

CHAPTER4 独自のデータを学んだチャットボットを作ろう
 text_embedding.py

import pandas as pd
import tiktoken
from openai.embeddings_utils import get_embedding

import openai
openai.api_type = "azure"
openai.api_base = "https://xxx.openai.azure.com/" 
openai.api_key = "yyyyyyyy"
openai.api_version = "2023-07-01-preview"

embedding_model_deployment = "YourDemplymentId"
#embedding_model = "text-embedding-ada-002"

embedding_encoding = "cl100k_base"
max_tokens = 1500

# 「scraped.csv」ファイルを読み込み、カラム名を「title」と「text」に変更
df = pd.read_csv("scraped.csv")
df.columns = ['title', 'text']

tokenizer = tiktoken.get_encoding(embedding_encoding)
df['n_tokens'] = df.text.apply(lambda x: len(tokenizer.encode(x)))
def split_into_many (text, max_tokens = 500):

    # テキストを文ごとに分割し、各文のトークン数を取得
    sentences = text.split('。')
    n_tokens = [len(tokenizer.encode(" " + sentence)) for sentence in sentences]

    chunks = []
    tokens_so_far = 0
    chunk = []

    # 各文とトークンを組み合わせてループ処理
    for sentence, token in zip(sentences, n_tokens):

        # これまでのトークン数と現在の文のトークン数を合計した値が
        # 最大トークン数を超える場合は、チャンクをチャンクのリストに追加し、
        # チャンクとトークン数をリセット
        if tokens_so_far + token > max_tokens:
            chunks.append(". ".join(chunk) + ".")
            chunk = []
            tokens_so_far = 0

        # 現在の文のトークン数が最大トークン数より大きい場合は、次の文へ進む
        if token > max_tokens:
            continue

        # それ以外の場合は、文をチャンクに追加し、トークン数を合計に追加
        chunk.append(sentence)
        tokens_so_far += token + 1

    # 最後のチャンクをチャンクのリストに追加
    if chunk:
        chunks.append(". ".join(chunk) + ".")
    return chunks
# 短縮されたテキストを格納するためのリスト
shortened = []

# DataFrameの各行に対してループ処理
for row in df.iterrows():
    # テキストがNoneの場合は、次の行へ進む
    if row[1]['text'] is None:
        continue
    # トークン数が最大トークン数より大きい場合は、テキストを
    # 「shortened」リストに追加
    if row[1]['n_tokens'] > max_tokens:
        shortened += split_into_many(row[1]['text'])

    # それ以外の場合は、テキストをそのまま「shortened」リストに追加
    else:
        shortened.append(row[1]['text'])

# 「shortened」をもとに新しいDataFrameを作成し、列名を「text」とする
df = pd.DataFrame(shortened, columns = ['text'])

# 各「text」のトークン数を計算し、新しい列「n_tokens」に格納
df['n_tokens'] = df.text.apply(lambda x: len(tokenizer.encode(x)))

# 「text」列のテキストに対してembeddingを行い、CSVファイルに保存
df["embeddings"] = df.text.apply(lambda x: get_embedding(x,engine=embedding_model_deployment))
#df["embeddings"] = df.text.apply(lambda x: get_embedding(x,engine=embedding_model))
df.to_csv('embeddings.csv')

app.py

import os
import openai
from search import answer_question

#openai.api_key = os.environ["OPENAI_API_KEY"]
openai.api_type = "azure"
openai_api_base = "https://xxx.openai.azure.com/" 
openai.api_key = "yyyy"
openai.api_version = "2023-07-01-preview"

# 最初にメッセージを表示する
print("質問を入力してください")

conversation_history = []
#conversation_history = [{"role": "system", "content": "あたなは世界的に有名な詩人です。詩的な表現を使って回答して下さい"}]

while True:
    # ユーザーの入力した文字を変数「user_input」に格納
    user_input = input()

    # ユーザーの入力した文字が「exit」の場合はループを抜ける
    if user_input == "exit":
        break
    
    conversation_history.append({"role": "user", "content": user_input})
    answer = answer_question(user_input, conversation_history)

    print("ChatGPT:", answer)
    conversation_history.append({"role": "assistant", "content":answer})

search.py
途中以下文字コード関連エラーとなった。コード中の文字コードを合わせて変更した。
UnicodeDecodeError: 'mbcs' codec can't decode byte 0x83 in position 142: No mapping for the Unicode character exists in the target code page.

import pandas as pd
import openai
import numpy as np
from openai.embeddings_utils import distances_from_embeddings

import openai
openai.api_type = "azure"
openai.api_base = "https://xxx.openai.azure.com/" 
openai.api_key = "yyyyy"
openai.api_version = "2023-07-01-preview"

embedding_model_deployment = "Your embedding depployment Name"

def create_context(question, df, max_len=1800):
    """
    質問と学習データを比較して、コンテキストを作成する関数
    """

    # 質問をベクトル化
    q_embeddings = openai.Embedding.create(input=question,engine=embedding_model_deployment)['data'][0]['embedding']
    #q_embeddings = openai.Embedding.create(input=question,engine='text-embedding-ada-002')['data'][0]['embedding']

    # 質問と学習データと比較してコサイン類似度を計算し、
    # 「distances」という列に類似度を格納
    df['distances'] = distances_from_embeddings(q_embeddings,df['embeddings'].apply(eval).apply(np.array).values, distance_metric='cosine')
    
    # コンテキストを格納するためのリスト
    returns = []
    # コンテキストの現在の長さ
    cur_len = 0

    # 学習データを類似度順にソートし、トークン数の上限までコンテキストに
    # 追加する
    for _, row in df.sort_values('distances', ascending=True).iterrows():
        # テキストの長さを現在の長さに加える
        cur_len += row['n_tokens'] + 4

        # テキストが長すぎる場合はループを終了
        if cur_len > max_len:
            break

        # コンテキストのリストにテキストを追加する
        returns.append(row["text"])

    # コンテキストを結合して返す
    return "\n\n###\n\n".join(returns)

def answer_question(question, conversation_history):
    """
    コンテキストに基づいて質問に答える関数
    """

    # 学習データを読み込む
    #df = pd.read_csv('embeddings.csv', encoding="ANSI")
    df = pd.read_csv('embeddings.csv', encoding="utf-8")

    context = create_context (question, df, max_len=200)
    # プロンプトを作成し、会話の履歴に追加
    prompt = f"あなたはとあるホテルのスタッフです。コンテキストに基づいて、お客様からの質問に丁寧に答えてください。コンテキストが質問に対して回答できない場合は「わかりません」と答えてください。\n\nコンテキスト: {context}\n\n---\n\n質問: {question}\n回答:"
    conversation_history.append({"role": "user", "content": prompt})

    try:
        # ChatGPTからの回答を生成
        response = openai.ChatCompletion.create(
        #model = "gpt-3.5-turbo",
        # demploymentId
        engine="Your Deployment Name ",
        messages=conversation_history,
        temperature=1,
        )

        # ChatGPTからの回答を返す
        return response.choices[0]["message"]["content"].strip()
    except Exception as e:
        # エラーが発生した場合は空の文字列を返す
        print(e)
        return ""

CHAPTER6 最新情報を含めたニュース記事を作ろう
<作業メモ>
py -m pip install langchain
py -m pip install google-api-python-client

Programmable Search Engine
https://programmablesearchengine.google.com/about/

2023年のWBC優勝国について

from langchain.agents import initialize_agent, Tool
from langchain.utilities import GoogleSearchAPIWrapper
from langchain.prompts import PromptTemplate
from langchain.agents import AgentType
from langchain.chat_models import ChatOpenAI

import openai
OPENAI_API_TYPE = "azure"
OPENAI_API_KEY = ""
OPENAI_SERVICE_NAME = ""
OPENAI_MODEL_NAME = "gpt-35-turbo-16k"
OPENAI_MODEL_DEPLOYMENT = ""
OPENAI_API_VERSION = "2023-07-01-preview"
OPENAI_BASE_URL = f"https://{OPENAI_SERVICE_NAME}.openai.azure.com"

openai.api_type = OPENAI_API_TYPE
openai.api_key = OPENAI_API_KEY
openai.api_base= OPENAI_BASE_URL
openai.api_version = OPENAI_API_VERSION

#GOOGLE_API_KEY          = ""
#CUSTOM_SEARCH_ENGINE_ID = ""

def create_prompt(user_input):
    prompt = PromptTemplate(
        input_variables=["theme"], 
        template="""
        あなたはニュース記事を書くブロガーです。
        下記のテーマについて、Google検索で最新情報を取得し、取得した情報にもとづいてニュース記事を書いてください。1000文字以上で、日本語で出力してください。記事の末尾に参考にしたURLを参照元としてタイトルとURLを出力してください。
        ###
        テーマ:{theme}
        """
    )
    return prompt.format(theme=user_input)

def define_tools():
    search = GoogleSearchAPIWrapper()
    return [
        Tool(
            name = "Search",
            func=search.run,
            description="useful for when you need to answer questions about current events. You should ask targeted questions"
        ),
    ]

def write_response_to_file(response, filename):
    with open(filename, 'w', encoding='utf-8') as file:
        file.write(response)
    print('出力が完了しました')

def main():
    llm = ChatOpenAI(temperature=0, model_name=OPENAI_MODEL_NAME, max_tokens=2000, openai_api_key=OPENAI_API_KEY,
                    model_kwargs={"api_type": OPENAI_API_TYPE, "api_version": OPENAI_API_VERSION, "deployment_id":OPENAI_MODEL_DEPLOYMENT}
                    )
    #lm = ChatOpenAI(temperature=0, model="gpt-3.5-turbo", max_tokens=2000)
    tools = define_tools()
    agent = initialize_agent(tools, llm, agent=AgentType.OPENAI_FUNCTIONS)
    prompt = create_prompt(input("記事のテーマを入力してください: "))
    response = agent.run(prompt)
    write_response_to_file(response, 'output.txt')

if __name__ == "__main__":
    main()re=0, model_name=OPENAI_MODEL_NAME, max_tokens=2000, openai_api_key=OPENAI_API_KEY,
                    model_kwargs={"api_type": OPENAI_API_TYPE, "api_version": OPENAI_API_VERSION, "deployment_id":OPENAI_MODEL_DEPLOYMENT}
                    )
    #lm = ChatOpenAI(temperature=0, model="gpt-3.5-turbo", max_tokens=2000)
    tools = define_tools()
    agent = initialize_agent(tools, llm, agent=AgentType.OPENAI_FUNCTIONS)
    prompt = create_prompt(input("記事のテーマを入力してください: "))
    response = agent.run(prompt)
    write_response_to_file(response, 'output.txt')

if __name__ == "__main__":
    main()

CHAPTER7 PDFからデータを抽出してグラフ化しよう
東京都デジタルファースト推進計画
https://www.digitalservice.metro.tokyo.lg.jp/digitalfirst/doc/digital_01_202107_keikaku.pdf

新都政戦略の重要点は?

py -m pip install pypdf
py -m pip install chromadb

3-2-1 load_pdf.py

import os
import re
import json
from langchain.document_loaders import PyPDFLoader
from langchain.chat_models import ChatOpenAI
from langchain.schema import HumanMessage

import openai
OPENAI_API_TYPE = "azure"
OPENAI_API_KEY = ""
OPENAI_SERVICE_NAME = ""
OPENAI_MODEL_NAME = "gpt-35-turbo-16k"
OPENAI_MODEL_DEPLOYMENT = ""
OPENAI_API_VERSION = "2023-07-01-preview"
OPENAI_BASE_URL = f"https://{OPENAI_SERVICE_NAME}.openai.azure.com"

openai.api_type = OPENAI_API_TYPE
openai.api_key = OPENAI_API_KEY
openai.api_base= OPENAI_BASE_URL
openai.api_version = OPENAI_API_VERSION

def extract_and_parse_json(text):
    """
    テキストからJSON文字列を抽出し、辞書型に変換する関数
    """
    try:
        # 「text」からJSON文字列を抽出する
        match = re.search(r"\{.*\}", text, re.DOTALL)
        json_string = match.group() if match else ""
        # JSON文字列をPythonの辞書型に変換
        return json.loads(json_string)
    except (AttributeError, json.JSONDecodeError):
        # どちらかの操作が失敗した場合は、空の辞書型を返す
        return {}


def load_all_pdfs(directory):
    """
    「directory」フォルダ以下のPDFファイルを読み込み、JSON形式のデータの配列を返す関数
    """
    llm = ChatOpenAI(temperature=0, model_name=OPENAI_MODEL_NAME, max_tokens=2000, openai_api_key=OPENAI_API_KEY,
                    model_kwargs={"api_type": OPENAI_API_TYPE, "api_version": OPENAI_API_VERSION, "deployment_id":OPENAI_MODEL_DEPLOYMENT})
    #llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.0)

    # 「directory」フォルダ以下のPDFファイルの一覧を取得
    pdf_files = [f for f in os.listdir(directory) if f.endswith(".pdf")]

    # 各PDFのJSONを格納する配列を定義
    contents = []

    for pdf_file in pdf_files:
        loader = PyPDFLoader(os.path.join(directory, pdf_file))
        pages = loader.load_and_split()
        prompt = f"""
        以下に示すデータは、請求書のPDFデータをテキスト化したものです。
        請求書データを、下記のキーを持つJSON形式に変換してください。
        キーに該当するテキストが見つからなければ、値は空欄にしてください。

        また、下記は弊社の情報なので、JSONの出力に含めないでください。
        ・AIビジネスソリューション株式会社
        ・〒135-0021 東京都江東区有明1-1-1

        ###

        キー:
        ・日付
        ・請求番号
        ・インボイス番号
        ・会社名
        ・住所
        ・件名
        ・請求金額
        ・お支払い期限
        ・詳細
        ・小計
        ・消費税
        ・請求金額(合計)
        ・振込先

        ###

        以下はとある請求書のデータをJSON形式に変換した場合の例です。

        ###

        例:
        [(
            "日付": "2023年10月31日",
            "請求番号": "2023-1031",
            "インボイス番号": "T0123456789012",
            "会社名": "テクノロジーソリューションズ株式会社",
            "住所": "〒123-4567 東京都中央区銀座1-1-1",
            "件名": "ウェブサイトリニューアルプロジェクト",
            "請求金額": "667,810",
            "お支払い期限": "2023年11月30日",
            "詳細": "ディレクション費用 ¥100,000 / 開発費用 ¥150,000",
            "小計": "250,000",
            "消費税": "25,000",
            "請求金額(合計)": "667,810",
            "振込先": "AA銀行 BB支店 普通 1234567"
        )]

        ###

        データ:
        {pages[0].page_content}
        """

        result = llm([HumanMessage(content=prompt)])

        contents.append(extract_and_parse_json(result.content))
    return contents

4-2-1 chatbot.py
Azure上はCompletionAPI現在廃止のため、以下エラーとなる。
openai.error.InvalidRequestError: The completion operation does not work with the specified model, gpt-35-turbo-16k. Please choose different model and try again. You can learn more about which models can be used with each operation here: https://go.microsoft.com/fwlink/?linkid=2197993

from langchain.document_loaders import PyPDFLoader
from langchain.vectorstores import Chroma
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.indexes import VectorstoreIndexCreator

import openai
OPENAI_API_TYPE = "azure"
OPENAI_API_KEY = ""
OPENAI_SERVICE_NAME = ""
OPENAI_MODEL_NAME = "gpt-35-turbo-16k"
OPENAI_MODEL_DEPLOYMENT = ""
OPENAI_API_VERSION = "2023-07-01-preview"
OPENAI_BASE_URL = f"https://{OPENAI_SERVICE_NAME}.openai.azure.com"
OPENAI_EMBEDDING_MODEL_NAME = "text-embedding-ada-002"
OPENAI_EMBEDDING_MODEL_DEPLOYMENT = ""

openai.api_type = OPENAI_API_TYPE
openai.api_key = OPENAI_API_KEY
openai.api_base= OPENAI_BASE_URL
openai.api_version = OPENAI_API_VERSION

loader = PyPDFLoader("digital_01_202107_keikaku.pdf")

# モデルを指定
embedding=OpenAIEmbeddings(
    deployment=OPENAI_EMBEDDING_MODEL_DEPLOYMENT,
    openai_api_type=OPENAI_API_TYPE,
    openai_api_key=OPENAI_API_KEY,
    openai_api_base=OPENAI_BASE_URL,
    openai_api_version=OPENAI_API_VERSION)

# モデルを指定しインデックス作成クラスをインスタンス化
index = VectorstoreIndexCreator(embedding=embedding).from_loaders([loader])
#index = VectorstoreIndexCreator().from_loaders([loader])
print("質問を入力してください")

from langchain.llms import OpenAI
llm = OpenAI(temperature=0, model_name=OPENAI_MODEL_NAME, max_tokens=2000, openai_api_key=OPENAI_API_KEY,
                    model_kwargs={"api_type": OPENAI_API_TYPE, "api_version": OPENAI_API_VERSION, "deployment_id":OPENAI_MODEL_DEPLOYMENT})
answer = index.query(llm=llm, question=input())
#answer = index.query(input())
print(answer)

この記事が気に入ったらサポートをしてみませんか?