Gemcook Tech Blog
🤖

VoltAgentで作るAIエージェント

に公開

はじめに

AI エージェント開発、盛り上がっていますよね! でも、実際に手を動かしてみると「うーん、中で何が起こってるんだ?」「デバッグが思ったより大変…」なんて壁にぶつかること、ありませんか?

2024 年に TypeScript ベースの AI エージェントフレームワークとして旋風を巻き起こした Mastra は、確かに画期的でした。多くの開発者がその可能性に熱狂し、あっという間にスターを集めたのも記憶に新しいでしょう。

しかし、Mastra がワークフローや RAG(Retrieval Augmented Generation)に強みを持つのに対し、「もっとエージェントの“気持ち”を理解したい」「開発中の手触り感を大事にしたい」と感じていた方もいるのではないでしょうか。

そんな TypeScript 開発者の皆さん、お待たせしました! 2025 年 4 月、VoltAgent という新たな選択肢が登場しました。

VoltAgentの最大の魅力は、すべてを使い慣れた TypeScript のコードで記述できる点。そして何より、エージェントが今何をしていて、どんな判断を下しているのかをリアルタイムでグラフィカルに確認できる専用コンソールが用意されていること。まるで、いつも使っているブラウザのデベロッパーツールのように、AI エージェントの内部を手に取るように把握できるのです。

この記事では、この VoltAgent を使って、実際に株価を取得する AI エージェントをゼロから作っていきます。「AI エージェント開発って、こんなにスムーズに進むんだ!」と、きっと驚くはず。Mastra とは一線を画す、VoltAgent ならではの開発体験を、ぜひ一緒に味わってみましょう!

環境

  • MacBook Pro (14-inch, M3, 2023)
  • OS: Mac OS 15.4.1
  • yarn: 1.22.22
  • node: 23.11.0

セットアップ

まず、VoltAgent のプロジェクトをセットアップします。以下のコマンドを実行して、my-agent-app という名前の新しい VoltAgent アプリケーションを作成します。

yarn create voltagent-app my-agent-app

インストール

プロジェクトが作成されたら、ディレクトリに移動し、開発サーバーを起動して動作を確認します。

cd  my-agent-app
yarn
yarn dev

これにより、ローカルで VoltAgent の開発環境が起動します。

開発環境

開発環境にアクセスする

HTTP Server になっている http://localhost:3141 にアクセスすると、以下の表示が出てきます。

サーバー

開発者コンソールにアクセスする

Developer Console になっている https://console.voltagent.dev にアクセスすると、以下のような表示が出てきます。
エージェントが「いつ」「何を」行っているのかを GUI で明確に把握できるのが Mastra とは違う良いところですね!

コンソール

株価取得 Agent を実装する

株価取得エージェントを VoltAgent で実装してみましょう。
今回はyahoo finance2を使用して、株価を取得する Agent を作成します。

ディレクトリ構成

my-agent-app/
├── src/
│   ├── agents/
│   │   └── stock.ts    # 株価エージェント
│   ├── tools/
│   │   └── stock.ts     # 株価 API ツール
│   └── index.ts # エントリーポイント
│
├── package.json
└── tsconfig.json

株価を取得するためのツールを定義します。VoltAgent のToolクラスを利用し、入力スキーマも TypeScript の型定義に近い形で記述できるのが特徴です。

src/tools/stock.ts
import { createTool } from '@voltagent/core';
import { z } from "zod";
import yahooFinance from 'yahoo-finance2';

// 株価取得のスキーマ
export const StockPriceSchema = z.object({
  symbol: z.string().describe("株式のシンボル(例:AAPL, GOOGL, 7203.T)"),
});

// 複数株式比較のスキーマ
export const MultipleStocksSchema = z.object({
  symbols: z.array(z.string()).describe("比較したい株式のシンボルの配列"),
});

// 株価履歴取得のスキーマ
export const StockHistorySchema = z.object({
  symbol: z.string().describe("株式のシンボル"),
  period: z.enum(['1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max']).default('1mo').describe("取得期間"),
});

// 現在の株価を取得するツール
export const getStockPriceTool = createTool({
  name: "get_stock_price",
  description: "指定された株式の現在の株価と詳細情報を取得します。日本株の場合は「.T」を付けてください(例:7203.T)",
  parameters: StockPriceSchema,
  execute: async (args) => {
    try {
      const quote = await yahooFinance.quote(args.symbol);

      return {
        symbol: quote.symbol,
        shortName: quote.shortName || quote.longName,
        currentPrice: quote.regularMarketPrice,
        currency: quote.currency,
        change: quote.regularMarketChange,
        changePercent: quote.regularMarketChangePercent,
        previousClose: quote.regularMarketPreviousClose,
        dayHigh: quote.regularMarketDayHigh,
        dayLow: quote.regularMarketDayLow,
        volume: quote.regularMarketVolume,
        marketCap: quote.marketCap,
        peRatio: quote.trailingPE,
        fiftyTwoWeekHigh: quote.fiftyTwoWeekHigh,
        fiftyTwoWeekLow: quote.fiftyTwoWeekLow,
        lastUpdated: new Date().toISOString(),
      };
    } catch (error) {
      throw new Error(`株式シンボル「${args.symbol}」の情報を取得できませんでした。正しいシンボルを入力してください。`);
    }
  },
});

// 複数の株式を比較するツール
export const compareStocksTool = createTool({
  name: "compare_stocks",
  description: "複数の株式の価格と基本情報を比較します",
  parameters: MultipleStocksSchema,
  execute: async (args) => {
    try {
      const quotes = await Promise.allSettled(
        args.symbols.map(symbol => yahooFinance.quote(symbol))
      );

      const results = quotes.map((result, index) => {
        if (result.status === 'fulfilled') {
          const quote = result.value;
          return {
            symbol: quote.symbol,
            shortName: quote.shortName || quote.longName,
            currentPrice: quote.regularMarketPrice,
            currency: quote.currency,
            change: quote.regularMarketChange,
            changePercent: quote.regularMarketChangePercent,
            marketCap: quote.marketCap,
            peRatio: quote.trailingPE,
          };
                 }
         return {
           symbol: args.symbols[index],
           error: "データを取得できませんでした",
         };
      });

      return {
        comparison: results,
        timestamp: new Date().toISOString(),
      };
    } catch (error) {
      throw new Error(`株式の比較中にエラーが発生しました: ${error instanceof Error ? error.message : String(error)}`);
    }
  },
});

// 株価履歴を取得するツール
export const getStockHistoryTool = createTool({
  name: "get_stock_history",
  description: "指定された期間の株価履歴データを取得します",
  parameters: StockHistorySchema,
  execute: async (args) => {
    try {
      const history = await yahooFinance.historical(args.symbol, {
        period1: getPeriodStartDate(args.period),
        period2: new Date(),
        interval: '1d',
      });

      const processedHistory = history.slice(-30).map(day => ({
        date: day.date.toISOString().split('T')[0],
        open: day.open,
        high: day.high,
        low: day.low,
        close: day.close,
        volume: day.volume,
      }));

      const latestPrice = history[history.length - 1]?.close;
      const oldestPrice = history[0]?.close;
      const totalReturn = latestPrice && oldestPrice ?
        ((latestPrice - oldestPrice) / oldestPrice * 100) : null;

      return {
        symbol: args.symbol,
        period: args.period,
        history: processedHistory,
        totalReturn: totalReturn,
        dataPoints: history.length,
        timestamp: new Date().toISOString(),
      };
    } catch (error) {
      throw new Error(`株式「${args.symbol}」の履歴データを取得できませんでした: ${error instanceof Error ? error.message : String(error)}`);
    }
  },
});

// 期間から開始日を計算するヘルパー関数
function getPeriodStartDate(period: string): Date {
  const now = new Date();
  const startDate = new Date(now);

  switch (period) {
    case '1d':
      startDate.setDate(now.getDate() - 1);
      break;
    case '5d':
      startDate.setDate(now.getDate() - 5);
      break;
    case '1mo':
      startDate.setMonth(now.getMonth() - 1);
      break;
    case '3mo':
      startDate.setMonth(now.getMonth() - 3);
      break;
    case '6mo':
      startDate.setMonth(now.getMonth() - 6);
      break;
    case '1y':
      startDate.setFullYear(now.getFullYear() - 1);
      break;
    case '2y':
      startDate.setFullYear(now.getFullYear() - 2);
      break;
    case '5y':
      startDate.setFullYear(now.getFullYear() - 5);
      break;
    case '10y':
      startDate.setFullYear(now.getFullYear() - 10);
      break;
    case 'ytd':
      startDate.setMonth(0, 1); // 年初
      break;
    case 'max':
      startDate.setFullYear(1970, 0, 1); // 可能な限り古い日付
      break;
    default:
      startDate.setMonth(now.getMonth() - 1);
  }

  return startDate;
}

src/agents/stock.ts

このツールを利用するエージェントを定義します。ここでも、設定はすべて TypeScript コード内で行います。

src/agents/stock.agent.ts
import { Agent } from "@voltagent/core";
import { VercelAIProvider } from "@voltagent/vercel-ai";
import {
  getStockPriceTool,
  compareStocksTool,
  getStockHistoryTool
} from "../tools/stock";

import { openai } from "@ai-sdk/openai";

// @ts-ignore - VoltAgentの型推論が複雑すぎるため
export const stockAgent = new Agent({
  name: "株価分析エージェント",
  description: "株価情報を取得・分析する専門エージェントです。",
  llm: new VercelAIProvider(),
  model: openai("gpt-4o-mini"),
  tools: [getStockPriceTool, compareStocksTool, getStockHistoryTool],
  systemPrompt: "あなたは株価情報の専門家です。日本語で回答し、投資判断は個人の責任であることを明記してください。",
});

src/index.ts (VoltAgent 初期化・エントリーポイント)

src/index.ts
import "@hono/zod-openapi";
import { VoltAgent } from "@voltagent/core";
import { stockAgent } from "./agents/stock";

const app = new VoltAgent({
  agents: {
    stockAgent,
  },
});


export default app;

実際に動かしてみよう !

開発サーバーの起動

実装が完了したら、開発サーバーを再起動しましょう。

yarn dev

開発者コンソールでテスト

https://console.voltagent.devにアクセスして、実際にエージェントをテストしてみます。

テスト

例えば、「クアルコムの株価を教えて」と質問してみます。

サーバー

正常に株価情報が取得できました!エージェントがツールを適切に使用して、詳細な株価情報を提供しています。

デバッグ機能の活用

VoltAgent の開発者コンソールでは、ツールの実行詳細も確認できます。「Tools」をクリックすると、以下が表示されます。

  • INPUT: ツールに渡された引数
  • OUTPUT: ツールから返された結果

サーバー

この機能によりエージェントがどのようにツールを使用しているかを詳細に把握でき、デバッグや最適化に非常に役立ちます。

今回の体験から見えた VoltAgent の価値

株価取得エージェントの構築を通じて、VoltAgent の真価は「開発者がエージェントの動作を深く理解できること」にあると感じました。
AI エージェントは「なぜその判断をしたのか」「どのツールをなぜ選んだのか」が見えにくい技術です。

Mastra などのフレームワークでは、この問題に対してログ出力やプレイグラウンドでの事後確認が主なアプローチでした。しかし、これだけでは複雑なエージェントの動作を理解するには限界があります。
VoltAgent の可視化機能は、この「ブラックボックス問題」に対する新しいアプローチを提示しています。エージェントの思考プロセス、ツール選択の理由、データフローをリアルタイムで視覚的に把握できることで、開発者はエージェントの動作を直感的に理解し、問題箇所を素早く特定できます。
特に本番環境でのトラブルシューティングや、エージェントの動作最適化において、この透明性は他のフレームワークにはない大きな武器になるでしょう。

まとめ

AI エージェント開発の選択肢が増えることは、開発者にとって良いことです。VoltAgent は「TypeScript 開発者がストレスなく AI エージェントを作れる」という明確な価値提案を持っており、特にデバッグと開発体験にこだわりたい場面では強力な選択肢となるでしょう。

今回の株価エージェントは基本的な実装でしたが、VoltAgent の拡張性を考えると、より高度な金融分析機能や、複数エージェントの協調システムも十分実現可能です。TypeScript で AI エージェントを作ってみたい方は、ぜひ一度試してみてください。

参考資料

Gemcook Tech Blog
Gemcook Tech Blog

Discussion