본문 바로가기
AI Study/AI Agent

Agent Tool Call 구현 및 Tool 호출에 따른 조건 추가하기

by 하람 Haram 2026. 2. 3.
728x90

Tool Call

Tool Call 방식은 Agent가 유기적으로 상황에 필요한 Tool을 호출하고 그거에 대한 답변을 반환하는 방식이다

그렇기 때문에 당연히 LLM을 로드하는 것부터가 시작이다

class My_Agent:
    def __init__(self):
        self.name = 'My_Agent'
        self.my_llm = ChatOpenAI(
            openai_api_base=base_url,
            openai_api_key=api_key,
            model="gpt-5.2",
            temperature=0
        )

대부분 Tool을 사용한다는 것은 일정한 대답을 원하는 경우가 많기 때문에

LLM의 temperature는 0으로 설정하였다

 

Tool 정의하기

그러면 이제 Tool을 정의해줄 차례인데

Tool은 Class 밖에 먼저 선언되어야 한다

@Tool  데코레이터를 사용하면 되는데 자주쓰는 양식은 다음과 같다

# Type 1
@tool("First_tool", return_direct=True, description="이것은 나의 첫번째 tool 입니다")
def First_tool(param1: str, param2: int) -> str:
    logger.info(f"MY First Tool")


# Type 2 
@tool("Second_tool", return_direct=False)
def Second_tool(param1: str, param2: int) -> str:
    '''
    이것은 나의 두번째 tool 입니다
    '''
    logger.info(f"MY First Tool")

지금은 별 기능이 없기 때문에 첫번째, 두번째 tool 입니다로 description과 doc-string 을 간단히 적었는데

여기에 tool에 대한 자세한 설명을 정의하면 좋다

 

추가적은 return_direct란 결과를 AI가 한번 더 가공할 것인지 아니면 그대로 뱉어낼 것인지를 결정하는 것이다

> return_direct
return_direct=False이면 도구의 결과가 모델 내부로 다시 들어가서 후처리나 추가 설명을 거친 뒤 사용자에게 전달.
return_direct=True이면 모델이 그 결과를 가공하지 않고 도구의 반환값을 그대로 사용자에게 보여줌

 

 

Tool 연결하기

이렇게 LLM과 Tool을 만들었으면 이 둘을 Bind (연결)시켜주면 된다

from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, ToolMessage

# tool은 class 밖에 있어야 한다
@tool("First_tool", return_direct=True, description=prompts["My_Agent"]["First_tool"])
def First_tool(param1: str, param2: int) -> str:
    logger.info(f"MY First Tool")

@tool("Second_tool", return_direct=False)
def Second_tool(param1: str, param2: int) -> str:
    '''
    이것은 나의 두번째 tool 입니다
    '''
    logger.info(f"MY First Tool")


class My_Agent:
    def __init__(self):
        self.name = 'My_Agent'
        self.my_llm = ChatOpenAI(
            openai_api_base=base_url,
            openai_api_key=api_key,
            model="gpt-5.2",
            temperature=0
        ) 
        

        # run 메서드의 docstring을 동적으로 주입
        # ✅ 클래스 함수 객체의 docstring 수정
        My_Agent.run.__doc__ = self.description
        #self.run.__doc__ = self.description
        
    def __str__(self):
        return f"[{self.name}] {self.description}"
        
        
    def run(self,state: State):
        """tool을 사용하는 Agent 입니다"""
        logger.info(f"[{self.name}]가 실행됩니다")
	    messages = HumanMessage(content=state["user_prompt"]),
        # 도구를 tools 리스트에 추가
        self.tools = [First_tool,Second_tool]
        self.my_agent = self.my_llm.bind_tools(self.tools)
        # 도구를 모델에 바인딩: 모델에 도구를 바인딩하면, 도구를 사용하여 llm 답변을 생성할 수 있음
        response = self.my_agent.invoke(messages)

 

이렇게 넣어서 답변을 생성해보면

response 객체에 tool_calls 속성이 있게 된다

 

Tool Call 에 따른 Control 해보기

 

이를 이용해서 조건문을 만들어보면

if isinstance(response, AIMessage):
	tool_calls = getattr(response, "tool_calls", None) or response.additional_kwargs.get("tool_calls")

이렇게 만들 수 있다

 

isinstance(response, AIMessage)

→ response가 AI 모델의 메시지 객체인지 확인. LangChain이나 LangGraph에서는 모델의 출력이 AIMessage 형태로 반환

 

getattr(response, "tool_calls", None)

→ response 객체에 tool_calls 속성이 있으면 그 값을 가져오고, 없으면 None을 반환.

 

response.additional_kwargs.get("tool_calls")

→ 일부 모델 응답은 tool_calls가 additional_kwargs 딕셔너리 안에 들어 있을 수 있기 때문에 첫 번째 방법으로 못 찾았을 때, 이 경로로 한 번 더 확인.

 

 

이제 이걸 연결하여서 조건을 만들어 본다면

def Tool_Control(self,response):
    tool_call_names = []
    # 5) tool_calls가 있으면 실제 실행하고 ToolMessage로 결과를 이어붙임
    if isinstance(response, AIMessage):
        tool_calls = getattr(response, "tool_calls", None) or response.additional_kwargs.get("tool_calls")
    #if isinstance(response, AIMessage) and response.tool_calls:
        tool_msgs = []
        for call in response.tool_calls:
            name = call["name"] # "First_Tool"
            args = call["args"] # {"param1": "test", "param2": 3} 등
            tool_call_names.append(name)
            
    return tool_call_names

이렇게 쪼개서 각각 Tool에 대한 시나리오를 설정할 수 있다

 

 

이제 이거를 run 함수에 연결해서 Agent가 자연스럽게 Tool_Control까지 할 수 있게 하면 끝

(Tool Control은 필요시에만 사용한다)

    def Tool_Control(self,response):
        tool_call_names = []
        # 5) tool_calls가 있으면 실제 실행하고 ToolMessage로 결과를 이어붙임
        if isinstance(response, AIMessage):
            tool_calls = getattr(response, "tool_calls", None) or response.additional_kwargs.get("tool_calls")
        #if isinstance(response, AIMessage) and response.tool_calls:
            tool_msgs = []
            for call in response.tool_calls:
                name = call["name"] # "First_Tool"
                args = call["args"] # {"param1": "test", "param2": 3} 등
                tool_call_names.append(name)
                
        return tool_call_names
    
    def run(self,state: State):
        """tool을 사용하는 Agent 입니다"""
        logger.info(f"[{self.name}]가 실행됩니다")
	    messages = HumanMessage(content=state["user_prompt"]),
        # 도구를 tools 리스트에 추가
        self.tools = [First_tool,Second_tool]
        self.my_agent = self.my_llm.bind_tools(self.tools)
        # 도구를 모델에 바인딩: 모델에 도구를 바인딩하면, 도구를 사용하여 llm 답변을 생성할 수 있음
        response = self.my_agent.invoke(messages)
        tool_control_result ,tool_call_names  =self.Tool_Control(response)
728x90