ReactじゃなくてReActでReactのwebサイトをつくってみた

## React と ReAct
※この記事はAWSを使ってAIエージェントをつくる記事です

### React
Reactは言わずと知れたWebサービスやアプリのUIを構築するのに便利な JavaScript のライブラリです。

https://ja.react.dev/

### ReAct
ReAct とは **REasoning and ACTing**  の略称で「推論と行動」を同時に行うLLM(大規模言語モデル)のプロンプト手法です。人間が意思決定をするときに「推論」と「行動」をすることイメージしています。
  
**ん？何か特殊なの？**
  
と思うかもしれませんが、**CoT(Chain of Thought)** という手法と比較してみます。


**👨‍🏫質問**

:::note alert
リンゴを1日2個ずつ食べています。現在のリンゴの在庫が何個あるか確認が必要ですが、わかりません。在庫がなくなるまで何日かかりますか？
:::

**💡CoT**
各ステップを順序立てて考え、段階ごとに説明していきます。

:::note 
- 思考の流れ：
1. リンゴの在庫が「何個か」はわからないが、1日に2個食べている。
2. 仮に在庫が「10個」だとすると、5日で食べ終わる。
よって、在庫が何個か次第で「何日かかるか」は変わる。
- 答え：
「在庫のリンゴの数がわからないので、具体的な日数は不明ですが、在庫が10個なら5日で食べ終わるはずです。」
:::

**💡ReAct**
必要な情報が不足している場合には、アクションをとって追加情報を取得し、再び思考を進めます。

:::note 
- 思考と行動の流れ：
1. 在庫のリンゴの数がわからないため、必要な情報を得るために「倉庫に問い合わせ」。
2. 倉庫からの回答：「リンゴは6個あります」。
3. 1日に2個ずつ食べるので、6 ÷ 2 = 3日で食べ終わる。
- 答え：
「残り3日で在庫のリンゴを食べ終わることがわかりました。」
:::

この例からわかるようにReActでは必要に応じて外部の情報源にアクセスして情報を取りに行って推論に使います。

## ReActでReactサイトをつくる
**入力したキーワードを詳しく調べ、その特徴を踏襲し、かつ React を用いたWebページをつくるAIエージェント**をつくります。
右側が実際のアプリケーションで、左側には裏側の処理を表示しています。行動として「喫茶店」について特徴を検索している様子がわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/986141/99ebde32-6b19-19e9-2613-57ee0699f927.png)


ReActで重要なのは外部の情報へのアクセスです。これを構築することでよりAIの回答の質を上げることが期待できます。**LangChain** の **tools** というユーティリティを使います。

こちらのURLから検索手段を選びます。**Google** や **Brave** などがありますが、無料でかつAPIキーの取得も必要ない **DuckDuckGo** を使います。

https://python.langchain.com/docs/integrations/tools/

### 大枠の理解
**Cloud 9** 上で開発していきます。Webアプリケーションのフレームワークは **Streamlit** です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/986141/37ac6216-af12-89fd-0113-8034409fa395.png)

以下の手順で進めます。

:::note
1. Cloud 9 と VPC の準備
1. Bedrock で claude を有効化
1. エージェントの作成( Streamlit、LangChain )
:::
※**Cloud 9**や**VPC**の準備に関しては参考になる記事を末尾に紹介します。

#### Cloud 9: オンラインのIDE(総合開発環境)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/986141/b16f8f5c-c15e-e954-1212-9ec38ec3557b.png)

※新規利用不可の話題も...

https://qiita.com/minorun365/items/f5289163795d5d7b21e2


#### VPC: 仮想のネットワーク空間を構築
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/986141/0eca2064-9a8b-0fe0-444c-3afbc79b2703.png)


### Amazon Bedrock
**Bedrock** はAWSが提供している生成AIサービスで、様々な会社が提供するモデルをサーバーレスで利用することができます。

https://aws.amazon.com/jp/bedrock/

#### Claude 3.5 Sonnetを有効にする
AWSのマネジメントコンソールに入り、サービスから「**Amazon Bedrock**」を選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/986141/71a651d2-fc30-e0c9-6049-fa105735e18d.png)

サイドバーの「**Bedrock configuration**」にある「**モデルアクセス**」を選択します。
すると、**Jamba** や **Llama** といった他社も含めたモデルが表示されます。
![スクリーンショット 2024-09-02 213655.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/986141/d7239df5-55ba-2c5d-8d72-b59d66a16894.png)
「**リクエスト可能**」と書かれたところからサイトの指示に従って**Cloud 3.5 Sonnet**の有効化リクエストをします。「**アクセスが付与されました**」と表示されれば準備完了です。

### エージェントの作成
Cloud 9はVSCなどのエディターと同じ操作感です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/986141/62f3f36a-f42c-0e07-3bc7-29ef262a1cce.png)
`ai-agent-react.py`という名前のファイルをつくります。


ターミナルから以下のパッケージをインポートし、コードを書いていきます。
```bash
pip install streamlit langchain langchain-aws langchain-community langchainhub beautifulsoup4 nest_asyncio duckduckgo-search
```

<details><summary>pythonのコード全体はこちらです</summary>

```python
import streamlit as st
from langchain import hub # アップロードされた様々なプロンプトを使うことができる
from langchain.agents import AgentExecutor, Tool, create_react_agent
from langchain_community.tools import DuckDuckGoSearchRun
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_aws import ChatBedrock

import nest_asyncio #非同期処理をするライブラリ
from bs4 import BeautifulSoup

nest_asyncio.apply()

search = DuckDuckGoSearchRun()
tools = [
    Tool(
        name="duckduckgo",
        func=search.run,
        description="キーワードに基づいて、その特徴を調べ人々が訪れたくなるウェブページのHTMLとJavaScriptコードを生成する"
    )
]

chat_model = ChatBedrock(
    model_id="anthropic.claude-3-5-sonnet-20240620-v1:0",
    model_kwargs={"max_tokens": 3000}
)

prompt = hub.pull("hwchase17/react")

agent = create_react_agent(chat_model, tools, prompt)

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

# プロンプトメッセージ
messages = [
    SystemMessage(
        content="\
        あなたは{#役割}です。{#制約条件}を必ず守り、{#依頼}を実行して、{#形式}で結果を出力してください。\
        #役割\
        ウェブデザイナー\
        #制約条件\
        ライブラリとしてReact.jsを使う\
        #依頼\
        ユーザーから入力された情報からキーワードを取得し、その特徴を調べ、人々が訪れたくなるウェブページをつくってください。\
        #形式\
        HTMLとJavaScriptのソースコードとプロジェクトのファイル構成を含む手順。\
        "
    )
]
st.title("ReActでReact")
prompt = st.chat_input("作って欲しいサイトのキーワードを教えてください")
if prompt:
    messages.append(HumanMessage(content=prompt))
    with st.chat_message("user"):
        st.markdown(prompt)
    with st.chat_message("agent"):
        result = agent_executor.invoke({"input": messages})
        st.write(messages) # わかりやすさのために出力しています
        st.write(result["output"])

```
</details>

### コードの解説

まず初めにWeb検索を行うツールを作成していきます。`langchain.agents`の`Tool`クラスを使います。

```python
# 非同期処理
nest_asyncio.apply()

# Web検索を行うツールの作成
search = DuckDuckGoSearchRun()
tools = [
    Tool(
        name="duckduckgo",
        func=search.run,
        description="キーワードに基づいて、その特徴を調べ人々が訪れたくなるウェブページのHTMLとJavaScriptコードを生成する"
    )
]

# Claud 3.5 Sonnetをモデルとして指定
chat_model = ChatBedrock(
    model_id="anthropic.claude-3-5-sonnet-20240620-v1:0",
    model_kwargs={"max_tokens": 3000}
)

# ReActのプロンプトテクニックをLangChain Hubから取得(reactだけどReAct！)
prompt = hub.pull("hwchase17/react")
```

ここからはいよいよエージェントをつくっていきますが、`create_react_agent()` に必要なものを入れるだけでOKです。
メッセージタイプは **HumanMessage** にしています。初めは単一のメッセージでしたが、ReAct によって検索のみやってWebページをつくるのを忘れることがあるので、細かく指示をするようにしました。

```python
# ↑でつくったモデルとツールとプロンプトでエージェントをつくる
agent = create_react_agent(chat_model, tools, prompt)

# エージェントの実行に関する設定
agent_executor = AgentExecutor(
    agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
)

# 
messages = [
    SystemMessage(
        content="\
        あなたは{#役割}です。{#制約条件}を必ず守り、{#依頼}を実行して、{#形式}で結果を出力してください。\
        #役割\
        ウェブデザイナー\
        #制約条件\
        ライブラリとしてReactを使う\
        #依頼\
        ユーザーから入力された情報からキーワードを取得し、その特徴を調べ、人々が訪れたくなるウェブページをつくってください。\
        #形式\
        HTMLとJavaScriptのソースコードとプロジェクトのファイル構成を含む手順。\
        "
    )
]

# Streamlitでwebアプリを作成
st.title("ReActでReact")
prompt = st.chat_input("作って欲しいサイトのキーワードを教えてください")
if prompt:
    messages.append(HumanMessage(content=prompt))
    with st.chat_message("user"):
        st.markdown(prompt)
    with st.chat_message("agent"):
        result = agent_executor.invoke({"input": messages})
        st.write(messages)
        st.write(result["output"])

```

なお、Streamlitの実行はこのようにして、Cloud 9のプレビュー機能を使ってWebアプリを確認しました。
```bash
streamlit run ai-agent-react.py --server.port 8080
```

### 出来上がったもの

キーワードに関して調べてからwebページを作ってくれるAIエージェントができました。動画を見るとわかるのですが、ReActの特徴として、検索により回答に時間がかかっています。

<iframe width="560" height="315" src="https://www.youtube.com/embed/L-mb1umWysg?si=rm6DbAWYpZVARocu" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

キーワード: `喫茶店`

#### 行動:
```
Action Input: 喫茶店 特徴 魅力
純喫茶の歴史や特徴、そして喫茶店との違いを知ることで、 それぞれの魅力を再発見し、訪れる際の楽しみ方が広がるで しょう。 純粋にコーヒーを楽しみたい方も、昭和レトロな雰囲気を味わいたい方も、ぜひ最後までお読みください。 純喫茶の名店は若者や外国人観光客にも人気で、開店前から行列 ができ、時間制を導入する店もあるほど。. この純喫茶ブー ムの立役者が難波 ... カフェと喫茶店、似ているようで違う。どちらに行くべきか迷ったことはありませんか。雰囲気や 提供されるメニュー、利用方法など、それぞれの特徴や魅力 があります。この記事ではカフェの楽しみ方をご紹介します 。 カフェ文化や基礎知識が分かります! 純喫茶と喫茶店の違い. 現在は、純喫茶と喫茶店を同じ意味で用いることも多く 、明確な違いはありません。ただし、次の特徴を持つお店は 、純喫茶と呼ばれる傾向にあります。 こだわりのコーヒーを提供している 落ち着いた雰囲気のカウンターがある 喫茶店 の魅力の一つといえば、美味しいコーヒーとそのバリエーシ ョンです。 ... を確認したり、オンラインで予約をすることができます。これによって、利用客は喫茶店の特徴や雰囲気 を先に知ることができ、より効率的な喫茶店利用が可能とな りました喫茶店に関する情報が得られました。これをもとに 、魅力的なウェブページを作成していきます。React.jsを使 用して、喫茶店の特徴や魅力を表現したウェブページを作成 しましょう。
```

#### 推論:

<details><summary>長いので省略します。</summary>

プロジェクトのファイル構成:
```
coffee-shop-website/
├── public/
│   └── index.html
├── src/
│   ├── components/
│   │   ├── Header.js
│   │   ├── Footer.js
│   │   ├── CoffeeShopInfo.js
│   │   └── Gallery.js
│   ├── App.js
│   └── index.js
└── package.json
```

1. まず、React.jsプロジェクトを作成します:
```
npx create-react-app coffee-shop-website
cd coffee-shop-website
```

2. 必要なコンポーネントを作成します。

src/components/Header.js:
```javascript
import React from 'react';

const Header = () => (
  <header>
    <h1>静かな時間の喫茶店</h1>
    <nav>
      <ul>
        <li><a href="#about">特徴</a></li>
        <li><a href="#menu">メニュー</a></li>
        <li><a href="#gallery">ギャラリー</a></li>
      </ul>
    </nav>
  </header>
);

export default Header;
```

src/components/Footer.js:
```javascript
import React from 'react';

const Footer = () => (
  <footer>
    <p>&copy; 2023 静かな時間の喫茶店. All rights reserved.</p>
  </footer>
);

export default Footer;
```

src/components/CoffeeShopInfo.js:
```javascript
import React from 'react';

const CoffeeShopInfo = () => (
  <section id="about">
    <h2>喫茶店の魅力</h2>
    <ul>
      <li>こだわりのコーヒー</li>
      <li>落ち着いた雰囲気</li>
      <li>昭和レトロな内装</li>
      <li>ゆったりとした時間</li>
    </ul>
    <h2 id="menu">メニュー</h2>
    <ul>
      <li>ブレンドコーヒー</li>
      <li>カフェオレ</li>
      <li>エスプレッソ</li>
      <li>モーニングセット</li>
    </ul>
  </section>
);

export default CoffeeShopInfo;
```

src/components/Gallery.js:
```javascript
import React from 'react';

const Gallery = () => (
  <section id="gallery">
    <h2>ギャラリー</h2>
    <div className="image-container">
      {/* 実際の画像URLに置き換えてください */}
      <img src="https://example.com/coffee-shop-1.jpg" alt="店内の様子" />
      <img src="https://example.com/coffee-shop-2.jpg" alt="コーヒーカップ" />
      <img src="https://example.com/coffee-shop-3.jpg" alt="カウンター" />
    </div>
  </section>
);

export default Gallery;
```

3. App.jsを更新します:

src/App.js:
```javascript
import React from 'react';
import Header from './components/Header';
import CoffeeShopInfo from './components/CoffeeShopInfo';
import Gallery from './components/Gallery';
import Footer from './components/Footer';
import './App.css';

function App() {
  return (
    <div className="App">
      <Header />
      <main>
        <CoffeeShopInfo />
        <Gallery />
      </main>
      <Footer />
    </div>
  );
}

export default App;
```

4. スタイリングを追加します。src/App.cssを以下のように 更新します:

```css
body {
  font-family: 'Arial', sans-serif;
  line-height: 1.6;
  color: #333;
  background-color: #f4f4f4;
}

.App {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
}

header {
  background-color: #8B4513;
  color: #fff;
  padding: 1rem;
  text-align: center;
}

header h1 {
  margin-bottom: 1rem;
}

nav ul {
  list-style-type: none;
  padding: 0;
}

nav ul li {
  display: inline;
  margin-right: 20px;
}

nav ul li a {
  color: #fff;
  text-decoration: none;
}

main {
  background-color: #fff;
  padding: 2rem;
  margin-top: 2rem;
  box-shadow: 0 0 10px rgba(0,0,0,0.1);
}

h2 {
  color: #8B4513;
}

.image-container {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
}

.image-container img {
  width: 30%;
  margin-bottom: 20px;
}

footer {
  text-align: center;
  margin-top: 2rem;
  padding: 1rem;
  background-color: #8B4513;
  color: #fff;
}
```

5. アプリケーションを起動します:
```
npm start
```

これで、React.jsを使用して喫茶店の魅力を表現したウェブ ページが完成しました。このウェブページは、喫茶店の特徴 、メニュー、ギャラリーを含み、訪れる人々に喫茶店の魅力 を伝えることができます。必要に応じて、実際の喫茶店の情 報や画像を追加し、さらにカスタマイズすることができます 。

</details>

以下のような部分がキーワードの魅力を伝えるためにエージェントが採用した内容です。

`src/components/CoffeeShopInfo.js`

```javascript
import React from 'react';

const CoffeeShopInfo = () => (
  <section id="about">
    <h2>喫茶店の魅力</h2>
    <ul>
      <li>こだわりのコーヒー</li>
      <li>落ち着いた雰囲気</li>
      <li>昭和レトロな内装</li>
      <li>ゆったりとした時間</li>
    </ul>
    <h2 id="menu">メニュー</h2>
    <ul>
      <li>ブレンドコーヒー</li>
      <li>カフェオレ</li>
      <li>エスプレッソ</li>
      <li>モーニングセット</li>
    </ul>
  </section>
);

export default CoffeeShopInfo
```

`src/components/Gallery.js`
```javascript
import React from 'react';

const Gallery = () => (
  <section id="gallery">
    <h2>ギャラリー</h2>
    <div className="image-container">
      {/* 実際の画像URLに置き換えてください */}
      <img src="https://example.com/coffee-shop-1.jpg" alt="店内の様子" />
      <img src="https://example.com/coffee-shop-2.jpg" alt="コーヒーカップ" />
      <img src="https://example.com/coffee-shop-3.jpg" alt="カウンター" />
    </div>
  </section>
);

export default Gallery;
```

## 参考

Cloud 9 と VPC について

https://qiita.com/azureus/items/92f287c164990daefb5b

https://qiita.com/a_b_/items/4ab82e2da6f7b61536ac

ReActはLangChain Hubから取得していましたが、最近だと **LangSmith** も使えます。

https://www.langchain.com/langsmith