JamJet
Migrate

Google ADK에서 마이그레이션하기

Google ADK 에이전트를 JamJet으로 마이그레이션하기 위한 개념 매핑 및 코드 비교 — 내구성 있는 실행, 재생 및 내장 평가 기능을 확보하세요.

개념 매핑

Google ADKJamJet
LlmAgent@workflow.step (Python) 또는 type: model 노드 (YAML)
SequentialAgentnext:를 사용한 YAML 노드 체인 또는 Python 선형 워크플로
ParallelAgenttype: parallel 노드
LoopAgent조건 노드를 사용한 순환
session.state (flat dict)검증 기능이 있는 타입화된 Pydantic State
output_keyYAML의 output_key 또는 Python의 state 할당
FunctionTool / plain functionMCP 도구 서버 또는 @tool 데코레이터
ToolContext.state스텝 함수의 state 매개변수
ToolContext.actions.transfer_to_agent라우팅 기능이 있는 코디네이터 노드
Runner + SessionServiceJamJetClient + Rust 런타임
InMemorySessionService불필요 — 런타임은 항상 지속성 보장
DatabaseSessionService기본 내장 — 이벤트가 기본적으로 저장됨
adk webjamjet 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.0

JamJet (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
  1. 에이전트를 노드에 매핑합니다.LlmAgenttype: model 노드가 됩니다. SequentialAgentnext:로 연결된 노드 체인이 됩니다. ParallelAgenttype: parallel 노드가 됩니다. LoopAgent는 조건이 있는 사이클이 됩니다.
  2. 도구를 MCP 서버로 변환합니다. 일반 Python 도구는 MCP 도구 노드가 되거나, 빠른 마이그레이션을 위해 @tool 데코레이터를 사용합니다. 기존 MCP 서버(Brave Search, GitHub, Postgres)는 즉시 작동합니다.
  3. session.state를 타입이 지정된 State로 교체합니다. 평면 딕셔너리 대신 명시적 필드가 있는 Pydantic BaseModel을 정의합니다. 템플릿 보간({key})은 Jinja({{ state.key }})가 됩니다.
  4. Runner 보일러플레이트를 제거합니다. SessionService, session.create_session(), 이벤트 반복이 필요 없습니다. 로컬에서는 wf.run_sync(State(...))로, 프로덕션에서는 jamjet dev로 실행합니다.
  5. 실행합니다. jamjet dev는 내구성 있는 실행, 재생, 비용 추적, 이벤트 타임라인을 제공합니다. 모두 자동입니다.

팁: 실제 다단계 워크플로를 위해 claims-processing 예제로 시작하거나, 전체 문서를 탐색하세요.

On this page