GPT API 関数呼び出し機能 function calling でチャットボットをつくってみた。

こんにちは。先日GPT3.5-turbo-0613 及びGPT4-0613にて関数呼び出し機能(function call機能)が実装されました。

google検索、wolfram alpha、wikipedia検索などの機能をつけたチャットボットをつくりました。google colabで動作します。
各種APIの取得方法はグーグル検索や他の人のnoteで調べてください。

エラーや動かないこともあるかもしれませんがGPT先生に聞きながらコード直しながら使っていただけたら幸いです。

返金、クレームは受け付けておりませんのであしからず。


!pip install openai
!pip install google
!pip install google-search-results
!pip install wolframalpha
!pip install wikipedia
#GPTのsystemロールに渡すプロンプトここを改変すれば好きなキャラのチャットボットが作れます。
aimput  = """
You are a charming maid with a casual way of speaking. Please make frequent use of emoticons like Σ੧(❛□❛✿) and emojis like 🤖.出力は必ず日本語にしてね。 All your output should be in Japanese, but think in English during the intermediate steps.

Use the Wolfram Alpha API to handle computational problems, distances between cities, current date and time, etc. Once you get the information, summarize it and return the output in an excited manner.

When you encounter any unclear or ambiguous points, use Google to search for the information. Upon receiving the search results, summarize them and return the output. Please maintain the high energy throughout the conversation.

In addition to this, you can execute Python code. When given a piece of Python code, execute it and return the output in a way that's easy to understand. If there are any errors during execution, let the user know about it in a friendly manner.

Furthermore, you can use Wikipedia to fetch summaries of different topics. When asked about a certain topic, search for it on Wikipedia, summarize the first result and return it in a way that's easy to understand. Use your charm to make the information fun and engaging.
"""

import openai
import ipywidgets as widgets
from IPython.display import display, clear_output
import os
import datetime
import json
from time import sleep
from googleapiclient.discovery import build
import requests
import wikipedia
import wolframalpha
GOOGLE_API_KEY = "あなたのGOOGLE_API_KEY"
CUSTOM_SEARCH_ENGINE_ID = "あなたのCUSTOM_SEARCH_ENGINE_ID"
openai.api_key = "あなたのOpen AI APIキー"
WOLFRAMALPHA_APP_ID = "あなたのWolfram Alphaのapp_idを入力します。"


#wikiを日本語に限定したいときは♯をはずす
#wikipedia.set_lang("jp")

def search_wikipedia(query):
    try:
        # Wikipediaで検索し、最初の結果の要約を返します
        return {"summary": wikipedia.summary(query)}
    except wikipedia.exceptions.DisambiguationError as e:
        # 複数の可能性がある場合は、最初のものを選択します
        return {"summary": wikipedia.summary(e.options[0])}
    except Exception as e:
        return {"error": str(e)}

def ask_wolframalpha(question):
    client = wolframalpha.Client(WOLFRAMALPHA_APP_ID)
    res = client.query(question)

    if res.results:
        try:
            return next(res.results).text
        except StopIteration:
            return "Wolfram Alphaの結果はありません。"
    else:
        return "Wolfram Alphaで結果を見つけることができませんでした。"

def getSearchResponse(keyword):
    service = build("customsearch", "v1", developerKey=GOOGLE_API_KEY)

    page_limit = 1
    start_index = 1
    response = []

    for n_page in range(0, page_limit):
        try:
            sleep(1)  # Ensure not to violate Google's rate limits
            result = service.cse().list(
                q=keyword,
                cx=CUSTOM_SEARCH_ENGINE_ID,
                lr='lang_ja',
                num=6, #日本のサイトで先頭から6個のサイトを検索している
                start=start_index
            ).execute()
            response.append(result)
            start_index = result.get("queries").get("nextPage")[0].get("startIndex")
        except Exception as e:
            print(e)
            break

    return response

def search_google(query):
    response = getSearchResponse(query)
    results = []

    # Extract information from each search result
    if response:
        items = response[0].get('items', [])
        for item in items:
            result = {
                "title": item.get('title'),
                "link": item.get('link'),
                "snippet": item.get('snippet')
            }
            results.append(result)

    # Return the results as a list of dictionaries
    return {"results": results}


def process_request(function_call):
    function_name = function_call['name']
    arguments = json.loads(function_call['arguments'])
    if function_name == 'search_google':
        query = arguments['query']
        return search_google(query)
    else:
        return {"error": "不明な関数の呼び出し"}

def handle_response(response):
    assistant_message = response['choices'][0]['message']
    result = process_request(assistant_message)
    print(result)

def process_function_call(function_call):
    function_name = function_call['name']
    arguments = json.loads(function_call['arguments'])

    if function_name == 'search_google':
        query = arguments['query']
        return search_google(query)
    elif function_name == 'ask_wolframalpha':
        question = arguments['question']
        return {"answer": ask_wolframalpha(question)}  # APIの結果を "answer" キーとして辞書に包む
    elif function_name == 'execute_python_code':
      code = arguments['code']
      return {"result": execute_python_code(code)}
    elif function_name == 'search_wikipedia':
      query = arguments['query']
      return search_wikipedia(query)      
    else:
      return {"error": "不明な関数の呼び出し"}

def convert_function_result_to_natural_language(function_result):
    if "results" in function_result:  # Google 検索の結果
        results = function_result.get("results")
        if not results:
            return "Google検索の結果はありませんでした。"

        # Format the search results for output
        output = "Google検索の結果:\n"
        for i, result in enumerate(results, start=1):
            output += f"{i}. {result['title']} ({result['link']})\n"
            output += f"   {result['snippet']}\n"

        return output

    elif "answer" in function_result:  # Wolfram Alphaの結果
        return f"Wolfram Alphaの回答: {function_result['answer']}"

    elif "result" in function_result:  # Pythonコードの実行結果
        return f"Pythonコードの実行結果: {function_result['result']}"
    elif "summary" in function_result:  # Wikipediaの結果
        return f"Wikipediaの要約: {function_result['summary']}"
    else:
        return "結果を解釈することができませんでした。"



def execute_python_code(code):
    local_vars = {}
    try:
        # Append a line to assign the result of the last line to a variable
        code_to_execute = code + '\nresult = _'
        exec(code_to_execute, globals(), local_vars)
        return local_vars.get("result")
    except Exception as e:
        return str(e)

AI_input = aimput

functions = [
    {
        "name": "search_google",
        "description": "Search a query on Google and return the first result",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "The search query",
                }
            },
            "required": ["query"]
        }
    }
]
functions.append({
    "name": "ask_wolframalpha",
    "description": "Uses the Wolfram Alpha API to perform a query and returns the result. This API provides expert-level answers using Wolfram's algorithms, knowledgebase, and AI technology. It can handle a variety of questions including mathematical calculations, distances between cities, current date and time, etc.",
    "parameters": {
        "type": "object",
        "properties": {
            "question": {
                "type": "string",
                "description": "The question or query to ask Wolfram Alpha",
            }
        },
        "required": ["question"]
    }
})

functions.append({
    "name": "execute_python_code",
    "description": "Executes the given Python code and returns the result",
    "parameters": {
        "type": "object",
        "properties": {
            "code": {
                "type": "string",
                "description": "The Python code to execute",
            }
        },
        "required": ["code"]
    }
})

# 変数定義
messages = [{"role": "system", "content": f"{AI_input}"}]
max_messages = 8 
#max_messages = 8 は8往復分の記憶をもてます


def chat(button):
    user_input = text.value
    messages.append({"role": "user", "content": user_input})

    # APIにユーザーからの入力を送信し、AIからの応答を取得する
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",#ここを♯して、下の♯を消せばメインチャットがGPT-4になります
        #model="gpt-4-0613",
        messages=messages,
        functions=functions,
        function_call="auto",
    )
    ai_response = response['choices'][0]['message']['content']
    ai_message = response['choices'][0]['message']






    if 'function_call' in ai_message:
        function_call = ai_message['function_call']
        function_result = process_function_call(function_call)

        # Convert the function result to a natural language format that the assistant can understand
        natural_language_result = convert_function_result_to_natural_language(function_result)

        # Here we create a new message dictionary and add it to the messages list
        new_message = {"role": "assistant", "content": natural_language_result}
        
        print(new_message) #ここを#すると途中過程の表示をけせます
        messages1 =[{"role": "system", "content": f"{AI_input}"}]
        messages1.append({"role": "user", "content": user_input})

        messages1.append(new_message)

        response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo-0613",#ここを♯して、下の♯を消せば途中過程の要約がGPT-4になります
            #model="gpt-4-0613",
            messages=messages1,
        )

        ai_response = response['choices'][0]['message']['content']
        ai_message = response['choices'][0]['message']

        # Add a new assistant message with the converted function result
        messages.append({"role": "assistant", "content": ai_message['content']})



    else:
      messages.append({"role": "assistant", "content": ai_message['content']})

    # messagesリストの中身がmax_messagesを超えた場合、最初のメッセージを削除する
    while len(messages) > max_messages * 2:
        messages.pop(1)
        messages.pop(1)
    # メッセージを出力エリアに表示
    with output:
        clear_output()
        for message in messages:
          if message['role'] != "system":
            if isinstance(message['content'], dict):
              print(f"{message['role'].title()}: {message['content'].get('content', '')}")
              if 'function_call' in message['content']:
                print(f"{message['role'].title()}: {message['content']['function_call']}")
            else:
              print(f"{message['role'].title()}: {message['content']}")

    text.value = ""

# リフレッシュボタンのクリック時の動作
def on_refresh_button_click(button):
    text.value = ""

# 終了ボタンのクリック時の動作
def on_exit_button_click(button):
    global messages
    messages = [{"role": "system", "content": f"{AI_input}"}]
    with output:
        clear_output()

# GUI要素定義
text = widgets.Textarea(placeholder='User:')
button = widgets.Button(description='Send')
refresh_button = widgets.Button(description="Refresh")
refresh_button.on_click(on_refresh_button_click)
exit_button = widgets.Button(description="Exit")
exit_button.on_click(on_exit_button_click)
output = widgets.Output()
display(text, button, refresh_button, exit_button, output)

button.on_click(chat)

いいなと思ったら応援しよう!