実務向けにするなら、**「議事録生成ツール」ではなく「音声資産からレビュー可能な業務成果物を生成するシステム」**として設計するのがよいです。
採用前提として、Ollama は JSON Schema による structured outputs をサポートしており、議事録JSONの安定化に使えます。faster-whisper は CTranslate2 ベースで、OpenAI Whisper 実装より高速・省メモリを掲げています。python-pptx は PowerPoint の作成・読み込み・更新、python-docx は Word 文書の作成・更新に使えるライブラリです。話者分離には pyannote.audio が実務候補になります。
以下をそのまま「実務向け設計書」として使える形で作成します。
⸻
ローカルLLM議事録生成システム 実務向け設計書
- システム目的
本システムは、会議の音声ファイルまたは動画ファイルを入力として、文字起こし、要約、議事録JSON化、Word議事録、PowerPoint報告資料を自動生成するローカル環境向け議事録生成システムである。
クラウドAIに機密情報を送信せず、社内PCまたは社内サーバー上で処理することを前提とする。
⸻
- 基本方針
重要なのは、LLMに音声や動画を直接渡さないこと。
実務では以下の流れにする。
audio / video
↓
音声抽出
↓
音声前処理
↓
ASR文字起こし
↓
話者分離・整形
↓
ローカルLLM
↓
議事録JSON
↓
Pydantic検証
↓
人間レビュー
↓
docx / pptx 出力
LLMの役割は、音声認識ではなく、文字起こし済みテキストを構造化することに限定する。
⸻
| 領域 | 採用技術 | 目的 |
|---|---|---|
| UI | Streamlit | 初期MVP、社内ツール向け |
| API | FastAPI | 将来的なAPI化、バッチ処理 |
| 音声抽出 | ffmpeg | mp4 / mov から wav を生成 |
| 文字起こし | faster-whisper | ローカルASR |
| 話者分離 | pyannote.audio | 誰が話したかの推定 |
| LLM | Ollama | ローカルLLM実行 |
| JSON制御 | Ollama structured outputs + Pydantic | JSON破綻防止 |
| Word生成 | python-docx | 詳細議事録 |
| PowerPoint生成 | python-pptx | 報告用資料 |
| 設定管理 | YAML | モデル、出力形式、テンプレート制御 |
| ログ | logging / SQLite | 処理履歴、エラー追跡 |
| ジョブ管理 | SQLite / Redis Queue | 長時間処理の管理 |
⸻
4. システム構成
local-minutes-system/
├── app.py
├── api.py
├── worker.py
├── requirements.txt
├── config.yaml
├── input/
├── work/
│ ├── audio/
│ ├── transcript/
│ ├── diarized/
│ └── logs/
├── output/
│ ├── json/
│ ├── docx/
│ └── pptx/
├── templates/
│ ├── minutes_template.docx
│ └── minutes_template.pptx
├── prompts/
│ ├── minutes_prompt.txt
│ ├── review_prompt.txt
│ └── correction_prompt.txt
├── dictionaries/
│ ├── company_terms.yaml
│ ├── member_names.yaml
│ └── project_terms.yaml
├── modules/
│ ├── audio_extract.py
│ ├── audio_preprocess.py
│ ├── transcribe.py
│ ├── diarize.py
│ ├── transcript_cleaner.py
│ ├── llm_client.py
│ ├── schema.py
│ ├── validator.py
│ ├── export_docx.py
│ ├── export_pptx.py
│ ├── review.py
│ └── logger.py
└── database/
└── jobs.db
⸻
5. 実務フロー
5.1 ファイル投入
対応形式。
音声:
- wav
- mp3
- m4a
- flac
動画: - mp4
- mov
- mkv
動画の場合は ffmpeg で音声を抽出する。
ffmpeg -i input.mp4 -vn -acodec pcm_s16le -ar 16000 -ac 1 output.wav
⸻
5.2 音声前処理
実務では、文字起こし前に以下を行う。
- モノラル化
- 16kHz変換
- 無音区間の処理
- 音量正規化
- ノイズが強いファイルの警告
初期MVPでは最低限、以下だけでよい。
- wav化
- 16kHz
- mono
⸻
5.3 文字起こし
最初は faster-whisper を使う。
from faster_whisper import WhisperModel
model = WhisperModel(
"large-v3",
device="cuda",
compute_type="float16"
)
segments, info = model.transcribe(
"work/audio/meeting.wav",
language="ja",
vad_filter=True
)
transcript = []
for segment in segments:
transcript.append({
"start": segment.start,
"end": segment.end,
"text": segment.text.strip()
})
GPUが弱い場合。
環境 推奨モデル
CPUのみ small / medium
RTX 3060クラス medium / large-v3
RTX 4070 Ti SUPER以上 large-v3
精度優先 large-v3
速度優先 small / medium
⸻
6. 話者分離の扱い
実務では、最初から完璧な話者名を出そうとしない。
話者分離は次の2段階に分ける。
Phase 1
SPEAKER_00
SPEAKER_01
SPEAKER_02
のような仮ラベルでよい。
Phase 2
レビュー画面で人間が名前を割り当てる。
SPEAKER_00 → 田中さん
SPEAKER_01 → 佐藤さん
SPEAKER_02 → 鈴木さん
実務ではこの方が安全。
LLMに勝手に参加者名を推測させない。
⸻
7. 中間JSON設計
このシステムの中心は minutes.json。
{
"metadata": {
"meeting_title": "string",
"date": "YYYY-MM-DD",
"start_time": "HH:mm or null",
"end_time": "HH:mm or null",
"source_file": "string",
"language": "ja",
"created_at": "YYYY-MM-DDTHH:mm:ss"
},
"participants": [
{
"name": "string",
"role": "string or null",
"speaker_label": "SPEAKER_00 or null"
}
],
"executive_summary": {
"summary": "string",
"key_points": [
"string"
]
},
"agenda": [
{
"title": "string",
"discussion": "string",
"decisions": [
"string"
],
"issues": [
"string"
],
"related_speakers": [
"string"
]
}
],
"decisions": [
{
"decision": "string",
"reason": "string or null",
"owner": "string or null"
}
],
"action_items": [
{
"task": "string",
"owner": "string or null",
"due_date": "YYYY-MM-DD or null",
"priority": "high | medium | low",
"status": "not_started | in_progress | done | pending",
"source": "string or null"
}
],
"risks": [
{
"risk": "string",
"impact": "high | medium | low | unknown",
"countermeasure": "string or null"
}
],
"open_questions": [
"string"
],
"next_meeting_topics": [
"string"
],
"review": {
"requires_human_review": true,
"warnings": [
"string"
]
}
}
⸻
8. Pydanticスキーマ
from pydantic import BaseModel, Field
from typing import List, Optional, Literal
class Metadata(BaseModel):
meeting_title: str
date: str
start_time: Optional[str] = None
end_time: Optional[str] = None
source_file: str
language: str = "ja"
created_at: str
class Participant(BaseModel):
name: str
role: Optional[str] = None
speaker_label: Optional[str] = None
class ExecutiveSummary(BaseModel):
summary: str
key_points: List[str] = Field(default_factory=list)
class AgendaItem(BaseModel):
title: str
discussion: str
decisions: List[str] = Field(default_factory=list)
issues: List[str] = Field(default_factory=list)
related_speakers: List[str] = Field(default_factory=list)
class Decision(BaseModel):
decision: str
reason: Optional[str] = None
owner: Optional[str] = None
class ActionItem(BaseModel):
task: str
owner: Optional[str] = None
due_date: Optional[str] = None
priority: Literal["high", "medium", "low"] = "medium"
status: Literal["not_started", "in_progress", "done", "pending"] = "not_started"
source: Optional[str] = None
class Risk(BaseModel):
risk: str
impact: Literal["high", "medium", "low", "unknown"] = "unknown"
countermeasure: Optional[str] = None
class Review(BaseModel):
requires_human_review: bool = True
warnings: List[str] = Field(default_factory=list)
class MeetingMinutes(BaseModel):
metadata: Metadata
participants: List[Participant] = Field(default_factory=list)
executive_summary: ExecutiveSummary
agenda: List[AgendaItem] = Field(default_factory=list)
decisions: List[Decision] = Field(default_factory=list)
action_items: List[ActionItem] = Field(default_factory=list)
risks: List[Risk] = Field(default_factory=list)
open_questions: List[str] = Field(default_factory=list)
next_meeting_topics: List[str] = Field(default_factory=list)
review: Review
⸻
9. LLMプロンプト設計
9.1 基本プロンプト
あなたは企業向け議事録作成アシスタントです。
以下の文字起こしを読み、指定されたJSONスキーマに厳密に従って議事録データを作成してください。
重要ルール:
- JSON以外の文章を出力しない
- 担当者名を推測で作らない
- 明確に発言されていない期限は null にする
- 決定事項、議論内容、課題、ToDoを分ける
- 雑談、相槌、重複表現は要約する
- 不明点は open_questions に入れる
- 確認が必要な内容は review.warnings に入れる
- 医療、法務、契約、金額、納期に関する内容は必ず review.warnings に入れる
- action_items には、実行すべき作業だけを入れる
- 「検討する」「確認する」などもタスクとして扱う
- 優先度が不明な場合は medium にする
文字起こし:
{transcript}
⸻
10. 実務向けの安全設計
Whisper系ASRは便利ですが、音声にない内容を文字起こしとして生成する、いわゆる hallucination リスクが研究されています。特に無音、雑音、低品質音声では注意が必要です。OpenAI Whisper 自体は多言語ASR・翻訳・言語識別に対応するモデルとして公開されていますが、実務利用ではレビュー前提にするべきです。
そのため、以下を必須にする。
- 原文文字起こしを保存する
- LLM要約後のJSONを保存する
- docx / pptx はレビュー後に確定出力する
- 金額、日付、契約、納期、担当者は警告対象にする
- 音声品質が悪い場合は警告を出す
- LLM出力をPydanticで必ず検証する
⸻
- レビュー画面で必要な機能
Streamlitで作る場合、最低限これを用意する。
- 音声/動画アップロード
- 文字起こし結果表示
- 話者ラベル編集
- 会議タイトル編集
- 参加者編集
- 要約編集
- 決定事項編集
- アクションアイテム編集
- 警告一覧表示
- docx / pptx ダウンロード
重要なのは、自動生成して終わりにしないこと。
実務では、以下の流れにする。
AI生成
↓
人間レビュー
↓
修正
↓
確定
↓
docx / pptx 出力
⸻
- docx出力仕様
Wordは詳細議事録として使う。
構成
議事録
- 会議概要
- 会議名
- 日付
- 開始時刻
- 終了時刻
- 参加者
- エグゼクティブサマリー
- 全体要約
- 重要ポイント
- 議題別内容
- 議題名
- 議論内容
- 決定事項
- 課題
- 決定事項一覧
- アクションアイテム
- タスク
- 担当者
- 期限
- 優先度
- ステータス
- リスク・懸念事項
- 未確認事項
- 次回確認事項
- AI生成に関する注意
- 本議事録はAIにより生成され、人間レビューを前提とする
⸻
- pptx出力仕様
PowerPointは報告・共有用に使う。
Slide 1: 会議タイトル
Slide 2: エグゼクティブサマリー
Slide 3: 主要決定事項
Slide 4: 主要論点
Slide 5: 課題・リスク
Slide 6: アクションアイテム
Slide 7: 未確認事項
Slide 8: 次回確認事項
pptxは詳細を書きすぎない。
目安。
1スライド = 3〜5項目
1項目 = 1〜2行
詳細はdocxに寄せる。
⸻
- 実装優先順位
Phase 1: MVP
まずここまで作る。
- Streamlit UI
- ファイルアップロード
- ffmpeg音声抽出
- faster-whisper文字起こし
- OllamaでJSON生成
- Pydantic検証
- docx生成
- pptx生成
この段階では、話者分離なしでよい。
⸻
Phase 2: 実務最低ライン
- 話者分離
- レビュー画面
- 用語辞書
- 参加者辞書
- 再生成ボタン
- JSON修正機能
- エラー時の再試行
- ログ保存
⸻
Phase 3: 社内運用レベル
- ユーザー認証
- 権限制御
- 処理履歴
- テンプレート選択
- プロジェクト別辞書
- 社内固有名詞補正
- 議事録承認フロー
- RAG連携
- バッチ処理
⸻
- config.yaml案
system:
app_name: "Local Minutes System"
language: "ja"
timezone: "Asia/Tokyo"
asr:
engine: "faster-whisper"
model: "large-v3"
device: "cuda"
compute_type: "float16"
vad_filter: true
audio:
sample_rate: 16000
channels: 1
format: "wav"
llm:
provider: "ollama"
model: "qwen3:14b"
temperature: 0.1
structured_output: true
max_retries: 2
output:
generate_docx: true
generate_pptx: true
require_review_before_export: true
templates:
docx: "templates/minutes_template.docx"
pptx: "templates/minutes_template.pptx"
review:
warn_keywords:
- "契約"
- "金額"
- "納期"
- "法律"
- "医療"
- "個人情報"
require_human_review: true
⸻
16. ローカルLLM選定
日本語議事録では、最初は以下が現実的。
用途 モデル候補
軽量 Qwen系 7B / 8B
バランス Qwen 14B
精度重視 Qwen 32B
CPU運用 7B量子化
GPU運用 14B以上
OllamaのQwen3ライブラリでは、Qwen3系モデルが提供されています。ローカルLLMはモデル更新が速いため、実装では config.yaml でモデル名を差し替えられるようにしておくのが安全です。
⸻
17. 実務で重要な例外処理
問題 対応
音声が長すぎる チャンク分割
LLMのコンテキストに入らない 議題単位で分割要約
JSONが壊れる Pydantic検証後に再生成
担当者が不明 owner を null
期限が不明 due_date を null
話者分離が不正確 レビュー画面で修正
専門用語が誤認識 用語辞書で補正
無音や雑音が多い 警告表示
出力文書が長すぎる docx詳細、pptx要約に分離
⸻
- 実務向けの最終構成
最終的にはこの構成がよいです。
[User]
↓
[Streamlit UI]
↓
[Job Manager]
↓
[Audio Processor]
↓
[ASR: faster-whisper]
↓
[Diarization: pyannote]
↓
[Transcript Cleaner]
↓
[Local LLM: Ollama]
↓
[JSON Validator: Pydantic]
↓
[Review UI]
↓
[Exporter]
├── docx
└── pptx
⸻
- MVPで作るべき最小機能
最初に作るなら、これで十分です。
- 動画/音声ファイルをアップロード
- ffmpegでwav化
- faster-whisperで文字起こし
- Ollamaで議事録JSON生成
- Pydanticで検証
- JSONを画面表示
- docx生成
- pptx生成
- ダウンロード
話者分離、RAG、承認フローは後回しでよいです。
⸻
- 結論
実務向けの正解構成は以下です。
Input audio/video
↓
Audio extraction
↓
Audio preprocessing
↓
ASR transcription
↓
Transcript cleanup
↓
Local LLM structured output
↓
Validated minutes JSON
↓
Human review
↓
docx / pptx generation
↓
Final output
この設計の肝は3つです。
- 音声処理とLLM処理を分ける
- JSONを中間データの中心にする
- 人間レビューを正式フローに入れる
これで、単なる自動要約ツールではなく、社内で運用できる議事録生成システムになります。