617 lines
24 KiB
Plaintext
617 lines
24 KiB
Plaintext
2026-01-28 mistral LLM 실행
|
|
대화 전체 내용을 정리하여 [Mistral AI 기반 Text-to-SQL 시스템 구축 및 트러블슈팅 보고서] 형식으로 문서화해 드립니다.
|
|
|
|
이 문서는 프로젝트의 구조, 설치 방법, 그리고 우리가 해결했던 주요 에러와 해결책을 포함합니다. 나중에 유지보수하거나 다시 세팅할 때 참고하시면 유용합니다.
|
|
|
|
📄 Mistral AI 기반 자연어 DB 조회 시스템 구축 문서
|
|
1. 프로젝트 개요
|
|
목표: 델파이(Delphi) 클라이언트에서 자연어로 질문하면, 우분투 서버의 AI(Mistral)가 이를 SQL로 변환하여 윈도우의 MariaDB 데이터를 조회해 답변하는 시스템 구축.
|
|
|
|
핵심 기능:
|
|
|
|
자연어 의도 파악 (일반 대화 vs DB 조회).
|
|
|
|
DB 스키마 기반 SQL 자동 생성.
|
|
|
|
생성된 SQL 정제 및 실행.
|
|
|
|
조회 결과를 텍스트로 요약하여 반환.
|
|
|
|
2. 시스템 구성도
|
|
Client: Delphi (Windows/Android/iOS) - REST API 요청 전송.
|
|
|
|
AI Server: Ubuntu Linux (FastAPI, Mistral-7B Model, Python 3.10).
|
|
|
|
Database: Windows PC (MariaDB 10.x, Port 3306).
|
|
|
|
3. 구축 과정 및 핵심 코드
|
|
A. 데이터베이스 설정 (Windows)
|
|
방화벽 개방: Windows Defender 방화벽 > 인바운드 규칙 > TCP 3306 포트 허용.
|
|
|
|
계정 권한: 외부(우분투)에서 접속 가능한 계정 사용.
|
|
|
|
사용자: qsentech (또는 ai_readonly)
|
|
|
|
호스트: % (모든 IP 허용) 또는 우분투 IP 지정.
|
|
|
|
B. 파이썬 서버 설정 (Ubuntu)
|
|
주요 라이브러리: fastapi, uvicorn, mistral_common, mistral_inference, pymysql
|
|
|
|
스키마 추출: make_schema.py를 통해 DB 테이블 정보를 텍스트로 추출하여 프롬프트에 주입.
|
|
|
|
최종 서버 코드 (serve_mistral.py 요약):
|
|
|
|
get_router_prompt: 사용자 질문이 SQL 생성용인지 일반 대화인지 분류.
|
|
|
|
get_sql_prompt: 실제 DB 스키마 정보를 AI에게 제공하고 SQL 생성 규칙 명시.
|
|
|
|
SQL 정제: 정규표현식(re.search)을 사용하여 AI의 사족을 제거하고 순수 SQL만 추출.
|
|
|
|
동적 결과 처리: 컬럼명이 바뀌어도 대응하도록 dict 반복문으로 결과 포맷팅.
|
|
|
|
C. 델파이 클라이언트 설정
|
|
컴포넌트: TRESTClient, TRESTRequest, TRESTResponse.
|
|
|
|
비동기 처리: 모바일 앱 멈춤 방지를 위해 ExecuteAsync 사용.
|
|
|
|
네트워크: Android 설정에서 CleartextTraffic 허용 (HTTP 통신).
|
|
|
|
4. 트러블슈팅 로그 (Troubleshooting Log)
|
|
개발 과정에서 발생한 주요 에러와 해결 방법입니다.
|
|
|
|
🔴 1. Python 라이브러리 버전 충돌
|
|
증상: ImportError: cannot import name 'ChatCompletionRequest', 'dict' object has no attribute 'messages'
|
|
|
|
원인: mistral_common 라이브러리 버전 차이로 클래스 위치 변경 및 객체 타입 불일치.
|
|
|
|
해결:
|
|
|
|
try-except 구문으로 여러 경로에서 import를 시도하도록 호환성 코드 작성.
|
|
|
|
단순 dict 대신 ChatCompletionRequest 객체를 명시적으로 생성하여 전달.
|
|
|
|
🔴 2. 데이터베이스 연결 실패
|
|
증상: (2003, "Can't connect to MySQL server on 'localhost' ([Errno 111] Connection refused)")
|
|
|
|
원인:
|
|
|
|
우분투에서 localhost는 우분투 자신을 의미함 (DB는 윈도우에 있음).
|
|
|
|
33063 등 잘못된 포트 번호 사용.
|
|
|
|
윈도우 방화벽이 3306 포트를 차단함.
|
|
|
|
해결:
|
|
|
|
DB_HOST를 윈도우의 실제 IP (192.168.0.x)로 변경.
|
|
|
|
DB_PORT를 표준 포트 3306으로 수정.
|
|
|
|
윈도우 방화벽 인바운드 규칙 추가.
|
|
|
|
🔴 3. DB 권한 및 계정 오류
|
|
증상: Error 1396 (Create User Failed), Error 1044 (Access Denied)
|
|
|
|
원인: 권한이 부족한 계정(qsentech)으로 새로운 유저(ai_readonly)를 생성하려 함.
|
|
|
|
해결: 기존에 권한이 있는 qsentech 계정을 그대로 사용하여 파이썬 코드 설정.
|
|
|
|
🔴 4. SQL 생성 및 실행 로직 오류
|
|
증상: KeyError: 'name' (없는 컬럼 참조), SQL 문법 오류.
|
|
|
|
원인:
|
|
|
|
AI가 SELECT price...만 했는데 코드는 row['name']을 찾음.
|
|
|
|
AI가 "Here is the SQL..." 같은 설명글을 같이 보내서 SQL 실행 실패.
|
|
|
|
해결:
|
|
|
|
결과 처리 로직을 for k, v in row.items()로 변경하여 동적 처리.
|
|
|
|
정규표현식 r"```(sql)?(.*?)```"을 적용하여 SQL 코드 블록만 추출.
|
|
|
|
5. 최종 결과물 (Server Code)
|
|
아래는 모든 수정 사항(포트, IP, 정규표현식, 경고 무시 등)이 반영된 최종 파이썬 서버 코드입니다.
|
|
|
|
Python
|
|
import warnings
|
|
# 불필요한 경고 메시지 숨김
|
|
warnings.filterwarnings("ignore", category=FutureWarning)
|
|
|
|
from fastapi import FastAPI
|
|
from pydantic import BaseModel
|
|
from mistral_inference.transformer import Transformer
|
|
from mistral_inference.generate import generate
|
|
from mistral_common.tokens.tokenizers.mistral import MistralTokenizer
|
|
from pathlib import Path
|
|
import uvicorn
|
|
import pymysql
|
|
import re
|
|
|
|
# ==========================================
|
|
# [설정] 데이터베이스 연결 정보
|
|
# ==========================================
|
|
DB_HOST = "192.168.0.15" # 윈도우 PC의 IP (ipconfig로 확인)
|
|
DB_PORT = 3306 # MariaDB 기본 포트
|
|
DB_USER = "qsentech" # DB 계정
|
|
DB_PASSWORD = "비밀번호" # DB 비밀번호
|
|
DB_NAME = "paradise" # DB 이름
|
|
|
|
# ==========================================
|
|
# [초기화] 모델 로드 및 라이브러리 호환성 처리
|
|
# ==========================================
|
|
try:
|
|
from mistral_common.protocol.instruct.messages import ChatCompletionRequest, UserMessage, SystemMessage
|
|
except ImportError:
|
|
try:
|
|
from mistral_common.protocol.instruct.request import ChatCompletionRequest
|
|
from mistral_common.protocol.instruct.messages import UserMessage, SystemMessage
|
|
except ImportError:
|
|
import mistral_common.protocol.instruct.messages as msg_module
|
|
UserMessage = msg_module.UserMessage
|
|
SystemMessage = msg_module.SystemMessage
|
|
pass
|
|
|
|
mistral_models_path = Path.home().joinpath('mistral_models', '7B-Instruct-v0.3')
|
|
tokenizer_path = mistral_models_path / "tokenizer.model.v3"
|
|
|
|
print("=== 모델 로딩 중 ===")
|
|
tokenizer = MistralTokenizer.from_file(str(tokenizer_path))
|
|
model = Transformer.from_folder(mistral_models_path)
|
|
print("=== 준비 완료 ===")
|
|
|
|
app = FastAPI()
|
|
|
|
class ChatRequest(BaseModel):
|
|
prompt: str
|
|
max_tokens: int = 512
|
|
|
|
# ==========================================
|
|
# [Helper] Mistral AI 호출 함수
|
|
# ==========================================
|
|
def ask_mistral(messages, max_tokens=512, temperature=0.7):
|
|
chat_request = ChatCompletionRequest(messages=messages)
|
|
tokens = tokenizer.encode_chat_completion(chat_request).tokens
|
|
out_tokens, _ = generate(
|
|
[tokens], model, max_tokens=max_tokens, temperature=temperature, eos_id=tokenizer.instruct_tokenizer.tokenizer.eos_id
|
|
)
|
|
return tokenizer.instruct_tokenizer.tokenizer.decode(out_tokens[0])
|
|
|
|
# ==========================================
|
|
# [Prompt] 프롬프트 정의
|
|
# ==========================================
|
|
def get_router_prompt(user_input):
|
|
return f"""
|
|
Classify the user input: 'SQL' or 'GENERAL'.
|
|
- 'SQL': asking for price, stock, user list, database search.
|
|
- 'GENERAL': greeting, generic questions.
|
|
User Input: {user_input}
|
|
Category:
|
|
"""
|
|
|
|
def get_sql_prompt():
|
|
# make_schema.py로 추출한 스키마 정보를 포함
|
|
return """
|
|
You are a SQL expert. Convert the user's question into a MariaDB SQL query.
|
|
|
|
[Schema]
|
|
Table: tbl_user_info
|
|
- userId (varchar): User ID
|
|
- userName (varchar): User Name
|
|
- ... (생략된 스키마 정보) ...
|
|
|
|
[Rules]
|
|
1. Output ONLY the SQL query inside a code block (```sql ... ```).
|
|
2. Use SELECT statement only.
|
|
"""
|
|
|
|
def get_general_prompt():
|
|
return "You are a helpful AI assistant. Answer kindly in Korean."
|
|
|
|
# ==========================================
|
|
# [DB] SQL 실행 함수
|
|
# ==========================================
|
|
def execute_sql_query(sql: str):
|
|
try:
|
|
conn = pymysql.connect(
|
|
host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASSWORD,
|
|
database=DB_NAME, charset='utf8mb4',
|
|
cursorclass=pymysql.cursors.DictCursor
|
|
)
|
|
with conn:
|
|
with conn.cursor() as cursor:
|
|
cursor.execute(sql)
|
|
result = cursor.fetchall()
|
|
return list(result)
|
|
except Exception as e:
|
|
return f"SQL_ERROR: {str(e)}"
|
|
|
|
# ==========================================
|
|
# [API] 메인 엔드포인트
|
|
# ==========================================
|
|
@app.post("/chat")
|
|
async def chat_endpoint(request: ChatRequest):
|
|
try:
|
|
user_input = request.prompt
|
|
print(f">> 질문: {user_input}")
|
|
|
|
# 1. 의도 파악
|
|
router_messages = [UserMessage(content=get_router_prompt(user_input))]
|
|
intent = ask_mistral(router_messages, max_tokens=10, temperature=0.1).strip().upper()
|
|
|
|
# 2. SQL 처리
|
|
if "SQL" in intent:
|
|
sql_messages = [
|
|
SystemMessage(content=get_sql_prompt()),
|
|
UserMessage(content=f"Question: {user_input}\nSQL Query:")
|
|
]
|
|
generated_text = ask_mistral(sql_messages, max_tokens=256, temperature=0.1)
|
|
|
|
# 정규표현식으로 SQL 추출
|
|
match = re.search(r"```(sql)?(.*?)```", generated_text, re.DOTALL | re.IGNORECASE)
|
|
if match:
|
|
clean_sql = match.group(2).strip()
|
|
else:
|
|
clean_sql = generated_text.strip()
|
|
if "select" in clean_sql.lower():
|
|
clean_sql = clean_sql[clean_sql.lower().find("select"):]
|
|
|
|
if ";" in clean_sql:
|
|
clean_sql = clean_sql.split(";")[0] + ";"
|
|
|
|
print(f">> 추출된 SQL: {clean_sql}")
|
|
|
|
if not clean_sql.lower().startswith("select"):
|
|
return {"response": "SQL 생성 실패"}
|
|
|
|
db_result = execute_sql_query(clean_sql)
|
|
|
|
# 결과 포맷팅
|
|
if isinstance(db_result, list):
|
|
if not db_result:
|
|
return {"response": "검색 결과가 없습니다."}
|
|
else:
|
|
final_response = f"[검색 결과]\n"
|
|
for row in db_result:
|
|
row_text = ", ".join([f"{k}: {v}" for k, v in row.items()])
|
|
final_response += f"- {row_text}\n"
|
|
return {"response": final_response}
|
|
else:
|
|
return {"response": f"DB 오류: {db_result}"}
|
|
|
|
# 3. 일반 대화
|
|
else:
|
|
chat_messages = [SystemMessage(content=get_general_prompt()), UserMessage(content=user_input)]
|
|
answer = ask_mistral(chat_messages)
|
|
return {"response": answer}
|
|
|
|
except Exception as e:
|
|
print(f"Server Error: {e}")
|
|
return {"response": f"시스템 오류: {str(e)}"}
|
|
|
|
if __name__ == "__main__":
|
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
|
|
|
|
|
|
========================================================================================================================
|
|
|
|
앞서 진행한 모든 대화 내용을 바탕으로, **[우분투 서버 실행 및 관리 가이드]**를 정리해 드립니다.현재 개발하신 파이썬 서버(serve_mistral.py)를 상황에 따라 실행하는 방법과, 서버를 관리(로그 확인, 종료)하는 필수 명령어 모음입니다.🐧 우분투 AI 서버 실행 및 관리 가이드0. 실행 전 필수 준비 (공통)어떤 방법으로 실행하든, 반드시 가상환경을 먼저 켜야 합니다.Bash# 1. 아나콘다 가상환경 활성화
|
|
conda activate mistral-env
|
|
|
|
# 2. 파일이 있는 폴더로 이동 (경로는 본인 환경에 맞게)
|
|
cd /home/qstai/
|
|
1. 테스트용 실행 (터미널 켜둔 상태)개발 중이거나, 에러를 눈으로 바로 확인하고 싶을 때 사용합니다.단점: 터미널 창을 닫거나 컴퓨터 연결이 끊기면 서버도 같이 꺼집니다.실행 명령어:Bashpython serve_mistral.py
|
|
종료 방법:키보드에서 Ctrl + C 를 누르면 꺼집니다.2. 백그라운드 실행 (터미널 꺼도 계속 유지)실제 서비스처럼 터미널을 꺼도 24시간 돌아가게 하려면 이 방법을 씁니다.방법 A: nohup 사용 (가장 간단, 추천 ⭐)서버를 뒷단(Background)으로 보내고, 로그는 파일로 저장하는 방식입니다.실행 명령어:Bashnohup python serve_mistral.py > server.log 2>&1 &
|
|
nohup: 터미널이 끊겨도 죽지 마라.> server.log: 화면에 나올 글자들을 server.log 파일에 적어라.&: 백그라운드에서 실행해라.잘 돌아가는지 로그 확인:Bash# 실시간으로 로그 파일 내용 보기 (Ctrl+C로 빠져나오기)
|
|
tail -f server.log
|
|
3. 서버 관리 및 종료 명령어백그라운드로 돌려놓은 서버를 찾아서 끄거나 상태를 확인할 때 씁니다.A. 서버가 켜져 있는지 확인하기프로세스 확인:Bashps -ef | grep serve_mistral.py
|
|
결과에 python serve_mistral.py가 보이면 켜져 있는 것입니다.포트 확인 (8000번):Bashsudo lsof -i :8000
|
|
B. 서버 강제 종료하기 (Kill)서버를 끄거나, 코드를 수정해서 재시작해야 할 때 사용합니다.PID(프로세스 번호) 찾기:위의 ps -ef 명령어를 쳤을 때 맨 앞에 나오는 숫자(예: 12345)를 찾습니다.죽이기 (Kill):Bashkill -9 [PID번호]
|
|
|
|
# 예시: kill -9 12345
|
|
한 방에 찾아서 죽이기 (편리함):Bashpkill -f serve_mistral.py
|
|
4. 문제 발생 시 체크리스트 (Troubleshooting)만약 서버가 실행되지 않거나 접속이 안 된다면 아래 순서대로 확인하세요.증상확인 명령어 / 해결책명령어 에러conda activate mistral-env를 했는지 확인하세요.Address already in use이미 서버가 켜져 있습니다. pkill -f serve_mistral.py로 끄고 다시 켜세요.Connection refused1. 윈도우 방화벽(3306)이 열렸는지 확인.2. 파이썬 코드의 DB_HOST IP가 윈도우 IP와 맞는지 확인.로그가 안 보임cat server.log 명령어로 저장된 로그 파일을 열어보세요.[💡 추천 운영 방식]코드 수정 후에는 **1. 테스트용 실행**으로 에러가 없는지 먼저 확인합니다.잘 되면 Ctrl+C로 끄고, **2. 백그라운드 실행(nohup)**으로 켜둡니다.델파이 앱에서 테스트합니다.로그 확인이 필요하면 tail -f server.log를 입력합니다.
|
|
|
|
|
|
|
|
=======================================================================================================================
|
|
|
|
|
|
|
|
우분투 리눅스 환경에서 파이썬 서버 파일을 수정하고 새로 만드는 방법을 단계별로 자세히 정리해 드립니다.
|
|
|
|
리눅스 터미널에서는 윈도우 메모장과 달리 nano 라는 편집기를 주로 사용합니다. 이 방법만 알면 모든 수정이 가능합니다.
|
|
|
|
1. 기본: 파일 열기 / 수정 / 저장 방법 (nano 에디터)
|
|
우분투 터미널에서 파일을 다루는 가장 쉬운 방법입니다.
|
|
|
|
파일 열기 (또는 생성):
|
|
|
|
Bash
|
|
nano 파일이름.py
|
|
예: nano serve_mistral.py
|
|
|
|
붙여넣기:
|
|
|
|
윈도우에서 코드를 복사(Ctrl+C)한 후, 터미널 화면에 대고 마우스 우클릭을 하면 붙여넣기가 됩니다.
|
|
|
|
저장하기:
|
|
|
|
키보드 Ctrl + O (알파벳 O)를 누릅니다.
|
|
|
|
아래쪽에 File Name to Write: ... 하고 파일명이 뜨면 Enter 키를 칩니다.
|
|
|
|
나가기:
|
|
|
|
키보드 Ctrl + X 를 누르면 편집기에서 빠져나옵니다.
|
|
|
|
2. 메인 서버 파일 만들기 (serve_mistral.py)
|
|
이 파일이 실제 AI 서버입니다. 기존 파일을 지우고 깨끗하게 새로 만드는 것을 추천합니다.
|
|
|
|
1) 기존 파일 삭제 (혹시 꼬였을 수 있으므로)
|
|
Bash
|
|
rm serve_mistral.py
|
|
2) 새 파일 생성 및 편집기 열기
|
|
Bash
|
|
nano serve_mistral.py
|
|
3) 코드 복사 및 붙여넣기 (최종 수정본)
|
|
아래 코드를 복사해서 붙여넣으세요. (주의: DB_HOST, DB_USER, DB_PASSWORD 부분은 본인 정보로 꼭 수정하세요!)
|
|
|
|
Python
|
|
import warnings
|
|
warnings.filterwarnings("ignore", category=FutureWarning) # 불필요한 경고 제거
|
|
|
|
from fastapi import FastAPI
|
|
from pydantic import BaseModel
|
|
from mistral_inference.transformer import Transformer
|
|
from mistral_inference.generate import generate
|
|
from mistral_common.tokens.tokenizers.mistral import MistralTokenizer
|
|
from pathlib import Path
|
|
import uvicorn
|
|
import pymysql
|
|
import re
|
|
|
|
# =========================================================
|
|
# [설정] 데이터베이스 연결 정보 (여기를 수정하세요)
|
|
# =========================================================
|
|
DB_HOST = "192.168.0.15" # 윈도우 PC의 IP 주소 (ipconfig로 확인)
|
|
DB_PORT = 3306 # MariaDB 포트 (보통 3306)
|
|
DB_USER = "qsentech" # DB 접속 아이디
|
|
DB_PASSWORD = "비밀번호" # DB 접속 비밀번호
|
|
DB_NAME = "paradise" # 사용할 데이터베이스 이름
|
|
|
|
# =========================================================
|
|
# [시스템] 모델 로드 (수정 불필요)
|
|
# =========================================================
|
|
try:
|
|
from mistral_common.protocol.instruct.messages import ChatCompletionRequest, UserMessage, SystemMessage
|
|
except ImportError:
|
|
try:
|
|
from mistral_common.protocol.instruct.request import ChatCompletionRequest
|
|
from mistral_common.protocol.instruct.messages import UserMessage, SystemMessage
|
|
except ImportError:
|
|
import mistral_common.protocol.instruct.messages as msg_module
|
|
UserMessage = msg_module.UserMessage
|
|
SystemMessage = msg_module.SystemMessage
|
|
pass
|
|
|
|
mistral_models_path = Path.home().joinpath('mistral_models', '7B-Instruct-v0.3')
|
|
tokenizer_path = mistral_models_path / "tokenizer.model.v3"
|
|
|
|
print("=== AI 모델 로딩 중... ===")
|
|
tokenizer = MistralTokenizer.from_file(str(tokenizer_path))
|
|
model = Transformer.from_folder(mistral_models_path)
|
|
print("=== AI 준비 완료 ===")
|
|
|
|
app = FastAPI()
|
|
|
|
class ChatRequest(BaseModel):
|
|
prompt: str
|
|
max_tokens: int = 512
|
|
|
|
# ---------------------------------------------------------
|
|
# [Helper] Mistral AI 호출 함수
|
|
# ---------------------------------------------------------
|
|
def ask_mistral(messages, max_tokens=512, temperature=0.7):
|
|
chat_request = ChatCompletionRequest(messages=messages)
|
|
tokens = tokenizer.encode_chat_completion(chat_request).tokens
|
|
out_tokens, _ = generate(
|
|
[tokens], model, max_tokens=max_tokens, temperature=temperature, eos_id=tokenizer.instruct_tokenizer.tokenizer.eos_id
|
|
)
|
|
return tokenizer.instruct_tokenizer.tokenizer.decode(out_tokens[0])
|
|
|
|
# ---------------------------------------------------------
|
|
# [프롬프트] AI에게 역할을 부여하는 곳
|
|
# ---------------------------------------------------------
|
|
def get_router_prompt(user_input):
|
|
return f"""
|
|
Classify the user input: 'SQL' or 'GENERAL'.
|
|
- 'SQL': asking for price, stock, user list, database search.
|
|
- 'GENERAL': greeting, generic questions.
|
|
User Input: {user_input}
|
|
Category:
|
|
"""
|
|
|
|
def get_sql_prompt():
|
|
# ★★★ 중요: make_schema.py로 뽑은 내용을 [Schema] 아래에 붙여넣어야 합니다 ★★★
|
|
return """
|
|
You are a SQL expert. Convert the user's question into a MariaDB SQL query.
|
|
|
|
[Schema]
|
|
-- 여기에 make_schema.py 실행 결과를 복사해서 붙여넣으세요 --
|
|
Table: tbl_user_info
|
|
- userName (varchar): User Name
|
|
|
|
[Rules]
|
|
1. Output ONLY the SQL query inside a code block (```sql ... ```).
|
|
2. Use SELECT statement only.
|
|
"""
|
|
|
|
def get_general_prompt():
|
|
return "You are a helpful AI assistant. Answer kindly in Korean."
|
|
|
|
# ---------------------------------------------------------
|
|
# [기능] SQL 실행 함수 (DB 연결)
|
|
# ---------------------------------------------------------
|
|
def execute_sql_query(sql: str):
|
|
try:
|
|
conn = pymysql.connect(
|
|
host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASSWORD,
|
|
database=DB_NAME, charset='utf8mb4',
|
|
cursorclass=pymysql.cursors.DictCursor
|
|
)
|
|
with conn:
|
|
with conn.cursor() as cursor:
|
|
cursor.execute(sql)
|
|
result = cursor.fetchall()
|
|
return list(result)
|
|
except Exception as e:
|
|
return f"SQL_ERROR: {str(e)}"
|
|
|
|
# ---------------------------------------------------------
|
|
# [API] 메인 엔드포인트
|
|
# ---------------------------------------------------------
|
|
@app.post("/chat")
|
|
async def chat_endpoint(request: ChatRequest):
|
|
try:
|
|
user_input = request.prompt
|
|
print(f">> 질문: {user_input}")
|
|
|
|
# 1. 의도 파악
|
|
router_messages = [UserMessage(content=get_router_prompt(user_input))]
|
|
intent = ask_mistral(router_messages, max_tokens=10, temperature=0.1).strip().upper()
|
|
|
|
# 2. SQL 처리
|
|
if "SQL" in intent:
|
|
sql_messages = [
|
|
SystemMessage(content=get_sql_prompt()),
|
|
UserMessage(content=f"Question: {user_input}\nSQL Query:")
|
|
]
|
|
generated_text = ask_mistral(sql_messages, max_tokens=256, temperature=0.1)
|
|
|
|
# 정규표현식으로 SQL만 쏙 뽑아내기
|
|
match = re.search(r"```(sql)?(.*?)```", generated_text, re.DOTALL | re.IGNORECASE)
|
|
|
|
if match:
|
|
clean_sql = match.group(2).strip()
|
|
else:
|
|
clean_sql = generated_text.strip()
|
|
if "select" in clean_sql.lower():
|
|
clean_sql = clean_sql[clean_sql.lower().find("select"):]
|
|
|
|
if ";" in clean_sql:
|
|
clean_sql = clean_sql.split(";")[0] + ";"
|
|
|
|
print(f">> 추출된 SQL: {clean_sql}")
|
|
|
|
if not clean_sql.lower().startswith("select"):
|
|
return {"response": "SQL 생성 실패: AI가 올바른 쿼리를 만들지 못했습니다."}
|
|
|
|
db_result = execute_sql_query(clean_sql)
|
|
|
|
if isinstance(db_result, list):
|
|
if not db_result:
|
|
return {"response": "검색 결과가 없습니다."}
|
|
else:
|
|
final_response = f"[검색 결과]\n"
|
|
for row in db_result:
|
|
row_text = ", ".join([f"{k}: {v}" for k, v in row.items()])
|
|
final_response += f"- {row_text}\n"
|
|
return {"response": final_response}
|
|
else:
|
|
return {"response": f"DB 오류: {db_result}"}
|
|
|
|
# 3. 일반 대화
|
|
else:
|
|
chat_messages = [SystemMessage(content=get_general_prompt()), UserMessage(content=user_input)]
|
|
answer = ask_mistral(chat_messages)
|
|
return {"response": answer}
|
|
|
|
except Exception as e:
|
|
print(f"Server Error: {e}")
|
|
return {"response": f"시스템 오류: {str(e)}"}
|
|
|
|
if __name__ == "__main__":
|
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
4) 저장 후 종료
|
|
Ctrl + O -> Enter -> Ctrl + X
|
|
|
|
3. DB 스키마 추출 도구 만들기 (make_schema.py)
|
|
DB 테이블 구조를 일일이 타이핑하지 않고 자동으로 뽑아주는 도구입니다.
|
|
|
|
1) 파일 생성
|
|
Bash
|
|
nano make_schema.py
|
|
2) 코드 붙여넣기
|
|
(여기도 DB_HOST, DB_USER 등을 본인 정보로 수정하세요)
|
|
|
|
Python
|
|
import pymysql
|
|
|
|
# [설정] 본인 환경에 맞게 수정하세요
|
|
HOST = "192.168.0.15" # 윈도우 IP
|
|
PORT = 3306
|
|
USER = "qsentech"
|
|
PASS = "비밀번호"
|
|
DB = "paradise"
|
|
|
|
# 조회할 테이블 목록
|
|
TARGET_TABLES = ["tbl_user_info", "tbl_game_info", "tbl_machine_info"]
|
|
|
|
try:
|
|
conn = pymysql.connect(host=HOST, port=PORT, user=USER, password=PASS, database=DB, charset='utf8mb4')
|
|
cursor = conn.cursor()
|
|
|
|
print("\n\n===== [아래 내용을 복사하세요] =====\n")
|
|
|
|
for table in TARGET_TABLES:
|
|
print(f"Table: {table}")
|
|
try:
|
|
# 컬럼 정보와 주석(Comment)까지 가져옴
|
|
cursor.execute(f"SHOW FULL COLUMNS FROM {table}")
|
|
columns = cursor.fetchall()
|
|
for col in columns:
|
|
# col[0]: Field, col[1]: Type, col[8]: Comment
|
|
comment = f": {col[8]}" if col[8] else ""
|
|
print(f"- {col[0]} ({col[1]}){comment}")
|
|
except Exception as e:
|
|
print(f"-- 테이블 없음: {e}")
|
|
print("")
|
|
|
|
print("===================================\n")
|
|
|
|
except Exception as e:
|
|
print(f"접속 실패: {e}")
|
|
3) 저장 후 실행
|
|
저장: Ctrl+O -> Enter -> Ctrl+X
|
|
|
|
실행:
|
|
|
|
Bash
|
|
python make_schema.py
|
|
결과 활용: 화면에 출력된 텍스트를 복사해서, serve_mistral.py의 get_sql_prompt 함수 안에 있는 [Schema] 부분에 붙여넣습니다.
|
|
|
|
4. 최종: 서버 실행하기
|
|
모든 준비가 끝났으면 서버를 실행합니다.
|
|
|
|
가상환경 켜기 (필수):
|
|
|
|
Bash
|
|
conda activate mistral-env
|
|
서버 실행 (백그라운드 모드 권장):
|
|
|
|
Bash
|
|
nohup python serve_mistral.py > server.log 2>&1 &
|
|
잘 켜졌는지 확인:
|
|
|
|
Bash
|
|
tail -f server.log
|
|
Application startup complete. 메시지가 보이면 성공입니다!
|
|
|
|
|
|
|
|
|
|
|