はじめに
こんにちは、いのせです。
最近はAIの進化が止まることなく、AIを使ってコーディングするのが当たり前な時代になりましたね。
GitHub Copilot、Claude Code、Cursorなど便利なツールが次々と登場し、コードを書く速度は格段に上がったと思います。ちなみに、僕はClaude Codeを使ってます。
しかし、新卒エンジニアの中でもデータ分析ばかりしてきて、アプリ開発の経験が薄い僕は、ある日気づいてしまったのです。
「あれ、俺、AIに頼りすぎて技術力が全然伸びてないんじゃね...?」
コードは書けている。動くものは作れている。でも、「なぜそう書くのか」を聞かれると答えられない。いわゆる 「コード書けてる風エンジニア」 になりかけていました。
これは、個人開発であれば問題ないのですが、チーム開発となると、コードの説明・他人の書いたコードを読んで理解は必須になってきます。
正直、年末あたりからかなり頭を抱えていました。
ちょっと前の自分
「俺はAIに職を奪われるエンジニアじゃない、はは、最先端の技術を使い回すことができていればいいんだ!」
年末の自分
「俺はAIに職を奪われるエンジニアじゃない、はは、最先端の技術を使い回すことができていればいい...のか?...え?...本当に...?(大汗)」
僕はとてもとても考えました。
そして、たどり着いた先は新世紀エヴァンゲリオンを見ることでした。(なんでやねん)
でも、エヴァをみたことによって僕の頭の中は
「(コードを自分で書くことから)逃げちゃダメだ!」
「(コードを自分で書くことから)逃げちゃダメだ!」
「(コードを自分で書くことから)逃げちゃダメだ!」
といった感じになりましたとさ。おしまいおしまい...
ではなく!!!!!
AIを使わずに勉強をし、AIがなくてもコーディングができるようになろうと決心したんです。
(ありがとう、シンジ君。)
とまあ、おふざけはここまでにしておいて、
ちょうどタイミングよく、Mastra v1がリリースされたこともあり、Mastraの復習がてらこの悩みを解決するアプリを作ることにしました。
前置きが長くなりましたが、今回は、開発したアプリについて記事を書いていこうと思います。
アプリのコンセプト:普通のコードレビューは面白くない
せっかくなら、自分の書いたコードをレビューしてくれる、そんなアプリを作りたいなと思いました。
ただコードレビューするだけじゃ面白くないなぁと。
そんなとき、ふと、大学時代の研究室を思い出しました。
教授にめちゃくちゃ詰められた日々。(※もちろんですがとても尊敬と感謝でいっぱいです。)
「これ、なんでこう書いたの?」
「このデータってソースは?」
「ジャーナルにそんなの載せられると思っているの?」
うーん!懐かしいですねー!今となっては本当にいい経験でしたね。
と、僕は思うことができますが、人によっては、「トラウマだ!」と仰る方もいるのでは...
そんな中、僕の尊敬する弊社の先輩と話していて、こんな会話が生まれました。
「素人質問投げてくる教授AIって面白くない?」
それだ。
というわけで、「素人質問教授コードレビューAI」 が爆誕したわけです。
今回開発した「素人質問教授コードレビューAI」には主に下記の2つのモードが実装されています。
2つのモード
| モード | 特徴 |
|---|---|
| やさしいモード | チャット形式で1問ずつ質問。じっくり考えられる |
| 鬼モード | 一括で大量の質問を投げつける。容赦なし(かなり怖い) |
基本的に鬼モードはなんか悲しくなるので、
もし鬼モードに挑戦し、詰められて悲しい時はやさしいモードを使ってくださいね...
デモ
やさしいモード
コードを貼ると、教授が「素人質問で恐縮ですが...」と1つずつ質問してきます。回答すると採点してくれ、問題箇所が無くなるまでさらに他の箇所を指摘してくれます。
まあ、やさしいモードと言いながらもちょっとこの時点で怖い感じあるんですけどね。
鬼モード
コードレビュー中
コードレビュー完了後
コードを貼ると、教授が容赦なく大量の質問を投げつけてきます。
これは怖いですよ、本当に。
なんせ、指摘箇所を一気に送ってくるあたり怖いですよね。
3枚目の「今すぐ直せ(言い訳無用)」って、あー怖い怖い!!
「素人質問で恐縮ですが...(恐縮などしていない)」
結果が表示される瞬間、画面中央にドーンと表示されて縮小していきます。鬼畜感マシマシ。
上記で用いている指摘箇所が多いコードは生成AIで作成しました。
また、アニメーションはデザイナーとしてAIに作成してもらいました。
アプリの挙動はこんな感じです。
技術解説(Mastra中心)
ここからは、実装で工夫した点を解説します。(ここは真面目に)
1. Mastra Agentのプロンプト設計 - デュアルロールパターン
ポイント: 1つのAgentで「質問フェーズ」と「採点フェーズ」を状態機械的に制御
export const professorReviewer = new Agent({
name: "Professor Reviewer",
model: openai("gpt-4o-mini"),
instructions: `
あなたは「素人質問を投げてくる教授」です。
## 会話の流れ(厳守)
### 1. 質問フェーズ(ユーザーがコードを送ってきたとき)
- 必ず「素人質問で恐縮ですが...」で始める
- 質問は **1つだけ** にする(複数禁止)
### 2. 採点フェーズ(ユーザーが回答したとき)
- 「素人質問で恐縮ですが...」は **絶対に付けない**
- 以下の形式で短く採点する:
- 採点: X/10
- 良い点(1〜2行)
- 改善点(1〜2行)
## 判断基準
- ユーザー入力がコード → 質問フェーズ
- ユーザー入力が文章で、直前にあなたが質問している → 採点フェーズ
`,
});
解説
- 「判断基準」をプロンプトに明示することで、LLMが入力から自動的にフェーズを判定します
- 特定の接頭語(「素人質問で恐縮ですが...」)で会話状態を可視化
- 採点フェーズでは接頭語を禁止することで、状態の区別が明確化
これにより、1つのAgentで複数のロールを持たせることができました。
2. Mastra Workflowでパイプライン処理
ポイント: createStepで「前処理→AI生成→整形」を型安全に連結
// Step 1: 前処理ステップ(純粋関数、AIを使わない)
const preprocessStep = createStep({
id: "preprocess",
inputSchema: z.object({ code: z.string() }),
outputSchema: z.object({ code: z.string() }),
execute: async ({ inputData }) => {
const MAX_CHARS = 8000;
const code = inputData.code.trim();
if (code.length <= MAX_CHARS) {
return { code };
}
return {
code: code.slice(0, MAX_CHARS) + "\n// ... (省略)",
};
},
});
// ワークフロー定義:.then()でパイプライン構築
export const professorReviewWorkflow = createWorkflow({
id: "professorReviewWorkflow",
inputSchema: z.object({ code: z.string() }),
outputSchema: z.object({ text: z.string(), data: ReviewSchema }),
})
.then(preprocessStep) // 前処理
.then(generateReviewStep) // AI生成
.then(formatStep) // 整形
.commit();
解説
- 各ステップがZodスキーマで型定義されるため、型安全なパイプラインが構築できます
-
.then()でチェーンすると、前のステップの出力が次の入力に自動的に渡されます - AIを使わない純粋な処理もステップにできる(preprocessStep, formatStep)ので、責務を分離しやすい
Mastra Workflowの良いところは、各ステップが独立しているのでテストしやすく、再利用もしやすい点です。
3. structuredOutputでAIの出力を構造化
ポイント: ZodスキーマでJSONの形式を強制 + union型でAIの揺らぎを吸収
// Zodスキーマ定義
const ReviewSchema = z.object({
title: z.string().describe("レビューのタイトル"),
questions: z.array(
z.object({
question: z.string().describe("質問文"),
intent: z.string().describe("質問の意図"),
hint: z.string().describe("考えるヒント"),
})
),
// AIが文字列で返す場合があるので、両方受け入れる
quickWins: z
.union([z.array(z.string()), z.string()])
.optional()
.describe("すぐできる改善点"),
});
// Workflow内でAgentを取得してstructuredOutput
const generateReviewStep = createStep({
id: "generateReview",
inputSchema: z.object({ code: z.string() }),
outputSchema: ReviewSchema,
execute: async ({ inputData, mastra }) => {
const agent = mastra.getAgent("professorReviewer");
const response = await agent.generate(prompt, {
structuredOutput: { schema: ReviewSchema },
});
return response.object; // 型安全にアクセス
},
});
解説
-
describe()でAIにフィールドの意図を伝えることで、より正確なJSON生成が可能に -
union型でAIが配列/文字列どちらで返しても対応(現実的な対処。AIは時々配列を文字列で返すことがある) -
mastra.getAgent()でWorkflow内からAgentを取得できるので、Workflow内でAgentの機能を活用できます
structuredOutputを使うことで、AIの出力を確実にパースでき、フロントエンドでの表示が楽になります。
4. chatRoute vs カスタムエンドポイント - 使い分け
ポイント: ストリーミングChat用と一括処理Workflow用で異なるエンドポイント
export const mastra = new Mastra({
agents: { professorReviewer },
workflows: { professorReviewWorkflow },
server: {
// カスタムエンドポイント:Workflow用(一括処理)
customRoutes: [
registerApiRoute("/review", {
method: "POST",
handler: async (c) => {
const body = await c.req.json<{ code: string }>();
const mastraInstance = c.get("mastra");
const workflow = mastraInstance.getWorkflow("professorReviewWorkflow");
const run = await workflow.createRun();
const result = await run.start({ inputData: { code: body.code } });
return c.json(result.result);
},
}),
],
// chatRoute:Agent用(ストリーミング)
routes: [
chatRoute({
path: "/chat",
agent: "professorReviewer",
}),
],
},
});
解説
| エンドポイント | 用途 | 特徴 |
|---|---|---|
chatRoute (/chat) |
やさしいモード | Agent直接呼び出し → SSEストリーミングで返却 |
カスタム (/review) |
鬼モード | Workflow経由 → JSON一括返却 |
- **
chatRoute**はMastraが提供する高レベルAPIで、SSEストリーミングを自動でセットアップしてくれます - カスタムエンドポイントではWorkflowを実行し、複数ステップの処理結果を一括で返します
まとめ
最初は、「ユーモア満載で面白いもの作ってみよー!」くらいの気持ちでいましたが、
開発をしていく中で、AIでコーディングをするのが当たり前の時代だからこそ、「なぜ?」を問われる経験は大事だなぁと思いました。
正直、AIがこれから先どこまで成長していくのかは未知数だと思います。
だからこそ、先のことを考えるのはやめて、まずは今の自分に何が足りないのかを明確にし、やるべきことを一個ずつこなしていくこと、
AIがコードを書いてくれる時代だからこそ、自分の頭で考える力を鍛える必要があります。が大事なのではないかなと僕は思います。
(僕は開発の基礎の基礎からスタートになるので偉そうなことは言えませんが...笑)
今後も少しずつ強化し、良いタイミングでアプリとして公開できたらなと思っております!
フォルダ一式はこちら↓
最後まで読んでいただきありがとうございました!
「素人質問で恐縮ですが...」と詰められたい方は、ぜひ試してみてください!!!
???「恐縮などしていない」





