
Genkit 미들웨어(Middleware)가 등장한 배경
LLM(Large Language Model)을 활용한 애플리케이션이 단순한 챗봇 수준을 넘어 에이전틱(Agentic) 시스템으로 진화하면서, 개발자들은 새로운 종류의 신뢰성 문제와 마주하게 되었습니다. 모델이 도구(tool)를 호출하고, 결과를 받아 다시 추론을 이어가며, 때로는 파일을 수정하거나 외부 API를 호출하는 등 실제 부작용(side effect)을 일으키는 작업까지 수행하기 때문입니다. 이런 환경에서는 단순히 좋은 프롬프트 와 강력한 모델 만으로는 충분하지 않으며, 재시도(retry), 폴백(fallback), 사람 검토(human-in-the-loop), 관찰성(observability) 같은 운영 수준의 제어 장치가 필수가 됩니다.
이러한 문제 의식에서, Google이 자사 오픈소스 AI 프레임워크인 Genkit에 미들웨어(middleware) 시스템을 정식 도입했습니다. Genkit은 TypeScript, Go, Dart, Python을 지원하는 풀스택 AI/에이전트 애플리케이션 프레임워크로, LangChain, LlamaIndex, Vercel AI SDK와 같은 도구들과 경쟁/보완 관계에 있습니다. 이번에 추가된 미들웨어는 모델 호출 파이프라인의 각 단계에 합성 가능한(composable) 훅(hook) 을 끼워 넣어, 생성 호출과 도구 실행 루프 전반에 사용자 정의 동작을 주입할 수 있게 해줍니다. 현재 TypeScript, Go, Dart에서 사용 가능하며 Python 지원은 곧 추가될 예정입니다.
Express.js의 미들웨어나 gRPC interceptor에 익숙한 개발자라면 그 디자인 철학을 그대로 LLM 도구 루프로 옮겨온 것이라고 이해하면 됩니다. 즉, 매번 비슷한 패턴(재시도, 로깅, 검증, 권한 확인)을 비즈니스 로직 안에 반복해서 작성하는 대신, 횡단 관심사(cross-cutting concern)를 미들웨어로 분리하여 결정론적으로 강제할 수 있습니다.
Genkit 미들웨어의 동작 구조: 세 개의 훅(Hook) 레이어
Genkit에서 모든 generate() 호출은 도구 루프(tool loop) 위에서 동작합니다. 모델이 출력을 생성하고, 요청된 도구가 실행되며, 그 결과가 다시 모델 입력으로 들어가는 사이클이 모델이 완료를 선언할 때까지 반복됩니다. 미들웨어는 바로 이 루프의 서로 다른 세 지점에 훅을 걸어 동작합니다.
| Hook | 실행 시점 | 대표적인 용도 |
|---|---|---|
| Generate | 도구 루프 1회 반복당 1회 | 컨텍스트 주입, 메시지 재작성, 대화 수준 로직 |
| Model | 모델 API 호출 1회당 1회 | 재시도, 폴백, 캐싱, 지연 시간 로깅 |
| Tool | 도구 실행 1회당 1회 | 사람 검토, 샌드박싱, 도구별 로깅 |
세 레이어를 구분하는 이유는 어디에서 무엇을 제어해야 하는가 가 명확히 다르기 때문입니다. 예를 들어 모델 API의 일시적 장애(transient failure)를 처리할 때는 도구 루프 전체를 다시 돌리고 싶지 않을 것입니다. 사람 검토는 모델이 아닌 특정 도구 호출 시점에만 끼어들어야 하며, 시스템 프롬프트 주입은 매 반복마다가 아닌 대화 진입 시점에 한 번 수행되는 것이 자연스럽습니다. Genkit 미들웨어는 이런 결정을 명시적인 레이어 선택 으로 표현하게 해줍니다.
즉시 사용 가능한 사전 빌트인(Pre-built) 미들웨어 다섯 가지
Genkit은 흔히 등장하는 운영 패턴을 미리 구현한 다섯 가지 빌트인 미들웨어를 함께 제공합니다. 각각이 어떤 문제를 해결하는지 살펴봅니다.
1. Retry: 일시적 실패에 대한 지수 백오프(exponential backoff)
모델 API 호출에서 발생하는 RESOURCE_EXHAUSTED(쿼터 초과), UNAVAILABLE(일시적 서비스 장애) 같은 일시적 오류(transient error) 에 대해, 지터(jitter)를 포함한 지수 백오프 방식으로 자동 재시도를 수행합니다. 중요한 점은 모델 호출만 재시도되며, 도구 루프 전체가 재실행되지는 않는다는 점입니다. 도구 실행은 부작용을 동반할 수 있어 멱등성(idempotency)을 보장하기 어려우므로, 자동 재시도의 범위를 모델 호출로 제한하는 것이 안전합니다.
resp, err := genkit.Generate(ctx, g,
ai.WithModelName("googleai/gemini-flash-latest"),
ai.WithPrompt("Summarize the quarterly earnings report."),
ai.WithUse(&middleware.Retry{
MaxRetries: 3,
InitialDelayMs: 1000,
BackoffFactor: 2,
}),
)
이 패턴은 AWS의 백오프 가이드나 Google Cloud의 재시도 권장 사항에서 설명하는 표준적인 접근법을 모델 호출에 그대로 적용한 것입니다.
2. Fallback: 주력 모델 실패 시 대체 모델로 전환
특정 오류 코드 집합에서 주력 모델(primary model)이 실패하면, 미리 지정한 대체 모델(fallback model)로 자동 전환합니다. 쿼터를 초과했을 때 Google AI의 Gemini에서 Anthropic의 Claude로 넘어가는 식의, 프로바이더 간(cross-provider) 폴백까지 한 번에 표현할 수 있다는 점이 특징적입니다.
resp, err := genkit.Generate(ctx, g,
ai.WithModelName("googleai/gemini-flash-latest"),
ai.WithPrompt("Analyze this complex document..."),
ai.WithUse(&middleware.Fallback{
Models: []ai.ModelRef{
anthropic.ModelRef("claude-sonnet-4-6", nil), // fall back to Claude
},
Statuses: []core.StatusName{core.RESOURCE_EXHAUSTED},
}),
)
벤더 락인(vendor lock-in)을 줄이고 SLA를 안정화하려는 프로덕션 시스템에서 특히 유용한 패턴으로, OpenRouter나 Portkey 같은 LLM 라우터가 제공하는 기능을 코드 레벨에서 명시적으로 다룰 수 있게 해줍니다.
3. Tool approval: 사람 검토(Human-in-the-loop)를 위한 도구 인터럽트
AllowedTools 허용 목록에 없는 도구가 호출되면 즉시 인터럽트(interrupt)를 발생시키고, 호스트 애플리케이션이 사람의 승인을 받은 뒤 재개(resume)할 수 있도록 합니다. 파일 삭제, 결제, 이메일 발송처럼 되돌리기 어려운(non-reversible) 작업에 안전망을 두는 표준 패턴입니다.
resp, _ := genkit.Generate(ctx, g,
ai.WithPrompt("Delete the temp files"),
ai.WithTools(deleteFilesTool),
ai.WithUse(&middleware.ToolApproval{
AllowedTools: []string{}, // empty = every tool call interrupts
}),
)
if len(resp.Interrupts()) > 0 {
interrupt := resp.Interrupts()[0]
// Prompt the user for approval, then resume with the approval flag.
approved, _ := deleteFilesTool.RestartWith(interrupt,
ai.WithResumedMetadata[DeleteInput](map[string]any{"toolApproved": true}),
)
resp, err := genkit.Generate(ctx, g,
ai.WithMessages(resp.History()...),
ai.WithTools(deleteFilesTool),
ai.WithToolRestarts(approved),
ai.WithUse(&middleware.ToolApproval{}),
)
fmt.Println(resp.Text())
}
이 패턴은 Claude Code, Cursor, Aider 등이 파일 수정 전에 사용자 확인을 받는 흐름과 본질적으로 동일하며, OpenAI의 Agents 안전성 가이드에서 강조하는 위험한 행동에 대한 사람 검토 원칙을 구현하는 표준화된 방법이라 볼 수 있습니다.
4. Skills: 디렉터리에서 SKILL.md를 읽어 시스템 프롬프트에 주입
지정한 디렉터리를 스캔해 그 안의 SKILL.md 파일들을 시스템 프롬프트에 자동으로 주입합니다. 추가로 use_skill 도구가 노출되어, 모델이 필요에 따라 특정 스킬을 온디맨드로 로드할 수 있습니다.
resp, err := genkit.Generate(ctx, g,
ai.WithPrompt("How do I deploy this service?"),
ai.WithUse(&middleware.Skills{SkillPaths: []string{"./skills"}}),
)
이 구조는 Anthropic이 제안한 Agent Skills 개념이나 Claude Code의 슬래시 커맨드/스킬 시스템과 매우 유사합니다. 스킬을 코드베이스에 파일 형태로 두고 모델에게 컨텍스트로 노출하면, 프롬프트 엔지니어링을 버전 관리 가능한 자산으로 전환할 수 있다는 장점이 있습니다.
5. Filesystem: 경로 안전성이 보장된 로컬 파일 접근
list_files, read_file 같은 읽기 도구와, 쓰기를 활성화하면 write_file, edit_file까지 모델에게 노출합니다. 핵심은 경로 안전성(path safety) 이 강제되어 모델이 루트 디렉터리를 벗어나지 못한다는 점입니다.
resp, err := genkit.Generate(ctx, g,
ai.WithPrompt("Create a hello world program in the workspace"),
ai.WithUse(&middleware.Filesystem{
RootDir: "./workspace",
AllowWriteAccess: true,
}),
)
Path traversal 공격은 LLM 에이전트 보안에서 가장 흔히 거론되는 위험 중 하나이며, OWASP LLM Top 10에서도 도구 권한 격리를 권고합니다. 미들웨어 레벨에서 루트 경계를 강제하면 이 위험을 프롬프트 지시 가 아니라 실행 시 검증 으로 차단할 수 있습니다.
커스텀 미들웨어(Custom Middleware) 작성하기
빌트인 미들웨어가 흔한 시나리오를 덮어주지만, 이 시스템의 진짜 가치는 사용자 정의 미들웨어 를 만들 수 있다는 데 있습니다. 예를 들어 고객 지원 에이전트를 만들면서 모델이 절대 경쟁사 제품명이나 내부 가격 정책을 언급해서는 안 된다 는 요구가 있다고 가정해 봅시다. 이 규칙을 매 프롬프트에 끼워 넣는 대신, 미들웨어로 모델 출력을 후처리하여 결정론적으로 차단할 수 있습니다.
커스텀 미들웨어의 계약은 모든 언어에서 동일합니다. 이름(name) 과, 필요한 훅을 반환하는 팩토리 함수(factory function) 를 제공하면 끝이며, 팩토리는 generate() 호출마다 한 번씩 호출됩니다. 필요한 훅만 골라 구현하면 되므로 보일러플레이트가 거의 없습니다. 다음은 약 20줄로 작성한 콘텐츠 필터의 전체 코드입니다.
// ContentFilter rejects model responses containing any forbidden term.
type ContentFilter struct {
ForbiddenTerms []string `json:"forbiddenTerms"`
}
func (ContentFilter) Name() string { return "app/contentFilter" }
func (f ContentFilter) New(ctx context.Context) (*ai.Hooks, error) {
return &ai.Hooks{
WrapModel: func(ctx context.Context, p *ai.ModelParams, next ai.ModelNext) (*ai.ModelResponse, error) {
resp, err := next(ctx, p)
if err != nil {
return nil, err
}
text := strings.ToLower(resp.Text())
for _, term := range f.ForbiddenTerms {
if strings.Contains(text, strings.ToLower(term)) {
return nil, fmt.Errorf("content filter: response contains %q", term)
}
}
return resp, nil
},
}, nil
}
미들웨어 스택의 합성 순서
여러 미들웨어를 함께 사용할 수 있으며, 왼쪽에서 오른쪽으로 스택됩니다. 즉, 가장 먼저 나열된 미들웨어가 가장 바깥쪽 래퍼(outermost wrapper)가 되어 안쪽 스택 전체를 감쌉니다.
resp, err := genkit.Generate(ctx, g,
ai.WithModelName("googleai/gemini-flash-latest"),
ai.WithPrompt("What CRM should our customer use?"),
ai.WithUse(
&middleware.Retry{MaxRetries: 3}, // outer: retries the inner stack
&ContentFilter{ // inner: validates model output
ForbiddenTerms: []string{"CompetitorCRM", "RivalCo", "internal price"},
},
),
)
위 예제에서 Retry는 ContentFilter를 감싸고, ContentFilter는 모델 호출을 감쌉니다. 만약 순서를 뒤집어 ContentFilter를 바깥에 두고 Retry를 안쪽에 두면, 필터가 거부된 응답에 대해 재시도가 일어나지 않는 다른 의미가 됩니다. Genkit은 이 순서를 암묵적인 우선순위 가 아니라 코드에 명시된 순서 로 강제함으로써, 의도를 분명하게 드러냅니다. 직접 만든 미들웨어가 다른 개발자에게도 유용하다고 판단되면 패키지로 공개해 생태계에 기여할 수도 있습니다.
Developer UI에서의 미들웨어 검사와 디버깅
Genkit은 자체 Developer UI를 제공하여, 애플리케이션의 실행을 시각적으로 검사하고 테스트할 수 있도록 합니다. 등록한 미들웨어는 이 UI에서 1급 시민(first-class citizen)으로 노출되며, 다음과 같은 작업이 가능합니다.
- 각 미들웨어의 설정 값 을 그대로 확인
- 각 훅 레이어(Generate, Model, Tool)를 거치는 동안의 트레이스(trace) 를 단계별로 추적
- 미들웨어 조합을 바꿔가며 즉시 테스트 하여 동작 차이를 비교
이 부분은 LangSmith나 Langfuse, Phoenix 같은 LLM 관찰성 도구가 제공하는 가치와 겹치지만, 프레임워크에 내장 되어 별도 인스트루멘테이션 없이 동작한다는 점이 차별 요소입니다.
기존 LLM 프레임워크의 인터셉터/콜백과 비교
미들웨어 자체는 새로운 아이디어가 아니지만, 에이전틱 도구 루프 라는 맥락에 맞춰 다층 훅 구조를 명시적으로 제공한다는 점에서 다른 프레임워크와 차별됩니다.
| 프레임워크 | 유사 기능 | 특징 |
|---|---|---|
| Genkit | Middleware(Generate/Model/Tool 3-layer) | 도구 루프 인식, 다중 언어, Dev UI 통합 |
| LangChain | Callbacks, Runnable | 이벤트 중심, 체인 전반에 걸친 후킹 |
| LlamaIndex | Instrumentation | 이벤트 디스패처 기반 |
| Vercel AI SDK | Middleware | 모델 호출 래퍼 중심, TS 전용 |
| Semantic Kernel | Filters | Function/Prompt/Auto-function-invocation 필터 |
Genkit의 강점은 도구 호출(tool) 과 모델 호출(model) 을 별도의 훅 레이어로 분리해, 사람 검토나 샌드박싱 같은 도구 단위 정책을 모델 단위 정책과 혼동하지 않고 표현할 수 있다는 점입니다. 또한 Firebase/Google Cloud 통합과 Vertex AI 모델 라우팅까지 매끄럽게 이어진다는 점이 Google 생태계 안에서 일하는 팀에게 매력적입니다.
Genkit 시작하기
Genkit 미들웨어는 공식 문서에서 더 자세한 사용법을 확인할 수 있으며, Genkit이 처음이라면 Get Started 가이드에서 입문할 수 있습니다. 새로운 빌트인 미들웨어 아이디어가 있다면 GitHub 이슈에 제안하여 프로젝트에 기여할 수도 있습니다.
운영 환경의 LLM 애플리케이션이 자주 마주하는 신뢰성, 안전성, 거버넌스 문제를 비즈니스 로직에서 분리 하여 횡단적으로 다루고자 한다면, Genkit 미들웨어는 충분히 검토해 볼 만한 도구입니다. 특히 도구 호출이 많고 부작용이 큰 에이전트를 운영하는 팀에서, 사람 검토와 모델 폴백을 명시적이고 검증 가능한 방식으로 도입하려는 시도에 잘 맞습니다.
라이선스
Genkit은 Apache License 2.0으로 배포되고 있어, 연구 목적은 물론 상업적 용도로도 자유롭게 사용 및 수정이 가능합니다.
Genkit 공식 홈페이지
Announcing Genkit Middleware 소개 블로그
Genkit GitHub 저장소
더 읽어보기
-
AI 에이전트 프로토콜 개발자 가이드: MCP부터 A2A, UCP, AP2, A2UI, AG-UI까지 (feat. Google)
-
Agentic Design Patterns: 지능형 에이전트 시대를 위한 설계 패턴 가이드 [Google Docs/영문/424p]
이 글은 GPT 모델로 정리한 글을 바탕으로 한 것으로, 원문의 내용 또는 의도와 다르게 정리된 내용이 있을 수 있습니다. 관심있는 내용이시라면 원문도 함께 참고해주세요! 읽으시면서 어색하거나 잘못된 내용을 발견하시면 덧글로 알려주시기를 부탁드립니다. ![]()
파이토치 한국 사용자 모임
이 정리한 이 글이 유용하셨나요? 회원으로 가입하시면 주요 글들을 이메일
로 보내드립니다! (기본은 Weekly지만 Daily로 변경도 가능합니다.)
아래
쪽에 좋아요
를 눌러주시면 새로운 소식들을 정리하고 공유하는데 힘이 됩니다~ ![]()
