๊ฐ€์ด๋“œ
AI Assistant

AI Assistant

์™ธ๋ถ€ AI์™€ ์—ฐ๋™ํ•˜๊ธฐ ์œ„ํ•œ ํŒจ๋„ยท๋„๊ตฌยท์‹คํ–‰ API ์„ค์ •๊ณผ ์‚ฌ์šฉ๋ฒ•์„ ์•ˆ๋‚ดํ•œ๋‹ค.

RealPivot2๋Š” AI ๋ชจ๋ธ์ด๋‚˜ AI ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š”๋‹ค. ์™ธ๋ถ€ LLMยท์—์ด์ „ํŠธ์™€ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ๋Š” ํŒจ๋„(UI), ํ”ผ๋ฒ— ์กฐ์ž‘ ๋„๊ตฌ(tool), ์‹คํ–‰ API, ๊ทธ๋ฆฌ๊ณ  RAG ์—ฐ๋™ ์ง€์ ์„ ์ œ๊ณตํ•œ๋‹ค. ์ถ”๋ก , ์‘๋‹ต ์ƒ์„ฑ, ์ง€์‹ ์ฝ”ํผ์Šค(Corpus) ๊ด€๋ฆฌ ๋“ฑ์€ ์—ฐ๋™ํ•œ ์™ธ๋ถ€ ๋ชจ๋ธ์ด๋‚˜ ์„œ๋ฒ„์—์„œ ๋‹ด๋‹นํ•œ๋‹ค.

Book ๊ฐœ์š”๋Š” ํ”ผ๋ฒ— ๋ถ(Book)์„ ์ฐธ๊ณ ํ•œ๋‹ค.

RealPivot2๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ

๊ตฌ๋ถ„๋‚ด์šฉ
AI ํŒจ๋„์งˆ์˜ ์ž…๋ ฅยท๋Œ€ํ™”ยท์ œ์•ˆ ๋ฌธ๊ตฌ๋ฅผ ํ‘œ์‹œํ•˜๋Š” UI (ai.panel)
๋ชจ๋ธ ์—ฐ๊ฒฐ์™ธ๋ถ€ OpenAI/Gemini API ๋˜๋Š” ์ž์ฒด ์›๊ฒฉ ์„œ๋ฒ„ ์—ฐ๋™ (ai.models)
๋„๊ตฌ(tool)ํ•„๋“œ ์ด๋™ยทํ•„ํ„ฐยท๋ถ„์„ ๋“ฑ ํ”ผ๋ฒ—์„ ์กฐ์ž‘ํ•˜๋Š” ๋ช…๋ น ์Šคํ‚ค๋งˆ
์‹คํ–‰ APIbook.aiApi๋กœ ๋„๊ตฌ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ด€๋ฌธ
RAG ์—ฐ๋™์„ค์ • ์ง€์‹ ๊ฒ€์ƒ‰(search_features)ยท๋กœ์ปฌ ์ž„๋ฒ ๋”ฉ ๊ฒ€์ƒ‰(useRag)ยท์ปจํ…์ŠคํŠธ ํŒŒ์ผ ๋กœ๋“œ

AI ๋ผ์ด์„ ์ŠคยทAPI ํ‚คยท์›๊ฒฉ ์„œ๋ฒ„ยท์ง€์‹๋ฒ ์ด์Šค ๊ตฌ์ถ•ยท์‘๋‹ต ํ’ˆ์งˆ์€ RealPivot2 ๋ฒ”์œ„๊ฐ€ ์•„๋‹ˆ๋‹ค. ์—ฐ๋™ ๋ชฉ์ ๊ณผ ์‚ฌ์šฉ ํ™˜๊ฒฝ์— ๋งž์ถฐ ์ง์ ‘ ๊ตฌ์ถ•ํ•ด์•ผ ํ•œ๋‹ค.

์ง€์ • ์œ„์น˜

ํ”ผ๋ฒ— ๋ถ(Book) ์„ค์ •์˜ ai ์˜ต์…˜(PivotAIOptions)์—์„œ ํ™œ์„ฑํ™”ํ•œ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ enabled: false์ด๋‹ค.

์†์„ฑ์—ญํ• 
enabledAI ์—ฐ๋™ UIยท๊ธฐ๋Šฅ ์‚ฌ์šฉ ์—ฌ๋ถ€
models์—ฐ๊ฒฐํ•  ์™ธ๋ถ€ ๋ชจ๋ธ(๋กœ์ปฌ API ๋˜๋Š” ์›๊ฒฉ ์„œ๋ฒ„)
panelํŒจ๋„ ์œ„์น˜ยทํฌ๊ธฐยทํ”„๋กฌํ”„ํ„ฐ UI
resources์ปจํ…์ŠคํŠธ ํŒŒ์ผยทRAG ๊ธฐ๋Šฅ ๊ฒ€์ƒ‰(featureSearch) ๋“ฑ
autoSubmit์ œ์•ˆ ๋ฌธ๊ตฌ ํด๋ฆญ ์‹œ ๋ฐ”๋กœ ์ „์†กํ• ์ง€ ์—ฌ๋ถ€
addModeํ…Œ์ด๋ธ” ์ถ”๊ฐ€ UI ('dialog' | 'page')
devMode๊ฐœ๋ฐœ์ž ๋ชจ๋“œ(๋ชจ๋ธ์ด ๋„˜๊ธด ์„ค์ • ๋‚ด์šฉ ํ‘œ์‹œ)
const config = {
  ai: {
    enabled: true,
    models: {
      type: 'remote',
      name: 'my-server',
      modelName: 'gpt-4.1',
      url: 'https://my-ai.example.com/v1',
      headers: {
        Authorization: 'Bearer <YOUR_TOKEN>',
      },
    },
    panel: {
      position: 'right',
      width: 320,
      prompter: {
        suggestions: [
          'ํŒ๋งค์ˆ˜๋Ÿ‰ ์ด์ƒ์น˜๋ฅผ ํƒ์ง€ํ•ด์ค˜.',
          'ํŒ๋งค์ˆ˜๋Ÿ‰์— ๋ฐ์ดํ„ฐ๋ฐ”๋ฅผ ์ ์šฉํ•ด.',
        ],
      },
    },
  },
  tables: {
    name: 'sales',
    cube: 'sales',
    // fields ...
  },
};

AI ํŒจ๋„

AI ํŒจ๋„์€ ์™ธ๋ถ€ LLM๊ณผ ๋Œ€ํ™”ํ•˜๋Š” UI ์˜์—ญ์ด๋‹ค. ai.panel.position ์˜ต์…˜์œผ๋กœ ์œ„์น˜๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

positionํ‘œ์‹œ ์œ„์น˜
'right'(๊ธฐ๋ณธ)๋ถ ์˜ค๋ฅธ์ชฝ
'left'๋ถ ์™ผ์ชฝ
'float'๋–  ์žˆ๋Š” ํŒจ๋„(floatBounds๋กœ ์ดˆ๊ธฐ ์œ„์น˜ยทํฌ๊ธฐ)
'inspector'์ธ์ŠคํŽ™ํ„ฐ ํƒญ
'explorer'ํƒ์ƒ‰๊ธฐ ํƒญ (ํƒ์ƒ‰๊ธฐ)
ai: {
  enabled: true,
  panel: {
    position: 'explorer',
    width: 320,
    autoDock: true,
    prompter: {
      greetings: 'ํ”ผ๋ฒ— ๊ตฌ์„ฑ์„ ์ž์—ฐ์–ด๋กœ ์š”์ฒญํ•ด ๋ณด์„ธ์š”.',
      placeholder: '์˜ˆ: ์ฐจ์ข…์„ ํ–‰์œผ๋กœ ์˜ฎ๊ฒจ์ค˜',
      waitingMessage: '๋ถ„์„ ์ค‘...',
      suggestions: ['๊ตญ๊ฐ€๋ฅผ ํ•„ํ„ฐ์— ๋„ฃ์–ด์ค˜.'],
    },
  },
}

ai.panel.prompter ํ•ญ๋ชฉ์€ ์ธ์‚ฌ๋ง, ์ œ์•ˆ์–ด, placeholder ๋“ฑ ํŒจ๋„ UI์— ํ‘œ์‹œ๋  ๋ฌธ๊ตฌ๋งŒ ์„ค์ •ํ•˜๋ฉฐ, ์‹ค์ œ ๋‹ต๋ณ€์€ ์—ฐ๋™๋œ AI ๋ชจ๋ธ์ด ์ƒ์„ฑํ•œ๋‹ค.

ํ—ค๋”ยทํƒญ๋ฐ”์˜ AI ๊ด€๋ จ ๋ฒ„ํŠผ์€ ai.enabled๊ฐ€ true์ผ ๋•Œ ํ‘œ์‹œ๋œ๋‹ค.

์™ธ๋ถ€ ๋ชจ๋ธ ์—ฐ๊ฒฐ

ai.models์— ๊ฐœ๋ฐœ์ž๊ฐ€ ์‚ฌ์šฉํ•  ๋ชจ๋ธ์„ ๋“ฑ๋กํ•œ๋‹ค. ๊ฐ์ฒด ํ•˜๋‚˜ ๋˜๋Š” ๋ฐฐ์—ด๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

์›๊ฒฉ ์„œ๋ฒ„ (type: 'remote')

์ž์ฒด ๋ฐฑ์—”๋“œ(๋˜๋Š” ํ”„๋ก์‹œ)๋ฅผ ๊ฒฝ์œ ํ•ด LLM์„ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

์†์„ฑ์„ค๋ช…
name๋ชจ๋ธ ์‹๋ณ„ ์ด๋ฆ„
modelName์„œ๋ฒ„์—์„œ ์“ฐ๋Š” ๋ชจ๋ธ ์ด๋ฆ„
url์„œ๋ฒ„ ๋ฒ ์ด์Šค URL
headersAuthorization ๋“ฑ ์š”์ฒญ ํ—ค๋”
commandsPath / contextPath / queryPathํ•˜์œ„ ๊ฒฝ๋กœ(๊ธฐ๋ณธ commands ยท context ยท query)
models: {
  type: 'remote',
  name: 'my-server',
  modelName: 'gpt-4.1',
  url: 'https://my-ai.example.com/v1',
  headers: { Authorization: 'Bearer <YOUR_TOKEN>' },
}

์›๊ฒฉ ๋ชจ๋ธ์˜ ์ „์ฒด ์„ค์ • ํ•ญ๋ชฉ์€ Config ยท ai ยท models๋ฅผ ์ฐธ๊ณ ํ•œ๋‹ค.

๋ธŒ๋ผ์šฐ์ €์—์„œ provider API ํ˜ธ์ถœ (type: 'local')

OpenAIยทGemini API๋ฅผ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง์ ‘ ํ˜ธ์ถœํ•œ๋‹ค. provider๋Š” 'openai' ๋˜๋Š” 'gemini'์ด๋‹ค.

์†์„ฑ์„ค๋ช…
provider'openai' | 'gemini'
apiKeyprovider API ํ‚ค
modelNameํ˜ธ์ถœํ•  ๋ชจ๋ธ ์ด๋ฆ„
endpoint์ปค์Šคํ…€ base URL(์„ ํƒ)
models: {
  type: 'local',
  name: 'gpt',
  provider: 'openai',
  modelName: 'gpt-4.1',
  apiKey: '<YOUR_OPENAI_API_KEY>',
}

๋กœ์ปฌ ๋ชจ๋ธ์˜ ์ „์ฒด ์„ค์ • ํ•ญ๋ชฉ์€ Config ยท ai ยท models๋ฅผ ์ฐธ๊ณ ํ•œ๋‹ค.

ํ”ผ๋ฒ— ๋„๊ตฌ(tool)

RealPivot2๋Š” LLM์ด ํ”ผ๋ฒ—์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋„๋ก ์กฐ์ž‘ ๋„๊ตฌ(Tool) ํ•ญ๋ชฉ๋“ค์„ ์ œ๊ณตํ•œ๋‹ค. ๋„๊ตฌ ์ž์ฒด๋Š” AI๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ์—ฐ๋™๋œ AI๊ฐ€ ํ”ผ๋ฒ—์„ ๋ณ€๊ฒฝํ•  ๋•Œ ํ˜ธ์ถœํ•˜๋Š” ๋ช…๋ น ์Šคํ‚ค๋งˆ ๋ชฉ๋ก์ด๋‹ค.

๋Œ€ํ‘œ ์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

๋„๊ตฌ์—ญํ• 
move_field / remove_field / add_value_fieldํ•„๋“œ ๋ฐฐ์น˜
filter_by_member / set_slicer_filterํ•„ํ„ฐ
configํ…Œ์ด๋ธ” ์˜ต์…˜ ๋ณ€๊ฒฝ
detect_outliers / analyze_growth ๋“ฑ๋ถ„์„
search_features์„ค์ • ๋ฐฉ๋ฒ• RAG ๊ฒ€์ƒ‰(resources.featureSearch.url์ด ์žˆ์„ ๋•Œ)
focus_cell์…€ ํฌ์ปค์Šค

์˜ต์…˜ ๊ตฌ์กฐ๊ฐ€ ํ™•์‹คํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ, AI๊ฐ€ ์ž„์˜๋กœ ์ถ”์ธกํ•˜๊ธฐ๋ณด๋‹ค๋Š” search_features ๋„๊ตฌ๋กœ ์ง€์‹๋ฒ ์ด์Šค๋ฅผ ๋จผ์ € ๊ฒ€์ƒ‰ํ•œ ํ›„ ๋ฐ˜ํ™˜๋œ config๋ฅผ ์ ์šฉํ•˜๋„๋ก ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.

์Šคํ‚ค๋งˆ ๋ชฉ๋ก์€ getAITools() ยท exportAITools() ยท book.aiApi.getToolSchemas()๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

import { getAITools, exportAITools } from 'realpivot2';
 
const tools = getAITools();
const schemaJson = exportAITools();

์™ธ๋ถ€ ์—์ด์ „ํŠธ์—์„œ ์‹คํ–‰ํ•˜๊ธฐ

์ž์ฒด ์ฑ„ํŒ… UIยท์—์ด์ „ํŠธ๋ฅผ ์“ฐ๋Š” ๊ฒฝ์šฐ์—๋„ book.aiApi(PivotAIApi)๋กœ ๋™์ผ ๋„๊ตฌ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‚ด์žฅ AI ํŒจ๋„์„ ์“ฐ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

const api = control.book.aiApi;
 
const schemas = api.getToolSchemas();
const state = api.serializeState({ include: ['config'] });
 
const result = await api.execute({
  tool: 'move_field',
  arguments: {
    // ๋„๊ตฌ๋ณ„ ์ธ์ž
  },
  reason: '์ฐจ์ข…์„ ํ–‰ ์˜์—ญ์œผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.',
});

์ปจํ…์ŠคํŠธยท๋„๊ตฌยท์ƒํƒœ ์กฐํšŒ์™€ ์‹คํ–‰ ์ง„์ž…์ ์ด API์— ๋ชจ์—ฌ ์žˆ๋‹ค. ์ƒ์„ธ๋Š” API ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•œ๋‹ค.

RAG ยท ๋ฆฌ์†Œ์Šค

RealPivot2๋Š” ์ง€์‹๋ฒ ์ด์Šค(์ฝ”ํผ์Šค)๋ฅผ ๋‚ด์žฅยทํ˜ธ์ŠคํŒ…ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋Œ€์‹  AI๊ฐ€ ํ”ผ๋ฒ— ์„ค์ • ๋ฐฉ๋ฒ•์„ ์Šค์Šค๋กœ ์ฐพ๋„๋ก RAG ์—ฐ๋™์„ ์ง€์›ํ•œ๋‹ค. ์ปจํ…์ŠคํŠธ ํŒŒ์ผยท๊ฒ€์ƒ‰ ์—”๋“œํฌ์ธํŠธยท์ž„๋ฒ ๋”ฉ ์ฝ”ํผ์Šค๋Š” ์•ฑ์—์„œ ์ œ๊ณตํ•œ๋‹ค.

์ปจํ…์ŠคํŠธ ํŒŒ์ผ

ai.resources๋กœ ๋ชจ๋ธ์— ๋„˜๊ธธ ์„ค๋ช… ๋ฌธ์„œ ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•œ๋‹ค.

์†์„ฑ๊ธฐ๋ณธ์„ค๋ช…
systemContextPath'rag/system_context.md'์‹œ์Šคํ…œ ์ปจํ…์ŠคํŠธ(์‚ฌ์šฉ ์„ค๋ช…)
developerContextPath'rag/developer_context.md'๊ฐœ๋ฐœ์ž ์ปจํ…์ŠคํŠธ

ํ•ด๋‹น ๊ฒฝ๋กœ์˜ ๋งˆํฌ๋‹ค์šด(.md) ํŒŒ์ผ์„ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ ‘๊ทผ(์„œ๋น™)ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•ด์•ผ ์ •์ƒ ๋กœ๋“œ๋œ๋‹ค.

์„ค์ • ๋ฐฉ๋ฒ• ๊ฒ€์ƒ‰ (featureSearch)

search_features ๋„๊ตฌ๊ฐ€ ํ˜ธ์ถœํ•˜๋Š” ์›๊ฒฉ RAG ์—”๋“œํฌ์ธํŠธ์ด๋‹ค. ํžˆํŠธ๋งต์ด๋‚˜ ๋ฐ์ดํ„ฐ๋ฐ” ๋“ฑ ๋ณต์žกํ•œ config ๊ตฌ์กฐ๋ฅผ ๋ชจ๋ฅผ ๋•Œ, AI๊ฐ€ ์ง€์‹๋ฒ ์ด์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•˜์—ฌ ๊ฐ€์ ธ์˜จ ์„ค์ •์„ config ๋„๊ตฌ์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•œ๋‹ค.

์†์„ฑ์„ค๋ช…
featureSearch.urlPOST ๋Œ€์ƒ URL(ํ•„์ˆ˜, ์—†์œผ๋ฉด ๋„๊ตฌ ๋ฏธ๋…ธ์ถœ)
featureSearch.headersAuthorization ๋“ฑ
featureSearch.enabled์‚ฌ์šฉ ์—ฌ๋ถ€(๊ธฐ๋ณธ true)

์š”์ฒญยท์‘๋‹ต ๊ณ„์•ฝ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ์š”์ฒญ: POST body { query }
  • ์‘๋‹ต: { count, grounding }
ai: {
  enabled: true,
  resources: {
    systemContextPath: 'rag/system_context.md',
    developerContextPath: 'rag/developer_context.md',
    featureSearch: {
      url: '/api/feature-search',
      headers: { Authorization: 'Bearer <YOUR_TOKEN>' },
    },
  },
  // models, panel ...
}

์ „์ฒด ํ•ญ๋ชฉ์€ Config ยท ai ยท resources ยท featureSearch๋ฅผ ์ฐธ๊ณ ํ•œ๋‹ค.

๋กœ์ปฌ ๋ชจ๋ธ RAG (useRag)

type: 'local' ๋ชจ๋ธ์—์„œ๋Š” ์งˆ์˜๋งˆ๋‹ค ์ž„๋ฒ ๋”ฉ ์ฝ”ํผ์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•ด ํ”„๋กฌํ”„ํŠธ์— ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฝ”ํผ์Šค ํŒŒ์ผ๋„ ์•ฑ์ด ์„œ๋น™ํ•˜๋ฉฐ, RealPivot2๋Š” ๊ฒ€์ƒ‰ยท์ฃผ์ž… ๊ฒฝ๋กœ๋งŒ ์ œ๊ณตํ•œ๋‹ค.

์†์„ฑ์„ค๋ช…๊ธฐ๋ณธ
useRag๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ํ”„๋กฌํ”„ํŠธ์— ์ž๋™ ์ฃผ์ž…false
embeddingModel์งˆ์˜ ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ(์ฝ”ํผ์Šค์™€ ๋™์ผํ•ด์•ผ ํ•จ)provider๋ณ„ ๊ธฐ๋ณธ
ragTopK์ƒ์œ„ chunk ์ˆ˜5
ragEmbeddingsPath์ž„๋ฒ ๋”ฉ ๋ฐ”์ด๋„ˆ๋ฆฌ(Float32)provider๋ณ„ (rag/openai/... ๋“ฑ)
ragChunksPath์›๋ฌธ chunk JSONprovider๋ณ„
ragMetaPath๋ฉ”ํƒ€(count/dim/model)provider๋ณ„
models: {
  type: 'local',
  name: 'gpt',
  provider: 'openai',
  modelName: 'gpt-4.1',
  apiKey: '<YOUR_OPENAI_API_KEY>',
  useRag: true,
  ragTopK: 5,
}

ํŒจ๋„ UI ๋ฉ”๋‰ด์—์„œ๋„ ๋กœ์ปฌ ๋ชจ๋ธ์˜ RAG ํ™œ์„ฑํ™” ์—ฌ๋ถ€๋ฅผ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค. featureSearch(์›๊ฒฉ ๋„๊ตฌ ๊ฒ€์ƒ‰) ๋ฐฉ์‹๊ณผ useRag(๋กœ์ปฌ ํ”„๋กฌํ”„ํŠธ ์ฃผ์ž…) ๋ฐฉ์‹์€ ๋…๋ฆฝ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋ฏ€๋กœ, ์ƒํ™ฉ์— ๋งž์ถฐ ๊ฐœ๋ณ„ ๋˜๋Š” ์กฐํ•ฉํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

API๋กœ ํŒจ๋„ยท์งˆ์˜ ๋‹ค๋ฃจ๊ธฐ

const ai = control.book.ai;
const panel = ai.panel;
const prompter = panel.prompter;
 
ai.enabled; // true / false
ai.setActiveModel('my-server');
 
prompter.send('ํŒ๋งค์ˆ˜๋Ÿ‰์— ํžˆํŠธ๋งต์„ ์ ์šฉํ•ด์ค˜.');
prompter.reset();

์˜ต์…˜ ๋ณ€๊ฒฝ ๊ทœ์น™์€ ๊ฐœ๋ฐœ ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ๊ณ ํ•œ๋‹ค.

See Also