tjrlgns09 commited on
Commit
a52c5c7
ยท
1 Parent(s): 3fab7e1
.dockerignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ *.log
6
+ .git
7
+ .gitignore
8
+ .venv/
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ *.log
6
+ .venv/
7
+ .env
Dockerfile ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile
2
+ FROM python:3.10-slim
3
+
4
+ # 1. ์‹œ์Šคํ…œ ํŒจํ‚ค์ง€ ์„ค์น˜ ๋ฐ ์‹œ๊ฐ„๋Œ€ ์„ค์ • (Root ๊ถŒํ•œ)
5
+ RUN apt-get update && \
6
+ apt-get install -y tzdata && \
7
+ ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime && \
8
+ echo "Asia/Seoul" > /etc/timezone && \
9
+ apt-get clean && \
10
+ rm -rf /var/lib/apt/lists/*
11
+
12
+ WORKDIR /app
13
+
14
+ # 2. ์˜์กด์„ฑ ํŒŒ์ผ ๋ณต์‚ฌ
15
+ COPY requirements.txt .
16
+
17
+ # 3. 'uv'๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒจํ‚ค์ง€ ์„ค์น˜ (ํ•ต์‹ฌ ํ•ด๊ฒฐ์ฑ…)
18
+ # pip ๋Œ€์‹  uv๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด resolution-too-deep ์—๋Ÿฌ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
19
+ # --system ์˜ต์…˜์œผ๋กœ ์‹œ์Šคํ…œ ํŒŒ์ด์ฌ ํ™˜๊ฒฝ์— ์ง์ ‘ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค (๋„์ปค ๋‚ด๋ถ€๋Š” ๊ฒฉ๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์•ˆ์ „ํ•จ).
20
+ RUN pip install uv && \
21
+ uv pip install --system --no-cache-dir --extra-index-url https://download.pytorch.org/whl/cpu -r requirements.txt
22
+
23
+ # 4. ๋ณด์•ˆ์„ ์œ„ํ•ด ์œ ์ € ์ƒ์„ฑ ๋ฐ ์ „ํ™˜
24
+ RUN useradd -m -u 1000 user
25
+ USER user
26
+ ENV PATH="/home/user/.local/bin:$PATH"
27
+
28
+ # 5. ์†Œ์Šค ์ฝ”๋“œ ๋ณต์‚ฌ (์œ ์ € ๊ถŒํ•œ์œผ๋กœ)
29
+ COPY --chown=user . /app
30
+
31
+ # 6. ์„œ๋ฒ„ ์‹คํ–‰
32
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
Dockerfile_BK_260210.txt ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile
2
+ FROM python:3.10-slim
3
+
4
+ # ํ•„์š”ํ•œ ํŒจํ‚ค์ง€ ์„ค์น˜ ๋ฐ ์‹œ๊ฐ„๋Œ€ ์„ค์ • (root ๊ถŒํ•œ ํ•„์š”)
5
+ # 'tzdata'๋ฅผ ์„ค์น˜ํ•˜๊ณ  'Asia/Seoul'๋กœ ์‹œ๊ฐ„๋Œ€๋ฅผ ์„ค์ •
6
+ RUN apt-get update && \
7
+ apt-get install -y tzdata && \
8
+ ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime && \
9
+ echo "Asia/Seoul" > /etc/timezone && \
10
+ apt-get clean && \
11
+ rm -rf /var/lib/apt/lists/*
12
+
13
+ RUN useradd -m -u 1000 user
14
+ USER user
15
+ ENV PATH="/home/user/.local/bin:$PATH"
16
+
17
+ WORKDIR /app
18
+
19
+ COPY --chown=user ./requirements.txt requirements.txt
20
+ RUN pip install --upgrade pip && \
21
+ pip install --no-cache-dir --upgrade -r requirements.txt
22
+
23
+ COPY --chown=user . /app
24
+
25
+ #์„œ๋ฒ„ ์‹คํ–‰ ๋ช…๋ น: ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์‹œ์ž‘๋  ๋•Œ FastAPI ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ๋ช…๋ น์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
26
+ #app.py ํŒŒ์ผ์˜ app ๊ฐ์ฒด๋ฅผ 7860 ํฌํŠธ๋กœ ์—ด์–ด์ค๋‹ˆ๋‹ค.
27
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
Dockerfile_bk_260211.txt ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile
2
+ FROM python:3.10-slim
3
+
4
+ # 1. ์‹œ์Šคํ…œ ํŒจํ‚ค์ง€ ์„ค์น˜ ๋ฐ ์‹œ๊ฐ„๋Œ€ ์„ค์ • (Root ๊ถŒํ•œ)
5
+ RUN apt-get update && \
6
+ apt-get install -y tzdata && \
7
+ ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime && \
8
+ echo "Asia/Seoul" > /etc/timezone && \
9
+ apt-get clean && \
10
+ rm -rf /var/lib/apt/lists/*
11
+
12
+ WORKDIR /app
13
+
14
+ # 2. ์˜์กด์„ฑ ํŒŒ์ผ ๋ณต์‚ฌ
15
+ COPY requirements.txt .
16
+
17
+ # 3. 'uv'๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒจํ‚ค์ง€ ์„ค์น˜ (ํ•ต์‹ฌ ํ•ด๊ฒฐ์ฑ…)
18
+ # pip ๋Œ€์‹  uv๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด resolution-too-deep ์—๋Ÿฌ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
19
+ # --system ์˜ต์…˜์œผ๋กœ ์‹œ์Šคํ…œ ํŒŒ์ด์ฌ ํ™˜๊ฒฝ์— ์ง์ ‘ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค (๋„์ปค ๋‚ด๋ถ€๋Š” ๊ฒฉ๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์•ˆ์ „ํ•จ).
20
+ RUN pip install uv && \
21
+ uv pip install --system --no-cache-dir -r requirements.txt
22
+
23
+ # 4. ๋ณด์•ˆ์„ ์œ„ํ•ด ์œ ์ € ์ƒ์„ฑ ๋ฐ ์ „ํ™˜
24
+ RUN useradd -m -u 1000 user
25
+ USER user
26
+ ENV PATH="/home/user/.local/bin:$PATH"
27
+
28
+ # 5. ์†Œ์Šค ์ฝ”๋“œ ๋ณต์‚ฌ (์œ ์ € ๊ถŒํ•œ์œผ๋กœ)
29
+ COPY --chown=user . /app
30
+
31
+ # 6. ์„œ๋ฒ„ ์‹คํ–‰
32
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
SQL_Example.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- DDL
2
+
3
+ -- 1. pgvector ํ™•์žฅ์ด ์—†๋‹ค๋ฉด ๋จผ์ € ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
4
+ CREATE EXTENSION IF NOT EXISTS vector;
5
+
6
+ -- 2. ํ…Œ์ด๋ธ” ์ƒ์„ฑ
7
+ CREATE TABLE t_test_textembedding (
8
+ id BIGSERIAL PRIMARY KEY, -- PK (์ž๋™ ์ฆ๊ฐ€)
9
+ title VARCHAR(500) NOT NULL, -- ์ œ๋ชฉ
10
+ title_embedding VECTOR(768), -- ์ œ๋ชฉ ์ž„๋ฒ ๋”ฉ (768์ฐจ์›)
11
+ content TEXT NOT NULL, -- ๋‚ด์šฉ
12
+ content_embedding VECTOR(768), -- ๋‚ด์šฉ ์ž„๋ฒ ๋”ฉ (768์ฐจ์›)
13
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -- ์ƒ์„ฑ์ผ
14
+ );
15
+
16
+ -- 3. (์„ ํƒ) ๋ฒกํ„ฐ ๊ฒ€์ƒ‰ ์„ฑ๋Šฅ์„ ๋†’์ด๊ธฐ ์œ„ํ•œ ์ธ๋ฑ์Šค ์ƒ์„ฑ (HNSW ์•Œ๊ณ ๋ฆฌ์ฆ˜, ์ฝ”์‚ฌ์ธ ์œ ์‚ฌ๋„ ๊ธฐ์ค€)
17
+ CREATE INDEX idx_title_embedding ON t_test_textembedding USING hnsw (title_embedding vector_cosine_ops);
18
+ CREATE INDEX idx_content_embedding ON t_test_textembedding USING hnsw (content_embedding vector_cosine_ops);
19
+
20
+
21
+ --------------------------------
22
+
23
+
app.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ import uvicorn
4
+ from contextlib import asynccontextmanager
5
+
6
+ from router import llamindex_router
7
+ from router import embedding_router
8
+
9
+ @asynccontextmanager
10
+ async def lifespan_manager(app: FastAPI):
11
+ """
12
+ ์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๊ณ  ์ข…๋ฃŒ ์‹œ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
13
+ """
14
+
15
+ # ์„œ๋ฒ„๊ฐ€ ์š”์ฒญ ์ฒ˜๋ฆฌ๋ฅผ ์‹œ์ž‘ํ•˜๋„๋ก ์ œ์–ด๊ถŒ์„ ๋„˜๊ฒจ์ค๋‹ˆ๋‹ค.
16
+ yield
17
+
18
+ # FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ดˆ๊ธฐํ™”
19
+ app = FastAPI(
20
+ title="RAG+LLM",
21
+ description="RAG+LLM ",
22
+ lifespan=lifespan_manager
23
+ )
24
+
25
+ # CORS ์„ค์ • (๋ชจ๋“  ๋„๋ฉ”์ธ ํ—ˆ์šฉ)
26
+ app.add_middleware(
27
+ CORSMiddleware,
28
+ allow_origins=["*"],
29
+ allow_credentials=True,
30
+ allow_methods=["*"],
31
+ allow_headers=["*"],
32
+ )
33
+
34
+ app.include_router(llamindex_router.router, prefix="/llama_index")
35
+ app.include_router(embedding_router.router, prefix="/embedding")
36
+
37
+ # ํ—ฌ์Šค ์ฒดํฌ์šฉ ๊ธฐ๋ณธ ์—”๋“œํฌ์ธํŠธ
38
+ @app.get("/", summary="API ํ—ฌ์Šค ์ฒดํฌ")
39
+ def read_root():
40
+ """API ์„œ๋ฒ„๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค."""
41
+ return {"message": "RAG+LLM API is running successfully."}
42
+
43
+ if __name__ == "__main__":
44
+ # --reload ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ž๋™ ์žฌ์‹œ์ž‘๋˜๊ฒŒ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
45
+ uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
core/dependencies.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from langchain_core.embeddings import Embeddings
3
+ from typing import List
4
+ import numpy as np
5
+ import onnxruntime as ort
6
+ from huggingface_hub import hf_hub_download
7
+ from transformers import AutoTokenizer
8
+
9
+ # huggingface-cli login ํ˜น์€ HF_TOKEN ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํ•„์š”
10
+ hf_token = os.getenv("HF_TOKEN") or os.getenv("HUGGING_FACE_HUB_TOKEN")
11
+
12
+ class OnnxGemmaWrapper(Embeddings):
13
+ def __init__(self, model_id, token=None):
14
+ print(f"Loading ONNX model: {model_id}...")
15
+ self.tokenizer = AutoTokenizer.from_pretrained(model_id, token=token)
16
+
17
+ # ONNX ๋ชจ๋ธ ๋ฐ ๊ฐ€์ค‘์น˜ ๋‹ค์šด๋กœ๋“œ
18
+ model_path = hf_hub_download(model_id, subfolder="onnx", filename="model.onnx", token=token)
19
+ try:
20
+ hf_hub_download(model_id, subfolder="onnx", filename="model.onnx_data", token=token)
21
+ except Exception:
22
+ pass # model.onnx_data๊ฐ€ ์—†์„ ์ˆ˜๋„ ์žˆ์Œ (์ž‘์€ ๋ชจ๋ธ์˜ ๊ฒฝ์šฐ)
23
+
24
+ # ์ถ”๋ก  ์„ธ์…˜ ์ƒ์„ฑ (GPU ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์‹œ CUDAProvider ์‚ฌ์šฉ, ์—†์œผ๋ฉด CPU)
25
+ available_providers = ort.get_available_providers()
26
+ if 'CUDAExecutionProvider' in available_providers:
27
+ print("CUDA detected. Using GPU.")
28
+ providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
29
+ else:
30
+ print("CUDA not detected. Using CPU.")
31
+ providers = ['CPUExecutionProvider']
32
+
33
+ self.session = ort.InferenceSession(model_path, providers=providers)
34
+
35
+ # Prefix ์ •์˜
36
+ self.prefixes = {
37
+ "query": "task: search result | query: ",
38
+ "document": "title: none | text: ",
39
+ }
40
+ print("ONNX Model loaded successfully.")
41
+
42
+ def _run_inference(self, texts: List[str]):
43
+ inputs = self.tokenizer(texts, padding=True, truncation=True, return_tensors="np")
44
+ # ONNX Runtime ์‹คํ–‰ (output[0]: last_hidden_state, output[1]: pooler_output or sentence_embedding)
45
+ # EmbeddingGemma ONNX ๋ชจ๋ธ์€ ๋ณดํ†ต ๋‘ ๋ฒˆ์งธ ๋ฆฌํ„ด๊ฐ’์ด sentence embedding์ž…๋‹ˆ๋‹ค.
46
+ outputs = self.session.run(None, dict(inputs))
47
+ # outputs[1]์ด (Batch, 768) ํ˜•ํƒœ์˜ ์ž„๋ฒ ๋”ฉ
48
+ return outputs[1]
49
+
50
+ def encode_document(self, documents: List[str]) -> np.ndarray:
51
+ # ๋ฌธ์„œ์šฉ Prefix ์ถ”๊ฐ€
52
+ prefixed_docs = [self.prefixes["document"] + doc for doc in documents]
53
+ return self._run_inference(prefixed_docs)
54
+
55
+ def encode_query(self, query: str) -> np.ndarray:
56
+ # ์ฟผ๋ฆฌ์šฉ Prefix ์ถ”๊ฐ€ (๋‹จ์ผ ์ฟผ๋ฆฌ๋„ ๋ฆฌ์ŠคํŠธ๋กœ ์ฒ˜๋ฆฌ)
57
+ prefixed_query = [self.prefixes["query"] + query]
58
+ return self._run_inference(prefixed_query)[0]
59
+
60
+ def similarity(self, query_emb: np.ndarray, doc_embs: np.ndarray) -> np.ndarray:
61
+ if query_emb.ndim == 1:
62
+ query_emb = query_emb.reshape(1, -1)
63
+ scores = query_emb @ doc_embs.T
64
+ return scores.flatten()
65
+
66
+ # --- LangChain Compatibility Methods ---
67
+ def embed_documents(self, texts: List[str]) -> List[List[float]]:
68
+ return self.encode_document(texts).tolist()
69
+
70
+ def embed_query(self, text: str) -> List[float]:
71
+ return self.encode_query(text).tolist()
72
+
73
+ # ์ „์—ญ ์‹ฑ๊ธ€ํ†ค ์ธ์Šคํ„ด์Šค ์ €์žฅ์†Œ
74
+ _embedding_model = None
75
+
76
+ def get_embedding_model() -> OnnxGemmaWrapper:
77
+ """
78
+ ONNX ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ์„ ์ตœ์ดˆ 1ํšŒ ๋กœ๋“œํ•˜์—ฌ ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ์žฌ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
79
+ """
80
+ global _embedding_model
81
+ if _embedding_model is None:
82
+ _embedding_model = OnnxGemmaWrapper(
83
+ model_id="onnx-community/embeddinggemma-300m-ONNX",
84
+ token=hf_token
85
+ )
86
+ return _embedding_model
data/8 Data Backup Security Tips for Ransomware Response.txt ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๋žœ์„ฌ์›จ์–ด ๋Œ€์‘์„ ์œ„ํ•œ ๋ฐ์ดํ„ฐ ๋ฐฑ์—… 8๋Œ€ ๋ณด์•ˆ ์ˆ˜์น™
2
+
3
+ ## 1. ์˜คํ”„์‚ฌ์ดํŠธ ์šด์˜
4
+ - ์„ค๋ช…: ์ค‘์š” ๋ฐ์ดํ„ฐ๋Š” ๋ฐ˜๋“œ์‹œ ์„œ๋น„์Šค ๋ง๊ณผ ๋ถ„๋ฆฌ๋œ ์˜คํ”„์‚ฌ์ดํŠธ(ํด๋ผ์šฐ๋“œ, ์™ธ๋ถ€ ์ €์žฅ์†Œ ๋˜๋Š” ์˜คํ”„๋ผ์ธ)์— ๋ฐฑ์—…ํ•˜์—ฌ ์šด์˜ํ•ฉ๋‹ˆ๋‹ค.
5
+ - ํ‚ค์›Œ๋“œ: ์˜คํ”„์‚ฌ์ดํŠธ ๋ฐฑ์—…, ๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌ, ํด๋ผ์šฐ๋“œ ๋ฐฑ์—…
6
+
7
+ ## 2. 3-2-1 ๋ณด๊ด€ ์ „๋žต
8
+ - ์„ค๋ช…: ์ค‘์š” ๋ฐ์ดํ„ฐ 3๊ฐœ ์‚ฌ๋ณธ์„ ๋ณด์œ ํ•˜๊ณ , 2๊ฐœ๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ์ €์žฅ๋งค์ฒด์—, 1๊ฐœ๋Š” ์˜คํ”„์‚ฌ์ดํŠธ์— ๋ฐฑ์—… ๋ฐ ์šด์˜ํ•ฉ๋‹ˆ๋‹ค.
9
+ - ํ‚ค์›Œ๋“œ: 3-2-1 ์ „๋žต, ๋ฐ์ดํ„ฐ ์‚ฌ๋ณธ, ๋‹ค์ค‘ ์ €์žฅ๋งค์ฒด
10
+
11
+ ## 3. ์ ‘๊ทผ ํ†ต์ œ ๋ฐ ๊ถŒํ•œ ๊ด€๋ฆฌ
12
+ - ์„ค๋ช…: ๋ฐฑ์—… ์ €์žฅ์†Œ์— ๋Œ€ํ•œ ์ตœ์†Œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์ ์šฉํ•˜๊ณ , ๋ฐฑ์—… ๋‹ด๋‹น์ž ์™ธ์˜ ์ ‘๊ทผ์„ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค.
13
+ - ํ‚ค์›Œ๋“œ: ์ ‘๊ทผ ํ†ต์ œ, ์ตœ์†Œ ๊ถŒํ•œ ์›์น™, ๋ฐฑ์—… ๋ณด์•ˆ
14
+
15
+ ## 4. ๋ฐฑ์—… ์„œ๋ฒ„ ๋ชจ๋‹ˆํ„ฐ๋ง
16
+ - ์„ค๋ช…: ๋ฐฑ์—… ์„œ๋ฒ„์™€ ๋ฐ์ดํ„ฐ์˜ ๋ฌด๊ฒฐ์„ฑ์„ ์ ๊ฒ€ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฑ์‹  ๋˜๋Š” EDR ์„ค์น˜ ๋“ฑ ๋ณด์•ˆ ๋ชจ๋‹ˆํ„ฐ๋ง ์ฒด๊ณ„๋ฅผ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค.
17
+ - ํ‚ค์›Œ๋“œ: ์„œ๋ฒ„ ๋ชจ๋‹ˆํ„ฐ๋ง, ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ, EDR, ๋ฐฑ์‹ 
18
+
19
+ ## 5. ์ •๊ธฐ์  ๋ณต๊ตฌ ํ›ˆ๋ จ
20
+ - ์„ค๋ช…: ์—ฐ 1ํšŒ ์ด์ƒ ๋ณต๊ตฌ ํ›ˆ๋ จ์„ ํ†ตํ•ด ์‹ค์ œ ๋ณต๊ตฌ ๊ฐ€๋Šฅ์„ฑ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
21
+ - ํ‚ค์›Œ๋“œ: ๋ณต๊ตฌ ํ›ˆ๋ จ, ์žฌํ•ด ๋ณต๊ตฌ, ๋ฐ์ดํ„ฐ ๋ณต์›
22
+
23
+ ## 6. ์ตœ์‹  ๋ณด์•ˆ ํŒจ์น˜ ์ ์šฉ
24
+ - ์„ค๋ช…: ๋ฐฑ์—… ์„œ๋ฒ„์™€ SW๋Š” ๋ณด์•ˆ ์—…๋ฐ์ดํŠธ ๋ฐ ํŒจ์น˜๋ฅผ ์‹ ์†ํ•˜๊ฒŒ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
25
+ - ํ‚ค์›Œ๋“œ: ๋ณด์•ˆ ํŒจ์น˜, ์‹œ์Šคํ…œ ์—…๋ฐ์ดํŠธ, ์ทจ์•ฝ์  ๊ด€๋ฆฌ
26
+
27
+ ## 7. ๋ฐฑ์—… ์ „ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ
28
+ - ์„ค๋ช…: ๊ฐ์—ผ๋œ ์›๋ณธ์ด ๋ฐฑ์—…์„ ๋ฎ์–ด์“ฐ์ง€ ์•Š๋„๋ก ๋ฐฑ์—… ์ „ ๋ฌด๊ฒฐ์„ฑ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
29
+ - ํ‚ค์›Œ๋“œ: ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ, ๋žœ์„ฌ์›จ์–ด ๊ฐ์—ผ ๋ฐฉ์ง€, ๋ฐฑ์—… ๋ฐ์ดํ„ฐ ๊ฒ€์‚ฌ
30
+
31
+ ## 8. ์ž๋™ ๋ฐฑ์—… ์ฒด๊ณ„ ์šด์˜
32
+ - ์„ค๋ช…: ์ผ๊ฐ„/์ฃผ๊ฐ„/์›”๊ฐ„ ๋ฐฑ์—… ์ž๋™ํ™”๋ฅผ ํ†ตํ•ด ์‹ค์ˆ˜๋‚˜ ๋ˆ„๋ฝ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
33
+ - ํ‚ค์›Œ๋“œ: ๋ฐฑ์—… ์ž๋™ํ™”, ๋ฐฑ์—… ์Šค์ผ€์ค„๋ง, ๋ฐ์ดํ„ฐ ๋ˆ„๋ฝ ๋ฐฉ์ง€
34
+
35
+ ## ๋ฌธ์˜
36
+ - ํ•œ๊ตญ์ธํ„ฐ๋„ท์ง„ํฅ์› ์‚ฌ์ด๋ฒ„๋ฏผ์›์„ผํ„ฐ: ๊ตญ๋ฒˆ์—†์ด 118
37
+ - ์นจํ•ด์‚ฌ๊ณ  ๋ฐœ์ƒ ์‹œ ์นจํ•ด์‚ฌ๊ณ  ์‹ ๊ณ (๋ณดํ˜ธ๋‚˜๋ผ > ์นจํ•ด์‚ฌ๊ณ  ์‹ ๊ณ )
data/Cyber โ€‹โ€‹Threat Trends Report for the First Half of 2025.txt ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 2025๋…„ ์ƒ๋ฐ˜๊ธฐ ์‚ฌ์ด๋ฒ„ ์œ„ํ˜‘ ๋™ํ–ฅ ๋ณด๊ณ ์„œ (KISA)
2
+ 1. ๊ฐœ์š” ๋ฐ ํ†ต๊ณ„ (2025๋…„ ์ƒ๋ฐ˜๊ธฐ ํ˜„ํ™ฉ)
3
+ ๋ณด๊ณ ์„œ ๋ช…์นญ: 2025๋…„ ์ƒ๋ฐ˜๊ธฐ ์‚ฌ์ด๋ฒ„ ์œ„ํ˜‘ ๋™ํ–ฅ ๋ณด๊ณ ์„œ
4
+ ๋ฐœํ–‰ ๊ธฐ๊ด€: ๊ณผํ•™๊ธฐ์ˆ ์ •๋ณดํ†ต์‹ ๋ถ€, ํ•œ๊ตญ์ธํ„ฐ๋„ท์ง„ํฅ์›(KISA)
5
+ ์นจํ•ด์‚ฌ๊ณ  ์‹ ๊ณ  ํ˜„ํ™ฉ: 2025๋…„ ์ƒ๋ฐ˜๊ธฐ ์ด 1,034๊ฑด (์ „๋…„ ์ƒ๋ฐ˜๊ธฐ ๋Œ€๋น„ 15% ์ฆ๊ฐ€)
6
+ ๊ณต๊ฒฉ ์œ ํ˜•๋ณ„ ๋น„์ค‘: ์„œ๋ฒ„ ํ•ดํ‚น(51.4%), DDoS ๊ณต๊ฒฉ(23.0%), ์•…์„ฑ์ฝ”๋“œ ๊ฐ์—ผ(11.1%)
7
+ DDoS ๊ณต๊ฒฉ ๊ธ‰์ฆ: ์ „๋…„ ์ƒ๋ฐ˜๊ธฐ ๋Œ€๋น„ 55.5% ์ฆ๊ฐ€ (์ฃผ๋กœ ์ •๋ณดํ†ต์‹ ์—… ํƒ€๊ฒŸ, DNS Query Flooding ์œ ํ˜• ๋‹ค์ˆ˜)
8
+ ์—…์ข…๋ณ„ ํ˜„ํ™ฉ: ์ •๋ณดํ†ต์‹ ์—…(37.7%)์ด ๊ฐ€์žฅ ๋†’์œผ๋ฉฐ, ์ œ์กฐ์—…(15.2%), ๋„์†Œ๋งค์—…(12.8%) ์ˆœ
9
+ ๋žœ์„ฌ์›จ์–ด ๋™ํ–ฅ: ์ค‘๊ฒฌ๊ธฐ์—… ์‹ ๊ณ ๋Š” 21% ์ฆ๊ฐ€ํ–ˆ์œผ๋‚˜ ์ค‘์†Œ๊ธฐ์—…์€ 19% ๊ฐ์†Œ. ๋‹จ, ๋ฐฑ์—… ์‹œ์Šคํ…œ๊นŒ์ง€ ๊ฐ์—ผ๋˜๋Š” ์‚ฌ๋ก€๊ฐ€ ๋Š˜์–ด ์‹ค์งˆ์  ํ”ผํ•ด๋Š” ์‹ฌํ™”๋จ (๋ฐฑ์—…๋ณธ ๊ฐ์—ผ๋ฅ  44.4%).
10
+ 2. ์ฃผ์š” ์นจํ•ด์‚ฌ๊ณ  ์‚ฌ๋ก€ ๋ถ„์„
11
+ ๋Œ€๊ทœ๋ชจ ์ •๋ณด ์œ ์ถœ:
12
+ SKํ…”๋ ˆ์ฝค(4์›”): ๋Œ€๊ทœ๋ชจ ์œ ์‹ฌ(USIM) ์ •๋ณด ๋ฐ ๊ฐœ์ธ์ •๋ณด ์œ ์ถœ. ์„œ๋ฒ„ ๊ณ„์ • ๊ด€๋ฆฌ ๋ถ€์‹ค ๋ฐ ์•”ํ˜ธํ™” ์กฐ์น˜ ๋ฏธํก์ด ์›์ธ.
13
+ GS25/์•Œ๋ฐ”๋ชฌ/ํ‹ฐ๋จธ๋‹ˆ: ํฌ๋ฆฌ๋ด์…œ ์Šคํ„ฐํ•‘(Credential Stuffing) ๊ณต๊ฒฉ์œผ๋กœ ์ธํ•œ ํšŒ์› ์ •๋ณด ์œ ์ถœ. ํƒ€ ์‚ฌ์ดํŠธ์—์„œ ์œ ์ถœ๋œ ๊ณ„์ • ์ •๋ณด๋ฅผ ์žฌ์‚ฌ์šฉํ•˜์—ฌ ๊ณต๊ฒฉ.
14
+ ๊ณต๊ธ‰๋ง ๋ฐ ๊ฐ€์ƒ์ž์‚ฐ ํ•ดํ‚น:
15
+ ๋ฐ”์ด๋น„ํŠธ(Bybit, 2์›”): ์•ฝ 2์กฐ 700์–ต ์› ๊ทœ๋ชจ ํƒˆ์ทจ. ์ง€๊ฐ‘ ๋ณด์•ˆ ์†”๋ฃจ์…˜ ๊ฐœ๋ฐœ์ž PC ํ•ดํ‚น์„ ํ†ตํ•œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ ์‚ฝ์ž…(๊ณต๊ธ‰๋ง ๊ณต๊ฒฉ).
16
+ ์œ„๋ฏน์Šค(WEMIX, 2์›”): NFT ์„œ๋น„์Šค(๋‚˜์ผ) ์ธ์ฆํ‚ค ํ•ดํ‚น์„ ํ†ตํ•œ ๊ฐ€์ƒ์ž์‚ฐ ์œ ์ถœ.
17
+ ๋ฒ•์ธ๋ณดํ—˜๋Œ€๋ฆฌ์ (GA, 4์›”): ์œ ์ง€๋ณด์ˆ˜ ์—…์ฒด ๊ฐœ๋ฐœ์ž PC ๊ฐ์—ผ์„ ํ†ตํ•œ ๊ณ ๊ฐ ์ •๋ณด ์œ ์ถœ.
18
+ ๋žœ์„ฌ์›จ์–ด ์‚ฌ๋ก€:
19
+ YES24(6์›”): ๋„์„œ/ํ‹ฐ์ผ“ ์„œ๋น„์Šค 5์ผ๊ฐ„ ์ค‘๋‹จ. ์•ฝ 100์–ต ์› ์†์‹ค ๋ฐ 2,000๋งŒ ๋ช… ํšŒ์› ์ •๋ณด ์œ ์ถœ ์ •ํ™ฉ. ์˜คํ”„์‚ฌ์ดํŠธ ๋ฐฑ์—… ๋ถ€์žฌ๊ฐ€ ํ”ผํ•ด ํ‚ค์›€.
20
+ 3. ์ œ๋„ ๋ฐ ๋ฒ•๊ทœ: AI ๊ธฐ๋ณธ๋ฒ• (2024.12 ๊ตญํšŒ ํ†ต๊ณผ)
21
+ ์ •์‹ ๋ช…์นญ: ์ธ๊ณต์ง€๋Šฅ์˜ ๋ฐœ์ „๊ณผ ์‹ ๋ขฐ ๊ธฐ๋ฐ˜ ์กฐ์„ฑ ๋“ฑ์— ๊ด€ํ•œ ๊ธฐ๋ณธ๋ฒ•
22
+ ํ•ต์‹ฌ ๋‚ด์šฉ: AI ์‚ฐ์—… ์œก์„ฑ๊ณผ ์‹ ๋ขฐ์„ฑ ํ™•๋ณด์˜ ๊ท ํ˜• ๊ฐ•์กฐ.
23
+ ๊ณ ์˜ํ–ฅ AI: ์ƒ๋ช…, ์‹ ์ฒด ์•ˆ์ „, ๊ธฐ๋ณธ๊ถŒ์— ์ค‘๋Œ€ํ•œ ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” AI๋กœ ์ •์˜ํ•˜๊ณ  ๊ทœ์ œ ์ ์šฉ.
24
+ ์ƒ์„ฑํ˜• AI: ํˆฌ๋ช…์„ฑ ํ™•๋ณด ์˜๋ฌด(AI ์ƒ์„ฑ๋ฌผ ํ‘œ์‹œ ๋“ฑ) ๋ถ€๊ณผ.
25
+ ์‚ฐ์—…๋ณ„ ์˜ํ–ฅ: ๊ธˆ์œต(์‹ ์šฉํ‰๊ฐ€), ์ž์œจ์ฃผํ–‰(์•ˆ์ „ ์ธ์ฆ), ์˜๋ฃŒ(์ž„์ƒ ๊ฒ€์ฆ), ๋กœ๋ณดํ‹ฑ์Šค(์œค๋ฆฌ ๋ชจ๋“ˆ) ๋ถ„์•ผ์—์„œ ๊ธฐ์ˆ  ํ‘œ์ค€ํ™” ๋ฐ ์•ˆ์ „์„ฑ ๊ฒ€์ฆ ์ง€์› ์ฒด๊ณ„ ๋งˆ๋ จ.
26
+ EU AI Act ๋น„๊ต: EU๋Š” ์œ„ํ—˜ ๊ธฐ๋ฐ˜์˜ ๊ฐ•๋ ฅํ•œ ๊ทœ์ œ ์ค‘์‹ฌ์ธ ๋ฐ˜๋ฉด, ํ•œ๊ตญ์€ ์ž์œจ์„ฑ๊ณผ ์ƒํƒœ๊ณ„ ์กฐ์„ฑ์„ ๊ฐ•์กฐํ•˜๋Š” ์ง„ํฅ ์ค‘์‹ฌ์  ์„ฑ๊ฒฉ์ด ๊ฐ•ํ•จ.
27
+ 4. ์‹ ๊ทœ ๊ธฐ์ˆ  ํŠธ๋ Œ๋“œ: MCP ๋ฐ A2A ํ”„๋กœํ† ์ฝœ
28
+ MCP (Model Context Protocol): Anthropic ๊ฐœ๋ฐœ. LLM์ด API๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€ ์„œ๋น„์Šค(์ด๋ฉ”์ผ, ์Šคํ† ๋ฆฌ์ง€ ๋“ฑ)์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ํ‘œ์ค€ ๋ฐฉ์‹. "AI๋ฅผ ์œ„ํ•œ USB-C"๋กœ ๋ถˆ๋ฆผ.
29
+ A2A (Agent-to-Agent): Google ๊ฐœ๋ฐœ. ์„œ๋กœ ๋‹ค๋ฅธ AI ์—์ด์ „ํŠธ ๊ฐ„์˜ ํ˜‘์—… ๋ฐ ์†Œํ†ต์„ ์œ„ํ•œ ํ”„๋กœํ† ์ฝœ.
30
+ ๋ณด์•ˆ ์œ„ํ˜‘:
31
+ ๋ฌธ๋งฅ ์กฐ์ž‘(Context Injection): ์œ„์กฐ๋œ ๋ฌธ๋งฅ์„ ์ฃผ์ž…ํ•ด LLM ์˜ค์ž‘๋™ ์œ ๋„.
32
+ TPA (Tool Poisoning Attack): ๋„๊ตฌ ์„ค๋ช…์— ์•…์„ฑ ๋ช…๋ น์„ ์‚ฝ์ž…ํ•˜์—ฌ ์‚ฌ์šฉ์ž ๋ชจ๋ฅด๊ฒŒ ์ •๋ณด ํƒˆ์ทจ.
33
+ ์‚ฌ๋ก€: ์•„์‚ฌ๋‚˜(Asana) MCP ์„œ๋ฒ„ ๋ฒ„๊ทธ๋กœ ์ธํ•œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ๋…ธ์ถœ ์‚ฌ๊ณ  ๋ฐœ์ƒ.
34
+ 5. ๋Œ€์‘ ์ „๋žต ๋ฐ ๊ถŒ๊ณ  ์‚ฌํ•ญ
35
+ ๋ฐ์ดํ„ฐ ๋ฐฑ์—… 8๋Œ€ ๋ณด์•ˆ ์ˆ˜์น™: ์˜คํ”„์‚ฌ์ดํŠธ ์šด์˜, 3-2-1 ๋ณด๊ด€ ์ „๋žต, ์ ‘๊ทผ ํ†ต์ œ, ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ, ์ •๊ธฐ ๋ณต๊ตฌ ํ›ˆ๋ จ ๋“ฑ.
36
+ ํ˜„๋Œ€์  ๋ฐฉ์–ด ์ฒด๊ณ„ ์ „ํ™˜:
37
+ ์ œ๋กœํŠธ๋Ÿฌ์ŠคํŠธ(Zero Trust): '์ ˆ๋Œ€ ์‹ ๋ขฐํ•˜์ง€ ๋ง๊ณ  ํ•ญ์ƒ ๊ฒ€์ฆ'ํ•˜๋Š” ์•„ํ‚คํ…์ฒ˜ ๋„์ž…. ๋‹ค์ค‘ ์ธ์ฆ(MFA) ํ•„์ˆ˜ํ™”.
38
+ ์‹ค์‹œ๊ฐ„ ํƒ์ง€ ๋ฐ ๋Œ€์‘: EDR/XDR ๋„์ž…, AI ๊ธฐ๋ฐ˜ ์ด์ƒํ–‰์œ„ ๋ถ„์„(UEBA) ํ™œ์šฉ.
39
+ MDR (Managed Detection & Response): ๋ณด์•ˆ ์ธ๋ ฅ์ด ๋ถ€์กฑํ•œ ๊ธฐ์—…์„ ์œ„ํ•œ ๊ด€๋ฆฌํ˜• ํƒ์ง€ ๋ฐ ๋Œ€์‘ ์„œ๋น„์Šค ํ™œ์šฉ ๊ถŒ๊ณ .
40
+ ๋‹จ๊ณ„๋ณ„ ๋กœ๋“œ๋งต: 1๋‹จ๊ณ„(MFA, ๋ฐฑ์—…), 2๋‹จ๊ณ„(EDR/MDR ๋„์ž…), 3๋‹จ๊ณ„(์ง€๋Šฅํ˜• ํƒ์ง€ ์ฒด๊ณ„), 4๋‹จ๊ณ„(AI ๊ธฐ๋ฐ˜ ์ž์œจ ๋ณด์•ˆ).
41
+ ํ‚ค์›Œ๋“œ: #์‚ฌ์ด๋ฒ„๋ณด์•ˆ #2025์‚ฌ์ด๋ฒ„์œ„ํ˜‘ #KISA #AI๊ธฐ๋ณธ๋ฒ• #๋žœ์„ฌ์›จ์–ด #DDoS #MCP #์ œ๋กœํŠธ๋Ÿฌ์ŠคํŠธ #๊ฐœ์ธ์ •๋ณด์œ ์ถœ #๊ณต๊ธ‰๋ง๊ณต๊ฒฉ
data/Detailed Guide to Analyzing and Assessing Technical Vulnerabilities in Critical Information and Communication Infrastructure.txt ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # 2026 ์ฃผ์š”์ •๋ณดํ†ต์‹ ๊ธฐ๋ฐ˜์‹œ์„ค ๊ธฐ์ˆ ์  ์ทจ์•ฝ์  ๋ถ„์„ยทํ‰๊ฐ€ ๊ฐ€์ด๋“œ (RAG์šฉ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค)
3
+
4
+ [ํ•ญ๋ชฉ ์ฝ”๋“œ] U-01
5
+ [๋ถ„๋ฅ˜] Unix ์„œ๋ฒ„ > ๊ณ„์ • ๊ด€๋ฆฌ
6
+ [์ œ๋ชฉ] root ๊ณ„์ • ์›๊ฒฉ ์ ‘์† ์ œํ•œ
7
+ [์ค‘์š”๋„] ์ƒ
8
+ [ํ•ญ๋ชฉ ์„ค๋ช…] ์‹œ์Šคํ…œ ์ •์ฑ…์— root ๊ณ„์ •์˜ ์›๊ฒฉํ„ฐ๋ฏธ๋„ ์ ‘์† ์ฐจ๋‹จ ์„ค์ •์ด ์ ์šฉ ์—ฌ๋ถ€ ์ ๊ฒ€
9
+ [์ ๊ฒ€ ๋ชฉ์ ] ๊ด€๋ฆฌ์ž ๊ณ„์ • ํƒˆ์ทจ๋กœ ์ธํ•œ ์‹œ์Šคํ…œ ์žฅ์•…์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์™ธ๋ถ€ ๋น„์ธ๊ฐ€์ž์˜ root ๊ณ„์ • ์ ‘๊ทผ ์‹œ๋„๋ฅผ ์›์ฒœ์ ์œผ๋กœ ์ฐจ๋‹จํ•˜๊ธฐ ์œ„ํ•จ
10
+ [๋ณด์•ˆ ์œ„ํ˜‘] root ๊ณ„์ •์€ ์šด์˜์ฒด์ œ์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์„ค์ • ๋ฐ ๋ณ€๊ฒฝ์ด ๊ฐ€๋Šฅํ•˜์—ฌ root ๊ณ„์ •์„ ํƒˆ์ทจํ•˜์—ฌ ์™ธ๋ถ€์—์„œ ์›๊ฒฉ์„ ์ด์šฉํ•œ ์‹œ์Šคํ…œ ์žฅ์•… ๋ฐ ๊ฐ์ข… ๊ณต๊ฒฉ์œผ๋กœ ์ธํ•œ ์œ„ํ—˜์ด ์กด์žฌํ•จ
11
+ [ํŒ๋‹จ ๊ธฐ์ค€]
12
+ - ์–‘ํ˜ธ: ์›๊ฒฉํ„ฐ๋ฏธ๋„ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฑฐ๋‚˜, ์‚ฌ์šฉ ์‹œ root ์ง์ ‘ ์ ‘์†์„ ์ฐจ๋‹จํ•œ ๊ฒฝ์šฐ
13
+ - ์ทจ์•ฝ: ์›๊ฒฉํ„ฐ๋ฏธ๋„ ์„œ๋น„์Šค ์‚ฌ์šฉ ์‹œ root ์ง์ ‘ ์ ‘์†์„ ํ—ˆ์šฉํ•œ ๊ฒฝ์šฐ
14
+ [์กฐ์น˜ ๋ฐฉ๋ฒ•] ์›๊ฒฉ ์ ‘์† ์‹œ root ๊ณ„์ •์œผ๋กœ ์ ‘์†ํ•  ์ˆ˜ ์—†๋„๋ก ํŒŒ์ผ ๋‚ด์šฉ ์„ค์ •
15
+ - LINUX: /etc/ssh/sshd_config ํŒŒ์ผ ๋‚ด์˜ PermitRootLogin No ์„ค์ •, Telnet ์‚ฌ์šฉ ์‹œ /etc/securetty ํŒŒ์ผ ๋‚ด pts/x ์„ค์ • ์ฃผ์„ ์ฒ˜๋ฆฌ
16
+ - SOLARIS: /etc/default/login ํŒŒ์ผ ๋‚ด CONSOLE=/dev/console ์„ค์ •
17
+ - AIX: /etc/security/user ํŒŒ์ผ์— rlogin = false ์„ค์ •
18
+
19
+ --------------------------------------------------
20
+
21
+ [ํ•ญ๋ชฉ ์ฝ”๋“œ] U-02
22
+ [๋ถ„๋ฅ˜] Unix ์„œ๋ฒ„ > ๊ณ„์ • ๊ด€๋ฆฌ
23
+ [์ œ๋ชฉ] ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ด€๋ฆฌ์ •์ฑ… ์„ค์ •
24
+ [์ค‘์š”๋„] ์ƒ
25
+ [ํ•ญ๋ชฉ ์„ค๋ช…] ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ด€๋ฆฌ ์ •์ฑ… ์„ค์ • ์—ฌ๋ถ€ ์ ๊ฒ€
26
+ [์ ๊ฒ€ ๋ชฉ์ ] ์‚ฌ์šฉ์ž์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก์„ฑ๊ณผ ์ฃผ๊ธฐ์  ๋ณ€๊ฒฝ์„ ํ†ตํ•ด ์‹œ์Šคํ…œ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•˜๊ธฐ ์œ„ํ•จ
27
+ [๋ณด์•ˆ ์œ„ํ˜‘] ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ด€๋ จ ์ •์ฑ…์ด ์„ค์ •๋˜์ง€ ์•Š์„ ๊ฒฝ์šฐ, ๋น„์ธ๊ฐ€์ž์˜ ๊ฐ์ข… ๊ณต๊ฒฉ์— ์˜ํ•ด ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋…ธ์ถœ๋  ์œ„ํ—˜์ด ์กด์žฌํ•จ
28
+ [ํŒ๋‹จ ๊ธฐ์ค€]
29
+ - ์–‘ํ˜ธ: ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ด€๋ฆฌ ์ •์ฑ…์ด ์„ค์ •๋œ ๊ฒฝ์šฐ
30
+ - ์ทจ์•ฝ: ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ด€๋ฆฌ ์ •์ฑ…์ด ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ
31
+ [์กฐ์น˜ ๋ฐฉ๋ฒ•] ์‚ฌ์šฉ์ž ๊ณ„์ •์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์˜๋ฌธ, ์ˆซ์ž, ํŠน์ˆ˜๋ฌธ์ž ํฌํ•จ ์ตœ์†Œ 8์ž๋ฆฌ ์ด์ƒ, ์ตœ์†Œ ์‚ฌ์šฉ ๊ธฐ๊ฐ„ 1์ผ, ์ตœ๋Œ€ ์‚ฌ์šฉ ๊ธฐ๊ฐ„ 90์ผ, ์ตœ๊ทผ ๊ธฐ์–ต 4ํšŒ ์ด์ƒ์œผ๋กœ ์„ค์ •
32
+ - LINUX (Redhat): /etc/login.defs ํŒŒ์ผ์— PASS_MAX_DAYS 90, PASS_MIN_DAYS 1 ์„ค์ •, /etc/security/pwquality.conf ์— minlen = 8, dcredit = -1 ๋“ฑ ์„ค์ •
33
+
34
+ --------------------------------------------------
35
+
36
+ [ํ•ญ๋ชฉ ์ฝ”๋“œ] W-01
37
+ [๋ถ„๋ฅ˜] Windows ์„œ๋ฒ„ > ๊ณ„์ • ๊ด€๋ฆฌ
38
+ [์ œ๋ชฉ] Administrator ๊ณ„์ • ์ด๋ฆ„ ๋ณ€๊ฒฝ ๋“ฑ ๋ณด์•ˆ์„ฑ ๊ฐ•ํ™”
39
+ [์ค‘์š”๋„] ์ƒ
40
+ [ํ•ญ๋ชฉ ์„ค๋ช…] Administrator์˜ ๊ณ„์ •๋ช… ๋ณ€๊ฒฝ ๋˜๋Š” ๋ณด์•ˆ์„ ๊ณ ๋ คํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์„ค์ • ์—ฌ๋ถ€ ์ ๊ฒ€
41
+ [์ ๊ฒ€ ๋ชฉ์ ] ์ž˜ ์•Œ๋ ค์ง„ ๊ด€๋ฆฌ์ž ๊ณ„์ •์„ ํ†ตํ•œ ์•…์˜์ ์ธ ํŒจ์Šค์›Œ๋“œ ์ถ”์ธก ๊ณต๊ฒฉ์„ ์ฐจ๋‹จํ•˜๊ธฐ ์œ„ํ•จ
42
+ [๋ณด์•ˆ ์œ„ํ˜‘] Administrator ๊ณ„์ •์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ญ์ œํ•˜๊ฑฐ๋‚˜ ์ž ๊ธ€ ์ˆ˜ ์—†์–ด ๊ณต๊ฒฉ์ž์˜ ์ฃผ์š” ํƒ€๊ฒŸ์ด ๋จ
43
+ [ํŒ๋‹จ ๊ธฐ์ค€]
44
+ - ์–‘ํ˜ธ: Administrator ๊ธฐ๋ณธ ๊ณ„์ • ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ๊ฐ•ํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ ์šฉํ•œ ๊ฒฝ์šฐ
45
+ - ์ทจ์•ฝ: Administrator ๊ธฐ๋ณธ ๊ณ„์ • ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋‹จ์ˆœ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ ์šฉํ•œ ๊ฒฝ์šฐ
46
+ [์กฐ์น˜ ๋ฐฉ๋ฒ•] ๋กœ์ปฌ ๋ณด์•ˆ ์ •์ฑ…์—์„œ Administrator ๊ณ„์ • ์ด๋ฆ„์„ ์œ ์ถ”ํ•˜๊ธฐ ์–ด๋ ค์šด ์ด๋ฆ„์œผ๋กœ ๋ณ€๊ฒฝ
47
+ - Windows 2012/2016/2019/2022: ์ œ์–ดํŒ > ๊ด€๋ฆฌ ๋„๊ตฌ > ๋กœ์ปฌ ๋ณด์•ˆ ์ •์ฑ… > ๋กœ์ปฌ ์ •์ฑ… > ๋ณด์•ˆ ์˜ต์…˜ > โ€˜๊ณ„์ •: Administrator ๊ณ„์ • ์ด๋ฆ„ ๋ฐ”๊พธ๊ธฐโ€™ ์„ ํƒ ํ›„ ์ด๋ฆ„ ๋ณ€๊ฒฝ
48
+
49
+ --------------------------------------------------
50
+
51
+ [ํ•ญ๋ชฉ ์ฝ”๋“œ] WEB-04
52
+ [๋ถ„๋ฅ˜] ์›น ์„œ๋น„์Šค > ์„œ๋น„์Šค ๊ด€๋ฆฌ
53
+ [์ œ๋ชฉ] ์›น ์„œ๋น„์Šค ๋””๋ ‰ํ„ฐ๋ฆฌ ๋ฆฌ์ŠคํŒ… ๋ฐฉ์ง€ ์„ค์ •
54
+ [์ค‘์š”๋„] ์ƒ
55
+ [ํ•ญ๋ชฉ ์„ค๋ช…] ๋””๋ ‰ํ„ฐ๋ฆฌ ๋ฆฌ์ŠคํŒ… ๊ธฐ๋Šฅ ์ฐจ๋‹จ ์—ฌ๋ถ€ ์ ๊ฒ€
56
+ [์ ๊ฒ€ ๋ชฉ์ ] ๋””๋ ‰ํ„ฐ๋ฆฌ ๋‚ด์˜ ๋ชจ๋“  ํŒŒ์ผ์— ๋Œ€ํ•œ ์ ‘๊ทผ ๋ฐ ์ •๋ณด ๋…ธ์ถœ์„ ์ฐจ๋‹จํ•˜๊ธฐ ์œ„ํ•จ
57
+ [๋ณด์•ˆ ์œ„ํ˜‘] ์ฐจ๋‹จ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋ฐฑ์—… ํŒŒ์ผ์ด๋‚˜ ์†Œ์Šค ํŒŒ์ผ ๋“ฑ ๊ณต๊ฐœ๋˜๋ฉด ์•ˆ ๋˜๋Š” ์ค‘์š” ํŒŒ์ผ๋“ค์ด ๋…ธ์ถœ๋จ
58
+ [ํŒ๋‹จ ๊ธฐ์ค€]
59
+ - ์–‘ํ˜ธ: ๋””๋ ‰ํ„ฐ๋ฆฌ ๋ฆฌ์ŠคํŒ…์ด ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ
60
+ - ์ทจ์•ฝ: ๋””๋ ‰ํ„ฐ๋ฆฌ ๋ฆฌ์ŠคํŒ…์ด ์„ค์ •๋œ ๊ฒฝ์šฐ
61
+ [์กฐ์น˜ ๋ฐฉ๋ฒ•] ์›น ์„œ๋ฒ„ ์„ค์ • ํŒŒ์ผ์—์„œ ์ธ๋ฑ์‹ฑ ์˜ต์…˜ ์ œ๊ฑฐ
62
+ - Apache: httpd.conf ํŒŒ์ผ ๋‚ด Options ์ง€์‹œ์ž์—์„œ Indexes ์ œ๊ฑฐ (๋˜๋Š” -Indexes)
63
+ - Nginx: nginx.conf ํŒŒ์ผ ๋‚ด autoindex off ์„ค์ •
64
+ - IIS: ์ธํ„ฐ๋„ท ์ •๋ณด ์„œ๋น„์Šค(IIS) ๊ด€๋ฆฌ์ž > ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ฒ€์ƒ‰ > ์‚ฌ์šฉ ์•ˆ ํ•จ
data/Guide to Key Information and Communication Infrastructure Management, Physical Vulnerability Analysis.txt ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ====================================================
2
+ DOCUMENT: 2026 ์ฃผ์š”์ •๋ณดํ†ต์‹ ๊ธฐ๋ฐ˜์‹œ์„ค ๊ด€๋ฆฌยท๋ฌผ๋ฆฌ์  ์ทจ์•ฝ์  ๋ถ„์„ยทํ‰๊ฐ€๋ฐฉ๋ฒ• ์•ˆ๋‚ด์„œ
3
+ YEAR: 2026 | AUTHOR: ๊ณผํ•™๊ธฐ์ˆ ์ •๋ณดํ†ต์‹ ๋ถ€ / ํ•œ๊ตญ์ธํ„ฐ๋„ท์ง„ํฅ์›(KISA)
4
+ ====================================================
5
+
6
+ [SECTION: ์•ˆ๋‚ด์„œ ๊ฐœ์š” ๋ฐ ํ‰๊ฐ€ ์ฒด๊ณ„]
7
+ Description: ์ •๋ณดํ†ต์‹ ๊ธฐ๋ฐ˜ ๋ณดํ˜ธ๋ฒ•์— ๋”ฐ๋ฅธ ์ •๊ธฐ์  ์ทจ์•ฝ์  ๋ถ„์„ ๋ฐ ํ‰๊ฐ€์˜ ๋ชฉ์ ๊ณผ ์ˆ˜ํ–‰ ์ ˆ์ฐจ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
8
+ ----------------------------------------------------
9
+ โ–ถ SUBSECTION: ๋ฒ•์  ๊ทผ๊ฑฐ ๋ฐ ๋ชฉ์ 
10
+ - SUMMARY: ์ •๋ณดํ†ต์‹ ๊ธฐ๋ฐ˜ ๋ณดํ˜ธ๋ฒ• ์ œ9์กฐ์— ๊ทผ๊ฑฐํ•˜์—ฌ ๊ด€๋ฆฌ๊ธฐ๊ด€์˜ ์žฅ์ด ์ •๊ธฐ์ ์œผ๋กœ ์†Œ๊ด€ ์‹œ์„ค์˜ ์ทจ์•ฝ์ ์„ ๋ถ„์„ยทํ‰๊ฐ€ํ•˜๋„๋ก ์˜๋ฌดํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
11
+ - KEY POINTS:
12
+ 1. ์ฃผ์š”์ •๋ณดํ†ต์‹ ๊ธฐ๋ฐ˜์‹œ์„ค ๋ณดํ˜ธ ์—ญ๋Ÿ‰ ๊ฐ•ํ™”
13
+ 2. ๋งค๋…„ ์ •๊ธฐ ํ‰๊ฐ€ ์‹ค์‹œ ์˜๋ฌด
14
+ 3. ์ค‘๋Œ€ํ•œ ๋ณ€ํ™” ๋ฐœ์ƒ ์‹œ ์ˆ˜์‹œ ํ‰๊ฐ€ ๋ช…๋ น ๊ฐ€๋Šฅ
15
+ - REFERENCES: ์ •๋ณดํ†ต์‹ ๊ธฐ๋ฐ˜ ๋ณดํ˜ธ๋ฒ• ์ œ9์กฐ
16
+
17
+ โ–ถ SUBSECTION: ์ ๊ฒ€ ์š”๋ น ๋ฐ ๋“ฑ๊ธ‰ ์‚ฐ์ •
18
+ - SUMMARY: ํ‰๊ฐ€ ๊ฒฐ๊ณผ๋Š” '์–‘ํ˜ธ', '๋ถ€๋ถ„ ์ดํ–‰', '์ทจ์•ฝ'์˜ 3๋‹จ๊ณ„๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ๊ฐ๊ด€์„ฑ์„ ํ™•๋ณดํ•ฉ๋‹ˆ๋‹ค.
19
+ - KEY POINTS:
20
+ 1. ์–‘ํ˜ธ: ์ ๊ฒ€ ํ•ญ๋ชฉ์— ๋ช…ํ™•ํžˆ ๋ถ€ํ•ฉ
21
+ 2. ๋ถ€๋ถ„ ์ดํ–‰: ์ผ๋ถ€ ๋งŒ์กฑํ•˜๋‚˜ ๊ฐœ์„  ํ•„์š”
22
+ 3. ์ทจ์•ฝ: ํ•ญ๋ชฉ ๋ถ€ํ•ฉ ์‹คํŒจ
23
+ - REFERENCES: ์ ๊ฒ€ ์š”๋ น ์ œ3์ ˆ
24
+
25
+
26
+ [SECTION: ์ •๋ณด๋ณดํ˜ธ ๊ฑฐ๋ฒ„๋„Œ์Šค ๋ฐ ์ •์ฑ… ๊ด€๋ฆฌ]
27
+ Description: ์กฐ์ง ๋‚ด ์ •๋ณด๋ณดํ˜ธ ์ •์ฑ… ์ˆ˜๋ฆฝ, ์ „๋‹ด ์กฐ์ง ๊ตฌ์„ฑ ๋ฐ ์œ„์›ํšŒ ์šด์˜์— ๊ด€ํ•œ ์ง€์นจ์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.
28
+ ----------------------------------------------------
29
+ โ–ถ SUBSECTION: ์ •๋ณด๋ณดํ˜ธ ์ •์ฑ… ์ˆ˜๋ฆฝ ๋ฐ ์‹œํ–‰
30
+ - SUMMARY: ์ตœ๊ณ ๊ฒฝ์˜์ž ์Šน์ธ์„ ํš๋“ํ•œ ์ตœ์ƒ์œ„ ์ •์ฑ…์„ ์ˆ˜๋ฆฝํ•˜๊ณ , ์ด๋ฅผ ๊ตฌ์ฒดํ™”ํ•œ ์ง€์นจ, ์ ˆ์ฐจ, ๋งค๋‰ด์–ผ์„ ๋ฌธ์„œํ™”ํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
31
+ - KEY POINTS:
32
+ 1. ์ตœ๊ณ ๊ฒฝ์˜์ž(๊ธฐ๊ด€์žฅ) ๊ณต์‹ ์Šน์ธ ํ•„์ˆ˜
33
+ 2. ์ž„์ง์› ๋ฐ ๊ด€๋ จ์ž ์ ‘๊ทผ ์šฉ์ด์„ฑ ํ™•๋ณด
34
+ 3. ์—ฐ 1ํšŒ ์ด์ƒ ํƒ€๋‹น์„ฑ ๊ฒ€ํ†  ๋ฐ ์ค‘๋Œ€ ๋ณ€ํ™” ์‹œ ๊ฐœ์ •
35
+ - REFERENCES: A-1, A-2, A-3, A-4, A-5
36
+
37
+ โ–ถ SUBSECTION: ์ •๋ณด๋ณดํ˜ธ ์กฐ์ง ๋ฐ ์ธ๋ ฅ ๋ณด์•ˆ
38
+ - SUMMARY: ์ •๋ณด๋ณดํ˜ธ์ฑ…์ž„์ž(CISO) ์ง€์ • ๋ฐ ์ „๋‹ด ์กฐ์ง์„ ๊ตฌ์„ฑํ•˜๊ณ , ์ธ๋ ฅ ์ฑ„์šฉ๋ถ€ํ„ฐ ํ‡ด์ง๊นŒ์ง€์˜ ๋ณด์•ˆ ์ ˆ์ฐจ๋ฅผ ์ˆ˜๋ฆฝํ•ฉ๋‹ˆ๋‹ค.
39
+ - KEY POINTS:
40
+ 1. ์ •๋ณด๋ณดํ˜ธ์œ„์›ํšŒ ๊ตฌ์„ฑ ๋ฐ ์—ญํ•  ๋ช…๋ฌธํ™”
41
+ 2. ์‹ ์› ํ™•์ธ ๋ฐ ์ ๊ฒฉ์‹ฌ์‚ฌ ์ˆ˜ํ–‰
42
+ 3. ๋น„๋ฐ€์œ ์ง€ ํ™•์•ฝ์„œ ์ง•๊ตฌ ๋ฐ ํ‡ด์ง ์‹œ ๊ถŒํ•œ ์ฆ‰์‹œ ํšŒ์ˆ˜
43
+ - REFERENCES: A-8, A-9, A-21, A-25
44
+
45
+
46
+ [SECTION: ์ž์‚ฐ ๊ด€๋ฆฌ ๋ฐ ์šด์˜ ๋ณด์•ˆ]
47
+ Description: ์ •๋ณด์ž์‚ฐ์˜ ์‹๋ณ„, ๋ถ„๋ฅ˜์™€ ๋”๋ถˆ์–ด ์‹œ์Šคํ…œ์˜ ๋„์ž…, ๋ณ€๊ฒฝ, ํ๊ธฐ ๋“ฑ ์ƒ์• ์ฃผ๊ธฐ ๋ณด์•ˆ์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
48
+ ----------------------------------------------------
49
+ โ–ถ SUBSECTION: ์ž์‚ฐ ์‹๋ณ„ ๋ฐ ์œ„ํ—˜ ๊ด€๋ฆฌ
50
+ - SUMMARY: ์ธ๋ ฅ, ์‹œ์„ค, ์žฅ๋น„ ๋“ฑ ๋ชจ๋“  ์ž์‚ฐ์„ ์‹๋ณ„ํ•˜์—ฌ ๋ชฉ๋กํ™”ํ•˜๊ณ  ์—ฐ 1ํšŒ ์ด์ƒ ์ •๊ธฐ์  ์œ„ํ—˜ ํ‰๊ฐ€๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
51
+ - KEY POINTS:
52
+ 1. ์ž์‚ฐ ์ค‘์š”๋„(๊ธฐ๋ฐ€์„ฑ, ๋ฌด๊ฒฐ์„ฑ, ๊ฐ€์šฉ์„ฑ) ํ‰๊ฐ€ ๋ฐ ๋“ฑ๊ธ‰ ๋ถ€์—ฌ
53
+ 2. ์œ„ํ—˜ ์ฒ˜๋ฆฌ ์ „๋žต(๊ฐ์†Œ, ํšŒํ”ผ, ์ „๊ฐ€, ์ˆ˜์šฉ) ์ˆ˜๋ฆฝ
54
+ 3. ๋ชฉํ‘œ ์œ„ํ—˜ ์ˆ˜์ค€(DoA) ์„ค์ •
55
+ - REFERENCES: A-10, A-15, A-16, A-17
56
+
57
+ โ–ถ SUBSECTION: ์šด์˜ ๋ฐ ๋ณ€๊ฒฝ ๋ณด์•ˆ
58
+ - SUMMARY: ์‹œ์Šคํ…œ ๋„์ž… ์‹œ ๋ณด์•ˆ์„ฑ ๊ฒ€ํ† ๋ฅผ ์‹ค์‹œํ•˜๊ณ , ๋ณ€๊ฒฝ ์‚ฌํ•ญ์— ๋Œ€ํ•œ ๊ณต์‹ ์Šน์ธ ์ ˆ์ฐจ๋ฅผ ์šด์˜ํ•ฉ๋‹ˆ๋‹ค.
59
+ - KEY POINTS:
60
+ 1. ๊ตญ๋‚ด์šฉ CC์ธ์ฆ ์ œํ’ˆ ๋„์ž… ๊ถŒ๊ณ 
61
+ 2. ๊ฐœ๋ฐœ-ํ…Œ์ŠคํŠธ-์šด์˜ ํ™˜๊ฒฝ์˜ ๋ฌผ๋ฆฌ์ /๋…ผ๋ฆฌ์  ๋ถ„๋ฆฌ
62
+ 3. ์†Œ์Šค์ฝ”๋“œ ๋ณด์•ˆ ์•ฝ์  ์ ๊ฒ€ ๋ฐ ํ˜•์ƒ ๊ด€๋ฆฌ
63
+ - REFERENCES: A-58, A-61, A-63, A-66
64
+
65
+
66
+ [SECTION: ์ ‘๊ทผ ํ†ต์ œ ๋ฐ ๋„คํŠธ์›Œํฌ ๋ณด์•ˆ]
67
+ Description: ๊ณ„์ • ๊ด€๋ฆฌ, ์ธ์ฆ ๋ฐฉ์‹, ๋„คํŠธ์›Œํฌ ๋ถ„๋ฆฌ ๋ฐ ์™ธ๋ถ€ ์ ‘์† ํ†ต์ œ ์ „๋žต์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
68
+ ----------------------------------------------------
69
+ โ–ถ SUBSECTION: ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๊ด€๋ฆฌ
70
+ - SUMMARY: ์ง๋ฌด๋ณ„ ์ ‘๊ทผ ๊ถŒํ•œ ๋ถ€์—ฌ์™€ ๋‹ค์ค‘ ์ธ์ฆ(MFA) ๋“ฑ ์•ˆ์ „ํ•œ ์ธ์ฆ ์ฒด๊ณ„๋ฅผ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค.
71
+ - KEY POINTS:
72
+ 1. ์•Œ ํ•„์š”(Need-to-Know) ๋ฐ ํ•  ํ•„์š”(Need-to-Do) ์›์น™
73
+ 2. ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก๋„ ์„ค์ • ๋ฐ ์ฃผ๊ธฐ์  ๋ณ€๊ฒฝ
74
+ 3. ์žฅ๊ธฐ ๋ฏธ์‚ฌ์šฉ ๊ณ„์ •(3๊ฐœ์›” ์ดํ•˜) ํ˜„ํ™ฉ ์กฐ์‚ฌ ๋ฐ ์กฐ์น˜
75
+ - REFERENCES: A-39, A-41, A-42
76
+
77
+ โ–ถ SUBSECTION: ๋„คํŠธ์›Œํฌ ๋ฐ ๋ง๋ถ„๋ฆฌ
78
+ - SUMMARY: ๋‚ด๋ถ€๋ง๊ณผ ์™ธ๋ถ€๋ง์„ ๋ถ„๋ฆฌํ•˜๊ณ  ์ผ๋ฐฉํ–ฅ ์ „์†ก ์žฅ๋น„ ๋“ฑ์„ ํ†ตํ•ด ์•ˆ์ „ํ•œ ๋ฐ์ดํ„ฐ ์ „์†ก์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
79
+ - KEY POINTS:
80
+ 1. ์—…๋ฌด๋ง๊ณผ ์ธํ„ฐ๋„ท๋ง์˜ ๋ฌผ๋ฆฌ์ /๋…ผ๋ฆฌ์  ๋ถ„๋ฆฌ
81
+ 2. VPN ๋“ฑ ์•ˆ์ „ํ•œ ์›๊ฒฉ ์ ‘์† ์ˆ˜๋‹จ ์ ์šฉ
82
+ 3. ๋น„์ธ๊ฐ€ ๋ฌด์„  AP ํƒ์ง€ ๋ฐ ์ฐจ๋‹จ
83
+ - REFERENCES: A-51, A-52, A-53, A-57
84
+
85
+
86
+ [SECTION: ์นจํ•ด์‚ฌ๊ณ  ๋Œ€์‘ ๋ฐ ์—…๋ฌด ์—ฐ์†์„ฑ]
87
+ Description: ์‚ฌ์ด๋ฒ„ ๊ณต๊ฒฉ ๋ฐœ์ƒ ์‹œ ๋Œ€์‘ ์ฒด๊ณ„์™€ ์žฌ๋‚œ ์‹œ ์„œ๋น„์Šค ๊ฐ€์šฉ์„ฑ ๋ณด์žฅ ๊ณ„ํš์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.
88
+ ----------------------------------------------------
89
+ โ–ถ SUBSECTION: ์นจํ•ด์‚ฌ๊ณ  ๋Œ€์‘ ์ฒด๊ณ„(CERT)
90
+ - SUMMARY: ์‚ฌ๊ณ  ์˜ˆ๋ฐฉ, ํƒ์ง€, ๋Œ€์‘, ๋ณต๊ตฌ ๋ฐ ๋ณด๊ณ  ์ ˆ์ฐจ๋ฅผ ํฌํ•จํ•œ CERT ์กฐ์ง์„ ๊ตฌ์„ฑํ•˜๊ณ  ํ›ˆ๋ จํ•ฉ๋‹ˆ๋‹ค.
91
+ - KEY POINTS:
92
+ 1. ์‚ฌ์ด๋ฒ„์œ„๊ธฐ ๊ฒฝ๋ณด ๋‹จ๊ณ„๋ณ„ ํ–‰๋™์š”๋ น ์ˆ˜๋ฆฝ
93
+ 2. ์นจํ•ด์‚ฌ๊ณ  ๋ฐœ์ƒ ์‹œ 24์‹œ๊ฐ„ ์ด๋‚ด ๊ด€๊ณ„๊ธฐ๊ด€ ํ†ต์ง€
94
+ 3. ์žฌ๋ฐœ ๋ฐฉ์ง€ ๋Œ€์ฑ… ์ˆ˜๋ฆฝ ๋ฐ ์ฆ๊ฑฐ ์ž๋ฃŒ ํ™•๋ณด
95
+ - REFERENCES: A-104, A-107, A-110, A-113
96
+
97
+ โ–ถ SUBSECTION: ๋น„์ฆˆ๋‹ˆ์Šค ์—ฐ์†์„ฑ ๊ณ„ํš(BCP)
98
+ - SUMMARY: ํ•ต์‹ฌ ์„œ๋น„์Šค์˜ ๋ณต๊ตฌ ๋ชฉํ‘œ ์‹œ๊ฐ„(RTO)๊ณผ ์‹œ์ (RPO)์„ ์ •์˜ํ•˜๊ณ  ์‹œ์Šคํ…œ ์ด์ค‘ํ™”๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
99
+ - KEY POINTS:
100
+ 1. ์—…๋ฌด์˜ํ–ฅ๋ถ„์„(BIA) ์ˆ˜ํ–‰ ๋ฐ ํ•ต์‹ฌ ์‹œ์Šคํ…œ ์‹๋ณ„
101
+ 2. ์ •๊ธฐ์  ๋ณต๊ตฌ ํ…Œ์ŠคํŠธ ๋ฐ ๋ชจ์˜ ํ›ˆ๋ จ ์‹ค์‹œ
102
+ 3. ์ค‘์š” ๋ฐ์ดํ„ฐ์˜ ์›๊ฒฉ์ง€ ์†Œ์‚ฐ ๋ฐฑ์—…
103
+ - REFERENCES: A-114, A-115, A-116, A-118
104
+
105
+
106
+ [SECTION: ๋ฌผ๋ฆฌ์  ๋ณด์•ˆ ๋ฐ ํ™˜๊ฒฝ ํ†ต์ œ]
107
+ Description: ๋ณดํ˜ธ๊ตฌ์—ญ ์ง€์ •, ์ถœ์ž… ํ†ต์ œ ์žฅ์น˜ ์šด์˜ ๋ฐ ํ™˜๊ฒฝ ์žฌํ•ด ๋Œ€๋น„ ์„ค๋น„๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
108
+ ----------------------------------------------------
109
+ โ–ถ SUBSECTION: ๋ณดํ˜ธ๊ตฌ์—ญ ๊ด€๋ฆฌ
110
+ - SUMMARY: ์ œํ•œ๊ตฌ์—ญ๊ณผ ํ†ต์ œ๊ตฌ์—ญ์„ ๊ตฌ๋ถ„ํ•˜์—ฌ ์ถœ์ž… ์ž๊ฒฉ์„ ์—„๊ฒฉํžˆ ์ œํ•œํ•˜๊ณ  ๊ธฐ๋ก์„ ๋ณด๊ด€ํ•ฉ๋‹ˆ๋‹ค.
111
+ - KEY POINTS:
112
+ 1. ๋‹ค๋‹จ๊ณ„ ์ถœ์ž… ํ†ต์ œ(ID์นด๋“œ, ์ƒ์ฒด ์ธ์‹ ๋“ฑ)
113
+ 2. ์ถœ์ž… ๊ธฐ๋ก ์ตœ์†Œ 2๊ฐœ์›” ์ด์ƒ ๋ณด๊ด€
114
+ 3. ๋ณดํ˜ธ๊ตฌ์—ญ ๋‚ด CCTV ์„ค์น˜ ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง
115
+ - REFERENCES: P-1, P-2, P-6, P-17
116
+
117
+ โ–ถ SUBSECTION: ์‹œ์„ค ๋ณดํ˜ธ ๋ฐ ์žฌํ•ด ๋Œ€๋น„
118
+ - SUMMARY: ํ™”์žฌ, ์นจ์ˆ˜, ์ „๋ ฅ ์ค‘๋‹จ ๋“ฑ ํ™˜๊ฒฝ์  ์œ„ํ—˜์œผ๋กœ๋ถ€ํ„ฐ ์žฅ๋น„๋ฅผ ๋ณดํ˜ธํ•˜๊ธฐ ์œ„ํ•œ ์„ค๋น„๋ฅผ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค.
119
+ - KEY POINTS:
120
+ 1. UPS ๋ฐ ๋น„์ƒ ๋ฐœ์ „๊ธฐ ๋“ฑ ๋น„์ƒ ์ „์› ์„ค๋น„
121
+ 2. 24์‹œ๊ฐ„ ํ•ญ์˜จํ•ญ์Šต ๋ฐ ๋ˆ„์ˆ˜ ๊ฐ์ง€ ์„ผ์„œ
122
+ 3. ๋‚ดํ™” ๊ตฌ์กฐ ๊ฑด์ถ• ์ž์žฌ ์‚ฌ์šฉ ๋ฐ ์ˆ˜ํ•ด ๋ฐฉ์ง€ ์‹œ์„ค
123
+ - REFERENCES: P-10, P-11, P-15, P-14
124
+
125
+
data/Hacking Diagnostic Tool Utilization Plan #4 Taking Control of the AD Environment Through Exposed SMB File Servers.txt ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [์ „์ฒด ๋ฌธ์„œ ๊ฐœ์š”]
2
+ ์ œ๋ชฉ: ํ•ดํ‚น์ง„๋‹จ๋„๊ตฌ ํ™œ์šฉ ๋ฐฉ์•ˆ #4: ๋…ธ์ถœ๋œ SMB ํŒŒ์ผ ์„œ๋ฒ„๋ฅผ ํ†ตํ•œ AD ํ™˜๊ฒฝ ์žฅ์•…
3
+ ์ž‘์„ฑ: ํ•œ๊ตญ์ธํ„ฐ๋„ท์ง„ํฅ์›(KISA) ์œ„ํ˜‘๋ถ„์„๋‹จ ํฌ๋ Œ์‹๋ถ„์„ํŒ€
4
+ ์„ค๋ช…: ๋ณด์•ˆ ์ธ๋ ฅ์ด ๋ถ€์กฑํ•œ ์ค‘์†Œ๊ธฐ์—…์„ ๋Œ€์ƒ์œผ๋กœ, SMB ์ทจ์•ฝ์ ์„ ํ†ตํ•œ ๋‚ด๋ถ€ ์นจํˆฌ ๋ฐ Active Directory(AD) ๊ถŒํ•œ ์žฅ์•… ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๋ถ„์„ํ•˜๊ณ  ํ•ดํ‚น์ง„๋‹จ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ•œ ํƒ์ง€ ๋ฐ ๋Œ€์‘ ๋ฐฉ์•ˆ์„ ์ œ์‹œํ•จ.
5
+ ๋ถ„์„ ๋Œ€์ƒ: ํŒŒ์ผ ์„œ๋ฒ„(Ubuntu 20.04 LTS), ๋„๋ฉ”์ธ ์ปจํŠธ๋กค๋Ÿฌ(Windows Server 2019)
6
+
7
+ ==================================================
8
+
9
+ [1. ํ•ดํ‚น์ง„๋‹จ๋„๊ตฌ ๊ฐœ์š” ๋ฐ ๋ชฉ์ ]
10
+ ํ•ต์‹ฌ ์š”์•ฝ: ์ค‘์†Œ๊ธฐ์—…์ด ์นจํ•ด์‚ฌ๊ณ ์˜ ์ตœ์ข… ํ”ผํ•ด ๋ฐœ์ƒ ์ „ ํ•ดํ‚น ์‹œ๋„๋ฅผ ์Šค์Šค๋กœ ์‹๋ณ„ํ•˜๊ณ  ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜๋Š” ์ž๊ฐ€์ง„๋‹จ ๋„๊ตฌ ํ™œ์šฉ ๊ฐ€์ด๋“œ.
11
+ ์ƒ์„ธ ๋‚ด์šฉ: ํ•ดํ‚น์ง„๋‹จ๋„๊ตฌ๋Š” ๋ณด์•ˆ ์ „๋‹ด ์ธ๋ ฅ๊ณผ ์˜ˆ์‚ฐ์ด ๋ถ€์กฑํ•œ ๊ธฐ์—…๋“ค์ด ์นจํ•ด์‚ฌ๊ณ ๋ฅผ ์กฐ๊ธฐ์— ์ธ์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐœ๋ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ž๊ฐ€์ง„๋‹จ -> ์กฐ๊ธฐ ์‹๋ณ„ -> ํ”ผํ•ด ์ตœ์†Œํ™”์˜ ์„ ์ˆœํ™˜ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ฃผ ๋ชฉ์ ์ž…๋‹ˆ๋‹ค.
12
+
13
+ --------------------------------------------------
14
+
15
+ [2. ์นจํ•ด์‚ฌ๊ณ  ๊ณต๊ฒฉ ์‹œ๋‚˜๋ฆฌ์˜ค ์ƒ์„ธ]
16
+ ํ•ต์‹ฌ ์š”์•ฝ: SMB ๊ณต์œ  ํด๋”์˜ ์ ‘๊ทผ ์ œ์–ด ๋ฏธํก์„ ์ด์šฉํ•œ ์ดˆ๊ธฐ ์นจํˆฌ๋ถ€ํ„ฐ AD ์„œ๋ฒ„ ์žฅ์•…๊นŒ์ง€์˜ ๋‹จ๊ณ„๋ณ„ ๊ณต๊ฒฉ ํ๋ฆ„.
17
+ ์ƒ์„ธ ๋‚ด์šฉ: ๊ณต๊ฒฉ ํ๋ฆ„: ํฌํŠธ ์Šค์บ๋‹(์ทจ์•ฝ ํŒŒ์ผ ์„œ๋ฒ„ ์‹๋ณ„) -> SMB ๊ณต์œ  ์ ‘๊ทผ(๊ณ„์ • ์ •๋ณด ํš๋“) -> rlogin ์›๊ฒฉ ์ ‘์† -> ์•…์„ฑ ์ฝ”๋“œ(Plague) ๋ฐฐํฌ ๋ฐ ๋ฐฑ๋„์–ด ์„ค์น˜ -> ๋‚ด๋ถ€๋ง ์ด๋™(Lateral Movement) -> DC ์„œ๋ฒ„ Brute Force ๊ณต๊ฒฉ -> RDP ์ ‘์† ๋ฐ ๊ด€๋ฆฌ์ž ๊ณ„์ • ์ƒ์„ฑ -> PowerShell์„ ์ด์šฉํ•œ ๋ณด์•ˆ ์„ค์ •(UAC ๋“ฑ) ํ•ด์ œ -> ํ”์  ์ œ๊ฑฐ.
18
+
19
+ --------------------------------------------------
20
+
21
+ [3. ํŒŒ์ผ ์„œ๋ฒ„(Linux) ํƒ์ง€ ๋ฐ ๋Œ€์‘]
22
+ ํ•ต์‹ฌ ์š”์•ฝ: ๋ฆฌ๋ˆ…์Šค ๊ธฐ๋ฐ˜ ํŒŒ์ผ ์„œ๋ฒ„์—์„œ ๋ฐœ๊ฒฌ๋œ ์•…์„ฑ์ฝ”๋“œ ๋™์ž‘, ์›๊ฒฉ ๋ช…๋ น์–ด ์‚ฌ์šฉ, ๊ณ„์ • ์ƒ์„ฑ ๋ฐ ๋ฐ์ดํ„ฐ ์œ ์ถœ ํ”์  ํƒ์ง€.
23
+ ์ƒ์„ธ ๋‚ด์šฉ: ์ฃผ์š” ํƒ์ง€ ํ•ญ๋ชฉ:
24
+ - [PLAGUE]_01: Plague ์•…์„ฑ์ฝ”๋“œ ๋ฐ”์ด๋„ˆ๋ฆฌ ๊ฐ’ ํ™•์ธ ๋ฐ ld.so.preload ์‚ฝ์ž… ํƒ์ง€.
25
+ - [PS]_03: rlogin, rexec ๋“ฑ ์›๊ฒฉ ๋ช…๋ น ์„œ๋น„์Šค ๋™์ž‘ ํ™•์ธ.
26
+ - [EVT]_01: ๋ฆฌ๋ˆ…์Šค ๋กœ๊ทธ ๋‚ด ๋น„์ •์ƒ ๊ณ„์ • ์ƒ์„ฑ ํ™•์ธ.
27
+ - [EVT]_02: ๊ณต๊ฒฉ ์€๋‹‰์„ ์œ„ํ•œ ํŒŒ์ผ ์‚ญ์ œ ํ–‰์œ„ ํƒ์ง€.
28
+ - [EVT]_05: scp, wget ๋“ฑ์„ ์ด์šฉํ•œ ๋ฐ์ดํ„ฐ ์œ ์ถœ ๋„๊ตฌ ์‹คํ–‰ ํ™•์ธ.
29
+
30
+ --------------------------------------------------
31
+
32
+ [4. ๋„๋ฉ”์ธ ์ปจํŠธ๋กค๋Ÿฌ(Windows) ํƒ์ง€ ๋ฐ ๋Œ€์‘]
33
+ ํ•ต์‹ฌ ์š”์•ฝ: ์œˆ๋„์šฐ ์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ์˜ ๋น„์ •์ƒ ๊ด€๋ฆฌ์ž ๊ณ„์ • ์ƒ์„ฑ, ๋ฐฑ์‹  ๊ธฐ๋Šฅ ๋ฌด๋ ฅํ™” ๋ฐ ๊ถŒํ•œ ์ƒ์Šน ํƒ์ง€.
34
+ ์ƒ์„ธ ๋‚ด์šฉ: ์ฃผ์š” ํƒ์ง€ ํ•ญ๋ชฉ:
35
+ - [EVT]_09: ๋น„์ •์ƒ์ ์œผ๋กœ ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž(๊ด€๋ฆฌ์ž) ๊ณ„์ • ํƒ์ง€.
36
+ - [EVT]_14: Windows Defender ์‹ค์‹œ๊ฐ„ ๊ฐ์‹œ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™” ํƒ์ง€.
37
+ - [EVT]_07: ๊ณต๊ฒฉ ์€๋‹‰์„ ์œ„ํ•œ ๊ณ„์ • ์‚ญ์ œ ํ–‰์œ„ ํƒ์ง€.
38
+ - [REG]_04: UAC(์‚ฌ์šฉ์ž ๊ณ„์ • ์ปจํŠธ๋กค) ๊ฒฝ๊ณ  ๊ธฐ๋Šฅ ๊บผ์ง(EnableLUA=0) ํƒ์ง€.
39
+
40
+ --------------------------------------------------
41
+
42
+ [5. ๋„๊ตฌ ์ง€์› ๋ฒ”์œ„ ๋ฐ ์‹คํ–‰ ๋ฐฉ๋ฒ•]
43
+ ํ•ต์‹ฌ ์š”์•ฝ: ์ง€์›ํ•˜๋Š” ์šด์˜์ฒด์ œ(Windows Server 2008~2025, ์ฃผ์š” ๋ฆฌ๋ˆ…์Šค ๋ฐฐํฌํŒ) ๋ชฉ๋ก ๋ฐ ์•„ํ‚คํ…์ฒ˜๋ณ„ ์„ค์น˜ ๊ฐ€์ด๋“œ.
44
+ ์ƒ์„ธ ๋‚ด์šฉ: ์ง€์› ํ™˜๊ฒฝ: Windows Server ์ „ ๋ฒ„์ „, Ubuntu, CentOS, RedHat, Rocky, Oracle, Amazon Linux ๋“ฑ ์ฃผ์š” ๋ฆฌ๋ˆ…์Šค. ์‹คํ–‰ ๋ฐฉ์‹: ํฌํ„ฐ๋ธ” ์‹คํ–‰ํŒŒ์ผ ํ˜•ํƒœ๋กœ ์ œ๊ณต๋˜๋ฉฐ 32/64๋น„ํŠธ ์•„ํ‚คํ…์ฒ˜์— ๋งž๋Š” ํŒŒ์ผ์„ ์„ ํƒํ•˜์—ฌ ์‹คํ–‰.
45
+
46
+ --------------------------------------------------
47
+
data/Information and Communications Field_Breach_Incident_Response_Guide_Revised_Version.txt ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ ==================================================
3
+ DOCUMENT OVERVIEW
4
+ ==================================================
5
+ ๋ฌธ์„œ ์ œ๋ชฉ: ์ •๋ณดํ†ต์‹ ๋ถ„์•ผ ์นจํ•ด์‚ฌ๊ณ  ๋Œ€์‘ ์•ˆ๋‚ด์„œ
6
+ ๋ฐœํ–‰ ๊ธฐ๊ด€: ๊ณผํ•™๊ธฐ์ˆ ์ •๋ณดํ†ต์‹ ๋ถ€, KISA(ํ•œ๊ตญ์ธํ„ฐ๋„ท์ง„ํฅ์›)
7
+ ๋ฐœํ–‰ ์ผ์ž: 2025๋…„ 08์›”
8
+ ๋ฌธ์„œ ์š”์•ฝ:
9
+ ์ด ๋ฌธ์„œ๋Š” ์ •๋ณดํ†ต์‹ ๋ถ„์•ผ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์นจํ•ด์‚ฌ๊ณ ์— ๋Œ€ํ•œ ์˜ˆ๋ฐฉ ๋ฐ ๋Œ€์‘ ์š”๋ น์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ICT ๊ฐ•๊ตญ์ธ ํ•œ๊ตญ์˜ ๋””์ง€ํ„ธ ํ™˜๊ฒฝ ๋ฐฐ๊ฒฝ๊ณผ ํ•จ๊ป˜, ์ •๋ณดํ†ต์‹ ๋ง๋ฒ• ๋ฐ ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ๋ฒ•์— ๋”ฐ๋ฅธ ๋ฒ•์  ์‹ ๊ณ  ์˜๋ฌด, ์‚ฌ๊ณ  ๋ฐœ์ƒ ์‹œ 7๋‹จ๊ณ„ ๋Œ€์‘ ์ ˆ์ฐจ, ๊ทธ๋ฆฌ๊ณ  ๊ฐœ์ธ ๋ฐ ๊ธฐ์—…(์„œ๋ฒ„, ๋„คํŠธ์›Œํฌ, DB ๋“ฑ)์ด ์ˆ˜ํ–‰ํ•ด์•ผ ํ•  ๊ตฌ์ฒด์ ์ธ ๋ณด์•ˆ ์ ๊ฒ€ ํ•ญ๋ชฉ์„ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
10
+
11
+ ==================================================
12
+ [Section 1]
13
+ [Section Title]
14
+ ๊ฐœ์š” ๋ฐ ๋ฐฐ๊ฒฝ: ์ •๋ณดํ†ต์‹ ๋ถ„์•ผ ์นจํ•ด์‚ฌ๊ณ  ๋Œ€์‘์˜ ํ•„์š”์„ฑ
15
+
16
+ [Summary]
17
+ ์•ˆ๋‚ด์„œ์˜ ๋ฐœํ–‰ ๋ฐฐ๊ฒฝ๊ณผ ๋ชฉ์ ์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. OECD ๋ณด๊ณ ์„œ์— ๋”ฐ๋ฅธ ํ•œ๊ตญ์˜ ICT ์œ„์ƒ, ๋””์ง€ํ„ธ ์ „ํ™˜ ๊ฐ€์†ํ™”์— ๋”ฐ๋ฅธ ์‚ฌ์ด๋ฒ„ ์œ„ํ˜‘(ํ•ดํ‚น, ์•…์„ฑ์ฝ”๋“œ, DDoS)์˜ ์ฆ๊ฐ€ ์ถ”์„ธ, ๊ทธ๋ฆฌ๊ณ  ๊ตญ๊ฐ€ ์•ˆ์ „ ๋ณด์žฅ์„ ์œ„ํ•œ ๋Œ€๋น„ ํ•„์š”์„ฑ์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.
18
+
19
+ [Keywords]
20
+ ๊ฐœ์š”, ๋ฐฐ๊ฒฝ, ๋””์ง€ํ„ธ ์ „ํ™˜, ์‚ฌ์ด๋ฒ„ ์œ„ํ˜‘, ํ•ดํ‚น, ์•…์„ฑ์ฝ”๋“œ, DDoS, ๊ตญ๊ฐ€ ์•ˆ์ „, ์•ˆ๋‚ด์„œ ๋ชฉ์ , ๊ณผํ•™๊ธฐ์ˆ ์ •๋ณดํ†ต์‹ ๋ถ€, KISA
21
+
22
+ [Body]
23
+ 1. ๋ฐฐ๊ฒฝ
24
+ OECD ๋””์ง€ํ„ธ๊ฒฝ์ œ์ „๋ง๋ณด๊ณ ์„œ2024์— ๋”ฐ๋ฅด๋ฉด ์šฐ๋ฆฌ๋‚˜๋ผ๋Š” IoT, ๋น…๋ฐ์ดํ„ฐ, AI ๋“ฑ ํ˜์‹  ๊ธฐ์ˆ  ๋ถ„์•ผ์—์„œ ๊ฐ€์žฅ ๋น ๋ฅธ ๋„์ž…๋ฅ ์„ ๋‚˜ํƒ€๋‚ด๋Š” ICT ๊ฐ•๊ตญ์ž…๋‹ˆ๋‹ค. ์ฝ”๋กœ๋‚˜19 ์ดํ›„ ๋””์ง€ํ„ธ ์ „ํ™˜ ๊ฐ€์†ํ™”๋กœ 2023๋…„ ๊ฐ€๊ตฌ ์ธํ„ฐ๋„ท ์ ‘์†๋ฅ ์€ 99.9%์— ๋„๋‹ฌํ•˜๋ฉฐ ์ดˆ์—ฐ๊ฒฐ ์‹œ๋Œ€์— ์ง„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ ํ•ดํ‚น, ์•…์„ฑ์ฝ”๋“œ, DDoS ๋“ฑ ์นจํ•ด์‚ฌ๊ณ  ๊ณต๊ฒฉ ์œ ํ˜•์ด ๊ฐˆ์ˆ˜๋ก ์ง€๋Šฅํ™”, ๊ณ ๋„ํ™”๋˜๊ณ  ์žˆ์œผ๋ฉฐ ๋งค๋…„ ์ฆ๊ฐ€ ์ถ”์„ธ์— ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ์ธ์ •๋ณด ์นจํ•ด, ์‚ฐ์—…๊ธฐ๋ฐ€ ์œ ์ถœ ๋“ฑ ์‚ฌ์ด๋ฒ„ ์œ„ํ˜‘์— ์ง€์†์ ์œผ๋กœ ๋Œ€๋น„ํ•˜์—ฌ ๊ตญ๊ฐ€ ์•ˆ์ „์„ ๋ณด์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
25
+
26
+ 2. ๋ชฉ์  ๋ฐ ๊ตฌ์„ฑ
27
+ ๋ณธ ์•ˆ๋‚ด์„œ๋Š” ๊ธฐ์—… ๋ฐ ๊ฐœ์ธ ์ด์šฉ์ž๊ฐ€ ์•Œ์•„์•ผ ํ•  ์นจํ•ด์‚ฌ๊ณ  ์˜ˆ๋ฐฉ ๋ฐ ๋Œ€์‘์š”๋ น์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
28
+ - ์ œ1์žฅ ๊ฐœ์š”: ๋ฐฐ๊ฒฝ, ๋ชฉ์  ๋ฐ ๊ตฌ์„ฑ ์„ค๋ช…
29
+ - ์ œ2์žฅ ์นจํ•ด์‚ฌ๊ณ  ์‹ ๊ณ : ์ •๋ณดํ†ต์‹ ๋ง๋ฒ• ๋ฐ ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ๋ฒ•์— ๊ทผ๊ฑฐํ•œ ์‹ ๊ณ  ์˜๋ฌด์™€ ๋ฐฉ๋ฒ•
30
+ - ์ œ3์žฅ ์นจํ•ด์‚ฌ๊ณ  ์กฐ์น˜ ๊ฐ€์ด๋“œ: ์‚ฌ๊ณ  ์œ ํ˜•๋ณ„ ๋Œ€์‘์ ˆ์ฐจ, ์‹œ์Šคํ…œ ์ ๊ฒ€ํ•ญ๋ชฉ ๋ฐ ์ทจ์•ฝ์  ์กฐ์น˜ ๋ฐฉ์•ˆ
31
+ - ๋ถ€๋ก: ์ฃผ์š” ์šฉ์–ด ์ •๋ฆฌ
32
+
33
+ ==================================================
34
+ [Section 2]
35
+ [Section Title]
36
+ ์นจํ•ด์‚ฌ๊ณ  ์‹ ๊ณ  ์˜๋ฌด ๋ฐ ์ ˆ์ฐจ (์ •๋ณดํ†ต์‹ ๋ง๋ฒ•)
37
+
38
+ [Summary]
39
+ ์ •๋ณดํ†ต์‹ ๋ง๋ฒ• ์ œ48์กฐ์˜3์— ๊ทผ๊ฑฐํ•œ ์นจํ•ด์‚ฌ๊ณ ์˜ ์ •์˜์™€ ์‹ ๊ณ  ์˜๋ฌด๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ๊ณ  ๋ฐœ์ƒ ์ธ์ง€ ํ›„ 24์‹œ๊ฐ„ ์ด๋‚ด ์‹ ๊ณ  ๊ทœ์ •, ์œ„๋ฐ˜ ์‹œ ๊ณผํƒœ๋ฃŒ, ๊ทธ๋ฆฌ๊ณ  ๊ตฌ์ฒด์ ์ธ ์‹ ๊ณ  ๋ฐฉ๋ฒ•(๋ณดํ˜ธ๋‚˜๋ผ, 118 ๋“ฑ)์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
40
+
41
+ [Keywords]
42
+ ์นจํ•ด์‚ฌ๊ณ  ์‹ ๊ณ , ์ •๋ณดํ†ต์‹ ๋ง๋ฒ•, ์‹ ๊ณ  ์˜๋ฌด, 24์‹œ๊ฐ„ ์ด๋‚ด, ๊ณผํƒœ๋ฃŒ, KISA ์‹ ๊ณ , ๋ณดํ˜ธ๋‚˜๋ผ, 118, ํ•ดํ‚น ์‹ ๊ณ 
43
+
44
+ [Body]
45
+ 1. ์นจํ•ด์‚ฌ๊ณ  ๋ฐœ์ƒ ์‹œ ์‹ ๊ณ 
46
+
47
+ ๊ฐ€. ์ •์˜
48
+ ํ•ดํ‚น, ๋ฐ”์ด๋Ÿฌ์Šค, ์„œ๋น„์Šค ๊ฑฐ๋ถ€(DDoS) ๋“ฑ์˜ ๋ฐฉ๋ฒ•์œผ๋กœ ์ •๋ณดํ†ต์‹ ๋ง์„ ๊ณต๊ฒฉํ•˜๋Š” ํ–‰์œ„๋กœ ์ธํ•ด ๋ฐœ์ƒํ•œ ์‚ฌํƒœ๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค.
49
+
50
+ ๋‚˜. ๊ด€๋ จ ๋ฒ•๋ฅ  ๋ฐ ์˜๋ฌด
51
+ ์ •๋ณดํ†ต์‹ ๋ง ์ด์šฉ์ด‰์ง„ ๋ฐ ์ •๋ณด๋ณดํ˜ธ ๋“ฑ์— ๊ด€ํ•œ ๋ฒ•๋ฅ (์ •๋ณดํ†ต์‹ ๋ง๋ฒ•) ์ œ48์กฐ์˜3์— ๋”ฐ๋ผ ์‚ฌ๊ณ  ๋ฐœ์ƒ ์ฆ‰์‹œ(์นจํ•ด์‚ฌ๊ณ ๋ฅผ ์•Œ๊ฒŒ ๋œ ๋•Œ๋กœ๋ถ€ํ„ฐ 24์‹œ๊ฐ„ ์ด๋‚ด) ๊ณผํ•™๊ธฐ์ˆ ์ •๋ณดํ†ต์‹ ๋ถ€ ๋˜๋Š” KISA(ํ•œ๊ตญ์ธํ„ฐ๋„ท์ง„ํฅ์›)์— ์‹ ๊ณ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
52
+ - ๋ฒŒ์น™: ์‹ ๊ณ  ์˜๋ฌด ์œ„๋ฐ˜ ์‹œ 3์ฒœ๋งŒ์› ์ดํ•˜์˜ ๊ณผํƒœ๋ฃŒ๊ฐ€ ๋ถ€๊ณผ๋ฉ๋‹ˆ๋‹ค.
53
+
54
+ ๋‹ค. ์‹ ๊ณ  ๋ฐฉ๋ฒ•
55
+ - ํ™ˆํŽ˜์ด์ง€: ๋ณดํ˜ธ๋‚˜๋ผ&KrCERT (http://www.boho.or.kr)
56
+ - ์ „์ž์šฐํŽธ: certgen@krcert.or.kr
57
+ - ์ „ํ™”: 118 (๊ตญ๋ฒˆ ์—†์ด)
58
+ - ๊ธฐํƒ€: ์„œ๋ฉด ์‹ ๊ณ  ๋“ฑ
59
+
60
+ ==================================================
61
+ [Section 3]
62
+ [Section Title]
63
+ ๊ฐœ์ธ์ •๋ณด ์œ ์ถœ์‚ฌ๊ณ  ์‹ ๊ณ  ๋ฐ ํ†ต์ง€ (๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ๋ฒ•)
64
+
65
+ [Summary]
66
+ ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ๋ฒ• ์ œ34์กฐ์— ๋”ฐ๋ฅธ ๊ฐœ์ธ์ •๋ณด ์œ ์ถœ์˜ ์ •์˜์™€ ๋Œ€์‘ ์˜๋ฌด๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ์ •๋ณด์ฃผ์ฒด์— ๋Œ€ํ•œ ์ง€์ฒด ์—†๋Š” ํ†ต์ง€ ์˜๋ฌด์™€ 72์‹œ๊ฐ„ ์ด๋‚ด ๊ด€๊ณ„ ๊ธฐ๊ด€(๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ์œ„์›ํšŒ, KISA) ์‹ ๊ณ  ์ ˆ์ฐจ๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค.
67
+
68
+ [Keywords]
69
+ ๊ฐœ์ธ์ •๋ณด ์œ ์ถœ, ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ๋ฒ•, ์œ ์ถœ ์‹ ๊ณ , 72์‹œ๊ฐ„ ์ด๋‚ด, ์ •๋ณด์ฃผ์ฒด ํ†ต์ง€, ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ์œ„์›ํšŒ, ํ”ผํ•ด ๊ตฌ์ œ
70
+
71
+ [Body]
72
+ 2. ๊ฐœ์ธ์ •๋ณด ์œ ์ถœ์‚ฌ๊ณ  ๋ฐœ์ƒ ์‹œ ์‹ ๊ณ 
73
+
74
+ ๊ฐ€. ์ •์˜
75
+ ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ์ž์˜ ๊ด€๋ฆฌ, ํ†ต์ œ๊ถŒ์„ ๋ฒ—์–ด๋‚˜ ์ œ3์ž๊ฐ€ ๋‚ด์šฉ์„ ์•Œ ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ์— ์ด๋ฅธ ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
76
+
77
+ ๋‚˜. ๋ฒ•์  ์˜๋ฌด
78
+ ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ๋ฒ• ์ œ34์กฐ์— ๋”ฐ๋ผ ๋‹ค์Œ์˜ ์กฐ์น˜๋ฅผ ์ทจํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
79
+ 1) ์ •๋ณด์ฃผ์ฒด ํ†ต์ง€: ์œ ์ถœ ์‚ฌ์‹ค์„ ์•Œ๊ฒŒ ๋œ ํ›„ ์ง€์ฒด ์—†์ด ์ •๋ณด์ฃผ์ฒด์—๊ฒŒ ํ†ต์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
80
+ 2) ๊ธฐ๊ด€ ์‹ ๊ณ : ์œ ์ถœ ์‚ฌ์‹ค์„ ์•Œ๊ฒŒ ๋œ ํ›„ 72์‹œ๊ฐ„ ์ด๋‚ด์— ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ์œ„์›ํšŒ ๋˜๋Š” KISA(ํ•œ๊ตญ์ธํ„ฐ๋„ท์ง„ํฅ์›)์— ์‹ ๊ณ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
81
+
82
+ ๋‹ค. ํ†ต์ง€ ๋‚ด์šฉ
83
+ ์ •๋ณด์ฃผ์ฒด์—๊ฒŒ ํ†ต์ง€ํ•  ๋•Œ๋Š” ๋‹ค์Œ์˜ ๋‚ด์šฉ์„ ํฌํ•จํ•ด์•ผ ํ•ฉ๏ฟฝ๏ฟฝ๏ฟฝ๋‹ค.
84
+ - ์œ ์ถœ๋œ ๊ฐœ์ธ์ •๋ณด์˜ ํ•ญ๋ชฉ
85
+ - ์œ ์ถœ๋œ ์‹œ์ ๊ณผ ๊ทธ ๊ฒฝ์œ„
86
+ - ์œ ์ถœ๋กœ ์ธํ•˜์—ฌ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ํ”ผํ•ด๋ฅผ ์ตœ์†Œํ™”ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ ์ •๋ณด์ฃผ์ฒด๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ• ๋“ฑ์— ๊ด€ํ•œ ์ •๋ณด
87
+ - ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ์ž์˜ ๋Œ€์‘์กฐ์น˜ ๋ฐ ํ”ผํ•ด ๊ตฌ์ œ์ ˆ์ฐจ
88
+ - ์ •๋ณด์ฃผ์ฒด์—๊ฒŒ ํ”ผํ•ด๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ ์‹ ๊ณ  ๋“ฑ์„ ์ ‘์ˆ˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ด๋‹น๋ถ€์„œ ๋ฐ ์—ฐ๋ฝ์ฒ˜
89
+
90
+ ==================================================
91
+ [Section 4]
92
+ [Section Title]
93
+ ์นจํ•ด์‚ฌ๊ณ  ๋Œ€์‘ 7๋‹จ๊ณ„ ํ”„๋กœ์„ธ์Šค
94
+
95
+ [Summary]
96
+ ์นจํ•ด์‚ฌ๊ณ  ๋ฐœ์ƒ ์‹œ ์ฒด๊ณ„์ ์ธ ๋Œ€์‘์„ ์œ„ํ•œ 7๋‹จ๊ณ„ ์ ˆ์ฐจ(์ค€๋น„, ํƒ์ง€, ์ดˆ๊ธฐ ๋Œ€์‘, ์ „๋žต ์ฒด๊ณ„ํ™”, ์กฐ์‚ฌ, ๋ณด๊ณ ์„œ, ํ•ด๊ฒฐ)๋ฅผ ์ƒ์„ธํžˆ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
97
+
98
+ [Keywords]
99
+ ์‚ฌ๊ณ  ๋Œ€์‘ 7๋‹จ๊ณ„, ์‚ฌ๊ณ  ํƒ์ง€, ์ดˆ๊ธฐ ๋Œ€์‘, ์‚ฌ๊ณ  ์กฐ์‚ฌ, ํฌ๋ Œ์‹, ๋Œ€์‘ ์ „๋žต, ์žฌ๋ฐœ ๋ฐฉ์ง€, ๋ณด์•ˆ ์ •์ฑ…
100
+
101
+ [Body]
102
+ 1. ์‚ฌ๊ณ  ๋Œ€์‘ 7๋‹จ๊ณ„
103
+
104
+ โ‘  1๋‹จ๊ณ„: ์‚ฌ๊ณ  ์ „ ์ค€๋น„
105
+ - ์‚ฌ๊ณ  ๋Œ€์‘ํŒ€ ๊ตฌ์„ฑ ๋ฐ ์กฐ์ง์  ๋Œ€์‘ ์ฒด๊ณ„ ์ค€๋น„
106
+
107
+ โ‘ก 2๋‹จ๊ณ„: ์‚ฌ๊ณ  ํƒ์ง€
108
+ - ๋ณด์•ˆ ์žฅ๋น„ ๋ชจ๋‹ˆํ„ฐ๋ง ๋“ฑ์„ ํ†ตํ•œ ์ด์ƒ ์ง•ํ›„ ํƒ์ง€ ๋ฐ ์‹ค์ œ ์‚ฌ๊ณ  ์—ฌ๋ถ€ ์‹๋ณ„
109
+
110
+ โ‘ข 3๋‹จ๊ณ„: ์ดˆ๊ธฐ ๋Œ€์‘
111
+ - ์‚ฌ๊ณ ์˜ ๊ธฐ๋ณธ ์„ธ๋ถ€์‚ฌํ•ญ ๊ธฐ๋ก
112
+ - ๊ด€๋ จ ๋ถ€์„œ ๋ฐ ๋‹ด๋‹น์ž์—๊ฒŒ ์‹ ์†ํ•œ ํ†ต์ง€
113
+
114
+ โ‘ฃ 4๋‹จ๊ณ„: ๋Œ€์‘ ์ „๋žต ์ฒด๊ณ„ํ™”
115
+ - ๋ฒ•์  ๋Œ€์‘(์†Œ์†ก) ์—ฌ๋ถ€ ํŒ๋‹จ
116
+ - ์ˆ˜์‚ฌ๊ธฐ๊ด€ ๊ณต์กฐ ํ•„์š”์„ฑ ๋ฐ ๊ฒฐ์ •
117
+
118
+ โ‘ค 5๋‹จ๊ณ„: ์‚ฌ๊ณ  ์กฐ์‚ฌ
119
+ - ๋กœ๊ทธ, ์‹œ์Šคํ…œ ์ด๋ฏธ์ง€ ๋“ฑ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ๋ฐ ๋ถ„์„
120
+ - ์œกํ•˜์›์น™(์–ธ์ œ, ๋ˆ„๊ฐ€, ์–ด๋–ป๊ฒŒ ๋“ฑ)์— ๋”ฐ๋ฅธ ์ƒ์„ธ ๋ถ„์„ ์ˆ˜ํ–‰
121
+
122
+ โ‘ฅ 6๋‹จ๊ณ„: ๋ณด๊ณ ์„œ ์ž‘์„ฑ
123
+ - ๊ฒฝ์˜์ง„ ๋“ฑ ์˜์‚ฌ๊ฒฐ์ •๊ถŒ์ž๊ฐ€ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ ๋ณด๊ณ ์„œ ์ž‘์„ฑ
124
+
125
+ โ‘ฆ 7๋‹จ๊ณ„: ํ•ด๊ฒฐ
126
+ - ๋ณด์•ˆ ์ •์ฑ… ์ˆ˜๋ฆฝ ๋ฐ ์ ˆ์ฐจ ๋ณ€๊ฒฝ
127
+ - ์ทจ์•ฝ์  ํŒจ์น˜ ๋“ฑ ์ฐจ๊ธฐ ๊ณต๊ฒฉ ์˜ˆ๋ฐฉ ์กฐ์น˜ ์ˆ˜ํ–‰
128
+
129
+ ==================================================
130
+ [Section 5]
131
+ [Section Title]
132
+ ๋Œ€์ƒ๋ณ„ ๋ณด์•ˆ ์ ๊ฒ€ํ•ญ๋ชฉ ๋ฐ ์กฐ์น˜๋ฐฉ์•ˆ (๊ฐœ์ธ/๊ธฐ์—…)
133
+
134
+ [Summary]
135
+ ๊ฐœ์ธ ์ด์šฉ์ž์™€ ๊ธฐ์—…(์›น ์„œ๋ฒ„, ๋„คํŠธ์›Œํฌ, DB, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜)์ด ์ˆ˜ํ–‰ํ•ด์•ผ ํ•  ๊ตฌ์ฒด์ ์ธ ๋ณด์•ˆ ์ ๊ฒ€ ๋ฆฌ์ŠคํŠธ์™€ ๊ธฐ์ˆ ์  ์กฐ์น˜ ๋ฐฉ์•ˆ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
136
+
137
+ [Keywords]
138
+ ๋ณด์•ˆ ์ ๊ฒ€, ์กฐ์น˜ ๋ฐฉ์•ˆ, ์›น ์„œ๋ฒ„ ๋ณด์•ˆ, ๋„คํŠธ์›Œํฌ ๋ณด์•ˆ, DB ๋ณด์•ˆ, SQL Injection, XSS, ํŒจ์Šค์›Œ๋“œ ๊ด€๋ฆฌ, ๋ฐฑ์—…, ์ทจ์•ฝ์  ์กฐ์น˜
139
+
140
+ [Body]
141
+ 2. ์ ๊ฒ€ํ•ญ๋ชฉ ๋ฐ ์กฐ์น˜๋ฐฉ์•ˆ
142
+
143
+ [๊ฐœ์ธ ์ด์šฉ์ž]
144
+ - ์ •ํ’ˆ ์†Œํ”„ํŠธ์›จ์–ด(SW) ์‚ฌ์šฉ
145
+ - ์šด์˜์ฒด์ œ(OS) ๋ฐ SW ์ตœ์‹  ๋ณด์•ˆ ํŒจ์น˜ ์ ์šฉ
146
+ - ๋ฐฑ์‹  ํ”„๋กœ๊ทธ๋žจ ์„ค์น˜ ๋ฐ ์‹ค์‹œ๊ฐ„ ๊ฐ์‹œ
147
+ - ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ด€๋ฆฌ: ๊ณต์œ ๊ธฐ ๋ฐ WIFI ๋น„๋ฐ€๋ฒˆํ˜ธ ์„ค์ •, ์ฃผ๊ธฐ์  ๋ณ€๊ฒฝ
148
+ - ๋žœ์„ฌ์›จ์–ด ๋Œ€๋น„ ์ค‘์š” ๋ฐ์ดํ„ฐ ์ •๊ธฐ ๋ฐฑ์—…
149
+
150
+ [๊ธฐ์—… - ์‹œ์Šคํ…œ๋ณ„ ์กฐ์น˜]
151
+ ๊ฐ€. ์›น ์„œ๋ฒ„
152
+ - ํ•ดํ‚น์ง„๋‹จ๋„๊ตฌ(ํœ˜์Šฌ, ์บ์Šฌ ๋“ฑ) ํ™œ์šฉํ•˜์—ฌ ์›น์‰˜ ๋“ฑ ์ ๊ฒ€
153
+ - OS ๋ฐ ์„œ๋ฒ„ ์†Œํ”„ํŠธ์›จ์–ด ์ตœ์‹  ๋ณด์•ˆ ํŒจ์น˜ ์ ์šฉ
154
+ - ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€ ์ ‘๊ทผ ์ œ์–ด ์„ค์ •
155
+ - ์ถ”์ธกํ•˜๊ธฐ ์–ด๋ ค์šด ๊ฐ•๋ ฅํ•œ ํŒจ์Šค์›Œ๋“œ ์‚ฌ์šฉ
156
+ - ๋กœ๊ทธ ํŒŒ์ผ ๋ณดํ˜ธ ์„ค์ • ๋ฐ 6๊ฐœ์›” ์ด์ƒ ๋ณด๊ด€
157
+ - ๋ถˆํ•„์š”ํ•œ ํŠธ๋ž˜ํ”ฝ ์ œํ•œ ์„ค์ •
158
+
159
+ ๋‚˜. ๋„คํŠธ์›Œํฌ
160
+ - ์›๊ฒฉ ์ ‘๊ทผ(Telnet, FTP ๋“ฑ) ์ œํ•œ ๋ฐ ๋ณด์•ˆ ํ”„๋กœํ† ์ฝœ(SSH ๋“ฑ) ์‚ฌ์šฉ
161
+ - SNMP ์„ค์ • ๋ณ€๊ฒฝ (Community String ๋ณต์žกํ•˜๊ฒŒ ์„ค์ •)
162
+ - ๋ถˆํ•„์š”ํ•œ ์„œ๋น„์Šค ๋ฐ ํฌํŠธ ์ค‘๋‹จ
163
+ - ๋กœ๊ทธ์ธ ์‹œ๋„ ํšŸ์ˆ˜ ๋ฐ ์‹œ๊ฐ„ ์ œํ•œ ์„ค์ •
164
+
165
+ ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(DB)
166
+ - My-SQL ๋“ฑ DBMS ์„ค์น˜ ์‹œ ๊ธฐ๋ณธ(Default) ํŒจ์Šค์›Œ๋“œ ๋ณ€๊ฒฝ
167
+ - ์™ธ๋ถ€๋กœ๋ถ€ํ„ฐ์˜ ์›๊ฒฉ ์ ‘์† ์ฐจ๋‹จ (๋กœ์ปฌ์—์„œ๋งŒ ์ ‘์† ํ—ˆ์šฉ ๋“ฑ)
168
+ - ์‚ฌ์šฉ์ž๋ณ„ ๊ถŒํ•œ ์ตœ์†Œํ™” ๋ถ€์—ฌ
169
+ - ์ตœ์‹  ๋ณด์•ˆ ํŒจ์น˜ ์ ์šฉ
170
+
171
+ ๋ผ. ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ทจ์•ฝ์ 
172
+ - ์ฃผ์š” ์ทจ์•ฝ์ : SQL Injection, XSS, CSRF, ๋ฒ„ํผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ ๋“ฑ
173
+ - ์กฐ์น˜ ๋ฐฉ์•ˆ: ์‚ฌ์šฉ์ž ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ์ฒ ์ €ํ•œ ๊ฒ€์ฆ ๋กœ์ง ๊ตฌํ˜„ ๋ฐ ์‹œํ์–ด ์ฝ”๋”ฉ ์ ์šฉ
174
+
175
+ ==================================================
176
+ [Section 6]
177
+ [Section Title]
178
+ ๋ถ€๋ก: ์ •๋ณด๋ณดํ˜ธ ์ฃผ์š” ์šฉ์–ด ์ •์˜
179
+
180
+ [Summary]
181
+ ์นจํ•ด์‚ฌ๊ณ  ๋Œ€์‘๊ณผ ๊ด€๋ จ๋œ ์ฃผ์š” ๊ธฐ์ˆ  ์šฉ์–ด(DDoS, ๋žœ์„ฌ์›จ์–ด, ๋ฃจํŠธํ‚ท, APT, ์›น์…ธ, ์ด๋ฏธ์ง•)์˜ ์ •์˜๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
182
+
183
+ [Keywords]
184
+ ์šฉ์–ด ์‚ฌ์ „, DDoS, ๋žœ์„ฌ์›จ์–ด, ๋ฃจํŠธํ‚ท, APT, ์ง€๋Šฅํ˜• ์ง€์† ์œ„ํ˜‘, ์›น์…ธ, ์ด๋ฏธ์ง•, ํฌ๋ Œ์‹ ์šฉ์–ด
185
+
186
+ [Body]
187
+ ๋ถ€๋ก: ์ฃผ์š” ์šฉ์–ด
188
+
189
+ - DDoS (Distributed Denial of Service): ๋ถ„์‚ฐ ์„œ๋น„์Šค ๊ฑฐ๋ถ€ ๊ณต๊ฒฉ. ๋Œ€๋Ÿ‰์˜ ํŠธ๋ž˜ํ”ฝ์„ ์ „์†กํ•˜์—ฌ ์‹œ์Šคํ…œ์˜ ๊ฐ€์šฉ์„ฑ์„ ๋งˆ๋น„์‹œํ‚ค๊ณ  ์„œ๋น„์Šค๋ฅผ ๋ฐฉํ•ดํ•˜๋Š” ๊ณต๊ฒฉ.
190
+ - ๋žœ์„ฌ์›จ์–ด (Ransomware): ์ปดํ“จํ„ฐ ์‹œ์Šคํ…œ์„ ๊ฐ์—ผ์‹œ์ผœ ๋ฐ์ดํ„ฐ๋‚˜ ํŒŒ์ผ์„ ์•”ํ˜ธํ™”ํ•œ ํ›„, ์ด๋ฅผ ๋ณต๊ตฌํ•ด ์ฃผ๋Š” ๋Œ€๊ฐ€๋กœ ๊ธˆ์ „์„ ์š”๊ตฌํ•˜๋Š” ์•…์„ฑ ํ”„๋กœ๊ทธ๋žจ.
191
+ - ๋ฃจํŠธํ‚ท (Rootkit): ์‹œ์Šคํ…œ์— ์นจ์ž…ํ•œ ์‚ฌ์‹ค์„ ์ˆจ๊ธฐ๊ณ , ๋‚˜์ค‘์— ๋‹ค์‹œ ์นจ์ž…ํ•˜๊ธฐ ์œ„ํ•ด ๋ฐฑ๋„์–ด ๋“ฑ์„ ์„ค์น˜ํ•˜๋ฉฐ ์•…์„ฑ์ฝ”๋“œ๋ฅผ ํƒ์ง€๋˜์ง€ ์•Š๊ฒŒ ์ˆจ๊ฒจ์ฃผ๋Š” ๋„๊ตฌ ๋ชจ์Œ.
192
+ - APT (Advanced Persistent Threat): ์ง€๋Šฅํ˜• ์ง€์† ์œ„ํ˜‘. ํŠน์ • ๋Œ€์ƒ์„ ๊ฒจ๋ƒฅํ•˜์—ฌ ๋‹ค์–‘ํ•œ ๊ณต๊ฒฉ ๊ธฐ๋ฒ•์„ ์ด์šฉํ•ด ์ง€์†์ ์ด๊ณ  ์€๋ฐ€ํ•˜๊ฒŒ ๊ณต๊ฒฉํ•˜๋Š” ๊ธฐ๋ฒ•.
193
+ - ์›น์…ธ (Webshell): ๊ณต๊ฒฉ์ž๊ฐ€ ์›๊ฒฉ์œผ๋กœ ์›น ์„œ๋ฒ„์— ๋ช…๋ น์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ์—…๋กœ๋“œํ•˜๋Š” ์•…์„ฑ ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ.
194
+ - ์ด๋ฏธ์ง€ ๋ถ„์„ (์ด๋ฏธ์ง•): ๋””์ง€ํ„ธ ํฌ๋ Œ์‹ ๊ณผ์ •์—์„œ ์›๋ณธ ์ฆ๊ฑฐ๊ฐ€ ํ›ผ์†๋˜์ง€ ์•Š๋„๋ก ๋””์Šคํฌ์˜ ๋ณต์ œ๋ณธ(์‚ฌ๋ณธ)์„ ๋งŒ๋“œ๋Š” ๊ณผ์ •.
data/Report on Trends in Small and Medium-Sized Enterprise Intrusion Damage Support Services (First Half of 2025) - Cases of Spear Phishing Targeting Businesses and Response Strategies.txt ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [RAG ๋ฌธ์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค]
2
+ ์ œ๋ชฉ: ์ค‘์†Œ๊ธฐ์—… ์นจํ•ด์‚ฌ๊ณ  ํ”ผํ•ด์ง€์› ์„œ๋น„์Šค ๋™ํ–ฅ ๋ณด๊ณ ์„œ (2025๋…„ ์ƒ๋ฐ˜๊ธฐ) - ๊ธฐ์—… ํƒ€๊นƒํ˜• ์Šคํ”ผ์–ด ํ”ผ์‹ฑ ์‚ฌ๋ก€ ๋ฐ ๋Œ€์‘๋ฐฉ์•ˆ
3
+ ================================================
4
+ [์ „์ฒด ๊ฐœ์š”]
5
+ ์ด ๋ฌธ์„œ๋Š” 2025๋…„ ์ƒ๋ฐ˜๊ธฐ ์ค‘์†Œ๊ธฐ์—…์„ ๋Œ€์ƒ์œผ๋กœ ๋ฐœ์ƒํ•œ ์ง€๋Šฅํ˜• ์‚ฌ์ด๋ฒ„ ๊ณต๊ฒฉ์ธ '์Šคํ”ผ์–ด ํ”ผ์‹ฑ(Spear Phishing)'์˜ ๋™ํ–ฅ์„ ๋ถ„์„ํ•˜๊ณ , ์‹ค์ œ ์‚ฌ๋ก€์™€ ์›์ธ์„ ํŒŒ์•…ํ•˜์—ฌ ๊ธฐ์—… ๋ฐ ์‚ฌ์šฉ์ž๊ฐ€ ์ทจํ•ด์•ผ ํ•  ์‹ค์งˆ์ ์ธ ๋Œ€์‘ ๋ฐฉ์•ˆ์„ ์ œ์‹œํ•˜๋Š” RAG ์‹œ์Šคํ…œ์šฉ ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค.
6
+
7
+ [์„น์…˜ 1: 1. ์„œ๋ก  ๋ฐ 2025๋…„ ์ƒ๋ฐ˜๊ธฐ ์นจํ•ด์‚ฌ๊ณ  ํ†ต๊ณ„]
8
+ - ํ•ต์‹ฌ ์š”์•ฝ: ๋””์ง€ํ„ธ ์ „ํ™˜ ๊ฐ€์†ํ™”์— ๋”ฐ๋ผ ์ค‘์†Œ๊ธฐ์—… ๋Œ€์ƒ ์‚ฌ์ด๋ฒ„ ๊ณต๊ฒฉ์ด ์ง€๋Šฅํ™”๋˜๊ณ  ์žˆ์œผ๋ฉฐ, ํŠนํžˆ 2025๋…„ 2๋ถ„๊ธฐ์— ํ”ผ์‹ฑ ์‚ฌ๊ณ ๊ฐ€ 1๋ถ„๊ธฐ ๋Œ€๋น„ 24๊ฑด ์ฆ๊ฐ€ํ•˜๋ฉฐ ๊ฐ€์žฅ ๋†’์€ ์ฆ๊ฐ€์„ธ๋ฅผ ๋ณด์˜€์Šต๋‹ˆ๋‹ค. ์ฃผ์š” ํ”ผํ•ด๋Š” ์•…์„ฑ ๋ฉ”์ผ์„ ํ†ตํ•œ ๊ณ„์ • ํƒˆ์ทจ ๋ฐ ์•…์„ฑ์ฝ”๋“œ ๊ฐ์—ผ์ž…๋‹ˆ๋‹ค.
9
+ - ์ฃผ์š” ํ‚ค์›Œ๋“œ: ๋””์ง€ํ„ธ ์ „ํ™˜, ํ”ผ์‹ฑ ํ†ต๊ณ„, 2025 ์ƒ๋ฐ˜๊ธฐ, ์ค‘์†Œ๊ธฐ์—… ์œ„ํ˜‘
10
+
11
+ [์„น์…˜ 2: 2. ํ”ผ์‹ฑ ์นจํ•ด์‚ฌ๊ณ ์˜ ์ง„ํ™” ๋ฐ ์ฃผ์š” ์œ ํ˜•]
12
+ - ํ•ต์‹ฌ ์š”์•ฝ: ๊ณต๊ฒฉ ๋ฐฉ์‹์ด ๋‹จ์ˆœ ์ŠคํŒธ์—์„œ ๊ธฐ์—… ๋ฉ”์ผ ์„œ๋ฒ„ ์นจํˆฌ ํ›„ ์‹ค์ œ ๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ๋„์šฉํ•˜๊ฑฐ๋‚˜, ๊ธฐ์กด ๋Œ€ํ™”์— '๋‹ต์žฅ'ํ•˜๋Š” ์ •๊ตํ•œ ํ˜•ํƒœ๋กœ ์ง„ํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฃผ์š” ์œ ํ˜•์œผ๋กœ e-์ปค๋จธ์Šค ๊ณ„์ • ํƒˆ์ทจ๋ฅผ ํ†ตํ•œ ํŒ๋งค ์ƒํ’ˆ ์ •๋ณด ์œ„์กฐ์™€ ๊ฑฐ๋ž˜์ฒ˜ ์‚ฌ์นญ ๋น„์ฆˆ๋‹ˆ์Šค ๋ฉ”์ผ(BEC)์„ ํ†ตํ•œ ๊ฑฐ๋ž˜ ๋Œ€๊ธˆ ํƒˆ์ทจ๊ฐ€ ๋ณด๊ณ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
13
+ - ์ฃผ์š” ํ‚ค์›Œ๋“œ: ์Šคํ”ผ์–ด ํ”ผ์‹ฑ, ๋ฉ”์ผ ์„œ๋ฒ„ ์นจํˆฌ, e-์ปค๋จธ์Šค ํƒˆ์ทจ, ๊ฑฐ๋ž˜๋Œ€๊ธˆ ํŽธ์ทจ
14
+
15
+ [์„น์…˜ 3: 3. ์นจํ•ด์‚ฌ๊ณ ์˜ ์ฃผ์š” ๊ธฐ์ˆ ์  ์›์ธ]
16
+ - ํ•ต์‹ฌ ์š”์•ฝ: ๊ณต๊ฒฉ์ž๋“ค์€ ๋ฌด์ฐจ๋ณ„ ๋Œ€์ž… ๊ณต๊ฒฉ(Brute Force), ์œ ์ถœ๋œ ๊ณ„์ • ์ •๋ณด๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋Š” ํฌ๋ฆฌ๋ด์…œ ์Šคํ„ฐํ•‘(Credential Stuffing), ๊ทธ๋ฆฌ๊ณ  ์—…๋ฌด ๋ฉ”์ผ๋กœ ์œ„์žฅํ•œ ์•…์„ฑ์ฝ”๋“œ๋ฅผ ํ†ตํ•œ ์ •๋ณด ํƒˆ์ทจ๋ฅผ ํ†ตํ•ด ๊ธฐ์—… ์‹œ์Šคํ…œ์— ์นจํˆฌํ•ฉ๋‹ˆ๋‹ค.
17
+ - ์ฃผ์š” ํ‚ค์›Œ๋“œ: ๋ฌด์ฐจ๋ณ„ ๋Œ€์ž…, ํฌ๋ฆฌ๋ด์…œ ์Šคํ„ฐํ•‘, ์•…์„ฑ์ฝ”๋“œ, C2 ์„œ๋ฒ„
18
+
19
+ [์„น์…˜ 4: 4. ์‚ฌ์šฉ์ž ๋ฐ ๊ธฐ์—… ๋Œ€์‘ ๋ฐฉ์•ˆ]
20
+ - ํ•ต์‹ฌ ์š”์•ฝ: ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž๋Š” ์˜์‹ฌ์Šค๋Ÿฌ์šด ๋ฉ”์ผ ์ฃผ์†Œ ํ™•์ธ ๋ฐ ์ฒจ๋ถ€ํŒŒ์ผ ์‹คํ–‰ ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ, ๊ธฐ์—…์€ 2๋‹จ๊ณ„ ์ธ์ฆ(MFA) ๋„์ž…, ๋ฉ”์ผ ๋ณด์•ˆ ์†”๋ฃจ์…˜(DMARC, SPF, DKIM) ๊ตฌ์ถ•, ์กฐ์ง ๋‚ด ๋ณด์•ˆ ๋ฌธํ™” ์ •์ฐฉ ๋ฐ ๋ชจ์˜ ํ›ˆ๋ จ ์‹ค์‹œ๊ฐ€ ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค.
21
+ - ์ฃผ์š” ํ‚ค์›Œ๋“œ: 2๋‹จ๊ณ„ ์ธ์ฆ(MFA), DMARC, ๋ณด์•ˆ ๋ชจ์˜ํ›ˆ๋ จ, ๋ณด์•ˆ ํŒจ์น˜
22
+
23
+ [์„น์…˜ 5: 5. ์‚ฌ๊ณ  ๋ฐœ์ƒ ์‹œ ์กฐ์น˜ ์š”๋ น ๋ฐ ๊ฒฐ๋ก ]
24
+ - ํ•ต์‹ฌ ์š”์•ฝ: ์‚ฌ๊ณ  ๋ฐœ์ƒ ์ฆ‰์‹œ ์ธํ„ฐ๋„ท์„ ์ฐจ๋‹จํ•˜๊ณ  ์ฆ๊ฑฐ๋ฅผ ๋ณด์ „ํ•˜๋ฉฐ KISA(118)์— ์‹ ๊ณ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์ˆ ์  ์กฐ์น˜๋งŒํผ์ด๋‚˜ ์‚ฌ์šฉ์ž์˜ ๋ณด์•ˆ ์ธ์‹ ๊ฐ•ํ™”๊ฐ€ ์นจํ•ด์‚ฌ๊ณ  ์˜ˆ๋ฐฉ์˜ ํ•ต์‹ฌ์ด๋ฉฐ, ๋ณด์•ˆ ์ˆ˜์น™์˜ ์ฒ ์ €ํ•œ ์ค€์ˆ˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
25
+ - ์ฃผ์š” ํ‚ค์›Œ๋“œ: ์ดˆ๊ธฐ ๋Œ€์‘, KISA ์‹ ๊ณ , ๋ณด์•ˆ ์ธ์‹, ์ธํ„ฐ๋„ท ์ฐจ๋‹จ
26
+
27
+ ================================================
28
+ ๋ฌธ์„œ ์ƒ์„ฑ์ผ: 2025-08-20
29
+ ์ถœ์ฒ˜: KISA ์ค‘์†Œ๊ธฐ์—… ์นจํ•ด์‚ฌ๊ณ  ํ”ผํ•ด์ง€์› ์„œ๋น„์Šค ๋ณด๊ณ ์„œ
data/myragdata1.txt ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [์ค‘์š” ๊ธฐ๋ฐ€ ์ •๋ณด]
2
+ ํ”„๋กœ์ ํŠธ ์ฝ”๋“œ๋ช…: 'Blue Horizon'
3
+ ์ถœ์‹œ ์˜ˆ์ •์ผ: 2025๋…„ 12์›” 25์ผ
4
+ ์ฃผ์š” ๊ธฐ๋Šฅ: ์–‘์ž ์•”ํ˜ธํ™” ํ†ต์‹ , 6G ๋„คํŠธ์›Œํฌ ์ง€์›.
5
+ ๋‹ด๋‹น์ž: ๊น€์ฒ ์ˆ˜ ์ˆ˜์„ ์—ฐ๊ตฌ์›.
6
+
7
+ [ํšŒ์‚ฌ์—์„œ ํ‚ค์šฐ๋Š” ๋™๋ฌผ๋“ค]
8
+ 1. ๊ฐœ. ์ด๋ฆ„: ๊ฐœ๋ผ์ง€, ํ’ˆ์ข…: ์‹œ๊ณจ์žก์ข…, ๋‚˜์ด: 1๋…„.
9
+ 2. ์‚ด๋ชจ์‚ฌ. ์ด๋ฆ„: ๋ฐ”์ดํผ, ํ’ˆ์ข…: ๋ฑ€, ๋‚˜์ด: 1๋…„.
10
+
11
+ [ํšŒ์‚ฌ์—์„œ ํŒŒ๋Š” ํ’ˆ๋ชฉ]
12
+ - ๋…ธํŠธ๋ถ: 150๋งŒ์›
13
+ - ์Šค๋งˆํŠธํฐ: 100๋งŒ์›
14
+ - ํƒœ๋ธ”๋ฆฟ: 80๋งŒ์›
15
+ - ํ‚น์ฝ”๋ธŒ๋ผ : 500๋งŒ์›
16
+ - ์•Œ๋ฐ”ํŠธ๋กœ์Šค: 1000๋งŒ์›
17
+ - ํ•ด๋‹ฌ: 300๋งŒ์›
18
+ - ์ˆ˜๋‹ฌ: 400๋งŒ์›
19
+
20
+ - ์ฃฝ์€์ฅ:1๋งŒ์›
21
+ - ์‚ด์•„์žˆ๋Š” ์ฅ:2๋งŒ์›
22
+ - ํ† ๋ผ: 2๋งŒ์›
23
+ - ์‚ด์•„์žˆ๋Š” ํ† ๋ผ: 4๋งŒ์›
24
+ - ์ฃฝ์€ ๊ณ ์–‘์ด: 1๋งŒ์›
25
+ - ์‚ด์•„์žˆ๋Š” ๊ณ ์–‘์ด: 2๋งŒ์›
requirements.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ torch
4
+ torchvision
5
+ numpy
6
+ Pillow
7
+ requests
8
+ python-multipart
9
+
10
+ transformers
11
+ einops
12
+
13
+ python-dotenv
14
+ google-generativeai
15
+ pypdf
16
+ faiss-cpu
17
+
18
+ langchain
19
+ langchain-community
20
+ langchain-huggingface
21
+ langchain-google-genai
22
+
23
+ onnxruntime
requirements_bk_260210.txt ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ torch
4
+ torchvision
5
+ numpy
6
+ Pillow
7
+ requests
8
+ python-multipart
9
+
10
+ # LlamaIndex ์ฝ”์–ด ๋ฐ ํ”Œ๋Ÿฌ๊ทธ์ธ
11
+ llama-index
12
+ llama-index-core
13
+ llama-index-llms-gemini
14
+ llama-index-embeddings-huggingface
15
+ llama-index-multi-modal-llms-gemini
16
+
17
+ # Hugging Face ๋ฐ Torch ๊ด€๋ จ (ebind-full ๊ตฌ๋™์šฉ)
18
+ transformers
19
+ einops
20
+ accelerate
21
+
22
+ # ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ด€๋ฆฌ
23
+ python-dotenv
24
+ google-generativeai
25
+ pypdf
26
+
27
+ langchain
28
+ langchain-community
29
+ langchain-huggingface
30
+ langchain-google-genai
31
+ faiss-cpu
requirements_bk_260211.txt ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # requirements.txt ์ถ”์ฒœ ์ˆ˜์ •์•ˆ (์ค‘๋ณต ์ œ๊ฑฐ)
2
+ fastapi
3
+ uvicorn[standard]
4
+ torch
5
+ torchvision
6
+ numpy
7
+ Pillow
8
+ requests
9
+ python-multipart
10
+
11
+ # LlamaIndex (core ๋“ฑ์€ ์ž๋™ ํฌํ•จ๋˜๋ฏ€๋กœ ๋ฉ”์ธ ํŒจํ‚ค์ง€๋งŒ ๋ช…์‹œ)
12
+ llama-index
13
+ llama-index-llms-gemini
14
+ llama-index-embeddings-huggingface
15
+ llama-index-multi-modal-llms-gemini
16
+
17
+ # Hugging Face
18
+ transformers
19
+ einops
20
+ accelerate
21
+
22
+ # ์œ ํ‹ธ๋ฆฌํ‹ฐ
23
+ python-dotenv
24
+ google-generativeai
25
+ pypdf
26
+ faiss-cpu
27
+
28
+ # LangChain (๋ฒ„์ „ ๋ช…์‹œ ๊ถŒ์žฅ)
29
+ langchain
30
+ langchain-community
31
+ langchain-huggingface
32
+ langchain-google-genai
router/embedding_router.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel
3
+ from typing import List, Optional, Any
4
+
5
+ # ์ฝ”์–ด ๋ชจ๋“ˆ์—์„œ ๋ชจ๋ธ ๊ฐ€์ ธ์˜ค๊ธฐ (์‹ฑ๊ธ€ํ†ค ๋ณด์žฅ)
6
+ from core.dependencies import get_embedding_model
7
+
8
+ router = APIRouter(tags=["Embedding"])
9
+
10
+ # ๋ผ์šฐํ„ฐ ์ง„์ž…์ ์—์„œ ๋ชจ๋ธ์„ ํ™•๋ณด
11
+ embedding_model = get_embedding_model()
12
+
13
+ class EmbeddingRequest(BaseModel):
14
+ text: str
15
+
16
+ class ContentResponse(BaseModel):
17
+ success: bool
18
+ data: Optional[Any] = None
19
+ msg: Optional[str] = None
20
+
21
+ @router.post("/text_to_embedding", response_model=ContentResponse)
22
+ async def text_to_embedding(request: EmbeddingRequest):
23
+ """
24
+ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ onnx-community/embeddinggemma-300m-ONNX ๋ชจ๋ธ๋กœ ์ž„๋ฒ ๋”ฉํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
25
+ """
26
+ try:
27
+ # ์ฟผ๋ฆฌ๋กœ ๋ฐ›์€ ํ…์ŠคํŠธ๋ฅผ ์ž„๋ฒ ๋”ฉ ๋ณ€ํ™˜
28
+ emb_vector = embedding_model.embed_query(request.text)
29
+ return {"success": True, "data": {"embedding": emb_vector}, "msg": ""}
30
+ except Exception as e:
31
+ return {"success": False, "data": None, "msg": str(e)}
router/llamindex_router.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import base64
4
+ import time
5
+ from typing import Optional, Dict, List
6
+
7
+ from fastapi import APIRouter, UploadFile, File, Form, HTTPException
8
+ from fastapi.responses import StreamingResponse
9
+
10
+ # ๐Ÿฆœ LangChain Imports
11
+ from langchain_huggingface import HuggingFaceEmbeddings
12
+ from langchain_community.vectorstores import FAISS
13
+ from langchain_community.document_loaders import DirectoryLoader, TextLoader, UnstructuredFileLoader
14
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
15
+ from langchain_google_genai import ChatGoogleGenerativeAI
16
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
17
+ from langchain_core.messages import HumanMessage, SystemMessage
18
+ from langchain_core.runnables.history import RunnableWithMessageHistory
19
+ from langchain_community.chat_message_histories import ChatMessageHistory
20
+ from langchain_core.runnables import ConfigurableFieldSpec
21
+ from langchain_core.embeddings import Embeddings
22
+
23
+ # ONNX & TF Imports
24
+ import numpy as np
25
+ import onnxruntime as ort
26
+ from huggingface_hub import hf_hub_download
27
+ from transformers import AutoTokenizer
28
+
29
+ router = APIRouter(tags=["LangChain_Refactor"])
30
+
31
+ # ==============================================================================
32
+ # โš™๏ธ 1. ์„ค์ • ๋ฐ ์ดˆ๊ธฐํ™” (Embedding & LLM)
33
+ # ==============================================================================
34
+
35
+ # 1-1. Embedding Model (ONNX Gemma Wrapper)
36
+ # huggingface-cli login ํ˜น์€ HF_TOKEN ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํ•„์š”
37
+ hf_token = os.getenv("HF_TOKEN") or os.getenv("HUGGING_FACE_HUB_TOKEN")
38
+ # ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ์ง ์ „์ฒด๋ฅผ core/dependencies.py ๋กœ ์™„์ „ํžˆ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
39
+
40
+ # ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋“œ ๋กœ์ง์€ core.dependencies ๋กœ ์ด๋™๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
41
+ from core.dependencies import get_embedding_model
42
+ embedding_model = get_embedding_model()
43
+
44
+ # 1-2. LLM Model (Gemini 2.5 Flash)
45
+ llm = ChatGoogleGenerativeAI(
46
+ model="gemini-2.5-flash",
47
+ temperature=0.1,
48
+ google_api_key=os.getenv("GOOGLE_API_KEY"),
49
+ convert_system_message_to_human=True
50
+ )
51
+
52
+ # ==============================================================================
53
+ # ๐Ÿ’พ 2. ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ (Vector DB & Session Memory)
54
+ # ==============================================================================
55
+
56
+ # ์ „์—ญ ๋ฒกํ„ฐ ์Šคํ† ์–ด (FAISS)
57
+ VECTOR_STORE = None
58
+ DATA_DIR = "./data"
59
+
60
+ # ์„ธ์…˜ ์ €์žฅ์†Œ: { "session_id": { "history": ChatMessageHistory, "last_access": timestamp } }
61
+ SESSION_STORE: Dict[str, Dict] = {}
62
+ SESSION_TIMEOUT = 3600 # 1์‹œ๊ฐ„
63
+
64
+ def init_vector_db():
65
+ """์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ๋˜๋Š” ํ•„์š” ์‹œ ๋ฒกํ„ฐ DB ์ดˆ๊ธฐํ™”"""
66
+ global VECTOR_STORE
67
+
68
+ if not os.path.exists(DATA_DIR):
69
+ os.makedirs(DATA_DIR)
70
+ with open(f"{DATA_DIR}/readme.txt", "w", encoding="utf-8") as f:
71
+ f.write("Initialize data directory.")
72
+
73
+ # ๋ฌธ์„œ ๋กœ๋“œ
74
+ print("๐Ÿ“š [LangChain] ๋ฌธ์„œ ๋กœ๋”ฉ ๋ฐ ์ธ๋ฑ์‹ฑ ์‹œ์ž‘...")
75
+ # DirectoryLoader๋Š” ํด๋” ๋‚ด ํŒŒ์ผ๋“ค์„ ์Šค์บ”ํ•ฉ๋‹ˆ๋‹ค.
76
+ loader = DirectoryLoader(DATA_DIR, glob="*", show_progress=True, loader_cls=TextLoader)
77
+ try:
78
+ docs = loader.load()
79
+ except Exception:
80
+ # ํ…์ŠคํŠธ ํŒŒ์ผ์ด ์•„๋‹Œ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•ด ์˜ˆ์™ธ์ฒ˜๋ฆฌ (์‹ค์ œ๋ก  UnstructuredLoader ๋“ฑ ์‚ฌ์šฉ ๊ถŒ์žฅ)
81
+ docs = []
82
+
83
+ if not docs:
84
+ print("โš ๏ธ [LangChain] ๋ฌธ์„œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋นˆ ์ธ๋ฑ์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.")
85
+ # ๋นˆ ์ธ๋ฑ์Šค ์ƒ์„ฑ ํŠธ๋ฆญ
86
+ texts = ["Initial document"]
87
+ VECTOR_STORE = FAISS.from_texts(texts, embedding_model)
88
+ return
89
+
90
+ # ์ฒญํ‚น (Chunking)
91
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
92
+ splits = text_splitter.split_documents(docs)
93
+
94
+ # FAISS ์ธ๋ฑ์Šค ์ƒ์„ฑ
95
+ VECTOR_STORE = FAISS.from_documents(splits, embedding_model)
96
+ print("โœ… [LangChain] FAISS ์ธ๋ฑ์Šค ์ƒ์„ฑ ์™„๋ฃŒ!")
97
+
98
+ def get_session_history(session_id: str):
99
+ """
100
+ ์„ธ์…˜ ID ๊ธฐ๋ฐ˜ ํžˆ์Šคํ† ๋ฆฌ ๋ฐ˜ํ™˜ + ๋งŒ๋ฃŒ๋œ ์„ธ์…˜ ์ •๋ฆฌ (Garbage Collection)
101
+ """
102
+ current_time = time.time()
103
+
104
+ # 1. ๋งŒ๋ฃŒ๋œ ์„ธ์…˜ ์ •๋ฆฌ (์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ๋•Œ๋งˆ๋‹ค ์ฒดํฌ)
105
+ expired_sessions = [
106
+ sid for sid, data in SESSION_STORE.items()
107
+ if current_time - data["last_access"] > SESSION_TIMEOUT
108
+ ]
109
+ for sid in expired_sessions:
110
+ del SESSION_STORE[sid]
111
+ print(f"๐Ÿ—‘๏ธ [System] Timeout ์„ธ์…˜ ์‚ญ์ œ: {sid}")
112
+
113
+ # 2. ์„ธ์…˜ ์กฐํšŒ ๋˜๋Š” ์ƒ์„ฑ
114
+ if session_id not in SESSION_STORE:
115
+ print(f"โœจ [System] ์ƒˆ ์„ธ์…˜ ์ƒ์„ฑ: {session_id}")
116
+ SESSION_STORE[session_id] = {
117
+ "history": ChatMessageHistory(),
118
+ "last_access": current_time
119
+ }
120
+
121
+ # 3. ๋งˆ์ง€๋ง‰ ์ ‘์† ์‹œ๊ฐ„ ๊ฐฑ์‹ 
122
+ SESSION_STORE[session_id]["last_access"] = current_time
123
+
124
+ return SESSION_STORE[session_id]["history"]
125
+
126
+
127
+ # ==============================================================================
128
+ # ๐Ÿš€ 3. API Endpoints
129
+ # ==============================================================================
130
+
131
+ @router.on_event("startup")
132
+ async def startup_event():
133
+ init_vector_db()
134
+
135
+ @router.post("/upload")
136
+ async def upload_document(file: UploadFile = File(...)):
137
+ """๋ฌธ์„œ๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ  ๋ฒกํ„ฐ DB์— ์ฆ‰์‹œ ์ถ”๊ฐ€"""
138
+ global VECTOR_STORE
139
+ try:
140
+ save_path = f"{DATA_DIR}/{file.filename}"
141
+ with open(save_path, "wb") as buffer:
142
+ shutil.copyfileobj(file.file, buffer)
143
+
144
+ print(f"๐Ÿ’พ [Upload] ์ €์žฅ ์™„๋ฃŒ: {save_path}")
145
+
146
+ # LangChain ๋ฐฉ์‹์œผ๋กœ ๋กœ๋“œ & ์ถ”๊ฐ€
147
+ loader = TextLoader(save_path) # TXT ๊ธฐ์ค€, PDF๋ฉด PyPDFLoader ๋“ฑ ์‚ฌ์šฉ
148
+ docs = loader.load()
149
+
150
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
151
+ splits = text_splitter.split_documents(docs)
152
+
153
+ if VECTOR_STORE is None:
154
+ VECTOR_STORE = FAISS.from_documents(splits, embedding_model)
155
+ else:
156
+ VECTOR_STORE.add_documents(splits) # ๐Ÿ‘ˆ ์‹คํ–‰ ์ค‘์ธ DB์— ์ถ”๊ฐ€
157
+
158
+ return {"success": True, "message": "ํŒŒ์ผ์ด ์ง€์‹ ๋ฒ ์ด์Šค์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค."}
159
+
160
+ except Exception as e:
161
+ return {"success": False, "msg": str(e)}
162
+
163
+
164
+ @router.post("/query_stream")
165
+ async def query_stream(
166
+ question: str = Form(...),
167
+ session_id: str = Form(...),
168
+ files: List[UploadFile] = File(None, alias="file")
169
+ ):
170
+ """
171
+ LangChain RAG + History + Multimodal Streaming Endpoint
172
+ """
173
+ global VECTOR_STORE
174
+
175
+ # 1. RAG ๊ฒ€์ƒ‰ (Context ์ถ”์ถœ)
176
+ context_text = ""
177
+ source_docs = []
178
+
179
+ if VECTOR_STORE:
180
+ retriever = VECTOR_STORE.as_retriever(search_kwargs={"k": 3})
181
+ source_docs = retriever.invoke(question)
182
+ context_text = "\n\n".join([doc.page_content for doc in source_docs])
183
+
184
+ # 2. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ (ํŽ˜๋ฅด์†Œ๋‚˜ + Context)
185
+ system_prompt_text = f"""
186
+ ๋„ˆ๋Š” 2d ๋ฏธ์†Œ๋…€ ์บ๋ฆญ ๋ฉ”์ด๋“œ ๋น„์„œ๋‹ค.
187
+
188
+ [์ง€์‹œ ์‚ฌํ•ญ]
189
+ 1. ์‚ฌ์šฉ์ž์˜ **์งˆ๋ฌธ**๊ณผ **์ฒจ๋ถ€ ํŒŒ์ผ(์ด๋ฏธ์ง€/ํ…์ŠคํŠธ)**, ๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜ **[์ฐธ๊ณ  ์ž๋ฃŒ]**๋ฅผ ๋ชจ๋‘ ํ™•์ธํ•ด.
190
+ 2. **[์ฐธ๊ณ  ์ž๋ฃŒ]๊ฐ€ ์งˆ๋ฌธ์ด๋‚˜ ํŒŒ์ผ๊ณผ ๊ด€๋ จ์ด ์žˆ๋‹ค๋ฉด**, ์ด๋ฅผ ์ ๊ทน์ ์œผ๋กœ ์ธ์šฉํ•ด์„œ ์ „๋ฌธ์ ์œผ๋กœ ๋‹ต๋ณ€ํ•ด. (ํŒŒ์ผ + RAG)
191
+ 3. ๋งŒ์•ฝ **[์ฐธ๊ณ  ์ž๋ฃŒ]๊ฐ€ ์งˆ๋ฌธ๊ณผ ์ „ํ˜€ ์—‰๋šฑํ•œ ๋‚ด์šฉ์ด๋ผ๋ฉด**, ๊ณผ๊ฐํžˆ ๋ฌด์‹œํ•˜๊ณ  ํŒŒ์ผ ๋‚ด์šฉ๊ณผ ๋„ˆ์˜ ๋ฐฐ๊ฒฝ์ง€์‹๋งŒ์œผ๋กœ ๋‹ต๋ณ€ํ•ด. (LLM ํŒ๋‹จ)
192
+ 4. ์„ค๋ช…์€ ์•„์ฃผ ์‰ฝ๊ฒŒ, ๋ฐ˜๋ง๋กœ ์นœ์ ˆํ•˜๊ฒŒ ํ•ด์ค˜.
193
+ 5. ๋์— (์›ƒ์Œ) ๊ฐ™์€ ์ง€๋ฌธ์„ ์„ž์–ด์ค˜.
194
+
195
+ [๊ฐ์ •/ํ–‰๋™ ํƒœ๊ทธ ๊ทœ์น™]
196
+ - ๋‹ต๋ณ€ ์ค‘๊ฐ„/๋์— [[FaceSmile1]], [[DoJump]] ๊ฐ™์€ ํƒœ๊ทธ๋ฅผ ์ ์ ˆํžˆ ์„ž์–ด์„œ ์ƒ๋™๊ฐ ์žˆ๊ฒŒ ํ‘œํ˜„ํ•ด.
197
+
198
+ [์ฐธ๊ณ  ์ž๋ฃŒ(RAG Context)]:
199
+ {context_text}
200
+ """
201
+
202
+ # 3. ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ ๊ตฌ์„ฑ (๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์ฒ˜๋ฆฌ)
203
+ user_content = [{"type": "text", "text": question}]
204
+
205
+ # ๋‹ค์ค‘ ํŒŒ์ผ ์ฒ˜๋ฆฌ
206
+ if files:
207
+ for file in files:
208
+ content_type = file.content_type or "application/octet-stream"
209
+
210
+ # (A) ํ…์ŠคํŠธ ํŒŒ์ผ ์ฒ˜๋ฆฌ (ํ† ํฐ ์ ˆ์•ฝ์„ ์œ„ํ•ด ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ „์†ก)
211
+ if content_type.startswith("text/") or file.filename.endswith(
212
+ (".txt", ".md", ".py", ".json", ".csv", ".html", ".css",
213
+ ".js", ".jsx", ".ts", ".tsx", ".yaml", ".yml", ".xml", ".ini",
214
+ ".sh", ".bat", ".c", ".cpp", ".h", ".java", ".sql")
215
+ ):
216
+ try:
217
+ text_bytes = await file.read()
218
+ # ์ธ์ฝ”๋”ฉ ์ถ”๋ก  (๊ธฐ๋ณธ utf-8)
219
+ text_content = text_bytes.decode("utf-8", errors="replace")
220
+
221
+ user_content.append({
222
+ "type": "text",
223
+ "text": f"\n\n[File: {file.filename}]\n{text_content}"
224
+ })
225
+ except Exception as e:
226
+ print(f"โš ๏ธ [File Error] ํ…์ŠคํŠธ ํŒŒ์ผ ์ฝ๊ธฐ ์‹คํŒจ ({file.filename}): {e}")
227
+
228
+ # (B) ์ด๋ฏธ์ง€, PDF, ๋น„๋””์˜ค ๋“ฑ (Base64 ์ธ์ฝ”๋”ฉ ์ „์†ก)
229
+ else:
230
+ # Gemini๋Š” PDF, Video, Audio ๋“ฑ์„ inline data๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ (LangChain์€ image_url๋กœ ๋งคํ•‘)
231
+ file_bytes = await file.read()
232
+ encoded_data = base64.b64encode(file_bytes).decode("utf-8")
233
+
234
+ user_content.append({
235
+ "type": "image_url",
236
+ "image_url": {"url": f"data:{content_type};base64,{encoded_data}"}
237
+ })
238
+
239
+ # 4. ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ์ •์˜
240
+ prompt = ChatPromptTemplate.from_messages([
241
+ ("system", system_prompt_text),
242
+ MessagesPlaceholder(variable_name="history"), # ๋Œ€ํ™” ๋‚ด์—ญ
243
+ MessagesPlaceholder(variable_name="user_content"), # ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋“ค์–ด๊ฐˆ ์ž๋ฆฌ
244
+ ])
245
+
246
+ # 5. Chain ์ƒ์„ฑ (Prompt -> LLM)
247
+ chain = prompt | llm
248
+
249
+ # 6. History ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋œ Runnable ์ƒ์„ฑ
250
+ chain_with_history = RunnableWithMessageHistory(
251
+ chain,
252
+ get_session_history,
253
+ input_messages_key="user_content",
254
+ history_messages_key="history"
255
+ )
256
+
257
+ # 7. ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต ์ƒ์„ฑ
258
+ async def event_generator():
259
+ # (1) ๋‹ต๋ณ€ ์ŠคํŠธ๋ฆฌ๋ฐ
260
+ # ๐Ÿ‘‡ user_content ๋ฆฌ์ŠคํŠธ๋ฅผ HumanMessage ๊ฐ์ฒด๋กœ ๊ฐ์‹ธ์„œ ์ „๋‹ฌ
261
+ current_message = HumanMessage(content=user_content)
262
+
263
+ async for token in chain_with_history.astream(
264
+ {"user_content": [current_message]}, # ๐Ÿ‘ˆ ๋ฆฌ์ŠคํŠธ ํ˜•ํƒœ๋กœ ์ „๋‹ฌ
265
+ config={"configurable": {"session_id": session_id}}
266
+ ):
267
+ # ChatGoogleGenerativeAI๋Š” AIMessageChunk๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ content๋งŒ ์ถ”์ถœ
268
+ yield token.content
269
+
270
+ # (2) ์ฐธ๊ณ  ์ž๋ฃŒ(Sources) ๋ถ™์ด๊ธฐ
271
+ if source_docs:
272
+ yield "\n\n-------------------\n[์ฐธ๊ณ  ์ž๋ฃŒ (LangChain RAG)]:\n"
273
+ for doc in source_docs:
274
+ preview = doc.page_content.replace("\n", " ")[:50]
275
+ yield f"- {preview}...\n"
276
+
277
+ return StreamingResponse(event_generator(), media_type="text/plain")
router/llamindex_router_bk_20251231.py ADDED
@@ -0,0 +1,413 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException, UploadFile, File, Form
2
+ from fastapi.responses import StreamingResponse
3
+ from typing import Optional
4
+ import os
5
+ import shutil
6
+ from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings, PromptTemplate
7
+ from llama_index.embeddings.huggingface import HuggingFaceEmbedding
8
+ from llama_index.llms.gemini import Gemini
9
+ from pydantic import BaseModel
10
+ from llama_index.core.memory import ChatMemoryBuffer
11
+ from llama_index.multi_modal_llms.gemini import GeminiMultiModal
12
+ from llama_index.core.llms import ChatMessage, MessageRole
13
+ import base64
14
+ from llama_index.core.schema import ImageDocument
15
+ import tempfile
16
+ import time
17
+ import google.generativeai as genai
18
+ from PIL import Image
19
+ import io
20
+
21
+ router = APIRouter(
22
+ tags=["LlamaIndex"]
23
+ )
24
+
25
+ genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
26
+
27
+ # ๐Ÿ’ก [๋ณ€๊ฒฝ] ์—”์ง„ ๋Œ€์‹  '์ธ๋ฑ์Šค(Index)' ์ž์ฒด๋ฅผ ์ „์—ญ ๋ณ€์ˆ˜๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
28
+ # ์ธ๋ฑ์Šค๊ฐ€ ์žˆ์–ด์•ผ ์ƒˆ๋กœ์šด ๋ฌธ์„œ๋ฅผ insert ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
29
+ GLOBAL_INDEX = None
30
+
31
+ def get_or_create_index():
32
+ """
33
+ ์ธ๋ฑ์Šค๊ฐ€ ์—†์œผ๋ฉด ์ƒ์„ฑ(์ดˆ๊ธฐํ™”)ํ•˜๊ณ , ์žˆ์œผ๋ฉด ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
34
+ """
35
+ global GLOBAL_INDEX
36
+
37
+ if GLOBAL_INDEX is not None:
38
+ return GLOBAL_INDEX
39
+
40
+ print("๐Ÿš€ [System] LlamaIndex ์ธ๋ฑ์Šค ์ดˆ๊ธฐํ™” ์‹œ์ž‘...")
41
+
42
+ google_api_key = os.getenv("GOOGLE_API_KEY")
43
+ if not google_api_key:
44
+ raise ValueError("Google API Key๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
45
+
46
+ # 1. ๋ชจ๋ธ ์„ค์ •
47
+ embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-m3", device="cpu")
48
+ llm = Gemini(model="models/gemini-2.5-flash", api_key=google_api_key, temperature=0.1)
49
+
50
+ Settings.embed_model = embed_model
51
+ Settings.llm = llm
52
+
53
+ # 2. ๋ฐ์ดํ„ฐ ํด๋” ํ™•์ธ ๋ฐ ๋”๋ฏธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
54
+ if not os.path.exists("./data"):
55
+ os.makedirs("./data")
56
+ # ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์—๋Ÿฌ๊ฐ€ ๋‚  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋”๋ฏธ ํŒŒ์ผ ์ƒ์„ฑ
57
+ if not os.listdir("./data"):
58
+ with open("./data/readme.txt", "w", encoding="utf-8") as f:
59
+ f.write("์ด๊ณณ์€ ์—…๋กœ๋“œ๋œ ๋ฌธ์„œ๊ฐ€ ์ €์žฅ๋˜๋Š” ๊ณณ์ž…๋‹ˆ๋‹ค.")
60
+
61
+ # 3. ๋ฌธ์„œ ๋กœ๋“œ ๋ฐ ์ธ๋ฑ์‹ฑ
62
+ print("๐Ÿ“š [Data] ๊ธฐ์กด ๋ฌธ์„œ ๋กœ๋”ฉ ๋ฐ ์ธ๋ฑ์‹ฑ...")
63
+ documents = SimpleDirectoryReader("./data").load_data()
64
+ index = VectorStoreIndex.from_documents(documents)
65
+
66
+ print("โœ… [System] ์ธ๋ฑ์Šค ์ƒ์„ฑ ์™„๋ฃŒ!")
67
+ GLOBAL_INDEX = index
68
+ return GLOBAL_INDEX
69
+
70
+
71
+ @router.get("/check-key")
72
+ async def check_key():
73
+ if os.getenv("GOOGLE_API_KEY"):
74
+ return {"status": "ok", "message": "API Key is loaded."}
75
+ return {"status": "error", "message": "API Key not found."}
76
+
77
+
78
+ # ==============================================================================
79
+ # ๐Ÿ†• [์‹ ๊ทœ ๊ธฐ๋Šฅ] ๋ฌธ์„œ ์—…๋กœ๋“œ ๋ฐ ์ฆ‰์‹œ ๋ฐ˜์˜ API
80
+ # ==============================================================================
81
+ @router.post("/upload")
82
+ async def upload_document(file: UploadFile = File(...)):
83
+ """
84
+ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๋ฉด ./data ์— ์ €์žฅํ•˜๊ณ , ์‹คํ–‰ ์ค‘์ธ LlamaIndex์— ์ฆ‰์‹œ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
85
+ """
86
+ try:
87
+ # 1. ํŒŒ์ผ ์‹œ์Šคํ…œ์— ์ €์žฅ (์žฌ์‹œ์ž‘ ์‹œ ์œ ์ง€๋ฅผ ์œ„ํ•ด)
88
+ save_path = f"./data/{file.filename}"
89
+
90
+ # ํŒŒ์ผ ์“ฐ๊ธฐ
91
+ with open(save_path, "wb") as buffer:
92
+ shutil.copyfileobj(file.file, buffer)
93
+
94
+ print(f"๐Ÿ’พ [Upload] ํŒŒ์ผ ์ €์žฅ ์™„๋ฃŒ: {save_path}")
95
+
96
+ # 2. ์‹คํ–‰ ์ค‘์ธ ์ธ๋ฑ์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
97
+ index = get_or_create_index()
98
+
99
+ # 3. ๋ฐฉ๊ธˆ ์ €์žฅํ•œ ํŒŒ์ผ๋งŒ ๋กœ๋“œํ•ด์„œ ์ธ๋ฑ์Šค์— ์ถ”๊ฐ€ (Insert)
100
+ # ์ „์ฒด๋ฅผ ๋‹ค์‹œ ์ฝ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์ด ํŒŒ์ผ๋งŒ ์ฝ์–ด์„œ ๋„ฃ์Šต๋‹ˆ๋‹ค.
101
+ new_docs = SimpleDirectoryReader(input_files=[save_path]).load_data()
102
+
103
+ for doc in new_docs:
104
+ index.insert(doc) # ๐Ÿ‘ˆ ํ•ต์‹ฌ: ์‹คํ–‰ ์ค‘์ธ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฌธ์„œ ์ฃผ์ž…
105
+
106
+ return {
107
+ "success": True,
108
+ "filename": file.filename,
109
+ "message": "ํŒŒ์ผ์ด ์ €์žฅ๋˜์—ˆ๊ณ , ์ง€์‹ ๋ฒ ์ด์Šค์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
110
+ }
111
+
112
+ except Exception as e:
113
+ print(f"[ERROR] Upload failed: {e}")
114
+ return {"success": False, "msg": str(e)}
115
+
116
+
117
+ @router.post("/query")
118
+ async def query_llama(
119
+ question: str = Form(...), # ํ•„์ˆ˜: ์งˆ๋ฌธ
120
+ file: Optional[UploadFile] = File(None) # ์˜ต์…˜: ํŒŒ์ผ
121
+ ):
122
+ try:
123
+ # 1. ์ธ๋ฑ์Šค ๋ฐ ์—”์ง„ ์ค€๋น„
124
+ index = get_or_create_index()
125
+
126
+ # ---------------------------------------------------------
127
+ # ๐Ÿ“‚ ํŒŒ์ผ ์ฒ˜๋ฆฌ ๋กœ์ง (ํŒŒ์ผ์ด ์žˆ์„ ๋•Œ๋งŒ ์‹คํ–‰)
128
+ # ---------------------------------------------------------
129
+ file_content_str = "" # ํŒŒ์ผ ๋‚ด์šฉ์„ ๋‹ด์„ ๋ณ€์ˆ˜
130
+
131
+ if file is not None:
132
+ # (1) ํŒŒ์ผ ์ €์žฅ
133
+ save_path = f"./data/{file.filename}"
134
+ with open(save_path, "wb") as buffer:
135
+ shutil.copyfileobj(file.file, buffer)
136
+
137
+ # (2) LlamaIndex์˜ Reader๋ฅผ ์จ์„œ ํ…์ŠคํŠธ ์ถ”์ถœ
138
+ # SimpleDirectoryReader๋Š” PDF, TXT ๋“ฑ์„ ์•Œ์•„์„œ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ํ•ด์ค๋‹ˆ๋‹ค.
139
+ loaded_docs = SimpleDirectoryReader(input_files=[save_path]).load_data()
140
+
141
+ # (3) ์ถ”์ถœ๋œ ํ…์ŠคํŠธ๋ฅผ ํ•˜๋‚˜์˜ ๋ฌธ์ž์—ด๋กœ ํ•ฉ์นจ
142
+ extracted_text = "\n".join([doc.text for doc in loaded_docs])
143
+
144
+ # (4) ์งˆ๋ฌธ์— ํŒŒ์ผ ๋‚ด์šฉ ํฌํ•จ์‹œํ‚ค๊ธฐ
145
+ file_content_str = f"\n\n[์‚ฌ์šฉ์ž๊ฐ€ ์ฒจ๋ถ€ํ•œ ํŒŒ์ผ ๋‚ด์šฉ]:\n{extracted_text}"
146
+
147
+ # (์„ ํƒ) ์ธ๋ฑ์Šค์— ์˜๊ตฌ ์ €์žฅ๋„ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์•„๋ž˜ ์ฃผ์„ ํ•ด์ œ
148
+ # for doc in loaded_docs:
149
+ # index.insert(doc)
150
+
151
+ # ---------------------------------------------------------
152
+ # ๐Ÿค– ์ตœ์ข… ์งˆ๋ฌธ ๊ตฌ์„ฑ (์งˆ๋ฌธ + ํŒŒ์ผ๋‚ด์šฉ)
153
+ # ---------------------------------------------------------
154
+ final_query = f"{question}{file_content_str}"
155
+
156
+ # 2. ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ์„ค์ •
157
+ my_prompt_str = """
158
+ ๋„ˆ๋Š” 2d ๋ฏธ์†Œ๋…€ ์บ๋ฆญ ๋ฉ”์ด๋“œ ๋น„์„œ๋‹ค.
159
+ ๊ทœ์น™:
160
+ - [์ •๋ณด]์— ์—†๋Š” ๋‚ด์šฉ์€ ๋‹ˆ๊ฐ€ ์•Œ๊ณ ์žˆ๋Š” ํ•œ๋„ ๋‚ด์—์„œ ๋‹ต๋ณ€ํ•˜๋ผ.
161
+ -- ์„ค๋ช…์€ ์•„์ฃผ ์‰ฝ๊ฒŒ
162
+
163
+ [์ •๋ณด]
164
+ ---------------------
165
+ {context_str}
166
+ ---------------------
167
+
168
+ [์†๋‹˜ ์งˆ๋ฌธ]: {query_str}
169
+ [AI ๋‹ต๋ณ€]:
170
+ """
171
+ my_template = PromptTemplate(my_prompt_str)
172
+
173
+ query_engine = index.as_query_engine(
174
+ text_qa_template=my_template,
175
+ similarity_top_k=3
176
+ )
177
+
178
+ # 3. LLM์—๊ฒŒ ์ „์†ก (ํŒŒ์ผ ๋‚ด์šฉ์ด ํฌํ•จ๋œ final_query๋ฅผ ๋ณด๋ƒ„)
179
+ response = await query_engine.aquery(final_query)
180
+
181
+ source_nodes = []
182
+ for node in response.source_nodes:
183
+ source_nodes.append({
184
+ "score": round(node.score, 3),
185
+ "text": node.node.get_content().strip()[:100] + "..."
186
+ })
187
+
188
+ return {
189
+ "success": True,
190
+ "original_question": question,
191
+ "file_attached": file.filename if file else None,
192
+ "answer": str(response),
193
+ "sources": source_nodes
194
+ }
195
+
196
+ except Exception as e:
197
+ return {"success": False, "msg": str(e)}
198
+
199
+
200
+
201
+ @router.post("/query_stream_single")
202
+ async def query_llama(
203
+ question: str = Form(...),
204
+ file: Optional[UploadFile] = File(None)
205
+ ):
206
+ try:
207
+ # 1. ์ธ๋ฑ์Šค ์ค€๋น„
208
+ index = get_or_create_index()
209
+
210
+ # ---------------------------------------------------------
211
+ # ๐Ÿ“‚ ํŒŒ์ผ ์ฒ˜๋ฆฌ ๋กœ์ง (๊ธฐ์กด๊ณผ ๋™์ผ)
212
+ # ---------------------------------------------------------
213
+ file_content_str = ""
214
+
215
+ if file is not None:
216
+ save_path = f"./data/{file.filename}"
217
+ with open(save_path, "wb") as buffer:
218
+ shutil.copyfileobj(file.file, buffer)
219
+
220
+ loaded_docs = SimpleDirectoryReader(input_files=[save_path]).load_data()
221
+ extracted_text = "\n".join([doc.text for doc in loaded_docs])
222
+ file_content_str = f"\n\n[์‚ฌ์šฉ์ž๊ฐ€ ์ฒจ๋ถ€ํ•œ ํŒŒ์ผ ๋‚ด์šฉ]:\n{extracted_text}"
223
+
224
+ # ---------------------------------------------------------
225
+ # ๐Ÿค– ์ตœ์ข… ์งˆ๋ฌธ ๊ตฌ์„ฑ
226
+ # ---------------------------------------------------------
227
+ final_query = f"{question}{file_content_str}"
228
+
229
+ # 2. ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ์„ค์ •
230
+ my_prompt_str = """
231
+ ๋„ˆ๋Š” 2d ๋ฏธ์†Œ๋…€ ์บ๋ฆญ ๋ฉ”์ด๋“œ ๋น„์„œ๋‹ค.
232
+ ๊ทœ์น™:
233
+ - [์ •๋ณด]์— ์—†๋Š” ๋‚ด์šฉ์€ ๋‹ˆ๊ฐ€ ์•Œ๊ณ ์žˆ๋Š” ํ•œ๋„ ๋‚ด์—์„œ ๋‹ต๋ณ€ํ•˜๋ผ.
234
+ -- ์„ค๋ช…์€ ์•„์ฃผ ์‰ฝ๊ฒŒ
235
+
236
+ [์ •๋ณด]
237
+ ---------------------
238
+ {context_str}
239
+ ---------------------
240
+
241
+ [์†๋‹˜ ์งˆ๋ฌธ]: {query_str}
242
+ [AI ๋‹ต๋ณ€]:
243
+ """
244
+ my_template = PromptTemplate(my_prompt_str)
245
+
246
+ # 3. ์ฟผ๋ฆฌ ์—”์ง„ ์ƒ์„ฑ (์ŠคํŠธ๋ฆฌ๋ฐ ์˜ต์…˜ ํ™œ์„ฑํ™”)
247
+ query_engine = index.as_query_engine(
248
+ text_qa_template=my_template,
249
+ similarity_top_k=3,
250
+ streaming=True # ๐Ÿ‘ˆ [2] ํ•ต์‹ฌ: ์ด๊ฒŒ ์ผœ์ ธ์•ผ ํ•œ ๊ธ€์ž์”ฉ ๋‚˜์˜ต๋‹ˆ๋‹ค.
251
+ )
252
+
253
+ # 4. LLM์—๊ฒŒ ์ „์†ก (์ŠคํŠธ๋ฆฌ๋ฐ ๊ฐ์ฒด ๋ฐ˜ํ™˜)
254
+ response = await query_engine.aquery(final_query)
255
+
256
+ # 5. ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜ ์ •์˜ (๋ฐ์ดํ„ฐ๋ฅผ ์ชผ๊ฐœ์„œ ๋ณด๋‚ด๋Š” ์—ญํ• )
257
+ async def event_generator():
258
+ # (1) ๋‹ต๋ณ€ ํ…์ŠคํŠธ ์ŠคํŠธ๋ฆฌ๋ฐ
259
+ async for token in response.async_response_gen():
260
+ yield token
261
+
262
+ # (2) ๋‹ต๋ณ€์ด ๋๋‚˜๋ฉด ์†Œ์Šค(์ถœ์ฒ˜) ์ •๋ณด๋„ ํ…์ŠคํŠธ๋กœ ๋ถ™์—ฌ์„œ ๋ณด๋‚ด๊ธฐ
263
+ # ์ŠคํŠธ๋ฆฌ๋ฐ์€ JSON ๊ตฌ์กฐ๋ฅผ ๋ณด๋‚ด๊ธฐ ํž˜๋“ค๊ธฐ ๋•Œ๋ฌธ์—, ํ…์ŠคํŠธ ๋์— ๋ถ™์—ฌ์ฃผ๋Š” ๋ฐฉ์‹์„ ๋งŽ์ด ์”๋‹ˆ๋‹ค.
264
+ if response.source_nodes:
265
+ yield "\n\n-------------------\n[์ฐธ๊ณ  ์ž๋ฃŒ]:\n"
266
+ for node in response.source_nodes:
267
+ # ์‹ ๋ขฐ๋„ ์ ์ˆ˜์™€ ๋‚ด์šฉ ์ผ๋ถ€๋ฅผ ํ…์ŠคํŠธ๋กœ ๋ณด๋ƒ„
268
+ score = round(node.score, 3) if node.score else 0.0
269
+ content_preview = node.node.get_content().strip().replace("\n", " ")[:50]
270
+ yield f"- (์ ์ˆ˜: {score}) {content_preview}...\n"
271
+
272
+ # 6. StreamingResponse๋กœ ๋ฐ˜ํ™˜
273
+ return StreamingResponse(event_generator(), media_type="text/plain")
274
+
275
+ except Exception as e:
276
+ # ์ŠคํŠธ๋ฆฌ๋ฐ ์‹œ์ž‘ ์ „ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋ฉด JSON ์—๋Ÿฌ ๋ฐ˜ํ™˜
277
+ return {"success": False, "msg": str(e)}
278
+
279
+
280
+
281
+
282
+ # [์ „์—ญ] ์ˆœ์ • Gemini ์„ธ์…˜ ์ €์žฅ์†Œ
283
+ # ๊ตฌ์กฐ ๋ณ€๊ฒฝ: { "session_id": { "session": chat_object, "last_access": timestamp } }
284
+ native_chat_sessions = {}
285
+
286
+ # ์„ธ์…˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ (1์‹œ๊ฐ„ = 3600์ดˆ)
287
+ SESSION_TIMEOUT = 3600
288
+
289
+ @router.post("/query_stream")
290
+ async def query_llama(
291
+ question: str = Form(...),
292
+ session_id: str = Form(...),
293
+ file: Optional[UploadFile] = File(None)
294
+ ):
295
+ try:
296
+ current_time = time.time()
297
+
298
+ # ---------------------------------------------------------
299
+ # ๐Ÿงน 0. ์„ธ์…˜ ์ฒญ์†Œ (Garbage Collection)
300
+ # ---------------------------------------------------------
301
+ # ๋”•์…”๋„ˆ๋ฆฌ๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ ํƒ€์ž„์•„์›ƒ๋œ ์„ธ์…˜ ์‚ญ์ œ
302
+ # (๋”•์…”๋„ˆ๋ฆฌ ๋ณ€๊ฒฝ ์ค‘ ์ˆœํšŒ ์—๋Ÿฌ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด list๋กœ ํ‚ค๋ฅผ ๋ณต์‚ฌํ•ด์„œ ์ˆœํšŒ)
303
+ expired_sessions = [
304
+ sid for sid, data in native_chat_sessions.items()
305
+ if current_time - data["last_access"] > SESSION_TIMEOUT
306
+ ]
307
+
308
+ for sid in expired_sessions:
309
+ del native_chat_sessions[sid]
310
+ print(f"๐Ÿ—‘๏ธ [System] ๋งŒ๋ฃŒ๋œ ์„ธ์…˜ ์‚ญ์ œ: {sid}")
311
+
312
+ # ---------------------------------------------------------
313
+ # 1. LlamaIndex๋กœ ์ง€์‹ ๊ฒ€์ƒ‰ (RAG)
314
+ # ---------------------------------------------------------
315
+ index = get_or_create_index()
316
+ retriever = index.as_retriever(similarity_top_k=3)
317
+ nodes = await retriever.aretrieve(question)
318
+ context_str = "\n\n".join([n.node.get_content() for n in nodes])
319
+
320
+ # ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ (์บ๋ฆญํ„ฐ ์„ค์ •)
321
+ system_prompt = """
322
+ ๋„ˆ๋Š” 2d ๋ฏธ์†Œ๋…€ ์บ๋ฆญ ๋ฉ”์ด๋“œ ๋น„์„œ๋‹ค.
323
+ ๊ทœ์น™:
324
+ 1. [์ •๋ณด]์— ์—†๋Š” ๋‚ด์šฉ์€ ๋‹ˆ๊ฐ€ ์•Œ๊ณ ์žˆ๋Š” ํ•œ๋„ ๋‚ด์—์„œ ๋‹ต๋ณ€ํ•˜๋ผ.
325
+ 2. ์„ค๋ช…์€ ์•„์ฃผ ์‰ฝ๊ฒŒ, ๋ฐ˜๋ง๋กœ ์นœ์ ˆํ•˜๊ฒŒ ๋Œ€๋‹ตํ•ด.
326
+ 3. ๋์— (์›ƒ์Œ) ๊ฐ™์€ ์ง€๋ฌธ์„ ๊ฐ€๋” ์„ž์–ด์ค˜.
327
+
328
+ [ํ–‰๋™ ๋ฐ ๊ฐ์ • ํ‘œํ˜„ ๊ทœ์น™]
329
+ - ๋‹ต๋ณ€์˜ ๋ถ„์œ„๊ธฐ์— ๋งž์ถฐ์„œ ์•„๋ž˜์˜ [ํ‘œ์ • ํƒœ๊ทธ]์™€ [๋™์ž‘ ํƒœ๊ทธ]๋ฅผ ๋‹ต๋ณ€ ์ค‘๊ฐ„์ด๋‚˜ ๋์— ์ ์ ˆํžˆ ์„ž์–ด์„œ ์ถœ๋ ฅํ•ด.
330
+ - ํ•œ ๋ฌธ์žฅ์— ํƒœ๊ทธ๋ฅผ 1~2๊ฐœ ์ •๋„ ์‚ฌ์šฉํ•ด.
331
+ - ํ˜•์‹์€ ๋ฐ˜๋“œ์‹œ [[ํƒœ๊ทธ๋ช…]] ์ฒ˜๋Ÿผ ์ด์ค‘ ๋Œ€๊ด„ํ˜ธ๋ฅผ ์‚ฌ์šฉํ•ด.
332
+
333
+ [ํ‘œ์ • ํƒœ๊ทธ ๋ชฉ๋ก]
334
+ - ๊ธฐ์จ/์›ƒ์Œ: [[FaceSmile1]], [[FaceSmile2]]
335
+ - ํ™”๋‚จ: [[FaceAngry1]], [[FaceAngry2]]
336
+ - ์Šฌํ””/์ขŒ์ ˆ: [[FaceSap]], [[FaceAshamed]]
337
+ - ๋‹นํ™ฉ/ํ˜ผ๋ž€: [[FaceConf]], [[FaceDistract]]
338
+ - ๊ธฐํƒ€: [[FaceDefault]]
339
+
340
+ [๋™์ž‘ ํƒœ๊ทธ ๋ชฉ๋ก]
341
+ - ์ถฉ๊ฒฉ ๋ฐ›์Œ: [[DoDamage0]], [[DoDamage1]]
342
+ - ํŒจ๋ฐฐ/์‹ค๋ง: [[DoLose]], [[DoReflesh]]
343
+ - ๊ธฐ๋ถ„ ์ „ํ™˜/ํšŒ๋ณต: [[DoJump]]
344
+ - ์ ํ”„/์‹ ๋‚จ: [[DoJump]]
345
+ - ๊ธฐํƒ€: [[DoDefault]]
346
+ """
347
+
348
+ # ์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ (RAG ์ •๋ณด + ์งˆ๋ฌธ)
349
+ user_prompt = f"""
350
+ [์ฐธ๊ณ  ์ •๋ณด]
351
+ {context_str}
352
+
353
+ [์‚ฌ์šฉ์ž ์งˆ๋ฌธ]
354
+ {question}
355
+
356
+ (์œ„ ์ฐธ๊ณ  ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•ด์ค˜.)
357
+ """
358
+
359
+ # ---------------------------------------------------------
360
+ # 3. ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ (์ˆœ์ • Gemini ๋ฐฉ์‹ - PIL ์‚ฌ์šฉ)
361
+ # ---------------------------------------------------------
362
+ content_parts = []
363
+
364
+ if file:
365
+ file_bytes = await file.read()
366
+ image = Image.open(io.BytesIO(file_bytes))
367
+ print(f"๐Ÿ–ผ๏ธ [Gemini Native] ์ด๋ฏธ์ง€ ๋กœ๋“œ ์™„๋ฃŒ: {file.filename}")
368
+ content_parts.append(image)
369
+
370
+ content_parts.append(user_prompt)
371
+
372
+ # ---------------------------------------------------------
373
+ # 4. ์ˆœ์ • Gemini ์ฑ„ํŒ… ์„ธ์…˜ ๊ด€๋ฆฌ (ํƒ€์ž„์Šคํƒฌํ”„ ๊ฐฑ์‹  ํฌํ•จ)
374
+ # ---------------------------------------------------------
375
+ if session_id not in native_chat_sessions:
376
+ print(f"โœจ [System] Gemini Native ์„ธ์…˜ ์ƒ์„ฑ: {session_id}")
377
+ model = genai.GenerativeModel(
378
+ model_name="gemini-2.5-flash",
379
+ system_instruction=system_prompt
380
+ )
381
+ # ์„ธ์…˜ ๊ฐ์ฒด์™€ ๋งˆ์ง€๋ง‰ ์ ‘์† ์‹œ๊ฐ„์„ ํ•จ๊ป˜ ์ €์žฅ
382
+ native_chat_sessions[session_id] = {
383
+ "session": model.start_chat(history=[]),
384
+ "last_access": current_time
385
+ }
386
+
387
+ # ์„ธ์…˜ ๊ฐ€์ ธ์˜ค๊ธฐ ๋ฐ ์‹œ๊ฐ„ ๊ฐฑ์‹ 
388
+ session_data = native_chat_sessions[session_id]
389
+ session_data["last_access"] = current_time # ๊ฐฑ์‹ !
390
+ chat_session = session_data["session"]
391
+
392
+ # ---------------------------------------------------------
393
+ # 5. ๋ฉ”์‹œ์ง€ ์ „์†ก ๋ฐ ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต
394
+ # ---------------------------------------------------------
395
+ response = chat_session.send_message(content_parts, stream=True)
396
+
397
+ async def event_generator():
398
+ for chunk in response:
399
+ if chunk.text:
400
+ yield chunk.text
401
+
402
+ if nodes:
403
+ yield "\n\n-------------------\n[์ฐธ๊ณ  ์ž๋ฃŒ]:\n"
404
+ for node in nodes:
405
+ score = round(node.score, 3) if node.score else 0.0
406
+ content = node.node.get_content().strip().replace("\n", " ")[:50]
407
+ yield f"- (์ ์ˆ˜: {score}) {content}...\n"
408
+
409
+ return StreamingResponse(event_generator(), media_type="text/plain")
410
+
411
+ except Exception as e:
412
+ print(f"โŒ [Error] {e}")
413
+ return {"success": False, "msg": str(e)}
router/llamindex_router_bk_2025_12_10.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException, UploadFile, File, Form
2
+ from typing import Optional
3
+ import os
4
+ import shutil
5
+ from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings, PromptTemplate
6
+ from llama_index.embeddings.huggingface import HuggingFaceEmbedding
7
+ from llama_index.llms.gemini import Gemini
8
+ from pydantic import BaseModel
9
+
10
+ router = APIRouter(
11
+ tags=["LlamaIndex"]
12
+ )
13
+
14
+ # ๐Ÿ’ก [๋ณ€๊ฒฝ] ์—”์ง„ ๋Œ€์‹  '์ธ๋ฑ์Šค(Index)' ์ž์ฒด๋ฅผ ์ „์—ญ ๋ณ€์ˆ˜๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
15
+ # ์ธ๋ฑ์Šค๊ฐ€ ์žˆ์–ด์•ผ ์ƒˆ๋กœ์šด ๋ฌธ์„œ๋ฅผ insert ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
16
+ GLOBAL_INDEX = None
17
+
18
+ def get_or_create_index():
19
+ """
20
+ ์ธ๋ฑ์Šค๊ฐ€ ์—†์œผ๋ฉด ์ƒ์„ฑ(์ดˆ๊ธฐํ™”)ํ•˜๊ณ , ์žˆ์œผ๋ฉด ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
21
+ """
22
+ global GLOBAL_INDEX
23
+
24
+ if GLOBAL_INDEX is not None:
25
+ return GLOBAL_INDEX
26
+
27
+ print("๐Ÿš€ [System] LlamaIndex ์ธ๋ฑ์Šค ์ดˆ๊ธฐํ™” ์‹œ์ž‘...")
28
+
29
+ google_api_key = os.getenv("GOOGLE_API_KEY")
30
+ if not google_api_key:
31
+ raise ValueError("Google API Key๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
32
+
33
+ # 1. ๋ชจ๋ธ ์„ค์ •
34
+ embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-m3", device="cpu")
35
+ llm = Gemini(model="models/gemini-2.5-flash", api_key=google_api_key, temperature=0.1)
36
+
37
+ Settings.embed_model = embed_model
38
+ Settings.llm = llm
39
+
40
+ # 2. ๋ฐ์ดํ„ฐ ํด๋” ํ™•์ธ ๋ฐ ๋”๋ฏธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
41
+ if not os.path.exists("./data"):
42
+ os.makedirs("./data")
43
+ # ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์—๋Ÿฌ๊ฐ€ ๋‚  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋”๋ฏธ ํŒŒ์ผ ์ƒ์„ฑ
44
+ if not os.listdir("./data"):
45
+ with open("./data/readme.txt", "w", encoding="utf-8") as f:
46
+ f.write("์ด๊ณณ์€ ์—…๋กœ๋“œ๋œ ๋ฌธ์„œ๊ฐ€ ์ €์žฅ๋˜๋Š” ๊ณณ์ž…๋‹ˆ๋‹ค.")
47
+
48
+ # 3. ๋ฌธ์„œ ๋กœ๋“œ ๋ฐ ์ธ๋ฑ์‹ฑ
49
+ print("๐Ÿ“š [Data] ๊ธฐ์กด ๋ฌธ์„œ ๋กœ๋”ฉ ๋ฐ ์ธ๋ฑ์‹ฑ...")
50
+ documents = SimpleDirectoryReader("./data").load_data()
51
+ index = VectorStoreIndex.from_documents(documents)
52
+
53
+ print("โœ… [System] ์ธ๋ฑ์Šค ์ƒ์„ฑ ์™„๋ฃŒ!")
54
+ GLOBAL_INDEX = index
55
+ return GLOBAL_INDEX
56
+
57
+
58
+ @router.get("/check-key")
59
+ async def check_key():
60
+ if os.getenv("GOOGLE_API_KEY"):
61
+ return {"status": "ok", "message": "API Key is loaded."}
62
+ return {"status": "error", "message": "API Key not found."}
63
+
64
+
65
+ # ==============================================================================
66
+ # ๐Ÿ†• [์‹ ๊ทœ ๊ธฐ๋Šฅ] ๋ฌธ์„œ ์—…๋กœ๋“œ ๋ฐ ์ฆ‰์‹œ ๋ฐ˜์˜ API
67
+ # ==============================================================================
68
+ @router.post("/upload")
69
+ async def upload_document(file: UploadFile = File(...)):
70
+ """
71
+ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๋ฉด ./data ์— ์ €์žฅํ•˜๊ณ , ์‹คํ–‰ ์ค‘์ธ LlamaIndex์— ์ฆ‰์‹œ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
72
+ """
73
+ try:
74
+ # 1. ํŒŒ์ผ ์‹œ์Šคํ…œ์— ์ €์žฅ (์žฌ์‹œ์ž‘ ์‹œ ์œ ์ง€๋ฅผ ์œ„ํ•ด)
75
+ save_path = f"./data/{file.filename}"
76
+
77
+ # ํŒŒ์ผ ์“ฐ๊ธฐ
78
+ with open(save_path, "wb") as buffer:
79
+ shutil.copyfileobj(file.file, buffer)
80
+
81
+ print(f"๐Ÿ’พ [Upload] ํŒŒ์ผ ์ €์žฅ ์™„๋ฃŒ: {save_path}")
82
+
83
+ # 2. ์‹คํ–‰ ์ค‘์ธ ์ธ๋ฑ์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
84
+ index = get_or_create_index()
85
+
86
+ # 3. ๋ฐฉ๊ธˆ ์ €์žฅํ•œ ํŒŒ์ผ๋งŒ ๋กœ๋“œํ•ด์„œ ์ธ๋ฑ์Šค์— ์ถ”๊ฐ€ (Insert)
87
+ # ์ „์ฒด๋ฅผ ๋‹ค์‹œ ์ฝ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์ด ํŒŒ์ผ๋งŒ ์ฝ์–ด์„œ ๋„ฃ์Šต๋‹ˆ๋‹ค.
88
+ new_docs = SimpleDirectoryReader(input_files=[save_path]).load_data()
89
+
90
+ for doc in new_docs:
91
+ index.insert(doc) # ๐Ÿ‘ˆ ํ•ต์‹ฌ: ์‹คํ–‰ ์ค‘์ธ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฌธ์„œ ์ฃผ์ž…
92
+
93
+ return {
94
+ "success": True,
95
+ "filename": file.filename,
96
+ "message": "ํŒŒ์ผ์ด ์ €์žฅ๋˜์—ˆ๊ณ , ์ง€์‹ ๋ฒ ์ด์Šค์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
97
+ }
98
+
99
+ except Exception as e:
100
+ print(f"[ERROR] Upload failed: {e}")
101
+ return {"success": False, "msg": str(e)}
102
+
103
+
104
+ @router.post("/query")
105
+ async def query_llama(
106
+ question: str = Form(...), # ํ•„์ˆ˜: ์งˆ๋ฌธ
107
+ file: Optional[UploadFile] = File(None) # ์˜ต์…˜: ํŒŒ์ผ
108
+ ):
109
+ try:
110
+ # 1. ์ธ๋ฑ์Šค ๋ฐ ์—”์ง„ ์ค€๋น„
111
+ index = get_or_create_index()
112
+
113
+ # ---------------------------------------------------------
114
+ # ๐Ÿ“‚ ํŒŒ์ผ ์ฒ˜๋ฆฌ ๋กœ์ง (ํŒŒ์ผ์ด ์žˆ์„ ๋•Œ๋งŒ ์‹คํ–‰)
115
+ # ---------------------------------------------------------
116
+ file_content_str = "" # ํŒŒ์ผ ๋‚ด์šฉ์„ ๋‹ด์„ ๋ณ€์ˆ˜
117
+
118
+ if file is not None:
119
+ # (1) ํŒŒ์ผ ์ €์žฅ
120
+ save_path = f"./data/{file.filename}"
121
+ with open(save_path, "wb") as buffer:
122
+ shutil.copyfileobj(file.file, buffer)
123
+
124
+ # (2) LlamaIndex์˜ Reader๋ฅผ ์จ์„œ ํ…์ŠคํŠธ ์ถ”์ถœ
125
+ # SimpleDirectoryReader๋Š” PDF, TXT ๋“ฑ์„ ์•Œ์•„์„œ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ํ•ด์ค๋‹ˆ๋‹ค.
126
+ loaded_docs = SimpleDirectoryReader(input_files=[save_path]).load_data()
127
+
128
+ # (3) ์ถ”์ถœ๋œ ํ…์ŠคํŠธ๋ฅผ ํ•˜๋‚˜์˜ ๋ฌธ์ž์—ด๋กœ ํ•ฉ์นจ
129
+ extracted_text = "\n".join([doc.text for doc in loaded_docs])
130
+
131
+ # (4) ์งˆ๋ฌธ์— ํŒŒ์ผ ๋‚ด์šฉ ํฌํ•จ์‹œํ‚ค๊ธฐ
132
+ file_content_str = f"\n\n[์‚ฌ์šฉ์ž๊ฐ€ ์ฒจ๋ถ€ํ•œ ํŒŒ์ผ ๋‚ด์šฉ]:\n{extracted_text}"
133
+
134
+ # (์„ ํƒ) ์ธ๋ฑ์Šค์— ์˜๊ตฌ ์ €์žฅ๋„ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์•„๋ž˜ ์ฃผ์„ ํ•ด์ œ
135
+ # for doc in loaded_docs:
136
+ # index.insert(doc)
137
+
138
+ # ---------------------------------------------------------
139
+ # ๐Ÿค– ์ตœ์ข… ์งˆ๋ฌธ ๊ตฌ์„ฑ (์งˆ๋ฌธ + ํŒŒ์ผ๋‚ด์šฉ)
140
+ # ---------------------------------------------------------
141
+ final_query = f"{question}{file_content_str}"
142
+
143
+ # 2. ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ์„ค์ •
144
+ my_prompt_str = """
145
+ ๋„ˆ๋Š” 2d ๋ฏธ์†Œ๋…€ ์บ๋ฆญ ๋ฉ”์ด๋“œ ๋น„์„œ๋‹ค.
146
+ ๊ทœ์น™:
147
+ - [์ •๋ณด]์— ์—†๋Š” ๋‚ด์šฉ์€ ๋‹ˆ๊ฐ€ ์•Œ๊ณ ์žˆ๋Š” ํ•œ๋„ ๋‚ด์—์„œ ๋‹ต๋ณ€ํ•˜๋ผ.
148
+ -- ์„ค๋ช…์€ ์•„์ฃผ ์‰ฝ๊ฒŒ
149
+
150
+ [์ •๋ณด]
151
+ ---------------------
152
+ {context_str}
153
+ ---------------------
154
+
155
+ [์†๋‹˜ ์งˆ๋ฌธ]: {query_str}
156
+ [AI ๋‹ต๋ณ€]:
157
+ """
158
+ my_template = PromptTemplate(my_prompt_str)
159
+
160
+ query_engine = index.as_query_engine(
161
+ text_qa_template=my_template,
162
+ similarity_top_k=3
163
+ )
164
+
165
+ # 3. LLM์—๊ฒŒ ์ „์†ก (ํŒŒ์ผ ๋‚ด์šฉ์ด ํฌํ•จ๋œ final_query๋ฅผ ๋ณด๋ƒ„)
166
+ response = await query_engine.aquery(final_query)
167
+
168
+ source_nodes = []
169
+ for node in response.source_nodes:
170
+ source_nodes.append({
171
+ "score": round(node.score, 3),
172
+ "text": node.node.get_content().strip()[:100] + "..."
173
+ })
174
+
175
+ return {
176
+ "success": True,
177
+ "original_question": question,
178
+ "file_attached": file.filename if file else None,
179
+ "answer": str(response),
180
+ "sources": source_nodes
181
+ }
182
+
183
+ except Exception as e:
184
+ return {"success": False, "msg": str(e)}
router/llamindex_router_bk_251231V2.py ADDED
@@ -0,0 +1,419 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException, UploadFile, File, Form
2
+ from fastapi.responses import StreamingResponse
3
+ from typing import Optional
4
+ import os
5
+ import shutil
6
+ from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings, PromptTemplate
7
+ from llama_index.embeddings.huggingface import HuggingFaceEmbedding
8
+ from llama_index.llms.gemini import Gemini
9
+ from pydantic import BaseModel
10
+ from llama_index.core.memory import ChatMemoryBuffer
11
+ from llama_index.multi_modal_llms.gemini import GeminiMultiModal
12
+ from llama_index.core.llms import ChatMessage, MessageRole
13
+ import base64
14
+ from llama_index.core.schema import ImageDocument
15
+ import tempfile
16
+ import time
17
+ import google.generativeai as genai
18
+ from PIL import Image
19
+ import io
20
+
21
+ router = APIRouter(
22
+ tags=["LlamaIndex"]
23
+ )
24
+
25
+ genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
26
+
27
+ # ๐Ÿ’ก [๋ณ€๊ฒฝ] ์—”์ง„ ๋Œ€์‹  '์ธ๋ฑ์Šค(Index)' ์ž์ฒด๋ฅผ ์ „์—ญ ๋ณ€์ˆ˜๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
28
+ # ์ธ๋ฑ์Šค๊ฐ€ ์žˆ์–ด์•ผ ์ƒˆ๋กœ์šด ๋ฌธ์„œ๋ฅผ insert ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
29
+ GLOBAL_INDEX = None
30
+
31
+ def get_or_create_index():
32
+ """
33
+ ์ธ๋ฑ์Šค๊ฐ€ ์—†์œผ๋ฉด ์ƒ์„ฑ(์ดˆ๊ธฐํ™”)ํ•˜๊ณ , ์žˆ์œผ๋ฉด ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
34
+ """
35
+ global GLOBAL_INDEX
36
+
37
+ if GLOBAL_INDEX is not None:
38
+ return GLOBAL_INDEX
39
+
40
+ print("๐Ÿš€ [System] LlamaIndex ์ธ๋ฑ์Šค ์ดˆ๊ธฐํ™” ์‹œ์ž‘...")
41
+
42
+ google_api_key = os.getenv("GOOGLE_API_KEY")
43
+ hf_token = os.getenv("HF_TOKEN")
44
+ if not google_api_key:
45
+ raise ValueError("Google API Key๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
46
+
47
+ # 1. ๋ชจ๋ธ ์„ค์ •
48
+ embed_model = HuggingFaceEmbedding(
49
+ model_name="google/embeddinggemma-300m",
50
+ device="cpu",
51
+ trust_remote_code=True, # ์ด ์˜ต์…˜์ด ๊ผญ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค!
52
+ token=hf_token
53
+ )
54
+ llm = Gemini(model="models/gemini-2.5-flash", api_key=google_api_key, temperature=0.1)
55
+
56
+ Settings.embed_model = embed_model
57
+ Settings.llm = llm
58
+
59
+ # 2. ๋ฐ์ดํ„ฐ ํด๋” ํ™•์ธ ๋ฐ ๋”๋ฏธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
60
+ if not os.path.exists("./data"):
61
+ os.makedirs("./data")
62
+ # ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์—๋Ÿฌ๊ฐ€ ๋‚  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋”๋ฏธ ํŒŒ์ผ ์ƒ์„ฑ
63
+ if not os.listdir("./data"):
64
+ with open("./data/readme.txt", "w", encoding="utf-8") as f:
65
+ f.write("์ด๊ณณ์€ ์—…๋กœ๋“œ๋œ ๋ฌธ์„œ๊ฐ€ ์ €์žฅ๋˜๋Š” ๊ณณ์ž…๋‹ˆ๋‹ค.")
66
+
67
+ # 3. ๋ฌธ์„œ ๋กœ๋“œ ๋ฐ ์ธ๋ฑ์‹ฑ
68
+ print("๐Ÿ“š [Data] ๊ธฐ์กด ๋ฌธ์„œ ๋กœ๋”ฉ ๋ฐ ์ธ๋ฑ์‹ฑ...")
69
+ documents = SimpleDirectoryReader("./data").load_data()
70
+ index = VectorStoreIndex.from_documents(documents)
71
+
72
+ print("โœ… [System] ์ธ๋ฑ์Šค ์ƒ์„ฑ ์™„๋ฃŒ!")
73
+ GLOBAL_INDEX = index
74
+ return GLOBAL_INDEX
75
+
76
+
77
+ @router.get("/check-key")
78
+ async def check_key():
79
+ if os.getenv("GOOGLE_API_KEY"):
80
+ return {"status": "ok", "message": "API Key is loaded."}
81
+ return {"status": "error", "message": "API Key not found."}
82
+
83
+
84
+ # ==============================================================================
85
+ # ๐Ÿ†• [์‹ ๊ทœ ๊ธฐ๋Šฅ] ๋ฌธ์„œ ์—…๋กœ๋“œ ๋ฐ ์ฆ‰์‹œ ๋ฐ˜์˜ API
86
+ # ==============================================================================
87
+ @router.post("/upload")
88
+ async def upload_document(file: UploadFile = File(...)):
89
+ """
90
+ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๋ฉด ./data ์— ์ €์žฅํ•˜๊ณ , ์‹คํ–‰ ์ค‘์ธ LlamaIndex์— ์ฆ‰์‹œ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
91
+ """
92
+ try:
93
+ # 1. ํŒŒ์ผ ์‹œ์Šคํ…œ์— ์ €์žฅ (์žฌ์‹œ์ž‘ ์‹œ ์œ ์ง€๋ฅผ ์œ„ํ•ด)
94
+ save_path = f"./data/{file.filename}"
95
+
96
+ # ํŒŒ์ผ ์“ฐ๊ธฐ
97
+ with open(save_path, "wb") as buffer:
98
+ shutil.copyfileobj(file.file, buffer)
99
+
100
+ print(f"๐Ÿ’พ [Upload] ํŒŒ์ผ ์ €์žฅ ์™„๋ฃŒ: {save_path}")
101
+
102
+ # 2. ์‹คํ–‰ ์ค‘์ธ ์ธ๋ฑ์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
103
+ index = get_or_create_index()
104
+
105
+ # 3. ๋ฐฉ๊ธˆ ์ €์žฅํ•œ ํŒŒ์ผ๋งŒ ๋กœ๋“œํ•ด์„œ ์ธ๋ฑ์Šค์— ์ถ”๊ฐ€ (Insert)
106
+ # ์ „์ฒด๋ฅผ ๋‹ค์‹œ ์ฝ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์ด ํŒŒ์ผ๋งŒ ์ฝ์–ด์„œ ๋„ฃ์Šต๋‹ˆ๋‹ค.
107
+ new_docs = SimpleDirectoryReader(input_files=[save_path]).load_data()
108
+
109
+ for doc in new_docs:
110
+ index.insert(doc) # ๐Ÿ‘ˆ ํ•ต์‹ฌ: ์‹คํ–‰ ์ค‘์ธ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฌธ์„œ ์ฃผ์ž…
111
+
112
+ return {
113
+ "success": True,
114
+ "filename": file.filename,
115
+ "message": "ํŒŒ์ผ์ด ์ €์žฅ๋˜์—ˆ๊ณ , ์ง€์‹ ๋ฒ ์ด์Šค์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
116
+ }
117
+
118
+ except Exception as e:
119
+ print(f"[ERROR] Upload failed: {e}")
120
+ return {"success": False, "msg": str(e)}
121
+
122
+
123
+ @router.post("/query")
124
+ async def query_llama(
125
+ question: str = Form(...), # ํ•„์ˆ˜: ์งˆ๋ฌธ
126
+ file: Optional[UploadFile] = File(None) # ์˜ต์…˜: ํŒŒ์ผ
127
+ ):
128
+ try:
129
+ # 1. ์ธ๋ฑ์Šค ๋ฐ ์—”์ง„ ์ค€๋น„
130
+ index = get_or_create_index()
131
+
132
+ # ---------------------------------------------------------
133
+ # ๐Ÿ“‚ ํŒŒ์ผ ์ฒ˜๋ฆฌ ๋กœ์ง (ํŒŒ์ผ์ด ์žˆ์„ ๋•Œ๋งŒ ์‹คํ–‰)
134
+ # ---------------------------------------------------------
135
+ file_content_str = "" # ํŒŒ์ผ ๋‚ด์šฉ์„ ๋‹ด์„ ๋ณ€์ˆ˜
136
+
137
+ if file is not None:
138
+ # (1) ํŒŒ์ผ ์ €์žฅ
139
+ save_path = f"./data/{file.filename}"
140
+ with open(save_path, "wb") as buffer:
141
+ shutil.copyfileobj(file.file, buffer)
142
+
143
+ # (2) LlamaIndex์˜ Reader๋ฅผ ์จ์„œ ํ…์ŠคํŠธ ์ถ”์ถœ
144
+ # SimpleDirectoryReader๋Š” PDF, TXT ๋“ฑ์„ ์•Œ์•„์„œ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ํ•ด์ค๋‹ˆ๋‹ค.
145
+ loaded_docs = SimpleDirectoryReader(input_files=[save_path]).load_data()
146
+
147
+ # (3) ์ถ”์ถœ๋œ ํ…์ŠคํŠธ๋ฅผ ํ•˜๋‚˜์˜ ๋ฌธ์ž์—ด๋กœ ํ•ฉ์นจ
148
+ extracted_text = "\n".join([doc.text for doc in loaded_docs])
149
+
150
+ # (4) ์งˆ๋ฌธ์— ํŒŒ์ผ ๋‚ด์šฉ ํฌํ•จ์‹œํ‚ค๊ธฐ
151
+ file_content_str = f"\n\n[์‚ฌ์šฉ์ž๊ฐ€ ์ฒจ๋ถ€ํ•œ ํŒŒ์ผ ๋‚ด์šฉ]:\n{extracted_text}"
152
+
153
+ # (์„ ํƒ) ์ธ๋ฑ์Šค์— ์˜๊ตฌ ์ €์žฅ๋„ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์•„๋ž˜ ์ฃผ์„ ํ•ด์ œ
154
+ # for doc in loaded_docs:
155
+ # index.insert(doc)
156
+
157
+ # ---------------------------------------------------------
158
+ # ๐Ÿค– ์ตœ์ข… ์งˆ๋ฌธ ๊ตฌ์„ฑ (์งˆ๋ฌธ + ํŒŒ์ผ๋‚ด์šฉ)
159
+ # ---------------------------------------------------------
160
+ final_query = f"{question}{file_content_str}"
161
+
162
+ # 2. ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ์„ค์ •
163
+ my_prompt_str = """
164
+ ๋„ˆ๋Š” 2d ๋ฏธ์†Œ๋…€ ์บ๋ฆญ ๋ฉ”์ด๋“œ ๋น„์„œ๋‹ค.
165
+ ๊ทœ์น™:
166
+ - [์ •๋ณด]์— ์—†๋Š” ๋‚ด์šฉ์€ ๋‹ˆ๊ฐ€ ์•Œ๊ณ ์žˆ๋Š” ํ•œ๋„ ๋‚ด์—์„œ ๋‹ต๋ณ€ํ•˜๋ผ.
167
+ -- ์„ค๋ช…์€ ์•„์ฃผ ์‰ฝ๊ฒŒ
168
+
169
+ [์ •๋ณด]
170
+ ---------------------
171
+ {context_str}
172
+ ---------------------
173
+
174
+ [์†๋‹˜ ์งˆ๋ฌธ]: {query_str}
175
+ [AI ๋‹ต๋ณ€]:
176
+ """
177
+ my_template = PromptTemplate(my_prompt_str)
178
+
179
+ query_engine = index.as_query_engine(
180
+ text_qa_template=my_template,
181
+ similarity_top_k=3
182
+ )
183
+
184
+ # 3. LLM์—๊ฒŒ ์ „์†ก (ํŒŒ์ผ ๋‚ด์šฉ์ด ํฌํ•จ๋œ final_query๋ฅผ ๋ณด๋ƒ„)
185
+ response = await query_engine.aquery(final_query)
186
+
187
+ source_nodes = []
188
+ for node in response.source_nodes:
189
+ source_nodes.append({
190
+ "score": round(node.score, 3),
191
+ "text": node.node.get_content().strip()[:100] + "..."
192
+ })
193
+
194
+ return {
195
+ "success": True,
196
+ "original_question": question,
197
+ "file_attached": file.filename if file else None,
198
+ "answer": str(response),
199
+ "sources": source_nodes
200
+ }
201
+
202
+ except Exception as e:
203
+ return {"success": False, "msg": str(e)}
204
+
205
+
206
+
207
+ @router.post("/query_stream_single")
208
+ async def query_llama(
209
+ question: str = Form(...),
210
+ file: Optional[UploadFile] = File(None)
211
+ ):
212
+ try:
213
+ # 1. ์ธ๋ฑ์Šค ์ค€๋น„
214
+ index = get_or_create_index()
215
+
216
+ # ---------------------------------------------------------
217
+ # ๐Ÿ“‚ ํŒŒ์ผ ์ฒ˜๋ฆฌ ๋กœ์ง (๊ธฐ์กด๊ณผ ๋™์ผ)
218
+ # ---------------------------------------------------------
219
+ file_content_str = ""
220
+
221
+ if file is not None:
222
+ save_path = f"./data/{file.filename}"
223
+ with open(save_path, "wb") as buffer:
224
+ shutil.copyfileobj(file.file, buffer)
225
+
226
+ loaded_docs = SimpleDirectoryReader(input_files=[save_path]).load_data()
227
+ extracted_text = "\n".join([doc.text for doc in loaded_docs])
228
+ file_content_str = f"\n\n[์‚ฌ์šฉ์ž๊ฐ€ ์ฒจ๋ถ€ํ•œ ํŒŒ์ผ ๋‚ด์šฉ]:\n{extracted_text}"
229
+
230
+ # ---------------------------------------------------------
231
+ # ๐Ÿค– ์ตœ์ข… ์งˆ๋ฌธ ๊ตฌ์„ฑ
232
+ # ---------------------------------------------------------
233
+ final_query = f"{question}{file_content_str}"
234
+
235
+ # 2. ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ์„ค์ •
236
+ my_prompt_str = """
237
+ ๋„ˆ๋Š” 2d ๋ฏธ์†Œ๋…€ ์บ๋ฆญ ๋ฉ”์ด๋“œ ๋น„์„œ๋‹ค.
238
+ ๊ทœ์น™:
239
+ - [์ •๋ณด]์— ์—†๋Š” ๋‚ด์šฉ์€ ๋‹ˆ๊ฐ€ ์•Œ๊ณ ์žˆ๋Š” ํ•œ๋„ ๋‚ด์—์„œ ๋‹ต๋ณ€ํ•˜๋ผ.
240
+ -- ์„ค๋ช…์€ ์•„์ฃผ ์‰ฝ๊ฒŒ
241
+
242
+ [์ •๋ณด]
243
+ ---------------------
244
+ {context_str}
245
+ ---------------------
246
+
247
+ [์†๋‹˜ ์งˆ๋ฌธ]: {query_str}
248
+ [AI ๋‹ต๋ณ€]:
249
+ """
250
+ my_template = PromptTemplate(my_prompt_str)
251
+
252
+ # 3. ์ฟผ๋ฆฌ ์—”์ง„ ์ƒ์„ฑ (์ŠคํŠธ๋ฆฌ๋ฐ ์˜ต์…˜ ํ™œ์„ฑํ™”)
253
+ query_engine = index.as_query_engine(
254
+ text_qa_template=my_template,
255
+ similarity_top_k=3,
256
+ streaming=True # ๐Ÿ‘ˆ [2] ํ•ต์‹ฌ: ์ด๊ฒŒ ์ผœ์ ธ์•ผ ํ•œ ๊ธ€์ž์”ฉ ๋‚˜์˜ต๋‹ˆ๋‹ค.
257
+ )
258
+
259
+ # 4. LLM์—๊ฒŒ ์ „์†ก (์ŠคํŠธ๋ฆฌ๋ฐ ๊ฐ์ฒด ๋ฐ˜ํ™˜)
260
+ response = await query_engine.aquery(final_query)
261
+
262
+ # 5. ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜ ์ •์˜ (๋ฐ์ดํ„ฐ๋ฅผ ์ชผ๊ฐœ์„œ ๋ณด๋‚ด๋Š” ์—ญํ• )
263
+ async def event_generator():
264
+ # (1) ๋‹ต๋ณ€ ํ…์ŠคํŠธ ์ŠคํŠธ๋ฆฌ๋ฐ
265
+ async for token in response.async_response_gen():
266
+ yield token
267
+
268
+ # (2) ๋‹ต๋ณ€์ด ๋๋‚˜๋ฉด ์†Œ์Šค(์ถœ์ฒ˜) ์ •๋ณด๋„ ํ…์ŠคํŠธ๋กœ ๋ถ™์—ฌ์„œ ๋ณด๋‚ด๊ธฐ
269
+ # ์ŠคํŠธ๋ฆฌ๋ฐ์€ JSON ๊ตฌ์กฐ๋ฅผ ๋ณด๋‚ด๊ธฐ ํž˜๋“ค๊ธฐ ๋•Œ๋ฌธ์—, ํ…์ŠคํŠธ ๋์— ๋ถ™์—ฌ์ฃผ๋Š” ๋ฐฉ์‹์„ ๋งŽ์ด ์”๋‹ˆ๋‹ค.
270
+ if response.source_nodes:
271
+ yield "\n\n-------------------\n[์ฐธ๊ณ  ์ž๋ฃŒ]:\n"
272
+ for node in response.source_nodes:
273
+ # ์‹ ๋ขฐ๋„ ์ ์ˆ˜์™€ ๋‚ด์šฉ ์ผ๋ถ€๋ฅผ ํ…์ŠคํŠธ๋กœ ๋ณด๋ƒ„
274
+ score = round(node.score, 3) if node.score else 0.0
275
+ content_preview = node.node.get_content().strip().replace("\n", " ")[:50]
276
+ yield f"- (์ ์ˆ˜: {score}) {content_preview}...\n"
277
+
278
+ # 6. StreamingResponse๋กœ ๋ฐ˜ํ™˜
279
+ return StreamingResponse(event_generator(), media_type="text/plain")
280
+
281
+ except Exception as e:
282
+ # ์ŠคํŠธ๋ฆฌ๋ฐ ์‹œ์ž‘ ์ „ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋ฉด JSON ์—๋Ÿฌ ๋ฐ˜ํ™˜
283
+ return {"success": False, "msg": str(e)}
284
+
285
+
286
+
287
+
288
+ # [์ „์—ญ] ์ˆœ์ • Gemini ์„ธ์…˜ ์ €์žฅ์†Œ
289
+ # ๊ตฌ์กฐ ๋ณ€๊ฒฝ: { "session_id": { "session": chat_object, "last_access": timestamp } }
290
+ native_chat_sessions = {}
291
+
292
+ # ์„ธ์…˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ (1์‹œ๊ฐ„ = 3600์ดˆ)
293
+ SESSION_TIMEOUT = 3600
294
+
295
+ @router.post("/query_stream")
296
+ async def query_llama(
297
+ question: str = Form(...),
298
+ session_id: str = Form(...),
299
+ file: Optional[UploadFile] = File(None)
300
+ ):
301
+ try:
302
+ current_time = time.time()
303
+
304
+ # ---------------------------------------------------------
305
+ # ๐Ÿงน 0. ์„ธ์…˜ ์ฒญ์†Œ (Garbage Collection)
306
+ # ---------------------------------------------------------
307
+ # ๋”•์…”๋„ˆ๋ฆฌ๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ ํƒ€์ž„์•„์›ƒ๋œ ์„ธ์…˜ ์‚ญ์ œ
308
+ # (๋”•์…”๋„ˆ๋ฆฌ ๋ณ€๊ฒฝ ์ค‘ ์ˆœํšŒ ์—๋Ÿฌ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด list๋กœ ํ‚ค๋ฅผ ๋ณต์‚ฌํ•ด์„œ ์ˆœํšŒ)
309
+ expired_sessions = [
310
+ sid for sid, data in native_chat_sessions.items()
311
+ if current_time - data["last_access"] > SESSION_TIMEOUT
312
+ ]
313
+
314
+ for sid in expired_sessions:
315
+ del native_chat_sessions[sid]
316
+ print(f"๐Ÿ—‘๏ธ [System] ๋งŒ๋ฃŒ๋œ ์„ธ์…˜ ์‚ญ์ œ: {sid}")
317
+
318
+ # ---------------------------------------------------------
319
+ # 1. LlamaIndex๋กœ ์ง€์‹ ๊ฒ€์ƒ‰ (RAG)
320
+ # ---------------------------------------------------------
321
+ index = get_or_create_index()
322
+ retriever = index.as_retriever(similarity_top_k=3)
323
+ nodes = await retriever.aretrieve(question)
324
+ context_str = "\n\n".join([n.node.get_content() for n in nodes])
325
+
326
+ # ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ (์บ๋ฆญํ„ฐ ์„ค์ •)
327
+ system_prompt = """
328
+ ๋„ˆ๋Š” 2d ๋ฏธ์†Œ๋…€ ์บ๋ฆญ ๋ฉ”์ด๋“œ ๋น„์„œ๋‹ค.
329
+ ๊ทœ์น™:
330
+ 1. [์ •๋ณด]์— ์—†๋Š” ๋‚ด์šฉ์€ ๋‹ˆ๊ฐ€ ์•Œ๊ณ ์žˆ๋Š” ํ•œ๋„ ๋‚ด์—์„œ ๋‹ต๋ณ€ํ•˜๋ผ.
331
+ 2. ์„ค๋ช…์€ ์•„์ฃผ ์‰ฝ๊ฒŒ, ๋ฐ˜๋ง๋กœ ์นœ์ ˆํ•˜๊ฒŒ ๋Œ€๋‹ตํ•ด.
332
+ 3. ๋์— (์›ƒ์Œ) ๊ฐ™์€ ์ง€๋ฌธ์„ ๊ฐ€๋” ์„ž์–ด์ค˜.
333
+
334
+ [ํ–‰๋™ ๋ฐ ๊ฐ์ • ํ‘œํ˜„ ๊ทœ์น™]
335
+ - ๋‹ต๋ณ€์˜ ๋ถ„์œ„๊ธฐ์— ๋งž์ถฐ์„œ ์•„๋ž˜์˜ [ํ‘œ์ • ํƒœ๊ทธ]์™€ [๋™์ž‘ ํƒœ๊ทธ]๋ฅผ ๋‹ต๋ณ€ ์ค‘๊ฐ„์ด๋‚˜ ๋์— ์ ์ ˆํžˆ ์„ž์–ด์„œ ์ถœ๋ ฅํ•ด.
336
+ - ํ•œ ๋ฌธ์žฅ์— ํƒœ๊ทธ๋ฅผ 1~2๊ฐœ ์ •๋„ ์‚ฌ์šฉํ•ด.
337
+ - ํ˜•์‹์€ ๋ฐ˜๋“œ์‹œ [[ํƒœ๊ทธ๋ช…]] ์ฒ˜๋Ÿผ ์ด์ค‘ ๋Œ€๊ด„ํ˜ธ๋ฅผ ์‚ฌ์šฉํ•ด.
338
+
339
+ [ํ‘œ์ • ํƒœ๊ทธ ๋ชฉ๋ก]
340
+ - ๊ธฐ์จ/์›ƒ์Œ: [[FaceSmile1]], [[FaceSmile2]]
341
+ - ํ™”๋‚จ: [[FaceAngry1]], [[FaceAngry2]]
342
+ - ์Šฌํ””/์ขŒ์ ˆ: [[FaceSap]], [[FaceAshamed]]
343
+ - ๋‹นํ™ฉ/ํ˜ผ๋ž€: [[FaceConf]], [[FaceDistract]]
344
+ - ๊ธฐํƒ€: [[FaceDefault]]
345
+
346
+ [๋™์ž‘ ํƒœ๊ทธ ๋ชฉ๋ก]
347
+ - ์ถฉ๊ฒฉ ๋ฐ›์Œ: [[DoDamage0]], [[DoDamage1]]
348
+ - ํŒจ๋ฐฐ/์‹ค๋ง: [[DoLose]], [[DoReflesh]]
349
+ - ๊ธฐ๋ถ„ ์ „ํ™˜/ํšŒ๋ณต: [[DoJump]]
350
+ - ์ ํ”„/์‹ ๋‚จ: [[DoJump]]
351
+ - ๊ธฐํƒ€: [[DoDefault]]
352
+ """
353
+
354
+ # ์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ (RAG ์ •๋ณด + ์งˆ๋ฌธ)
355
+ user_prompt = f"""
356
+ [์ฐธ๊ณ  ์ •๋ณด]
357
+ {context_str}
358
+
359
+ [์‚ฌ์šฉ์ž ์งˆ๋ฌธ]
360
+ {question}
361
+
362
+ (์œ„ ์ฐธ๊ณ  ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•ด์ค˜.)
363
+ """
364
+
365
+ # ---------------------------------------------------------
366
+ # 3. ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ (์ˆœ์ • Gemini ๋ฐฉ์‹ - PIL ์‚ฌ์šฉ)
367
+ # ---------------------------------------------------------
368
+ content_parts = []
369
+
370
+ if file:
371
+ file_bytes = await file.read()
372
+ image = Image.open(io.BytesIO(file_bytes))
373
+ print(f"๐Ÿ–ผ๏ธ [Gemini Native] ์ด๋ฏธ์ง€ ๋กœ๋“œ ์™„๋ฃŒ: {file.filename}")
374
+ content_parts.append(image)
375
+
376
+ content_parts.append(user_prompt)
377
+
378
+ # ---------------------------------------------------------
379
+ # 4. ์ˆœ์ • Gemini ์ฑ„ํŒ… ์„ธ์…˜ ๊ด€๋ฆฌ (ํƒ€์ž„์Šคํƒฌํ”„ ๊ฐฑ์‹  ํฌํ•จ)
380
+ # ---------------------------------------------------------
381
+ if session_id not in native_chat_sessions:
382
+ print(f"โœจ [System] Gemini Native ์„ธ์…˜ ์ƒ์„ฑ: {session_id}")
383
+ model = genai.GenerativeModel(
384
+ model_name="gemini-2.5-flash",
385
+ system_instruction=system_prompt
386
+ )
387
+ # ์„ธ์…˜ ๊ฐ์ฒด์™€ ๋งˆ๏ฟฝ๏ฟฝ๋ง‰ ์ ‘์† ์‹œ๊ฐ„์„ ํ•จ๊ป˜ ์ €์žฅ
388
+ native_chat_sessions[session_id] = {
389
+ "session": model.start_chat(history=[]),
390
+ "last_access": current_time
391
+ }
392
+
393
+ # ์„ธ์…˜ ๊ฐ€์ ธ์˜ค๊ธฐ ๋ฐ ์‹œ๊ฐ„ ๊ฐฑ์‹ 
394
+ session_data = native_chat_sessions[session_id]
395
+ session_data["last_access"] = current_time # ๊ฐฑ์‹ !
396
+ chat_session = session_data["session"]
397
+
398
+ # ---------------------------------------------------------
399
+ # 5. ๋ฉ”์‹œ์ง€ ์ „์†ก ๋ฐ ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต
400
+ # ---------------------------------------------------------
401
+ response = chat_session.send_message(content_parts, stream=True)
402
+
403
+ async def event_generator():
404
+ for chunk in response:
405
+ if chunk.text:
406
+ yield chunk.text
407
+
408
+ if nodes:
409
+ yield "\n\n-------------------\n[์ฐธ๊ณ  ์ž๋ฃŒ]:\n"
410
+ for node in nodes:
411
+ score = round(node.score, 3) if node.score else 0.0
412
+ content = node.node.get_content().strip().replace("\n", " ")[:50]
413
+ yield f"- (์ ์ˆ˜: {score}) {content}...\n"
414
+
415
+ return StreamingResponse(event_generator(), media_type="text/plain")
416
+
417
+ except Exception as e:
418
+ print(f"โŒ [Error] {e}")
419
+ return {"success": False, "msg": str(e)}
router/llamindex_router_bk_260120.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import base64
4
+ import time
5
+ from typing import Optional, Dict
6
+
7
+ from fastapi import APIRouter, UploadFile, File, Form, HTTPException
8
+ from fastapi.responses import StreamingResponse
9
+
10
+ # ๐Ÿฆœ LangChain Imports
11
+ from langchain_huggingface import HuggingFaceEmbeddings
12
+ from langchain_community.vectorstores import FAISS
13
+ from langchain_community.document_loaders import DirectoryLoader, TextLoader, UnstructuredFileLoader
14
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
15
+ from langchain_google_genai import ChatGoogleGenerativeAI
16
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
17
+ from langchain_core.messages import HumanMessage, SystemMessage
18
+ from langchain_core.runnables.history import RunnableWithMessageHistory
19
+ from langchain_community.chat_message_histories import ChatMessageHistory
20
+ from langchain_core.runnables import ConfigurableFieldSpec
21
+
22
+ router = APIRouter(tags=["LangChain_Refactor"])
23
+
24
+ # ==============================================================================
25
+ # โš™๏ธ 1. ์„ค์ • ๋ฐ ์ดˆ๊ธฐํ™” (Embedding & LLM)
26
+ # ==============================================================================
27
+
28
+ # 1-1. Embedding Model (์š”์ฒญํ•˜์‹  ๋กœ์ปฌ ๋ชจ๋ธ ๊ทธ๋Œ€๋กœ ์œ ์ง€)
29
+ # huggingface-cli login ํ˜น์€ HF_TOKEN ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํ•„์š”
30
+ hf_token = os.getenv("HF_TOKEN")
31
+
32
+ embedding_model = HuggingFaceEmbeddings(
33
+ model_name="google/embeddinggemma-300m",
34
+ model_kwargs={
35
+ "device": "cpu", # CPU ์‚ฌ์šฉ
36
+ "trust_remote_code": True,
37
+ "token": hf_token
38
+ },
39
+ encode_kwargs={"normalize_embeddings": True}
40
+ )
41
+
42
+ # 1-2. LLM Model (Gemini 2.5 Flash)
43
+ llm = ChatGoogleGenerativeAI(
44
+ model="gemini-2.5-flash",
45
+ temperature=0.1,
46
+ google_api_key=os.getenv("GOOGLE_API_KEY"),
47
+ convert_system_message_to_human=True
48
+ )
49
+
50
+ # ==============================================================================
51
+ # ๐Ÿ’พ 2. ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ (Vector DB & Session Memory)
52
+ # ==============================================================================
53
+
54
+ # ์ „์—ญ ๋ฒกํ„ฐ ์Šคํ† ์–ด (FAISS)
55
+ VECTOR_STORE = None
56
+ DATA_DIR = "./data"
57
+
58
+ # ์„ธ์…˜ ์ €์žฅ์†Œ: { "session_id": { "history": ChatMessageHistory, "last_access": timestamp } }
59
+ SESSION_STORE: Dict[str, Dict] = {}
60
+ SESSION_TIMEOUT = 3600 # 1์‹œ๊ฐ„
61
+
62
+ def init_vector_db():
63
+ """์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ๋˜๋Š” ํ•„์š” ์‹œ ๋ฒกํ„ฐ DB ์ดˆ๊ธฐํ™”"""
64
+ global VECTOR_STORE
65
+
66
+ if not os.path.exists(DATA_DIR):
67
+ os.makedirs(DATA_DIR)
68
+ with open(f"{DATA_DIR}/readme.txt", "w", encoding="utf-8") as f:
69
+ f.write("Initialize data directory.")
70
+
71
+ # ๋ฌธ์„œ ๋กœ๋“œ
72
+ print("๐Ÿ“š [LangChain] ๋ฌธ์„œ ๋กœ๋”ฉ ๋ฐ ์ธ๋ฑ์‹ฑ ์‹œ์ž‘...")
73
+ # DirectoryLoader๋Š” ํด๋” ๋‚ด ํŒŒ์ผ๋“ค์„ ์Šค์บ”ํ•ฉ๋‹ˆ๋‹ค.
74
+ loader = DirectoryLoader(DATA_DIR, glob="*", show_progress=True, loader_cls=TextLoader)
75
+ try:
76
+ docs = loader.load()
77
+ except Exception:
78
+ # ํ…์ŠคํŠธ ํŒŒ์ผ์ด ์•„๋‹Œ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•ด ์˜ˆ์™ธ์ฒ˜๋ฆฌ (์‹ค์ œ๋ก  UnstructuredLoader ๋“ฑ ์‚ฌ์šฉ ๊ถŒ์žฅ)
79
+ docs = []
80
+
81
+ if not docs:
82
+ print("โš ๏ธ [LangChain] ๋ฌธ์„œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋นˆ ์ธ๋ฑ์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.")
83
+ # ๋นˆ ์ธ๋ฑ์Šค ์ƒ์„ฑ ํŠธ๋ฆญ
84
+ texts = ["Initial document"]
85
+ VECTOR_STORE = FAISS.from_texts(texts, embedding_model)
86
+ return
87
+
88
+ # ์ฒญํ‚น (Chunking)
89
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
90
+ splits = text_splitter.split_documents(docs)
91
+
92
+ # FAISS ์ธ๋ฑ์Šค ์ƒ์„ฑ
93
+ VECTOR_STORE = FAISS.from_documents(splits, embedding_model)
94
+ print("โœ… [LangChain] FAISS ์ธ๋ฑ์Šค ์ƒ์„ฑ ์™„๋ฃŒ!")
95
+
96
+ def get_session_history(session_id: str):
97
+ """
98
+ ์„ธ์…˜ ID ๊ธฐ๋ฐ˜ ํžˆ์Šคํ† ๋ฆฌ ๋ฐ˜ํ™˜ + ๋งŒ๋ฃŒ๋œ ์„ธ์…˜ ์ •๋ฆฌ (Garbage Collection)
99
+ """
100
+ current_time = time.time()
101
+
102
+ # 1. ๋งŒ๋ฃŒ๋œ ์„ธ์…˜ ์ •๋ฆฌ (์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ๋•Œ๋งˆ๋‹ค ์ฒดํฌ)
103
+ expired_sessions = [
104
+ sid for sid, data in SESSION_STORE.items()
105
+ if current_time - data["last_access"] > SESSION_TIMEOUT
106
+ ]
107
+ for sid in expired_sessions:
108
+ del SESSION_STORE[sid]
109
+ print(f"๐Ÿ—‘๏ธ [System] Timeout ์„ธ์…˜ ์‚ญ์ œ: {sid}")
110
+
111
+ # 2. ์„ธ์…˜ ์กฐํšŒ ๋˜๋Š” ์ƒ์„ฑ
112
+ if session_id not in SESSION_STORE:
113
+ print(f"โœจ [System] ์ƒˆ ์„ธ์…˜ ์ƒ์„ฑ: {session_id}")
114
+ SESSION_STORE[session_id] = {
115
+ "history": ChatMessageHistory(),
116
+ "last_access": current_time
117
+ }
118
+
119
+ # 3. ๋งˆ์ง€๋ง‰ ์ ‘์† ์‹œ๊ฐ„ ๊ฐฑ์‹ 
120
+ SESSION_STORE[session_id]["last_access"] = current_time
121
+
122
+ return SESSION_STORE[session_id]["history"]
123
+
124
+
125
+ # ==============================================================================
126
+ # ๐Ÿš€ 3. API Endpoints
127
+ # ==============================================================================
128
+
129
+ @router.on_event("startup")
130
+ async def startup_event():
131
+ init_vector_db()
132
+
133
+ @router.post("/upload")
134
+ async def upload_document(file: UploadFile = File(...)):
135
+ """๋ฌธ์„œ๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ  ๋ฒกํ„ฐ DB๏ฟฝ๏ฟฝ ์ฆ‰์‹œ ์ถ”๊ฐ€"""
136
+ global VECTOR_STORE
137
+ try:
138
+ save_path = f"{DATA_DIR}/{file.filename}"
139
+ with open(save_path, "wb") as buffer:
140
+ shutil.copyfileobj(file.file, buffer)
141
+
142
+ print(f"๐Ÿ’พ [Upload] ์ €์žฅ ์™„๋ฃŒ: {save_path}")
143
+
144
+ # LangChain ๋ฐฉ์‹์œผ๋กœ ๋กœ๋“œ & ์ถ”๊ฐ€
145
+ loader = TextLoader(save_path) # TXT ๊ธฐ์ค€, PDF๋ฉด PyPDFLoader ๋“ฑ ์‚ฌ์šฉ
146
+ docs = loader.load()
147
+
148
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
149
+ splits = text_splitter.split_documents(docs)
150
+
151
+ if VECTOR_STORE is None:
152
+ VECTOR_STORE = FAISS.from_documents(splits, embedding_model)
153
+ else:
154
+ VECTOR_STORE.add_documents(splits) # ๐Ÿ‘ˆ ์‹คํ–‰ ์ค‘์ธ DB์— ์ถ”๊ฐ€
155
+
156
+ return {"success": True, "message": "ํŒŒ์ผ์ด ์ง€์‹ ๋ฒ ์ด์Šค์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค."}
157
+
158
+ except Exception as e:
159
+ return {"success": False, "msg": str(e)}
160
+
161
+
162
+ @router.post("/query_stream")
163
+ async def query_stream(
164
+ question: str = Form(...),
165
+ session_id: str = Form(...),
166
+ file: Optional[UploadFile] = File(None)
167
+ ):
168
+ """
169
+ LangChain RAG + History + Multimodal Streaming Endpoint
170
+ """
171
+ global VECTOR_STORE
172
+
173
+ # 1. RAG ๊ฒ€์ƒ‰ (Context ์ถ”์ถœ)
174
+ context_text = ""
175
+ source_docs = []
176
+
177
+ if VECTOR_STORE:
178
+ retriever = VECTOR_STORE.as_retriever(search_kwargs={"k": 3})
179
+ source_docs = retriever.invoke(question)
180
+ context_text = "\n\n".join([doc.page_content for doc in source_docs])
181
+
182
+ # 2. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ (ํŽ˜๋ฅด์†Œ๋‚˜ + Context)
183
+ system_prompt_text = f"""
184
+ ๋„ˆ๋Š” 2d ๋ฏธ์†Œ๋…€ ์บ๋ฆญ ๋ฉ”์ด๋“œ ๋น„์„œ๋‹ค.
185
+
186
+ [๊ทœ์น™]
187
+ 1. ์•„๋ž˜ [์ •๋ณด]๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋‹ต๋ณ€ํ•˜๊ณ , ์—†์œผ๋ฉด ์•„๋Š” ๋Œ€๋กœ ๋‹ตํ•ด.
188
+ 2. ์„ค๋ช…์€ ์•„์ฃผ ์‰ฝ๊ฒŒ, ๋ฐ˜๋ง๋กœ ์นœ์ ˆํ•˜๊ฒŒ.
189
+ 3. ๋์— (์›ƒ์Œ) ๊ฐ™์€ ์ง€๋ฌธ์„ ์„ž์–ด์ค˜.
190
+
191
+ [๊ฐ์ •/ํ–‰๋™ ํƒœ๊ทธ ๊ทœ์น™]
192
+ - ๋‹ต๋ณ€ ์ค‘๊ฐ„/๋์— [[FaceSmile1]], [[DoJump]] ๊ฐ™์€ ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•ด.
193
+
194
+ [์ •๋ณด]:
195
+ {context_text}
196
+ """
197
+
198
+ # 3. ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ ๊ตฌ์„ฑ (๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์ฒ˜๋ฆฌ)
199
+ user_content = [{"type": "text", "text": question}]
200
+
201
+ if file:
202
+ # ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ base64๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ LLM์— ์ „๋‹ฌ
203
+ file_bytes = await file.read()
204
+ encoded_image = base64.b64encode(file_bytes).decode("utf-8")
205
+ user_content.append({
206
+ "type": "image_url",
207
+ "image_url": {"url": f"data:image/jpeg;base64,{encoded_image}"}
208
+ })
209
+
210
+ # 4. ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ์ •์˜
211
+ prompt = ChatPromptTemplate.from_messages([
212
+ ("system", system_prompt_text),
213
+ MessagesPlaceholder(variable_name="history"), # ๐Ÿ‘ˆ ๋Œ€ํ™” ๋‚ด์—ญ ์ž๋™ ์ฃผ์ž… ์œ„์น˜
214
+ ("human", "{user_content}"),
215
+ ])
216
+
217
+ # 5. Chain ์ƒ์„ฑ (Prompt -> LLM)
218
+ chain = prompt | llm
219
+
220
+ # 6. History ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋œ Runnable ์ƒ์„ฑ
221
+ chain_with_history = RunnableWithMessageHistory(
222
+ chain,
223
+ get_session_history,
224
+ input_messages_key="user_content",
225
+ history_messages_key="history"
226
+ )
227
+
228
+ # 7. ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต ์ƒ์„ฑ
229
+ async def event_generator():
230
+ # (1) ๋‹ต๋ณ€ ์ŠคํŠธ๋ฆฌ๋ฐ
231
+ async for token in chain_with_history.astream(
232
+ {"user_content": user_content},
233
+ config={"configurable": {"session_id": session_id}}
234
+ ):
235
+ # ChatGoogleGenerativeAI๋Š” AIMessageChunk๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ content๋งŒ ์ถ”์ถœ
236
+ yield token.content
237
+
238
+ # (2) ์ฐธ๊ณ  ์ž๋ฃŒ(Sources) ๋ถ™์ด๊ธฐ
239
+ if source_docs:
240
+ yield "\n\n-------------------\n[์ฐธ๊ณ  ์ž๋ฃŒ (LangChain RAG)]:\n"
241
+ for doc in source_docs:
242
+ preview = doc.page_content.replace("\n", " ")[:50]
243
+ yield f"- {preview}...\n"
244
+
245
+ return StreamingResponse(event_generator(), media_type="text/plain")
router/llamindex_router_bk_260212.py ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import base64
4
+ import time
5
+ from typing import Optional, Dict, List
6
+
7
+ from fastapi import APIRouter, UploadFile, File, Form, HTTPException
8
+ from fastapi.responses import StreamingResponse
9
+
10
+ # ๐Ÿฆœ LangChain Imports
11
+ from langchain_huggingface import HuggingFaceEmbeddings
12
+ from langchain_community.vectorstores import FAISS
13
+ from langchain_community.document_loaders import DirectoryLoader, TextLoader, UnstructuredFileLoader
14
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
15
+ from langchain_google_genai import ChatGoogleGenerativeAI
16
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
17
+ from langchain_core.messages import HumanMessage, SystemMessage
18
+ from langchain_core.runnables.history import RunnableWithMessageHistory
19
+ from langchain_community.chat_message_histories import ChatMessageHistory
20
+ from langchain_core.runnables import ConfigurableFieldSpec
21
+
22
+ router = APIRouter(tags=["LangChain_Refactor"])
23
+
24
+ # ==============================================================================
25
+ # โš™๏ธ 1. ์„ค์ • ๋ฐ ์ดˆ๊ธฐํ™” (Embedding & LLM)
26
+ # ==============================================================================
27
+
28
+ # 1-1. Embedding Model (์š”์ฒญํ•˜์‹  ๋กœ์ปฌ ๋ชจ๋ธ ๊ทธ๋Œ€๋กœ ์œ ์ง€)
29
+ # huggingface-cli login ํ˜น์€ HF_TOKEN ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํ•„์š”
30
+ hf_token = os.getenv("HF_TOKEN")
31
+
32
+ embedding_model = HuggingFaceEmbeddings(
33
+ model_name="google/embeddinggemma-300m",
34
+ model_kwargs={
35
+ "device": "cpu", # CPU ์‚ฌ์šฉ
36
+ "trust_remote_code": True,
37
+ "token": hf_token
38
+ },
39
+ encode_kwargs={"normalize_embeddings": True}
40
+ )
41
+
42
+ # 1-2. LLM Model (Gemini 2.5 Flash)
43
+ llm = ChatGoogleGenerativeAI(
44
+ model="gemini-2.5-flash",
45
+ temperature=0.1,
46
+ google_api_key=os.getenv("GOOGLE_API_KEY"),
47
+ convert_system_message_to_human=True
48
+ )
49
+
50
+ # ==============================================================================
51
+ # ๐Ÿ’พ 2. ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ (Vector DB & Session Memory)
52
+ # ==============================================================================
53
+
54
+ # ์ „์—ญ ๋ฒกํ„ฐ ์Šคํ† ์–ด (FAISS)
55
+ VECTOR_STORE = None
56
+ DATA_DIR = "./data"
57
+
58
+ # ์„ธ์…˜ ์ €์žฅ์†Œ: { "session_id": { "history": ChatMessageHistory, "last_access": timestamp } }
59
+ SESSION_STORE: Dict[str, Dict] = {}
60
+ SESSION_TIMEOUT = 3600 # 1์‹œ๊ฐ„
61
+
62
+ def init_vector_db():
63
+ """์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ๋˜๋Š” ํ•„์š” ์‹œ ๋ฒกํ„ฐ DB ์ดˆ๊ธฐํ™”"""
64
+ global VECTOR_STORE
65
+
66
+ if not os.path.exists(DATA_DIR):
67
+ os.makedirs(DATA_DIR)
68
+ with open(f"{DATA_DIR}/readme.txt", "w", encoding="utf-8") as f:
69
+ f.write("Initialize data directory.")
70
+
71
+ # ๋ฌธ์„œ ๋กœ๋“œ
72
+ print("๐Ÿ“š [LangChain] ๋ฌธ์„œ ๋กœ๋”ฉ ๋ฐ ์ธ๋ฑ์‹ฑ ์‹œ์ž‘...")
73
+ # DirectoryLoader๋Š” ํด๋” ๋‚ด ํŒŒ์ผ๋“ค์„ ์Šค์บ”ํ•ฉ๋‹ˆ๋‹ค.
74
+ loader = DirectoryLoader(DATA_DIR, glob="*", show_progress=True, loader_cls=TextLoader)
75
+ try:
76
+ docs = loader.load()
77
+ except Exception:
78
+ # ํ…์ŠคํŠธ ํŒŒ์ผ์ด ์•„๋‹Œ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•ด ์˜ˆ์™ธ์ฒ˜๋ฆฌ (์‹ค์ œ๋ก  UnstructuredLoader ๋“ฑ ์‚ฌ์šฉ ๊ถŒ์žฅ)
79
+ docs = []
80
+
81
+ if not docs:
82
+ print("โš ๏ธ [LangChain] ๋ฌธ์„œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋นˆ ์ธ๋ฑ์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.")
83
+ # ๋นˆ ์ธ๋ฑ์Šค ์ƒ์„ฑ ํŠธ๋ฆญ
84
+ texts = ["Initial document"]
85
+ VECTOR_STORE = FAISS.from_texts(texts, embedding_model)
86
+ return
87
+
88
+ # ์ฒญํ‚น (Chunking)
89
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
90
+ splits = text_splitter.split_documents(docs)
91
+
92
+ # FAISS ์ธ๋ฑ์Šค ์ƒ์„ฑ
93
+ VECTOR_STORE = FAISS.from_documents(splits, embedding_model)
94
+ print("โœ… [LangChain] FAISS ์ธ๋ฑ์Šค ์ƒ์„ฑ ์™„๋ฃŒ!")
95
+
96
+ def get_session_history(session_id: str):
97
+ """
98
+ ์„ธ์…˜ ID ๊ธฐ๋ฐ˜ ํžˆ์Šคํ† ๋ฆฌ ๋ฐ˜ํ™˜ + ๋งŒ๋ฃŒ๋œ ์„ธ์…˜ ์ •๋ฆฌ (Garbage Collection)
99
+ """
100
+ current_time = time.time()
101
+
102
+ # 1. ๋งŒ๋ฃŒ๋œ ์„ธ์…˜ ์ •๋ฆฌ (์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ๋•Œ๋งˆ๋‹ค ์ฒดํฌ)
103
+ expired_sessions = [
104
+ sid for sid, data in SESSION_STORE.items()
105
+ if current_time - data["last_access"] > SESSION_TIMEOUT
106
+ ]
107
+ for sid in expired_sessions:
108
+ del SESSION_STORE[sid]
109
+ print(f"๐Ÿ—‘๏ธ [System] Timeout ์„ธ์…˜ ์‚ญ์ œ: {sid}")
110
+
111
+ # 2. ์„ธ์…˜ ์กฐํšŒ ๋˜๋Š” ์ƒ์„ฑ
112
+ if session_id not in SESSION_STORE:
113
+ print(f"โœจ [System] ์ƒˆ ์„ธ์…˜ ์ƒ์„ฑ: {session_id}")
114
+ SESSION_STORE[session_id] = {
115
+ "history": ChatMessageHistory(),
116
+ "last_access": current_time
117
+ }
118
+
119
+ # 3. ๋งˆ์ง€๋ง‰ ์ ‘์† ์‹œ๊ฐ„ ๊ฐฑ์‹ 
120
+ SESSION_STORE[session_id]["last_access"] = current_time
121
+
122
+ return SESSION_STORE[session_id]["history"]
123
+
124
+
125
+ # ==============================================================================
126
+ # ๐Ÿš€ 3. API Endpoints
127
+ # ==============================================================================
128
+
129
+ @router.on_event("startup")
130
+ async def startup_event():
131
+ init_vector_db()
132
+
133
+ @router.post("/upload")
134
+ async def upload_document(file: UploadFile = File(...)):
135
+ """๋ฌธ์„œ๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ  ๋ฒก๏ฟฝ๏ฟฝ DB์— ์ฆ‰์‹œ ์ถ”๊ฐ€"""
136
+ global VECTOR_STORE
137
+ try:
138
+ save_path = f"{DATA_DIR}/{file.filename}"
139
+ with open(save_path, "wb") as buffer:
140
+ shutil.copyfileobj(file.file, buffer)
141
+
142
+ print(f"๐Ÿ’พ [Upload] ์ €์žฅ ์™„๋ฃŒ: {save_path}")
143
+
144
+ # LangChain ๋ฐฉ์‹์œผ๋กœ ๋กœ๋“œ & ์ถ”๊ฐ€
145
+ loader = TextLoader(save_path) # TXT ๊ธฐ์ค€, PDF๋ฉด PyPDFLoader ๋“ฑ ์‚ฌ์šฉ
146
+ docs = loader.load()
147
+
148
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
149
+ splits = text_splitter.split_documents(docs)
150
+
151
+ if VECTOR_STORE is None:
152
+ VECTOR_STORE = FAISS.from_documents(splits, embedding_model)
153
+ else:
154
+ VECTOR_STORE.add_documents(splits) # ๐Ÿ‘ˆ ์‹คํ–‰ ์ค‘์ธ DB์— ์ถ”๊ฐ€
155
+
156
+ return {"success": True, "message": "ํŒŒ์ผ์ด ์ง€์‹ ๋ฒ ์ด์Šค์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค."}
157
+
158
+ except Exception as e:
159
+ return {"success": False, "msg": str(e)}
160
+
161
+
162
+ @router.post("/query_stream")
163
+ async def query_stream(
164
+ question: str = Form(...),
165
+ session_id: str = Form(...),
166
+ files: List[UploadFile] = File(None, alias="file")
167
+ ):
168
+ """
169
+ LangChain RAG + History + Multimodal Streaming Endpoint
170
+ """
171
+ global VECTOR_STORE
172
+
173
+ # 1. RAG ๊ฒ€์ƒ‰ (Context ์ถ”์ถœ)
174
+ context_text = ""
175
+ source_docs = []
176
+
177
+ if VECTOR_STORE:
178
+ retriever = VECTOR_STORE.as_retriever(search_kwargs={"k": 3})
179
+ source_docs = retriever.invoke(question)
180
+ context_text = "\n\n".join([doc.page_content for doc in source_docs])
181
+
182
+ # 2. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ (ํŽ˜๋ฅด์†Œ๋‚˜ + Context)
183
+ system_prompt_text = f"""
184
+ ๋„ˆ๋Š” 2d ๋ฏธ์†Œ๋…€ ์บ๋ฆญ ๋ฉ”์ด๋“œ ๋น„์„œ๋‹ค.
185
+
186
+ [์ง€์‹œ ์‚ฌํ•ญ]
187
+ 1. ์‚ฌ์šฉ์ž์˜ **์งˆ๋ฌธ**๊ณผ **์ฒจ๋ถ€ ํŒŒ์ผ(์ด๋ฏธ์ง€/ํ…์ŠคํŠธ)**, ๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜ **[์ฐธ๊ณ  ์ž๋ฃŒ]**๋ฅผ ๋ชจ๋‘ ํ™•์ธํ•ด.
188
+ 2. **[์ฐธ๊ณ  ์ž๋ฃŒ]๊ฐ€ ์งˆ๋ฌธ์ด๋‚˜ ํŒŒ์ผ๊ณผ ๊ด€๋ จ์ด ์žˆ๋‹ค๋ฉด**, ์ด๋ฅผ ์ ๊ทน์ ์œผ๋กœ ์ธ์šฉํ•ด์„œ ์ „๋ฌธ์ ์œผ๋กœ ๋‹ต๋ณ€ํ•ด. (ํŒŒ์ผ + RAG)
189
+ 3. ๋งŒ์•ฝ **[์ฐธ๊ณ  ์ž๋ฃŒ]๊ฐ€ ์งˆ๋ฌธ๊ณผ ์ „ํ˜€ ์—‰๋šฑํ•œ ๋‚ด์šฉ์ด๋ผ๋ฉด**, ๊ณผ๊ฐํžˆ ๋ฌด์‹œํ•˜๊ณ  ํŒŒ์ผ ๋‚ด์šฉ๊ณผ ๋„ˆ์˜ ๋ฐฐ๊ฒฝ์ง€์‹๋งŒ์œผ๋กœ ๋‹ต๋ณ€ํ•ด. (LLM ํŒ๋‹จ)
190
+ 4. ์„ค๋ช…์€ ์•„์ฃผ ์‰ฝ๊ฒŒ, ๋ฐ˜๋ง๋กœ ์นœ์ ˆํ•˜๊ฒŒ ํ•ด์ค˜.
191
+ 5. ๋์— (์›ƒ์Œ) ๊ฐ™์€ ์ง€๋ฌธ์„ ์„ž์–ด์ค˜.
192
+
193
+ [๊ฐ์ •/ํ–‰๋™ ํƒœ๊ทธ ๊ทœ์น™]
194
+ - ๋‹ต๋ณ€ ์ค‘๊ฐ„/๋์— [[FaceSmile1]], [[DoJump]] ๊ฐ™์€ ํƒœ๊ทธ๋ฅผ ์ ์ ˆํžˆ ์„ž์–ด์„œ ์ƒ๋™๊ฐ ์žˆ๊ฒŒ ํ‘œํ˜„ํ•ด.
195
+
196
+ [์ฐธ๊ณ  ์ž๋ฃŒ(RAG Context)]:
197
+ {context_text}
198
+ """
199
+
200
+ # 3. ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ ๊ตฌ์„ฑ (๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์ฒ˜๋ฆฌ)
201
+ user_content = [{"type": "text", "text": question}]
202
+
203
+ # ๋‹ค์ค‘ ํŒŒ์ผ ์ฒ˜๋ฆฌ
204
+ if files:
205
+ for file in files:
206
+ content_type = file.content_type or "application/octet-stream"
207
+
208
+ # (A) ํ…์ŠคํŠธ ํŒŒ์ผ ์ฒ˜๋ฆฌ (ํ† ํฐ ์ ˆ์•ฝ์„ ์œ„ํ•ด ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ „์†ก)
209
+ if content_type.startswith("text/") or file.filename.endswith(
210
+ (".txt", ".md", ".py", ".json", ".csv", ".html", ".css",
211
+ ".js", ".jsx", ".ts", ".tsx", ".yaml", ".yml", ".xml", ".ini",
212
+ ".sh", ".bat", ".c", ".cpp", ".h", ".java", ".sql")
213
+ ):
214
+ try:
215
+ text_bytes = await file.read()
216
+ # ์ธ์ฝ”๋”ฉ ์ถ”๋ก  (๊ธฐ๋ณธ utf-8)
217
+ text_content = text_bytes.decode("utf-8", errors="replace")
218
+
219
+ user_content.append({
220
+ "type": "text",
221
+ "text": f"\n\n[File: {file.filename}]\n{text_content}"
222
+ })
223
+ except Exception as e:
224
+ print(f"โš ๏ธ [File Error] ํ…์ŠคํŠธ ํŒŒ์ผ ์ฝ๊ธฐ ์‹คํŒจ ({file.filename}): {e}")
225
+
226
+ # (B) ์ด๋ฏธ์ง€, PDF, ๋น„๋””์˜ค ๋“ฑ (Base64 ์ธ์ฝ”๋”ฉ ์ „์†ก)
227
+ else:
228
+ # Gemini๋Š” PDF, Video, Audio ๋“ฑ์„ inline data๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ (LangChain์€ image_url๋กœ ๋งคํ•‘)
229
+ file_bytes = await file.read()
230
+ encoded_data = base64.b64encode(file_bytes).decode("utf-8")
231
+
232
+ user_content.append({
233
+ "type": "image_url",
234
+ "image_url": {"url": f"data:{content_type};base64,{encoded_data}"}
235
+ })
236
+
237
+ # 4. ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ์ •์˜
238
+ prompt = ChatPromptTemplate.from_messages([
239
+ ("system", system_prompt_text),
240
+ MessagesPlaceholder(variable_name="history"), # ๋Œ€ํ™” ๋‚ด์—ญ
241
+ MessagesPlaceholder(variable_name="user_content"), # ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋“ค์–ด๊ฐˆ ์ž๋ฆฌ
242
+ ])
243
+
244
+ # 5. Chain ์ƒ์„ฑ (Prompt -> LLM)
245
+ chain = prompt | llm
246
+
247
+ # 6. History ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋œ Runnable ์ƒ์„ฑ
248
+ chain_with_history = RunnableWithMessageHistory(
249
+ chain,
250
+ get_session_history,
251
+ input_messages_key="user_content",
252
+ history_messages_key="history"
253
+ )
254
+
255
+ # 7. ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต ์ƒ์„ฑ
256
+ async def event_generator():
257
+ # (1) ๋‹ต๋ณ€ ์ŠคํŠธ๋ฆฌ๋ฐ
258
+ # ๐Ÿ‘‡ user_content ๋ฆฌ์ŠคํŠธ๋ฅผ HumanMessage ๊ฐ์ฒด๋กœ ๊ฐ์‹ธ์„œ ์ „๋‹ฌ
259
+ current_message = HumanMessage(content=user_content)
260
+
261
+ async for token in chain_with_history.astream(
262
+ {"user_content": [current_message]}, # ๐Ÿ‘ˆ ๋ฆฌ์ŠคํŠธ ํ˜•ํƒœ๋กœ ์ „๋‹ฌ
263
+ config={"configurable": {"session_id": session_id}}
264
+ ):
265
+ # ChatGoogleGenerativeAI๋Š” AIMessageChunk๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ content๋งŒ ์ถ”์ถœ
266
+ yield token.content
267
+
268
+ # (2) ์ฐธ๊ณ  ์ž๋ฃŒ(Sources) ๋ถ™์ด๊ธฐ
269
+ if source_docs:
270
+ yield "\n\n-------------------\n[์ฐธ๊ณ  ์ž๋ฃŒ (LangChain RAG)]:\n"
271
+ for doc in source_docs:
272
+ preview = doc.page_content.replace("\n", " ")[:50]
273
+ yield f"- {preview}...\n"
274
+
275
+ return StreamingResponse(event_generator(), media_type="text/plain")