[BETA] 제네릭 가드레일 API - PR 없이 통합하기
[BETA] 제네릭 가드레일 API - PR 없이 통합하기
가드레일 프로바이더로서 LiteLLM에 통합하는 전통적인 방법은 다음을 요구했어요:
- LiteLLM 저장소에 PR 제출
- 리뷰와 병합 대기
- LiteLLM 코드베이스에서 프로바이더별 코드 유지
- API 변경에 맞춰 통합 업데이트
해결책
제네릭 가드레일 API(Generic Guardrail API)를 사용하면 간단한 API 엔드포인트를 구현해 PR 없이 즉시 LiteLLM에 통합할 수 있어요.
주요 이점
- PR 불필요 - 즉시 배포하고 통합하세요
- 범용 지원 - 모든 LiteLLM 엔드포인트에서 동작 (chat, embeddings, image generation 등)
- 간단한 계약 - 엔드포인트 하나, 응답 유형 세 가지
- 멀티모달 지원 - 요청/응답에서 텍스트와 이미지 모두 처리
- 커스텀 파라미터 - config를 통해 프로바이더별 params 전달
- 완전한 제어 - 가드레일 API를 직접 소유하고 유지
지원되는 엔드포인트
제네릭 가드레일 API는 다음 LiteLLM 엔드포인트와 동작해요:
/v1/chat/completions- OpenAI Chat Completions/v1/completions- OpenAI Text Completions/v1/responses- OpenAI Responses API/v1/images/generations- OpenAI Image Generation/v1/audio/transcriptions- OpenAI Audio Transcriptions/v1/audio/speech- OpenAI Text-to-Speech/v1/messages- Anthropic Messages/v1/rerank- Cohere Rerank- 패스스루(pass-through) 엔드포인트
출처: 문서
본문
동작 방식
- LiteLLM이 모든 요청(챗 메시지, 임베딩, 이미지 프롬프트 등)에서 텍스트와 이미지를 추출합니다
- 추출된 콘텐츠 + 메타데이터를 내 API 엔드포인트로 보냅니다
- 내 API가
BLOCKED,NONE,GUARDRAIL_INTERVENED중 하나로 응답합니다 - LiteLLM이 결정을 집행하고 수정 사항을 적용합니다
API 계약
엔드포인트
POST /beta/litellm_basic_guardrail_api를 구현하세요.
요청 형식
{
"texts": ["extracted text from the request"], // array of text strings
"images": ["base64_encoded_image_data"], // optional array of images
"tools": [ // tool calls sent to the LLM (in the OpenAI Chat Completions spec)
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"}
}
}
}
}
],
"tool_calls": [ // tool calls received from the LLM (in the OpenAI Chat Completions spec)
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\": \"San Francisco\"}"
}
}
],
"structured_messages": [ // optional, full messages in OpenAI format (for chat endpoints)
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "Hello"}
],
"request_data": {
"user_api_key_hash": "hash of the litellm virtual key used",
"user_api_key_alias": "alias of the litellm virtual key used",
"user_api_key_user_id": "user id associated with the litellm virtual key used",
"user_api_key_user_email": "user email associated with the litellm virtual key used",
"user_api_key_team_id": "team id associated with the litellm virtual key used",
"user_api_key_team_alias": "team alias associated with the litellm virtual key used",
"user_api_key_end_user_id": "end user id associated with the litellm virtual key used",
"user_api_key_org_id": "org id associated with the litellm virtual key used"
},
"request_headers": { // optional: inbound request headers (allowlist). Allowed headers show their value; all others show "[present]" to indicate the header existed.
"User-Agent": "OpenAI/Python 2.17.0",
"Content-Type": "application/json",
"X-Request-Id": "[present]"
},
"litellm_version": "1.x.y", // optional: LiteLLM library version running this proxy
"input_type": "request", // "request" or "response"
"litellm_call_id": "unique_call_id", // the call id of the individual LLM call
"litellm_trace_id": "trace_id", // the trace id of the LLM call - useful if there are multiple LLM calls for the same conversation
"additional_provider_specific_params": {
// your custom params from config
}
}
응답 형식
{
"action": "BLOCKED" | "NONE" | "GUARDRAIL_INTERVENED",
"blocked_reason": "why content was blocked", // required if action=BLOCKED
"texts": ["modified text"], // optional array of modified text strings
"images": ["modified_base64_image"], // optional array of modified images
"structured_messages": [{"role": "user", "content": "modified message"}] // optional array of rewritten chat messages
}
액션:
BLOCKED- LiteLLM이 오류를 발생시키고 요청을 차단합니다NONE- 요청이 변경 없이 진행됩니다GUARDRAIL_INTERVENED- 수정된 texts/images로 요청이 진행됩니다 (texts및/또는images필드 제공)
메시지별 재작성: texts는 LiteLLM이 보낸 texts 배열과 일대일로 맞아야 해요. 엔드포인트가 대신 채팅 메시지별로 요청을 재작성하면, 재작성된 행을 structured_messages로 반환하세요 (Returning rewritten messages 참고). /v1/responses에서 메시지당 하나를 세는 texts 배열은 LiteLLM이 추출한 것과 맞지 않으므로, 재작성되지 않은 채 전송되는 대신 가드레일을 명명한 500으로 요청이 거부됩니다.
파라미터
tools 파라미터
tools 파라미터는 요청에서 사용 가능한 함수/도구 정의에 대한 정보를 제공해요.
형식: OpenAI ChatCompletionToolParam 형식 (OpenAI API reference 참고)
type만 있고 function 블록이 없는 빌트인 도구(예: {"type": "code_interpreter"} 또는 {"type": "file_search", "vector_store_ids": [...]})도 허용되며, 원형 그대로(도구별 config 포함) 엔드포인트로 전달됩니다. 엔드포인트는 function을 선택 사항으로 취급하고 type으로 분기해야 해요.
예시:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather in a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"]
}
}
}
가용성:
- 입력 전용:
tools는input_type="request"(사전 호출 가드레일)에서만 전달됩니다. 출력/응답 가드레일은 현재 도구 정의를 받지 못해요. - 지원 엔드포인트:
tools파라미터는/v1/chat/completions,/v1/responses,/v1/messages에서 지원됩니다. 다른 엔드포인트는 도구 지원이 없어요.
사용 사례:
- 도구 권한 정책 강제 (예: 특정 사용자/팀만 특정 도구에 접근 허용)
- LLM에 보내기 전 도구 스키마 검증
- 감사 목적으로 도구 사용 기록
- 사용자 컨텍스트 기반 민감 도구 차단
tool_calls 파라미터
tool_calls 파라미터는 요청 또는 응답에서 실제로 이뤄지는 함수/도구 호출을 담고 있어요.
형식: OpenAI ChatCompletionMessageToolCall 형식 (OpenAI API reference 참고)
예시:
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\": \"San Francisco\", \"unit\": \"celsius\"}"
}
}
tools와의 핵심 차이:
tools= 도구 정의/스키마 (어떤 도구가 사용 가능한지)tool_calls= 도구 호출/실행 (어떤 인자로 어떤 도구가 호출되는지)
가용성:
- 입력과 출력 모두: 도구 호출은
input_type="request"(도구 호출을 요청하는 assistant 메시지)와input_type="response"(도구 호출을 가진 LLM 응답) 모두에 존재할 수 있어요. - 지원 엔드포인트:
tool_calls파라미터는/v1/chat/completions,/v1/responses,/v1/messages에서 지원됩니다.
사용 사례:
- 실행 전 도구 호출 인자 검증
- 도구 호출 인자에서 민감 데이터(PII 등) 마스킹
- 감사/디버깅을 위한 도구 호출 기록
- 위험한 파라미터를 가진 도구 호출 차단
- 도구 호출 인자 수정 (예: 제약 강제, 입력 정화)
- 사용자/팀별 도구 사용 패턴 모니터링
structured_messages 파라미터
structured_messages 파라미터는 OpenAI 채팅 컴플리션 스펙 형식의 전체 입력을 제공하며, system과 user 메시지를 구분하는 데 유용해요.
형식: OpenAI 채팅 컴플리션 메시지 배열 (OpenAI API reference 참고)
예시:
[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "Hello"}
]
가용성:
- 지원 엔드포인트:
/v1/chat/completions,/v1/messages,/v1/responses - 입력 전용:
input_type="request"(사전 호출 가드레일)에서만 전달됩니다.
사용 사례:
- system vs user 메시지에 다른 정책 적용
- 역할 기반 콘텐츠 제한 강제
- 구조화된 대화 컨텍스트 기록
재작성된 메시지 반환
요청을 메시지별로 재작성하려면, 받은 각 행에 대해 같은 순서와 역할·형태를 유지한 채 재작성하려는 콘텐츠만 바꿔 structured_messages를 응답에 반환하세요. LiteLLM은 지원되는 모든 엔드포인트에서 원래 요청에 행을 다시 써요. 여기에는 instructions나 tool 항목을 담은 /v1/responses 턴도 포함되며, 그 경우 메시지별 texts 배열을 넣을 수 없습니다. 받은 그대로 반환한 행은 변경된 것으로 간주되지 않으므로, 건드리지 않은 행을 그대로 에코할 수 있어요. 모든 행이 변경 없이 돌아오면 LiteLLM은 texts를 적용합니다. 받은 것과 길이가 다른 배열을 반환하면 대화 전체를 대체합니다.
예시:
{
"action": "GUARDRAIL_INTERVENED",
"structured_messages": [
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "My SSN is <US_SSN>"}
]
}
LiteLLM 설정
config.yaml에 추가하세요:
litellm_settings:
guardrails:
- guardrail_name: "my-guardrail"
litellm_params:
guardrail: generic_guardrail_api
mode: pre_call # or post_call, during_call
api_base: https://your-guardrail-api.com
api_key: os.environ/YOUR_GUARDRAIL_API_KEY # optional
unreachable_fallback: fail_closed # default: fail_closed. Set to fail_open to proceed if the guardrail endpoint is unreachable (network errors, or HTTP 502/503/504 from an upstream proxy/LB).
fail_on_error: true # default: true (fail closed). Set to false to proceed on ANY guardrail error. See "Error handling" below before changing this.
additional_provider_specific_params:
# your custom parameters
threshold: 0.8
language: "en"
오류 처리: unreachable_fallback와 fail_on_error
가드레일 자체가 실패할 때(verdict를 반환하지 못할 때) LiteLLM이 무엇을 하는지 제어하는 두 가지 설정이 있어요. 엄격함에서 관대함으로 이어지는 스펙트럼 위에 있으며, 서로 결합됩니다:
unreachable_fallback(기본fail_closed)는 가드레일 엔드포인트가 도달 불가능할 때만 반응합니다: 네트워크 오류, 타임아웃, 또는 업스트림 프록시/로드 밸런서의 HTTP 502/503/504. 그런 경우에만 요청을 진행하려면fail_open으로 설정하세요.fail_on_error(기본true)는 더 넓은 제어예요. 도달 불가능뿐만 아니라 모든 가드레일 오류를 다룹니다.
| fail_on_error | 가드레일 오류 시 동작 |
|---|---|
true (기본) |
Fail closed. 모든 오류가 요청을 차단합니다: non-2xx 응답, 손상되었거나 파싱 불가능한 body, 네트워크 실패, 내부 직렬화/검증 오류. 기존 LiteLLM 동작을 유지합니다 |
false |
Fail open (전체). 모든 가드레일 오류가 critical 수준 로그 줄로 격하되고 요청은 가드레일이 설정되지 않은 것처럼 진행됩니다 |
유효한 가드레일 응답만이 행동할 수 있어요. fail_on_error: false에서 파싱된 BLOCKED 결정은 여전히 차단합니다. 유효한 응답이 아닌 모든 것(오류, 손상된 body, 도달 불가능한 엔드포인트)은 우회됩니다. 이는 요청 훅(pre_call)과 응답 훅(post_call) 모두에 적용되며, 응답 경로에서 fail-open은 이미 생성된 모델 출력을 반환하고, fail-closed는 성공적인 생성을 오류로 바꿉니다.
fail_on_error: false는 실패 시 완전한 우회예요. 가드레일이나 그 엔드포인트의 어떤 실패든, 어떤 이유든 해당 요청에 대해 가드레일을 차단 대신 건너뛰게 합니다. 그 트레이드오프를 이해하고 수락한 경우에만 활성화하세요: 보안 제약보다 가용성과 운영 제약이 더 강할 때 선택하세요. 가드레일이 하드 보안 경계라면 기본값 true(fail closed)로 두세요.
기본이 fail closed인 것은 가드레일이 보통 보안 제어이기 때문이에요. 매 fail-open 우회는 call id와 trace id와 함께 critical 수준으로 기록됩니다 (Generic Guardrail API error (fail-open) ...). 알림을 만들고 얼마나 자주 발생하는지 감사할 수 있어요.
정적 및 동적 헤더
가드레일 엔드포인트에 두 종류의 헤더를 보낼 수 있어요:
- 정적 헤더 (
headers): 가드레일에 매 요청과 함께 보내는 키/값 맵. 고정 값(예: API 키,X-Service-Name)에 사용하세요.litellm_params에서 구성:
litellm_params:
guardrail: generic_guardrail_api
api_base: https://your-guardrail-api.com
headers:
X-Service-Name: "my-app"
X-API-Key: ***
- 동적 헤더 (
extra_headers): 클라이언트 요청에서 가드레일로 전달되는 헤더 이름 목록. 이 목록에 있는 헤더(작은 기본 allowlist인x-litellm-*등)만 값이 전송되고, 나머지는[present]로 전송됩니다. 클라이언트 제공 헤더(예:x-request-id,x-correlation-id)를 패스스루하려면 사용하세요.litellm_params에서 구성:
litellm_params:
guardrail: generic_guardrail_api
api_base: https://your-guardrail-api.com
extra_headers:
- x-request-id
- x-correlation-id
- x-custom-auth
이는 MCP의 정적/추가 헤더 동작을 반영한 것입니다.
예시: Pillar Security
Pillar Security는 제네릭 가드레일 API를 사용해 AI 보안 스캐닝(프롬프트 인젝션 방지, PII/PCI 감지, 시크릿 감지, 콘텐츠 모더레이션)을 제공해요.
guardrails:
- guardrail_name: "pillar-security"
litellm_params:
guardrail: generic_guardrail_api
mode: [pre_call, post_call]
api_base: https://api.pillar.security/api/v1/integrations/litellm
api_key: os.environ/PILLAR_API_KEY
default_on: true
additional_provider_specific_params:
plr_mask: true # Enable automatic masking of sensitive data
plr_evidence: true # Include detection evidence in response
plr_scanners: true # Include scanner details in response
전체 구성 옵션은 Pillar Security 문서를 참고하세요.
사용법
사용자는 가드레일을 이름으로 적용해요:
response = client.chat.completions.create(
model="gpt-5.6-terra",
messages=[{"role": "user", "content": "hello"}],
guardrails=["my-guardrail"]
)
또는 동적 파라미터로:
response = client.chat.completions.create(
model="gpt-5.6-terra",
messages=[{"role": "user", "content": "hello"}],
guardrails=[{
"my-guardrail": {
"extra_body": {
"custom_threshold": 0.9
}
}
}]
)
구현 예시
완전한 참조 구현은 mock_bedrock_guardrail_server.py를 참고하세요.
최소 FastAPI 예시:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional, Dict, Any
app = FastAPI()
class GuardrailRequest(BaseModel):
texts: List[str]
images: Optional[List[str]] = None
tools: Optional[List[Dict[str, Any]]] = None # OpenAI ChatCompletionToolParam format (tool definitions)
tool_calls: Optional[List[Dict[str, Any]]] = None # OpenAI ChatCompletionMessageToolCall format (tool invocations)
structured_messages: Optional[List[Dict[str, Any]]] = None # OpenAI messages format (for chat endpoints)
request_data: Dict[str, Any]
input_type: str # "request" or "response"
litellm_call_id: Optional[str] = None
litellm_trace_id: Optional[str] = None
additional_provider_specific_params: Dict[str, Any]
class GuardrailResponse(BaseModel):
action: str # BLOCKED, NONE, or GUARDRAIL_INTERVENED
blocked_reason: Optional[str] = None
texts: Optional[List[str]] = None
images: Optional[List[str]] = None
structured_messages: Optional[List[Dict[str, Any]]] = None # rewritten OpenAI messages, one per row received
@app.post("/beta/litellm_basic_guardrail_api")
async def apply_guardrail(request: GuardrailRequest):
# Your guardrail logic here
# Example: Check text content
for text in request.texts:
if "badword" in text.lower():
return GuardrailResponse(
action="BLOCKED",
blocked_reason="Content contains prohibited terms"
)
# Example: Check tool definitions (if present in request)
if request.tools:
for tool in request.tools:
if tool.get("type") == "function":
function_name = tool.get("function", {}).get("name", "")
# Block sensitive tool definitions
if function_name in ["delete_data", "access_admin_panel"]:
return GuardrailResponse(
action="BLOCKED",
blocked_reason=f"Tool '{function_name}' is not allowed"
)
# Example: Check tool calls (if present in request or response)
if request.tool_calls:
for tool_call in request.tool_calls:
if tool_call.get("type") == "function":
function_name = tool_call.get("function", {}).get("name", "")
arguments_str = tool_call.get("function", {}).get("arguments", "{}")
# Parse arguments and validate
import json
try:
arguments = json.loads(arguments_str)
# Block dangerous arguments
if "file_path" in arguments and ".." in str(arguments["file_path"]):
return GuardrailResponse(
action="BLOCKED",
blocked_reason="Tool call contains path traversal attempt"
)
except json.JSONDecodeError:
pass
# Example: Check structured messages (if present in request)
if request.structured_messages:
for message in request.structured_messages:
if message.get("role") == "system":
# Apply stricter policies to system messages
if "admin" in message.get("content", "").lower():
return GuardrailResponse(
action="BLOCKED",
blocked_reason="System message contains restricted terms"
)
return GuardrailResponse(action="NONE")
언제 이것을 써야 하나
✅ 제네릭 가드레일 API를 사용하세요:
- PR을 기다리지 않고 즉시 통합하고 싶을 때
- 가드레일 서비스를 직접 유지할 때
- 업데이트와 기능을 완전히 제어해야 할 때
- 모든 LiteLLM 엔드포인트를 자동으로 지원하고 싶을 때
❌ PR을 만들기:
- LiteLLM 내부와 더 깊이 통합하고 싶을 때
- 가드레일에 복잡한 LiteLLM 전용 로직이 필요할 때
- 빌트인 프로바이더로 소개되고 싶을 때
질문?
이것은 베타 API예요. 피드백에 따라 적극적으로 개선 중입니다. 추가 기능이 필요하면 이슈나 PR을 열어 주세요.