0

에이전틱 AI - 랭체인코리아 밋업 2025Q1

랭체인 코리아

Function Calling 연쇄 호출 테스트

AF 김태영
2025.01.09 16:29
383

OpenAI의 ChatCompletion API에서 Function Calling(함수 호출) 기능을 통해 모델이 스스로 적절한 함수를 선택하고, 필요한 인자를 구성하여 호출할 수 있게 되었습니다. 기존에는 모델의 답변을 파싱하여 사용자가 직접 “필요한 정보를 추출”하곤 했지만, 이제는 모델이 명시적으로 “특정 함수”를 호출하고, 그 결과를 받아 다음 문맥에 활용할 수 있습니다.

이 글에서는 a부터 z까지 총 26개의 함수가 서로 의존성을 갖는 예시를 통해, Function Calling을 어떻게 구성할 수 있는지 살펴보겠습니다. 각 함수는 이전 함수가 만들어낸 “토큰”을 입력받아 새로운 “토큰”을 생성하고, 마지막에 a_func가 최종 결과를 만들어내는 구조입니다.

1. 시나리오: a_func를 호출하기 위해 z_func부터 차례대로 호출

예시 상황은 다음과 같습니다.

  • z_func: 의존성이 전혀 없는 함수. 내부적으로 "z"라는 토큰을 만듦
  • y_func: z_token을 받아서 "y" + z_token을 리턴
  • x_func: y_token"x" + y_token
  • b_func: c_token"b" + c_token
  • a_func: b_token"a" + b_token (최종 결과)

사용자는 “A 함수를 호출해”라고만 말해도, 모델은 z_func → y_func → x_func → ... → b_func → a_func 순으로 호출해야 한다는 사실을 인식하고, 중간 단계 함수들을 모두 호출할 수 있습니다.

2. 전체 예시 코드

아래 코드에서는:

  • z_func ~ a_func: 파이썬 상의 실제 함수들(문자열을 이어붙이는 단순 예시)
  • tools: OpenAI에 노출할 함수의 정의(각 함수 이름, 설명, 파라미터 스키마)
  • toolkits: 함수 이름과 실제 파이썬 함수의 매핑
  • openai_llm(...): ChatCompletion API를 호출하고, 모델이 함수 호출을 요청하면 실제로 파이썬 함수를 실행하여 결과를 다시 모델에게 전달(while 루프로 재시도)

코드 전문

# -*- coding: utf-8 -*-
"""
function_calling_demo.py

OpenAI의 Function Calling 기능을 활용해,
a_func부터 z_func까지 26개의 함수가 서로 의존성을 가지도록 구성한 예시 코드.

'도구(tools)' 정의를 통해 모델이 필요할 때 각 함수를 호출하고,
함수 결과를 다시 모델에 전달하는 과정을 반복하여 최종 값을 얻는다.
"""

from openai import OpenAI
import requests
import json
import yfinance as yf

########################################
# 1. OpenAI 클라이언트 초기화
########################################
client = OpenAI(
    api_key="sk-p..."
)


########################################
# 2. a~z 함수 정의
#    - z_func: 의존성 없는 가장 기초
#    - y_func: z_token 필요
#    - ...
#    - a_func: b_token 필요(최종)
#
#  실제 로직(토큰 생성 등)은 단순히 문자열을 이어붙이는 것으로 예시 구성
########################################

def z_func():
    """z_func: 의존성 없음, 결과로 'z'를 리턴"""
    return "z"

def y_func(z_token):
    """y_func: z_token -> 'y' + z_token"""
    return "y" + z_token

def x_func(y_token):
    """x_func: y_token -> 'x' + y_token"""
    return "x" + y_token

def w_func(x_token):
    """w_func: x_token -> 'w' + x_token"""
    return "w" + x_token

def v_func(w_token):
    """v_func: w_token -> 'v' + w_token"""
    return "v" + w_token

def u_func(v_token):
    """u_func: v_token -> 'u' + v_token"""
    return "u" + v_token

def t_func(u_token):
    """t_func: u_token -> 't' + u_token"""
    return "t" + u_token

def s_func(t_token):
    """s_func: t_token -> 's' + t_token"""
    return "s" + t_token

def r_func(s_token):
    """r_func: s_token -> 'r' + s_token"""
    return "r" + s_token

def q_func(r_token):
    """q_func: r_token -> 'q' + r_token"""
    return "q" + r_token

def p_func(q_token):
    """p_func: q_token -> 'p' + q_token"""
    return "p" + q_token

def o_func(p_token):
    """o_func: p_token -> 'o' + p_token"""
    return "o" + p_token

def n_func(o_token):
    """n_func: o_token -> 'n' + o_token"""
    return "n" + o_token

def m_func(n_token):
    """m_func: n_token -> 'm' + n_token"""
    return "m" + n_token

def l_func(m_token):
    """l_func: m_token -> 'l' + m_token"""
    return "l" + m_token

def k_func(l_token):
    """k_func: l_token -> 'k' + l_token"""
    return "k" + l_token

def j_func(k_token):
    """j_func: k_token -> 'j' + k_token"""
    return "j" + k_token

def i_func(j_token):
    """i_func: j_token -> 'i' + j_token"""
    return "i" + j_token

def h_func(i_token):
    """h_func: i_token -> 'h' + i_token"""
    return "h" + i_token

def g_func(h_token):
    """g_func: h_token -> 'g' + h_token"""
    return "g" + h_token

def f_func(g_token):
    """f_func: g_token -> 'f' + g_token"""
    return "f" + g_token

def e_func(f_token):
    """e_func: f_token -> 'e' + f_token"""
    return "e" + f_token

def d_func(e_token):
    """d_func: e_token -> 'd' + e_token"""
    return "d" + e_token

def c_func(d_token):
    """c_func: d_token -> 'c' + d_token"""
    return "c" + d_token

def b_func(c_token):
    """b_func: c_token -> 'b' + c_token"""
    return "b" + c_token

def a_func(b_token):
    """a_func: b_token -> 'a' + b_token (최종)"""
    return "a" + b_token


##############################################
# 3. tools 정의 (Function Calling용 스키마)
#    - a~z 모든 함수를 등록하고, 각 함수에 필요한 인자와 설명을 JSON Schema로 표시
##############################################

tools = [
    {
        "type": "function",
        "function": {
            "name": "z_func",
            "description": (
                "Z 함수: 의존성이 없는 가장 기초 함수. 결과값으로 z_token을 "
                "생성한다고 가정. 다른 많은 함수들이 z_token을 필요로 할 수 있음."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    # z_func는 인자가 없음
                },
                "required": []
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "y_func",
            "description": (
                "Y 함수: z_token이 필요. z_func의 결과로부터 z_token을 받아서 "
                "y_token을 생성한다고 가정."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "z_token": {
                        "type": "string",
                        "description": "z_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["z_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "x_func",
            "description": (
                "X 함수: y_token이 필요. y_func의 결과로부터 y_token을 받아서 "
                "x_token을 생성한다고 가정."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "y_token": {
                        "type": "string",
                        "description": "y_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["y_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "w_func",
            "description": "W 함수: x_token 필요, 결과로 w_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "x_token": {
                        "type": "string",
                        "description": "x_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["x_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "v_func",
            "description": "V 함수: w_token 필요, 결과로 v_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "w_token": {
                        "type": "string",
                        "description": "w_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["w_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "u_func",
            "description": "U 함수: v_token 필요, 결과로 u_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "v_token": {
                        "type": "string",
                        "description": "v_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["v_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "t_func",
            "description": "T 함수: u_token 필요, 결과로 t_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "u_token": {
                        "type": "string",
                        "description": "u_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["u_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "s_func",
            "description": "S 함수: t_token 필요, 결과로 s_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "t_token": {
                        "type": "string",
                        "description": "t_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["t_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "r_func",
            "description": "R 함수: s_token 필요, 결과로 r_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "s_token": {
                        "type": "string",
                        "description": "s_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["s_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "q_func",
            "description": "Q 함수: r_token 필요, 결과로 q_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "r_token": {
                        "type": "string",
                        "description": "r_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["r_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "p_func",
            "description": "P 함수: q_token 필요, 결과로 p_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "q_token": {
                        "type": "string",
                        "description": "q_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["q_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "o_func",
            "description": "O 함수: p_token 필요, 결과로 o_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "p_token": {
                        "type": "string",
                        "description": "p_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["p_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "n_func",
            "description": "N 함수: o_token 필요, 결과로 n_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "o_token": {
                        "type": "string",
                        "description": "o_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["o_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "m_func",
            "description": "M 함수: n_token 필요, 결과로 m_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "n_token": {
                        "type": "string",
                        "description": "n_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["n_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "l_func",
            "description": "L 함수: m_token 필요, 결과로 l_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "m_token": {
                        "type": "string",
                        "description": "m_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["m_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "k_func",
            "description": "K 함수: l_token 필요, 결과로 k_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "l_token": {
                        "type": "string",
                        "description": "l_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["l_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "j_func",
            "description": "J 함수: k_token 필요, 결과로 j_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "k_token": {
                        "type": "string",
                        "description": "k_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["k_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "i_func",
            "description": "I 함수: j_token 필요, 결과로 i_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "j_token": {
                        "type": "string",
                        "description": "j_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["j_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "h_func",
            "description": "H 함수: i_token 필요, 결과로 h_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "i_token": {
                        "type": "string",
                        "description": "i_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["i_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "g_func",
            "description": "G 함수: h_token 필요, 결과로 g_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "h_token": {
                        "type": "string",
                        "description": "h_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["h_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "f_func",
            "description": "F 함수: g_token 필요, 결과로 f_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "g_token": {
                        "type": "string",
                        "description": "g_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["g_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "e_func",
            "description": "E 함수: f_token 필요, 결과로 e_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "f_token": {
                        "type": "string",
                        "description": "f_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["f_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "d_func",
            "description": "D 함수: e_token 필요, 결과로 d_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "e_token": {
                        "type": "string",
                        "description": "e_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["e_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "c_func",
            "description": "C 함수: d_token 필요, 결과로 c_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "d_token": {
                        "type": "string",
                        "description": "d_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["d_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "b_func",
            "description": "B 함수: c_token 필요, 결과로 b_token을 만든다고 가정.",
            "parameters": {
                "type": "object",
                "properties": {
                    "c_token": {
                        "type": "string",
                        "description": "c_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["c_token"]
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "a_func",
            "description": (
                "A 함수: b_token이 필요. b_func 호출 결과로 b_token을 받고, "
                "최종 결과를 생성한다고 가정."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "b_token": {
                        "type": "string",
                        "description": "b_func 호출 결과로 얻은 토큰"
                    }
                },
                "required": ["b_token"]
            },
        },
    }
]

##############################################
# 4. 실제 Python 함수(코드)와 tool name 매핑
##############################################

toolkits = {
    "z_func": z_func,
    "y_func": y_func,
    "x_func": x_func,
    "w_func": w_func,
    "v_func": v_func,
    "u_func": u_func,
    "t_func": t_func,
    "s_func": s_func,
    "r_func": r_func,
    "q_func": q_func,
    "p_func": p_func,
    "o_func": o_func,
    "n_func": n_func,
    "m_func": m_func,
    "l_func": l_func,
    "k_func": k_func,
    "j_func": j_func,
    "i_func": i_func,
    "h_func": h_func,
    "g_func": g_func,
    "f_func": f_func,
    "e_func": e_func,
    "d_func": d_func,
    "c_func": c_func,
    "b_func": b_func,
    "a_func": a_func,
}

########################################
# 5. 시스템 메시지
#    - 모델에게 "알아서 필요한 함수는 호출하라" 고 지시
########################################

system_prompt = """
You are a helpful assistant. Write in Korean.
You have access to the following tools. If the user asks for something that requires calling a function, call it with the correct arguments.
Otherwise, respond directly in natural language.
"""


########################################
# 6. 대화(챗) + 함수 호출 로직
########################################

def openai_llm(user_input, chat_history):
    """
    - chat_history에 대화 내용(system, user, assistant, tool 등)을 순차적으로 기록
    - 모델에게 질의 후, 만약 tool_calls가 있으면 해당 함수를 실제로 호출하고,
      그 결과를 tool 메시지로 추가
    - 다시 모델에게 재요청을 반복하여, 함수 호출이 더 이상 없을 때 최종 답변을 반환
    """

    # 최초 대화에 system 메시지를 넣음
    if len(chat_history) == 0:
        chat_history.append({"role": "system", "content": system_prompt})

    # 사용자 메시지를 추가
    chat_history.append({"role": "user", "content": user_input})

    # 함수 호출이 없을 때까지 반복
    while True:
        # 1) OpenAI ChatCompletion 요청
        completion = client.chat.completions.create(
            model="gpt-4o",   # 예시 모델
            messages=chat_history,
            tools=tools
        )

        # 모델이 생성한 assistant 메시지를 획득
        assistant_msg = completion.choices[0].message

        # assistant_msg를 대화 기록에 추가
        chat_history.append(assistant_msg)

        # 2) 모델이 함수 호출을 했는지 (tool_calls) 확인
        if not hasattr(assistant_msg, "tool_calls") or not assistant_msg.tool_calls:
            # 함수 호출이 없으면 일반 답변으로 간주, while 종료
            break

        # 3) 함수 호출이 있을 경우 -> 실제 파이썬 함수를 호출
        for tool_call in assistant_msg.tool_calls:
            fn_name = tool_call.function.name
            fn_args_str = tool_call.function.arguments

            # 함수 인자를 JSON 디코딩
            fn_args = json.loads(fn_args_str) if fn_args_str else {}

            print(fn_name, fn_args_str)  # 디버그 출력 (어떤 함수가 어떻게 호출되는지)

            # 실제 함수 실행
            result = toolkits[fn_name](**fn_args)

            # 실행 결과를 role="tool" 메시지로 추가
            tool_message = {
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": result
            }
            chat_history.append(tool_message)

        # 4) tool 메시지를 추가한 뒤, while 루프를 반복하여
        #    모델이 다시 한 번(또는 여러 번) 함수 호출할지 확인
        #    없으면 break

    # 함수 호출이 없을 때(while 탈출 시), 최종적으로 assistant_msg.content 리턴
    return assistant_msg.content


########################################
# 7. 실행 예시
########################################

if __name__ == "__main__":
    # 첫 테스트: "A 함수를 호출해"라고 요청해보기
    # 모델이 z부터 b까지 차례로 호출 후 최종적으로 a_func를 호출하는지 확인
    answer = openai_llm("A 함수를 호출해", [])
    print("A I >", answer)

    # 대화 루프 예시
    # - 사용자가 원하는 요청을 입력하면, 모델이
    #   필요한 함수를 순차적으로 호출할 수 있음
    chat_history = []
    while True:
        user_input = input("\nUSER> ")
        if user_input.lower() == "quit":
            break
        ai_response = openai_llm(user_input, chat_history)
        print(f"A I> {ai_response}")

3. 동작 확인

예시로, “A 함수를 호출해”라고 입력하면, 콘솔에 함수 호출 과정이 순서대로 찍히고, 최종적으로 “a_func”가 호출된 결과(문자열 "abcdefghijklmnopqrstuvwxyz" 등)가 출력됩니다.
실제로는 모델이 단계마다 z_func를 호출하여 "z" 토큰을 만들고, y_func(z_token="z")를 호출해 "yz"를 만든 뒤, x_func(y_token="yz")를 호출해 "xyz" … 이런 식으로 중간 함수 결과를 축적합니다.

콘솔 로그:

4. 정리 및 활용 아이디어

  • Function Calling을 통해, 단순 “모델 출력 파싱” 과정을 자동화
  • 복잡한 함수 간 의존이 있어도, 모델이 “먼저 필요한 정보를 얻기 위한 함수 → 후속 함수” 순으로 여러 번 호출 가능
  • 실제로는 사내 API, DB, 외부 API 등을 호출하여 다양한 작업을 수행할 수 있음
  • 함수 개수가 많을수록(10~20개 이상) 모델이 올바른 함수를 찾기 어려울 수 있으므로, 함수명을 직관적으로 지어주고, 시스템 메시지나 함수 description에서 활용 맥락을 자세히 안내해주면 좋습니다.

이로써, 서로 의존관계를 가지는 다수의 함수를 OpenAI Function Calling으로 유연하게 구성하는 방법을 살펴봤습니다. 

Reference

0
0개의 댓글
로그인 후 이용해주세요!