모델은 exaone3.5:2.4b 로 테스트 했으며, temperature 값을 변경할 때마다 서버를 재시작한 후 첫 요청에 대한 응답값을 포스팅 하였다. 늘 그렇듯이 모델의 응답은 정확하지는 않을 것이며 참고 정도로만 할 것이다.
오늘의 테스트 항목은 temperature 속성값이다.
xyz 사용자인증 > 주민등록번호 > 휴대폰번호 순으로 이어지는 간단한 인증 프로그램이다.
authenticate_username 도구만 사용될 것이다.
from flask import Flask, request, jsonify, render_template
from langchain.chat_models import ChatOllama
from langchain.agents import Tool, initialize_agent
from langchain.tools import tool
from langchain.memory import ConversationBufferMemory
import re
app = Flask(__name__)
# ✅ LangChain LLM 설정 (Ollama)
llm = ChatOllama(model="exaone3.5:2.4b", temperature=0.0)
# ✅ 인증 대상 사용자 목록
VALID_USERS = {
"kim": {"ssn": "121223-2234567", "phone": "010-1234-5678"},
"lee": {"ssn": "121223-1234567", "phone": "010-2234-5678"},
"park": {"ssn": "121223-3234567", "phone": "010-0234-5678"}
}
# ✅ 대화 상태 메모리 설정
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
# ✅ 도구 정의: 사용자명 인증
@tool
def authenticate_username(input: str) -> str:
"""
사용자명을 입력하면 등록 여부를 확인하고 상태를 갱신합니다.
예시 : lee 사용자인증
"""
match = re.search(r"[가-힣a-zA-Z0-9_]+", input)
if not match:
return "사용자명을 추출할 수 없습니다. 다시 입력해주세요."
username = match.group(0).lower()
if username in VALID_USERS:
memory.chat_memory.add_user_message("STEP:USERNAME_OK")
memory.chat_memory.add_user_message(f"CURRENT_USER:{username}")
return f"{username}님이 확인되었습니다. 주민등록번호를 입력해주세요. (예: 123456-1234567)"
else:
return f"{username}은(는) 등록되지 않은 사용자입니다."
# ✅ 도구 정의: 주민등록번호 인증
@tool
def authenticate_ssn(input: str) -> str:
"""
주민등록번호를 입력하면 등록 여부를 확인하고 상태를 갱신합니다.
예시 : 123456-1234567 주민등록번호인증
"""
match = re.search(r"\d{6}-\d{7}", input)
if not match:
return "주민등록번호를 추출할 수 없습니다. 다시 입력해주세요."
ssn = match.group(0)
# 대화 히스토리에서 CURRENT_USER 찾기
history = memory.load_memory_variables({})["chat_history"]
user_match = re.search(r"CURRENT_USER:(\w+)", str(history))
if not user_match:
return "현재 사용자 정보가 없습니다. 사용자명부터 인증해주세요."
username = user_match.group(1)
# 인증 대상 이름의 주민등록번호와 일치하는지 확인
if VALID_USERS.get(username, {}).get("ssn") == ssn:
memory.chat_memory.add_user_message("STEP:SSN_OK")
memory.chat_memory.add_user_message(f"CURRENT_SSN:{ssn}")
return f"{ssn} 주민등록번호가 확인되었습니다. 휴대폰번호를 입력해주세요. (예: 010-1234-5678)"
else:
return f"{ssn} 주민등록번호는 등록되지 않은 번호입니다."
# ✅ 도구 정의: 휴대폰번호 인증
@tool
def authenticate_phone(input: str) -> str:
"""
휴대폰번호를 입력하면 등록 여부를 확인하고 상태를 갱신합니다.
예시 : 010-1234-5678 휴대폰번호인증
"""
match = re.search(r"\d{3}-\d{4}-\d{4}", input)
if not match:
return "휴대폰번호를 추출할 수 없습니다. 다시 입력해주세요."
phone = match.group(0)
# 대화 히스토리에서 CURRENT_USER 찾기
history = memory.load_memory_variables({})["chat_history"]
user_match = re.search(r"CURRENT_USER:(\w+)", str(history))
if not user_match:
return "현재 사용자 정보가 없습니다. 사용자명부터 인증해주세요."
username = user_match.group(1)
# 인증 대상 이름의 휴대폰번호와 일치하는지 확인
if VALID_USERS.get(username, {}).get("phone") == phone:
memory.chat_memory.add_user_message("STEP:PHONE_OK")
memory.chat_memory.add_user_message(f"CURRENT_PHONE:{phone}")
return f"{phone} 휴대폰번호가 확인되었습니다. 인증이 완료되었습니다. 축하합니다."
else:
return f"{phone} 휴대폰번호는 등록되지 않은 번호입니다."
# ✅ 도구 목록
tools = [
Tool.from_function(authenticate_username, name="authenticate_username", description="사용자명을 인증합니다."),
Tool.from_function(authenticate_ssn, name="authenticate_ssn", description="주민등록번호를 인증합니다."),
Tool.from_function(authenticate_phone, name="authenticate_phone", description="휴대폰번호를 인증합니다.")
]
# ✅ LangChain ReAct Agent 생성
agent = initialize_agent(
tools=tools,
llm=llm,
agent="zero-shot-react-description",
memory=memory,
verbose=True,
handle_parsing_errors=True
)
# ✅ 웹 라우팅
@app.route("/")
def index():
return render_template("index.html") # HTML UI가 있는 경우
@app.route("/mcp-auth", methods=["POST"])
def mcp_auth():
user_input = request.json.get("message", "")
if not user_input:
return jsonify({"error": "메시지가 제공되지 않았습니다"}), 400
try:
response = agent.run(user_input)
return jsonify({"response": response})
except Exception as e:
return jsonify({"error": str(e)}), 500
# ✅ 서버 실행
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=5001)
authenticate_username 도구만 가지고 테스트 해보겠다.
"XXX 사용자인증" 이라고 요청을 내려보겠다.
나름 깔끔한 응답을 해줬다.
잡설이 좀 늘었지만 이해할 수준의 문장이다.
0.1 보다는 깔끔하지만 0.0에 비하면 좀 조잡한 응답이다.
음... 잘 모르겠다.
점점 잡소리를 많이 갈아넣는듯...
0.0 을 제외한 나머지는 적당량의 잡소리를 섞어가며 응답을 해줬는데 그마저도 거의 랜덤이었다. 검색을 좀 해보니 0에 가까울수록 직관적이며 1에 가까울수록 창의적(인 잡소리)으로 응답을 해준다고 한다.
값 범위 | 의미 | 사용 가능 여부 |
---|---|---|
0 | 완전히 결정적 (무작위 없음) | ✅ 예 |
0 ~ 1 | 일반적인 무작위성 조절 | ✅ 예 |
>1 | 매우 무작위함 | ⚠️ 가능하지만 품질 저하 |
< 0 (음수) | 수학적으로 무의미 / 에러 | ❌ 불가능 |