alibaba/page-agent는 이미 떠 있는 웹페이지 안에 직접 심는 자바스크립트 라이브러리입니다. agent.execute('로그인 버튼 눌러')처럼 자연어를 주면, 화면(DOM)을 스크린샷이 아니라 텍스트로 읽어 LLM에 보내고, LLM이 번호로 가리킨 요소를 실제로 클릭·입력합니다. 별도 브라우저도, 파이썬도, 확장 프로그램도 필요 없이 내 웹앱 안에 곧장 'AI 조수'를 띄우는 것이 목표입니다. (저장소: alibaba/page-agent · 언어 TypeScript · ★ 약 1.8만(빠르게 성장 중) · 라이선스 MIT · v1.10.0 · Alibaba Group)
"브라우저를 바깥에서 원격조종하는 게 아니다. 페이지 '안'에 들어가, 화면을 읽고 직접 손을 댄다."
보통의 웹 자동화 도구(Playwright, browser-use 등)는 '바깥'에 따로 띄운 브라우저를 원격조종합니다. 화면을 사진(스크린샷)으로 찍어 멀티모달 모델에 보내고, 별도의 서버·파이썬·로그인이 필요하죠. page-agent는 정반대입니다. npm install로 내 앱에 그냥 포함되는 라이브러리라, 사용자가 이미 로그인해 둔 바로 그 페이지 안에서 돕니다. 화면은 사진이 아니라 '번호 붙은 텍스트 목록'으로 바꿔 LLM에 건네고, LLM이 "3번을 눌러"라고 답하면 3번 요소를 진짜로 클릭합니다.
[3]<button>로그인</button>처럼 번호 + 태그 + 핵심 글자만 남깁니다. 사진을 안 쓰니 멀티모달 모델·특수 권한이 필요 없고, 토큰도 훨씬 적게 듭니다.좀 더 구체적으로, page-agent는 TypeScript로 만든 프런트엔드 라이브러리이며 React·Vue·순수 HTML 어디에도 붙습니다. 핵심 사용법은 단 두 줄 — new PageAgent({...})로 만들고 await agent.execute('할 일')로 시킵니다. 그러면 내부에서 '관찰 → 생각 → 행동'을 한 발씩 반복하며 일을 끝까지 처리합니다. 이 문서가 파고드는 건 사용법이 아니라 그 안의 설계입니다 — "화면을 어떻게 글로 바꾸나", "LLM이 헛소리를 해도 어떻게 버티나", "왜 셀렉터(선택자) 대신 '번호'로만 요소를 가리키나".
"웹 자동화는 많다. 하지만 대부분 '바깥 브라우저'를 몬다. page-agent는 '페이지 안'으로 들어온 게 차별점이다."
웹을 자동으로 조작하는 도구는 이미 많습니다. Playwright·Puppeteer 같은 테스트 자동화 도구, browser-use 같은 에이전트형 브라우저 조종 라이브러리, OpenAI Operator 같은 클라우드 브라우징 에이전트. 그런데 이들은 공통적으로 '사용자 화면과는 별개인 브라우저'를 따로 띄워 원격조종합니다. 그래서 서버·파이썬 환경이 필요하고, 사용자의 로그인 세션을 다시 만들어야 하며, 화면 인식을 위해 스크린샷을 멀티모달 모델에 보내느라 느리고 비쌉니다.
| 관점 | page-agent (페이지 안) | 기존 외부 자동화 (browser-use·Playwright·Operator) |
|---|---|---|
| 실행 위치 | 내 웹앱 안(같은 JS 컨텍스트) | 바깥에 띄운 별도 브라우저 |
| 설치 | npm install 또는 <script> 한 줄 | 서버·파이썬·헤드리스 브라우저·확장 |
| 화면 인식 | 텍스트(DOM 직렬화) — 사진 안 씀 | 주로 스크린샷 + 멀티모달 모델 |
| 로그인 | 사용자가 이미 로그인한 세션 그대로 | 세션·쿠키를 따로 마련 |
| 모델 | 아무 OpenAI 호환 LLM(로컬 Ollama 포함) | 특정 멀티모달 모델에 묶이기 쉬움 |
외부 자동화 = 옆방에서 CCTV로 내 컴퓨터를 보며 원격조종하는 기사. 화면을 사진으로 계속 받아 봐야 하고, 내 컴퓨터에 따로 로그인해야 하며, 방을 하나 더 빌려야 합니다.
page-agent = 내 책상에 같이 앉은 비서. 내가 보던 화면을 그대로 보고(이미 로그인됨), 화면을 통째로 사진 찍는 대신 "지금 화면엔 1.검색창 2.로그인버튼 3.회원가입…이 있다"는 목록을 손에 들고, 시키는 대로 그 자리에서 누릅니다.
화면을 이미지로 찍어 멀티모달 모델에 보내는 방식은 직관적이지만, 토큰이 많이 들고 느리며, 화면 전체가 외부 모델로 전송돼 민감 정보 노출 우려가 큽니다. 또 픽셀 좌표로 클릭하므로 화면 크기·해상도가 바뀌면 흔들립니다.
page-agent는 화면을 번호 붙은 텍스트 목록으로 바꿔 보냅니다. LLM은 좌표가 아니라 "3번을 눌러"라고만 답하고, 라이브러리가 내부의 selectorMap(번호→실제 요소 표)을 통해 진짜 요소를 찾아 누릅니다. 모델은 CSS 셀렉터·XPath를 전혀 보지 않습니다. 게다가 보내기 전에 transformPageContent 훅으로 전화번호 같은 민감 텍스트를 가릴 수 있습니다.
한 가지 정직하게 짚을 점 — page-agent의 DOM 처리 로직과 프롬프트는 browser-use에서 가져와(port) 개조한 것입니다(코드 주석과 README가 명시). 차별점은 알고리즘 자체가 아니라 "그걸 서버가 아니라 페이지 안에서 돌린다"는 배치 전략, 그리고 그 위에 올린 안전장치(시각 마스크·프레임워크 패치·자동 복구)입니다.
"화면도 DB도 없는 순수 브라우저 라이브러리. 무게중심은 'DOM을 텍스트로 바꾸는 코어'와 'zod로 잠근 도구 스키마'에 있다."
page-agent는 npm 워크스페이스 모노레포(여러 패키지를 한 저장소에 둠)로 짜인 TypeScript 프로젝트입니다. 빌드는 Vite, 검증은 zod가 맡고, 외부 런타임 의존성은 거의 없습니다. 패키지별로 역할이 뚜렷하게 갈립니다.
| 기술 | 역할 | 한 줄 설명 |
|---|---|---|
| TypeScript ^6 (ESM, target es2025) | 전체 언어 | strict 타입으로 코어를 빡빡하게 잠근다. Node ^22 또는 ≥24 필요. |
| Vite ^8 | 빌드·번들 | 각 패키지를 vite build로 묶는다. CSS를 JS에 주입해(css-injected-by-js) 스크립트 한 줄로 스타일까지 따라온다. |
| zod ^3/^4 (peer) | 도구 스키마·검증 | 이 프로젝트의 척추. 모든 '도구(tool)'의 입력을 zod 스키마로 정의하고, LLM이 뱉은 JSON이 그 모양인지 런타임에 검사한다. |
| Vitest + happy-dom | 테스트 | 브라우저 없이 DOM을 흉내 내 단위 테스트. |
core(에이전트 두뇌)·llms(LLM 통신)·page-controller(DOM 조작)·ui(패널)·page-agent(사용자용 묶음 진입점)·extension·mcp로 나뉩니다. 서로를 버전으로 참조하되 한 곳에서 같이 빌드하므로, '역할 분리'와 '한 번에 관리'를 동시에 얻습니다.| 기술 | 역할 |
|---|---|
| OpenAI 호환 클라이언트 (자체 구현) | ${baseURL}/chat/completions로 요청을 보낸다. 제공사 SDK를 끼워 넣지 않고 설정(baseURL·model·apiKey)만 바꾸면 Qwen·OpenAI·Claude·Gemini·DeepSeek·로컬 Ollama 어디든 붙는다. |
| ai-motion ^0.4 | page-controller의 유일한 런타임 의존성. 자동화 중 화면에 뜨는 커서·마스크 애니메이션을 그린다. |
| @modelcontextprotocol/sdk | mcp 패키지용. 외부 에이전트(Claude Desktop·Cursor)가 내 실제 브라우저를 조종하게 하는 (베타) MCP 서버. |
주목할 설계 하나 — LLM 온도(temperature)를 일부러 0.7로 높게 둡니다. 코드 주석에 "높은 무작위성이 자동 복구를 돕는다"고 적혀 있습니다. 형식을 어긴 응답이 와도 다시 시도하면 다른 답이 나와 빠져나올 확률이 커지기 때문입니다.
| 형태 | 내용 |
|---|---|
| npm 패키지 | npm install page-agent. @page-agent/core·llms·page-controller·mcp도 따로 받을 수 있다. |
| CDN 스크립트(IIFE) | jsDelivr에서 <script> 한 줄로 즉시 체험. window.PageAgent가 생기고, 데모 에이전트가 자동 초기화된다(?autoInit=false로 끌 수 있음). |
"핵심 발상 하나 — 에이전트는 한 발씩만 움직이되, '움직이기 전에 반드시 스스로를 점검'하게 강제한다."
가장 대표적인 한 동작을 입력부터 끝까지 따라가 봅니다. 한 번의 execute()는 아래 한 스텝(step)을 'done'이 나올 때까지(기본 최대 40번) 반복합니다.
가장 핵심적인 발상입니다. 보통은 LLM이 여러 도구 중 하나를 자유롭게 고릅니다. page-agent는 모든 도구를 하나의 스키마(AgentOutput)로 합치고, tool_choice를 그것 하나로 못 박습니다. 그 입력 모양이 "이전 목표 평가 + 기억 + 다음 목표 + 행동"이라, 모델은 행동을 고르기 전에 반드시 자기 점검을 먼저 적어야 합니다. 설계 철학(AGENTS.md)은 "성공률보다 추적 가능성·예측 가능성이 더 중요하다"입니다.
// 모든 도구를 union으로 합쳐 하나의 'AgentOutput' 스키마로
const macroToolSchema = z.object({
evaluation_previous_goal: z.string().optional(), // 직전 행동이 잘됐나 자기평가
memory: z.string().optional(), // 지금까지 기억
next_goal: z.string().optional(), // 다음 한 수의 목표
action: z.union(actionSchemas), // 그제서야 실제 행동
})
LLM은 CSS 셀렉터·XPath를 절대 못 봅니다. 오직 정수 인덱스만 다루고, page-controller가 비공개 selectorMap으로 그 번호를 실제 요소로 되돌립니다. 이게 안전·추상화의 경계선입니다 — 모델이 페이지 내부 구조를 직접 만질 수 없습니다.
execute()가 바로 이 루프입니다.실제 LLM은 형식을 자주 어깁니다. page-agent는 withRetry(2회 재시도)에 더해, autoFixer가 흔한 6가지 잘못된 응답 모양(행동 이름을 도구로 반환, 이중 문자열화된 인자, 함수호출 중첩 형식, 행동 누락 시 wait로 대체 등)을 알아서 바로잡습니다. 방어적 LLM 통합의 좋은 교본입니다.
자동화가 도는 동안 SimulatorMask가 화면을 덮어 애니메이션 커서를 그리고, 사용자의 진짜 클릭·키 입력을 삼켜 에이전트와 사람이 동시에 화면을 건드리는 사고를 막습니다(DOM을 읽는 순간만 잠깐 pointer-events:none).
"패키지 이름만 봐도 '두뇌 / 입 / 손 / 얼굴'이 나뉜 게 보인다."
| 파일/폴더 | 역할 |
|---|---|
| PageAgentCore.ts | 에이전트의 심장. 한 스텝(관찰→프롬프트→LLM→행동→기록)을 도는 루프와 MacroTool 패킹. |
| dom/index.ts | flatTreeToString — 살아있는 DOM을 번호 붙은 텍스트로 '탈수'. 학습 핵심. |
| actions.ts | 합성 입력의 디테일. 사람처럼 보이도록 이벤트를 순서대로 발사한다. |
| OpenAIClient.ts | 제공사 SDK 없이 OpenAI 호환 엔드포인트 하나로 모든 모델 지원. |
"이 레포는 'LLM에게 화면을 어떻게 보여줄까'와 '브라우저에서 진짜처럼 클릭하기'의 살아있는 교본이다."
flatTreeToString은 DOM을 [번호]<태그 속성>글자 /> 줄로 압축합니다. 들여쓰기(\t)로 부모-자식을 표현하고, 새로 나타난 요소엔 *를 붙이며, 의미 있는 속성만 추려 담습니다. 멀티모달 모델 없이 화면을 이해시키는 '접근성 트리(accessibility tree)'식 인코딩의 깔끔한 예시입니다.
// LLM이 보는 화면 — 좌표도 셀렉터도 없이 번호와 글자뿐
[0]<a aria-label=홈>홈 />
[3]<button>로그인 />
*[5]<input placeholder=아이디 /> // * = 직전 대비 새로 등장
그냥 element.click()만으로는 React·Quill·Slate 같은 프레임워크가 반응하지 않는 경우가 많습니다. actions.ts는 사람이 누를 때 브라우저가 실제로 발사하는 이벤트 순서를 그대로 재현합니다 — 입력창 처리(contenteditable)에선 합성 InputEvent를 먼저 시도하고 안 되면 execCommand로 넘어가는 2단 폴백까지. 브라우저 이벤트의 세계를 배우는 데 최고의 사례입니다.
// pointerdown → mousedown → focus → pointerup → mouseup → click 순서
target.dispatchEvent(new PointerEvent('pointerdown', opts))
target.dispatchEvent(new MouseEvent('mousedown', opts))
element.focus({ preventScroll: true })
target.click() // 활성화 동작이 상위 요소로 전파
도구를 자유 선택하게 두지 않고, 하나의 스키마에 자기평가·기억·다음목표·행동을 묶어 매 발 반성을 강제하는 패턴. 에이전트가 왜 그렇게 행동했는지 로그만 봐도 읽히게 만드는, 재사용성 높은 ReAct 설계입니다. zod union으로 도구들을 합치는 코드가 그대로 본받을 만합니다.
OpenAIClient의 촘촘한 에러 분류(인증·레이트리밋·컨텍스트 초과 등)와 autoFixer의 응답 정규화는 실전 LLM이 형식을 어기는 현실에 대비하는 법을 보여줍니다. 또 하나의 AbortController 신호가 LLM 요청·도구·콜백까지 닿아, 한 번에 안전하게 멈추는 '협조적 취소'도 좋은 학습거리입니다.
"서버도 GPU도 필요 없다. 모던 브라우저와 'OpenAI 호환' API 키 하나면 끝."
| 항목 | 요구사항 |
|---|---|
| 실행 환경 | 최신 에버그린 브라우저(Chrome·Edge·Firefox·Safari 등). 헤드리스 브라우저·파이썬·확장 프로그램 불필요. |
| 개발(라이브러리 빌드) | Node ^22.13 또는 ≥24, npm ≥ 11. macOS·Linux·WSL. |
| LLM | 아무 OpenAI 호환 엔드포인트. Qwen(DashScope)·OpenAI·Claude·Gemini·DeepSeek·GLM·Kimi, 그리고 로컬 Ollama(예: RTX 3090 24GB에서 qwen3:14b 검증). |
| 핵심 API | AbortController·WeakMap·elementFromPoint·InputEvent 등 표준 웹 API(모던 브라우저면 기본 제공). |
"스크립트 한 줄 체험 → 커스텀 도구 → 코어 읽기 순으로 난이도를 올려 보세요."
아무 웹페이지 콘솔이나 빈 HTML에 <script> 한 줄을 넣고 window.PageAgent로 데모 에이전트를 띄워, "검색창에 고양이 입력하고 검색"처럼 시켜 봅니다. baseURL·model·apiKey를 쿼리스트링으로 바꿔 가며 동작을 관찰하세요.
Ollama를 깔고 qwen3 계열 모델을 받아, baseURL을 로컬 Ollama 주소로 지정합니다. 데이터가 외부로 나가지 않는 완전 로컬 GUI 에이전트를 만들어 보세요. 작은 모델이 어디서 실수하는지 관찰하면 프롬프트 설계 감이 생깁니다.
customTools로 나만의 도구(my_tool)를 zod 스키마와 함께 추가하고, transformPageContent로 전화번호·이메일을 ***로 가린 뒤 LLM에 넘겨 봅니다. 에이전트의 행동 범위와 프라이버시를 동시에 통제하는 법을 익힙니다.
flatTreeToString 읽고 직접 흉내 내기page-controller/src/dom/index.ts를 읽고, 임의의 페이지에서 DOM을 번호 붙은 텍스트로 직렬화하는 미니 함수를 직접 써 봅니다. '어떤 속성을 담고 무엇을 버릴지', '보이는 요소만 어떻게 추릴지'를 고민하면 LLM 컨텍스트 설계의 본질이 보입니다.
"브라우저 DOM·이벤트 → LLM 도구 호출 → 에이전트 루프 순으로 쌓으면 가장 단단하다."
| 주차 | 주제 | 학습 자료 / 키워드 |
|---|---|---|
| 1주차 | DOM·접근성 트리 기초 | MDN DOM, elementFromPoint, ARIA, 접근성 트리 개념 |
| 2주차 | 브라우저 이벤트 모델 | Pointer/Mouse/Input 이벤트 순서, contenteditable, React 합성 이벤트 |
| 3주차 | LLM 도구 호출 + zod | OpenAI tool calling, tool_choice, zod 스키마/검증 |
| 4주차 | 에이전트 루프(ReAct) | 관찰→생각→행동 반복, 재시도·취소·자동 교정 |
| 5주차 | 비교 학습: browser-use | 외부 브라우저 조종 방식과 page-agent의 '페이지 안' 방식 비교 |
| 6주차 | MCP로 확장 | Model Context Protocol, 외부 에이전트가 도구를 부르는 구조 |
"문서에 나온 용어를 한자리에."
| 용어 | 의미 |
|---|---|
| GUI 에이전트 | 사람이 화면을 보고 누르듯, AI가 그래픽 화면을 이해하고 직접 조작하는 프로그램. |
| DOM 직렬화(탈수) | 살아있는 DOM을 LLM이 읽을 번호 붙은 텍스트로 압축하는 일. |
| selectorMap | '번호 → 실제 요소' 대응표. 모델은 번호만, 라이브러리는 실제 요소를 다룬다. |
| MacroTool | 모든 도구를 하나로 합쳐, 행동 전에 자기평가·기억·목표를 먼저 적게 강제하는 스키마. |
| ReAct 루프 | '생각→행동→결과 보고 다시 생각'을 반복하는 에이전트 패턴. |
| OpenAI 호환 | OpenAI의 chat/completions 형식을 따르는 API. 이 형식만 맞으면 어떤 모델이든 연결. |
| IIFE | '즉시 실행 함수'. <script>로 불러오면 바로 실행돼 window.PageAgent를 만든다. |
| MCP | Model Context Protocol. 외부 AI 클라이언트가 표준 방식으로 도구를 호출하게 하는 규약. |