skillby trust-chain-organization
baml-integration
SagebaseでのBAML (Boundary ML) 統合に関する知識とベストプラクティス。BAML定義ファイルの作成、クライアント再生成、Factory Pattern実装、ハイブリッドアプローチの設計を支援します。
Installs: 0
Used in: 1 repos
Updated: 2d ago
$
npx ai-builder add skill trust-chain-organization/baml-integrationInstalls to .claude/skills/baml-integration/
# BAML Integration
## Purpose
SagebaseにおけるBAML (Boundary ML) 統合の正しい知識と実装パターンを提供します。
## When to Activate
This skill activates automatically when:
- Creating or modifying `.baml` files in `baml_src/` directory
- Implementing BAML-based services
- Working with `baml_client/` generated code
- User mentions "BAML", "Boundary ML", or "structured output"
- Creating factory classes for BAML implementations
## BAML Overview
### What is BAML?
BAML (Boundary ML) は、LLMの構造化出力を型安全に扱うためのドメイン特化言語(DSL)です。
### Key Benefits
- **型安全性**: Pydanticモデルと完全に互換性のある型定義
- **トークン効率**: 最適化されたプロンプト生成により、従来のPydantic実装よりトークン使用量を5-15%削減
- **パース精度**: LLMの出力を確実に構造化データに変換
- **プロンプト一元管理**: `.baml`ファイルにプロンプトとスキーマを集約
## BAML File Structure
### Directory Layout
```
sagebase/
├── baml_src/ # BAML定義ファイル(.baml)
│ ├── clients.baml # LLMクライアント設定
│ ├── generators.baml # コード生成設定
│ ├── minutes_divider.baml # 議事録分割
│ ├── speaker_matching.baml # 話者マッチング
│ └── politician_matching.baml # 政治家マッチング
└── baml_client/ # 自動生成されたPythonコード
├── async_client.py # 非同期クライアント
├── sync_client.py # 同期クライアント
└── types.py # 型定義
```
### BAML File Components
#### 1. Class Definitions
```baml
// Pydanticモデルに対応する型定義
class SpeakerMatch {
matched bool @description("マッチングが成功したか")
speaker_id int? @description("マッチした発言者のID")
speaker_name string? @description("マッチした発言者の名前")
confidence float @description("マッチングの信頼度(0.0-1.0)")
reason string @description("マッチング判定の理由")
}
```
**重要**:
- クラス名はPydanticモデル名と一致させる
- オプショナルフィールドは `?` を付ける
- `@description`で各フィールドの意味を明示
#### 2. Function Definitions
```baml
function MatchSpeaker(
speaker_name: string,
available_speakers: string
) -> SpeakerMatch {
client Gemini2Flash // 使用するLLMクライアント
prompt #"
あなたは議事録の発言者名マッチング専門家です。
# 抽出された発言者名
{{ speaker_name }}
# 既存の発言者リスト
{{ available_speakers }}
# マッチング基準
1. 完全一致を最優先
2. 括弧内の名前との一致
3. 信頼度は 0.8 以上の場合のみマッチ
"#
}
```
**ベストプラクティス**:
- プロンプトは `#"..."#` で囲む(ヒアドキュメント)
- 変数は `{{ variable_name }}` で展開
- マッチング基準や出力要件を明確に記述
## BAML Client Generation
### Generating Client Code
**コマンド**:
```bash
uv run python -c "import sys; sys.argv = ['baml', 'generate', '--from', 'baml_src']; from baml_py import invoke_runtime_cli; invoke_runtime_cli()"
```
**いつ実行するか**:
- 新しい`.baml`ファイルを作成した後
- 既存の`.baml`ファイルを修正した後
- `baml_client/`のコードが古い場合
**確認方法**:
```bash
# 新しい関数が生成されたか確認
grep -n "MatchSpeaker" baml_client/async_client.py
# 型チェックでエラーがないか確認
uv run --frozen pyright src/domain/services/baml_speaker_matching_service.py
```
### Generated Code Usage
```python
from baml_client.async_client import b
# BAML関数を呼び出し
baml_result = await b.MatchSpeaker(
speaker_name="山田太郎",
available_speakers="ID: 1, 名前: 山田太郎\nID: 2, 名前: 佐藤花子"
)
# 結果をPydanticモデルに変換
match_result = SpeakerMatch(
matched=baml_result.matched,
speaker_id=baml_result.speaker_id,
speaker_name=baml_result.speaker_name,
confidence=baml_result.confidence,
reason=baml_result.reason,
)
```
## Implementation Patterns
### 1. Hybrid Approach (推奨パターン)
**コンセプト**: ルールベースマッチング(高速パス)+ BAML(複雑ケース)
```python
class BAMLSpeakerMatchingService:
async def find_best_match(self, speaker_name: str) -> SpeakerMatch:
# 1. 高速パス: ルールベースマッチング
rule_based_match = self._rule_based_matching(speaker_name, available_speakers)
if rule_based_match.matched and rule_based_match.confidence >= 0.9:
return rule_based_match # LLMをスキップ
# 2. BAML: 複雑なケースのみ
baml_result = await b.MatchSpeaker(
speaker_name=speaker_name,
available_speakers=self._format_speakers_for_llm(filtered_speakers)
)
return SpeakerMatch(**baml_result.__dict__)
```
**利点**:
- トークン使用量削減(完全一致は即座に判定)
- レイテンシ削減(LLM呼び出しを最小化)
- コスト削減
### 2. Factory Pattern
**環境変数で実装を切り替え**:
```python
# src/domain/services/factories/speaker_matching_factory.py
import os
from src.domain.services.interfaces.llm_service import ILLMService
from src.domain.repositories.speaker_repository import SpeakerRepository
class SpeakerMatchingServiceFactory:
@staticmethod
def create(
llm_service: ILLMService,
speaker_repository: SpeakerRepository,
):
use_baml = os.getenv("USE_BAML_SPEAKER_MATCHING", "false").lower() == "true"
if use_baml:
from src.domain.services.baml_speaker_matching_service import (
BAMLSpeakerMatchingService,
)
return BAMLSpeakerMatchingService(llm_service, speaker_repository)
from src.domain.services.speaker_matching_service import (
SpeakerMatchingService,
)
return SpeakerMatchingService(llm_service, speaker_repository)
```
**環境変数設定** (`.env`):
```bash
USE_BAML_SPEAKER_MATCHING=false # デフォルトはfalse
USE_BAML_POLITICIAN_MATCHING=false
```
### 3. Service Implementation Structure
```python
class BAMLSpeakerMatchingService:
def __init__(
self,
llm_service: ILLMService, # 互換性のため保持(BAML使用時は不要)
speaker_repository: SpeakerRepository,
):
self.llm_service = llm_service
self.speaker_repository = speaker_repository
async def find_best_match(self, speaker_name: str) -> SpeakerMatch:
# 実装
pass
def _rule_based_matching(self, speaker_name: str, available_speakers) -> SpeakerMatch:
# ルールベースマッチング(高速パス)
pass
def _filter_candidates(self, speaker_name: str, available_speakers) -> list:
# 候補絞り込み(トークン削減)
pass
def _format_speakers_for_llm(self, speakers: list) -> str:
# LLM用フォーマット
pass
```
## Testing BAML Services
### Mock BAML Client
```python
import pytest
from unittest.mock import AsyncMock, MagicMock
@pytest.fixture
def mock_baml_client(monkeypatch):
"""BAML clientをモック化"""
mock_b = MagicMock()
mock_match_speaker = AsyncMock()
mock_match_speaker.return_value = MagicMock(
matched=True,
speaker_id=1,
speaker_name="山田太郎",
confidence=0.95,
reason="完全一致"
)
mock_b.MatchSpeaker = mock_match_speaker
monkeypatch.setattr("baml_client.async_client.b", mock_b)
return mock_b
@pytest.mark.asyncio
async def test_baml_speaker_matching(mock_baml_client):
"""BAMLマッチングのテスト"""
service = BAMLSpeakerMatchingService(mock_llm, mock_repo)
result = await service.find_best_match("山田太郎")
assert result.matched is True
assert result.speaker_id == 1
mock_baml_client.MatchSpeaker.assert_awaited_once()
```
## Token Optimization Tips
### 1. Candidate Filtering
```python
def _filter_candidates(self, speaker_name: str, available_speakers: list) -> list:
"""候補を絞り込んでトークン削減"""
candidates = []
for speaker in available_speakers:
score = 0
# 部分一致スコア
if speaker["name"] in speaker_name:
score += 3
# 会議体所属ボーナス
if speaker["id"] in affiliated_speaker_ids:
score += 10
if score > 0:
candidates.append({**speaker, "score": score})
# 上位10件のみLLMに渡す
candidates.sort(key=lambda x: x["score"], reverse=True)
return candidates[:10] # トークン削減!
```
### 2. Compact Formatting
```python
def _format_speakers_for_llm(self, speakers: list) -> str:
"""簡潔なフォーマットでトークン削減"""
formatted = []
for speaker in speakers:
# 必要最小限の情報のみ
entry = f"ID: {speaker['id']}, 名前: {speaker['name']}"
if speaker['id'] in affiliated_speaker_ids:
entry += " ★" # 短い記号で会議体所属を示す
formatted.append(entry)
return "\n".join(formatted)
```
### 3. Prompt Optimization
```baml
// ❌ 冗長なプロンプト
prompt #"
あなたは議事録の発言者名マッチング専門家です。
以下の詳細な手順に従って、慎重にマッチングを行ってください。
...(長い説明)
"#
// ✅ 簡潔なプロンプト
prompt #"
発言者名と既存リストから最適マッチを見つけてください。
# 基準
1. 完全一致優先
2. ★付きを優先
3. 信頼度0.8以上のみマッチ
"#
```
## Common Pitfalls
### ❌ Don't: Forget to Regenerate Client
```python
# speaker_matching.bamlを作成
# ❌ すぐに使おうとする
result = await b.MatchSpeaker(...) # AttributeError!
```
### ✅ Do: Regenerate After Changes
```bash
# 1. BAMLファイル作成/修正
# 2. クライアント再生成
uv run python -c "import sys; sys.argv = ['baml', 'generate', '--from', 'baml_src']; from baml_py import invoke_runtime_cli; invoke_runtime_cli()"
# 3. 使用
result = await b.MatchSpeaker(...) # OK!
```
### ❌ Don't: Skip Type Conversion
```python
# ❌ BAML結果をそのまま返す
return baml_result # 型が不明確
```
### ✅ Do: Convert to Pydantic Model
```python
# ✅ Pydanticモデルに変換
return SpeakerMatch(
matched=baml_result.matched,
speaker_id=baml_result.speaker_id,
...
)
```
## Environment Variables
### BAML Feature Flags in Sagebase
**Note:** 議事録分割、議員団メンバー抽出、政党メンバー抽出は現在BAML専用です。
Pydantic実装は削除されており、環境変数による切り替えはできません。
```bash
# .env または .env.example
# 以下の機能はBAML専用(環境変数不要、Pydantic実装削除済み):
# - 議事録分割(Minutes Divider)
# - 会議体メンバー抽出(Conference Member Extractor)
# - 議員団メンバー抽出(Parliamentary Group Member Extractor)
# - 政党メンバー抽出(Party Member Extractor)
USE_BAML_SPEAKER_MATCHING=false # 話者マッチング(デフォルト: false)
USE_BAML_POLITICIAN_MATCHING=false # 政治家マッチング(デフォルト: false)
```
## Debugging
### Check Generated Code
```bash
# 関数が生成されたか確認
grep -n "MatchSpeaker" baml_client/async_client.py
# 型定義を確認
cat baml_client/types.py | grep -A 10 "class SpeakerMatch"
```
### Type Check
```bash
# 型エラーを確認
uv run --frozen pyright src/domain/services/baml_speaker_matching_service.py
```
### Run Tests
```bash
# BAML実装のテストを実行
uv run pytest tests/domain/services/test_baml_speaker_matching_service.py -v
```
## Migration Checklist
新しいBAML実装を追加する際のチェックリスト:
- [ ] `baml_src/`に`.baml`ファイルを作成
- [ ] クラス定義(Pydanticモデルと一致)
- [ ] 関数定義(プロンプト含む)
- [ ] BAMLクライアント再生成
- [ ] BAML実装サービスを作成
- [ ] ハイブリッドアプローチ(ルールベース + BAML)
- [ ] 候補フィルタリング
- [ ] トークン最適化
- [ ] ファクトリークラスを作成
- [ ] 環境変数で切り替え
- [ ] デフォルト値を設定
- [ ] `.env.example`に環境変数を追加
- [ ] テストを作成
- [ ] BAMLクライアントをモック
- [ ] ルールベースマッチングのテスト
- [ ] BAML統合テスト
- [ ] `CLAUDE.md`を更新
- [ ] BAML対応機能リストに追加
- [ ] トークン削減効果を記載
- [ ] ドキュメント更新
- [ ] 使い方を説明
- [ ] 環境変数の説明
## References
- [BAML Documentation](https://docs.boundaryml.com)
- [Sagebase CLAUDE.md - BAML Integration](../../CLAUDE.md#baml-integration)
- [BAML Client Generation Script](../../scripts/generate_baml_client.sh)
## Examples
実装例は以下のファイルを参照:
- `baml_src/speaker_matching.baml` - 話者マッチングBAML定義
- `src/domain/services/baml_speaker_matching_service.py` - BAML実装サービス
- `src/domain/services/factories/speaker_matching_factory.py` - ファクトリー
- `tests/domain/services/test_baml_speaker_matching_service.py` - テストQuick Install
$
npx ai-builder add skill trust-chain-organization/baml-integrationDetails
- Type
- skill
- Author
- trust-chain-organization
- Slug
- trust-chain-organization/baml-integration
- Created
- 6d ago