따라하기 — chat() 함수 만들기
한 줄 요약
code/ch02_chat.py 를 만듭니다. 앞으로 계속 쓸 chat() 함수를 정의하고, 시스템 지시만 바꿔 같은 질문에 전혀 다른 답이 나오는 것을 확인합니다.
1. 파일 만들기
code 폴더에 ch02_chat.py 를 만들고 아래를 전부 붙여 넣습니다.
새 파일을 만드세요 —
agentic_ai/code/ch02_chat.py
VS Code 왼쪽 탐색기에서code폴더를 오른쪽 클릭 → New File → 파일명ch02_chat.py입력 → 아래를 전부 복사해 붙여 넣고 저장(Ctrl+S/Cmd+S).
# -*- coding: utf-8 -*-
"""2장 — LLM API 기초: 시스템 지시와 멀티턴
실행: python code/ch02_chat.py
배우는 것
1) 시스템 지시(system_instruction)로 말투와 역할을 고정한다
2) LLM 은 이전 대화를 기억하지 못한다는 것을 눈으로 확인한다
3) 대화를 우리가 직접 쌓아 보내면 '기억하는 것처럼' 만들 수 있다
"""
import sys
import pathlib
sys.path.append(str(pathlib.Path(__file__).resolve().parent))
from common import get_genai_client, GEMINI_MODEL
from google.genai import types # 설정·대화 객체를 만드는 도구 모음
client = get_genai_client()
def chat(system: str, user: str, temperature: float = 0.3) -> str:
"""시스템 지시와 사용자 질문을 받아 답변을 돌려준다.
system : 모델에게 미리 주는 역할·규칙 (대화 전체에 계속 적용된다)
user : 이번에 던지는 질문
temperature : 0에 가까울수록 매번 같은 답, 높을수록 다양한 답
"""
resp = client.models.generate_content(
model=GEMINI_MODEL,
contents=user,
config=types.GenerateContentConfig(
system_instruction=system,
temperature=temperature,
),
)
return (resp.text or "").strip()
def chat_multi(system: str, history: list) -> str:
"""지금까지의 대화 전체(history)를 통째로 보내 답변을 받는다.
[핵심] LLM 은 이전 호출을 기억하지 못한다.
그래서 '기억'을 만들려면 우리가 지난 대화를 매번 다시 실어 보내야 한다.
"""
resp = client.models.generate_content(
model=GEMINI_MODEL,
contents=history, # 문자열 하나가 아니라 '대화 목록'
config=types.GenerateContentConfig(
system_instruction=system, temperature=0),
)
return (resp.text or "").strip()
def say(role: str, text: str) -> types.Content:
"""대화 한 줄을 만든다. role 은 'user'(고객) 또는 'model'(상담원)."""
return types.Content(role=role, parts=[types.Part(text=text)])
SYSTEM = (
"너는 승승장구몰의 CS 상담원이다. "
"고객에게 존댓말로, 두 문장 이내로 간결하게 답한다. "
"확실하지 않은 정보는 '확인 후 안내드리겠습니다'라고 답한다. "
"생각 과정은 쓰지 않고 최종 답변만 말한다."
)
if __name__ == "__main__":
# ── 1. 시스템 지시가 답을 어떻게 바꾸는가 ──────────────────
print("=" * 62)
print("[1] 같은 질문, 다른 역할")
q = "환불하면 언제 입금되나요?"
print(f" Q: {q}\n")
print(" [상담원 역할]")
print(" ", chat(SYSTEM, q))
print("\n [무뚝뚝한 엔지니어 역할]")
print(" ", chat("너는 무뚝뚝한 엔지니어다. 한 문장으로만 답한다.", q))
print("\n → 질문은 같은데 말투와 길이가 완전히 달라집니다.")
# ── 2. LLM 은 기억하지 못한다 ──────────────────────────────
print("\n" + "=" * 62)
print("[2] 기억하지 못하는 상담원")
print(" 고객: 제 이름은 김민준입니다.")
print(" 상담원:", chat(SYSTEM, "제 이름은 김민준입니다."))
print(" 고객: 제 이름이 뭐라고 했죠?")
print(" 상담원:", chat(SYSTEM, "제 이름이 뭐라고 했죠?"))
print("\n → 방금 들은 이름을 잊었습니다. 두 번의 호출이 서로 남남이기 때문입니다.")
# ── 3. 대화를 쌓아 보내면 기억한다 ─────────────────────────
print("\n" + "=" * 62)
print("[3] 대화를 쌓아 보내는 상담원")
history = [] # 여기에 대화를 차곡차곡 쌓는다
first_turn = "제 이름은 김민준입니다."
history.append(say("user", first_turn))
answer = chat_multi(SYSTEM, history)
history.append(say("model", answer)) # 상담원의 답도 기록에 남긴다
print(f" 고객 : {first_turn}")
print(f" 상담원: {answer}")
second_turn = "제 이름이 뭐라고 했죠?"
history.append(say("user", second_turn))
answer = chat_multi(SYSTEM, history)
history.append(say("model", answer))
print(f" 고객 : {second_turn}")
print(f" 상담원: {answer}")
print(f"\n → 이번에는 기억합니다. 쌓인 대화 {len(history)}개를 통째로 보냈기 때문입니다.")
print(" → 대화가 길어질수록 매번 보내는 양이 늘어납니다(= 비용 증가).")
print(" 7장에서 이 일을 라이브러리에게 맡깁니다.")
2. 실행
터미널에서 실행 — 프로젝트 폴더
agentic_ai에서,(agentic)표시를 확인한 뒤.
python code/ch02_chat.py
30초~1분 걸립니다. LLM 호출이 여섯 번 순서대로 일어납니다. 화면이 멈춘 것처럼 보여도 기다리세요.
3. [1] 부분의 예상 출력
이 절에서는 [1] 만 봅니다. [2] 와 [3] 은 다음 두 절에서 다룹니다.
==============================================================
[1] 같은 질문, 다른 역할
Q: 환불하면 언제 입금되나요?
[상담원 역할]
환불은 접수일로부터 3영업일 이내에 처리되며, 카드사 및 은행 사정에 따라
입금 시기는 달라질 수 있습니다.
[무뚝뚝한 엔지니어 역할]
영업일 기준 3~5일 소요됩니다.
→ 질문은 같은데 말투와 길이가 완전히 달라집니다.
표현은 다를 수 있습니다. 상담원 쪽이 길고 공손하며, 엔지니어 쪽이 짧고 건조하면 성공입니다.
4. 코드 뜯어보기
chat() — 이번 장의 주력 함수
아래는 code/ch02_chat.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
def chat(system: str, user: str, temperature: float = 0.3) -> str:
resp = client.models.generate_content(
model=GEMINI_MODEL,
contents=user,
config=types.GenerateContentConfig(
system_instruction=system,
temperature=temperature,
),
)
return (resp.text or "").strip()
1장의 호출에 config 만 추가된 것입니다. 그게 전부입니다.
-> str 과 system: str 같은 표기는 타입힌트(type hint) 입니다. "이 자리에는 문자열이 들어온다"는 메모로, 파이썬이 강제하지는 않습니다. 그런데 3장에서 이 메모가 갑자기 아주 중요해집니다 — LLM이 도구 사용법을 읽는 설명서가 되기 때문입니다. 지금은 "친절한 주석" 정도로 보면 됩니다.
.strip() 은 답변 앞뒤의 공백·줄바꿈을 잘라 냅니다. 출력이 깔끔해집니다.
(resp.text or "") 의 or "" 는 1장 9절에서 본 그대로입니다. 여기서는 뒤에 .strip() 이 붙어 있어 더 필요합니다.
say() — 대화 한 줄 만들기
아래는 code/ch02_chat.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
def say(role: str, text: str) -> types.Content:
return types.Content(role=role, parts=[types.Part(text=text)])
[3] 에서 쓸 도우미입니다. 누가(role) 무슨 말을(text) 했는지 를 한 덩어리로 묶습니다.
| role | 누구 |
|---|---|
"user" |
사람(고객) |
"model" |
LLM(상담원) |
Content 안에 parts 라는 목록이 또 들어 있는 게 번거로워 보이지만, 이유가 있습니다. 한 번의 발언에 글·이미지·도구 호출 결과가 섞여 들어갈 수 있기 때문입니다. 4장에서 Part.from_function_response(...) 로 도구 실행 결과를 넣게 되면 이 구조가 왜 이런지 납득이 됩니다.
5. 바꿔 보기
[1] 부분의 두 번째 호출을 이것저것 바꿔 실행해 보세요.
직접 고쳐 보기 —
agentic_ai/code/ch02_chat.py를 열어 아래처럼 바꾸거나 덧붙인 뒤 다시 실행하세요.
print(" ", chat("너는 초등학생이다. 쉬운 말로 답한다.", q))
print(" ", chat("너는 변호사다. 법률 용어를 써서 답한다.", q))
print(" ", chat("너는 시인이다. 세 줄짜리 시로 답한다.", q))
같은 질문에 얼마나 다른 답이 나오는지 확인해 보세요. 시스템 지시가 답을 얼마나 크게 좌우하는지 체감하는 것이 이 실습의 목적입니다.
temperature 도 바꿔 보세요.
직접 고쳐 보기 —
agentic_ai/code/ch02_chat.py를 열어 아래처럼 바꾸거나 덧붙인 뒤 다시 실행하세요.
print(chat(SYSTEM, q, temperature=0)) # 세 번 돌려도 거의 같은 답
print(chat(SYSTEM, q, temperature=1.5)) # 돌릴 때마다 다른 답
앞 절에서 적어 둔 예측과 맞았나요?
핵심 정리
chat(system, user, temperature)— 이 과정에서 계속 쓸 기본 호출을 함수로 감쌌습니다.- 1장 호출에
config만 추가한 것이 전부입니다. - 같은 질문이라도 시스템 지시에 따라 답이 완전히 달라집니다.
say(role, text)는 대화 한 줄을 만드는 도우미입니다.role은"user"또는"model".- 타입힌트(
system: str)는 지금은 메모지만, 3장에서 LLM이 읽는 설명서가 됩니다. - 다음 절에서
[2]— LLM이 방금 한 말도 잊는다는 것을 확인합니다.