LangChain OpenWeatherMap

OpenWeatherMapのAPIを使ってLLMに天気を聞く方法を記述します。
参考までに、MacGPTで、ChatGPT o1-miniに「東京の天気はどうですか？」と聞くとこんな感じの回答でした。

```text
申し訳ありませんが、リアルタイムの天気情報にはアクセスできません。
最新の天気予報については、天気予報サイトやアプリをご利用ください。
```
おまけとして、気象庁API（非公式）で取得した東京の情報を使ったツールの作り方も記載しておきます



# 環境
```text
Python 3.12.7
openai                                   1.59.3
langchain-openai                         0.1.25
langchain                                0.2.16
langchain-core                           0.2.41
langchain-community                      0.2.17
langgraph                                0.2.26
langgraph-checkpoint                     1.0.12
langsmith                                0.1.147

pyowm                                    3.3.0
```

# 0.準備
## 1.OpenWeatherMap APIにアクセスして、ユーザー登録をします。

https://openweathermap.org


## 2.取得したAPIキーを、.envファイルに追記します
```.env
# OpenWeatherMap API key
OPENWEATHERMAP_API_KEY = xxxxxxxxxxxxx
```


```python:python
import os
from os.path import join, dirname, abspath
from dotenv import load_dotenv

dir_path = dirname(abspath("__file__"))
dotenv_path = join(dir_path, '../[your directory]/.env')
load_dotenv(dotenv_path, verbose=True)

#print(os.environ['OPENAI_API_KEY'])
#print(os.environ["OPENWEATHERMAP_API_KEY"])
```



# 1.OpenWeatherMapWrapper
以下公式ドキュメントに従って実施してみます

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

## 1. pyowmのインストールが必要です

```bash:
# Please install it with `pip install pyowm`
%pip install pyowm
#Successfully installed PySocks-1.7.1 geojson-2.5.0 pyowm-3.3.0
```

## 2.環境変数の準備
環境変数を設定する必要があります:

API キーを OPENWEATHERMAP_API_KEY 環境変数に保存します

```python:python
import os
from langchain_community.utilities import OpenWeatherMapAPIWrapper

# .envに記述しない場合
#os.environ["OPENWEATHERMAP_API_KEY"] = "your openweathermap api key"

weather = OpenWeatherMapAPIWrapper()
```

## 3.試験実行
### 東京
```python:python
weather_data = weather.run("Tokyo,JP")
print(weather_data)
```

```text
In Tokyo,JP, the current weather is as follows:
Detailed status: scattered clouds
Wind speed: 2.68 m/s, direction: 223°
Humidity: 41%
Temperature: 
  - Current: 6.3°C
  - High: 7.59°C
  - Low: 4.77°C
  - Feels like: 4.3°C
Rain: {}
Heat index: None
Cloud cover: 48%
```

### ロンドン
```python:python
weather_data = weather.run("London,GB")
print(weather_data)
```

```text
In London,GB, the current weather is as follows:
Detailed status: overcast clouds
Wind speed: 2.57 m/s, direction: 90°
Humidity: 90%
Temperature: 
  - Current: 2.27°C
  - High: 3.13°C
  - Low: 0.34°C
  - Feels like: -0.4°C
Rain: {}
Heat index: None
Cloud cover: 100%
```

# 3.OpenWeatherMap with LangChain
LangChain公式によると簡単に使えるように記載があった

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

しかしながら、公式に記載のサンプルで利用するように記載がある、initialize_agentは
deprecatedの様なので、代わりに、create_react_agentを使った方法で実施する

```text
LangChainDeprecationWarning: 
The function `initialize_agent` was deprecated in LangChain 0.1.0 and will be removed in 1.0.
Use Use new agent constructor methods like create_react_agent, create_json_agent, create_structured_chat_agent, etc. instead.
agent_chain = initialize_agent(
```

```python:python
import os

# LangChainDeprecationWarning: 
#     The function `initialize_agent` was deprecated in LangChain 0.1.0 and will be removed in 1.0.
#from langchain.agents import AgentType, initialize_agent, load_tools

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langgraph.prebuilt import create_react_agent
from langchain_openai import OpenAI

#os.environ["OPENAI_API_KEY"] = ""
#os.environ["OPENWEATHERMAP_API_KEY"] = ""

from langchain.agents import load_tools
owm_tools = load_tools(["openweathermap-api"])

llm = ChatOpenAI(temperature=0,model="gpt-4o-mini")

# プロンプトの定義
owm_prompt = ChatPromptTemplate.from_messages(
    [('system','与えられたinputに従って天気の関数を呼び出してください',),
     ('placeholder', '{messages}'),])

owm_agent = create_react_agent(
    model=llm,
    tools=owm_tools,
    state_modifier=owm_prompt
)

result = owm_agent.invoke({"messages":"東京の天気はどうですか？"})
print(result['messages'][-1].content)
```

```text
東京の現在の天気は以下の通りです：

- 詳細な天候状況: くもり
- 風速: 2.33 m/s（方向: 262°）
- 湿度: 41%
- 温度:
  - 現在: 6.1°C
  - 最高: 7.02°C
  - 最低: 4.77°C
  - 体感温度: 4.35°C
- 雨: なし
- 雲のカバー: 55%
```

明日についても聞いてみる

```python
result = owm_agent.invoke({"messages":"東京の明日の天気はどうですか？"})
print(result['messages'][-1].content)
```

```text
現在の東京の天気は、曇りで気温は5.43°Cです。明日の天気についての具体的な情報は提供できませんが、
現在の気温の範囲は最高6.42°C、最低3.22°Cとなっています。風速は1.54 m/sで、湿度は44%です。
```


# 4.おまけ：気象庁API(非公式)を使ったツール作成



ツールとするAPIを自作する

```python:python
# -*- coding:utf-8 -*-
import requests
import json
import datetime

from langchain_core.tools import tool

# toolの定義
@tool
def get_tokyo_forecast_weather(location):
    """
    指定された地域名に「東京」を含む場合、東京の天気予報を取得してJSON形式で返します。
    
    Args:
        location (str): 地域名の文字列（例: "東京", "東京都"）
    
    Returns:
        str: 天気予報データのJSON文字列。エラーが発生した場合はエラーメッセージを含むJSON。
    """
    # 「東京」を含まない場合はエラーメッセージを返す
    if "東京" not in location:
        return json.dumps({"error": "入力された地域名に「東京」が含まれません。東京以外の天候には答えられません。"}, ensure_ascii=False, indent=4)
    
    location_code = "130000"  # 東京の地域コード

    try:
        # 現在の日付を取得し、フォーマットする
        current_date = datetime.datetime.today().strftime("%Y年%m月%d日")
        
        # 気象庁データの取得
        url = f"https://www.jma.go.jp/bosai/forecast/data/forecast/{location_code}.json"
        response = requests.get(url)
        response.raise_for_status()  # HTTPエラーが発生した場合例外を発生させる
        data_json = response.json()
        
        # 地域名の取得と整形
        area_info = data_json[0]["timeSeries"][0]["areas"][0]["area"]["name"]
        area = area_info.replace('地方', '')
        
        # 日付の取得とフォーマット
        time_defines = data_json[0]["timeSeries"][0]["timeDefines"]
        if len(time_defines) < 2:
            return json.dumps({"error": "十分な日付データが存在しません。"}, ensure_ascii=False, indent=4)

        formatted_dates = [
            datetime.datetime.fromisoformat(date_str).strftime("%Y年%m月%d日") 
            for date_str in time_defines[:2]
        ]
        
        # 天候情報の取得と整形
        weathers = data_json[0]["timeSeries"][0]["areas"][0]["weathers"]
        if len(weathers) < 2:
            return json.dumps({"error": "十分な天候データが存在しません。"}, ensure_ascii=False, indent=4)

        formatted_weathers = [weather.replace('\u3000', ' ') for weather in weathers[:2]]
        
        # 予報データの構築
        forecast_data = {
            "today": current_date,
            "location": area,
            "forecast": (
                f"{formatted_dates[0]}の天気は「{formatted_weathers[0]}」で、"
                f"{formatted_dates[1]}の天気は「{formatted_weathers[1]}」の予報です。"
            ),
        }
        
        return json.dumps(forecast_data, ensure_ascii=False, indent=4)
    
    except requests.exceptions.RequestException as e:
        # ネットワーク関連のエラー処理
        error_message = f"データ取得中にエラーが発生しました: {e}"
        return json.dumps({"error": error_message}, ensure_ascii=False, indent=4)
    
    except (KeyError, IndexError, ValueError) as e:
        # データ解析関連のエラー処理
        error_message = f"データ解析中にエラーが発生しました: {e}"
        return json.dumps({"error": error_message}, ensure_ascii=False, indent=4)

# 使用例
#if __name__ == "__main__":
    # 「東京」を含む地域名を入力
#    print(get_tokyo_forecast_weather("東京"))
    
    # 「東京都」を含む地域名を入力
#    print(get_tokyo_forecast_weather("東京都"))
    
    # 「大阪」を含まない地域名を入力（エラーメッセージが返る）
#    print(get_tokyo_forecast_weather("大阪"))
```

## 方法１ LangGraph（create_react_agent）

https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent


```python
from langchain_core.tools import tool
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent

#from langchain_core.output_parsers import StrOutputParser

# プロンプトの定義
weather_prompt = ChatPromptTemplate.from_messages(
    [
        ('system','東京の天気を聞かれたら、天気を取得する関数を呼び出してください',),
        ('placeholder', '{messages}'), #('placeholder', '{messages}'),
    ]
)

# エージェントの作成(書き方1)
check_tools=[get_tokyo_forecast_weather]
llm = ChatOpenAI(model="gpt-4o-mini")
weather_agent = create_react_agent(
    model=llm,
    tools=check_tools,
    state_modifier=weather_prompt
) 

# エージェントの作成(書き方2)
#weather_agent = create_react_agent(
#    model=ChatOpenAI(model='gpt-4o-mini'),
#    tools=[get_tokyo_forecast_weather],
#    state_modifier=weather_prompt #prompt
#) 

# エージェントの実行
response = weather_agent.invoke({'messages': ['東京の天気は？']})
#print(response)
print(response['messages'][-1].content)

# '東京の天気は？'
#=> 東京の天気は、2025年01月04日が「晴れ 夜 くもり」で、2025年01月05日は「晴れ 時々 くもり」の予報です。

# '明日の東京の天気は？'
# => 明日の東京の天気は「晴れ 時々 くもり」の予報です。

# '今日の大阪の天気は？'
# => 申し訳ありませんが、東京以外の地域の天気予報を取得することはできません。東京の天気について知りたい場合はお知らせください。
```

##  方法２（非推奨）AgentExecutor（create_tool_calling_agent）

https://python.langchain.com/api_reference/langchain/agents/langchain.agents.react.agent.create_react_agent.html

上記に以下の記載があるように非推奨のようです

:::note warn
警告
この実装は基礎となる ReAct 論文に基づいていますが、古いため本番アプリケーションには適していません。より堅牢で機能豊富な実装には、LangGraph ライブラリの create_react_agent 関数を使用することをお勧めします。詳細については、[リファレンス ドキュメント](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent) を参照してください。
:::




```python
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

# toolの定義
#@tool
#def get_tokyo_forecast_weather(
# 省略
#)


# プロンプトの定義
weather_prompt2 = ChatPromptTemplate.from_messages(
    [
        ('system','東京の天気を聞かれたら、天気を取得する関数を呼び出してください',),
        ('human', '{input}'), # human input
        ('placeholder', '{agent_scratchpad}'), # messagesではない
    ]
)

# エージェントを作成
weather_agent2 = create_tool_calling_agent(ChatOpenAI(model='gpt-4o-mini'), [get_tokyo_forecast_weather], weather_prompt2)

# エージェントを実行
agent_executor = AgentExecutor(agent=weather_agent2, tools=[get_tokyo_forecast_weather])
response2 = agent_executor.invoke({'input': '明日の東京の天気は？'})
print(response2)
# => {'input': '明日の東京の天気は？', 'output': '明日の東京の天気は「晴れ 時々 くもり」の予報です。'}

```


# 5.参考

https://qiita.com/Tatsuki_Yo/items/121afaecad59a7e11c61

https://zenn.dev/pharmax/articles/1b351b730eef61