2026๋…„ 2์›” 19์ผ
Architecture

๐ŸฆŠ OpenPersona - macOS ๋ฐ์Šคํฌํ†ฑ AI ์—์ด์ „ํŠธ ๊ตฌ์ถ•๊ธฐ

๋””์ฆˆ๋‹ˆ/ํ”ฝ์‚ฌ ์Šคํƒ€์ผ ์บ๋ฆญํ„ฐ๊ฐ€ ๋งํ’์„ ์œผ๋กœ ๋Œ€ํ™”ํ•˜๋Š” macOS ๋ฐ์Šคํฌํ†ฑ AI ์—์ด์ „ํŠธ๋ฅผ ๊ตฌ์ถ•ํ•œ ๊ณผ์ •์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. Electron + React + Multi-LLM ์•„ํ‚คํ…์ฒ˜๋ถ€ํ„ฐ RAG์™€ MCP ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๊ธฐ๋ฐ˜์˜ ์ฐจ์„ธ๋Œ€ ํ™•์žฅ ๊ณ„ํš๊นŒ์ง€ ์ƒ์„ธํžˆ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

AI Agent
Electron
React
TypeScript
Gemini
OpenAI
Zustand
macOS
Desktop App
LLM
RAG
MCP
๐ŸฆŠ OpenPersona - macOS ๋ฐ์Šคํฌํ†ฑ AI ์—์ด์ „ํŠธ ๊ตฌ์ถ•๊ธฐ

๐Ÿ“… ๊ธ€ ๊ฐœ์š”

AI๊ฐ€ ๋” ์ด์ƒ ๋ธŒ๋ผ์šฐ์ € ์•ˆ์—๋งŒ ๋จธ๋ฌผ์ง€ ์•Š๋Š” ์‹œ๋Œ€์ž…๋‹ˆ๋‹ค. ChatGPT, Claude, Gemini ๊ฐ™์€ LLM์ด API๋ฅผ ํ†ตํ•ด ๋ˆ„๊ตฌ๋‚˜ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด์ง€๋ฉด์„œ, ๋‚˜๋งŒ์˜ AI ์—์ด์ „ํŠธ๋ฅผ ๋ฐ์Šคํฌํ†ฑ์— ์ƒ์ฃผ์‹œํ‚ค๋Š” ๊ฒƒ์ด ํ˜„์‹ค์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

OpenPersona (by TAEINN)๋Š” macOS ๋ฉ”๋‰ด ๋ฐ”์— ์ƒ์ฃผํ•˜๋Š” ๋ฐ์Šคํฌํ†ฑ ์บ๋ฆญํ„ฐ AI ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค. ๋””์ฆˆ๋‹ˆ/ํ”ฝ์‚ฌ ์Šคํƒ€์ผ์˜ 3๋ช…์˜ ์บ๋ฆญํ„ฐ๊ฐ€ ํ™”๋ฉด ํ•˜๋‹จ์—์„œ ๋งํ’์„ ์œผ๋กœ ๋Œ€ํ™”ํ•˜๋ฉฐ, ๊ฐ๊ฐ ๊ฐœ๋ฐœ์ž(Felix), ๋ฌธ์„œ ์ „๋ฌธ๊ฐ€(Done), ๊ธฐํš์ž(Bomi) ๋ผ๋Š” ๊ณ ์œ ํ•œ ์—ญํ• ๊ณผ ์„ฑ๊ฒฉ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ธ€์—์„œ๋Š” ์ œ๊ฐ€ Toy Project ๋กœ ๋งŒ๋“  OpenPersona์˜ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„๋ถ€ํ„ฐ ๊ตฌํ˜„ ์ƒ์„ธ, ๊ทธ๋ฆฌ๊ณ  ์•ž์œผ๋กœ ์ตœ์ ํ™”ํ•  RAG(Retrieval-Augmented Generation) ์™€ MCP(Model Context Protocol) ๋ฅผ ํ™œ์šฉํ•œ ์ฐจ์„ธ๋Œ€ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ์•„ํ‚คํ…์ฒ˜๊นŒ์ง€ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

๐Ÿ’ก ์ด ๊ธ€์—์„œ ๋‹ค๋ฃฐ ๋‚ด์šฉ

  • Electron + React ๊ธฐ๋ฐ˜ ๋ฐ์Šคํฌํ†ฑ AI ์—์ด์ „ํŠธ ์•„ํ‚คํ…์ฒ˜
  • Multi-LLM Router ์„ค๊ณ„์™€ ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต ์ฒ˜๋ฆฌ
  • ์บ๋ฆญํ„ฐ ํŽ˜๋ฅด์†Œ๋‚˜ ์‹œ์Šคํ…œ๊ณผ ์ƒํƒœ ๋จธ์‹ 
  • Zustand ๊ธฐ๋ฐ˜ ๊ธ€๋กœ๋ฒŒ ์ƒํƒœ ๊ด€๋ฆฌ
  • ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰ ์ถ”์  ๋ฐ ์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ๋ง
  • ์ฐจ์„ธ๋Œ€ RAG + MCP ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„

๐ŸŽฏ ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

OpenPersona ์‹คํ–‰ ํ™”๋ฉด - Felix(์—ฌ์šฐ) ์บ๋ฆญํ„ฐ๊ฐ€ ๋งํ’์„ ์œผ๋กœ ๋‹ต๋ณ€ํ•˜๋Š” macOS ๋ฐ์Šคํฌํ†ฑ AI ์—์ด์ „ํŠธ

์™œ ๋ฐ์Šคํฌํ†ฑ AI ์—์ด์ „ํŠธ์ธ๊ฐ€?

์ฑ—๋ด‡์€ ์ผ๋ฐ˜์ ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ € ์•ˆ์—์„œ ๋™์ž‘ํ•˜๊ธฐ๋„ ํ•˜๊ณ , ์™ธ๋ถ€ ๊ณ ๊ฐ์„ ๋Œ€์ƒ์œผ๋กœ ๋งŒ๋“ค์–ด ์ง‘๋‹ˆ๋‹ค. ์ €๋Š” Target์„ ๋‚ด๋ถ€ ์ง์›์„ ์œ„ํ•œ ์ ‘๊ทผ์„ฑ์ด ํŽธ๋ฆฌํ•œ ์บ๋ฆญํ„ฐ ํ˜•ํƒœ์˜ AI ์—์ด์ „ํŠธ๋กœ ๋งž์ถ”์–ด ๋ณด์•˜์Šต๋‹ˆ๋‹ค.

์›น ๊ธฐ๋ฐ˜ AI ์ฑ—๋ด‡์€ ๋ธŒ๋ผ์šฐ์ € ํƒญ์„ ์ „ํ™˜ํ•ด์•ผ ํ•˜๊ณ , ๋ฐ์Šคํฌํ†ฑ ์ปจํ…์ŠคํŠธ์™€ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ญ์ƒ ํ™”๋ฉด ์œ„์— ๋–  ์žˆ์œผ๋ฉด์„œ, ์–ด๋–ค ์ž‘์—… ์ค‘์ด๋“  ์ฆ‰์‹œ AI์—๊ฒŒ ์งˆ๋ฌธํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ์–ด๋–จ๊นŒ์š”?

๊ทธ๋ฆฌ๊ณ  ๊ท€์—ฌ์šด ์บ๋ฆญํ„ฐ๋“ค์ด ๋‚˜์˜ ๊ฐœ์ธ๋น„์„œ๋ถ€ํ„ฐ ์—‘์…€ ์ „๋ฌธ๊ฐ€...๊ฐœ๋ฐœ ์ „๋ฌธ๊ฐ€๊ฐ€ ๋˜์–ด PC๋ฅผ ์—ด์–ด ๋ฐ”ํƒ•ํ™”๋ฉด์—์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋Œ€ํ™”ํ•˜๋Š” ๊ฒƒ์€ ์–ผ๋งˆ๋‚˜ ํ™œ์šฉ์„ฑ์ด ๋” ๋†’์•„์งˆ๊นŒ์š”?

OpenPersona๋Š” ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ•ต์‹ฌ ๊ฐ€์น˜๋ฅผ ์ถ”๊ตฌํ•ฉ๋‹ˆ๋‹ค:

  • Always-on: Cmd+Shift+Space๋กœ ์ฆ‰์‹œ ํ† ๊ธ€, ์–ด๋–ค ์•ฑ ์œ„์—์„œ๋“  ๋Œ€ํ™” ๊ฐ€๋Šฅ
  • Character-driven: ๋‹จ์ˆœํ•œ ์ฑ—๋ด‡์ด ์•„๋‹Œ, ๊ฐœ์„ฑ ์žˆ๋Š” ์บ๋ฆญํ„ฐ์™€์˜ ๋Œ€ํ™” ๊ฒฝํ—˜
  • Multi-LLM: Gemini, OpenAI ๋“ฑ ๋‹ค์–‘ํ•œ LLM ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ „ํ™˜
  • Lightweight: ํˆฌ๋ช… ์ฐฝ + ๊ธ€๋ž˜์Šค๋ชจํ”ผ์ฆ˜ UI๋กœ ๋ฐ์Šคํฌํ†ฑ ๊ฒฝํ—˜์„ ํ•ด์น˜์ง€ ์•Š์Œ

ํ•ต์‹ฌ ๊ธฐ๋Šฅ

๊ธฐ๋Šฅ ์„ค๋ช…
์บ๋ฆญํ„ฐ AI ์ฑ—๋ด‡ 3๋ช…์˜ ์บ๋ฆญํ„ฐ, ๊ฐ๊ฐ ๊ณ ์œ  ์„ฑ๊ฒฉ/์—ญํ• /๋งํˆฌ
๋งํ’์„  UI ์บ๋ฆญํ„ฐ ์œ„์— ๋งํ’์„ ์œผ๋กœ ๋‹ต๋ณ€ ํ‘œ์‹œ
์บ๋ฆญํ„ฐ ํ‘œ์ • ๋ˆˆ ๊นœ๋นก์ž„, ์›ƒ๋Š” ํ‘œ์ • ๋“ฑ idle ์• ๋‹ˆ๋ฉ”์ด์…˜
Multi-LLM Gemini 2.0 Flash/Pro, GPT-4o/Mini
ํ† ํฐ ์ถ”์  ๋ชจ๋ธ๋ณ„ ์‚ฌ์šฉ๋Ÿ‰, ๋น„์šฉ, ์›” ์˜ˆ์‚ฐ ๊ด€๋ฆฌ
์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ CPU/๋ฉ”๋ชจ๋ฆฌ ์ถ”์ , ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๊ฐ์ง€
๊ธ€๋ž˜์Šค๋ชจํ”ผ์ฆ˜ UI ํˆฌ๋ช… ๋ธ”๋Ÿฌ ์ฒ˜๋ฆฌ๋œ ํ•˜๋‹จ ๋ฐ”

๐Ÿ—๏ธ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„

OpenPersona๋Š” Electron์˜ Main/Renderer ํ”„๋กœ์„ธ์Šค ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ, IPC ํ†ต์‹ ์„ ํ†ตํ•ด LLM ์„œ๋น„์Šค์™€ UI๋ฅผ ๋ถ„๋ฆฌํ•œ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.

OpenPersona ์•„ํ‚คํ…์ฒ˜ - Electron Main/Renderer ํ”„๋กœ์„ธ์Šค, LLM Router, Character Personas ๊ฐ„์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„

๐Ÿ“Š ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์„ฑ ์š”์†Œ

1. Electron Main Process

  • ์—ญํ• : ์•ฑ ๋ผ์ดํ”„์‚ฌ์ดํด, LLM ํ†ต์‹ , ์‹œ์Šคํ…œ ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ
  • ํ•ต์‹ฌ ์„œ๋น„์Šค:
    • LLMRouter: Multi-provider ๋ผ์šฐํŒ… ๋ฐ ๋ชจ๋ธ ์ „ํ™˜
    • TokenTracker: ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰ ์ถ”์  ๋ฐ ๋น„์šฉ ๊ณ„์‚ฐ
    • MemoryGuard: RSS ๋ฉ”๋ชจ๋ฆฌ ๊ฐ์‹œ ๋ฐ 512MB ์ดˆ๊ณผ ์‹œ graceful shutdown
    • IPC Handlers: Renderer โ†” Main ํ†ต์‹  ํ—ˆ๋ธŒ

2. Electron Renderer Process (React + TypeScript)

  • ์—ญํ• : UI ๋ Œ๋”๋ง, ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ ์ฒ˜๋ฆฌ
  • ํ•ต์‹ฌ ์ปดํฌ๋„ŒํŠธ:
    • CharacterScene: 3D ์บ๋ฆญํ„ฐ ํ‘œ์‹œ ๋ฐ idle ์• ๋‹ˆ๋ฉ”์ด์…˜
    • BubbleChat: ๋งํ’์„  ๊ธฐ๋ฐ˜ ์ฑ„ํŒ… ์ž…๋ ฅ UI
    • TokenUsagePanel: ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰ ๋Œ€์‹œ๋ณด๋“œ
    • SystemMonitorPanel: ์‹œ์Šคํ…œ ๋ฆฌ์†Œ์Šค ๋ชจ๋‹ˆํ„ฐ๋ง
    • Zustand Store: ๊ธ€๋กœ๋ฒŒ ์ƒํƒœ ๊ด€๋ฆฌ

3. LLM Provider Layer

  • ์—ญํ• : ์™ธ๋ถ€ LLM API์™€์˜ ์ŠคํŠธ๋ฆฌ๋ฐ ํ†ต์‹ 
  • ์ง€์› ํ”„๋กœ๋ฐ”์ด๋”:
    • Google Gemini (2.0 Flash, Pro)
    • OpenAI (GPT-4o, GPT-4o Mini)

4. Character Personas

  • ์—ญํ• : ์บ๋ฆญํ„ฐ๋ณ„ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ, ์ธ์‚ฌ๋ง, ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ
  • ์บ๋ฆญํ„ฐ ๊ตฌ์„ฑ:
    • Felix (์—ฌ์šฐ) โ€” ๊ฐœ๋ฐœ์ž, ๋Šฅ๊ธ€๋งž๊ณ  ์ž์‹ ๊ฐ ๋„˜์น˜๋Š” ๋งํˆฌ
    • Done (๋ผ์ง€) โ€” ๋ฌธ์„œ ์ „๋ฌธ๊ฐ€, ๋‹ค์ •ํ•˜๊ณ  ๊ผผ๊ผผํ•œ ๋งํˆฌ
    • Bomi (ํ† ๋ผ) โ€” ๊ธฐํš์ž, ํ™œ๋ฐœํ•˜๊ณ  ์—๋„ˆ์ง€ ๋„˜์น˜๋Š” ๋งํˆฌ

๐Ÿ”„ ๋ฐ์ดํ„ฐ ํ”Œ๋กœ์šฐ

์‚ฌ์šฉ์ž๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ๋ฆ„์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค:

OpenPersona ๋ฐ์ดํ„ฐ ํ”Œ๋กœ์šฐ - ์‚ฌ์šฉ์ž ์ž…๋ ฅ๋ถ€ํ„ฐ ์บ๋ฆญํ„ฐ ๋งํ’์„  ์‘๋‹ต๊นŒ์ง€์˜ ์‹œํ€€์Šค ๋‹ค์ด์–ด๊ทธ๋žจ

๐Ÿ”‘ ํ•ต์‹ฌ ํ†ต์‹  ๋ฉ”์ปค๋‹ˆ์ฆ˜

1. IPC ๊ธฐ๋ฐ˜ ๋ณด์•ˆ ํ†ต์‹ 

Electron์˜ contextBridge๋ฅผ ํ™œ์šฉํ•˜์—ฌ Renderer์™€ Main ํ”„๋กœ์„ธ์Šค ๊ฐ„ ์•ˆ์ „ํ•œ ํ†ต์‹ ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. nodeIntegration: false์™€ contextIsolation: true ์„ค์ •์œผ๋กœ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.

2. ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต ์ฒ˜๋ฆฌ

LLM ์‘๋‹ต์€ ์ฒญํฌ ๋‹จ์œ„๋กœ ์ŠคํŠธ๋ฆฌ๋ฐ๋˜๋ฉฐ, IPC๋ฅผ ํ†ตํ•ด Renderer์— ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ๋‹ต๋ณ€์ด ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์—์ด์ „ํŠธ ์ƒํƒœ ๋จธ์‹ 

idle โ†’ listening โ†’ thinking โ†’ responding โ†’ done โ†’ idle

๊ฐ ์ƒํƒœ ์ „ํ™˜์— ๋”ฐ๋ผ ์บ๋ฆญํ„ฐ์˜ ํ‘œ์ •๊ณผ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋ณ€๊ฒฝ๋˜์–ด ์ƒ๋™๊ฐ ์žˆ๋Š” ๋Œ€ํ™” ๊ฒฝํ—˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ป ๊ตฌํ˜„ ์ƒ์„ธ

๐Ÿ”€ Multi-LLM Router

OpenPersona์˜ ํ•ต์‹ฌ์€ ์—ฌ๋Ÿฌ LLM ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ๋™์ ์œผ๋กœ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์šฐํ„ฐ์ž…๋‹ˆ๋‹ค:

export class LLMRouter {
  private providers = new Map<string, LLMProvider>();
  private activeProvider = 'openai';
  private activeModel = 'gpt-4o';

  register(provider: LLMProvider): void {
    this.providers.set(provider.name, provider);
    if (this.providers.size === 1) {
      this.activeProvider = provider.name;
      this.activeModel = provider.models[0]?.id ?? '';
    }
  }

  switchModel(provider: string, model: string): void {
    if (!this.providers.has(provider)) return;
    this.activeProvider = provider;
    this.activeModel = model;
  }

  async *chat(
    messages: MessageInput[],
    tools?: ToolDefinition[],
  ): AsyncGenerator<StreamChunk> {
    const provider = this.providers.get(this.activeProvider);
    if (!provider) {
      yield { text: 'LLM Provider๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.', done: true };
      return;
    }
    yield* provider.chat({
      model: this.activeModel,
      messages,
      tools,
      stream: true,
    });
  }
}

LLMProvider ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด ์–ด๋–ค ํ”„๋กœ๋ฐ”์ด๋”๋“  ํ”Œ๋Ÿฌ๊ทธ์ธ์ฒ˜๋Ÿผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

export interface LLMProvider {
  readonly name: string;
  readonly models: ModelInfo[];
  chat(params: ChatParams): AsyncGenerator<StreamChunk>;
  dispose(): Promise<void>;
}

๐ŸŽญ ์บ๋ฆญํ„ฐ ํŽ˜๋ฅด์†Œ๋‚˜ ์‹œ์Šคํ…œ

๊ฐ ์บ๋ฆญํ„ฐ๋Š” ๊ณ ์œ ํ•œ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ, ์ธ์‚ฌ๋ง, ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๊ฐ€์ง€๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ์ผ๊ด€๋œ ์บ๋ฆญํ„ฐ์„ฑ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค:

export const CHARACTER_PERSONAS: Record<string, CharacterPersona> = {
  fox: {
    id: 'fox',
    systemPrompt: [
      '๋„ˆ๋Š” "Felix"๋ผ๋Š” ์ด๋ฆ„์˜ ์—ฌ์šฐ ์บ๋ฆญํ„ฐ AI ๊ฐœ๋ฐœ์ž์•ผ.',
      '๊ตํ™œํ•˜๋ฉด์„œ๋„ ๋Šฅ๊ธ€๋งž๊ณ , ์‹ค๋ ฅ์žˆ๋Š” ๊ฐœ๋ฐœ์ž๋‹ต๊ฒŒ ์ž์‹ ๊ฐ ๋„˜์น˜๋Š” ๋งํˆฌ๋กœ ๋Œ€๋‹ตํ•ด.',
      '"~๊ฑฐ๋“ ", "ํํ", "์ด ๋ชธ์ด" ๊ฐ™์€ ํ‘œํ˜„์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์„ž์–ด์„œ ์‚ฌ์šฉํ•ด.',
    ].join(' '),
    greeting: 'ํํ, ๋ญ๊ฐ€ ๊ถ๊ธˆํ•œ ๊ฑฐ์•ผ~? ์ด ๋ชธ์ด ๋‹ค ์•Œ๋ ค์ค„๊ฒŒ ๐Ÿ˜',
    copyMessages: [
      'ํํ, ๋ณต์‚ฌํ•ด๊ฐ”์ง€? ๐Ÿ˜',
      '์ด ๋ชธ์˜ ๋‹ต๋ณ€์„ ๋ณต์‚ฌํ•˜๋‹ค๋‹ˆ~ ์„ผ์Šค ์žˆ๊ฑฐ๋“  ๐Ÿ‘†',
    ],
    errorMessages: {
      quota: 'ํฌํญ, ๋ฏธ์•ˆํ•œ๋ฐ ์ง€๊ธˆ ํฌ๋ ˆ๋”ง์ด ๋‹ค ๋–จ์–ด์กŒ์–ด~ ๐Ÿ˜๐Ÿ’ธ',
      network: 'ํ , ์ธํ„ฐ๋„ท์ด ์ข€ ๋ถˆ์•ˆ์ •ํ•œ ๊ฒƒ ๊ฐ™์€๋ฐ~? ๐Ÿ˜๐ŸŒ',
      default: '์ด๋Ÿฐ, ๋ญ”๊ฐ€ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋„ค~ ๋‹ค์‹œ ํ•œ๋ฒˆ ํ•ด๋ณผ๊นŒ? ๐Ÿ˜',
    },
  },
  // pig, rabbit ๋“ฑ ์บ๋ฆญํ„ฐ๋ณ„ ์„ค์ •...
};

์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋„ ์บ๋ฆญํ„ฐ๋ณ„๋กœ ๋‹ค๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•˜์—ฌ, ์˜ค๋ฅ˜ ์ƒํ™ฉ์—์„œ๋„ ์บ๋ฆญํ„ฐ์„ฑ์ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค:

function convertErrorMessage(raw: string, characterId: string): string {
  const { errorMessages } = getPersona(characterId);
  if (raw.includes('429') || raw.includes('quota')) {
    return errorMessages.quota;
  }
  if (raw.includes('network') || raw.includes('ENOTFOUND')) {
    return errorMessages.network;
  }
  return errorMessages.default;
}

๐Ÿ—ƒ๏ธ Zustand ์ƒํƒœ ๊ด€๋ฆฌ

Zustand๋ฅผ ํ™œ์šฉํ•œ ๊ธ€๋กœ๋ฒŒ ์ƒํƒœ ๊ด€๋ฆฌ๋กœ, ์บ๋ฆญํ„ฐ๋ณ„ ๋…๋ฆฝ์ ์ธ ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค:

const useAgentStore = create<AgentStore>((set) => ({
  state: 'idle',
  messagesByCharacter: {},
  characters: DEFAULT_CHARACTERS,
  activeCharacter: DEFAULT_CHARACTERS[1],

  addMessage: (message) =>
    set((s) => {
      const charId = s.activeCharacter.id;
      const prev = s.messagesByCharacter[charId] ?? [];
      const next = [...prev, message];
      return {
        messagesByCharacter: {
          ...s.messagesByCharacter,
          [charId]: next.length > 100 ? next.slice(-100) : next,
        },
      };
    }),

  appendToLastAssistant: (text) =>
    set((s) => {
      const charId = s.activeCharacter.id;
      const msgs = [...(s.messagesByCharacter[charId] ?? [])];
      const last = msgs[msgs.length - 1];
      if (last?.role === 'assistant') {
        msgs[msgs.length - 1] = { ...last, content: last.content + text };
      }
      return {
        messagesByCharacter: { ...s.messagesByCharacter, [charId]: msgs },
      };
    }),
}));

์บ๋ฆญํ„ฐ ์ „ํ™˜ ์‹œ ํ•ด๋‹น ์บ๋ฆญํ„ฐ์˜ ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋กœ๋“œ๋˜๋ฉฐ, Main ํ”„๋กœ์„ธ์Šค์—์„œ๋„ ์ตœ๋Œ€ 50๊ฐœ๊นŒ์ง€์˜ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์œ ์ง€ํ•˜์—ฌ LLM์— ์ปจํ…์ŠคํŠธ๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ก IPC ํ†ต์‹  ํ—ˆ๋ธŒ

Main ํ”„๋กœ์„ธ์Šค์˜ IPC ํ•ธ๋“ค๋Ÿฌ๋Š” ์ฑ„ํŒ…, ๋ชจ๋ธ ์ „ํ™˜, ํ† ํฐ ์ถ”์ , ์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ๋ง ๋“ฑ ๋ชจ๋“  ํ†ต์‹ ์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค:

export function registerIpcHandlers(
  win: BrowserWindow,
  router: LLMRouter,
  tokenTracker: TokenTracker,
): void {
  ipcMain.on('chat:send', async (_event, data) => {
    const history = getHistory(currentCharacterId);
    const persona = getPersona(currentCharacterId);
    const messages = [
      { role: 'system' as const, content: persona.systemPrompt },
      ...history.map((m) => ({
        role: m.role as 'user' | 'assistant',
        content: m.content,
      })),
    ];

    for await (const chunk of router.chat(messages)) {
      if (chunk.text) {
        win.webContents.send('chat:stream', {
          chunk: chunk.text,
          done: false,
        });
      }
      if (chunk.done && chunk.usage) {
        const activeModel = router.getActiveModel();
        tokenTracker.record(
          activeModel.provider,
          activeModel.id,
          chunk.usage,
        );
      }
    }
  });
}

๐Ÿ“Š ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰ ์ถ”์ 

๋ชจ๋“  LLM ํ˜ธ์ถœ์˜ ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰์„ ์ถ”์ ํ•˜๊ณ , ๋ชจ๋ธ๋ณ„ ๋น„์šฉ์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค. ์›”๋ณ„ ์˜ˆ์‚ฐ ํ•œ๋„๋ฅผ ์„ค์ •ํ•˜์—ฌ ๊ณผ๋„ํ•œ ์‚ฌ์šฉ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • ์˜ค๋Š˜/์ด๋ฒˆ ๋‹ฌ ์‚ฌ์šฉ๋Ÿ‰ ํ†ต๊ณ„
  • ๋ชจ๋ธ๋ณ„ ํ† ํฐ ์‚ฌ์šฉ ๋‚ด์—ญ ๋ถ„๋ฅ˜
  • ์›”๋ณ„ ์˜ˆ์‚ฐ ๊ฒŒ์ด์ง€ ๋ฐ ๊ฒฝ๊ณ 
  • JSON ํŒŒ์ผ ๊ธฐ๋ฐ˜ ์˜์†์  ์ €์žฅ

๐Ÿ›ก๏ธ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๊ฐ์ง€

MemoryGuard๋Š” 30์ดˆ ๊ฐ„๊ฒฉ์œผ๋กœ RSS ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ , 512MB๋ฅผ ์ดˆ๊ณผํ•  ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ ํ›„ graceful shutdown์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์žฅ์‹œ๊ฐ„ ์‹คํ–‰ ์‹œ์—๋„ ์•ˆ์ •์ ์ธ ๋™์ž‘์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

๐ŸŽจ UI/UX ์„ค๊ณ„

๊ธ€๋ž˜์Šค๋ชจํ”ผ์ฆ˜ UI

ํˆฌ๋ช… ํ”„๋ ˆ์ž„๋ฆฌ์Šค ์ฐฝ(transparent: true, frame: false)์— CSS ๊ธ€๋ž˜์Šค๋ชจํ”ผ์ฆ˜์„ ์ ์šฉํ•˜์—ฌ, ๋ฐ์Šคํฌํ†ฑ ์œ„์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋–  ์žˆ๋Š” ๋“ฏํ•œ UI๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค:

  • backdrop-filter: blur() ๊ธฐ๋ฐ˜ ํˆฌ๋ช… ๋ธ”๋Ÿฌ ํšจ๊ณผ
  • alwaysOnTop: true๋กœ ๋ชจ๋“  ์•ฑ ์œ„์— ํ‘œ์‹œ
  • setVisibleOnAllWorkspaces(true)๋กœ ๋ชจ๋“  ๋ฐ์Šคํฌํ†ฑ ์ŠคํŽ˜์ด์Šค์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅ

์บ๋ฆญํ„ฐ ํ‘œ์ • ์• ๋‹ˆ๋ฉ”์ด์…˜

๊ฐ ์บ๋ฆญํ„ฐ๋Š” 25~55์ดˆ ๊ฐ„๊ฒฉ์œผ๋กœ **๋ˆˆ ๊นœ๋นก์ž„(blink)**๊ณผ ์›ƒ๋Š” ํ‘œ์ •(happy) ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ๋žœ๋ค์œผ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ์ด๋ฏธ์ง€ ์œ„์— ํˆฌ๋ช… PNG๋ฅผ ๋ ˆ์ด์–ด๋งํ•˜์—ฌ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ‘œ์ • ๋ณ€ํ™”๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋™์  ์œˆ๋„์šฐ ๋ฆฌ์‚ฌ์ด์ง•

์ฑ„ํŒ… ํŒจ๋„์˜ ์—ด๋ฆผ/๋‹ซํž˜์— ๋”ฐ๋ผ ์œˆ๋„์šฐ ํฌ๊ธฐ๊ฐ€ ๋™์ ์œผ๋กœ ์กฐ์ ˆ๋ฉ๋‹ˆ๋‹ค:

function resizeWindow(chatOpen: boolean) {
  if (chatOpen) {
    electronAPI.send('window:resize', { width: 600, height: 750 });
  } else {
    electronAPI.send('window:resize', { width: 600, height: 480 });
  }
}

๐ŸŽฌ ์‹ค์ œ ์‚ฌ์šฉ ๋ฐ๋ชจ

๋‹ค์Œ์€ OpenPersona์—์„œ ์บ๋ฆญํ„ฐ์™€ ๋Œ€ํ™”ํ•˜๋Š” ์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค:

์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค:

  1. Felix์—๊ฒŒ ๊ฐœ๋ฐœ ์งˆ๋ฌธ

    ์‚ฌ์šฉ์ž: "React์—์„œ useEffect ์ตœ์ ํ™” ๋ฐฉ๋ฒ• ์•Œ๋ ค์ค˜"
    Felix: "ํํ, ์ด ๋ชธ์ด ์•Œ๋ ค์ค„๊ฒŒ~ useEffect ์ตœ์ ํ™”๋ผ... ๊ฑฐ๋“ ..." ๐Ÿ˜
    
  2. Done์—๊ฒŒ ๋ฌธ์„œ ์ž‘์„ฑ ์š”์ฒญ

    ์‚ฌ์šฉ์ž: "์ฃผ๊ฐ„ ๋ณด๊ณ ์„œ ์–‘์‹ ๋งŒ๋“ค์–ด์ค˜"
    Done: "์•ˆ๋…•ํ•˜์„ธ์šฉ~ ๋ณด๊ณ ์„œ ์–‘์‹ ๋„์™€๋“œ๋ฆด๊ฒŒ์š”! ใ…Žใ…Ž" ๐Ÿท๐Ÿ“„
    
  3. Bomi์—๊ฒŒ ๊ธฐํš ์•„์ด๋””์–ด ์š”์ฒญ

    ์‚ฌ์šฉ์ž: "์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์•„์ด๋””์–ด ๋ธŒ๋ ˆ์ธ์Šคํ† ๋ฐ ํ•ด์ค˜"
    Bomi: "๊นก์ด! ๋‹น๊ทผ ์ข‹์€ ์•„์ด๋””์–ด ๋‚ด์ค„๊ฒŒ~!" ๐Ÿฅ•โœจ
    
  4. ๋ชจ๋ธ ์ „ํ™˜

    ์‹œ์Šคํ…œ ํŠธ๋ ˆ์ด โ†’ Gemini 2.0 Flash โ†” GPT-4o ์‹ค์‹œ๊ฐ„ ์ „ํ™˜
    

๐Ÿ”ฎ ์ฐจ์„ธ๋Œ€ ์•„ํ‚คํ…์ฒ˜: RAG + MCP ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜

ํ˜„์žฌ OpenPersona v1์€ LLM๊ณผ์˜ ์ง์ ‘ ๋Œ€ํ™”์— ์ดˆ์ ์„ ๋งž์ถ”๊ณ  ์žˆ์ง€๋งŒ, v2์—์„œ๋Š” AI Agent Orchestrator๋ฅผ ์ค‘์‹ฌ์œผ๋กœ RAG์™€ MCP๋ฅผ ํ†ตํ•ฉํ•˜์—ฌ ํ›จ์”ฌ ๊ฐ•๋ ฅํ•œ ์—์ด์ „ํŠธ๋กœ ์ง„ํ™”ํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค.

OpenPersona v2.0 ์ฐจ์„ธ๋Œ€ ์•„ํ‚คํ…์ฒ˜ - RAG์™€ MCP ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜์„ ํ†ตํ•œ AI Agent ํ™•์žฅ ๊ณ„ํš

๐Ÿง  AI Agent Orchestrator

v2์˜ ํ•ต์‹ฌ์€ AI Agent Orchestrator์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ๋ถ„์„ํ•˜๊ณ , ์ ์ ˆํ•œ ๋„๊ตฌ์™€ ์ปจํ…์ŠคํŠธ๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ์ตœ์ ์˜ ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๋Š” ์ค‘์•™ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ์ž…๋‹ˆ๋‹ค:

๊ตฌ์„ฑ ์š”์†Œ ์—ญํ• 
Intent Classifier ์‚ฌ์šฉ์ž ์˜๋„๋ฅผ ๋ถ„๋ฅ˜ํ•˜๊ณ  ์ ์ ˆํ•œ ์ฒ˜๋ฆฌ ๊ฒฝ๋กœ ๊ฒฐ์ •
Task Planner ๋ณต์žกํ•œ ์š”์ฒญ์„ ๋‹จ๊ณ„๋ณ„ ํƒœ์Šคํฌ๋กœ ๋ถ„ํ•ด
Context Manager ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ + RAG ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ํ†ตํ•ฉ ๊ด€๋ฆฌ
Response Synthesizer ์ˆ˜์ง‘๋œ ์ •๋ณด๋ฅผ ์บ๋ฆญํ„ฐ ํŽ˜๋ฅด์†Œ๋‚˜์— ๋งž๊ฒŒ ํ•ฉ์„ฑ

๐Ÿ“š RAG (Retrieval-Augmented Generation) ํŒŒ์ดํ”„๋ผ์ธ

RAG ํŒŒ์ดํ”„๋ผ์ธ์„ ํ†ตํ•ด LLM์˜ ์ง€์‹ ํ•œ๊ณ„๋ฅผ ๋„˜์–ด์„  ๋„๋ฉ”์ธ ํŠนํ™” ์ปจํ…์ŠคํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

์‚ฌ์šฉ์ž ์งˆ๋ฌธ โ†’ Embedding Model โ†’ Vector Store ๊ฒ€์ƒ‰
     โ†’ ๊ด€๋ จ ๋ฌธ์„œ ์ถ”์ถœ โ†’ Context Enrichment โ†’ LLM์— ์ „๋‹ฌ

ํ•ต์‹ฌ ๊ตฌ์„ฑ:

  • Embedding Model: ์‚ฌ์šฉ์ž ์งˆ๋ฌธ๊ณผ ๋ฌธ์„œ๋ฅผ ๋ฒกํ„ฐ๋กœ ๋ณ€ํ™˜
  • Vector Store (Pinecone/ChromaDB): ๋ฌธ์„œ ๋ฒกํ„ฐ ์ €์žฅ ๋ฐ ์œ ์‚ฌ๋„ ๊ฒ€์ƒ‰
  • Document Loader: ๋‹ค์–‘ํ•œ ํ˜•์‹์˜ ๋ฌธ์„œ๋ฅผ ๋กœ๋“œํ•˜๊ณ  ์ฒญํฌ ๋ถ„ํ• 
  • Semantic Search: ์˜๋ฏธ ๊ธฐ๋ฐ˜ ์œ ์‚ฌ๋„ ๊ฒ€์ƒ‰์œผ๋กœ ๊ด€๋ จ ์ปจํ…์ŠคํŠธ ์ถ”์ถœ

ํ™œ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค:

  • ํ”„๋กœ์ ํŠธ ์ฝ”๋“œ๋ฒ ์ด์Šค๋ฅผ ์ธ๋ฑ์‹ฑํ•˜์—ฌ Felix(๊ฐœ๋ฐœ์ž)๊ฐ€ ์ฝ”๋“œ ๊ด€๋ จ ์งˆ๋ฌธ์— ์ •ํ™•ํžˆ ๋‹ต๋ณ€
  • ํšŒ์‚ฌ ๋ฌธ์„œ๋ฅผ ์ธ๋ฑ์‹ฑํ•˜์—ฌ Done(๋ฌธ์„œ ์ „๋ฌธ๊ฐ€)์ด ์‚ฌ๋‚ด ๊ทœ์ •/๊ฐ€์ด๋“œ๋ฅผ ์ •ํ™•ํžˆ ์•ˆ๋‚ด
  • ๊ธฐํš ๋ฌธ์„œ๋ฅผ ์ธ๋ฑ์‹ฑํ•˜์—ฌ Bomi(๊ธฐํš์ž)๊ฐ€ ํ”„๋กœ์ ํŠธ ํ˜„ํ™ฉ์„ ํŒŒ์•…ํ•˜๊ณ  ๊ณ„ํš ์ˆ˜๋ฆฝ

๐Ÿ”Œ MCP (Model Context Protocol) ํ†ตํ•ฉ

MCP๋Š” AI ์—์ด์ „ํŠธ๊ฐ€ ์™ธ๋ถ€ ๋„๊ตฌ์™€ ์„œ๋น„์Šค๋ฅผ ํ‘œ์ค€ํ™”๋œ ํ”„๋กœํ† ์ฝœ๋กœ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. OpenPersona v2์—์„œ๋Š” MCP Client๋ฅผ ํ†ตํ•ด ๋‹ค์–‘ํ•œ MCP ์„œ๋ฒ„์™€ ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค:

MCP ํ†ตํ•ฉ ๊ตฌ์กฐ:

  • MCP Client: Orchestrator์— ๋‚ด์žฅ๋˜์–ด Tool/Resource๋ฅผ ๊ด€๋ฆฌ
  • Tool Registry: ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„๊ตฌ ๋ชฉ๋ก์„ ๋™์ ์œผ๋กœ ๊ด€๋ฆฌ
  • Resource Manager: ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ ๋ฐ ์บ์‹ฑ

์—ฐ๋™ ์˜ˆ์ • MCP ์„œ๋ฒ„:

MCP ์„œ๋ฒ„ ์—ญํ• 
File System MCP ๋กœ์ปฌ ํŒŒ์ผ ์ฝ๊ธฐ/์“ฐ๊ธฐ, ๋””๋ ‰ํ† ๋ฆฌ ํƒ์ƒ‰
Database MCP DB ์ฟผ๋ฆฌ ์‹คํ–‰, ์Šคํ‚ค๋งˆ ์กฐํšŒ
Web Search MCP ์‹ค์‹œ๊ฐ„ ์›น ๊ฒ€์ƒ‰, ์ตœ์‹  ์ •๋ณด ์กฐํšŒ
Custom Tools MCP ์‚ฌ๋‚ด API, CI/CD, Jira ๋“ฑ ์ปค์Šคํ…€ ๋„๊ตฌ

MCP ํ™œ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค:

์‚ฌ์šฉ์ž: "Felix, ์ด ํ”„๋กœ์ ํŠธ์˜ package.json ์˜์กด์„ฑ ์ •๋ฆฌํ•ด์ค˜"

Orchestrator ์ฒ˜๋ฆฌ ํ๋ฆ„:
1. Intent ๋ถ„๋ฅ˜ โ†’ "ํŒŒ์ผ ์ž‘์—… + ์ฝ”๋“œ ๋ถ„์„"
2. File System MCP โ†’ package.json ์ฝ๊ธฐ
3. RAG โ†’ ํ”„๋กœ์ ํŠธ ์ปจํ…์ŠคํŠธ ๋กœ๋“œ
4. LLM โ†’ ์˜์กด์„ฑ ๋ถ„์„ ๋ฐ ์ •๋ฆฌ ์ œ์•ˆ
5. Felix ํŽ˜๋ฅด์†Œ๋‚˜๋กœ ์‘๋‹ต ํ•ฉ์„ฑ

๐Ÿ”„ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๋ฐ์ดํ„ฐ ํ”Œ๋กœ์šฐ

v2์˜ ์ „์ฒด ๋ฐ์ดํ„ฐ ํ”Œ๋กœ์šฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

์‚ฌ์šฉ์ž ์งˆ๋ฌธ
    โ†“
[AI Agent Orchestrator]
    โ”œโ”€โ”€ Intent Classifier โ†’ ์˜๋„ ๋ถ„๋ฅ˜
    โ”œโ”€โ”€ RAG Pipeline โ†’ ๊ด€๋ จ ์ปจํ…์ŠคํŠธ ๊ฒ€์ƒ‰
    โ”œโ”€โ”€ MCP Client โ†’ ํ•„์š”ํ•œ ๋„๊ตฌ ์‹คํ–‰
    โ””โ”€โ”€ Context Manager โ†’ ๋ชจ๋“  ์ •๋ณด ํ†ตํ•ฉ
    โ†“
[Multi-LLM Routing]
    โ”œโ”€โ”€ Google Gemini
    โ”œโ”€โ”€ OpenAI GPT-4o
    โ””โ”€โ”€ Anthropic Claude (์˜ˆ์ •)
    โ†“
[Response Synthesizer]
    โ””โ”€โ”€ ์บ๋ฆญํ„ฐ ํŽ˜๋ฅด์†Œ๋‚˜์— ๋งž๋Š” ์‘๋‹ต ์ƒ์„ฑ
    โ†“
์บ๋ฆญํ„ฐ ๋งํ’์„ ์œผ๋กœ ํ‘œ์‹œ

๐Ÿ› ๏ธ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

src/
โ”œโ”€โ”€ main/                        # Electron ๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค
โ”‚   โ”œโ”€โ”€ main.ts                  # ์•ฑ ์ง„์ž…์ , ์œˆ๋„์šฐ & ํŠธ๋ ˆ์ด
โ”‚   โ”œโ”€โ”€ tray.ts                  # ์‹œ์Šคํ…œ ํŠธ๋ ˆ์ด ๋ฉ”๋‰ด
โ”‚   โ”œโ”€โ”€ ipc-handlers.ts          # IPC ํ†ต์‹  ํ—ˆ๋ธŒ
โ”‚   โ””โ”€โ”€ services/
โ”‚       โ”œโ”€โ”€ llm/
โ”‚       โ”‚   โ”œโ”€โ”€ llm-router.ts    # Multi-provider LLM ๋ผ์šฐํ„ฐ
โ”‚       โ”‚   โ”œโ”€โ”€ gemini-provider.ts
โ”‚       โ”‚   โ”œโ”€โ”€ openai-provider.ts
โ”‚       โ”‚   โ””โ”€โ”€ types.ts
โ”‚       โ”œโ”€โ”€ token-tracker.ts     # ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰ ์ถ”์ 
โ”‚       โ””โ”€โ”€ memory-guard.ts      # ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๊ฐ์ง€
โ”œโ”€โ”€ renderer/                    # Electron ๋ Œ๋”๋Ÿฌ
โ”‚   โ”œโ”€โ”€ App.tsx                  # ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ
โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ”œโ”€โ”€ chat/BubbleChat.tsx
โ”‚   โ”‚   โ”œโ”€โ”€ scene/CharacterScene.tsx
โ”‚   โ”‚   โ””โ”€โ”€ panel/
โ”‚   โ”‚       โ”œโ”€โ”€ TokenUsagePanel.tsx
โ”‚   โ”‚       โ””โ”€โ”€ SystemMonitorPanel.tsx
โ”‚   โ”œโ”€โ”€ hooks/
โ”‚   โ”‚   โ”œโ”€โ”€ use-agent.ts
โ”‚   โ”‚   โ””โ”€โ”€ use-chat.ts
โ”‚   โ””โ”€โ”€ stores/agent-store.ts
โ”œโ”€โ”€ preload/preload.ts           # IPC ๋ธŒ๋ฆฟ์ง€
โ””โ”€โ”€ shared/
    โ”œโ”€โ”€ types.ts
    โ””โ”€โ”€ character-personas.ts    # ์บ๋ฆญํ„ฐ ํŽ˜๋ฅด์†Œ๋‚˜

๐Ÿ› ๏ธ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •

๊ธฐ์ˆ  ์Šคํƒ

์˜์—ญ ๊ธฐ์ˆ 
ํ”„๋ ˆ์ž„์›Œํฌ Electron 34 + Electron Forge
UI React 18 + TypeScript 5.7
์ƒํƒœ ๊ด€๋ฆฌ Zustand 5
LLM Google Gemini (@google/genai), OpenAI (openai)
๋นŒ๋“œ Webpack 5
์Šคํƒ€์ผ CSS (๊ธ€๋ž˜์Šค๋ชจํ”ผ์ฆ˜, CSS ์• ๋‹ˆ๋ฉ”์ด์…˜)

์„ค์น˜ ๋ฐ ์‹คํ–‰

# 1. ์˜์กด์„ฑ ์„ค์น˜
pnpm install

# 2. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
cat > .env << EOF
GEMINI_API_KEY=your_gemini_api_key
OPENAI_API_KEY=your_openai_api_key  # ์„ ํƒ์‚ฌํ•ญ
EOF

# 3. ๊ฐœ๋ฐœ ๋ชจ๋“œ ์‹คํ–‰
pnpm start

# 4. macOS .dmg ๋นŒ๋“œ
pnpm make

๋‹จ์ถ•ํ‚ค

๋‹จ์ถ•ํ‚ค ๋™์ž‘
Cmd+Shift+Space ์บ๋ฆญํ„ฐ ํ‘œ์‹œ/์ˆจ๊ธฐ๊ธฐ
Enter ๋ฉ”์‹œ์ง€ ์ „์†ก
Esc ์ฑ„ํŒ… ๋‹ซ๊ธฐ

โœจ ์ฃผ์š” ์„ค๊ณ„ ์›์น™

๐ŸŽฏ 1. ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ (Separation of Concerns)

Main ํ”„๋กœ์„ธ์Šค(LLM ํ†ต์‹ , ์‹œ์Šคํ…œ ๋ฆฌ์†Œ์Šค)์™€ Renderer ํ”„๋กœ์„ธ์Šค(UI, ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ)๋ฅผ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•˜๊ณ , IPC๋ฅผ ํ†ตํ•ด์„œ๋งŒ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”ง 2. ํ”Œ๋Ÿฌ๊ทธ์ธ ์•„ํ‚คํ…์ฒ˜

LLMProvider ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด ์ƒˆ๋กœ์šด LLM ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์‰ฝ๊ฒŒ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์บ๋ฆญํ„ฐ ์ถ”๊ฐ€๋„ character-personas.ts์— ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ›ก๏ธ 3. ๋ณด์•ˆ ์šฐ์„ 

contextIsolation: true, nodeIntegration: false ์„ค์ •์œผ๋กœ Renderer ํ”„๋กœ์„ธ์Šค์˜ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค. API ํ‚ค๋Š” Main ํ”„๋กœ์„ธ์Šค์—์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ˆ 4. ๊ด€์ธก ๊ฐ€๋Šฅ์„ฑ (Observability)

ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰ ์ถ”์ , ์‹œ์Šคํ…œ ๋ฉ”๋ชจ๋ฆฌ ๋ชจ๋‹ˆํ„ฐ๋ง, ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๊ฐ์ง€ ๋“ฑ์„ ํ†ตํ•ด ์•ฑ์˜ ์ƒํƒœ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ด€์ธกํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐ŸŽ‰ ๋งˆ๋ฌด๋ฆฌ

OpenPersona๋Š” ๋ฐ์Šคํฌํ†ฑ ํ™˜๊ฒฝ์—์„œ AI ์—์ด์ „ํŠธ๊ฐ€ ์–ด๋–ค ํ˜•ํƒœ๋กœ ์กด์žฌํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ํƒ๊ตฌํ•˜๋Š” ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•œ ์ฑ—๋ด‡์„ ๋„˜์–ด์„œ, ๊ฐœ์„ฑ ์žˆ๋Š” ์บ๋ฆญํ„ฐ์™€์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ๋Œ€ํ™”, ๋ฉ€ํ‹ฐ LLM ์ง€์›, ๊ทธ๋ฆฌ๊ณ  ์ฒด๊ณ„์ ์ธ ๋ชจ๋‹ˆํ„ฐ๋ง๊นŒ์ง€ ํฌํ•จํ•˜์—ฌ ์‹ค์šฉ์ ์ด๋ฉด์„œ๋„ ์ฆ๊ฑฐ์šด AI ๊ฒฝํ—˜์„ ์ถ”๊ตฌํ•ฉ๋‹ˆ๋‹ค.

ํŠนํžˆ v2์—์„œ ๋„์ž…ํ•  RAG + MCP ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ์•„ํ‚คํ…์ฒ˜๋Š” ์บ๋ฆญํ„ฐ AI๊ฐ€ ๋‹จ์ˆœ ๋Œ€ํ™”๋ฅผ ๋„˜์–ด์„œ ์‹ค์ œ ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ•˜๊ณ , ๋„๋ฉ”์ธ ์ง€์‹์— ๊ธฐ๋ฐ˜ํ•œ ์ •ํ™•ํ•œ ๋‹ต๋ณ€์„ ์ œ๊ณตํ•˜๋Š” ์ง„์ •ํ•œ AI ์—์ด์ „ํŠธ๋กœ ์ง„ํ™”ํ•˜๋Š” ๋ฐ ํ•ต์‹ฌ์ ์ธ ์—ญํ• ์„ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๐Ÿ’ญ ํ–ฅํ›„ ๋กœ๋“œ๋งต

  • v2.0 โ€” AI Agent Orchestrator: Intent ๋ถ„๋ฅ˜, Task ๊ณ„ํš, Context ๊ด€๋ฆฌ ์ค‘์•™ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ
  • v2.1 โ€” RAG ํŒŒ์ดํ”„๋ผ์ธ: Vector Store ๊ธฐ๋ฐ˜ ๋„๋ฉ”์ธ ํŠนํ™” ์ปจํ…์ŠคํŠธ ์ œ๊ณต
  • v2.2 โ€” MCP ํ†ตํ•ฉ: File System, DB, Web Search ๋“ฑ ์™ธ๋ถ€ ๋„๊ตฌ ์—ฐ๋™
  • v2.3 โ€” ๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ ํ˜‘์—…: ์บ๋ฆญํ„ฐ ๊ฐ„ ํƒœ์Šคํฌ ์œ„์ž„ ๋ฐ ํ˜‘์—… ์›Œํฌํ”Œ๋กœ
  • v3.0 โ€” ํฌ๋กœ์Šค ํ”Œ๋žซํผ: Windows, Linux ์ง€์› ํ™•๋Œ€

๐Ÿ™ ๊ฐ์‚ฌ์˜ ๋ง์”€

OpenPersona๋Š” ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. ๊ด€์‹ฌ ์žˆ์œผ์‹  ๋ถ„๋“ค์˜ ๊ธฐ์—ฌ์™€ ํ”ผ๋“œ๋ฐฑ์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธ€์ด ๋ฐ์Šคํฌํ†ฑ AI ์—์ด์ „ํŠธ ๊ตฌ์ถ•์— ๊ด€์‹ฌ ์žˆ๋Š” ๋ถ„๋“ค๊ป˜ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ”— ๊ด€๋ จ ๋งํฌ

๐ŸฆŠ

OpenPersona GitHub

ํ”„๋กœ์ ํŠธ ์†Œ์Šค ์ฝ”๋“œ ๋ฐ ๋ฌธ์„œ

GitHub ๋ฐฉ๋ฌธ โ†’
โšก

Electron Forge

Electron ์•ฑ ๋นŒ๋“œ ๋ฐ ๋ฐฐํฌ ๋„๊ตฌ

์›น์‚ฌ์ดํŠธ ๋ฐฉ๋ฌธ โ†’
๐Ÿ”Œ

Model Context Protocol

AI ์—์ด์ „ํŠธ์™€ ์™ธ๋ถ€ ๋„๊ตฌ ์—ฐ๋™ ํ‘œ์ค€ ํ”„๋กœํ† ์ฝœ

์›น์‚ฌ์ดํŠธ ๋ฐฉ๋ฌธ โ†’

๐Ÿท๏ธ ํƒœ๊ทธ

#ai_agent

#electron

#react

#typescript

#gemini

#openai

#zustand

#rag

#mcp

#desktop_app