MastraのWorkflow学習用に、人間に判断させるワークフロー(朗読に使用するVOICEVOXキャラをオーディション形式で選ぶ)を作ります。
作るWorkflow
- 実行時に青空文庫の『走れメロス』を取得
- VOICEVOXの
/speakersAPIから話者一覧を取得 - LLMが2つの観点で朗読キャラを提案する
- 同じ冒頭文で、それぞれのサンプルWAVを作る
- ユーザーがサンプルを聞いて選ぶ
- 選ばれなければ、別候補を再提案する
- 決定したキャラで全文朗読WAVを分割生成する
提案は content と meta の2方針として、出力を固定します。
- pattern1: 本文の内容から選ぶ
- pattern2: 作品の評価や教材性など、メタ情報から選ぶ
たとえばpattern1では「緊迫感、友情、メロスの人物像、男性的な声質、叙事性」などを見ます。pattern2では「日本の教材としての定番性、信実の物語、パロディにもされる認知度」などを見ます。
Mastraでのworkflow実装方法
Mastraでは処理をStepとして分けます。
const voiceWorkflow = createWorkflow({
id: 'hashire-melos-audition-workflow',
inputSchema: workflowInputSchema,
outputSchema,
})
.then(loadSourceStep)
.then(loadVoiceCatalogStep)
.then(researchMetaStep)
.then(prepareAuditionStep)
.then(auditionStep)
.then(synthesizeFullStep)
.commit()
人間の判断を仰ぐauditionStepでは、まずLLMが2つの提案を作り、VOICEVOXでサンプルを生成します。
サンプルWAVは候補ごとの違いを比較しやすいよう、どちらも同じ冒頭文を読み上げます。
LLMには、Agent側の固定指示と、実行時に組み立てるpromptの2つを渡します。
instructions: `
あなたは日本語朗読のキャスティングディレクターです。
太宰治「走れメロス」をVOICEVOXで朗読するため、候補キャラクターを選びます。
必ず2つの提案を作ります。
- pattern1/content: 作品本文の概要、語り口、登場人物、緊張感、感情曲線から声を選ぶ
- pattern2/meta: 作品の評価、教材性、信実・友情・パロディ性などのメタ情報から声を選ぶ
`
const prompt = `
作品: ${inputData.title} / ${inputData.author}
本文冒頭:
${inputData.fullText.slice(0, 1800)}
メタ情報:
${inputData.metaResearch}
VOICEVOX候補:
${compactCatalog(availableStyles.slice(0, 40))}
rejectedStyleIds:
${JSON.stringify(rejectedStyleIds)}
pattern1/content と pattern2/meta の2案を返してください。
`
その後、Workflowをsuspendします。
const audition = await createAuditionSet(inputData, rejectedStyleIds)
return await suspend({
message:
'2つの提案とサンプルWAVを確認してください。pattern1 / pattern2 / none のいずれかでresumeします。',
audition,
})
この時点でWorkflowは停止し、ユーザーの入力を待ちます
[pattern1: 本文内容から選ぶ]
キャラ: 青山龍星 / ノーマル (styleId=13)
理由: メロスの力強さと物語の重厚さに合うため
サンプル: output/audition-attempt-1-.../pattern1-13.wav
[pattern2: メタ評価から選ぶ]
キャラ: 春日部つむぎ / ノーマル (styleId=8)
理由: 教材として聞きやすく、親しみやすさがあるため
サンプル: output/audition-attempt-1-.../pattern2-8.wav
選択してください (1=pattern1 / 2=pattern2 / n=どちらでもない):
選ばれたらresumeDataとしてWorkflowに戻します。
result = await run.resume({
step: auditionStep,
resumeData: {
choice,
selectedProposal: selectedProposalFor(choice, audition),
rejectedStyleIds,
},
})
pattern1またはpattern2なら、候補が確定して次の全文朗読ファイル作成へ進みます。
noneなら、現在の2候補をrejectedStyleIdsに入れて、同じauditionStep内でもう一度候補を作ります。LLMの構造化出力に失敗した場合は、フォールバック提案で継続します。
「人間が選んだら進む」「納得しなければ再提案してまた止まる」という流れを、Workflowの状態として扱えます。
実際に出てきたもの
[pattern1: 本文内容から選ぶ]
キャラ: 玄野武宏 / ノーマル (styleId=11)
理由: 主人公メロスが激怒し、行動にでるための声質は強い意志を示す必要があり、玄野武宏の正常な声質を使うことで、その意気込んでいる気持ちを表現できる。
[pattern2: メタ評価から選ぶ]
キャラ: 四国めたん / ツンツン (styleId=6)
理由: 王が自分の疑心暗鬼を正当化する場面で、緊張感のある声は王の威圧と自負を強調できる。
pattern1が無難かなぁ
理由に関してはそれらしいですが、VOICEVOXのキャラのメタ情報を整備していないのでずんだもんのほのぼのした声が頻繁に登場します。
現状の調整は行っていないため、場面ごとの感情ディレクションを入れるなどでさらに改善できそうです。