はじめに
先日の記事「コーディング・エージェント Cline の Workflows で OCI Speech サービスを使った音声データの文字起こしと要約を半自動化してみた」で、Cline の Workflows を使った音声文字起こしの半自動化ワークフローを紹介しました。
あの記事を公開した後もワークフローを日常的に使っているのですが、Cline がコマンドの実行結果を誤読して余計なリトライを繰り返したり、環境に起因するエスケープの問題でコマンドが失敗したりと、「試行錯誤ループ」に陥るケースが散見されました。ワークフローの趣旨は手順を定型化して手間を減らすことなのに、エージェントが迷走してトークンと時間を浪費してしまうようなことがありました。
そこで、実際に遭遇した典型的な問題とその回避策をワークフロー定義に反映した改訂版を作成しました。本記事では、どんな問題が起きていたのか、それをワークフロー定義のレベルでどう防いだのかを紹介します。
改訂の方針
改訂にあたっては、大きく4つの方向性を意識しました。
1つ目は、CLI コマンドの確実な実行と出力取得です。VS Code のターミナル上で CLI の出力が折り返されると、Cline がその境界を正しく読めず、中間にゴミ文字が入った状態でパラメータとして使ってしまうことがありました。これを根本的に防ぐための仕組みをルールに組み込んでいます。
2つ目は、実行環境の差異への対応です。Windows 上の Git Bash で動かしているにもかかわらず、Cline が PowerShell 流のエスケープを適用してコマンドが失敗するケースがありました。環境を最初に判定させ、以降のすべてのステップでエスケープ方式を統一するようにしています。
3つ目は、不要なインタラクションの削減です。オリジナル版では各パラメータを1つずつ順に確認していたため、すべてデフォルトで問題ない場合でも応答を何往復もする必要がありました。また、Cline がアップロードやジョブ作成のたびに承認を求めてくるのも煩雑でした。
4つ目は、エラー原因の事前排除です。前回の実行で残った中間ファイルが次回に干渉したり、OCI CLI の 404 レスポンスをエラーと誤解したりといった問題を、ワークフロー定義側で先回りして防止しています。
以降、具体的な改善点を順に見ていきます。
改善点
1. CLI 出力のファイルリダイレクト
元記事の TIPS でも触れたとおり、VS Code ターミナルで CLI の出力が折り返されると、Cline がその境界を誤読することがあります。たとえば compartmentId のような長い OCID 文字列が途中で壊れてしまい、認証エラーの原因になることがありました。
改訂版では、ルールに「oci コマンドは、出力を tmp フォルダ下のファイルへリダイレクトして、実行後、出力ファイルを読み出して結果を取得します」という指示を追加しました。ターミナルのバッファを経由せず、ファイルから出力を読み取ることで、折り返しや制御文字の影響を完全に排除できます。
2. 実行環境の明示的な判定(STEP-0 の追加)
Git Bash 上で Cline を使っていると、Cline が PowerShell 向けのエスケープ(バッククォートやシングルクォートの扱いなど)を適用してしまい、コマンドが通らないことがありました。
改訂版では、ワークフローの冒頭に STEP-0 として環境判定ステップを新設しています。$BASH_VERSION と $PSModulePath の2つの環境変数をチェックし、どちらの環境で動いているかを確認します。両方セットされている場合は bash と判定するルールにしていますが、これは Git Bash が Windows 上で動作するときに PowerShell の環境変数を継承しているケースを想定したものです。以降のステップでは、ここで判定した環境に合わせてコマンドラインを構築するよう指示しています。
3. tmp フォルダのクリーンアップ(STEP-1 の追加)
ワークフローを繰り返し実行していると、前回の実行で生成された JSON ファイルや OCI CLI の出力ファイルが tmp フォルダに残ったままになります。これが後続のステップ、特にパラメータ JSON の生成やジョブ作成に干渉して、古い設定が混入するといった問題が起きていました。
改訂版では、STEP-1 として tmp フォルダのクリーンアップをユーザーに促すステップを追加しました。あえて Cline 自身にファイルを削除させず、ユーザーに手動で削除してもらう設計にしています。「Cline はファイルの削除は決して行わない」というルールを明示することで、意図しないファイル消去の事故を防ぐためです。
4. object head の 404 レスポンスの意味の明記
OCI Object Storage へのアップロード前に、同名のオブジェクトが既に存在しないかを oci os object head で確認するのですが、オブジェクトが存在しない場合は HTTP 404 が返ります。人間にとっては「存在しないという正常な応答」ですが、Cline はこれを「コマンドが失敗した」と解釈して処理を中断したり、リトライを試みたりすることがありました。
改訂版では「status 404 は、ファイル名で指定したファイルが存在しないことを意味する」という補足を追加し、Cline が 404 を正しく解釈できるようにしています。
5. ファイル名候補の末尾の ) 禁止
同名オブジェクトが既に存在する場合、Cline は別名のファイル名候補を自動生成します。ところが候補の末尾に ) が入ることがあり、シェル上でサブシェルのメタ文字として解釈されてコマンドがエラーになるケースがありました。
改訂版では「ファイル名の末尾に ) を付けることは禁止です」という一行を追加して、この問題を事前に回避しています。小さな制約ですが、このような些細な問題がエージェントの試行錯誤ループの引き金になりがちです。
6. compartment-id の動的取得
オリジナル版では compartment-id を oci_cli_rc から暗黙的に参照する想定でしたが、JSON パラメータファイルに compartmentId フィールドとして直接記載する必要があります。Cline がこの値をどこから取得するかが曖昧だったため、間違った値を使ってしまうことがありました。
改訂版では oci os bucket get --bucket-name バケット名 --query 'data."compartment-id"' --raw-out コマンドでバケット情報から動的に取得する手順を明示しています。併せて、bash 環境ではバックスラッシュによるエスケープが不要であることも注記し、STEP-0 の環境判定と連動させています。
7. パラメータ確認の UX 改善
オリジナル版では、出力先バケット、ジョブ名、言語、句読点、ダイアライゼーションの各パラメータを1つずつ順番に確認していました。5項目すべてデフォルトのままでよい場合でも、5回の応答が必要です。
改訂版では、まず全パラメータのデフォルト値を一覧で提示し、「このまま承認するか、変更するか」を1回で聞くフローに変更しました。変更したい場合だけ個別の確認に入ります。また、句読点とダイアライゼーションのデフォルト値を「有効化」と明示しました。多くのケースで有効化したいオプションなので、わざわざ毎回「有効化しますか?」と聞かれるのは手間でした。
8. ジョブ完了待機の戦略改善
OCI Speech のジョブは作成直後にすぐ処理が始まるわけではなく、ACCEPTED(受付済み・未開始)の状態を経て IN_PROGRESS になります。オリジナル版では IN_PROGRESS のみを待機条件としていたため、ACCEPTED 状態が見落とされることがありました。また、作成直後から60秒ごとにポーリングを開始していたため、最初の数回は確実に「まだ始まっていない」という無駄な API 呼び出しになっていました。
改訂版では、ジョブ作成直後に120秒の初回待機を設け、その後60秒ごとにポーリングするループ構造に変更しています。待機条件にも ACCEPTED を追加し、ジョブのライフサイクル全体をカバーするようにしました。
9. 安全なコマンドの明示
Cline は副作用のある操作(ファイルのアップロード、ジョブの作成など)を実行する前にユーザーの承認を求めます。安全性の観点では正しい動作ですが、このワークフローにおいてはアップロードもジョブ作成もユーザーが意図的に実行する操作であり、毎回承認を求められるとワークフローの流れが中断されて煩わしく感じます。
改訂版では「安全なコマンド」セクションを設け、oci os object put と oci speech transcription-job create をユーザー承認不要のコマンドとして明示しました。list/read/get 系のコマンドに加えてこの2つを許可することで、ワークフロー全体がスムーズに流れるようになりました。
10. 要約ファイル名のユーザーへの提示
オリジナル版では、要約結果のマークダウンファイルを output フォルダに保存するところまでは指示していましたが、そのファイル名をユーザーに通知する指示がありませんでした。ワークフローが完了した後に「あのファイルどこだっけ」と探す手間が生じていました。
改訂版では「ファイル名をユーザーへ提示します」という一文を追加し、成果物の所在を明確にしています。
改訂版ワークフロー
以下が改訂版のワークフロー定義ファイル全文です。
改訂版ワークフロー定義ファイルを展開する
# OCI Speech を使って文字起こしを行うワークフロー
## ルール
- ユーザーへ何かを確認する場合は、ツールを使って選択肢を提示して、その中からユーザーに選ばせます
- 確認は1項目づつ順番に行います
- `OCI setup` を呼び出すことは禁止です
- OCI の `config` ファイルを読み出すことは禁止です
- OCI CLI が必要とするテナンシID、設定ファイルに指定されているので、CLI のオプションに明示的な指定は行いません
- `oci` コマンドは、出力を tmp フォルダ下のファイルへリダイレクトして、実行後、出力ファイルを読み出して結果を取得します
## 安全なコマンド
list/read/get 系コマンドに加えて、以下のコマンドは安全とみなしユーザーの承認は不要です
- `oci os object put`
- `oci speech transcription-job create`
## STEP
### STEP-0 環境の確認
コマンドラインのエスケープ判定のためコマンドの実行環境を確認する
- 環境変数 $BASH_VERSION がセットされていれば bash 環境
- 環境変数 $PSModulePath がセットされていれば PowerShell 環境
- 両方がセットされていれば、 bash 環境
[IMPORTANT]以後は、ここで確認した環境に合わせてコマンドラインのエスケープの要否、タイプを判断します
### STEP-1 tmp フォルダの掃除を促す
[IMPORTANT] Cline は、ファイルの削除は決して行いません。ユーザーへ tmp フォルダ下のファイルの削除を促してください。
- tmp フォルダにファイルがあるかどうか確認します
- ファイルがある場合は、ユーザーへファイル削除を促し、削除が完了したら「削除完了」と応答するように促します
- ユーザーから「削除完了」応答があったら次の処理へ進みます
### STEP-2 音声データの指定
- `audio_data` フォルダーへ音声データを配置するようにユーザーへ促します
- `audio_data` フォルダーのファイルを一覧表示して、ユーザーに文字起こし対象の音声データファイルを選択させます
- 選択されたファイル名にスペース、空白や特殊記号が含まれる場合は、ユーザーに確認した上で、"_"に置き換えて`mv`でファイル名を変更します
### STEP-3 音声データのオブジェクトストレージへのアップロード
- アップロード先のバケットは一覧を提示してユーザーに選択させます
- `oci os object head --bucket-name バケット名 --name ファイル名` コマンドで既に同名のオブジェクトがバケットに存在しないかどうか確認します。status 404 は、ファイル名で指定したファイルが存在しないことを意味する
- 同名オブジェクトがある場合は、上書きするか、別の名前でアップロードするか確認します
- 上書きするときは、`oci os object put` コマンドに `--force`オプションを指定します
- 別の名前でアップロードする際は適切なファイル名候補を提示して確認します。ファイル名の末尾に')'を付けることは禁止です
- 音声データファイルを選択したバケットへアップロードします
- アップロードが成功したかとうかを `oci os object head --bucket-name バケット名 --name ファイル名` コマンドで確認します
### STEP-4 OCI Speech ジョブのパラメータの確認
以下をツールを使ってユーザーに確認して、パラメータを OCI CLI が期待するフォーマットの JSON ファイルに書きだします。JSON ファイルは、tmp フォルダーに配置します
- 出力先バケットは、音声データと同じバケットをデフォルトとして確認
- ジョブ名と説明は、同じ文字列として、音声データファイル名から生成した英数字と"-"、"_"で構成されるものをデフォルトとして確認。日本語、スペース、空白と特殊記号は使用できません
- 言語は英語をデフォルトとして確認
- 句読点を有効化するかどうか?(デフォルトは有効化)
- ダイアライゼーションを有効化するかどうか?有効化する場合、人数は自動検知。(デフォルトは有効化)
なお、文字起こし結果は json と srt の両方を取得します
- 確認方法は、最初に「出力先バケット」、「ジョブ名と説明(共通)」、「言語」、「句読点」、「ダイアライゼーション」についてデフォルトを箇条書きで列挙して、デフォルトを承認するか、変更するかをユーザーに尋ね、変更の場合は個々のパラメータについて確認します
#### compartment-id
compartment-id は、`oci os bucket get --bucket-name バケット名 --query 'data."compartment-id"' --raw-out` で取得する。bash 環境の場合は、バックスラッシュによるエスケープは不要です。
#### [CRITICAL]JSON ファイルのパラメータの制約
- `description`は、英数字と"-"、"_"で構成されます。日本語、スペース、空白と特殊記号は使用できません
- `display-name`は、英数字と"-"、"_"で構成されます。日本語、スペース、空白と特殊記号は使用できません
- `object-names`は、日本語、スペース、空白と特殊記号は使用できません
#### サンプル JSON ファイル
```
{
"compartmentId": "ocid1.compartment.oc1..aaaaaaaaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"inputLocation": {
"locationType": "OBJECT_LIST_INLINE_INPUT_LOCATION",
"objectLocations": [
{
"bucketName": "speech-bucket-20250127-1510",
"namespaceName": "orasejapan",
"objectNames": [
"developer_coaching_oci_log_analytics_gen_ai_real_time_intelligence.mp3"
]
}
]
},
"outputLocation": {
"bucketName": "speech-bucket-20250127-1510",
"namespaceName": "orasejapan",
"prefix": "transcription_output/"
},
"displayName": "developer_coaching_transcription",
"additionalTranscriptionFormats": ["SRT"],
"modelDetails": {
"languageCode": "en-US",
"domain": "GENERIC",
"transcriptionSettings": {
"diarization": {
"isDiarizationEnabled": true
}
}
}
}
```
### STEP-5 OCI Speech ジョブを作成
OCI CLI と STEP-3 で作成した JSON ファイルで、Speech ジョブを作成します
ジョブ作成コマンドの例
```
oci speech transcription-job create --from-json file://temp_speech_config.json
```
### STEP-6 ジョブの状態確認
#### STEP-6-1 初回待機
120秒待機します
#### STEP-6-2 ジョブの状態確認コマンド実行
ジョブの状態を確認します
- ジョブの状態確認コマンドの例
```
oci speech transcription-job get --transcription-job-id ocid1.aispeechtranscriptionjob.oc1.{REAGION}.xxxxxxxxxxxxxxxxxxxxxxxxxxx
```
#### STEP-6-3ジョブ完了を待機
ジョブの状態が、`ACCEPT` あるいは `IN_PROGRESS` である限り、確認と60秒待機をジョブが完了するまで繰り返す
### STEP-7 結果の取得
結果の JSONと SRT ファイルをダウンロードして、outputフォルダーへ配置
### STEp-8 要約
以下のプロンプトで SRT ファイルを要約して、output フォルダーへマークダウン形式で保存します。ファイル名はどの音声データの要約であるかがわかる名前にします。ファイル名をユーザーへ提示します
```
テキストデータは、動画の文字起こしです。日本語で整理してください。短くまとめることよりも文字起こしを読みのものとして読みやすく理解しやすいように整理してください。出力にはこの動画のテーマ、スピーカーなどの登場人物、全体の概要と取り上げられているすべてのトピックの要約を含めてください。トピック毎に話者が異なる場合は誰の発言の要約か明記してください。新しい製品、サービス、技術を紹介している場合はそれらをもれなく網羅してください。過度に箇条書きにせず句構造文法を意識した自然で各トピックに熟知していない読者にも読みやすい文章にしてください。ただし、文字起こしに含まれていない情報は付け加えないでください。
```
ワークフロー改訂から見えてきたこと
今回の改訂を通じて感じたのは、コーディングエージェントに渡すワークフローやプロンプトは「正しい手順を書けば動く」ものではなく、「エージェントがつまずきそうなポイントを先回りして塞いでおく」ものだということです。
人間であれば「404 が返ったら存在しないんだな」「Git Bash なんだから bash のエスケープだな」と暗黙に判断できますが、エージェントはそうした文脈を持ちません。ワークフロー定義に明示されていないことは、LLM の学習済み知識に頼ることになり、それが環境や状況によっては的外れな判断につながります。
特に印象的だったのは、ターミナル出力の折り返しという、一見ワークフローとは関係なさそうな問題が、compartmentId の破損を通じてジョブ作成の失敗にまで連鎖したことです。このような環境起因の問題は原因の特定が難しく、エージェントが自力で解決するのはほぼ不可能です。ファイルリダイレクトという単純な回避策で根本的に防げるのであれば、ルールに組み込んでおくべきでしょう。
今回のような改訂を重ねてワークフロー定義を育てていくことで、エージェントの試行錯誤を減らし、より安定した半自動化が実現できるはずです。
あとがき
ここまでくると、そもそも、スクリプトにしては?という声が聞こえてきそうです。無理やりエージェントに任せる必要性はないのはそのとおりで、このワークフローのうち固定的な処理のパイプラインはスクリプト化して、必要なパラメータの収集や決定だけをエージェントが人に確認して、スクリプトを実行するという流れも筋が良さそうですね。
一方で今回のワークフローに組み込んでいるような要素を細粒度のスキルにして蓄積していくと汎用的に利用できて将来作るワークフローや大きなスキルに細かなことを書かなくても精度よく動作してくれるようになるのではなどとも思いました。
こうしたワークフローやスキルを作っていると、どこにワークフロー、スキル、あるいはエージェントではなくスクリプトを使うべきかとか、その組み合わせや育てかたへの解像度も上げていくことができそうです。