from langchain.chat_models import ChatOllama
from langchain.agents import initialize_agent, AgentType
from langchain.memory import ConversationBufferMemory
from langchain.tools import Tool
from langchain.prompts import MessagesPlaceholder
from langchain.schema import SystemMessage
from pydantic import BaseModel
import re
from typing import Optional
class WeatherInput(BaseModel):
location: str
# MCP 도구 데코레이터 정의
def mcp_tool(func):
def wrapper(*args, **kwargs):
print(f"\n==== MCP_TOOL '{func.__name__}' CALLED ====")
print(f"args: {args}")
print(f"kwargs: {kwargs}")
return func(*args, **kwargs)
return wrapper
# ChatOllama 인스턴스 생성 - 로컬에서 실행 중인 gemma3:12b 모델 사용
llm = ChatOllama(
model="llama3:8b"
)
# 대화 히스토리를 기억하는 메모리 설정
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=False)
# 날씨 정보를 반환하는 함수를 @mcp_tool 데코레이터로 수정
@mcp_tool
def get_weather(*args, **kwargs):
location = None
# args에서 위치 정보 추출 시도
if args and len(args) > 0:
arg_value = args[0]
if isinstance(arg_value, str):
# 괄호와 추가 설명 제거 (Seoul (assuming...) 같은 형식 처리)
clean_location = re.sub(r'\s*\([^)]*\)', '', arg_value).strip()
location = clean_location
print(f"Cleaned location from args: '{location}'")
# kwargs에서 위치 정보 추출 시도 (fallback)
if not location and kwargs:
if "location" in kwargs and kwargs["location"]:
location = kwargs["location"]
if isinstance(location, str):
location = re.sub(r'\s*\([^)]*\)', '', location).strip()
print(f"Location from kwargs: '{location}'")
elif "input" in kwargs and kwargs["input"]:
location = kwargs["input"]
if isinstance(location, str):
location = re.sub(r'\s*\([^)]*\)', '', location).strip()
print(f"Location from input: '{location}'")
# 위치 정보 검증
if not location or not isinstance(location, str) or not location.strip():
print("ERROR: No valid location found")
raise ValueError("위치 정보가 필요합니다. 어느 지역의 날씨를 알고 싶으신가요?")
# 원래 함수는 여기서 실제 날씨 정보를 반환해야 하지만, 원본 코드에서는 반환값이 없었으므로
# 실제 날씨 정보를 반환하는 예시 코드를 추가합니다.
return f"{location}의 날씨는 맑고 온도는 22°C입니다."
# SystemMessage에서 명확한 위치 정보를 요구하는 메시지 추가
weather_tool = Tool(
name="get_weather", # 도구 이름
func=get_weather, # 실제 실행될 함수
description=""" # 도구 설명: 에이전트가 이 설명을 참고하여 도구 사용 여부를 판단함
1. 위치 정보가 명확한 경우만 사용하세요. 예: '서울', '부산', '도쿄'.
2. '어느 지역', '다른 지역', '그곳' 등의 표현은 도구를 실행하면 안 됩니다.
""",
system_message=SystemMessage(content=""" # 에이전트가 내부적으로 참고하는 시스템 지침
🚨 이 도구(get_weather)는 명확한 지명이 있을 때만 호출해야 합니다.
1. 입력이 반드시 '서울', '부산', '도쿄' 등 **명확한 지명**일 것.
2. 사용자가 명확한 지명을 말하지 않으면, '어느 지역의 날씨를 알고 싶으신가요?'라고 물어보세요.
"""),
args_schema=WeatherInput
)
# 에이전트 프롬프트 부분에서 명확한 위치를 요구하는 부분 강화
agent_kwargs = {
"extra_prompt_messages": [
MessagesPlaceholder(variable_name="chat_history"),
SystemMessage(content="""
1. 사용자가 명확한 위치를 제공하지 않았을 경우, 절대로 get_weather 도구를 호출하지 마세요.
2. 대신 '어느 지역의 날씨를 알고 싶으신가요?'라고 물어보세요.
""")
],
"system_message": SystemMessage(content="""
당신은 날씨 정보를 제공하는 AI 어시스턴트입니다.
중요 규칙:
1. 사용자가 구체적인 지명을 언급할 때만 weather_tool을 사용하세요.
2. 사용자 입력에 명확한 위치 정보가 없으면 반드시 먼저 위치를 물어보세요.
""")
}
# 도구와 메모리를 포함한 LangChain 에이전트 초기화
agent = initialize_agent(
tools=[weather_tool], # 사용할 도구 목록
llm=llm, # 사용할 언어 모델
agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION, # 에이전트 타입: 대화형 ReAct 기반
memory=memory,
verbose=True, # 내부 처리 과정을 출력 (디버깅용)
handle_parsing_errors=True, # 응답 파싱 오류를 자동 처리
max_iterations=3, # 에이전트의 최대 reasoning 반복 횟수
agent_kwargs=agent_kwargs # 추가 프롬프트 메시지 삽입 설정
)
# 콘솔 대화 루프 시작
print("🧠 채팅 시작! (종료: 'exit')")
while True:
user_input = input("🧑💻 You: ")
if user_input.lower() in ["exit", "quit"]:
break
try:
response = agent.run(user_input) # 에이전트를 통해 사용자 입력 처리
print(f"🤖 AI: {response}")
except Exception as e:
error_msg = str(e)[:100] # 에러 메시지 일부만 출력
print(f"🤖 AI: 죄송합니다, 오류가 발생했습니다: {error_msg}")
나 : 날씨 알려줘.
일단 get_weather 툴을 사용하겠다고 한다.
AI (Thought) : Do I need to use a tool? Yes
입력값이 확인되지 않는다!!
AI (Action Input) : ? (need more information, what city?)
파라미터가 없다고 한다.
AI (Action Input) : ?
없는 파라미터로 결과를 전망해본다.
AI (Observation) : ?의 날씨는 맑고 온도는 22°C입니다.
이제서야 툴이 필요없다는걸 느낌 AI
AI (Thought) : Do I need to use a tool? No
도시를 입력하지 않았다고... 입력하라고 응답을 준다.
AI : (AI) : You want to know the weather! Unfortunately, you didn't specify the city. Could you please tell me which city's weather you're interested in knowing? For example, "서울" for Seoul or "도쿄" for Tokyo.
Please respond with the city name, and I'll be happy to provide you with the current weather conditions using my get_weather tool!
두번째 질문 시작
나 : 서울
툴을 사용하겠다고 한다.
AI (Thought) : Do I need to use a tool? Yes
난 한글을 입력했지만... 영어로 표시가 된다.
AI (Action Input) : Seoul
결과를 전망해본다...
AI (Observation) : Seoul의 날씨는 맑고 온도는 22°C입니다.
근데 넌 왜 No ?????????????????
AI (Thought) : Do I need to use a tool? No
AI 의 최종 대답. Tool 을 사용했다면 "서울의 날씨는 맑고 온도는 22°C입니다" 라고 응답했을 것이다.
AI (AI) : Ah, the weather in Seoul is quite pleasant today! It's a clear day with a temperature of 22°C. Would you like to know more about the forecast or would you like to chat about something else?
🧠 채팅 시작! (종료: 'exit')
🧑💻 You: 날씨 알려줘.
D:\ollama-mcp\main.py:123: LangChainDeprecationWarning: The method `Chain.run` was deprecated in langchain 0.1.0 and will be removed in 1.0. Use :meth:`~invoke` instead.
response = agent.run(user_input) # 에이전트를 통해 사용자 입력 처리
[1m> Entering new AgentExecutor chain...[0m
[32;1m[1;3mThought: Do I need to use a tool? Yes
Action: get_weather
Action Input: ? (need more information, what city?)[0m
==== MCP_TOOL 'get_weather' CALLED ====
args: ('? (need more information, what city?)',)
kwargs: {}
Cleaned location from args: '?'
Observation: [36;1m[1;3m?의 날씨는 맑고 온도는 22°C입니다.[0m
Thought:[32;1m[1;3mDo I need to use a tool? No
AI: You want to know the weather! Unfortunately, you didn't specify the city. Could you please tell me which city's weather you're interested in knowing? For example, "서울" for Seoul or "도쿄" for Tokyo.
Please respond with the city name, and I'll be happy to provide you with the current weather conditions using my get_weather tool![0m
[1m> Finished chain.[0m
🤖 AI: You want to know the weather! Unfortunately, you didn't specify the city. Could you please tell me which city's weather you're interested in knowing? For example, "서울" for Seoul or "도쿄" for Tokyo.
Please respond with the city name, and I'll be happy to provide you with the current weather conditions using my get_weather tool!
🧑💻 You: 서울
[1m> Entering new AgentExecutor chain...[0m
[32;1m[1;3mThought: Do I need to use a tool? Yes
Action: get_weather
Action Input: Seoul[0m
==== MCP_TOOL 'get_weather' CALLED ====
args: ('Seoul',)
kwargs: {}
Cleaned location from args: 'Seoul'
Observation: [36;1m[1;3mSeoul의 날씨는 맑고 온도는 22°C입니다.[0m
Thought:[32;1m[1;3mThought: Do I need to use a tool? No
AI: Ah, the weather in Seoul is quite pleasant today! It's a clear day with a temperature of 22°C. Would you like to know more about the forecast or would you like to chat about something else?[0m
[1m> Finished chain.[0m
🤖 AI: Ah, the weather in Seoul is quite pleasant today! It's a clear day with a temperature of 22°C. Would you like to know more about the forecast or would you like to chat about something else?
🧑💻 You:
모델에게 적절한 응답을 유도하는 것이 생각보다 빡센거 같다. 여러 모델로 테스트해 보았는데 저마다의 결과 편차가 너무 커서 샘플로 작성할 수는 없었다. 의미 있는 결과를 얻을 때까지 ㄱㄱㄱㄱ~~