본문으로 건너뛰기

AI

cb.ai는 LLM 채팅을 ConnectBase 서버 프록시로 호출하는 모듈입니다. 동기 응답(chat)과 SSE 스트리밍(chatStream)을 제공하고, AI 데이터베이스(RAG) 검색, 도구(Tool) 호출, 추론 모델의 사고 과정 스트리밍까지 지원합니다.

시작하기 전에

콘솔 > 설정 > AI 에서 사용할 프로바이더와 API 키를 먼저 등록하세요. 등록한 키는 암호화 저장되며 서버에서만 복호화됩니다.

🔒 프로바이더 API 키를 클라이언트에 하드코딩하지 마세요. chat / chatStream 은 ConnectBase 서버 프록시를 경유하므로 클라이언트에는 Public Key만 있으면 됩니다. 브라우저에서 api.openai.com 같은 프로바이더 URL을 직접 호출하면 DevTools 한 번으로 키가 노출됩니다.

기본 채팅

typescript
const res = await cb.ai.chat({
  messages: [{ role: 'user', content: '안녕하세요' }],
})

console.log(res.content)  // 답변 텍스트
console.log(res.usage)    // { promptTokens, completionTokens, totalTokens }

provider / model 을 생략하면 콘솔 > 설정 > AI 의 기본 설정을 사용합니다. 요청 단위로 바꿀 수도 있습니다:

typescript
const res = await cb.ai.chat({
  messages: [{ role: 'user', content: '이 글을 요약해줘: ...' }],
  provider: 'gemini',
  temperature: 0.3,
  maxTokens: 1024,
})

스트리밍 (SSE)

typescript
await cb.ai.chatStream({
  messages: [{ role: 'user', content: '안녕하세요' }],
}, {
  onToken: (text) => appendAnswer(text),
  onDone: () => finish(),
  onError: (err) => showError(err),
})

추론 모델(OpenAI o-series, DeepSeek-R1, Qwen3 reasoning 등)을 쓰면 사고 과정이 onReasoning 으로 분리되어 전달되고, 최종 답변은 그대로 onToken 으로 옵니다:

typescript
await cb.ai.chatStream({ messages }, {
  onReasoning: (thought) => renderThinking(thought),
  onToken: (text) => renderAnswer(text),
  onDone: () => finish(),
})

스트림 취소 (stop 버튼)

AbortSignal 을 전달하면 진행 중인 스트림을 취소할 수 있습니다. abort 는 에러가 아니라 정상 취소로 처리됩니다 — onError 대신 onAbort 가 호출되고 onDone 은 호출되지 않습니다.

typescript
const controller = new AbortController()

await cb.ai.chatStream(
  { messages: [{ role: 'user', content: '...' }] },
  {
    onToken: (text) => appendAnswer(text),
    onAbort: () => showCancelled(),
  },
  { signal: controller.signal },
)

// stop 버튼 클릭 시
controller.abort()

toolGroupId 를 사용하는 서버측 도구 실행 루프도 abort 시 함께 중단됩니다.

AI 데이터베이스(RAG) 연동

knowledgeBaseId 를 지정하면 문서를 먼저 검색해 컨텍스트로 포함한 뒤 답변합니다. 참조된 문서 청크는 onSources 로 전달되어 출처 UI를 만들 수 있습니다.

typescript
await cb.ai.chatStream({
  messages: [{ role: 'user', content: '환불 정책이 어떻게 되나요?' }],
  knowledgeBaseId: 'kb_xxx',
  topK: 5,
}, {
  onSources: (sources) => renderCitations(sources),
  onToken: (text) => renderAnswer(text),
  onDone: () => finish(),
})
옵션설명
topK검색할 문서 청크 수
hybridBM25 + 벡터 하이브리드 검색. 미설정 시 앱 embedding 설정을 따르고, false 면 키워드 전용
agenticAI가 검색어를 생성해 다중 라운드로 검색. 진행 상황은 onSearching 으로 수신
typescript
// Agentic 검색 — "검색 중..." 진행 UI
await cb.ai.chatStream({
  messages: [{ role: 'user', content: '작년 대비 정책 변경점을 정리해줘' }],
  knowledgeBaseId: 'kb_xxx',
  agentic: true,
}, {
  onSearching: (p) => showProgress(p.phase, p.queries),
  onSources: (sources) => renderCitations(sources),
  onToken: (text) => renderAnswer(text),
})

문서 업로드/색인은 지식 베이스 (Knowledge / RAG) 문서를 참고하세요.

도구(Tool) 호출

콘솔에서 만든 도구 그룹을 toolGroupId 로 지정하면 서버가 도구 실행 루프를 대신 수행하고, 각 도구의 시작/종료가 onToolEvent 로 전달됩니다:

typescript
await cb.ai.chatStream({
  messages: [{ role: 'user', content: '오늘 가입자 수 알려줘' }],
  toolGroupId: 'tg_xxx',
}, {
  onToolEvent: (e) => {
    if (e.type === 'tool_start') showRunning(e.name)
    if (e.type === 'tool_end') showResult(e.name, e.success)
  },
  onToken: (text) => renderAnswer(text),
})
옵션설명
tools요청에 직접 포함하는 도구 정의 (name, description, inputSchema)
toolGroupId콘솔 도구 그룹 ID — 서버측 도구 실행 루프 사용
mcpPublicKeytools 로 외부 MCP 도구를 넘길 때 해당 MCP 서버의 Public Key (cb_pk_*)
toolResultMaxChars도구 결과를 모델에 재투입하기 전 N자로 절단 (화면 표시는 원문 유지)

응답 구조

typescript
const res = await cb.ai.chat({ messages })

res.content    // 답변 텍스트
res.reasoning  // 추론 모델의 사고 과정 (추론 모델이 아니면 빈 값)
res.usage      // { promptTokens, completionTokens, totalTokens }
res.provider   // 실제 사용된 프로바이더
res.model      // 실제 사용된 모델
res.sources    // RAG 사용 시 참조 문서 청크

흔한 에러

에러원인해결
AI provider not configured콘솔에 프로바이더/키 미등록콘솔 > 설정 > AI 에서 등록
Knowledge base not found잘못된 knowledgeBaseId콘솔에서 ID 확인
스트림 중 onError 호출프로바이더 오류가 스트림 본문(in-band)으로 전달됨에러 메시지 확인 후 재시도