
MastraとNext.jsで製造業の工場トラブル事例検索チャットボットを作ってみた
はじめに
MastraはAIアプリケーションを構築するためのTypeScriptのフレームワークです。ELv2ライセンスで、マネージドサービスとして他社に提供することはNGですが、業務アプリの構築は可能です。なぜELv2なのかドキュメントもありますので詳細はこちらをご参照ください。
The primary limitation is that you cannot provide Mastra as a hosted or managed service that offers users access to the substantial functionality of the software.
(訳) 主な制限は、ソフトウェアの重要な機能にユーザーがアクセスできるホスト型または管理型のサービスとして Mastra を提供できないことです。
またAIを活用したTypeScriptツールキットは、Vercel社がAI SDKを公開しています。
こちらに AI SDKとMastraの関係が書いており
Mastra acts as a layer on top of AI SDK to help teams productionize their proof-of-concepts quickly and easily.
とあるように、MastraはAI SDK 上位レイヤーとして機能するという位置付けでもあります。
今回はMastraを試しつつ、製造業の保全業務周りで使えそうな、テキストや画像で過去のトラブル記録を検索するというアプリを作成します。
今回は最初なのでMastraのAgentとWorkflow機能を試しつつ、MastraのPlaygroundで便利な機能を適宜紹介します。なので今回紹介する機能はMastraのほんの一部となります。
概要
構成図
今回はローカルの動作に限定します。
デモ
アプリは以下のような形で動作します。容量の都合上冒頭のみです..。一瞬止まりますが、これはtool use(function calling)が走っているためです。
別の画像の例も貼ります。前のデモは写真にまんま事例の文字列が入っていたため...
全体的な出力の画像は以下の通りです。
補足は以下の通りです。
- 本当はMastraのPlayGroundのチャットUIで済ませる予定でしたが、マルチモーダルの対応ができていないため今回はUIをNext.jsで自作しています。
- チャットUIはQuick Start用にコード量は抑えています。リッチなものを求めている場合、mckaywrigley/chatbot-uiがおすすめです。
- チャットはストリーミングにし、
@ai-sdk/react
に寄せるようにExperimentalな機能を利用してます(注意)
ソースコード
リポジトリ、サンプルコード、データセットは以下のリポジトリです。必要なコードやデータは全て格納しています。
利用するもの
サービス名 | 用途 |
---|---|
Bedrock | 埋め込みモデル(cohere.embed-multilingual-v3)と生成AIモデル(anthropic.claude-3-5-sonnet-20241022-v2:0)を利用します |
Cohere | 事例書き込みと検索文字列のベクトル化 |
TypeScript | ソースコード |
Pinecone local[1] | ベクトルデータベース |
Grafana関連 | TempoとGrafanaですが、イメージにはprometheusやlokiも含まれています |
基本 @ai-sdk/amazon-bedrock
は使っていますが、Cohereで埋め込みベクトルを生成する箇所は、@aws-sdk/client-bedrock-runtime
を利用しています。
@mastra/pinecone
はローカルpineconeに未対応のため未採用
検索に関して
今回は埋め込みモデルにテキストと画像の両方に対応しているcohere.embed-multilingual-v3
を利用します。モデルの詳細はこちらをご参照ください。事例をベクトル化してpineconeに格納します。その後テキスト、画像のクエリをベクトル化し、コサイン類似度で類似事例をセマンティック検索します。全文検索は単語の一致で検索するのに対し、セマンテック検索は言葉の意味や文脈を理解して関連する概念も含めて検索するので、曖昧な検索を行うケースが多い場合に便利です。
ソース構成解説
階層は2つまで表示しています。
$ eza -T --git-ignore -L 2
.
├── bin
│ └── init-vector-store.ts # pinecorneにデータを突っ込むスクリプト
├── biome.json
├── compose.yml # pinecorne, tmpo, grafanaが起動するdokcerファイル
├── data
│ ├── dataset.csv # トラブル事例のデータセット(LLMで生成)
│ └── trobules # トラブル事例の画像群(geminiで生成)
├── docs
│ ├── app.gif
│ └── architecture.drawio.png # アーキテクチャ図
├── next.config.ts
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
│ ├── file.svg
│ ├── globe.svg
│ ├── next.svg
│ ├── vercel.svg
│ └── window.svg
├── README.md
├── src
│ ├── app # Next.jsのソースコード
│ ├── lib # 共通のライブラリ
│ └── mastra # Mastra関連コード
├── test
│ ├── globalSetup.ts
│ └── testSetup.ts
├── tsconfig.json
└── vitest.config.ts
準備
Next.jsとMastraのセットアップ
前述のリポジトリを利用することを推奨しますが、初期化方法を知りたい方向けに書きます。
基本的に好みで大丈夫です。自分的にはNext.jsもMastraのsrcディレクトリを作るのが良いと思います。これはMastraのCLIがデフォルトでsrc/mastraに一連のコードがあることを期待するためです。(もちろんCLIオプションで設定可能ですが、個人的な好みで推奨しています。)
Next.jsセットアップ
$ npx create-next-app@latest
✔ What is your project named? … nextjs-mastra-sample
✔ Would you like to use TypeScript? … No / Yes # Yes
✔ Would you like to use ESLint? … No / Yes # No
✔ Would you like to use Tailwind CSS? … No / Yes # Yes
✔ Would you like your code inside a `src/` directory? … No / Yes # YES
✔ Would you like to use App Router? (recommended) … No / Yes # Yes
✔ Would you like to use Turbopack for `next dev`? … No / Yes # Yes
✔ Would you like to customize the import alias (`@/*` by default)? … No / Yes # Yes
Creating a new Next.js app in /Users/shuntaka/repos/github.com/shuntaka9576/nextjs-mastra-sample.
Using npm.
Initializing project with template: app-tw
Installing dependencies:
- react
- react-dom
- next
Installing devDependencies:
- typescript
- @types/node
- @types/react
- @types/react-dom
- @tailwindcss/postcss
- tailwindcss
added 48 packages, and audited 49 packages in 13s
10 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Initialized a git repository.
Success! Created nextjs-mastra-sample at /Users/shuntaka/repos/github.com/shuntaka9576/nextjs-mastra-sample
cd nextjs-mastra-sample
Mastraはsrc配下に定義しました。これはMastraのCLIがデフォルトでsrc/mastra
を指定するためです。--dir
オプションで変更可能なので、適宜指定してください。
Mastrasセットアップ
❯ npx mastra@latest init
│
◇ You do not have the @mastra/core package installed. Would you like to install it?
│ Yes
⠼ Installing Mastra core dependencies
removed 1 package, and audited 436 packages in 3s
48 packages are looking for funding
run `npm fund` for details
✔ @mastra/core installed successfully
┌ Mastra Init
│
◇ Where should we create the Mastra files? (default: src/)
│ /src
│
◇ Choose components to install:
│ Agents
│
◇ Add tools?
│ Yes
│
◇ Select default provider:
│ OpenAI
│
◇ Enter your openai API key?
│ Skip for now
│
◇ Add example
│ Yes
│
◇ Make your AI IDE into a Mastra expert? (installs Mastra docs MCP server)
│ Cursor
│
│
│ Note: you will need to go into Cursor Settings -> MCP Settings and manually enable the installed Mastra MCP server.
│
│
◇
│
◇ ─────────────────────────────────────────────────────────╮
│ │
│ │
│ Mastra initialized successfully! │
│ │
│ Add your OPENAI_API_KEY as an environment variable │
│ in your .env.development file │
│ │
│ │
├────────────────────────────────────────────────────────────╯
ミドルウェアの準備
今回は ghcr.io/pinecone-io/pinecone-local:latest
docker.io/grafana/otel-lgtm:0.8.1
のイメージを利用します。ソースは以下の通りです。
以下のコマンドで立ち上げてください。
docker compose up -d
次にpineconeにトラブル事例を投入します。
npx tsx ./bin/init-vector-store.ts
投入されるデータは以下のような形です。使わない列もあります。
ベクトル化は、タイトルと説明と解決策で行います。
これにより画像やテキストで全文検索とは異なる意味的に類似性に基づいた検索が可能になります。
MastraのAgent実装
はじめに
冒頭でも紹介したこちらの実装を解説します。
処理はシンプルです。
- ユーザーが画像かテキストで問い合わせる
- 生成AIがテキストを解析、画像が含まれる場合LLM側でマルチモーダルで解釈し、文字列にし、tool use(function calling)し、pinecorneから類似事例を検索(※ なぜ画像をテキストにするかは後述)
- 2の出力のフォーマットを整えて出力
実装して気づきましたが、ストリーミングで実装するとツール呼び出し前でもレスポンスが返却される点です。これはユーザーの体感時間を下げることに繋がるため体験として良いと思いました。
Next.js 実装
フロントの実装は以下の通りで、特筆することは @ai-sdk/react
を利用して非常に簡単にストリーミングを実装していることです
ただ画像送信に関しては、experimental機能を活用してます。まだ安定していないと思いますので利用の際は注意してください。また、画像だけでも送信できるようにallowEmptySubmitを許可しています。
APIルートで事例検索を呼び出しています。
APIルートの実装は、最新のメッセージを取り出してmastra呼び出しをしています。なので過去履歴を読み取ってくれる実装になっていません。。これは今後対応したいです。
ストリーミングで返す処理もとても簡単です!
ではmastraの実装を見てみます。
Mastra 実装
まずMastraのエージェント実装について解説します。
こちらがNext.jsのAPI RouteでimportしたMastra実装です。
こちらでツールを指定しています。これは画像ないし、テキストを元にpinecorneから類似事例を引っ張ってくるツールです。
ツールの詳細は以下の通りです。画像かテキストまたその両方のベクトルを取得し事例検索をかけます。テキストと画像が両方指定された場合は、ベクトルの平均をとっています。
ただ画像に関しては、function callingで指定させないようにしています。これは、おそらくトークン量の都合で呼び出しに失敗する(適当な文字列指定してくる)もしくは呼び出しに30秒以上時間がかかるケースが多く安定しなかったためです。画像を使ったコサイン類似度による検索は次項のWorkflow機能で試します。
Mastra Playground
Mastraには、playgroundがついています。これが検証しながら実装を調整できるので非常の便利です。
トップ画面
実装したAgent機能はMastraのplaygroundで処理を確認することができます。以下のコマンドでlocalhost:4111
でサーバーが立ち上がります。余談ですが、内部的にはHonoを採用しているそうです。
npx mastra dev
トップ画面
Tool
前項でAgentを作成する過程で、テキストないし画像で、Pineconeから類似事例を検索するツールを紹介しました。ツールとして作成しているのでPlaygroundからテストができます。
簡単にテキスト電動回転パーツに普段と違う響きが聞こえる
で試してみます。
ベアリング摩耗による異音
という事例が引っかかりました。セマンティック検索は単なる文字列の一致ではなく、このように意味的に類似性が高いものを検索できるため、用語の名寄せをしなくてもある程度柔軟に検索が可能です。
JSON詳細
※ expectedInquiry
という項目がありますが、こちらは想定するクエリです。意味的類似性の検証のためのデータセットを作るため入れています。前述の通りベクトルには含めていないため、影響は一切ありません。
{
"results": [
{
"id": "trouble-2023-01-07T14:15:00.000Z-assembly-motor",
"title": "ベアリング摩耗による異音",
"description": "ベアリング摩耗の兆候あり。3号機モーター部から断続的な金属音を検知。回転数を下げて様子見。部品交換の準備を開始。",
"date": "2023-01-07T14:15:00.000Z",
"line": "assembly",
"machine": "motor",
"type": "hardware",
"solution": "ベアリングの交換と定期点検スケジュールの見直し",
"expectedInquiry": "電動回転パーツに普段と違う響きが聞こえる",
"similarity": 0.6632226
},
{
"id": "trouble-2023-01-05T09:30:00.000Z-assembly-motor",
"title": "軸受け部の金属異音と振動",
"description": "駆動系の振動が通常より大きく、軸受け部分から金属的な異音を確認。緊急対応として減速運転で対処。明日保全部に連絡予定。",
"date": "2023-01-05T09:30:00.000Z",
"line": "assembly",
"machine": "motor",
"type": "hardware",
"solution": "軸受けの交換と潤滑油の補充を実施",
"expectedInquiry": "電動回転パーツに普段と違う響きが聞こえる",
"similarity": 0.65542156
},
{
"id": "trouble-2023-03-08T08:45:00.000Z-painting-controller",
"title": "送風機ローター不均衡による振動音",
"description": "塗装ブース換気装置から不規則な振動音が発生。点検の結果、送風機ローターにコーティング剤の付着による不均衡を確認。この状態での継続運転はベアリング破損リスクあり。",
"date": "2023-03-08T08:45:00.000Z",
"line": "painting",
"machine": "controller",
"type": "hardware",
"solution": "ローターの洗浄と動的バランス調整、および定期清掃頻度の見直し",
"expectedInquiry": "電動回転パーツに普段と違う響きが聞こえる",
"similarity": 0.62663317
},
{
"id": "trouble-2023-01-10T14:00:00.000Z-assembly-conveyor",
"title": "冷却ファン固定ボルト緩みによる異常音",
"description": "午後2時頃、A棟コンベア駆動モーターから異常音発生。点検したところ冷却ファンの固定ボルトが緩んでいたため増し締め実施。現在は正常運転中。",
"date": "2023-01-10T14:00:00.000Z",
"line": "assembly",
"machine": "conveyor",
"type": "hardware",
"solution": "固定ボルトの増し締めと緩み止め剤の塗布",
"expectedInquiry": "電動回転パーツに普段と違う響きが聞こえる",
"similarity": 0.6034444
}
]
}
次は以下の画像で試しています。
cat ./data/trobules/04_trobule.jpeg| base64 | pbcopy
冷却液循環系統からの液剤漏出
が一番近似しているので、上手くいってそうということがわかります。
JSON詳細
{
"results": [
{
"id": "trouble-2023-02-10T13:25:00.000Z-machining-controller",
"title": "冷却液循環系統からの液剤漏出",
"description": "加工ラインで床面に青色の痕跡を発見。機械本体下部から冷却液が少量ずつ滴下していることを確認。ホースの接続部に微細なひび割れが発生し、圧力がかかった際に液体が噴出する状態になっていた。作業を一時中断し、該当箇所の清掃を実施。",
"date": "2023-02-10T13:25:00.000Z",
"line": "machining",
"machine": "controller",
"type": "maintenance",
"solution": "劣化したホース全体の交換と接続部の固定具強化による恒久対策を実施。漏出防止用の受け皿を設置。",
"expectedInquiry": "なんらかの液体が流出している",
"similarity": 0.4914673
},
{
"id": "trouble-2023-02-15T11:20:00.000Z-machining-robot",
"title": "関節部の摩擦による金属摩耗音",
"description": "第二工場自動加工セクションのアーム関節から周期的な摩擦音が発生。精密部品の加工精度に影響が出始めているため、作業速度を70%に制限して対応中。",
"date": "2023-02-15T11:20:00.000Z",
"line": "machining",
"machine": "robot",
"type": "hardware",
"solution": "関節部の分解清掃とグリス再充填及びシール部品の交換",
"expectedInquiry": "電動回転パーツに普段と違う響きが聞こえる",
"similarity": 0.48170584
},
{
"id": "trouble-2023-09-05T09:40:00.000Z-painting-motor",
"title": "塗装ミキサーの回転子バランス不良による機械的動揺",
"description": "塗装材料ミキサーから異常な機械的動揺が発生。分析の結果、回転子のバランスウェイトが一部脱落し、高速回転時に遠心力による不均衡状態が生じていることが判明。",
"date": "2023-09-05T09:40:00.000Z",
"line": "painting",
"machine": "motor",
"type": "hardware",
"solution": "回転子の再バランシング作業を実施し、バランスウェイトの固定方法を改良。また、始動前点検手順に動的バランス確認項目を追加。",
"expectedInquiry": "マシンが激しく揺れているように感じる",
"similarity": 0.44172436
},
{
"id": "trouble-2023-05-12T08:45:00.000Z-machining-robot",
"title": "アーム関節部故障による加工中断",
"description": "精密切削工程中に自動操作機の可動接合部が固着。作業ラインが自動防護機能により稼働中止。技術班による緊急診断の結果、軸受け摩耗を確認。代替ユニットへの交換作業で3時間の工程遅延が発生。",
"date": "2023-05-12T08:45:00.000Z",
"line": "machining",
"machine": "robot",
"type": "hardware",
"solution": "接合部の完全交換と予防点検プログラムの強化",
"expectedInquiry": "製造フローがストップしてしまった",
"similarity": 0.4380265
}
]
}
Agent - Chat
作成したエージェントごとにチャット画面があります。これはUIと繋げる手間が省けるので非常に便利です。
マルチモーダルには対応していませんが、ストリーミングで挙動を確認できます。
Agent - 評価(Evals)
Evalsは、エージェントの品質を測定できる機能です。AI の出力は非決定論的であり、同じ入力でも変化する可能性があるためです。こちらもplaygroundで確認可能です。
evalsの設定をすることで、回答内容の評価も見ることができます。実装はこちらです。
evalsの結果は以下です。mastra playgroundのChatで行った内容は、Liveのタブにに表示されます。
UIをみていて、気づいた方がいると思いますが、CIタブもあります。vitestでテストを書いてその結果もこちらの画面で確認することが可能です。私は公式の手順では上手くいかず、DBを明示的に指定することで閲覧できるようになりました。実装を紹介します。
こちらでストレージ LibSQLStore を明示的に指定します。
あとはvitest実行時に1回実行されるglobalSetupを実装
あとはテスト毎に実行されるtestSetupを実装
テストを実装します。
テスト実行しました。指定したメトリクスで指定した閾値より低いと当然ですがエラーになります。人間が毎回チャットで確認するのは大変あので便利ですね!
$ npx vitest
DEV v3.0.9 /Users/shuntaka/repos/github.com/shuntaka9576/nextjs-mastra-manufacturing
❯ src/mastra/agents/factoryTroubleResarchAgent.test.ts (1 test | 1 failed) 17242ms
× factoryTroubleResarchAgent > should return similar cases 17240ms
→ expected 0.8214285714285714 to be 1 // Object.is equality
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
FAIL src/mastra/agents/factoryTroubleResarchAgent.test.ts > factoryTroubleResarchAgent > should return similar cases
AssertionError: expected 0.8214285714285714 to be 1 // Object.is equality
- Expected
+ Received
- 1
+ 0.8214285714285714
❯ src/mastra/agents/factoryTroubleResarchAgent.test.ts:15:26
13| );
14|
15| expect(result.score).toBe(1);
| ^
16| });
17| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files 1 failed (1)
Tests 1 failed (1)
Start at 06:31:06
Duration 18.94s (transform 63ms, setup 699ms, collect 7ms, tests 17.24s, environment 0ms, prepare 37ms)
FAIL Tests failed. Watching for file changes...
press h to show help, press q to quit
この結果はPlaygroundで確認できます。上記の実行例は上から2番目です、Socreに0.82とあります。1が出なかったので泣きの1回をしましたがダメでした。
Observability
Mastra は、アプリケーションのトレースと監視のための OpenTelemetry Protocol (OTLP) をサポートしています。基本的に上記のドキュメント通りで実装できます。
念の為該当実装を示します。
本リポジトリで、dokcer-compse
をするとlocalhost:3001
にGrafanaが閲覧できるようになります。以下が一覧です。
例えばエージェントがツール呼び出しにどの程度時間がかかっていたかは以下のように確認することできます。
MastraのWorkflow実装
はじめに
さてAgent機能を先ほど試しましたが、画像を引数にfunction callingをすると安定しない問題がありました。もちろんURLベースにして関数側でイメージを取得すれば可能になりますが、Workflow機能を呼び出して、この問題を解消します。
Workflowは、LangChainでいうところのLangGraphです。今回は作るのはWorkflowを作るには簡単なものですが、お試しなのでご容赦ください。
実行した結果は以下の通りです。Workflowだとストリーミングではなくなるので画像です。。
Mastra実装
フローはシンプルで以下の通りです。
- 画像ないしテキスト受け取り類似事例検索
- 生成AIを使ってフォーマット調整
簡単に言えば決定的にできる処理を先にして、その後生成AIを使うという話です。
Mastraの実装は以下の通りです。
Mastra側に登録するのもの忘れないでください。
実装すると、Mastraのplaygroundで確認できます。シンプルすぎ...もっと複雑なワークフローを定義したい人生でした。
もちろんワークフローもテストできます。
Next.js実装
@ai-sdk/react
のレスポンス形式に合わせるのが難しかったので、通常の機能でコードを書きました。フロントエンド力は無いので参考程度でお願いします。
同様にAPIルートでMastraのWorkflowを呼び出すことで、実行が可能です。
最後に
MastraのAgent, Workflow, Playground機能について紹介しました。AgentやWorkflowが簡単に試せるPlaygroundがあるのは便利した。UIと毎回繋ぎこんだり、Workflowは全体像が逐次で確認できないと辛いところがあります。
LangGraphのときはmermaidで出力していましたが、これもワンテンポかかって不便でした。現在は別のソリューションがありそうですが。。
またo11yの機能も充実していそうでした。有名なところでLangsmith, LangfuseはもちろんNew Relicなども対応しています。
Langfuse統合は試してみたいですね!Workflowのo11yは今回出来なかったので時間があるときにまた試してみようと思います。冒頭でも書きましたが、MCPなどまだまだ機能が沢山あるので、ネタは尽きないのと思います。
TypeScript系の生成AI FWはVercel ai-sdkも頑張っていて非常に面白いです!experimentalなところも多い分、今は色々貢献できるタイミングでもあります!AI Agentも型で固めていきたいですね!
ささやかですが、ドキュメント修正をしました。実装でも貢献したいところです!
Dockerイメージとして利用できるメモリ上で動作するPineconeエミュで、目的に合致しているため採用 ↩︎