LangGraph実践ガイド 2025 - 複雑なAIワークフローを構築する最新手法
LangGraphを使った高度なAIエージェントとワークフローの構築方法を徹底解説。ステートフルなマルチエージェントシステム、Human-in-the-Loop、本番環境へのデプロイまで、実践的なコード例と共に紹介します。
Google Scholar APIとn8nワークフローを組み合わせて、学術論文の検索から分析、レポート生成まで完全自動化するシステムの構築方法を徹底解説。研究効率を10倍向上させる実践的手法を紹介します。
学術研究において、関連論文の検索、分析、引用管理は膨大な時間を要する作業です。 本記事では、Google Scholar API と n8n を組み合わせることで、 研究プロセス全体を自動化し、研究効率を飛躍的に向上させる方法を解説します。
チャートを読み込み中...
コンポーネント | 機能 | 使用技術 | メリット |
---|---|---|---|
論文検索エンジン | 高度な検索クエリ実行 | Google Scholar API | 包括的な学術データベース |
ワークフローエンジン | プロセス自動化 | n8n | ノーコードで柔軟な設計 |
AI分析モジュール | 論文要約・分類 | OpenAI/Claude API | 高精度な内容理解 |
データストレージ | 論文・メタデータ保存 | PostgreSQL/MongoDB | スケーラブルな保存 |
通知システム | アラート・レポート配信 | Email/Slack/Discord | リアルタイム情報共有 |
可視化ダッシュボード | トレンド・統計表示 | Grafana/Metabase | 直感的なデータ把握 |
Google Scholar は公式 API を提供していないため、SerpAPI などのサードパーティサービスを使用します。 利用規約を遵守し、適切なレート制限を設けることが重要です。
// src/scholar/google-scholar-client.js
import axios from 'axios';
import { RateLimiter } from '../utils/rate-limiter.js';
export class GoogleScholarClient {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://serpapi.com/search';
this.rateLimiter = new RateLimiter({
maxRequests: 100,
perInterval: 3600000, // 1時間
});
}
async search(params) {
await this.rateLimiter.checkLimit();
const searchParams = {
engine: 'google_scholar',
api_key: this.apiKey,
...this.buildSearchParams(params),
};
try {
const response = await axios.get(this.baseUrl, { params: searchParams });
return this.parseResults(response.data);
} catch (error) {
console.error('Google Scholar search error:', error);
throw new Error(`検索エラー: ${error.message}`);
}
}
buildSearchParams(params) {
const {
query,
yearFrom,
yearTo,
authors,
publication,
sortBy = 'relevance',
limit = 20,
offset = 0,
} = params;
// 高度な検索クエリの構築
let searchQuery = query;
if (authors && authors.length > 0) {
searchQuery += ` author:"${authors.join('" OR author:"')}"`;
}
if (publication) {
searchQuery += ` source:"${publication}"`;
}
return {
q: searchQuery,
as_ylo: yearFrom,
as_yhi: yearTo,
num: limit,
start: offset,
scisbd: sortBy === 'date' ? 1 : 0,
hl: 'ja', // 日本語インターフェース
};
}
parseResults(data) {
const results = {
papers: [],
totalResults: data.search_information?.total_results || 0,
searchTime: data.search_information?.time_taken_displayed || 0,
};
if (data.organic_results) {
results.papers = data.organic_results.map(paper => ({
title: paper.title,
authors: this.extractAuthors(paper.publication_info?.authors),
year: this.extractYear(paper.publication_info?.summary),
citations: paper.inline_links?.cited_by?.total || 0,
link: paper.link,
snippets: paper.snippet,
publication: paper.publication_info?.summary,
pdfLink: paper.resources?.find(r => r.file_format === 'PDF')?.link,
citationId: paper.inline_links?.cited_by?.cites_id,
}));
}
return results;
}
extractAuthors(authorsString) {
if (!authorsString) return [];
return authorsString.split(',').map(author => author.trim());
}
extractYear(publicationInfo) {
if (!publicationInfo) return null;
const yearMatch = publicationInfo.match(/\b(19|20)\d{2}\b/);
return yearMatch ? parseInt(yearMatch[0]) : null;
}
async getCitations(citationId, limit = 100) {
const citations = [];
let offset = 0;
while (citations.length < limit) {
const params = {
engine: 'google_scholar',
api_key: this.apiKey,
cites: citationId,
num: Math.min(20, limit - citations.length),
start: offset,
};
const response = await axios.get(this.baseUrl, { params });
if (response.data.organic_results) {
citations.push(...this.parseResults(response.data).papers);
offset += response.data.organic_results.length;
if (response.data.organic_results.length < 20) break;
} else {
break;
}
}
return citations;
}
async getAuthorProfile(authorId) {
const params = {
engine: 'google_scholar_author',
api_key: this.apiKey,
author_id: authorId,
};
const response = await axios.get(this.baseUrl, { params });
return {
name: response.data.author?.name,
affiliation: response.data.author?.affiliations,
interests: response.data.author?.interests || [],
citations: {
total: response.data.cited_by?.table?.[0]?.citations?.all || 0,
hIndex: response.data.cited_by?.table?.[1]?.h_index?.all || 0,
i10Index: response.data.cited_by?.table?.[2]?.i10_index?.all || 0,
},
articles: response.data.articles || [],
};
}
}
{
"name": "学術研究自動化ワークフロー",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 24
}
]
}
},
"name": "Daily Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [250, 300]
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT * FROM research_topics WHERE active = true"
},
"name": "Get Research Topics",
"type": "n8n-nodes-base.postgres",
"position": [450, 300]
},
{
"parameters": {
"url": "={{$env.SCHOLAR_API_URL}}/search",
"method": "GET",
"queryParameters": {
"parameters": [
{
"name": "query",
"value": "={{$json.keywords}}"
},
{
"name": "year_from",
"value": "={{$json.year_from}}"
}
]
}
},
"name": "Search Google Scholar",
"type": "n8n-nodes-base.httpRequest",
"position": [650, 300]
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": `
// 重複チェックとフィルタリング
const papers = $input.all();
const existingPapers = await $db.query(
'SELECT paper_id FROM papers WHERE paper_id = ANY($1)',
[papers.map(p => p.json.id)]
);
const newPapers = papers.filter(paper =>
!existingPapers.find(ep => ep.paper_id === paper.json.id)
);
// 関連度スコアリング
return newPapers.map(paper => {
const relevanceScore = calculateRelevance(
paper.json,
$node["Get Research Topics"].json
);
return {
...paper.json,
relevanceScore,
shouldAnalyze: relevanceScore > 0.7
};
});
function calculateRelevance(paper, topic) {
// キーワードマッチング、引用数、発表年などを考慮
let score = 0;
// キーワードマッチング
const keywords = topic.keywords.toLowerCase().split(',');
const text = (paper.title + ' ' + paper.snippet).toLowerCase();
keywords.forEach(keyword => {
if (text.includes(keyword.trim())) {
score += 0.3;
}
});
// 引用数による重み付け
if (paper.citations > 100) score += 0.2;
else if (paper.citations > 50) score += 0.1;
// 最新性
const currentYear = new Date().getFullYear();
if (paper.year >= currentYear - 2) score += 0.2;
return Math.min(score, 1);
}
`
},
"name": "Filter & Score Papers",
"type": "n8n-nodes-base.code",
"position": [850, 300]
},
{
"parameters": {
"resource": "chatCompletion",
"model": "gpt-4",
"messages": {
"values": [
{
"role": "system",
"content": "あなたは学術論文の専門的な分析者です。論文の要約と重要ポイントの抽出を行ってください。"
},
{
"role": "user",
"content": `
論文情報:
タイトル: {{$json.title}}
著者: {{$json.authors.join(', ')}}
概要: {{$json.snippet}}
引用数: {{$json.citations}}
以下の形式で分析してください:
1. 要約(200文字以内)
2. 主要な貢献(箇条書き3点)
3. 手法の特徴
4. 実験結果の要点
5. 今後の研究への示唆
6. 関連研究との差異
`
}
]
}
},
"name": "AI Paper Analysis",
"type": "n8n-nodes-base.openAi",
"position": [1050, 300]
},
{
"parameters": {
"operation": "insert",
"table": "analyzed_papers",
"columns": "paper_id,title,authors,year,citations,analysis,relevance_score,created_at",
"values": "={{$json.id}},={{$json.title}},={{$json.authors}},={{$json.year}},={{$json.citations}},={{$json.analysis}},={{$json.relevanceScore}},={{$now}}"
},
"name": "Save to Database",
"type": "n8n-nodes-base.postgres",
"position": [1250, 300]
},
{
"parameters": {
"authentication": "oAuth2",
"resource": "database",
"operation": "appendPage",
"databaseId": "={{$env.NOTION_DATABASE_ID}}",
"properties": {
"Title": "={{$json.title}}",
"Authors": "={{$json.authors.join(', ')}}",
"Year": "={{$json.year}}",
"Citations": "={{$json.citations}}",
"Summary": "={{$json.analysis.summary}}",
"Tags": "={{$json.keywords}}",
"URL": "={{$json.link}}",
"PDF": "={{$json.pdfLink}}",
"Relevance": "={{$json.relevanceScore}}"
}
},
"name": "Export to Notion",
"type": "n8n-nodes-base.notion",
"position": [1450, 200]
},
{
"parameters": {
"fromEmail": "research-bot@example.com",
"toEmail": "={{$env.RESEARCHER_EMAIL}}",
"subject": "新しい関連論文が見つかりました: {{$json.title}}",
"html": `
<h2>{{$json.title}}</h2>
<p><strong>著者:</strong> {{$json.authors.join(', ')}}</p>
<p><strong>発表年:</strong> {{$json.year}} | <strong>引用数:</strong> {{$json.citations}}</p>
<h3>AI分析結果</h3>
<p><strong>要約:</strong> {{$json.analysis.summary}}</p>
<h4>主要な貢献:</h4>
<ul>
{{#each $json.analysis.contributions}}
<li>{{this}}</li>
{{/each}}
</ul>
<p><a href="{{$json.link}}">論文を読む</a> | <a href="{{$json.pdfLink}}">PDF</a></p>
`
},
"name": "Email Notification",
"type": "n8n-nodes-base.emailSend",
"position": [1450, 400]
}
]
}
// custom-nodes/scholar-advanced-search.node.js
import {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
export class ScholarAdvancedSearch implements INodeType {
description: INodeTypeDescription = {
displayName: 'Google Scholar Advanced Search',
name: 'scholarAdvancedSearch',
icon: 'file:scholar.svg',
group: ['transform'],
version: 1,
description: 'Advanced Google Scholar search with filtering',
defaults: {
name: 'Scholar Search',
},
inputs: ['main'],
outputs: ['main'],
properties: [
{
displayName: 'Search Query',
name: 'query',
type: 'string',
default: '',
required: true,
description: 'The search query for Google Scholar',
},
{
displayName: 'Filters',
name: 'filters',
type: 'collection',
placeholder: 'Add Filter',
default: {},
options: [
{
displayName: 'Year Range',
name: 'yearRange',
type: 'fixedCollection',
default: {},
options: [
{
name: 'range',
displayName: 'Range',
values: [
{
displayName: 'From',
name: 'from',
type: 'number',
default: 2020,
},
{
displayName: 'To',
name: 'to',
type: 'number',
default: new Date().getFullYear(),
},
],
},
],
},
{
displayName: 'Minimum Citations',
name: 'minCitations',
type: 'number',
default: 0,
description: 'Minimum number of citations',
},
{
displayName: 'Authors',
name: 'authors',
type: 'string',
default: '',
description: 'Comma-separated list of authors',
},
{
displayName: 'Exclude Keywords',
name: 'excludeKeywords',
type: 'string',
default: '',
description: 'Comma-separated keywords to exclude',
},
],
},
{
displayName: 'AI Enhancement',
name: 'aiEnhancement',
type: 'boolean',
default: true,
description: 'Use AI to enhance search results',
},
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
for (let i = 0; i < items.length; i++) {
const query = this.getNodeParameter('query', i) as string;
const filters = this.getNodeParameter('filters', i) as any;
const aiEnhancement = this.getNodeParameter('aiEnhancement', i) as boolean;
// 検索クエリの構築
let enhancedQuery = query;
if (filters.excludeKeywords) {
const excludes = filters.excludeKeywords.split(',').map((k: string) => k.trim());
enhancedQuery += ' ' + excludes.map((k: string) => `-"${k}"`).join(' ');
}
// Google Scholar API 呼び出し
const searchResults = await this.helpers.request({
method: 'GET',
url: 'https://serpapi.com/search',
qs: {
engine: 'google_scholar',
q: enhancedQuery,
api_key: this.getCredentials('serpApiApi')?.apiKey,
as_ylo: filters.yearRange?.range[0]?.from,
as_yhi: filters.yearRange?.range[0]?.to,
num: 20,
},
});
let papers = JSON.parse(searchResults).organic_results || [];
// フィルタリング
if (filters.minCitations) {
papers = papers.filter((paper: any) =>
(paper.inline_links?.cited_by?.total || 0) >= filters.minCitations
);
}
// AI による拡張
if (aiEnhancement && papers.length > 0) {
const enhancedPapers = await this.enhancePapersWithAI(papers, query);
papers = enhancedPapers;
}
returnData.push({
json: {
query,
filters,
resultsCount: papers.length,
papers,
},
});
}
return [returnData];
}
private async enhancePapersWithAI(papers: any[], originalQuery: string) {
// AI を使用して論文の関連性スコアリングと分類
const aiPrompt = `
以下の論文リストを「${originalQuery}」との関連性で評価し、分類してください。
論文リスト:
${papers.map((p, i) => `${i + 1}. ${p.title}`).join('\n')}
各論文について以下を判定してください:
1. 関連性スコア(0-1)
2. カテゴリ(理論/実装/応用/レビュー)
3. 重要度(高/中/低)
4. 読むべき優先順位
`;
// AI API 呼び出し(実装は省略)
const aiResponse = await this.callAIAPI(aiPrompt);
return papers.map((paper, index) => ({
...paper,
aiAnalysis: aiResponse.papers[index],
}));
}
}
PDFまたは全文テキストの取得
Abstract, Introduction, Method等に分割
各セクションの詳細分析
構造化された要約の作成
既存研究との関連付け
// src/analysis/paper-analyzer.js
import { OpenAI } from 'openai';
import { PDFExtractor } from './pdf-extractor.js';
import { CitationAnalyzer } from './citation-analyzer.js';
export class PaperAnalyzer {
constructor(openaiKey) {
this.openai = new OpenAI({ apiKey: openaiKey });
this.pdfExtractor = new PDFExtractor();
this.citationAnalyzer = new CitationAnalyzer();
}
async analyzePaper(paperData) {
// 全文取得
const fullText = await this.getFullText(paperData);
// セクション分割
const sections = this.extractSections(fullText);
// 各セクションの分析
const analysis = await this.performDeepAnalysis(sections);
// 引用ネットワーク分析
const citationNetwork = await this.citationAnalyzer.analyze(paperData);
// 総合評価
return this.synthesizeAnalysis(analysis, citationNetwork);
}
async getFullText(paperData) {
if (paperData.pdfLink) {
return await this.pdfExtractor.extractText(paperData.pdfLink);
} else if (paperData.htmlLink) {
return await this.extractFromHTML(paperData.htmlLink);
} else {
// フォールバック: Abstract のみ
return paperData.snippet || paperData.abstract;
}
}
extractSections(fullText) {
const sections = {
title: '',
abstract: '',
introduction: '',
relatedWork: '',
methodology: '',
experiments: '',
results: '',
discussion: '',
conclusion: '',
references: '',
};
// セクションヘッダーのパターン
const sectionPatterns = {
abstract: /(?:abstract|要旨|概要)[\s\S]*?(?=\n(?:1\.|introduction|はじめに))/i,
introduction: /(?:1\.|introduction|はじめに)[\s\S]*?(?=\n(?:2\.|related|background))/i,
relatedWork: /(?:2\.|related work|background|関連研究)[\s\S]*?(?=\n(?:3\.|method|approach))/i,
methodology: /(?:3\.|method|approach|提案手法)[\s\S]*?(?=\n(?:4\.|experiment|evaluation))/i,
experiments: /(?:4\.|experiment|evaluation|実験)[\s\S]*?(?=\n(?:5\.|result|discussion))/i,
results: /(?:5\.|result|結果)[\s\S]*?(?=\n(?:6\.|discussion|conclusion))/i,
discussion: /(?:6\.|discussion|考察)[\s\S]*?(?=\n(?:7\.|conclusion|まとめ))/i,
conclusion: /(?:7\.|conclusion|まとめ|結論)[\s\S]*?(?=\n(?:reference|参考文献))/i,
references: /(?:reference|参考文献|bibliography)[\s\S]*/i,
};
// 各セクションの抽出
for (const [section, pattern] of Object.entries(sectionPatterns)) {
const match = fullText.match(pattern);
if (match) {
sections[section] = match[0].trim();
}
}
return sections;
}
async performDeepAnalysis(sections) {
const analyses = {};
// Abstract 分析
if (sections.abstract) {
analyses.abstract = await this.analyzeAbstract(sections.abstract);
}
// Methodology 分析
if (sections.methodology) {
analyses.methodology = await this.analyzeMethodology(sections.methodology);
}
// Results 分析
if (sections.results) {
analyses.results = await this.analyzeResults(sections.results);
}
// 新規性評価
analyses.novelty = await this.assessNovelty(sections);
// 実装可能性評価
analyses.reproducibility = await this.assessReproducibility(sections);
return analyses;
}
async analyzeAbstract(abstract) {
const prompt = `
論文のAbstractを分析して、以下の要素を抽出してください:
Abstract:
${abstract}
抽出項目:
1. 研究の背景と動機
2. 提案手法の概要
3. 主要な貢献
4. 実験結果の要約
5. キーワード(5-10個)
JSON形式で回答してください。
`;
const response = await this.openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' },
});
return JSON.parse(response.choices[0].message.content);
}
async analyzeMethodology(methodology) {
const prompt = `
論文の手法セクションを分析して、技術的詳細を抽出してください:
${methodology}
分析項目:
1. アルゴリズムの概要
2. 主要な技術的革新
3. 実装の複雑さ(低/中/高)
4. 必要な計算リソース
5. 前提条件と制約
6. 他手法との違い
構造化されたJSON形式で回答してください。
`;
const response = await this.openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' },
});
return JSON.parse(response.choices[0].message.content);
}
async assessNovelty(sections) {
const combinedText = Object.values(sections).join('\n');
const prompt = `
この論文の新規性を評価してください:
${combinedText.substring(0, 3000)}...
評価基準:
1. 技術的新規性(0-10)
2. 応用的新規性(0-10)
3. 理論的貢献(0-10)
4. 実用性(0-10)
5. 既存研究との差別化ポイント
各項目にスコアと理由を付けてJSON形式で回答してください。
`;
const response = await this.openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' },
});
return JSON.parse(response.choices[0].message.content);
}
synthesizeAnalysis(analysis, citationNetwork) {
return {
summary: this.generateExecutiveSummary(analysis),
detailedAnalysis: analysis,
citationInsights: citationNetwork,
recommendations: this.generateRecommendations(analysis),
relatedPapers: this.identifyRelatedPapers(analysis, citationNetwork),
metadata: {
analyzedAt: new Date().toISOString(),
confidence: this.calculateConfidence(analysis),
},
};
}
}
チャートを読み込み中...
// src/citation/citation-manager.js
export class CitationManager {
constructor(database) {
this.db = database;
}
async buildCitationNetwork(paperId, depth = 2) {
const network = {
nodes: [],
edges: [],
clusters: [],
};
// 再帰的に引用ネットワークを構築
await this.exploreCitations(paperId, depth, network, new Set());
// クラスタリング
network.clusters = this.detectCommunities(network);
// 影響度分析
this.calculateInfluenceScores(network);
return network;
}
async exploreCitations(paperId, remainingDepth, network, visited) {
if (remainingDepth === 0 || visited.has(paperId)) return;
visited.add(paperId);
// 論文情報の取得
const paper = await this.getPaperDetails(paperId);
network.nodes.push({
id: paperId,
label: paper.title,
level: 3 - remainingDepth,
citations: paper.citations,
year: paper.year,
});
// 引用論文の取得
const citations = await this.getCitingPapers(paperId);
for (const citingPaper of citations) {
network.edges.push({
from: citingPaper.id,
to: paperId,
weight: citingPaper.relevance,
});
// 再帰的探索
await this.exploreCitations(
citingPaper.id,
remainingDepth - 1,
network,
visited
);
}
// 被引用論文の取得
const references = await this.getReferencedPapers(paperId);
for (const referencedPaper of references) {
network.edges.push({
from: paperId,
to: referencedPaper.id,
weight: referencedPaper.importance,
});
await this.exploreCitations(
referencedPaper.id,
remainingDepth - 1,
network,
visited
);
}
}
detectCommunities(network) {
// Louvain アルゴリズムによるコミュニティ検出
const communities = {};
const modularity = this.calculateModularity(network);
// 各ノードをコミュニティに割り当て
network.nodes.forEach(node => {
const community = this.findBestCommunity(node, network, communities);
communities[community] = communities[community] || [];
communities[community].push(node.id);
});
return Object.entries(communities).map(([id, members]) => ({
id,
members,
topic: this.identifyCommonTopic(members, network),
size: members.length,
}));
}
calculateInfluenceScores(network) {
// PageRank アルゴリズムによる影響度計算
const damping = 0.85;
const iterations = 100;
const scores = {};
// 初期化
network.nodes.forEach(node => {
scores[node.id] = 1 / network.nodes.length;
});
// 反復計算
for (let i = 0; i < iterations; i++) {
const newScores = {};
network.nodes.forEach(node => {
let score = (1 - damping) / network.nodes.length;
// 入ってくるエッジからのスコア
const incomingEdges = network.edges.filter(e => e.to === node.id);
incomingEdges.forEach(edge => {
const fromNode = network.nodes.find(n => n.id === edge.from);
const outDegree = network.edges.filter(e => e.from === edge.from).length;
score += damping * (scores[edge.from] / outDegree) * edge.weight;
});
newScores[node.id] = score;
});
Object.assign(scores, newScores);
}
// スコアをノードに適用
network.nodes.forEach(node => {
node.influenceScore = scores[node.id];
node.rank = this.calculateRank(scores[node.id], node.citations, node.year);
});
}
async generateBibliography(paperIds, style = 'APA') {
const papers = await this.db.getPapers(paperIds);
const bibliography = [];
for (const paper of papers) {
const citation = this.formatCitation(paper, style);
bibliography.push({
id: paper.id,
citation,
bibtex: this.generateBibtex(paper),
ris: this.generateRIS(paper),
});
}
return bibliography.sort((a, b) => a.citation.localeCompare(b.citation));
}
formatCitation(paper, style) {
const styles = {
APA: (p) => {
const authors = p.authors.join(', ');
return `${authors} (${p.year}). ${p.title}. ${p.publication}. ${p.doi ? `https://doi.org/${p.doi}` : ''}`;
},
MLA: (p) => {
const firstAuthor = p.authors[0];
const otherAuthors = p.authors.slice(1).join(', ');
return `${firstAuthor}${otherAuthors ? `, ${otherAuthors}` : ''}. "${p.title}." ${p.publication} (${p.year}).`;
},
Chicago: (p) => {
const authors = p.authors.join(', ');
return `${authors}. "${p.title}." ${p.publication} (${p.year}).`;
},
};
return styles[style](paper);
}
generateBibtex(paper) {
const type = paper.type || 'article';
const key = this.generateCiteKey(paper);
return `@${type}{${key},
title={${paper.title}},
author={${paper.authors.join(' and ')}},
journal={${paper.publication}},
year={${paper.year}},
volume={${paper.volume || ''}},
number={${paper.issue || ''}},
pages={${paper.pages || ''}},
doi={${paper.doi || ''}},
url={${paper.url || ''}}
}`;
}
}
// src/trends/trend-analyzer.js
export class TrendAnalyzer {
constructor() {
this.timeSeriesAnalyzer = new TimeSeriesAnalyzer();
this.topicModeler = new TopicModeler();
this.predictiveModel = new PredictiveModel();
}
async analyzeResearchTrends(field, timeRange) {
// データ収集
const papers = await this.collectPapersOverTime(field, timeRange);
// トピックモデリング
const topics = await this.topicModeler.extractTopics(papers);
// 時系列分析
const trends = this.analyzeTopicEvolution(topics, papers);
// 予測分析
const predictions = await this.predictFutureTrends(trends);
// 可視化データ生成
const visualizations = this.generateVisualizationData(trends, predictions);
return {
currentTrends: this.identifyCurrentHotTopics(trends),
emergingTopics: this.detectEmergingTopics(trends),
decliningTopics: this.detectDecliningTopics(trends),
predictions,
visualizations,
insights: this.generateInsights(trends, predictions),
};
}
analyzeTopicEvolution(topics, papers) {
const evolution = {};
topics.forEach(topic => {
evolution[topic.id] = {
name: topic.name,
keywords: topic.keywords,
timeline: this.createTimeline(topic, papers),
growth: this.calculateGrowthRate(topic, papers),
momentum: this.calculateMomentum(topic, papers),
maturity: this.assessMaturity(topic, papers),
};
});
return evolution;
}
createTimeline(topic, papers) {
const timeline = {};
papers.forEach(paper => {
const year = paper.year;
const relevance = this.calculateTopicRelevance(paper, topic);
if (relevance > 0.3) {
timeline[year] = timeline[year] || {
count: 0,
totalCitations: 0,
papers: [],
};
timeline[year].count++;
timeline[year].totalCitations += paper.citations;
timeline[year].papers.push({
id: paper.id,
title: paper.title,
relevance,
});
}
});
return timeline;
}
identifyCurrentHotTopics(trends) {
const currentYear = new Date().getFullYear();
const recentYears = [currentYear - 2, currentYear - 1, currentYear];
return Object.entries(trends)
.map(([topicId, data]) => {
const recentActivity = recentYears.reduce((sum, year) => {
return sum + (data.timeline[year]?.count || 0);
}, 0);
const recentCitations = recentYears.reduce((sum, year) => {
return sum + (data.timeline[year]?.totalCitations || 0);
}, 0);
return {
topic: data.name,
keywords: data.keywords,
score: recentActivity * 0.4 + recentCitations * 0.6,
growth: data.growth,
papers: recentActivity,
};
})
.sort((a, b) => b.score - a.score)
.slice(0, 10);
}
async predictFutureTrends(trends) {
const predictions = {};
for (const [topicId, data] of Object.entries(trends)) {
const timeSeries = this.extractTimeSeries(data.timeline);
// ARIMA モデルによる予測
const forecast = await this.timeSeriesAnalyzer.forecast(timeSeries, {
periods: 3, // 3年先まで予測
method: 'ARIMA',
});
predictions[topicId] = {
topic: data.name,
forecast: forecast.predictions,
confidence: forecast.confidence,
trend: this.classifyTrend(forecast),
};
}
return predictions;
}
generateVisualizationData(trends, predictions) {
return {
heatmap: this.generateTrendHeatmap(trends),
networkGraph: this.generateTopicNetwork(trends),
timeSeriesChart: this.generateTimeSeriesData(trends, predictions),
bubbleChart: this.generateBubbleChartData(trends),
sankeyDiagram: this.generateTopicFlowData(trends),
};
}
generateTrendHeatmap(trends) {
const years = this.getYearRange(trends);
const topics = Object.keys(trends);
const heatmapData = topics.map(topicId => {
const topic = trends[topicId];
return years.map(year => ({
x: year,
y: topic.name,
value: topic.timeline[year]?.count || 0,
citations: topic.timeline[year]?.totalCitations || 0,
}));
}).flat();
return {
data: heatmapData,
xAxis: years,
yAxis: topics.map(id => trends[id].name),
};
}
}
このシステムの導入により、文献調査にかかる時間が週 20 時間から 2 時間に短縮されました。 特に、関連研究の見落としがなくなり、研究の質が大幅に向上しました。 博士課程の学生たちも、より研究に集中できるようになっています。
// examples/auto-review-generator.js
async function generateSystematicReview(topic, criteria) {
const pipeline = new ResearchPipeline();
// 1. 包括的な文献検索
const papers = await pipeline.comprehensiveSearch({
topic,
databases: ['Google Scholar', 'PubMed', 'ArXiv', 'IEEE Xplore'],
yearRange: { from: 2015, to: 2025 },
languages: ['en', 'ja'],
minCitations: 10,
});
console.log(`Found ${papers.length} papers`);
// 2. スクリーニング
const screened = await pipeline.screenPapers(papers, {
inclusionCriteria: criteria.inclusion,
exclusionCriteria: criteria.exclusion,
qualityThreshold: 0.7,
});
console.log(`${screened.length} papers after screening`);
// 3. データ抽出
const extractedData = await pipeline.extractData(screened, {
fields: [
'methodology',
'results',
'limitations',
'future_work',
'datasets_used',
'evaluation_metrics',
],
});
// 4. メタ分析
const metaAnalysis = await pipeline.performMetaAnalysis(extractedData, {
synthesisMethod: 'thematic',
statisticalAnalysis: true,
effectSizeCalculation: true,
});
// 5. レビュー論文生成
const review = await pipeline.generateReview({
title: `${topic}: A Systematic Review`,
sections: {
introduction: await generateIntroduction(topic, papers.length),
methodology: await generateMethodologySection(criteria),
results: await generateResultsSection(metaAnalysis),
discussion: await generateDiscussion(metaAnalysis),
conclusion: await generateConclusion(metaAnalysis),
references: await generateReferences(screened),
},
style: 'ACM Computing Surveys',
length: 10000, // words
});
// 6. 図表の生成
const figures = await pipeline.generateFigures({
prismaFlowDiagram: true,
trendAnalysis: true,
qualityAssessment: true,
forestPlot: metaAnalysis.quantitative,
});
return {
review,
figures,
supplementaryMaterials: {
searchStrategy: pipeline.getSearchLog(),
extractedData: extractedData,
analysisCode: pipeline.getAnalysisCode(),
},
};
}
// 使用例
const reviewTopic = "Transformer Models in Computer Vision";
const criteria = {
inclusion: [
"Empirical studies using transformer architectures",
"Computer vision applications",
"Published in peer-reviewed venues",
"Includes quantitative evaluation",
],
exclusion: [
"Survey papers",
"Workshop papers",
"Preprints without peer review",
"Non-English papers",
],
};
const systematicReview = await generateSystematicReview(reviewTopic, criteria);
機能 | 説明 | 更新頻度 | 利用者 |
---|---|---|---|
ホットトピック | 急上昇中の研究テーマ | 毎日 | 研究者全般 |
引用分析 | 高被引用論文の追跡 | 週次 | PI・教授 |
著者ネットワーク | 共同研究の可視化 | 月次 | 研究企画 |
予測モデル | 将来のトレンド予測 | 四半期 | 戦略立案 |
アラート | 関連論文の即時通知 | リアルタイム | 個人研究者 |
図表・数式の自動解析機能追加
中国語・韓国語論文への対応
論文査読プロセスの自動化
AIによる研究計画書作成支援