Node-REDで実現するAI論文生成フロー - Gemini 2.5 ProとOpenAI o3-miniを活用

## はじめに

[@zazen_inu](https://qiita.com/zazen_inu)さんの「[人類はもう生成AIに勝てないと痛感したDeep Researchの使い方](https://qiita.com/zazen_inu/items/5dc2ea32aa4de18c02c7)」と[@osamasao](https://qiita.com/osamasao)さんの「[OpenAI Agents SDK で「人類はもう生成AIに勝てないと痛感したDeep Researchの使い方」を自動化してみた](https://qiita.com/osamasao/items/ffbd58f741be6ac1ea4f)」を大変興味深く読みました。

最近、生成AIを活用したさまざまなツールやサービスが登場していますが、既存の開発ツールを使ってAIの力を手軽に取り入れる方法もあります。私はIoTの開発でよく使われるNode-REDもフロー作成、特に繰り返しや条件分岐に強みがあると感じています。

この記事ではNode-REDでAI論文生成フローを構築してみます。

## 前提知識

### Node-REDとは

[Node-RED](https://nodered.org/)は、IBMが開発したオープンソースのビジュアルプログラミングツールです。ブラウザ上でノードと呼ばれる機能ブロックを配置し、それらを線（ワイヤー）でつなぐことで、データのフローを視覚的に設計できます。IoTデバイスの制御やデータの収集・処理などに広く活用されていますが、APIを使った様々なサービスとの連携も容易で、AIサービスとの統合にも適しています。

![Screenshot 2025-04-02 at 11.53.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79562/7ef41393-2633-4214-95fc-1ea5354f20f7.png)


### 使用するAIモデル

本実装では、いま熱い注目を集める（？）2つのAIモデルを使用しました：

- **Gemini 2.5 Pro**: Googleが2025年3月に発表した最新のAIモデル。マルチモーダル（テキスト、画像、音声）に対応し、推論能力とコーディング能力が大幅に向上しています。特に複雑な思考プロセスを必要とするタスクで高いパフォーマンスを発揮します（まだExperiment版のみが提供されています）。

- **OpenAI o3-mini**: OpenAIが提供する高速かつ低コストなAIモデル。優れたSTEM（科学、技術、工学、数学）能力と低レイテンシを両立しています。論理的思考を要するタスクに適しているとのことです。

## フローの概要

今回実装するフローは、生成AIを活用して論文の構造に沿った文書を段階的に生成するもので、以下のステップで構成されています：

1. 背景と目的の設定・生成（OpenAI o3-mini）
2. 論文の全体像を作成（Gemini 2.5 Pro）
3. アブストラクト（要約）の作成（OpenAI o3-mini）
4. アブストラクトから詳細な論文を生成（Gemini 2.5 Pro）

この流れは、[@zazen_inu](https://qiita.com/zazen_inu)さんが提案した「論文フレームワーク」を活用した論理思考の組み立て方法に倣い、[@osamasao](https://qiita.com/osamasao)さんのフロー図を参考にしました。

## Node-REDフローの実装

### 準備

1. Node-REDをインストールして起動します。インストール方法は[公式ドキュメント](https://nodered.org/docs/getting-started/)を参照してください。

2. 環境変数を使っています。OpenAIとGoogleのAPIキーを取得し、環境変数に設定します。

### フロー構成

フローのJSONは長いので最下部にあります。

今回のフローは、4つの主要な部分に分かれています：

1. 「背景」と「目的」を作成するパート（OpenAI o3-mini）
2. 背景・目的から論文の全体像を作成するパート（Gemini 2.5 Pro）
3. 論文のアブストラクト（要約）を作成するパート（OpenAI o3-mini）
4. アブストラクトから詳細な論文を生成するパート（Gemini 2.5 Pro）

各パートは、以下のような共通構造を持ちます：
- フロー開始のトリガー（injectノード）
- プロンプトとペイロードの構築（functionノード）
- APIリクエストのURL/ヘッダー設定（functionノード）
- HTTP APIリクエスト（http requestノード）
- レスポンス処理とテキスト抽出（functionノード）
- 結果表示（debugノード）

### 1. 「背景」と「目的」を作るフロー

このパートでは、OpenAI o3-miniを使用して、与えられた背景と目的の文章を要約・整理します。こうする必要はないのですが、function callingで値を取得するようにしました。

```javascript
// プロンプト構築用のfunctionノード
const tools = [
  {
    type: "function",
    function: {
      name: "paper_completion",
      description: "背景・目的をもとに、論文を完成させます",
      strict: true,
      parameters: {
        type: "object",
        properties: {
          background: {
            "type": "string",
            "value": "論文の背景",
          },
          objective: {
            "type": "string",
            "value": "論文の目的",
          }
        },
        required: ["background", "objective"],
        additionalProperties: false
      },
    }
  }
]

msg.payload = {
  "model": "o3-mini",
  "messages": [
    { role: "system", content: "与えられる背景と目的を3-5行で要約し、内容に飛躍があれば修正します。" },
    {
      role: "user",
      content: [
        {
          type: "text", text: `
### 背景
${flow.get("background")}

### 目的
${flow.get("objective")}
        ` },
      ]
    }
  ],
  tools: tools,
  reasoning_effort: "high",  // low, medium, highが選択可能
  tool_choice: "required"
};
return msg;
```

### 2. 背景・目的から論文の全体像を書かせるフロー

このパートでは、Gemini 2.5 Proを使用して、背景と目的から論文の全体像を生成します。

```javascript
// プロンプト構築用のfunctionノード
msg.payload = {
  contents: [
    {
      role: "user",
      parts: [
        { text: `
### 背景
${flow.get("background")}

### 目的
${flow.get("objective")}
        ` },
      ]
    }
  ],
  systemInstruction: {
    parts: [
      {
        text: `与えられる論文の背景・目的をもとに、論文を完成させてください。
- 論文の構成は①背景②目的③材料と方法④結果⑤考察⑥結論のように、形式を守ってください。
- 不明な数値があれば、不正確な値を入れずフェルミ推定などの推定を行い、推定値を代入してください。
- ただし、推定値には必ず（推定値）と明記してください。
- 文字数はおよそ1万文字とします。
`,
      }
    ]
  }
};
return msg;
```

### 3. 論文のアブストラクト（要約）を書かせるフロー

このパートでは、OpenAI o3-miniを使用して、論文のアブストラクト（要約）を作成します。

```javascript
// プロンプト構築用のfunctionノード
msg.payload = {
  "model": "o3-mini",
  "messages": [
    { role: "system", content: "与えられる論文のアブストラクトを作成してください。推定値はアブストラクトに入らないように考慮してください。" },
    {
      role: "user",
      content: [
        {
          type: "text", text: `${msg.payload.text}` },
      ]
    }
  ],
  reasoning_effort: "high",  // low, medium, highが選択可能
};
return msg;
```

### 4. アブストラクトから、論文を書かせるフロー

最後に、Gemini 2.5 Proを使用して、アブストラクトから詳細な論文を生成します。

```javascript
// プロンプト構築用のfunctionノード
msg.payload = {
  contents: [
    {
      role: "user",
      parts: [
        { text: `${msg.payload.text}` },
      ]
    }
  ],
  systemInstruction: {
    parts: [
      {
        text: `
### 依頼

与えられるアブストラクトをもとに、論文を完成してください。

### 条件

- 推定値で研究を作成しているため、先行研究から得られる値は代入してください。
- 先行研究などの参考文献が得られることを期待しています。信頼性が高いものを選んでください
- 新規性の検討等、論文としてのロバスト性を高めてください
`
      }
    ]
  }
};
return msg;
```

## 動作の様子

<blockquote class="twitter-tweet" data-media-max-width="560"><p lang="ja" dir="ltr">Node-RED + Gemini 2.5 Pro + OpenAI o3-mini でAIフローっぽいものをつくってみました <a href="https://t.co/xvIeqo3Bpj">pic.twitter.com/xvIeqo3Bpj</a></p>&mdash; よなか (@yonaka158) <a href="https://twitter.com/yonaka158/status/1907270365694599669?ref_src=twsrc%5Etfw">April 2, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 使用方法

1. Node-REDのフローエディタに最下部JSONのフローをインポートします。
2. 「1. 背景と目的を設定」ノードをクリックして、初期値を設定します。
2. AIが書き直した背景と目的を確認します。
2. さらに推敲してほしい場合は「2. 背景と目的を再度生成する」ノードをクリックします。
3. 「3. 論文記述開始」ノードをクリックして本文を記述してもらいます。
4. 各ステップの結果は、デバッグタブで確認できます。

## AIモデルの選択について

本実装では、異なるプロバイダのAIモデルを組み合わせて使用しました。それぞれの特徴を活かした使い分けをしています：

- **OpenAI o3-mini**: 速度と効率が求められるタスク（要約やテキスト整形）に使用
- **Gemini 2.5 Pro**: 複雑な思考や幅広い知識が必要なタスク（論文全体の構成や詳細な内容の生成）に使用

異なるモデルを組み合わせることで、各モデルの強みを活かしつつ、全体のパフォーマンスとコストのバランスを取ります。

## メリットと考察

### Node-REDでAIフローを構築するメリット

1. **視覚的なフロー設計**: 複雑なAIプロセスも視覚的に設計・管理できるため、理解しやすく、修正も容易です。

2. **簡単なAPI連携**: OpenAIやGoogleなどのAPIとの連携が容易で、専門的なプログラミング知識が少なくても実装できます。

3. **段階的処理の自動化**: 複数のAIモデルを連携させて段階的に処理を行うフローを簡単に構築できます。

4. **拡張性**: 既存のNode-REDエコシステムのノードを活用して、AIフローを他のシステムやサービスと連携させることができます。

## 論文フレームワークの効果

**背景と目的を繰り返し生成し、それをユーザーがチェックするというプロセス**は、最終的に得られる論文を目的にあったものに近づける上で効果的だと感じました。

また、**一度生成した論文からさらによりよい成果物を得させようとする手法**はまさにエージェンティックで参考になりました。

## 発展的活用方法

Node-REDを使うことでさらにエージェント的な使い方ができます：

1. **ユーザーインターフェースの追加**: Webインターフェースを追加し、ブラウザから操作できるようにする

2. **複数のAIモデルの比較実行**: 同じプロンプトで異なるAIモデルの結果を比較できるよう拡張する

3. **外部データ連携**: ウェブ検索結果やデータベースからの情報取得を組み込み、より充実した内容生成を可能にする

4. **定期実行と通知**: Node-REDは定期実行も得意です。特定のトピックについて定期的に最新の論文を生成し、結果をメールやSlackに通知する機能を追加する

## まとめ

Node-REDとAI APIを組み合わせることで、複雑なAI処理フローを視覚的に設計・実装することができるのではないでしょうか。今回紹介した論文生成フローが、AIの能力を引き出すためのフレームワークの一例となれば幸いです。

## 参考資料

1. [人類はもう生成AIに勝てないと痛感したDeep Researchの使い方](https://qiita.com/zazen_inu/items/5dc2ea32aa4de18c02c7) - @zazen_inu
2. [OpenAI Agents SDK で「人類はもう生成AIに勝てないと痛感したDeep Researchの使い方」を自動化してみた](https://qiita.com/osamasao/items/ffbd58f741be6ac1ea4f) - @osamasao
3. [Node-RED公式ドキュメント](https://nodered.org/docs/)
4. [Google Gemini 2.5 公式ブログ](https://blog.google/technology/google-deepmind/gemini-model-thinking-updates-march-2025/)
5. [OpenAI o3モデルについて](https://openai.com/blog/o3/)


```json
[
    {
        "id": "6476eb6bcc7aeafc",
        "type": "function",
        "z": "743c65fa15f4ddf8",
        "name": "Set Query",
        "func": "flow.set(\"background\", \"多くのAIツールが新たに登場しているが、以前から存在するNode-REDに私は注目している\");\nflow.set(\"objective\", \"Node-REDがAIツールとして使えることを示す\");\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 300,
        "y": 220,
        "wires": [
            [
                "6ca7c252661fa1e8"
            ]
        ]
    },
    {
        "id": "d79146319c3cd06d",
        "type": "inject",
        "z": "743c65fa15f4ddf8",
        "name": "1. 背景と目的を設定",
        "props": [],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 130,
        "y": 220,
        "wires": [
            [
                "6476eb6bcc7aeafc"
            ]
        ]
    },
    {
        "id": "f9b6fd61e49814df",
        "type": "function",
        "z": "743c65fa15f4ddf8",
        "name": "背景と目的の取り出し",
        "func": "const arguments = JSON.parse(msg.payload.choices[0].message.tool_calls[0].function.arguments);\nconst background = arguments.background;\nconst objective = arguments.objective;\n\nflow.set(\"background\", background);\nflow.set(\"objective\", objective);\n\nmsg.payload = {\n    background,\n    objective\n}\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 820,
        "y": 380,
        "wires": [
            [
                "716f06bca3cabd05",
                "59558259ffa07623"
            ]
        ]
    },
    {
        "id": "56047e54f486a735",
        "type": "inject",
        "z": "743c65fa15f4ddf8",
        "name": "2. 背景と目的を再度生成する",
        "props": [],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 160,
        "y": 300,
        "wires": [
            [
                "6ca7c252661fa1e8"
            ]
        ]
    },
    {
        "id": "716f06bca3cabd05",
        "type": "debug",
        "z": "743c65fa15f4ddf8",
        "name": "背景",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload.background",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1010,
        "y": 360,
        "wires": []
    },
    {
        "id": "59558259ffa07623",
        "type": "debug",
        "z": "743c65fa15f4ddf8",
        "name": "目的",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload.objective",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1010,
        "y": 400,
        "wires": []
    },
    {
        "id": "6c37183ad3f771de",
        "type": "inject",
        "z": "743c65fa15f4ddf8",
        "name": "3. 論文記述開始",
        "props": [],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 140,
        "y": 580,
        "wires": [
            [
                "94fb8d24096893fa"
            ]
        ]
    },
    {
        "id": "0ca4acc4124f0b74",
        "type": "function",
        "z": "743c65fa15f4ddf8",
        "name": "Extract text",
        "func": "const text = msg.payload.candidates[0].content.parts[0].text;\nmsg.payload = {\n    text\n}\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 530,
        "y": 720,
        "wires": [
            [
                "1e6283143ecfd882",
                "3f815545ebed8a63"
            ]
        ]
    },
    {
        "id": "1e6283143ecfd882",
        "type": "debug",
        "z": "743c65fa15f4ddf8",
        "name": "全体像",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload.text",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 750,
        "y": 720,
        "wires": []
    },
    {
        "id": "874b5dcb83449c29",
        "type": "function",
        "z": "743c65fa15f4ddf8",
        "name": "Extract text",
        "func": "const text = msg.payload.choices[0].message.content;\nmsg.payload = {\n    text\n};\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 530,
        "y": 960,
        "wires": [
            [
                "4f2f9e5de66275f4",
                "849a80b9c002b666"
            ]
        ]
    },
    {
        "id": "4f2f9e5de66275f4",
        "type": "debug",
        "z": "743c65fa15f4ddf8",
        "name": "概要",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload.text",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 770,
        "y": 960,
        "wires": []
    },
    {
        "id": "b9b2721f140ed0ab",
        "type": "function",
        "z": "743c65fa15f4ddf8",
        "name": "Extract text",
        "func": "const text = msg.payload.candidates[0].content.parts[0].text;\nmsg.payload = {\n    text\n}\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 510,
        "y": 1220,
        "wires": [
            [
                "6242a925d0f6666e"
            ]
        ]
    },
    {
        "id": "6242a925d0f6666e",
        "type": "debug",
        "z": "743c65fa15f4ddf8",
        "name": "本論文",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload.text",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 770,
        "y": 1220,
        "wires": []
    },
    {
        "id": "97404cd231e0862e",
        "type": "group",
        "z": "743c65fa15f4ddf8",
        "name": "「背景」と「目的」を作る（OpenAI o3-mini)",
        "style": {
            "fill": "#bfdbef",
            "label": true,
            "color": "#777777"
        },
        "nodes": [
            "e84b0fcda1193f72",
            "fd503f1bf5fce744",
            "92a240d10d37abe8",
            "6ca7c252661fa1e8"
        ],
        "x": 394,
        "y": 219,
        "w": 692,
        "h": 82
    },
    {
        "id": "e84b0fcda1193f72",
        "type": "function",
        "z": "743c65fa15f4ddf8",
        "g": "97404cd231e0862e",
        "name": "url and headers",
        "func": "msg.url = \"https://api.openai.com/v1/chat/completions\";\n\nmsg.headers = {\n    \"Content-Type\": \"application/json\",\n    Authorization: `Bearer ${env.get(\"OPENAI_API_KEY\")}`\n};\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 640,
        "y": 260,
        "wires": [
            [
                "fd503f1bf5fce744"
            ]
        ]
    },
    {
        "id": "fd503f1bf5fce744",
        "type": "http request",
        "z": "743c65fa15f4ddf8",
        "g": "97404cd231e0862e",
        "name": "",
        "method": "POST",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 810,
        "y": 260,
        "wires": [
            [
                "92a240d10d37abe8",
                "f9b6fd61e49814df"
            ]
        ]
    },
    {
        "id": "92a240d10d37abe8",
        "type": "debug",
        "z": "743c65fa15f4ddf8",
        "g": "97404cd231e0862e",
        "name": "debug 49",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 980,
        "y": 260,
        "wires": []
    },
    {
        "id": "6ca7c252661fa1e8",
        "type": "function",
        "z": "743c65fa15f4ddf8",
        "g": "97404cd231e0862e",
        "name": "payload",
        "func": "const tools = [\n  {\n    type: \"function\",\n    function: {\n      name: \"paper_completion\",\n      description: \"背景・目的をもとに、論文を完成させます\",\n      strict: true,\n      parameters: {\n        type: \"object\",\n        properties: {\n          background: {\n            \"type\": \"string\",\n            \"value\": \"論文の背景\",\n          },\n          objective: {\n            \"type\": \"string\",\n            \"value\": \"論文の目的\",\n          }\n        },\n        required: [\"background\", \"objective\"],\n        additionalProperties: false\n      },\n    }\n  }\n]\n\n\nmsg.payload = {\n  \"model\": \"o3-mini\",\n  \"messages\": [\n    { role: \"system\", content: \"与えられる背景と目的を3-5行で要約し、内容に飛躍があれば修正します。\" },\n    {\n      role: \"user\",\n      content: [\n        {\n          type: \"text\", text: `\n### 背景\n${flow.get(\"background\")}\n\n### 目的\n${flow.get(\"objective\")}\n        ` },\n      ]\n    }\n  ],\n  tools: tools,\n  reasoning_effort: \"high\",  // low, medium, highが選択可能\n  tool_choice: \"required\"\n};\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 480,
        "y": 260,
        "wires": [
            [
                "e84b0fcda1193f72"
            ]
        ]
    },
    {
        "id": "7d58a87b17bcb1ff",
        "type": "group",
        "z": "743c65fa15f4ddf8",
        "name": "背景・目的から論文の全体像を書かせる (Gemini 2.5 Pro)",
        "style": {
            "fill": "#bfdbef",
            "label": true,
            "color": "#777777"
        },
        "nodes": [
            "740a06178cc9c8a9",
            "a17a420e266fa94f",
            "b541aff3e496beb0",
            "94fb8d24096893fa"
        ],
        "x": 274,
        "y": 539,
        "w": 672,
        "h": 82
    },
    {
        "id": "740a06178cc9c8a9",
        "type": "function",
        "z": "743c65fa15f4ddf8",
        "g": "7d58a87b17bcb1ff",
        "name": "url and headers",
        "func": "msg.url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro-exp-03-25:generateContent?key=${env.get(\"GEMINI_API_KEY\")}`;\n\nmsg.headers = {\n    \"Content-Type\": \"application/json\"\n};\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 520,
        "y": 580,
        "wires": [
            [
                "a17a420e266fa94f"
            ]
        ]
    },
    {
        "id": "a17a420e266fa94f",
        "type": "http request",
        "z": "743c65fa15f4ddf8",
        "g": "7d58a87b17bcb1ff",
        "name": "",
        "method": "POST",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 690,
        "y": 580,
        "wires": [
            [
                "b541aff3e496beb0",
                "0ca4acc4124f0b74"
            ]
        ]
    },
    {
        "id": "b541aff3e496beb0",
        "type": "debug",
        "z": "743c65fa15f4ddf8",
        "g": "7d58a87b17bcb1ff",
        "name": "debug 51",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 840,
        "y": 580,
        "wires": []
    },
    {
        "id": "94fb8d24096893fa",
        "type": "function",
        "z": "743c65fa15f4ddf8",
        "g": "7d58a87b17bcb1ff",
        "name": "payload",
        "func": "msg.payload = {\n  contents: [\n    {\n      role: \"user\",\n      parts: [\n        { text: `\n### 背景\n${flow.get(\"background\")}\n\n### 目的\n${flow.get(\"objective\")}\n        ` },\n      ]\n    }\n  ],\n  systemInstruction: {\n    parts: [\n      {\n        text: `与えられる論文の背景・目的をもとに、論文を完成させてください。\n- 論文の構成は①背景②目的③材料と方法④結果⑤考察⑥結論のように、形式を守ってください。\n- 不明な数値があれば、不正確な値を入れずフェルミ推定などの推定を行い、推定値を代入してください。\n- ただし、推定値には必ず（推定値）と明記してください。\n- 文字数はおよそ1万文字とします。\n`,\n      }\n    ]\n  }\n};\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 360,
        "y": 580,
        "wires": [
            [
                "740a06178cc9c8a9"
            ]
        ]
    },
    {
        "id": "41cc49ac8c7428c0",
        "type": "group",
        "z": "743c65fa15f4ddf8",
        "name": "アブストラクトから、論文を書かせる (Gemini 2.5 Pro)",
        "style": {
            "fill": "#bfdbef",
            "label": true,
            "color": "#777777"
        },
        "nodes": [
            "be7bfb2803dc51c3",
            "463b6f27dcabd9eb",
            "60cc0271880f6679",
            "849a80b9c002b666"
        ],
        "x": 274,
        "y": 1039,
        "w": 672,
        "h": 82
    },
    {
        "id": "be7bfb2803dc51c3",
        "type": "function",
        "z": "743c65fa15f4ddf8",
        "g": "41cc49ac8c7428c0",
        "name": "url and headers",
        "func": "msg.url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro-exp-03-25:generateContent?key=${env.get(\"GEMINI_API_KEY\")}`;\n\nmsg.headers = {\n    \"Content-Type\": \"application/json\"\n};\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 520,
        "y": 1080,
        "wires": [
            [
                "463b6f27dcabd9eb"
            ]
        ]
    },
    {
        "id": "463b6f27dcabd9eb",
        "type": "http request",
        "z": "743c65fa15f4ddf8",
        "g": "41cc49ac8c7428c0",
        "name": "",
        "method": "POST",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 690,
        "y": 1080,
        "wires": [
            [
                "60cc0271880f6679",
                "b9b2721f140ed0ab"
            ]
        ]
    },
    {
        "id": "60cc0271880f6679",
        "type": "debug",
        "z": "743c65fa15f4ddf8",
        "g": "41cc49ac8c7428c0",
        "name": "debug 50",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 840,
        "y": 1080,
        "wires": []
    },
    {
        "id": "849a80b9c002b666",
        "type": "function",
        "z": "743c65fa15f4ddf8",
        "g": "41cc49ac8c7428c0",
        "name": "payload",
        "func": "msg.payload = {\n  contents: [\n    {\n      role: \"user\",\n      parts: [\n        { text: `${msg.payload.text}` },\n      ]\n    }\n  ],\n  systemInstruction: {\n    parts: [\n      {\n        text: `\n### 依頼\n\n与えられるアブストラクトをもとに、論文を完成してください。\n\n### 条件\n\n- 推定値で研究を作成しているため、先行研究から得られる値は代入してください。\n- 先行研究などの参考文献が得られることを期待しています。信頼性が高いものを選んでください\n- 新規性の検討等、論文としてのロバスト性を高めてください\n`\n      }\n    ]\n  }\n};\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 360,
        "y": 1080,
        "wires": [
            [
                "be7bfb2803dc51c3"
            ]
        ]
    },
    {
        "id": "60fb7a571369e879",
        "type": "group",
        "z": "743c65fa15f4ddf8",
        "name": "論文のアブストラクト（要約）を書かせる（OpenAI o3-mini)",
        "style": {
            "fill": "#bfdbef",
            "label": true,
            "color": "#777777"
        },
        "nodes": [
            "43e9d367668338e0",
            "57d6aff31ab1c259",
            "a7e5dc0914b3266f",
            "3f815545ebed8a63"
        ],
        "x": 274,
        "y": 819,
        "w": 692,
        "h": 82
    },
    {
        "id": "43e9d367668338e0",
        "type": "function",
        "z": "743c65fa15f4ddf8",
        "g": "60fb7a571369e879",
        "name": "url and headers",
        "func": "msg.url = \"https://api.openai.com/v1/chat/completions\";\n\nmsg.headers = {\n    \"Content-Type\": \"application/json\",\n    Authorization: `Bearer ${env.get(\"OPENAI_API_KEY\")}`\n};\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 520,
        "y": 860,
        "wires": [
            [
                "57d6aff31ab1c259"
            ]
        ]
    },
    {
        "id": "57d6aff31ab1c259",
        "type": "http request",
        "z": "743c65fa15f4ddf8",
        "g": "60fb7a571369e879",
        "name": "",
        "method": "POST",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 690,
        "y": 860,
        "wires": [
            [
                "a7e5dc0914b3266f",
                "874b5dcb83449c29"
            ]
        ]
    },
    {
        "id": "a7e5dc0914b3266f",
        "type": "debug",
        "z": "743c65fa15f4ddf8",
        "g": "60fb7a571369e879",
        "name": "debug 52",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 860,
        "y": 860,
        "wires": []
    },
    {
        "id": "3f815545ebed8a63",
        "type": "function",
        "z": "743c65fa15f4ddf8",
        "g": "60fb7a571369e879",
        "name": "payload",
        "func": "msg.payload = {\n  \"model\": \"o3-mini\",\n  \"messages\": [\n    { role: \"system\", content: \"与えられる論文のアブストラクトを作成してください。推定値はアブストラクトに入らないように考慮してください。\" },\n    {\n      role: \"user\",\n      content: [\n        {\n          type: \"text\", text: `${msg.payload.text}` },\n      ]\n    }\n  ],\n  reasoning_effort: \"high\",  // low, medium, highが選択可能\n};\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 360,
        "y": 860,
        "wires": [
            [
                "43e9d367668338e0"
            ]
        ]
    }
]
```