다양한 주제에 대해 논의하거나 지식을 공유합니다.
다양한 주제에 대해 논의하거나 지식을 공유합니다.
🏆 당첨자 발표※ 참여 신청을 하지 않은 신청자는 당첨에서 제외됩니다.no.구분성함(닉네임)경품1이벤트 1당첨자chaeyo스타벅스 커피 쿠폰 (tall사이즈) 2용병대주둔지스타벅스 커피 쿠폰 (tall사이즈) 3cocudeny스타벅스 커피 쿠폰 (tall사이즈) 4yuri스타벅스 커피 쿠폰 (tall사이즈) 5RYUN스타벅스 커피 쿠폰 (tall사이즈) 6여리스타벅스 커피 쿠폰 (tall사이즈) 7지니동동 스타벅스 커피 쿠폰 (tall사이즈) 8김평스타벅스 커피 쿠폰 (tall사이즈) 9어텐션스타벅스 커피 쿠폰 (tall사이즈) 10스티브킴스타벅스 커피 쿠폰 (tall사이즈) 11윤종인스타벅스 커피 쿠폰 (tall사이즈) 12ㅐㅓㅐ스타벅스 커피 쿠폰 (tall사이즈) 13손동성스타벅스 커피 쿠폰 (tall사이즈) 14사과맛123스타벅스 커피 쿠폰 (tall사이즈) 15어쿠스틱 스타벅스 커피 쿠폰 (tall사이즈) 16권형준스타벅스 커피 쿠폰 (tall사이즈) 17민준진스타벅스 커피 쿠폰 (tall사이즈) 18다영김스타벅스 커피 쿠폰 (tall사이즈) 19팔랑귀스타벅스 커피 쿠폰 (tall사이즈) 20달빛초월스타벅스 커피 쿠폰 (tall사이즈) 21버니스타벅스 커피 쿠폰 (tall사이즈) 22류나이스타벅스 커피 쿠폰 (tall사이즈) 23기저스타벅스 커피 쿠폰 (tall사이즈) 24유선우스타벅스 커피 쿠폰 (tall사이즈) 25비전트스타벅스 커피 쿠폰 (tall사이즈) 26김연승스타벅스 커피 쿠폰 (tall사이즈) 27차민준스타벅스 커피 쿠폰 (tall사이즈) 28문기렴스타벅스 커피 쿠폰 (tall사이즈) 29안단희스타벅스 커피 쿠폰 (tall사이즈) 30손동성스타벅스 커피 쿠폰 (tall사이즈) 31공선의스타벅스 커피 쿠폰 (tall사이즈) 32RYUN스타벅스 커피 쿠폰 (tall사이즈) 33곽진숙스타벅스 커피 쿠폰 (tall사이즈) 34곽진영스타벅스 커피 쿠폰 (tall사이즈) 35김재엽스타벅스 커피 쿠폰 (tall사이즈) 36홍지민스타벅스 커피 쿠폰 (tall사이즈) 37유선우스타벅스 커피 쿠폰 (tall사이즈) 38김가영스타벅스 커피 쿠폰 (tall사이즈) 39박주영스타벅스 커피 쿠폰 (tall사이즈) 40김평기스타벅스 커피 쿠폰 (tall사이즈) 41윤현지스타벅스 커피 쿠폰 (tall사이즈) 42루루빈스타벅스 커피 쿠폰 (tall사이즈) 43vitus9988스타벅스 커피 쿠폰 (tall사이즈) 44윤우식스타벅스 커피 쿠폰 (tall사이즈) 45이서희스타벅스 커피 쿠폰 (tall사이즈) 46조은서스타벅스 커피 쿠폰 (tall사이즈) 47윤채영스타벅스 커피 쿠폰 (tall사이즈) 48이정민스타벅스 커피 쿠폰 (tall사이즈) 49홍지민스타벅스 커피 쿠폰 (tall사이즈) 50정선아스타벅스 커피 쿠폰 (tall사이즈) 51이동규스타벅스 커피 쿠폰 (tall사이즈) 52김재엽스타벅스 커피 쿠폰 (tall사이즈) 53홍지민스타벅스 커피 쿠폰 (tall사이즈) 54노호현스타벅스 커피 쿠폰 (tall사이즈) 55임주왕스타벅스 커피 쿠폰 (tall사이즈) 56이벤트 2당첨자스티브킴편의점 상품권 1만원권 57윤종인편의점 상품권 1만원권 58기저편의점 상품권 1만원권 59주레피편의점 상품권 1만원권 60버니편의점 상품권 1만원권 61김평편의점 상품권 1만원권 62chaeyo편의점 상품권 1만원권 인공지능팩토리가 AOAI톤 아이디어 경진대회를 개최했습니다! 🎉이번 경진대회에서는 인공지능팩토리의 생성형 AI 플랫폼, 어시웍스를 직접 체험해보실 수 있습니다. 참가 응모만 해도 푸짐한 경품을 드립니다!놓치지 말고 지금 바로 참여하세요! 🎁 ✅ 참여 기간 : 24.12.16(월) ~ 24.12.22(일)✅ 결과 발표 : 12/27(금) *당첨자 개별 문자 발표✅ 참여 방법 : 인공지능팩토리에 가입하고 댓글로 적어주세요 (예시 : 1번 참여 완료했습니다!)✅ EVENT 상세 내용 이벤트 대상 페이지 : https://aifactory.space/task/8779/overview1️⃣ 경진대회 참가만 Click 해도!스타벅스 커피 쿠폰 (tall사이즈)총 50명 추첨2️⃣ 참가하고 + 토론 게시판에 샘플에 맞춰 게시글 작성하면편의점 상품권 1만원권 총 20명 추첨샘플을 참고해 작성 : https://aifactory.space/task/8779/discussion/3804많은 참여와 관심 부탁 드립니다. 😊 * 본 이벤트는 개인 정보를 포함하고 있으며, 개인 정보 활용 동의를 받고 있습니다. * 개인정보는 경품 지급을 위해서 활용하며 경품 지급 후 폐기됩니다. * 당첨 발표 후 2주 내 응답이 없을 시 당첨이 취소됩니다.
“어시웍스 - 생성형AI부터 에이전틱AI까지 비개발자도 뚝딱” 세미나가 1월 15일 오후 9시에 유튜브 라이브로 진행됩니다. 접속하기 >> https://www.youtube.com/live/7m5Xzfd95hw ✅ 라이브에서 다룰 내용생성형AI를 적용하는 업무 레벨 정의Function : 텍스트 생성(LLM)이나 API 호출을 하나의 작업 단위 생성Flow : 여러 Function, 혹은 사용자 입력을 연결하여 복잡한 로직 생성Agent : 사용자와의 대화환경에서, 등록된 여러 Function을 동적으로 호출할 수 있는 “지능형 어시스턴트" 생성Team : 여러 Agent를 협업 환경 구축🙇♂️ 연사님을 소개합니다.💬 김태영 대표(주)인공지능팩토리 CEOMicrosoft Regional Director & MVP랭체인 코리아, 케라스 코리아, 캐글 코리아 운영진(전) 한국항공우주연구원 연구원(논문) 네이처 자매지 공동1저자 : Nature Astronomy “Solar farside magnetograms from deep learning analysis of STEREO/EUVI data(저서) 블록과 함께 하는 파이썬 딥러닝 케라스 (디지털북스, 2017)💌 SNS & EmailEmail: tykim@aifactory.pageYoutube : https://www.youtube.com/@tykimosFacebook : https://www.facebook.com/tykimoBlog : https://tykimos.github.io📍 테디노트 유튜브 채널📍 "테디노트의 RAG 비법노트" 랭체인 강의: https://fastcampus.co.kr/data_online_…📘 랭체인 한국어 튜토리얼(무료 전자책): https://wikidocs.net/book/14314📝 테디노트(깃헙 블로그) : https://teddylee777.github.io💻 GitHub 소스코드 저장소: https://github.com/teddylee777
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_tokena_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 루프로 재시도)코드 전문<code class="language-python"># -*- 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}")</code>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으로 유연하게 구성하는 방법을 살펴봤습니다. ReferenceOpenAI Function Calling 문서OpenAI Cookbook: How to call functions with chat models
이 글에서는 LangGraph 라이브러리의 체크포인트 기능을 이용해, 사용자별로 대화 맥락을 유지할 수 있는 챗봇을 구현해보겠습니다. Anthropic Claude 모델을 활용하여 자연스럽게 대화를 이어가면서, thread_id를 통해 서로 다른 사용자의 대화를 독립적으로 관리하는 방법을 시연합니다.1. 배경: 왜 멀티턴 메모리가 필요한가?지금까지 단순 챗봇을 구성해본 분들은, 한 번의 질의-응답 이후 맥락이 사라지거나, 프로그램이 재시작되면 이전 대화 내용을 잊어버리는 문제를 종종 겪었을 것입니다.예:“내 이름이 홍길동이야” → “반갑습니다, 홍길동님”…(대화 종료 후) 다시 시작하면 홍길동이라는 사실을 잊어버림.LangGraph는 이런 문제를 해결하기 위해 **체크포인트(Checkpoint)**라는 개념을 도입했습니다. 체크포인트는 노드가 실행된 후의 상태(State)를 자동으로 저장해두고, 동일 thread_id로 그래프를 다시 호출하면 그 상태를 자동 로드하여 이어갈 수 있도록 해줍니다.2. 코드 예시다음 코드를 ch4_memory_chatbot.py라 하고, python ch4_memory_chatbot.py로 실행하면 됩니다.<code class="language-plaintext">import os # Anthropic Claude from langchain_anthropic import ChatAnthropic # LangGraph 핵심 from langgraph.graph import StateGraph, START from langgraph.graph.message import add_messages # 체크포인트 from langgraph.checkpoint.memory import MemorySaver # (DB 사용 시 SqliteSaver, PostgresSaver 등 사용 가능) from typing import Annotated from typing_extensions import TypedDict ############################################################################### # 1) 상태(State) 정의 ############################################################################### class State(TypedDict): messages: Annotated[list, add_messages] ############################################################################### # 2) 그래프 빌더 생성 ############################################################################### graph_builder = StateGraph(State) ############################################################################### # 3) LLM ############################################################################### llm = ChatAnthropic( model="claude-3-5-sonnet-20241022", anthropic_api_key=os.environ.get("ANTHROPIC_API_KEY") ) def chatbot_node(state: State): """ state["messages"]를 LLM에게 전달하고, 새 AI 응답을 반환. """ ai_resp = llm.invoke(state["messages"]) return {"messages": [ai_resp]} # 노드 등록 graph_builder.add_node("chatbot", chatbot_node) # START -> chatbot graph_builder.add_edge(START, "chatbot") ############################################################################### # 4) 체크포인터 설정 ############################################################################### memory = MemorySaver() ############################################################################### # 5) 그래프 컴파일 ############################################################################### # compile(...) 시 checkpointer 지정 graph = graph_builder.compile(checkpointer=memory) ################################################################################ # 그래프 시각화를 위한 ASCII 아트 생성 ################################################################################ # - graph.get_graph().draw_ascii()를 호출하여 그래프 구조를 ASCII 아트로 표현 # - 노드와 엣지의 연결 관계를 시각적으로 보여줌 ascii_graph = graph.get_graph().draw_ascii() print(ascii_graph) ############################################################################### # 6) 메인 함수 ############################################################################### def main(): print("==== [ch4] Memory/Checkpoint Demo ====") print("Type 'quit' or 'q' to exit.\n") # 각 유저별 thread_id를 가정 thread_id = input("Enter thread_id (e.g., 'user1'): ").strip() or "default" while True: user_input = input(f"(thread={thread_id}) You: ") if user_input.lower() in ["quit", "q"]: print("Goodbye!") break # graph 실행: # (1) input: {"messages": [("user", user_input)]} # (2) config: {"configurable": {"thread_id": thread_id}} # => LangGraph가 thread_id='...' 체크포인트 상태를 로드 & 업데이트 events = graph.stream( {"messages": [("user", user_input)]}, {"configurable": {"thread_id": thread_id}}, ) # 실행 중 생기는 event를 출력 for event in events: for node_out in event.values(): if "messages" in node_out: last_msg = node_out["messages"][-1] print("Assistant:", last_msg.content) if __name__ == "__main__": main()</code>3. 코드 흐름 분석State(TypedDict) 정의<code class="language-python">class State(TypedDict): messages: Annotated[list, add_messages]</code>챗봇 대화에는 사용자 메시지와 AI(Claude) 메시지를 순서대로 저장하는 messages 리스트가 필요합니다.add_messages를 사용하면 새 메시지를 추가할 때마다 기존 리스트에 “append” 방식으로 쌓입니다.그래프 빌더(StateGraph)graph_builder = StateGraph(State)로 그래프 설계자(Builder)를 생성합니다.노드와 엣지를 등록한 뒤 compile하여 실행 가능한 그래프 객체를 만들어냅니다.LLM 노드(chatbot_node)<code class="language-python">def chatbot_node(state: State): ai_resp = llm.invoke(state["messages"]) return {"messages": [ai_resp]}</code>state["messages"]를 Anthropic Claude에 전달해 새 AI 응답을 생성하는 노드입니다.반환되는 AI 메시지가 messages 리스트에 append되어 대화 내역이 누적됩니다.체크포인터(MemorySaver)<code class="language-python">memory = MemorySaver() graph = graph_builder.compile(checkpointer=memory)</code>이 설정 덕분에 LangGraph가 노드 실행 때마다 상태(State)를 memory에 저장합니다.만약 동일한 thread_id로 다시 그래프를 실행하면, 이전에 저장된 상태를 자동 로드해 여러 턴 대화를 이어갈 수 있습니다.메인 함수thread_id를 입력 받음: 예) user1사용자 메시지를 graph.stream(...)으로 전달할 때,로 지정하면 LangGraph가 해당 스레드의 상태를 로드/업데이트합니다.<code class="language-python">{"configurable": {"thread_id": thread_id}}</code>4. 실행 예시한 터미널에서:<code class="language-plaintext">$ python ch4_memory_chatbot.py +-----------+ | __start__ | +-----------+ * * * +---------+ | chatbot | +---------+ ==== [ch4] Memory/Checkpoint Demo ==== Type 'quit' or 'q' to exit. Enter thread_id (e.g., 'user1'): userA (thread=userA) You: 안녕 Assistant: 안녕하세요! 무엇을 도와드릴까요? (thread=userA) You: 내 이름은 길동이야 Assistant: 길동님, 만나서 반갑습니다. ... </code>또 다른 터미널에서:<code class="language-plaintext">$ python ch4_memory_chatbot.py ==== [ch4] Memory/Checkpoint Demo ==== Type 'quit' or 'q' to exit. Enter thread_id (e.g., 'user1'): userB (thread=userB) You: 안녕, 난 영희야 Assistant: 영희님, 안녕하세요!</code>코드 복사userA와 userB는 서로 다른 thread_id로 대화를 유지하므로, 서로의 대화가 섞이지 않고 독립된 맥락을 이어갑니다.만약 같은 thread_id로 접속하면, LangGraph는 “이전에 저장된 State가 있다”고 보고 과거 메시지를 이어서 참조합니다(프로세스를 종료하기 전까지).5. 확장 아이디어SqliteSaver / PostgresSaverMemorySaver는 파이썬 프로세스를 종료하면 저장된 상태가 사라집니다.SqliteSaver나 PostgresSaver를 쓰면 DB에 저장되므로, 프로그램을 재시작해도 동일 thread_id의 대화를 이어갈 수 있습니다.복수 노드(툴 등) 결합ch2, ch3처럼 툴(검색, DB 조회) 노드를 추가하면, 사용자별로 검색 내역도 messages에 누적되어 “이전에 검색했던 내용”까지 기억하는 챗봇이 됩니다.Human-in-the-loop특정 노드 실행 전/후에 interrupt_before/interrupt_after를 설정해, “사람이 승인해야 다음 단계로 진행”처럼 구현할 수도 있습니다.이때도 체크포인트가 있어야, 중간에 멈췄다가 몇 분/몇 시간 뒤에 다시 재개 가능합니다.마무리이상으로, LangGraph의 체크포인트 기능을 활용해 여러 턴 대화(멀티턴)와 사용자별(thread_id 기반) 대화 맥락 유지를 구현하는 방법을 살펴봤습니다.핵심 포인트:State에 messages 리스트를 넣어두고,compile(checkpointer=...)로 그래프 실행 결과를 저장,graph.stream(..., {"configurable": {"thread_id": "..."}})로 이전 기록을 로드.이를 통해 간단히 사용자별 맞춤형 기억을 유지할 수 있으며, 좀 더 고도화하면 DB 연동으로 서버 재시작에도 대화 이력이 남도록 만들 수 있습니다.
ch1에서는 LangGraph를 사용한 기본 챗봇을, ch2에서는 특정 상황에서 도구(검색 엔진)를 자동 호출하는 흐름을 살펴봤습니다. 하지만, ch2에서 우리는 LangGraph가 미리 만들어놓은 ToolNode & tools_condition 을 그대로 썼죠. 이번 ch3에서는 이러한 편의 기능을 직접 구현해봄으로써, LangGraph의 노드 정의와 조건부 라우팅(conditional_edges)을 보다 깊이 이해해보겠습니다.1. 준비 사항가상환경 & 패키지 설치<code class="language-plaintext">python -m venv .venv source .venv/bin/activate pip install -U langgraph langsmith langchain_anthropic tavily-python langchain_community</code>API Key 설정Anthropic: export ANTHROPIC_API_KEY="..."Tavily: export TAVILY_API_KEY="..."기본 개념LangGraph에서 노드(Node) 는 파이썬 함수(또는 객체의 __call__)로 구현되며,“START -> (여러 노드) -> END” 형태의 흐름을 edges로 연결한다.conditional_edges를 통해 조건부 분기를 제어할 수 있다.3. 전체 코드아래 예시는 Anthropic Claude와 TavilySearchResults를 연동한 LangGraph 챗봇으로, LLM이 “검색이 필요하다”고 판단할 때, 직접 만든 BasicToolNode가 실행되어 웹 검색을 수행합니다.<code class="language-python">import os import json # Anthropic Claude from langchain_anthropic import ChatAnthropic # LangGraph from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages # Tavily 검색 툴 (웹 검색) from langchain_community.tools.tavily_search import TavilySearchResults # typing from typing import Annotated from typing_extensions import TypedDict # langchain-core에서 ToolMessage (툴 호출 결과를 담아 LLM에 전달하기 위함) from langchain_core.messages import ToolMessage ############################################################################### # 1) 상태(State) 정의 ############################################################################### class State(TypedDict): # messages: 사용자/AI 메시지를 누적 저장 messages: Annotated[list, add_messages] ############################################################################### # 2) 그래프 빌더 생성 ############################################################################### graph_builder = StateGraph(State) ############################################################################### # 3) LLM 및 툴 초기화 ############################################################################### # - TavilySearchResults: 검색어를 넣으면 JSON 형식으로 결과 반환 search_tool = TavilySearchResults( max_results=2, tavily_api_key=os.environ.get("TAVILY_API_KEY") # 환경변수에서 가져오기 ) # - Anthropic Claude llm = ChatAnthropic( model="claude-3-5-sonnet-20240620", anthropic_api_key=os.environ.get("ANTHROPIC_API_KEY") ) # - LLM에 툴 목록을 "bind"하여, # LLM이 tool_calls(JSON) 형태로 특정 툴을 호출할 수 있음 llm_with_tools = llm.bind_tools([search_tool]) ############################################################################### # 4) 노드: chatbot ############################################################################### def chatbot_node(state: State): """ 1) state["messages"]를 LLM에게 전달 2) LLM이 필요 시 tool_calls를 생성 (search_tool 등) 3) 새 메시지를 반환 -> LangGraph가 append """ ai_resp = llm_with_tools.invoke(state["messages"]) return {"messages": [ai_resp]} ############################################################################### # 5) 노드: BasicToolNode (직접 구현) ############################################################################### # - LangGraph가 기본 제공하는 ToolNode가 아니라, # LLM이 호출하겠다고 선언한 tool_calls를 직접 처리하는 로직 class BasicToolNode: """ LLM의 마지막 메시지에 tool_calls가 있으면, 그에 따라 실제 툴을 실행하고 ToolMessage를 생성한다. """ def __init__(self, tools: list): # tools_by_name = {툴_이름: 툴_인스턴스} self.tools_by_name = {t.name: t for t in tools} def __call__(self, inputs: dict): # inputs에는 "messages" 리스트가 들어있음 if messages := inputs.get("messages", []): last_msg = messages[-1] # 가장 최근 AI 메시지 else: raise ValueError("No messages found in input.") outputs = [] # AI 메시지에 tool_calls가 있는지 확인 if hasattr(last_msg, "tool_calls"): for call_info in last_msg.tool_calls: tool_name = call_info["name"] args = call_info["args"] # 툴 이름에 맞춰 실제 invoke if tool_name in self.tools_by_name: result = self.tools_by_name[tool_name].invoke(args) # 검색 결과(등등)를 ToolMessage 형태로 담아 반환 # -> chatbot 노드에서 이어받아 최종 답변에 활용 outputs.append( ToolMessage( content=json.dumps(result), name=tool_name, tool_call_id=call_info["id"], ) ) return {"messages": outputs} ############################################################################### # 6) 노드 등록 ############################################################################### graph_builder.add_node("chatbot", chatbot_node) tool_node = BasicToolNode([search_tool]) graph_builder.add_node("tools", tool_node) ############################################################################### # 7) 조건부 라우팅 ############################################################################### # - chatbot 노드가 실행된 후, LLM이 도구를 호출했는지 여부를 판단해 # "tools" 노드로 갈지, "END"로 갈지 결정 def route_tools(state: State): """ chatbot 노드가 끝난 직후 실행되는 함수 - state["messages"][-1] 에 tool_calls가 있으면 'tools' - 없으면 'END' """ if messages := state.get("messages", []): last_msg = messages[-1] # tool_calls가 있고, 갯수가 > 0이면 'tools' if hasattr(last_msg, "tool_calls") and last_msg.tool_calls: return "tools" # 그렇지 않으면 'END' return END # - add_conditional_edges("chatbot", route_tools, {"tools":"tools", END:END}) graph_builder.add_conditional_edges( "chatbot", route_tools, { "tools": "tools", END: END } ) # - tools -> chatbot: 툴 실행 후 다시 chatbot 노드로 돌아가서 최종 답변을 완성 graph_builder.add_edge("tools", "chatbot") # - START -> chatbot graph_builder.add_edge(START, "chatbot") ############################################################################### # 8) 그래프 컴파일 ############################################################################### graph = graph_builder.compile() ################################################################################ # 그래프 시각화를 위한 ASCII 아트 생성 ################################################################################ # - graph.get_graph().draw_ascii()를 호출하여 그래프 구조를 ASCII 아트로 표현 # - 노드와 엣지의 연결 관계를 시각적으로 보여줌 ascii_graph = graph.get_graph().draw_ascii() print(ascii_graph) ############################################################################### # 9) 메인 함수 ############################################################################### def main(): print("==== [ch3] Custom BasicToolNode Demo ====") print("Type 'quit' or 'q' to exit.\n") while True: user_input = input("User: ") if user_input.lower() in ["quit", "q", "exit"]: print("Goodbye!") break # 그래프를 단계별로 실행 # "messages": [("user", user_input)] => 대화 목록에 새 사용자 메시지 추가 events = graph.stream({"messages": [("user", user_input)]}) # 실행 중 발생하는 모든 노드의 업데이트를 받아 메시지가 생성되면 출력 for event in events: for node_output in event.values(): if "messages" in node_output: last_msg = node_output["messages"][-1] # AIMessage 혹은 ToolMessage print("Assistant:", last_msg.content) if __name__ == "__main__": main()</code>4. 코드 해설chatbot_nodestate["messages"]를 LLM(Claude)에 전달해 새 응답을 생성.Claude가 “검색이 필요하다”고 판단하면 응답에 tool_calls를 담아 돌려준다.BasicToolNode (직접 구현)LangGraph의 빌트인 ToolNode와 유사한 역할을 하지만, 커스텀 로직이 필요할 경우 이처럼 직접 짤 수 있다.마지막 AI 메시지(messages[-1])에 tool_calls가 있으면, 해당 툴을 invoke(args)로 실행.그 결과를 ToolMessage로 만들어 대화에 추가.한 번 검색을 수행하면, 다음 턴에 다시 chatbot_node로 돌아와 최종 답변이 완성되는 구조.조건부 라우팅 (route_tools)chatbot 노드가 끝난 뒤 실행되는 함수로,메시지에 tool_calls가 있으면 "tools", 없으면 "END"로 분기.이후 "tools" -> "chatbot"으로 돌아와 LLM이 새로운 정보를 활용할 수 있게 된다.메인 함수사용자 입력 -> graph.stream(...) -> 순차적으로 노드가 실행되며 메시지를 추가 -> 결과 출력사용자가 “quit”를 입력하기 전까지 반복.5. 실행 예시<code class="language-python">$ python ch3_chatbot_with_tools_using_customnode.py +-----------+ | __start__ | +-----------+ * * * +---------+ | chatbot | +---------+ . . .. .. . . +-------+ +---------+ | tools | | __end__ | +-------+ +---------+ ==== [ch3] Custom BasicToolNode Demo ==== Type 'quit' or 'q' to exit. User: 안녕 Claude Assistant: 안녕하세요! 어떻게 도와드릴까요? User: LangGraph가 뭐야? Assistant: [{'text': "Let me look that up for you...", 'type': 'text'}, {'id': 'toolu_01A...', 'input': {'query': 'LangGraph'}, ...}] Assistant: [{"url": "https://github.com/langchain-ai/langgraph", "content": "LangGraph is a library for ..."}] Assistant: LangGraph는 ....</code>Claude가 “검색해야겠다”고 판단하면, tool_calls JSON이 AI 메시지에 들어 있음 → BasicToolNode가 search_tool을 실제로 호출 → 그 결과를 ToolMessage로 만들어 대화에 추가 → 다시 chatbot_node에서 최종 요약/답변을 준다.6. add_conditional_edges 좀 더 살펴보기노드(Node)가 완료된 뒤, 현재 상태(State)를 분석하여 다음에 어디로 갈지 분기(조건부 라우팅) 하고 싶을 때 사용합니다. 즉, “조건에 따라 서로 다른 노드로 이동”하는 흐름을 간단히 정의할 수 있습니다.일반적인 add_edge("A", "B")는 “A 후에 무조건 B”로 연결되지만,add_conditional_edges("A", condition_fn, {...})는 “A 후에 condition_fn의 결과값에 따라 여러 갈래”로 분기할 수 있습니다.함수 문법<code class="language-python">StateGraph.add_conditional_edges( from_node: str, condition: Callable[[Any], str | list[str]], mapping: dict[str, str] = {}, ... ) -> None</code>from_node: str어떤 노드가 끝난 뒤 이 조건부 로직을 적용할지 지정.예: "chatbot" → chatbot 노드가 실행을 마치면 condition 함수를 호출하여 분기 결정.condition: Callable[[Any], str | list[str]]실제 분기 로직이 들어 있는 함수(“조건 함수”).그래프의 현재 State(예: TypedDict)를 입력으로 받아, 다음으로 이동할 노드 이름(문자열)을 반환.하나의 문자열을 반환하면 “단일 분기”, 문자열 리스트를 반환하면 “여러 노드로 병렬 분기”도 가능.예시:<code class="language-python">def route_tools(state: State) -> str: last_msg = state["messages"][-1] if hasattr(last_msg, "tool_calls") and last_msg.tool_calls: return "tools" return "END"</code>mapping: dict[str, str]condition 함수가 반환한 문자열(또는 문자열 중 하나)을 실제 노드 이름 혹은 특수 키(END)에 매핑.예: { "tools": "tools", "end": END }condition이 "tools"를 반환하면 실제로 tools 노드로 이동."end"를 반환하면 그래프 종료(END).만약 condition이 "my_tools" 같은 임의 문자열을 반환하고, mapping에 {"my_tools": "tools"} 라고 작성하면, 실제로는 "tools" 노드로 이동시키는 식으로 커스터마이징 가능.보통은 "tools" → "tools", "END" → END 처럼 간단하게 1:1 매핑하는 경우가 많습니다.리턴(반환) 값add_conditional_edges 자체는 그래프에 조건부 엣지를 추가하고 None을 반환합니다.이후 그래프가 from_node를 마친 시점에 내부적으로 condition(state)를 호출해, 반환값을 mapping으로 해석하여 다음 노드를 결정하게 됩니다.7. 마무리 및 확장직접 구현한 ToolNode를 통해 LangGraph에서 다양한 모듈(예: 검색, DB 조회, 사내 API 호출 등)을 자유롭게 연결할 수 있습니다.함수형으로 만들 수도 있고, 이번처럼 클래스(__call__)로 만들어 노드 로직을 더 체계적으로 관리할 수도 있습니다.조건부 라우팅 역시 직접 route_tools 함수를 짜보면, LangGraph가 제공하는 편의 함수(tools_condition)의 내부 동작을 파악하는 데 도움이 됩니다.이상으로, ch3: LangGraph에서 직접 만든 ToolNode로 검색 툴을 연동해보는 방법을 소개해드렸습니다!
우리는 ch1에서 LangGraph의 StateGraph를 만들고, 간단한 노드(chatbot)와 엣지(START->chatbot->END)를 연결해, “사용자 입력 -> LLM 응답”의 기초적인 챗봇을 완성했습니다. 그 결과, Claude가 이미 알고 있는 지식 범위 내에서는 문제없이 대화를 이어갈 수 있었죠. 하지만 LLM이 미처 학습하지 못했거나 최신 정보를 요구하는 상황에서는 응답이 부정확해질 수 있었습니다. 이를 해결하기 위해 검색 엔진 등의 외부 도구(tool) 를 연결해 필요한 데이터를 실시간으로 가져오는 방식을 고려해볼 수 있습니다. 이번 ch2에서는 LangGraph에 툴 노드(ToolNode) 를 추가해, LLM이 필요시 스스로 검색 툴을 사용할 수 있도록 만들고, 조건부 분기(conditional edges)를 적용해 LLM이 검색을 원하는 경우와 원치 않는 경우를 구분하여 처리합니다. 이를 통해, 사용자 질문에 대해 LLM이 필요 시 웹 검색을 수행하고, 그 결과를 바탕으로 대답을 보강할 수 있게 됩니다.1. 사전 준비 사항가상환경 활성화<code class="language-plaintext">python -m venv .venv source .venv/bin/activate</code>필요 라이브러리 설치<code class="language-plaintext">pip install -U langgraph langsmith langchain_anthropic tavily-python langchain_community</code>환경변수 설정Anthropic API 키: export ANTHROPIC_API_KEY="..."Tavily API 키: export TAVILY_API_KEY="..."(Windows PowerShell: $env:ANTHROPIC_API_KEY="..." 식)2. 코드 전체 (ch2_chatbot_with_tools.py)아래 코드를 chatbot_with_tools.py로 저장한 뒤, python chatbot_with_tools.py로 실행해보세요.<code class="language-plaintext">import os # Anthropic Claude 연결 from langchain_anthropic import ChatAnthropic # LangGraph 핵심 구성요소 # - StateGraph: 그래프 정의 # - START, END: 시작/종료 지점 # - add_messages: 새로운 메시지를 State에 append하는 편의함수 from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages # 웹 검색 툴 (Tavily) from langchain_community.tools.tavily_search import TavilySearchResults # 미리 만들어진 'ToolNode'와 'tools_condition' (LangGraph 제공) # - ToolNode: 여러 툴(도구) 실행을 자동 처리 # - tools_condition: "LLM이 툴을 호출했는지" 보고 다음 노드를 결정 from langgraph.prebuilt import ToolNode, tools_condition # 타입 정의 from typing import Annotated from typing_extensions import TypedDict ################################################################################ # 1) State(상태) 정의 ################################################################################ # - messages: 사용자/AI 메시지를 저장해두는 리스트 # - add_messages: 새 메시지를 append class State(TypedDict): messages: Annotated[list, add_messages] ################################################################################ # 2) 그래프 빌더 생성 ################################################################################ # - 우리가 만든 State 구조를 기반으로 동작하는 StateGraph # - 노드와 엣지를 등록한 후, compile() 하면 실행 가능해짐 graph_builder = StateGraph(State) ################################################################################ # 3) LLM + 툴 생성 ################################################################################ # (A) Tavily 웹 검색 툴 search_tool = TavilySearchResults( max_results=2, # 검색 결과 최대 개수 tavily_api_key=os.environ.get("TAVILY_API_KEY") # 환경변수에서 가져오기 ) # (B) Anthropic Claude llm = ChatAnthropic( model="claude-3-5-sonnet-20241022", # 원하시는 모델 버전으로 수정 가능 anthropic_api_key=os.environ.get("ANTHROPIC_API_KEY") ) # (C) LLM + 툴 바인딩 # - LLM이 JSON 포맷을 통해 툴을 "호출"할 수 있게 설정 llm_with_tools = llm.bind_tools([search_tool]) ################################################################################ # 4) 노드(Node) 정의 ################################################################################ # (1) chatbot 노드 # - state["messages"]를 보고 LLM이 답변 # - 필요하면 (tool_calls)를 포함하여, 툴을 호출하도록 지시 def chatbot_node(state: State): ai_response = llm_with_tools.invoke(state["messages"]) return { "messages": [ai_response] } # (2) tools 노드 # - LLM 메시지에 툴 호출 정보(tool_calls)가 있으면 # 실제 검색 툴을 실행하고, 그 결과를 messages에 append # -> LangGraph에는 이미 'ToolNode'라는 편의 클래스가 있으므로 직접 구현 필요 없음 # 이번에는 prebuilt된 ToolNode를 사용해보겠습니다. tool_node = ToolNode(tools=[search_tool]) ################################################################################ # 5) 그래프 빌더에 노드 등록 ################################################################################ # - "chatbot" 노드 -> chatbot_node(함수) graph_builder.add_node("chatbot", chatbot_node) # - "tools" 노드 -> tool_node(객체) # => tool_node(...) 실행 시 LLM이 요청한 툴들을 병렬/직렬 등으로 실행 가능 graph_builder.add_node("tools", tool_node) ################################################################################ # 6) 조건부 엣지(conditional_edges) 설정 ################################################################################ # - LangGraph의 핵심: "조건부 엣지"로 다음 노드를 동적으로 결정 # - tools_condition: # => "LLM이 툴을 호출했으면 tools 노드로, 아니면 END로" 라우팅 # - chatbot 노드가 실행된 후, tools_condition로 분기 # (예: "search_tool" 호출 요청이 있으면 'tools'로 이동 / 없으면 'END') graph_builder.add_conditional_edges( "chatbot", tools_condition, # condition 함수 { "tools": "tools", # condition이 "tools"라 반환하면, 실제로 "tools" 노드로 이동 END: END # condition이 END라 반환하면 그래프 종료 } ) ################################################################################ # 7) 추가 엣지 설정 ################################################################################ # - "tools" -> "chatbot" : 툴 실행 후 다시 LLM에 돌아와 응답을 마무리 # - START -> "chatbot" : 시작하면 곧바로 챗봇 노드부터 실행 graph_builder.add_edge("tools", "chatbot") graph_builder.add_edge(START, "chatbot") ################################################################################ # 8) 컴파일 ################################################################################ # - 이렇게 해서 완성된 노드/엣지 구조를 실제로 "실행할 수 있는" 그래프로 변환 graph = graph_builder.compile() ################################################################################ # 그래프 시각화를 위한 ASCII 아트 생성 ################################################################################ # - graph.get_graph().draw_ascii()를 호출하여 그래프 구조를 ASCII 아트로 표현 # - 노드와 엣지의 연결 관계를 시각적으로 보여줌 ascii_graph = graph.get_graph().draw_ascii() print(ascii_graph) ################################################################################ # 9) 메인 함수 ################################################################################ def main(): print("==== [ch2] LangGraph + Tools (Conditional) Demo ====") print("Type 'quit' or 'q' to exit.\n") while True: user_input = input("User: ") if user_input.lower() in ["quit", "q", "exit"]: print("Goodbye!") break # 그래프 실행 # - 입력 형태: {"messages": [("user", user_input)]} # - chatbot 노드 -> tools_condition -> (tools or end) -> ... # - 툴 실행이 발생하면 "tools" 노드가 검색 수행, 결과를 messages에 저장 # - 이후 다시 chatbot 노드에서 최종 답변 events = graph.stream({"messages": [("user", user_input)]}) # 실행 중 발생하는 event(상태 업데이트)마다 새 메시지가 있다면 출력 for event in events: for node_output in event.values(): if "messages" in node_output: # 가장 마지막 메시지(새로운 답변) last_msg = node_output["messages"][-1] # AIMessage의 .content 속성 출력 print("Assistant:", last_msg.content) if __name__ == "__main__": main()</code>3. 주요 흐름 설명chatbot 노드가 사용자 메시지를 보고 Claude에게 질의Claude가 “외부 검색이 필요하다”고 판단하면, tool_calls에 search_tool을 호출하는 JSON을 담아 반환조건부 엣지 (tools_condition)LLM 응답에 툴 호출이 있으면 "tools" 노드로 이동, 없으면 END로 종료tools 노드실제 TavilySearchResults를 호출해 검색 결과(JSON)를 messages에 추가이후 다시 "chatbot" 노드로 돌아가, 검색 결과를 이용해 최종 답변 생성메인 루프사용자 입력 -> 그래프 stream -> (필요 시 검색) -> 최종 답변 출력“quit” 입력 시 종료4. 실행 예시 <code class="language-plaintext">$ python ch2_chatbot_with_tools.py +-----------+ | __start__ | +-----------+ * * * +---------+ | chatbot | +---------+ . . .. .. . . +-------+ +---------+ | tools | | __end__ | +-------+ +---------+ ==== [ch2] LangGraph + Tools (Conditional) Demo ==== Type 'quit' or 'q' to exit. User: LangGraph는 뭐야? Assistant: (Claude가 답변 중...) Assistant: ... (검색 필요 판단) tool_calls -> search_tool Assistant: ... (검색 결과) ... Assistant: LangGraph는 ~~~ 입니다!</code>Claude가 학습된 지식만으로 답할 수 있으면 툴을 안 쓰고 바로 끝(END)추가 정보가 필요하면 tool_calls를 통해 search_tool을 실행하고, 검색 결과를 메시지로 받아와 더 풍부한 답변을 구성합니다.이상으로, LangGraph + Anthropic Claude + TavilySearchResults를 이용한 도구(검색) 활용 챗봇 예시(ch2)를 살펴봤습니다. 소스코드는 간단하지만 add_conditional_edges에 대해 생소하시다면 완벽히 이해가 되지 않을 수 있습니다. 이 부분에는 다음 챕터에서 살펴보겠습니다.
최근 LLM(Large Language Model) 기반 애플리케이션이 폭발적으로 증가함에 따라, LangChain 계열 라이브러리를 활용해 챗봇을 구축하는 사례도 점차 늘어나고 있습니다. 그중 LangGraph는 다중 노드(다중 에이전트) 워크플로우를 직관적으로 만들 수 있도록 돕는 프레임워크로, 상태(State)를 쉽게 관리하고 제어 흐름을 유연하게 구성해줍니다.이번 글에서는 LangGraph와 Anthropic Claude를 간단히 연동해, 터미널에서 동작하는 챗봇 예제를 만들어보겠습니다.1. 사전 준비Python 가상환경<code class="language-plaintext">python -m venv .venv source .venv/bin/activate</code>필요 라이브러리 설치<code class="language-plaintext">pip install langgraph langsmith langchain_anthropic grandalf</code>Anthropic API Key 발급Anthropic Console에서 API Key를 생성아래와 같이 환경변수 설정(예: Linux/Mac):<code class="language-plaintext">export ANTHROPIC_API_KEY="여러분의_키"</code>이 과정을 거치면 준비는 끝났습니다.2. 코드 구조 개요LangGraph는 StateGraph라는 객체에 각 노드(함수)와 엣지(실행 흐름)를 등록하고, compile()을 통해 실제 실행 가능한 그래프(CompiledGraph)를 생성합니다.Anthropic Claude는 langchain_anthropic.ChatAnthropic 클래스로 LLM 객체를 만들고, 여기에 .invoke(messages) 메서드를 사용해 대화를 주고받습니다.3. 전체 코드아래 ch1_chatbot.py 코드를 작성한 후, python ch1_chatbot.py로 실행하면 됩니다.<code class="language-python">import os # LangChain + Anthropic 연동을 위한 클래스 from langchain_anthropic import ChatAnthropic # LangGraph 기본 구성요소 (StateGraph, START, END, add_messages 등) from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages # 타입 어노테이션 from typing import Annotated from typing_extensions import TypedDict ################################################################################ # 1) State(상태) 정의 ################################################################################ # - LangGraph에서는 "상태(State)"가 매우 중요합니다. # - 챗봇이라면, 이전까지 오간 모든 메시지를 'messages' 리스트에 저장할 수 있습니다. # - 아래는 그런 목적을 가진 State를 정의합니다. # - 'add_messages'를 통해, 새 메시지를 덧붙이는 형태로 유지(append)할 것입니다. class State(TypedDict): messages: Annotated[list, add_messages] ################################################################################ # 2) 그래프 빌더(StateGraph) 생성 ################################################################################ # - LangGraph의 핵심: "그래프(Graph)"라는 개념. # - 그래프는 여러 "노드(Node)"들이 서로 연결(Edge)되어 있는 형태를 말합니다. # - StateGraph(...) 생성 시, 우리가 어떤 'State' 구조를 쓸지 지정해야 합니다. # - 추후 노드를 등록하고, 엣지(edge)로 어떤 순서로 실행할지 짜면 "워크플로우"가 완성됩니다. graph_builder = StateGraph(State) ################################################################################ # 3) LLM(Anthropic Claude) 객체 생성 ################################################################################ # - LangChain의 ChatAnthropic 클래스를 이용해 Anthropic Claude 모델과 연결합니다. # - model: "claude-3-5-sonnet-20241022" 등 원하는 버전명으로 지정 가능 # - anthropic_api_key: Anthropic 계정에서 발급받은 API 키를 환경변수로 설정 후 가져오기 llm = ChatAnthropic( model="claude-3-5-sonnet-20241022", anthropic_api_key=os.environ.get("ANTHROPIC_API_KEY") ) ################################################################################ # 4) 노드(Node) 정의: chatbot 함수 ################################################################################ # - "노드(Node)"란, 그래프 내에서 특정 기능(로직)을 담당하는 '단계(step)'를 의미합니다. # - 여기서는 'chatbot' 노드가, "LLM에게 메시지를 던지고 응답을 받는 역할"을 합니다. # - LangGraph가 노드를 실행할 때, 현재 상태(State)를 함수 인자로 넣어주고 # 함수가 반환한 dict를 통해 state가 업데이트됩니다. def chatbot(state: State): """ chatbot 노드 함수: 1) state["messages"] 내 기존 메시지(사용자 발화 등)를 LLM에 전달 2) LLM이 준 새 AI 응답을 'messages' 리스트에 추가한다. """ # LLM.invoke(...) -> state["messages"]를 토대로 AI가 답변(메시지 객체)을 생성 ai_resp = llm.invoke(state["messages"]) # 반환: {"messages": [새로운 메시지 객체]} # -> LangGraph가 add_messages 방식으로 기존 리스트에 덧붙임 return { "messages": [ai_resp] } ################################################################################ # 5) 그래프 빌더에 "노드" 등록 ################################################################################ # - 아래에서 graph_builder.add_node("chatbot", chatbot)를 호출: # => "chatbot"이란 이름의 노드(Node)를 그래프에 추가 # => 실제 로직은 chatbot(...) 함수로 실행 # - 노드 이름(첫 번째 인자) = "chatbot" # - 노드 함수(두 번째 인자) = 위에서 정의한 chatbot 함수 graph_builder.add_node("chatbot", chatbot) ################################################################################ # 6) 그래프(Workflow) 내 "엣지(Edge)" 정의 ################################################################################ # - 그래프 = 노드 + 엣지(노드와 노드를 연결) # - LangGraph가 "시작(START)" 노드로부터 어디로 가야 하는지, 끝(END)은 어디인지 정의. # - 여기서는 단순히, # START -> "chatbot" -> END # 라는 흐름을 만든다. # - 즉, 그래프를 실행할 때: # (1) START 노드로부터 시작 # (2) "chatbot" 노드를 실행 (chatbot 함수가 동작) # (3) "chatbot" 노드 완료되면 END로 가서 종료 graph_builder.add_edge(START, "chatbot") graph_builder.add_edge("chatbot", END) ################################################################################ # 7) 그래프를 "컴파일(compile)"하여 사용 가능하게 만듦 ################################################################################ # - graph_builder.compile()을 호출하면, 우리가 지금까지 정의한 노드와 엣지를 바탕으로 # 실제로 실행 가능한 'CompiledGraph' 객체가 생성됩니다. # - compile()을 하지 않으면, 아직은 그래프가 "설계도 상태"입니다. # - compile() 결과물인 graph는 .stream() 또는 .invoke()를 통해 실행할 수 있습니다. graph = graph_builder.compile() ################################################################################ # 그래프 시각화를 위한 ASCII 아트 생성 ################################################################################ # - graph.get_graph().draw_ascii()를 호출하여 그래프 구조를 ASCII 아트로 표현 # - 노드와 엣지의 연결 관계를 시각적으로 보여줌 # - START -> chatbot -> END 형태의 단순한 흐름이 표현됨 ascii_graph = graph.get_graph().draw_ascii() print(ascii_graph) ################################################################################ # 8) 간단한 메인 함수(main) ################################################################################ # - 아래 함수는 콘솔용 대화 루프를 제공합니다. # - 사용자가 입력을 하면, LangGraph의 graph.stream(...)으로 메시지를 주고 # AI 응답을 받아 출력하는 식입니다. # - 'quit', 'exit', 'q' 등을 입력하면 대화를 종료. def main(): while True: # 사용자 입력 받음 user_input = input("User: ") # 특정 단어 입력 시 종료 if user_input.lower() in ["quit", "exit", "q"]: print("Goodbye!") break # (A) graph.stream({"messages": [("user", user_input)]}) # => LangGraph에 "messages" 키로 [('user', user_input)] 튜플을 넘김 # => State 에는 [('user', '...')] 형태가 메시지로 추가됨 # # (B) for event in graph.stream(...): # => 그래프 실행 과정에서 발생하는 "상태 업데이트" 이벤트들을 순차적으로 받음 # => chatbot 노드가 실행되면 AI의 응답 메시지를 생성하여 event.values()에 포함 # # (C) event.values() 안의 "messages"를 찾아, 가장 마지막 메시지를 출력 (AI 답변) for event in graph.stream({"messages": [("user", user_input)]}): # event = {"(노드이름)": {"messages": [...]} ...} 형태 등 # event.values() = 각 노드 실행 결과(딕셔너리)들의 값 for value in event.values(): # value["messages"] -> 이번 단계에서 추가된 메시지 리스트 # [-1] => 가장 마지막 메시지(새로 생성된 AI 메시지) print("Assistant:", value["messages"][-1].content) ################################################################################ # 9) 실행 엔트리포인트 ################################################################################ # - 아래 if 문은 "이 파일을 python 으로 직접 실행했을 때"만 main() 함수를 호출. # - 이 파일이 다른 모듈에서 import될 경우 main()은 자동으로 실행되지 않는다. if __name__ == "__main__": main()</code>실행 예시 <code class="language-python">$ python ch1_chatbot.py +-----------+ | __start__ | +-----------+ * * * +---------+ | chatbot | +---------+ * * * +---------+ | __end__ | +---------+ User: 안녕 Assistant: 안녕하세요! 무엇을 도와드릴까요? User: LangGraph가 뭐야? Assistant: LangGraph는 ... ...</code>User: 라고 입력하면 Anthropic Claude에게 메시지를 전달하고,Assistant: Claude가 생성한 응답을 출력해줍니다.quit, exit, q 등을 입력하면 프로그램을 종료합니다.4. 정리 및 확장 아이디어이 예시는 가장 기본적인 흐름인 START -> chatbot -> END만 사용했습니다.LangGraph의 강점은 다양한 노드(Node)를 연결해 여러 단계(Workflow)로 확장할 수 있다는 점입니다.예: “챗봇 노드 -> 검색 노드 -> 요약 노드 -> 챗봇 노드” 등메모리 체크포인트, Human-in-the-loop, ToolNode 등을 추가하면 훨씬 강력한 다중 단계 챗봇/에이전트가 만들어집니다.langchain_community.tools 등 다양한 툴을 연동해 검색, DB 조회, API 호출 등을 자유자재로 수행시킬 수 있습니다.이상으로 LangGraph와 Anthropic Claude를 연동해 간단한 터미널 챗봇을 만드는 방법을 살펴봤습니다. 더 자세한 내용은 LangGraph 공식 문서나 LangChain의 다양한 레퍼런스를 참고해보시기 바랍니다.
안녕하세요! 😊 인공지능팩토리입니다. 오늘은 저희 인공지능팩토리가 스마트팜 교육기업 메이티와 함께한 ‘2024 테스트베드 서울 실증사업’ 소식을 전해드립니다! 🌱✨이번 프로젝트는 미래 교육의 새로운 길을 열며 큰 성과를 거두었습니다. ‘2024 테스트베드 서울 실증사업’이란?‘2024 테스트베드 서울 실증지원 사업’은 서울시와 서울경제진흥원이 주최한 프로그램으로, AI와 생태 교육을 융합한 혁신적인 교육 솔루션을 실제 교육 현장에 적용해 효과를 검증하는 프로젝트입니다.이번 사업에서 메이티는 스마트팜 기반 교육 콘텐츠와 시스템을 주관했고, 저희 인공지능팩토리는 AI 기반 학습 솔루션을 공동 개발해 지원했어요! 😊 실증사업이 이루어진 현장이번 프로젝트는 서울 은평구 관내 초등학교 4곳(상신초, 선일초, 증산초, 은진초)과 유치원 2곳(진관 유치원, 은평국제유치원)에서 8월 26일부터 10월 29일까지 진행되었습니다.학생들은 이 기간 동안 첨단 기술과 생태 교육이 융합된 특별한 학습 프로그램을 경험했답니다! 🌟 인공지능팩토리의 AI 기반 학습 솔루션저희 인공지능팩토리는 미니스마트팜 디바이스에 생성형 AI 모듈과 음성 인터페이스 등 최신 ICT 기술을 적용했어요. 이를 통해 학생들은 음성 명령을 사용해 스마트팜 환경을 제어하고 대화형 학습 서비스를 통해 보다 몰입감 있는 학습을 할 수 있었습니다.특히 유아 및 초등학생과 같은 디지털 기기 사용이 어려운 학생들도 텍스트 입력의 불편함 없이 학습에 참여할 수 있었어요. 그 덕분에 많은 학생들이 보다 능동적으로 학습에 몰입할 수 있었답니다. 🚀 대표들의 메시지김태영 인공지능팩토리 대표는 이렇게 말씀하셨습니다."생성형 AI와 음성 인터페이스는 학생들에게 새로운 학습 경험을 제공하는 중요한 도구입니다. 앞으로도 기술과 교육의 융합을 통해 혁신적인 학습 환경을 만들어 나갈 것입니다."김택규 메이티 대표 역시 생태 교육의 중요성을 강조하며 이렇게 전했습니다.“생태전환은 지속 가능한 사회로 나아가는 필수적인 과정입니다. 저희는 혁신적인 교육 솔루션을 통해 학습자의 성장에 기여할 수 있도록 다양한 협력을 이어갈 계획입니다.” 앞으로의 계획저희 인공지능팩토리는 앞으로도 더 많은 학생들에게 확장 가능한 스마트 학습 솔루션을 제공하기 위해 최선을 다할 예정입니다. 더 많은 소식이 궁금하신가요? 앞으로도 저희의 새로운 도전에 많은 관심과 응원 부탁드립니다! 😊 감사합니다! 📰 뉴스룸https://sentv.co.kr/article/view/sentv202412230045?d=pchttps://www.gpkorea.com/news/articleView.html?idxno=123215https://www.itbiznews.com/news/articleView.html?idxno=156054
⏰놓치지 말아야 할 얼리버드 혜택(~1/19(일))⏰ <AI 시대 일잘러를 위한 ChatGPT 활용 바이블> 강의 무료 제공!!▶ 얼리버드 합류하기 : https://bit.ly/3W514Wc-------------------------요즘 채용공고에서 자주 보이는 GA, SQL, Python, Tableau···. '데이터 분석 역량 우대'만 보면 매번 스쳐지나갔나요..?🤔 핫한 유망 IT직무 취업의 핵심 비결, 데이터 분석의 사이클을 모두 경험하고🔁 직무별 실무 역량을 완성하세요! 유명 기업 출신의 현직자에게 직접 배우는 데이터 분석의 A to Z 다양한 데이터 분석 툴을 활용한 프로젝트와 ChatGPT 특강으로 실무 역량을 탄탄히 쌓고 실제 이커머스 기업과 진행하는 미니 인턴십으로 든든한 포트폴리오까지 완성! 커널 아카데미는 데이터 분석 역량을 기반으로 여러분의 커리어 지름길을 뾰족하게 알려드려요✨ 🚩커널 아카데미 : 데이터 분석 부트캠프 18기💻교육 내용 - 데이터 분석 역량으로 완성하는 직무별 맞춤 커리어 설계 - 이커머스 기업과 함께하는 미니 인턴십 실무 프로젝트 - 대기업, 외국계 기업, 네카라쿠배 기업별 취업 지원 서비스 - 삼성, SK, 토스 등 입사하고 싶은 기업 현직자들의 멘토링 - AI 시대 맞춤형 ChatGPT 활용 데이터 분석법 😀모집 대상 - 평일 9시부터 18시까지 실시간으로 공부할 수 있는 취업준비생 - 내일배움카드 발급 가능자 📢모집 개요 - 얼리버드 모집 기간 : ~ 25년 1월 19일(일) 23시 59분 *선착순 마감으로 조기마감 될 수 있음 - 합격 발표 : 영업일 기준 +5일 이내 최종 합격 발표 - 과정 기간 : 25년 2월 24일(월) ~ 25년 7월 18일(금) - 과정 비용: 전액무료 (국비지원) 👨🏫수업 방식 - 실시간 온라인 수업 * 필요시 강남역 도보 1분거리 오프라인 학습장 제공 🔍지원 방법 1) 하단 링크 or QR코드 스캔 후 페이지 내 신청하기 버튼 클릭 2) 사전 접수 완료시 서류접수 등 안내 메일 발송 3) 1:1 상담까지 완료하면 지원 완료! 취업 치트키, 데이터 분석 역량 마스터하고 마케터, 기획자·PM, 데이터 분석가 등 원하는 직무로 골라서 취업하세요! ▶ 지원하러 가기 : https://bit.ly/3W514Wc - 문의처 메일 : help.edu@fastcampus.co.kr 전화번호: 02-518-4831
경제전망 레포트를 올려서 분석하라고 하고 싶었는데, 파일이 첨부가 안되서, CHAT GPT한테 요약시키고 그 내용을 가지고 분석하라고 해보았습니다. # 투자 보고서: 2025년 경제 전망 분석 및 투자 결정## 1. 경제 성장 전망2025년 세계 경제 성장률이 3.2%로 예상되며, 이는 안정적이지만 기대 이하의 수치입니다. 선진국은 회복세를 지속하고 있으나, 중국 부동산 위기와 같은 구조적 문제로 신흥국의 성장은 둔화될 가능성이 있습니다. 이는 투자자들에게 신흥국 시장에 대한 신중한 접근이 필요함을 시사합니다.## 2. 인플레이션 동향글로벌 인플레이션이 점진적으로 감소하여 2025년 말까지 4.3%에 도달할 것으로 예상됩니다. 특히 서비스 부문에서 여전히 높은 인플레이션이 지속될 것으로 보이며, 이는 통화 정책 완화에 어려움을 초래할 수 있습니다. 따라서 인플레이션에 민감한 자산에 대한 투자에 주의가 필요합니다.## 3. 금융 시장 및 정책 변화금융 시장의 변동성이 증가할 것으로 예상되며, 특히 신흥국은 외화 유출과 높은 부채 상환 부담에 취약합니다. 주요 중앙은행의 금리 인하가 시작될 것으로 보이며, 이는 주식 시장 및 채권 시장에 긍정적인 영향을 미칠 수 있습니다. 하지만, 재정 정책은 부채 안정화 및 재정 여력을 강화하는 방향으로 조정될 필요가 있어, 정책 변화에 따른 시장 반응을 면밀히 관찰해야 합니다.## 4. 위험 요인지역 분쟁, 지정학적 긴장, 기후 변화 등으로 인해 공급망 혼란과 성장 둔화가 발생할 가능성이 있습니다. 이러한 위험 요인은 투자 포트폴리오에 부정적인 영향을 미칠 수 있으므로, 방어적인 투자 전략이 필요합니다.## 5. 투자 전략 제안### 5.1. 선진국 주식 투자 - 선진국 주식 시장(특히 미국 및 유럽)에서 안정적인 대형주 투자. - 금리 인하에 따른 주식 시장의 회복세를 활용할 수 있음.### 5.2. 방어적 자산 - 채권 및 금과 같은 안전 자산에 대한 비율을 높이는 것을 고려. - 인플레이션 위험을 헤지할 수 있는 자산에 대한 투자 필요.### 5.3. 대체 투자 - 부동산 및 인프라 프로젝트에 대한 투자 고려. - 장기적인 수익성을 기대할 수 있는 자산군으로, 경제 불확실성에 대한 방어 효과 기대.### 5.4. 신흥국 시장에 대한 신중한 접근 - 신흥국 투자는 구조적 문제와 금융 불안정성을 고려해 보다 신중하게 접근. - 특정 산업이나 기업에 대한 집중적인 분석을 통해 투자 기회 모색.## 6. 결론2025년 경제 전망은 안정적인 성장세를 보일 것으로 예상되나, 신흥국의 둔화, 여전히 높은 인플레이션, 금융 시장의 변동성 및 다양한 위험 요소로 인해 투자에 신중을 기해야 합니다. 포트폴리오 다변화와 방어적 투자 전략을 통해 리스크를 관리하는 것이 중요합니다.