Genkit으로 에이전트 기반 풀스택 앱 만들기
요약
Genkit Agents API는 대화형 AI 앱 개발에 필요한 메시지 기록, 도구 루프, 스트리밍 등 복잡한 '배관' 작업을 한데 묶어 간편하게 풀스택 에이전트 앱을 구축할 수 있게 해줘요.
인사이트
- Genkit Agents API는 대화형 AI 앱 개발 시 반복되는 메시지 기록, 도구 통합, 스트리밍, 영속성 등의 복잡한 구현 과정을 하나의 인터페이스로 추상화하여 개발을 간소화해요.
- 서버에서 에이전트를 정의하고, 클라이언트(TypeScript, Go, Dart, Python 지원)에서 동일한 `chat()` API를 통해 구동할 수 있어 백엔드와 프론트엔드의 통합을 쉽게 해줘요.
- 서버 관리형 또는 클라이언트 관리형 상태 관리를 유연하게 선택할 수 있으며, 사용자 개입을 위한 일시 중지, 장기 실행 작업 분리, 전문 에이전트 조정 등 고급 기능을 내장하고 있어요.
왜 중요한가
대화형 AI 앱을 개발할 때 반복되는 복잡한 '배관' 작업을 줄여줘서 개발자들이 핵심 기능에 집중할 수 있게 도와줘요. 풀스택 통합, 유연한 상태 관리, 인간 개입, 장기 실행 작업까지 지원해서 강력한 AI 앱을 더 쉽고 빠르게 만들 수 있게 해주죠.
2026년 7월 1일
Genkit Agents 출시: 대화형 AI를 위한 풀스택 기반
Genkit은 TypeScript, Go, Dart, Python을 지원하며, 모든 플랫폼을 위한 풀스택, AI 기반 에이전트 애플리케이션을 만드는 오픈소스 프레임워크예요. 가장 매력적인 AI 기능 중 일부는 대화형이에요. 예를 들어, 티켓 기록을 기억하는 지원 어시스턴트나 여러 단계에 걸쳐 작동하는 코파일럿 같은 것들이죠. 이런 기능들은 단순히 generate() 호출 한 번으로 끝나는 게 아니에요. 현재는 메시지 기록, 도구 루프, 스트리밍, 영속성, 프론트엔드 프로토콜까지 손수 연결해야 해요. 이런 반복적인 '배관' 작업은 모든 프로젝트에서 계속되는데, 정작 우리 앱을 특별하게 만드는 것과는 거리가 멀죠.
Genkit은 이 문제를 Agents API로 해결해줘요. 이 API는 이 모든 것을 하나의 인터페이스 뒤에 깔끔하게 묶어 놓았어요. 서버에서 에이전트를 정의하고 나면, 인-프로세스로 실행되든 HTTP 엔드포인트 뒤에서 실행되든 상관없이 동일한 chat() API로 에이전트를 구동할 수 있어요.
> Agents API는 현재 TypeScript와 Go에서 미리 보기 버전으로 제공되고 있어요. 마이너 버전 릴리스에서 호환성 문제가 발생할 수도 있다는 점 참고해주세요.
에이전트 정의하기
에이전트는 시작할 때 이름과 시스템 프롬프트가 필요해요. 거기서부터 기능이 늘어날수록 도구, 상태, 세션 저장소를 추가하게 돼요.
import genkitx "github.com/firebase/genkit/go/genkit/exp"
g := genkit.Init(ctx,
genkit.WithPlugins(&googlegenai.GoogleAI{}),
genkit.WithExperimental(), // Enables preview features like Agents API.
)
assistant := genkitx.DefineAgent(g, "assistant",
aix.InlinePrompt{
ai.WithModelName("googleai/gemini-flash-latest"),
ai.WithSystem("You are a helpful assistant."),
},
)
out, err := assistant.RunText(ctx, "Hello. What can you do?")
if err != nil {
log.Fatal(err)
}
fmt.Println(out.Message.Text())
Go
Copied
이 에이전트 객체는 정말 유연해서 한 번의 응답, 스트리밍 턴, 일시 중지된 도구 호출, 그리고 여러 단계의 대화까지 모두 처리할 수 있어요. 기능이 커진다고 해서 다른 추상화로 바꿀 필요가 없다는 거죠.
원하는 곳에 상태 저장하기
모든 대화는 턴(turn) 사이의 연속성이 필요하고, 누가 이 상태를 소유할지 여러분이 결정할 수 있어요.
store를 추가하면 에이전트가 서버 관리형이 돼요. 서버는 메시지, 사용자 정의 상태, 아티팩트를 스냅샷으로 영구 저장하고, 클라이언트는 세션 ID를 다시 보내서 대화를 이어나갈 수 있어요. 영구적인 채팅 앱, 공유 기기, 또는 클라이언트가 전체 대화를 가지고 있으면 안 되는 모든 워크플로에 이 방식을 선택하면 돼요.
import firebasex "github.com/firebase/genkit/go/plugins/firebase/exp"
import genkitx "github.com/firebase/genkit/go/genkit/exp"
store, err := firebasex.NewFirestoreSessionStore[WeatherState](ctx, g,
firebasex.WithCollection("snapshots"),
firebasex.WithCheckpointInterval(10),
)
if err != nil {
log.Fatal(err)
}
weatherAgent := genkitx.DefineAgent(g, "weatherAgent",
aix.InlinePrompt{
ai.WithSystem("Answer weather questions. Ask for a location when one is missing."),
ai.WithTools(getWeather),
},
aix.WithSessionStore(store),
)
Go
Copied
어떤 스토어를 구성하느냐에 따라 스냅샷이 어디에 저장될지 결정돼요. 프로덕션 환경에서는 Firestore가 관리형 멀티 인스턴스 데이터베이스를 제공해서 여러 서버 인스턴스가 공유할 수 있죠. Genkit은 로컬 작업용으로 더 가벼운 스토어도 제공하고, 여러분이 직접 구현할 수도 있어요. 이 부분은 아래 섹션에서 다룰 거예요.
스토어를 설정하지 않으면 에이전트는 클라이언트 관리형이 돼요. 서버는 전체 상태를 반환하고, 클라이언트는 다음 턴에 다시 이 상태를 보내는 식이죠. 앱이 이미 영속성을 직접 관리하거나 무상태 서버 배포가 필요할 때 이 방법을 사용하면 좋아요.
서버 관리형으로 성공적인 턴을 마칠 때마다 스냅샷이 기록돼요. 그래서 sessionId로 최신 상태를 이어서 진행하거나 snapshotId로 정확한 과거 시점에서 분기할 수 있어요. 분기 기능을 사용하면 사용자가 원본 대화 흐름을 방해하지 않고도 저장된 시점부터 다른 대화를 탐색할 수 있어요.
// Continue the latest state in a conversation.
out, err := weatherAgent.RunText(ctx, "Continue where we left off.",
aix.WithSessionID[WeatherState]("user-session-123"),
)
// Or branch from a specific saved point.
branch, err := weatherAgent.RunText(ctx, "Revise this plan for a smaller budget.",
aix.WithSnapshotID[WeatherState](approvedPlanSnapshotID),
)
Go
Copied
메시지 기록 외에도 에이전트는 두 가지 상태를 더 가지고 있어요. **사용자 정의 상태(Custom state)**는 워크플로 상태, 작업 목록, 선택된 엔티티처럼 다음 턴을 구동하는 여러분의 타입 지정된 애플리케이션 데이터, 즉 간결한 제어 및 UI 값들이에요. **아티팩트(Artifacts)**는 보고서, 패치, 여행 일정표처럼 사용자가 직접 확인하거나 다운로드하거나 버전을 관리할 수 있는 생성된 결과물들이죠. 도구는 활성 세션을 통해 이 둘 중 하나를 업데이트하고, Genkit은 변경 사항이 발생하면 클라이언트로 스트리밍해줘요.
HTTP를 통해 서비스하기
모든 에이전트는 이미 서비스 가능한 액션이에요. 그래서 HTTP 엔드포인트 뒤에 에이전트를 두는 건 몇 줄의 코드면 충분하죠. 라우트 헬퍼는 표준 http.ServeMux에 마운트할 수 있는 디스크립터를 반환하고, 턴 엔드포인트와 스냅샷 및 중단(abort) 기능을 자동으로 연결해줘요.
import genkitx "github.com/firebase/genkit/go/genkit/exp"
mux := http.NewServeMux()
for _, route := range genkitx.AllAgentRoutes(g) {
mux.HandleFunc(route.Pattern(), route.Handler())
}
log.Fatal(http.ListenAndServe(":8080", mux))
Go
Copied
아래 클라이언트가 사용하는 동일한 와이어 프로토콜이에요. 그래서 JavaScript나 Go 백엔드는 어떤 클라이언트에게든 똑같이 서비스를 제공할 수 있어요.
풀스택 통합을 위한 풍부한 클라이언트
서버와 클라이언트를 연결해주는 핵심은 바로 원격 에이전트(remote agent)예요. remoteAgent()는 로컬 에이전트와 동일한 chat() 인터페이스를 가진 핸들을 반환해요. 덕분에 백엔드 테스트에서 에이전트를 구동하는 코드가 브라우저에서 에이전트를 구동하는 코드와 똑같아지는 거죠. 별도의 요청/응답 프로토콜을 설계할 필요도 없고, 스트리밍 형식을 새로 만들어낼 필요도 없어요.
구글 팀은 JavaScript 클라이언트를 출시하고 있어서, 웹 프론트엔드도 동일한 에이전트 엔드포인트와 통신할 수 있어요. 다음은 TypeScript 프론트엔드에서 원격 에이전트에 연결하는 방법을 보여주는 예시예요.
import { remoteAgent } from 'genkit/beta/client';
const agent = remoteAgent<WeatherState>({
url: 'http://localhost:8080/api/weatherAgent',
});
const chat = agent.chat();
const res = await chat.send('Weather in Tokyo?');
console.log(res.text);
JavaScript
Copied
클라이언트는 에이전트 라우트를 통해 하나의 와이어 프로토콜로 통신하기 때문에 JavaScript 백엔드든 Go 백엔드든 동일하게 작동해요. 요청별로 동적 인증 헤더를 해결하고, 스트리밍되는 상태 패치를 적용하며, 여러분의 에이전트가 어떤 방식을 사용하든 세션 ID, 스냅샷 ID, 또는 클라이언트 관리형 상태로 다음 턴을 이어나갈 수 있어요.
스트리밍도 동일한 인터페이스에 내장되어 있어요. sendStream()은 청크 스트림과 최종 응답을 제공하고, 각 청크는 생성되는 대로 텍스트, 사용자 정의 상태 또는 아티팩트를 담을 수 있어요.
const turn = agent.chat().sendStream('Write a long report.');
for await (const chunk of turn.stream) {
if (chunk.text) process.stdout.write(chunk.text);
if (chunk.custom) updateStatus(chunk.custom);
if (chunk.artifact) renderArtifact(chunk.artifact);
}
const res = await turn.response;
JavaScript
Copied
만약 이미 Vercel AI SDK UI 라이브러리를 사용하는 앱이 있다면, @genkit-ai/vercel-ai 패키지에서 useChat 훅을 위한 어댑터를 제공하고 있어요. GenkitChatTransport 어댑터는 useChat을 Genkit 에이전트에 연결해주기 때문에, Vercel의 AI Elements 컴포넌트로 인터페이스를 구성하면서도 백엔드에서는 Genkit의 모든 이점을 누릴 수 있어요.
사람의 승인 기능, 내장되어 있어요
도구가 에이전트를 일시 중지시키고 사용자에게 제어권을 다시 넘겨줄 수 있어요. 모델이 외부 입력이 필요하다고 판단하면 도구가 중단되고, 클라이언트는 턴이 계속되기 전에 승인, 거부 또는 누락된 값을 제공해요. 결제, 배포 또는 자동으로 실행되기를 원하지 않는 모든 작업 전에 이렇게 '인간 개입(human in the loop)'을 적용하는 거예요.
import genkitx "github.com/firebase/genkit/go/genkit/exp"
import "github.com/firebase/genkit/go/ai/exp/tool"
runShell := genkitx.DefineInterruptibleTool(g, "run_shell",
"Run a shell command after a safety check.",
func(ctx context.Context, input ShellInput, confirm *Confirmation) (ShellOutput, error) {
if isRisky(input.Command) {
if confirm == nil {
return ShellOutput{}, tool.Interrupt(ShellInterrupt{
Command: input.Command,
Reason: "The command can modify files.",
})
} else if !confirm.Approved {
return ShellOutput{}, errors.New("user rejected shell command execution")
}
}
return execute(input.Command)
},
)
Go
Copied
턴은 interrupted 이유와 응답에 포함된 일시 중지된 요청으로 마무리돼요. 사용자가 응답하면 클라이언트가 재개되고, 런타임은 재개 페이로드를 세션 기록과 비교해서 검증하기 때문에 도구가 위조된 입력으로 실행되지 않도록 속일 수 없어요.
요청을 넘어서는 작업
어떤 턴은 사용자가 기다리고 싶어 하는 시간보다 더 오래 걸리기도 해요. 서버 관리형 상태를 사용하면 클라이언트가 턴을 분리하고, 탭을 닫았다가 나중에 스냅샷 ID로 다시 연결할 수 있어요. 에이전트는 서버에서 계속 작동하면서 진행 상황을 대기 중인 스냅샷에 기록하고, 다른 세션은 이 스냅샷을 폴링(poll)하거나 기다리거나 중단할 수 있어요.
const chat = reportAgent.chat({ sessionId: 'report-123' });
const task = await chat.detach('Write the quarterly market report.');
// Persist this so any client can reconnect to the work later.
savePendingSnapshot(task.snapshotId);
for await (const snapshot of task.poll({ intervalMs: 1000 })) {
renderStatus(snapshot.status);
if (snapshot.status === 'completed') renderMessages(snapshot.state.messages);
}
JavaScript
Copied
이렇게 하면 긴 연구 작업, 다단계 계획, 도구가 많이 사용되는 워크플로를 연결을 계속 열어두거나 별도의 작업 큐를 만들 필요 없이 실용적으로 만들 수 있어요.
전문가들 조율하기
하나의 프롬프트로 모든 것을 잘 처리하기 어려울 때는 작업을 전문화된 에이전트들로 나누고, 오케스트레이터가 그 결과들을 결합하도록 할 수 있어요. Agents 미들웨어는 각 서브 에이전트(sub-agent)에 위임 도구(delegation tool)를 주입해서, 오케스트레이터 모델이 요청의 일부를 적절한 전문가에게 라우팅할 수 있게 해줘요. Genkit을 이용한 서브 에이전트들은 여러분에게 완전한 제어권과 자신만의 오케스트레이션을 구현할 수 있는 능력을 제공해요.
import middlewarex "github.com/firebase/genkit/go/plugins/middleware/exp"
coordinator := genkit.DefineAgent(g, "coordinator",
aix.InlinePrompt{
ai.WithSystem("Delegate to specialists, inspect their results, then answer the user."),
ai.WithUse(
&middlewarex.Agents{
Agents: []aix.AgentRef{researcher.Ref(), coder.Ref()},
MaxDelegations: 5,
ArtifactStrategy: middlewarex.ArtifactStrategySession,
},
&middlewarex.Artifacts{Readonly: true},
),
},
)
Go
Copied
위임은 오케스트레이터 스트림에서 일반적인 도구 활동으로 나타나고, 전문가 아티팩트들은 부모 세션으로 병합될 수 있어서 최종 답변은 각 전문가가 생성한 것을 기반으로 만들어질 수 있어요.
ADK를 대신 사용해야 할 때
Genkit 에이전트는 풀스택 사용자 대면 앱 안에 존재하도록 만들어진 애플리케이션 프리미티브예요. 다음의 경우에는 Agent Development Kit (ADK)를 대신 고려해보세요:
- 다중 에이전트 오케스트레이션이 단지 하나의 기능이 아니라 시스템 전체일 때. ADK는 복잡한 에이전트 토폴로지를 위해 특별히 제작되었어요. 반면에 Genkit의 위임 미들웨어는 의도적으로 더 가볍게 만들어졌고 에이전트 추상화의 핵심 부분에 내장되어 있지 않아요.
- 단순한 라이브러리가 아니라 관리형 런타임을 원할 때. ADK는 호스팅, 스케일링, 관리형 세션을 위해 Gemini Enterprise Agent Platform의 Agent Runtime과 함께 작동해요.
영속성 선택하기
서버 관리형 에이전트는 세션 저장소를 통해 스냅샷을 저장하는데, Genkit은 여러 가지를 제공해서 여러분이 실행하는 환경에 맞는 저장소를 선택할 수 있어요:
- 인메모리(In-memory): 테스트, 데모, 단일 프로세스 실험용.
- 파일(File): 로컬 개발 및 재시작 후에도 스냅샷이 유지되어야 하는 단일 호스트 앱용.
- Firestore: Google Cloud 또는 Firebase에서 실행되는 프로덕션 앱용. 관리형 멀티 인스턴스 데이터베이스를 원하고 스토어 코드를 직접 작성하고 싶지 않을 때 좋아요.
- 사용자 정의(Custom): 자신만의 데이터베이스, 권한 부여 또는 특정 보존 정책이 필요할 때 사용해요.
store인터페이스를 사용해서 자신만의 영속성 레이어를 구현할 수 있어요.
개발자 UI에서 테스트하고 탐색하기
에이전트는 Genkit 개발자 UI에서 일급 객체로 다뤄져요. 새로운 Agent Runner를 사용하면 클라이언트를 직접 작성할 필요 없이 대화를 시작하고, 턴을 보내고, 스트리밍 출력과 상태 업데이트를 보고, 도구 중단을 유도하고, 스냅샷을 검사할 수 있어요. 에이전트를 만들 때 테스트하고, 디버깅할 때 대화를 재현하는 가장 빠른 방법이 될 거예요.
시작하기
Agents API는 대화형 풀스택 AI의 반복적인 '배관' 작업을 재구축하는 대신 구성하는 것으로 바꿔줘요. 서버에서 에이전트를 정의하고, 영속성이 필요하면 스토어를 지정하고, remoteAgent()를 통해 프론트엔드에서 동일한 chat() 인터페이스로 에이전트를 구동해보세요.
더 자세히 알고 싶다면 풀스택 에이전트 문서를 확인하거나, Genkit이 처음이라면 Genkit 시작하기 페이지를 방문해보세요. 이 API는 베타 버전이니, 여러분의 피드백이 중요해요. 어떤 것을 만들었는지, 무엇을 바꾸고 싶은지 이슈를 남겨주세요.
즐거운 코딩하세요! 🚀
다음