📚 Agentic AI - 기업용 자율 에이전트 개발 2장 · LLM API 기초 Agentic AI란

따라하기 — 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 만 추가된 것입니다. 그게 전부입니다.

-> strsystem: 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이 방금 한 말도 잊는다는 것을 확인합니다.
← 이전 절03. temperature 일관성과 다양성 사이다음 절 →05. LLM은 방금 한 말도 잊는다