Ollama를 통한 도구 호출

작성자

Joshua Noble

Data Scientist

Ollama를 통한 도구 호출

대규모 언어 모델(LLM)에서의 도구 호출은 LLM이 외부 도구, 서비스, API와 상호 작용하여 작업을 수행하는 능력입니다. 이를 통해 LLM은 기능을 확장하여 외부 데이터, 실시간 정보 또는 특정 애플리케이션에 대한 액세스가 필요할 수 있는 실제 작업에 대한 처리 능력을 향상시킬 수 있습니다. LLM이 웹 검색 도구를 사용하는 경우, 웹을 호출하여 모델의 학습 데이터에서 제공되지 않는 실시간 데이터를 가져올 수 있습니다. 다른 도구 유형으로 생각할 수 있는 Python은 계산, 데이터 분석, 시각화, 데이터를 위한 서비스 엔드포인트 호출에 사용됩니다. 도구 호출을 하면 챗봇의 역동성과 적응 능력을 높여서, 자체적인 지식 기반 바깥의 전문화된 작업 또는 실시간 데이터를 바탕으로 더욱 정확하고 관련성이 높으며 상세한 응답을 제공할 수 있습니다. 인기 있는 도구 호출용 프레임워크에는 Langchain이 있고, 이제 ollama도 주목 받고 있습니다.

Ollama는 개인 장치에서 사용할 수 있는 오픈 소스 로컬 AI 모델을 제공하는 플랫폼입니다. 그래서 사용자가 자신의 컴퓨터에서 직접 LLM을 실행할 수 있습니다. OpenAI API 같은 서비스와 달리 모델이 로컬 컴퓨터에 있으므로 계정이 필요하지 않습니다. Ollama는 개인정보 보호, 성능, 사용 편의성에 중점을 두고 사용자가 외부 서버로 데이터를 전송하지 않고도 AI 모델을 이용하며 상호 작용할 수 있도록 지원합니다. 이는 데이터 프라이버시를 중시하거나 외부 API에 의존하지 않으려는 사람들에게 특히 매력적일 수 있습니다. Ollama 플랫폼은 설정하고 사용하기 쉽게 설계되었으며 다양한 모델을 지원합니다. 사용자는 자연어 처리, 코드 생성을 비롯한 AI 작업을 위한 다양한 도구를 자신의 하드웨어에서 바로 사용할 수 있습니다. 데이터, 프로그램, 맞춤 소프트웨어 등 로컬 환경의 모든 기능을 이용할 수 있기 때문에 도구 호출 아키텍처에 적합합니다.

로컬 파일 시스템을 살펴보는 ollama를 사용하여 도구 호출을 설정하는 방법을 배우는 튜토리얼입니다. 원격 LLM으로는 수행하기 어려운 작업입니다. olllama에는 도구 호출과 AI 에이전트 구축에 사용할 수 있는 Mistral과 Llama 3.2 등 많은 모델이 있습니다. ollama 웹사이트에서 모든 모델을 확인하세요. 우리가 사용해볼 모델은 도구 지원이 되는 IBM Granite 3.2 Dense입니다. 2B 모델과 8B 모델은 텍스트 전용 고밀도 LLM입니다. 도구 기반 사용 사례와 검색 증강 생성(RAG)을 지원하고 코드 생성, 번역, 버그 수정을 간소화하도록 설계되었습니다.

이 튜토리얼에 대한 노트북은 Github에서 내겨받을 수 있습니다.

1단계: Ollama 설치

먼저 https://ollama.com/download에서 ollama를 다운로드하고 운영 체제에 맞게 설치하세요. OSX에서는 .dmg 파일을 통해, Linux에서는 단일 셸 명령을 통해, Windows에서는 설치 프로그램을 사용하여 ollama를 설치할 수 있습니다. 설치 프로그램을 실행하려면 컴퓨터에 대한 관리자 액세스 권한이 필요할 수 있습니다.

터미널 또는 명령 프롬프트를 열고 다음을 입력하여 ollama가 올바르게 설치되었는지 테스트할 수 있습니다.

ollama -v 

 

2단계: 라이브러리 설치

다음으로 초기 가져오기를 추가합니다. 이 데모에서는 ollama python 라이브러리를 사용하여 ollama와 통신하고 pymupdf 라이브러리를 사용하여 파일 시스템에서 PDF 파일을 읽습니다.

!pip install pymupdf

import ollama
import os
import pymupdf

 

다음으로 이 튜토리얼에서 사용할 모델을 가져옵니다. 이렇게 하면 ollama에서 모델 가중치를 로컬 컴퓨터로 다운로드하여 나중에 원격 API를 호출할 필요 없이 사용할 수 있도록 저장합니다.

!ollama pull granite3.2
!ollama pull granite3.2-vision

3단계: 도구 정의

이제 ollama 도구 인스턴스가 액세스할 수 있는 도구를 정의합니다. 도구의 의도는 로컬 파일 시스템에서 파일을 읽고 이미지를 살펴보는 것이므로 각 도구에 대해 두 개의 Python 함수를 만듭니다. 첫 번째 함수는search_text_files 로, 로컬 파일에서 검색하려면 키워드가 필요합니다. 이 데모에서 코드는 특정 폴더의 파일만 검색하지만 코드를 확장하여 도구가 검색할 폴더를 설정하는 두 번째 매개변수를 포함하도록 할 수 있습니다.

간단한 문자열 일치를 사용하여 키워드가 문서에 있는지 확인할 수 있지만, ollama를 사용하면 로컬 llm을 쉽게 호출할 수 있으므로search_text_files 가 Granite 3.2를 사용하여 키워드가 문서 텍스트를 설명하는지 여부를 결정합니다. 이는 문서를document_text 라는 문자열로 읽어서 수행됩니다. 그런 다음 함수는 ollama.chat을 호출하고 모델에 다음과 같은 프롬프트를 표시합니다.

"Respond only 'yes' or 'no', do not add any additional information. Is the following text about " + keyword + "? " + document_text 

모델이 '예(yes)'라고 응답하면 함수는 사용자가 프롬프트에 표시한 키워드가 포함된 파일 이름을 반환합니다. 파일에 정보가 포함되어 있지 않은 것 같으면 함수는 'None'을 문자열로 반환합니다.

ollama가 Granite 3.2 Dense를 다운로드하기 때문에 이 기능이 처음에는 느리게 실행될 수 있습니다. 

def search_text_files(keyword: str) -> str:
  
  directory = os.listdir("./files/")
  for fname in directory:
    
    # look through all the files in our directory that aren't hidden files
    if os.path.isfile("./files/" + fname) and not fname.startswith('.'):

        if(fname.endswith(".pdf")):
           
           document_text = ""
           doc = pymupdf.open("./files/" + fname)

           for page in doc: # iterate the document pages
               document_text += page.get_text() # get plain text (is in UTF-8)
               
           doc.close()

           prompt = "Respond only 'yes' or 'no', do not add any additional information. Is the following text about " + keyword + "? " + document_text 

           res = ollama.chat(
                model="granite3.2:8b",
                messages=[{'role': 'user', 'content': prompt}]
            )

           if 'Yes' in res['message']['content']:
                return "./files/" + fname

        elif(fname.endswith(".txt")):

            f = open("./files/" + fname, 'r')
            file_content = f.read()
            
            prompt = "Respond only 'yes' or 'no', do not add any additional information. Is the following text about " + keyword + "? " + file_content 

            res = ollama.chat(
                model="granite3.2:8b",
                messages=[{'role': 'user', 'content': prompt}]
            )
           
            if 'Yes' in res['message']['content']:
                f.close()
                return "./files/" + fname

  return "None"

두 번째 도구는 search_image_files 로, 로컬 사진에서 검색하려면 키워드가 필요합니다. 검색은 ollama를 통해 Granite 3.2 Vision 이미지 설명 모델을 사용하여 수행됩니다. 이 모델은 폴더의 각 이미지 파일에 대한 설명 텍스트를 반환하고 설명에서 키워드를 검색합니다. ollama를 사용하는 장점 중 하나는 다중 에이전트 시스템이 한 모델을 다른 모델로 호출하도록 쉽게 구축할 수 있다는 점입니다.

이 함수는 사용자가 프롬프트에서 지정한 키워드가 포함된 설명이 포함된 파일 이름인 문자열을 반환합니다.

def search_image_files(keyword:str) -> str:

    directory = os.listdir("./files/")
    image_file_types = ("jpg", "png", "jpeg")

    for fname in directory:

        if os.path.isfile("./files/" + fname) and not fname.startswith('.') and fname.endswith(image_file_types):
            res = ollama.chat(
                model="granite3.2-vision",
                messages=[
                    {
                        'role': 'user',
                        'content': 'Describe this image in short sentences. Use simple phrases first and then describe it more fully.',
                        'images': ["./files/" + fname]
                    }
                ]
            )

            if keyword in res['message']['content']:
                return "./files/" + fname
    
    return "None"

4단계: ollama용 도구 정의

이제 ollama가 호출할 함수가 정의되었으므로 ollama 자체에 대한 도구 정보를 구성합니다. 첫 번째 단계는 도구 이름을 ollama 함수 호출을 위한 함수에 매핑하는 객체를 만드는 것입니다.

available_functions = {
  'Search inside text files':search_text_files,
  'Search inside image files':search_image_files
}

다음으로, ollama가 액세스할 수 있는 도구와 해당 도구에 필요한 것을 알리도록 도구 배열을 구성합니다. 이는 도구당 하나의 개체 스키마가 있는 배열로, ollama 도구 호출 프레임워크에 도구를 호출하는 방법과 반환하는 내용을 알려줍니다.

이전에 만든 두 도구 모두 keyword 매개변수가 필요한 함수입니다. 현재는 함수만 지원되지만 향후 변경될 수 있습니다. 함수 및 매개변수에 대한 설명은 모델이 도구를 올바르게 호출하는 데 도움이 됩니다. 각 도구의 함수에 대한 설명 필드는 LLM이 사용할 도구를 선택할 때 LLM에 전달됩니다. 키워드 설명 은 도구에 전달될 매개변수를 생성할 때 모델에 전달됩니다. 이 두 가지 모두 ollama로 애플리케이션을 호출하는 자체 도구를 만들 때 프롬프트를 미세 조정할 수 있는 곳입니다.

# tools don't need to be defined as an object but this helps pass the correct parameters
# to the tool call itself by giving the model a prompt of how the tool is to be used
ollama_tools=[
     {
      'type': 'function',
      'function': {
        'name': 'Search inside text files',
        'description': 'This tool searches in PDF or plaintext or text files in the local file system for descriptions or mentions of the keyword.',
        'parameters': {
          'type': 'object',
          'properties': {
            'keyword': {
              'type': 'string',
              'description': 'Generate one keyword from the user request to search for in text files',
            },
          },
          'required': ['keyword'],
        },
      },
    },
    {
      'type': 'function',
      'function': {
        'name': 'Search inside image files',
        'description': 'This tool searches for photos or image files in the local file system for the keyword.',
        'parameters': {
          'type': 'object',
          'properties': {
            'keyword': {
              'type': 'string',
              'description': 'Generate one keyword from the user request to search for in image files',
            },
          },
          'required': ['keyword'],
        },
      },
    },
  ]


사용자 입력으로 ollama를 호출할 때 이 도구 정의를 사용하게 됩니다.

5단계: ollama에 사용자 인풋 전달

이제 사용자 입력을 ollama에 전달하고 도구 호출 결과를 반환하도록 할 차례입니다. 먼저 시스템에서 ollama가 실행 중인지 확인합니다.

# if ollama is not currently running, start it
import subprocess
subprocess.Popen(["ollama","serve"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)

Ollama가 실행 중인 경우 다음이 반환됩니다.

<Popen: returncode: None args: ['ollama', 'serve']>

이제 사용자에게 입력을 요청합니다. 애플리케이션의 구성에 따라 입력을 하드코딩하거나 채팅에서 검색할 수도 있습니다. 함수 입력 이 계속 진행하기 전에 사용자 입력을 기다립니다.

# input
user_input = input("What would you like to search for?")
print(user_input)

예를 들어 사용자가 '개에 대한 정보'를 입력하면 다음 셀이 인쇄됩니다.

Information about dogs

이제 사용자 쿼리가 ollama 자체로 전달됩니다. 메시지에는 사용자에 대한 역할과 사용자가 입력한 콘텐츠가 필요합니다. 이는 chat 함수를 사용해 ollama에 전달됩니다. 첫 번째 매개변수는 사용하려는 모델(이 경우 Granite 3.2 Dense)이고, 그 다음은 사용자 입력이 포함된 메시지, 마지막은 이전에 구성한 도구 배열입니다.

함수 chat 은 사용할 도구와 후속 도구 호출에서 전달해야 하는 매개변수를 선택하는 아웃풋을 생성합니다.

messages = [{'role': 'user', 'content':user_input}]

response: ollama.ChatResponse = ollama.chat(
   
  # set which model we're using
  'granite3.2:8b',

  # use the message from the user
  messages=messages,

  tools=ollama_tools
)

이제 모델이 아웃풋에서 도구 호출을 생성했으므로 모델이 생성한 매개 변수를 사용하여 모든 도구 호출을 실행하고 아웃풋을 확인합니다. 이 애플리케이션에서는 Granite 3.2 Dense를 사용하여 아웃풋을 생성하므로 도구 호출 결과가 초기 사용자 입력에 추가된 다음 모델로 전달됩니다.

여러 도구 호출이 일치하는 파일을 반환할 수 있으므로 응답은 배열로 수집된 다음 Granite 3.2로 전달되어 응답을 생성합니다. 데이터 앞에 있는 프롬프트는 모델에 응답 방법을 지시합니다.

If the tool output contains one or more file names, then give the user only the filename found. Do not add additional details. 
If the tool output is empty ask the user to try again. Here is the tool output: 

그런 다음 최종 출력은 반환된 파일 이름이나 다음을 사용하여 생성됩니다. 

# this is a place holder that to use to see whether the tools return anything 
output = []

if response.message.tool_calls:
  
  # There may be multiple tool calls in the response
  for tool_call in response.message.tool_calls:

    # Ensure the function is available, and then call it
    if function_to_call := available_functions.get(tool_call.function.name):
      print('Calling tool: ', tool_call.function.name, ' \n with arguments: ', tool_call.function.arguments)
      tool_res = function_to_call(**tool_call.function.arguments)

      print(" Tool response is " + str(tool_res))

      if(str(tool_res) != "None"):
        output.append(str(tool_res))
        print(tool_call.function.name, ' has output: ', output)
    else:
      print('Could not find ', tool_call.function.name)

  # Now chat with the model using the tool call results
  # Add the function response to messages for the model to use
  messages.append(response.message)

  prompt = '''
    If the tool output contains one or more file names, 
    then give the user only the filename found. Do not add additional details. 
    If the tool output is empty ask the user to try again. Here is the tool output: 
  '''

  messages.append({'role': 'tool', 'content': prompt + " " + ", ".join(str(x) for x in output)})
  
  # Get a response from model with function outputs
  final_response = ollama.chat('granite3.2:8b', messages=messages)
  print('Final response:', final_response.message.content)

else:

  # the model wasn't able to pick the correct tool from the prompt
  print('No tool calls returned from model')

이 튜토리얼에 제공된 파일을 사용하면 '개에 대한 정보' 프롬프트가 반환됩니다.

    Calling tool:  Search inside text files  
     with arguments:  {'keyword': 'dogs'}
     Tool response is ./files/File4.pdf
    Search inside text files  has output:  ['./files/File4.pdf']
    Calling tool:  Search inside image files  
     with arguments:  {'keyword': 'dogs'}
     Tool response is None
    Final response: The keyword "dogs" was found in File4.pdf.

Granite 3.2가 '개'라는 입력에서 올바른 키워드를 선택하고 폴더의 파일을 검색하여 PDF 파일에서 키워드를 찾은 것을 볼 수 있습니다. LLM 결과는 순전히 결정론적이지 않기 때문에 동일한 프롬프트 또는 매우 유사한 프롬프트에서 약간 다른 결과를 얻을 수 있습니다.

관련 솔루션
비즈니스용 AI 에이전트

생성형 AI로 워크플로와 프로세스를 자동화하는 강력한 AI 어시스턴트 및 에이전트를 구축, 배포, 관리하세요.

    watsonx Orchestrate 살펴보기
    IBM AI 에이전트 솔루션

    믿을 수 있는 AI 솔루션으로 비즈니스의 미래를 설계하세요.

    AI 에이전트 솔루션 살펴보기
    IBM Consulting AI 서비스

    IBM Consulting AI 서비스는 기업이 AI 활용 방식을 재구상하여 혁신을 달성하도록 지원합니다.

    인공 지능 서비스 살펴보기
    다음 단계 안내

    사전 구축된 앱과 스킬을 사용자 정의하든, AI 스튜디오를 사용하여 맞춤형 에이전틱 서비스를 구축하고 배포하든, IBM watsonx 플랫폼이 모든 것을 지원합니다.

    watsonx Orchestrate 살펴보기 watsonx.ai 살펴보기