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. 메시지가 보이면 성공입니다!