model="openai:gpt-4o"를 "anthropic:claude-..."로 문자열만 바꾸면 같은 코드가 다른 회사 모델로 흐른다. 그 위에는 도구 호출·툴킷·MCP를 갖춘 Agents API가, 다시 그 위에는 데스크탑 AI 동료 앱 OpenCoworker가 올라간 3층 구조다. (저장소: andrewyng/aisuite · Python 3.10+ · MIT 라이선스 · Poetry · v0.1.14)provider.py·client.py·각 공급자 어댑터 소스를 직접 읽어 "멀티 공급자 추상화 레이어를 어떻게 설계하는가"에 초점을 둔 해설이다.이 레포가 무엇을 하는 물건인가.
나라마다 콘센트 모양이 다르듯, OpenAI·Anthropic·Google·Mistral은 SDK 호출 방식·요청/응답 형태가 전부 다르다. aisuite는 "멀티 어댑터"처럼, 코드는 늘 OpenAI 형식 한 가지로 짜되 내부에서 각 공급자의 SDK로 변환해 꽂아 준다.
덕분에 client.chat.completions.create(model="openai:gpt-4o", ...) 한 줄에서 model 문자열의 접두사(openai:, anthropic:)만 바꾸면 호출 대상이 다른 회사 모델로 즉시 바뀐다. 모델 비교·A/B 테스트·공급자 종속(lock-in) 탈출이 한 줄로 끝난다.
aisuite는 유니폼 액세스 레이어(uniform access layer)를 표방하는 경량 라이브러리다. 두 개의 층으로 나뉜다 — ① 공급자 간 통일된 Chat Completions API, ② 그 위에서 모델에게 파이썬 함수를 도구로 쥐여 주는 Agents API. 여기에 같은 저장소에서 두 층을 실제로 써먹는 데스크탑 앱 OpenCoworker까지 함께 배포된다. 핵심 패키지(aisuite/)는 순수 파이썬이며, 공급자 SDK는 모두 선택적(optional) 의존성이라 필요한 것만 골라 설치한다(pip install 'aisuite[anthropic]').
트렌딩 이유 · LiteLLM 등 경쟁 대비 장점.
멀티 공급자 통합 라이브러리는 이미 여럿(LiteLLM, LangChain 등) 있다. aisuite가 빠르게 주목받은 이유는 셋이다. 첫째 Andrew Ng이라는 강력한 브랜드와 "최소한의 추상화" 철학, 둘째 OpenAI SDK를 그대로 베낀 친숙한 표면(surface), 셋째 단순 채팅을 넘어 Agents API·MCP·OpenCoworker 앱까지 한 저장소에서 위로 쌓아 올린 점이다.
| 비교 항목 | 직접 각 SDK 사용 | aisuite |
|---|---|---|
| 공급자 전환 | 호출 코드 전체 재작성 | model 문자열 접두사만 변경 |
| 학습 곡선 | SDK마다 다른 API 학습 | OpenAI 형식 하나만 알면 됨 |
| 의존성 | 필요 SDK 직접 관리 | extras로 고른 공급자만 설치 |
| 도구 호출 | SDK별 다른 tool 스키마 직접 작성 | 파이썬 함수를 넘기면 스키마 자동 생성 |
| 에이전트 루프 | 직접 구현(요청→실행→결과 회신 반복) | max_turns 한 줄 또는 Agents API |
| MCP | 별도 통합 작업 | 네이티브 지원(config dict 또는 MCPClient) |
| 실사용 예 | 스스로 앱 작성 | OpenCoworker(데스크탑 AI 동료) 참조 구현 동봉 |
OpenAI는 response.choices[0].message.content로 답을 꺼내지만, Anthropic은 content 블록 배열, finish_reason도 end_turn/tool_use처럼 이름이 다르다. 도구 호출(tool calling) 메시지 형식, tool_result 회신 방식까지 전부 제각각이라, 공급자를 바꾸면 요청 만들기·응답 파싱·도구 루프를 모두 다시 짜야 한다.
각 공급자 어댑터는 ① 들어오는 OpenAI 형식 메시지를 그 회사 형식으로 변환(convert_request)하고, ② 돌아온 응답을 다시 OpenAI 형식으로 정규화(convert_response)한다. 그래서 호출부는 어떤 공급자든 response.choices[0].message.content로 일관되게 답을 꺼낸다. "차이는 어댑터 안에 가두고, 바깥은 항상 같은 모양"이 설계 철학이다.
코어 라이브러리·공급자 SDK·에이전트/인프라 각각.
코어는 외부 의존성이 거의 없다. 공급자 SDK들은 전부 optional이라, 코어 자체는 pydantic·httpx·docstring-parser 정도만 필수로 요구한다. 무거운 SDK를 강제하지 않는 것이 "경량(lightweight)"의 핵심.
| 요소 | 역할 |
|---|---|
| Python 3.10+ | 전체 언어. 타입힌트·dataclass·Protocol 적극 사용. |
importlib (표준) | 동적 공급자 로딩의 핵심. 이름 규칙으로 모듈을 런타임에 import. |
pydantic v2 | 응답 모델·검증. 프레임워크 데이터 구조 정의. |
docstring-parser | 파이썬 함수의 docstring을 읽어 도구 스키마 자동 생성. |
httpx | HTTP 클라이언트(일부 공급자·트레이싱 뷰어). |
pyproject.toml의 [tool.poetry.extras]에 공급자별 설치 그룹이 정의돼 있다. pip install 'aisuite[all]'로 전부, 또는 aisuite[anthropic]처럼 하나만 설치한다.
| 공급자(extras) | 내부에서 쓰는 SDK |
|---|---|
openai | openai — 표면 API의 기준점. |
anthropic | anthropic — Claude. |
google | vertexai, google-cloud-speech — Gemini·STT. |
aws | boto3 — Bedrock. |
mistral / cohere / groq | 각 회사 공식 SDK. |
ollama / lmstudio / deepseek | openai SDK 재사용 — OpenAI 호환 엔드포인트라서. |
watsonx / cerebras | IBM watsonx · Cerebras 클라우드. |
deepgram | 음성 인식(ASR) — soundfile·scipy·numpy 동반. |
mcp | mcp·nest-asyncio — Model Context Protocol 도구. |
postgres | psycopg — 에이전트 상태 저장소(Postgres). |
base_url만 바꿔 꽂는다 — 코드 재사용의 정석.| 요소 | 역할 |
|---|---|
| Agents API | Agent·Runner·ToolPolicy — 도구·정책·상태 저장·트레이싱. |
| Toolkits | files·git·shell — 샌드박스된 도구 묶음(바로 붙이는 능력). |
| MCP | MCPClient — 외부 MCP 서버 도구를 모델에 연결. |
| 상태 저장소 | in-memory · 파일 · Postgres — 실행 중단/재개·대화 영속화. |
| Tracing 뷰어 | 에이전트가 거친 모든 단계를 시각화하는 정적 viewer 동봉. |
| OpenCoworker | platform/ — aisuite로 만든 데스크탑 AI 동료(앱). macOS·Windows 빌드. |
| 패키징 | Poetry(pyproject.toml+poetry.lock) · PyPI 배포. |
model 문자열 → 동적 어댑터 로딩 → 변환/정규화, 그리고 도구 루프.
호출부는 model="provider:model-name" 문자열을 넘긴다. Client는 콜론 앞부분(provider)을 키로 삼아, ProviderFactory가 이름 규칙으로 해당 어댑터 모듈을 런타임에 import하고 인스턴스를 만든다. 그 어댑터가 요청을 그 회사 SDK 형식으로 변환해 호출하고, 응답을 다시 OpenAI 형식으로 정규화해 돌려준다.
aisuite의 가장 우아한 부분이다. 공급자가 20개가 넘는데도, 코드 어디에도 if provider == "openai": ... elif "anthropic": ... 같은 거대한 분기가 없다. 대신 파일 이름과 클래스 이름의 규칙으로 어댑터를 찾아낸다.
class ProviderFactory:
PROVIDERS_DIR = Path(__file__).parent / "providers"
@classmethod
def create_provider(cls, provider_key, config):
# "anthropic" → 클래스 "AnthropicProvider"
provider_class_name = f"{provider_key.capitalize()}Provider"
# "anthropic" → 모듈 "anthropic_provider"
provider_module_name = f"{provider_key}_provider"
module_path = f"aisuite.providers.{provider_module_name}"
module = importlib.import_module(module_path) # 런타임 import
provider_class = getattr(module, provider_class_name)
return provider_class(**config)
@classmethod
@functools.cache
def get_supported_providers(cls):
# providers/ 폴더의 *_provider.py 파일을 스캔해 목록 생성
provider_files = Path(cls.PROVIDERS_DIR).glob("*_provider.py")
return {f.stem.replace("_provider", "") for f in provider_files}
새 공급자를 추가하려면 코드 수정 없이 providers/foo_provider.py 파일에 FooProvider 클래스만 넣으면 끝이다. 지원 목록조차 폴더 스캔으로 자동 산출된다. 이것이 컨벤션 기반 플러그인 발견(convention over configuration) 패턴이다.
importlib로 필요한 어댑터만 그때그때 로딩하는 지연 로딩(lazy loading)을 더했다 — 설치하지 않은 공급자 SDK는 import조차 안 되니 가볍다.각 어댑터는 Provider 추상 클래스를 상속해 chat_completions_create 하나만 구현하면 된다. 예를 들어 Anthropic 어댑터의 AnthropicMessageConverter는 OpenAI 형식과 Anthropic 형식 사이를 양방향 통역한다.
| 차이점 | OpenAI 형식 | Anthropic 형식 | 어댑터가 하는 일 |
|---|---|---|---|
| system 메시지 | messages[] 안의 한 항목 | 별도 system 파라미터 | 분리해서 따로 전달 |
| 도구 결과 | role:"tool" 메시지 | tool_result 콘텐츠 블록 | 블록 형태로 재구성 |
| 종료 사유 | stop/length/tool_calls | end_turn/max_tokens/tool_use | FINISH_REASON_MAPPING으로 역매핑 |
aisuite 어댑터는 "동시통역사"다. 당신(코드)이 한국어(OpenAI 형식)로 질문하면, 통역사가 그걸 상대방 언어(Anthropic 형식)로 옮겨 전하고, 상대 답변을 다시 한국어로 옮겨 당신에게 준다. 당신은 끝까지 한국어만 쓰면 된다 — 상대가 영어를 쓰든 일어를 쓰든. 통역사(어댑터)를 한 명 더 고용하면(파일 하나 추가) 새 언어(공급자)가 지원된다.
OpenAI 도구 호출은 원래 JSON 스키마를 손으로 적어야 한다. aisuite는 docstring-parser로 파이썬 함수의 시그니처와 docstring을 읽어 스키마를 자동 생성한다. max_turns를 주면 "모델 호출 → 도구 실행 → 결과 회신 → 재호출"의 루프까지 라이브러리가 돌려 준다.
def will_it_rain(location: str, time_of_day: str):
"""비가 올지 확인한다.
Args:
location (str): 도시 이름
time_of_day (str): HH:MM 형식 시각
"""
return "YES"
response = client.chat.completions.create(
model="openai:gpt-4o",
messages=[{"role":"user","content":"2시에 소풍 가도 될까?"}],
tools=[will_it_rain], # 파이썬 함수 그대로! 스키마 자동 생성
max_turns=2) # 도구 왕복 최대 2회까지 자동
response.choices[0].intermediate_messages에 도구 왕복 전체 기록이 담겨, 이어서 대화를 계속할 수 있다.
코어 패키지 aisuite/ + 에이전트 + 데스크탑 앱 platform/.
① provider.py의 ProviderFactory로 "어떻게 어댑터를 찾나"(동적 로딩) → ② providers/anthropic_provider.py로 변환/정규화 실물 → ③ providers/ollama_provider.py로 "OpenAI 어댑터 상속해 base_url만 바꾸는" 재사용 패턴 → ④ client.py로 도구 루프·MCP 처리 → ⑤ agents/types.py·runner.py로 Agents 층. 설계의 통찰은 ①②③에 거의 다 있다.
멀티 공급자 추상화 라이브러리에서 무엇을 배우나.
aisuite는 "확장 가능한 플러그인 시스템을 어떻게 만드나"의 살아 있는 교과서다. 핵심은 ① importlib.import_module로 이름 규칙 기반 동적 로딩, ② 폴더 스캔(glob("*_provider.py"))으로 지원 목록 자동 산출, ③ 추상 베이스 클래스(Provider(ABC))로 계약 강제. 새 기능 추가가 "거대한 if-else 수정"이 아니라 "규칙에 맞는 파일 하나 추가"로 끝난다.
handlers/ 폴더의 *_handler.py를 스캔해, 파일명으로 핸들러 클래스를 동적 로딩하는" 30줄짜리 미니 팩토리를 직접 짜 보라. importlib+getattr+functools.cache 조합을 그대로 따라해 본다.서로 다른 외부 API를 하나의 인터페이스로 묶는 전형적 어댑터/파사드 패턴이다. 배울 점은 "공통 데이터 모델을 먼저 정하고(=framework/), 각 어댑터가 그 모델로 변환/정규화한다"는 방향이다. 호출부는 ChatCompletionResponse라는 한 가지 모델만 알면 된다. 외부 시스템을 여럿 붙이는 모든 통합 작업에 그대로 옮길 수 있다.
Response dataclass로 정규화하는 어댑터를 작성하라. 호출부가 두 공급자를 구분 없이 쓸 수 있게 만들기.OllamaProvider(OpenaiProvider)처럼, OpenAI 호환 엔드포인트는 어댑터를 새로 안 짜고 OpenAI 어댑터를 상속해 base_url만 교체한다. DeepSeek도 OpenAI SDK를 그대로 쓴다. "호환 표준이 있으면 바퀴를 다시 만들지 말라"는 실용주의의 모범. __init__에서 config를 살짝 손보는 것만으로 새 공급자가 생긴다.
ollama_provider.py를 읽고, "왜 native /api/chat가 아니라 /v1(OpenAI 호환)을 쓰는가"를 한 문단으로 설명하라(힌트: 도구 호출·finish_reason 흐름).코어는 무거운 SDK를 강제하지 않는다. pyproject.toml의 [tool.poetry.extras]로 "필요한 공급자만 설치"를 가능하게 했다. 라이브러리 저자라면 반드시 알아야 할 패턴 — 사용자에게 불필요한 의존성을 떠넘기지 않는 것. import 실패 시 친절한 에러로 "pip install 'aisuite[mcp]'"를 안내하는 점도 배울 만하다.
[project.optional-dependencies](또는 poetry extras)를 정의하고, 미설치 시 try/except ImportError로 "설치 명령을 알려 주는" 폴백을 구현하라.라이브러리는 가볍다 — 진짜 요구사항은 "API 키".
| 항목 | 요구사항 |
|---|---|
| 런타임 | Python 3.10 이상. pip install aisuite 한 줄(코어). |
| 공급자 SDK | 호출할 공급자만 extras로: aisuite[anthropic] / aisuite[all]. |
| API 키 | 각 공급자의 키(OPENAI_API_KEY·ANTHROPIC_API_KEY 등). 환경 변수 또는 provider_configs로 주입. |
| 로컬 모델 | Ollama(http://localhost:11434) / LM Studio — 키 없이 로컬 추론. |
| MCP 도구 | aisuite[mcp] + 외부 MCP 서버(예: npx @modelcontextprotocol/server-filesystem). |
| 에이전트 상태(Postgres) | aisuite[postgres] + PostgreSQL 인스턴스(중단/재개용). |
| OpenCoworker 앱 | macOS 13+(Apple Silicon) 또는 Windows 10/11(x64). 본인 API 키 지참 또는 Ollama 로컬. |
클라우드 모델은 토큰당 과금된다. 개발·테스트는 Ollama 로컬 모델로 같은 코드를 무료로 돌리다가, 운영에서만 model 문자열을 클라우드 모델로 바꾸는 전략이 가능하다 — aisuite 추상화의 실질적 이득. 키는 코드에 하드코딩하지 말고 환경 변수(.env+python-dotenv)로 관리하라(레포의 .env.sample 참고).
난이도별 5단계 — 동일 코드로 공급자 갈아 끼우기부터 에이전트까지.
pip install 'aisuite[openai,ollama]' 후, 같은 messages로 model="ollama:llama3"과 "openai:gpt-4o-mini"를 번갈아 호출해 응답을 비교한다. 바뀐 코드가 model 문자열 하나뿐임을 확인하는 것이 목적.
모델 리스트(["openai:...","anthropic:...","ollama:..."])를 for 루프로 돌려 같은 질문에 대한 답·응답 시간(time)·토큰 사용량(response.usage)을 표로 출력한다. aisuite가 usage까지 정규화해 줌을 체감.
파이썬 함수 2~3개(예: 날씨 조회·계산기)를 정의해 tools=[...]로 넘기고 max_turns=3으로 실행, 모델이 함수를 호출·결과를 받아 최종 답을 만드는 과정을 관찰한다. response.choices[0].intermediate_messages로 도구 왕복 기록을 들여다본다.
providers/에 myecho_provider.py를 만들어 MyechoProvider(Provider)를 구현(입력 메시지를 그대로 에코해 ChatCompletionResponse로 반환)하고, model="myecho:test"로 호출한다. 코드를 한 줄도 안 고치고 새 공급자가 인식됨을 확인 — 동적 로딩의 위력 체감.
Agent(tools=[*ai.toolkits.files(root="."), *ai.toolkits.git(root=".")])를 만들고 Runner.run(agent, "최근 커밋 변경점을 3줄로 요약")을 실행한다. RequireApprovalPolicy로 위험한 도구 호출에 승인 게이트를 걸고, Postgres 상태 저장소로 실행을 중단·재개해 본다.
6주 코스 — LLM API 기초부터 에이전트 하베스 설계까지.
| 주차 | 주제 | 실습 · 참고 |
|---|---|---|
| 1주차 | LLM API 기초 — OpenAI Chat Completions 형식·messages·파라미터 | OpenAI 공식 문서 · aisuite 단일 호출 실습(과제 1) |
| 2주차 | 추상화/어댑터 패턴 — 공통 모델 vs 공급자 차이 | provider.py·anthropic_provider.py 정독 · 과제 4 |
| 3주차 | 동적 로딩·플러그인 시스템 — importlib·컨벤션·extras | ProviderFactory 분석 · pyproject.toml extras 설계 |
| 4주차 | 도구 호출(function calling) — 스키마 자동화·도구 루프 | utils/tools.py · max_turns 동작 추적(과제 3) |
| 5주차 | 에이전트 설계 — Runner·정책·상태 저장·트레이싱 | agents/runner.py·types.py·policies.py |
| 6주차 | MCP·에이전트 하베스 — 외부 도구 연결·OpenCoworker 구조 | mcp/ · platform/coworker/ · agents-quickstart |
본문·소스에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| uniform access layer | 여러 LLM 공급자를 하나의 API로 통일하는 층. aisuite의 정체성. |
| Provider (어댑터) | 한 공급자의 SDK를 감싸 변환/정규화하는 클래스. Provider(ABC) 상속. |
| ProviderFactory | 이름 규칙으로 어댑터를 동적 로딩·생성하는 팩토리. |
| model 문자열 | "provider:model-name" 형식. 콜론 앞이 공급자 키. |
| convert_request / convert_response | OpenAI 형식 ↔ 공급자 형식 양방향 변환·정규화. |
| ChatCompletionResponse | 공급자 무관한 정규화된 응답 모델(framework/). |
| finish_reason 매핑 | end_turn→stop 등 종료 사유를 OpenAI 형식으로 역매핑. |
| extras (optional deps) | aisuite[anthropic]처럼 필요한 공급자 SDK만 선택 설치. |
| OpenAI 호환 엔드포인트 | OpenAI 형식을 흉내 내는 API. Ollama·LM Studio·DeepSeek — OpenAI 어댑터 재사용. |
| tool calling | 모델이 파이썬 함수를 도구로 호출. aisuite는 스키마 자동 생성. |
| max_turns | 도구 호출 왕복(모델↔도구) 자동 반복 횟수 상한. |
| Agents API | Agent·Runner — 장기 실행·정책·상태·트레이싱. |
| Toolkit | files·git·shell 같은 샌드박스 도구 묶음. |
| ToolPolicy | RequireApprovalPolicy·allow/deny — 어떤 도구 호출을 실행할지 통제. |
| State Store | 실행 상태 영속화(메모리·파일·Postgres). 중단/재개·대화 영속. |
| MCP | Model Context Protocol. 외부 도구 서버를 표준 방식으로 모델에 연결. |
| OpenCoworker | aisuite로 만든 데스크탑 AI 동료 앱(platform/). 참조 구현. |
pip install aisuite)