Google ADK에서 마이그레이션하기
Google ADK 에이전트를 JamJet으로 마이그레이션하기 위한 개념 매핑 및 코드 비교 — 내구성 있는 실행, 재생 및 내장 평가 기능을 확보하세요.
개념 매핑
| Google ADK | JamJet |
|---|---|
LlmAgent | @workflow.step (Python) 또는 type: model 노드 (YAML) |
SequentialAgent | next:를 사용한 YAML 노드 체인 또는 Python 선형 워크플로 |
ParallelAgent | type: parallel 노드 |
LoopAgent | 조건 노드를 사용한 순환 |
session.state (flat dict) | 검증 기능이 있는 타입화된 Pydantic State |
output_key | YAML의 output_key 또는 Python의 state 할당 |
FunctionTool / plain function | MCP 도구 서버 또는 @tool 데코레이터 |
ToolContext.state | 스텝 함수의 state 매개변수 |
ToolContext.actions.transfer_to_agent | 라우팅 기능이 있는 코디네이터 노드 |
Runner + SessionService | JamJetClient + Rust 런타임 |
InMemorySessionService | 불필요 — 런타임은 항상 지속성 보장 |
DatabaseSessionService | 기본 내장 — 이벤트가 기본적으로 저장됨 |
adk web | jamjet inspect CLI + Web Companion |
AgentEvaluator | 내장 평가 노드 (type: eval) + 스코어러 |
adk eval | --fail-under 옵션을 사용한 jamjet eval |
| 크래시 복구 없음 | 이벤트 소싱 기반 지속 가능한 실행 (기본) |
| 재실행 없음 | jamjet replay <execution_id> |
| human-in-the-loop 프리미티브 없음 | type: wait 노드 (지속 가능) |
to_a2a() | 런타임의 네이티브 A2A 지원 |
LiteLlm(model="...") | 직접 멀티모델 지원 (어댑터 레이어 불필요) |
나란히 비교
웹 검색을 수행하고 결과를 분석한 후 품질 점수가 매겨진 요약을 생성하는 연구 어시스턴트.
Google ADK
from google.adk.agents import LlmAgent, SequentialAgent
from google.adk.tools import FunctionTool
def web_search(query: str) -> dict:
"""Searches the web for information on the given query."""
# Implementation
return {"results": ["result 1", "result 2"]}
search_agent = LlmAgent(
name="searcher",
model="gemini-2.5-flash",
instruction="Search for: {query}",
tools=[web_search],
output_key="search_results",
)
analyst = LlmAgent(
name="analyst",
model="gemini-2.5-pro",
instruction=(
"Analyze these results: {search_results}. "
"Write a comprehensive summary with key findings."
),
output_key="summary",
)
pipeline = SequentialAgent(
name="research_assistant",
sub_agents=[search_agent, analyst],
)
# 파이프라인 실행하기
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
runner = Runner(
agent=pipeline,
app_name="research_app",
session_service=InMemorySessionService(),
)
session = runner.session_service.create_session(
app_name="research_app", user_id="user1"
)
events = runner.run(
user_id="user1",
session_id=session.id,
new_message=types.Content(
parts=[types.Part(text="내구성 있는 AI 워크플로 오케스트레이션")]
),
)
for event in events:
if event.is_final_response():
print(event.content.parts[0].text)JamJet (YAML)
id: research-assistant
version: "0.1.0"
nodes:
search:
type: tool
server: brave-search
tool: web_search
arguments:
query: "{{ state.query }}"
output_key: search_results
retry:
max_attempts: 3
backoff: exponential
next: analyze
analyze:
type: model
model: claude-sonnet-4-6
prompt: |
다음 검색 결과를 분석하세요: {{ state.search_results }}.
주요 발견사항을 포함한 포괄적인 요약을 작성하세요.
output_key: summary
next: evaluate
evaluate:
type: eval
scorers:
- type: llm_judge
model: claude-haiku-4-5-20251001
rubric: "요약이 정확하고 체계적이며 포괄적인가?"
fail_under: 4.0JamJet (Python)
from openai import OpenAI
from pydantic import BaseModel
from jamjet import Workflow
client = OpenAI()
class State(BaseModel):
query: str
search_results: str = ""
summary: str = ""
wf = Workflow("research-assistant")
@wf.state
class ResearchState(State):
pass
@wf.step
async def search(state: ResearchState) -> ResearchState:
# 프로덕션 환경: type: tool + MCP 서버가 이를 처리
results = f"[검색 결과: {state.query}]"
return state.model_copy(update={"search_results": results})
@wf.step
async def analyze(state: ResearchState) -> ResearchState:
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": (
"당신은 전문 리서치 분석가입니다. "
"주요 발견사항을 포함한 포괄적인 요약을 작성하세요."
)},
{"role": "user", "content": (
f"다음 결과를 분석하세요: {state.search_results}"
)},
],
)
return state.model_copy(update={"summary": resp.choices[0].message.content or ""})
result = wf.run_sync(ResearchState(query="내구성 있는 AI 워크플로 오케스트레이션"))
print(result.state.summary)주요 차이점
상태 관리
ADK는 평면적인 session.state 딕셔너리를 사용합니다. 언제든지 어떤 키든 읽거나 쓸 수 있으며, 스키마 검증이 없습니다. 인스트럭션 내 템플릿 보간({key})을 통해 상태를 쉽게 연결할 수 있지만, 키가 존재하거나 올바른 타입인지에 대한 컴파일 타임 보장은 없습니다.
JamJet은 Pydantic 모델을 사용합니다. 모든 필드는 타입, 기본값, 선택적 검증기를 가집니다. 스텝이 누락된 필드나 잘못된 타입을 가진 상태를 반환하면, 세 단계 후 조용한 오류가 아니라 해당 스텝에서 즉시 에러를 받습니다.
# ADK: 모든 것이 허용됨
session.state["results"] = 42 # 리스트여야 하는 거 아니었나?
session.state["resutls"] = [...] # 오타 — 오류 없음, 조용한 버그
# JamJet: 즉시 포착됨
class State(BaseModel):
results: list[str] = []
state.model_copy(update={"results": 42}) # ValidationError내구성
ADK의 InMemorySessionService는 프로세스 종료 시 모든 상태를 잃습니다. DatabaseSessionService는 요청 간 세션 상태를 유지하지만, 에이전트가 실행 중에 충돌하면 자동 복구가 없습니다. SequentialAgent가 5개 중 3번째 하위 에이전트에서 실패하면 처음부터 다시 시작해야 합니다.
JamJet의 Rust 런타임은 모든 노드 전환을 이벤트 소싱합니다. 5개 중 3번째 노드에서 충돌했나요? 런타임이 이벤트 로그를 리플레이하여 3번째 노드에서 재개합니다. 이것이 기본 동작이며 별도 설정이 필요 없습니다.
# 과거 실행 리플레이
jamjet replay exec-abc123
# 특정 단계에서 분기하여 다른 경로 시도
jamjet replay exec-abc123 --fork-at analyze도구
ADK 도구는 docstring이 있는 일반 Python 함수입니다. 함수 시그니처와 docstring이 도구 선택을 위해 모델에 전송됩니다. 프로토타이핑에는 편리하지만, 각 도구가 에이전트 코드베이스에 결합되어 있습니다.
JamJet은 오픈 표준인 MCP(Model Context Protocol)를 사용합니다. 도구는 모든 에이전트가 연결할 수 있는 독립적인 서버입니다. Brave Search MCP 서버는 JamJet, Claude Desktop 및 기타 모든 MCP 클라이언트와 작동합니다. 벤더 종속성도, 재구현도 없습니다.
# ADK: 도구가 에이전트 코드 내부에 존재
# def web_search(query: str) -> dict:
# """웹을 검색합니다."""
# JamJet: 도구는 독립적인 MCP 서버
nodes:
search:
type: tool
server: brave-search # 모든 MCP 호환 서버
tool: web_search
arguments:
query: "{{ state.query }}"
retry:
max_attempts: 3
backoff: exponential테스트 및 평가
ADK는 AgentEvaluator를 별도의 테스트 프레임워크로 제공합니다. 테스트 케이스를 작성하고 adk eval을 실행한 다음, 에이전트 실행 흐름 외부에서 결과를 검토합니다. 평가는 에이전트 자체와 분리된 관심사입니다.
JamJet은 평가를 일급 워크플로 노드로 만듭니다. eval 노드는 그래프의 일부로 실행되어 에이전트 파이프라인 내부에 품질 게이트를 제공합니다. CLI에서 CI용 통과/실패 임계값과 함께 배치 평가를 실행할 수도 있습니다:
jamjet eval run evals/research.jsonl \
--workflow research-assistant \
--fail-under 0.85멀티모델 지원
ADK는 Gemini를 중심으로 구축되었습니다. 다른 모델은 LiteLlm을 통해 사용할 수 있으며, 이는 자체 구성과 실패 모드를 가진 어댑터 레이어를 추가합니다. JamJet은 설계상 모델에 구애받지 않습니다. YAML 또는 Python 코드에서 직접 모델을 지정하면 됩니다. 어댑터 레이어도, 래퍼도, 추가 종속성도 필요 없습니다.
nodes:
fast-search:
type: model
model: claude-haiku-4-5-20251001 # Anthropic
# ...
deep-analysis:
type: model
model: gpt-4o # OpenAI
# ...
local-draft:
type: model
model: ollama/llama3 # Local
# ...빠른 시작 마이그레이션
pip install jamjet- 에이전트를 노드에 매핑합니다. 각
LlmAgent는type: model노드가 됩니다.SequentialAgent는next:로 연결된 노드 체인이 됩니다.ParallelAgent는type: parallel노드가 됩니다.LoopAgent는 조건이 있는 사이클이 됩니다. - 도구를 MCP 서버로 변환합니다. 일반 Python 도구는 MCP 도구 노드가 되거나, 빠른 마이그레이션을 위해
@tool데코레이터를 사용합니다. 기존 MCP 서버(Brave Search, GitHub, Postgres)는 즉시 작동합니다. session.state를 타입이 지정된 State로 교체합니다. 평면 딕셔너리 대신 명시적 필드가 있는 PydanticBaseModel을 정의합니다. 템플릿 보간({key})은 Jinja({{ state.key }})가 됩니다.- Runner 보일러플레이트를 제거합니다.
SessionService,session.create_session(), 이벤트 반복이 필요 없습니다. 로컬에서는wf.run_sync(State(...))로, 프로덕션에서는jamjet dev로 실행합니다. - 실행합니다.
jamjet dev는 내구성 있는 실행, 재생, 비용 추적, 이벤트 타임라인을 제공합니다. 모두 자동입니다.
팁: 실제 다단계 워크플로를 위해 claims-processing 예제로 시작하거나, 전체 문서를 탐색하세요.