ゼロから始めるAIシステム開発 #08 「AIエージェント」

## 便利な自律型AIエージェントを作ってみよう
**Amazon Bedrock 生成AIアプリ開発入門 ［AWS深掘りガイド］**
https://amzn.asia/d/co7MB5S

今回は入門書の5章、AIエージェント…でしたが、結論から言うと道半ばで挫折しました。**LangChain**を使うタイプは実装できたけど**Agents for Amazon Bedrock**で**Pinecone**（サードパーティーのベクトルDB）を使ったナレッジベースを作るときのエラーが解決できなかった。これまでもエラーは毎回あって、そのたびLLMに聞いたりして解決してきたんだけど今回はリタイア。入門書では一番ページ数を割いてるところなのでなんとかやりたかったですが…。一応LangChainでの実装はできて骨組みは理解し、入門書は順番にやらなくてもいい構成になっているので6章に行きます。

以下、5章でできた部分までまとめます。
## AIエージェントとは
ユーザーの要求を達成するためにAI自身が行動を選択して実行する機構のこと。例えば
- ChatGPTでは**ChatGPT Plugins**という機能が追加されたことによってWeb検索など外部サービスとの連携や特定の分野に特化した回答の生成が可能になった
- **Advanced Data Analysis**は必要なら自動でプログラムのコードを用意して実行結果を出力する
- **Function Calling**は事前に用意された関数の中から適切なものを自ら判断して採用する
## ReAct
自身の行動と理由を推論すること（Reasoning）とそれに基づいて行動すること（Acting）を組み合わせて対応する手段のこと。これらを繰り返して（Observation）最終回答を生成する。
ReActについて紹介した論文では、標準、Reasoningのみ、Actingのみ、ReActの4種類のプロンプトを比較した結果、多くのタスクでReActが他を上回る性能を示した。このことからReActを活用することがより高度なAIエージェントを実現するために重要と言える。

スキルの習得や作業の実行など、従来は人に頼らざるを得なかったタスクを人工知能に任せられるようになった。またAIは人間が気づきにくいパターンや関係性を見つけることができるため人間の能力を大幅に補完できるので、AIエージェントのユースケースは多岐に渡る。

## LangChainでAIエージェントを実装
```python:1_langchain-agent.py
import nest_asyncio
import streamlit as st
from bs4 import BeautifulSoup
from langchain import hub
from langchain.agents import AgentExecutor, Tool, create_xml_agent
from langchain_aws import ChatBedrock
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.tools import DuckDuckGoSearchRun
from langchain_core.messages import HumanMessage, SystemMessage

nest_asyncio.apply()

# Web ページの内容を読み込む関数
def web_page_reader(url: str) -> str:
    loader = WebBaseLoader(url)
    content = loader.load()[0].page_content
    return content

# 検索ツールと Web ページ読み込みツールの設定
search = DuckDuckGoSearchRun()
tools = [
    Tool(
        name="duckduckgo-search",
        func=search.run,
        description="このツールはユーザーから検索キーワードを受け取り、Web上の最新情報を検索します。",
    ),
    Tool(
        name="WebBaseLoader",
        func=web_page_reader,
        description="このツールはユーザーからURLを渡された場合に内容をテキストを返却します。URLの文字列のみを受け付けます。",
    ),
]

# チャットモデルの設定
chat = ChatBedrock(
    model_id="anthropic.claude-3-sonnet-20240229-v1:0",
    model_kwargs={"max_tokens": 1500},
)

# エージェントの設定
agent = create_xml_agent(chat, tools, prompt=hub.pull("hwchase17/xml-agent-convo"))

agent_executor = AgentExecutor(
    agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
)

# Streamlit アプリケーションの設定
st.title("Bedrock Agent チャット")
messages = [SystemMessage(content="あなたは質問に対して必ず日本語で回答します。")]

# ユーザー入力の処理
prompt = st.chat_input("何でも聞いてください。")
if prompt:
    messages.append(HumanMessage(content=prompt))
    with st.chat_message("user"):
        st.markdown(prompt)
    with st.chat_message("assistant"):
        # エージェントを呼び出す
        result = agent_executor.invoke({"input": prompt})
        st.write(result["output"])
```
- Web_page_reader関数（Web読み込みツール・URLを受け取りWebの内容をテキストで返す）を定義
- 検索ツール（DuckDuckGoSearchRun）とWeb読み込みツールを設定
- ChatBedrockモジュールを設定
- create_xml_agent関数を使用してエージェントを生成
- ユーザーからの入力を取得しprompt関数に格納
- エージェント呼び出し、ユーザー入力への応答を生成し、それを表示

普通に質問した場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885923/82628c56-63b7-18c2-bf22-07cf1e0b667c.png)
検索ツールduckduckgo-searchが選択されて実行されている

URLを入力した場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885923/05f51009-41fb-aade-ba32-558b73c269f5.png)
Web読み込みツールWebBaseLorderが選択されて実行されている
## ReActエージェント
次はLangChainのReActAgentモジュールを使ってみる。
```python:2_langchain-react-agent.py
import nest_asyncio
import streamlit as st
from bs4 import BeautifulSoup
from langchain import hub
from langchain.agents import AgentExecutor, Tool, create_react_agent
from langchain_aws import ChatBedrock
from langchain_community.tools import DuckDuckGoSearchRun
from langchain_core.messages import HumanMessage, SystemMessage

nest_asyncio.apply()

search = DuckDuckGoSearchRun()
tools = [
    Tool(
        name="duckduckgo-search",
        func=search.run,
        description="このツールはユーザーから検索キーワードを受け取り、Web上の最新情報を検索します。",
    )
]

# チャットモデルの設定
chat = ChatBedrock(
    model_id="anthropic.claude-3-sonnet-20240229-v1:0",
    model_kwargs={"max_tokens": 1500},
)

# エージェントの設定
agent = create_react_agent(chat, tools, prompt=hub.pull("hwchase17/react"))
agent_executor = AgentExecutor(
    agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
)

# Streamlit アプリケーションの設定
st.title("Bedrock ReAct Agent チャット")
messages = [SystemMessage(content="あなたは質問に対して必ず日本語で回答します。")]

# ユーザー入力の処理
prompt = st.chat_input("何でも聞いてください。")
if prompt:
    messages.append(HumanMessage(content=prompt))
    with st.chat_message("user"):
        st.markdown(prompt)
    with st.chat_message("assistant"):
        # エージェントを呼び出す
        result = agent_executor.invoke({"input": prompt})
        st.write(result["output"])
```
- DuckDuckGoでWeb検索ツールを作成
- ChatBedrockでClaude3 Sonnetのモデルを設定
- create_react_agent関数を使用してReActエージェントを生成
- ユーザー入力処理は先ほどと同じ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885923/60be17d3-07cf-804f-924c-1b2273b177b5.png)
Steamlitのほうでは最終結果のみが表示されているが、以下のターミナルを確認するとQuestion→Thought→Action→Thought→Final AnswerとAIが推論から行動までを自己判断して回答を生成していることが分かる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885923/c37ab529-1380-7699-7373-b59b81f38c2c.png)
## まとめ
とりあえず概要は理解。実装となるとLangChain型でもいいのか、Agentsなどを使うべきなのかは機能を把握した上でユースケースによって判断することになりそう。次回は6章、7章のBedrockの他の機能、AWSサービスとの連携についてやる予定。