0
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?

Mastraで作る超簡易CiNii検索エージェント

0
Posted at

Mastra

Mastraは、TypeScript製AIエージェントフレームワークです。
Node.js v20以上があれば、簡単に始められますので、ここではセットアップなどは省略します。

CiNii超簡易検索エージェント

CiNiiは、論文、図書・雑誌や博士論文などの学術情報を、人工知能 研究」のような検索キーワードで検索するデータベース・サービスです。
今回は、これをMastraを使って、自然文で検索できるようにしてみます。
本来は検索APIを使った方がよいのですが、お遊び半分なので、簡易的にOpenSearchインターフェースからJSON形式でデータを取得しています。

.env

インストールすると、.env ファイルも用意されるので、そこにAPIキーを書き込みます。

.env
GOOGLE_GENERATIVE_AI_API_KEY=XXXXXX
HF_TOKEN=XXXXX

index.ts

index.ts
import { Mastra } from "@mastra/core/mastra";
import { PinoLogger } from "@mastra/loggers";
import { LibSQLStore } from "@mastra/libsql";
import {
  Observability,
  DefaultExporter,
  CloudExporter,
  SensitiveDataFilter,
} from "@mastra/observability";
import { weatherWorkflow } from "./workflows/weather-workflow";
import { weatherAgent } from "./agents/weather-agent";
import { CiNii_Agent } from "./agents/cinii_agent"; // これを追加
import {
  toolCallAppropriatenessScorer,
  completenessScorer,
  translationScorer,
} from "./scorers/weather-scorer";
import { ollama } from "ollama-ai-provider-v2";
import 'dotenv/config'; // .envからAPIキーを詠み込むために、これが最初の方に必要です

export const mastra = new Mastra({
  workflows: { weatherWorkflow },
  agents: { weatherAgent, CiNii_Agent }, // ここでimportしたエージェントを詠み込む
  scorers: {
    toolCallAppropriatenessScorer,
    completenessScorer,
    translationScorer,
  },
  storage: new LibSQLStore({
    id: "mastra-storage",
    // stores observability, scores, ... into persistent file storage
    url: "file:./mastra.db",
  }),
  logger: new PinoLogger({
    name: "Mastra",
    level: "info",
  }),
  observability: new Observability({
    configs: {
      default: {
        serviceName: "mastra",
        exporters: [
          new DefaultExporter(), // Persists traces to storage for Mastra Studio
          new CloudExporter(), // Sends traces to Mastra Cloud (if MASTRA_CLOUD_ACCESS_TOKEN is set)
        ],
        spanOutputProcessors: [
          new SensitiveDataFilter(), // Redacts sensitive data like passwords, tokens, keys
        ],
      },
    },
  }),
});

エージェント

インストールするとサンプルの
\src\mastra\agents\weather-agent.ts
が用意されていますが、
同じフォルダ \src\mastra\agents\ に、
以下のファイルを作成します。

cinii_agent.ts
import { Agent } from "@mastra/core/agent";
import { huggingface } from "@ai-sdk/huggingface"; // プロバイダーをインポート
import "dotenv/config";
import { fetchJsonTool } from "../tools/fetchJsonTool";
import { Memory } from "@mastra/memory";

export const CiNii_Agent = new Agent({
  id: "CiNii_Agent",
  name: "CiNii_Agent",
  model: "huggingface/Qwen/Qwen3.5-9B", // ここでモデルを指定
  // tools または enabledTools (バージョンに合わせて)
  tools: {
    fetchJsonTool, 
  },
  memory: new Memory({
    options: {
      lastMessages: 0, // 過去のメッセージをコンテキストに含めない
    },
  }),
  instructions: `
    必要に応じてAPIからデータを取得し、それを元にユーザーの質問に答えてください。
    あなたは学術情報のリサーチアシスタントです。
    
    ユーザーの依頼から検索キーワードを特定する際は、以下のルールを守ってください:
    1. 文章から重要な名詞を2〜3個抽出する。
    2.入力が英語など日本語以外の場合は、名詞を日本語に翻訳してください。
    3. 抽出したキーワードを半角スペースで繋いで fetchJsonTool の query に渡す。
    2. 抽出したキーワードを半角スペースで繋いで fetchJsonTool の query に渡す。
    (例:「日本の少子化と経済への影響」→「日本 少子化 経済」)
    
    fetchJsonTool は内部でこれらを " "(space) で結合して検索します。
    【重要】
    - 検索が必要な場合は fetchJsonTool を使用してください。
    - ツールを実行した後、その結果(JSON)をそのまま表示せず、
      内容を要約して「〜という論文が見つかりました」のように日本語で報告してください。
    - JSONの形式({"name": ...})のまま回答を終えないでください。
    3. 検索結果を、もとのクエリに関連する順番に並び替えてください。
    4. 検索結果の JSON に含まれる 'title' と 'url' の値を、そのまま箇条書きで出力してください。解釈や省略は不要です。
  `,
 });

今回私は、GeminiとhuggingfaceのAPIで試しましたが、Ollamaを使ってローカルLLMで試す場合は、以下のようにします。

import { ollama } from "ollama-ai-provider-v2";
・・・
  model: ollama("qwen3.5:9b"),  //"qwen3:0.6b"),

ただ、私の環境(ノートPC、GPUなし)では、非力で実用に耐えませんでした。

順番的には、cinii_agent.ts をモジュールとして export したものを、
index.ts で

import { CiNii_Agent } from "./agents/cinii_agent";
・・・
  agents: { weatherAgent, CiNii_Agent }, 

でインポートする、というのが正確なところです。

tool

さきほどのエージェントに

  tools: {
    fetchJsonTool, 
  },

としていた部分を
\src\mastra\tools\fetchJsonTool.ts
に作ります。

fetchJsonTool.ts
import { createTool } from "@mastra/core/tools";
import { z } from "zod";

export const fetchJsonTool = createTool({
  id: "fetchJsonTool",
  description: "CiNiiからJSONを取得。複数のキーワードはスペースで入力してください。",
  inputSchema: z.object({
    query: z.string().describe("検索クエリ(例:'人工知能 ロボット')"),
  }),
  outputSchema: z.object({
    data: z.any(),
    status: z.number(),
  }),
  execute: async ({ query }) => {
    // 1. 全角スペースを半角に変換し、前後の余白を削除
    // 2. スペースで分割して配列にする
    // 3. 空の文字列を除去して、"&" で結合する
    const formattedQuery = query
      .trim()
      .replace(/ /g, " ") // 全角を半角へ
      .split(/\s+/)        // 連続する空白で分割
      .join(" ");          // " " で繋ぐ

    return await getResult(formattedQuery);
  },
});

// tools/fetchJsonTool.ts の修正例
const getResult = async (query: string) => {
  const ciniiUrl = `https://cir.nii.ac.jp/opensearch/all?count=3&sortorder=0&format=json&q=${encodeURIComponent(query)}`;
  
  try {
    const response = await fetch(ciniiUrl);
    const data = await response.json();

    // 1. JSONのルートにある 'items' 配列を取得(存在しない場合は空配列)
    const items = data.items || [];

    // 2. 各 item から 'title' のみを抽出
    const titles = items.map((item: any) => ({
      title: item.title || "タイトルなし",
      url: item.link || "URLなし",
    }));

    // 3. 5件分など、Agentが扱いやすいように整形して返す
    return {
      data: titles,
      status: response.status,
    };
  } catch (error) {
    console.error("Fetch error:", error);
    return {
      data: [],
      status: 500,
    };
  }
};

冒頭で書いたとおり、簡易的に単純な fetchで、OpenSearchインターフェースからJSON形式でデータを取得しています。
テストで無料の範囲でやっているので、数件のみ取得しています。

起動

で、これを起動します。

npm run dev
> mymastraapp@1.0.0 dev
> mastra dev

 Preparing development environment...
 Initial bundle complete
 Starting Mastra dev server...
mastra-cloud-observability-exporter disabled: MASTRA_CLOUD_ACCESS_TOKEN environment variable not set.

 mastra  1.3.7 ready in 38686 ms

 Studio: http://localhost:4111
 API:    http://localhost:4111/api

 watching for file changes...

40秒くらいかかりました。
http://localhost:4111
にアクセスします。

回答例

生成AIによるエージェントの活用の動向について調べてください

とすると、

生成AIの活用に関する以下の論文が見つかりました。

生成AIの活用によるデスクワーカーの生産性向上 : AIエージェントは労働力不足を補うか? 
url: https://cir.nii.ac.jp/crid/1520307579484898048
MCP、AIエージェント連携による生成AIを用いた中小企業の活用事例 
url: https://cir.nii.ac.jp/crid/1390588534714008704
AIエージェントを活用した知財とマーケティング分析の自動化・効率化 : GeminiとPerplexity Patentsの連携による市場起点の技術開発戦略 
url: https://cir.nii.ac.jp/crid/1520307037461227136

のような結果が出ました。

LLMがキーワードを生成するので、英語で

tell me about Edo Bakufu?

などと聞いても、日本語で検索してくれます。

MCP不要

ちまたでは検索用のMCPサーバも流行っていますが、今回のような簡単なケースでは、検索結果を普通に fetch するだけでそこそこのことはできると思います。

0
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
0
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?