데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      Function Calling: LLM이 외부 세계와 소통하는 방법 (ft. Qwen, llama, Gemma)

      alankim 25.04.22
      14,851 8 3
      DEVOTEE 요약
      에이전틱 AI는 AI가 스스로 계획을 세우고 외부 도구를 활용해 문제를 해결하는 자율성과 판단력을 가진 지능형 도구로 진화한 것을 말합니다. Function Calling은 LLM이 자연어 요청을 이해하고 적합한 외부 API를 호출해 실시간 정보를 가져오는 기술로, 사용자가 요청한 정보를 보다 정확하고 유연하게 제공할 수 있게 합니다. 오픈소스 LLM을 통해 Function Calling을 구현하는 방법도 제공되며, 프롬프트 엔지니어링을 통해 모델이 함수 호출 정보를 생성하고, 외부 애플리케이션이 이를 처리해 결과를 제공하는 구조입니다.
      DEVOTEE 추천 블로그

      들어가며

      요즘 AI 업계에서 가장 뜨거운 키워드를 하나만 꼽으라면 단연 Agentic AI일 것입니다.

      기존 소프트웨어에서 'Agent'라는 용어는 주로 단순하고 반복적인 작업을 자동화하는 도구를 지칭했습니다.

      그러나 LLM의 등장은 이 개념에 새로운 의미를 부여했습니다. 이제 AI Agent는 단순한 자동화를 넘어서 일정 수준의 자율성과 판단력을 갖춘 지능형 도구로 진화하고 있습니다.

      이렇게 스스로 판단하고 행동하는 AI를 우리는 "에이전틱(agentic)하다"라고 표현하며, 이러한 시스템을 에이전틱 AI라고 부릅니다.

      마치 영화 속 자비스처럼, 사용자의 복잡한 요구사항을 이해하고 필요한 정보를 찾아오거나 외부 시스템과 연동하여 작업을 수행하는 미래가 현실로 다가오고 있는 것이죠.


      그렇다면 이렇게 자율적인 AI가 어떻게 현실 세계와 상호작용하며 복잡한 임무를 수행할 수 있을까요?

      이러한 상호 작용을 가능하게 하는 기반 기술 중 하나가 바로 Function Calling입니다.

      Function Calling은 LLM이 자연어 요청을 이해하고 적합한 외부 함수(API)를 호출하도록 유도하는 기술로, 사용자의 의도를 정확히 파악해 필요한 정보를 준비하는 메커니즘입니다.

      예를 들어 사용자가 “오늘 날씨 어때?”라고 물으면, LLM이 날씨 API 호출에 필요한 정보를 생성하여 실시간 날씨 정보를 가져오게 됩니다.

      이 덕분에 LLM은 더 이상 학습된 데이터에만 의존하지 않습니다.

      실시간 정보에 접근하고, 반복적인 작업을 자동화하며, 복잡한 워크플로우를 조율하는 등 훨씬 더 강력하고 유연한 능력을 갖추게 되었습니다.


      이번 글에서는 LLM의 잠재력을 한 단계 끌어올리는 Function Calling 기술에 대해 알아보겠습니다.

      이 기술이 왜 필요하게 되었는지, 어떤 원리로 작동하는지, 그리고 실제 오픈소스 LLM에서 어떻게 활용할 수 있는지 차근차근 살펴보겠습니다.

      사전 안내 사항

      본문에 사용된 코드는 아래 GitHub 링크에서 다운로드할 수 있습니다.

      1. Function Calling 개념의 등장

      기존의 LLM은 실시간 정보를 가져오거나 복잡한 계산을 수행하거나 외부 시스템과 상호작용하는 데 한계가 있었습니다.

      예를 들면, 특정 시점 이후의 최신 뉴스나 현재 날씨와 같은 정보는 정확히 답변하기 어려웠죠.

      이러한 한계를 극복하기 위해, 외부 정보를 LLM의 답변 생성 과정에 통합하려는 다양한 아이디어들이 등장하기 시작했습니다.

      그 중 대표적인 사례가 2022년에 발표된 ReAct (Reasoning and Acting) 기법입니다.

      ReAct는 모델이 질문에 대해 먼저 내부적 추론(Thought)을 수행하고 이를 바탕으로 필요한 외부 행동(Act)을 결정합니다.

      외부 행동을 수행하면 그 결과를 관찰(Observation)하고, 이 관찰 결과를 바탕으로 다시 추론(Thought)을 반복하여 점진적으로 최적의 답변에 도달 할 수 있다는 기법입니다.

      어찌 보면 실제 인간의 문제 해결 과정과 유사합니다.

      예를 들어 여행 계획을 세울 때, 우리는 목적지를 정하고 교통편이나 숙소를 알아본 뒤, 필요하면 날씨나 현지 정보를 검색합니다. 그런 다음 세부 일정을 조율하고 예산을 세운 후 실제 예약을 진행하죠.

      ReAct 역시 이러한 과정을 모방하여 언어 모델이 추론과 행동을 번갈아 수행하며 문제를 해결하도록 설계되었습니다.


      2023년 초, Meta AI에서 발표한 Toolformer가 등장하면서 한 걸음 더 나아갔습니다.

      Toolformer는 GPT 계열 모델을 기반으로, 모델 스스로 언제 어떤 API를 호출해야 하는지를 자기지도 학습(self-supervised learning) 방식으로 학습하도록 설계되었습니다.

      이를 통해 계산기, 위키백과 검색, 달력 확인 등 다양한 외부 도구 활용이 가능해졌으며, 이후 여러 툴 기반 LLM 프레임워크 개발에 큰 영향을 주었습니다.


      이러한 연구 흐름 속에서 2023년 6월, OpenAI가 GPT 모델에 Function Calling 기능을 공식적으로 도입하면서 이 기술은 본격적으로 대중화되었습니다.

      특히 ChatGPT 플러그인이 등장하며 LLM이 사용자의 요청을 이행하기 위해 외부 API를 호출해야 할 필요성이 커졌고, Function Calling은 이를 위한 표준화된 인터페이스로 자리 잡게 됩니다.



      2. Function Calling의 원리

      일반적으로 소프트웨어 개발에서 서비스 간 통신 규약을 API(Application Programming Interface)라고 부릅니다.

      개발자는 필요에 따라 API를 통해 외부 시스템과 데이터를 주고받을 수 있는데, 이때 주로 사용하는 데이터 형식 중 하나가 JSON 입니다.

      Function Calling의 핵심 아이디어는 LLM이 자연어 답변을 생성하는 대신, 특정 상황에서 소프트웨어가 이해하고 처리할 수 있는 구조화된 데이터(예: JSON 형식)를 출력하도록 유도하는 것입니다.


      예를 하나 들어보죠.

      사용자가 “오늘 서울 날씨 어때?”라고 질문했을 때, 기존 LLM은 학습된 데이터에 기반하기 때문에 과거의 날씨 정보나 잘못된 정보를 제공했을 것입니다.

      하지만 Function Calling이 적용된 모델은 사용자의 요청을 분석하여 “정확한 최신 날씨 정보는 외부 날씨 API를 통해 가져오는 것이 좋겠다”라고 판단하고, API 호출에 필요한 정보를 다음과 같은 JSON 형식으로 생성합니다.

      {
        "name": "get_current_weather",
        "arguments": {
          "location": "Seoul"
          }
      }

      이렇게 모델이 생성한 JSON 데이터를 사용자 애플리케이션이 받아 처리하고, 이 정보를 바탕으로 실제 날씨 API를 호출하여 최신 날씨 정보를 가져옵니다.

      API로부터 받은 응답 데이터를 다시 LLM에게 전달하면, 모델은 이 정보를 활용하여 최종 답변을 정확하고 자연스럽게 생성할 수 있습니다.


      Function Calling의 주요 특징 중 하나는 이를 구현하기 위해 모델의 기본 구조를 변경할 필요가 없다는 점입니다.

      이는 모델이 특정 조건(예: 함수 호출이 필요하다고 판단될 때)에서 JSON 형식의 출력을 생성하도록 파인튜닝 하거나, 프롬프트 엔지니어링을 통해 유도하는 방식으로 구현됩니다.

      이러한 접근 방식 덕분에 기존 LLM 기술 위에 유연하게 Function Calling 기능을 추가할 수 있다는 장점이 있습니다.



      3. Function Calling의 단계별 작동 방식

      아래 다이어그램은 Function Calling의 일반적인 처리 흐름을 단계별로 보여줍니다.

      아래 플로우의 각 단계를 자세히 살펴보겠습니다.


      3.1 1단계: 사용자 요청 및 함수 준비

      함수 정의

      첫번째로 해야 할일은 LLM이 사용할 수 있는 함수를 미리 정의 하는 것입니다.

      함수 정의는 일반적으로 함수 이름(name), 기능 설명(description), 필요한 입력값(parameters) 등을 포함하며, 명확한 구조 전달을 위해 JSON 스키마 형식으로 작성됩니다.

      예를 들면 특정 도시의 현재 날씨를 조회 하는 함수 get_weather는 아래와 같은 형식으로 정의할 수 있을 것입니다.

      {
        "name": "get_weather",
        "description": "특정 도시의 현재 날씨 정보를 조회합니다.",
        "parameters": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "날씨를 조회할 도시 이름 (예: 서울, 부산, New York)"
            },
          },
          "required": ["location"]
        }
      }

      함수 정의 형식을 JSON 스키마로 사용하는 이유는 LLM 에게 함수를 전달 할 때 함수가 어떠한 역활을 하는지 자연어로 주저리 주저리 설명하는 것보다

      함수의 구조와 요구사항을 LLM에게 보다 명확하고 체계적으로 전달 수 있기 때문입니다.

      사용자 요청

      이제 정의된 함수 목록(Tools)과 함께 사용자의 질문(예: "서울 날씨 알려줘")을 모델 API에 전달합니다.

      Function 에서 TOOLS로

      초기에는 OpenAI API 등에서 functions라는 파라미터를 사용해 호출 가능한 함수 목록을 지정했지만, 이후에는 이를 tools라는 더 확장성 있는 구조로 대체하게 되었습니다.

      tools는 단순한 함수 호출 만을 위한 것이 아니라, 검색, 코드 실행, 데이터베이스 질의 등 다양한 종류의 외부 도구를 포괄할 수 있도록 설계된 구조입니다.

      즉, 함수 호출이라는 좁은 개념에서 벗어나, LLM이 외부 세계와 더 유연하고 풍부하게 상호작용할 수 있도록 진화한 것입니다.


      3.2 2단계: 함수 호출 생성

      모델이 함수 목록과 사용자의 요청을 받으면 다음 과정을 수행하게 됩니다.

      1. 요청에서 '날씨 조회'라는 의도와 '서울'이라는 위치 정보를 추출합니다.

      2. get_weather 함수의 설명("특정 도시의 현재 날씨 정보를 조회합니다.")과 파라미터(location)가 사용자의 의도 및 추출된 정보와 가장 잘 맞는다고 판단합니다.

      3. get_weather 함수의 필수 파라미터인 location에 추출된 정보 '서울'을 매핑합니다.

      4. 최종적으로 다음과 같은 함수 호출 정보를 JSON 형식으로 생성하여 반환합니다.

      {
        "name": "get_weather",
        "arguments": {
          "location": "서울"
        }
      }


      3.3 3단계: 외부 시스템에서 함수 실행

      LLM이 생성한 JSON 형식의 함수 호출 정보는 개발자가 구축한 애플리케이션 코드로 전달됩니다.

      여기서 핵심은 LLM은 이 호출 정보를 생성할 뿐, 스스로 외부 함수나 API를 직접 실행하지는 않는다는 것입니다.

      Function Calling 개념을 처음 접할 때 LLM이 함수 실행까지 모두 처리하는 것으로 오해하기 쉽습니다. ("LLM이 호출 정보를 만들고 직접 함수를 실행해서 결과까지 가져오는 것 아닌가?")

      하지만 실제 역할은 명확히 나뉩니다. LLM은 사용자의 요청 의도를 파악하고, 가장 적합한 함수를 선택하여 '어떻게 호출해야 하는지'에 대한 구조화된 지시(JSON)를 만드는 역할에 집중합니다.

      실제 함수의 실행 책임은 애플리케이션에게 있습니다.

      애플리케이션은 LLM으로부터 이 지시(JSON)를 받아, 자신의 코드 환경 내에서 해당 함수를 직접 실행하고 그 결과를 다시 LLM에게 전달합니다.

      즉, LLM은 '지시 생성자', 애플리케이션은 '실행자' 로 역할이 분담된 구조로 이해해야 합니다.

      따라서 애플리케이션 코드는 전달받은 JSON을 파싱하여 함수 이름(get_weather)과 인자({"location": "서울"})를 얻고, 이를 바탕으로 실제 get_weather 함수 로직을 실행하는 것입니다.


      3.4 4단계: 실행 결과 전달

      애플리케이션 코드가 get_weather 함수를 실행하고 날씨 API로부터 결과를 받으면, 이를 다시 LLM에게 전달합니다.

      이 결과 역시 구조화된 형태(주로 JSON)로 전달하는 것이 일반적입니다.

      {
        "location": "서울",
        "temperature": 21,
      }


      3.5 5단계: 최종 응답 생성

      LLM은 4단계에서 전달받은 함수 실행 결과를 활용하여, 사용자 질문("서울 날씨 알려줘")에 대한 최종 답변을 자연어로 생성합니다.

      예를 들어 최종적으로 사용자는 다음과 같은 답변을 받게 되는 것이죠.

      “현재 서울의 기온은 21도 입니다. "



      4. 오픈소스 모델로 Function Calling 구현하기

      Function Calling의 동작 방식을 살펴보았으니, 실제 오픈소스 LLM을 사용하여 Function Calling을 구현해보겠습니다.

      여기서는 Qwen 2.5, Llama 3.1, Gemma 3 모델을 예시로 사용할텐데, 각 모델이 Function Calling을 처리하는 방식이 조금씩 차이가 있습니다.

      우선 Qwen2.5 모델을 대상으로 전체 플로우를 살펴본 후 Llama 3.1과 Gemma3의 차이점에 대해 알야보겠습니다.

      예제 코드는 사용자가 특정 KOSPI 주식 정보를 요청하면, Function Calling을 통해 관련 정보를 조회하여 답변하는 시나리오를 기반으로 합니다.

      코드는 핵심 로직 위주로 설명하기 때문에 서두에 안내된 Github 링크의 전체 코드를 보면서 읽어 보면 좀 더 파악하기 쉬울 것입니다.



      5. Qwen 2.5

      5.1 특징

      Qwen 모델은 설계 단계부터 외부 도구 사용(Tool Use)과 에이전트 기능을 지원하도록 개발되었으며, 함수 호출(Function Calling) 기능이 모델에 내장되어 있습니다.

      함수 호출은 주로 프롬프트 내 특정 XML 스타일 태그를 활용하여 제어할 수 있습니다.

      • 사용 가능한 함수 목록은 프롬프트 내 <tools>...</tools> 태그에 JSON 형식으로 정의하여 모델에 전달할 수 있습니다.

      • 모델은 호출할 함수명과 인자 정보를 <tool_call>...</tool_call> 태그 안에 JSON 형식으로 생성하도록 학습되어 있습니다.

      또한, Qwen 2.5는 OpenAI와 유사한 API 파라미터 기반의 함수 호출 방식도 지원하므로, 사용 환경에 따라 다양한 방식으로 함수 호출을 구현할 수 있습니다.


      5.2 Function Calling 구현

      5.2.1 모델 및 토크나이저 초기화

      추론 속도를 높이기 위해 vLLM 으로 모델을 로드 합니다.

      MODEL_ID = "Qwen/Qwen2.5-7B-Instruct"
      model = LLM(MODEL_ID, tensor_parallel_size=1)
      tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)


      5.2.2 샘플링 파라메터 설정

      vLLM으로 텍스트를 생성할 때, 출력의 특성을 조절하기 위해 디코딩 파라메터를 조절합니다.

      함수 호출 생성 시에는 정확성을 위해 temperature를 낮추고, 최종 답변 생성 시에는 자연스러움을 위해 약간 높입니다.

      sampling_params_func_call = SamplingParams(
          max_tokens=256, temperature=0.0, stop=["<|im_end|>"], skip_special_tokens=False
      )
      sampling_params_text = SamplingParams(
          max_tokens=512, temperature=0.1, top_p=0.95, stop=["<|im_end|>"], skip_special_tokens=False
      )


      5.2.3 TOOL의 정의

      주식 정보를 가져오는 함수 get_kospi_stock_info 에 대해 JSON 스키마 형식으로 함수를 정의합니다.

      TOOLS = [{
          "type": "function",
          "function": {
              "name": "get_kospi_stock_info",
              "description": "특정 KOSPI 주식의 현재 가격 및 기본 정보를 가져옵니다.",
              "parameters": {
                  "type": "object",
                  "properties": {
                      "stock_name_or_code": {
                          "type": "string",
                          "description": "주식 이름(예: 'SK텔레콤') 또는 종목 코드(예: '017670')"
                      }
                  },
                  "required": ["stock_name_or_code"]
              }
          }
      }]


      5.2.4 주식 정보 조회 함수 (사용자 애플리케이션)

      TOOLS에 정의된 get_kospi_stock_info 함수의 실행 코드 입니다. 이 코드는 사용자 애플리케이션의 일부이며, yfinance 라이브러리를 사용해 주식 정보를 조회합니다.

      함수 실행의 결과 즉 주식명, 현재가, 전일 종가, 통화단위는 JSON 코드로 반환됩니다.

      다시 한번 상기하지만, 이 함수는 LLM이 실행하는 것이 아니라 사용자 애플리케이션으로 실행됩니다.

      즉 LLM이 이 함수를 호출하는 것이 아니라 LLM이 생성한 함수 호출 정보를 바탕으로 사용자의 코드에서 이 함수를 호출 하고, 결과 값을 리턴 받게 됩니다.

      def get_kospi_stock_info(stock_name_or_code: str) -> str:
      
      ... 중략 ...
      
          result = {
              "ticker": ticker_symbol,
              "stock_name": stock_info.get('shortName', name_or_code),
              "current_price": price_display,
              "previous_close": previous_close_display,
              "currency": stock_info.get('currency', 'KRW')
          }
          
          return json.dumps(result, ensure_ascii=False)


      5.2.5 함수 호출 파싱

      LLM이 함수를 호출하기로 결정하면, Qwen 모델은 특정 형식(<tool_call>...</tool_call>)으로 응답을 생성합니다.

      때문에 이 응답에서 실제 호출할 함수 이름과 인자를 추출하는 함수가 필요합니다.

      llama나 gemma가 응답 형식이 다르기 때문에 추후 이들 모델을 사용한 예제에서는 parse_tool_calls 함수를 해당 모델의 출력 형식에 맞게 수정해야 합니다.

      def parse_tool_calls(content: str):
          
          tool_calls = []
          pattern = r"<tool_call>(.*?)</tool_call>"
          matches = re.finditer(pattern, content, re.DOTALL)
      
      ... 중략 ...
      
          return assistant_message


      5.2.6 Function Calling 메인 함수

      이제 정의된 요소들을 사용하여 실제 Function Calling 과정을 실행하는 메인 함수 query_kospi_info를 살펴보겠습니다.

      예제 코드에서는 전체 동작 방식을 이해하도록 단계별로 결과를 출력하도록 하였습니다.

      지면상 전체 코드 는 생략하고 출력 결과를 통해 단계별 동작을 확인해보겠습니다.

      1단계 초기 메시지 작성

      가장 먼저 LLM과의 대화를 위한 메시지 스택을 구성합니다.

      [{'role': 'system', 'content': 'You are a helpful assistant. Current Date: 2025-04-20'}, {'role': 'user', 'content': 'SK텔레콤의 주가를 알려줘'}]
      2단계 함수 선택/응답 생성을 위한 프롬프트 구성

      1단계에서 만든 메시지를 포함해서 LLM에게 어떤 도구를 사용할 수 있고, 사용하려면 이런 형식으로 응답해야 한다라는 지침을 만들어 프롬프트화 합니다.

      특히 Qwen 모델은 아래와 같은 형식을 사용합니다.

      • <tools>...</tools> 태그를 통해 정의된 함수 목록을 모델에게 전달하고,

      • <tool_call> .. </tool_call> 태그를 통해 모델이 함수 호출 방법을 생성하도록 유도 합니다.

      <|im_start|>system
      You are a helpful assistant. Current Date: 2025-04-20
      
      # Tools
      
      You may call one or more functions to assist with the user query.
      
      You are provided with function signatures within <tools></tools> XML tags:
      <tools>
      {"type": "function", "function": {"name": "get_kospi_stock_info", "description": "특정 KOSPI 주식의 현재 가격 및 기본 정보를 가져옵니다.", "parameters": {"type": "object", "properties": {"stock_name_or_code": {"type": "string", "description": "주식 이름(예: 'SK텔레콤') 또는 종목 코드(예: '017670')"}}, "required": ["stock_name_or_code"]}}}
      </tools>
      
      For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
      <tool_call>
      {"name": <function-name>, "arguments": <args-json-object>}
      </tool_call><|im_end|>
      <|im_start|>user
      SK텔레콤의 주가를 알려줘<|im_end|>
      <|im_start|>assistant
      3단계 함수 호출/초기 응답을 위한 LLM 응답

      2단계에서 만든 프롬프트를 Qwen에 입력으로 넣으면 모델이 이 프롬프트 형식을 이해하고 특정 함수를 호출해야겠다고 판단하게 됩니다.

      그러면 <tool_call> 태그 형식으로 호출할 함수 이름과 필요한 인자를 JSON 형식으로 만들어 출력합니다

      <tool_call>
      {"name": "get_kospi_stock_info", "arguments": {"stock_name_or_code": "SK텔레콤"}}
      </tool_call>
      4단계 함수 호출 내용 파싱 및 메시지 스택에 추가

      모델의 응답은 <tool_call> 태그에 감싸져 있으므로 이를 파싱해야 실제 함수 실행에 사용할 수 있습니다.

      이 파싱 결과는 messages 리스트에 추가되어 모델과의 대화 흐름에 “어떤 도구를 호출할 예정이다” 라는 맥락을 명확히 추가해줍니다. 이 스택은 나중에 최종 응답 생성을 위한 프롬프트로 활용됩니다.

      {'role': 'assistant', 'tool_calls': [{'type': 'function', 'function': {'name': 'get_kospi_stock_info', 'arguments': {'stock_name_or_code': 'SK텔레콤'}}, 'id': 'call_0'}]}
      5단계 함수 실행 결과

      모델이 호출하라고 지시한 함수를 실제로 실행합니다. 이때 내부적으로 get_kospi_stock_info("SK텔레콤")을 호출하며, Yahoo Finance API를 통해 실제 주식 정보를 가져옵니다.

      {"ticker": "017670.KS", "stock_name": "SKTelecom", "current_price": 57700.0, "previous_close": 57900.0, "currency": "KRW"}
      6단계 최종 응답 생성을 위한 프롬프트 작성

      지금까지의 모든 대화 흐름(system → user → assistant의 함수 호출 → tool의 응답)을 기반으로, 모델에게 자연어 응답을 요청할 차례입니다. 이때 messages를 그대로 사용해 최종 프롬프트를 구성합니다.

      <|im_start|>system
      You are a helpful assistant. Current Date: 2025-04-20<|im_end|>
      <|im_start|>user
      SK텔레콤의 주가를 알려줘<|im_end|>
      <|im_start|>assistant
      <tool_call>
      {"name": "get_kospi_stock_info", "arguments": {"stock_name_or_code": "SK텔레콤"}}
      </tool_call><|im_end|>
      <|im_start|>user
      <tool_response>
      {"ticker": "017670.KS", "stock_name": "SKTelecom", "current_price": 57700.0, "previous_close": 57900.0, "currency": "KRW"}
      </tool_response><|im_end|>
      <|im_start|>assistant

      5.2.7 실행 및 결과 확인

      이제 작성된 코드로 "SK텔레콤의 주가르 알려줘"라는 실제 질문으로 테스트 해보죠.

      답변: SK텔레콤의 주가는 현재 57,700원입니다. 전일 종가 대비로는 약 200원 하락했습니다. 화폐 단위는 한국 원(KRW)입니다.

      사용자의 애플리케이션 함수에서 실행된 현재의 주가 결과를 받아서 자연스럽게 답변을 하네요!



      6. Llama 3.1

      6.1 특징

      Llama 3.1은 외부 도구(함수) 호출을 위해 내장 도구(Brave Search, Wolfram Alpha, Code Interpreter), JSON 기반, 사용자 정의 형식의 세 가지 방식을 지원합니다.

      이 글에서는 다른 모델과의 비교를 위해 JSON 기반 방식을 사용합니다.

      • JSON 기반 호출 방식

        개발자가 함수 명세를 JSON 스키마로 제공하면, 모델은 OpenAI Function Calling과 유사하게

        {"name": "함수명", "parameters": {"인자명": "값", ...}} 형태의 JSON 문자열을 생성하여 함수 호출을 요청합니다.

      llama에서 유의할 점은 어떤 방식으로 도구 호출을 요청했든지 간에, 외부에서 실행된 도구의 결과를 다시 모델에게 전달할 때는 공통적으로 ipython 역할(role)을 사용한다는 것입니다.

      6.2 Function Calling 구현

      예제 코드는 위에 알아본 세가지 방식 중 JSON 방식으로 구현 했습니다. 마찬가지로 지면상 관계상, 특별하게 다른 점에 대해 짚어보겠습니다.

      6.2.1 함수 호출 파싱

      parse_tool_calls 함수에서는 JSON 형식인지 확인하는 형태로 단순화 됩니다.

      def parse_tool_calls(output_text: str):
      
          cleaned_output = output_text.strip()
          json.loads(cleaned_output)
      
          return {
              "role": "assistant",
              "content": cleaned_output
          }

      6.2.2 IPython 메시지 생성 함수

      함수 실행 결과를 받아서 llama 3.1이 이해 할 수 있는 역활 즉 ipython 역활로 재정의하는 함수가 새롭게 추가 됩니다.

      def create_ipython_message(tool_result_dict: dict):
          return {
              "role": "ipython",
              "content": {
                  "output": tool_result_dict
              }
          }

      6.2.3 Function Calling 메인 함수

      메인 함수의 구현은 Qwen과 유사합니다. 다만 아래 부분은 확인이 필요한데요.

      tokenizer.apply_chat_template을 사용하면 "Environment: ipython" 라는 헤더가 붙는데 이는 이후 메시지가 IPython 세션에서 온 것 처럼 모델에 전달 됩니다.

      여기서 함수 호출 부분만 JSON으로 내보내기 위해 이 헤더를 제거하는 로직을 삽입합니다.

      prompt = tokenizer.apply_chat_template(messages, tools=TOOLS, add_generation_prompt=True, tokenize=False)
      cleaned_prompt = "\n".join(
              line for line in prompt.splitlines() if "Environment: ipython" not in line
          )

      llama3.1 의 응답이 일반 텍스트인지, 아니면 함수 호출용 JSON 블록인지를 구분하기 위해 맨 앞에 고정된 태그<|python_tag|>를 붙입니다.

      tool_call_content = func_call_response_text
      python_tag = "<|python_tag|>"
      if tool_call_content.startswith(python_tag):
          tool_call_content = tool_call_content[len(python_tag):].strip()


      7. Gemma 3

      7.1 특징

      가이드 문서 : https://ai.google.dev/gemma/docs/capabilities/function-calling

      Gemma3는 Qwen2.5나 Llama3.1과 달리, 함수 호출을 위한 별도의 전용 토큰이나 내장된 표준 호출 형식을 지원하지 않습니다.

      대신 사용자가 프롬프트에서 원하는 함수 호출 포맷(예: Python 함수 호출 형태, JSON 등)을 명확히 지정하면, Gemma3는 해당 구조에 맞춘 출력을 생성합니다.

      이러한 방식은 구현의 자유도가 높다는 장점이 있지만, 출력 결과가 프롬프트에서 지정한 구조와 정확히 일치하는지 확인하는 과정(파싱 및 검증)을 개발자가 직접 구현해야 한다는 단점도 있습니다.

      반면, Qwen2.5나 Llama3.1은 함수 호출에 특화된 템플릿과 구조를 내장하고 있어, 개발 및 자동화 측면에서 더 표준화된 워크플로우를 제공합니다.

      따라서 일관된 함수 호출 결과를 쉽게 얻을 수 있고, 프레임워크와의 연동도 상대적으로 용이합니다.

      그러나 Gemma3는 표준화된 형식에 얽매이지 않고, 다양한 사용자 정의 포맷을 적용할 수 있어 맞춤형 워크플로우나 특수한 함수 호출 구조가 필요한 상황에서는 더 유연하게 활용할 수 있습니다.

      즉, Gemma3는 함수 호출의 포맷과 파싱을 자유롭게 설계할 수 있다는 점에서 유연성이 뛰어나지만, 표준화 및 자동화된 개발 환경이 필요한 경우에는 Qwen2.5나 Llama3.1이 더 적합할 수 있습니다.

      7.2 Function Calling 구현

      Gemma 3는 Llama 3.1과 달리 함수 호출을 위한 내장 형식이나 특정 역할(role)을 사용하지 않습니다. 대신 프롬프트를 통해 원하는 형식을 명시하고, 응답 파싱 및 결과 전달 방식을 직접 구현해야 합니다.

      7.2.1 함수 호출 파싱

      Gemma 3는 표준화된 함수 호출 형식이 없으므로, 시스템 프롬프트에서 지정한 커스텀 형식을 파싱했습니다.

      # 지정된 형식 '[함수명(...)]'을 찾음
      call = re.search(r'\[(\w+\(.*?\))\]', output_text)
      # 함수명과 파라미터 문자열 분리
      func_match = re.match(r'(\w+)\((.*?)\)', call.group(1))
      # 파라미터를 딕셔너리로 변환
      params = dict(re.findall(r"(\w+)='([^']*)'", params_str))

      7.2.2 Function Calling 메인 함수

      llama3.1과 마찬가지로 확인이 필요한 부분만 짚어보도록 하겠습니다.

      함수 호출 형식([func_name(...)])과 사용 가능한 도구 목록(TOOLS) 정보를 시스템 프롬프트(system_msg)에 명시적으로 포함시킵니다.

      system_msg = f"""...
      You MUST put it in the format of
      [func_name1(params_name1=params_value1, ...)]
      ...
      {json.dumps(TOOLS, indent=2, ensure_ascii=False)}
      """

      Llama 3.1의 ipython 역할 처럼, 새로운 사용자(user) 메시지를 생성하여 함수 실행 결과를 모델에게 명시적으로 전달합니다.

      이 메시지에는 어떤 함수가 어떤 파라미터로 호출되었고, 그 결과가 무엇인지 설명하며, 이 정보를 바탕으로 사용자 질문에 답하라고 지시합니다.

      followup_prompt = (
          f"I called the function {func_name} with parameters {params}.\n"
          f"The function returned the following result:\n{function_result}\n\n"
          f'Based on this information, please provide a helpful response to the user\'s query: "{query}".'
      )


      마무리

      이번 글에서는 Function Calling의 개념적인 배경부터 작동 원리까지 살펴보고, 주요 오픈소스 모델인 Qwen, Llama, Gemma를 통해 실제 코드 과정을 살펴보았습니다.

      각 모델이 고유한 철학을 바탕으로 Function Calling에 접근하는 방식은 흥미로웠자만, 동시에 표준화의 부재가 개발과정에 부담이 될 수 있을 것이라는 생각도 들었습니다.

      그럼에도 불구하고 Function Calling은 LLM이 언어 모델의 한계를 넘어 현실 세계와 상호작용하게 하는 핵심 기술입니다.

      이미 LLM 애플리케이션의 필수 요소이며, AI 에이전트 시대를 맞아 그 중요성은 더욱 커지고 있습니다.

      따라서 앞으로도 지속적인 연구와 발전이 필수적인 기술 분야임이 분명합니다.


      개념 정리와 더불어 각 모델의 구현 코드까지 작성하려니 글이 다소 길어진 것 같습니다.

      벛꽃 엔딩이 여기저기서 들리네요. 따스한 봄날, 행복한 시간 보내시길 바라며 글을 마칩니다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      alankim 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기