Skip to main content
Glama
simple_agent.py17.7 kB
""" 간단한 LangGraph 에이전트 구현 (Simple LangGraph Agent Implementation) 이 파일은 AI 에이전트의 핵심 로직을 구현합니다. 사용자의 질문을 받아서 AI 모델(LLM)에게 전달하고, 응답을 반환하는 역할을 합니다. LangGraph란? - AI 에이전트의 작업 흐름(workflow)을 그래프 구조로 정의할 수 있게 해주는 라이브러리입니다. - Java로 비유하면: 상태 머신(State Machine) 또는 워크플로우 엔진과 유사합니다. - 노드(Node)와 엣지(Edge)로 구성되며, 각 노드는 특정 작업을 수행합니다. LangChain이란? - AI 애플리케이션을 만들기 위한 프레임워크입니다. - 다양한 AI 모델(OpenAI, Google Gemini 등)을 통합하여 사용할 수 있게 해줍니다. - Java의 Spring Framework와 비슷한 역할입니다. """ import logging import sys # typing: Python의 타입 힌트를 위한 모듈 (Java의 제네릭과 유사) # - Literal: 특정 값만 허용하는 타입 (예: Literal["end"]는 "end"만 가능) # - TypedDict: 딕셔너리의 구조를 정의 (Java의 DTO 클래스와 유사) from typing import Literal, TypedDict # langchain_core.messages: AI와 대화할 때 사용하는 메시지 타입들 # - SystemMessage: 시스템 프롬프트 (AI의 역할/성격 정의) # - HumanMessage: 사용자의 메시지 # - AIMessage: AI의 응답 메시지 # - BaseMessage: 모든 메시지의 기본 타입 (Java의 인터페이스와 유사) from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage # ChatOpenAI: OpenAI API (또는 호환 API)를 호출하는 클래스 # Java로 비유하면: RestTemplate 또는 HttpClient와 유사한 API 클라이언트입니다. from langchain_openai import ChatOpenAI # LangGraph 관련 import: # - StateGraph: 상태 기반 워크플로우를 정의하는 클래스 # - END: 워크플로우의 종료를 나타내는 상수 # - CompiledStateGraph: 컴파일된(실행 가능한) 워크플로우 from langgraph.graph import END, StateGraph from langgraph.graph.state import CompiledStateGraph # 애플리케이션 설정을 가져옵니다. from app.core.config import config # ======================================== # 로거 설정 (Logger Configuration) # ======================================== # 로거 인스턴스 생성 logger = logging.getLogger(__name__) # StreamHandler: 로그를 어디로 출력할지 결정합니다. # sys.stderr = 표준 에러 스트림 (콘솔의 에러 출력) # Java의 System.err과 동일합니다. # 참고: 일반 출력(sys.stdout)과 에러 출력(sys.stderr)을 분리하여 관리합니다. handler = logging.StreamHandler(sys.stderr) # Formatter: 로그 메시지의 형식을 지정합니다. # Java의 PatternLayout과 동일한 역할입니다. formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) # ======================================== # 에이전트 상태 정의 (Agent State Definition) # ======================================== class AgentState(TypedDict): """ 에이전트의 상태를 정의하는 클래스 TypedDict는 Python의 딕셔너리에 타입 정보를 추가한 것입니다. Java로 비유하면: public class AgentState { private List<BaseMessage> messages; private String userQuery; private String finalResponse; } 워크플로우가 실행되는 동안 이 상태 객체가 각 노드를 거치면서 업데이트됩니다. 마치 Java의 Context 객체나 DTO가 메서드 체인을 따라 전달되는 것과 비슷합니다. """ # messages: 대화 히스토리를 저장하는 리스트 # Java의 List<BaseMessage>와 동일합니다. messages: list[BaseMessage] # user_query: 사용자가 입력한 질문 # Java의 String userQuery;와 동일합니다. user_query: str # final_response: AI의 최종 응답 # str | None = Java의 String (nullable) 또는 Optional<String>과 유사합니다. # Python 3.10+에서는 | 연산자로 Union 타입을 표현합니다. final_response: str | None # ======================================== # SimpleAgent 클래스 # ======================================== class SimpleAgent: """ 간단한 AI 에이전트 클래스 역할: 1. 사용자의 질문을 받습니다. 2. AI 모델(LLM)에게 질문을 전달합니다. 3. AI의 응답을 받아서 반환합니다. LangGraph 워크플로우 구조: [시작] -> [LLM 호출] -> [종료] Java로 비유하면 Service 클래스와 유사합니다: @Service public class SimpleAgent { ... } """ def __init__(self): """ 생성자 (Constructor) Java의 생성자와 동일합니다: public SimpleAgent() { ... } 초기화 작업: 1. ChatOpenAI 클라이언트를 생성합니다. (AI 모델과 통신하는 객체) 2. LangGraph 워크플로우를 정의하고 컴파일합니다. """ # ======================================== # LLM (Large Language Model) 클라이언트 생성 # ======================================== # ChatOpenAI: OpenAI API를 호출하는 클라이언트 객체 # 실제로는 Gemini API를 사용하지만, OpenAI 호환 인터페이스로 호출합니다. # # Java로 비유하면: # RestTemplate llm = new RestTemplate(); # llm.setUrl("https://api.openai.com/..."); self.llm = ChatOpenAI( model=config.OPENAI_MODEL, # 사용할 AI 모델 이름 (예: "gemini-2.5-flash") temperature=config.OPENAI_TEMPERATURE, # 응답의 창의성 조절 (0.0 ~ 2.0) api_key=config.OPENAI_API_KEY, # API 인증 키 ) # 로그 출력: LLM 객체 정보 확인 logger.info(f"어떻게 나오는지 보자 : {self.llm}") # ======================================== # LangGraph 워크플로우 생성 # ======================================== # StateGraph: 상태 기반 워크플로우를 생성합니다. # AgentState를 인자로 전달하여, 이 워크플로우가 AgentState 타입의 상태를 사용함을 명시합니다. # # Java로 비유하면: # WorkflowBuilder<AgentState> workflow = new WorkflowBuilder<>(); self.workflow = StateGraph(AgentState) # ======================================== # 워크플로우 컴파일 # ======================================== # _compile() 메서드를 호출하여 워크플로우를 정의하고 컴파일합니다. # 컴파일된 워크플로우는 실행 가능한 상태가 됩니다. # # Java로 비유하면: # CompiledWorkflow compiledWorkflow = workflow.build(); # # 타입 힌트 : CompiledStateGraph는 이 변수의 타입을 명시합니다. self.compiled_workflow: CompiledStateGraph = self._compile() # ======================================== # 메인 실행 메서드 # ======================================== async def invoke(self, user_query: str) -> str: """ 에이전트를 실행하고 응답을 반환합니다. 이것이 외부에서 호출하는 메인 메서드입니다. Java로 비유하면: public String invoke(String userQuery) { ... } async/await란? - Python의 비동기 프로그래밍 키워드입니다. - Java의 CompletableFuture, Spring의 @Async와 유사합니다. - 네트워크 I/O 대기 시간 동안 다른 작업을 처리할 수 있게 해줍니다. 매개변수 (Parameters): user_query (str): 사용자의 질문 반환값 (Returns): str: AI의 응답 실행 흐름: 1. 초기 상태(initial_state)를 생성합니다. 2. 워크플로우를 실행합니다. (ainvoke = async invoke) 3. 최종 응답을 추출하여 반환합니다. """ # ======================================== # 초기 상태 생성 # ======================================== # AgentState 타입의 딕셔너리를 생성합니다. # Java로 비유하면: # AgentState initialState = new AgentState(); # initialState.setMessages(new ArrayList<>()); # initialState.setUserQuery(userQuery); # initialState.setFinalResponse(null); initial_state: AgentState = { "messages": [], # 빈 메시지 리스트로 시작 "user_query": user_query, # 사용자 질문 저장 "final_response": None, # 아직 응답이 없으므로 None (Java의 null) } # ======================================== # 워크플로우 실행 # ======================================== # await: 비동기 함수의 완료를 기다립니다. # Java의 future.get()이나 CompletableFuture.join()과 유사합니다. # # ainvoke(): 워크플로우를 비동기로 실행합니다. # - initial_state를 입력으로 전달 # - 워크플로우의 각 노드를 순서대로 실행 # - 최종 상태(result)를 반환 result: AgentState = await self.compiled_workflow.ainvoke(initial_state) # ======================================== # 최종 응답 반환 # ======================================== # result.get("final_response", "No response generated"): # - result 딕셔너리에서 "final_response" 키의 값을 가져옵니다. # - 만약 키가 없거나 None이면 "No response generated"를 기본값으로 반환합니다. # # Java로 비유하면: # return Optional.ofNullable(result.getFinalResponse()) # .orElse("No response generated"); return result.get("final_response", "No response generated") # ======================================== # LLM 호출 메서드 (워크플로우 노드) # ======================================== async def call_llm(self, state: AgentState) -> AgentState: """ AI 모델(LLM)을 호출하는 노드 이 메서드는 워크플로우의 노드로 등록됩니다. 워크플로우가 실행될 때 자동으로 호출됩니다. Java로 비유하면: public AgentState callLlm(AgentState state) { ... } 매개변수 (Parameters): state (AgentState): 현재 워크플로우 상태 반환값 (Returns): AgentState: 업데이트된 상태 (AI 응답이 추가됨) 실행 흐름: 1. 시스템 메시지와 사용자 메시지를 준비합니다. 2. AI 모델을 호출합니다. 3. 응답을 상태에 저장하고 반환합니다. """ # ======================================== # 메시지 준비 # ======================================== # AI에게 보낼 메시지 리스트를 생성합니다. # Java로 비유하면: # List<Message> messages = new ArrayList<>(); # messages.add(new SystemMessage("You are...")); # messages.add(new HumanMessage(userQuery)); messages = [ # SystemMessage: AI의 역할/성격을 정의하는 시스템 프롬프트 # "당신은 도움이 되는 AI 어시스턴트입니다"라는 지시를 AI에게 줍니다. SystemMessage(content="You are a helpful AI assistant."), # HumanMessage: 사용자의 실제 질문 # state["user_query"]에서 사용자가 입력한 질문을 가져옵니다. HumanMessage(content=state["user_query"]), ] # ======================================== # AI 모델 호출 # ======================================== # await self.llm.ainvoke(messages): # - AI 모델에게 메시지를 보내고 응답을 기다립니다. # - 비동기 HTTP 요청을 보내는 것과 유사합니다. # # Java로 비유하면: # AIMessage response = restTemplate.postForObject( # "https://api.openai.com/chat/completions", # messages, # AIMessage.class # ); # # 반환 타입: AIMessage (AI의 응답 메시지 객체) response: AIMessage = await self.llm.ainvoke(messages) # ======================================== # 상태 업데이트 # ======================================== # state["messages"]: 전체 대화 히스토리를 저장합니다. # messages + [response]: 기존 메시지 리스트에 AI 응답을 추가합니다. # Python의 리스트 연결: [a, b] + [c] = [a, b, c] state["messages"] = messages + [response] # state["final_response"]: AI의 응답 텍스트를 저장합니다. # response.content: AIMessage 객체에서 실제 텍스트 내용을 추출합니다. state["final_response"] = response.content # 업데이트된 상태를 반환합니다. # LangGraph가 이 상태를 다음 노드로 전달합니다. return state # ======================================== # 종료 조건 판단 메서드 # ======================================== def should_end(self, state: AgentState) -> Literal["end"]: """ 워크플로우를 종료할지 결정하는 메서드 Literal["end"]: 반환값이 정확히 "end" 문자열만 가능함을 의미합니다. Java의 enum과 비슷한 개념입니다: public enum Decision { END } public Decision shouldEnd(AgentState state) { return Decision.END; } 이 간단한 예제에서는 항상 종료합니다. 더 복잡한 에이전트에서는 조건에 따라 다른 노드로 이동할 수 있습니다. 예를 들어: - "end": 종료 - "continue": 다른 노드로 계속 - "retry": 재시도 """ return "end" # ======================================== # 워크플로우 컴파일 메서드 # ======================================== def _compile(self) -> CompiledStateGraph: """ LangGraph 워크플로우를 정의하고 컴파일합니다. Java의 Builder 패턴과 유사합니다: return new WorkflowBuilder() .addNode("llm", this::callLlm) .setEntryPoint("llm") .addEdge("llm", END) .build(); 워크플로우 구조: [시작] -> "llm" 노드 (call_llm 실행) -> [종료] 반환값 (Returns): CompiledStateGraph: 실행 가능한 워크플로우 """ # ======================================== # 노드 추가 # ======================================== # add_node("이름", 함수): # - "llm"이라는 이름의 노드를 추가합니다. # - 이 노드가 실행되면 self.call_llm 메서드가 호출됩니다. # # Java로 비유하면: # workflow.addNode("llm", this::callLlm); self.workflow.add_node("llm", self.call_llm) # ======================================== # 시작점 설정 # ======================================== # set_entry_point("노드 이름"): # - 워크플로우가 시작될 때 가장 먼저 실행될 노드를 지정합니다. # - "llm" 노드부터 시작합니다. self.workflow.set_entry_point("llm") # ======================================== # 조건부 엣지 추가 # ======================================== # add_conditional_edges(): # - 특정 노드 실행 후, 조건에 따라 다음 노드를 결정합니다. # # 매개변수: # 1. "llm": 이 노드가 실행된 후 # 2. self.should_end: 이 함수를 호출하여 다음 행동을 결정 # 3. {"end": END}: should_end()가 "end"를 반환하면 워크플로우 종료 # # Java로 비유하면: # workflow.addConditionalEdge("llm", this::shouldEnd, Map.of("end", END)); # # END: LangGraph의 특수 상수로, 워크플로우 종료를 의미합니다. self.workflow.add_conditional_edges( "llm", # 출발 노드 self.should_end, # 조건 판단 함수 {"end": END}, # 반환값에 따른 다음 노드 매핑 ) # ======================================== # 컴파일 # ======================================== # compile(): 워크플로우를 실행 가능한 상태로 컴파일합니다. # Java의 build() 메서드와 유사합니다. # 컴파일 과정에서 워크플로우의 유효성을 검증하고 최적화합니다. return self.workflow.compile() # ======================================== # 전역 에이전트 인스턴스 (Global Agent Instance) # ======================================== # SimpleAgent의 싱글톤 인스턴스를 생성합니다. # 애플리케이션 전체에서 하나의 에이전트 인스턴스만 사용합니다. # # Java로 비유하면: # public static final SimpleAgent agent = new SimpleAgent(); # # 또는 Spring의 @Bean: # @Bean # public SimpleAgent agent() { # return new SimpleAgent(); # } # # 이 인스턴스는 tool_router.py에서 import되어 사용됩니다. agent = SimpleAgent()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tmzmfldkqls/test-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server