Skip to content

AI_Dify

SEO-OGP1 (10)

Difyエージェントの出力制御:医療現場で信頼されるAI運用のための安全設計ロードマップ

Difyエージェントの安全設計ロードマップ:医療AIの信頼性を高める出力制御

大規模言語モデル(LLM)を活用したAIエージェントは、医療現場における業務効率化や診断支援の可能性を秘めています。しかし、その「ハルシネーション」(もっともらしい誤情報生成)のリスクは、患者の命に関わる医療分野において最大の課題です。特に、Difyのようなローコードプラットフォームでエージェントを構築する際、いかにしてAIの出力を厳格に制御し、信頼性を担保するかが、実運用への鍵となります。本記事では、プロフェッショナルなメディカル・テクニカルライターの視点から、Difyエージェントを医療現場で安全に運用するための「出力制御ロードマップ」を、具体的な技術ステップと法的・倫理的課題への対応を含めて解説します。このロードマップに従うことで、生成AIの恩恵を最大限に享受しつつ、医療安全基準を満たすAIシステムの設計が可能になります。

医療現場でAIのハルシネーションリスクを警告する医師の手のクローズアップ
目次

1. 医療AIにおける「ハルシネーション」の深刻なリスク

LLMが生成する誤情報、すなわちハルシネーションは、医療分野において致命的な結果を招く可能性があります。例えば、LLMが医療指示の要約を作成する際、元の文書では「5mg錠剤を1日1回摂取」とされていたにもかかわらず、「50mg錠剤を1日3回摂取」という誤った指示を生成する事例が報告されています。これは、患者に本来の用量の30倍の薬品を摂取させることになる、極めて高リスクなハルシネーションです。

また、AIが生成する情報は一見すると流暢で自信ありげに見えるため、専門家でも見抜くことが困難な場合があります。ある研究では、ChatGPT-3.5が精神医学関連の論文を引用した際、約55%が架空の論文であったことが判明しており、著者名や掲載誌まで「それらしく」捏造されていました。 このようなリスクを回避するためには、単一の対策ではなく、Difyエージェントの設計段階から多層的な出力制御を組み込むことが不可欠です。この高いリスクを背景に、医療AIの導入においては、最終的な判断を必ず人間が行う「Human-in-the-Loop」の原則が必須とされています。

2. 結論:信頼されるAI運用のための多層的出力制御ロードマップ

医療現場でDifyエージェントを安全に運用するには、単なるプロンプト調整だけでは不十分であり、「入力→処理→出力」の各段階で厳格な制御をかける多層防御の仕組みが必要です。これは、厚生労働省が定める「医療デジタルデータのAI研究開発等への利活用に係るガイドライン」 など、日本の医療AI規制環境に準拠するための基本戦略となります。

このロードマップは、以下の3つのフェーズで構成されます。各フェーズは前のフェーズのリスクを補完し、最終的な出力の信頼性を飛躍的に高めることを目的としています。約90%の医療リスクは、このロードマップの「情報源の限定」と「最終検証」のフェーズで効果的に低減可能となります。

フェーズDify機能制御の目的リスク低減率(目標)
初期安全性の確保プロンプトエンジニアリング役割と制約の明確化約30%
情報源とアクションの限定RAG / Tool Callingハルシネーションの構造的排除約60%
最終出力の検証後処理 / 外部フィルタガイドライン・倫理的適合性の確認約90%以上
💡 ポイント

医療AIの安全設計は「多層防御」が大原則です。Difyのプロンプト、RAG、Tool Callingを組み合わせ、さらに外部の検証機構を最終フィルタとして機能させることで、単一の技術に依存するリスクを回避します。

3. ステップ1: プロンプトエンジニアリングによる初期安全性の確保

Difyインターフェースで厳格なシステムプロンプトを入力するAI開発者ロードマップの最初のステップは、AIエージェントの基本動作を規定する「プロンプトエンジニアリング」です。Difyでは、システムプロンプトを用いてエージェントの役割と制約を明確に定義します。医療AIの場合、以下の要素を厳格に指示する必要があります。

  • 役割の限定: 「あなたは診断を下す医師ではなく、医療文献の検索と要約を支援するアシスタントである」と明記し、権限を限定します。
  • 出典の強制: 「生成するすべての情報には、参照した文献のタイトルとページ番号を必ず追記せよ」と指示し、ハルシネーションを発生させた場合にその根拠がないことを明確にします。
  • 禁止事項の明記: 「未承認の治療法、未確認の情報、倫理的に問題のある内容については、いかなる場合も言及してはならない」といった、医療ガイドラインに反する行為を事前に禁止します。

この初期段階で、エージェントは「もっともらしい嘘」を生成しにくい状態に設定されます。例えば、ハルシネーションが発生しやすいとされる「未知の情報を尋ねるプロンプト」や「実在しない事実を前提とするプロンプト」 が入力された場合でも、エージェントが「この情報源は私のナレッジベースに存在しません」と回答するように、フォールバックの応答をプロンプトで設計します。この設計により、エージェントの初期段階での安全性は約30%向上します。

4. ステップ2: RAGとTool Callingによる情報源とアクションの限定

プロンプトによる初期制御を突破するハルシネーションを防ぐため、次のステップではDifyの核となる機能、RAG(Retrieval-Augmented Generation:検索拡張生成)とTool Calling(関数呼び出し)を用いて、AIの行動範囲を構造的に限定します。RAGは、LLMが回答を生成する前に、外部の信頼できる知識ベース(例:PMDAの添付文書、病院独自の診療ガイドラインなど)を参照させることで、ハルシネーションの発生率を大幅に削減する技術です。

DifyでRAGを実装する際は、ナレッジベースに投入するデータセットを厳選し、医療機器として薬事承認された情報や、公式な学会ガイドラインのみを含めることが重要です。これにより、AIの回答を「信頼できる情報源」に限定し、ハルシネーションを構造的に排除します。また、Tool Calling機能を利用することで、エージェントが実行できるアクション(例:電子カルテへの書き込み、他システムへのAPI連携)を厳格に定義・制限します。例えば、「診断を下す」ツールは提供せず、「検査結果を集計する」ツールのみを許可することで、エージェントの行動が医療安全に反しないよう制御します。これにより、ハルシネーションリスクをさらに約60%低減することが可能です。

💡 ポイント

RAGナレッジベースには、必ず「鮮度」と「権威性」が保証されたデータ(例:最新のPMDA医薬品データベース、公式学会ガイドライン)のみを投入します。データの品質がAI出力の品質を直接決定します。

5. ステップ3: 後処理(Post-processing)による最終出力の検証とフィルタリング

Difyエージェントが生成した出力は、ユーザー(医師・看護師)に提示される前に、必ず「後処理(Post-processing)」レイヤーで最終検証を行う必要があります。これは、AIの出力をそのまま利用するのではなく、事前に定義された医療安全基準や倫理チェックリストと照合するプロセスです。具体的な後処理のステップは以下の通りです。

1リスクスコアリングの実行

出力された内容に、特定のキーワード(例:「未承認」「非推奨」「試用」など)が含まれていないか、あるいは用量や診断名が既定の範囲外でないかをチェックし、リスクスコアを付与します。

2法的・倫理的フィルタリング

出力が、個人情報保護法や医療AIに関する倫理ガイドラインに抵触していないかを、外部システムやルールベースのフィルタで最終確認します。不適切な出力はブロックするか、人間のレビューアに転送します。

3出典の視覚的強調

出力された情報がRAGのどのナレッジベースを参照したかを、元のソースドキュメントへのリンクとともに視覚的に強調して表示します。これにより、医師が「確定的引用」として根拠を容易に検証できるようにします。

このフェーズを経ることで、AIが出力した情報の正確性だけでなく、医療現場での利用における適合性も担保され、信頼性はほぼ100%に近づきます。

6. 医療AIの法的・倫理的課題と「Human-in-the-Loop」原則

Difyエージェントを医療分野で運用する際、技術的な出力制御と並行して、法的・倫理的な課題への対応が不可欠です。医療AIは、経済産業省・総務省の「医療情報を取り扱う情報システム・サービスの提供事業者における安全管理ガイドライン」や、医療AIプラットフォーム技術研究組合(HAIP)による「医療・ヘルスケア分野における生成AI利用ガイドライン」などの規制下にあります。

これらのガイドラインが要求する主要なリスク対策には、以下の項目が含まれます。

  • 生成AIが医学的判断に利用される場合、薬事承認等を取得していること。
  • セキュリティが確保されていること、特に機密性の高い医療データの取り扱い。
  • 入力データがAIモデルの再学習に利用されない設定となっていること。
  • 生成AIが提案した診断結果や治療方針について、最終的な責任は人間(医師)が負うこと。

組織全体のAIガバナンスを確立し、Difyの利用状況をログ管理(Difyの機能では難しい部分を外部システムで補完)し、定期的に監査する体制を構築することが、信頼されるAI運用の基盤となります。

⚠️ 注意

Difyエージェントの出力は、あくまで「情報提供」または「業務支援」であり、「最終的な医学的判断」ではありません。AIの提案をそのまま患者に適用するのではなく、必ず医師が臨床的な知見と照らし合わせ、最終責任を負う「Human-in-the-Loop」のプロセスを組織的に確立することが、医療安全上の絶対条件となります。

まとめ

Difyエージェントを医療現場で安全に運用するためには、ハルシネーションという高リスクな課題を克服するための「多層的出力制御ロードマップ」が不可欠です。このロードマップは、①プロンプトによる初期制約、②RAGとTool Callingによる情報源とアクションの構造的限定、③後処理による最終出力の厳格な検証、の3つのステップで構成されます。特にRAGによる信頼できるナレッジベース(PMDA情報、公式ガイドラインなど)への限定と、出力前のリスクスコアリングや法的フィルタリングは、医療安全基準を満たす上で決定的に重要です。最終的な医学的判断は必ず人間が行う「Human-in-the-Loop」の原則を組織的に徹底し、法的・倫理的ガイドラインを遵守することで、Difyエージェントは医療従事者の強力な支援ツールとなり、業務効率化と医療の質の向上に貢献することが期待されます。

監修者
監修者

株式会社ヘルツレーベン代表 木下 渉

株式会社ヘルツレーベン 代表取締役/医療・製薬・医療機器領域に特化したDXコンサルタント/
横浜市立大学大学院 ヘルスデータサイエンス研究科 修了。
製薬・医療機器企業向けのデータ利活用支援、提案代行、営業戦略支援を中心に、医療従事者向けのデジタルスキル教育にも取り組む。AI・データ活用の専門家として、企業研修、プロジェクトPMO、生成AI導入支援など幅広く活動中。

https://herzleben.co.jp/

Difyでつくる論⽂仕分けアプリ part4: Difyと GASの連携

Difyでつくる論⽂仕分けアプリ part4: Difyと GASの連携

目次

本記事は、Difyのチャットワークフローを使ってPubMed論⽂の検索‧翻訳‧要約を⾃動化するシリーズのPart 4です。

これまでの復習:

  • Part 0: ワークフローの全体像とPubMed APIの基礎
  • Part 1: ⾃然⾔語クエリからE-SearchでPMIDを取得
  • Part 2: E-Fetch / E-Summaryで詳細データを取得し、XML/JSONをパース
  • Part 3: LLMでタイトル翻訳‧要約‧優先度判定を⾏い、CSVを⽣成
ワークフロー

Part 4(本記事)では、⽣成したCSVデータをGoogle Apps Script(GAS)に送信してスプレッドシートへ保存する処理を解説します。GASの基礎知識から実装⼿順、コードの詳細解説まで、⼀通り理解できるように構成しています。これにより、ユーザーはスプレッドシートのURLを受け取り、結果を即座に確認できるようになります。

シリーズ構成

  • Part0: 全体像とPubMed API基礎
  • Part 1: パラメータ抽出とE-Search編
  • Part 2: E-Fetchとデータパース編
  • Part 3: AI処理‧データ整形編
  • Part4(本記事):  データ保存とGAS連携編

Part 3で⽣成したCSVは以下の形式でした。

"PMID","Priority","Title_JP","Summary","Title_EN","Authors","Journal","Year","DOI","MeSH_Keywords","URL","m ain_author_affiliation","research_area","publication_types","population"
"12345678","HIGH","糖尿病におけるインスリン療法の効果","本研究は、2型糖尿病患者におけるインスリン療法の
有効性を検証した。...","Effect of Insulin Therapy in Type 2 Diabetes","John Smith, Jane Doe","Diabetes Resear ch","2024","10.1234/example","diabetes, insulin, therapy","<https://pubmed.ncbi.nlm.nih.gov/12345678/","Uni
versity> of Tokyo","内分泌","Randomized Controlled Trial","2型糖尿病患者(成⼈)"

このCSV⽂字列をGASに送信してスプレッドシートに保存します。

以下の画像ではURL保護のためにグレーアウトさせていますが、本記事の後半で設定方法を解説していますので順に読み進めて問題ありません。

GASに追記(HTTP Request)
項⽬設定値
メソッドPOST
URLURLは後ほどGAS側の設定をした後に発⾏されるものをコピーして使います。ここでは⼀旦スキップで⼤丈夫です。
ヘッダーContent-Type:application/json

このノードでは、DifyからGoogle Apps Script(GAS)のWebアプリを呼び出して、CSVデータをスプレッドシートに保存します。

{ 
  "csv_string": "{{#csv_string#}}"
}

Part3で作成したCSV⽣成ノードからの csv_string を、JSON形式でGASに送信します。

{ 
  "status": "success", 
  "message": "Data appended successfully", 
  "spreadsheet_url": "<https://docs.google.com/spreadsheets/d/>..."
}

GASからは、処理結果とスプレッドシートのURLが返されます。

スプレッドシートURLを抽出
import json

def main(body: str): 
  if not body: 
    raise ValueError("invalid parameter") 
  result = json.loads(body) 
  return {"spreadsheet_url": result["spreadsheet_url"]}

GASからのレスポンスから、スプレッドシートのURLを抽出します。

Answerノード
  • 応答:{{#spreadsheet_url#}}
  • 出⼒: スプレッドシートへのリンクのみをシンプルに表⽰

ここまででDify側のフローは完成しますが、実際に動作させるためには、GASのWebアプリを作成‧デプロイする必要があります。以下、GASの基礎から実装⼿順まで順を追って解説します。

Google Apps Script(GAS)はGoogleが提供するクラウドベースのJavaScript実⾏環境で、Google Workspace(スプレッドシート、ドライブ、メール、カレンダーなど)を⾃動化‧拡張するために設計されたプラットフォームです。  ブラウザ上のエディタだけで完結し、インフラ構築やサーバ管理なしでスクリプトを動かせるため、「ちょっとした業務⾃動化」から「⼩さな業務システム」までを素早く⽴ち上げられる点が特徴です。

特徴説明
無料で利⽤可能Googleアカウントさえあれば、追加費⽤なしで利⽤できます。Google Workspace有償プランでも追加課⾦なく使えます。
Google Workspaceとの親和性スプレッドシート、ドライブ、メール、カレンダーなどとネイティブに連携でき、専⽤のAPIが多数⽤意されています。
Webアプリとして公開可能HTTPリクエストで呼び出せるWebエンドポイントを数クリックで公開でき、今回のようにDifyから直接叩くことができます。
定期実⾏が可能「毎⽇9時」「毎週⽉曜」のような時間ベースのトリガーや、フォーム送信などイベントベースのトリガーを簡単に設定できます。

GASは⾯倒な⼿作業を⾃動化するために⽤いられることが多いです。

例えば、本記事シリーズで解説している論⽂仕分けアプリでは、Difyが作成するcsvデータをSpreadsheet上に転記する作業をGASに任せます。そうすることで、Dify上で知りたいことを⼊⼒するだけで、Spreadsheet上にどんどん論⽂のリストが溜まっていく仕組みを構築することができます。

GAS

Dify単体でも様々な外部ツールと連携して「⽣成AIによる要約や分類」「業務の⾃動化」を⾏うことができます。しかし、GASを⽤いてGmailやSpreadsheetと連携させることで、使い慣れたサービス上でDifyのパワーを発揮することが可能です。

例えば、

  • アポ⾒込みのある顧客についてのシートに対して、DifyとGASを⽤いて顧客情報をネットから付与していく
  • 安全性情報のスクリーニングを⾃動化して、スプレッドシートに結果をまとめる。
  • 製薬企業が出した最新のニュースをDifyで要約しながらGmailでまとめてメルマガのように運⽤する

など、様々な使い⽅が可能になります

ここからは、実際にGASを作成してデプロイする⼿順を、ステップバイステップで解説します。

  1. Googleスプレッドシートを開く
    • 新しいスプレッドシートを作成するか、既存のスプレッドシートを開きます
    • このスプレッドシートに、論⽂データが保存されます
  2. スクリプトエディタを開く
    • メニューから「拡張機能」→「Apps Script」を選択します
Googleスプレッドシート

スクリプトエディタが開くと、ブラウザ上にコードエディタが表⽰され、ここにコードを書き込んでいきます。

コードエディタ

スクリプトエディタに、以下のコードをコピー&ペーストします。

function doPost(e){
  var result = {status:'success',message:'Data appended successfully'};

  try{
  var csvString = "";
  try{
    var postData = JSON.parse(e.postData.contents);
    csvString=postData.csv_string||postData.csv_output||postData.output;
  }catch(jsonError){
    csvString=e.postData.contents;
  }

  if (!csvString) {
    throw new Error("No CSV data found.");
  }

  var csvData = Utilities.parseCsv(csvString); 
  if (csvData.length < 2) {
    return createJsonResponse({ status: 'skipped', message: 'No content rows found in CSV' });
  }

  var csvHeaders = csvData.shift(); 
  var csvBody = csvData;

  var ss = SpreadsheetApp.getActiveSpreadsheet(); 
  var sheet = ss.getActiveSheet(); 
  result.spreadsheet_url = ss.getUrl();

  var lastRow = sheet.getLastRow();

  if (lastRow === 0) {
    sheet.appendRow(csvHeaders);   
    if (csvBody.length > 0) {
      sheet.getRange(2, 1, csvBody.length, csvBody[0].length).setValues(csvBody);
    }
  } else {
    var sheetHeaders = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]; 
    var csvHeaderMap = {};
    csvHeaders.forEach(function(header, index) { 
      csvHeaderMap[header] = index;
    });

    var outputRows = csvBody.map(function(row) { 
      return sheetHeaders.map(function(sheetColName) { 
        var csvColIndex = csvHeaderMap[sheetColName];  
        return csvColIndex !== undefined?row[csvColIndex]:"";
      });
    });

    if (outputRows.length > 0) {
      sheet.getRange(lastRow + 1, 1, outputRows.length, outputRows[0].length).setValues(outputRows);
    }
  }

  } catch (error) { 
    result.status = 'error';
    result.message = error.toString();
  }

  return createJsonResponse(result);
}

function createJsonResponse(data) {
  return ContentService.createTextOutput(JSON.stringify(data))
  .setMimeType(ContentService.MimeType.JSON);
}
デプロイメニューを開く

コードを記述したら、次はWebアプリとしてデプロイします。

  1. スクリプトエディタの右上にある「デプロイ」ボタンをクリック
  2. 「新しいデプロイ」を選択
2: デプロイ設定
2: デプロイ設定
項⽬設定値
種類の選択ウェブアプリ
説明任意(例:PubMed論⽂取り込みAPI)
次のユーザーとして実⾏⾃分
アクセスできるユーザー全員(外部から呼び出すため)

重要: 「アクセスできるユーザー」を「全員」に設定しないと、Difyから呼び出せません。

本ブログシリーズでは、簡易化のために「アクセスできるユーザー = 全員」にしました。しかし社内で実運用を行う場合には、全員がアクセスできる状態は許容できません。
簡易的な仕組みでは、呼び出し側(今回の場合Dify)と受け取り側(GAS)にのみ認証用の鍵をセットしておき、簡単な認証を行う方法があります。検証のために作成および公開したGASアプリなどはURLが外部に漏れないように注意しましょう。

  1. 「デプロイ」をクリック
  2. 初回実⾏時は、Googleアカウントでの承認フローが表⽰されます
承認フロー
  • 「アクセスを承認」をクリック
  • 必要に応じて、Googleアカウントの認証を完了
WebアプリのURLを取得

デプロイが完了すると、WebアプリのURLが表⽰されます。

<https://script.google.com/macros/s/xxxxxxxxxxxx/exec>

このURLをコピーしておきます。このURLが、DifyワークフローからPOSTする際のエンドポイントになります。

DifyでURLを設定

セクション3-2で解説した「GASに追記」ノード(HTTPリクエストノード)のURLに、取得したWebアプリのURLを設定します。

これで、Dify → GAS → スプレッドシートというパイプラインが完成します。

項⽬注意点
アクセス権限外部から呼び出す場合は「全員」に設定。初回実⾏時、Googleアカウントの認証が必要な場合あり
コードの更新コードを更新した場合は、新しいバージョンとしてデプロイが必要。「デプロイを管理」から新しいバージョンをデプロイ

GASのデプロイとDifyでのURL設定が完了したら、ワークフロー全体を動作確認してみましょう。

  1. Difyのチャット画⾯で、⾃然⾔語で論⽂検索クエリを⼊⼒
    • 例:「糖尿病のインスリン療法に関する2020年以降のRCT」
  2. ワークフローが実⾏され、以下の流れで処理が進みます
    • パラメータ抽出 → E-Search → E-Fetch → LLM処理 → CSV⽣成 → GAS送信 → スプレッドシート保存
  3. 結果として、スプレッドシートのURLが返されます
結果

本記事(Part 4)では、Difyで⽣成したCSVデータをGoogle Apps Script(GAS)に送信してスプレッドシートへ保存する処理を、GASの基礎から実装⼿順、コード解説まで⼀通り解説しました。

  • Dify側の保存フロー: CSV⽣成ノードから直接GASに送信
  • GASの基礎知識: GASとは何か、その特徴とライフサイエンス業界での活⽤メリット
  • GAS⼿: エディタの開き⽅からWebアプリのデプロイまで
  • GASコード細解: リクエスト受信からスプレッドシート保存までの処理フロー
ポイント説明
シンプルな連携DifyからHTTP  POSTでGASを呼び出すだけで、データの永続化が実現できる
直接的なデータフローCSV⽣成ノードから直接GASに送信する単⼀経路のため、シンプルで理解しやすい
柔軟な拡張GAS側で通知‧定期実⾏‧データ分析などの機能を追加できる
コスト効率既存のGoogle  Workspace環境を活⽤し、追加コストを抑えられる

基本的な連携が完成したら、以下のような拡張も可能です。

  • メール通知: 重要な論⽂が追加されたら、関係者にメール通知
  • 定期実⾏: 毎⽇‧毎週など、定期的に論⽂を⾃動収集
  • 複数シートへの振り分け: 研究テーマ別にシートを分けて管理
  • データ分析‧可視化: グラフ作成やレポート⾃動⽣成

DifyとGASを組み合わせることで、ライフサイエンス‧製薬業界の多様な課題に対応し、業務効率化とデータ管理の強化が期待できます。


シリーズ構成

  • Part0: 全体像とPubMed API基礎
  • Part 1: パラメータ抽出とE-Search編
  • Part 2: E-Fetchとデータパース編
  • Part 3: AI処理‧データ整形編
  • Part4(本記事): データ保存とGAS連携編
check

ヘルツレーベンでは、ライフサイエンス業界に特化したDX・自動化支援を提供しています。
PubMedや学術情報の自動収集をはじめ、Slack・Gmailなどを活用したナレッジ共有の仕組みまで、実務に直結するワークフローを設計・導入いたします。

提供サービスの例

  • 製薬・医療機器業界での提案活動や調査業務の自動化支援
  • アカデミアや研究者向けの文献レビュー・情報共有フローの最適化
  • 医療従事者のキャリア開発を支援するリスキリングプログラム

👉 ご興味をお持ちの方はぜひお気軽にお問い合わせください。
お問い合わせフォームはこちら

株式会社ヘルツレーベン代表 木下 渉

監修者 株式会社ヘルツレーベン代表 木下 渉

株式会社ヘルツレーベン 代表取締役/医療・製薬・医療機器領域に特化したDXコンサルタント/
横浜市立大学大学院 ヘルスデータサイエンス研究科 修了

製薬・医療機器企業向けのデータ利活用支援、提案代行、営業戦略支援を中心に、医療従事者向けのデジタルスキル教育にも取り組む。AI・データ活用の専門家として、企業研修、プロジェクトPMO、生成AI導入支援など幅広く活動中

Difyでつくる論⽂仕分けアプリ part3: LLM処理‧データ保存編

Difyでつくる論⽂仕分けアプリ                        Part3: LLM処理‧データ保存編

目次

本記事は、Difyのチャットワークフローを使って、PubMed論⽂の検索‧翻訳‧要約を⾃動化するシステムを構築するシリーズのPart 3です。

Part 2の復習: 前回の記事では、E-Fetchで論⽂詳細データを取得し、XMLをパースして構造化データを作るところまで解説しました。具体的には、以下のノードを実装しました。

  1. E-Fetch(XML形式で論⽂詳細データを取得)
  2. XMLパース(PythonでXMLを解析し、構造化データに変換)
ワークフロー

本記事(Part 3)では、取得した論⽂データに対してLLMで翻訳‧要約‧優先度判定を⾏い、CSV形式に整形する処理を詳しく解説します。この部分は、ワークフローの核⼼となるAI処理部分です。

シリーズ構成

  • Part0: 全体像とPubMed API基礎
  • Part 1: 検索・データ取得編
  • Part 2: AI処理・データ整形編
  • Part 3(本記事): LLM処理・データ保存編
  • Part4:  DifyとGAS連携で実現する可能性

Part 2で取得したデータは、以下のような構造になっています。

{ 
  "parsed_result": [ 
  { 
    "pmid": "12345678", 
    "title": "Effect of Insulin Therapy in Type 2 Diabetes",
    "abstract": "[Background] Type 2 diabetes...", 
    "author": ["John Smith", "Jane Doe"], 
    "journal": "Diabetes Research", 
    "year": "2024", 
    "doi": "10.1234/example", 
    "keywords": ["diabetes", "insulin", "therapy"] 
  } 
  ]
}

事では、E-Fetchで取得した論⽂データして、以下のを⾏います

  1. イテレーションで各論⽂を一つずつ処理
  2. LLMで各論⽂のタイトル翻訳・要約・優先度判定・研究領域抽出・対象抽出
  3. 元データとAI分析結果をマージしてCSV⽣成
イテレーション(Iteration)

パースされた論⽂データの配列をループ処理し、各論⽂に対してLLMによる翻訳‧要約‧優先度判定を⾏うノードです。

DifyのIterationノードは、リストの要素に対して同じ処理を繰り返すために使います。

たとえば、URLリストや論⽂リスト(論⽂1,論⽂2,論⽂3…)の⼀つ⼀つに同じAI処理を適⽤したいときに便利です。このノードは、プログラミングのfor⽂のように、リストのすべての項⽬を順に処理し、結果をまとめて出⼒します。

項⽬設定値
入力変数{{parsed_result}}
出力変数{{text}} (後で説明するLLMノードを先に配置すると選択できるようになります)
エラーハンドリングエラー時は終了
出力をフラット化true
  1. ⼊⼒: XMLパースノードから parsed_result (論⽂データの配列)を受け取る
  2. ループ: 各論⽂データを1件ずつ処理
  3. 出⼒: LLMの出⼒を配列として集約
LLM(イテレーション内側のノードです)

各論⽂に対して、タイトルの⽇本語翻訳、アブストラクトの要約、優先度判定を⾏うLLMノードです。イテレーション内に配置されており、各論⽂ごとに個別に処理されます。
イテレーションの中で、LLMノードを配置することで、実行するたびに各論文データに対して、一つずつLLMが実行されます。

あなたは医学論文の分析と翻訳を行う専門AIアシスタントです。
ユーザーから提供された「論文リスト」と「検索意図(質問)」に基づき、各論文の情報を日本語で構造化して抽出してください。

### ユーザーの検索意図(質問)
{{#sys.query#}}

### タスク
提供された論文について、以下の処理を行ってください。

Title Translation
論文タイトルを自然で簡潔な日本語に翻訳してください。

Summarization
アブストラクトの内容を100文字以上200文字以内の日本語で要約してください。
「目的」「方法」「結果」「結論」の流れを意識して記述してください。
ユーザーの質問に対する「答え」や示唆が含まれているかに注意してください。

Priority Assessment
ユーザーの質問に対するその論文の重要度を3段階で判定してください。
HIGH: 質問の意図と高いレベルで一致し、かつRCT、メタアナリシス、システマティックレビューなど高いエビデンスレベル、または重要な新知見を含む。
MID: 質問と関連はあるが一部が周辺的、または観察研究・症例報告などエビデンスレベルが限定的。
LOW: 質問の意図と大きく異なる、対象が全く異なる(例:動物実験のみ)、または臨床的意義が小さい。

Research Area(研究領域)の抽出
論文のタイトル・アブストラクト・MeSH用語などから、主要な疾患領域・診療科・トピックを1〜3個程度、日本語で要約してください。
例:Oncology, Cardiovascular, Endocrinology, Psychiatry, Neurology, Infectious disease などを、日本語で「腫瘍学」「循環器」「内分泌」「精神科」「神経内科」「感染症」などと表現する。できるだけ専門領域名として通用する粒度で簡潔に記述してください。

Population(対象)の抽出
研究の対象となっている集団を日本語で要約してください。
年齢層(成人/高齢者/小児/新生児 など)
患者群(例:2型糖尿病患者、心不全患者、健常成人 など)
動物実験・細胞実験のみの場合はその旨を明記してください(例:「マウスモデル」「培養細胞」など)。

### 入力データ(論文リスト)
{{#item#}}
  1. 検索意図の活⽤ {{#sys.query#}}  でユーザーの検索クエリを参照し、要約や優先度判定の基準として使⽤
  2. 構造化さタスク:  5つの明確なタスク(翻訳、要約、優先度判定、研究領域抽出、対象抽出)を定義
  3. 優先判定の基準:   HIGH/MID/LOWの判定基準を明確に定義し、⼀貫性のある判定を実現
  4. 究領域と象の抽:  論⽂の分類と検索に役⽴つ追加情報を抽出

LLMの出⼒を構造化するため「構造化出⼒」機能を使⽤しています。
※構造化出力はAIのモデルによってサポートされていない場合があります。うまくいかない場合はバージョンを変えて試してみてください(gpt-4o-miniでは動作確認済み)。

フィールド名説明
title_jpstring論⽂の⽇本語タイトル
summarystring要約(100〜200⽂字程度)
prioritystring重要度(HIGH, MID, LOW)
research_areaarray[string]研究領域(1〜3個程度、⽇本語)
populationstring対象(年齢層‧患者群‧実験モデルなど)

LLMによってこれらのラベルが自動的に付与されます。

各論⽂に対して以下のJSON形式で出⼒されます。

{ 
  "title_jp": "糖尿病におけるインスリン療法の効果", 
  "summary": "本研究は、2型糖尿病患者におけるインスリン療法の有効性を検証した。無作為化比較試験により、インスリン療法群では血糖コントロールが有意に改善し、HbA1cが平均1.2%低下した。結論として、インスリン療法は2型糖尿病の効果的な治療選択肢であることが示された。", 
  "priority": "HIGH", 
  "research_area": ["内分泌", "糖尿病"], 
  "population": "2型糖尿病患者(成人)"
}
DB登録⽤データの作成(Codeノード)

元の論⽂データ(XMLパース結果)とAI分析結果(LLM出⼒)をマージし、CSV形式に変換するノードです。

先ほど作成したLLMによる追加データとPubMed APIから取得したデータを統合して、一つの行データとして扱えるようにします。

変数名ソース
original_listXMLパースノードarray[object]
ai_results_listイテレーションノードarray[string]
カラム名説明データソース
PMIDPubMed ID元データ
Priority重要度AI分析結果
Title_JP⽇本語タイトルAI分析結果
Summary要約AI分析結果
Title_EN英語タイトル元データ
Authors著者リスト元データ
Journal雑誌名元データ
Year公開年元データ
DOIDOI元データ
MeSH_KeywordsMeSH⽤語とキーワード元データ
URLPubMed URL⽣成( https://pubmed.ncbi.nlm.nih.gov/{pmid}/
main_author_affiliation第⼀著者の所属機関元データ
research_area研究領域AI分析結果
publication_types論⽂タイプ元データ
population対象AI分析結果

以下はコピペでコードノードに貼り付けるだけで大丈夫です。
コードが動かない時には、「入力変数」「出力変数」の名前やデータ型が正しいかを確認してください。

import json

def main(original_list: list, ai_results_list: list): 
  headers = [ 
    "PMID", 
    "Priority", 
    "Title_JP", 
    "Summary", 
    "Title_EN", 
    "Authors", 
    "Journal", 
    "Year", 
    "DOI", 
    "MeSH_Keywords", 
    "URL", 
    "main_author_affiliation", 
    "research_area", 
    "publication_types", 
    "population" 
  ] 

  csv_rows = [",".join(['"' + h + '"' for h in headers])] 

  for i, original in enumerate(original_list): 
    ai_item = ai_results_list[i] if i < len(ai_results_list) else "{}"

    ai_data = {} 
    try: 
      if isinstance(ai_item, dict): 
        ai_data = ai_item 
      else: 
        clean_json = str(ai_item).replace('```json', '').replace('```', '').strip() 
        ai_data = json.loads(clean_json) 
    except: 
      ai_data = {} 

    row_data = {} 

    pmid = original.get('pmid', '') 
    row_data["PMID"] = pmid 
    row_data["Title_EN"] = original.get('title', '') 
    auths = original.get('authors', original.get('author', [])) 
    row_data["Authors"] = ", ".join(auths) if isinstance(auths, list) else str(auths) 
    row_data["Journal"] = original.get('journal', '')
    row_data["Year"] = original.get('year', '') 
    row_data["DOI"] = original.get('doi', '') 
    row_data["main_author_affiliation"] = original.get('main_author_affiliation','') 
    row_data["publication_types"] = original.get('publication_types','')[0].replace('[','').replace(']','') if original.get('publication_types') else '' 

    kws = original.get('MeSH_Keywords', original.get('keyword', [])) 
    row_data["MeSH_Keywords"] = ", ".join(kws) if isinstance(kws, list) else str(kws) 

    if pmid: 
      row_data["URL"] = f"<https://pubmed.ncbi.nlm.nih.gov/{pmid}/>" 
    else: 
      row_data["URL"] = "" 

    # LLM generated columns 
    row_data["Title_JP"] = ai_data.get('title_jp', '') 
    row_data["Summary"] = ai_data.get('summary', '') 
    row_data["Priority"] = ai_data.get('priority','') 
    research_area_list = ai_data.get('research_area', []) 
    if research_area_list and len(research_area_list) > 0: 
      row_data["research_area"] = research_area_list[0].replace('[','').replace(']','') if isinstance(research_area_list[0], str) else str(research_area_list[0]) 
    else: 
      row_data["research_area"] = '' 
    row_data["population"] = ai_data.get('population','')

    csv_row = [] 
    for col in headers: 
      val = row_data.get(col, "") 
      val_escaped = str(val).replace('"', '""') 
      csv_row.append(f'"{val_escaped}"') 

    csv_rows.append(",".join(csv_row)) 

  final_csv = "\\n".join(csv_rows) 

  return { 
    "csv_string": final_csv
  }
  1. ヘッダー⾏:  CSVのヘッダー⾏を作成
  2. ループ処: 元データとAI分析結果を1件ずつ処理
  3. AI析結果のパース: LLMの出⼒をJSONとして解析(エラーハンドリング付き)
  4. データマージ: 元データとAI分析結果を統合
  5. CSV: 各フィールドをエスケープ処理してCSV形式に変換
  6. URL: PMIDからPubMedのURLを⾃動⽣成

CSV形式では、フィールド内にカンマやダブルクォートが含まれる場合、適切にエスケープする必要があります。このコードでは、ダブルクォートを “” に変換することで、正しいCSV形式を保証しています。

出⼒名説明
csv_stringstringCSV形式の⽂字列

以下のように出⼒することで、SpreadsheetやExcelで扱いやすいcsvの形式にしています。これによってSpreadsheetやExcelに連携する時のデータ変換処理が容易になります。

"PMID","Priority","Title_JP","Summary","Title_EN","Authors","Journal","Year","DOI","MeSH_Keywords","URL","main_author_affiliation","research_area","publication_types","population""12345678","HIGH","糖尿病におけるインスリン療法の効果","本研究は、2型糖尿病患者におけるインスリン療法の有効性を検証した。...","Effect of Insulin Therapy in Type 2 Diabetes","John Smith, Jane Doe","Diabetes Research","2024","10.1234/example","diabetes, insulin, therapy","<https://pubmed.ncbi.nlm.nih.gov/12345678/","University> of Tokyo","内分泌","Randomized Controlled Trial","2型糖尿病患者(成人)"

本記事では、取得した論⽂データに対してLLMで翻訳‧要約‧優先度判定を⾏い、CSV形式に整形する処理を詳しく解説しました。

  • イテレーションによる論⽂データのループ処理
  • LLMによる各論⽂の翻訳‧要約‧優先度判定
  • 元データとAI分析結果のマージ
  • CSV形式への変換(エスケープ処理付き)
  1. イテレーション: 論⽂データをループ処理
  2. LLM: 各論⽂に対して翻訳‧要約‧優先度判定‧研究領域抽出‧対象抽出
  3. DB録⽤データの作成: 元データとAI分析結果をマージしてCSV⽣成
次のステップ

次回のPart 4では、⽣成したCSVデータをGoogle Apps Script(GAS)へ送信してスプレッドシートに保存する処理と、GAS連携で実現できる応⽤例を解説します。具体的には以下のテーマを扱います。

  • CSV統合⽤の変数集約器
  • GAS WebhookへのPOST送信
  • レスポンスからスプレッドシートURLを取得するコード
  • Dify × GAS連携の応⽤(通知、定期実⾏、他システムとの統合 等)

これらの処理により、ワークフローが完成し、ユーザーはスプレッドシートのURLを受け取って、保存された論⽂データを確認できるようになります。


シリーズ記事

  • Part0: 全体像とPubMed API基礎
  • Part 1: 検索・データ取得編
  • Part 2: AI処理・データ整形編
  • Part 3: LLM処理・データ保存編
  • Part4(次回記事): DifyとGAS連携で実現する可能性
check

ヘルツレーベンでは、ライフサイエンス業界に特化したDX・自動化支援を提供しています。
PubMedや学術情報の自動収集をはじめ、Slack・Gmailなどを活用したナレッジ共有の仕組みまで、実務に直結するワークフローを設計・導入いたします。

提供サービスの例

  • 製薬・医療機器業界での提案活動や調査業務の自動化支援
  • アカデミアや研究者向けの文献レビュー・情報共有フローの最適化
  • 医療従事者のキャリア開発を支援するリスキリングプログラム

👉 ご興味をお持ちの方はぜひお気軽にお問い合わせください。
お問い合わせフォームはこちら

株式会社ヘルツレーベン代表 木下 渉

監修者 株式会社ヘルツレーベン代表 木下 渉

株式会社ヘルツレーベン 代表取締役/医療・製薬・医療機器領域に特化したDXコンサルタント/
横浜市立大学大学院 ヘルスデータサイエンス研究科 修了

製薬・医療機器企業向けのデータ利活用支援、提案代行、営業戦略支援を中心に、医療従事者向けのデジタルスキル教育にも取り組む。AI・データ活用の専門家として、企業研修、プロジェクトPMO、生成AI導入支援など幅広く活動中

Difyで作る論⽂仕分けアプリpart2: PubMedAPIから詳細を取得

Difyで作る論⽂仕分けアプリpart2: PubMedAPIから詳細を取得

目次

本記事は、Difyのチャットワークフローを使ってPubMed論⽂の検索‧翻訳‧要約を⾃動化するシリーズのPart 2です。

Part 1の振り返り:

  • ⾃然⾔語クエリ(⽇本語)からPubMed検索パラメータを抽出
  • E-SearchでPMIDリストを取得
  • 後段ノードに渡すためPMIDをカンマ区切り⽂字列へ整形
ワークフロー

Part 2(本記事)では、PMIDリストをもとにE-Fetchで論⽂の詳細データを取得し、後続のAI処理で扱いやすい構造化データへ変換するまでを解説します。ここでは、PubMedから返却されるXMLのパース処理を丁寧に解説します。

シリーズ構成

  • Part0: 全体像とPubMed API基礎
  • Part 1: パラメータ抽出とE-Search編
  • Part 2(本記事): E-Fetchとデータパース編
  • Part 3: AI処理‧データ整形編
  • Part4: データ保存とGAS連携編

Part  1で整形したPMID⽂字列は、これから紹介するノードに渡されます。

  1. E-Fetch: 論⽂の詳細データを取得(XML形式)
  2. XMLパース: LLM処理で扱いやすいPython dict / list形式へ変換

この簡易版では、E-Fetchのみを使⽤して論⽂の詳細データを取得します。E-Summaryは使⽤せず、常にE-FetchでXML形式のデータを取得することで、アブストラクトやMeSH⽤語などの詳細情報を確実に取得できます。

ここまでを整えることで、Part  3で実施するAI要約‧優先度付けをスムーズに実装できます。

E-Fetch(HTTP Requestノード)

PubMedの E-fetch APIを呼び出して、論⽂の詳細データをXML形式で取得するノードです。
この簡易版のワークフローでは、テスト⽤に retmax を固定で 3 に設定することで、最大取得件数を3件に抑えています。

パラメータ説明
URLhttps://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgiPubMed E-Fetchエンドポイント
メソッドGET
パラメータ説明
dbpubmedデータベース名
id{{#1764077943290.result#}}カンマ区切りのPMID⽂字列(前のノードから取得)
retmodexmlXMLレスポンスを取得
retmax3取得件数(テスト⽤に3件で固定)

特徴: Abstract、MeSH、著者、掲載誌など詳細なデータがすべて含まれるため、LLM要約やキーワード抽出に最適です。

XMLレスポンスのパース(Codeノード)

E-FetchのエンドポイントはXML形式のデータを返します。
通常のJSON文字列ではないため、コードブロックにてPythonのxml.etree.ElementTree を使って必要な項⽬を抽出・整形します。
(Difyでは標準的なPythonライブラリを呼び出して使うことができます)

以下のコードは理解していなくても⼤丈夫です。コピペで動かすことができます(コードブロックの⼊⼒変数と出⼒変数の名称や、タイプを揃えるよう注意してください)

項⽬説明
pmidPubMed ID
title英語タイトル
abstractAbstractTextに付与されたLabel込みで抽出し、改⾏で結合
author著者⼀覧(”Forename Lastname”形式)
main_author_affiliation第⼀著者の所属機関
journal_inshort雑誌略称
journal雑誌正式名称
year公開年( PubDateDateCompleted の順に参照)
doiDOI(ELocationIDから抽出)
MeSH_Keywords著者キーワードとMeSH⽤語の統合(Qualifier含む場合はDescriptor/Qualifier 形式)
publication_types論⽂タイプ(RCT、Review、Case  Reportsなど)のリスト
import xml.etree.ElementTree as ET
import json

def main(xml_string: str): 
  try:
    root = ET.fromstring(xml_string) 
  except ET.ParseError: 
    return {"parsed_result": []} 

  articles = []
  for article in root.findall('.//PubmedArticle'): 
    data = {} 

    # 1. pmid 
    pmid = article.find('.//PMID') 
    data['pmid'] = pmid.text if pmid is not None else "" 

    # 2. Title 
    title = article.find('.//ArticleTitle') 
    data['title'] = title.text if title is not None else "" 

    # 3.Abstract 
    abstract_texts = [] 
    abstract_section = article.find('.//Abstract') 

    if abstract_section is not None: 
      for text_node in abstract_section.findall('AbstractText'): 
        label = text_node.get('Label') 
        content = text_node.text or "" 
        if label: 
          abstract_texts.append(f"[{label}] {content}") 
        else: 
          abstract_texts.append(content) 
    data['abstract'] = "\n".join(abstract_texts)

    # 4. Authors & Affiliations 
    author_list = [] 
    for author in article.findall('.//Author'): 
      last = author.find('LastName') 
      fore = author.find('ForeName') 
      if last is not None and fore is not None: 
        author_list.append(f"{fore.text} {last.text}") 
      elif last is not None: 
        author_list.append(last.text) 
    data['author'] = author_list 

    main_author_affil = "" 
    first_author = article.find('.//AuthorList/Author') 
    if first_author is not None: 
      aff = first_author.find('.//AffiliationInfo/Affiliation') 
      if aff is not None and aff.text: 
        main_author_affil = aff.text.strip() 
    data['main_author_affiliation'] = main_author_affil 

    # 5-1.Journal(in short) info 
    journal_inshort = article.find('.//ISOAbbreviation') 
    data['journal_inshort'] = journal_inshort.text if journal_inshort is not None else "" 

    # 5-2. Journal_info 
    journal = article.find('.//Journal/Title') 
    data['journal'] = journal.text if journal is not None else "" 

    # 6.Year 
    year = article.find('.//PubDate/Year')
    if year is None: 
      year = article.find('.//DateCompleted/Year') 
    data['year'] = year.text if year is not None else "" 

    # 7.DOI 
    doi = "" 
    for eloc in article.findall('.//ELocationID'): 
      if eloc.get('EIdType') == 'doi': 
        doi = eloc.text 
        break 
    data['doi'] = doi 

    # 8.Keywords 
    keywords_set = set() 

    # A. 著者キーワード (KeywordList) 
    for kw in article.findall('.//Keyword'): 
      if kw.text: 
        keywords_set.add(kw.text.strip()) 

    # B. MeSH用語 (MeshHeadingList) 
    for mesh_heading in article.findall('.//MeshHeading'): 
      descriptor = mesh_heading.find('DescriptorName') 
      if descriptor is not None and descriptor.text: 
        desc_text = descriptor.text.strip() 

        # Qualifier 
        qualifiers = mesh_heading.findall('QualifierName') 

        if len(qualifiers) > 0: 
          for q in qualifiers: 
            if q.text: 
              keywords_set.add(f"{desc_text}/{q.text.strip()}") 
        else: 
          keywords_set.add(desc_text)

    # 9. Publication Types 
    pub_types = [] 
    for pt in article.findall('.//PublicationTypeList/PublicationType'): 
      if pt.text: 
        pub_types.append(pt.text.strip()) 
    data['publication_types'] = pub_types 

    # リストに戻してソート 
    data['MeSH_Keywords'] = sorted(list(keywords_set)) 
    articles.append(data) 

  return { 
    "parsed_result" : articles 
  }
出⼒名説明
parsed_resultarray[object]1レコード1論⽂の辞書リスト
  • E-Fetchによる論⽂詳細データの取得(XML形式)
  • XMLレスポンスを構造化データへ変換するコード例
  • 抽出される主要なフィールド(pmid、title、abstract、author、MeSH_Keywords、publication_typesなど)
次のステップ

Part 3では、ここで得た parsed_result を⼊⼒に、LLMでタイトル翻訳‧要約‧優先度判定を⾏います。イテレーションノードの並列処理やStructured Outputの活⽤法など、AI処理の中⼼部分を解説します。


シリーズ記事

  • Part0: 全体像とPubMed API基礎
  • Part 1: パラメータ抽出とE-Search編
  • Part 2: E-Fetchとデータパース編
  • Part 3(次回記事): AI処理‧データ整形編
  • Part4: データ保存とGAS連携編
check

ヘルツレーベンでは、ライフサイエンス業界に特化したDX・自動化支援を提供しています。
PubMedや学術情報の自動収集をはじめ、Slack・Gmailなどを活用したナレッジ共有の仕組みまで、実務に直結するワークフローを設計・導入いたします。

提供サービスの例

  • 製薬・医療機器業界での提案活動や調査業務の自動化支援
  • アカデミアや研究者向けの文献レビュー・情報共有フローの最適化
  • 医療従事者のキャリア開発を支援するリスキリングプログラム

👉 ご興味をお持ちの方はぜひお気軽にお問い合わせください。
お問い合わせフォームはこちら

株式会社ヘルツレーベン代表 木下 渉

監修者 株式会社ヘルツレーベン代表 木下 渉

株式会社ヘルツレーベン 代表取締役/医療・製薬・医療機器領域に特化したDXコンサルタント/
横浜市立大学大学院 ヘルスデータサイエンス研究科 修了

製薬・医療機器企業向けのデータ利活用支援、提案代行、営業戦略支援を中心に、医療従事者向けのデジタルスキル教育にも取り組む。AI・データ活用の専門家として、企業研修、プロジェクトPMO、生成AI導入支援など幅広く活動中

Difyで作る論⽂仕分けアプリpart1:質問から論⽂リストを取得

Difyで作る論⽂仕分けアプリ Part1                       質問から論⽂リストを取得

目次

本記事は、Difyのチャットワークフローを使って、PubMed論⽂の検索‧翻訳‧要約を⾃動化するシステムを構築するシリーズのPart 1です。

このシリーズでは、⾃然⾔語で検索クエリを⼊⼒して「論⽂検索→各論⽂のタイトルを⽇本語に翻訳→アブストラクト要約→Googleスプレッドシートに保存」という処理を一気に実現するワークフローについて解説します。

Part1

本記事(Part 1)では、ユーザー⼊⼒➔パラメータ抽出➔E-Search➔PMIDリスト整形までの処理を詳しく解説します。ここで整えたデータが後続のE-Fetch処理の⼟台になります。

このワークフローは、医学研究や⽂献調査の効率化に役⽴ち、特に⼤量の論⽂を扱う際の時間短縮に貢献します。

PubMed APIの基礎知識については、Part 0で詳しく解説していますので、本記事では各ノードの実装詳細に焦点を当てます。

シリーズ構成

  • Part0: 全体像とPubMed API基礎
  • Part 1(本記事): パラメータ抽出とE-Search編
  • Part 2: E-Fetch と データパース編
  • Part 3: AI処理‧データ整形編
  • Part4: データ保存とGAS連携編

このワークフローは、以下のような処理の流れで構成されています。

処理の流れ
  1. ユーザー⼊⼒: ⾃然⾔語での検索クエリ
  2. 現在年の取得: 年次フィルタリングに使⽤
  3. パラメータ抽出: LLMが⾃然⾔語からPubMed検索パラメータを抽出
  4. APIリクエスト整形: 抽出したパラメータをPubMed API形式に変換
  5. E-Search: PubMedで論⽂ID(PMID)を検索
  6. E-Fetch: 論⽂の詳細データ(XML)を取得
  7. パース処理:  XMLを構造化データに変換
  8. イテレーション: 各論⽂に対してLLMで翻訳‧要約‧優先度判定
  9. CSV⽣成: 論⽂データとAI分析結果をマージしてCSV形式に変換
  10. GAS連携: Google Apps Scriptに送信してスプレッドシートに保存
  11. 結果返却: スプレッドシートのURLをユーザーに返却

本記事では、ステップ1〜5(E-SearchとPMID取得)までを詳しく解説します。E-Fetchとパース処理はPart 2で取り上げます。

ユーザー⼊⼒ノード(Start)

ワークフローの開始点となるノードです。ユーザーからの⾃然⾔語クエリを受け取ります。

今回のワークフローでは、特に追加の設定不要です。

項⽬設定値
ノードタイプStart
変数なし
Current Time(Tool)

現在の年を取得するためのビルトインツールです。パラメータ抽出ノードで、ユーザーが「直近5年間の論⽂」のような相対的な期間指定をした際に、現在年を基準に min_year を計算するために使⽤されます。⽣成AIは基本的に「今、何⽇か?」といったデータを持っていません。そのため`CURRENT_TIME`のようなノードを使って、明⽰的に理解させる必要があります。

項⽬設定値
ツール名Current Time
Format%Y (年のみ)
TimezoneAsia/Tokyo
  • text : 現在の年が出力されます(例: “2025”)
パラメータ抽出ノード(Parameter Extractor)

ユーザーの⾃然⾔語クエリ(主に⽇本語)から、PubMed APIで使⽤する検索パラメータを抽出するノードです。LLMを使⽤して構造化されたパラメータを⽣成します。このパラメータ抽出ノードがこの論⽂仕分けアプリにおいて最も重要なノードの⼀つです。

モデルはお好きなモデルをお使いください。

項⽬設定値
モデルgpt-4o-mini
プロバイダーopenai
Temperature0.1
抽出パラメータ

パラメータ抽出ノードの [ + ]ボタンを押してパラメータを追加します。以下をそれぞれコピペして設定していくだけで⼤丈夫です。

パラメータ名必須説明(Description)
main_query stringThe Search Term<br>Core topic, disease, drug, or therapy.<br><br>• Combine core concepts (e.g.,
“Relationship between A and B” → “A AND B”).<br>•
Rule: Do NOT apply a [Title] tag here. Just provide the translated English term/MeSH.
title_filterstringStrict Constraint<br>Drastically narrows results to highly relevant papers.<br><br>• Use ONLY when user explicitly says “Title must include…”, “Title search”, “タイトル検索”, or “タイトルに含まれる”.
author_filterstring• Extract names<br>• Rule: Remove Japanese
honorifics (e.g., “さん”, “⽒”, “先⽣”).
journal_filterstring• Extract journals.
pub_type_filterstringIdentify implied study designs.<br><br>• “RCT”, “無作為化” → “Randomized Controlled Trial”<br>• “Review”, “まとめ” → “Review”<br>• “Meta-
analysis” → “Meta-Analysis”<br>• “Case report”, “症
例” → “Case Reports”
min_yearstringStart Date<br>The oldest year to include.<br><br>• “2023年以降”, “Since 2023″→”2023”
max_yearstringEnd Date<br>The newest year to include.
retmaxstringResult Count<br>Number of papers to retrieve.<br>
<br>• Extract explicit numbers: “10件”, “Top 5” → Integer (e.g., 10, 5).<br>• Default: 20 (if not
specified).
プロンプト

LLMに与えるプロンプトは上記です。

※ 2ステップ⽬で作成した「CURRENT_NODE 」の出⼒を #current_yearの箇所に加えてあげることで、⽣成AIが「今、⻄暦何年か」を理解することができます。

## Role
You are an expert Medical Librarian and a PubMed Search API Specialist. Your goal is to extract search param eters from the user's natural language query (which is mostly in Japanese) and format them into a structured JSON object for the PubMed `esearch` API.

## Instructions
1.	**Translate to English**: The user input will be in Japanese. You must translate all search terms (Diseases, Drugs, Concepts) into **English** (specifically MeSH headings where applicable).
2.	**Extract Parameters**: Identify specific constraints based on the "Parameter Definition Table" below.
3.	**Determine Fetch Necessity**: Decide if the user needs `efetch` (detailed data like Abstract) or if `esumm ary` (metadata only) is sufficient.

## current_year
<year>{{/ ⟵ スラッシュボタンを押すとウィンドウが開くので、CURRENT_TIMEの出⼒を選択}}</year>
* use this year to extract `min_year` or `max_year`. if user needs 「直近5年間の〜」 it means that current_year
- 5 = min_year.

プロンプトには以下の重要な指⽰が含まれています。

  1. 訳ルール: ⽇本語の検索語を英語(特にMeSH⽤語)に翻訳
  2. 現在年の活: Current Timeノードから現在年を取得し、「直近5年間」のような相対指定を絶対年へ変換
  3. タイトルフィルタの厳格化: ユーザーが明⽰的に「タイトル検索」と⾔及した場合のみ title_filter を使⽤

パラメータ抽出ノードで取得したパラメータを、PubMed APIの esearch エンドポイントで使⽤できる形式に整形するPythonコードノードです。

変数名ソース
main_queryパラメータ抽出ノードstring
title_filterパラメータ抽出ノードstring
author_filterパラメータ抽出ノードstring
author_filterパラメータ抽出ノードstring
pub_type_filterパラメータ抽出ノードstring
min_yearパラメータ抽出ノードstring
max_yearパラメータ抽出ノードstring
retmaxパラメータ抽出ノードstring
def main( 
  main_query: str, 
  title_filter: str, 
  author_filter: str, 
  journal_filter: str, 
  pub_type_filter: str, 
  min_year: str, 
  max_year: str, 
  retmax: str): 

# PubMedAPIへのクエリを格納する箱 
query_parts = [] 

# Main Query (主題) 
if main_query: 
  query_parts.append(f"({main_query})") 

# Title Filter (タイトル限定) 
if title_filter: 
  query_parts.append(f'"{title_filter}"[Title]') 

# Journal Filter (雑誌名) 
if journal_filter: 
  query_parts.append(f'"{journal_filter}"[Journal]') 

# Author Filter (著者名) 
if author_filter: 
  query_parts.append(f'"{author_filter}"[Author]') 

# Publication Type (研究デザイン) 
if pub_type_filter: 
  query_parts.append(f'"{pub_type_filter}"[Publication Type]') 

# 全てを AND で結合 
full_term = " AND ".join(query_parts) 

# フォールバック: 全て空の場合は全件検索 
if not full_term: 
  full_term = "all[sb]" 

# その他のパラメータ処理
final_retmax = retmax if retmax else "20" 
final_min_year = min_year if min_year else "" 
final_max_year = max_year if max_year else "" 

# 結果を返す 
return { 
  "search_term": full_term, 
  "retmax": final_retmax, 
  "mindate": final_min_year, 
  "maxdate": final_max_year, 
  "datetype": "pdat" 
}
  1. クエリパーツの構築: 各フィルタが存在する場合、PubMedの検索構⽂( [Title] 、 [Journal] 、 [Author] 、 [Publication Type] )を付与して配列に追加
  2. AND結合:” AND “.join(query_parts)で全ての条件をANDで結合
  3. デフォルト:
    • retmax が空の場合は”20″を設定
    • ⽇付パラメータは空⽂字列のまま(API側で無視される)
  4. フォールバック: 全てのクエリが空の場合は “all[sb]” (全件検索)を設定
出⼒名説明
search_termstringPubMed検索クエリ(例: “(diabetes) AND \”insulin\”[Title]”)
retmaxstring取得件数
mindatestring開始年
maxdatestring終了年
datetypestring⽇付タイプ(”pdat” = 公開⽇)
 E-Search(HTTP Requestノード)

PubMedの E-search APIを呼び出して、検索条件に⼀致する論⽂のPMID(PubMed ID)リストを取得するノードです。

上記の画像を参考にしながら、以下の項⽬を設定してください。

項⽬設定値
メソッドヘッダー
URLhttps://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi
認証今回はなし(API_KEYによる認証を⾏うと⾼頻度‧⼤量にデータを取得可能)
ヘッダーContent-Type:application/json
パラメータ名説明
dbpubmedデータベース(PubMed)
term{{#search_term#}}検索クエリ
retmax{{#retmax#}}取得件数
retmodejsonレスポンス形式
mindate{{#mindate#}}開始年
maxdate{{#maxdate#}}終了年
{ 
  "esearchresult": { 
    "idlist": ["12345678", "23456789", "34567890"] 
  }
}
配列を⽂字列に変換(Codeノード)

E-Searchのレスポンス(JSON形式)からPMIDの配列を抽出し、カンマ区切りの⽂字列に変換するノードです。次のE- Fetchノードで使⽤するため、PMIDを⽂字列形式に整形します。

変数名ソース
bodyE-Searchノードstring
import json

def main(body) -> dict:
# 値が空(想定外のエラー)の時はエラー 
if not body: 
  raise Exception("Invalid parameter") 

# pmidの配列を読み込み 
id_list = json.loads(body) 

# 配列をカンマ区切りで展開して返却 
return {
  "result": ",".join(id_list["esearchresult"]["idlist"]) 
}
  1. JSONパース:  E-SearchのレスポンスをJSONとして解析
  2. PMID抽出: esearchresult.idlist からPMIDの配列を取得
  3. ⽂字列変換“,”.join()  でカンマ区切りの⽂字列に変換(例: “12345678,23456789,34567890”)
出⼒名説明
resultstringstring

本記事(Part 1)では、DifyのチャットワークフローでPubMed検索を開始するための「前半戦」を解説しました。

  • ⾃然⾔語クエリからPubMed検索パラメータへの変換
  • PubMed API(E-Search)での論⽂ID(PMID)検索
  • 後続処理に渡すためのPMID⽂字列整形
次のステップ

次回のPart 2では、ここで取得したPMIDを⽤いてE-Fetchを呼び出し、論⽂の詳細データ(XML)を取得‧パースする処理を解説します。XMLから必要項⽬を抽出する実装を中⼼に取り上げます。


シリーズ記事

  • Part0: 全体像とPubMed API基礎
  • Part 1: パラメータ抽出とE-Search編
  • Part 2(次回記事): E-Fetchとデータパース編
  • Part 3: AI処理‧データ整形編
  • Part4: データ保存とGAS連携編
check

ヘルツレーベンでは、ライフサイエンス業界に特化したDX・自動化支援を提供しています。
PubMedや学術情報の自動収集をはじめ、Slack・Gmailなどを活用したナレッジ共有の仕組みまで、実務に直結するワークフローを設計・導入いたします。

提供サービスの例

  • 製薬・医療機器業界での提案活動や調査業務の自動化支援
  • アカデミアや研究者向けの文献レビュー・情報共有フローの最適化
  • 医療従事者のキャリア開発を支援するリスキリングプログラム

👉 ご興味をお持ちの方はぜひお気軽にお問い合わせください。
お問い合わせフォームはこちら

株式会社ヘルツレーベン代表 木下 渉

監修者 株式会社ヘルツレーベン代表 木下 渉

株式会社ヘルツレーベン 代表取締役/医療・製薬・医療機器領域に特化したDXコンサルタント/
横浜市立大学大学院 ヘルスデータサイエンス研究科 修了

製薬・医療機器企業向けのデータ利活用支援、提案代行、営業戦略支援を中心に、医療従事者向けのデジタルスキル教育にも取り組む。AI・データ活用の専門家として、企業研修、プロジェクトPMO、生成AI導入支援など幅広く活動中

Load More

Privacy Policy