콘텐츠로 이동
ABTO 가이드

연동 가이드

Browser JavaScript

브라우저에서 Custom Event와 LLM 트레이스를 기록하는 SDK입니다.

클라이언트 SDK브라우저와 모바일 앱에서 실행 (Event Key)

Browser SDK는 고객사가 선택한 사용자 행동처럼 브라우저가 직접 관찰하는 사실만 기록합니다. 모델, 토큰, 비용은 추정하지 않습니다. 이 값들은 게이트웨이가 기록합니다.

앱의 루트에서 한 번 초기화합니다.

import { defineEvents, initAbto } from '@abto-app/event';
const events = defineEvents({
checkout_completed: {
properties: {
order_id: { type: 'string', required: true },
value: { type: 'number', required: true },
scale: { type: 'string', enum: ['KRW', 'USD'], required: true },
},
},
});
export const abto = initAbto({
projectKey: 'ek-abto-...',
apiHost: 'https://api.abto.app',
environment: 'production',
events,
});
설정기본값역할
projectKey필수브라우저에 둘 수 있는 공개 Event Key
apiHosthttps://api.abto.appEvent API host. SDK가 /v1/collect/events를 붙입니다
environmentproductiondevelopment에서는 미등록 event와 schema drift를 경고 후 전송합니다
appVersion미설정지정하면 event context의 $app_version으로 전송합니다
events{}defineEvents()로 만든 Custom Event registry
capture.promptmetadata_onlyoff, hash, metadata_only, full 중 prompt 수집 정책
capture.responsemetadata_onlyoff, metadata_only, full 중 response 수집 정책

이 초기화만으로는 어떤 이벤트도 발생하지 않습니다. 기기 identity와 trace context만 준비되므로, 필요한 Custom Event와 LlmTrace 이벤트를 제품 동작 지점에서 직접 호출합니다.

abto.events.ts가 제품 Custom Event의 정본입니다. 대시보드에서 따로 등록하는 대신 코드에 두면 이벤트 계약 변경이 코드 리뷰와 배포 과정을 함께 거칩니다. Development에서는 미등록 event와 schema drift를 경고하면서 전송하고, Production에서는 미등록 event와 required/type/enum 위반을 drop합니다. 스키마에 없는 일반 property는 두 환경 모두 전송합니다.

Browser SDK는 설치되는 순간 익명 device_id를 만들어 브라우저에 보관합니다. 이 device_id가 제품 행동과 AI 사용을 잇는 기본 축이라, 로그인 없이도 연결이 유지됩니다.

로그인 사용자를 추가로 묶으려면 로그인 직후 identify를 호출합니다.

abto.identify('user-123', 'tenant-123');
const { deviceId } = abto.getIdentity();

두 번째 tenantId는 선택입니다. 이후 이벤트에는 $user_id가, 지정했다면 $tenant_id도 함께 실립니다. $device_id 값 자체는 그대로입니다. getIdentity().deviceId를 백엔드 요청에 전달하면 Server SDK가 같은 기기 축으로 Gateway를 호출합니다. 로그아웃할 때는 abto.reset()으로 사용자 컨텍스트를 지우고 새 세션을 시작합니다. 기기는 그대로 유지됩니다.

abto.forgetDevice()는 새 device_id를 발급합니다. 이때 아직 전송되지 않은 이벤트는 폐기됩니다. 보존하려면 flush() 를 먼저 호출하세요.

abto.capture('checkout_completed', {
order_id: 'order-123',
value: 49_000,
scale: 'KRW',
});

order_id는 ABTO 필수 필드가 아니라 주문 도메인을 설명하는 제품 쪽 속성입니다. 위 선언에서는 required지만 제품에 맞게 optional로 두거나 다른 속성으로 바꿔도 됩니다. 이벤트 식별자는 SDK가 자동으로 생성합니다.

value와 단위 라벨 scale은 예외로, Success Metric이 집계 대상으로 읽는 예약된 이름입니다. 금액이나 개수처럼 합계나 평균을 낼 수치는 이 두 이름으로 실어야 하며, 다른 이름으로 보내면 값은 이벤트에 남지만 집계되지 않습니다.

LlmTrace는 prompt 제출부터 응답 렌더링과 후속 행동까지 하나의 흐름으로 묶습니다. Browser SDK는 모델을 직접 호출하지 않습니다. Calling Key와 provider key는 백엔드에만 두고, 브라우저가 만든 요청 context만 백엔드로 전달하세요.

const trace = abto.startLlmTrace();
await trace.submitPrompt({
prompt: promptText,
language: 'ko',
});
const backendResponse = await fetch('/api/generate', {
method: 'POST',
headers: {
'content-type': 'application/json',
...trace.getHeaders(),
},
body: JSON.stringify({ prompt: promptText }),
});
trace.attachRequestId(backendResponse);
const result = await backendResponse.json();
await trace.markResponseRendered({
responseId: result.responseId,
timeToRenderMs: 1_380,
});
await trace.captureResponseInteraction('copied', {
responseId: result.responseId,
source: 'copy_button',
});

응답 상호작용은 copied, inserted, accepted, rejected, shared, downloaded, expanded, collapsed, rated_positive, rated_negative, regenerated, aborted만 허용합니다. TypeScript literal union과 JavaScript runtime이 같은 목록을 강제합니다. 지원하지 않는 값은 enqueue 전에 경고와 함께 제외되므로, 제품 고유 행동은 Custom Event로 기록하세요.

trace.getHeaders()는 브라우저가 소유하는 x-abto-device-id만 반환합니다. 백엔드는 이 값을 검증해 실제 모델 호출의 featureId와 함께 Server SDK의 context로 전달하고, Gateway 응답의 x-abto-request-id를 브라우저 응답에도 포함해야 합니다. Browser SDK는 서버가 소유하는 feature ID를 만들지 않습니다.

백엔드가 다른 origin이면 preflight 응답의 Access-Control-Allow-Headerscontent-typex-abto-device-id를 허용하세요. 브라우저가 응답 ID를 읽도록 실제 응답의 Access-Control-Expose-Headers에는 x-abto-request-id를 추가해야 합니다. attachRequestId() 이후의 렌더링과 상호작용 event에는 같은 $request_id가 실립니다.

  • localStorage를 사용할 수 있으면 Event는 먼저 durable outbox에 저장되고, 서버가 확인한 뒤 제거됩니다.
  • 브라우저가 localStorage를 차단하면 SDK는 memory-only queue로 동작하므로 페이지 종료 후 복구되지 않습니다.
  • 기본 배치는 20건이며 collector 요청 하나에는 최대 100건을 보냅니다.
  • 페이지 이탈 때는 약 60 KiB 이내 payload에만 fetch(..., { keepalive: true })를 사용합니다.
  • 408, 429, 5xx와 event별 retry 결과만 재시도합니다. 지수 backoff에 jitter를 더해 최대 2분까지 벌리며, 여러 탭이 동시에 실패해도 재시도가 한 시점에 몰리지 않습니다.
  • 메모리 큐는 최대 1,000건을 유지하며 넘치면 가장 오래된 것부터 버립니다. durable outbox가 있어 시도 횟수로 이벤트를 버리지는 않습니다.
  • 영구 4xx와 event별 drop 결과는 outbox에서 제거합니다.
  • localStorage outbox에 남은 이벤트는 다음 SDK instance가 재전송합니다.

SDK는 send_failed, outbox_write_failed, identity_persist_failed, storage_unavailable을 고정 counter로 기록합니다. 다음 Event 배치의 선택적 diagnostics 필드에만 동승하므로 별도 요청이나 제품 Event를 만들지 않습니다. 성공 응답 뒤에만 counter를 지우고, 전송 실패 시 다음 배치까지 유지합니다.

diagnostics에는 고정 SDK 이름과 실패 종류별 횟수만 포함됩니다. 사용자 ID, Event 속성, URL, 오류 원문은 복사하지 않습니다. 네 counter로 고정된 스키마이므로 별도 크기 제한이나 복구 계층이 필요하지 않습니다. Event가 전혀 없으면 diagnostics만 보내는 요청도 발생하지 않습니다.

이벤트 이름과 스키마를 정하는 기준은 이벤트 설계에서 이어집니다.