본문 바로가기
AI Study/AI Agent

LangGraph, Agent의 Description, Doc-string Yaml로 관리법

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

Doc-string 관리

Agent를 LangGraph로 구현해서 사용하다 보면

run 함수나 tool 정의에 있어서 다음과 같은 Error 가 나온다

ValueError: Function must have a docstring if description not provided

해결방법은 생각보다 단순한데 그냥 해당 오류가 나오는 곳에 doc-string으로 description을 적어서

Agent에게 해당 함수를 설명해 주면된다

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

근데 이거는 단순하게 구현을 했을 때는 문제가 없이 깔끔해 보일 수 있지만

Prompt가 길어질 수록 Prompt를 따로 관리하고 싶어지게 된다 (본인은 yaml로)

 

이에 따라 두 가지 경우가 나뉜다

 

- tool 에 대한 Description 관리

이거는 굉장히 단순하게 해결할 수 있다

 

위에 ValueError라 하고 뒤에 나오 듯이 doc-string을 적어주던가 description attribute를 추가해주면 된다

- a. description을 추가하는 방법

@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")

 

- b. doc-string 을 직접 적어주는 방법

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

 

 

- Agent Class 내부의 Description 관리

이 경우는 약간의 꼼수를 부리면 된다

나같은 경우는 Orchestration Agent가 각각 Agent의 run을 호출하는 다음과 같은 구조여서

class Orchestration_Agent:
    def __init__(self):
        self.name = 'Orchestration_Agent'
        self.orch_llm = ChatOpenAI(
            openai_api_base=base_url,
            openai_api_key=api_key,
            model="gpt-5.2",
            temperature=0
        ) 
        self.agents = [Agent1().run, Agent2().run, My_Agent().run]
        self.orch_agent = self.orch_llm.bind_tools(self.agents)

각 Agent에 구현한 run함수가 tool 방식을 채택하고 있다.

골치 아픈 것은 이 run 함수 자체가 tool이 아니기 때문 @tool 데코레이터를 쓰기에는 좀 껄끄러운 점도 있다

 

그러면 doc-string을 다이렉트로 쓰는 방법 밖에 없냐?

 

다음과 같이 꼼수로 init 생성자에 run 함수에 대한 doc string 을 덮어쓰기 시키는 방식을 하면 된다

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을 yaml description으로 설정
        self.description = prompts["My_Agent"]["description"]

        # run 메서드의 docstring을 동적으로 주입
        # ✅ 클래스 함수 객체의 docstring 수정
        My_Agent.run.__doc__ = self.description
        #self.run.__doc__ = self.description

 

참고 : 만약 @tool을 사용하겠다고 하면
@tool("My_Agent_run", description=prompts["My_Agent"]["description"])
    def run(self,state: State):
        logger.info(f"[{self.name}]가 실행됩니다")
TypeError:'StructuredTool' object is not callable

이런 걸 볼 수 있다

즉, @tool 데코레이터가 붙는 순간 더이상 함수가 아니기 때문에

위에 Agent run을 list로 나열하고

이중에 뽑아서 쓰는 방식이 아닌

agent1 = Agent1().run
result = agent1.invoke({"state": state})

뭐 이런식으로 해야되서 귀찮아 진다...;;;

 

아래 같은 Error 가 나오면

AttributeError:attribute '__doc__' of 'method' objects is not writable

위에 구조를 잘 따랐는지 확인해보자

 

 

최종적으로는 다음과 같이 예시코드를 짜볼 수 있다

'''
Agent에 Doc-string을 yaml로 관리하는 방법
'''
from core.graph.state import State
import yaml
with open("config/prompt.yaml", "r", encoding="utf-8") as f:
    prompts = yaml.safe_load(f)

# main에 사용한 logger 불러오기
import logging
logger = logging.getLogger("main")


'''
return_direct=False이면 도구의 결과가 모델 내부로 다시 들어가서 후처리나 추가 설명을 거친 뒤 사용자에게 전달.
return_direct=True이면 모델이 그 결과를 가공하지 않고 도구의 반환값을 그대로 사용자에게 보여줌.
'''
# 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=True)
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을 yaml description으로 설정
        self.description = prompts["My_Agent"]["description"]

        # 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):
        logger.info(f"[{self.name}]가 실행됩니다")
        # 도구를 tools 리스트에 추가
        self.tools = [production_stats,production_stats2]
        self.my_agent = self.my_llm.bind_tools(self.tools)
        # 도구를 모델에 바인딩: 모델에 도구를 바인딩하면, 도구를 사용하여 llm 답변을 생성할 수 있음
        
        
class Orchestration_Agent:
    def __init__(self):
        self.name = 'Orchestration_Agent'
        self.orch_llm = ChatOpenAI(
            openai_api_base=base_url,
            openai_api_key=api_key,
            model="gpt-5.2",
            temperature=0
        ) 
        self.agents = [Agent1().run, Agent2().run, My_Agent().run]
        self.agent_dict = {
        "Doc-Agent": Agent1().run,
        "Graph-Agent": Agent2().run,
        "Trans-Agent": My_Agent().run,
        }
        self.orch_agent = self.orch_llm.bind_tools(self.agents)
        
        
    def run(self,state: State):
        agent = self.agent_dict.get(action)
        if not agent:
            continue

        # logger.info(f"[Processing] {action} 실행")
        # try:
        if action == "Agent":
            result = agent(state, factory_name)
            # logger.info("search_result : ",result)
        elif action == "Graph-Agent":
            result = agent(state, state["search_result"]["processed_df"])
            # 
        elif action == "Trans-Agent":
            result = agent(state)
        else:# 이거 나올 일 없긴 함
            logger.info(f"please make this Agent : {action}",)
            result = agent(state)
        if result is not None:
            state.update(result)

 

 

728x90