~16 min · provider-port, abstraction, architecture
Level 0Observer
0 XP0/64 lessons0/13 achievements
0/150 XP to next level150 XP to go0% complete
Valid 패턴 둘, wrong move 하나
멀티-프로바이더 시스템엔 valid 패턴 둘 — (1) 프로바이더 port — 스트리밍 API에 대한 narrow ABC, 매 프로바이더가 구현, downstream 코드 neutral; 또는 (2) canonical-shape 베리언트 — 한 프로바이더를 canonical로 픽, 나머지는 downstream에서 specialize. 둘 다 ship; 둘 다 살아남아. Wrong move는 day one에 한 프로바이더로 prematurely generalize.
cwkPippa는 베리언트 픽
cwkPippa의 어댑터 ABC가 의도적으로 narrow — 스트리밍-턴 경계만. Routes, store, frontend가 Claude shape 가정. ChatGPT, Gemini, Ollama 브레인이 베리언트지 peer 아님. 이게 cwkPippa 아키텍처 룰 2 — cost is absorbed downstream, never pushed upstream. 결과는 더 적은 추상과 더 직접적인 코드, canonical 결정 일찍 필요한 비용.
Port 선택 시점
프로바이더가 진짜 interchangeable이면(같은 능력, 같은 비용 모양, mid-traffic swappable), strict port 보상 — A/B 프로바이더 가능, 깔끔히 fail over, downstream 안 만지고 새 거 bring up. Interchangeable 아니면(하나는 prompt caching, 다른 건 agent harness, 다른 건 local-only), 베리언트가 더 나은 service.
원칙: 구체 먼저, 추상 그다음. 두 프로바이더 갖는 날이 결정의 날. 한 프로바이더의 day one은 너무 일러.
Code
cwkPippa-스타일 narrow ABC·python
from abc import ABC, abstractmethod
from typing import AsyncIterator, Any
class Adapter(ABC):
"""시스템에서 유일한 추상.
Routes, store, frontend가 이게 반환하는 응답 모양 가정."""
@abstractmethod
async def stream_turn(
self,
*,
conversation_id: str,
messages: list[dict[str, Any]],
system: str,
) -> AsyncIterator[dict[str, Any]]:
...
# claude.py가 canonical 구현;
# codex.py / gemini.py / ollama.py가 downstream에서 SPECIALIZE — 위로 abstract X.
Strict port (대안 패턴)·python
# 프로바이더가 정말 interchangeable일 때만 옳음.
class ModelProvider(ABC):
capabilities: dict # prompt_caching, files, agent 등 advertise
@abstractmethod
async def generate(self, req: GenerateRequest) -> GenerateResult: ...
@abstractmethod
async def stream(self, req: GenerateRequest) -> AsyncIterator[Event]: ...
async def count_tokens(self, req: GenerateRequest) -> int: ... # 선택
# Caller가 capabilities로 분기, common surface로 fall through.