언어 모델 (Language Models)

언어 모델 (Language Models)

어떤 DSPy 코드든 첫 발걸음은 언어 모델(LM)을 설정하는 것이에요. 예를 들어 OpenAI의 GPT-4o-mini를 기본 LM으로 다음과 같이 설정할 수 있습니다.

# Authenticate via `OPENAI_API_KEY` env: import os; os.environ['OPENAI_API_KEY'] = 'here'
lm = dspy.LM('openai/gpt-4o-mini')
dspy.configure(lm=lm)

!!! info "여러 가지 다른 LM들"

=== "OpenAI"
    `OPENAI_API_KEY` 환경변수를 설정하거나 아래처럼 `api_key`를 넘겨 인증할 수 있어요.

    ```python linenums="1"
    import dspy
    lm = dspy.LM('openai/gpt-4o-mini', api_key='YOUR_OPENAI_API_KEY')
    dspy.configure(lm=lm)
    ```

=== "Gemini (AI Studio)"
    `GEMINI_API_KEY` 환경변수를 설정하거나 아래처럼 `api_key`를 넘겨 인증할 수 있어요.

    ```python linenums="1"
    import dspy
    lm = dspy.LM('gemini/gemini-2.5-pro-preview-03-25', api_key='GEMINI_API_KEY')
    dspy.configure(lm=lm)
    ```

=== "Anthropic"
    `ANTHROPIC_API_KEY` 환경변수를 설정하거나 아래처럼 `api_key`를 넘겨 인증할 수 있어요.

    ```python linenums="1"
    import dspy
    lm = dspy.LM('anthropic/claude-sonnet-4-5-20250929', api_key='YOUR_ANTHROPIC_API_KEY')
    dspy.configure(lm=lm)
    ```

=== "Vertex AI (GCP)"
    Google Cloud의 Vertex AI는 서비스 계정 JSON 키나 Application Default Credentials로 인증해요. 코드에서 직접 자격 증명을 넘기거나 환경변수를 설정하면 됩니다.

    ```python linenums="1"
    import dspy
    import json

    # Load the service account JSON and convert to a string
    with open("service_account.json") as f:
        credentials = json.dumps(json.load(f))

    lm = dspy.LM(
        "vertex_ai/gemini-2.0-flash",
        vertex_credentials=credentials,
        vertex_project="your-gcp-project-id",
        vertex_location="us-central1",
    )
    dspy.configure(lm=lm)
    ```

    대신 환경변수를 설정하고 kwargs를 생략해도 됩니다.

    ```bash
    export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service_account.json"
    export VERTEXAI_PROJECT="your-gcp-project-id"
    export VERTEXAI_LOCATION="us-central1"
    ```

    ```python linenums="1"
    import dspy
    lm = dspy.LM("vertex_ai/gemini-2.0-flash")
    dspy.configure(lm=lm)
    ```

    !!! warning "흔한 함정"
        - `vertex_ai/` 모델 접두어를 쓰세요. `gemini/`는 아니에요. `gemini/` 접두어는 GCP 자격 증명 대신 API 키가 필요한 Gemini API로 라우팅됩니다.
        - `vertex_project`와 `vertex_location`을 쓰세요. `project`나 `location`은 아니에요. `vertex_` 접두어가 없는 파라미터는 조용히 무시되고 LiteLLM이 기본값으로 폴백해서, 요청이 의도하지 않은 리전으로 갈 수 있습니다.

=== "Databricks"
    Databricks 플랫폼에 있다면 그들의 SDK로 자동 인증됩니다. 그게 아니면 `DATABRICKS_API_KEY`와 `DATABRICKS_API_BASE` 환경변수를 설정하거나, 아래처럼 `api_key`와 `api_base`를 넘기세요.

    ```python linenums="1"
    import dspy
    lm = dspy.LM('databricks/databricks-meta-llama-3-1-70b-instruct')
    dspy.configure(lm=lm)
    ```

=== "GPU 서버의 로컬 LM"
      먼저 [SGLang](https://docs.sglang.ai/docs/get-started/install)을 설치하고 LM으로 서버를 띄우세요.

      ```bash
      > pip install "sglang[all]"
      > pip install flashinfer -i https://flashinfer.ai/whl/cu121/torch2.4/ 

      > CUDA_VISIBLE_DEVICES=0 python -m sglang.launch_server --port 7501 --model-path meta-llama/Meta-Llama-3-8B-Instruct
      ```

      그다음 DSPy 코드에서 **OpenAI 호환 엔드포인트**로 연결합니다.

      ```python linenums="1"
      lm = dspy.LM("openai/meta-llama/Meta-Llama-3-8B-Instruct",
                       api_base="http://localhost:7501/v1",  # ensure this points to your port
                       api_key="", model_type='chat')
      dspy.configure(lm=lm)
      ```

=== "노트북의 로컬 LM"
      먼저 [Ollama](https://github.com/ollama/ollama)을 설치하고 LM으로 서버를 띄우세요.

      ```bash
      > curl -fsSL https://ollama.ai/install.sh | sh
      > ollama run llama3.2:1b
      ```

      그다음 DSPy 코드에서 연결합니다.

    ```python linenums="1"
    import dspy
    lm = dspy.LM('ollama_chat/llama3.2', api_base='http://localhost:11434', api_key='')
    dspy.configure(lm=lm)
    ```

=== "기타 프로바이더"
    DSPy에서는 [LiteLLM이 지원하는 수십 개의 LLM 프로바이더](https://docs.litellm.ai/docs/providers) 중 아무거나 쓸 수 있어요. 설정할 `{PROVIDER}_API_KEY`와 생성자에 넘길 `{provider_name}/{model_name}` 작성법은 그들의 안내를 따르면 됩니다.

    몇 가지 예시:
    - `anyscale/mistralai/Mistral-7B-Instruct-v0.1`, `ANYSCALE_API_KEY` 사용
    - `together_ai/togethercomputer/llama-2-70b-chat`, `TOGETHERAI_API_KEY` 사용
    - `sagemaker/<your-endpoint-name>`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION_NAME` 사용
    - `azure/<your_deployment_name>`, 환경변수로 `AZURE_API_KEY`, `AZURE_API_BASE`, `AZURE_API_VERSION`, 그리고 선택적으로 `AZURE_AD_TOKEN`, `AZURE_API_TYPE` 사용. 환경변수 없이 외부 모델을 시작한다면:
    `lm = dspy.LM('azure/<your_deployment_name>', api_key = 'AZURE_API_KEY' , api_base = 'AZURE_API_BASE', api_version = 'AZURE_API_VERSION')`

    만약 여러분의 프로바이더가 **OpenAI 호환 엔드포인트**를 제공한다면, 전체 모델 이름 앞에 `openai/` 접두어만 붙이면 됩니다.

    ```python linenums="1"
    import dspy
    lm = dspy.LM('openai/your-model-name', api_key='PROVIDER_API_KEY', api_base='YOUR_PROVIDER_URL')
    dspy.configure(lm=lm)
    ```

에러가 나면 LiteLLM Docs를 확인해서 같은 변수명을 쓰는지/올바른 절차를 따르는지 검증하세요.

LM 직접 호출하기

위에서 설정한 lm을 직접 호출하는 건 아주 쉽습니다. 통일된 API를 얻고 자동 캐싱 같은 유틸리티의 혜택도 받아요.

lm("Say this is a test!", temperature=0.7)  # => ['This is a test!']
lm(messages=[{"role": "user", "content": "Say this is a test!"}])  # => ['This is a test!']

DSPy 모듈과 함께 LM 사용하기

관용적인 DSPy는 _모듈_을 쓰는 것입니다. 이건 다음 가이드에서 다룰게요.

# Define a module (ChainOfThought) and assign it a signature (return an answer, given a question).
qa = dspy.ChainOfThought('question -> answer')

# Run with the default LM configured with `dspy.configure` above.
response = qa(question="How many floors are in the castle David Gregory inherited?")
print(response.answer)

가능한 출력:

The castle David Gregory inherited has 7 floors.

여러 LM 사용하기

기본 LM을 dspy.configure로 전역적으로 바꾸거나, dspy.context로 코드 블록 안에서만 바꿀 수 있습니다.

!!! tip dspy.configuredspy.context는 스레드 안전(thread-safe)해요!

dspy.configure(lm=dspy.LM('openai/gpt-4o-mini'))
response = qa(question="How many floors are in the castle David Gregory inherited?")
print('GPT-4o-mini:', response.answer)

with dspy.context(lm=dspy.LM('openai/gpt-3.5-turbo')):
    response = qa(question="How many floors are in the castle David Gregory inherited?")
    print('GPT-3.5-turbo:', response.answer)

가능한 출력:

GPT-4o-mini: The number of floors in the castle David Gregory inherited cannot be determined with the information provided.
GPT-3.5-turbo: The castle David Gregory inherited has 7 floors.

LM 생성 설정하기

어떤 LM이든 초기화 시점이나 이후 각 호출에서 다음 속성들을 설정할 수 있어요.

gpt_4o_mini = dspy.LM('openai/gpt-4o-mini', temperature=0.9, max_tokens=3000, stop=None, cache=False)

기본적으로 DSPy의 LM은 캐시됩니다. 같은 호출을 반복하면 같은 출력을 얻어요. 하지만 cache=False로 캐싱을 끌 수 있습니다.

캐싱은 켜둔 채 새 요청을 강제하고 싶다면(예: 다양한 출력을 얻으려고), 호출에 **고유한 rollout_id**를 넘기고 **0이 아닌 temperature**를 설정하세요. DSPy는 캐시 항목을 찾을 때 입력과 rollout_id를 함께 해시해서, 다른 값이면 새 LM 요청을 강제하면서도 같은 입력과 rollout_id를 쓰는 이후 호출은 여전히 캐시합니다. 이 ID는 lm.history에도 기록되어 실험 중 다른 롤아웃을 추적·비교하기 쉽습니다. temperature=0을 유지한 채 rollout_id만 바꾸면 LM 출력에는 영향이 없어요.

lm("Say this is a test!", rollout_id=1, temperature=1.0)

이 LM kwargs를 DSPy 모듈에도 직접 넘길 수 있어요. 초기화 시 넘기면 매 호출의 기본값이 됩니다.

predict = dspy.Predict("question -> answer", rollout_id=1, temperature=1.0)

단일 호출에서 그것들을 덮어쓰려면, 모듈을 호출할 때 config 딕셔너리를 넘기세요.

predict = dspy.Predict("question -> answer")
predict(question="What is 1 + 52?", config={"rollout_id": 5, "temperature": 1.0})

두 경우 모두 rollout_id는 기본 LM으로 전달되고, 그 LM의 캐싱 동작에 영향을 미치며, 각 응답과 함께 저장되어 나중에 특정 롤아웃을 재생하거나 분석할 수 있습니다.

출력과 사용량 메타데이터 검사하기

모든 LM 객체는 상호작용의 history를 유지합니다. 입력, 출력, 토큰 사용량(그리고 $$ 비용), 메타데이터가 들어 있죠.

len(lm.history)  # e.g., 3 calls to the LM

lm.history[-1].keys()  # access the last call to the LM, with all metadata

출력:

dict_keys(['prompt', 'messages', 'kwargs', 'response', 'outputs', 'usage', 'cost', 'timestamp', 'uuid', 'model', 'response_model', 'model_type])

LM 에러 처리

DSPy 내장 dspy.LM은 프로바이더와 LiteLLM 실패를 구조화된 DSPy 예외로 감쌉니다. dspy.LMError를 잡아 어떤 LM 실패든 처리하거나, 더 구체적인 서브클래스를 잡아 대상 동작을 수행하세요.

try:
    answer = qa(question="...")
except dspy.ContextWindowExceededError:
    # Reduce prompt size, retrieved passages, or demos before retrying.
    raise
except dspy.LMRateLimitError as e:
    print(f"Rate limited by {e.provider}; retry after {e.retry_after} seconds")
except dspy.LMError as e:
    print(f"LM failed: code={e.code}, model={e.model}, request_id={e.request_id}")

모든 DSPy LM 에러는 안정적인 code를 노출하고 model, provider, 프로바이더 status, request_id, retry_after를 포함할 수 있습니다. LM 백엔드 경계에서 알 수 없는 예외가 발생하면 DSPy는 그것을 어댑터 파싱 실패로 취급하는 대신 dspy.LMUnexpectedError를 던집니다. 전체 계층은 Errors API 참조를 보세요.

Responses API 사용하기

기본적으로 DSPy는 LiteLLM의 Chat Completions API로 LM을 호출합니다. 이건 대부분의 표준 모델과 작업에 적합해요. 하지만 일부 고급 모델 — 예를 들어 OpenAI의 reasoning 모델(gpt-5 같은, 혹은 그 이후 모델) — 은 DSPy가 지원하는 Responses API를 통해 접근하면 품질이나 부가 기능이 개선될 수 있습니다.

언제 Responses API를 써야 하나요?

  • responses 엔드포인트를 지원하거나 요구하는 모델(OpenAI의 reasoning 모델 같은)을 다룰 때.
  • 특정 모델이 제공하는 향상된 추론, 멀티턴, 더 풍부한 출력 능력을 활용하고 싶을 때.

DSPy에서 Responses API를 켜는 법: dspy.LM 인스턴스를 만들 때 model_type="responses"만 설정하면 됩니다.

import dspy

# Configure DSPy to use the Responses API for your language model
dspy.configure(
    lm=dspy.LM(
        "openai/gpt-5-mini",
        model_type="responses",
        temperature=1.0,
        max_tokens=16000,
    ),
)

모든 모델이나 프로바이더가 Responses API를 지원하는 건 아니라는 점을 기억하세요. 자세한 내용은 LiteLLM 문서를 확인하세요.

고급: 커스텀 LM 만들기와 나만의 어댑터 작성

드물게 필요하지만, dspy.BaseLM을 상속해 커스텀 LM을 작성할 수 있어요. DSPy 생태계의 또 다른 고급 계층은 DSPy 시그니처와 LM 사이에 있는 _어댑터_입니다. 이 가이드의 향후 버전에서 이 고급 기능을 다룰 예정이지만, 아마 필요하지 않을 거예요.

커스텀 LM을 쓰는 프로그램 저장

DSPy 프로그램이 저장되면, 모듈에 붙은 각 LM은 lm.dump_state()로 직렬화됩니다. 내장 dspy.LM과 상태가 BaseLM.__init__으로 포착되는 dspy.BaseLM 서브클래스는 기본 상태 형식을 쓸 수 있어요. 커스텀 LM에 추가 생성자 인자나 런타임 상태가 필요하면 dump_stateload_state 둘 다 오버라이드하세요.

import dspy


class MyLM(dspy.BaseLM):
    def __init__(self, model: str, *, deployment: str, **kwargs):
        super().__init__(model=model, **kwargs)
        self.deployment = deployment

    def dump_state(self):
        state = super().dump_state()
        state["deployment"] = self.deployment
        return state

    @classmethod
    def load_state(cls, state):
        state = dict(state)
        state.pop("_dspy_lm_class", None)
        return cls(**state)

    def forward(self, prompt=None, messages=None, **kwargs):
        ...

커스텀 LM 클래스는 모듈 한정 클래스 경로에서 다시 로드되므로, 저장된 프로그램을 로드할 때 그것이 import 가능해야 합니다. 커스텀 LM 클래스를 import하는 저장 상태를 로드하려면 신뢰 기반의 opt-in이 필요해요.

program.load("program.json", allow_unsafe_lm_state=True)

신뢰하는 파일에만 쓰세요. 이 플래그는 api_base, base_url, model_list 같은 직렬화된 LM 엔드포인트 설정도 보존합니다.

커스텀 LM 복사

DSPy는 같은 LM을 다른 요청 파라미터(다른 temperaturerollout_id 같은)로 필요로 할 때 lm.copy(...)를 써요. 기본 BaseLM.copy() 구현은 얕은 런타임 복사를 만듭니다. 프로바이더 클라이언트, 세션, 로컬 모델 핸들은 참조로 공유되고, DSPy 소유의 변경 가능 상태(history, callbacks 목록, kwargs)는 복사본에 격리됩니다. 콜백 목록은 복사되지만 콜백 객체 자체는 공유돼요.

커스텀 LM이 복사본 간에 공유되지 않아야 하는 추가 변경 가능한 DSPy 소유 상태를 저장한다면 copy()를 오버라이드해 그 상태를 명시적으로 격리하세요. 복제하기 비싸거나 안전하지 않은 런타임 핸들은 보통 계속 참조로 공유하는 게 맞습니다.