์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿค– Clojure๋กœ ๊ตฌํ˜„ํ•˜๋Š” ์ธ๊ณต์ง€๋Šฅ ์ฑ—๋ด‡: ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์œผ๋กœ ๋งŒ๋“œ๋Š” ๋˜‘๋˜‘ํ•œ ๋Œ€ํ™” ์‹œ์Šคํ…œ

๐Ÿค– Clojure๋กœ ๊ตฌํ˜„ํ•˜๋Š” ์ธ๊ณต์ง€๋Šฅ ์ฑ—๋ด‡: ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์œผ๋กœ ๋งŒ๋“œ๋Š” ๋˜‘๋˜‘ํ•œ ๋Œ€ํ™” ์‹œ์Šคํ…œ

Lisp์˜ ์šฐ์•„ํ•จ๊ณผ JVM์˜ ๊ฐ•๋ ฅํ•จ์„ ๊ฒฐํ•ฉํ•œ ์ฑ—๋ด‡ ๊ฐœ๋ฐœ ์™„๋ฒฝ ๊ฐ€์ด๋“œ ๐Ÿ’ฌ

Clojure (defn chat []) AI Response Processing... ๐Ÿ’ก Functional AI

์•ˆ๋…•, ์นœ๊ตฌ! ๐Ÿ˜Š ์˜ค๋Š˜์€ ์ •๋ง ํฅ๋ฏธ์ง„์ง„ํ•œ ์ฃผ์ œ๋กœ ์ด์•ผ๊ธฐ๋ฅผ ๋‚˜๋ˆ ๋ณผ ๊ฑฐ์•ผ. ๋ฐ”๋กœ Clojure๋กœ ์ธ๊ณต์ง€๋Šฅ ์ฑ—๋ด‡์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์ด์ง€. "Clojure๊ฐ€ ๋ญ์•ผ?"๋ผ๊ณ  ๋ฌผ์„ ์ˆ˜๋„ ์žˆ๊ฒ ์ง€๋งŒ, ๊ฑฑ์ • ๋งˆ! ์ฐจ๊ทผ์ฐจ๊ทผ ์žฌ๋ฏธ์žˆ๊ฒŒ ์„ค๋ช…ํ•ด์ค„๊ฒŒ.

์š”์ฆ˜ ChatGPT, Bard ๊ฐ™์€ AI ์ฑ—๋ด‡๋“ค์ด ๋Œ€์„ธ์ž–์•„? ๐Ÿ”ฅ ๊ทผ๋ฐ ์ด๋Ÿฐ ์ฑ—๋ด‡์„ ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณผ ์ˆ˜ ์žˆ๋‹ค๋ฉด ์–ผ๋งˆ๋‚˜ ๋ฉ‹์งˆ๊นŒ? ํŠนํžˆ Clojure๋ผ๋Š” ๋…ํŠนํ•˜๊ณ  ๊ฐ•๋ ฅํ•œ ์–ธ์–ด๋กœ ๋ง์ด์•ผ. Clojure๋Š” ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์ •์ˆ˜๋ฅผ ๋‹ด๊ณ  ์žˆ์œผ๋ฉด์„œ๋„ JVM ์œ„์—์„œ ๋Œ์•„๊ฐ€๋Š” ์‹ค์šฉ์ ์ธ ์–ธ์–ด๊ฑฐ๋“ . ๋งˆ์น˜ ์ฒ ํ•™์ž์˜ ์‚ฌ๊ณ ๋ฐฉ์‹๊ณผ ์—”์ง€๋‹ˆ์–ด์˜ ์‹ค์šฉ์„ฑ์„ ๋™์‹œ์— ๊ฐ€์ง„ ๊ฒƒ ๊ฐ™๋‹ฌ๊นŒ? ๐ŸŽฏ

๐ŸŒŸ Clojure๊ฐ€ ๋ญ๊ธธ๋ž˜? ์ฑ—๋ด‡ ๊ฐœ๋ฐœ์— ์™œ ์ข‹์„๊นŒ?

๋จผ์ € Clojure์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž. Clojure๋Š” 2007๋…„ Rich Hickey๊ฐ€ ๋งŒ๋“  Lisp ๊ณ„์—ด์˜ ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์•ผ. "Lisp? ๊ทธ๊ฒŒ ๋ญ”๋ฐ?"๋ผ๊ณ  ํ•  ์ˆ˜๋„ ์žˆ๋Š”๋ฐ, Lisp๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์˜ ํ• ์•„๋ฒ„์ง€๋ป˜ ๋˜๋Š” ์–ธ์–ด๋กœ, ๊ด„ํ˜ธ๊ฐ€ ์—„์ฒญ ๋งŽ์ด ๋‚˜์˜ค๋Š” ๊ฒŒ ํŠน์ง•์ด์ง€. ๐Ÿ˜…

๊ทผ๋ฐ Clojure๊ฐ€ ์ฑ—๋ด‡ ๊ฐœ๋ฐœ์— ์™œ ์ข‹๋ƒ๊ณ ? ๋ช‡ ๊ฐ€์ง€ ์ด์œ ๊ฐ€ ์žˆ์–ด:

๐ŸŽฏ Clojure์˜ ์ฑ—๋ด‡ ๊ฐœ๋ฐœ ๊ฐ•์ 

1. ๋ถˆ๋ณ€์„ฑ(Immutability)
๋ฐ์ดํ„ฐ๊ฐ€ ํ•œ๋ฒˆ ๋งŒ๋“ค์–ด์ง€๋ฉด ๋ณ€ํ•˜์ง€ ์•Š์•„. ์ด๊ฒŒ ์™œ ์ข‹๋ƒ๋ฉด, ์ฑ—๋ด‡์ด ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž์™€ ๋™์‹œ์— ๋Œ€ํ™”ํ•  ๋•Œ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ผฌ์ผ ์—ผ๋ ค๊ฐ€ ์—†๊ฑฐ๋“ . ๋งˆ์น˜ ๊ฐ ๋Œ€ํ™”๊ฐ€ ๋…๋ฆฝ๋œ ํƒ€์ž„๋ผ์ธ์„ ๊ฐ€์ง€๋Š” ๊ฒƒ์ฒ˜๋Ÿผ! โฐ

2. ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ
์ˆ˜๋ฐฑ, ์ˆ˜์ฒœ ๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ์ฑ—๋ด‡๊ณผ ๋Œ€ํ™”ํ•ด๋„ ๋ฌธ์ œ์—†์–ด. Clojure๋Š” ํƒœ์ƒ๋ถ€ํ„ฐ ๋™์‹œ์„ฑ์„ ๊ณ ๋ คํ•ด์„œ ์„ค๊ณ„๋๊ฑฐ๋“ . ๐Ÿš€

3. REPL ๊ธฐ๋ฐ˜ ๊ฐœ๋ฐœ
์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด์„œ ๋ฐ”๋กœ๋ฐ”๋กœ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์–ด. ์ฑ—๋ด‡์˜ ์‘๋‹ต์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•˜๋ฉด์„œ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑฐ์ง€. ์ด๊ฑด ์ •๋ง ๊ฐœ๋ฐœ ์†๋„๋ฅผ ์—„์ฒญ๋‚˜๊ฒŒ ๋†’์—ฌ์ค˜! โšก

4. JVM ์ƒํƒœ๊ณ„ ํ™œ์šฉ
Java๋กœ ๋งŒ๋“ค์–ด์ง„ ์ˆ˜๋งŽ์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ๋จธ์‹ ๋Ÿฌ๋‹, ์ž์—ฐ์–ด ์ฒ˜๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์„ ์ž์œ ๋กญ๊ฒŒ ๊ฐ€์ ธ๋‹ค ์“ธ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋œป์ด์ง€. ๐ŸŽ

๐Ÿ’ก ์žฌ๋Šฅ๋„ท ํŒ: ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋ฅผ ๋ฐฐ์šฐ๊ณ  ์‹ถ๊ฑฐ๋‚˜ ์ฑ—๋ด‡ ๊ฐœ๋ฐœ ํ”„๋กœ์ ํŠธ์— ๋„์›€์ด ํ•„์š”ํ•˜๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท์—์„œ ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด. ์‹ค์ „ ๊ฒฝํ—˜์ด ํ’๋ถ€ํ•œ ๊ฐœ๋ฐœ์ž๋“ค์ด ๋ฉ˜ํ† ๋ง์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ๊ฑฐ๋“ !

๐Ÿ› ๏ธ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ธํŒ…: ์ฒซ ๊ฑธ์Œ๋ถ€ํ„ฐ ์ฐจ๊ทผ์ฐจ๊ทผ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ? ๋จผ์ € ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์„ ์„ธํŒ…ํ•ด์•ผ ํ•ด. ๊ฑฑ์ • ๋งˆ, ์ƒ๊ฐ๋ณด๋‹ค ์–ด๋ ต์ง€ ์•Š์•„! ๐Ÿ˜Š

๐Ÿ“ฆ ํ•„์š”ํ•œ ๋„๊ตฌ๋“ค

1. Java Development Kit (JDK)
Clojure๋Š” JVM ์œ„์—์„œ ๋Œ์•„๊ฐ€๋‹ˆ๊นŒ JDK๊ฐ€ ํ•„์ˆ˜์•ผ. JDK 11 ์ด์ƒ์„ ์ถ”์ฒœํ•ด. Oracle JDK๋‚˜ OpenJDK ๋‘˜ ๋‹ค ๊ดœ์ฐฎ์•„.

2. Leiningen
Clojure์˜ ํ”„๋กœ์ ํŠธ ๊ด€๋ฆฌ ๋„๊ตฌ์•ผ. Node.js์˜ npm์ด๋‚˜ Python์˜ pip ๊ฐ™์€ ๊ฑฐ๋ผ๊ณ  ๋ณด๋ฉด ๋ผ. ์„ค์น˜๋Š” ๊ฐ„๋‹จํ•ด:

# macOS/Linux
brew install leiningen

# Windows
# leiningen.org์—์„œ lein.bat ๋‹ค์šด๋กœ๋“œ

3. ์—๋””ํ„ฐ/IDE
๊ฐœ์ธ์ ์œผ๋กœ๋Š” ๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋ฅผ ์ถ”์ฒœํ•ด:
- VS Code + Calva ํ”Œ๋Ÿฌ๊ทธ์ธ (์ดˆ๋ณด์ž ์ถ”์ฒœ! ๐ŸŒฑ)
- IntelliJ IDEA + Cursive ํ”Œ๋Ÿฌ๊ทธ์ธ (๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ!)
- Emacs + CIDER (๊ณ ์ˆ˜์šฉ ๐Ÿ˜Ž)

๐ŸŽฌ ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ํ•˜๊ธฐ

ํ„ฐ๋ฏธ๋„์„ ์—ด๊ณ  ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•ด๋ด:

lein new app chatbot-ai
cd chatbot-ai

์ง ! ๐ŸŽ‰ ์ด์ œ ๊ธฐ๋ณธ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๊ฐ€ ๋งŒ๋“ค์–ด์กŒ์–ด. project.clj ํŒŒ์ผ์„ ์—ด์–ด๋ณด๋ฉด ํ”„๋กœ์ ํŠธ ์„ค์ •์ด ์žˆ์„ ๊ฑฐ์•ผ. ์—ฌ๊ธฐ์— ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์„ ์ถ”๊ฐ€ํ•  ๊ฑฐ์•ผ.

๐Ÿง  ์ฑ—๋ด‡์˜ ๋‘๋‡Œ ๋งŒ๋“ค๊ธฐ: ์ž์—ฐ์–ด ์ฒ˜๋ฆฌ ๊ธฐ์ดˆ

์ฑ—๋ด‡์ด ์‚ฌ๋žŒ์˜ ๋ง์„ ์ดํ•ดํ•˜๋ ค๋ฉด ์ž์—ฐ์–ด ์ฒ˜๋ฆฌ(NLP)๊ฐ€ ํ•„์š”ํ•ด. Clojure์—์„œ๋Š” ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์œผ๋กœ NLP๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ฐ€์žฅ ์‹ค์šฉ์ ์ธ ์ ‘๊ทผ๋ฒ•๋“ค์„ ์•Œ์•„๋ณด์ž! ๐Ÿ”

๐Ÿ“š ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€

project.clj ํŒŒ์ผ์˜ :dependencies ์„น์…˜์— ๋‹ค์Œ์„ ์ถ”๊ฐ€ํ•ด:

(defproject chatbot-ai "0.1.0-SNAPSHOT"
  :description "AI Chatbot built with Clojure"
  :dependencies [[org.clojure/clojure "1.11.1"]
                 [clojure-opennlp "0.5.0"]
                 [org.apache.opennlp/opennlp-tools "1.9.4"]
                 [cheshire "5.11.0"]  ; JSON ์ฒ˜๋ฆฌ
                 [clj-http "3.12.3"]  ; HTTP ํด๋ผ์ด์–ธํŠธ
                 [compojure "1.7.0"]  ; ์›น ๋ผ์šฐํŒ…
                 [ring/ring-core "1.9.6"]
                 [ring/ring-jetty-adapter "1.9.6"]])

์ด์ œ lein deps ๋ช…๋ น์–ด๋กœ ์˜์กด์„ฑ์„ ๋‹ค์šด๋กœ๋“œ๋ฐ›์ž! โฌ‡๏ธ

๐ŸŽฏ ๊ธฐ๋ณธ ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ

src/chatbot_ai/core.clj ํŒŒ์ผ์„ ์—ด๊ณ  ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ด:

(ns chatbot-ai.core
  (:require [clojure.string :as str]
            [opennlp.nlp :as nlp]
            [cheshire.core :as json]))

;; ํ…์ŠคํŠธ ์ „์ฒ˜๋ฆฌ ํ•จ์ˆ˜
(defn preprocess-text
  "์ž…๋ ฅ ํ…์ŠคํŠธ๋ฅผ ์†Œ๋ฌธ์ž๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  ๋ถˆํ•„์š”ํ•œ ๊ณต๋ฐฑ ์ œ๊ฑฐ"
  [text]
  (-> text
      str/lower-case
      str/trim
      (str/replace #"\s+" " ")))

;; ํ† ํฐํ™” ํ•จ์ˆ˜
(defn tokenize
  "๋ฌธ์žฅ์„ ๋‹จ์–ด ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌ"
  [text]
  (str/split (preprocess-text text) #"\s+"))

;; ๊ฐ„๋‹จํ•œ ๊ฐ์ • ๋ถ„์„
(defn analyze-sentiment
  "๊ธ์ •/๋ถ€์ • ๊ฐ์ • ๋ถ„์„ (๊ฐ„๋‹จํ•œ ํ‚ค์›Œ๋“œ ๊ธฐ๋ฐ˜)"
  [text]
  (let [positive-words #{"์ข‹์•„" "์ตœ๊ณ " "๊ฐ์‚ฌ" "ํ›Œ๋ฅญ" "๋ฉ‹์ ธ" "ํ–‰๋ณต" "์‚ฌ๋ž‘"}
        negative-words #{"์‹ซ์–ด" "๋‚˜์˜" "์ตœ์•…" "ํ™”๋‚˜" "์Šฌํผ" "์งœ์ฆ"}
        tokens (set (tokenize text))
        pos-count (count (clojure.set/intersection tokens positive-words))
        neg-count (count (clojure.set/intersection tokens negative-words))]
    (cond
      (> pos-count neg-count) :positive
      (> neg-count pos-count) :negative
      :else :neutral)))

;; ํ…Œ์ŠคํŠธํ•ด๋ณด๊ธฐ
(comment
  (preprocess-text "  ์•ˆ๋…•ํ•˜์„ธ์š”!   ์˜ค๋Š˜   ๋‚ ์”จ๊ฐ€   ์ข‹๋„ค์š”  ")
  ;; => "์•ˆ๋…•ํ•˜์„ธ์š”! ์˜ค๋Š˜ ๋‚ ์”จ๊ฐ€ ์ข‹๋„ค์š”"
  
  (tokenize "Clojure๋กœ ์ฑ—๋ด‡์„ ๋งŒ๋“ค์–ด์š”")
  ;; => ["clojure๋กœ" "์ฑ—๋ด‡์„" "๋งŒ๋“ค์–ด์š”"]
  
  (analyze-sentiment "์˜ค๋Š˜ ์ •๋ง ์ข‹์€ ๋‚ ์ด์—์š”!")
  ;; => :positive
  )

์ด ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด Clojure์˜ ํŠน์ง•์ด ์ž˜ ๋“œ๋Ÿฌ๋‚˜ ์žˆ์–ด. ์Šค๋ ˆ๋”ฉ ๋งคํฌ๋กœ ->๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๊ณผ์ •์„ ํŒŒ์ดํ”„๋ผ์ธ์ฒ˜๋Ÿผ ํ‘œํ˜„ํ–ˆ์ง€. ์ฝ๊ธฐ ์‰ฝ๊ณ  ์ง๊ด€์ ์ด์ง€ ์•Š์•„? ๐Ÿ˜Š

๐Ÿ” ์ฝ”๋“œ ํ•ด์„ค

preprocess-text ํ•จ์ˆ˜:
์ž…๋ ฅ ํ…์ŠคํŠธ๋ฅผ ์ •์ œํ•˜๋Š” ํ•จ์ˆ˜์•ผ. ์Šค๋ ˆ๋”ฉ ๋งคํฌ๋กœ ->๋ฅผ ์‚ฌ์šฉํ•ด์„œ text๋ฅผ ์†Œ๋ฌธ์ž๋กœ ๋ฐ”๊พธ๊ณ , ์•ž๋’ค ๊ณต๋ฐฑ์„ ์ œ๊ฑฐํ•˜๊ณ , ์—ฐ์†๋œ ๊ณต๋ฐฑ์„ ํ•˜๋‚˜๋กœ ๋งŒ๋“ค์–ด. ๋งˆ์น˜ ๊ณต์žฅ์˜ ์ปจ๋ฒ ์ด์–ด ๋ฒจํŠธ์ฒ˜๋Ÿผ ๋ฐ์ดํ„ฐ๊ฐ€ ์ˆœ์ฐจ์ ์œผ๋กœ ๋ณ€ํ™˜๋˜๋Š” ๊ฑฐ์ง€! ๐Ÿญ

tokenize ํ•จ์ˆ˜:
๋ฌธ์žฅ์„ ๋‹จ์–ด ๋‹จ์œ„๋กœ ์ชผ๊ฐœ๋Š” ํ•จ์ˆ˜์•ผ. ์ •๊ทœํ‘œํ˜„์‹ #"\s+"๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ณต๋ฐฑ์„ ๊ธฐ์ค€์œผ๋กœ ๋ถ„๋ฆฌํ•ด.

analyze-sentiment ํ•จ์ˆ˜:
๊ฐ„๋‹จํ•œ ๊ฐ์ • ๋ถ„์„ ํ•จ์ˆ˜์•ผ. ์‹ค์ œ ํ”„๋กœ๋•์…˜์—์„œ๋Š” ๋” ์ •๊ตํ•œ ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๊ฒ ์ง€๋งŒ, ๊ธฐ๋ณธ ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•˜๊ธฐ์—” ์ด ์ •๋„๋ฉด ์ถฉ๋ถ„ํ•ด!

๐Ÿ’ฌ ๋Œ€ํ™” ๊ด€๋ฆฌ ์‹œ์Šคํ…œ ๊ตฌ์ถ•ํ•˜๊ธฐ

์ด์ œ ์ฑ—๋ด‡์˜ ํ•ต์‹ฌ์ธ ๋Œ€ํ™” ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณผ ๊ฑฐ์•ผ. ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์„ ๋ฐ›์•„์„œ ์ ์ ˆํ•œ ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๋Š” ๋ถ€๋ถ„์ด์ง€. ์—ฌ๊ธฐ์„œ Clojure์˜ ์ง„๊ฐ€๊ฐ€ ๋ฐœํœ˜๋ผ! โœจ

๐Ÿ—‚๏ธ ๋Œ€ํ™” ์ƒํƒœ ๊ด€๋ฆฌ

์ฑ—๋ด‡์€ ๋Œ€ํ™”์˜ ๋งฅ๋ฝ์„ ๊ธฐ์–ตํ•ด์•ผ ํ•ด. "๋„ˆ ์ด๋ฆ„์ด ๋ญ์•ผ?"๋ผ๊ณ  ๋ฌผ์—ˆ๋‹ค๊ฐ€ "๊ทธ๊ฑฐ ์–ด๋–ป๊ฒŒ ์ง€์—ˆ์–ด?"๋ผ๊ณ  ๋ฌผ์œผ๋ฉด, ์ฑ—๋ด‡์€ "๊ทธ๊ฑฐ"๊ฐ€ ์ด๋ฆ„์„ ๊ฐ€๋ฆฌํ‚จ๋‹ค๋Š” ๊ฑธ ์•Œ์•„์•ผ ํ•˜๊ฑฐ๋“ . ์ด๋ฅผ ์œ„ํ•ด ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•ด:

(ns chatbot-ai.conversation
  (:require [clojure.string :as str]))

;; ๋Œ€ํ™” ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” atom
(def conversation-state
  (atom {}))

;; ์‚ฌ์šฉ์ž๋ณ„ ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ ์ดˆ๊ธฐํ™”
(defn init-user-session
  "์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž ์„ธ์…˜ ์ดˆ๊ธฐํ™”"
  [user-id]
  (swap! conversation-state assoc user-id
         {:history []
          :context {}
          :last-intent nil
          :created-at (java.time.Instant/now)}))

;; ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ์— ๋ฉ”์‹œ์ง€ ์ถ”๊ฐ€
(defn add-message
  "์‚ฌ์šฉ์ž ๋˜๋Š” ๋ด‡์˜ ๋ฉ”์‹œ์ง€๋ฅผ ํžˆ์Šคํ† ๋ฆฌ์— ์ถ”๊ฐ€"
  [user-id role message]
  (swap! conversation-state
         update-in [user-id :history]
         conj {:role role
               :message message
               :timestamp (java.time.Instant/now)}))

;; ์ปจํ…์ŠคํŠธ ์ •๋ณด ์—…๋ฐ์ดํŠธ
(defn update-context
  "๋Œ€ํ™” ์ปจํ…์ŠคํŠธ ์ •๋ณด ์—…๋ฐ์ดํŠธ"
  [user-id key value]
  (swap! conversation-state
         assoc-in [user-id :context key] value))

;; ์ตœ๊ทผ ๋Œ€ํ™” ๊ฐ€์ ธ์˜ค๊ธฐ
(defn get-recent-messages
  "์ตœ๊ทผ n๊ฐœ์˜ ๋ฉ”์‹œ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ"
  [user-id n]
  (->> (get-in @conversation-state [user-id :history])
       (take-last n)))

;; ์‚ฌ์šฉ ์˜ˆ์‹œ
(comment
  (init-user-session "user123")
  (add-message "user123" :user "์•ˆ๋…•ํ•˜์„ธ์š”!")
  (add-message "user123" :bot "์•ˆ๋…•ํ•˜์„ธ์š”! ๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?")
  (update-context "user123" :name "์ฒ ์ˆ˜")
  (get-recent-messages "user123" 5)
  )

์—ฌ๊ธฐ์„œ atom์„ ์‚ฌ์šฉํ•œ ๊ฒŒ ๋ณด์ด์ง€? Atom์€ Clojure์˜ ์ƒํƒœ ๊ด€๋ฆฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ์ค‘ ํ•˜๋‚˜์•ผ. ๋™์‹œ์„ฑ ์•ˆ์ „ํ•˜๋ฉด์„œ๋„ ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์›Œ์„œ ์ด๋Ÿฐ ์šฉ๋„๋กœ ๋”ฑ์ด์ง€! ๐Ÿ”’

๐ŸŽญ ์˜๋„(Intent) ํŒŒ์•…ํ•˜๊ธฐ

์‚ฌ์šฉ์ž๊ฐ€ ๋ฌด์—‡์„ ์›ํ•˜๋Š”์ง€ ํŒŒ์•…ํ•˜๋Š” ๊ฑด ์ฑ—๋ด‡์˜ ํ•ต์‹ฌ์ด์•ผ. ๊ฐ„๋‹จํ•œ ํŒจํ„ด ๋งค์นญ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด์ž:

(ns chatbot-ai.intent
  (:require [clojure.string :as str]
            [chatbot-ai.core :as core]))

;; ์˜๋„ ํŒจํ„ด ์ •์˜
(def intent-patterns
  {:greeting {:patterns ["์•ˆ๋…•" "ํ•˜์ด" "ํ—ฌ๋กœ" "hi" "hello"]
              :response-templates ["์•ˆ๋…•ํ•˜์„ธ์š”! ๐Ÿ˜Š"
                                   "๋ฐ˜๊ฐ‘์Šต๋‹ˆ๋‹ค!"
                                   "์•ˆ๋…•ํ•˜์„ธ์š”! ๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?"]}
   
   :farewell {:patterns ["์ž˜๊ฐ€" "๋ฐ”์ด" "bye" "์•ˆ๋…•ํžˆ"]
              :response-templates ["์•ˆ๋…•ํžˆ ๊ฐ€์„ธ์š”! ๐Ÿ‘‹"
                                   "์ข‹์€ ํ•˜๋ฃจ ๋˜์„ธ์š”!"
                                   "๋˜ ๋งŒ๋‚˜์š”!"]}
   
   :thanks {:patterns ["๊ณ ๋งˆ์›Œ" "๊ฐ์‚ฌ" "thanks" "thank you"]
            :response-templates ["์ฒœ๋งŒ์—์š”! ๐Ÿ˜Š"
                                 "๋„์›€์ด ๋˜์—ˆ๋‹ค๋‹ˆ ๊ธฐ์ฉ๋‹ˆ๋‹ค!"
                                 "์–ธ์ œ๋“ ์ง€ ๋ฌผ์–ด๋ณด์„ธ์š”!"]}
   
   :help {:patterns ["๋„์›€" "help" "๋ชจ๋ฅด๊ฒ ์–ด" "์–ด๋–ป๊ฒŒ"]
          :response-templates ["๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?"
                               "๊ถ๊ธˆํ•œ ์ ์„ ๋ง์”€ํ•ด์ฃผ์„ธ์š”!"
                               "์ž์„ธํžˆ ์„ค๋ช…ํ•ด๋“œ๋ฆด๊ฒŒ์š”!"]}
   
   :weather {:patterns ["๋‚ ์”จ" "weather" "๊ธฐ์˜จ" "๋น„" "๋ˆˆ"]
             :response-templates ["๋‚ ์”จ ์ •๋ณด๋ฅผ ํ™•์ธํ•ด๋“œ๋ฆด๊ฒŒ์š”! โ˜€๏ธ"
                                  "์˜ค๋Š˜ ๋‚ ์”จ๊ฐ€ ๊ถ๊ธˆํ•˜์‹ ๊ฐ€์š”?"]}
   
   :time {:patterns ["์‹œ๊ฐ„" "time" "๋ช‡์‹œ" "์ง€๊ธˆ"]
          :response-templates ["ํ˜„์žฌ ์‹œ๊ฐ„์„ ์•Œ๋ ค๋“œ๋ฆด๊ฒŒ์š”! โฐ"]}})

;; ์˜๋„ ๋งค์นญ ํ•จ์ˆ˜
(defn match-intent
  "์‚ฌ์šฉ์ž ์ž…๋ ฅ์—์„œ ์˜๋„๋ฅผ ํŒŒ์•…"
  [user-input]
  (let [processed (core/preprocess-text user-input)]
    (->> intent-patterns
         (filter (fn [[intent data]]
                   (some #(str/includes? processed %)
                         (:patterns data))))
         first
         first)))

;; ์‘๋‹ต ์ƒ์„ฑ
(defn generate-response
  "์˜๋„์— ๋งž๋Š” ์‘๋‹ต ์ƒ์„ฑ"
  [intent]
  (if-let [templates (get-in intent-patterns [intent :response-templates])]
    (rand-nth templates)
    "์ฃ„์†กํ•ด์š”, ์ž˜ ์ดํ•ดํ•˜์ง€ ๋ชปํ–ˆ์–ด์š”. ๋‹ค์‹œ ๋ง์”€ํ•ด์ฃผ์‹œ๊ฒ ์–ด์š”? ๐Ÿค”"))

;; ํ†ตํ•ฉ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
(defn process-user-input
  "์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‘๋‹ต ์ƒ์„ฑ"
  [user-input]
  (let [intent (match-intent user-input)
        response (generate-response intent)]
    {:intent intent
     :response response
     :confidence (if intent 0.8 0.3)}))

;; ํ…Œ์ŠคํŠธ
(comment
  (process-user-input "์•ˆ๋…•ํ•˜์„ธ์š”!")
  ;; => {:intent :greeting, :response "์•ˆ๋…•ํ•˜์„ธ์š”! ๐Ÿ˜Š", :confidence 0.8}
  
  (process-user-input "์˜ค๋Š˜ ๋‚ ์”จ ์–ด๋•Œ?")
  ;; => {:intent :weather, :response "๋‚ ์”จ ์ •๋ณด๋ฅผ ํ™•์ธํ•ด๋“œ๋ฆด๊ฒŒ์š”! โ˜€๏ธ", :confidence 0.8}
  )

์ด ์ฝ”๋“œ๋Š” ๊ทœ์น™ ๊ธฐ๋ฐ˜(Rule-based) ์ ‘๊ทผ๋ฒ•์ด์•ผ. ๊ฐ„๋‹จํ•˜์ง€๋งŒ ํšจ๊ณผ์ ์ด์ง€! ๋ฌผ๋ก  ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ๋” ์ข‹์•„. ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ์‹œ์ž‘ํ•ด์„œ ์ ์ง„์ ์œผ๋กœ ๊ฐœ์„ ํ•ด๋‚˜๊ฐ€๋Š” ๊ฒŒ ์ข‹์€ ์ „๋žต์ด์•ผ. ๐ŸŽฏ

๐Ÿ’ก ์‹ค์ „ ํŒ: ํŒจํ„ด ๋งค์นญ๋งŒ์œผ๋กœ๋„ ๊ฝค ๊ดœ์ฐฎ์€ ์ฑ—๋ด‡์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ ํŠน์ • ๋„๋ฉ”์ธ(์˜ˆ: ๊ณ ๊ฐ ์„œ๋น„์Šค, FAQ)์— ํŠนํ™”๋œ ์ฑ—๋ด‡์ด๋ผ๋ฉด ๋”์šฑ ๊ทธ๋ž˜. ๋ณต์žก๋„์™€ ์„ฑ๋Šฅ ์‚ฌ์ด์˜ ๊ท ํ˜•์„ ์ž˜ ๋งž์ถ”๋Š” ๊ฒŒ ์ค‘์š”ํ•ด!

๐Ÿค– ๋จธ์‹ ๋Ÿฌ๋‹ ํ†ตํ•ฉ: ๋” ๋˜‘๋˜‘ํ•œ ์ฑ—๋ด‡ ๋งŒ๋“ค๊ธฐ

์ด์ œ ์ง„์งœ AI๋ฅผ ๋„ฃ์–ด๋ณผ ์‹œ๊ฐ„์ด์•ผ! Clojure์—์„œ ๋จธ์‹ ๋Ÿฌ๋‹์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์–ด. ๊ฐ€์žฅ ์‹ค์šฉ์ ์ธ ๋ฐฉ๋ฒ•๋“ค์„ ์•Œ์•„๋ณด์ž. ๐Ÿš€

๐Ÿ”— ์™ธ๋ถ€ AI API ์—ฐ๋™ํ•˜๊ธฐ

์ฒ˜์Œ๋ถ€ํ„ฐ ๋ชจ๋ธ์„ ํ•™์Šต์‹œํ‚ค๋Š” ๊ฑด ์‹œ๊ฐ„๋„ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๊ณ  ๋ฆฌ์†Œ์Šค๋„ ๋งŽ์ด ํ•„์š”ํ•ด. ๊ทธ๋ž˜์„œ OpenAI API๋‚˜ Hugging Face ๊ฐ™์€ ์„œ๋น„์Šค๋ฅผ ํ™œ์šฉํ•˜๋Š” ๊ฒŒ ํ˜„๋ช…ํ•ด:

(ns chatbot-ai.ai-service
  (:require [clj-http.client :as http]
            [cheshire.core :as json]))

;; OpenAI API ์„ค์ •
(def openai-config
  {:api-key (System/getenv "OPENAI_API_KEY")
   :model "gpt-3.5-turbo"
   :base-url "https://api.openai.com/v1"})

;; OpenAI API ํ˜ธ์ถœ
(defn call-openai
  "OpenAI API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์‘๋‹ต ์ƒ์„ฑ"
  [messages]
  (try
    (let [response (http/post
                    (str (:base-url openai-config) "/chat/completions")
                    {:headers {"Authorization" (str "Bearer " (:api-key openai-config))
                               "Content-Type" "application/json"}
                     :body (json/generate-string
                            {:model (:model openai-config)
                             :messages messages
                             :temperature 0.7
                             :max_tokens 500})
                     :as :json})]
      (get-in response [:body :choices 0 :message :content]))
    (catch Exception e
      (str "์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: " (.getMessage e)))))

;; ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ๋ฅผ OpenAI ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜
(defn format-conversation-history
  "๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ๋ฅผ OpenAI API ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜"
  [history]
  (map (fn [{:keys [role message]}]
         {:role (name role)
          :content message})
       history))

;; ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ์„ค์ •
(def system-prompt
  {:role "system"
   :content "๋‹น์‹ ์€ ์นœ์ ˆํ•˜๊ณ  ๋„์›€์ด ๋˜๋Š” AI ์–ด์‹œ์Šคํ„ดํŠธ์ž…๋‹ˆ๋‹ค. 
             ์‚ฌ์šฉ์ž์˜ ์งˆ๋ฌธ์— ์ •ํ™•ํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”."})

;; ํ†ตํ•ฉ AI ์‘๋‹ต ์ƒ์„ฑ
(defn generate-ai-response
  "AI๋ฅผ ํ™œ์šฉํ•œ ์‘๋‹ต ์ƒ์„ฑ"
  [user-message conversation-history]
  (let [messages (concat [system-prompt]
                         (format-conversation-history conversation-history)
                         [{:role "user" :content user-message}])]
    (call-openai messages)))

;; ์‚ฌ์šฉ ์˜ˆ์‹œ
(comment
  (generate-ai-response 
    "Clojure๋กœ ์ฑ—๋ด‡์„ ๋งŒ๋“œ๋Š” ๊ฒŒ ์–ด๋ ค์šด๊ฐ€์š”?"
    [{:role :bot :message "์•ˆ๋…•ํ•˜์„ธ์š”! ๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?"}
     {:role :user :message "ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด ์ถ”์ฒœํ•ด์ฃผ์„ธ์š”"}
     {:role :bot :message "์–ด๋–ค ์šฉ๋„๋กœ ์‚ฌ์šฉํ•˜์‹ค ๊ฑด๊ฐ€์š”?"}])
  )

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด OpenAI์˜ ๊ฐ•๋ ฅํ•œ ์–ธ์–ด ๋ชจ๋ธ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด! ๋ฌผ๋ก  API ํ‚ค๊ฐ€ ํ•„์š”ํ•˜๊ณ  ๋น„์šฉ์ด ๋ฐœ์ƒํ•˜์ง€๋งŒ, ์ง์ ‘ ๋ชจ๋ธ์„ ํ•™์Šต์‹œํ‚ค๋Š” ๊ฒƒ๋ณด๋‹ค ํ›จ์”ฌ ๋น ๋ฅด๊ณ  ํšจ์œจ์ ์ด์•ผ. ๐Ÿ’ฐ

๐Ÿงฎ ๋กœ์ปฌ ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ ์‚ฌ์šฉํ•˜๊ธฐ

๋น„์šฉ์ด ๊ฑฑ์ •๋˜๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ ํ”„๋ผ์ด๋ฒ„์‹œ๊ฐ€ ์ค‘์š”ํ•˜๋‹ค๋ฉด, ๋กœ์ปฌ์—์„œ ๋Œ์•„๊ฐ€๋Š” ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์–ด. Clojure์—์„œ Java ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•˜๋ฉด ๋ผ:

(ns chatbot-ai.local-ml
  (:import [opennlp.tools.doccat DoccatModel DocumentCategorizerME]
           [opennlp.tools.util InputStreamFactory PlainTextByLineStream]
           [java.io FileInputStream]))

;; ๋ฌธ์„œ ๋ถ„๋ฅ˜ ๋ชจ๋ธ ๋กœ๋“œ
(defn load-doccat-model
  "ํ•™์Šต๋œ ๋ฌธ์„œ ๋ถ„๋ฅ˜ ๋ชจ๋ธ ๋กœ๋“œ"
  [model-path]
  (with-open [model-stream (FileInputStream. model-path)]
    (DoccatModel. model-stream)))

;; ํ…์ŠคํŠธ ๋ถ„๋ฅ˜
(defn categorize-text
  "ํ…์ŠคํŠธ๋ฅผ ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ๋ถ„๋ฅ˜"
  [model text]
  (let [categorizer (DocumentCategorizerME. model)
        outcomes (.categorize categorizer (into-array String [text]))
        best-category (.getBestCategory categorizer outcomes)]
    {:category best-category
     :confidence (apply max outcomes)
     :all-scores (zipmap (.getCategories categorizer) outcomes)}))

;; ๊ฐ„๋‹จํ•œ TF-IDF ๊ตฌํ˜„
(defn calculate-tf
  "๋‹จ์–ด ๋นˆ๋„(Term Frequency) ๊ณ„์‚ฐ"
  [tokens]
  (let [total (count tokens)
        freqs (frequencies tokens)]
    (into {} (map (fn [[word count]]
                    [word (/ count total)])
                  freqs))))

(defn calculate-idf
  "์—ญ๋ฌธ์„œ ๋นˆ๋„(Inverse Document Frequency) ๊ณ„์‚ฐ"
  [documents]
  (let [doc-count (count documents)
        word-doc-count (reduce
                        (fn [acc doc]
                          (reduce (fn [m word]
                                    (update m word (fnil inc 0)))
                                  acc
                                  (distinct doc)))
                        {}
                        documents)]
    (into {} (map (fn [[word count]]
                    [word (Math/log (/ doc-count count))])
                  word-doc-count))))

(defn calculate-tfidf
  "TF-IDF ์ ์ˆ˜ ๊ณ„์‚ฐ"
  [tf idf]
  (into {} (map (fn [[word tf-score]]
                  [word (* tf-score (get idf word 0))])
                tf)))

;; ๋ฌธ์„œ ์œ ์‚ฌ๋„ ๊ณ„์‚ฐ (์ฝ”์‚ฌ์ธ ์œ ์‚ฌ๋„)
(defn cosine-similarity
  "๋‘ ๋ฒกํ„ฐ ๊ฐ„์˜ ์ฝ”์‚ฌ์ธ ์œ ์‚ฌ๋„ ๊ณ„์‚ฐ"
  [vec1 vec2]
  (let [all-keys (distinct (concat (keys vec1) (keys vec2)))
        dot-product (reduce + (map #(* (get vec1 % 0) (get vec2 % 0)) all-keys))
        magnitude1 (Math/sqrt (reduce + (map #(* % %) (vals vec1))))
        magnitude2 (Math/sqrt (reduce + (map #(* % %) (vals vec2))))]
    (if (or (zero? magnitude1) (zero? magnitude2))
      0
      (/ dot-product (* magnitude1 magnitude2)))))

;; ์‚ฌ์šฉ ์˜ˆ์‹œ
(comment
  (def doc1 ["clojure" "is" "a" "functional" "programming" "language"])
  (def doc2 ["python" "is" "a" "programming" "language"])
  (def documents [doc1 doc2])
  
  (def tf1 (calculate-tf doc1))
  (def idf (calculate-idf documents))
  (def tfidf1 (calculate-tfidf tf1 idf))
  
  ;; ์œ ์‚ฌ๋„ ๊ณ„์‚ฐ
  (cosine-similarity tfidf1 (calculate-tfidf (calculate-tf doc2) idf))
  )

TF-IDF์™€ ์ฝ”์‚ฌ์ธ ์œ ์‚ฌ๋„๋Š” ์ •๋ณด ๊ฒ€์ƒ‰๊ณผ ํ…์ŠคํŠธ ์œ ์‚ฌ๋„ ์ธก์ •์˜ ๊ธฐ๋ณธ์ด์•ผ. ์ด๊ฑธ ํ™œ์šฉํ•˜๋ฉด ์‚ฌ์šฉ์ž์˜ ์งˆ๋ฌธ๊ณผ ๊ฐ€์žฅ ์œ ์‚ฌํ•œ FAQ๋ฅผ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ์–ด! ๐Ÿ“Š

๐ŸŽ“ ๋จธ์‹ ๋Ÿฌ๋‹ ๊ฐœ๋… ์ •๋ฆฌ

TF-IDF (Term Frequency-Inverse Document Frequency):
๋‹จ์–ด๊ฐ€ ๋ฌธ์„œ์—์„œ ์–ผ๋งˆ๋‚˜ ์ค‘์š”ํ•œ์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ง€ํ‘œ์•ผ. ์ž์ฃผ ๋‚˜์˜ค์ง€๋งŒ ๋‹ค๋ฅธ ๋ฌธ์„œ์—๋Š” ์ž˜ ์•ˆ ๋‚˜์˜ค๋Š” ๋‹จ์–ด์ผ์ˆ˜๋ก ๋†’์€ ์ ์ˆ˜๋ฅผ ๋ฐ›์•„. ์˜ˆ๋ฅผ ๋“ค์–ด "Clojure"๋ผ๋Š” ๋‹จ์–ด๋Š” Clojure ๊ด€๋ จ ๋ฌธ์„œ์—์„œ๋Š” ์ค‘์š”ํ•˜์ง€๋งŒ, ๋ชจ๋“  ๋ฌธ์„œ์— ๋‚˜์˜ค๋Š” "the"๋Š” ์ค‘์š”ํ•˜์ง€ ์•Š์€ ๊ฑฐ์ง€.

์ฝ”์‚ฌ์ธ ์œ ์‚ฌ๋„:
๋‘ ๋ฒกํ„ฐ ์‚ฌ์ด์˜ ๊ฐ๋„๋ฅผ ์ธก์ •ํ•ด์„œ ์œ ์‚ฌ๋„๋ฅผ ๊ณ„์‚ฐํ•ด. 0์—์„œ 1 ์‚ฌ์ด์˜ ๊ฐ’์„ ๊ฐ€์ง€๋ฉฐ, 1์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ์œ ์‚ฌํ•ด. ๋ฐฉํ–ฅ์€ ๊ฐ™์€๋ฐ ํฌ๊ธฐ๋งŒ ๋‹ค๋ฅธ ๋ฒกํ„ฐ๋“ค๋„ ๋†’์€ ์œ ์‚ฌ๋„๋ฅผ ๊ฐ€์ ธ์„œ ํ…์ŠคํŠธ ๋น„๊ต์— ์ ํ•ฉํ•ด!

๐ŸŒ ์›น ์ธํ„ฐํŽ˜์ด์Šค ๋งŒ๋“ค๊ธฐ: ์‚ฌ์šฉ์ž์™€ ๋งŒ๋‚˜๋Š” ์ ‘์ 

์ด์ œ ์ฑ—๋ด‡์˜ ๋‘๋‡Œ๋Š” ๋งŒ๋“ค์—ˆ์œผ๋‹ˆ, ์‚ฌ์šฉ์ž๊ฐ€ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•ด. Clojure์—์„œ๋Š” Ring๊ณผ Compojure๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์›น ์„œ๋ฒ„๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด! ๐Ÿ–ฅ๏ธ

๐Ÿš€ REST API ์„œ๋ฒ„ ๊ตฌ์ถ•

(ns chatbot-ai.server
  (:require [compojure.core :refer [defroutes GET POST]]
            [compojure.route :as route]
            [ring.adapter.jetty :as jetty]
            [ring.middleware.json :refer [wrap-json-body wrap-json-response]]
            [ring.middleware.cors :refer [wrap-cors]]
            [ring.util.response :refer [response]]
            [chatbot-ai.conversation :as conv]
            [chatbot-ai.intent :as intent]
            [chatbot-ai.ai-service :as ai]))

;; API ์—”๋“œํฌ์ธํŠธ ์ •์˜
(defroutes app-routes
  ;; ํ—ฌ์Šค์ฒดํฌ
  (GET "/health" []
    (response {:status "ok" :message "Chatbot is running! ๐Ÿค–"}))
  
  ;; ์ƒˆ ์„ธ์…˜ ์‹œ์ž‘
  (POST "/session/start" {body :body}
    (let [user-id (or (:user-id body) (str (java.util.UUID/randomUUID)))]
      (conv/init-user-session user-id)
      (response {:user-id user-id
                 :message "์„ธ์…˜์ด ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค!"})))
  
  ;; ๋ฉ”์‹œ์ง€ ์ „์†ก
  (POST "/chat" {body :body}
    (let [user-id (:user-id body)
          message (:message body)
          use-ai? (:use-ai body false)]
      
      ;; ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ ์ €์žฅ
      (conv/add-message user-id :user message)
      
      ;; ์‘๋‹ต ์ƒ์„ฑ
      (let [response-text (if use-ai?
                            ;; AI ์‚ฌ์šฉ
                            (let [history (conv/get-recent-messages user-id 10)]
                              (ai/generate-ai-response message history))
                            ;; ๊ทœ์น™ ๊ธฐ๋ฐ˜
                            (let [result (intent/process-user-input message)]
                              (:response result)))]
        
        ;; ๋ด‡ ์‘๋‹ต ์ €์žฅ
        (conv/add-message user-id :bot response-text)
        
        (response {:user-id user-id
                   :message response-text
                   :timestamp (str (java.time.Instant/now))}))))
  
  ;; ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ ์กฐํšŒ
  (GET "/chat/history/:user-id" [user-id]
    (let [history (conv/get-recent-messages user-id 50)]
      (response {:user-id user-id
                 :history history})))
  
  ;; 404 ์ฒ˜๋ฆฌ
  (route/not-found
    (response {:error "Not found" :status 404})))

;; ๋ฏธ๋“ค์›จ์–ด ์ ์šฉ
(def app
  (-> app-routes
      (wrap-json-body {:keywords? true})
      wrap-json-response
      (wrap-cors :access-control-allow-origin [#".*"]
                 :access-control-allow-methods [:get :post :put :delete]
                 :access-control-allow-headers ["Content-Type"])))

;; ์„œ๋ฒ„ ์‹œ์ž‘
(defn start-server
  "์›น ์„œ๋ฒ„ ์‹œ์ž‘"
  [port]
  (jetty/run-jetty app {:port port :join? false})
  (println (str "๐Ÿš€ Chatbot server started on port " port)))

;; ๊ฐœ๋ฐœ์šฉ ์„œ๋ฒ„ ์‹œ์ž‘
(defn -main
  [& args]
  (let [port (Integer/parseInt (or (first args) "3000"))]
    (start-server port)))

์ด์ œ lein run ๋ช…๋ น์–ด๋กœ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, http://localhost:3000์—์„œ ์ฑ—๋ด‡ API๊ฐ€ ๋™์ž‘ํ•ด! ๐ŸŽ‰

๐Ÿ’ป ๊ฐ„๋‹จํ•œ ์›น ํด๋ผ์ด์–ธํŠธ

API๋งŒ ์žˆ์œผ๋ฉด ๋ญํ•ด, ์‚ฌ์šฉ์ž๊ฐ€ ์“ธ ์ˆ˜ ์žˆ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์žˆ์–ด์•ผ์ง€! ๊ฐ„๋‹จํ•œ HTML/JavaScript ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž:

;; resources/public/index.html
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Clojure AI Chatbot</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { 
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .chat-container {
            width: 90%;
            max-width: 600px;
            height: 80vh;
            background: white;
            border-radius: 20px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.3);
            display: flex;
            flex-direction: column;
        }
        .chat-header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 20px;
            border-radius: 20px 20px 0 0;
            text-align: center;
        }
        .chat-messages {
            flex: 1;
            overflow-y: auto;
            padding: 20px;
            display: flex;
            flex-direction: column;
            gap: 15px;
        }
        .message {
            max-width: 70%;
            padding: 12px 18px;
            border-radius: 18px;
            word-wrap: break-word;
        }
        .message.user {
            align-self: flex-end;
            background: #667eea;
            color: white;
        }
        .message.bot {
            align-self: flex-start;
            background: #f0f0f0;
            color: #333;
        }
        .chat-input {
            display: flex;
            padding: 20px;
            border-top: 1px solid #eee;
        }
        .chat-input input {
            flex: 1;
            padding: 12px 18px;
            border: 2px solid #eee;
            border-radius: 25px;
            font-size: 16px;
            outline: none;
        }
        .chat-input button {
            margin-left: 10px;
            padding: 12px 25px;
            background: #667eea;
            color: white;
            border: none;
            border-radius: 25px;
            cursor: pointer;
            font-size: 16px;
        }
        .chat-input button:hover {
            background: #5568d3;
        }
    </style>
</head>
<body>
    <div class="chat-container">
        <div class="chat-header">
            <h2>๐Ÿค– Clojure AI Chatbot</h2>
            <p>ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์œผ๋กœ ๋งŒ๋“  ๋˜‘๋˜‘ํ•œ ์ฑ—๋ด‡</p>
        </div>
        <div class="chat-messages" id="chatMessages">
            <div class="message bot">
                ์•ˆ๋…•ํ•˜์„ธ์š”! ๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”? ๐Ÿ˜Š
            </div>
        </div>
        <div class="chat-input">
            <input type="text" id="messageInput" placeholder="๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”..." />
            <button onclick="sendMessage()">์ „์†ก</button>
        </div>
    </div>

    <script>
        let userId = null;

        // ์„ธ์…˜ ์ดˆ๊ธฐํ™”
        async function initSession() {
            const response = await fetch('http://localhost:3000/session/start', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({})
            });
            const data = await response.json();
            userId = data.userId;
        }

        // ๋ฉ”์‹œ์ง€ ์ „์†ก
        async function sendMessage() {
            const input = document.getElementById('messageInput');
            const message = input.value.trim();
            
            if (!message) return;
            
            // ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
            addMessage(message, 'user');
            input.value = '';
            
            // API ํ˜ธ์ถœ
            const response = await fetch('http://localhost:3000/chat', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    userId: userId,
                    message: message,
                    useAi: false  // true๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด AI ์‚ฌ์šฉ
                })
            });
            
            const data = await response.json();
            addMessage(data.message, 'bot');
        }

        // ๋ฉ”์‹œ์ง€ ์ถ”๊ฐ€
        function addMessage(text, role) {
            const messagesDiv = document.getElementById('chatMessages');
            const messageDiv = document.createElement('div');
            messageDiv.className = `message ${role}`;
            messageDiv.textContent = text;
            messagesDiv.appendChild(messageDiv);
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        }

        // Enter ํ‚ค๋กœ ์ „์†ก
        document.getElementById('messageInput').addEventListener('keypress', (e) => {
            if (e.key === 'Enter') sendMessage();
        });

        // ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์„ธ์…˜ ์ดˆ๊ธฐํ™”
        initSession();
    </script>
</body>
</html>

์ด์ œ ๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:3000/index.html์— ์ ‘์†ํ•˜๋ฉด ์˜ˆ์œ ์ฑ—๋ด‡ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์–ด! ๐ŸŽจ

๐Ÿ”ง ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•˜๊ธฐ

๊ธฐ๋ณธ์ ์ธ ์ฑ—๋ด‡์€ ๋งŒ๋“ค์—ˆ์œผ๋‹ˆ, ์ด์ œ ๋” ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๋“ค์„ ์ถ”๊ฐ€ํ•ด๋ณด์ž! ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋“ค์ด์•ผ. ๐Ÿ’ช

๐Ÿ—„๏ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™

๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์—๋งŒ ์ €์žฅํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ ์žฌ์‹œ์ž‘๋˜๋ฉด ๋‹ค ๋‚ ์•„๊ฐ€์ž–์•„? ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•ด์•ผ ํ•ด:

(ns chatbot-ai.database
  (:require [clojure.java.jdbc :as jdbc]))

;; ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ • (SQLite ์˜ˆ์‹œ)
(def db-spec
  {:classname "org.sqlite.JDBC"
   :subprotocol "sqlite"
   :subname "chatbot.db"})

;; ํ…Œ์ด๋ธ” ์ƒ์„ฑ
(defn create-tables!
  "ํ•„์š”ํ•œ ํ…Œ์ด๋ธ” ์ƒ์„ฑ"
  []
  (jdbc/execute! db-spec
    ["CREATE TABLE IF NOT EXISTS users (
        id TEXT PRIMARY KEY,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
      )"])
  
  (jdbc/execute! db-spec
    ["CREATE TABLE IF NOT EXISTS messages (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id TEXT NOT NULL,
        role TEXT NOT NULL,
        message TEXT NOT NULL,
        timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (user_id) REFERENCES users(id)
      )"])
  
  (jdbc/execute! db-spec
    ["CREATE TABLE IF NOT EXISTS contexts (
        user_id TEXT NOT NULL,
        key TEXT NOT NULL,
        value TEXT,
        PRIMARY KEY (user_id, key),
        FOREIGN KEY (user_id) REFERENCES users(id)
      )"]))

;; ์‚ฌ์šฉ์ž ์ƒ์„ฑ
(defn create-user!
  "์ƒˆ ์‚ฌ์šฉ์ž ์ƒ์„ฑ"
  [user-id]
  (jdbc/insert! db-spec :users {:id user-id}))

;; ๋ฉ”์‹œ์ง€ ์ €์žฅ
(defn save-message!
  "๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ"
  [user-id role message]
  (jdbc/insert! db-spec :messages
    {:user_id user-id
     :role (name role)
     :message message}))

;; ๋ฉ”์‹œ์ง€ ์กฐํšŒ
(defn get-messages
  "์‚ฌ์šฉ์ž์˜ ์ตœ๊ทผ ๋ฉ”์‹œ์ง€ ์กฐํšŒ"
  [user-id limit]
  (jdbc/query db-spec
    ["SELECT role, message, timestamp 
      FROM messages 
      WHERE user_id = ? 
      ORDER BY timestamp DESC 
      LIMIT ?"
     user-id limit]))

;; ์ปจํ…์ŠคํŠธ ์ €์žฅ
(defn save-context!
  "์ปจํ…์ŠคํŠธ ์ •๋ณด ์ €์žฅ"
  [user-id key value]
  (jdbc/execute! db-spec
    ["INSERT OR REPLACE INTO contexts (user_id, key, value) 
      VALUES (?, ?, ?)"
     user-id (name key) value]))

;; ์ปจํ…์ŠคํŠธ ์กฐํšŒ
(defn get-context
  "์ปจํ…์ŠคํŠธ ์ •๋ณด ์กฐํšŒ"
  [user-id key]
  (first
    (jdbc/query db-spec
      ["SELECT value FROM contexts WHERE user_id = ? AND key = ?"
       user-id (name key)])))

;; ์ดˆ๊ธฐํ™”
(defn init-db!
  "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ดˆ๊ธฐํ™”"
  []
  (create-tables!)
  (println "โœ… Database initialized successfully!"))

์ด์ œ ๋Œ€ํ™” ๋‚ด์šฉ์ด ์˜๊ตฌ์ ์œผ๋กœ ์ €์žฅ๋ผ! ์‚ฌ์šฉ์ž๊ฐ€ ๋‚˜์ค‘์— ๋‹ค์‹œ ๋Œ์•„์™€๋„ ์ด์ „ ๋Œ€ํ™”๋ฅผ ์ด์–ด๊ฐˆ ์ˆ˜ ์žˆ์–ด. ๐Ÿ“š

๐Ÿ“Š ๋กœ๊น…๊ณผ ๋ชจ๋‹ˆํ„ฐ๋ง

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋กœ๊น…์ด ํ•„์ˆ˜์•ผ. ๋ญ”๊ฐ€ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ ์›์ธ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ :

(ns chatbot-ai.logging
  (:require [clojure.tools.logging :as log]))

;; ๋กœ๊น… ๋งคํฌ๋กœ
(defmacro log-execution
  "ํ•จ์ˆ˜ ์‹คํ–‰์„ ๋กœ๊น…"
  [fn-name & body]
  `(do
     (log/info (str "Executing: " ~fn-name))
     (let [start# (System/currentTimeMillis)
           result# (do ~@body)
           duration# (- (System/currentTimeMillis) start#)]
       (log/info (str "Completed: " ~fn-name " in " duration# "ms"))
       result#)))

;; ์—๋Ÿฌ ํ•ธ๋“ค๋ง
(defn safe-execute
  "์•ˆ์ „ํ•˜๊ฒŒ ํ•จ์ˆ˜ ์‹คํ–‰ (์—๋Ÿฌ ์ฒ˜๋ฆฌ ํฌํ•จ)"
  [f & args]
  (try
    {:success true
     :result (apply f args)}
    (catch Exception e
      (log/error e "Error executing function")
      {:success false
       :error (.getMessage e)})))

;; ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘
(def metrics (atom {:total-messages 0
                    :successful-responses 0
                    :failed-responses 0
                    :average-response-time 0}))

(defn update-metrics!
  "๋ฉ”ํŠธ๋ฆญ ์—…๋ฐ์ดํŠธ"
  [metric-key value]
  (swap! metrics update metric-key
         (if (= metric-key :average-response-time)
           #(/ (+ (* % (:total-messages @metrics)) value)
               (inc (:total-messages @metrics)))
           (if (fn? value) value inc))))

(defn get-metrics
  "ํ˜„์žฌ ๋ฉ”ํŠธ๋ฆญ ์กฐํšŒ"
  []
  @metrics)

๐Ÿ” ๋ณด์•ˆ ๊ฐ•ํ™”

์ฑ—๋ด‡๋„ ๋ณด์•ˆ์ด ์ค‘์š”ํ•ด! ํŠนํžˆ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋‹ˆ๊นŒ ๋”์šฑ ๊ทธ๋ž˜:

(ns chatbot-ai.security
  (:require [buddy.hashers :as hashers]
            [buddy.sign.jwt :as jwt]
            [clj-time.core :as time]))

;; JWT ์‹œํฌ๋ฆฟ ํ‚ค (์‹ค์ œ๋กœ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๊ด€๋ฆฌ!)
(def secret-key "your-super-secret-key-change-this!")

;; JWT ํ† ํฐ ์ƒ์„ฑ
(defn generate-token
  "์‚ฌ์šฉ์ž ์ธ์ฆ ํ† ํฐ ์ƒ์„ฑ"
  [user-id]
  (let [claims {:user-id user-id
                :exp (time/plus (time/now) (time/hours 24))}]
    (jwt/sign claims secret-key)))

;; JWT ํ† ํฐ ๊ฒ€์ฆ
(defn verify-token
  "ํ† ํฐ ๊ฒ€์ฆ"
  [token]
  (try
    (jwt/unsign token secret-key)
    (catch Exception e
      nil)))

;; ์ž…๋ ฅ ๊ฒ€์ฆ
(defn sanitize-input
  "์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฒ€์ฆ ๋ฐ ์ •์ œ"
  [input]
  (-> input
      (clojure.string/trim)
      (clojure.string/replace #"<script.*?>.*?</script>" "")
      (clojure.string/replace #"<.*?>" "")))

;; Rate limiting
(def rate-limit-store (atom {}))

(defn check-rate-limit
  "์š”์ฒญ ๋นˆ๋„ ์ œํ•œ ํ™•์ธ"
  [user-id max-requests time-window-ms]
  (let [now (System/currentTimeMillis)
        user-requests (get @rate-limit-store user-id [])
        recent-requests (filter #(> % (- now time-window-ms)) user-requests)]
    (if (>= (count recent-requests) max-requests)
      false
      (do
        (swap! rate-limit-store assoc user-id (conj recent-requests now))
        true))))
๐Ÿ’ก ๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ: โœ… ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฒ€์ฆ ๋ฐ ์ •์ œ
โœ… SQL ์ธ์ ์…˜ ๋ฐฉ์ง€ (prepared statements ์‚ฌ์šฉ)
โœ… XSS ๊ณต๊ฒฉ ๋ฐฉ์ง€
โœ… Rate limiting ๊ตฌํ˜„
โœ… HTTPS ์‚ฌ์šฉ
โœ… ๋ฏผ๊ฐํ•œ ์ •๋ณด ์•”ํ˜ธํ™”
โœ… ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์‹œํฌ๋ฆฟ ๊ด€๋ฆฌ

๐Ÿงช ํ…Œ์ŠคํŠธ์™€ ๋””๋ฒ„๊น…

์ข‹์€ ์ฝ”๋“œ๋Š” ํ…Œ์ŠคํŠธ๋œ ์ฝ”๋“œ์•ผ! Clojure๋Š” ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์ •๋ง ์ข‹์€ ์–ธ์–ด๊ฑฐ๋“ . ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ํŠน์„ฑ์ƒ ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋“ค์ด ๋งŽ์•„์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ์‰ฌ์›Œ. ๐ŸŽฏ

โœ… ์œ ๋‹› ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

(ns chatbot-ai.core-test
  (:require [clojure.test :refer :all]
            [chatbot-ai.core :as core]
            [chatbot-ai.intent :as intent]))

;; ํ…์ŠคํŠธ ์ „์ฒ˜๋ฆฌ ํ…Œ์ŠคํŠธ
(deftest test-preprocess-text
  (testing "Text preprocessing"
    (is (= "hello world"
           (core/preprocess-text "  HELLO   WORLD  ")))
    (is (= "test"
           (core/preprocess-text "TEST")))
    (is (= ""
           (core/preprocess-text "   ")))))

;; ํ† ํฐํ™” ํ…Œ์ŠคํŠธ
(deftest test-tokenize
  (testing "Text tokenization"
    (is (= ["hello" "world"]
           (core/tokenize "Hello World")))
    (is (= ["clojure" "is" "awesome"]
           (core/tokenize "Clojure is awesome")))))

;; ๊ฐ์ • ๋ถ„์„ ํ…Œ์ŠคํŠธ
(deftest test-sentiment-analysis
  (testing "Sentiment analysis"
    (is (= :positive
           (core/analyze-sentiment "์˜ค๋Š˜ ์ •๋ง ์ข‹์€ ๋‚ ์ด์—์š”!")))
    (is (= :negative
           (core/analyze-sentiment "์ •๋ง ํ™”๋‚˜๊ณ  ์งœ์ฆ๋‚˜์š”")))
    (is (= :neutral
           (core/analyze-sentiment "์˜ค๋Š˜์€ ํ‰๋ฒ”ํ•œ ๋‚ ์ž…๋‹ˆ๋‹ค")))))

;; ์˜๋„ ํŒŒ์•… ํ…Œ์ŠคํŠธ
(deftest test-intent-matching
  (testing "Intent matching"
    (is (= :greeting
           (intent/match-intent "์•ˆ๋…•ํ•˜์„ธ์š”")))
    (is (= :farewell
           (intent/match-intent "์•ˆ๋…•ํžˆ ๊ฐ€์„ธ์š”")))
    (is (= :weather
           (intent/match-intent "์˜ค๋Š˜ ๋‚ ์”จ ์–ด๋•Œ์š”?")))))

;; ์‘๋‹ต ์ƒ์„ฑ ํ…Œ์ŠคํŠธ
(deftest test-response-generation
  (testing "Response generation"
    (let [response (intent/generate-response :greeting)]
      (is (string? response))
      (is (not (empty? response))))))

;; ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ
(deftest test-full-conversation-flow
  (testing "Full conversation flow"
    (let [result (intent/process-user-input "์•ˆ๋…•ํ•˜์„ธ์š”!")]
      (is (= :greeting (:intent result)))
      (is (string? (:response result)))
      (is (> (:confidence result) 0.5)))))

;; ํ…Œ์ŠคํŠธ ์‹คํ–‰
(run-tests)

ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด lein test ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ผ! ๐Ÿš€

๐Ÿ› REPL ๊ธฐ๋ฐ˜ ๋””๋ฒ„๊น…

Clojure์˜ ๊ฐ€์žฅ ํฐ ์žฅ์  ์ค‘ ํ•˜๋‚˜๊ฐ€ REPL์ด์•ผ. ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ :

;; REPL์—์„œ ์‹คํ–‰ํ•ด๋ณด๊ธฐ
(require '[chatbot-ai.core :as core])
(require '[chatbot-ai.intent :as intent])

;; ํ•จ์ˆ˜ ํ…Œ์ŠคํŠธ
(core/preprocess-text "  Hello   World  ")
;; => "hello world"

;; ์˜๋„ ํŒŒ์•… ํ…Œ์ŠคํŠธ
(intent/match-intent "์•ˆ๋…•ํ•˜์„ธ์š”!")
;; => :greeting

;; ์ „์ฒด ํ”„๋กœ์„ธ์Šค ํ…Œ์ŠคํŠธ
(intent/process-user-input "์˜ค๋Š˜ ๋‚ ์”จ๊ฐ€ ์–ด๋•Œ์š”?")
;; => {:intent :weather, :response "๋‚ ์”จ ์ •๋ณด๋ฅผ ํ™•์ธํ•ด๋“œ๋ฆด๊ฒŒ์š”! โ˜€๏ธ", :confidence 0.8}

;; ์ƒํƒœ ํ™•์ธ
@chatbot-ai.conversation/conversation-state
;; => ํ˜„์žฌ ๋Œ€ํ™” ์ƒํƒœ ํ™•์ธ

;; ํŠน์ • ์‚ฌ์šฉ์ž์˜ ํžˆ์Šคํ† ๋ฆฌ ํ™•์ธ
(chatbot-ai.conversation/get-recent-messages "user123" 10)

๐ŸŽ“ REPL ํ™œ์šฉ ํŒ

1. ์ ์ง„์  ๊ฐœ๋ฐœ:
์ž‘์€ ํ•จ์ˆ˜๋ถ€ํ„ฐ ๋งŒ๋“ค๊ณ  REPL์—์„œ ๋ฐ”๋กœ ํ…Œ์ŠคํŠธํ•ด๋ด. ๋ฌธ์ œ๊ฐ€ ์žˆ์œผ๋ฉด ์ฆ‰์‹œ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์–ด!

2. ๋ฐ์ดํ„ฐ ํƒ์ƒ‰:
๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ๋‹ค๋ฃฐ ๋•Œ REPL์—์„œ ์ง์ ‘ ํ™•์ธํ•˜๋ฉด์„œ ์ž‘์—…ํ•˜๋ฉด ํ›จ์”ฌ ์‰ฌ์›Œ.

3. ํ”„๋กœํ† ํƒ€์ดํ•‘:
์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „์— REPL์—์„œ ๋จผ์ € ์‹คํ—˜ํ•ด๋ด. ๋น ๋ฅด๊ฒŒ ์•„์ด๋””์–ด๋ฅผ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์–ด!

4. ๋””๋ฒ„๊น…:
๋ฒ„๊ทธ๊ฐ€ ์žˆ์„ ๋•Œ REPL์—์„œ ํ•จ์ˆ˜๋ฅผ ๋‹จ๊ณ„๋ณ„๋กœ ์‹คํ–‰ํ•˜๋ฉด์„œ ์–ด๋””์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋Š”์ง€ ์ฐพ์„ ์ˆ˜ ์žˆ์–ด.

๐Ÿš€ ๋ฐฐํฌ์™€ ์šด์˜

๊ฐœ๋ฐœ์ด ๋๋‚ฌ์œผ๋ฉด ์ด์ œ ์‹ค์ œ๋กœ ๋ฐฐํฌํ•ด์•ผ์ง€! ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”๋ฐ, ๊ฐ€์žฅ ์‹ค์šฉ์ ์ธ ๋ฐฉ๋ฒ•๋“ค์„ ์•Œ์•„๋ณด์ž. โ˜๏ธ

๐Ÿ“ฆ Uberjar ๋นŒ๋“œ

Clojure ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•˜๋Š” ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ uberjar๋ฅผ ๋งŒ๋“œ๋Š” ๊ฑฐ์•ผ. ๋ชจ๋“  ์˜์กด์„ฑ์ด ํฌํ•จ๋œ ํ•˜๋‚˜์˜ JAR ํŒŒ์ผ์ด์ง€:

;; project.clj์— ์ถ”๊ฐ€
:main chatbot-ai.server
:aot [chatbot-ai.server]
:uberjar-name "chatbot-standalone.jar"

;; ๋นŒ๋“œ ๋ช…๋ น์–ด
;; lein uberjar

;; ์‹คํ–‰
;; java -jar target/chatbot-standalone.jar

๐Ÿณ Docker ์ปจํ…Œ์ด๋„ˆํ™”

Docker๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋””์„œ๋“  ๋™์ผํ•œ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด:

# Dockerfile
FROM clojure:openjdk-11-lein AS builder

WORKDIR /app
COPY project.clj .
RUN lein deps

COPY . .
RUN lein uberjar

FROM openjdk:11-jre-slim

WORKDIR /app
COPY --from=builder /app/target/chatbot-standalone.jar .

EXPOSE 3000

CMD ["java", "-jar", "chatbot-standalone.jar"]

# ๋นŒ๋“œ ๋ฐ ์‹คํ–‰
# docker build -t chatbot-ai .
# docker run -p 3000:3000 chatbot-ai

โ˜๏ธ ํด๋ผ์šฐ๋“œ ๋ฐฐํฌ

AWS, Google Cloud, Heroku ๋“ฑ ๋‹ค์–‘ํ•œ ํด๋ผ์šฐ๋“œ ํ”Œ๋žซํผ์— ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์–ด. Heroku ์˜ˆ์‹œ๋ฅผ ๋ณด์ž:

;; Procfile ์ƒ์„ฑ
web: java -jar target/chatbot-standalone.jar $PORT

;; ๋ฐฐํฌ ๋ช…๋ น์–ด
;; heroku create my-clojure-chatbot
;; git push heroku main
๐Ÿ’ก ์šด์˜ ํŒ: โœ… ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์„ค์ • ๊ด€๋ฆฌ (API ํ‚ค, DB ์—ฐ๊ฒฐ ์ •๋ณด ๋“ฑ)
โœ… ๋กœ๊ทธ ์ˆ˜์ง‘ ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง ์„ค์ • (Papertrail, Datadog ๋“ฑ)
โœ… ์ž๋™ ์Šค์ผ€์ผ๋ง ์„ค์ •
โœ… ๋ฐฑ์—… ์ „๋žต ์ˆ˜๋ฆฝ
โœ… CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ• (GitHub Actions, Jenkins ๋“ฑ)

๐ŸŽจ ์‹ค์ „ ์˜ˆ์ œ: ๊ณ ๊ฐ ์„œ๋น„์Šค ์ฑ—๋ด‡

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ณ ๊ฐ ์„œ๋น„์Šค ์ฑ—๋ด‡์„ ๋งŒ๋“ค์–ด๋ณด์ž! ์ด๊ฑด ์‹ค์ œ ๋น„์ฆˆ๋‹ˆ์Šค์—์„œ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ค€์ด์•ผ. ๐Ÿ’ผ

๐Ÿ“‹ ์š”๊ตฌ์‚ฌํ•ญ ์ •์˜

์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“ค ์ฑ—๋ด‡์€ ๋‹ค์Œ ๊ธฐ๋Šฅ๋“ค์„ ๊ฐ€์ ธ์•ผ ํ•ด:
- ์ œํ’ˆ ์ •๋ณด ๋ฌธ์˜ ์‘๋‹ต
- ์ฃผ๋ฌธ ์ƒํƒœ ํ™•์ธ
- ํ™˜๋ถˆ/๊ตํ™˜ ์•ˆ๋‚ด
- FAQ ์ž๋™ ์‘๋‹ต
- ๋ณต์žกํ•œ ๋ฌธ์˜๋Š” ์ƒ๋‹ด์› ์—ฐ๊ฒฐ

(ns chatbot-ai.customer-service
  (:require [chatbot-ai.core :as core]
            [chatbot-ai.intent :as intent]
            [clojure.string :as str]))

;; ์ œํ’ˆ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค (์‹ค์ œ๋กœ๋Š” DB์—์„œ ๊ฐ€์ ธ์™€์•ผ ํ•จ)
(def products
  {"๋…ธํŠธ๋ถ" {:name "๊ณ ์„ฑ๋Šฅ ๋…ธํŠธ๋ถ"
            :price 1500000
            :stock 10
            :description "์ตœ์‹  CPU์™€ GPU๋ฅผ ํƒ‘์žฌํ•œ ๊ณ ์„ฑ๋Šฅ ๋…ธํŠธ๋ถ"}
   "๋งˆ์šฐ์Šค" {:name "๋ฌด์„  ๋งˆ์šฐ์Šค"
            :price 50000
            :stock 50
            :description "์ธ์ฒด๊ณตํ•™์  ๋””์ž์ธ์˜ ๋ฌด์„  ๋งˆ์šฐ์Šค"}
   "ํ‚ค๋ณด๋“œ" {:name "๊ธฐ๊ณ„์‹ ํ‚ค๋ณด๋“œ"
             :price 120000
             :stock 30
             :description "์ฒญ์ถ• ์Šค์œ„์น˜์˜ ๊ธฐ๊ณ„์‹ ํ‚ค๋ณด๋“œ"}})

;; ์ฃผ๋ฌธ ๋ฐ์ดํ„ฐ (์‹ค์ œ๋กœ๋Š” DB์—์„œ ๊ฐ€์ ธ์™€์•ผ ํ•จ)
(def orders
  {"ORD-001" {:order-id "ORD-001"
              :product "๋…ธํŠธ๋ถ"
              :status "๋ฐฐ์†ก์ค‘"
              :tracking "CJ-123456789"}
   "ORD-002" {:order-id "ORD-002"
              :product "๋งˆ์šฐ์Šค"
              :status "๋ฐฐ์†ก์™„๋ฃŒ"
              :tracking "CJ-987654321"}})

;; FAQ ๋ฐ์ดํ„ฐ
(def faq-data
  [{:question "๋ฐฐ์†ก์€ ์–ผ๋งˆ๋‚˜ ๊ฑธ๋ฆฌ๋‚˜์š”?"
    :answer "์ผ๋ฐ˜ ๋ฐฐ์†ก์€ 2-3์ผ, ๋น ๋ฅธ ๋ฐฐ์†ก์€ 1์ผ ์†Œ์š”๋ฉ๋‹ˆ๋‹ค."}
   {:question "ํ™˜๋ถˆ์€ ์–ด๋–ป๊ฒŒ ํ•˜๋‚˜์š”?"
    :answer "์ œํ’ˆ ์ˆ˜๋ น ํ›„ 7์ผ ์ด๋‚ด ๋ฏธ๊ฐœ๋ด‰ ์ƒํƒœ๋กœ ํ™˜๋ถˆ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค."}
   {:question "๊ตํ™˜์€ ๊ฐ€๋Šฅํ•œ๊ฐ€์š”?"
    :answer "์ œํ’ˆ ํ•˜์ž๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ 14์ผ ์ด๋‚ด ๊ตํ™˜ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค."}
   {:question "๊ฒฐ์ œ ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ด ์žˆ๋‚˜์š”?"
    :answer "์‹ ์šฉ์นด๋“œ, ๊ณ„์ขŒ์ด์ฒด, ๋ฌดํ†ต์žฅ์ž…๊ธˆ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค."}])

;; ์ œํ’ˆ ๊ฒ€์ƒ‰
(defn search-product
  "์ œํ’ˆ ์ด๋ฆ„์œผ๋กœ ๊ฒ€์ƒ‰"
  [query]
  (let [query-lower (str/lower-case query)]
    (->> products
         (filter (fn [[key data]]
                   (str/includes? (str/lower-case key) query-lower)))
         first)))

;; ์ฃผ๋ฌธ ์กฐํšŒ
(defn get-order-status
  "์ฃผ๋ฌธ ๋ฒˆํ˜ธ๋กœ ์ƒํƒœ ์กฐํšŒ"
  [order-id]
  (get orders order-id))

;; FAQ ๊ฒ€์ƒ‰
(defn search-faq
  "์งˆ๋ฌธ๊ณผ ๊ฐ€์žฅ ์œ ์‚ฌํ•œ FAQ ์ฐพ๊ธฐ"
  [question]
  (let [question-tokens (set (core/tokenize question))]
    (->> faq-data
         (map (fn [faq]
                (let [faq-tokens (set (core/tokenize (:question faq)))
                      similarity (/ (count (clojure.set/intersection question-tokens faq-tokens))
                                   (max (count question-tokens) (count faq-tokens)))]
                  (assoc faq :similarity similarity))))
         (filter #(> (:similarity %) 0.2))
         (sort-by :similarity >)
         first)))

;; ๊ณ ๊ฐ ์„œ๋น„์Šค ์˜๋„ ํŒŒ์•…
(defn detect-cs-intent
  "๊ณ ๊ฐ ์„œ๋น„์Šค ๊ด€๋ จ ์˜๋„ ํŒŒ์•…"
  [message]
  (let [msg-lower (str/lower-case message)]
    (cond
      (some #(str/includes? msg-lower %) ["์ œํ’ˆ" "์ƒํ’ˆ" "๊ฐ€๊ฒฉ" "์žฌ๊ณ "])
      :product-inquiry
      
      (some #(str/includes? msg-lower %) ["์ฃผ๋ฌธ" "๋ฐฐ์†ก" "์ถ”์ "])
      :order-inquiry
      
      (some #(str/includes? msg-lower %) ["ํ™˜๋ถˆ" "๊ตํ™˜" "๋ฐ˜ํ’ˆ"])
      :refund-inquiry
      
      (some #(str/includes? msg-lower %) ["์ƒ๋‹ด์›" "์ง์›" "์‚ฌ๋žŒ"])
      :agent-request
      
      :else
      :faq-search)))

;; ๊ณ ๊ฐ ์„œ๋น„์Šค ์‘๋‹ต ์ƒ์„ฑ
(defn generate-cs-response
  "๊ณ ๊ฐ ์„œ๋น„์Šค ์‘๋‹ต ์ƒ์„ฑ"
  [message]
  (let [cs-intent (detect-cs-intent message)]
    (case cs-intent
      :product-inquiry
      (if-let [[product-key product-data] (search-product message)]
        (format "๐Ÿ›๏ธ %s\n๊ฐ€๊ฒฉ: %,d์›\n์žฌ๊ณ : %d๊ฐœ\n%s"
                (:name product-data)
                (:price product-data)
                (:stock product-data)
                (:description product-data))
        "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ์ œํ’ˆ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์ œํ’ˆ๋ช…์œผ๋กœ ๊ฒ€์ƒ‰ํ•ด์ฃผ์„ธ์š”.")
      
      :order-inquiry
      (let [order-id (re-find #"ORD-\d+" message)]
        (if-let [order (get-order-status order-id)]
          (format "๐Ÿ“ฆ ์ฃผ๋ฌธ ์ •๋ณด\n์ฃผ๋ฌธ๋ฒˆํ˜ธ: %s\n์ œํ’ˆ: %s\n์ƒํƒœ: %s\n์†ก์žฅ๋ฒˆํ˜ธ: %s"
                  (:order-id order)
                  (:product order)
                  (:status order)
                  (:tracking order))
          "์ฃผ๋ฌธ๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”. (์˜ˆ: ORD-001)"))
      
      :refund-inquiry
      "๐Ÿ’ฐ ํ™˜๋ถˆ/๊ตํ™˜ ์•ˆ๋‚ด\n- ํ™˜๋ถˆ: ์ œํ’ˆ ์ˆ˜๋ น ํ›„ 7์ผ ์ด๋‚ด, ๋ฏธ๊ฐœ๋ด‰ ์ƒํƒœ\n- ๊ตํ™˜: ์ œํ’ˆ ํ•˜์ž ์‹œ 14์ผ ์ด๋‚ด\n์ž์„ธํ•œ ๋ฌธ์˜๋Š” ๊ณ ๊ฐ์„ผํ„ฐ(1588-1234)๋กœ ์—ฐ๋ฝ์ฃผ์„ธ์š”."
      
      :agent-request
      "๐Ÿ‘จโ€๐Ÿ’ผ ์ƒ๋‹ด์› ์—ฐ๊ฒฐ์„ ์›ํ•˜์‹œ๋‚˜์š”?\nํ‰์ผ 09:00-18:00์— ๊ณ ๊ฐ์„ผํ„ฐ(1588-1234)๋กœ ์—ฐ๋ฝ์ฃผ์‹œ๊ฑฐ๋‚˜,\n'์ƒ๋‹ด์› ์—ฐ๊ฒฐ'์„ ์ž…๋ ฅํ•˜์‹œ๋ฉด ๋Œ€๊ธฐ์—ด์— ๋“ฑ๋กํ•ด๋“œ๋ฆฝ๋‹ˆ๋‹ค."
      
      :faq-search
      (if-let [faq (search-faq message)]
        (format "โ“ %s\n\nโœ… %s" (:question faq) (:answer faq))
        "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ๊ด€๋ จ ์ •๋ณด๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ์ƒ๋‹ด์› ์—ฐ๊ฒฐ์„ ์›ํ•˜์‹œ๋ฉด '์ƒ๋‹ด์›'์ด๋ผ๊ณ  ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."))))

;; ํ…Œ์ŠคํŠธ
(comment
  (generate-cs-response "๋…ธํŠธ๋ถ ๊ฐ€๊ฒฉ์ด ์–ผ๋งˆ์ธ๊ฐ€์š”?")
  ;; => "๐Ÿ›๏ธ ๊ณ ์„ฑ๋Šฅ ๋…ธํŠธ๋ถ\n๊ฐ€๊ฒฉ: 1,500,000์›\n์žฌ๊ณ : 10๊ฐœ\n์ตœ์‹  CPU์™€ GPU๋ฅผ ํƒ‘์žฌํ•œ ๊ณ ์„ฑ๋Šฅ ๋…ธํŠธ๋ถ"
  
  (generate-cs-response "์ฃผ๋ฌธ๋ฒˆํ˜ธ ORD-001 ๋ฐฐ์†ก ์–ด๋””๊นŒ์ง€ ์™”๋‚˜์š”?")
  ;; => "๐Ÿ“ฆ ์ฃผ๋ฌธ ์ •๋ณด\n์ฃผ๋ฌธ๋ฒˆํ˜ธ: ORD-001\n์ œํ’ˆ: ๋…ธํŠธ๋ถ\n์ƒํƒœ: ๋ฐฐ์†ก์ค‘\n์†ก์žฅ๋ฒˆํ˜ธ: CJ-123456789"
  
  (generate-cs-response "ํ™˜๋ถˆํ•˜๊ณ  ์‹ถ์–ด์š”")
  ;; => "๐Ÿ’ฐ ํ™˜๋ถˆ/๊ตํ™˜ ์•ˆ๋‚ด..."
  
  (generate-cs-response "๋ฐฐ์†ก์€ ์–ผ๋งˆ๋‚˜ ๊ฑธ๋ฆฌ๋‚˜์š”?")
  ;; => "โ“ ๋ฐฐ์†ก์€ ์–ผ๋งˆ๋‚˜ ๊ฑธ๋ฆฌ๋‚˜์š”?\n\nโœ… ์ผ๋ฐ˜ ๋ฐฐ์†ก์€ 2-3์ผ, ๋น ๋ฅธ ๋ฐฐ์†ก์€ 1์ผ ์†Œ์š”๋ฉ๋‹ˆ๋‹ค."
  )

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์‹ค์ œ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ณ ๊ฐ ์„œ๋น„์Šค ์ฑ—๋ด‡์ด ์™„์„ฑ๋ผ! ๋ฌผ๋ก  ์‹ค์ œ ํ”„๋กœ๋•์…˜์—์„œ๋Š” ๋” ๋งŽ์€ ๊ธฐ๋Šฅ๊ณผ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜์ง€๋งŒ, ๊ธฐ๋ณธ ๊ณจ๊ฒฉ์€ ์ด ์ •๋„๋ฉด ์ถฉ๋ถ„ํ•ด. ๐ŸŽ‰

๐Ÿ“ˆ ์„ฑ๋Šฅ ์ตœ์ ํ™”์™€ ์Šค์ผ€์ผ๋ง

์ฑ—๋ด‡์ด ์ธ๊ธฐ๋ฅผ ๋Œ๋ฉด ๋™์‹œ ์‚ฌ์šฉ์ž๊ฐ€ ๋งŽ์•„์งˆ ๊ฑฐ์•ผ. ๊ทธ๋Ÿด ๋•Œ๋ฅผ ๋Œ€๋น„ํ•ด์„œ ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•ด! ๐Ÿ’จ

โšก ์บ์‹ฑ ์ „๋žต

(ns chatbot-ai.cache
  (:require [clojure.core.cache :as cache]))

;; LRU ์บ์‹œ ์ƒ์„ฑ
(def response-cache
  (atom (cache/lru-cache-factory {} :threshold 1000)))

;; ์บ์‹œ์—์„œ ์‘๋‹ต ๊ฐ€์ ธ์˜ค๊ธฐ
(defn get-cached-response
  "์บ์‹œ์—์„œ ์‘๋‹ต ์กฐํšŒ"
  [key]
  (cache/lookup @response-cache key))

;; ์บ์‹œ์— ์‘๋‹ต ์ €์žฅ
(defn cache-response!
  "์‘๋‹ต์„ ์บ์‹œ์— ์ €์žฅ"
  [key value]
  (swap! response-cache cache/miss key value))

;; ์บ์‹ฑ์„ ์ ์šฉํ•œ ์‘๋‹ต ์ƒ์„ฑ
(defn cached-generate-response
  "์บ์‹ฑ์„ ํ™œ์šฉํ•œ ์‘๋‹ต ์ƒ์„ฑ"
  [message]
  (let [cache-key (hash message)]
    (if-let [cached (get-cached-response cache-key)]
      (do
        (println "Cache hit! ๐ŸŽฏ")
        cached)
      (let [response (generate-cs-response message)]
        (cache-response! cache-key response)
        response))))

๐Ÿ”„ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ

(ns chatbot-ai.async
  (:require [clojure.core.async :as async]))

;; ๋น„๋™๊ธฐ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ
(defn process-message-async
  "๋ฉ”์‹œ์ง€๋ฅผ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌ"
  [message callback]
  (async/go
    (let [response (generate-cs-response message)]
      (callback response))))

;; ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ
(defn process-batch
  "์—ฌ๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐฐ์น˜๋กœ ์ฒ˜๋ฆฌ"
  [messages]
  (let [results-chan (async/chan (count messages))]
    (doseq [msg messages]
      (async/go
        (let [response (generate-cs-response msg)]
          (async/>! results-chan {:message msg :response response}))))
    
    ;; ๋ชจ๋“  ๊ฒฐ๊ณผ ์ˆ˜์ง‘
    (async/go-loop [results []]
      (if (= (count results) (count messages))
        results
        (recur (conj results (async/<! results-chan)))))))

๐Ÿ“Š ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง

(ns chatbot-ai.monitoring
  (:require [clojure.tools.logging :as log]))

;; ์„ฑ๋Šฅ ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘
(defn measure-performance
  "ํ•จ์ˆ˜ ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •"
  [f & args]
  (let [start (System/nanoTime)
        result (apply f args)
        end (System/nanoTime)
        duration-ms (/ (- end start) 1000000.0)]
    (log/info (format "Execution time: %.2fms" duration-ms))
    {:result result
     :duration-ms duration-ms}))

;; ์‘๋‹ต ์‹œ๊ฐ„ ํ†ต๊ณ„
(def response-times (atom []))

(defn record-response-time
  "์‘๋‹ต ์‹œ๊ฐ„ ๊ธฐ๋ก"
  [duration]
  (swap! response-times conj duration)
  (when (> (count @response-times) 1000)
    (swap! response-times #(vec (take-last 1000 %)))))

(defn get-performance-stats
  "์„ฑ๋Šฅ ํ†ต๊ณ„ ์กฐํšŒ"
  []
  (let [times @response-times]
    (when (seq times)
      {:count (count times)
       :avg (/ (reduce + times) (count times))
       :min (apply min times)
       :max (apply max times)
       :p95 (nth (sort times) (int (* 0.95 (count times))))})))

๐Ÿš€ ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ฒดํฌ๋ฆฌ์ŠคํŠธ

โœ… ์บ์‹ฑ: ์ž์ฃผ ์š”์ฒญ๋˜๋Š” ์‘๋‹ต์€ ์บ์‹œ์— ์ €์žฅ
โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ธ๋ฑ์‹ฑ: ์ž์ฃผ ์กฐํšŒ๋˜๋Š” ์ปฌ๋Ÿผ์— ์ธ๋ฑ์Šค ์ถ”๊ฐ€
โœ… ์ปค๋„ฅ์…˜ ํ’€๋ง: DB ์—ฐ๊ฒฐ์„ ์žฌ์‚ฌ์šฉ
โœ… ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ: ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…์€ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌ
โœ… ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ: ์—ฌ๋Ÿฌ ์„œ๋ฒ„์— ๋ถ€ํ•˜ ๋ถ„์‚ฐ
โœ… CDN ํ™œ์šฉ: ์ •์  ๋ฆฌ์†Œ์Šค๋Š” CDN์œผ๋กœ ์ œ๊ณต
โœ… ์••์ถ•: ์‘๋‹ต ๋ฐ์ดํ„ฐ gzip ์••์ถ•
โœ… ๋ชจ๋‹ˆํ„ฐ๋ง: ์‹ค์‹œ๊ฐ„ ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ์•Œ๋ฆผ

๐ŸŽฏ ์‹ค์ „ ํŒ๊ณผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์‹ค์ „์—์„œ ์œ ์šฉํ•œ ํŒ๋“ค์„ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ! ์ด๊ฑด ๋‚ด๊ฐ€ ์‹ค์ œ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋ฉด์„œ ๋ฐฐ์šด ๊ฒƒ๋“ค์ด์•ผ. ๐Ÿ’ก

๐Ÿ“ ์ฝ”๋“œ ๊ตฌ์กฐํ™”

ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ฝ”๋“œ ๊ตฌ์กฐ๊ฐ€ ์ค‘์š”ํ•ด์ ธ. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ์ถ”์ฒœํ•ด:

chatbot-ai/
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ chatbot_ai/
โ”‚       โ”œโ”€โ”€ core.clj              # ํ•ต์‹ฌ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜
โ”‚       โ”œโ”€โ”€ server.clj            # ์›น ์„œ๋ฒ„
โ”‚       โ”œโ”€โ”€ conversation.clj      # ๋Œ€ํ™” ๊ด€๋ฆฌ
โ”‚       โ”œโ”€โ”€ intent.clj            # ์˜๋„ ํŒŒ์•…
โ”‚       โ”œโ”€โ”€ ai_service.clj        # AI ์„œ๋น„์Šค ์—ฐ๋™
โ”‚       โ”œโ”€โ”€ database.clj          # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
โ”‚       โ”œโ”€โ”€ security.clj          # ๋ณด์•ˆ
โ”‚       โ”œโ”€โ”€ cache.clj             # ์บ์‹ฑ
โ”‚       โ””โ”€โ”€ monitoring.clj        # ๋ชจ๋‹ˆํ„ฐ๋ง
โ”œโ”€โ”€ test/
โ”‚   โ””โ”€โ”€ chatbot_ai/
โ”‚       โ”œโ”€โ”€ core_test.clj
โ”‚       โ””โ”€โ”€ intent_test.clj
โ”œโ”€โ”€ resources/
โ”‚   โ”œโ”€โ”€ public/
โ”‚   โ”‚   โ””โ”€โ”€ index.html
โ”‚   โ””โ”€โ”€ config/
โ”‚       โ””โ”€โ”€ config.edn
โ”œโ”€โ”€ project.clj
โ””โ”€โ”€ README.md

๐ŸŽจ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„ 

์ฑ—๋ด‡์˜ ์„ฑ๋Šฅ๋„ ์ค‘์š”ํ•˜์ง€๋งŒ, ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ๋” ์ค‘์š”ํ•ด! ๋‹ค์Œ ์‚ฌํ•ญ๋“ค์„ ๊ณ ๋ คํ•ด๋ด:

1. ํƒ€์ดํ•‘ ์ธ๋””์ผ€์ดํ„ฐ ์ฑ—๋ด‡์ด ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๋Š” ๋™์•ˆ "์ž…๋ ฅ ์ค‘..." ํ‘œ์‹œ๋ฅผ ๋ณด์—ฌ์ค˜. ์‚ฌ์šฉ์ž๊ฐ€ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ๋‹ค๋Š” ๊ฑธ ์•Œ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฑฐ์ง€.

2. ๋น ๋ฅธ ์‘๋‹ต ๋ฒ„ํŠผ
์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ๋“ค์„ ๋ฒ„ํŠผ์œผ๋กœ ์ œ๊ณตํ•ด. ์‚ฌ์šฉ์ž๊ฐ€ ํƒ€์ดํ•‘ํ•˜์ง€ ์•Š์•„๋„ ๋˜๊ฒŒ!

3. ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๊ฐœ์„ 
"์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค" ๋Œ€์‹  "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ์ž ์‹œ ๋ฌธ์ œ๊ฐ€ ์žˆ๋„ค์š”. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์‹œ๊ฒ ์–ด์š”?" ๊ฐ™์ด ์นœ๊ทผํ•˜๊ฒŒ!

4. ์ปจํ…์ŠคํŠธ ์œ ์ง€
์ด์ „ ๋Œ€ํ™”๋ฅผ ๊ธฐ์–ตํ•ด์„œ ์ž์—ฐ์Šค๋Ÿฌ์šด ๋Œ€ํ™”๊ฐ€ ๋˜๋„๋ก ํ•ด์ค˜.

5. ์ด๋ชจ์ง€ ํ™œ์šฉ
์ ์ ˆํ•œ ์ด๋ชจ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋Œ€ํ™”๊ฐ€ ๋” ์นœ๊ทผํ•ด์ ธ! ๐Ÿ˜Š

๐Ÿ” ๋””๋ฒ„๊น… ํŒ

;; ๋””๋ฒ„๊น…์šฉ ๋กœ๊น… ๋งคํฌ๋กœ
(defmacro debug-log
  "๋””๋ฒ„๊น… ์ •๋ณด ์ถœ๋ ฅ"
  [expr]
  `(let [result# ~expr]
     (println "DEBUG:" '~expr "=>" result#)
     result#))

;; ์‚ฌ์šฉ ์˜ˆ์‹œ
(debug-log (+ 1 2))
;; DEBUG: (+ 1 2) => 3

;; ์กฐ๊ฑด๋ถ€ ๋กœ๊น…
(def debug-mode (atom true))

(defn log-when-debug
  "๋””๋ฒ„๊ทธ ๋ชจ๋“œ์ผ ๋•Œ๋งŒ ๋กœ๊น…"
  [& args]
  (when @debug-mode
    (apply println "DEBUG:" args)))

๐Ÿ’ฐ ๋น„์šฉ ์ตœ์ ํ™”

AI API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋น„์šฉ์ด ๋ฐœ์ƒํ•ด. ๋‹ค์Œ ๋ฐฉ๋ฒ•๋“ค๋กœ ๋น„์šฉ์„ ์ ˆ๊ฐํ•  ์ˆ˜ ์žˆ์–ด:

1. ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ ‘๊ทผ ๊ฐ„๋‹จํ•œ ์งˆ๋ฌธ์€ ๊ทœ์น™ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ , ๋ณต์žกํ•œ ์งˆ๋ฌธ๋งŒ AI API ์‚ฌ์šฉ

2. ์‘๋‹ต ์บ์‹ฑ
๊ฐ™์€ ์งˆ๋ฌธ์— ๋Œ€ํ•œ ์‘๋‹ต์€ ์บ์‹œ์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ

3. ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ
์—ฌ๋Ÿฌ ์š”์ฒญ์„ ๋ชจ์•„์„œ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋ฉด ๋น„์šฉ ์ ˆ๊ฐ

4. ํ† ํฐ ์ตœ์ ํ™”
๋ถˆํ•„์š”ํ•œ ์ปจํ…์ŠคํŠธ๋Š” ์ œ๊ฑฐํ•˜๊ณ  ํ•ต์‹ฌ๋งŒ ์ „์†ก

5. ๋กœ์ปฌ ๋ชจ๋ธ ํ™œ์šฉ
๊ฐ€๋Šฅํ•˜๋ฉด ๋กœ์ปฌ์—์„œ ๋Œ์•„๊ฐ€๋Š” ์ž‘์€ ๋ชจ๋ธ ์‚ฌ์šฉ

๐Ÿ’ก ์žฌ๋Šฅ๋„ท ํ™œ์šฉ: ์ฑ—๋ด‡ ๊ฐœ๋ฐœ์ด ์ฒ˜์Œ์ด๋ผ ๋ง‰๋ง‰ํ•˜๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท์—์„œ ๊ฒฝํ—˜ ๋งŽ์€ ๊ฐœ๋ฐœ์ž์˜ ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋‚˜ ๋ฉ˜ํ† ๋ง์„ ๋ฐ›์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ์‹ค์ „ ๊ฒฝํ—˜์„ ํ†ตํ•ด ๋ฐฐ์šฐ๋Š” ๊ฒŒ ๊ฐ€์žฅ ๋น ๋ฅด๊ฑฐ๋“ !

๐ŸŒŸ ๋ฏธ๋ž˜ ์ „๋ง๊ณผ ๋ฐœ์ „ ๋ฐฉํ–ฅ

์ฑ—๋ด‡ ๊ธฐ์ˆ ์€ ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด. ์•ž์œผ๋กœ ์–ด๋–ค ๋ฐฉํ–ฅ์œผ๋กœ ๋ฐœ์ „ํ• ์ง€, ๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ค€๋น„ํ•ด์•ผ ํ• ์ง€ ์•Œ์•„๋ณด์ž! ๐Ÿ”ฎ

๐Ÿš€ ์ตœ์‹  ํŠธ๋ Œ๋“œ

1. ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ AI
ํ…์ŠคํŠธ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ด๋ฏธ์ง€, ์Œ์„ฑ, ๋น„๋””์˜ค๊นŒ์ง€ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฑ—๋ด‡์ด ๋Š˜์–ด๋‚˜๊ณ  ์žˆ์–ด. Clojure์—์„œ๋„ ์ด๋Ÿฐ ๊ธฐ๋Šฅ๋“ค์„ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์–ด:

2. ๊ฐœ์ธํ™”
์‚ฌ์šฉ์ž์˜ ์„ ํ˜ธ๋„์™€ ์ด์ „ ๋Œ€ํ™”๋ฅผ ํ•™์Šตํ•ด์„œ ๋งž์ถคํ˜• ์‘๋‹ต์„ ์ œ๊ณตํ•˜๋Š” ๊ฑฐ์•ผ. ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ์„ ํ™œ์šฉํ•˜๋ฉด ๊ฐ€๋Šฅํ•ด!

3. ๊ฐ์ • ์ธ์‹
์‚ฌ์šฉ์ž์˜ ๊ฐ์ • ์ƒํƒœ๋ฅผ ํŒŒ์•…ํ•ด์„œ ๊ทธ์— ๋งž๋Š” ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๋Š” ๊ฑฐ์ง€. ํ™”๋‚œ ๊ณ ๊ฐ์—๊ฒŒ๋Š” ๋” ์‹ ์ค‘ํ•˜๊ฒŒ ์‘๋‹ตํ•˜๋Š” ์‹์œผ๋กœ!

4. ๋‹ค๊ตญ์–ด ์ง€์›
ํ•˜๋‚˜์˜ ์ฑ—๋ด‡์ด ์—ฌ๋Ÿฌ ์–ธ์–ด๋ฅผ ์ง€์›ํ•˜๋Š” ๊ฒŒ ํ‘œ์ค€์ด ๋˜๊ณ  ์žˆ์–ด. ๋ฒˆ์—ญ API๋ฅผ ํ†ตํ•ฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด.

๐Ÿ“š ๋” ๊ณต๋ถ€ํ•˜๋ฉด ์ข‹์€ ๊ฒƒ๋“ค

์ฃผ์ œ ์„ค๋ช… ์ถ”์ฒœ ๋ฆฌ์†Œ์Šค
์ž์—ฐ์–ด ์ฒ˜๋ฆฌ ํ…์ŠคํŠธ ์ดํ•ด์™€ ์ƒ์„ฑ์˜ ๊ธฐ์ดˆ Stanford NLP ๊ฐ•์˜, spaCy ๋ฌธ์„œ
๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ ํ•™์Šต๊ณผ ์˜ˆ์ธก Andrew Ng์˜ ML ๊ฐ•์˜, scikit-learn
๋”ฅ๋Ÿฌ๋‹ ์‹ ๊ฒฝ๋ง๊ณผ ํŠธ๋žœ์Šคํฌ๋จธ fast.ai, PyTorch ํŠœํ† ๋ฆฌ์–ผ
ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ Clojure์˜ ํ•ต์‹ฌ ํŒจ๋Ÿฌ๋‹ค์ž„ Clojure for the Brave and True
์‹œ์Šคํ…œ ๋””์ž์ธ ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ์•„ํ‚คํ…์ฒ˜ Designing Data-Intensive Applications

๐ŸŽฏ ๋‹ค์Œ ๋‹จ๊ณ„

๊ธฐ๋ณธ์ ์ธ ์ฑ—๋ด‡์„ ๋งŒ๋“ค์—ˆ๋‹ค๋ฉด, ๋‹ค์Œ ๊ธฐ๋Šฅ๋“ค์„ ์ถ”๊ฐ€ํ•ด๋ณด๋Š” ๊ฑด ์–ด๋•Œ?

๋ ˆ๋ฒจ 1: ๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ฐ•ํ™” โœ… ๋” ๋งŽ์€ ์˜๋„ ํŒจํ„ด ์ถ”๊ฐ€
โœ… ์‘๋‹ต ํ…œํ”Œ๋ฆฟ ๋‹ค์–‘ํ™”
โœ… ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ฐœ์„ 
โœ… ๋กœ๊น… ์‹œ์Šคํ…œ ๊ตฌ์ถ•

๋ ˆ๋ฒจ 2: ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ
โœ… ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ ํ†ตํ•ฉ
โœ… ๋Œ€ํ™” ํ๋ฆ„ ๊ด€๋ฆฌ
โœ… ๋ฉ€ํ‹ฐํ„ด ๋Œ€ํ™” ์ง€์›
โœ… ๊ฐœ์ธํ™” ๊ธฐ๋Šฅ

๋ ˆ๋ฒจ 3: ํ”„๋กœ๋•์…˜ ์ค€๋น„
โœ… ์„ฑ๋Šฅ ์ตœ์ ํ™”
โœ… ๋ณด์•ˆ ๊ฐ•ํ™”
โœ… ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ์•Œ๋ฆผ
โœ… A/B ํ…Œ์ŠคํŒ…

๋ ˆ๋ฒจ 4: ๊ณ ๊ธ‰ AI
โœ… ์ปค์Šคํ…€ ๋ชจ๋ธ ํ•™์Šต
โœ… ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์ง€์›
โœ… ์‹ค์‹œ๊ฐ„ ํ•™์Šต
โœ… ๊ฐ์ • ๋ถ„์„

๐ŸŽ“ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ์—ˆ๋‹ค๋ฉด ์ •๋ง ๋Œ€๋‹จํ•ด! ๐Ÿ‘ ์šฐ๋ฆฌ๋Š” Clojure๋กœ ์ธ๊ณต์ง€๋Šฅ ์ฑ—๋ด‡์„ ๋งŒ๋“œ๋Š” ์ „์ฒด ๊ณผ์ •์„ ์‚ดํŽด๋ดค์–ด. ๊ธฐ์ดˆ์ ์ธ ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ๋จธ์‹ ๋Ÿฌ๋‹ ํ†ตํ•ฉ, ์›น ์„œ๋ฒ„ ๊ตฌ์ถ•, ๊ทธ๋ฆฌ๊ณ  ์‹ค์ „ ์˜ˆ์ œ๊นŒ์ง€ ๋‹ค๋ค˜์ง€.

Clojure๋Š” ์ฒ˜์Œ์—” ๊ด„ํ˜ธ๊ฐ€ ๋งŽ์•„์„œ ์–ด์ƒ‰ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ต์ˆ™ํ•ด์ง€๋ฉด ์ •๋ง ๊ฐ•๋ ฅํ•œ ์–ธ์–ด์•ผ. ํŠนํžˆ ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์žฅ์ ๋“ค - ๋ถˆ๋ณ€์„ฑ, ์ˆœ์ˆ˜ ํ•จ์ˆ˜, ๊ณ ์ฐจ ํ•จ์ˆ˜ ๋“ฑ - ์ด ์ฑ—๋ด‡ ๊ฐœ๋ฐœ์— ๋”ฑ ๋งž์•„๋–จ์–ด์ ธ. ๐ŸŽฏ

์ฑ—๋ด‡ ๊ฐœ๋ฐœ์€ ๋์ด ์—†๋Š” ์—ฌ์ •์ด์•ผ. ๊ธฐ์ˆ ์€ ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ , ์‚ฌ์šฉ์ž์˜ ์š”๊ตฌ์‚ฌํ•ญ๋„ ๊ณ„์† ๋ณ€ํ•ด. ํ•˜์ง€๋งŒ ๊ทธ๊ฒŒ ๋ฐ”๋กœ ์ด ๋ถ„์•ผ์˜ ๋งค๋ ฅ์ด์ง€! ํ•ญ์ƒ ์ƒˆ๋กœ์šด ๊ฑธ ๋ฐฐ์šฐ๊ณ , ๋” ๋‚˜์€ ์†”๋ฃจ์…˜์„ ๋งŒ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๐Ÿš€

์ด ๊ธ€์—์„œ ๋‹ค๋ฃฌ ๋‚ด์šฉ๋“ค์ด ๋„ˆ์˜ ์ฑ—๋ด‡ ๊ฐœ๋ฐœ ์—ฌ์ •์— ๋„์›€์ด ๋˜๊ธธ ๋ฐ”๋ผ. ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๊ฑฐ๋‚˜ ๋” ๊นŠ์ด ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด, ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์งˆ๋ฌธํ•˜๊ฑฐ๋‚˜ ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์„ ๋ฐ›๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ํŠนํžˆ ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋Š” ์‹ค์ „ ๊ฒฝํ—˜์ด ํ’๋ถ€ํ•œ ๊ฐœ๋ฐœ์ž๋“ค์„ ๋งŒ๋‚  ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ ๋ง์ด์•ผ! ๐Ÿ˜Š

์ž, ์ด์ œ ์ง์ ‘ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด์ž! ์ž‘์€ ๊ฒƒ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์ง„์ ์œผ๋กœ ๊ฐœ์„ ํ•ด๋‚˜๊ฐ€๋ฉด ๋ผ. ์™„๋ฒฝํ•œ ์ฑ—๋ด‡์„ ํ•œ ๋ฒˆ์— ๋งŒ๋“ค๋ ค๊ณ  ํ•˜์ง€ ๋ง๊ณ , ์ž‘๋™ํ•˜๋Š” ์ตœ์†Œํ•œ์˜ ์ œํ’ˆ(MVP)๋ถ€ํ„ฐ ๋งŒ๋“ค์–ด๋ณด๋Š” ๊ฑฐ์•ผ. ๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์œผ๋ฉด์„œ ๊ณ„์† ๊ฐœ์„ ํ•ด๋‚˜๊ฐ€๋Š” ๊ฑฐ์ง€. ๐Ÿ’ช

ํ•ต์‹ฌ ํฌ์ธํŠธ ์ •๋ฆฌ:
๐ŸŽฏ Clojure๋Š” ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์žฅ์ ์„ ํ™œ์šฉํ•œ ์ฑ—๋ด‡ ๊ฐœ๋ฐœ์— ์ ํ•ฉํ•ด
๐Ÿค– ๊ทœ์น™ ๊ธฐ๋ฐ˜๊ณผ AI ๊ธฐ๋ฐ˜ ์ ‘๊ทผ๋ฒ•์„ ์ ์ ˆํžˆ ์กฐํ•ฉํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด
๐Ÿ’พ ์ƒํƒœ ๊ด€๋ฆฌ์™€ ๋Œ€ํ™” ํ๋ฆ„ ๊ด€๋ฆฌ๊ฐ€ ์ฑ—๋ด‡์˜ ํ•ต์‹ฌ์ด์•ผ
๐Ÿ”’ ๋ณด์•ˆ๊ณผ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ๊ณ ๋ คํ•ด์•ผ ํ•ด
๐Ÿ“Š ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๋กœ๊น…์œผ๋กœ ์ง€์†์ ์ธ ๊ฐœ์„ ์ด ๊ฐ€๋Šฅํ•ด
๐Ÿš€ ์ž‘์€ ๊ฒƒ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์ง„์ ์œผ๋กœ ๋ฐœ์ „์‹œ์ผœ ๋‚˜๊ฐ€์ž!

๋งˆ์ง€๋ง‰์œผ๋กœ, ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ํ˜ผ์ž ํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ์•ผ. ์ปค๋ฎค๋‹ˆํ‹ฐ์™€ ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ๊ฑฐ์ง€. ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ์œผ๋ฉด ์ฃผ์ €ํ•˜์ง€ ๋ง๊ณ  ์งˆ๋ฌธํ•˜๊ณ , ๋„ˆ์˜ ๊ฒฝํ—˜๋„ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๊ณผ ๊ณต์œ ํ•ด๋ด. ๊ทธ๋ ‡๊ฒŒ ์šฐ๋ฆฌ ๋ชจ๋‘๊ฐ€ ํ•จ๊ป˜ ์„ฑ์žฅํ•  ์ˆ˜ ์žˆ์–ด! ๐ŸŒฑ

ํ–‰์šด์„ ๋นŒ์–ด! ๋ฉ‹์ง„ ์ฑ—๋ด‡์„ ๋งŒ๋“ค์–ด๋ณด์ž! ํ™”์ดํŒ…! ๐Ÿ’ชโœจ

Happy Coding! Clojure + AI = ๐Ÿš€ ๐Ÿค– ๐Ÿ’ฌ โœจ
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.