Udemy를 대체할 수 있는 AI 에이전트 구축 가능성
요약
본 글은 기존의 비디오 강의 플랫폼(Udemy 등)을 대체할 수 있는 새로운 학습 방식을 제안합니다. 이는 시스템에 제공된 문서를 기반으로 '직접 해보며 배우는 커리큘럼'을 생성하는 에이전트적 접근 방식입니다. AWS Strands Agents와 Google ADK를 비교하며, 에이전트의 정의, 도구 연결, 상태 관리 등 핵심 기술 질문들을 다루고 있습니다.
핵심 포인트
- 문서 기반 학습을 '직접 해보는 커리큘럼'으로 변환하는 것이 목표입니다.
- AWS Strands Agents와 Google ADK를 비교하며 에이전트 프레임워크의 구조적 측면을 분석합니다.
- 에이전트 시스템 설계 시, 상태 관리 및 관찰 가능성(observability) 확보가 중요합니다.
- 실제 개발 환경에서는 패키지 버전 충돌 방지를 위해 버전을 고정하는 것이 권장됩니다.
하하, 그럴 리가요! 하지만 제 관심사를 불러일으켰다니 기쁩니다.
물론 저 자신을 위해 이를 대체하는 것을 만들고 싶긴 합니다.
오해하지 마세요. 저는 Udemy를 좋아합니다. Udemy에서 역량을 쌓았고, 덕분에 여러 자격증을 취득할 수 있었습니다. Stéphane Maarek과 함께 첫 자격증을 준비했던 것이 아직도 기억납니다.
하지만 지금은 삶의 단계가 달라져서, 긴 형식의 비디오 강의는 더 이상 저에게 맞지 않습니다. 짧고 핵심적인 내용일 때는 여전히 사용합니다. 어쩌면 이게 ADHD 때문일지도 모르겠네요. 공감하는 분 손들어 보세요 🙋♀️
그래서 저는 대신 무엇을 원할지에 대해 생각하기 시작했습니다. 시스템에 문서(documentation)를 제공하고, 그것을 '직접 해보며 배우는 커리큘럼 (learn by doing curriculum)'으로 변환하는 것을 원합니다.
이것이 시중에 나와 있는 수많은 다른 강의 생성기들과 무엇이 다를까요?
음, 당연히... 바로 ✨ 에이전트적(agentic)✨입니다.
그리고 AWS Strands Agents와 Google
- 에이전트를 어떻게 정의하나요?
- 에이전트는 모델과 어떻게 상호작용하나요?
- 도구는 어떻게 정의되고 연결되나요?
- 상태(state)는 어디에 존재하나요?
- 실행 중에 어떤 제어 지점(control points)을 가질 수 있나요?
- 여러 에이전트가 어떻게 함께 작동하나요?
- 프레임워크는 평가 및 관찰 가능성(observability)을 위해 무엇을 제공하나요?
- 그리고 마지막으로, 제가 구축한 것을 어떻게 배포할 수 있나요?
저희는 이 질문들을 비교 전반에 걸쳐 사용할 것입니다.
그리고 저는 컴퓨터 과학 학위가 있고, 분명히 다른 방법으로는 기술 프로젝트를 시작할 수 없기 때문에...
참고: 이 시리즈의 예제들은 다음 환경에서 작성 및 테스트되었습니다:
- AWS Strands Agents:
1.55.1- Google ADK:
2.9.0- Python:
3.13이 두 가지를 하나의 환경에 모두 설치할 경우, 패키지 버전 충돌이 발생할 수 있습니다. 따라서 버전을 고정(Pin)하는 것이 좋습니다. 만약 고정하지 않으면(
pip install strands-agents google-adk), ADK가 Strands보다 OpenTelemetry의 버전을 낮게 제한하기 때문에google-adk 1.14.1로 돌아갈 수 있습니다. 이는 pip가 두 가지를 모두 만족시키기 위해 ADK를 거꾸로(backtracks) 조정하기 때문입니다. 따라서 OpenTelemetry 쌍도 함께 고정하면, 나중에 어느 프레임워크가 이동하더라도 이 문제가 발생하는 것을 방지할 수 있습니다:strands-agents==1.55.1 google-adk==2.9.0 opentelemetry-api==1.42.1
...
두 프레임워크 모두 활발하게 개발되고 있습니다. 이 글을 작성할 무렵에는 `strands-agents 1.58.0`과 `google-adk 2.11.0`이 출시되었으며, API는 위 버전 이후에 변경될 수 있습니다. 여기에 있는 모든 내용은 2026년 9월 27일 설치된 패키지를 기준으로 확인했으며, 본 게시물의 코드는 이를 조합하여 실행되었습니다.
아래의 모든 예제들은 보조 레포지토리 **github.com/coolchigi/strands-vs-adk**에 있습니다. 코드는 part1-hello-world/ 폴더 안에 있으며, 각 섹션별로 번호가 매겨진 폴더(01-hello-world부터 08-deployment까지)가 있고, 두 프레임워크의 코드가 나란히 배치되어 있습니다. 이 레포지토리는 smoke_test.py 파일을 포함하고 있는데, 이 파일은 모델을 호출하지 않고도 모든 파일을 구문 분석(parse)하고, 모든 임포트를 해결하며, 구축 가능한 모든 에이전트를 빌드하여 설정이 제대로 작동하는지 토큰을 소모하기 전에 확인할 수 있게 해줍니다.
Hello World
두 프레임워크가 어떻게 구현했는지 보여드리겠습니다:
Hello World
두 프레임워크가 어떻게 구현했는지 보여드리겠습니다:
| Strands | ADK |
|---|---|
pip install strands-agents | pip install google-adk |
| ... |
Strands
가장 작은 Strands 에이전트는 이것이며, python hello-world.py로 실행할 수 있습니다:
# hello-world.py
from strands import Agent
...
Strands는 여러 모델 제공업체와 함께 작동합니다. 기본적으로 Amazon Bedrock의 Claude Sonnet 4.6을 사용하므로, 이를 실행하려면 AWS 자격 증명과 Bedrock에서 해당 모델에 대한 접근 권한이 필요합니다. Strands Python 빠른 시작에서는 설정 방법을 안내하고 있습니다. 다른 모델을 사용하려면 Agent에 model="..."로 ID를 전달하세요.
Agent는 Strands에서 작업하는 주요 요소입니다. 이를 호출하면 메시지를 모델로 전송하고, 도구를 추가하면 해당 도구도 실행하여 그 결과를 다시 모델에 전달합니다.
ADK
ADK는 폴더를 예상합니다.
course_agent/
├── __init__.py # from .
우리의 학습 시스템은 궁극적으로 문서를 검색하고, 연습 문제를 생성하며, 우리가 무엇을 다루었는지 추적하는 등의 작업을 수행해야 할 것입니다. 지금은 훨씬 더 작은 것부터 시작해 봅시다.
에이전트에게 강좌 주제를 반환하는 함수를 제공할 것입니다:
def get_course_topic() -> str:
"""Get the topic for the course."""
return "Building AI agents"
각 프레임워크는 이 함수를 에이전트가 사용할 수 있는 도구로 변환하는 고유한 방법을 가지고 있습니다.
두 가지 방법은 다음과 같습니다:
| Strands | ADK |
|---|---|
함수에 @tool 사용 | 함수에 아무것도 사용하지 않음 |
| ... |
Strands
Strands에서는 @tool 데코레이터를 사용하여 간단한 Python 함수를 에이전트의 도구로 변환합니다. 이 데코레이터는 함수의 이름, docstring, 타입 힌트 및 기본값을 사용하여 도구를 설명합니다. 이름과 docstring은 모델에게 도구가 무엇을 하는지 알려주고, 타입 힌트는 어떤 값을 전달해야 하는지 알려줍니다. 그런 다음 이를 tools에 에이전트에 전달합니다:
# hello-world.py
from strands import Agent, tool
...
ADK
ADK에서는 데코레이터가 없습니다. 평범한 함수를 tools에 전달하고, ADK는 동일한 것들(이름, docstring, 타입 힌트 및 기본값)을 읽은 다음, 자체적으로 FunctionTool로 감싸줍니다:
# course_agent/agent.py
from google.adk.agents import Agent
...
이것은 이전에 했던 방식과 동일하게, 02-tools 폴더에서 이번에는 질문을 사용하여 실행합니다:
adk run course_agent "What topic should I teach?"
두 가지를 나란히 놓고 비교하기
양쪽 모두 동일한 평범한 Python 함수를 사용합니다. 유일한 차이점은 Strands가 @tool을 요구하는 반면, ADK는 있는 그대로 받아들인다는 것입니다.
우리는 에이전트에게 이 함수를 제공했지만, 호출하라고 말한 적은 없습니다. 그래서
| Strands | ADK |
|---|---|
agent("What topic should I teach?") |
Agent → Model → Tool → Model → Response | runner.run_async(...)
Runner → Agent → Model → Tool → Event → Runner → ... |
| Agent가 루프를 자체적으로 실행합니다. | 별도의 Runner가 루프를 실행하고 각 단계를 이벤트로 코드로 전달합니다. |
Strands
저희는 에이전트를 직접 호출합니다. 이는 hello-world.py의 마지막 줄에 있습니다:
# hello-world.py
agent("What topic should I teach?")
여기서부터 Agent가 루프를 자체적으로 실행합니다. 모델과 사용자의 도구(tool) 사이에서 왔다 갔다 하며, 모델이 최종 답변을 제공할 때까지 진행됩니다.
Strands 에이전트 루프 문서에서는 이를 반복적인 주기(cycle)로 설명합니다:
사용자 입력
│
▼
...
모델은 도구가 필요한지 여부를 결정합니다. 필요하다면, Strands가 해당 도구를 실행하고 그 결과를 대화에 추가한 후 모델을 다시 호출합니다. 만약 모델이 최종 응답을 생성한다면, 루프는 종료됩니다.
Strands는 또한 이 루프 주변에서 제어 기능을 제공합니다. 우리는 턴(turns) 및 토큰(tokens) 제한 설정, 호출 취소(cancel an invocation), 그리고 에이전트 수명 주기(lifecycle)의 여러 지점에 훅(hooks)을 연결할 수 있습니다.
ADK
ADK에서는 저희 코드 중 어느 것도 root_agent를 호출하지 않습니다.
Hello World 예제에서 우리는 adk run course_agent라고 입력했고, CLI가 이를 대신 호출했습니다. 이는 Runner를 구축하고, 세션을 생성하며, 우리가 입력한 내용을 types.Content로 감싸서 runner.run_async()에 전달했습니다.
따라서 루프를 확인하려면 이 네 단계를 직접 작성해야 합니다.
도구(tools) 섹션의 agent.py는 변경되지 않습니다. 새로운 코드는 course_agent 폴더 옆의 main.py에 들어가서 에이전트를 가져옵니다:
course_agent/
├── init.py
├── agent.py # root_agent는 여기에 있으며 변경되지 않습니다.
...
먼저 대화 내용을 추적하는 세션 서비스(session service)와 에이전트 루프를 실행하는 Runner가 필요합니다:
# main.py
import asyncio
...
세션을 생성하고 에이전트를 실행하는 과정 모두 비동기적으로 기다려야(await) 하며, await는 비동기 함수 내부에서만 작동하므로 나머지 코드는 하나에 넣어야 합니다:
# main.py 계속
async def main():
session = await session_service.create_session(...)
우리는 run_async가 session_id를 필요로 하기 때문에 먼저 세션을 생성합니다. Runner는 auto_create_session=True로 빌드하지 않는 한 자동으로 세션을 만들어주지 않습니다.
다음은 메시지 자체입니다. ADK는 누가 보냈는지 역할(role)을 가진 하나의 메시지와 그 내용이 담긴 types.Part 목록인 types.Content를 예상합니다. 우리의 메시지는 사용자로부터 오며, 질문이 담긴 하나의 파트로 구성됩니다:
# main.py, 여전히 main() 내부
message = types.Content(
role="user",...
이제 루프 자체입니다. 마지막 줄인 asyncio.run(main())이 모든 것을 시작합니다:
# main.py, 파일의 나머지 부분
async for event in runner.run_async(
user_id="user",...
실행하기 전에 같은 터미널에서 키를 내보내야 합니다. python main.py는 .env 파일을 읽지 않습니다. 에이전트를 로드하는 adk run이나 adk web과 같은 ADK 명령어만 그렇게 합니다. main.py와 course_agent 모두가 있는 폴더인 03-agent-loop에서 실행하세요:
cd part1-hello-world/03-agent-loop
export GOOGLE_API_KEY="="...
python main.py
Windows PowerShell에서는 $env:GOOGLE_API_KEY="="...로 키를 설정합니다.
main.py에서 중요한 부분은 async for입니다.
run_async()는 에이전트가 실행되는 동안 이벤트 스트림을 우리에게 전달합니다. ADK event loop documentation에 따르면, Runner는 에이전트를 시작하고, 에이전트가 이벤트를 생성할 때마다 Runner는 그것을 저장하고 에이전트가 계속 진행하기 전에 전달합니다.
간단히 말해, 이벤트 루프는 다음과 같습니다:
Application
│
▼
...
Event는 사용자 입력, 에이전트 응답, 도구 호출 또는 결과, 또는 상태 변경과 같은 것을 나타낼 수 있습니다. Runner는 이러한 이벤트를 구성된 SessionService로 전달하며, SessionService는 이벤트가 애플리케이션으로 다시 전달되기 전에 상태 변경을 적용하고 해당 이벤트를 세션 기록에 추가할 수 있습니다. 전체 흐름은 ADK 이벤트 루프 문서를 참조하십시오.
루프의 else 구문은 유지할 가치가 있습니다. is_final_response()는 오류 이벤트뿐만 아니라 답변에 대해서도 True를 반환하며, 오류 이벤트에는 content가 없습니다. else 구문이 없다면, 잘못된 API 키로 인해 실행이 AttributeError: 'NoneType' object has no attribute 'parts'로 종료되는데, 이는 키에 대한 아무 정보도 제공하지 않습니다. 하지만 else를 사용하면, Google 자체의 오류인 no content: 400 INVALID_ARGUMENT ... API key not valid와 함께 실행이 종료됩니다.
두 가지를 나란히 놓고 비교하기
따라서 이미 첫 번째 중요한 차이점을 갖게 되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기