Azure OpenAI Service の function calling を使用してReAct document storeを実装してみる

2023年7月に追加されたAzure OpenAI Serviceの関数呼び出しを使用して、今までプロンプトを駆使して作られていたReAct document storeと同じようなことをプログラムしてみます。

LangChainのReAct document storeプロンプトなどは過去記事を参照してください。

https://qiita.com/kekekekenta/items/87ed85f3e9d21d308250

# 関数呼び出しについて
gpt-35-turbo と gpt-4 の最新バージョンのモデルでは、関数を操作するように調整されているようで、APIにより関数を呼び出すタイミングとどの関数を使用するか判断してくれます。詳しくは以下のドキュメントをご確認ください。

OpenAIの関数呼び出しについて

https://platform.openai.com/docs/guides/gpt/function-calling

Azure OpenAI Serviceで関数呼び出しを使用する方法

https://learn.microsoft.com/ja-jp/azure/ai-services/openai/how-to/function-calling

# 使用するモデルについて
Azure OpenAI Serviceを使用している人は、gpt-35-turbo、gpt-35-turbo-16k、gpt-4、gpt-4-32k のバージョン 0613をデプロイで設定しておいてください。
OpenAIは、gpt-3.5-turbo-0613、gpt-4-0613と書かれていました。

# プログラムを書いてみる
私は環境変数をdotenvを使用して読み込んでいます。私はAzure OpenAI Serviceを使用していますが、OpenAIを使用している人は読み替えてください。

使用するPythonのパッケージをインストールします。

```sh
pip install python-dotenv openai wikipedia
```

環境変数を.envファイルで設定しておきます。

```sh:.env
OPENAI_API_TYPE="azure"
OPENAI_API_BASE="https://<あなたの環境>.openai.azure.com/"
OPENAI_API_KEY="<あなたのAPIキー>"
DEPLOYMENT_NAME="<あなたのデプロイ名>"
```

プログラムを書いていきます。まずは、環境変数を読み込みます。

```python
from dotenv import load_dotenv
load_dotenv()
```

環境変数をopenaiに設定します。

```python
import openai
import json
import os
import wikipedia

deployment_name = os.getenv("DEPLOYMENT_NAME")
openai.api_type = os.getenv("OPENAI_API_TYPE")
openai.api_key = os.getenv("OPENAI_API_KEY")
openai.api_base = os.getenv("OPENAI_API_BASE")
openai.api_version = "2023-07-01-preview"
```

関数呼び出しで使用する関数を作成します。今回は、wikipediaを検索する関数と、APIが回答を出したときに使用する関数の2つを作成しました。

```python
def search_wikipedia(arguments: str):
        try:
            arg_dict = json.loads(arguments)
            words = arg_dict["words"]
            page_content = wikipedia.page(words).content
            result = page_content
        except wikipedia.PageError:
            result = f"Could not find \"{words}\" page.\n similar_page = {wikipedia.search(words)}"
        except wikipedia.DisambiguationError:
            result = f"Could not find [{words}].\n similar_page = {wikipedia.search(words)}"
        return result

def answer(arguments: str):
    arg_dict = json.loads(arguments)
    return arg_dict["content"]
```

APIに渡す関数の定義を作成します。descriptionの書き方によって動作が異なってきますので、期待した動作にならない場合は、descriptionを調整してください。

```python
functions = [
        {
            "name": "wikipedia",
            "description": "Get wikipedia search results",
            "parameters": {
                "type": "object",
                "properties": {
                    "words": {
                        "type": "string",
                        "description": "The Wikipedia search words. Search words are used to retrieve information from Wikipedia.",
                    }
                },
                "required": ["words"],
            },
        },
        {
            "name": "answer",
            "description": "Displays the answer to the question.",
            "parameters": {
                "type": "object",
                "properties": {
                    "content": {
                        "type": "string",
                        "description": "The answer. Displays the answer to the question. The answer is not uncertain.",
                    }
                },
                "required": ["content"],
            },
        },
    ]

available_functions = {
            "wikipedia": search_wikipedia,
            "answer": answer
        } 
```

APIとの会話のループを行う関数を作成します。あと、関数を確認するコードは削除していますので安全に動作させる場合は、確認のコードを追加してください。

```python
def run_conversation(messages, functions, available_functions, deployment_id):
    max_loops = 10
    function_call_flag = False
    for i in range(max_loops):
        response = openai.ChatCompletion.create(
            deployment_id=deployment_id,
            messages=messages,
            functions=functions,
            function_call="auto" if function_call_flag else "none", 
            temperature=0
        )
        response_message = response["choices"][0]["message"]
        response_reason = response["choices"][0]["finish_reason"]
        function_role = response_message["role"]

        if response_reason == 'function_call':
            function_name = response_message["function_call"]["name"]
            function_arguments = response_message["function_call"]["arguments"]
            if function_name not in available_functions:
                return f"Function {function_name} does not exist."
            function_to_call = available_functions[function_name]
            function_response = function_to_call(function_arguments)
            messages.append(
                {
                    "role": function_role,
                    "name": function_name,
                    "content": function_arguments,
                }
            )
            messages.append(
                {
                    "role": "function",
                    "name": function_name,
                    "content": function_response,
                }
            )
            function_call_flag = False
        else:
            messages.append(
                {
                    "role": function_role,
                    "content": response_message["content"],
                }
            )
            function_call_flag = True

        finish_flag = False
        for message in messages:
            # 回答が出たら終了にする
            if message['role'] == 'function' and message['name'] == 'answer':
                finish_flag = True
            print(message)
        print("-------------------------------------")
        if finish_flag:
            break
    return response
```

全て準備が整ったので、質問を送ってみます。

```python
messages = [
    {
        "role": "system",
        "content": "You are interacting with GPT-3.5, a large language model trained by OpenAI. Step-by-step guide you step-by-step as you search Wikipedia for answers to questions."
    }
]
messages.append({"role": "user", "content": "Author David Chanoff has collaborated with a U.S. Navy admiral who served as the ambassador to the United Kingdom under which President?"})

assistant_response = run_conversation(messages, functions, available_functions, deployment_name)
print(f"Final Response: {assistant_response['choices'][0]['message']}")
```

# 結果
良い結果がでましたね。

```
{'role': 'system', 'content': 'You are interacting with GPT-3.5, a large language model trained by OpenAI. Step-by-step guide you step-by-step as you search Wikipedia for answers to questions.'}
{'role': 'user', 'content': 'Author David Chanoff has collaborated with a U.S. Navy admiral who served as the ambassador to the United Kingdom under which President?'}
{'role': 'assistant', 'content': 'To find the answer to your question, I will search for information about David Chanoff and his collaboration with a U.S. Navy admiral who served as the ambassador to the United Kingdom.'}
{'role': 'assistant', 'name': 'wikipedia', 'content': '{\n  "words": "David Chanoff"\n}'}
{'role': 'function', 'name': 'wikipedia', 'content': "David Chanoff is a noted author of non-fiction work. His work has typically involved collaborations with the principal protagonist of the work concerned. His collaborators have included; Augustus A. White, Joycelyn Elders, Đoàn Văn Toại, William J. Crowe, Ariel Sharon, Kenneth Good and Felix Zandman. He has also written about a wide range of subjects including literary history, education and foreign for The Washington Post, The New Republic and The New York Times Magazine. He has published more than twelve books.\n\n\n== Selected publications ==\nDavid Chanoff and Doan Van Toai (1986) Vietnam: A Portrait of Its People at War, I.B. Tauris Publishers\nSharon, Ariel and David Chanoff (1989) Warrior : the autobiography of Ariel Sharon; New York : Simon and Schuster\nGood, Kenneth and David Chanoff (1992) Into the heart : one man's pursuit of love and knowledge among the Yanomami, Ulverscroft\nCrowe, William J and David Chanoff (1993) The line of fire : from Washington to the Gulf, the politics and battles of the new military, Simon & Schuster\nElders, M Joycelyn and David Chanoff (1996) Joycelyn Elders, M.D. : from sharecropper's daughter to surgeon general of the United States of America, Morrow\nWhite, Augustus A. and David Chanoff (2011) Seeing Patients: Unconscious Bias in Health Care, Harvard University Press\nZadman, Felix and David Chanoff (1995) Never the last journey: a Fortune 500 founder's life story from Holocaust survivor to victor on Wall Street, Shocken.\n\n\n== References =="}
{'role': 'assistant', 'content': 'According to the information I found, David Chanoff has collaborated with various individuals for his non-fiction work. One of his collaborators is William J. Crowe, a U.S. Navy admiral who served as the ambassador to the United Kingdom. However, it does not specify under which president Crowe served as the ambassador.'}
{'role': 'assistant', 'name': 'wikipedia', 'content': '{\n  "words": "William J. Crowe"\n}'}
{'role': 'function', 'name': 'wikipedia', 'content': 'William James Crowe Jr. (January 2, 1925 – October 18, 2007) was a United States Navy admiral and diplomat who served as the 11th chairman of the Joint Chiefs of Staff under Presidents Ronald Reagan and George H. W. Bush, and as the ambassador to the United Kingdom and Chair of the Intelligence Oversight Board under President Bill Clinton.\n\n\n== Early life and education ==\nCrowe was born in La Grange, Kentucky on January 2, 1925. At the beginning of the Great Depression, Crowe\'s father moved the family to Oklahoma City. In June 1946, Crowe completed a war-accelerated course of study and graduated with the Class of 1947 from the United States Naval Academy in Annapolis, Maryland.\n\n\n== Career ==\nFrom 1954 to 1955, Crowe served as assistant to the naval aide of President Dwight D. Eisenhower. From 1956 to 1958, Crowe served as executive officer of the submarine USS Wahoo (SS-565). \nIn 1958, he served as an aide to the Deputy Chief of Naval Operations. In 1960, Crowe took command of USS Trout (SS-566), homeported in Charleston, South Carolina, and served as commanding officer of that ship until 1962. From there, Crowe earned a master\'s degree in education at the Stanford Graduate School of Education. After turning down an invitation from Admiral Hyman G. Rickover to enter the Navy\'s nuclear power program, Crowe earned a Ph.D. in politics from Princeton University in 1965 after completing a doctoral dissertation titled "The policy roots of the modern Royal Navy 1946-1963."During the Vietnam War he was the senior adviser to the Vietnamese Riverine Force. In 1969, he took command of Submarine Division 31, homeported in San Diego, California.A long string of assignments followed:\n\n1967 – Head of East Asia Pacific Branch, Politico-Military Division, Office of the Chief of Naval Operations\n1970 – Senior adviser to the Vietnamese Navy Riverine Force\n1973 – promoted to rear admiral and named Deputy Director, Strategic Plans, Policy, Nuclear Systems, and NSC Affairs Division, Office of the Chief of Naval Operations\n1975 – Director, East Asia and Pacific Region, Office of the Secretary of Defense\n1976 – Commander, Middle East Force (COMMIDEASTFOR)\n1977 – promoted to vice admiral and named Deputy Chief of Naval Operations, Plans, Policy and Operations\n1980 – promoted to admiral and named Commander-in-Chief, Allied Forces Southern Europe (CINCSOUTH)\n1983 – as CINCSOUTH, named Commander-in-Chief, United States Naval Forces Europe (CINCUSNAVEUR)\n1983 – Commander-in-Chief, United States Pacific Command (CINCPAC)On July 10, 1985, Crowe was appointed by President Ronald Reagan to serve as Chairman of the Joint Chiefs of Staff (CJCS). He continued to serve as CJCS through the Bush administration until 1989, when he retired from active duty. He was the first Chairman of the Joint Chiefs of Staff to serve under the provisions of the Goldwater-Nichols Department of Defense Reorganization Act of 1986, where he as chairman became (not the collegial body of the Joint Chiefs of Staff), by statute, the principal military adviser to the president, the National Security Council, and the Secretary of Defense. In 1989, Army General Colin L. Powell succeeded him as CJCS.\n\n\n== Later life and death ==\nAfter he retired in October 1989, Crowe returned to the University of Oklahoma and William J. Crowe chair in geopolitics. Crowe surprised politicians when he endorsed Bill Clinton in the presidential election of 1992. President Clinton named Crowe chairman of the President\'s Foreign Intelligence Advisory Board in 1993. In 1994, Clinton appointed Crowe the United States Ambassador to the United Kingdom, and he served in that capacity until 1997.\nCrowe sat on the boards of Texaco, Merrill Lynch, Pfizer, Norfolk Southern Corporation, and General Dynamics. He also served on the board of Emergent BioSolutions (then Bioport), a company that provided controversial anthrax vaccinations to the U.S. military in the 1990s. The deal was approved by the Clinton administration, with which Crowe had a previous relationship. At the time of his death, Crowe served as the chairman of the board of Global Options, Inc., an international risk-management and business solutions company headquartered in Washington, D.C.\nAs he did at the University of Oklahoma in 1990–91, Crowe taught a seminar class on national security at the United States Naval Academy from 2000 to 2007.\nIn 2004, Crowe was among 27 retired diplomats and military commanders called Diplomats and Military Commanders for Change who publicly said the administration of President George W. Bush did not understand the world and was unable to handle "in either style or substance" the responsibilities of global leadership.On June 16, 2004 the former senior diplomats and military commanders issued a statement against the Iraq War.\n\n\n== Death ==\nCrowe died on October 18, 2007, at Bethesda Naval Hospital in Maryland at age 82 due to a heart condition.His funeral was held on October 31, 2007, at the Naval Academy chapel; Bill Clinton spoke. Crowe was buried later that day in the United States Naval Academy Cemetery.As of 2016, he is one of only two deceased former Chairman of the Joint Chiefs to not be buried at Arlington National Cemetery. His predecessor, John William Vessey Jr. died in 2016 and was buried in Minnesota State Veterans Cemetery, Little Falls, Minnesota.\n\n\n== Legacy ==\nIn 2008, a fellowship was established in Crowe\'s honor at the University of Kentucky\'s Patterson School of Diplomacy and International Commerce to support a former member of the U.S. armed forces who – like Crowe – is shifting from military to diplomatic service.\nIn 2009, the International Programs Center at the University of Oklahoma established the Admiral William J. Crowe Jr. Award.  This award is presented to an outstanding International and Area Studies (IAS) graduate every spring semester. The award recognizes an IAS student who has demonstrated high academic achievement, a commitment to public service, and a desire to pursue a career in global affairs.\nAlso in 2009, the Xbox/ PS2 game, Heroes of the Pacific, was released. The main character\'s name is also William Crowe, though whether or not this was inspired by the real-life Crowe is unknown.\n\n\n== Personal life ==\nCrowe married to Shirley Grennell in 1954. They had three children.\n\n\n== Dates of rank ==\nApprentice Seaman, United States Naval Reserve: December 4, 1942\nMidshipman, United States Naval Academy: June 23, 1943At the time of Admiral Crowe\'s promotion, all rear admirals wore two stars, but the rank was divided into an "upper" and "lower half" for pay purposes\n\n\n== Awards and recognition ==\nCrowe was awarded Doctor of Laws (LL.D.) honorary degrees from numerous universities, including University of Liverpool, The George Washington University, and Knox College.\nIn 1986, he received the Golden Plate Award of the American Academy of Achievement presented by Awards Council member and Supreme Allied Commander Europe, General Bernard W. Rogers, USA.In 1989, Crowe appeared in an episode of the television sitcom Cheers (Season 7, Episode 17 "Hot Rocks"), where he played himself, and was accused of stealing the General Managers (Kirstie Alley) diamond earrings.On 1990, he was the first recipient of the Distinguished Sea Service Award of Naval Order of the United States.\nIn 1993, Crowe published his memoirs in the book The Line of Fire: From Washington to the Gulf, the Politics and Battles of the New Military.\nCrowe received four Defense Distinguished Service Medals and numerous military decorations from heads of state. In 1998, the American Atatürk Association honored Crowe with the "Atatürk Peace and Democracy Award". Following his retirement from the Navy, he was awarded a 2000 Presidential Medal of Freedom, the United States\' highest civilian honor.\n\n\n=== Awards and decorations ===\nBadges\n Submarine Warfare Insignia\n Office of the Joint Chiefs of Staff Identification Badge\n\n\n== References ==\n\n\n== External links ==\n\n"Admiral William Crowe". The Times. October 23, 2007. Archived from the original on May 24, 2011.\n"Biographies in Naval History: Admiral William James Crowe, USN". Naval Historical Center. October 18, 2007. Archived from the original on October 29, 2007.\n"Senate statement on Crowe\'s tenure as CJCS". Congress.gov. Archived from the original on January 8, 2016. Retrieved August 5, 2006.\n"President Clinton Nominates William Crowe Ambassador to the United Kingdom". White House Press Office. March 22, 1994.\nPeniston, Bradley (2006). No Higher Honor: Saving the USS Samuel B. Roberts in the Persian Gulf. Annapolis: Naval Institute Press. ISBN 978-1-59114-661-2. Archived from the original on October 15, 2007. Foreword by Adm. (ret.) William J. Crowe.\n"Selected Works of Admiral William J. Crowe, Jr., USN" (PDF). Washington, DC: Joint History Office, Office of the Chairman of the Joint Chiefs of Staff. 2013.\n"William J. Crowe Papers" (PDF). The Library of Congress.\nAppearances on C-SPAN'}
{'role': 'assistant', 'content': 'According to the information I found, William J. Crowe served as the ambassador to the United Kingdom under President Bill Clinton.'}
{'role': 'assistant', 'name': 'answer', 'content': '{\n  "content": "William J. Crowe served as the ambassador to the United Kingdom under President Bill Clinton."\n}'}
{'role': 'function', 'name': 'answer', 'content': 'William J. Crowe served as the ambassador to the United Kingdom under President Bill Clinton.'}
-------------------------------------
Final Response: {
  "role": "assistant",
  "function_call": {
    "name": "answer",
    "arguments": "{\n  \"content\": \"William J. Crowe served as the ambassador to the United Kingdom under President Bill Clinton.\"\n}"
  }
}
```

# さいごに
今まではReActパターンのプロンプトを駆使して論理的思考を再現していましたが、関数呼び出しによりシンプルになりましたね。いやー面白い。