안녕하세요! 오늘은 최근 업데이트된 Ollama의 Tool 기능에 대해 살펴보겠습니다. Tool 지원 기능은 AI 모델들이 더 복잡한 작업을 수행하고 외부 세계와 상호 작용할 수 있도록 해주는 역할을 하며, Ollama는 Llama 3.1, Mistral Nemo와 같은 최신 인기 AI 모델을 통해 다양한 도구를 호출할 수 있도록 지원하고, 이를 통해 모델은 주어진 프롬프트에 대해 알고 있는 도구를 사용하여 더욱 정확하고 유용한 답변을 제공할 수 있습니다. 이 블로그에서는 Ollama Tool 기능의 개요와 지원모델에 대해 알아보고, 로컬 AI 모델, Mistral Nemo를 통해 날씨와 인터넷 정보를 검색하는 예제를 구현해 보겠습니다.
https://ollama.com/blog/tool-support
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
Ollama Tool 개요
Ollama의 Tool 지원은 단순한 정보 제공을 넘어, 사용자가 필요로 하는 특정 기능을 수행할 수 있도록 돕습니다. 예를 들어, 일반 언어 모델이 할 수 없는 현재 날씨를 확인하거나, 웹 브라우징, 특정 코드 해석기를 통한 코드 분석 등 다양한 함수와 API를 호출하는 기능을 수행하도록 할 수 있습니다. 이는 모델의 응답을 더욱 정교하게 만들고, 사용자에게 보다 유용한 정보를 제공하도록 지원합니다.
Tool 기능을 활용한 유용한 작업의 종류는 다음과 같습니다.
- 날씨정보 확인: 날씨 API를 호출하여 현재날씨, 기상예보 등을 확인할 수 있습니다.
- 웹 브라우징: Duckduckgo 인터넷 검색으로 최신 정보를 검색할 수 있습니다.
- 계산기: 수학 계산기능을 호출하여 실행할 수 있습니다.
Ollama Tool 지원을 제공하는 모델은 위 화면과 같이 모델 검색화면에서 Tools를 클릭하거나, 아래 링크에서 확인할 수 있습니다. 현재 Llama 3.1, Mistral Nemo, Command-R + 등 다양한 모델이 Ollama Tool을 지원하며, 모델은 특정 도구와 결합되어 사용자에게 최적의 성능을 제공하고, 사용자는 Ollama API를 통해 손쉽게 이 모델들을 활용할 수 있습니다.
https://ollama.com/search?c=tools
Ollama Tool 예제
다음은 Ollama Tool 기능을 활용한 예제를 만들어 보겠습니다. 예제는 Gradio를 사용하여 웹 인터페이스를 만들고, Ollama AI 모델, Mistral NeMo를 활용해 사용자 질문에 답변합니다. 날씨 정보 조회, DuckDuckGo 검색, 한영 번역 기능을 제공하며, OpenWeather API로 실시간 날씨 데이터를 가져옵니다. 사용자는 텍스트 입력이나 미리 정의된 버튼으로 질문할 수 있고, 시스템은 적절한 응답을 생성하여 표시합니다.
이 블로그의 작업 환경은 Windows 11 Pro(23H2), WSL2, 파이썬 버전 3.11, 비주얼 스튜디오 코드(이하 VSC) 1.90.2이며, VSC를 실행하여 "WSL 연결"을 통해 Windows Subsystem for Linux(WSL) Linux 환경에 액세스 하도록 구성하였습니다. 작업순서는 다음과 같습니다.
Ollama 설치 및 모델 다운로드
1. Ollama 모델 다운로드: Ollama 설치 후, Tool 지원 모델 중 한국어를 지원하는 Mistral Nemo 모델을 다운로드합니다.
환경설정
2. 가상환경 생성 및 활성화: VSC 프롬프트 메인 디렉토리에서 다음 명령어를 통해 가상환경을 만들고 활성화합니다.
python3.11 -m venv myenv
source myenv/bin/activate
3. 의존성 패키지 설치: 가상환경이 활성화된 상태에서 아래 명령어로 의존성을 설치합니다.
pip install gradio ollama deep-translator requests duckduckgo-search
4. OpenWeather API KEY 발급: 아래 사이트에서 날씨정보 검색을 위한 OpenWeather API KEY를 발급합니다.
https://home.openweathermap.org/api_keys
코드작성 및 실행
5. 코드 작성: VSC에서 새 파이썬 파일을 만들고 아래 코드를 복사해서 붙여 넣고, OpenWeather API KEY를 입력한 후, app.py로 저장합니다. 이 코드는 Gradio를 사용하여 날씨 정보 및 인터넷 검색 결과를 제공하는 인터페이스를 구성합니다. 사용자는 검색 쿼리를 입력하거나 미리 정의된 버튼을 클릭하여 자동으로 쿼리를 설정하고, 그에 대한 응답을 출력 박스에서 확인할 수 있습니다. `Submit` 버튼 클릭이나 엔터 키를 통해 쿼리를 실행하고, `Clear` 버튼을 사용해 입력 및 응답 필드를 비울 수 있습니다.
import os
import gradio as gr
import ollama
from deep_translator import GoogleTranslator
from datetime import datetime, timedelta
import requests
import re
from duckduckgo_search import DDGS
# 환경 변수를 통해 API 키를 가져옵니다.
os.environ["OPENWEATHER_API_KEY"] = "발급받은 API KEY" # OpenWeather API 키
# DuckDuckGo 검색 함수
def duckduckgo_search(query: str, max_results: int = 5):
"""Perform a DuckDuckGo search and return results."""
params = {
"keywords": query,
"max_results": int(max_results),
}
results = []
with DDGS() as ddg:
for result in ddg.text(**params):
results.append({
'title': result['title'],
'body': result['body']
})
return results
# 현재 날씨 정보를 가져오는 함수
def get_current_weather(city):
api_key = os.getenv('OPENWEATHER_API_KEY') # 환경 변수에서 API 키를 읽어옵니다.
base_url = 'http://api.openweathermap.org/data/2.5/weather'
params = {
'q': city,
'appid': api_key,
'units': 'metric'
}
response = requests.get(base_url, params=params)
data = response.json()
if response.status_code == 200:
weather = {
'temperature': f"{data['main']['temp']}°C",
'condition': data['weather'][0]['description'].capitalize(),
'humidity': f"{data['main']['humidity']}%",
'wind_speed': f"{data['wind']['speed']} m/s"
}
return weather
else:
return {'error': '날씨 데이터를 가져올 수 없습니다.'}
# 48시간 예보를 가져오는 함수
def get_hourly_forecast(city):
api_key = os.getenv('OPENWEATHER_API_KEY')
base_url = 'http://api.openweathermap.org/data/2.5/forecast'
params = {
'q': city,
'appid': api_key,
'units': 'metric'
}
response = requests.get(base_url, params=params)
data = response.json()
if response.status_code == 200:
hours = data['list'][:16] # 48시간의 예보(3시간 간격)
forecast = []
for hour in hours:
# UTC 시간을 한국 시간으로 변환
utc_time = datetime.strptime(hour['dt_txt'], '%Y-%m-%d %H:%M:%S') # 데이터를 datetime 객체로 변환
korean_time = utc_time + timedelta(hours=9) # UTC 시간에 9시간 추가
forecast_entry = {
'time': korean_time.strftime('%Y-%m-%d %H:%M:%S'), # 한국 시간으로 포매팅
'temperature': f"{hour['main']['temp']}°C",
'condition': translate_to_korean(hour['weather'][0]['description']),
'humidity': f"{hour['main']['humidity']}%",
'wind_speed': f"{hour['wind']['speed']} m/s"
}
forecast.append(forecast_entry)
return forecast
else:
return {'error': '시간별 예보 데이터를 가져올 수 없습니다.'}
# 영어 답변을 한국어로 번역하는 함수
def translate_to_korean(text: str) -> str:
"""Translate English text to Korean using deep-translator."""
translated = GoogleTranslator(source='en', target='ko').translate(text)
return translated
# 한국어를 영어로 번역하는 함수
def translate_to_english(text: str) -> str:
"""Translate Korean text to English using deep-translator."""
translated = GoogleTranslator(source='ko', target='en').translate(text)
return translated
def is_korean(text):
return bool(re.search('[가-힣]', text))
def extract_city_name(text):
# Ollama 모델을 사용하여 도시 이름 추출
prompt = f"""다음 텍스트에서 도시 이름을 추출하세요: '{text}'.
1. 서울의 경우 '특별시'를 추가해주세요. (예: '서울' -> '서울특별시')
2. 부산, 대구, 인천, 광주, 대전, 울산의 경우 반드시 '광역시'를 추가해주세요. (예: '부산' -> '부산광역시')
3. 그 외의 모든 도시는 이름 뒤에 '시'를 붙여주세요. (예: '포항' -> '포항시', '화성' -> '화성시')
4. 이미 '시', '군', '구'로 끝나는 도시 이름은 그대로 유지해주세요.
5. 도시 이름만 간단히 반환해주세요.
6. 도시 이름이 없으면 '없음'이라고 반환해주세요.
7. 반드시 위의 규칙을 따라 도시 이름을 정확히 반환해주세요.
"""
response = ollama.chat(
model='mistral-nemo',
messages=[{'role': 'user', 'content': prompt}]
)
extracted_city = response['message']['content'].strip()
# 도시 이름이 추출되지 않았을 경우 기본값 반환
if not extracted_city or extracted_city.lower() == "없음":
return None
# 서울특별시 처리
if extracted_city == "서울" or extracted_city == "서울시":
return "서울특별시"
# 광역시 처리
metropolitan_cities = ['부산', '대구', '인천', '광주', '대전', '울산']
for city in metropolitan_cities:
if city in extracted_city and not extracted_city.endswith('광역시'):
return f"{city}광역시"
# 나머지 도시 처리
if not extracted_city.endswith(('시', '군', '구')):
return f"{extracted_city}시"
return extracted_city
# 검색 결과를 요약하는 함수 추가
def summarize_search_results(results):
combined_text = "\n\n".join([f"제목: {result['title']}\n내용: {result['body']}" for result in results])
# Ollama를 사용하여 요약 생성
summary_prompt = f"다음 검색 결과들을 한국어로 간단히 요약하고 출처 url을 보여주세요:\n\n{combined_text}\n\n요약:"
summary_response = ollama.chat(
model='mistral-nemo',
messages=[{'role': 'user', 'content': summary_prompt}]
)
return summary_response['message']['content']
# 대화를 실행하는 함수
def run_conversation(user_prompt: str) -> str:
# 한국어로 입력된 경우 처리
if is_korean(user_prompt):
if "날씨" in user_prompt or "예보" in user_prompt or "온도" in user_prompt:
city = extract_city_name(user_prompt)
print(f"추출된 도시 이름: {city}") # 디버깅을 위한 출력
if city is None:
return "죄송합니다. 도시 이름을 인식할 수 없습니다. 도시 이름을 명확히 말씀해 주세요."
if "시간대별" in user_prompt or "예보" in user_prompt:
forecast_info = get_hourly_forecast(city)
if 'error' not in forecast_info:
forecast_output = f"🌤️ {city}의 48시간 날씨 예보:\n"
for hour in forecast_info:
forecast_output += (
f"시간: {hour['time']}, "
f"온도: {hour['temperature']}, "
f"날씨 상태: {hour['condition']}, "
f"습도: {hour['humidity']}, "
f"풍속: {hour['wind_speed']}\n"
)
return forecast_output
else:
return f"{city}의 날씨 정보를 가져오는 데 실패했습니다. 도시 이름을 확인해 주세요."
else:
weather_info = get_current_weather(city)
if 'error' not in weather_info:
weather_output = (
f"🌤️ 현재 날씨 정보:\n"
f"도시: {city}\n"
f"온도: {weather_info['temperature']}\n"
f"날씨 상태: {weather_info['condition']}\n"
f"습도: {weather_info['humidity']}\n"
f"풍속: {weather_info['wind_speed']}\n"
)
return weather_output
else:
return f"{city}의 날씨 정보를 가져오는 데 실패했습니다. 도시 이름을 확인해 주세요."
else:
# DuckDuckGo 검색 수행 및 요약
search_results = duckduckgo_search(user_prompt)
summary = summarize_search_results(search_results) # 요약할 때 한국어로 요청됨
return f"검색 결과 요약:\n\n{summary}" # 번역 없이 그대로 응답
user_prompt_translated = translate_to_english(user_prompt) if is_korean(user_prompt) else user_prompt
response = ollama.chat(
model='mistral-nemo',
messages=[{'role': 'user', 'content': user_prompt_translated}],
tools=[
{
'type': 'function',
'function': {
'name': 'get_current_weather',
'description': 'Get the current weather for a city',
'parameters': {
'type': 'object',
'properties': {
'city': {
'type': 'string',
'description': 'The name of the city',
},
},
'required': ['city'],
},
},
},
{
'type': 'function',
'function': {
'name': 'duckduckgo_search',
'description': 'Perform a DuckDuckGo search',
'parameters': {
'type': 'object',
'properties': {
'query': {
'type': 'string',
'description': 'The search query to execute.',
},
'max_results': {
'type': 'integer',
'description': 'The maximum number of search results to return.',
'default': 5,
},
},
'required': ['query'],
},
},
},
{
'type': 'function',
'function': {
'name': 'get_hourly_forecast',
'description': 'Get the hourly weather forecast for a city',
'parameters': {
'type': 'object',
'properties': {
'city': {
'type': 'string',
'description': 'The name of the city',
},
},
'required': ['city'],
},
},
}
],
)
tool_calls = response['message'].get('tool_calls', [])
if tool_calls:
results = []
for tool_call in tool_calls:
function_name = tool_call['function']['name']
arguments = tool_call['function']['arguments']
if function_name == 'get_current_weather':
city = arguments.get('city')
if city:
weather_info = get_current_weather(city)
if 'error' not in weather_info:
weather_output = (
f"🌤️ 현재 날씨 정보:\n"
f"도시: {city}\n"
f"온도: {weather_info['temperature']}\n"
f"날씨 상태: {weather_info['condition']}\n"
f"습도: {weather_info['humidity']}\n"
f"풍속: {weather_info['wind_speed']}\n"
)
results.append(weather_output)
else:
results.append(weather_info['error'])
elif function_name == 'duckduckgo_search':
query = arguments.get('query')
max_results = arguments.get('max_results', 5)
if query:
search_results = duckduckgo_search(query, max_results)
summary = summarize_search_results(search_results)
results.append(f"검색 결과 요약:\n\n{summary}")
elif function_name == 'get_hourly_forecast':
city = arguments.get('city')
if city:
forecast_info = get_hourly_forecast(city)
if 'error' not in forecast_info:
forecast_output = f"🌤️ {city}의 48시간 날씨 예보:\n"
for hour in forecast_info:
forecast_output += (
f"시간: {hour['time']}, "
f"온도: {hour['temperature']}, "
f"날씨 상태: {hour['condition']}, "
f"습도: {hour['humidity']}, "
f"풍속: {hour['wind_speed']}\n"
)
results.append(forecast_output)
else:
results.append(forecast_info['error'])
return "\n\n".join(results)
else:
return "도구 호출이 없습니다." # 도구 호출이 발견되지 않았을 때 메시지
# Gradio 인터페이스 정의
def process_query(query: str) -> str:
# 입력이 None이거나 공백인 경우 처리
if query is None or query.strip() == "":
return "입력 내용이 없습니다. 검색어를 입력해주세요."
return run_conversation(query)
def on_button_click(query):
return run_conversation(query)
title = "Ollama Mistral-Nemo 인터넷 검색 및 국내 날씨 정보"
with gr.Blocks() as iface:
gr.Markdown(f"<h1 style='text-align: center;'>{title}</h1>")
gr.Markdown("질문을 입력하면 Ollama AI가 DuckDuckGo 인터넷 검색 및 현재 날씨 정보를 응답합니다.")
inputs = gr.Textbox(label="검색 쿼리", placeholder="대/중소 도시명('서울', '대전', '하남' 등)과 함께 날씨정보를 요청하거나 검색어를 입력하세요.", interactive=True)
# 미리 정의된 쿼리 버튼들
button1 = gr.Button("서울특별시 날씨 알려줘")
button2 = gr.Button("하남시 시간대별 날씨 알려줘")
button3 = gr.Button("갤럭시 Z 플립6 검색해줘")
output_box = gr.Textbox(label="응답", interactive=False)
# 각 버튼 클릭 이벤트 처리
button1.click(fn=on_button_click, inputs=gr.Textbox(value="서울특별시 날씨 알려줘", visible=False), outputs=output_box)
button2.click(fn=on_button_click, inputs=gr.Textbox(value="하남시 시간대별 날씨 알려줘", visible=False), outputs=output_box)
button3.click(fn=on_button_click, inputs=gr.Textbox(value="갤럭시 Z 플립6 검색해줘", visible=False), outputs=output_box)
# 입력된 쿼리에 대해 Submit 버튼 (variant="primary"로 설정)
submit_button = gr.Button("Submit", variant="primary")
submit_button.click(fn=process_query, inputs=inputs, outputs=output_box)
# 입력 필드에서 Enter 키를 눌렀을 때 이벤트
inputs.submit(fn=process_query, inputs=inputs, outputs=output_box)
# Clear 버튼 추가 (모든 텍스트를 비우는 기능)
def clear_inputs():
return "", "" # inputs와 outputs 모두 빈 문자열로 반환
clear_button = gr.Button("Clear")
clear_button.click(fn=clear_inputs, inputs=None, outputs=[inputs, output_box])
# Submit 및 Clear 버튼을 한 줄에 배치
with gr.Row():
submit_button
clear_button
# 인터페이스 실행
iface.launch(server_name="127.0.0.1", server_port=7866, inbrowser=True)
위 코드를 실행하고, http://127.0.0.1:7866/주소를 브라우저 사이드바에서 열면 아래와 같이 초기화면이 열립니다.
맺음말
이 블로그를 통해 Ollama의 Tool 기능에 대해 자세히 알아보고, 다양한 도구를 활용하여 더 복잡한 작업을 수행하는 방법을 살펴보았습니다. Ollama는 최신 AI 모델을 통해 외부 세계와의 상호작용을 가능하게 하며, Llama 3.1, Mistral Nemo와 같은 모델을 통해 다양한 기능을 구현할 수 있습니다. 특히, 날씨 정보 조회, 웹 브라우징, 계산기 기능 등을 통해 AI의 활용 범위를 넓히는 데 기여하고 있습니다.
이번 블로그에서는 Gradio를 사용하여 웹 인터페이스를 구축하고, Ollama AI 모델을 통해 날씨 정보와 검색 결과를 제공하는 예제를 구현했습니다. 이를 통해 실시간 날씨 데이터 검색, DuckDuckGo를 통한 인터넷 검색, 그리고 간단한 번역 기능까지 다양한 기능을 실습해 보았습니다.
Ollama Tool 기능의 적용 사례를 통해 여러분의 AI 활용에 도움이 되시기를 바라면서 저는 다음 시간에 더 유익한 정보를 가지고 다시 찾아뵙겠습니다. 감사합니다. 다음 블로그 포스트에서 또 만나요! 🌟
Ollama Tool 예제 제작 및 사용후기 세줄
- API 방식과 달리 Tool방식은 자연어로 검색할 수 있어서 편리하다.
- 광역시, 특별시, 시 등 복잡한 도시명의 반환은 AI에게 프롬프트로 요청한다.
- Mistral NeMo 모델은 Llama 3.1 보다 한국어 표현이 자연스럽다.
2024.07.26 - [AI 언어 모델] - 🧠 최강 AI 검색 비서: Mistral Large 2 모델 설정 가이드
'AI 도구' 카테고리의 다른 글
Morphic: 🔍질문을 잘 이해하는 생성형 UI 기반 검색 엔진의 혁신 (2) | 2024.08.02 |
---|---|
Open-WebUI: 🔍실시간 웹 검색과 개인 메모리 기능을 갖춘 LLM 실행기 (15) | 2024.07.31 |
🔍 Llama-3-Groq 최신 AI 모델로 브라우저 사이드바 웹 검색 구현하기 (6) | 2024.07.23 |
PraisonAI Code: AI 코딩 혁신! 이젠 전체 코드 베이스와 대화하세요!(feat. 제미나이) 💬🚀 (0) | 2024.07.16 |
🤖STORM: AI로 논문 작성하기,📈주제만 입력하면 고품질 리포트 3분 완성! (2) | 2024.07.12 |