10. InvestorGPT - 자율적으로 조사하고 분석하는 AI 에이전트 만들기

이번 챕터에서는 지금까지 배운 모든 기술을 집약하여, 스스로 생각하고, 도구를 사용하며, 심지어 페르소나(개성)까지 가진 자율적인 AI, **에이전트(Agent)**를 만들어 보겠습니다. LangChain의 가장 강력하고 흥미로운 기능인 에이전트를 통해 “InvestorGPT”를 구축하는 과정을 함께 하겠습니다.

🎯 이번 챕터에서 배울 것

  • LangChain Agent: LLM이 외부 도구와 상호작용하며 스스로 문제를 해결하는 에이전트의 개념을 이해합니다.
  • Tools: 에이전트가 사용할 수 있는 ‘도구’를 직접 만들고, DuckDuckGo 검색, Alpha Vantage 금융 API, SQL Database 등 다양한 외부 세계와 연결하는 방법을 배웁니다.
  • Agent Types: ReActOpenAI Functions 등 다양한 에이전트 타입의 차이점과 사용법을 익힙니다.
  • System Prompt: 에이전트에게 “당신은 헤지펀드 매니저입니다”와 같은 역할과 페르소나를 부여하는 방법을 배웁니다.
  • Chain of Thought: 에이전트가 어떤 ‘생각’의 흐름으로 문제를 해결해 나가는지 그 과정을 상세히 분석합니다.

첫 번째 LangChain 에이전트 만들기

🎯 이번 단계에서 배울 것

  • LangChain 에이전트(Agent)의 기본 개념 이해하기
  • 에이전트가 사용할 도구(Tool)를 정의하는 방법 배우기
  • initialize_agent를 사용하여 에이전트를 생성하고 실행하는 방법 익히기
  • 에이전트의 “사고 과정(Thought Process)”을 분석하고 이해하기 (ReAct 프레임워크)

📝 1단계: 에이전트(Agent)란 무엇인가?

에이전트는 LLM을 한 단계 더 발전시킨 개념입니다. 단순히 주어진 텍스트에 이어 답변을 생성하는 것을 넘어, 목표를 달성하기 위해 스스로 생각하고, 계획을 세우고, 필요에 따라 도구를 사용하는 자율적인 행위자입니다.

  • 두뇌 (Brain): LLM (예: GPT-4)이 추론과 계획을 담당합니다.
  • 손과 발 (Hands & Feet): ‘도구(Tools)’를 사용하여 LLM이 할 수 없는 일(계산, 웹 검색, API 호출 등)을 수행합니다.

에이전트는 이 두뇌와 손발을 연결하여, 복잡한 문제도 여러 단계를 거쳐 해결할 수 있습니다.

📝 2단계: 도구(Tool) 정의하기

에이전트에게 가장 먼저 필요한 것은 바로 ‘도구’입니다. LLM은 수학 계산에 매우 취약하기 때문에, 간단한 덧셈을 수행하는 도구를 만들어 보겠습니다.

전체 코드 (notebook.ipynb):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from langchain.chat_models import ChatOpenAI
from langchain.tools import StructuredTool
from langchain.agents import initialize_agent, AgentType

llm = ChatOpenAI(temperature=0.1)

# 1. 도구로 사용할 일반 파이썬 함수를 정의합니다.
def plus(a, b):
return a + b

# 2. 함수를 StructuredTool로 감싸줍니다.
tools = [
StructuredTool.from_function(
func=plus,
name="Sum Calculator",
description="Use this to perform sums of two numbers. This tool take two arguments, both should be numbers.",
),
]

🔍 코드 상세 설명

  • StructuredTool.from_function: 일반 파이썬 함수를 LangChain 에이전트가 사용할 수 있는 도구로 만들어 줍니다.
  • name: 도구의 이름입니다. 에이전트가 어떤 도구를 호출할지 결정할 때 사용됩니다.
  • description: 매우 중요합니다. 에이전트는 이 설명을 보고 “이 도구가 어떤 상황에 필요한지”, “어떻게 사용해야 하는지”를 판단합니다. 설명이 명확하고 상세할수록 에이전트가 더 똑똑하게 행동합니다.

📝 3단계: 에이전트 생성 및 실행

이제 도구를 사용할 에이전트를 만듭니다.

전체 코드 (notebook.ipynb):

1
2
3
4
5
6
7
8
9
10
agent = initialize_agent(
llm=llm,
verbose=True, # 에이전트의 생각 과정을 모두 출력합니다.
agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
tools=tools,
)

prompt = "Cost of $355.39 + $924.87 + $721.2 + $1940.29 + $573.63 + $65.72 + $35.00 + $552.00 + $76.16 + $29.12"

agent.invoke(prompt)

🔍 코드 상세 설명

  • initialize_agent: LLM, 도구, 에이전트 타입을 조합하여 에이전트를 생성합니다.
  • verbose=True: 에이전트의 내부 “생각”을 모두 보여주는 설정입니다. 디버깅과 학습에 매우 유용합니다.
  • agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION: 에이전트의 행동 방식을 결정하는 타입입니다.
    • ReAct: Reason(추론)과 Act(행동)의 약자입니다. 에이전트가 생각하고, 행동(도구 사용)하고, 관찰하고, 다시 생각하는 과정을 반복합니다.
    • STRUCTURED_CHAT: plus(a, b)처럼 여러 인자를 가진 구조화된 도구에 특화된 타입입니다.

📝 4단계: 에이전트의 사고 과정 분석

verbose=True 덕분에 우리는 에이전트의 머릿속을 들여다볼 수 있습니다. 출력된 로그는 다음과 같은 “Thought -> Action -> Observation” 루프를 보여줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
> Entering new AgentExecutor chain...

Thought: To calculate the sum of these numbers, I will use the Sum Calculator tool.
Action:
{
"action": "Sum Calculator",
"action_input": {"a": 355.39, "b": 924.87}
}

Observation: 1280.26

Thought: Now that I have the sum of the first two numbers, I can continue calculating the sum of the remaining numbers.
Action:
{
"action": "Sum Calculator",
"action_input": {"a": 1280.26, "b": 721.2}
}

Observation: 2001.46

... (이 과정을 계속 반복) ...

Thought: Now that I have the sum of all the numbers, I can provide the final answer.
Final Answer: The sum of ... is $5273.38.

> Finished chain.
  • Thought: 에이전트는 먼저 무엇을 해야 할지 생각합니다. “이 숫자들을 더하려면 Sum Calculator 도구를 써야겠다.”
  • Action: 생각에 따라 Sum Calculator 도구를 호출하고, 필요한 인자 ab를 전달합니다.
  • Observation: 도구 실행 결과를 관찰합니다. 1280.26이라는 결과를 얻었습니다.
  • Repeat: 에이전트는 이 관찰 결과를 바탕으로 다음 생각을 합니다. “이제 첫 두 숫자의 합을 구했으니, 여기에 다음 숫자를 더해야겠다.” 그리고 다시 Action을 수행합니다.
  • 이 과정을 모든 숫자를 더할 때까지 반복한 뒤, 최종 답변을 생성합니다.

✅ 체크리스트

  • 에이전트가 LLM과 어떻게 다른지 설명할 수 있나요?
  • 도구를 만들 때 description이 왜 중요한지 이해했나요?
  • ReAct 프레임워크의 “Thought -> Action -> Observation” 순환 과정을 이해했나요?

💡 연습 과제

  1. 곱셈 도구 추가: plus 함수와 StructuredTool을 참고하여, 두 숫자를 곱하는 multiply 함수와 Multiply Calculator 도구를 만들어 보세요. 그리고 tools 리스트에 추가한 뒤, 덧셈과 곱셈이 섞인 질문(예: “(123 * 4) + 56”)을 던져보세요. 에이전트가 두 도구를 잘 구분해서 사용하나요?

기본 ReAct 에이전트 이해하기

🎯 이번 단계에서 배울 것

  • ZERO_SHOT_REACT_DESCRIPTION 에이전트 타입의 특징 이해하기
  • 단일 문자열 입력을 받는 간단한 Tool을 정의하는 방법 배우기
  • StructuredToolTool의 차이점 비교하기
  • handle_parsing_errors의 역할과 중요성 이해하기

📝 1단계: 에이전트 타입 및 도구 형식 변경

이전 단계에서 사용한 STRUCTURED_CHAT_... 에이전트는 여러 인자를 받는 복잡한 도구에 특화되어 있습니다. 이번에는 더 기본적이고 일반적인 ZERO_SHOT_REACT_DESCRIPTION 에이전트와, 이 에이전트가 사용하는 간단한 Tool 형식을 알아보겠습니다.

비교 예시:

Before (10.1):

1
2
3
4
5
6
7
8
9
10
11
12
13
# 함수가 여러 인자를 직접 받음
def plus(a, b):
return a + b

StructuredTool.from_function(
func=plus,
# ...
)

agent = initialize_agent(
agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
# ...
)

After (10.3):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from langchain.tools import Tool

# 함수가 하나의 문자열 인자만 받음
def plus(inputs):
# 함수 내부에서 문자열을 직접 파싱(parsing)해야 함
a, b = inputs.split(",")
return float(a) + float(b)

Tool.from_function(
func=plus,
name="Sum Calculator",
# 도구 설명에 입력 형식 예시를 명확히 알려줘야 함
description="Use this to perform sums of two numbers. Use this tool by sending a pair of number separated by a comma.\nExample:1,2",
)

agent = initialize_agent(
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
handle_parsing_errors=True, # 에러 처리 기능 추가
# ...
)

🔍 코드 상세 설명

  • Tool vs StructuredTool: StructuredTool이 함수 시그니처(def plus(a, b))를 보고 알아서 인자를 처리해준 반면, 기본 Tool은 함수에 무조건 하나의 문자열만 전달합니다. 따라서 개발자가 inputs.split(",")처럼 직접 문자열을 해석(parsing)하는 로직을 함수 안에 만들어야 합니다.
  • description의 중요성 증가: 에이전트가 1,2와 같은 형식의 문자열을 만들도록 descriptionExample:1,2 처럼 매우 구체적인 가이드를 제공해야 합니다. StructuredTool을 쓸 때보다 더 명시적으로 알려줘야 합니다.
  • AgentType.ZERO_SHOT_REACT_DESCRIPTION: 여러 인자를 받는 복잡한 도구 대신, 단일 문자열 입력을 받는 간단한 도구들과 함께 사용되는 가장 일반적인 ReAct 에이전트입니다.
  • handle_parsing_errors=True: 에이전트가 Action Input을 잘못된 형식으로 만들었을 때(예: 1, 2, 3 처럼 인자를 3개 보낼 때) 에러로 멈추는 대신, “네가 만든 형식을 파싱할 수 없어. 다시 시도해줘.” 라고 스스로에게 알려주어 에이전트가 실수를 바로잡을 기회를 줍니다. 에이전트의 안정성을 높여주는 중요한 옵션입니다.

✅ 체크리스트

  • StructuredToolTool의 가장 큰 차이점(인자 처리 방식)을 이해했나요?
  • ZERO_SHOT_REACT_DESCRIPTION 에이전트를 사용할 때 왜 description에 입력 형식 예시를 적어주는 것이 좋은지 이해했나요?
  • handle_parsing_errors가 왜 필요한지 설명할 수 있나요?

💡 연습 과제

  1. 문자열 도구 만들기: 두 개의 문자열을 입력받아 하나로 합쳐주는 concatenate 함수와 Concatenator 도구를 Tool.from_function을 사용하여 만들어 보세요. (예: 입력: "hello,world", 출력: "helloworld")

OpenAI Functions 에이전트 활용하기

🎯 이번 단계에서 배울 것

  • OpenAI의 “Function Calling” 기능과 그 장점 이해하기
  • Pydantic을 사용하여 도구의 인자(argument) 스키마를 명확하게 정의하는 방법 배우기
  • BaseTool을 상속받아 직접 커스텀 도구를 만드는 방법 익히기
  • OPENAI_FUNCTIONS 에이전트 타입의 작동 원리 이해하기

📝 1단계: Function Calling이란?

최신 OpenAI 모델(gpt-3.5-turbo-1106, gpt-4 등)들은 Function Calling이라는 특별한 기능을 내장하고 있습니다. 이는 LLM이 단순히 텍스트를 생성하는 것을 넘어, 개발자가 정의한 함수(도구)의 이름과 인자들을 구조화된 JSON 형식으로 출력하는 기능입니다.

  • ReAct 에이전트: LLM이 Action: Sum Calculator\nAction Input: 1,2 와 같은 텍스트를 생성하면, LangChain이 이 텍스트를 파싱하여 도구를 실행합니다. 텍스트 기반이라 실수가 잦을 수 있습니다.
  • Functions 에이전트: LLM이 {"name": "Sum Calculator", "arguments": {"a": 1, "b": 2}} 와 같은 JSON을 직접 출력합니다. 훨씬 안정적이고 명확합니다.

📝 2단계: Pydantic으로 인자 스키마 정의 및 커스텀 도구 만들기

Function Calling을 사용하려면, 도구와 그 인자들을 좀 더 엄격한 형식으로 정의해야 합니다. 이때 Pydantic 라이브러리가 사용됩니다.

전체 코드 (notebook.ipynb):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from typing import Type
from langchain.tools import BaseTool
from pydantic import BaseModel, Field

# 1. Pydantic으로 도구에 전달될 인자의 형태(Schema)를 정의합니다.
class CalculatorToolArgsSchema(BaseModel):
a: float = Field(description="The first number")
b: float = Field(description="The second number")

# 2. BaseTool을 상속받아 커스텀 도구 클래스를 만듭니다.
class CalculatorTool(BaseTool):
name = "CalculatorTool"
description = "Use this to perform sums of two numbers."
# 이 도구가 어떤 인자 스키마를 사용하는지 알려줍니다.
args_schema: Type[CalculatorToolArgsSchema] = CalculatorToolArgsSchema

# 실제 로직은 _run 메소드에 구현합니다.
def _run(self, a, b):
return a + b

🔍 코드 상세 설명

  • pydantic.BaseModel: 데이터의 형태, 타입, 설명을 강제하는 모델을 만들 때 사용합니다. a: float 처럼 각 인자의 타입과 description을 명확히 정의할 수 있습니다.
  • langchain.tools.BaseTool: Tool.from_function 보다 더 상세하게 도구를 커스터마이징할 때 사용하는 기반 클래스입니다.
  • args_schema: 이 도구가 어떤 인자 구조를 사용하는지 Pydantic 모델로 지정합니다. LangChain은 이 정보를 OpenAI API에 전달하여 Function Calling을 가능하게 합니다.
  • _run(self, a, b): 도구의 실제 실행 로직입니다. args_schema에 정의된 ab가 인자로 직접 전달됩니다.

📝 3단계: OPENAI_FUNCTIONS 에이전트 생성

이제 새로 만든 커스텀 도구를 OPENAI_FUNCTIONS 타입의 에이전트와 함께 사용합니다.

전체 코드 (notebook.ipynb):

1
2
3
4
5
6
7
8
9
agent = initialize_agent(
llm=llm,
verbose=True,
agent=AgentType.OPENAI_FUNCTIONS, # 에이전트 타입을 변경합니다.
handle_parsing_errors=True,
tools=[
CalculatorTool(), # 직접 만든 도구 클래스의 인스턴스를 전달합니다.
],
)

이 방식은 ReAct처럼 텍스트를 파싱할 필요 없이 모델의 내장 기능을 사용하므로, 현재 LangChain에서 OpenAI 모델과 함께 에이전트를 만드는 가장 안정적이고 권장되는 방법입니다.

✅ 체크리스트

  • ReAct 에이전트와 Functions 에이전트의 차이점을 설명할 수 있나요?
  • Pydantic을 사용하여 도구의 인자 스키마를 정의할 수 있나요?
  • BaseTool을 상속받아 나만의 커스텀 도구를 만들 수 있나요?

💡 연습 과제

  1. 커스텀 곱셈 도구: CalculatorTool을 참고하여, Pydantic 스키마와 BaseTool을 사용하는 MultiplierTool을 직접 만들어 보세요. 그리고 에이전트의 tools 리스트에 추가하여 테스트해 보세요.

에이전트에게 검색 능력 부여하기

🎯 이번 단계에서 배울 것

  • LangChain의 DuckDuckGoSearchAPIWrapper를 사용하여 웹 검색 기능을 도구로 만드는 방법 배우기
  • 에이전트가 외부 정보에 접근하여 질문에 답변하도록 하는 방법 이해하기
  • 에이전트가 검색 결과를 단순히 보여주는 것이 아니라, 내용을 이해하고 종합하여 답변을 생성하는 과정 살펴보기

📝 1단계: 검색 도구(Search Tool)의 필요성

지금까지의 에이전트는 계산기처럼 주어진 기능만 수행할 수 있었습니다. 만약 에이전트에게 “Cloudflare 주식 심볼이 뭐야?” 라고 묻는다면 어떨까요? LLM은 훈련 데이터에 있는 오래된 정보는 알지 몰라도, 최신 정보나 특정 정보는 알지 못합니다. 에이전트에게 인터넷 검색 능력을 부여하면, 이 한계를 뛰어넘어 거의 모든 질문에 답변할 수 있는 강력한 비서가 될 수 있습니다.

📝 2단계: 검색 도구 만들기

LangChain은 웹 검색을 쉽게 구현할 수 있도록 DuckDuckGoSearchAPIWrapper라는 유틸리티를 제공합니다. 이것을 사용하여 검색 도구를 만들어 보겠습니다.

전체 코드 (notebook.ipynb):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from langchain.utilities import DuckDuckGoSearchAPIWrapper

# 검색어(query)를 인자로 받는 Pydantic 스키마
class StockMarketSymbolSearchToolArgsSchema(BaseModel):
query: str = Field(description="The query you will search for...")

# 검색 도구 클래스
class StockMarketSymbolSearchTool(BaseTool):
name = "StockMarketSymbolSearchTool"
description = "Use this tool to find the stock market symbol for a company."
args_schema: Type[StockMarketSymbolSearchToolArgsSchema] = StockMarketSymbolSearchToolArgsSchema

def _run(self, query):
# _run 메소드 안에서 검색 API를 호출합니다.
ddg = DuckDuckGoSearchAPIWrapper()
return ddg.run(query)

# 에이전트의 tools 리스트에 검색 도구를 추가합니다.
tools = [StockMarketSymbolSearchTool()]

📝 3단계: 검색 에이전트 실행 및 분석

이제 에이전트는 검색 능력을 갖췄습니다. 더 복잡한 질문을 던져 보겠습니다.

실행 코드:

1
2
3
prompt = "Give me information on Cloudflare's stock and help me analyze if it's a potential good investment. Also tell me what symbol does the stock have."

agent.invoke(prompt)

사고 과정 분석:

1
2
3
4
5
6
7
8
9
10
11
12
13
> Entering new AgentExecutor chain...

Invoking: `StockMarketSymbolSearchTool` with `{'query': 'Stock Market Symbol for Cloudflare'}`

Observation: Stocks Cloudflare Inc Cloudflare Inc NET Morningstar Rating ...

Final Answer: The stock market symbol for Cloudflare is NET.

As for analyzing if Cloudflare is a potential good investment, here are some key points to consider:

1. Financial Performance: Cloudflare has shown strong revenue growth...
2. Stock Performance: Cloudflare's stock has performed well in 2023...
...
  • Invoking: 에이전트는 질문을 받고, 주식 심볼을 모른다는 것을 인지합니다. 그래서 스스로 StockMarketSymbolSearchTool을 사용해야겠다고 결정하고, Stock Market Symbol for Cloudflare라는 검색어를 만들어 도구를 호출합니다.
  • Observation: DuckDuckGo 검색 결과(긴 텍스트)를 받습니다.
  • Final Answer: 에이전트는 검색 결과를 이해하고 종합하여, 단순히 텍스트를 뱉어내는 것이 아니라 사용자의 질문에 맞춰 답변을 생성합니다.
    • “주식 심볼은 NET입니다.”
    • “투자에 대한 분석은 다음과 같습니다: 1. 재무 성과… 2. 주가 성과…”

이것이 바로 에이전트의 강력함입니다. 스스로 필요한 정보를 찾고, 그 정보를 바탕으로 사용자의 최종 목표에 맞는 답변을 생성합니다.

✅ 체크리스트

  • 에이전트에게 인터넷 검색 기능이 왜 필요한지 이해했나요?
  • DuckDuckGoSearchAPIWrapper를 사용하여 검색 도구를 만들 수 있나요?
  • 에이전트가 검색 결과를 어떻게 이해하고 답변 생성에 활용하는지 그 과정을 설명할 수 있나요?

💡 연습 과제

  1. 다른 질문하기: 에이전트에게 “오늘 한국 날씨 어때?” 또는 “최신 아이폰 모델에 대한 정보 알려줘” 와 같이 다양한 질문을 던져보고, 에이전트가 어떻게 검색어를 만들고 답변하는지 관찰해 보세요.

전문적인 금융 도구 세트 만들기

🎯 이번 단계에서 배울 것

  • 여러 개의 전문적인 도구를 에이전트에게 제공하는 것의 이점 이해하기
  • 외부 API(Alpha Vantage)를 호출하여 특정 데이터를 가져오는 커스텀 도구 만들기
  • 에이전트가 복잡한 요청을 해결하기 위해 여러 도구를 순차적으로 사용하는 과정 분석하기
  • 환경 변수에서 API 키를 안전하게 로드하는 방법 배우기

📝 1단계: 전문 도구의 필요성

범용 검색 도구는 편리하지만, 특정 분야의 질문에는 부족할 수 있습니다. “Cloudflare의 작년 매출은?” 이라는 질문에 웹 검색 결과는 여러 뉴스 기사나 블로그 글을 뒤섞어 보여줄 수 있습니다. 하지만 금융 정보를 제공하는 전문 API를 사용하면 정확한 데이터를 즉시 얻을 수 있습니다.

에이전트에게 여러 개의 전문적인 도구를 주면, 에이전트는 질문의 의도를 파악하고 가장 적합한 도구를 선택하여 더 효율적이고 정확하게 임무를 수행할 수 있습니다.

📝 2단계: Alpha Vantage API 및 금융 도구 구현

이번에는 주식 데이터를 제공하는 Alpha Vantage API를 사용하여 3개의 전문 금융 도구를 만들어 보겠습니다.

추가된 코드 (notebook.ipynb):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import os
import requests

# .env 파일 등에서 API 키를 로드합니다.
alpha_vantage_api_key = os.environ.get("ALPHA_VANTAGE_API_KEY")

# 도구 1: 회사 개요 정보
class CompanyOverviewTool(BaseTool):
name = "CompanyOverview"
description = "Use this to get an overview of the financials of the company..."
# ... (Pydantic 스키마 정의) ...
def _run(self, symbol):
r = requests.get(f"https://www.alphavantage.co/query?function=OVERVIEW&symbol={symbol}&apikey={alpha_vantage_api_key}")
return r.json()

# 도구 2: 손익 계산서
class CompanyIncomeStatementTool(BaseTool):
name = "CompanyIncomeStatement"
description = "Use this to get the income statement of a company..."
# ... (Pydantic 스키마 정의) ...
def _run(self, symbol):
r = requests.get(f"https://www.alphavantage.co/query?function=INCOME_STATEMENT&symbol={symbol}&apikey={alpha_vantage_api_key}")
return r.json()["annualReports"]

# 도구 3: 주가 정보
class CompanyStockPerformanceTool(BaseTool):
name = "CompanyStockPerformance"
description = "Use this to get the weekly performance of a company stock..."
# ... (Pydantic 스키마 정의) ...
def _run(self, symbol):
r = requests.get(f"https://www.alphavantage.co/query?function=TIME_SERIES_WEEKLY&symbol={symbol}&apikey={alpha_vantage_api_key}")
return r.json()

📝 3단계: 에이전트의 도구 상자 확장 및 다단계 추론 분석

이제 에이전트의 tools 리스트에 기존의 검색 도구와 함께 새로운 금융 도구 3개를 모두 추가합니다.

실행 코드:

1
2
3
prompt = "Give me financial information on Cloudflare's stock, considering its financials, income statements and stock performance help me analyze if it's a potential good investment."

agent.invoke(prompt)

사고 과정 분석:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> Entering new AgentExecutor chain...

Invoking: `StockMarketSymbolSearchTool` with `{'query': 'Cloudflare stock market symbol'}`
Observation: Ticker Symbol NET ...

Invoking: `CompanyOverview` with `{'symbol': 'NET'}`
Observation: {'Symbol': 'NET', 'AssetType': 'Common Stock', ...}

Invoking: `CompanyIncomeStatement` with `{'symbol': 'NET'}`
Observation: [{'fiscalDateEnding': '2022-12-31', ...}]

Invoking: `CompanyStockPerformance` with `{'symbol': 'NET'}`
Observation: {'Meta Data': ..., 'Weekly Time Series': ...}

Final Answer: Here's the financial information for Cloudflare Inc. (NYSE: NET): ...
  • 1단계 (검색): 에이전트는 “Cloudflare”의 주식 심볼을 모르기 때문에, 가장 먼저 StockMarketSymbolSearchTool을 사용하여 “NET”이라는 심볼을 찾아냅니다.
  • 2단계 (개요 파악): 알아낸 심볼 “NET”을 사용하여 CompanyOverviewTool을 호출하여 회사의 전반적인 재무 정보를 얻습니다.
  • 3단계 (상세 분석): CompanyIncomeStatementTool을 호출하여 구체적인 손익 계산서 데이터를 가져옵니다.
  • 4단계 (주가 확인): CompanyStockPerformanceTool을 호출하여 최근 주가 동향을 파악합니다.
  • 최종 답변: 에이전트는 4개 도구에서 얻은 모든 정보를 종합하여, 마치 전문 애널리스트처럼 체계적인 투자 분석 보고서를 생성합니다.

✅ 체크리스트

  • 하나의 범용 도구 대신 여러 개의 전문 도구를 사용하는 것의 장점을 이해했나요?
  • requests 라이브러리를 사용하여 외부 API를 호출하는 도구를 만들 수 있나요?
  • 에이전트가 목표를 달성하기 위해 여러 도구를 순차적으로, 지능적으로 사용하는 과정을 분석할 수 있나요?

💡 연습 과제

  1. 새로운 금융 도구 추가: Alpha Vantage API 문서(링크)를 보고, BALANCE_SHEET(대차대조표)나 CASH_FLOW(현금흐름표)를 가져오는 새로운 도구를 만들어 에이전트에 추가해 보세요. 그리고 “Cloudflare의 현금 흐름은 어때?”라고 질문해 보세요.

에이전트에게 페르소나 부여하기 (시스템 프롬프트)

🎯 이번 단계에서 배울 것

  • 에이전트의 행동과 역할을 정의하는 시스템 프롬프트(System Prompt)의 중요성 이해하기
  • initialize_agentagent_kwargs를 사용하여 시스템 프롬프트를 설정하는 방법 배우기
  • Jupyter Notebook의 에이전트 코드를 실제 Streamlit 애플리케이션으로 전환하는 과정 살펴보기
  • st.text_input으로 사용자 입력을 받아 에이전트를 실행하는 방법 익히기

📝 1단계: 시스템 프롬프트(System Prompt)란?

시스템 프롬프트는 에이전트의 “영혼”을 불어넣는 작업입니다. 이것은 에이전트의 모든 행동에 앞서 가장 먼저 주어지는 최상위 지시사항으로, 에이전트의 역할, 개성, 목표, 따라야 할 규칙 등을 정의합니다. 잘 만들어진 시스템 프롬프트는 에이전트의 답변 스타일과 품질을 극적으로 향상시킬 수 있습니다.

📝 2단계: 에이전트에 페르소나 주입하기

initialize_agent 함수의 agent_kwargs 인자를 사용하면 이 시스템 프롬프트를 주입할 수 있습니다.

전체 코드 (pages/06_InvestorGPT.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from langchain.schema import SystemMessage

agent = initialize_agent(
# ... (llm, tools 등 기존 인자) ...
agent_kwargs={
"system_message": SystemMessage(
content="""
You are a hedge fund manager.

You evaluate a company and provide your opinion and reasons why the stock is a buy or not.

Consider the performance of a stock, the company overview and the income statement.

Be assertive in your judgement and recommend the stock or advise the user against it.
"""
)
},
)

🔍 코드 상세 설명

  • agent_kwargs: 에이전트의 프롬프트에 추가적인 인자를 전달할 때 사용하는 딕셔너리입니다.
  • system_message: 이 키를 사용하여 시스템 메시지를 설정합니다.
  • SystemMessage(content=...): LangChain에서 시스템 메시지를 나타내는 객체입니다.
  • 페르소나 정의: 이제 에이전트는 더 이상 중립적인 정보 전달자가 아닙니다. 시스템 프롬프트에 따라, 그는 단호한 판단을 내리는 헤지펀드 매니저로서 행동하게 됩니다. “~인 것 같습니다”가 아니라 “이 주식은 매수해야 합니다. 왜냐하면…” 과 같은 assertive한 답변을 생성할 것입니다.

📝 3단계: InvestorGPT 앱 구현

이제 Jupyter Notebook에서 만들었던 모든 에이전트 로직을 pages/06_InvestorGPT.py 파일로 옮겨와 완전한 Streamlit 앱으로 만듭니다.

전체 코드 (pages/06_InvestorGPT.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ... (모든 Tool 클래스 및 agent 정의) ...

st.set_page_config(
page_title="InvestorGPT",
page_icon="💼",
)

st.markdown(""" # InvestorGPT ... """)

# 사용자가 회사 이름을 입력할 수 있는 텍스트 상자
company = st.text_input("Write the name of the company you are interested on.")

# 사용자가 회사 이름을 입력하면
if company:
# 에이전트를 실행합니다.
result = agent.invoke(company)
# 결과를 화면에 표시합니다. (달러 기호 이스케이프 처리)
st.write(result["output"].replace("$", "\$"))

🔍 코드 상세 설명

  • st.text_input: 사용자가 텍스트를 입력할 수 있는 상자를 만듭니다.
  • if company:: 사용자가 무언가를 입력하고 Enter를 치면, company 변수에 그 값이 할당되고 이 if 블록이 실행됩니다.
  • result["output"].replace("$", "\$"): Streamlit은 $ 기호를 수학 공식(LaTeX)의 시작으로 인식하는 경우가 있어 에러가 날 수 있습니다. \$로 바꿔주면 이 문제를 방지하고 문자 그대로의 달러 기호를 표시할 수 있습니다.

✅ 체크리스트

  • 시스템 프롬프트가 에이전트의 행동에 어떤 영향을 미치는지 이해했나요?
  • agent_kwargs를 사용하여 에이전트에게 페르소나를 부여할 수 있나요?
  • st.text_input을 사용하여 사용자 입력을 받을 수 있나요?

💡 연습 과제

  1. 페르소나 변경: 시스템 프롬프트를 수정하여 에이전트의 페르소나를 바꿔보세요. 예를 들어, “당신은 매우 신중하고 보수적인 투자 분석가입니다. 항상 최악의 시나리오를 먼저 경고하세요.” 또는 “당신은 환경(ESG)을 매우 중시하는 사회적 책임 투자자입니다. 재무 정보와 함께 회사의 환경 관련 활동도 언급하세요.” 와 같이 변경하고 결과가 어떻게 달라지는지 확인해 보세요.

SQL 데이터베이스와 대화하는 에이전트

🎯 이번 단계에서 배울 것

  • SQLDatabaseToolkit을 사용하여 에이전트에게 데이터베이스와 상호작용하는 능력 부여하기
  • create_sql_agent를 사용하여 Text-to-SQL 에이전트를 쉽게 생성하는 방법 배우기
  • 에이전트가 데이터베이스 스키마를 탐색하고, SQL 쿼리를 작성하고, 실행하고, 결과를 해석하는 전체 과정 분석하기
  • 에이전트가 에러를 만나고 스스로 복구하는 과정 살펴보기

📝 1단계: Text-to-SQL과 SQL 툴킷

Text-to-SQL은 사람이 쓰는 자연어(예: “가장 인기 있는 영화 10개 보여줘”)를 컴퓨터가 이해하는 SQL 쿼리문(SELECT * FROM movies ORDER BY popularity DESC LIMIT 10)으로 자동 변환하는 기술입니다. 에이전트에게 이 능력을 부여하면, 사용자는 복잡한 SQL을 몰라도 데이터베이스와 자유롭게 대화할 수 있게 됩니다.

LangChain은 이를 쉽게 구현할 수 있도록 SQLDatabaseToolkit을 제공합니다. 이 툴킷에는 데이터베이스와 상호작용하는 데 필요한 필수 도구들이 미리 패키징되어 있습니다.

  • sql_db_list_tables: 데이터베이스에 어떤 테이블이 있는지 목록을 봅니다.
  • sql_db_schema: 특정 테이블의 구조(컬럼, 데이터 타입 등)를 봅니다.
  • sql_db_query: SQL 쿼리를 실행합니다.
  • sql_db_query_checker: 쿼리를 실행하기 전에 문법 오류가 없는지 확인합니다.

📝 2단계: SQL 에이전트 생성

create_sql_agent 헬퍼 함수를 사용하면 단 몇 줄의 코드로 SQL 에이전트를 만들 수 있습니다.

전체 코드 (notebook.ipynb):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from langchain.agents import create_sql_agent, AgentType
from langchain.chat_models import ChatOpenAI
from langchain.agents.agent_toolkits import SQLDatabaseToolkit
from langchain.sql_database import SQLDatabase

llm = ChatOpenAI(temperature=0.1, model_name="gpt-4-1106-preview")

# 1. 데이터베이스에 연결합니다.
db = SQLDatabase.from_uri("sqlite:///movies.sqlite")

# 2. LLM과 DB를 사용하여 SQL 툴킷을 만듭니다.
toolkit = SQLDatabaseToolkit(db=db, llm=llm)

# 3. SQL 에이전트를 생성합니다.
agent = create_sql_agent(
llm=llm,
toolkit=toolkit,
agent_type=AgentType.OPENAI_FUNCTIONS,
verbose=True,
)

agent.invoke(
"Give me the movies that have the highest votes but the lowest budgets and give me the name of their directors also include their gross revenue."
)

📝 3단계: 데이터베이스 탐색 및 쿼리 생성 과정 분석

verbose=True로 실행된 에이전트의 사고 과정은 경이롭습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
> Entering new AgentExecutor chain...

Invoking: `sql_db_list_tables` with ``
Observation: directors, movies

Invoking: `sql_db_schema` with `movies,directors`
Observation: Error: table_names {'movies,directors'} not found in database

Invoking: `sql_db_schema` with `movies`
Observation: CREATE TABLE movies (id INTEGER, ...)

Invoking: `sql_db_schema` with `directors`
Observation: CREATE TABLE directors (name TEXT, ...)

Invoking: `sql_db_query_checker` with `SELECT ... FROM movies m JOIN directors d ...`
Observation: The provided SQLite query does not appear to contain any of the common mistakes...

Invoking: `sql_db_query` with `SELECT ... FROM movies m JOIN directors d ...`
Observation: [('Stiff Upper Lips', 'Gary Sinyor', 0, 10.0, 0), ...]

Final Answer: Here are movies that have the highest votes but the lowest budgets...
  • 1. 테이블 목록 확인: 에이전트는 질문을 받고, 먼저 어떤 테이블이 있는지 확인하기 위해 sql_db_list_tables를 호출합니다.
  • 2. 스키마 확인 (과 에러 복구): directorsmovies 테이블이 있다는 것을 알고, 두 테이블의 구조를 한 번에 보려고 시도하다가 에러를 만납니다. 하지만 에이전트는 멈추지 않고, 스스로 문제를 해결하기 위해 각 테이블의 스키마를 하나씩 따로따로 요청합니다.
  • 3. 쿼리 작성 및 검증: 두 테이블의 구조를 완벽히 파악한 뒤, JOIN, ORDER BY, LIMIT이 포함된 복잡한 SQL 쿼리를 작성합니다. 그리고 쿼리를 실행하기 전에 sql_db_query_checker로 문법을 검사하는 신중함까지 보여줍니다.
  • 4. 쿼리 실행 및 최종 답변: 검증된 쿼리를 sql_db_query로 실행하여 데이터를 가져온 뒤, 사용자가 이해하기 쉬운 자연어 문장으로 최종 답변을 가공하여 보여줍니다.

✅ 체크리스트

  • Text-to-SQL이 무엇인지, 왜 유용한지 설명할 수 있나요?
  • SQLDatabaseToolkit에 어떤 도구들이 포함되어 있는지 알고 있나요?
  • 에이전트가 자연어 질문을 SQL 쿼리로 바꾸기 위해 어떤 단계를 거치는지 설명할 수 있나요?

💡 연습 과제

  1. 다른 질문하기: movies.sqlite 데이터베이스를 사용하여 에이전트에게 다른 질문을 해보세요. 예를 들어, “가장 많은 영화를 감독한 감독은 누구야?”, “2015년에 개봉한 영화 중 수익이 가장 높은 영화는 뭐야?” 와 같은 질문을 하고 에이전트의 사고 과정을 분석해 보세요.

챕터 결론 및 요약

🎯 이번 단계에서 배울 것

  • LangChain 에이전트의 핵심 구성 요소(LLM, Tools, AgentType, Prompt) 복습하기
  • 다양한 에이전트 타입과 도구의 종류를 되짚어보기
  • 에이전트 구축의 전체적인 흐름 정리하기

📝 1단계: InvestorGPT 앱 완성

마지막으로, Home.py 파일을 수정하여 InvestorGPT 프로젝트가 완료되었음을 표시합니다. 이로써 우리의 포트폴리오에 또 하나의 멋진 AI 앱이 추가되었습니다.

수정 코드 (Home.py):

1
2
3
4
5
# Before
# - [ ] [📈 InvestorGPT](/InvestorGPT)

# After
# - [x] [📈 InvestorGPT](/InvestorGPT)

📝 2단계: 에이전트 챕터 총정리

축하합니다! 10장을 통해 우리는 LangChain 에이전트의 핵심적인 모든 것을 배웠습니다. 내용을 다시 한번 정리해 봅시다.

  • 두뇌 (The Brain) - LLM: 에이전트의 모든 추론과 계획을 담당하는 핵심 엔진입니다. 우리는 주로 ChatOpenAI 모델을 사용했습니다.

  • 손과 발 (The Hands) - Tools: 에이전트가 외부 세계와 상호작용하게 해주는 능력입니다. 우리는 직접 함수를 만들어 Tool, StructuredTool, BaseTool로 도구를 만들었고, DuckDuckGoSearchAPIWrapperSQLDatabaseToolkit처럼 미리 만들어진 강력한 도구 세트도 사용해 보았습니다.

  • 규칙과 지침 (The Rulebook) - AgentType & Prompt: 에이전트가 두뇌와 손발을 어떻게 연결하고 사용할지를 결정하는 규칙입니다.

    • AgentType: ZERO_SHOT_REACT_DESCRIPTION(범용), STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION(복잡한 도구용), 그리고 가장 강력한 OPENAI_FUNCTIONS(함수 호출용)까지 다양한 타입을 경험했습니다.
    • Prompt: 도구의 description을 명확하게 작성하는 것이 얼마나 중요한지 배웠고, SystemMessage를 통해 에이전트에게 “헤지펀드 매니저”와 같은 페르소나를 부여하여 답변의 수준을 한 차원 높이는 방법도 배웠습니다.

📝 3단계: 에이전트의 무한한 가능성

이제 여러분은 LangChain 에이전트를 만드는 데 필요한 모든 기본 지식을 갖추었습니다. 가능성은 무한합니다. 여러분의 이메일을 읽고 답장을 써주는 에이전트, 여러 API를 조합하여 여행 계획을 짜주는 에이전트, 코드 저장소를 분석하고 버그를 찾아주는 에이전트 등 상상하는 모든 것을 만들 수 있습니다. 다양한 도구를 직접 만들고 조합하며 여러분만의 강력한 AI 비서를 만들어 보세요!


출처 : https://nomadcoders.co/fullstack-gpt