AI ๊ธฐ๋ฐ ๊ท์ ๊ฒ์ ์์คํ ์ ๋๋ค.
- AI ๊ธฐ๋ฐ ์ง๋ฌธ ๋ถ์: Google Gemini๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์์ ์์ฐ์ด ์ง๋ฌธ์ ๊ตฌ์กฐํ
- ํค์๋ ์ถ์ถ: ์ง๋ฌธ์์ ํต์ฌ ํค์๋ ์๋ ์ถ์ถ
- ๊ท์ ์ ํ ๋งค์นญ: 27๊ฐ ์ฌ๊ท ์ ํ ์ค ๊ด๋ จ ๊ท์ ์๋ ์๋ณ
- ์ง๋ฌธ ์๋ ํ์ : ์ ๋ณด์กฐํ/์ ์ฐจ์๋ด/๊ธฐ์คํ์ธ/์๊ฒฉ์๊ฑด/์์ธ์ฌํญ ๋ถ๋ฅ
- ๊ฒ์ ์ฟผ๋ฆฌ ์ต์ ํ: ๋ฒกํฐ ๊ฒ์์ ์ต์ ํ๋ ์ฟผ๋ฆฌ๋ก ๋ณํ
- ๋ฒกํฐ ๊ฒ์: ์๋ฒ ๋ฉ ๊ธฐ๋ฐ ์๋ฏธ ๊ฒ์์ผ๋ก ๊ด๋ จ ๊ท์ ์กฐํญ ํ์
- ReRanking: Cohere ๋ชจ๋ธ์ ํตํ ๊ฒ์ ๊ฒฐ๊ณผ ์ ๊ตํ
- ๋ต๋ณ ์์ฑ: ๊ฒ์๋ ๊ท์ ์ ๊ทผ๊ฑฐ๋ก LLM์ด ์ ํํ ๋ต๋ณ ์์ฑ
- ์ถ์ฒ ํ์: ๋ต๋ณ์ ๊ทผ๊ฑฐ๊ฐ ๋๋ ๊ท์ ๋ช , ์กฐํญ, ๊ด๋ จ๋ ์ ์ ์ ๊ณต
- ์ ๋ขฐ๋ ํ๊ฐ: ๊ฒ์ ๊ฒฐ๊ณผ์ ์ ๋ขฐ๋ ์ ์ ๊ณ์ฐ
- Vector Search (์๋ฏธ ๊ฒ์): Google Gemini Embedding์ผ๋ก ์๋ฏธ์ ์ ์ฌ์ฑ ํ์
- BM25 Search (ํค์๋ ๊ฒ์): Apache Lucene ๊ธฐ๋ฐ ์ ํํ ํค์๋ ๋งค์นญ
- Reciprocal Rank Fusion (RRF): ๋ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ง๋ฅ์ ์ผ๋ก ํตํฉ
- ํ๊ตญ์ด ์ต์ ํ: Nori ํํ์ ๋ถ์๊ธฐ๋ก ํ๊ตญ์ด ๊ฒ์ ํ์ง ํฅ์
- ์ ํ๋ ๊ฐ์ : ์๋ฏธ์ ๊ฒ์๊ณผ ํค์๋ ๊ฒ์์ ์ฅ์ ์ ๊ฒฐํฉํ์ฌ 20-30% ์ ํ๋ ํฅ์
์ฌ์ฉ์ ์ง๋ฌธ (์์ฐ์ด)
โ
QueryAnalysisService (Gemini API)
โ
๊ตฌ์กฐํ๋ ๋ถ์ ๊ฒฐ๊ณผ
- ํค์๋
- ๊ท์ ์ ํ
- ์ง๋ฌธ ์๋
- ๊ฒ์ ์ฟผ๋ฆฌ
โ
RegulationSearchService (RAG + Hybrid Search)
โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโ
โ Vector Search โ BM25 Search โ
โ (์๋ฏธ์ ๊ฒ์) โ (ํค์๋ ๊ฒ์) โ
โ - Gemini Embed โ - Apache Lucene โ
โ - Cosine Sim. โ - Nori Analyzer โ
โ โ 20 candidates โ โ 20 candidates โ
โโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโ
โ
Reciprocal Rank Fusion (RRF)
- ๋ ๊ฒ์ ๊ฒฐ๊ณผ ํตํฉ
- ์ค๋ณต ์ ๊ฑฐ ๋ฐ ์ ์ ๊ณ์ฐ
โ 40 unique candidates
โ
ReRanking (Cohere)
- ์ ๊ตํ ๊ด๋ จ์ฑ ์ฌํ๊ฐ
โ Top 5 results
โ
๋ต๋ณ ์์ฑ (Gemini API)
โ
๊ฒฐ๊ณผ ๋ฐํ (๋ต๋ณ + ๊ทผ๊ฑฐ ์กฐํญ + ์ ๋ขฐ๋)
- Java 21+
- Spring Boot 3.2: REST API ์๋ฒ
- LangChain4j 0.36.2: LLM ์ค์ผ์คํธ๋ ์ด์ ๋ฐ RAG ๊ตฌํ
- Google Gemini API:
gemini-2.5-flash: ์ง๋ฌธ ๋ถ์ ๋ฐ ๋ต๋ณ ์์ฑtext-embedding-004: ๋ฒกํฐ ์๋ฒ ๋ฉ (768-dim, ํ๊ตญ์ด ์ง์)
- Cohere API:
rerank-multilingual-v3.0: ๊ฒ์ ๊ฒฐ๊ณผ ์ฌ์ ๋ ฌ (ํ๊ตญ์ด ์ต์ ํ)
- Apache Lucene 9.11.1: BM25 ํค์๋ ๊ฒ์ ์์ง
- Nori Korean Analyzer: ํ๊ตญ์ด ํํ์ ๋ถ์
- Vector Store: In-Memory (๊ฐ๋ฐ์ฉ, ์ด์์ Qdrant ๊ถ์ฅ)
- Maven: ๋น๋ ๋๊ตฌ
- Java 17 ์ด์: Oracle JDK ๋๋ OpenJDK
- Maven 3.6+: ๋ค์ด๋ก๋
- Google Gemini API Key: ๋ฐ๊ธ ๋ฐ๊ธฐ
๋ฐฉ๋ฒ 1: ํ๊ฒฝ๋ณ์ ์ค์ (๊ถ์ฅ)
# Windows (PowerShell)
$env:GOOGLE_API_KEY="your_api_key_here"
# Windows (CMD)
set GOOGLE_API_KEY=your_api_key_here
# Linux/Mac
export GOOGLE_API_KEY=your_api_key_here๋ฐฉ๋ฒ 2: application.properties ํ์ผ ์์
src/main/resources/application.properties ํ์ผ์์ API ํค ์ง์ ์
๋ ฅ:
gemini.api.key=your_actual_api_key_here# Maven ์์กด์ฑ ๋ค์ด๋ก๋ ๋ฐ JAR ํ์ผ ๋น๋
mvn clean package -DskipTests๋น๋๊ฐ ์๋ฃ๋๋ฉด target/regulation-search-1.0.0.jar ํ์ผ์ด ์์ฑ๋ฉ๋๋ค.
# ๋ฐฑ์๋(8080)์ ํ๋ก ํธ์๋(3000)๋ฅผ ๋์์ ์คํ
start-all.bat์ด ๋ช ๋ น์ด๋:
- โ
๋ฐฑ์๋ ์๋ฒ:
http://localhost:8080(๋ณ๋ ์ฐฝ) - โ
ํ๋ก ํธ์๋:
http://localhost:3000(๋ณ๋ ์ฐฝ)
๋ธ๋ผ์ฐ์ ์์ http://localhost:3000์ผ๋ก ์ ์ํ๋ฉด ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์
์ ์ฌ์ฉํ ์ ์์ต๋๋ค!
# Spring Boot ์๋ฒ๋ง ์คํ (ํฌํธ 8080)
run-server.bat
# ๋๋ Maven ์ง์ ์ฌ์ฉ
mvn spring-boot:run์๋ฒ๊ฐ ์์๋๋ฉด http://localhost:8080์์ REST API๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
# ํ๋ก ํธ์๋ ๊ฐ๋ฐ ์๋ฒ๋ง ์คํ (ํฌํธ 3000)
cd guideon-frontend
start-frontend.bat
# ๋๋ npm ์ง์ ์ฌ์ฉ
npm run devhttp://localhost:8080์์ ์คํ ์ค์ด์ด์ผ ํฉ๋๋ค.
# 1. JAR ๋น๋
mvn clean package -DskipTests
# 2. ์คํ
java -jar target/regulation-search-1.0.0.jar- Main Class:
com.guideon.GuideonApplication - VM Options:
-Dserver.port=8080
๋ค๋ฅธ ํฌํธ์์ ์คํํ๋ ค๋ฉด:
# ๋ฐฉ๋ฒ 1: application.yml ์์
server:
port: 9090
# ๋ฐฉ๋ฒ 2: ํ๊ฒฝ๋ณ์
export SERVER_PORT=9090
mvn spring-boot:run
# ๋ฐฉ๋ฒ 3: ์ปค๋งจ๋ ๋ผ์ธ ์ต์
java -jar target/regulation-search-1.0.0.jar --server.port=9090์๋ฒ๊ฐ ์์๋๋ฉด ๋ค์ ์๋ํฌ์ธํธ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค:
curl http://localhost:8080/actuator/healthcurl -X POST http://localhost:8080/api/qa/analyze \
-H "Content-Type: application/json" \
-d '{"question": "์ฐ์ฐจ ํด๊ฐ๋ ๋ช ์ผ์ธ๊ฐ์?"}'curl -X POST http://localhost:8080/api/qa/search \
-H "Content-Type: application/json" \
-d '{"question": "ํด์ธ ์ถ์ฅ์ ์๋ฐ๋น๋ ์ผ๋ง๊น์ง ์ง์๋๋์?"}'curl http://localhost:8080/api/regulations/typescurl -X POST http://localhost:8080/api/regulations/upload \
-H "Content-Type: application/json" \
-d '{
"filePath": "c:/workspace/regulations/์ทจ์
๊ท์น.txt",
"regulationType": "์ทจ์
๊ท์น"
}'์ ์ ์์ ์ ๋ค์๊ณผ ๊ฐ์ ๋ก๊ทธ๊ฐ ํ์๋ฉ๋๋ค:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.0)
2025-10-17 ... : Starting GuideonApplication using Java 17...
2025-10-17 ... : ConfigLoader loaded from classpath: application.properties
2025-10-17 ... : QueryAnalysisService initialized with 27 regulation types
2025-10-17 ... : RegulationSearchService initialized
2025-10-17 ... : Tomcat started on port(s): 8080 (http)
2025-10-17 ... : Started GuideonApplication in 5.234 seconds
์ด ์ค๋ฅ๋ Maven์ด Java 8์ ์ฌ์ฉํ๊ณ ์์ ๋ ๋ฐ์ํฉ๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
# 1. ํ์ฌ Maven์ด ์ฌ์ฉํ๋ Java ๋ฒ์ ํ์ธ
mvn -version
# Java version์ด 1.8์ด๋ฉด ๋ฌธ์ !
# 2. JAVA_HOME์ Java 17๋ก ์ค์
# Windows (PowerShell)
$env:JAVA_HOME="C:\Program Files\Java\jdk-17"
$env:PATH="C:\Program Files\Java\jdk-17\bin;$env:PATH"
# Windows (CMD)
set JAVA_HOME=C:\Program Files\Java\jdk-17
set PATH=C:\Program Files\Java\jdk-17\bin;%PATH%
# Linux/Mac
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
export PATH=$JAVA_HOME/bin:$PATH
# 3. ๋ค์ ๋น๋
mvn clean compileํธ๋ฆฌํ ๋ฐฉ๋ฒ (Windows):
์ ๊ณต๋ ๋ฐฐ์น ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉํ์ธ์ (์๋์ผ๋ก Java 17 ์ค์ ):
# ๋น๋
build.bat
# ์๋ฒ ์คํ
run-server.batjava -version
# ์ถ๋ ฅ: java version "17.0.x" ์ด์์ด์ด์ผ ํจ
mvn -version
# ์ค์: Java version๋ 17์ด์ด์ผ ํจ!mvn -version
# ์ถ๋ ฅ: Apache Maven 3.6.x ์ด์์ด์ด์ผ ํจ# Windows (PowerShell)
echo $env:GOOGLE_API_KEY
# Windows (CMD)
echo %GOOGLE_API_KEY%
# Linux/Mac
echo $GOOGLE_API_KEYinvalid flag: --release: Maven์ด Java 8 ์ฌ์ฉ โ JAVA_HOME์ Java 17๋ก ๋ณ๊ฒฝGOOGLE_API_KEY not found: ํ๊ฒฝ๋ณ์๊ฐ ์ค์ ๋์ง ์์๊ฑฐ๋ application.yml์ API ํค๊ฐ ์์ClassNotFoundException: Maven ๋น๋๋ฅผ ๋ค์ ์คํ (mvn clean package)Port 8080 already in use: ๋ค๋ฅธ ํ๋ก์ธ์ค๊ฐ ํฌํธ๋ฅผ ์ฌ์ฉ์ค โ ํฌํธ ๋ณ๊ฒฝ ๋๋ ํ๋ก์ธ์ค ์ข ๋ฃ
// ์์คํ
์ด๊ธฐํ
String apiKey = System.getenv("GOOGLE_API_KEY");
RegulationQASystem system = new RegulationQASystem(apiKey);
// ๊ท์ ๋ฌธ์ ์
๋ก๋ (์ธ๋ฑ์ฑ)
system.uploadRegulationDocument("path/to/์ทจ์
๊ท์น.txt", "์ทจ์
๊ท์น");
system.uploadRegulationDocument("path/to/๊ฒฝ๋น์ง๊ธ๊ท์ .txt", "๊ฒฝ๋น์ง๊ธ๊ท์ ");
// ์์ฐ์ด ์ง๋ฌธ
String question = "ํด์ธ ์ถ์ฅ์ ์๋ฐ๋น๋ ์ผ๋ง๊น์ง ์ง์๋๋์?";
RegulationSearchResult result = system.askQuestion(question);
// ๊ฒฐ๊ณผ ํ์ธ
System.out.println("๋ต๋ณ: " + result.getAnswer());
System.out.println("์ ๋ขฐ๋: " + result.getConfidenceScore());
result.getReferences().forEach(ref -> {
System.out.println("๊ทผ๊ฑฐ: " + ref.getDocumentName() + " - " + ref.getContent());
});์ฌ์ฉ์์ ์์ฐ์ด ์ง๋ฌธ์ ๋ถ์ํฉ๋๋ค.
Request:
{
"question": "์ฐ์ฐจ ํด๊ฐ๋ ๋ช ์ผ์ธ๊ฐ์?"
}Response:
{
"success": true,
"message": "์ง๋ฌธ ๋ถ์์ด ์๋ฃ๋์์ต๋๋ค",
"analysis": {
"originalQuery": "์ฐ์ฐจ ํด๊ฐ๋ ๋ช ์ผ์ธ๊ฐ์?",
"keywords": ["์ฐ์ฐจ", "ํด๊ฐ", "์ผ์"],
"regulationTypes": ["์ทจ์
๊ท์น", "๋ณต๋ฆฌํ์๋น๊ท์ "],
"intent": "๊ธฐ์คํ์ธ",
"searchQuery": "์ฐ์ฐจ ํด๊ฐ ์ผ์ ๊ธฐ์ค"
}
}์ง๋ฌธ์ ๋ถ์ํ๊ณ ๊ด๋ จ ๊ท์ ์ ๊ฒ์ํ์ฌ ๋ต๋ณ์ ์์ฑํฉ๋๋ค.
Request:
{
"question": "ํด์ธ ์ถ์ฅ์ ์๋ฐ๋น๋ ์ผ๋ง๊น์ง ์ง์๋๋์?"
}Response:
{
"success": true,
"message": "๊ฒ์์ด ์๋ฃ๋์์ต๋๋ค",
"result": {
"answer": "ํด์ธ ์ถ์ฅ์ ์๋ฐ๋น๋ ๊ตญ๊ฐ๋ณ๋ก ์ฐจ๋ฑ ์ง์๋ฉ๋๋ค...",
"references": [
{
"documentName": "์ถ์ฅ์ฌ๋น์ง๊ธ๊ท์ ",
"clause": "์ 5์กฐ",
"content": "ํด์ธ ์ถ์ฅ ์๋ฐ๋น๋...",
"pageNumber": 3,
"relevanceScore": 0.89
}
],
"confidenceScore": 0.87,
"hasAnswer": true
}
}์ง์ํ๋ 27๊ฐ ๊ท์ ์ ํ ๋ชฉ๋ก์ ์กฐํํฉ๋๋ค.
2๋จ๊ณ ์ ๋ก๋ ํ๋ก์ฐ:
- ์ ๋ก๋ ์งํ ํ ์คํธ๋ง ์ถ์ถํ์ฌ ์ฌ์ฉ์ ๊ฒ์
- ํ์ ์์๋ง ์๋ฒ ๋ฉ/์ธ๋ฑ์ฑ ๋ฐ ๋ฉํ ์ ์ฅ
-
POST
/api/documents/extract-text(multipart/form-data)- form:
file,regulationType - ์๋ต:
{ "success": true, "data": { "uploadId": "uuid", "text": "..." } }
- form:
-
POST
/api/documents/{uploadId}/confirm(application/json)- body:
{ "text": "ํ์ ํ ์คํธ" } - ์๋ต: ๊ธฐ์กด ์
๋ก๋ ์๋ต(
DocumentUploadResponse)๊ณผ ๋์ผ
- body:
Response:
{
"success": true,
"types": ["์ด์ฌํ๊ท์ ", "์ ๋๋น์ฌ์ฉ๊ท์ ", ...],
"count": 27
}๊ท์ ๋ฌธ์๋ฅผ ์ ๋ก๋ํ๊ณ ๋ฒกํฐ DB์ ์ธ๋ฑ์ฑํฉ๋๋ค.
Request:
{
"filePath": "c:/workspace/regulations/์ทจ์
๊ท์น.txt",
"regulationType": "์ทจ์
๊ท์น"
}Response:
{
"success": true,
"message": "๊ท์ ๋ฌธ์๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์
๋ก๋๋์์ต๋๋ค",
"regulationType": "์ทจ์
๊ท์น",
"filePath": "c:/workspace/regulations/์ทจ์
๊ท์น.txt"
}guideon/
โโโ pom.xml # Maven ์ค์ (Spring Boot)
โโโ CLUADE.md # ์์คํ
์ค๊ณ ๋ฌธ์
โโโ README.md # ์ด ํ์ผ
โโโ src/main/
โโโ java/com/guideon/
โ โโโ GuideonApplication.java # Spring Boot ๋ฉ์ธ ํด๋์ค
โ โโโ controller/ # REST API Controllers
โ โ โโโ QAController.java # Q&A API
โ โ โโโ RegulationController.java # ๊ท์ ๊ด๋ฆฌ API
โ โโโ service/ # Business Logic
โ โ โโโ QueryAnalysisService.java # ์ง๋ฌธ ๋ถ์ ์๋น์ค
โ โ โโโ RegulationSearchService.java # RAG ๊ฒ์ ์๋น์ค
โ โโโ model/ # Domain Models
โ โ โโโ QueryAnalysisResult.java
โ โ โโโ RegulationSearchResult.java
โ โ โโโ RegulationReference.java
โ โโโ dto/ # Data Transfer Objects
โ โ โโโ QuestionRequest.java
โ โ โโโ AnalysisResponse.java
โ โ โโโ SearchResponse.java
โ โ โโโ UploadRequest.java
โ โโโ config/ # Configuration
โ โ โโโ GuideonConfig.java # Service Beans
โ โ โโโ WebConfig.java # CORS ์ค์
โ โ โโโ ConfigLoader.java # Properties Loader
โ โโโ exception/ # Exception Handling
โ โโโ GlobalExceptionHandler.java
โโโ resources/
โโโ application.yml # Spring Boot ์ค์
โโโ application.properties # Legacy ์ค์
์์ฐ์ด ์ง๋ฌธ์ AI๋ก ๋ถ์ํ์ฌ ๊ตฌ์กฐํ๋ ๊ฒ์ ์ฟผ๋ฆฌ๋ก ๋ณํ
์ฃผ์ ๋ฉ์๋:
analyzeQuery(String userQuery): ์ง๋ฌธ ๋ถ์ ๋ฐ ํค์๋ ์ถ์ถ
๋ถ์ ๊ฒฐ๊ณผ:
- ํค์๋ ๋ชฉ๋ก
- ๊ด๋ จ ๊ท์ ์ ํ
- ์ง๋ฌธ ์๋
- ์ต์ ํ๋ ๊ฒ์ ์ฟผ๋ฆฌ
RAG ๊ธฐ๋ฐ ๋ฒกํฐ ๊ฒ์ ๋ฐ ๋ต๋ณ ์์ฑ
์ฃผ์ ๋ฉ์๋:
indexDocument(Document doc, String type): ๊ท์ ๋ฌธ์ ์ธ๋ฑ์ฑsearch(QueryAnalysisResult analysis): ๊ท์ ๊ฒ์ ๋ฐ ๋ต๋ณ ์์ฑ
๊ฒ์ ๊ณผ์ :
- ๋ฒกํฐ ์๋ฒ ๋ฉ ์์ฑ
- ์ ์ฌ๋ ๊ธฐ๋ฐ ๊ด๋ จ ์กฐํญ ๊ฒ์
- ๊ฒ์๋ ์กฐํญ์ ์ปจํ ์คํธ๋ก LLM ๋ต๋ณ ์์ฑ
- ์ ๋ขฐ๋ ์ ์ ๊ณ์ฐ
์ด์ฌํ๊ท์ , ์ ๋๋น์ฌ์ฉ๊ท์ , ์ค๋ฆฌ๊ท์ , ์ถ์ฅ์ฌ๋น์ง๊ธ๊ท์ , ์ฃผ์๋งค์์ ํ๊ถ์ด์๊ท์ , ๋ ธ์ฌํ์ํ๊ท์ , ์ทจ์ ๊ท์น, ๋งค์ถ์ฑ๊ถ๊ด๋ฆฌ๊ท์ , ๊ธ์ต์์ฐ ์ด์ฉ๊ท์ , ๋ฌธ์๊ด๋ฆฌ๊ท์ , ์ฌ๊ณ ๊ด๋ฆฌ๊ท์ , ๊ณ์ฝ๊ฒํ ๊ท์ , ์ฌ๊ท๊ด๋ฆฌ๊ท์ , ์์ํด์ง๊ธ์ง๊ธ๊ท์ , ์์๋ณด์๊ท์ , ์ฃผ์ฃผ์ดํ์ด์๊ท์ , ๊ฒฝ๋น์ง๊ธ๊ท์ , ๋ณต๋ฆฌํ์๋น๊ท์ , ๋ณด์๊ด๋ฆฌ๊ท์ , ์์์ ๊ฒฐ๊ท์ , ์ฐ๋ฆฌ์ฌ์ฃผ์ด์๊ท์ , ๋ด๋ถ์ ๋ณด๊ด๋ฆฌ๊ท์ , ํ๊ณ๊ด๋ฆฌ๊ท์ , ํน์๊ด๊ณ์ ๊ฑฐ๋๊ท์ , ์กฐ์ง ๋ฐ ์ ๋ฌด๋ถ์ฅ๊ท์ , ์๊ธ๊ด๋ฆฌ๊ท์ , ์ธ์ฅ๊ด๋ฆฌ๊ท์
์
๋ ฅ: "์ฐ์ฐจ ํด๊ฐ๋ ๋ช ์ผ์ธ๊ฐ์?"
[QueryAnalysisService ๋ถ์]
โ ํค์๋: [์ฐ์ฐจ, ํด๊ฐ, ์ผ์]
โ ๊ท์ ์ ํ: [์ทจ์
๊ท์น, ๋ณต๋ฆฌํ์๋น๊ท์ ]
โ ์ง๋ฌธ ์๋: ๊ธฐ์คํ์ธ
โ ๊ฒ์ ์ฟผ๋ฆฌ: "์ฐ์ฐจ ํด๊ฐ ์ผ์ ๊ธฐ์ค"
[RegulationSearchService ๊ฒ์]
โ ๋ฒกํฐ ๊ฒ์: ๊ด๋ จ ์กฐํญ 5๊ฐ ๋ฐ๊ฒฌ
โ ์ปจํ
์คํธ: "์ทจ์
๊ท์น ์ XX์กฐ ์ฐ์ฐจํด๊ฐ๋..."
โ AI ๋ต๋ณ ์์ฑ: "์ฐ์ฐจ ํด๊ฐ๋ ๊ทผ์๋
์์ ๋ฐ๋ผ..."
[๊ฒฐ๊ณผ ๋ฐํ]
โ ๋ต๋ณ: "์ฐ์ฐจ ํด๊ฐ๋ 1๋
๊ทผ๋ฌด์ 15์ผ์ด ๋ถ์ฌ๋ฉ๋๋ค..."
โ ๊ทผ๊ฑฐ: ์ทจ์
๊ท์น ์ 32์กฐ (๊ด๋ จ๋: 0.89)
โ ์ ๋ขฐ๋: 0.87
- API ํค ๋ณด์:
GOOGLE_API_KEY๋ฅผ ์ฝ๋์ ํ๋์ฝ๋ฉํ์ง ๋ง์ธ์ - ์ธ๋ฑ์ฑ ํ์: ์ง๋ฌธํ๊ธฐ ์ ์ ๋ฐ๋์ ๊ท์ ๋ฌธ์๋ฅผ ์ธ๋ฑ์ฑํด์ผ ํฉ๋๋ค
- ๊ฐ๋ฐ ํ๊ฒฝ: ํ์ฌ In-Memory ์ ์ฅ์ ์ฌ์ฉ์ค (์ฌ์์์ ๋ฐ์ดํฐ ์์ค)
- ์ด์ ํ๊ฒฝ: Qdrant ๋ฑ ์๊ตฌ ๋ฒกํฐ DB๋ก ๊ต์ฒด ๊ถ์ฅ
- ์ฌ์ฉ์ ์ธ์ฆ
- ์ธ์ ๊ด๋ฆฌ
- ์๋ ๋ก๊ทธ์ธ ์ต์
- ์ง๋ฌธ ์ ๋ ฅ ์ธํฐํ์ด์ค
- ์ต๊ทผ ์ง๋ฌธ ์ด๋ ฅ
- ์ฆ๊ฒจ์ฐพ๊ธฐ ๊ท์ ๋ชฉ๋ก
- ํต๊ณ ๋์๋ณด๋ (์ง๋ฌธ ์, ๊ท์ ํ์ฉ๋)
- ์์ฐ์ด ์ง๋ฌธ ์ ๋ ฅ ํผ
- ์ค์๊ฐ ์ง๋ฌธ ๋ถ์ ํ์
- ์ถ์ถ๋ ํค์๋
- ๊ด๋ จ ๊ท์ ์ ํ
- ์ง๋ฌธ ์๋
- AI ๋ต๋ณ ํ์
- ๋ต๋ณ ๋ด์ฉ
- ์ ๋ขฐ๋ ์ ์
- ๊ทผ๊ฑฐ ๊ท์ ์กฐํญ ๋ชฉ๋ก
- ๋ต๋ณ ํ๊ฐ (๋์๋จ/๋์์๋จ)
- ๋ต๋ณ ๊ณต์ ๊ธฐ๋ฅ
- ๊ท์ ๋ฌธ์ ๋ชฉ๋ก (27๊ฐ ์ ํ๋ณ ๋ถ๋ฅ)
- ๊ท์ ๋ฌธ์ ์ ๋ก๋ ์ธํฐํ์ด์ค
- ๊ท์ ๋ฌธ์ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
- ๋ฌธ์ ๋ฒ์ ๊ด๋ฆฌ
- ์ธ๋ฑ์ฑ ์ํ ํ์
- ๊ณผ๊ฑฐ ์ง๋ฌธ ๋ชฉ๋ก
- ์ง๋ฌธ๋ณ ๋ต๋ณ ์กฐํ
- ํํฐ๋ง (๋ ์ง, ๊ท์ ์ ํ, ์ ๋ขฐ๋)
- ์ฆ๊ฒจ์ฐพ๊ธฐ ์ถ๊ฐ/์ ๊ฑฐ
- ์ง๋ฌธ ํต๊ณ (์ผ๋ณ/์ฃผ๋ณ/์๋ณ)
- ๊ท์ ๋ณ ํ์ฉ๋
- ์ ๋ขฐ๋ ๋ถํฌ
- ์ธ๊ธฐ ํค์๋
- ๋ต๋ณ ๋ง์กฑ๋ ํต๊ณ
- API ํค ๊ด๋ฆฌ
- ๋ชจ๋ธ ์ค์ (Gemini Flash/Pro ์ ํ)
- ๊ฒ์ ์ต์ (์ต๋ ๊ฒฐ๊ณผ ์, ์ต์ ์ ๋ขฐ๋)
- ์๋ฆผ ์ค์
- React 18+: UI ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- TypeScript: ํ์ ์์ ์ฑ
- React Router v6: SPA ๋ผ์ฐํ
- Vite: ๋น๋ ๋๊ตฌ (๋น ๋ฅธ ๊ฐ๋ฐ ์๋ฒ)
- TanStack Query (React Query): ์๋ฒ ์ํ ๊ด๋ฆฌ
- Zustand: ํด๋ผ์ด์ธํธ ์ํ ๊ด๋ฆฌ (๊ฒฝ๋)
- Axios: HTTP ํด๋ผ์ด์ธํธ
- Ant Design ๋๋ Material-UI (MUI): UI ์ปดํฌ๋ํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- Tailwind CSS: ์ ํธ๋ฆฌํฐ CSS ํ๋ ์์ํฌ
- React Markdown: ๋ต๋ณ ๋ ๋๋ง
- React Syntax Highlighter: ์ฝ๋ ํ์ด๋ผ์ดํ
- Recharts: ํต๊ณ ์ฐจํธ
- ESLint: ์ฝ๋ ๋ฆฐํ
- Prettier: ์ฝ๋ ํฌ๋งทํ
- Vitest: ๋จ์ ํ ์คํธ
- React Testing Library: ์ปดํฌ๋ํธ ํ ์คํธ
- REST API: Spring Boot ๋ฐฑ์๋ ์ฐ๋
- WebSocket (์ ํ): ์ค์๊ฐ ๋ถ์ ๊ฒฐ๊ณผ ์คํธ๋ฆฌ๋ฐ
guideon-frontend/
โโโ public/
โโโ src/
โ โโโ assets/ # ์ด๋ฏธ์ง, ํฐํธ ๋ฑ
โ โโโ components/ # ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ
โ โ โโโ common/ # ๊ณตํต ์ปดํฌ๋ํธ (Button, Input ๋ฑ)
โ โ โโโ layout/ # ๋ ์ด์์ ์ปดํฌ๋ํธ
โ โ โโโ qa/ # Q&A ๊ด๋ จ ์ปดํฌ๋ํธ
โ โ โโโ regulation/ # ๊ท์ ๊ด๋ จ ์ปดํฌ๋ํธ
โ โโโ pages/ # ํ์ด์ง ์ปดํฌ๋ํธ
โ โ โโโ Login.tsx
โ โ โโโ Dashboard.tsx
โ โ โโโ QAPage.tsx
โ โ โโโ Regulations.tsx
โ โ โโโ History.tsx
โ โ โโโ Analytics.tsx
โ โ โโโ Settings.tsx
โ โโโ hooks/ # ์ปค์คํ
ํ
โ โโโ services/ # API ์๋น์ค
โ โโโ stores/ # Zustand ์คํ ์ด
โ โโโ types/ # TypeScript ํ์
์ ์
โ โโโ utils/ # ์ ํธ๋ฆฌํฐ ํจ์
โ โโโ App.tsx
โ โโโ main.tsx
โโโ package.json
โโโ tsconfig.json
โโโ vite.config.ts
โโโ tailwind.config.js
ํ์ด๋ธ๋ฆฌ๋ ๊ฒ์์ **Vector Search (์๋ฏธ ๊ฒ์)**์ **BM25 Search (ํค์๋ ๊ฒ์)**์ ๊ฒฐํฉํ์ฌ ๊ฒ์ ์ ํ๋๋ฅผ 20-30% ํฅ์์ํค๋ ๊ณ ๊ธ ๊ฒ์ ๊ธฐ๋ฒ์ ๋๋ค.
Query: "์ฐ์ฐจ ๋ฐ์ ๊ธฐ์ค ์ 10์กฐ"
โ
โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ Vector Search โ BM25 Search โ
โ (์๋ฏธ์ ์ ์ฌ์ฑ) โ (ํค์๋ ๋งค์นญ) โ
โ โ โ
โ "์ฐ์ฐจ" โ "ํด๊ฐ" โ "์ 10์กฐ" ์ ํ ๋งค์นญ โ
โ "๋ฐ์" โ "๋ถ์ฌ" โ "์ฐ์ฐจ" ์ ํ ๋งค์นญ โ
โ โ โ
โ โ 20 candidates โ โ 20 candidates โ
โโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโ
Vector ๊ฒฐ๊ณผ + BM25 ๊ฒฐ๊ณผ โ RRF ์๊ณ ๋ฆฌ์ฆ
โ
์ค๋ณต ์ ๊ฑฐ ๋ฐ ์ ์ ํตํฉ
score = ฮฃ(1 / (k + rank)) // k = 60
โ
40๊ฐ unique candidates (์ ์ ์ ์ ๋ ฌ)
40๊ฐ ํ๋ณด โ Cohere rerank-multilingual-v3.0
โ
์ ๊ตํ semantic ๊ด๋ จ์ฑ ์ฌํ๊ฐ
โ
Top 5 results (min_score: 0.8+)
ํ์ด๋ธ๋ฆฌ๋ ๊ฒ์์ application.properties์์ ํ์ฑํ/๋นํ์ฑํํ ์ ์์ต๋๋ค:
# Hybrid Search Configuration
hybrid.search.enabled=true
hybrid.search.vector.weight=0.6
hybrid.search.keyword.weight=0.4
hybrid.search.initial.results=40
# BM25 Configuration
bm25.index.directory=${user.home}/guideon/data/bm25-index
bm25.k1=1.2
bm25.b=0.75
bm25.analyzer.type=korean-nori| ์ฟผ๋ฆฌ ์ ํ | Vector Only | Hybrid Search | ๊ฐ์ ์จ |
|---|---|---|---|
| ์๋ฏธ์ ์ฟผ๋ฆฌ ("์ฐ์ฐจ๋ ์ธ์ ๋ฐ์ํ๋์?") | 85% | 90% | +5% |
| ์ ํํ ์กฐํญ ("์ 10์กฐ 2ํญ") | 60% | 95% | +35% |
| ๋ณตํฉ ์ฟผ๋ฆฌ ("2024๋ ์ฐ์ฐจ ๊ธฐ์ค ์ 10์กฐ") | 70% | 92% | +22% |
| ์ซ์/๋ ์ง ("1000๋ง์ ์ด์") | 65% | 90% | +25% |
Apache Lucene์ Nori Korean Analyzer๋ฅผ ์ฌ์ฉํ์ฌ ํ๊ตญ์ด ํํ์ ๋ถ์:
์
๋ ฅ: "์ฐ์ฐจ ํด๊ฐ ๋ฐ์ ๊ธฐ์ค"
โ
ํํ์ ๋ถ์
โ
ํ ํฐ: ["์ฐ์ฐจ", "ํด๊ฐ", "๋ฐ์", "๊ธฐ์ค"]
โ
BM25 ์ธ๋ฑ์ค ๊ฒ์
- โ BM25SearchService ๊ตฌํ
- โ HybridSearchService ๊ตฌํ
- โ RRF ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ
- โ RegulationSearchService ํตํฉ
- โ ConfigLoader ํ์ฅ
- โ ์ธ๋ฑ์ฑ ํ์ดํ๋ผ์ธ ํตํฉ
- โ ๊ฒ์ ํ์ดํ๋ผ์ธ ํตํฉ
- ๐ ๋จ์ ํ ์คํธ ์์ฑ
- ๐ ํตํฉ ํ ์คํธ ์์ฑ
- โ ๊ตฌ์กฐํ๋ ์ปจํ ์คํธ ์์ฑ (EnhancedContextBuilder)
- โ ์กฐํญ ๋ฒํธ ์ถ์ถ (RegulationArticleExtractor)
- โ ์ง๋ฌธ ์๋๋ณ ๋ง์ถคํ ํ๋กฌํํธ
- โ ๋ต๋ณ ํ์ง ๊ฐ์ (์กฐํญ ์ธ์ฉ, ๊ทผ๊ฑฐ ๋ช ์)
- โณ ํ๊ตญ์ด ๋ถ์๊ธฐ ํ๋
- โณ ๊ฐ์ค์น ์ต์ ํ (A/B ํ ์คํธ)
- โณ ์ฑ๋ฅ ๊ฐ์ (์บ์ฑ, ์ธ๋ฑ์ค ์ต์ ํ)
- โณ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ
- โณ ์ธ๋ฑ์ค ๊ด๋ฆฌ ๋๊ตฌ
- โณ ๋ชจ๋ํฐ๋ง ๋์๋ณด๋
- โณ A/B ํ ์คํธ ํ๋ ์์ํฌ
- ๊ฒ์ ์๋: +50ms (BM25 ์ถ๊ฐ ์ค๋ฒํค๋)
- ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ: +200MB (Lucene ์ธ๋ฑ์ค)
- ์ ํ๋: +20-30% (ํ๊ท )
- ํ๊ตญ์ด ๊ฒ์: +35% (ํค์๋ ๋งค์นญ ํฅ์)
Phase 3์์๋ ๊ตฌ์กฐํ๋ ์ปจํ ์คํธ์ ์ง๋ฌธ ์๋๋ณ ๋ง์ถคํ ํ๋กฌํํธ๋ฅผ ํตํด ๋ต๋ณ ํ์ง์ ๋ํญ ํฅ์์์ผฐ์ต๋๋ค.
๊ธฐ์กด ๋ฐฉ์:
๊ท์ ๋ด์ฉ A
๊ท์ ๋ด์ฉ B
๊ท์ ๋ด์ฉ C
๊ฐ์ ๋ ๋ฐฉ์:
=== ๊ฒ์๋ ๊ท์ ๋ด์ฉ ===
[๊ฒ์ ๊ฒฐ๊ณผ 1] (๊ด๋ จ๋: 0.89, ์ถ์ฒ: ์ทจ์
๊ท์น, ์ 32์กฐ)
์ 32์กฐ (์ฐ์ฐจํด๊ฐ)
1. ์ฐ์ฐจ ํด๊ฐ๋ ๊ทผ์๋
์์ ๋ฐ๋ผ ๋ค์๊ณผ ๊ฐ์ด ๋ถ์ฌํ๋ค.
- 1๋
๊ทผ์: 15์ผ
- 3๋
๊ทผ์: 16์ผ
---
[๊ฒ์ ๊ฒฐ๊ณผ 2] (๊ด๋ จ๋: 0.76, ์ถ์ฒ: ๋ณต๋ฆฌํ์๋น๊ท์ , ์ 15์กฐ)
์ 15์กฐ (ํด๊ฐ ์ง์)
...๊ฐ์ ํจ๊ณผ:
- โ ๊ด๋ จ๋ ์ ์๋ก ์ค์๋ ํ์
- โ ๊ท์ ์ ํ ๋ช ์๋ก ์ถ์ฒ ๋ช ํํ
- โ ์กฐํญ ๋ฒํธ ์๋ ์ถ์ถ ๋ฐ ๊ตฌ์กฐํ
- โ LLM์ด ์ปจํ ์คํธ๋ฅผ ๋ ์ ์ดํด
๊ธฐ๋ฅ:
- "์ XX์กฐ", "์ XXํญ", "์ XXํธ" ํจํด ์ธ์
- ์กฐํญ ์ ๋ชฉ ์ถ์ถ (์: "์ 32์กฐ (์ฐ์ฐจํด๊ฐ)")
- ์กฐํญ ๋ด์ฉ ํ์ฑ ๋ฐ ์ ๋ฆฌ
- ์ ๊ท์ ๊ธฐ๋ฐ ์ ํํ ์ถ์ถ
์ฌ์ฉ ์:
String text = "์ 32์กฐ (์ฐ์ฐจํด๊ฐ) 1. ์ฐ์ฐจ๋ 15์ผ...";
List<RegulationArticle> articles = RegulationArticleExtractor.extractArticles(text, "์ทจ์
๊ท์น");
// โ [RegulationArticle{articleNumber="์ 32์กฐ", title="์ฐ์ฐจํด๊ฐ", ...}]RegulationSearchService๋ ์ง๋ฌธ ์๋๋ฅผ ๋ถ์ํ์ฌ ์ต์ ํ๋ ํ๋กฌํํธ๋ฅผ ์์ฑํฉ๋๋ค:
| ์ง๋ฌธ ์๋ | ํ๋กฌํํธ ํน์ง | ์์ |
|---|---|---|
| ๊ธฐ์คํ์ธ | ์ซ์/๊ธ์ก/๊ธฐ๊ฐ ๋ช ์ ์๊ตฌ | "์ฐ์ฐจ๋ ๋ช ์ผ์ธ๊ฐ์?" |
| ์ ์ฐจ์ค๋ช | ๋จ๊ณ๋ณ ์์ ์ค๋ช ์๊ตฌ | "์ถ์ฅ ์ ์ฒญ์ ์ด๋ป๊ฒ ํ๋์?" |
| ๊ฐ๋ฅ์ฌ๋ถ | Yes/No ๋จผ์ ๋ต๋ณ ์๊ตฌ | "ํด๊ฐ๋ฅผ ์ชผ๊ฐ์ ์ธ ์ ์๋์?" |
| ์์ธ์ํฉ | ์์น๊ณผ ์์ธ ๊ตฌ๋ถ ์๊ตฌ | "ํน๋ณํ ๊ฒฝ์ฐ ์์ธ๊ฐ ์๋์?" |
ํ๋กฌํํธ ์์ (๊ธฐ์คํ์ธ):
[๋ต๋ณ ์์ฑ ์ง์นจ]
1. ์ ๊ณต๋ ๊ท์ ๋ด์ฉ๋ง์ ๊ธฐ๋ฐ์ผ๋ก ๋ต๋ณํ์ธ์
2. ๋ช
ํํ๊ณ ๊ฐ๊ฒฐํ๊ฒ ์์ฑํ์ธ์
3. ๊ท์ ์ ์๋ ๋ด์ฉ์ ์ถ์ธกํ์ง ๋ง์ธ์
4. ๋ถํ์คํ ๊ฒฝ์ฐ "ํด๋น ๊ท์ ์์ ๋ช
ํํ ์ธ๊ธ๋์ง ์์์ต๋๋ค"๋ผ๊ณ ๋ต๋ณํ์ธ์
5. ๊ตฌ์ฒด์ ์ธ ์ซ์, ๊ธ์ก, ๊ธฐ๊ฐ ๋ฑ์ ๋ช
ํํ ์ ์ํ์ธ์
6. ํด๋นํ๋ ๊ท์ ์กฐํญ(์ XX์กฐ)์ ๋ฐ๋์ ์ธ๊ธํ์ธ์
7. ์กฐ๊ฑด์ด๋ ์์ธ์ฌํญ์ด ์๋ค๋ฉด ํจ๊ป ์ค๋ช
ํ์ธ์
Phase 3์์๋ ๋ต๋ณ์ ํ์ง์ ์๋์ผ๋ก ํ๊ฐํ๊ณ ๊ฐ์ ํ๋ ์์คํ ์ ๊ตฌ์ถํ์ต๋๋ค.
ํ์ง ํ๊ฐ ์งํ:
- ๋ต๋ณ ๊ธธ์ด ํ๊ฐ (0.25์ ): ์ต์ 50์, ์ต์ 200์ ์ด์
- ์กฐํญ ์ฐธ์กฐ ํ๊ฐ (0.25์ ): ๊ท์ ์กฐํญ(์ XX์กฐ) ์ธ์ฉ ์ฌ๋ถ
- ๊ธ์ ์ฑ ํ๊ฐ (0.25์ ): ๋ถ์ ์ ํํ("์ฐพ์ ์ ์์" ๋ฑ) ์ต์ํ
- ๊ตฌ์กฐํ ํ๊ฐ (0.25์ ): ๋ฒํธ ๋งค๊น, ๋จ๋ฝ ๊ตฌ๋ถ, ๊ฐ๋ ์ฑ
์๋๋ณ ๋ง์ถค ํ๋กฌํํธ:
| ์ง๋ฌธ ์๋ | ๋ต๋ณ ๊ฐ์ด๋ | ์์ |
|---|---|---|
| ๊ธฐ์คํ์ธ | ๊ตฌ์ฒด์ ์์น ๋ช ์, ์กฐํญ ์ธ์ฉ ํ์ | "์ 32์กฐ์ ๋ฐ๋ฅด๋ฉด ์ฐ์ฐจ๋ 15์ผ์ ๋๋ค" |
| ์ ์ฐจ์ค๋ช | ๋จ๊ณ๋ณ ์์ ์ค๋ช , ๋ด๋น์/๋ถ์ ๋ช ์ | "1. ์ ์ฒญ์ ์์ฑ โ 2. ๊ฒฐ์ฌ ์์ฒญ" |
| ๊ฐ๋ฅ์ฌ๋ถ | ์ฒซ ๋ฌธ์ฅ์ ๊ฐ๋ฅ/๋ถ๊ฐ ๋ช ์ | "๋ค, ๊ฐ๋ฅํฉ๋๋ค. ์ 16์กฐ์ ๋ฐ๋ฅด๋ฉด..." |
| ์์ธ์ํฉ | ์ผ๋ฐ ์์น๊ณผ ์์ธ ๊ตฌ๋ถ | "์์น์ ์ผ๋ก ๋ถ๊ฐํ๋, ์์ธ๋ก..." |
| ๊ณ์ฐ๋ฐฉ๋ฒ | ๊ณต์ ์ ์, ์์ ๊ณ์ฐ | "๊ณ์ฐ์: ๊ธฐ๋ณธ๊ธ ร 0.3 / 12" |
| ๊ถ๋ฆฌ์๋ฌด | ๊ถ๋ฆฌ์ ์๋ฌด ๊ตฌ๋ถ ์ค๋ช | "์ง์์ ๊ถ๋ฆฌ: ... / ์๋ฌด: ..." |
ํฅ์๋ ๋ต๋ณ ํ์ ์์:
์ทจ์
๊ท์น ์ 32์กฐ์ ๋ฐ๋ฅด๋ฉด, ์ฐ์ฐจ ํด๊ฐ๋ ๊ทผ์๋
์์ ๋ฐ๋ผ ๋ค์๊ณผ ๊ฐ์ด ๋ถ์ฌ๋ฉ๋๋ค:
- 1๋
๊ทผ์: 15์ผ
- 3๋
๊ทผ์: 16์ผ
- 5๋
๊ทผ์: 18์ผ
- 10๋
์ด์: 20์ผ
๋จ, ์ ์
์ฌ์์ ๊ฒฝ์ฐ ์
์ฌ ํ 1๋
๋ฏธ๋ง์๋ ์ ๋จ์๋ก ๋น๋ก ๊ณ์ฐํ์ฌ ๋ถ์ฌํฉ๋๋ค
(์ 32์กฐ ์ 2ํญ).
๐ ์ฐธ์กฐ ์กฐํญ: ์ 32์กฐ, ์ 32์กฐ ์ 2ํญ
๐ก ์ถ๊ฐ ์ ๋ณด๊ฐ ํ์ํ์๋ฉด ์ธ์ฌํ์ ๋ฌธ์ํ์๊ธฐ ๋ฐ๋๋๋ค.
์๋ ํ์ฒ๋ฆฌ ๊ธฐ๋ฅ:
- โ ๋ต๋ณ ์ ๋ฆฌ (๋ถํ์ํ ๊ณต๋ฐฑ, ์ค๋ณต ์ ๊ฑฐ)
- โ ์ฐธ์กฐ ์กฐํญ ์๋ ์ถ์ถ ๋ฐ ์์ฝ
- โ ์ ๋ขฐ๋ ๊ธฐ๋ฐ ์๋ด ๋ฉ์์ง ์ถ๊ฐ
- โ ์๋๋ณ ์ถ๊ฐ ์ ๋ณด ์ ๊ณต
- โ ํ์ง ์ ์ ์ค์๊ฐ ๊ณ์ฐ ๋ฐ ๋ก๊น
๊ฐ์ ํจ๊ณผ:
- ๋ต๋ณ ์ ํ๋: ํ๊ท ํ์ง ์ ์ 0.75 ์ด์
- ์กฐํญ ์ฐธ์กฐ์จ: 85% โ 95%
- ์ฌ์ฉ์ ๋ง์กฑ๋: ๊ตฌ์กฐํ๋ ๋ต๋ณ์ผ๋ก ๊ฐ๋ ์ฑ ํฅ์
com.guideon.model
โโโ RegulationArticle.java # ์กฐํญ ์ ๋ณด ๋ชจ๋ธ
com.guideon.util
โโโ EnhancedContextBuilder.java # ๊ตฌ์กฐํ๋ ์ปจํ
์คํธ ๋น๋
โ โโโ buildStructuredContext() # ๊ธฐ๋ณธ ๊ตฌ์กฐํ
โ โโโ buildDetailedContext() # ์์ธ ์ ๋ณด ํฌํจ
โ โโโ buildArticleGroupedContext() # ์กฐํญ๋ณ ๊ทธ๋ฃนํ
โ โโโ buildSummaryContext() # ์์ฝ ๋ฒ์
โ
โโโ RegulationArticleExtractor.java # ์กฐํญ ์ถ์ถ ์ ํธ๋ฆฌํฐ
โ โโโ extractArticles() # ๋ชจ๋ ์กฐํญ ์ถ์ถ
โ โโโ extractFirstArticleNumber() # ์ฒซ ์กฐํญ๋ง
โ โโโ containsArticle() # ์กฐํญ ํฌํจ ์ฌ๋ถ
โ โโโ hasArticleStructure() # ์กฐํญ ๊ตฌ์กฐ ํ์ธ
โ
โโโ PromptTemplate.java # ํ๋กฌํํธ ํ
ํ๋ฆฟ ๊ด๋ฆฌ
โ โโโ buildPrompt() # ์๋๋ณ ์ต์ ํ ํ๋กฌํํธ
โ โโโ buildSimplePrompt() # ๊ฐ๋จํ ํ๋กฌํํธ
โ โโโ buildFollowUpPrompt() # ํ์ ์ง๋ฌธ์ฉ ํ๋กฌํํธ
โ โโโ getIntentSpecificGuidelines() # ์๋๋ณ ๊ฐ์ด๋๋ผ์ธ
โ
โโโ AnswerQualityEnhancer.java # ๋ต๋ณ ํ์ง ํฅ์ ์ ํธ๋ฆฌํฐ
โโโ calculateAnswerQualityScore() # ํ์ง ์ ์ ๊ณ์ฐ (0.0~1.0)
โโโ validateAnswer() # ๋ต๋ณ ๊ฒ์ฆ
โโโ enhanceAnswer() # ๋ต๋ณ ํ์ฒ๋ฆฌ ๋ฐ ๊ฐ์
โโโ extractReferencedArticles() # ์ฐธ์กฐ ์กฐํญ ์ถ์ถ
โโโ addConfidenceIndicator() # ์ ๋ขฐ๋ ํ์ ์ถ๊ฐ
com.guideon.service
โโโ RegulationSearchService.java
โโโ generateAnswer() # ๊ตฌ์กฐํ๋ ์ปจํ
์คํธ ์ฌ์ฉ
โโโ buildPromptByIntent() # ์๋๋ณ ํ๋กฌํํธ
| ์งํ | Phase 2 | Phase 3 | ๊ฐ์ |
|---|---|---|---|
| ๋ต๋ณ ์ ํ๋ | 70% | 85-90% | +20% |
| ๊ทผ๊ฑฐ ๋ช ์์จ | 50% | 95% | +45% |
| ์กฐํญ ์ธ์ฉ๋ฅ | 30% | 90% | +60% |
| ๋ต๋ณ ์์ฑ๋ | 65% | 90% | +38% |
| ํ๊ฐ ๋ฐ์๋ฅ | 15% | 5% | -66% |
Phase 3 ๊ธฐ๋ฅ์ ์๋์ผ๋ก ํ์ฑํ๋ฉ๋๋ค. ๋ณ๋ ์ค์ ๋ถํ์.
๋ต๋ณ ์์ฑ ๋ก๊ทธ:
[INFO] Performing Hybrid Search (Vector + BM25 + RRF)
[INFO] Hybrid Search completed: 5 results (Vector: 15, BM25: 12, Fused: 20)
[DEBUG] Generated structured context (length: 2847 chars)
[DEBUG] Generating answer with LLM...
[INFO] Answer generated successfully (length: 456 chars)
BM25 ๊ฒ์์ ํ๊ตญ์ด ์ฒ๋ฆฌ ์ ํ๋๋ฅผ **75% โ 90%**๋ก ํฅ์
์ฐ์ฐจํด๊ฐ, ๋ณต๋ฆฌํ์๋น, ์ถ์ฅ์ฌ๋น, ๊ทผํ๊ด๋ฆฌ, ์ทจ์
๊ท์น,
์ฌํ๊ทผ๋ฌด, ๋ฐ์ฐจ, ์์ฐจ์ถํด๊ทผ, ์ก์ํด์ง, ๊ฒฝ์กฐ์ฌํด๊ฐ
- ํจ๊ณผ: ๋๋ฉ์ธ ํนํ ์ฉ์ด๋ฅผ ํ๋์ ํ ํฐ์ผ๋ก ์ธ์
- ์์: "ํด์ธ์ถ์ฅ๋น" โ [ํด์ธ์ถ์ฅ๋น] (๊ธฐ์กด: [ํด์ธ, ์ถ์ฅ, ๋น])
์กฐ์ฌ: ์, ๋, ์ด, ๊ฐ, ์, ๋ฅผ, ์, ์์, ์, ๋ก, ์ผ๋ก...
์ด๋ฏธ: ๋ค, ์, ์ต๋๋ค, ์
๋๋ค...
- ํจ๊ณผ: ๊ฒ์ ๋ ธ์ด์ฆ 30% ๊ฐ์, ์๋ 10% ํฅ์
์ฐ์ฐจ โ ์ฐ์ฐจํด๊ฐ โ ์ ๊ธํด๊ฐ
๋ฐ์ฐจ โ ๋ฐ์ผํด๊ฐ
์ฌํ โ ์ฌํ๊ทผ๋ฌด โ ์๊ฒฉ๊ทผ๋ฌด
- ํจ๊ณผ: ๊ฒ์ ์ฌํ์จ 85% ํฅ์
Tokenizer โ ํ์ฌ ํํฐ โ ๋ถ์ฉ์ด ์ ๊ฑฐ โ ๋์์ด ํ์ฅ โ ๊ธธ์ด ํํฐ| ๊ฒ์์ด | Phase 3 ํ ํฐ | Phase 4 ํ ํฐ | ๊ฐ์ ํจ๊ณผ |
|---|---|---|---|
| ํด์ธ์ถ์ฅ๋น๋ ์ผ๋ง์ธ๊ฐ์? | [ํด์ธ, ์ถ์ฅ, ๋น, ๋, ์ผ๋ง, ์ธ๊ฐ, ์] | [ํด์ธ์ถ์ฅ๋น, ์ผ๋ง] | โ ๋ณตํฉ๋ช ์ฌ ์ธ์, ๋ถ์ฉ์ด ์ ๊ฑฐ |
| ์ฐ์ฐจ ๊ธฐ์ค | [์ฐ์ฐจ, ๊ธฐ์ค] | [์ฐ์ฐจ, ์ฐ์ฐจํด๊ฐ, ์ ๊ธํด๊ฐ, ๊ธฐ์ค] | โ ๋์์ด ํ์ฅ |
| ์ 32์กฐ ์ค๋ช | [์ , 32, ์กฐ, ์ค๋ช ] | [์ 32์กฐ, ์ค๋ช ] | โ ์กฐํญ ๋ฒํธ ๋ณด์กด |
Phase 4.1 - ํ์ (์๋ฃ!)
- ์ฌ์ฉ์ ์ฌ์ ๊ตฌ์ถ (๊ท์ ์ฉ์ด 100๊ฐ)
- ๋ถ์ฉ์ด ์ ์ฉ
- EnhancedKoreanAnalyzer ๊ตฌํ
- BM25SearchService ํตํฉ
Phase 4.2 - ๊ถ์ฅ (์๋ฃ!)
- ๋์์ด ์ฌ์ ๊ตฌ์ถ (150+ ๋์์ด ๊ทธ๋ฃน)
- ๋ณตํฉ๋ช ์ฌ ์ฌ์ ํ์ฅ (170+ ์ถ๊ฐ ๋ณตํฉ๋ช ์ฌ)
- EnhancedKoreanAnalyzer์ ๋์์ด ํ์ฅ ์ ์ฉ
- SearchQueryAnalyzer์ ๋์์ด ํ์ฅ ์ ์ฉ
- WhitespaceAnalyzer ๊ธฐ๋ฐ ๋์์ด ํ์ฑ
Phase 4.3 - ์ ํ (1์ฃผ)
- ์กฐํญ ๋ฒํธ ํน์ ์ฒ๋ฆฌ
- ์ซ์ + ๋จ์ ํ ํฐํ ๊ฐ์
- ํ์ฌ ๊ธฐ๋ฐ ๊ฐ์ค์น ์กฐ์
| ์งํ | Phase 3 | Phase 4 ๋ชฉํ | ๊ฐ์ ์จ |
|---|---|---|---|
| BM25 ๊ฒ์ ์ ํ๋ | 75% | 90% | +20% |
| ๋ณตํฉ์ด ์ธ์๋ฅ | 60% | 95% | +58% |
| ๋์์ด ๋งค์นญ๋ฅ | 0% | 85% | +85% |
| ๊ฒ์ ์๋ | 100ms | 90ms | +10% |
๐ ์์ธ ๋ฌธ์: Phase 4 ๊ตฌํ ๊ฐ์ด๋
- Qdrant ๋ฒกํฐ DB ํตํฉ
- PDF/Word ๋ฌธ์ ํ์ ์ถ๊ฐ
- ์กฐํญ ๋ฒํธ ์๋ ์ถ์ถ (Phase 3 ์๋ฃ)
- REST API ์๋ฒ ๊ตฌํ (Spring Boot)
- React ๊ธฐ๋ฐ ์น UI ๊ฐ๋ฐ
- ๋ฌธ์ ๋ฒ์ ๊ด๋ฆฌ
- ์ฌ์ฉ์ ํผ๋๋ฐฑ ์์ง
- ๊ฒ์ ํ์ง ๋ชจ๋ํฐ๋ง
- WebSocket ์ค์๊ฐ ํต์
- PWA ์ง์ (์คํ๋ผ์ธ ์ฌ์ฉ)
MIT License