hueonsu654-arch commited on
Commit
9a8fd3e
ยท
1 Parent(s): c77e45a
Files changed (34) hide show
  1. .dockerignore +8 -0
  2. .gitignore +7 -0
  3. Dockerfile +32 -0
  4. Dockerfile_BK_260210.txt +27 -0
  5. Dockerfile_bk_260211.txt +32 -0
  6. app.py +50 -0
  7. app_bk.py +7 -0
  8. bee_wasp_efficientnetv2-s_aio.pth +3 -0
  9. data/8 Data Backup Security Tips for Ransomware Response.txt +37 -0
  10. data/Cyber โ€‹โ€‹Threat Trends Report for the First Half of 2025.txt +41 -0
  11. data/Detailed Guide to Analyzing and Assessing Technical Vulnerabilities in Critical Information and Communication Infrastructure.txt +64 -0
  12. data/Guide to Key Information and Communication Infrastructure Management, Physical Vulnerability Analysis.txt +125 -0
  13. data/Hacking Diagnostic Tool Utilization Plan #4 Taking Control of the AD Environment Through Exposed SMB File Servers.txt +47 -0
  14. data/Information and Communications Field_Breach_Incident_Response_Guide_Revised_Version.txt +194 -0
  15. 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 +29 -0
  16. data/myragdata1.txt +25 -0
  17. efficientnet_b0_chihuahua_muffin.json +1 -0
  18. efficientnet_b0_chihuahua_muffin.pt +3 -0
  19. efficientnet_b0_chihuahua_muffin_fsgmdef.json +1 -0
  20. efficientnet_b0_chihuahua_muffin_fsgmdef.pt +3 -0
  21. efficientnet_v2_s_plantforestdisease.json +1 -0
  22. efficientnet_v2_s_plantforestdisease.pt +3 -0
  23. effinet_basic.py +111 -0
  24. effinet_basic_compo.py +126 -0
  25. labels_map.txt +1 -0
  26. requirements.txt +27 -0
  27. requirements_bk_260210.txt +31 -0
  28. requirements_bk_260211.txt +32 -0
  29. router/cnn_router.py +534 -0
  30. router/llamindex_router.py +275 -0
  31. router/llamindex_router_bk_20251231.py +413 -0
  32. router/llamindex_router_bk_2025_12_10.py +184 -0
  33. router/llamindex_router_bk_251231V2.py +419 -0
  34. router/llamindex_router_bk_260120.py +245 -0
.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 -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"]
app.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 cnn_router
8
+
9
+ @asynccontextmanager
10
+ async def lifespan_manager(app: FastAPI):
11
+ """
12
+ ์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๊ณ  ์ข…๋ฃŒ ์‹œ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
13
+ """
14
+ # 1. CNN ๋ชจ๋ธ ๋กœ๋“œ (cnn_router์— ์œ„์ž„)
15
+ await cnn_router.load_models()
16
+
17
+ # ์„œ๋ฒ„๊ฐ€ ์š”์ฒญ ์ฒ˜๋ฆฌ๋ฅผ ์‹œ์ž‘ํ•˜๋„๋ก ์ œ์–ด๊ถŒ์„ ๋„˜๊ฒจ์ค๋‹ˆ๋‹ค.
18
+ yield
19
+
20
+ # ์„œ๋ฒ„ ์ข…๋ฃŒ ์‹œ ์‹คํ–‰๋˜๋Š” ์ •๋ฆฌ(shutdown) ๋กœ์ง
21
+ cnn_router.shutdown_models()
22
+
23
+ # FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ดˆ๊ธฐํ™”
24
+ app = FastAPI(
25
+ title="EfficientNetB0 ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ API",
26
+ description="Fine-tuned EfficientNetB0 ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€๋ฅผ ์˜ˆ์ธกํ•ฉ๋‹ˆ๋‹ค.",
27
+ lifespan=lifespan_manager
28
+ )
29
+
30
+ # CORS ์„ค์ • (๋ชจ๋“  ๋„๋ฉ”์ธ ํ—ˆ์šฉ)
31
+ app.add_middleware(
32
+ CORSMiddleware,
33
+ allow_origins=["*"],
34
+ allow_credentials=True,
35
+ allow_methods=["*"],
36
+ allow_headers=["*"],
37
+ )
38
+
39
+ #app.include_router(llamindex_router.router, prefix="/llama_index")
40
+ app.include_router(cnn_router.router) # CNN ๋ผ์šฐํ„ฐ ์ถ”๊ฐ€
41
+
42
+ # ํ—ฌ์Šค ์ฒดํฌ์šฉ ๊ธฐ๋ณธ ์—”๋“œํฌ์ธํŠธ
43
+ @app.get("/", summary="API ํ—ฌ์Šค ์ฒดํฌ")
44
+ def read_root():
45
+ """API ์„œ๋ฒ„๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค."""
46
+ return {"message": "EfficientNetB0 Classification API is running successfully."}
47
+
48
+ if __name__ == "__main__":
49
+ # --reload ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ž๋™ ์žฌ์‹œ์ž‘๋˜๊ฒŒ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
50
+ uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
app_bk.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+
3
+ app = FastAPI()
4
+
5
+ @app.get("/")
6
+ def greet_json():
7
+ return {"Hello": "World!"}
bee_wasp_efficientnetv2-s_aio.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:093f24660259f92883976b0265824ebd63a98ffedbdd98d826b6ef51f9365835
3
+ size 81642267
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๋งŒ์›
efficientnet_b0_chihuahua_muffin.json ADDED
@@ -0,0 +1 @@
 
 
1
+ ["chihuahua", "muffin"]
efficientnet_b0_chihuahua_muffin.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d93f94d7cf902593bf19f9bd9315235dee476509029711880333b0cbc8bfbbe8
3
+ size 16348525
efficientnet_b0_chihuahua_muffin_fsgmdef.json ADDED
@@ -0,0 +1 @@
 
 
1
+ ["chihuahua", "muffin"]
efficientnet_b0_chihuahua_muffin_fsgmdef.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:82280e17f0ce15bf897ccd2d92314de3e1e749f7b6b59fe67cfe5ee8f884999f
3
+ size 16358049
efficientnet_v2_s_plantforestdisease.json ADDED
@@ -0,0 +1 @@
 
 
1
+ ["Aphid", "Black Rust", "Blast", "Brown Rust", "Fusarium Head Blight", "Healthy Wheat", "Leaf Blight", "Mildew", "Mite", "Septoria", "Smut", "Stem fly", "Tan spot", "Yellow Rust"]
efficientnet_v2_s_plantforestdisease.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a3ff9aea0459fe346d687d108873057bb77741fb7e47dad47b2ba08281a37031
3
+ size 81704423
effinet_basic.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torchvision.models import efficientnet_b0, EfficientNet_B0_Weights
3
+ import torchvision.transforms as transforms
4
+ from PIL import Image
5
+ import torch.nn.functional as F
6
+ import json
7
+ import os
8
+ import sys # ํŒŒ์ผ ๋ˆ„๋ฝ ์‹œ ์ข…๋ฃŒ๋ฅผ ์œ„ํ•ด ์ถ”๊ฐ€
9
+
10
+ # ==============================================================================
11
+ # 0. ImageNet ํด๋ž˜์Šค ์ด๋ฆ„ ๋กœ๋“œ
12
+ # ==============================================================================
13
+ CLASS_MAP_FILENAME = 'labels_map.txt'
14
+ class_name_map = None # ์ „์—ญ ๋ณ€์ˆ˜๋กœ ์ดˆ๊ธฐํ™”
15
+
16
+ try:
17
+ if not os.path.exists(CLASS_MAP_FILENAME):
18
+ print(f"[์˜ค๋ฅ˜] ํด๋ž˜์Šค ์ด๋ฆ„ ํŒŒ์ผ('{CLASS_MAP_FILENAME}')์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
19
+ print("ํŒŒ์ผ์„ ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ์— ์ €์žฅํ–ˆ๋Š”์ง€ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.")
20
+ sys.exit(1) # ํŒŒ์ผ์ด ์—†์œผ๋ฉด ํ”„๋กœ๊ทธ๋žจ ์ข…๋ฃŒ
21
+
22
+ # 1. ํŒŒ์ผ ๋กœ๋“œ (JSON ํ˜•์‹)
23
+ with open(CLASS_MAP_FILENAME, 'r') as f:
24
+ class_map_json = json.load(f)
25
+
26
+ # 2. ์ œ๊ณตํ•ด์ฃผ์‹  ๋กœ์ง ์ ์šฉ: ์ธ๋ฑ์Šค 0๋ถ€ํ„ฐ 999๊นŒ์ง€ ์ด๋ฆ„๋งŒ ์ถ”์ถœํ•˜์—ฌ ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ
27
+ # JSON ํŒŒ์ผ์˜ ํ‚ค๊ฐ€ ๋ฌธ์ž์—ด์ด๋ฏ€๋กœ str(i)๋กœ ์ ‘๊ทผํ•˜๊ณ , ๊ฐ’ ๋ฆฌ์ŠคํŠธ์˜ ๋‘ ๋ฒˆ์งธ ์š”์†Œ(์ด๋ฆ„)๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
28
+ labels_list = [class_map_json[str(i)] for i in range(1000)]
29
+
30
+ # 3. ์ธ๋ฑ์Šค์™€ ์ด๋ฆ„์„ ๋งคํ•‘ํ•˜๋Š” ๋”•์…”๋„ˆ๋ฆฌ๋กœ ๋ณ€ํ™˜ (๋‚˜์ค‘์— ํด๋ž˜์Šค ID๋กœ ์ด๋ฆ„ ์กฐํšŒ ์šฉ์ด)
31
+ class_name_map = {i: name for i, name in enumerate(labels_list)}
32
+
33
+ print(f"ImageNet ํด๋ž˜์Šค ์ด๋ฆ„ ({len(class_name_map)}๊ฐœ) ๋กœ๋“œ ์™„๋ฃŒ.")
34
+
35
+ except Exception as e:
36
+ print(f"[์˜ค๋ฅ˜] ํด๋ž˜์Šค ํŒŒ์ผ ๋กœ๋“œ ๋˜๋Š” ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
37
+ sys.exit(1) # ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ํ”„๋กœ๊ทธ๋žจ ์ข…๋ฃŒ
38
+
39
+
40
+ # ==============================================================================
41
+ # 1. ์„ค์ • ๋ฐ ๋ชจ๋ธ ๋กœ๋“œ
42
+ # ==============================================================================
43
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
44
+ print(f"์‚ฌ์šฉ ์žฅ์น˜: {device}")
45
+
46
+ # ImageNet์œผ๋กœ ์‚ฌ์ „ ํ›ˆ๋ จ๋œ EfficientNetB0 ๋ชจ๋ธ ๋กœ๋“œ
47
+ print("์‚ฌ์ „ ํ›ˆ๋ จ๋œ EfficientNetB0 ๋ชจ๋ธ ๋กœ๋“œ ์ค‘...")
48
+ model = efficientnet_b0(weights=EfficientNet_B0_Weights.IMAGENET1K_V1)
49
+ model.eval() # ํ‰๊ฐ€ ๋ชจ๋“œ ์„ค์ •
50
+ model = model.to(device)
51
+ print("๋ชจ๋ธ ๋กœ๋“œ ๋ฐ ํ‰๊ฐ€ ๋ชจ๋“œ ์„ค์ • ์™„๋ฃŒ.")
52
+
53
+ # ==============================================================================
54
+ # 2. ํ•„์ˆ˜ ์ „์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ ์ •์˜
55
+ # ==============================================================================
56
+ preprocess = transforms.Compose([
57
+ transforms.Resize(256),
58
+ transforms.CenterCrop(224),
59
+ transforms.ToTensor(),
60
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
61
+ ])
62
+
63
+ # ==============================================================================
64
+ # 3. ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ๋ฐ ์ถœ๋ ฅ ํ•จ์ˆ˜
65
+ # ==============================================================================
66
+
67
+ def classify_image(image_path_string):
68
+ """
69
+ ์ฃผ์–ด์ง„ ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ์˜ ํŒŒ์ผ์„ EfficientNetB0 ๋ชจ๋ธ๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
70
+ (ํด๋ž˜์Šค ์ด๋ฆ„ ํฌํ•จ)
71
+ """
72
+ try:
73
+ # 1. ์ด๋ฏธ์ง€ ๋กœ๋“œ ๋ฐ RGB ๋ณ€ํ™˜
74
+ img = Image.open(image_path_string).convert('RGB')
75
+ print(f"\n[INFO] ์ด๋ฏธ์ง€ ๋กœ๋“œ ์„ฑ๊ณต: {image_path_string}")
76
+
77
+ input_tensor = preprocess(img)
78
+ input_batch = input_tensor.unsqueeze(0).to(device)
79
+
80
+ with torch.no_grad():
81
+ output = model(input_batch)
82
+
83
+ probabilities = F.softmax(output[0], dim=0)
84
+ top_prob, top_catid = torch.topk(probabilities, 5)
85
+
86
+ print("\n--- ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ (Top-5) ---")
87
+
88
+ for i in range(top_prob.size(0)):
89
+ idx = top_catid[i].item()
90
+
91
+ # ํด๋ž˜์Šค ์ด๋ฆ„ ๋งคํ•‘ ์ ์šฉ: ๋กœ๋“œ๋œ ๋”•์…”๋„ˆ๋ฆฌ ์‚ฌ์šฉ
92
+ class_name = class_name_map.get(idx, f"์•Œ ์ˆ˜ ์—†๋Š” ํด๋ž˜์Šค (ID: {idx})")
93
+
94
+ print(f"์ˆœ์œ„ {i+1}:")
95
+ print(f" - ํด๋ž˜์Šค ์ด๋ฆ„: **{class_name}**")
96
+ print(f" - ํด๋ž˜์Šค ์ธ๋ฑ์Šค (ID): {idx}")
97
+ print(f" - ํ™•๋ฅ : {top_prob[i].item():.4f}")
98
+
99
+ except FileNotFoundError:
100
+ print(f"\n[์˜ค๋ฅ˜] ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {image_path_string}")
101
+ print("๊ฒฝ๋กœ๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•ด์ฃผ์„ธ์š”.")
102
+ except Exception as e:
103
+ print(f"\n[์˜ค๋ฅ˜] ๋ถ„๋ฅ˜ ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {e}")
104
+
105
+
106
+ # --- ์‹คํ–‰ ---
107
+ # ๋ถ„๋ฅ˜ํ•  ์ด๋ฏธ์ง€ ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ๋ฌธ์ž์—ด๋กœ ์ง€์ • (์‚ฌ์šฉ์ž ํ™˜๊ฒฝ์— ๋งž๊ฒŒ ์ˆ˜์ • ํ•„์š”!)
108
+ CLASSIFY_TARGET_PATH = 'C:/Users/itg/Pictures/muffin1.png'
109
+
110
+ # ํ•จ์ˆ˜ ์‹คํ–‰
111
+ classify_image(CLASSIFY_TARGET_PATH)
effinet_basic_compo.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torchvision.models import efficientnet_b0, EfficientNet_B0_Weights
3
+ import torchvision.transforms as transforms
4
+ from PIL import Image
5
+ import torch.nn.functional as F
6
+ import json
7
+ import os
8
+ import sys
9
+
10
+ # ==============================================================================
11
+ # 0. ImageNet ํด๋ž˜์Šค ์ด๋ฆ„ ๋กœ๋“œ
12
+ # ==============================================================================
13
+ CLASS_MAP_FILENAME = 'labels_map.txt'
14
+ class_name_map = None
15
+
16
+ # API ์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ImageNet ํด๋ž˜์Šค ๋งต์„ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋“œ
17
+ try:
18
+ if not os.path.exists(CLASS_MAP_FILENAME):
19
+ # NOTE: API ํ™˜๊ฒฝ์—์„œ๋Š” sys.exit ๋Œ€์‹  ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
20
+ raise FileNotFoundError(f"[์˜ค๋ฅ˜] ํด๋ž˜์Šค ์ด๋ฆ„ ํŒŒ์ผ('{CLASS_MAP_FILENAME}')์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. API ์„œ๋ฒ„๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
21
+
22
+ with open(CLASS_MAP_FILENAME, 'r') as f:
23
+ class_map_json = json.load(f)
24
+
25
+ # ๐Ÿšจ๐Ÿšจ๐Ÿšจ ์ด ๋ถ€๋ถ„์ด ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿšจ๐Ÿšจ๐Ÿšจ
26
+ # ๊ฐ’(Value)์ด ๋ฌธ์ž์—ด์ธ ๊ฒฝ์šฐ: v ์ž์ฒด๊ฐ€ ํด๋ž˜์Šค ์ด๋ฆ„์ž…๋‹ˆ๋‹ค.
27
+ # ๊ฐ’(Value)์ด ๋ฆฌ์ŠคํŠธ์ธ ๊ฒฝ์šฐ: ๋ฆฌ์ŠคํŠธ์˜ ๋งˆ์ง€๋ง‰ ์š”์†Œ(์ผ๋ฐ˜์ ์œผ๋กœ ์ธ๋ฑ์Šค 1)๋ฅผ ํด๋ž˜์Šค ์ด๋ฆ„์œผ๋กœ ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.
28
+ labels_list = []
29
+ for k, v in class_map_json.items():
30
+ if k.isdigit() and 0 <= int(k) < 1000:
31
+ if isinstance(v, list) and len(v) > 1:
32
+ labels_list.append(v[1]) # ๋ฆฌ์ŠคํŠธ์ผ ๊ฒฝ์šฐ ๋‘ ๋ฒˆ์งธ ์š”์†Œ (์ด์ „ ์ฝ”๋“œ ์œ ์ง€)
33
+ elif isinstance(v, str):
34
+ labels_list.append(v) # ๋ฌธ์ž์—ด์ผ ๊ฒฝ์šฐ ์ „์ฒด ๋ฌธ์ž์—ด ์‚ฌ์šฉ (์ˆ˜์ •๋œ ํ•ต์‹ฌ)
35
+ else:
36
+ # ์•Œ ์ˆ˜ ์—†๋Š” ํ˜•์‹์€ ๋ฌด์‹œํ•˜๊ฑฐ๋‚˜, ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •
37
+ labels_list.append(f"Unknown Class Index {k}")
38
+
39
+ # ์ธ๋ฑ์Šค์™€ ์ด๋ฆ„ ๋งคํ•‘ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ๋ณ€ํ™˜
40
+ # labels_list์˜ ์ˆœ์„œ๊ฐ€ ๋ชจ๋ธ์˜ ์ถœ๋ ฅ ์ธ๋ฑ์Šค (0~999)์™€ ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
41
+ class_name_map = {i: name for i, name in enumerate(labels_list)}
42
+
43
+ # ํด๋ž˜์Šค ๋งต์ด 1000๊ฐœ๊ฐ€ ๋งž๋Š”์ง€ ํ™•์ธ (ImageNet ๊ธฐ์ค€)
44
+ if len(class_name_map) != 1000:
45
+ print(f"[๊ฒฝ๊ณ ] ๋กœ๋“œ๋œ ํด๋ž˜์Šค ์ˆ˜: {len(class_name_map)}๊ฐœ. ImageNet (1000๊ฐœ)๊ณผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ํ™•์ธํ•ด ์ฃผ์„ธ์š”.")
46
+
47
+ print(f"ImageNet ํด๋ž˜์Šค ๋งต ๋กœ๋“œ ์„ฑ๊ณต. (์ด {len(class_name_map)}๊ฐœ)")
48
+
49
+ except FileNotFoundError as e:
50
+ # API ์„œ๋ฒ„ ์‹œ์ž‘์„ ๋ง‰๊ธฐ ์œ„ํ•ด ๋ฐœ์ƒ๋œ ์˜ค๋ฅ˜๋ฅผ ๋‹ค์‹œ ๋ฐœ์ƒ
51
+ raise e
52
+ except Exception as e:
53
+ # JSON ํŒŒ์‹ฑ ์˜ค๋ฅ˜ ๋“ฑ ๊ธฐํƒ€ ๋กœ๋”ฉ ์˜ค๋ฅ˜
54
+ print(f"[์˜ค๋ฅ˜] ํด๋ž˜์Šค ๋งต ๋กœ๋“œ ์ค‘ ์˜ˆ๊ธฐ์น˜ ์•Š์€ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
55
+ class_name_map = None # ๋กœ๋“œ ์‹คํŒจ ์‹œ None ์œ ์ง€
56
+ # API ์„œ๋ฒ„ ์‹œ์ž‘์„ ๋ง‰๊ธฐ ์œ„ํ•ด RuntimeError ๋ฐœ์ƒ
57
+ raise RuntimeError(f"ํด๋ž˜์Šค ๋งต ๋กœ๋“œ ์˜ค๋ฅ˜: {e}")
58
+
59
+ # ==============================================================================
60
+ # 1. ๋ชจ๋ธ ๋ฐ ์ „์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ ๋กœ๋“œ (์ „์—ญ์ ์œผ๋กœ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰)
61
+ # ==============================================================================
62
+
63
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
64
+
65
+ # ์‚ฌ์ „ ํ›ˆ๋ จ๋œ EfficientNetB0 ๋ชจ๋ธ ๋กœ๋“œ
66
+ # ๋ชจ๋ธ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ try-except ๋ธ”๋ก์œผ๋กœ ๊ฐ์Œ‰๋‹ˆ๋‹ค.
67
+ try:
68
+ # weights ๊ฐ์ฒด๋Š” ์ „์ฒ˜๋ฆฌ(transforms) ์ •๋ณด๋„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
69
+ weights = EfficientNet_B0_Weights.DEFAULT
70
+ model = efficientnet_b0(weights=weights).to(device).eval() # eval ๋ชจ๋“œ๋กœ ์„ค์ •
71
+ preprocess = weights.transforms() # ์ „์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ ๋กœ๋“œ
72
+ print("EfficientNetB0 ๋ชจ๋ธ ๋ฐ ์ „์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ ๋กœ๋“œ ์„ฑ๊ณต.")
73
+ except Exception as e:
74
+ print(f"[์˜ค๋ฅ˜] EfficientNetB0 ๋ชจ๋ธ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
75
+ # ๋ชจ๋ธ ๋กœ๋“œ ์‹คํŒจ ์‹œ None ์„ค์ • ํ›„, ๋ถ„๋ฅ˜ ์‹œ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋„๋ก ํ•จ
76
+ model = None
77
+ preprocess = None
78
+ raise RuntimeError(f"๋ชจ๋ธ ๋กœ๋“œ ์˜ค๋ฅ˜: {e}")
79
+
80
+
81
+ # ==============================================================================
82
+ # 2. ๋ถ„๋ฅ˜ ํ•จ์ˆ˜ (API์—์„œ ์‚ฌ์šฉ)
83
+ # ==============================================================================
84
+
85
+ def classify_image_pil(img: Image.Image) -> list:
86
+ """
87
+ ์ฃผ์–ด์ง„ PIL Image ๊ฐ์ฒด๋ฅผ EfficientNetB0 ๋ชจ๋ธ๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ 
88
+ Top-5 ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
89
+ """
90
+ if class_name_map is None or not model:
91
+ raise RuntimeError("๋ชจ๋ธ ๋˜๋Š” ํด๋ž˜์Šค ๋งต์ด ์•„์ง ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
92
+
93
+ try:
94
+ # 1. ์ด๋ฏธ์ง€ RGB ๋ณ€ํ™˜ ๋ฐ ์ „์ฒ˜๋ฆฌ
95
+ img = img.convert('RGB')
96
+ input_tensor = preprocess(img)
97
+ input_batch = input_tensor.unsqueeze(0).to(device)
98
+
99
+ # 2. ์ถ”๋ก  ์ˆ˜ํ–‰
100
+ with torch.no_grad():
101
+ output = model(input_batch)
102
+
103
+ # 3. ํ™•๋ฅ  ๋ฐ Top-K ์ถ”์ถœ
104
+ probabilities = F.softmax(output[0], dim=0)
105
+ # Top-5 ํ™•๋ฅ  ๋ฐ ์ธ๋ฑ์Šค (์นดํ…Œ๊ณ ๋ฆฌ ID) ์ถ”์ถœ
106
+ top_prob, top_catid = torch.topk(probabilities, 5)
107
+
108
+ results = []
109
+ for i in range(top_prob.size(0)):
110
+ idx = top_catid[i].item()
111
+
112
+ # ํด๋ž˜์Šค ์ด๋ฆ„ ๋งคํ•‘ ์ ์šฉ
113
+ class_name = class_name_map.get(idx, f"์•Œ ์ˆ˜ ์—†๋Š” ํด๋ž˜์Šค (ID: {idx})")
114
+
115
+ results.append({
116
+ "rank": i + 1,
117
+ "class_name": class_name,
118
+ "class_index": idx,
119
+ "probability": top_prob[i].item()
120
+ })
121
+
122
+ return results
123
+
124
+ except Exception as e:
125
+ # ๋ถ„๋ฅ˜ ์ค‘ ๋ฐœ์ƒํ•˜๋Š” ๋ชจ๋“  ์˜ค๋ฅ˜๋Š” ํ˜ธ์ถœ์ž(app.py)์—๊ฒŒ RuntimeError๋กœ ์ „๋‹ฌ
126
+ raise RuntimeError(f"์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ์ค‘ PyTorch/CUDA ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
labels_map.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ {"0": "tench, Tinca tinca", "1": "goldfish, Carassius auratus", "2": "great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias", "3": "tiger shark, Galeocerdo cuvieri", "4": "hammerhead, hammerhead shark", "5": "electric ray, crampfish, numbfish, torpedo", "6": "stingray", "7": "cock", "8": "hen", "9": "ostrich, Struthio camelus", "10": "brambling, Fringilla montifringilla", "11": "goldfinch, Carduelis carduelis", "12": "house finch, linnet, Carpodacus mexicanus", "13": "junco, snowbird", "14": "indigo bunting, indigo finch, indigo bird, Passerina cyanea", "15": "robin, American robin, Turdus migratorius", "16": "bulbul", "17": "jay", "18": "magpie", "19": "chickadee", "20": "water ouzel, dipper", "21": "kite", "22": "bald eagle, American eagle, Haliaeetus leucocephalus", "23": "vulture", "24": "great grey owl, great gray owl, Strix nebulosa", "25": "European fire salamander, Salamandra salamandra", "26": "common newt, Triturus vulgaris", "27": "eft", "28": "spotted salamander, Ambystoma maculatum", "29": "axolotl, mud puppy, Ambystoma mexicanum", "30": "bullfrog, Rana catesbeiana", "31": "tree frog, tree-frog", "32": "tailed frog, bell toad, ribbed toad, tailed toad, Ascaphus trui", "33": "loggerhead, loggerhead turtle, Caretta caretta", "34": "leatherback turtle, leatherback, leathery turtle, Dermochelys coriacea", "35": "mud turtle", "36": "terrapin", "37": "box turtle, box tortoise", "38": "banded gecko", "39": "common iguana, iguana, Iguana iguana", "40": "American chameleon, anole, Anolis carolinensis", "41": "whiptail, whiptail lizard", "42": "agama", "43": "frilled lizard, Chlamydosaurus kingi", "44": "alligator lizard", "45": "Gila monster, Heloderma suspectum", "46": "green lizard, Lacerta viridis", "47": "African chameleon, Chamaeleo chamaeleon", "48": "Komodo dragon, Komodo lizard, dragon lizard, giant lizard, Varanus komodoensis", "49": "African crocodile, Nile crocodile, Crocodylus niloticus", "50": "American alligator, Alligator mississipiensis", "51": "triceratops", "52": "thunder snake, worm snake, Carphophis amoenus", "53": "ringneck snake, ring-necked snake, ring snake", "54": "hognose snake, puff adder, sand viper", "55": "green snake, grass snake", "56": "king snake, kingsnake", "57": "garter snake, grass snake", "58": "water snake", "59": "vine snake", "60": "night snake, Hypsiglena torquata", "61": "boa constrictor, Constrictor constrictor", "62": "rock python, rock snake, Python sebae", "63": "Indian cobra, Naja naja", "64": "green mamba", "65": "sea snake", "66": "horned viper, cerastes, sand viper, horned asp, Cerastes cornutus", "67": "diamondback, diamondback rattlesnake, Crotalus adamanteus", "68": "sidewinder, horned rattlesnake, Crotalus cerastes", "69": "trilobite", "70": "harvestman, daddy longlegs, Phalangium opilio", "71": "scorpion", "72": "black and gold garden spider, Argiope aurantia", "73": "barn spider, Araneus cavaticus", "74": "garden spider, Aranea diademata", "75": "black widow, Latrodectus mactans", "76": "tarantula", "77": "wolf spider, hunting spider", "78": "tick", "79": "centipede", "80": "black grouse", "81": "ptarmigan", "82": "ruffed grouse, partridge, Bonasa umbellus", "83": "prairie chicken, prairie grouse, prairie fowl", "84": "peacock", "85": "quail", "86": "partridge", "87": "African grey, African gray, Psittacus erithacus", "88": "macaw", "89": "sulphur-crested cockatoo, Kakatoe galerita, Cacatua galerita", "90": "lorikeet", "91": "coucal", "92": "bee eater", "93": "hornbill", "94": "hummingbird", "95": "jacamar", "96": "toucan", "97": "drake", "98": "red-breasted merganser, Mergus serrator", "99": "goose", "100": "black swan, Cygnus atratus", "101": "tusker", "102": "echidna, spiny anteater, anteater", "103": "platypus, duckbill, duckbilled platypus, duck-billed platypus, Ornithorhynchus anatinus", "104": "wallaby, brush kangaroo", "105": "koala, koala bear, kangaroo bear, native bear, Phascolarctos cinereus", "106": "wombat", "107": "jellyfish", "108": "sea anemone, anemone", "109": "brain coral", "110": "flatworm, platyhelminth", "111": "nematode, nematode worm, roundworm", "112": "conch", "113": "snail", "114": "slug", "115": "sea slug, nudibranch", "116": "chiton, coat-of-mail shell, sea cradle, polyplacophore", "117": "chambered nautilus, pearly nautilus, nautilus", "118": "Dungeness crab, Cancer magister", "119": "rock crab, Cancer irroratus", "120": "fiddler crab", "121": "king crab, Alaska crab, Alaskan king crab, Alaska king crab, Paralithodes camtschatica", "122": "American lobster, Northern lobster, Maine lobster, Homarus americanus", "123": "spiny lobster, langouste, rock lobster, crawfish, crayfish, sea crawfish", "124": "crayfish, crawfish, crawdad, crawdaddy", "125": "hermit crab", "126": "isopod", "127": "white stork, Ciconia ciconia", "128": "black stork, Ciconia nigra", "129": "spoonbill", "130": "flamingo", "131": "little blue heron, Egretta caerulea", "132": "American egret, great white heron, Egretta albus", "133": "bittern", "134": "crane", "135": "limpkin, Aramus pictus", "136": "European gallinule, Porphyrio porphyrio", "137": "American coot, marsh hen, mud hen, water hen, Fulica americana", "138": "bustard", "139": "ruddy turnstone, Arenaria interpres", "140": "red-backed sandpiper, dunlin, Erolia alpina", "141": "redshank, Tringa totanus", "142": "dowitcher", "143": "oystercatcher, oyster catcher", "144": "pelican", "145": "king penguin, Aptenodytes patagonica", "146": "albatross, mollymawk", "147": "grey whale, gray whale, devilfish, Eschrichtius gibbosus, Eschrichtius robustus", "148": "killer whale, killer, orca, grampus, sea wolf, Orcinus orca", "149": "dugong, Dugong dugon", "150": "sea lion", "151": "Chihuahua", "152": "Japanese spaniel", "153": "Maltese dog, Maltese terrier, Maltese", "154": "Pekinese, Pekingese, Peke", "155": "Shih-Tzu", "156": "Blenheim spaniel", "157": "papillon", "158": "toy terrier", "159": "Rhodesian ridgeback", "160": "Afghan hound, Afghan", "161": "basset, basset hound", "162": "beagle", "163": "bloodhound, sleuthhound", "164": "bluetick", "165": "black-and-tan coonhound", "166": "Walker hound, Walker foxhound", "167": "English foxhound", "168": "redbone", "169": "borzoi, Russian wolfhound", "170": "Irish wolfhound", "171": "Italian greyhound", "172": "whippet", "173": "Ibizan hound, Ibizan Podenco", "174": "Norwegian elkhound, elkhound", "175": "otterhound, otter hound", "176": "Saluki, gazelle hound", "177": "Scottish deerhound, deerhound", "178": "Weimaraner", "179": "Staffordshire bullterrier, Staffordshire bull terrier", "180": "American Staffordshire terrier, Staffordshire terrier, American pit bull terrier, pit bull terrier", "181": "Bedlington terrier", "182": "Border terrier", "183": "Kerry blue terrier", "184": "Irish terrier", "185": "Norfolk terrier", "186": "Norwich terrier", "187": "Yorkshire terrier", "188": "wire-haired fox terrier", "189": "Lakeland terrier", "190": "Sealyham terrier, Sealyham", "191": "Airedale, Airedale terrier", "192": "cairn, cairn terrier", "193": "Australian terrier", "194": "Dandie Dinmont, Dandie Dinmont terrier", "195": "Boston bull, Boston terrier", "196": "miniature schnauzer", "197": "giant schnauzer", "198": "standard schnauzer", "199": "Scotch terrier, Scottish terrier, Scottie", "200": "Tibetan terrier, chrysanthemum dog", "201": "silky terrier, Sydney silky", "202": "soft-coated wheaten terrier", "203": "West Highland white terrier", "204": "Lhasa, Lhasa apso", "205": "flat-coated retriever", "206": "curly-coated retriever", "207": "golden retriever", "208": "Labrador retriever", "209": "Chesapeake Bay retriever", "210": "German short-haired pointer", "211": "vizsla, Hungarian pointer", "212": "English setter", "213": "Irish setter, red setter", "214": "Gordon setter", "215": "Brittany spaniel", "216": "clumber, clumber spaniel", "217": "English springer, English springer spaniel", "218": "Welsh springer spaniel", "219": "cocker spaniel, English cocker spaniel, cocker", "220": "Sussex spaniel", "221": "Irish water spaniel", "222": "kuvasz", "223": "schipperke", "224": "groenendael", "225": "malinois", "226": "briard", "227": "kelpie", "228": "komondor", "229": "Old English sheepdog, bobtail", "230": "Shetland sheepdog, Shetland sheep dog, Shetland", "231": "collie", "232": "Border collie", "233": "Bouvier des Flandres, Bouviers des Flandres", "234": "Rottweiler", "235": "German shepherd, German shepherd dog, German police dog, alsatian", "236": "Doberman, Doberman pinscher", "237": "miniature pinscher", "238": "Greater Swiss Mountain dog", "239": "Bernese mountain dog", "240": "Appenzeller", "241": "EntleBucher", "242": "boxer", "243": "bull mastiff", "244": "Tibetan mastiff", "245": "French bulldog", "246": "Great Dane", "247": "Saint Bernard, St Bernard", "248": "Eskimo dog, husky", "249": "malamute, malemute, Alaskan malamute", "250": "Siberian husky", "251": "dalmatian, coach dog, carriage dog", "252": "affenpinscher, monkey pinscher, monkey dog", "253": "basenji", "254": "pug, pug-dog", "255": "Leonberg", "256": "Newfoundland, Newfoundland dog", "257": "Great Pyrenees", "258": "Samoyed, Samoyede", "259": "Pomeranian", "260": "chow, chow chow", "261": "keeshond", "262": "Brabancon griffon", "263": "Pembroke, Pembroke Welsh corgi", "264": "Cardigan, Cardigan Welsh corgi", "265": "toy poodle", "266": "miniature poodle", "267": "standard poodle", "268": "Mexican hairless", "269": "timber wolf, grey wolf, gray wolf, Canis lupus", "270": "white wolf, Arctic wolf, Canis lupus tundrarum", "271": "red wolf, maned wolf, Canis rufus, Canis niger", "272": "coyote, prairie wolf, brush wolf, Canis latrans", "273": "dingo, warrigal, warragal, Canis dingo", "274": "dhole, Cuon alpinus", "275": "African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus", "276": "hyena, hyaena", "277": "red fox, Vulpes vulpes", "278": "kit fox, Vulpes macrotis", "279": "Arctic fox, white fox, Alopex lagopus", "280": "grey fox, gray fox, Urocyon cinereoargenteus", "281": "tabby, tabby cat", "282": "tiger cat", "283": "Persian cat", "284": "Siamese cat, Siamese", "285": "Egyptian cat", "286": "cougar, puma, catamount, mountain lion, painter, panther, Felis concolor", "287": "lynx, catamount", "288": "leopard, Panthera pardus", "289": "snow leopard, ounce, Panthera uncia", "290": "jaguar, panther, Panthera onca, Felis onca", "291": "lion, king of beasts, Panthera leo", "292": "tiger, Panthera tigris", "293": "cheetah, chetah, Acinonyx jubatus", "294": "brown bear, bruin, Ursus arctos", "295": "American black bear, black bear, Ursus americanus, Euarctos americanus", "296": "ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus", "297": "sloth bear, Melursus ursinus, Ursus ursinus", "298": "mongoose", "299": "meerkat, mierkat", "300": "tiger beetle", "301": "ladybug, ladybeetle, lady beetle, ladybird, ladybird beetle", "302": "ground beetle, carabid beetle", "303": "long-horned beetle, longicorn, longicorn beetle", "304": "leaf beetle, chrysomelid", "305": "dung beetle", "306": "rhinoceros beetle", "307": "weevil", "308": "fly", "309": "bee", "310": "ant, emmet, pismire", "311": "grasshopper, hopper", "312": "cricket", "313": "walking stick, walkingstick, stick insect", "314": "cockroach, roach", "315": "mantis, mantid", "316": "cicada, cicala", "317": "leafhopper", "318": "lacewing, lacewing fly", "319": "dragonfly, darning needle, devil's darning needle, sewing needle, snake feeder, snake doctor, mosquito hawk, skeeter hawk", "320": "damselfly", "321": "admiral", "322": "ringlet, ringlet butterfly", "323": "monarch, monarch butterfly, milkweed butterfly, Danaus plexippus", "324": "cabbage butterfly", "325": "sulphur butterfly, sulfur butterfly", "326": "lycaenid, lycaenid butterfly", "327": "starfish, sea star", "328": "sea urchin", "329": "sea cucumber, holothurian", "330": "wood rabbit, cottontail, cottontail rabbit", "331": "hare", "332": "Angora, Angora rabbit", "333": "hamster", "334": "porcupine, hedgehog", "335": "fox squirrel, eastern fox squirrel, Sciurus niger", "336": "marmot", "337": "beaver", "338": "guinea pig, Cavia cobaya", "339": "sorrel", "340": "zebra", "341": "hog, pig, grunter, squealer, Sus scrofa", "342": "wild boar, boar, Sus scrofa", "343": "warthog", "344": "hippopotamus, hippo, river horse, Hippopotamus amphibius", "345": "ox", "346": "water buffalo, water ox, Asiatic buffalo, Bubalus bubalis", "347": "bison", "348": "ram, tup", "349": "bighorn, bighorn sheep, cimarron, Rocky Mountain bighorn, Rocky Mountain sheep, Ovis canadensis", "350": "ibex, Capra ibex", "351": "hartebeest", "352": "impala, Aepyceros melampus", "353": "gazelle", "354": "Arabian camel, dromedary, Camelus dromedarius", "355": "llama", "356": "weasel", "357": "mink", "358": "polecat, fitch, foulmart, foumart, Mustela putorius", "359": "black-footed ferret, ferret, Mustela nigripes", "360": "otter", "361": "skunk, polecat, wood pussy", "362": "badger", "363": "armadillo", "364": "three-toed sloth, ai, Bradypus tridactylus", "365": "orangutan, orang, orangutang, Pongo pygmaeus", "366": "gorilla, Gorilla gorilla", "367": "chimpanzee, chimp, Pan troglodytes", "368": "gibbon, Hylobates lar", "369": "siamang, Hylobates syndactylus, Symphalangus syndactylus", "370": "guenon, guenon monkey", "371": "patas, hussar monkey, Erythrocebus patas", "372": "baboon", "373": "macaque", "374": "langur", "375": "colobus, colobus monkey", "376": "proboscis monkey, Nasalis larvatus", "377": "marmoset", "378": "capuchin, ringtail, Cebus capucinus", "379": "howler monkey, howler", "380": "titi, titi monkey", "381": "spider monkey, Ateles geoffroyi", "382": "squirrel monkey, Saimiri sciureus", "383": "Madagascar cat, ring-tailed lemur, Lemur catta", "384": "indri, indris, Indri indri, Indri brevicaudatus", "385": "Indian elephant, Elephas maximus", "386": "African elephant, Loxodonta africana", "387": "lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens", "388": "giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca", "389": "barracouta, snoek", "390": "eel", "391": "coho, cohoe, coho salmon, blue jack, silver salmon, Oncorhynchus kisutch", "392": "rock beauty, Holocanthus tricolor", "393": "anemone fish", "394": "sturgeon", "395": "gar, garfish, garpike, billfish, Lepisosteus osseus", "396": "lionfish", "397": "puffer, pufferfish, blowfish, globefish", "398": "abacus", "399": "abaya", "400": "academic gown, academic robe, judge's robe", "401": "accordion, piano accordion, squeeze box", "402": "acoustic guitar", "403": "aircraft carrier, carrier, flattop, attack aircraft carrier", "404": "airliner", "405": "airship, dirigible", "406": "altar", "407": "ambulance", "408": "amphibian, amphibious vehicle", "409": "analog clock", "410": "apiary, bee house", "411": "apron", "412": "ashcan, trash can, garbage can, wastebin, ash bin, ash-bin, ashbin, dustbin, trash barrel, trash bin", "413": "assault rifle, assault gun", "414": "backpack, back pack, knapsack, packsack, rucksack, haversack", "415": "bakery, bakeshop, bakehouse", "416": "balance beam, beam", "417": "balloon", "418": "ballpoint, ballpoint pen, ballpen, Biro", "419": "Band Aid", "420": "banjo", "421": "bannister, banister, balustrade, balusters, handrail", "422": "barbell", "423": "barber chair", "424": "barbershop", "425": "barn", "426": "barometer", "427": "barrel, cask", "428": "barrow, garden cart, lawn cart, wheelbarrow", "429": "baseball", "430": "basketball", "431": "bassinet", "432": "bassoon", "433": "bathing cap, swimming cap", "434": "bath towel", "435": "bathtub, bathing tub, bath, tub", "436": "beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon", "437": "beacon, lighthouse, beacon light, pharos", "438": "beaker", "439": "bearskin, busby, shako", "440": "beer bottle", "441": "beer glass", "442": "bell cote, bell cot", "443": "bib", "444": "bicycle-built-for-two, tandem bicycle, tandem", "445": "bikini, two-piece", "446": "binder, ring-binder", "447": "binoculars, field glasses, opera glasses", "448": "birdhouse", "449": "boathouse", "450": "bobsled, bobsleigh, bob", "451": "bolo tie, bolo, bola tie, bola", "452": "bonnet, poke bonnet", "453": "bookcase", "454": "bookshop, bookstore, bookstall", "455": "bottlecap", "456": "bow", "457": "bow tie, bow-tie, bowtie", "458": "brass, memorial tablet, plaque", "459": "brassiere, bra, bandeau", "460": "breakwater, groin, groyne, mole, bulwark, seawall, jetty", "461": "breastplate, aegis, egis", "462": "broom", "463": "bucket, pail", "464": "buckle", "465": "bulletproof vest", "466": "bullet train, bullet", "467": "butcher shop, meat market", "468": "cab, hack, taxi, taxicab", "469": "caldron, cauldron", "470": "candle, taper, wax light", "471": "cannon", "472": "canoe", "473": "can opener, tin opener", "474": "cardigan", "475": "car mirror", "476": "carousel, carrousel, merry-go-round, roundabout, whirligig", "477": "carpenter's kit, tool kit", "478": "carton", "479": "car wheel", "480": "cash machine, cash dispenser, automated teller machine, automatic teller machine, automated teller, automatic teller, ATM", "481": "cassette", "482": "cassette player", "483": "castle", "484": "catamaran", "485": "CD player", "486": "cello, violoncello", "487": "cellular telephone, cellular phone, cellphone, cell, mobile phone", "488": "chain", "489": "chainlink fence", "490": "chain mail, ring mail, mail, chain armor, chain armour, ring armor, ring armour", "491": "chain saw, chainsaw", "492": "chest", "493": "chiffonier, commode", "494": "chime, bell, gong", "495": "china cabinet, china closet", "496": "Christmas stocking", "497": "church, church building", "498": "cinema, movie theater, movie theatre, movie house, picture palace", "499": "cleaver, meat cleaver, chopper", "500": "cliff dwelling", "501": "cloak", "502": "clog, geta, patten, sabot", "503": "cocktail shaker", "504": "coffee mug", "505": "coffeepot", "506": "coil, spiral, volute, whorl, helix", "507": "combination lock", "508": "computer keyboard, keypad", "509": "confectionery, confectionary, candy store", "510": "container ship, containership, container vessel", "511": "convertible", "512": "corkscrew, bottle screw", "513": "cornet, horn, trumpet, trump", "514": "cowboy boot", "515": "cowboy hat, ten-gallon hat", "516": "cradle", "517": "crane", "518": "crash helmet", "519": "crate", "520": "crib, cot", "521": "Crock Pot", "522": "croquet ball", "523": "crutch", "524": "cuirass", "525": "dam, dike, dyke", "526": "desk", "527": "desktop computer", "528": "dial telephone, dial phone", "529": "diaper, nappy, napkin", "530": "digital clock", "531": "digital watch", "532": "dining table, board", "533": "dishrag, dishcloth", "534": "dishwasher, dish washer, dishwashing machine", "535": "disk brake, disc brake", "536": "dock, dockage, docking facility", "537": "dogsled, dog sled, dog sleigh", "538": "dome", "539": "doormat, welcome mat", "540": "drilling platform, offshore rig", "541": "drum, membranophone, tympan", "542": "drumstick", "543": "dumbbell", "544": "Dutch oven", "545": "electric fan, blower", "546": "electric guitar", "547": "electric locomotive", "548": "entertainment center", "549": "envelope", "550": "espresso maker", "551": "face powder", "552": "feather boa, boa", "553": "file, file cabinet, filing cabinet", "554": "fireboat", "555": "fire engine, fire truck", "556": "fire screen, fireguard", "557": "flagpole, flagstaff", "558": "flute, transverse flute", "559": "folding chair", "560": "football helmet", "561": "forklift", "562": "fountain", "563": "fountain pen", "564": "four-poster", "565": "freight car", "566": "French horn, horn", "567": "frying pan, frypan, skillet", "568": "fur coat", "569": "garbage truck, dustcart", "570": "gasmask, respirator, gas helmet", "571": "gas pump, gasoline pump, petrol pump, island dispenser", "572": "goblet", "573": "go-kart", "574": "golf ball", "575": "golfcart, golf cart", "576": "gondola", "577": "gong, tam-tam", "578": "gown", "579": "grand piano, grand", "580": "greenhouse, nursery, glasshouse", "581": "grille, radiator grille", "582": "grocery store, grocery, food market, market", "583": "guillotine", "584": "hair slide", "585": "hair spray", "586": "half track", "587": "hammer", "588": "hamper", "589": "hand blower, blow dryer, blow drier, hair dryer, hair drier", "590": "hand-held computer, hand-held microcomputer", "591": "handkerchief, hankie, hanky, hankey", "592": "hard disc, hard disk, fixed disk", "593": "harmonica, mouth organ, harp, mouth harp", "594": "harp", "595": "harvester, reaper", "596": "hatchet", "597": "holster", "598": "home theater, home theatre", "599": "honeycomb", "600": "hook, claw", "601": "hoopskirt, crinoline", "602": "horizontal bar, high bar", "603": "horse cart, horse-cart", "604": "hourglass", "605": "iPod", "606": "iron, smoothing iron", "607": "jack-o'-lantern", "608": "jean, blue jean, denim", "609": "jeep, landrover", "610": "jersey, T-shirt, tee shirt", "611": "jigsaw puzzle", "612": "jinrikisha, ricksha, rickshaw", "613": "joystick", "614": "kimono", "615": "knee pad", "616": "knot", "617": "lab coat, laboratory coat", "618": "ladle", "619": "lampshade, lamp shade", "620": "laptop, laptop computer", "621": "lawn mower, mower", "622": "lens cap, lens cover", "623": "letter opener, paper knife, paperknife", "624": "library", "625": "lifeboat", "626": "lighter, light, igniter, ignitor", "627": "limousine, limo", "628": "liner, ocean liner", "629": "lipstick, lip rouge", "630": "Loafer", "631": "lotion", "632": "loudspeaker, speaker, speaker unit, loudspeaker system, speaker system", "633": "loupe, jeweler's loupe", "634": "lumbermill, sawmill", "635": "magnetic compass", "636": "mailbag, postbag", "637": "mailbox, letter box", "638": "maillot", "639": "maillot, tank suit", "640": "manhole cover", "641": "maraca", "642": "marimba, xylophone", "643": "mask", "644": "matchstick", "645": "maypole", "646": "maze, labyrinth", "647": "measuring cup", "648": "medicine chest, medicine cabinet", "649": "megalith, megalithic structure", "650": "microphone, mike", "651": "microwave, microwave oven", "652": "military uniform", "653": "milk can", "654": "minibus", "655": "miniskirt, mini", "656": "minivan", "657": "missile", "658": "mitten", "659": "mixing bowl", "660": "mobile home, manufactured home", "661": "Model T", "662": "modem", "663": "monastery", "664": "monitor", "665": "moped", "666": "mortar", "667": "mortarboard", "668": "mosque", "669": "mosquito net", "670": "motor scooter, scooter", "671": "mountain bike, all-terrain bike, off-roader", "672": "mountain tent", "673": "mouse, computer mouse", "674": "mousetrap", "675": "moving van", "676": "muzzle", "677": "nail", "678": "neck brace", "679": "necklace", "680": "nipple", "681": "notebook, notebook computer", "682": "obelisk", "683": "oboe, hautboy, hautbois", "684": "ocarina, sweet potato", "685": "odometer, hodometer, mileometer, milometer", "686": "oil filter", "687": "organ, pipe organ", "688": "oscilloscope, scope, cathode-ray oscilloscope, CRO", "689": "overskirt", "690": "oxcart", "691": "oxygen mask", "692": "packet", "693": "paddle, boat paddle", "694": "paddlewheel, paddle wheel", "695": "padlock", "696": "paintbrush", "697": "pajama, pyjama, pj's, jammies", "698": "palace", "699": "panpipe, pandean pipe, syrinx", "700": "paper towel", "701": "parachute, chute", "702": "parallel bars, bars", "703": "park bench", "704": "parking meter", "705": "passenger car, coach, carriage", "706": "patio, terrace", "707": "pay-phone, pay-station", "708": "pedestal, plinth, footstall", "709": "pencil box, pencil case", "710": "pencil sharpener", "711": "perfume, essence", "712": "Petri dish", "713": "photocopier", "714": "pick, plectrum, plectron", "715": "pickelhaube", "716": "picket fence, paling", "717": "pickup, pickup truck", "718": "pier", "719": "piggy bank, penny bank", "720": "pill bottle", "721": "pillow", "722": "ping-pong ball", "723": "pinwheel", "724": "pirate, pirate ship", "725": "pitcher, ewer", "726": "plane, carpenter's plane, woodworking plane", "727": "planetarium", "728": "plastic bag", "729": "plate rack", "730": "plow, plough", "731": "plunger, plumber's helper", "732": "Polaroid camera, Polaroid Land camera", "733": "pole", "734": "police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria", "735": "poncho", "736": "pool table, billiard table, snooker table", "737": "pop bottle, soda bottle", "738": "pot, flowerpot", "739": "potter's wheel", "740": "power drill", "741": "prayer rug, prayer mat", "742": "printer", "743": "prison, prison house", "744": "projectile, missile", "745": "projector", "746": "puck, hockey puck", "747": "punching bag, punch bag, punching ball, punchball", "748": "purse", "749": "quill, quill pen", "750": "quilt, comforter, comfort, puff", "751": "racer, race car, racing car", "752": "racket, racquet", "753": "radiator", "754": "radio, wireless", "755": "radio telescope, radio reflector", "756": "rain barrel", "757": "recreational vehicle, RV, R.V.", "758": "reel", "759": "reflex camera", "760": "refrigerator, icebox", "761": "remote control, remote", "762": "restaurant, eating house, eating place, eatery", "763": "revolver, six-gun, six-shooter", "764": "rifle", "765": "rocking chair, rocker", "766": "rotisserie", "767": "rubber eraser, rubber, pencil eraser", "768": "rugby ball", "769": "rule, ruler", "770": "running shoe", "771": "safe", "772": "safety pin", "773": "saltshaker, salt shaker", "774": "sandal", "775": "sarong", "776": "sax, saxophone", "777": "scabbard", "778": "scale, weighing machine", "779": "school bus", "780": "schooner", "781": "scoreboard", "782": "screen, CRT screen", "783": "screw", "784": "screwdriver", "785": "seat belt, seatbelt", "786": "sewing machine", "787": "shield, buckler", "788": "shoe shop, shoe-shop, shoe store", "789": "shoji", "790": "shopping basket", "791": "shopping cart", "792": "shovel", "793": "shower cap", "794": "shower curtain", "795": "ski", "796": "ski mask", "797": "sleeping bag", "798": "slide rule, slipstick", "799": "sliding door", "800": "slot, one-armed bandit", "801": "snorkel", "802": "snowmobile", "803": "snowplow, snowplough", "804": "soap dispenser", "805": "soccer ball", "806": "sock", "807": "solar dish, solar collector, solar furnace", "808": "sombrero", "809": "soup bowl", "810": "space bar", "811": "space heater", "812": "space shuttle", "813": "spatula", "814": "speedboat", "815": "spider web, spider's web", "816": "spindle", "817": "sports car, sport car", "818": "spotlight, spot", "819": "stage", "820": "steam locomotive", "821": "steel arch bridge", "822": "steel drum", "823": "stethoscope", "824": "stole", "825": "stone wall", "826": "stopwatch, stop watch", "827": "stove", "828": "strainer", "829": "streetcar, tram, tramcar, trolley, trolley car", "830": "stretcher", "831": "studio couch, day bed", "832": "stupa, tope", "833": "submarine, pigboat, sub, U-boat", "834": "suit, suit of clothes", "835": "sundial", "836": "sunglass", "837": "sunglasses, dark glasses, shades", "838": "sunscreen, sunblock, sun blocker", "839": "suspension bridge", "840": "swab, swob, mop", "841": "sweatshirt", "842": "swimming trunks, bathing trunks", "843": "swing", "844": "switch, electric switch, electrical switch", "845": "syringe", "846": "table lamp", "847": "tank, army tank, armored combat vehicle, armoured combat vehicle", "848": "tape player", "849": "teapot", "850": "teddy, teddy bear", "851": "television, television system", "852": "tennis ball", "853": "thatch, thatched roof", "854": "theater curtain, theatre curtain", "855": "thimble", "856": "thresher, thrasher, threshing machine", "857": "throne", "858": "tile roof", "859": "toaster", "860": "tobacco shop, tobacconist shop, tobacconist", "861": "toilet seat", "862": "torch", "863": "totem pole", "864": "tow truck, tow car, wrecker", "865": "toyshop", "866": "tractor", "867": "trailer truck, tractor trailer, trucking rig, rig, articulated lorry, semi", "868": "tray", "869": "trench coat", "870": "tricycle, trike, velocipede", "871": "trimaran", "872": "tripod", "873": "triumphal arch", "874": "trolleybus, trolley coach, trackless trolley", "875": "trombone", "876": "tub, vat", "877": "turnstile", "878": "typewriter keyboard", "879": "umbrella", "880": "unicycle, monocycle", "881": "upright, upright piano", "882": "vacuum, vacuum cleaner", "883": "vase", "884": "vault", "885": "velvet", "886": "vending machine", "887": "vestment", "888": "viaduct", "889": "violin, fiddle", "890": "volleyball", "891": "waffle iron", "892": "wall clock", "893": "wallet, billfold, notecase, pocketbook", "894": "wardrobe, closet, press", "895": "warplane, military plane", "896": "washbasin, handbasin, washbowl, lavabo, wash-hand basin", "897": "washer, automatic washer, washing machine", "898": "water bottle", "899": "water jug", "900": "water tower", "901": "whiskey jug", "902": "whistle", "903": "wig", "904": "window screen", "905": "window shade", "906": "Windsor tie", "907": "wine bottle", "908": "wing", "909": "wok", "910": "wooden spoon", "911": "wool, woolen, woollen", "912": "worm fence, snake fence, snake-rail fence, Virginia fence", "913": "wreck", "914": "yawl", "915": "yurt", "916": "web site, website, internet site, site", "917": "comic book", "918": "crossword puzzle, crossword", "919": "street sign", "920": "traffic light, traffic signal, stoplight", "921": "book jacket, dust cover, dust jacket, dust wrapper", "922": "menu", "923": "plate", "924": "guacamole", "925": "consomme", "926": "hot pot, hotpot", "927": "trifle", "928": "ice cream, icecream", "929": "ice lolly, lolly, lollipop, popsicle", "930": "French loaf", "931": "bagel, beigel", "932": "pretzel", "933": "cheeseburger", "934": "hotdog, hot dog, red hot", "935": "mashed potato", "936": "head cabbage", "937": "broccoli", "938": "cauliflower", "939": "zucchini, courgette", "940": "spaghetti squash", "941": "acorn squash", "942": "butternut squash", "943": "cucumber, cuke", "944": "artichoke, globe artichoke", "945": "bell pepper", "946": "cardoon", "947": "mushroom", "948": "Granny Smith", "949": "strawberry", "950": "orange", "951": "lemon", "952": "fig", "953": "pineapple, ananas", "954": "banana", "955": "jackfruit, jak, jack", "956": "custard apple", "957": "pomegranate", "958": "hay", "959": "carbonara", "960": "chocolate sauce, chocolate syrup", "961": "dough", "962": "meat loaf, meatloaf", "963": "pizza, pizza pie", "964": "potpie", "965": "burrito", "966": "red wine", "967": "espresso", "968": "cup", "969": "eggnog", "970": "alp", "971": "bubble", "972": "cliff, drop, drop-off", "973": "coral reef", "974": "geyser", "975": "lakeside, lakeshore", "976": "promontory, headland, head, foreland", "977": "sandbar, sand bar", "978": "seashore, coast, seacoast, sea-coast", "979": "valley, vale", "980": "volcano", "981": "ballplayer, baseball player", "982": "groom, bridegroom", "983": "scuba diver", "984": "rapeseed", "985": "daisy", "986": "yellow lady's slipper, yellow lady-slipper, Cypripedium calceolus, Cypripedium parviflorum", "987": "corn", "988": "acorn", "989": "hip, rose hip, rosehip", "990": "buckeye, horse chestnut, conker", "991": "coral fungus", "992": "agaric", "993": "gyromitra", "994": "stinkhorn, carrion fungus", "995": "earthstar", "996": "hen-of-the-woods, hen of the woods, Polyporus frondosus, Grifola frondosa", "997": "bolete", "998": "ear, spike, capitulum", "999": "toilet tissue, toilet paper, bathroom tissue"}
requirements.txt ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ torch
4
+ torchvision
5
+ numpy
6
+ Pillow
7
+ requests
8
+ python-multipart
9
+
10
+ llama-index
11
+ llama-index-llms-gemini
12
+ llama-index-embeddings-huggingface
13
+ llama-index-multi-modal-llms-gemini
14
+
15
+ transformers
16
+ einops
17
+ accelerate
18
+
19
+ python-dotenv
20
+ google-generativeai
21
+ pypdf
22
+ faiss-cpu
23
+
24
+ langchain
25
+ langchain-community
26
+ langchain-huggingface
27
+ langchain-google-genai
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/cnn_router.py ADDED
@@ -0,0 +1,534 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, UploadFile, File, HTTPException
2
+ from io import BytesIO
3
+ from PIL import Image
4
+ import torch
5
+ import torch.nn as nn
6
+ from torchvision import models, transforms
7
+ import json
8
+ import os
9
+ from typing import List, Dict, Any
10
+
11
+ # effinet_basic_compo.py ์—์„œ ๋ถ„๋ฅ˜ ํ•จ์ˆ˜ ์ž„ํฌํŠธ
12
+ # ์ฃผ์˜: ์ด ๋ชจ๋“ˆ์€ ์ž„ํฌํŠธ ์‹œ์ ์— ์ „์—ญ ์ดˆ๊ธฐํ™” ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Œ
13
+ try:
14
+ from effinet_basic_compo import classify_image_pil
15
+ except ImportError:
16
+ # router ํด๋” ๋‚ด์— ์žˆ์„ ๊ฒฝ์šฐ ์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ ์ฐธ์กฐ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Œ
17
+ import sys
18
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
19
+ from effinet_basic_compo import classify_image_pil
20
+
21
+ router = APIRouter(tags=["CNN_Image_Classification"])
22
+
23
+ # ==============================================================================
24
+ # โš™๏ธ ์„ค์ • ๋ฐ ์ „์—ญ ๋ณ€์ˆ˜
25
+ # ==============================================================================
26
+
27
+ # ํŒŒ์ผ ์ด๋ฆ„ ์ƒ์ˆ˜ ์ •์˜
28
+ MODEL_PATH = "efficientnet_b0_chihuahua_muffin.pt"
29
+ CLASS_MAP_PATH = "efficientnet_b0_chihuahua_muffin.json"
30
+
31
+ FGSM_MODEL_PATH = "efficientnet_b0_chihuahua_muffin_fsgmdef.pt"
32
+ FGSM_CLASS_MAP_PATH = "efficientnet_b0_chihuahua_muffin_fsgmdef.json"
33
+
34
+ PLANTDISEASE_MODEL_PATH_2 = "efficientnet_v2_s_plantforestdisease.pt"
35
+ PLANTDISEASE_CLASS_MAP_PATH_2 = "efficientnet_v2_s_plantforestdisease.json"
36
+
37
+ BEE_WASP_MODEL_PATH = "bee_wasp_efficientnetv2-s_aio.pth"
38
+
39
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
40
+ # ๋ชจ๋ธ ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•  ์ „์—ญ ๋ณ€์ˆ˜
41
+ MODEL = {
42
+ "model": None,
43
+ "preprocess": None,
44
+ "class_idx_to_name": None,
45
+
46
+ "fgsm_model": None,
47
+ "fgsm_preprocess": None,
48
+ "fgsm_class_idx_to_name": None,
49
+
50
+ "plantdisease_model_2": None,
51
+ "plantdisease_preprocess_2": None,
52
+ "plantdisease_class_idx_to_name_2": None,
53
+
54
+ "bee_wasp_model": None,
55
+ "bee_wasp_preprocess": None,
56
+ "bee_wasp_class_names": None,
57
+ }
58
+
59
+ # ==============================================================================
60
+ # ๐Ÿ”„ ๋ชจ๋ธ ์ˆ˜๋ช…์ฃผ๊ธฐ ๊ด€๋ฆฌ (Load / Shutdown)
61
+ # ==============================================================================
62
+
63
+ async def load_models():
64
+ """
65
+ ์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ EfficientNetB0 (Wierd Animals) ๋ชจ๋ธ๊ณผ EfficientNetV2-S (Plant Disease) ๋ชจ๋ธ์„ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.
66
+ """
67
+
68
+ # ====================================================================
69
+ # ๐Ÿฅ‡ 1. EfficientNetB0 (efficientnet_b0_chihuahua_muffin) ๋ชจ๋ธ ๋กœ๋“œ
70
+ # ====================================================================
71
+ print(f"[MODEL INIT] EfficientNetB0 ๋ชจ๋ธ ๋กœ๋“œ ์‹œ์ž‘... ({MODEL_PATH})")
72
+ try:
73
+ # 1-1. ํด๋ž˜์Šค ๋งต ๋กœ๋“œ
74
+ if not os.path.exists(CLASS_MAP_PATH):
75
+ raise FileNotFoundError(f"ํด๋ž˜์Šค ๋งต ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค: {CLASS_MAP_PATH}")
76
+
77
+ with open(CLASS_MAP_PATH, 'r', encoding='utf-8') as f:
78
+ raw_class_data = json.load(f)
79
+ if isinstance(raw_class_data, list):
80
+ class_idx_to_name = {i: name for i, name in enumerate(raw_class_data)}
81
+ elif isinstance(raw_class_data, dict):
82
+ class_idx_to_name = {int(k): v for k, v in raw_class_data.items()}
83
+ MODEL["class_idx_to_name"] = class_idx_to_name
84
+
85
+ # 1-2. ๋ชจ๋ธ ๊ตฌ์กฐ ์ •์˜ ๋ฐ ๊ฐ€์ค‘์น˜ ๋กœ๋“œ
86
+ if not os.path.exists(MODEL_PATH):
87
+ raise FileNotFoundError(f"๋ชจ๋ธ ๊ฐ€์ค‘์น˜ ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค: {MODEL_PATH}")
88
+
89
+ num_classes = len(class_idx_to_name)
90
+ model = models.efficientnet_b0(weights=None)
91
+ in_features = model.classifier[1].in_features
92
+ model.classifier[1] = nn.Linear(in_features, num_classes)
93
+
94
+ ckpt = torch.load(MODEL_PATH, map_location='cpu')
95
+
96
+ if isinstance(ckpt, dict) and 'state_dict' in ckpt:
97
+ state_dict_to_load = ckpt['state_dict']
98
+ else:
99
+ state_dict_to_load = ckpt
100
+
101
+ model.load_state_dict(state_dict_to_load, strict=False)
102
+ model.to(device)
103
+ model.eval()
104
+ MODEL["model"] = model
105
+
106
+ # 1-3. ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ ์ •์˜
107
+ preprocess = transforms.Compose([
108
+ transforms.Resize((224, 224)),
109
+ transforms.ToTensor(),
110
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
111
+ ])
112
+ MODEL["preprocess"] = preprocess
113
+
114
+ print(f"[MODEL INIT] EfficientNetB0 ๋ชจ๋ธ ๋กœ๋“œ ์™„๋ฃŒ. ํด๋ž˜์Šค ์ˆ˜: {num_classes}")
115
+
116
+ except Exception as e:
117
+ print(f"[ERROR] EfficientNetB0 ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ. ์›์ธ: {type(e).__name__} - {e}")
118
+
119
+
120
+ # ====================================================================
121
+ # ๐Ÿฅˆ 2. EfficientNetV2-S (Plant Disease) - [์—ฌ๊ธฐ๊ฐ€ ์ˆ˜์ •๋จ]
122
+ # ====================================================================
123
+ # ์ „์—ญ ๋ณ€์ˆ˜ ์ƒ๋‹จ์—์„œ ํŒŒ์ผ๋ช… ์ˆ˜์ • ํ•„์ˆ˜:
124
+ # PLANTDISEASE_MODEL_PATH_2 = "efficientnet_v2_s_plantforestdisease.pt"
125
+
126
+ print(f"[MODEL INIT] EfficientNetV2-S ๋ชจ๋ธ ๋กœ๋“œ ์‹œ์ž‘... ({PLANTDISEASE_MODEL_PATH_2})")
127
+ try:
128
+ # 2-1. ํด๋ž˜์Šค ๋งต ๋กœ๋“œ
129
+ if not os.path.exists(PLANTDISEASE_CLASS_MAP_PATH_2):
130
+ raise FileNotFoundError(f"ํŒŒ์ผ ์—†์Œ: {PLANTDISEASE_CLASS_MAP_PATH_2}")
131
+
132
+ with open(PLANTDISEASE_CLASS_MAP_PATH_2, 'r', encoding='utf-8') as f:
133
+ raw_class_data_2 = json.load(f)
134
+ if isinstance(raw_class_data_2, list):
135
+ class_idx_to_name_2 = {i: name for i, name in enumerate(raw_class_data_2)}
136
+ elif isinstance(raw_class_data_2, dict):
137
+ class_idx_to_name_2 = {int(k): v for k, v in raw_class_data_2.items()}
138
+ MODEL["plantdisease_class_idx_to_name_2"] = class_idx_to_name_2
139
+
140
+ # 2-2. ๋ชจ๋ธ ๊ตฌ์กฐ ์ •์˜ (EfficientNet V2-S)
141
+ if not os.path.exists(PLANTDISEASE_MODEL_PATH_2):
142
+ raise FileNotFoundError(f"ํŒŒ์ผ ์—†์Œ: {PLANTDISEASE_MODEL_PATH_2}")
143
+
144
+ num_classes_2 = len(class_idx_to_name_2)
145
+
146
+ # โ˜… ํ•ต์‹ฌ ๋ณ€๊ฒฝ: ResNet50 -> EfficientNetV2-S
147
+ model_2 = models.efficientnet_v2_s(weights=None)
148
+
149
+ # โ˜… ํ•ต์‹ฌ ๋ณ€๊ฒฝ: .fc -> .classifier[1]
150
+ in_features_2 = model_2.classifier[1].in_features
151
+ model_2.classifier[1] = nn.Linear(in_features_2, num_classes_2)
152
+
153
+ ckpt_2 = torch.load(PLANTDISEASE_MODEL_PATH_2, map_location='cpu')
154
+ if isinstance(ckpt_2, dict) and 'state_dict' in ckpt_2:
155
+ state_dict_to_load_2 = ckpt_2['state_dict']
156
+ else:
157
+ state_dict_to_load_2 = ckpt_2
158
+
159
+ model_2.load_state_dict(state_dict_to_load_2, strict=False)
160
+ model_2.to(device)
161
+ model_2.eval()
162
+ MODEL["plantdisease_model_2"] = model_2
163
+
164
+ # โ˜… ํ•ต์‹ฌ ๋ณ€๊ฒฝ: ์ „์ฒ˜๋ฆฌ ์‚ฌ์ด์ฆˆ 384x384 (ํ•™์Šต ์ฝ”๋“œ์™€ ๋™์ผํ•˜๊ฒŒ ๋งž์ถค)
165
+ preprocess_2 = transforms.Compose([
166
+ transforms.Resize((384, 384)),
167
+ transforms.ToTensor(),
168
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
169
+ ])
170
+ MODEL["plantdisease_preprocess_2"] = preprocess_2
171
+
172
+ print(f"[MODEL INIT] EfficientNetV2-S (Plant) ๋กœ๋“œ ์™„๋ฃŒ.")
173
+
174
+ except Exception as e:
175
+ print(f"[ERROR] EfficientNetV2-S ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
176
+
177
+ # ====================================================================
178
+ # ๐Ÿฅ‰ 3. EfficientNetV2-S (Bee vs Wasp) - [์ƒˆ๋กœ ์ถ”๊ฐ€๋œ ๋ถ€๋ถ„]
179
+ # ====================================================================
180
+ print(f"[MODEL INIT] 3. Bee/Wasp ๋ชจ๋ธ ๋กœ๋“œ ์‹œ์ž‘... ({BEE_WASP_MODEL_PATH})")
181
+ try:
182
+ if not os.path.exists(BEE_WASP_MODEL_PATH):
183
+ raise FileNotFoundError(f"ํŒŒ์ผ ์—†์Œ: {BEE_WASP_MODEL_PATH}")
184
+
185
+ # 3-1. ํ†ตํ•ฉ ์ฒดํฌํฌ์ธํŠธ(.pth) ๋กœ๋“œ
186
+ # ์ด ํŒŒ์ผ์€ {'model_state_dict': ..., 'class_names': ..., 'input_size': ...} ํ˜•ํƒœ์ž„
187
+ checkpoint_bee = torch.load(BEE_WASP_MODEL_PATH, map_location=device)
188
+
189
+ # 3-2. ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ถ”์ถœ (ํด๋ž˜์Šค ์ด๋ฆ„, ์ž…๋ ฅ ํฌ๊ธฐ)
190
+ bee_classes = checkpoint_bee.get('class_names', [])
191
+ input_size_bee = checkpoint_bee.get('input_size', 384) # ๊ธฐ๋ณธ๊ฐ’ 384
192
+
193
+ # MODEL ๋”•์…”๋„ˆ๋ฆฌ์— ํด๋ž˜์Šค ์ •๋ณด ์ €์žฅ
194
+ # (API ๋ฐ˜ํ™˜ ์‹œ ์ธ๋ฑ์Šค๋กœ ์ด๋ฆ„์„ ์ฐพ๊ธฐ ์‰ฝ๊ฒŒ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ €์žฅํ•˜๊ฑฐ๋‚˜, ๋ฆฌ์ŠคํŠธ ๊ทธ๋Œ€๋กœ ์ €์žฅ)
195
+ MODEL["bee_wasp_class_names"] = bee_classes
196
+
197
+ # 3-3. ๋ชจ๋ธ ๊ตฌ์กฐ ์ •์˜ (EfficientNetV2-S)
198
+ num_classes_bee = len(bee_classes)
199
+ model_bee = models.efficientnet_v2_s(weights=None)
200
+
201
+ # Head ๊ต์ฒด
202
+ in_features_bee = model_bee.classifier[1].in_features
203
+ model_bee.classifier[1] = nn.Linear(in_features_bee, num_classes_bee)
204
+
205
+ # ๊ฐ€์ค‘์น˜ ๋กœ๋“œ
206
+ model_bee.load_state_dict(checkpoint_bee['model_state_dict'])
207
+ model_bee.to(device)
208
+ model_bee.eval()
209
+ MODEL["bee_wasp_model"] = model_bee
210
+
211
+ # 3-4. ์ „์ฒ˜๋ฆฌ ์ •์˜ (์ €์žฅ๋œ input_size ํ™œ์šฉ)
212
+ preprocess_bee = transforms.Compose([
213
+ transforms.Resize((input_size_bee, input_size_bee)),
214
+ transforms.ToTensor(),
215
+ transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
216
+ ])
217
+ MODEL["bee_wasp_preprocess"] = preprocess_bee
218
+
219
+ print(f"[MODEL INIT] โœ… Bee/Wasp ๋ชจ๋ธ ๋กœ๋“œ ์™„๋ฃŒ. (Classes: {len(bee_classes)})")
220
+
221
+ except Exception as e:
222
+ print(f"[ERROR] โŒ Bee/Wasp ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
223
+
224
+ # ====================================================================
225
+ # ๐Ÿ… 4. EfficientNetB0 (FGSM Defended - Muffin vs Chihuahua) - [์ƒˆ๋กœ ์ถ”๊ฐ€๋จ]
226
+ # ====================================================================
227
+ print(f"[MODEL INIT] 4. FGSM ๋ฐฉ์–ด ๋ชจ๋ธ ๋กœ๋“œ ์‹œ์ž‘... ({FGSM_MODEL_PATH})")
228
+ try:
229
+ with open(FGSM_CLASS_MAP_PATH, 'r', encoding='utf-8') as f:
230
+ raw_class_data_fgsm = json.load(f)
231
+ if isinstance(raw_class_data_fgsm, list):
232
+ class_idx_to_name_fgsm = {i: name for i, name in enumerate(raw_class_data_fgsm)}
233
+ elif isinstance(raw_class_data_fgsm, dict):
234
+ class_idx_to_name_fgsm = {int(k): v for k, v in raw_class_data_fgsm.items()}
235
+ MODEL["fgsm_class_idx_to_name"] = class_idx_to_name_fgsm
236
+
237
+ num_classes_fgsm = len(class_idx_to_name_fgsm)
238
+ model_fgsm = models.efficientnet_b0(weights=None)
239
+ in_features_fgsm = model_fgsm.classifier[1].in_features
240
+ model_fgsm.classifier[1] = nn.Linear(in_features_fgsm, num_classes_fgsm)
241
+
242
+ ckpt_fgsm = torch.load(FGSM_MODEL_PATH, map_location='cpu')
243
+ state_dict_to_load_fgsm = ckpt_fgsm['state_dict'] if isinstance(ckpt_fgsm, dict) and 'state_dict' in ckpt_fgsm else ckpt_fgsm
244
+
245
+ model_fgsm.load_state_dict(state_dict_to_load_fgsm, strict=False)
246
+ model_fgsm.to(device)
247
+ model_fgsm.eval()
248
+ MODEL["fgsm_model"] = model_fgsm
249
+
250
+ # โ˜… ์ฃผ์˜: ํ•™์Šต/์˜ˆ์ธก ์ฝ”๋“œ์™€ ๋™์ผํ•˜๊ฒŒ CenterCrop์„ ์ œ์™ธํ•˜๊ณ  Resize๋งŒ ์ง„ํ–‰
251
+ preprocess_fgsm = transforms.Compose([
252
+ transforms.Resize((224, 224)),
253
+ transforms.ToTensor(),
254
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
255
+ ])
256
+ MODEL["fgsm_preprocess"] = preprocess_fgsm
257
+
258
+ print(f"[MODEL INIT] โœ… FGSM ๋ฐฉ์–ด ๋ชจ๋ธ ๋กœ๋“œ ์™„๋ฃŒ.")
259
+
260
+ except Exception as e:
261
+ print(f"[ERROR] โŒ FGSM ๋ฐฉ์–ด ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
262
+
263
+
264
+ def shutdown_models():
265
+ """์„œ๋ฒ„ ์ข…๋ฃŒ ์‹œ ๋ชจ๋ธ ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ"""
266
+ print("[SERVER SHUTDOWN] ๋ชจ๋ธ ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ ์ค‘...")
267
+ MODEL["model"] = None
268
+ MODEL["plantdisease_model_2"] = None
269
+ MODEL["bee_wasp_model"] = None
270
+ MODEL["fgsm_model"] = None # ์ถ”๊ฐ€๋จ
271
+ if torch.cuda.is_available():
272
+ torch.cuda.empty_cache()
273
+
274
+
275
+
276
+ # ==============================================================================
277
+ # ๐Ÿš€ API Endpoints
278
+ # ==============================================================================
279
+
280
+ @router.post("/classify", summary="์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ (๊ธฐ๋ณธ)", response_description="์˜ˆ์ธก ๊ฒฐ๊ณผ ๋ฐ ์„ฑ๊ณต ์—ฌ๋ถ€")
281
+ async def classify_image(file: UploadFile = File(..., description="๋ถ„๋ฅ˜ํ•  ์ด๋ฏธ์ง€ ํŒŒ์ผ (JPG, PNG ๋“ฑ)")):
282
+ """
283
+ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๊ณ  EfficientNetB0 ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์—ฌ
284
+ ImageNet ํด๋ž˜์Šค์— ๋Œ€ํ•œ Top-5 ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ๋ฅผ JSON ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
285
+ """
286
+
287
+ # 1. ํŒŒ์ผ ํ˜•์‹ ๊ฒ€์ฆ (์„ ํƒ์ )
288
+ if not file.content_type or not file.content_type.startswith('image/'):
289
+ return {
290
+ "success": False,
291
+ "msg": "์ž˜๋ชป๋œ ํŒŒ์ผ ํ˜•์‹์ž…๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•ด ์ฃผ์„ธ์š”."
292
+ }
293
+
294
+ try:
295
+ # 2. ์—…๋กœ๋“œ๋œ ํŒŒ์ผ์˜ ๋‚ด์šฉ์„ ๋ฉ”๋ชจ๋ฆฌ๋กœ ์ฝ์–ด์˜ต๋‹ˆ๋‹ค. (๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ)
296
+ file_content = await file.read()
297
+
298
+ # 3. ๋ฐ”์ดํŠธ ๋ฐ์ดํ„ฐ๋ฅผ PIL Image ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜
299
+ try:
300
+ img = Image.open(BytesIO(file_content))
301
+ except IOError:
302
+ return {
303
+ "success": False,
304
+ "msg": "์—…๋กœ๋“œ๋œ ํŒŒ์ผ์˜ ๋‚ด์šฉ์ด ์œ ํšจํ•œ ์ด๋ฏธ์ง€ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค."
305
+ }
306
+
307
+ # 4. effinet_basic.py์˜ ๋ถ„๋ฅ˜ ํ•จ์ˆ˜ ํ˜ธ์ถœ (์—ฌ๊ธฐ์„œ๋Š” ๊ธฐ์กด ๋กœ์ง๋Œ€๋กœ PIL ์ด๋ฏธ์ง€๋ฅผ ๋„˜๊น€)
308
+ print(f"[API INFO] ํŒŒ์ผ '{file.filename}' ๋ถ„๋ฅ˜ ์‹œ์ž‘...")
309
+ classification_results = classify_image_pil(img)
310
+ print(f"[API INFO] ํŒŒ์ผ '{file.filename}' ๋ถ„๋ฅ˜ ์™„๋ฃŒ.")
311
+
312
+ # 5. ์„ฑ๊ณต ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜
313
+ return {
314
+ "success": True,
315
+ "msg": "์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.",
316
+ "filename": file.filename,
317
+ "predictions": classification_results
318
+ }
319
+
320
+ except RuntimeError as e:
321
+ error_msg = f"๋ชจ๋ธ ๋ถ„๋ฅ˜ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
322
+ print(f"[ERROR] Classification Runtime Error: {error_msg}")
323
+ return {"success": False, "msg": error_msg}
324
+ except Exception as e:
325
+ error_msg = f"์„œ๋ฒ„์—์„œ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {type(e).__name__}"
326
+ print(f"[ERROR] An unexpected error occurred: {e}")
327
+ return {"success": False, "msg": error_msg}
328
+
329
+
330
+ @router.post("/classify_muffin_chihuahua", summary="๋จธํ•€/์น˜์™€์™€ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜", response_description="์˜ˆ์ธก ๊ฒฐ๊ณผ ๋ฐ ์„ฑ๊ณต ์—ฌ๋ถ€")
331
+ async def classify_muffin_chihuahua(file: UploadFile = File(..., description="๋ถ„๋ฅ˜ํ•  ์ด๋ฏธ์ง€ ํŒŒ์ผ (JPG, PNG ๋“ฑ)"), top_k: int = 1):
332
+ """
333
+ ์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋“œ๋œ EfficientNetB0 ๋ชจ๋ธ๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
334
+ """
335
+
336
+ # 0. ๋ชจ๋ธ ๋กœ๋“œ ์ƒํƒœ ํ™•์ธ
337
+ model = MODEL["model"]
338
+ preprocess = MODEL["preprocess"]
339
+ class_idx_to_name = MODEL["class_idx_to_name"]
340
+
341
+ if model is None:
342
+ raise HTTPException(status_code=503, detail="๋ชจ๋ธ์ด ์„œ๋ฒ„์— ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•˜์„ธ์š”.")
343
+
344
+ # 1. ํŒŒ์ผ ํ˜•์‹ ๊ฒ€์ฆ
345
+ if not file.content_type or not file.content_type.startswith('image/'):
346
+ return {"success": False, "msg": "์ž˜๋ชป๋œ ํŒŒ์ผ ํ˜•์‹์ž…๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•ด ์ฃผ์„ธ์š”."}
347
+
348
+ try:
349
+ file_content = await file.read()
350
+
351
+ # 2. ๋ฐ”์ดํŠธ ๋ฐ์ดํ„ฐ๋ฅผ PIL Image ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜
352
+ try:
353
+ img = Image.open(BytesIO(file_content)).convert("RGB")
354
+ except IOError:
355
+ return {"success": False, "msg": "์—…๋กœ๋“œ๋œ ํŒŒ์ผ์˜ ๋‚ด์šฉ์ด ์œ ํšจํ•œ ์ด๋ฏธ์ง€ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค."}
356
+
357
+ # 3. ์ถ”๋ก  ๋กœ์ง
358
+ input_tensor = preprocess(img)
359
+ input_batch = input_tensor.unsqueeze(0)
360
+
361
+ # ์ถ”๋ก 
362
+ with torch.no_grad():
363
+ device = next(model.parameters()).device
364
+ input_batch = input_batch.to(device)
365
+ output = model(input_batch)
366
+
367
+ # ํ™•๋ฅ  ๋ฐ Top-K ์˜ˆ์ธก ๊ฒฐ๊ณผ ๊ณ„์‚ฐ
368
+ probabilities = torch.nn.functional.softmax(output[0], dim=0)
369
+ top_prob, top_indices = torch.topk(probabilities, top_k)
370
+
371
+ # ๊ฒฐ๊ณผ ํฌ๋งคํŒ…
372
+ results = []
373
+ for i in range(top_k):
374
+ index = top_indices[i].item()
375
+ probability = top_prob[i].item()
376
+ class_name = class_idx_to_name.get(index, f"Unknown Class Index {index}")
377
+
378
+ results.append({
379
+ "rank": i + 1,
380
+ "class_index": index,
381
+ "class_name": class_name,
382
+ "probability": f"{probability:.4f}"
383
+ })
384
+
385
+ # 4. ์„ฑ๊ณต ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜
386
+ return {
387
+ "success": True,
388
+ "msg": "์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.",
389
+ "filename": file.filename,
390
+ "predictions": results
391
+ }
392
+
393
+ except Exception as e:
394
+ error_msg = f"์„œ๋ฒ„์—์„œ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {type(e).__name__} - {e}"
395
+ print(f"[ERROR] An unexpected error occurred: {e}")
396
+ return {"success": False, "msg": error_msg}
397
+
398
+
399
+
400
+ @router.post("/classify_muffin_chihuahua_fgsm", summary="๋จธํ•€/์น˜์™€์™€ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜", response_description="์˜ˆ์ธก ๊ฒฐ๊ณผ ๋ฐ ์„ฑ๊ณต ์—ฌ๋ถ€")
401
+ async def classify_muffin_chihuahua_fgsm(file: UploadFile = File(...), top_k: int = 1):
402
+ model, preprocess, class_idx_to_name = MODEL["fgsm_model"], MODEL["fgsm_preprocess"], MODEL["fgsm_class_idx_to_name"]
403
+ if model is None: raise HTTPException(status_code=503, detail="๋ชจ๋ธ์ด ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
404
+
405
+ try:
406
+ img = Image.open(BytesIO(await file.read())).convert("RGB")
407
+ input_batch = preprocess(img).unsqueeze(0).to(next(model.parameters()).device)
408
+ with torch.no_grad():
409
+ output = model(input_batch)
410
+ top_prob, top_indices = torch.topk(torch.nn.functional.softmax(output[0], dim=0), top_k)
411
+
412
+ results = [{"rank": i + 1, "class_index": top_indices[i].item(), "class_name": class_idx_to_name.get(top_indices[i].item(), "Unknown"), "probability": f"{top_prob[i].item():.4f}"} for i in range(top_k)]
413
+ return {"success": True, "filename": file.filename, "predictions": results}
414
+ except Exception as e:
415
+ return {"success": False, "msg": str(e)}
416
+
417
+ @router.post("/classify_plant_disease", summary="์‹๋ฌผ๋ณ‘์ถฉํ•ด ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜", response_description="์˜ˆ์ธก ๊ฒฐ๊ณผ ๋ฐ ์„ฑ๊ณต ์—ฌ๋ถ€")
418
+ async def classify_plant_disease(file: UploadFile = File(..., description="๋ถ„๋ฅ˜ํ•  ์ด๋ฏธ์ง€ ํŒŒ์ผ (JPG, PNG ๋“ฑ)"), top_k: int = 1):
419
+ """
420
+ ์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋“œ๋œ EfficientNetV2-S ๋ชจ๋ธ๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
421
+ """
422
+ # 0. ๋ชจ๋ธ ๋กœ๋“œ ์ƒํƒœ ํ™•์ธ (๋ณ€์ˆ˜๋ช… ์ˆ˜์ •)
423
+ model = MODEL["plantdisease_model_2"]
424
+ preprocess = MODEL["plantdisease_preprocess_2"]
425
+ class_idx_to_name = MODEL["plantdisease_class_idx_to_name_2"]
426
+
427
+ if model is None:
428
+ raise HTTPException(status_code=503, detail="๋ชจ๋ธ์ด ์„œ๋ฒ„์— ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•˜์„ธ์š”.")
429
+
430
+ # 1. ํŒŒ์ผ ํ˜•์‹ ๊ฒ€์ฆ
431
+ if not file.content_type or not file.content_type.startswith('image/'):
432
+ return {"success": False, "msg": "์ž˜๋ชป๋œ ํŒŒ์ผ ํ˜•์‹์ž…๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•ด ์ฃผ์„ธ์š”."}
433
+
434
+ try:
435
+ file_content = await file.read()
436
+
437
+ # 2. ๋ฐ”์ดํŠธ ๋ฐ์ดํ„ฐ๋ฅผ PIL Image ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜
438
+ try:
439
+ img = Image.open(BytesIO(file_content)).convert("RGB")
440
+ except IOError:
441
+ return {"success": False, "msg": "์—…๋กœ๋“œ๋œ ํŒŒ์ผ์˜ ๋‚ด์šฉ์ด ์œ ํšจํ•œ ์ด๋ฏธ์ง€ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค."}
442
+
443
+ # 3. ์ถ”๋ก  ๋กœ์ง
444
+ input_tensor = preprocess(img)
445
+ input_batch = input_tensor.unsqueeze(0)
446
+
447
+ # ์ถ”๋ก 
448
+ with torch.no_grad():
449
+ device = next(model.parameters()).device
450
+ input_batch = input_batch.to(device)
451
+ output = model(input_batch)
452
+
453
+ # ํ™•๋ฅ  ๋ฐ Top-K ์˜ˆ์ธก ๊ฒฐ๊ณผ ๊ณ„์‚ฐ
454
+ probabilities = torch.nn.functional.softmax(output[0], dim=0)
455
+ top_prob, top_indices = torch.topk(probabilities, top_k)
456
+
457
+ # ๊ฒฐ๊ณผ ํฌ๋งคํŒ…
458
+ results = []
459
+ for i in range(top_k):
460
+ index = top_indices[i].item()
461
+ probability = top_prob[i].item()
462
+ class_name = class_idx_to_name.get(index, f"Unknown Class Index {index}")
463
+
464
+ results.append({
465
+ "rank": i + 1,
466
+ "class_index": index,
467
+ "class_name": class_name,
468
+ "probability": f"{probability:.4f}"
469
+ })
470
+
471
+ # 4. ์„ฑ๊ณต ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜
472
+ return {
473
+ "success": True,
474
+ "msg": "์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.",
475
+ "filename": file.filename,
476
+ "predictions": results
477
+ }
478
+
479
+ except Exception as e:
480
+ error_msg = f"์„œ๋ฒ„์—์„œ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {type(e).__name__} - {e}"
481
+ print(f"[ERROR] An unexpected error occurred: {e}")
482
+ return {"success": False, "msg": error_msg}
483
+
484
+ @router.post("/classify_bee_wasp")
485
+ async def classify_bee_wasp(file: UploadFile = File(...)):
486
+ """
487
+ ๋ฒŒ(Bee) vs ๋ง๋ฒŒ(Wasp) ๋ฐ ๊ธฐํƒ€ ๊ณค์ถฉ ๋ถ„๋ฅ˜ ์—”๋“œํฌ์ธํŠธ
488
+ - ๋ชจ๋ธ: EfficientNetV2-S
489
+ """
490
+ bee_wasp_model = MODEL["bee_wasp_model"]
491
+ bee_wasp_transform = MODEL["bee_wasp_preprocess"]
492
+ bee_wasp_classes = MODEL["bee_wasp_class_names"]
493
+ if bee_wasp_model is None:
494
+ raise HTTPException(status_code=503, detail="๋ชจ๋ธ์ด ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
495
+
496
+ # 1. ์ด๋ฏธ์ง€ ์ฝ๊ธฐ
497
+ try:
498
+ contents = await file.read()
499
+ image = Image.open(BytesIO(contents)).convert("RGB")
500
+ except Exception:
501
+ raise HTTPException(status_code=400, detail="์œ ํšจํ•˜์ง€ ์•Š์€ ์ด๋ฏธ์ง€ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.")
502
+
503
+ # 2. ์ „์ฒ˜๋ฆฌ
504
+ try:
505
+ input_tensor = bee_wasp_transform(image).unsqueeze(0).to(device)
506
+ except Exception as e:
507
+ raise HTTPException(status_code=500, detail=f"์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {str(e)}")
508
+
509
+ # 3. ์ถ”๋ก 
510
+ with torch.no_grad():
511
+ outputs = bee_wasp_model(input_tensor)
512
+ probabilities = torch.nn.functional.softmax(outputs[0], dim=0)
513
+
514
+ # 4. ๊ฒฐ๊ณผ ํฌ๋งคํŒ… (Top 3 ํ™•๋ฅ  ๋ฐ˜ํ™˜)
515
+ top_k = min(3, len(bee_wasp_classes))
516
+ top_prob, top_indices = torch.topk(probabilities, top_k)
517
+
518
+ results = []
519
+ for i in range(top_k):
520
+ idx = top_indices[i].item()
521
+ score = top_prob[i].item()
522
+
523
+ results.append({
524
+ "rank": i + 1,
525
+ "class_name": bee_wasp_classes[idx],
526
+ "confidence": f"{score * 100:.2f}%",
527
+ "probability": score
528
+ })
529
+
530
+ return {
531
+ "filename": file.filename,
532
+ "top_prediction": results[0]["class_name"],
533
+ "predictions": results
534
+ }
router/llamindex_router.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")
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")