2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

こんにちは、いのせです。

最近はAIの進化が止まることなく、AIを使ってコーディングするのが当たり前な時代になりましたね。

GitHub Copilot、Claude Code、Cursorなど便利なツールが次々と登場し、コードを書く速度は格段に上がったと思います。ちなみに、僕はClaude Codeを使ってます。

しかし、新卒エンジニアの中でもデータ分析ばかりしてきて、アプリ開発の経験が薄い僕は、ある日気づいてしまったのです。

「あれ、俺、AIに頼りすぎて技術力が全然伸びてないんじゃね...?」

コードは書けている。動くものは作れている。でも、「なぜそう書くのか」を聞かれると答えられない。いわゆる 「コード書けてる風エンジニア」 になりかけていました。

これは、個人開発であれば問題ないのですが、チーム開発となると、コードの説明・他人の書いたコードを読んで理解は必須になってきます。

正直、年末あたりからかなり頭を抱えていました。

ちょっと前の自分
「俺はAIに職を奪われるエンジニアじゃない、はは、最先端の技術を使い回すことができていればいいんだ!」

年末の自分
「俺はAIに職を奪われるエンジニアじゃない、はは、最先端の技術を使い回すことができていればいい...のか?...え?...本当に...?(大汗)」

僕はとてもとても考えました。

そして、たどり着いた先は新世紀エヴァンゲリオンを見ることでした。(なんでやねん)

でも、エヴァをみたことによって僕の頭の中は

「(コードを自分で書くことから)逃げちゃダメだ!」
「(コードを自分で書くことから)逃げちゃダメだ!」
「(コードを自分で書くことから)逃げちゃダメだ!」

といった感じになりましたとさ。おしまいおしまい...

ではなく!!!!!

AIを使わずに勉強をし、AIがなくてもコーディングができるようになろうと決心したんです。

(ありがとう、シンジ君。)

とまあ、おふざけはここまでにしておいて、

ちょうどタイミングよく、Mastra v1がリリースされたこともあり、Mastraの復習がてらこの悩みを解決するアプリを作ることにしました。

前置きが長くなりましたが、今回は、開発したアプリについて記事を書いていこうと思います。

アプリのコンセプト:普通のコードレビューは面白くない

せっかくなら、自分の書いたコードをレビューしてくれる、そんなアプリを作りたいなと思いました。

ただコードレビューするだけじゃ面白くないなぁと。

そんなとき、ふと、大学時代の研究室を思い出しました。

教授にめちゃくちゃ詰められた日々。(※もちろんですがとても尊敬と感謝でいっぱいです。)

「これ、なんでこう書いたの?」
「このデータってソースは?」
「ジャーナルにそんなの載せられると思っているの?」

うーん!懐かしいですねー!今となっては本当にいい経験でしたね。

と、僕は思うことができますが、人によっては、「トラウマだ!」と仰る方もいるのでは...

そんな中、僕の尊敬する弊社の先輩と話していて、こんな会話が生まれました。

「素人質問投げてくる教授AIって面白くない?」

それだ。

というわけで、「素人質問教授コードレビューAI」 が爆誕したわけです。

今回開発した「素人質問教授コードレビューAI」には主に下記の2つのモードが実装されています。

2つのモード

モード 特徴
やさしいモード チャット形式で1問ずつ質問。じっくり考えられる
鬼モード 一括で大量の質問を投げつける。容赦なし(かなり怖い)

基本的に鬼モードはなんか悲しくなるので、
もし鬼モードに挑戦し、詰められて悲しい時はやさしいモードを使ってくださいね...

デモ

やさしいモード

image.png

コードを貼ると、教授が「素人質問で恐縮ですが...」と1つずつ質問してきます。回答すると採点してくれ、問題箇所が無くなるまでさらに他の箇所を指摘してくれます。

まあ、やさしいモードと言いながらもちょっとこの時点で怖い感じあるんですけどね。

鬼モード

コードレビュー中

image.png

コードレビュー完了後

image.png

image.png

image.png

コードを貼ると、教授が容赦なく大量の質問を投げつけてきます。
これは怖いですよ、本当に。
なんせ、指摘箇所を一気に送ってくるあたり怖いですよね。
3枚目の「今すぐ直せ(言い訳無用)」って、あー怖い怖い!!

「素人質問で恐縮ですが...(恐縮などしていない)」

image.png

結果が表示される瞬間、画面中央にドーンと表示されて縮小していきます。鬼畜感マシマシ。

上記で用いている指摘箇所が多いコードは生成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がコードを書いてくれる時代だからこそ、自分の頭で考える力を鍛える必要があります。が大事なのではないかなと僕は思います。
(僕は開発の基礎の基礎からスタートになるので偉そうなことは言えませんが...笑)

今後も少しずつ強化し、良いタイミングでアプリとして公開できたらなと思っております!

フォルダ一式はこちら↓

最後まで読んでいただきありがとうございました!
「素人質問で恐縮ですが...」と詰められたい方は、ぜひ試してみてください!!!

???「恐縮などしていない」

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?