🧠 LangChain + Gemini: SystemMessage를 우회하는 재미있는 여정

LangChain을 이용해 Gemini API와 대화할 때, 우리는 중요한 문제가 하나 생깁니다.

"Gemini는 SystemMessage를 지원하지 않아요!"

하지만 걱정 마세요. LangChain은 마치 똑똑한 연출가처럼 대본 첫 줄에 시스템 지시사항을 몰래 적어두는 방식으로 이를 우회할 수 있죠.

지금부터 그 과정을 역할극처럼 스텝 바이 스텝으로 알아봅시다!


🎭 Step 1: 등장인물 소개

  • 👩‍🏫 SystemMessage — 원래는 AI의 성격과 말투를 정하는 지시자. (Gemini는 안 받아요!)
  • 🧑‍💻 HumanMessage — 유저의 질문이나 지시사항
  • 🧩 LangChain Prompt Template — 메시지를 조합해주는 마법의 틀
  • 🤖 Gemini AI — 시크하지만 강력한 AI. 앞에 뭐라고 적혀있든 그대로 읽어요.

🧾 Step 2: SystemMessage를 가장한 “HumanMessage 서론” 만들기

우리는 이렇게 쓸 수 없어요 👎:

SystemMessage(content="너는 항상 시처럼 대답해줘.")
 

하지만 이렇게는 됩니다! 👍

HumanMessage(content="너는 항상 시처럼 대답해줘.\n\n질문: 바다가 왜 푸르지?")

즉, SystemMessage를 HumanMessage의 첫 줄에 넣는 것이 핵심이에요.


🧪 Step 3: LangChain 코드로 구현하기

from langchain_core.messages import HumanMessage
from langchain_core.runnables import Runnable
from langchain_google_genai import ChatGoogleGenerativeAI

# Gemini 모델 인스턴스 (gemini-1.5-flash는 ChatModel 역할)
llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash", temperature=0.7)

# SystemMessage를 HumanMessage로 위장!
system_instruction = "너는 항상 해적처럼 말하는 AI야. 'Yo-ho-ho!'로 대답을 시작해."
user_question = "오늘의 날씨를 알려줘."

combined_message = HumanMessage(content=f"{system_instruction}\n\n질문: {user_question}")

# 실행
response = llm.invoke([combined_message])
print(response.content)
 

🎉 결과:

"Yo-ho-ho! 오늘의 날씨는 해적이 항해하기 딱 좋은 맑음이야, 선장님!"


🛠️ Step 4: PromptTemplate + Runnable로 일반화

LangChain의 prompt template을 이용하면 더 재사용 가능한 구조도 만들 수 있어요.

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import Runnable
from langchain_google_genai import ChatGoogleGenerativeAI

# ✅ Gemini LLM 인스턴스 생성
llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash", temperature=0.7)

# ✅ LangChain PromptTemplate 정의
prompt = ChatPromptTemplate.from_messages([
    ("human", "{system_message}\n\n질문: {user_input}")
])

# ✅ Runnable 체인 구성
chain: Runnable = prompt | llm

# ✅ 실행 테스트
response = chain.invoke({
    "system_message": "너는 5살 어린이처럼 말해야 해.",
    "user_input": "블랙홀이 뭐야?"
})

print(response.content)

🍼 결과:

"블랙홀은 큰 먼지 빨아들이는 우주 청소기야! 근데 무서운 건 아니야!"


🧩 Step 5: 마무리 - 이것이 Gemini의 ‘SystemMessage 우회’ 전략이다!

역할 지원 여부 대안
SystemMessage ❌ 미지원 (Gemini 기준) ✅ HumanMessage의 앞단에 삽입
LangChain PromptTemplate ✅ 가능 메시지 조합으로 우회
성격 설정, 말투 제어 ✅ 가능 “지시사항 + 질문” 패턴 활용
 

✨ 마치며

Gemini는 "나는 SystemMessage 몰라요"라고 말하지만,
LangChain은 이렇게 말하죠:

“그럼 그냥 첫 줄에 몰래 써두면 되지!”

이 우회 전략은 매우 강력하며, 다양한 캐릭터 지정, 역할극, 말투 제어 등에 유용하게 활용됩니다.

Posted by 제이브레인
,

🎈 Colab vs Conda, 두 가지 방식으로 Gemini에게 물어보기


🧁 1. [Colab] 써보면 디저트 같은 즐거움

“클릭 두 번이면 AI랑 대화할 수 있다니!”

💻 실행 단계

  1. https://colab.research.google.com 접속
  2. 새 노트북 열기
  3. 다음 코드 셀 실행:
!pip install -q langchain-google-genai google-generativeai

API 키 설정:

import os
os.environ["GOOGLE_API_KEY"] = "여기에_당신의_API_키를_입력하세요"

사용 가능한 모델 확인

import google.generativeai as genai

from _settings.config import GEMINI_KEY

# API 키 설정
genai.configure(api_key=GEMINI_KEY)

# 모델 목록 확인 (이걸로 어떤 모델이 가능한지 확인 가능)
for model in genai.list_models():
    print(model.name)

위 코드 실행 시 다음과 같은 결과를 얻었습니다.

이는 사용자 마다 요금제에 따라 다를 수 있습니다.

models/embedding-gecko-001
models/gemini-1.0-pro-vision-latest
models/gemini-pro-vision
models/gemini-1.5-pro-latest
models/gemini-1.5-pro-002
models/gemini-1.5-pro
models/gemini-1.5-flash-latest
models/gemini-1.5-flash
models/gemini-1.5-flash-002
models/gemini-1.5-flash-8b
models/gemini-1.5-flash-8b-001
models/gemini-1.5-flash-8b-latest
models/gemini-2.5-pro-preview-03-25
models/gemini-2.5-flash-preview-04-17
models/gemini-2.5-flash-preview-05-20
models/gemini-2.5-flash
models/gemini-2.5-flash-preview-04-17-thinking
models/gemini-2.5-flash-lite-preview-06-17
models/gemini-2.5-pro-preview-05-06
models/gemini-2.5-pro-preview-06-05
models/gemini-2.5-pro
models/gemini-2.0-flash-exp
models/gemini-2.0-flash
models/gemini-2.0-flash-001
models/gemini-2.0-flash-exp-image-generation
models/gemini-2.0-flash-lite-001
models/gemini-2.0-flash-lite
models/gemini-2.0-flash-preview-image-generation
models/gemini-2.0-flash-lite-preview-02-05
models/gemini-2.0-flash-lite-preview
models/gemini-2.0-pro-exp
models/gemini-2.0-pro-exp-02-05
models/gemini-exp-1206
models/gemini-2.0-flash-thinking-exp-01-21
models/gemini-2.0-flash-thinking-exp
models/gemini-2.0-flash-thinking-exp-1219
models/gemini-2.5-flash-preview-tts
models/gemini-2.5-pro-preview-tts
models/learnlm-2.0-flash-experimental
models/gemma-3-1b-it
models/gemma-3-4b-it
models/gemma-3-12b-it
models/gemma-3-27b-it
models/gemma-3n-e4b-it
models/gemma-3n-e2b-it
models/embedding-001
models/text-embedding-004
models/gemini-embedding-exp-03-07
models/gemini-embedding-exp
models/aqa
models/imagen-3.0-generate-002
models/imagen-4.0-generate-preview-06-06
models/imagen-4.0-ultra-generate-preview-06-06
models/veo-2.0-generate-001
models/gemini-2.5-flash-preview-native-audio-dialog
models/gemini-2.5-flash-exp-native-audio-thinking-dialog
models/gemini-2.0-flash-live-001
models/gemini-live-2.5-flash-preview

질문하기:

from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.messages import HumanMessage

chat = ChatGoogleGenerativeAI(model="models/gemini-2.5-pro", google_api_key=os.environ["GOOGLE_API_KEY"])
response = chat.invoke([HumanMessage(content="대한민국의 수도는 어디인가요?")])
print(response.content)
 

저는 실행해보니 다음과 같은 에러가 발생하네요.

ResourceExhausted: 429 You exceeded your current quota, please check your plan and billing details.

즉, 무료 요금제에서 할당된 API 호출량(쿼터)를 다 써버려서 발생한 에러입니다.


왜 이런 일이 생길까요?

  • Gemini API 무료 티어는 하루, 분 단위로 호출량과 토큰 사용량 제한이 있습니다.
  • 이 제한을 초과하면 429 에러가 나오며, 일정 시간 후 다시 시도해야 합니다.
  • 구글 클라우드 콘솔에서 할당량(Quota)을 확인하거나, 요금제 업그레이드가 필요합니다.

해결 방법

1️⃣ 쿼터 초기화까지 기다리기

  • 일반적으로 하루 기준이므로, 하루가 지나면 다시 사용 가능해집니다.

2️⃣ 쿼터 확인 및 업그레이드

3️⃣ 호출량 줄이기

  • 테스트용 짧은 문장으로 API 호출 횟수 줄이기
  • 불필요한 반복 호출 자제

4️⃣ 경량화된 flash 모델 사용하기

  • gemini-2.5-flash 같은 경량 모델은 쿼터 부담이 적고, 무료 티어 할당량이 더 넉넉할 수 있습니다.
  • 동일한 API 키로도 pro 모델 대신 flash 모델을 사용하면 쿼터 초과 문제를 완화할 수 있습니다.

다음은 flash 모델을 사용한 예입니다.

from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.messages import HumanMessage

chat = ChatGoogleGenerativeAI(model="models/gemini-2.5-flash", google_api_key=os.environ["GOOGLE_API_KEY"])
response = chat.invoke([HumanMessage(content="대한민국의 수도는 어디인가요?")])
print(response.content)

 

결과는 다음과 같습니다.

대한민국의 수도는 **서울**입니다.

참고 링크


요약

에러 코드 의미 대처법
429 할당량(쿼터) 초과 기다리거나 업그레이드 필요
 

 


🧱 2. [PC + Conda] 본격 연구소 느낌!

“이건 내 실험실이다!”

🧪 Conda 기반 환경 구축 및 실행


✅ Step 1. Conda 가상환경 만들기

conda create -n gemini_env python=3.10 -y conda activate gemini_env
 

✅ Step 2. 필요한 패키지 설치

pip install langchain-google-genai google-generativeai
 

✅ Step 3. API 키 설정 및 실행 코드 작성

from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.messages import HumanMessage

chat = ChatGoogleGenerativeAI(model="models/gemini-2.5-flash", google_api_key=os.environ["GOOGLE_API_KEY"])
response = chat.invoke([HumanMessage(content="대한민국의 수도는 어디인가요?")])
print(response.content)

위 코드를 gemini_test.py로 저장한 후, 터미널에서 실행하면 됩니다:

python gemini_test.py
 

🎭 Colab vs Conda 재미 비교

항목 🍰 Google Colab 🔬 Conda 가상환경 (PC)
설치 없음 (브라우저만 필요) Anaconda 설치 필수
가상환경 자동 관리 수동 생성 (conda create)
시작 속도 매우 빠름 다소 설정 시간 필요
리소스 제한 Google 제한 (시간, 메모리) 있음 내 PC 성능만큼 무제한
사용 장소 인터넷 연결된 어디서나 가능 해당 PC에서만 가능
유지보수 코드 유지 및 파일 관리 어려움 장기 프로젝트에 유리 (버전 고정 등)
감성 한 줄 평 “앗 귀여워! 바로 실행되네?” “으음… 깔끔하게 환경 관리하자”
 

✅ 결론

  • 처음 써보거나 빠르게 테스트하고 싶을 때 👉 Colab
  • 꾸준히 개발하고 버전 관리하며 프로젝트화하고 싶을 때 👉 Conda
Posted by 제이브레인
,

왜 미국 주식 자동매매가 좋은가요?

🎯 1. 시장이 24시간 돌아가진 않지만, 야간 거래 가능

미국 주식 매매 장점은 야간 거래가 가능하다는 점입니다.

저처럼 회사에 다니면서 야간에 시스템 트레이딩을 공부하는 사람은 야간에는 테스트가 힘듭니다.

NXT 가 가능한 시간이 있기는 하지만 퇴근 하고 저녁 식사를 하고 나면 대부분 해당 시간 이후 입니다.

정말 하루 휴가를 내지 않으면 테스트가 불가능합니다. TOT

오~~ 그런데 미국 주식은 야간에도 가능합니다. 

구분 국내(KRX) 국내 NXT 미국 주식 (서머타임 적용) 미국 주식 (서머타임 미적용)
프리마켓 08:30~08:40 (종가매매) 08:00~08:50 (프리마켓) 17:00~22:30 18:00~23:30
정규장 09:00~15:30 09:00~15:20 (메인마켓) 22:30~05:00 (다음날) 23:30~06:00 (다음날)
애프터마켓 15:40~16:00 (종가매매) 15:30~20:00 (애프터마켓) 05:00~07:00 (다음날, 1차)
07:00~09:00 (2차)
06:00~07:00 (다음날, 1차)
07:00~09:00 (2차)
단일가매매 16:00~18:00 없음 없음 없음
총 거래 가능 시간 약 6.5시간 12시간 최대 16시간(프리+정규+애프터) 최대 16시간(프리+정규+애프터)
 

✔️ 자동매매 시스템을 퇴근 후에도 작동하게 만들면 "자고 있는 동안 돈이 일한다"는 진짜 자동 수익 모델 가능!


🌊 2. 변동성이 크고 기회가 많다

테슬라, 엔비디아 같은 미국 종목은 하루에 5~10%도 쉽게 움직입니다.
즉, 간단한 자동전략으로도 먹을 게 많아요.

✔️ "코스피야 눈치게임, 나스닥은 롤러코스터!"
변동성이 있어야 자동 전략도 수익을 낼 확률이 높습니다.


💵 3. 환율 상승도 추가 수익!

주가도 오르고 환율도 오르면?
👉 두 배로 이익! 반대로 손해도 두 배니까 리스크 관리 중요.


🤖 Step-by-Step 코드 설명 (미국 주식 자동매매 시스템)


🧱 Step 1: 프로그램 시작 + 기본 설정

self.kiwoom = Kiwoom()
self.kiwoom.CommConnect(block=True)
self.account = self.kiwoom.GetLoginInfo("ACCNO")[0]

키움 API에 연결하고 로그인 계좌를 불러옵니다. 미국 주식은 해외주식 거래 가능한 계좌여야 해요.


📊 Step 2: 엑셀에서 종목, 매수가, 매도가 불러오기

def read_excel(self):
    return pd.read_excel("stocks.xlsx")
excel
종목명 | 매수가 | 매도가  
------|--------|--------  
AAPL  | 180    | 200  
TSLA  | 250    | 300

📂 엑셀 파일로 전략 관리 가능 = 비전문가도 쉽게 전략 수정 가능!


🔍 Step 3: 현재가 조회 (미국 주식용)

def get_current_price(self, code):
    data = self.kiwoom.block_request("opt40001", 종목코드=code, output="해외주식기본정보", next=0)
    price = data.get("현재가", "0")
    return int(str(price).replace(",", "").split('.')[0])

미국 주식은 opt40001이라는 TR을 써야 시세 조회가 됩니다.
일반 국내주식 TR(opt10001)은 사용할 수 없어요.


🤝 Step 4: 조건 체크 → 자동 매수/매도

if current <= buy_price and name not in self.bought_set:
    self.kiwoom.SendOrder("미국주식매수", "1001", self.account, 1, code, qty, 0, "03", "")
elif current >= sell_price and name not in self.sold_set:
    self.kiwoom.SendOrder("미국주식매도", "1002", self.account, 2, code, qty, 0, "03", "")
 

📥 매수가 이하면 매수, 📤 매도가 이상이면 매도
매수/매도는 **시장가("03")**로 실행해서 빠르게 체결시킵니다.
self.bought_set은 중복 주문 방지용입니다.


📦 Step 5: 현재 보유 종목 업데이트

self.kiwoom.block_request("opw00018", 계좌번호=..., 비밀번호=..., ...)

이 부분은 아직 국내 주식 기준입니다.
미국 주식은 opw00005나 opw00004와 같은 별도 해외계좌 평가잔고 조회 TR을 사용해야 할 수도 있어요.
추후 확장 포인트!

 

✅ 전체 코드 (미국 주식용)

import sys
import time
import pandas as pd
from datetime import datetime
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QTimer
from pykiwoom.kiwoom import Kiwoom

EXCEL_PATH = "stocks.xlsx"
LOG_FILE = "trade_log.txt"
INTERVAL_MS = 30000  # 30초 주기

class TradingBot(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("📈 미국주식 자동 매매 시스템")
        self.setGeometry(300, 100, 900, 700)

        self.kiwoom = Kiwoom()
        self.kiwoom.CommConnect(block=True)
        self.account = self.kiwoom.GetLoginInfo("ACCNO")[0]
        self.account_pw = ""

        self.bought_set = set()
        self.sold_set = set()

        self.init_ui()
        self.get_password()
        self.update_holdings()
        self.log("미국주식 자동매매 시작 ✅")

        self.timer = QTimer()
        self.timer.timeout.connect(self.run_trading)
        self.timer.start(INTERVAL_MS)

    def init_ui(self):
        self.text_log = QTextEdit()
        self.text_log.setReadOnly(True)

        self.btn_manual = QPushButton("🔁 수동 실행")
        self.btn_manual.clicked.connect(self.run_trading)

        self.status_label = QLabel("⏱️ 대기 중...")

        self.table = QTableWidget()
        self.table.setColumnCount(4)
        self.table.setHorizontalHeaderLabels(["종목명", "수량", "매입가", "현재가"])
        self.table.horizontalHeader().setStretchLastSection(True)

        layout = QVBoxLayout()
        layout.addWidget(self.text_log)
        layout.addWidget(self.status_label)
        layout.addWidget(self.btn_manual)
        layout.addWidget(QLabel("\n📦 현재 보유 종목"))
        layout.addWidget(self.table)

        central = QWidget()
        central.setLayout(layout)
        self.setCentralWidget(central)

    def get_password(self):
        pw, ok = QInputDialog.getText(self, "계좌 비밀번호 입력", "계좌 비밀번호를 입력하세요:", QLineEdit.Password)
        if ok and pw:
            self.account_pw = pw
            self.log("🔐 계좌 비밀번호가 등록되었습니다.")
        else:
            QMessageBox.critical(self, "오류", "비밀번호가 입력되지 않았습니다. 프로그램을 종료합니다.")
            sys.exit(1)

    def log(self, message):
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        log_msg = f"[{now}] {message}"
        self.text_log.append(log_msg)
        with open(LOG_FILE, "a", encoding="utf-8") as f:
            f.write(log_msg + "\n")

    def read_excel(self):
        return pd.read_excel(EXCEL_PATH)

    def name_to_code(self, name):
        # 미국 주식은 'A' 접두사를 붙이는 경우가 많습니다
        return f"A{name.strip().upper()}"

    def get_current_price(self, code):
        try:
            data = self.kiwoom.block_request(
                "opt40001",
                종목코드=code,
                output="해외주식기본정보",
                next=0
            )
            price = data.get("현재가", "0")
            return int(str(price).replace(",", "").split('.')[0])
        except Exception as e:
            self.log(f"[현재가 조회 실패] {code} : {e}")
            return 0

    def run_trading(self):
        self.status_label.setText("🔍 주가 확인 중...")
        df = self.read_excel()

        for _, row in df.iterrows():
            name = row['종목명']
            buy_price = int(row['매수가'])
            sell_price = int(row['매도가'])
            code = self.name_to_code(name)

            current = self.get_current_price(code)
            self.log(f"{name} 현재가: {current} / 매수: {buy_price} / 매도: {sell_price}")

            qty = 1  # 테스트용 수량

            if current <= buy_price and name not in self.bought_set:
                self.log(f"📥 매수 주문: {name} @ {current}")
                self.kiwoom.SendOrder("미국주식매수", "1001", self.account, 1, code, qty, 0, "03", "")
                self.bought_set.add(name)
                self.sold_set.discard(name)

            elif current >= sell_price and name not in self.sold_set:
                self.log(f"📤 매도 주문: {name} @ {current}")
                self.kiwoom.SendOrder("미국주식매도", "1002", self.account, 2, code, qty, 0, "03", "")
                self.sold_set.add(name)
                self.bought_set.discard(name)

        self.update_holdings()
        self.status_label.setText("⏱️ 대기 중...")

    def update_holdings(self):
        try:
            data = self.kiwoom.block_request("opw00018",
                                             계좌번호=self.account,
                                             비밀번호=self.account_pw,
                                             비밀번호입력매체구분="00",
                                             조회구분=2,
                                             output="계좌평가잔고개별합산",
                                             next=0)

            if not isinstance(data, dict) or '종목번호' not in data:
                self.log("[경고] 보유 종목 정보가 없습니다.")
                self.table.setRowCount(0)
                return

            codes = data['종목번호']
            names = data['종목명']
            qtys = data['보유수량']
            prices = data['매입가']

            self.table.setRowCount(len(codes))
            for i in range(len(codes)):
                name = names[i].strip()
                qty = int(qtys[i]) if str(qtys[i]).strip().isdigit() else 0
                buy_price = int(prices[i]) if str(prices[i]).strip().isdigit() else 0
                cur_price = self.get_current_price(codes[i].strip())

                self.table.setItem(i, 0, QTableWidgetItem(name))
                self.table.setItem(i, 1, QTableWidgetItem(str(qty)))
                self.table.setItem(i, 2, QTableWidgetItem(str(buy_price)))
                self.table.setItem(i, 3, QTableWidgetItem(str(cur_price)))
        except Exception as e:
            self.log(f"[보유 종목 조회 오류] {str(e)}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    bot = TradingBot()
    bot.show()
    sys.exit(app.exec_())

🔚 마무리 - 미국 주식 자동매매, 지금 시작할 이유

국내 주식미국 주식
낮에만 거래 퇴근 후 거래 가능
하루 수익 적음 하루 5~10% 기회
코스피 지수에 민감 다양한 종목 개별성장
환율효과 없음 환차익도 가능

 

Posted by 제이브레인
,