-
Dependency Injection for LLM이코에코(Eco²) Knowledge Base/Applied 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: """도구 호출이 가능한 채팅 완성""" passStep 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: 모델 장애 시 자동 전환
참고 자료
'이코에코(Eco²) Knowledge Base > Applied' 카테고리의 다른 글
Stateless Reducer Pattern for AI Agents (0) 2026.01.05 LangGraph : 상태관리, SSE, 오케스트레이션, 비동기 태스크 (0) 2026.01.05 LLM Gateway & Unified Interface Pattern (0) 2026.01.05 Karpenter: Kubernetes Node Autoscaling (0) 2025.12.26 KEDA: Kubernetes Event-Driven Autoscaling (0) 2025.12.26