analyze_file
Analyze Japanese text files for detailed morphological and linguistic features, including sentence complexity, part-of-speech distribution, and vocabulary diversity.
Instructions
ファイルの詳細な形態素解析と言語的特徴の分析を行います。文の複雑さ、品詞の割合、語彙の多様性などを解析します。
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| filePath | Yes | 分析するファイルのパス(Windows形式かWSL/Linux形式の絶対パスを推奨) |
Implementation Reference
- src/index.ts:550-572 (registration)Registration of the analyze_file tool with MCP server, including name, description, input schema, and handler function.this.server.tool( 'analyze_file', 'ファイルの詳細な形態素解析と言語的特徴の分析を行います。文の複雑さ、品詞の割合、語彙の多様性などを解析します。', { filePath: z.string().describe('分析するファイルのパス(Windows形式かWSL/Linux形式の絶対パスを推奨)') }, async ({ filePath }) => { try { // ファイルパスを解決 const resolvedPath = resolveFilePath(filePath); const fileContent = fs.readFileSync(resolvedPath, 'utf8'); return await this.analyzeTextImpl(fileContent); } catch (error: any) { return { content: [{ type: 'text' as const, text: `ファイル読み込みエラー: ${error.message}` }], isError: true }; } } );
- src/index.ts:268-465 (handler)Core handler logic for analyze_file tool: initializes tokenizer, performs morphological analysis with kuromoji, computes POS ratios, script types, vocabulary diversity, honorifics, and formats detailed analysis report.private async analyzeTextImpl(text: string) { try { // 形態素解析器の初期化チェック let tokenizer; try { tokenizer = await initializeTokenizer(); } catch (error) { return { content: [{ type: 'text' as const, text: '形態素解析器の初期化に失敗しました。しばらく待ってから再試行してください。' }], isError: true }; } // テキストを文に分割(。で区切る) const sentences = text.split(/[。.!?!?]/g).filter(s => s.trim().length > 0); // 形態素解析を実行 const tokens = tokenizer.tokenize(text); // 基本的な分析結果 const totalChars = text.replace(/[\s\n\r]/g, '').length; const totalSentences = sentences.length; const totalMorphemes = tokens.length; // 品詞別のカウント const posCounts: Record<string, number> = {}; const particleCounts: Record<string, number> = {}; let totalParticles = 0; // 文字種別のカウント const scriptCounts = { hiragana: 0, katakana: 0, kanji: 0, alphabet: 0, digit: 0, other: 0 }; // 単語の一意性確認用 const uniqueWords = new Set<string>(); let katakanaWords = 0; let punctuationCount = 0; const honorificExpressions = ['です', 'ます', 'でした', 'ました', 'ございます', 'いただく', 'なさる', 'れる', 'られる', 'どうぞ', 'お', 'ご']; let honorificCount = 0; // 各トークンを処理 tokens.forEach((token: any) => { // 品詞カウント posCounts[token.pos] = (posCounts[token.pos] || 0) + 1; // 助詞カウント if (token.pos === '助詞') { particleCounts[token.surface_form] = (particleCounts[token.surface_form] || 0) + 1; totalParticles++; } // 単語カウント uniqueWords.add(token.basic_form); // カタカナ語カウント if (/^[\u30A0-\u30FF]+$/.test(token.surface_form)) { katakanaWords++; } // 句読点カウント if (token.pos === '記号' && (token.pos_detail_1 === '句点' || token.pos_detail_1 === '読点')) { punctuationCount++; } // 敬語表現カウント if (honorificExpressions.some(expr => token.surface_form.includes(expr) || token.basic_form.includes(expr))) { honorificCount++; } }); // 文字種のカウント for (const char of text) { if (/[\u3040-\u309F]/.test(char)) { scriptCounts.hiragana++; } else if (/[\u30A0-\u30FF]/.test(char)) { scriptCounts.katakana++; } else if (/[\u4E00-\u9FAF]/.test(char)) { scriptCounts.kanji++; } else if (/[a-zA-Z]/.test(char)) { scriptCounts.alphabet++; } else if (/[0-90-9]/.test(char)) { scriptCounts.digit++; } else if (!/\s/.test(char)) { scriptCounts.other++; } } // 各指標の計算 const totalNonSpaceChars = Object.values(scriptCounts).reduce((a, b) => a + b, 0); // features.ymlに基づく解析結果 const analysisResults = { average_sentence_length: { name: '平均文長', value: totalSentences > 0 ? (totalChars / totalSentences).toFixed(2) : '0.00', unit: '文字/文', description: '一文の長さ。長すぎると読みにくくなる。' }, average_morphemes_per_sentence: { name: '文あたりの形態素数', value: totalSentences > 0 ? (totalMorphemes / totalSentences).toFixed(2) : '0.00', unit: '形態素/文', description: '文の密度や構文の複雑さを表す。' }, pos_ratio: { name: '品詞の割合', value: Object.entries(posCounts).map(([pos, count]) => { return `${pos}: ${((count / totalMorphemes) * 100).toFixed(2)}%`; }).join(', '), unit: '%', description: '名詞・動詞・形容詞などの使用バランスを分析。' }, particle_ratio: { name: '助詞の割合', value: Object.entries(particleCounts) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map(([particle, count]) => { return `${particle}: ${((count / totalParticles) * 100).toFixed(2)}%`; }).join(', '), unit: '%', description: '主語・目的語などの構造分析や文の流れを判断。' }, script_type_ratio: { name: '文字種の割合', value: Object.entries(scriptCounts).map(([type, count]) => { return `${type}: ${((count / totalNonSpaceChars) * 100).toFixed(2)}%`; }).join(', '), unit: '%', description: 'ひらがな・カタカナ・漢字・英数字の構成比率。' }, vocabulary_diversity: { name: '語彙の多様性(タイプ/トークン比)', value: ((uniqueWords.size / totalMorphemes) * 100).toFixed(2), unit: '%', description: '語彙の豊かさや表現力の指標。' }, katakana_word_ratio: { name: 'カタカナ語の割合', value: ((katakanaWords / totalMorphemes) * 100).toFixed(2), unit: '%', description: '外来語や専門用語の多さ、カジュアルさを示す。' }, honorific_frequency: { name: '敬語の頻度', value: totalSentences > 0 ? (honorificCount / totalSentences).toFixed(2) : '0.00', unit: '回/文', description: '丁寧・フォーマルさを示す。' }, punctuation_per_sentence: { name: '句読点の平均数', value: totalSentences > 0 ? (punctuationCount / totalSentences).toFixed(2) : '0.00', unit: '個/文', description: '文の区切りや読みやすさに影響。' } }; // 結果をテキスト形式で整形 const resultText = `# テキスト分析結果 ## 基本情報 - 総文字数: ${totalChars}文字 - 文の数: ${totalSentences} - 総形態素数: ${totalMorphemes} ## 詳細分析 ${Object.entries(analysisResults).map(([key, data]) => { return `### ${data.name} (${data.unit}) - 値: ${data.value} - 説明: ${data.description}`; }).join('\n\n')} `; return { content: [{ type: 'text' as const, text: resultText }] }; } catch (error: any) { return { content: [{ type: 'text' as const, text: `分析中にエラーが発生しました: ${error.message}` }], isError: true }; } }
- src/index.ts:553-555 (schema)Input schema using Zod: requires 'filePath' string parameter describing the file to analyze.{ filePath: z.string().describe('分析するファイルのパス(Windows形式かWSL/Linux形式の絶対パスを推奨)') },
- src/index.ts:125-166 (helper)Helper function to resolve file paths, supporting Windows absolute paths and WSL/Linux (/c/... ) formats, with existence checks.function resolveFilePath(filePath: string): string { try { // WSL/Linux形式のパス (/c/Users/...) をWindows形式 (C:\Users\...) に変換 if (filePath.match(/^\/[a-zA-Z]\//)) { // /c/Users/... 形式を C:\Users\... 形式に変換 const drive = filePath.charAt(1).toUpperCase(); let windowsPath = `${drive}:${filePath.substring(2).replace(/\//g, '\\')}`; console.error(`WSL/Linux形式のパスをWindows形式に変換: ${filePath} -> ${windowsPath}`); if (fs.existsSync(windowsPath)) { console.error(`変換されたパスでファイルを発見: ${windowsPath}`); return windowsPath; } } // 通常の絶対パスの処理 if (path.isAbsolute(filePath)) { if (fs.existsSync(filePath)) { console.error(`絶対パスでファイルを発見: ${filePath}`); return filePath; } // 絶対パスでファイルが見つからない場合はエラー throw new Error(`指定された絶対パス "${filePath}" が存在しません。パスが正しいか確認してください。` + ` Windows形式(C:\\Users\\...)かWSL/Linux形式(/c/Users/...)で指定してください。`); } // 相対パスの場合、カレントワーキングディレクトリから検索 const cwdPath = path.resolve(process.cwd(), filePath); if (fs.existsSync(cwdPath)) { console.error(`カレントディレクトリでファイルを発見: ${cwdPath}`); return cwdPath; } // どこにも見つからなかった場合 throw new Error(`ファイル "${filePath}" が見つかりませんでした。絶対パスで指定してください。` + ` Windows形式(C:\\Users\\...)かWSL/Linux形式(/c/Users/...)で指定可能です。`); } catch (error) { throw error; } }
- src/index.ts:77-122 (helper)Helper to asynchronously initialize the kuromoji tokenizer instance, locating dictionary paths automatically.async function initializeTokenizer() { // すでに初期化されている場合 if (tokenizerInstance) { return tokenizerInstance; } // 初期化中の場合は既存のPromiseを返す if (initializingPromise) { return initializingPromise; } console.error('形態素解析器の初期化を開始...'); // 辞書パスを取得 const dicPath = findDictionaryPath(); console.error(`使用する辞書パス: ${dicPath}`); // 初期化処理をPromiseでラップ initializingPromise = new Promise((resolve, reject) => { try { kuromoji.builder({ dicPath }).build((err, tokenizer) => { if (err) { console.error(`形態素解析器の初期化エラー: ${err.message || err}`); initializationError = err; initializingPromise = null; // リセットして再試行できるようにする tokenizerReady = false; reject(err); return; } console.error('形態素解析器の初期化が完了しました'); tokenizerInstance = tokenizer; tokenizerReady = true; resolve(tokenizer); }); } catch (error) { console.error(`形態素解析器の初期化中に例外が発生: ${error.message || error}`); initializationError = error; initializingPromise = null; // リセットして再試行できるようにする tokenizerReady = false; reject(error); } }); return initializingPromise; }