
LiteLLM の s3_use_team_prefix と s3_use_key_prefix の挙動を検証して Team と Key 命名の方針を考えてみた
こんにちは。クラウド事業本部コンサルティング部の桑野です。
前回はLiteLLM ProxyをAmazon ECS on FargateにTerraform + ecspressoでデプロイする構成を共有しました。
その構成ではs3_v2コールバックでリクエストログをS3バケットへ出力していますが、運用を考えていくとひとつ気になる点があります。1つのバケットに全Teamと全Keyのログが混ざるという点です。
LiteLLMには s3_use_team_prefix と s3_use_key_prefix というフラグがあり、有効化するとオブジェクトキーの先頭にTeam aliasやKey aliasがprefixとして付くようになります。とはいえ「実際にどんなパスになるのか」「両方ONにしたときの結合順は」「aliasが空のときは」「日本語やスラッシュを混ぜたら」といった具体的な挙動はドキュメントだけでは見えにくいです。
今回はこれらのフラグを実際に検証し、Team / Key命名における運用上の方針を考えてみたので共有します。
s3_use_team_prefix / s3_use_key_prefix とは?
LiteLLMのs3_v2コールバックのオプションで、S3に書き出すオブジェクトキーにTeam alias / Key aliasをprefixとして付けるかどうかを制御します。
config.yamlではlitellm_settings配下にフラグとして指定します。
litellm_settings:
success_callback: ["s3_v2"]
s3_callback_params:
s3_bucket_name: litellm-log-archive
s3_region_name: ap-northeast-1
s3_use_team_prefix: true
s3_use_key_prefix: true
s3_use_key_prefix はPR #16237で2025-11-05にmainにマージされた比較的新しめのフラグです。本記事執筆時点での最新版で利用できます。
ソースコードから見る挙動
検証前に実装箇所を確認しました。
以下リンクのL478-L496あたりです。
# Base prefix (default empty)
prefix_components = []
if self.s3_use_team_prefix:
team_alias = standard_logging_payload.get("metadata", {}).get(
"user_api_key_team_alias", None
)
if team_alias:
prefix_components.append(team_alias)
if self.s3_use_key_prefix:
user_api_key_alias = standard_logging_payload.get("metadata", {}).get(
"user_api_key_alias", None
)
if user_api_key_alias:
prefix_components.append(user_api_key_alias)
# Construct full prefix path
prefix_path = "/".join(prefix_components)
if prefix_path:
prefix_path += "/"
ここから読み取れる仕様は以下です。
- コード上の追加順から、結合順は
team_alias→key_aliasになる - aliasが
Noneや空文字ならifで弾かれてprefixに含まれない - 値はそのまま使われ、サニタイズ処理は無い
つまりaliasの値次第でオブジェクトキーがそのまま変わってしまうわけです。実際にどうなるかを動かして確認していきます。
検証環境と手順
前回の記事で構築したリポジトリのローカル開発構成(LiteLLM + MinIO + PostgreSQL のdocker compose)を流用しています。
- LiteLLMバージョン:本記事執筆時点の最新版(
s3_use_key_prefixが含まれていれば再現可能) - モデル:
bedrock/amazon.nova-lite-v1:0 - リクエスト経路:Admin UI の Test Key ボタン
- ログ確認:MinIOコンソール(http://localhost:9001)でバケット
litellm-log-archiveのrequest_logs/配下を確認
検証では以下の組み合わせを順番に試しました。
- 基本動作マトリクス:両フラグの4通り × Team所属あり / なし / master key
- 特殊文字耐性:Key aliasに
/や日本語を混ぜた場合の挙動
検証用のTeam / Keyを作成する
LiteLLMをローカル起動して http://localhost:4000/ui にmaster keyでログインし、検証用のTeamとKeyを事前にまとめて作成しておきます。
Team
| alias | 用途 |
|---|---|
team-alpha |
通常ケース |
team/sub |
特殊文字(スラッシュ) |
営業チーム |
特殊文字(マルチバイト) |
team alpha |
特殊文字(スペース) |
teamA/ |
特殊文字(末尾スラッシュ) |





5つ登録しました。

Key(Virtual Keys)
| alias | 所属Team | 用途 |
|---|---|---|
key-prod |
team-alpha |
通常ケース(Team所属) |
key-noteam |
(なし) | Team未所属 |
key/sub |
team-alpha |
特殊文字(スラッシュ) |
キー日本語 |
team-alpha |
特殊文字(マルチバイト) |




4つ登録しました。

なお、Keyのalias空欄での作成も試みましたが、Admin UI側のバリデーションで弾かれ、作成自体ができませんでした。

master key を使うケースは、Admin UI で master key の値を直接入力してTest Keyボタンから送信します。
フラグ切り替えとリクエスト送信
ラウンドごとに config.yaml の以下2行を書き換えて docker compose restart litellm で反映します。
s3_use_team_prefix: true # ラウンドごとに true / false を切り替え
s3_use_key_prefix: true # ラウンドごとに true / false を切り替え
各キーからのリクエスト送信はAdmin UIの「Virtual Keys → Test Key」ボタンから行います。master keyの場合もTest Key画面でmaster keyの値を直接入力して送信します。
ラウンドの境界がわかりやすいよう、各ラウンド前にMinIOコンソール上でバケット litellm-log-archive 配下のオブジェクトをまとめて削除しています。
検証結果
A. 基本動作マトリクス
ラウンド1: s3_use_team_prefix: true / s3_use_key_prefix: true
| 使ったKey | team_alias | key_alias | 実際のオブジェクトキー |
|---|---|---|---|
key-prod |
team-alpha |
key-prod |
request_logs/team-alpha/key-prod/2026-05-18/time-...json |
key-noteam |
(なし) | key-noteam |
request_logs/key-noteam/2026-05-18/time-...json |
| master key | (なし) | (なし) | request_logs/2026-05-18/time-...json |
MinIO上での実際の格納先は以下です。
key-prod(team所属)

key-noteam(team未所属)

master key

期待していた通り、request_logs/<team>/<key>/<date>/time-... の階層になりました。
aliasがNoneのときは想定通りprefixからスキップされています。
ラウンド2: s3_use_team_prefix: true / s3_use_key_prefix: false
| 使ったKey | team_alias | key_alias | 実際のオブジェクトキー |
|---|---|---|---|
key-prod |
team-alpha |
key-prod |
request_logs/team-alpha/2026-05-18/time-...json |
key-noteam |
(なし) | key-noteam |
request_logs/2026-05-18/time-...json |
| master key | (なし) | (なし) | request_logs/2026-05-18/time-...json |
key-prod(team所属)

key-noteam / master key(同じ階層に並列で格納される。タイムスタンプが小さい方がkey-noteam、大きい方がmaster key)

Team prefixのみ有効です。Team未所属のKeyとmaster keyは同じrequest_logs/<date>/ 直下に並列で格納されます。
ラウンド3: s3_use_team_prefix: false / s3_use_key_prefix: true
| 使ったKey | team_alias | key_alias | 実際のオブジェクトキー |
|---|---|---|---|
key-prod |
team-alpha |
key-prod |
request_logs/key-prod/2026-05-18/time-...json |
key-noteam |
(なし) | key-noteam |
request_logs/key-noteam/2026-05-18/time-...json |
| master key | (なし) | (なし) | request_logs/2026-05-18/time-...json |
key-prod

key-noteam

master key

Team所属のKeyでもTeam prefixは付かず、Key prefixのみが効きます。team-alpha所属 という情報はオブジェクトキーから読み取れなくなります。
ラウンド4: s3_use_team_prefix: false / s3_use_key_prefix: false
| 使ったKey | team_alias | key_alias | 実際のオブジェクトキー |
|---|---|---|---|
key-prod |
team-alpha |
key-prod |
request_logs/2026-05-18/time-...json |
| master key | (なし) | (なし) | request_logs/2026-05-18/time-...json |
key-prod / master key(同じ階層に並列で格納される)

全件 request_logs/<date>/ 直下に並列で格納されます。Team / Keyによる区別は完全にオブジェクトキー上消失します。
B. master keyの挙動
各ラウンドを通して、master keyからのリクエストは 両フラグの設定に関わらず一貫してprefix無し で request_logs/<date>/ 直下に格納されました。
これはLiteLLM内部で user_api_key_team_alias / user_api_key_alias がいずれも None になるためで、ソースコードの if alias: で弾かれる挙動と一致しています。
C. 特殊文字を含むaliasの挙動
ラウンド1(両フラグtrue)状態でKey aliasに / や日本語を入れた場合の挙動を確認しました。
| パターン | 設定したalias | Admin UIで作れたか | 実際のオブジェクトキー |
|---|---|---|---|
| スラッシュ含む | key/sub |
作れた | request_logs/team-alpha/key/sub/2026-05-18/time-...json |
| 日本語 | キー日本語 |
作れた | MinIOに書き込みなし(オブジェクト未生成) |
key/sub のオブジェクトキー

team-alpha 配下の様子(意図しない key フォルダが出現)

スラッシュ入りaliasは、/ がそのままディレクトリ階層として展開されました。team-alpha/key/sub/ の3階層になり、team-alpha 配下に意図しない key というフォルダが、本来の key-prod と並んで出現しています。Admin UI / LiteLLM本体ともにバリデーションがかかっていないため、aliasに / を含めた瞬間に階層構造が壊れます。
日本語aliasの方は、Admin UIへの登録自体は通るもののS3への書き込みが行われませんでした。リクエスト自体はLiteLLM Proxy側では成功していたため、s3_v2 callback内のどこかで失敗していると見られます。S3のオブジェクトキー仕様上、ASCIIの「Safe characters」(0-9 A-Z a-z や ! - _ . * ' ( ))以外は「Characters that might require special handling」(取り扱い注意)の扱いになっており、LiteLLM / boto3 / S3 のいずれかでエンコードが噛み合わず失敗していると考えられます。
運用方針として考えたこと
検証結果を踏まえて、本番運用時のTeam alias / Key aliasの命名 / フラグ設定としてどうするべきかを考えてみました。
基本は両フラグONで運用する
s3_use_team_prefix: true / s3_use_key_prefix: true の組み合わせが、
- Team単位での絞り込みもKey単位での絞り込みもオブジェクトキー上で完結する
- aliasが無いログ(master key等)は自然と
request_logs/直下に集まり区別が付く
という観点で最も運用しやすいです。ロギング基盤連携(Athena / Glue等での集計)でも、prefixでパーティション分割しやすくなります。
master keyはアプリから使わせない
master keyから発行されたリクエストは、両フラグをONにしていてもprefixが付かずrequest_logs/<date>/直下に格納されます。これはLiteLLM内部で user_api_key_team_alias / user_api_key_alias が None になるためで、ソースコードの仕様上避けられません。
結果として、
- Team / Keyごとの集計から漏れる
- Team未所属の virtual key と同じ階層に混在して識別が難しくなる
という二重の問題が起きます。本番ではアプリケーションから渡すのはvirtual key(sk-...)のみとし、master keyは管理操作のみに限定する方針が安全かと考えました。
Team / Key aliasの命名規約を決める
検証で見えた通り、aliasの値はそのままオブジェクトキーになります。命名規約を決めておかないとあとからS3階層が荒れる原因になります。最低限以下は揃えておきたいところです。
- 半角英数 + ハイフン / アンダースコアのみ
- スラッシュ・スペース・マルチバイト文字は禁止
- 末尾スラッシュ禁止(
//二重スラッシュ生成の懸念)
LiteLLM側にサニタイズが無いので、運用側のレビューやAdmin UI操作の手順書でカバーする必要があります。
Team未所属のvirtual keyを作らない
ラウンド2の結果から分かるように、Team未所属のKeyとmaster keyは request_logs/<date>/ 直下に並んで区別が付きません。s3_use_team_prefix: true を活かすためにも、virtual keyは必ず何らかのTeamに紐付けて発行する運用にしておくと、集計や監査が実施しやすいのではないかと考えています。
まとめ
いかがだったでしょうか。LiteLLMのs3_use_team_prefix / s3_use_key_prefixの挙動を検証し、Team / Key命名の運用方針を考えてみました。
検証から見えたポイントを改めて整理すると以下です。
- 両フラグONで
request_logs/<team>/<key>/<date>/time-...jsonの階層になる - aliasが
Noneの場合は該当prefixがスキップされる(master keyは常にprefix無し) - aliasにサニタイズが無いため、
/や日本語を混ぜるとオブジェクトキーが破綻する - 運用上は両フラグON / master keyはアプリから使わせない / Team / Key alias命名規約を整備 / Team未所属key禁止、の方針が扱いやすい
LiteLLMの運用上の細かい挙動はドキュメントだけでは見えにくいので、実際に動かして確かめる作業はやはり大切だなと感じます。今回検証したことは、実際にS3にフォールバックする際にも同じ挙動になるかと思いますので、この階層構造を前提にAthenaで分析してみるのも面白いかもなと思っています。
引き続き検証していきますので、新しい気づきがあれば別の記事で共有します。
最後までご覧いただきありがとうございました。







