본문으로 건너뛰기

AiRelay

LLM relay 어댑터 — OpenAI-compatible 호출 + 응답 정규화 (LiteLLM 호환, Chorus 제외)

상태​

항목값
Layercore
TierL1
Statusreleased
Version1.0.0
가격Free (free)
카테고리AI / LLM

개요​

개요​

AiRelay 는 multi-saas-kit 의 Layer 1 Core Plugin 입니다. apis.how 의 External-First 원칙(/api/v1/edge/... mint → relay 단일 흐름)에 따라 OpenAI-compatible LLM 호출 어댑터를 표준화한 plugin.

LiteLLM 을 LLM control plane 으로, virtual key + model alias 사용. apis.how 는 자체 LLM 을 재구현하지 않고 운영 정책/토큰 관리/문서화/확장 레이어에 집중.

범위 경계: Chorus 는 로컬 또는 서버에 설치된 AI Agent CLI 를 subprocess 로 실행하는 orchestration 계층이므로 AiRelay 범위가 아닙니다. Chorus 통합은 별도 AgentCliBridge/ChorusBridge plugin 책임입니다.

핵심 컴포넌트​

TextContentExtractor (Pure)​

OpenAI-compatible chat completion payload 에서 text 추출. apis.how LlmApiRelayClient::extractText 추출.

두 가지 shape 지원:

  1. choices[0].message.content = "string"
  2. choices[0].message.content = [{type:'text', text:'...'}, ...] (multi-part)

image 등 비-text 항목은 자동 skip.

RelayResponseNormalizer (Pure)​

LLM relay 응답 표준화 — apis.how 의 표준 shape 으로 변환:

success: { status: 'success', data: { text, model, usage, raw }, relay_meta: {...} }
error: { status: 'error', error: { code, message, http_status }, relay_meta: {...} }

relay_meta — mode, relay_elapsed_ms, upstream_http_status. 호출자가 audit log / 사용량 분석에 활용.

HTTP 4xx/5xx 자동 감지, error payload 에서 error.code / error.message 추출.

LlmRelayClient​

OpenAI-compatible LLM relay 클라이언트.

  • complete($payload) — /v1/chat/completions POST + 정규화된 표준 응답 반환
  • 인증: Authorization: Bearer {api_key} + x-litellm-api-key 동시 헤더 (LiteLLM 호환)
  • apiKey 미설정 → AiRelayNotConfiguredException
  • 네트워크 예외 → error.code = 'llm_relay_exception' 안전 fallback
  • 후행 슬래시 base_url 자동 정규화

env 다중 후보 fallback (apis.how 의 resolveApiKey 패턴) 은 caller 책임으로 분리. 본 client 는 명시적 base_url + api_key 만 받음.

설정 (config/ai-relay.php)​

return [
'enabled' => env('PLG_AI_RELAY_ENABLED', true),
'litellm' => [
'base_url' => env('LITELLM_BASE_URL', 'https://llm.apis.how'),
'api_key' => env('LITELLM_API_KEY'),
'completion_path' => '/v1/chat/completions',
'timeout_seconds' => 60,
],
'response_mode' => 'server_relay_llm',
];

사용 예시​

use App\Plugins\AiRelay\Services\LlmRelayClient;

$client = LlmRelayClient::fromConfig(config('ai-relay'));
$result = $client->complete([
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'user', 'content' => '안녕하세요'],
],
]);

if ($result['status'] === 'success') {
return $result['data']['text']; // '안녕하세요!'
} else {
Log::warning('LLM relay failed', $result['error']);
}

// usage 정보는 호출 프로젝트의 metering/pricing 레이어가 소비
$usage = $result['data']['usage']; // ['prompt_tokens' => 10, 'completion_tokens' => 5]

출처​

apis.how LlmApiRelayClient (174줄) 추출. env 다중 후보 resolution 부분은 프로젝트 책임으로 분리하고, 순수 호출 + 응답 정규화 만 plugin 으로 분리.

apis.how External-First 원칙​

[모든 호출자]
↓
POST /api/v1/edge/{service}/sessions ← 공통 mint endpoint (Bearer 장기 키)
↓
← 단기 JWT (Ed25519, 3분)
↓
POST relay.apis.how/v1/{service}/{endpoint} ← 공통 relay (단기 JWT)

본 plugin 은 relay 부분의 표준 어댑터.

도매 단가 조회 (재과금용)​

apis.how API 사용료를 회원에게 재과금하려면 우리 계약 단가를 알아야 합니다. 그 값을 요금표에 복제해 두면 공급자가 단가를 바꿀 때 조용히 틀린 금액을 청구하게 되므로, 필요할 때 공급자에게 물어봅니다.

운영자가 준비할 것 (둘 다 필요)​

#항목없으면
1apis.how 측 우리 계약 요금표 항목조회 거부 — 임의 단가로 답하지 않습니다
2사용 중인 API 토큰에 billing:read 권한조회 거부

billing:read 는 apis.how 토큰 편집 화면의 허용 동작 목록에서 체크합니다. 전용 토큰을 새로 발급할 필요는 없습니다 — 이미 쓰는 토큰에 권한만 추가하면 됩니다.

조회 결과에 "전용 단가 아님" 표시가 계속 나온다면 그 고객사 전용 요금표가 아직 없다는 신호입니다.

이 조회는 과금하지 않습니다(읽기). 사용량 대시보드에 나타나지 않습니다.

자세한 사용법은 UsageMetering (사용량 과금) 문서를 참고하십시오.

다른 Plugin 과의 관계​

  • PromptTemplating (선택) — 시스템 프롬프트 합성 후 messages 구성
  • 프로젝트 고유 metering/pricing 레이어 — data.usage 를 기반으로 비용 계산 (apis.how 등)
  • AiTracking (Phase 2) — 호출 이력 / 비용 / latency 기록

다음 단계 (Phase 3+)​

  • Streaming 지원 (/v1/chat/completions SSE)
  • Speech relay (apis.how SpeechApiRelayClient) 통합
  • EdgeRelay JWT 자동 mint + relay (/api/v1/edge/... 두 단계 통합)
  • OpenAI-compatible gateway 어댑터 추가 (LiteLLM-compatible 계열)
  • Chorus/Agent CLI 실행은 별도 AgentCliBridge/ChorusBridge plugin 책임
  • AiTracking plugin 자동 hook (호출 이력 자동 기록)

라이선스​

MIT

의존성​

데모​


🛒 Plugin Store에서 보기: store.codebase.how/plugins/ai-relay