한마디 요약

타입 힌트(type hint) 는 “이 변수/파라미터/반환값이 어떤 모양의 데이터를 담을지”를 코드에 박아두는 선언이다. Python 은 이 선언을 실행 중에 강제하지 않는다(런타임 무검증). 대신 IDE, mypy, Pydantic, LangChain 같은 도구가 이걸 읽어서 자동완성, 정적 검사, 스키마 생성, Tool 등록을 수행한다. AI Agent 코드에서는 선택이 아니라 거의 필수.


왜 쓰는가 (한 번만 짚고 간다)

관점힌트 없음힌트 있음
IDE 자동완성변수. 찍어도 아무것도 안 뜸멤버/메서드 자동 제안
정적 검사 (mypy, pyright)런타임 에러로만 확인커밋 전 타입 오류 검출
문서docstring 없으면 본문 읽어야 함시그니처만 봐도 계약 파악
Pydantic 모델선언 불가필드 타입으로 직결, 자동 검증
LangChain @tool에러 (타입 힌트 필수)JSON Schema 자동 추출
FastAPI 라우트불가Query/Body/Response 자동 파싱 + Swagger
LangGraph State타입 추론 실패TypedDict 로 노드 함수 안전성 확보

핵심 트레이드오프: Python 은 타입 힌트를 런타임에 강제하지 않는다. x: int = "hello" 도 실행은 된다. 힌트는 “도구가 읽는 메타데이터”일 뿐. 실제 검증은 Pydantic / mypy / pyright 같은 외부 도구가 담당.


기본 문법 3가지 위치

# 1. 파라미터 (parameter annotation)
def greet(name: str, age: int = 0) -> None:
    #         ↑         ↑           ↑
    #      파라미터  기본값 있어도 힌트 먼저
    print(f"{name}, {age}")
 
# 2. 반환값 (return annotation, 화살표)
def get_count() -> int:
    return 42
 
# 3. 변수 (variable annotation, PEP 526)
retry_count: int = 0
user_map: dict[str, "User"] = {}

포함 관계

함수 타입 힌트 (function type hints)
├─ 파라미터 힌트:  def f(x: int, y: list[str])   ← 이 문서 주력
├─ 반환 타입 힌트: -> ReturnType                 ← [[반환 타입 힌트(화살표➡️)]]
└─ 가변 인자 힌트: def f(*args: int, **kwargs: Any)  ← [[함수(별args, 별별kwargs)]]

typing 모듈 주요 타입 지도

Python 3.9 부터는 내장 제네릭(list[int], dict[str, int])이 가능해졌지만, 특수 타입은 여전히 typing 에서 가져와야 한다.

from typing import (
    Optional, Union, Any, Literal, Final,
    Callable, Awaitable, Coroutine,
    TypedDict, NotRequired,
    Protocol, TypeVar, Generic, NewType,
    Annotated, ClassVar,
)

자주 쓰는 것들 요약표

타입의미AI Agent 사용 예
Optional[X]X 또는 None (== Union[X, None])도구 검색 결과 없을 때 Optional[Tool]
Union[X, Y] / X | Y (3.10+)여러 타입 중 하나LLM 파싱 결과 dict | str
Any타입 검사 끄기외부 JSON, LLM 원본 응답
Literal["a", "b"]특정 값만 허용의도 분류 Literal["search","answer"]
Callable[[Args], Ret]함수 타입콜백, 데코레이터 인자
Awaitable[X]await 가능한 것async LLM 호출 반환
TypedDictdict 인데 키/값 타입 고정LangGraph State 스키마
Annotated[T, meta]타입 + 메타데이터LangGraph Annotated[list, add_messages]
Protocol덕타이핑용 구조적 타입”이 메서드만 있으면 OK”
TypeVar + Generic제네릭 함수/클래스범용 Agent 래퍼

실전 예시 1: Langfuse CallbackHandler 시그니처 해석

from typing import Optional, List, Dict, Any
from langfuse.callback import CallbackHandler
 
def get_callback_handler(
    trace_name: str,
    tags: Optional[List[str]] = None,
    session_id: Optional[str] = None,
    user_id: Optional[str] = None,
    metadata: Optional[Dict[str, Any]] = None,
) -> Optional[CallbackHandler]:
    ...

한 줄씩 뜯어보기

tags: Optional[List[str]] = None

조각의미
List[str]문자열들이 담긴 리스트. 예: ["urgent", "test"]
Optional[List[str]]리스트일 수도, None 일 수도 있음
= None호출 시 안 넘기면 기본값으로 None 들어감

metadata: Optional[Dict[str, Any]] = None

  • Dict[str, Any] : 키는 문자열, 값은 아무 타입이나. LLM trace 같은 자유 형식 메타데이터에 자주 쓴다.

-> Optional[CallbackHandler]

  • 반환값은 CallbackHandler 객체 또는 None. 설정이 없어 핸들러를 만들 수 없으면 None 을 돌려주는 흔한 “팩토리 실패 패턴”.

실전 예시 2: LangChain @tool 과 타입 힌트

from typing import Literal
from langchain_core.tools import tool
from pydantic import BaseModel, Field
 
class WeatherQuery(BaseModel):
    city: str = Field(description="조회할 도시명")
    unit: Literal["celsius", "fahrenheit"] = "celsius"
 
@tool(args_schema=WeatherQuery)
def get_weather(city: str, unit: Literal["celsius", "fahrenheit"] = "celsius") -> dict:
    """도시의 현재 날씨 정보를 반환합니다."""
    return {"city": city, "temp": 22, "unit": unit}
 
# LangChain 이 내부에서 한 일:
# 1. 파라미터 힌트 → JSON Schema "properties"
# 2. Literal → enum 으로 변환
# 3. 기본값 → required 여부 결정
# 4. docstring → tool description
# 5. LLM 은 이 스키마를 보고 올바른 인자로 호출

타입 힌트가 도구 등록의 필수 재료가 된다. 힌트 누락 시 ValueError 발생.


실전 예시 3: LangGraph State 와 TypedDict

from typing import TypedDict, Annotated, NotRequired
from operator import add
from langchain_core.messages import BaseMessage
 
class AgentState(TypedDict):
    # Annotated[T, reducer] 가 LangGraph 의 시그니처
    messages: Annotated[list[BaseMessage], add]  # 노드별 반환을 누적
    step: int                                    # 기본은 덮어쓰기
    tool_name: NotRequired[str]                  # 있을 수도, 없을 수도
 
def planner_node(state: AgentState) -> AgentState:
    # state["messages"] 에 자동완성 동작, step 이 int 임을 IDE 가 앎
    return {"step": state["step"] + 1, "tool_name": "search"}

Annotated 의 두 번째 인자(add)는 LangGraph 런타임이 reducer 로 해석하는 메타데이터. 타입 힌트에 “실행 동작”을 끼워 넣는 예.


실전 예시 4: Pydantic 과의 관계

Pydantic 은 “타입 힌트를 런타임에도 강제하는” 라이브러리. AI Agent 에서 LLM 출력 파싱, API 요청/응답 검증에 필수.

from pydantic import BaseModel, Field
from typing import Optional
 
class SearchResult(BaseModel):
    title: str
    url: str
    score: float = Field(ge=0.0, le=1.0)        # 0 <= x <= 1 검증
    snippet: Optional[str] = None
 
# 타입 힌트 = 검증 규칙
result = SearchResult(title="AI", url="http://...", score=1.5)
# → ValidationError: score must be <= 1.0

typing.Optional 은 단순 표시이지만, Pydantic BaseModel 안에 들어가면 실제 검증 로직이 붙는다.


함정: Optional[X] 는 “없어도 됨”이 아니다

def f(x: Optional[int]):  # x 는 반드시 전달해야 함. 단, None 도 허용.
    ...
 
f()       # ❌ TypeError: 인자 누락
f(None)   # ✅ 허용
f(3)      # ✅ 허용
 
def g(x: Optional[int] = None):  # 이렇게 해야 "생략 가능"
    ...
g()       # ✅

Optional[X] = Union[X, None] 일 뿐, “optional parameter(선택 파라미터)” 와는 별개 개념. 선택 파라미터는 = 기본값 이 따로 있어야 한다.


함정: list vs List vs list[int] 버전별 차이

Python 버전권장 문법
3.8 이하from typing import ListList[int]
3.9+내장 list[int] 직접 사용
3.10+X | Y (Union 대체), X | None (Optional 대체)

실무 프로젝트는 python_requires 가 3.9/3.10 중 어디까지 지원하는지 먼저 본 뒤 문법 결정. Agent 프레임워크(LangChain, LangGraph) 는 보통 3.9+ 를 요구.


직접 확인: 런타임에 힌트 꺼내 보기

from typing import get_type_hints
 
def handler(msg: str, count: int = 1) -> bool:
    return True
 
print(get_type_hints(handler))
# {'msg': <class 'str'>, 'count': <class 'int'>, 'return': <class 'bool'>}
 
print(handler.__annotations__)
# 위와 동일. LangChain/Pydantic 이 실제로 이걸 읽어감

이 딕셔너리가 Agent 프레임워크들이 Tool 스키마를 만들 때 실제로 참조하는 원천.


관련 문서


한마디 재요약

타입 힌트는 **Python 에게 하는 선언이 아니라 “도구들에게 하는 약속”**이다. IDE, mypy, Pydantic, LangChain, FastAPI 가 이 약속을 읽고 각자의 방식으로 안전장치를 걸어준다. Agent 코드는 타입 힌트 없이는 거의 돌아가지 않는다.