LangChainの実行結果をスプレッドシートとメール送信で管理したい

# 動機
AIエージェントを組む際、実行時間が長くなるため放っておくことが多い。その結果、せっかく解析したのに後で確認し忘れる・・・というもったいない事態が頻繁に起きてしまう。
そこで、実行結果たちを自動でメール送信する仕組みを作ればこういった事を防げるのではないかと考えた。なんなら解析も自動実行して欲しいケースがあるが、それは別の機会にでも。

・・・書き上がってから気付いたんだけど、これLangChainの実行結果だけじゃなくて汎用的に使える仕組みじゃんね。タイトルがあんまりよくない。

# 全体構成
システム全体の構成は下図の通り。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925524/0ebfee35-0776-4beb-b421-a20d9ca37286.png)
1. LangSmithにAIエージェントの構築と推論を依頼する。
1. AIエージェントが推論を実行する。
1. 推論結果がGoogle Colaboratoryに返ってくる。
1. 推論結果をスプレッドシートに出力する。
1. 例えば一日一回、その日得られた推論結果たちをメール送信する。

# 実装
これから登場するpart1, part2, part3のソースコードを順番にColaboratoryで記述することで、推論結果をスプレッドシートに書き出すところまでが実行される。
### 0. 下準備
AIエージェントのコードが長くなってくると、Colaboratory内に記述するのは見にくい。ローカルで作って毎回アップロードするのも面倒。ということで、ソースはGoogle Driveに保存しておき読み込むことにする。フォルダ構成は下図の通り。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925524/60d89819-baa7-435e-9026-12380bc0eb65.png)


スプレッドシートを読み込むために必要なJSONキーの取得方法は、下記サイトの方法1を参考にした。

https://zenn.dev/dualdepth/articles/493d8289cb98c9

また、APIキーをあらかじめGoogle Colaboratoryに登録しておく必要がある。
ウィンドウ左側の鍵アイコンをクリックし、「新しいシークレットを追加」から3種類のAPIキーを入力する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925524/218c5ae9-b7df-481b-a3e1-2fd847dfde8f.png)
・LANGCHAIN_API_KEY
LangSmithのサイトに登録してAPIキーを発行する。

https://smith.langchain.com/

・OPENAI_API_KEY
OpenAIのサイトに登録してAPIキーを発行する。
注）APIを叩くためには支払い情報を登録し、従量課金できるようにしておく必要がある。

https://openai.com/ja-JP/api/

・TAVILY_API_KEY
Tavilyのサイトに登録してAPIキーを発行する。
月1000回の検索までは無料で利用できる。

https://app.tavily.com/home

以上の準備を行った上で、Colaboratory内で下記ソースを実行する。
```python:part1
# 各種ライブラリをインストール
!pip install --upgrade gspread gspread_dataframe
!pip install --upgrade oauth2client
!pip install openai==1.40.6 httpx==0.27.2 \
  langchain-core==0.3.0 langchain-community==0.3.0 \
    langgraph==0.2.22 langchain-openai==0.2.0 \
      python-dotenv==1.0.1 numpy==1.26.4 faiss-cpu==1.8.0.post1 \
        pydantic-settings==2.5.2 retry==0.9.2 decorator==4.4.2 \ pytz
!pip uninstall -y pydantic # pydanticのエラーが出る場合、ダウングレードするといいケースがある
!pip install pydantic==2.10.6

# Google Driveをマウント
from google.colab import drive
drive.mount('/content/drive')

# 解析フォルダへ移動
import os
os.chdir('/content/drive/MyDrive/[実行用フォルダ]') # 自身の実行用フォルダを記載

# スプレッドシートと連携
import gspread
from oauth2client.service_account import ServiceAccountCredentials
scope = ['https://spreadsheets.google.com/feeds',
         'https://www.googleapis.com/auth/spreadsheets',
         'https://www.googleapis.com/auth/drive.file',
         'https://www.googleapis.com/auth/drive']

# 認証用JSONキーのパス
creds = ServiceAccountCredentials.from_json_keyfile_name('[認証用JSONキーの名前]', scope) #認証用JSONキーの名前を記載
client = gspread.authorize(creds)

# スプレッドシートのor URL
spreadsheet_key = "[スプレッドシートのURLのうち、https://docs.google.com/spreadsheets/d/に続く部分]"
sheet = client.open_by_key(spreadsheet_key).sheet1

# APIキー読込み
from google.colab import userdata
os.environ["OPENAI_API_KEY"] = userdata.get("OPENAI_API_KEY")
os.environ["TAVILY_API_KEY"] = userdata.get("TAVILY_API_KEY")
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com"
os.environ["LANGCHAIN_API_KEY"] = userdata.get("LANGCHAIN_API_KEY")
os.environ["LANGCHAIN_PROJECT"] = "[プロジェクト名]" # プロジェクト名を記載
```

### 1~3. AIエージェントの構築と推論実行
ここでは例として、簡単なプロンプトを実行する。
（もうちょっとまともな例はないのか？でも一気に書き上げたかったし・・・）
下記ソースをGoogle Drive上の実行用フォルダ内にtest.pyとして保存しておく。
```python:test.py
import sys
from langchain_openai import OpenAI

args = sys.argv

model = OpenAI(model="gpt-4o-mini", temperature=0)
message = model.invoke(args[1])
print(message)
```

Colaboratory側で下記ソースを実行し、test.pyを動かす。
```python:part2
# 指示を記載
instruction = "ごきげんよう。"

# 推論実行
!python /content/drive/MyDrive/[実行用フォルダ名]/test.py "{instruction}" > output.log #実行用フォルダ名を記載
```

### 4. 推論結果をスプレッドシートに出力
先程得られた推論結果（output.log）をスプレッドシートに書き出す。
後述のメール送信の周期中に複数個の結果が得られることもあるので、最終行に追加していく形とする。
```python:part3
# 実行結果出力準備
import datetime
import pytz
tz = pytz.timezone("Asia/Tokyo")
dt_now = datetime.datetime.now(tz)
content_to_write = [dt_now.strftime("%Y/%m/%d %H:%M"), ""] # A列：実行日時, B列：実行結果

# 推論結果読込み
with open("output.log") as fi:
  for line in fi:
    value = line.strip("\n")
    print(value)
    content_to_write[1] += value
    content_to_write[1] += "\n"

# スプレッドシートへ書き出し（最終行へ追加）
data = sheet.get_all_values()
empty_row_index = len(data) + 1
sheet.insert_row(content_to_write, empty_row_index)
```
下図は書き出された推論結果の例。入力が適当だから出力も当然適当。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925524/84810e0f-4aad-4599-9d22-89e6fbaf87af.png)

ちなみに、発展的なAIエージェントの構築に関しては下記書籍が実践的でかつ分かりやすい。すぐに使えるデザインパターンも載っている。

https://www.amazon.co.jp/dp/4297145308/

### 5. スプレッドシートからの定期的なメール送信
GAS（Google App Script）で記述していく。

GASとスプレッドシートを連携させる方法は、例えば下記サイトを参照。

https://ex-ture.com/blog/2022/12/21/google-app-script%E3%81%A8google%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E3%82%B7%E3%83%BC%E3%83%88%E3%82%92%E9%80%A3%E6%90%BA%E3%81%95%E3%81%9B%E3%82%8B/

```go
function sendEmailsFromSheet() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("シート1");
  const data = sheet.getDataRange().getValues();

  const name = "test";
  const email = "[送信先アドレスをここに記載]";

  for (let i = 1; i < data.length; i++) {  // 1行目はヘッダー行
    const dt_time = data[i][0]; // 実行日時読込み
    const message = data[i][1]; // 実行結果読込み
    const subject = `${name} ${dt_time}`;  // 件名指定

    const sentFlag = data[i][2];
    if (!email || !message || sentFlag === "送信済み") {
      continue;  // データが不完全 or 送信済みならスキップ
    }

    try {
      GmailApp.sendEmail(email, subject, message); // 送信
      sheet.getRange(i + 1, 3).setValue("送信済み");  // C列に送信済みかどうかのフラグをセット
      SpreadsheetApp.flush();  // 更新を反映
    } catch (e) {
      Logger.log(`送信失敗: ${email} - ${e}`); // 送信失敗
    }
  }
}

```
これを定期実行することで、実行周期内に貯まった推論結果がメール送信される。過去に送信された結果は再送信されない。

定期実行の方法は下記ページなどを参照。

https://zenn.dev/eito_blog/articles/50202178ef91d3

下図はスプレッドシートから送られてきたメールの例。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925524/980090df-c25c-4f08-a7fe-36840a939dec.png)

# 展望
例えば市場の動向を毎日キャッチアップする時など、推論を定期的に自動実行したい場合が考えられる。
Google Colaboratoryには定期実行の仕組みがあるらしいが、Pro+以上のみ（月額5k~6k円）。このためだけにPro+を契約するよりは、ソースをローカルに落とし込んでバッチファイルで定期実行する方が良い。