tracks
대규모 언어 모델은 전문 역할, 도구, 실시간 데이터 접근을 제공할 때 더 유용해집니다. 단일 모델에 의존하는 대신, 각 에이전트가 서로 다른 분석 파트에 집중하는 소형 리서치 팀을 만들 수 있습니다.
이 튜토리얼에서 바로 그 작업을 수행합니다. GLM-5.3-Flash와 CrewAI를 사용해 멀티-에이전트 주식 애널리스트를 구축합니다. 세 개의 에이전트가 기초재무, 기술 지표, 최신 뉴스를 분석하고, 포트폴리오 매니저가 이 결과를 결합해 최종 매수(BUY), 보유(HOLD), 매도(SELL) 리서치 신호를 도출합니다.
먼저 주피터 노트북에서 워크플로를 테스트한 후, 주식 분석과 후속 질문에 답할 수 있는 간단한 터미널 UI로 전환합니다.
이 프로젝트에서는 다음을 사용합니다:
- GLM-5.3-Flash: 추론
- CrewAI: 에이전트 조정
- Finnhub: 시세 및 기초재무 데이터
- Yahoo Finance: 과거 가격
- Tavily: 최신 뉴스
- Pandas와 NumPy: 기술적 지표
프레임워크가 처음이신가요? Building AI Agents with CrewAI 코스에서 에이전트, 크루, 플로우를 기초부터 다룹니다.
멀티 에이전트 애플리케이션에 GLM-5.3-Flash를 쓰는 이유
GLM-5.3-Flash는 성능, 효율성, 가격의 균형이 뛰어납니다. 전체 개요는 GLM-5.3-Flash 가이드와 Qwen3.8-Flash-Next 비교 글을 참고하세요.
경쟁력 있는 성능과 효율
Artificial Analysis Intelligence Index에서 57점을 기록해 상위권 모델에 속하며, 훨씬 비싼 프런티어 모델들과도 경쟁합니다. 예를 들어:
-
GPT-5.6 Sol도 57점(
high추론 노력)이나, 입력 토큰은 25배 이상, 출력 토큰은 40배 이상 비쌉니다. -
Claude Sonnet 5는
max추론에서도 55점으로 더 낮고, GLM-5.3-Flash 대비 입력/출력 토큰 가격이 각각 20배/30배 비쌉니다.
총 320B 파라미터지만 토큰당 활성 파라미터는 약 18B에 불과해, 계산 효율을 유지하면서도 강한 성능을 냅니다.

출처: AI Model & API Providers Analysis | Artificial Analysis
매력적인 가격
앞서 언급했듯, 이 성능은 비교적 낮은 API 요금으로 제공됩니다. 작성 시점 기준으로 OpenRouter는 GLM-5.3-Flash를 50% 할인된, 백만 입력 토큰당 $0.075, 백만 출력 토큰당 $0.25에 제공하고 있습니다.
정가 $0.15/$0.50라고 해도 OpenAI의 GPT-5.6 Luna($0.20/$1.20, max 추론에서 AA Intelligence Index 52점)보다 여전히 저렴합니다. 덕분에 실험뿐 아니라 규모 있는 프로덕션급 에이전트 애플리케이션에도 적합합니다.

출처: GLM 5.3 Flash - API Pricing & Benchmarks | OpenRouter
멀티 에이전트 시스템을 구축할 때 이 점은 더욱 중요합니다. 사용자 한 번의 요청이 백엔드에서 많은 LLM 호출을 촉발할 수 있기 때문입니다. 단일 LLM 에이전트만 해도 추론, 도구 선택, 도구 출력 처리, 최종 응답 생성을 위해 여러 차례 모델을 호출합니다. 에이전트 하나가 약 7회 호출하고 워크플로에 5개 에이전트가 있다면, 요청 한 번에 35회 이상의 호출이 발생할 수 있습니다.
정확한 횟수는 설계에 따라 달라지지만 요지는 간단합니다. 에이전트 워크플로는 금방 비싸질 수 있습니다. GLM-5.3-Flash처럼 성능이 충분하면서 토큰 단가가 낮은 모델을 쓰면 데모에서 실제 프로덕션으로 확장할 때 큰 차이를 만듭니다.
1. GLM-5.3-Flash 환경 설정
멀티 에이전트 워크플로를 만들기 전에 Z.ai, Finnhub, Tavily의 API 키를 생성하세요.
API 키 만들기
Z.ai의 경우 일반 API를 사용해야 하며, GLM Coding Plan은 사용하지 마세요. Coding Plan은 지원되는 AI 코딩 도구용이며, 자체 멀티 에이전트 애플리케이션의 범용 API로 쓰면 안 됩니다.
일반 Z.ai API 키를 만들고 소액의 크레딧을 충전하세요. GLM-5.3-Flash의 비용이 저렴하므로 테스트에는 소액이면 충분합니다.

출처: Z.ai API Platform — Start building with GLM-5.3
또한 Finnhub와 Tavily에서 무료 계정을 만들고 API 키를 생성할 수 있습니다. 이 프로젝트를 테스트하기에는 무료 요금제가 충분합니다.
프로젝트 파일 설정
다음으로 프로젝트 폴더에 .env 파일을 만들고 API 키를 추가합니다:
ZAI_API_KEY="your_zai_api_key"
FINNHUB_API_KEY="your_finnhub_api_key"
TAVILY_API_KEY="your_tavily_api_key"
이제 새로운 주피터 노트북을 만듭니다. 여기서 완전한 멀티 에이전트 워크플로를 먼저 구축해 테스트하고, 각 에이전트의 출력을 확인하며, 애플리케이션으로 전환하기 전 문제를 수정합니다.
필요한 라이브러리 설치
에이전트 생성과 조정을 위해 CrewAI, 시세 데이터를 위해 Finnhub, 실시간 웹 검색을 위해 Tavily를 사용합니다. 필요한 파이썬 패키지를 설치하세요:
!pip install -q crewai crewai-tools finnhub-python tavily-python pandas numpy requests python-dotenv openai
라이브러리와 API 키 가져오기
이제 필요한 라이브러리를 임포트하고 API 키를 로드합니다:
import os
import re
import time
import finnhub
import numpy as np
import pandas as pd
import requests
from dotenv import load_dotenv
from openai import OpenAI
from tavily import TavilyClient
from crewai import Agent, Crew, LLM, Process, Task
from crewai.tools import tool
from crewai_tools import TavilySearchTool
# Load environment variables
load_dotenv()
# Disable CrewAI tracing
os.environ.setdefault("CREWAI_TRACING_ENABLED", "false")
os.environ.setdefault("OTEL_SDK_DISABLED", "true")
마지막으로, 에이전트가 분석할 종목을 선택하세요:
TICKER = "NVDA"
노트북 전체에서 NVIDIA(NVDA)를 사용하겠지만, 관심 있는 다른 티커(AAPL, MSFT, TSLA 등)로 교체하셔도 됩니다.
2. GLM-5.3-Flash와 데이터 API 구성
API 키가 준비되었으니, 프로젝트 전반에서 에이전트가 사용할 서비스를 연결합니다.
먼저 주식 시장 데이터용 Finnhub과 웹 검색용 Tavily를 초기화합니다:
finnhub_client = finnhub.Client(
api_key=os.environ["FINNHUB_API_KEY"]
)
tavily_client = TavilyClient(
api_key=os.environ["TAVILY_API_KEY"]
)
금융 에이전트를 만들 때 어떤 데이터 소스를 써야 할지 고민된다면, 주식 시장 데이터 API 선택 가이드를 참고하세요.
다음으로 CrewAI의 LLM 클래스를 통해 GLM-5.3-Flash를 에이전트의 기본 LLM으로 설정합니다. Z.ai는 OpenAI 호환 API를 제공하므로 CrewAI에서 바로 연결할 수 있습니다:
llm = LLM(
model="openai/glm-5.3-flash",
api_key=os.environ["ZAI_API_KEY"],
base_url="https://api.z.ai/api/paas/v4/",
temperature=0.1,
)
이 애플리케이션은 금융 리서치와 분석에 초점을 맞추므로 일관되고 집중된 응답을 위해 temperature를 낮게 유지합니다.
모델과 시세 데이터, 웹 검색 API 연결을 마쳤으니, 이제 에이전트가 사용할 도구를 구축합니다.
3. 과거 시장 데이터 수집
다음으로, 주가의 추이를 파악할 수 있도록 과거 가격 데이터가 필요합니다.
이를 위해 Yahoo Finance의 공개 차트 엔드포인트를 사용해 일별 시가, 고가, 저가, 종가, 거래량 데이터를 수집합니다.
가격 이력 다운로드
HTTP = requests.Session()
HTTP.headers.update({"User-Agent": "glm-stock-swarm/1.0"})
def _yahoo_history(ticker: str, days: int) -> pd.DataFrame:
end = int(time.time())
start = end - days * 24 * 60 * 60
response = HTTP.get(
f"https://query1.finance.yahoo.com/v8/finance/chart/{ticker}",
params={
"period1": start,
"period2": end,
"interval": "1d",
"events": "history",
},
timeout=30,
)
response.raise_for_status()
result = response.json()["chart"]["result"][0]
values = result["indicators"]["quote"][0]
frame = pd.DataFrame({
"date": pd.to_datetime(
result["timestamp"], unit="s", utc=True
).tz_localize(None),
"open": values["open"],
"high": values["high"],
"low": values["low"],
"close": values["close"],
"volume": values["volume"],
})
frame = (
frame.dropna(subset=["close"])
.sort_values("date")
.reset_index(drop=True)
)
frame.attrs["source"] = "Yahoo public chart"
return frame
def get_price_history(ticker: str, days: int = 450) -> pd.DataFrame:
return _yahoo_history(ticker.strip().upper(), days)
약 450일의 이력을 수집합니다. 이렇게 하면 200일 이동평균 같은 장기 지표를 계산할 수 있을 만큼 충분한 데이터가 확보됩니다.
기술적 지표 계산
다음으로 자주 사용하는 지표 몇 가지를 계산합니다:
- 이동평균: 최근 20일, 50일, 200일 구간 평균
- RSI(상대강도지수): 0~100 스케일로, 과매수(>70)/과매도(<30) 상태를 가늠
- MACD: 장단기 지수이동평균(EMA)의 차이
def add_indicators(frame: pd.DataFrame) -> pd.DataFrame:
if frame.empty:
return frame.copy()
result = frame.copy()
result.attrs.update(frame.attrs)
# Moving averages
result["SMA20"] = result["close"].rolling(20).mean()
result["SMA50"] = result["close"].rolling(50).mean()
result["SMA200"] = result["close"].rolling(200).mean()
# RSI
delta = result["close"].diff()
gain = delta.clip(lower=0)
loss = -delta.clip(upper=0)
avg_gain = gain.ewm(
alpha=1 / 14, min_periods=14, adjust=False
).mean()
avg_loss = loss.ewm(
alpha=1 / 14, min_periods=14, adjust=False
).mean()
result["RSI14"] = 100 - (
100 / (1 + avg_gain / avg_loss.replace(0, np.nan))
)
# MACD
result["EMA12"] = result["close"].ewm(
span=12, adjust=False
).mean()
result["EMA26"] = result["close"].ewm(
span=26, adjust=False
).mean()
result["MACD"] = result["EMA12"] - result["EMA26"]
return result
이 지표들은 기술적 분석 에이전트가 추세와 모멘텀을 빠르게 파악하는 데 유용하며, LLM이 모든 계산을 직접 수행할 필요를 줄여줍니다.
최신 값 확인
이제 데이터를 수집하고 지표를 계산한 뒤, 최신 행을 표시합니다:
history = add_indicators(
get_price_history(TICKER)
)
if len(history) < 200:
raise RuntimeError(
f"Need at least 200 daily observations; received {len(history)}."
)
print(
f"Loaded {len(history)} rows "
f"from {history.attrs['source']}."
)
history.tail(3)[
[
"date",
"close",
"SMA20",
"SMA50",
"SMA200",
"RSI14",
"MACD",
]
]

위와 유사하게 최신 주가와 이동평균, RSI, MACD가 함께 표시되는 것을 볼 수 있습니다.
이 데이터는 이후 기술적 분석 에이전트에 전달해 가격 추세, 모멘텀, 상승/하락 신호를 파악하게 합니다.
4. 데이터 소스를 CrewAI 도구로 변환
데이터 소스가 준비되었으니 CrewAI 도구로 변환해야 합니다. 이렇게 하면 에이전트가 재무, 기술, 뉴스 정보가 필요할 때 직접 호출할 수 있습니다.
주식 데이터 도구 생성
먼저 수치를 깔끔하고 일관되게 유지하기 위한 간단한 헬퍼 함수를 만듭니다:
def _number(value, decimals: int = 2) -> str:
if value is None or pd.isna(value):
return "N/A"
return f"{float(value):,.{decimals}f}"
다음으로 기초재무 도구를 만듭니다. Finnhub를 사용해 최신 주가, 밸류에이션, 성장성, 수익성, 기타 기업 기초 정보를 가져옵니다.
@tool("Get Stock Fundamentals")
def get_fundamentals(ticker: str) -> str:
"""Get current price and company fundamentals from Finnhub for one ticker."""
ticker = ticker.strip().upper()
quote = finnhub_client.quote(ticker)
metrics = finnhub_client.company_basic_financials(ticker, "all")
m = metrics.get("metric", {})
return f"""Ticker: {ticker}
Source: Finnhub
Current price: {_number(quote.get('c'))}
Previous close: {_number(quote.get('pc'))}
Daily change %: {_number(quote.get('dp'))}
Day high / low: {_number(quote.get('h'))} / {_number(quote.get('l'))}
Market cap (USD millions): {_number(m.get('marketCapitalization'))}
52-week high / low: {_number(m.get('52WeekHigh'))} / {_number(m.get('52WeekLow'))}
Normalized annual P/E: {_number(m.get('peNormalizedAnnual'))}
Annual P/B: {_number(m.get('pbAnnual'))}
Annual P/S: {_number(m.get('psAnnual'))}
ROE TTM: {_number(m.get('roeTTM'))}
Net margin TTM: {_number(m.get('netProfitMarginTTM'))}
Revenue growth TTM YoY: {_number(m.get('revenueGrowthTTMYoy'))}
EPS growth TTM YoY: {_number(m.get('epsGrowthTTMYoy'))}
Annual debt/equity: {_number(m.get('totalDebt/totalEquityAnnual'))}"""
앞서 만든 과거 데이터와 지표를 사용해 기술적 분석 도구도 만듭니다.
@tool("Analyze Stock Technicals")
def get_technicals(ticker: str) -> str:
"""Calculate price trends, returns, moving averages, RSI, and MACD."""
ticker = ticker.strip().upper()
frame = add_indicators(get_price_history(ticker))
if len(frame) < 200:
return (
f"Technical data unavailable for {ticker}: "
"fewer than 200 observations."
)
latest = frame.iloc[-1]
return f"""Ticker: {ticker}
Source: {frame.attrs.get('source', 'unknown')}
Last market date: {latest['date'].date()}
Close: {_number(latest['close'])}
SMA20 / SMA50 / SMA200: {_number(latest['SMA20'])} / {_number(latest['SMA50'])} / {_number(latest['SMA200'])}
RSI14: {_number(latest['RSI14'])}
MACD: {_number(latest['MACD'])}
20-day return: {_number((latest['close'] / frame['close'].iloc[-21] - 1) * 100)}%
50-day return: {_number((latest['close'] / frame['close'].iloc[-51] - 1) * 100)}%
Price vs SMA20: {_number((latest['close'] / latest['SMA20'] - 1) * 100)}%
Price vs SMA50: {_number((latest['close'] / latest['SMA50'] - 1) * 100)}%
Price vs SMA200: {_number((latest['close'] / latest['SMA200'] - 1) * 100)}%"""
여기서 중요한 부분은 @tool 데코레이터입니다. 일반 파이썬 함수를 CrewAI 에이전트가 태스크 중에 호출할 수 있는 도구로 바꿔줍니다.
웹 검색 도구 추가
기업 뉴스와 최근 동향은 CrewAI의 Tavily 검색 도구를 사용합니다:
web_search = TavilySearchTool(
api_key=os.environ["TAVILY_API_KEY"],
topic="news",
search_depth="advanced",
days=30,
max_results=5,
)
검색은 최근 30일로 제한하고 최대 5개 결과만 반환합니다. 이렇게 하면 충분한 최신 정보를 제공하면서 불필요한 콘텐츠로 연구 에이전트를 압도하지 않습니다.
주식 도구 테스트
에이전트를 만들기 전에 두 개의 사용자 정의 도구를 직접 실행해 보세요:
print(get_fundamentals.run(ticker=TICKER))
print()
print(get_technicals.run(ticker=TICKER))

이제 선택한 주식의 최신 기초재무 지표와 이어서 기술적 지표가 표시됩니다.
5. 멀티 에이전트 리서치 팀 구성
도구가 준비되었으니 멀티 에이전트 리서치 팀을 만듭니다.
기초재무, 기술적 분석, 최신 뉴스에 각각 특화된 세 명의 에이전트와, 이들의 결과를 결합해 최종 판단을 내리는 포트폴리오 매니저를 사용합니다.
전문 에이전트 생성
먼저 기초재무 애널리스트를 만듭니다:
fundamental_agent = Agent(
role="Fundamental Analyst",
goal=(
"Evaluate financial health, growth, profitability, "
"valuation, and balance-sheet risk."
),
backstory=(
"You are a careful long-term equity analyst. "
"Use the fundamentals tool and never invent a figure. "
"Treat N/A as missing, not as zero."
),
tools=[get_fundamentals],
llm=llm,
allow_delegation=False,
max_iter=4,
verbose=False,
)
다음으로 기술적 애널리스트를 만듭니다:
technical_agent = Agent(
role="Technical Analyst",
goal=(
"Determine whether the current price setup is "
"bullish, bearish, or neutral."
),
backstory=(
"You interpret indicators calculated by Python. "
"Never guess market prices or indicator values."
),
tools=[get_technicals],
llm=llm,
allow_delegation=False,
max_iter=4,
verbose=False,
)
그다음 금융 뉴스 애널리스트를 만듭니다:
news_agent = Agent(
role="Financial News Analyst",
goal=(
"Find material recent developments and distinguish "
"confirmed reporting from speculation."
),
backstory=(
"You are a skeptical financial-news researcher. "
"Prioritize primary and reputable sources, dates, and links."
),
tools=[web_search],
llm=llm,
allow_delegation=False,
max_iter=5,
verbose=False,
)
각 전문가는 실제로 필요한 도구만 받습니다. 이렇게 하면 워크플로가 집중되고, 관련 없는 도구 호출을 방지합니다.
포트폴리오 매니저 생성
마지막으로 세 전문가의 보고서를 검토하는 매니저 에이전트를 만듭니다:
manager_agent = Agent(
role="Portfolio Manager",
goal=(
"Combine the specialist reports into a balanced, "
"evidence-based research signal."
),
backstory=(
"You lead an equity research team. "
"Weigh bullish and bearish evidence, use HOLD when evidence "
"is mixed, and never add unsupported facts."
),
llm=llm,
allow_delegation=False,
max_iter=4,
verbose=False,
)
포트폴리오 매니저는 데이터 도구에 직접 접근할 필요가 없습니다. 대신 세 전문가가 만든 보고서를 바탕으로 작업합니다.
allow_delegation=False 설정은 워크플로를 예측 가능하게 하며, max_iter는 각 에이전트의 추론·도구 사용 사이클 수를 제한합니다.
6. 전문화된 리서치 태스크 지정
에이전트를 만드는 것은 그들의 정체성을 정의하는 일입니다. 이제 각 에이전트에 무엇을 해야 하며 최종 출력에 무엇이 포함되어야 하는지를 정확히 알려줘야 합니다.
리서치 태스크 정의
기초재무 분석 태스크부터 시작합니다:
fundamental_task = Task(
description=(
f"Analyze {TICKER}'s growth, profitability, valuation, "
"balance sheet, and business quality. "
"Use Get Stock Fundamentals. "
"Return a Fundamental Score from 0-100, strongest positive, "
"biggest risk, and note all material missing data."
),
expected_output=(
"A concise fundamental assessment with a 0-100 score, "
"evidence, strongest positive, biggest risk, "
"and missing-data note."
),
agent=fundamental_agent,
)
다음으로 기술적 분석 태스크를 정의합니다:
technical_task = Task(
description=(
f"Analyze {TICKER}'s technical setup. "
"Use Analyze Stock Technicals. "
"Consider SMA20/50/200, RSI14, MACD, recent returns, and trend. "
"Return Technical Score 0-100, Bullish/Neutral/Bearish signal, "
"trend, momentum, and main technical risk."
),
expected_output=(
"A concise technical assessment with score, signal, trend, "
"momentum, data source, and main risk."
),
agent=technical_agent,
)
최근 동향을 위해 뉴스 리서치 태스크를 만듭니다:
news_task = Task(
description=(
f"Research the most important recent news for {TICKER}, "
"focusing on the last 30 days: earnings, guidance, analyst "
"revisions, products, partnerships, M&A, regulation, lawsuits, "
"management, and industry developments. "
"Use Tavily Search. "
"Return News Score 0-100, sentiment, positive and negative "
"catalysts, publication dates, and source URLs. "
"Ignore low-quality speculation."
),
expected_output=(
"A sourced recent-news assessment with score, sentiment, "
"catalysts, dates, and clickable URLs."
),
agent=news_agent,
)
각 에이전트에 명확한 태스크와 출력 형식을 제공하는 것이 중요합니다. 이렇게 하면 포트폴리오 매니저가 보고서를 쉽게 비교할 수 있으며, 매번 완전히 다른 형태의 응답을 받지 않게 됩니다.
최종 결정 태스크 생성
마지막 태스크는 세 개의 보고서를 통합합니다:
decision_task = Task(
description=(
f"Review all three specialist reports and make the final "
f"educational research assessment for {TICKER}. "
"Return exactly one signal: BUY, HOLD, or SELL. "
"Use HOLD when evidence is mixed or confidence is insufficient. "
"Base every claim only on the supplied reports."
),
expected_output=(
"Ticker; Signal; Overall Score /100; Confidence %; "
"Fundamental, Technical, and News scores; Bull Case; "
"Bear Case; Main Catalyst; Main Risk; Final Explanation; "
"Sources; and an educational-not-financial-advice disclaimer."
),
agent=manager_agent,
context=[
fundamental_task,
technical_task,
news_task,
],
)
핵심은 context입니다. 이를 통해 포트폴리오 매니저는 세 전문가 태스크의 출력을 모두 확인하고, 기초재무·기술·뉴스 신호를 비교한 뒤 최종 매수/보유/매도 판단을 내릴 수 있습니다.
이제 에이전트, 도구, 태스크가 준비되었습니다. 다음으로 CrewAI 워크플로로 모두 연결해 전체 리서치 팀을 실행합니다.
7. 멀티 에이전트 리서치 크루 실행
이제 모든 요소를 결합해 완전한 주식 리서치 워크플로를 실행합니다.
크루 생성
세 명의 전문 에이전트와 포트폴리오 매니저를 포함하는 CrewAI 크루를 만듭니다:
crew = Crew(
agents=[
fundamental_agent,
technical_agent,
news_agent,
manager_agent,
],
tasks=[
fundamental_task,
technical_task,
news_task,
decision_task,
],
process=Process.sequential,
verbose=False,
tracing=False,
)
Process.sequential을 사용해 태스크가 순차적으로 실행됩니다. 기초재무, 기술, 뉴스 에이전트가 먼저 분석을 마치고, 포트폴리오 매니저가 이 보고서를 받아 최종 평가를 생성합니다.
리서치 팀 실행
워크플로를 비동기로 시작합니다:
result = await crew.kickoff_async()
에이전트들이 여러 번 LLM과 도구를 호출하므로 시간이 걸릴 수 있습니다. 테스트에서는 전체 리서치 워크플로가 약 4~5분 소요되었습니다.
완료되면 최종 포트폴리오 매니저 보고서를 표시합니다:
print(result.raw)

다음이 포함된 상세 보고서를 보게 됩니다:
- 매수, 보유, 매도 신호
- 종합 점수
- 신뢰도
- 전문가별 점수
- 강세 요인
- 약세 요인
- 촉매
- 리스크
- 근거 자료
이 예시에서 에이전트들은 NVIDIA를 독립적으로 분석한 뒤, 그 결과를 결합해 근거 기반의 단일 리서치 보고서를 만들었습니다. 이것이 멀티 에이전트 접근의 장점입니다. 하나의 모델이 한 번에 모든 걸 처리하게 하는 대신, 각 에이전트가 자기 영역에 집중하고 포트폴리오 매니저가 모든 리서치를 통합합니다.
위 코드를 실행하는 데 문제가 있다면, 전체 코드와 출력이 포함된 주피터 노트북을 참고하거나 직접 실행해 보세요: glm-stock-swarm/stock_analyst_crew.ipynb
8. 터미널 UI 구축 및 실행
주피터 노트북에서 모든 것을 테스트한 후, 전체 워크플로를 터미널 사용자 인터페이스(TUI)로 변환했습니다. 이제 노트북 셀을 일일이 실행하지 않고, 단 한 번의 명령으로 멀티 에이전트 주식 애널리스트를 실행할 수 있습니다.
코드와 설정 안내를 포함한 전체 프로젝트는 GitHub에서 확인할 수 있습니다: glm-stock-swarm GitHub 리포지토리.
앱 설치 및 실행
먼저 리포지토리를 클론합니다:
git clone https://github.com/kingabzpro/glm-stock-swarm.git
cd glm-stock-swarm
.env 파일에 Z.ai, Finnhub, Tavily API 키를 추가한 후, uv로 모든 의존성을 설치합니다:
uv sync
마지막으로 TUI를 실행합니다:
uv run glm-stock-swarm
주식 분석하기
인터페이스에는 지원되는 주식 티커를 입력해 분석을 시작할 수 있는 간단한 입력창이 있습니다.

티커를 제출하면 에이전트가 순서대로 작업하는 과정을 볼 수 있습니다. 기초재무 애널리스트가 먼저 시작하고, 이어서 기술적 애널리스트, 뉴스 애널리스트가 진행한 뒤, 마지막으로 포트폴리오 매니저가 모든 내용을 결합해 최종 결정을 내립니다.

마지막에 TUI는 터미널 내에서 바로 완성 보고서를 생성합니다. 기초재무 점수, 기술 점수, 뉴스 점수, 종합 점수, 신뢰도, 강세/약세 요인, 리스크, 촉매, 최종 매수/보유/매도 신호가 포함됩니다.
후속 질문하기
전체 멀티 에이전트 파이프라인을 매번 다시 실행하지 않도록 후속 채팅 기능도 추가했습니다.

예를 들어, SELL 권고를 받은 뒤 보유 물량을 전량 매도할지 일부만 매도할지 질문했습니다.

네 개의 에이전트를 다시 모두 실행하는 대신, 애플리케이션은 생성된 리서치 보고서의 컨텍스트를 사용해 훨씬 빠르게 후속 응답을 제공합니다.
즉, 비용이 큰 멀티 에이전트 리서치는 한 번만 수행하고, 후속 질문은 기존 분석을 재사용할 수 있어 애플리케이션이 더 실용적입니다.
마무리 생각
테스트에서 주목할 점은 도구 호출의 신뢰성입니다. 세 전문가 모두 올바른 도구를 선택했고, 누락 데이터를 0이 아닌 N/A로 처리했으며, 포트폴리오 매니저가 필요로 하는 점수+근거 형식을 반환했습니다. 보통 저렴한 모델들이 실패하는 부분입니다.
속도는 트레이드오프입니다. Artificial Analysis에 따르면 Z.ai API에서 GLM-5.3-Flash의 출력 속도는 초당 약 45 토큰으로, 동급 개방 가중치 모델 대비 낮은 편이며 전체 크루 실행이 4~5분 걸릴 때 체감됩니다. 또한 인텔리전스 지수 57점으로 GLM-5.3의 60점 대비 소폭 낮아 약간의 추론 품질을 양보하지만, 가격이 약 10분의 1이라는 점을 고려하면 합리적인 선택입니다.
이 어느 것도 고정되지 않습니다. OpenRouter, Hugging Face Inference, Ollama를 통한 로컬 모델로 전환하려면 보통 API 키, 모델명, 베이스 URL 세 가지만 바꾸면 됩니다. 도구도 마찬가지로, 여기서 Finnhub와 Yahoo가 나눈 역할을 yfinance 하나로 대체할 수 있습니다.
다음 단계로는 페이퍼 트레이딩 API에 연결하고 아침마다 실행되도록 스케줄링하겠습니다. 다만 데모 계정에서 유지하세요. 이 프로젝트의 목적은 에이전트 설계를 가르치는 것이며, 실제 매매가 아닙니다.
에이전트형 AI를 처음부터 제대로 배우고 싶다면, 입문에 적합한 AI Agent Fundamentals 스킬 트랙을 추천합니다.