브라우저에서 완전히 실행되는 LLM 에이전트
요약
LiquidAI의 LFM2.5 소형 모델을 활용하여 서버나 API 없이 브라우저에서 로컬로 실행되는 프론트엔드 에이전트를 구현했습니다. 도메인 지식 대신 상호작용 패턴을 학습하여, 동일한 가중치로 다양한 상점 환경에 유연하게 대응할 수 있습니다.
핵심 포인트
- WebGPU 및 WASM을 활용한 브라우저 내 완전 로컬 실행
- 네트워크 지연 및 토큰 비용, 데이터 프라이버시 문제 해결
- 도메인 지식이 아닌 도구 호출 및 상호작용 패턴 학습
- 런타임 컨텍스트 주입을 통한 높은 일반화 성능 확보
- 고정된 도구 목록(Frozen tool roster)을 통한 안정적인 호출
요약 (TL;DR): 저는 LiquidAI의 LFM2.5 (230M 및 350M)를 서버, API 키, 클라우드 비용 없이 브라우저에서 완전히 실행되는 범용 프론트엔드 에이전트로 파인튜닝 (Fine-tuned)했습니다.
단순히 채팅만 하는 것이 아니라, 카탈로그를 탐색하기 위해 실제 도구 (Tools)를 호출하고, 근거 있는 질문에 답하며, 장바구니를 관리합니다.
핵심 비결은 도메인 지식이 아닌 상호작용 *패턴 (Patterns)*을 학습했다는 점입니다. 따라서 동일한 가중치 (Weights)로 재학습 없이도 커피 전문점, 부조리한 상점, 혹은 동네 식료품점을 운영할 수 있습니다.
라이브 데모 (Live demo)는 Github pages에서 확인할 수 있습니다.
왜인가요?
대부분의 "AI 어시스턴트" 기능은 누군가의 데이터 센터에 있는 프론티어 모델 (Frontier model)에 연결된 텍스트 박스 형태입니다.
그것도 괜찮지만, 이는 매 턴마다 네트워크 왕복 (Network round-trip)이 발생하고, 토큰 (Token)당 비용이 청구되며, 사용자의 입력 데이터가 기기를 떠나야 함을 의미합니다.
저는 그 반대를 원했습니다. 페이지와 함께 배포될 수 있을 만큼 작은 에이전트 말입니다.
한 번 로드하면 사용자의 자체 하드웨어에서 실행하고 (WebGPU가 사용 가능하면 WebGPU, 그렇지 않으면 wllama를 통한 CPU/WASM), UI에서 단순히 설명하는 대신 실제로 무언가를 수행하게 만드는 것입니다.
작은 모델은 유용한 양의 세상 지식을 담을 수는 없지만, 유용할 정도로 압축된 일련의 행동 양식을 충분히 잘 학습할 수 있다는 것에 베팅했습니다.
지식이 아닌 패턴
이 모델은 "BrewCraft Pico"가 무엇인지, 혹은 그것이 699달러인지 알지 못합니다.
대신 다음과 같은 방법을 알고 있습니다:
- 의도 (Intent)에 맞는 적절한 도구 선택
- 인자 (Arguments) 및 아이템 ID를 정확하게 바인딩 (Bind)
- 참조 해결 ("두 번째 것", "그것들 한 다스" 등)
- 검색된 텍스트에 근거하여 답변을 생성하고, 텍스트에 답이 없는 경우 답변을 거부 (Refuse)
- 주제에서 벗어난 질문을 받았을 때 정중하게 다시 유도
도메인 특화된 모든 정보는 런타임 (Runtime)에 주입됩니다.
매 턴마다 호스트 앱은 모델에 압축된 컨텍스트 (Context)를 전달합니다: 현재 화면에 있는 아이템 (ID 및 가격 포함), 장바구니, 그리고 검색된 모든 지식입니다.
모델은 엄격하게 그 정보에만 근거합니다. 상점을 바꾸고 주입되는 컨텍스트를 바꾸면, 동일한 가중치가 작동합니다.
그렇기 때문에 데모에서는 하나의 모델로 세 개의 상점을 제공합니다.
그리고 결정적으로, 이 상점들은 학습 과정에서 완전히 제외되었습니다.
모델이 한 번도 본 적 없는 상점을 운영할 수 있다면, 일반화 (generalization)가 작동하는 것입니다.
실제 작동 방식
세 가지 설계 선택이 가장 큰 비중을 차지합니다.
고정된 도구 목록 (A frozen tool roster).
초기에는 모델이 임의의 도구 스키마 (tool schemas)를 읽도록 가르치려 시도했습니다. 즉, 고정된 세트를 암기하지 않도록 학습 예제마다 도구 이름과 인자 (arguments)를 가변적으로 설정했습니다.
230M 모델에게 그것은 너무 무리한 요구였으며, 호출 (calls) 결과가 엉망이 되었습니다.
따라서 이제 목록은 고정되었습니다: 모델이 이름으로 학습하는 안정적인 이름을 가진 8개의 도구(list_items, get_item, search_knowledge, add_to_cart, remove_from_cart, clear_cart, checkout, navigate)입니다.
작고 암기 가능한 행동 공간 (action space)을 구축한 것입니다.
다양성이 유지되는 유일한 곳은 list_items의 _필터 세트 (filter set)_이며, 모델은 이를 주입된 스키마로부터 읽습니다.
파이프라인이 아닌 도구로서의 RAG.
검색 (Retrieval)은 단지 list_items (카탈로그)와 search_knowledge (가이드, 정책)일 뿐입니다.
모델은 언제 이들을 호출할지 결정하고, 그 결과에 근거하여 답변을 생성합니다.
백엔드는 교체 가능합니다 (BM25, 벡터, 하이브리드 등). 오직 _결과 형태 (result shape)_만이 계약이기 때문입니다.
데모는 브라우저 내 BM25를 사용하며, 어떤 데이터도 기기 외부로 나가지 않습니다.
문법 제약 디코딩 (Grammar-constrained decoding).
도구 호출은 GBNF 문법에 따라 디코딩되므로, 모든 호출은 구문론적으로 유효하며 — 중요한 부분인데 — 모델이 생성하는 모든 ID는 주입된 컨텍스트에 실제로 존재하는 ID입니다.
모델은 제품 ID를 말 그대로 환각 (hallucinate)할 수 없습니다.
이 단 하나의 제약이, 그렇지 않았다면 이토록 작은 모델을 무너뜨렸을 일련의 실패 유형들을 제거해 줍니다.
학습시키기
파이프라인은 합성 데이터 증류 (synthetic-data distillation) 방식입니다:
- 약 18개의 상호작용 _레시피 (interaction recipes)_를 정의했습니다 (장바구니 담기, 탐색, 비교, 가격 조회, 지식 Q&A, 거절, 범위를 벗어난 유도, 잡담 등). 각 레시피는 예시 런타임 컨텍스트 (runtime context)가 첨부된 짧고 제한된 대화 세트를 생성합니다.
- 티처 모델 (teacher model)이 자연어 부분(고객의 표현, 근거 있는 답변)을 작성합니다. _구조 (structure)_는 결정론적 (deterministic)이며 코드로 생성되므로, 도구 호출 (tool calls)과 ID는 항상 정확합니다.
- 이 데이터를 사용하여 약 3,000만 토큰 규모로 베이스 모델 (base model)을 파인튜닝 (Fine-tune)합니다. 전체 파인튜닝 (Full fine-tune) 방식이며, 단일 16GB GPU에 적합한 규모입니다.
- 모델이 한 번도 본 적 없는 수직적 영역 (verticals)에서 평가하며, 데모에 충실한 "실제 UI에서 생존 가능한가"를 확인하는 하네스 (harness)를 사용합니다.
동일한 작업을 수행하려는 분들을 위해 주목할 만한 의도적인 선택 사항 하나를 말씀드리자면, 블랙리스트 (blocklist)가 아니라 _패턴 (pattern)_을 학습시켜야 한다는 점입니다.
"주제에서 벗어난 요청 처리"를 위해, 주제에서 벗어난 항목들을 고정된 목록으로 나열하고 싶은 유혹이 생길 수 있습니다.
하지만 그렇게 하지 마세요. 모델은 단순히 그 문자열들을 암기할 뿐입니다.
대신, 매번 진정으로 다른 주제 이탈 예시를 생성하여 (수천 개의 고유한 예시), 모델이 15개의 금지된 문구가 아니라 유도 (steering)하는 _행동 (behavior)_을 배우도록 해야 합니다.
일부 LLM 제공업체는 요청을 캐싱 (cache)할 수 있습니다. 이는 다양성이 필요한 학습 데이터의 중복을 초래할 수 있습니다. 이를 방지하려면 각 요청에 적절한 시드 (seed)를 부여해야 합니다.
물론 티처 모델의 품질은 매우 중요합니다. 저는 Apache 2.0 라이선스 모델로만 제한했으며, 글을 쓰는 시점에 가성비가 가장 좋았던 모델은 Openrouter.ai를 통한 Qwen3 30B였습니다.
그래서... 작동하나요?
음, 네, 하지만 실제로는 아니에요.
작동하는 부분:
구조화된 도구 호출 (Structured tool calls)은 신뢰할 수 있습니다. 이름이나 위치로 아이템을 추가하고, 가격을 묻고, 근거 있는 답변을 얻고, 결제하는 것까지 — 학습하지 않은 도메인에서도 핵심 루프 (core loop)가 잘 유지됩니다.
서버 없이 노트북에서 실행되는 150MB 크기의 모델이라는 점을 고려하면, 이는 여전히 마법처럼 느껴집니다.
아직은 불가능한 것들:
여러 도메인에 걸쳐 분산된 230M 파라미터(parameters)는 매우 박한 수준입니다.
학습(training) 과정에서 일반화된 사용 사례(generalized use-cases)들이 동일한 제한된 용량을 두고 경쟁하게 됩니다.
멀티 턴 충실도(Multi-turn fidelity)가 가장 취약한 부분입니다. 긴 대화가 이어지면, 방금 사용자가 말한 구체적인 내용보다는 모델이 학습된 형태 쪽으로 흐를 수 있습니다.
이 때문에 학습 체계(training regime)는 대화 패턴을 단 2턴으로 제한하며, JS 런타임(JS runtime) 또한 슬라이딩 윈도우(sliding window) 방식을 사용하여 에이전트가 2턴 이상의 대화를 결코 볼 수 없게 합니다.
350M 변체(variant)는 점유 공간(footprint)이 약 1.5배 더 크지만 실질적인 여유 공간(headroom)을 제공하며, 데모에서 두 모델을 전환하며 차이를 느껴볼 수 있습니다.
저는 이것이 흥미로운 개척지라고 생각합니다. "거대 모델이 이것을 할 수 있는가"(당연히 가능합니다) 대신, 온디바이스(on-device)에서 여전히 진정으로 유용하면서도 얼마나 작게 만들 수 있는가를 묻는 것입니다.
왜 굳이 온디바이스인가
단순히 멋있다는 점을 넘어:
- 개인정보 보호 (Privacy) - 입력값이 절대 기기를 떠나지 않습니다. 많은 사용 사례에서 이는 있으면 좋은 기능이 아니라 필수 요구 사항입니다.
- 오프라인 (Offline) - 첫 로드 이후에는 연결 없이도 계속 작동합니다.
- 비용 (Cost) - 추론(inference) 비용이 제로입니다. GPU 플릿(fleet)을 보내는 대신 모델 가중치(weights)를 전달하면 됩니다.
- 접근성 (Accessibility) - 적절한 ARIA 역할(roles) 및 시맨틱 HTML(semantic HTML)과 결합하면, 온디바이스 에이전트는 복잡한 UI를 음성이나 의도(intent)로 탐색할 수 있게 해줍니다. (데모의 음성-텍스트 변환(Speech-to-text) 및 텍스트-음성 변환(text-to-speech) 또한 온디바이스 모델이며, 아무것도 외부로 전송되지 않습니다.)
체험하기 / 분석하기
- 데모 (Demo): https://lajosbencz.github.io/frontend-agent/ (세 개의 상점, 하나의 모델, 모두 당신의 브라우저 탭 안에서 실행됩니다)
- 코드 (Code): GitHub - Apache-2.0 클라이언트 라이브러리 + Nuxt 데모
- 모델 (Models): Hugging Face의 230M · 350M
- 데이터셋 (Dataset): frontend-agent-sft
LiquidAI LFM2.5를 기반으로 구축되었습니다.
모델 가중치(Model weights)는 LFM Open License v1.0을 계승하며, 코드와 이 게시물은 Apache-2.0 / CC-BY 라이선스를 따릅니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기