이코에코(Eco²) Knowledge Base/Applied
Dependency Injection for LLM
mango_fr
2026. 1. 5. 06:31
LLM 모델을 함수의 파라미터로 전달하여 에이전트 로직과 모델 선택을 분리하는 설계 패턴
용어 정의
⚠️ Note: 이 문서에서 설명하는 패턴은 공식적인 명칭이 없습니다.
기존 소프트웨어 설계 패턴(Dependency Injection, Strategy Pattern)을 LLM 컨텍스트에 적용한 것입니다.
이 패턴을 설명하는 다양한 표현
- Dependency Injection for LLM - DI 패턴의 LLM 적용
- Strategy Pattern for Model Selection - 전략 패턴 관점
- Provider Abstraction - 프로바이더 추상화
- Pluggable LLM Backend - 교체 가능한 LLM 백엔드
- Model as Configuration - 모델을 설정으로 취급
실제 구현 사례
- Cursor IDE - API 요청 시
model파라미터로 전달 - LangChain -
llm파라미터로 모델 객체 주입 - CrewAI - Agent 생성 시
llm인자 지정 - 당근 LLM Router - API 호출 시
model필드로 지정
개요
LLM-as-Parameter 패턴은 AI 에이전트나 LLM 기반 애플리케이션에서 모델 선택을 외부화하는 설계 패턴이다. 이 패턴에서 LLM은 에이전트 내부에서 고정되지 않고, 함수 호출 시 파라미터로 전달된다.
핵심 원칙
의존성 역전 원칙 (Dependency Inversion)
전통적 접근 (High Coupling)
────────────────────────────
Agent → GPT-4 (직접 의존)
LLM-as-Parameter (Low Coupling)
───────────────────────────────
Agent → LLM Interface ← GPT-4
← Claude
← Gemini
에이전트가 구체적인 LLM 구현체가 아닌 추상화된 인터페이스에 의존하게 함으로써 결합도를 낮춘다.
패턴 구조
기본 구조
# ❌ Anti-pattern: 모델 하드코딩
class ChatAgent:
def __init__(self):
self.llm = OpenAI(model="gpt-4") # 하드코딩된 의존성
def chat(self, message: str) -> str:
return self.llm.complete(message)
# ✅ LLM-as-Parameter 패턴
class ChatAgent:
def __init__(self, llm: LLMInterface): # 주입받은 LLM
self.llm = llm
def chat(self, message: str) -> str:
return self.llm.complete(message)
# 사용 시점에 모델 결정
agent = ChatAgent(llm=OpenAI(model="gpt-4"))
agent = ChatAgent(llm=Anthropic(model="claude-3-opus"))
API 레벨 적용
# HTTP API 엔드포인트에서 모델을 파라미터로 받음
@app.post("/v1/agents/run")
async def run_agent(request: AgentRunRequest):
"""
Request Body:
{
"prompt": "...",
"model": "claude-4-sonnet", ← 클라이언트가 모델 선택
"tools": [...]
}
"""
agent = create_agent(model=request.model)
return await agent.run(request.prompt)
실제 구현 사례
1. Cursor IDE
프론트엔드 (Model Selector UI)
// IDE UI에서 모델 선택
interface ModelSelectorProps {
selectedModel: string;
onModelChange: (model: string) => void;
availableModels: Model[];
}
const ModelSelector: React.FC<ModelSelectorProps> = ({
selectedModel,
onModelChange,
availableModels
}) => (
<Select value={selectedModel} onChange={onModelChange}>
{availableModels.map(model => (
<Option key={model.id} value={model.id}>
{model.name}
</Option>
))}
</Select>
);
백엔드 (Cloud Agents API)
POST /v0/agents
{
"prompt": {
"text": "Create a REST API for user management"
},
"model": "claude-4-sonnet",
"source": {
"repository": "https://github.com/example/project"
}
}
API 응답 스펙
POST /v0/agents
Request Body:
- prompt (required): 실행할 프롬프트
- model (optional): 사용할 LLM (예: claude-4-sonnet)
지정하지 않으면 가장 적합한 모델을 자동으로 선택
- source (required): 소스 저장소 정보
2. LangChain
LangChain은 llm 파라미터를 통해 모델을 주입받는다.
from langchain.agents import create_react_agent
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
# 동일한 에이전트 로직, 다른 모델
def create_coding_agent(model: str):
if model.startswith("gpt"):
llm = ChatOpenAI(model=model)
elif model.startswith("claude"):
llm = ChatAnthropic(model=model)
else:
raise ValueError(f"Unsupported model: {model}")
return create_react_agent(
llm=llm, # 파라미터로 전달
tools=[code_executor, file_manager, searcher],
prompt=REACT_PROMPT
)
# 런타임에 모델 결정
agent = create_coding_agent(model="gpt-4-turbo")
agent = create_coding_agent(model="claude-3-sonnet")
3. CrewAI
멀티에이전트 시스템에서 각 에이전트에 다른 모델 할당.
from crewai import Agent, Task, Crew
# 연구 작업에는 GPT-4 (추론 능력)
researcher = Agent(
role="Senior Researcher",
goal="Research emerging technologies",
llm="gpt-4", # 파라미터로 모델 지정
tools=[web_search, arxiv_search]
)
# 작문 작업에는 Claude (자연어 생성 능력)
writer = Agent(
role="Technical Writer",
goal="Create comprehensive documentation",
llm="claude-3-opus", # 다른 모델 지정
tools=[document_writer]
)
# 리뷰 작업에는 경량 모델 (비용 최적화)
reviewer = Agent(
role="Quality Reviewer",
goal="Review and improve content",
llm="gpt-3.5-turbo", # 비용 효율적 모델
tools=[grammar_checker]
)
4. 당근 LLM Router
플랫폼 레벨에서 모델을 파라미터로 받아 라우팅.
from openai import OpenAI
# 당근 LLM Router는 OpenAI SDK 인터페이스 사용
client = OpenAI(base_url="https://llm-router.karrot.ai")
# model 파라미터로 원하는 모델 지정
# Router가 자동으로 해당 Provider로 라우팅
response = client.chat.completions.create(
model="claude-4.5-sonnet", # Anthropic으로 라우팅
messages=[{"role": "user", "content": "안녕하세요"}]
)
response = client.chat.completions.create(
model="gpt-5.2", # OpenAI로 라우팅
messages=[{"role": "user", "content": "Hello"}]
)
패턴 장점
1. 유연성 (Flexibility)
# 동일 코드로 다양한 모델 테스트
for model in ["gpt-4", "claude-3-opus", "gemini-pro"]:
result = agent.run(prompt, model=model)
evaluate(result, model)
2. 테스트 용이성 (Testability)
# Mock LLM으로 단위 테스트
class MockLLM(LLMInterface):
def complete(self, prompt):
return "Mocked response"
def test_agent_logic():
agent = ChatAgent(llm=MockLLM())
result = agent.chat("test")
assert result == "Mocked response"
3. 비용 최적화 (Cost Optimization)
def select_model_by_complexity(task):
"""작업 복잡도에 따라 모델 선택"""
if task.complexity == "low":
return "gpt-3.5-turbo" # $0.0005/1K tokens
elif task.complexity == "medium":
return "gpt-4-turbo" # $0.01/1K tokens
else:
return "claude-3-opus" # $0.015/1K tokens
# 비용 효율적 모델 자동 선택
model = select_model_by_complexity(task)
result = agent.run(task.prompt, model=model)
4. 장애 격리 (Fault Isolation)
async def run_with_fallback(prompt, primary_model, fallback_models):
"""Fallback 체인으로 안정성 확보"""
models = [primary_model] + fallback_models
for model in models:
try:
return await agent.run(prompt, model=model)
except (RateLimitError, ServiceUnavailableError):
logger.warning(f"{model} failed, trying next")
continue
raise AllModelsFailed("All models in fallback chain failed")
# 사용 예
result = await run_with_fallback(
prompt="Complex analysis",
primary_model="claude-3-opus",
fallback_models=["gpt-4", "gemini-pro"]
)
5. A/B 테스트
def ab_test_models(prompt, user_id):
"""사용자 기반 A/B 테스트"""
if hash(user_id) % 100 < 50:
model = "gpt-4"
variant = "A"
else:
model = "claude-3-opus"
variant = "B"
result = agent.run(prompt, model=model)
track_metrics(variant, result)
return result
구현 가이드
Step 1: 인터페이스 정의
from abc import ABC, abstractmethod
from typing import List, Dict, Any, Optional
from pydantic import BaseModel
class Message(BaseModel):
role: str # "system" | "user" | "assistant" | "tool"
content: str
tool_calls: Optional[List[Dict]] = None
class LLMInterface(ABC):
"""Model-Agnostic LLM 인터페이스"""
@abstractmethod
async def complete(
self,
messages: List[Message],
**kwargs
) -> Message:
"""기본 채팅 완성"""
pass
@abstractmethod
async def complete_with_tools(
self,
messages: List[Message],
tools: List[Dict[str, Any]],
**kwargs
) -> Message:
"""도구 호출이 가능한 채팅 완성"""
pass
Step 2: Provider 구현체 작성
from openai import AsyncOpenAI
from anthropic import AsyncAnthropic
class OpenAIProvider(LLMInterface):
def __init__(self, model: str):
self.client = AsyncOpenAI()
self.model = model
async def complete(self, messages: List[Message], **kwargs) -> Message:
response = await self.client.chat.completions.create(
model=self.model,
messages=[m.dict() for m in messages],
**kwargs
)
return Message(
role="assistant",
content=response.choices[0].message.content
)
class AnthropicProvider(LLMInterface):
def __init__(self, model: str):
self.client = AsyncAnthropic()
self.model = model
async def complete(self, messages: List[Message], **kwargs) -> Message:
response = await self.client.messages.create(
model=self.model,
messages=self._convert_messages(messages),
**kwargs
)
return Message(
role="assistant",
content=response.content[0].text
)
Step 3: Factory 패턴으로 생성
class LLMFactory:
"""모델명으로 적절한 Provider 인스턴스 생성"""
MODEL_MAPPING = {
"gpt-4": ("openai", OpenAIProvider),
"gpt-4-turbo": ("openai", OpenAIProvider),
"gpt-3.5-turbo": ("openai", OpenAIProvider),
"claude-3-opus": ("anthropic", AnthropicProvider),
"claude-3-sonnet": ("anthropic", AnthropicProvider),
"claude-3-haiku": ("anthropic", AnthropicProvider),
}
@classmethod
def create(cls, model: str) -> LLMInterface:
if model not in cls.MODEL_MAPPING:
raise ValueError(f"Unknown model: {model}")
_, provider_class = cls.MODEL_MAPPING[model]
return provider_class(model=model)
# 사용
llm = LLMFactory.create("claude-3-opus")
Step 4: Agent에서 사용
class ReActAgent:
"""LLM-as-Parameter를 적용한 ReAct Agent"""
def __init__(self, tools: List[Tool]):
self.tools = {t.name: t for t in tools}
async def run(
self,
prompt: str,
model: str, # 파라미터로 모델 받음
max_iterations: int = 10
) -> str:
llm = LLMFactory.create(model)
messages = [Message(role="user", content=prompt)]
for i in range(max_iterations):
# Think
response = await llm.complete_with_tools(
messages=messages,
tools=[t.schema for t in self.tools.values()]
)
if response.tool_calls:
# Act
for tool_call in response.tool_calls:
tool = self.tools[tool_call["name"]]
result = await tool.execute(tool_call["arguments"])
# Observe
messages.append(Message(
role="tool",
content=result
))
else:
return response.content
return "Max iterations reached"
Step 5: API 엔드포인트 노출
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class AgentRequest(BaseModel):
prompt: str
model: str = "claude-3-sonnet" # 기본값
max_iterations: int = 10
class AgentResponse(BaseModel):
result: str
model_used: str
iterations: int
@app.post("/v1/agent/run")
async def run_agent(request: AgentRequest) -> AgentResponse:
agent = ReActAgent(tools=[...])
result = await agent.run(
prompt=request.prompt,
model=request.model, # 클라이언트가 선택한 모델
max_iterations=request.max_iterations
)
return AgentResponse(
result=result,
model_used=request.model,
iterations=agent.iteration_count
)
Auto Mode 구현
사용자가 모델을 지정하지 않을 때 자동으로 최적 모델을 선택하는 기능.
class AutoModelSelector:
"""작업 특성에 따른 자동 모델 선택"""
def __init__(self):
self.classifier = TaskClassifier()
async def select(self, prompt: str, context: Dict) -> str:
"""프롬프트 분석 후 최적 모델 반환"""
task_type = await self.classifier.classify(prompt)
model_preferences = {
"coding": "claude-3-sonnet", # 코드 생성에 강함
"reasoning": "gpt-4", # 복잡한 추론
"creative": "claude-3-opus", # 창의적 작업
"simple_qa": "gpt-3.5-turbo", # 비용 효율적
"math": "gpt-4-turbo", # 수학 문제
}
return model_preferences.get(task_type, "claude-3-sonnet")
# API에서 Auto Mode 지원
@app.post("/v1/agent/run")
async def run_agent(request: AgentRequest) -> AgentResponse:
if request.model == "auto":
model = await auto_selector.select(request.prompt, {})
else:
model = request.model
result = await agent.run(prompt=request.prompt, model=model)
return AgentResponse(result=result, model_used=model, ...)
안티패턴
❌ 모델 하드코딩
# Bad: 모델이 코드에 하드코딩됨
class Agent:
def __init__(self):
self.llm = OpenAI(model="gpt-4")
❌ 환경변수로 고정
# Bad: 환경변수로 전역 설정
MODEL = os.getenv("LLM_MODEL", "gpt-4")
class Agent:
def __init__(self):
self.llm = OpenAI(model=MODEL) # 여전히 런타임 변경 불가
❌ 조건문 분기 과다
# Bad: 모든 모델을 조건문으로 처리
def chat(prompt, model):
if model == "gpt-4":
return openai_chat(prompt)
elif model == "gpt-3.5":
return openai_chat_35(prompt)
elif model == "claude":
return anthropic_chat(prompt)
# ... 무한 확장
✅ 권장 패턴
# Good: 인터페이스 + Factory
llm = LLMFactory.create(model)
result = await llm.complete(messages)
관련 패턴
- Strategy Pattern: LLM 선택 전략을 런타임에 교체
- Factory Pattern: 모델명으로 적절한 Provider 인스턴스 생성
- Dependency Injection: 외부에서 LLM 의존성 주입
- Circuit Breaker: 모델 장애 시 자동 전환