これら3つのポイントは、単独ではなく多層防御(Defense in Depth)として機能させることで、ハルシネーションの発生率を実務に支障がないレベルまで大幅に抑制することが可能です。特に医療・金融・法務といったクリティカルな領域では、ハルシネーションが「致命的な問題」につながるため、これらの運用体制の確立は不可欠です。
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連携編
2. Part 3からの流れ
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型糖尿病患者(成⼈)"
Google Apps Script(GAS)はGoogleが提供するクラウドベースのJavaScript実⾏環境で、Google Workspace(スプレッドシート、ドライブ、メール、カレンダーなど)を⾃動化‧拡張するために設計されたプラットフォームです。 ブラウザ上のエディタだけで完結し、インフラ構築やサーバ管理なしでスクリプトを動かせるため、「ちょっとした業務⾃動化」から「⼩さな業務システム」までを素早く⽴ち上げられる点が特徴です。
"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型糖尿病患者(成人)"
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
}
The 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_filter
string
–
Strict Constraint<br>Drastically narrows results to highly relevant papers.<br><br>• Use ONLY when user explicitly says “Title must include…”, “Title search”, “タイトル検索”, or “タイトルに含まれる”.
## 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.
プロンプトには以下の重要な指⽰が含まれています。
翻訳ルール: ⽇本語の検索語を英語(特にMeSH⽤語)に翻訳
現在年の活⽤: Current Timeノードから現在年を取得し、「直近5年間」のような相対指定を絶対年へ変換