DeepSeek V4 API Python: 스트리밍을 포함한 최소 코드 예제
I’ll translate this article to Korean. Let me read it carefully and provide a complete translation.
안녕하세요! 저는 도라입니다. 작은 짜증에서 시작했습니다: 프로젝트 간에 동일한 채팅 완료 보일러플레이트를 계속 복사하고 항아리의 라벨처럼 기본 URL과 모델 이름을 바꾸고 있었습니다. 어려운 작업은 아니지만, 하루하루에 모래를 더하는 그런 종류의 작업이었습니다. 2026년 1월 말에 DeepSeek “V4” API를 Python 스택에 연결하고 실제 사용에서 어떤 느낌인지 확인하기 위해 몇 아침을 할애할 충분한 호기심을 느껴서 DeepSeek이 자주 나타나는 것을 봤습니다.
저는 벤치마크를 추구하지 않았습니다. 저는 알고 싶었습니다: 클라이언트가 내 길을 방해하지 않습니까? 안정적으로 스트리밍할 수 있습니까? 그리고 오류가 추론하기 쉬운 방식으로 실패합니까? 여기 제가 시도한 것, 절차를 밟은 것, 그리고 조용히 작동한 것입니다. 시작해봅시다!
환경 설정
종속성
macOS에서 Python 3.11로 설정을 간단하게 유지했습니다. 표준 라이브러리로 이것을 할 수 있지만, 세 개의 작은 패키지가 생활을 더 쉽게 만들었습니다:
- requests (간단한 HTTP: 대부분의 경우 충분함)
- httpx (비동기 및 잘 작동하는 타임아웃)
- python-dotenv (키를 붙여넣지 않도록)
서버 전송 이벤트를 사용하여 스트리밍할 계획이라면 요청을 사용하고 줄을 직접 구문 분석할 수 있습니다 (제가 한 것). 또는 sseclient-py와 같은 도우미를 가져올 수 있습니다. 저는 요청에 충실했고, 더 적은 동작 부분입니다.
설치
pip install requests httpx python-dotenv
또한 프로젝트당 최소한의 가상 환경을 만들었습니다. 지루한 조언이지만, 3개월 후에 이것을 다시 방문할 때 종속성 드리프트에서 벗어날 수 있습니다.
API 키 구성
키를 환경 변수에 저장했습니다. 꾸며낸 것은 없습니다:
# .env
DEEPSEEK_API_KEY=your_key_here
그런 다음 Python에서:
from dotenv import load_dotenv
import os
load_dotenv()
API_KEY = os.getenv("DEEPSEEK_API_KEY")
if not API_KEY:
raise RuntimeError("Missing DEEPSEEK_API_KEY")
설정에서 두 가지 작은 주의사항:
- 기본 URL 및 모델 이름은 생각보다 자주 변경됩니다. 각 실행 전에 경로 및 사용 가능한 모델을 확인하기 위해 공식 DeepSeek API 문서를 확인했습니다.
- 타임아웃을 명시적으로 유지했습니다. 속도 제한이나 네트워크 노이즈에 부딪히면 보상하는 습관입니다.

기본 채팅 요청
정신 모델은 다른 곳에서 채팅 완료를 사용한 경우 익숙합니다. DeepSeek은 messages=[{"role": "...", "content": "..."}]로 채팅 끝점을 노출합니다. 그것은 도움이 됩니다. 프롬프트를 다시 프레임할 필요가 없었기 때문입니다.
여기 요청과 함께 제가 사용한 최소한의 요청입니다. 모델 이름은 계정 및 지역에 따라 다르며, 테스트 중에 deepseek-chat 및 deepseek-reasoner와 같은 참조를 보았습니다. 문서에서 “V4” 모델 문자열을 언급하면 그것을 사용하십시오. 그렇지 않으면 콘솔에 나열된 가장 가까운 일반 목적 채팅 모델을 선택하십시오.
import os
import requests
API_KEY = os.environ["DEEPSEEK_API_KEY"]
BASE_URL = "https://api.deepseek.com/v1/chat/completions"
payload = {
"model": "deepseek-chat", # check docs/console for the exact model
"messages": [
{"role": "system", "content": "You are a concise assistant."},
{"role": "user", "content": "Give me two bullet points on the value of clear commit messages."}
],
"temperature": 0.3,
"max_tokens": 200
}
resp = requests.post(
BASE_URL,
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json=payload,
timeout=30
)
resp.raise_for_status()
data = resp.json()
content = data["choices"][0]["message"]["content"]
print(content)
현장 노트
- 첫 번째 실행은 순조로웠습니다 (안도의 한숨). 구조가 예상했던 것과 일치했으므로 작은 프롬프트 라이브러리를 빠르게 마이그레이션할 수 있었습니다.
- 반복 가능한 답변을 위해 온도를 낮게 유지했습니다. 당연한 것 같지만, 문제 해결할 때 여전히 잊습니다.
- 결정론적 실행이 필요한 경우 API가 지원하면 top_p 및 seed도 고정하십시오. 문서가 침묵할 때, 나는 비결정론적이라고 가정합니다.
공급자를 비교하는 경우, 여기서의 장점은 마찰이 낮습니다. 단점은 차이가 모서리, 오류 페이로드, 토큰 회계 및 스트리밍 모양에 숨어있다는 것입니다. 이 모서리들은 당신의 통합이 견실하거나 짜증나는 것처럼 느껴지는 곳입니다.
코드 생성 예제
저는 모델이 전체 모듈을 작성하도록 요청하지 않습니다. 청소 작업이 됩니다. 하지만 “이 타임스탐프 형식 분석” 또는 “자리 표시자를 사용하여 SQL 초안”과 같은 작은 도우미의 경우, 편리합니다. 좁은 프롬프트, 명확한 계약 및 작은 출력 제한을 사용했습니다. 모델이 방황하는 것을 막고 diffs를 검토하기 쉽게 만들었습니다.
import requests, os
API_KEY = os.environ["DEEPSEEK_API_KEY"]
BASE_URL = "https://api.deepseek.com/v1/chat/completions"
messages = [
{"role": "system", "content": (
"You generate small, safe Python helpers. "
"Return only code inside one block."
)},
{"role": "user", "content": (
"Write a Python function `parse_yyyymmdd` that takes a string like '2026-01-31' "
"and returns a datetime.date. If invalid, return None. No external deps."
)}
]
resp = requests.post(
BASE_URL,
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json={
"model": "deepseek-chat", # or your V4-capable model
"messages": messages,
"temperature": 0,
"max_tokens": 250
},
timeout=30
)
resp.raise_for_status()
code = resp.json()["choices"][0]["message"]["content"]
print(code)
실제로 도움이 된 것
- 저는 항상 코드만 반환하도록 말합니다. 그것을 건너뛰면, 나는 필요하지 않은 마무리 문장을 얻습니다.
- 온도 0은 까다로운 편집을 줄입니다.
- 어쨌든 논리를 읽습니다. 내 실행에서 ValueError를 처리했지만, 공백에 대한 추가 테스트를 추가했습니다. 지금 2분 추가로 나중에 몇 시간의 놀라움을 절약합니다.
첫 샷에서 시간을 절약하지 못했습니다. 3개 또는 4개의 작은 도우미 후, 저는 정신적 노력을 줄였다는 것을 알았습니다: 탭 전환 감소, “정확한 strptime 코드가 뭐였더라?” 순간 감소. 그것으로 충분합니다.
스트리밍 응답
나는 클 수 있는 모든 프롬프트에 대해 스트리밍을 좋아합니다. 그것은 대답이 드리프트하는 경우 일찍 구제할 수 있게 하고, 긴 응답이 덜 무거움을 느끼게 합니다.
DeepSeek의 스트리밍은 내 테스트에서 일반적인 패턴을 사용했습니다: stream=true를 설정하고 [DONE]까지 데이터 줄을 읽습니다. 특별한 클라이언트가 필요하지 않았고, requests와 iter_lines는 좋았습니다.
import os, json, requests
API_KEY = os.environ["DEEPSEEK_API_KEY"]
BASE_URL = "https://api.deepseek.com/v1/chat/completions"
payload = {
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": "Be brief."},
{"role": "user", "content": "Summarize this: Streaming keeps the UI responsive and lets me stop early."}
],
"stream": True,
"temperature": 0.2,
}
with requests.post(
BASE_URL,
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json=payload,
stream=True,
timeout=60
) as r:
r.raise_for_status()
for line in r.iter_lines(decode_unicode=True):
if not line:
continue
if line.startswith("data: "):
chunk = line[len("data: "):]
if chunk == "[DONE]":
break
try:
obj = json.loads(chunk)
delta = obj["choices"][0]["delta"].get("content", "")
if delta:
print(delta, end="", flush=True)
except json.JSONDecodeError:
# I keep a small log when this happens: usually network blips
pass
print()
두 가지 작은 동작이 마음에 들었습니다:
- 초기 토큰이 빠르게 도착했습니다 (깨끗한 연결에서 1초 또는 2초). 과학적이지는 않지만 CLI 도구에 연결할 때 충분히 민첩합니다.
[DONE]마커가 안정적으로 나타났습니다. 그것이 없을 때까지 사소하게 들리지만, 누락된 종료자는 UI를 중단하게 합니다.
웹 앱으로 스트리밍해야 하는 경우 이벤트를 정규화하기 위해 중간에 얇은 서버 레이어를 넣겠습니다. 한 가지 추가 단계이지만 프론트엔드를 간단하게 유지합니다.
서버 전송 이벤트
실제로는 서버 전송 이벤트를 효과적으로 읽고 있습니다. 도우미를 선호하는 경우 sseclient-py가 작동하지만, 부분 줄과 타임아웃을 방지하는 한 여기서 자신의 것을 굴리는 것이 좋습니다. DeepSeek API 문서의 스트리밍에 대한 문서 페이지는 놀라움 없이 이것을 실행하기에 충분했습니다.
오류 처리
제 오류의 대부분은 예측 가능했습니다: 누락된 키, 잘못된 모델 이름, 또는 여행 Wi-Fi를 시뮬레이션하기 위해 네트워크를 제한할 때의 타임아웃.
재사용하는 작은 패턴:
import httpx, time, os
API_KEY = os.environ["DEEPSEEK_API_KEY"]
BASE_URL = "https://api.deepseek.com/v1/chat/completions"
RETRIABLE = {408, 409, 425, 429, 500, 502, 503, 504}
async def chat_once(client, messages):
resp = await client.post(
BASE_URL,
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"model": "deepseek-chat",
"messages": messages,
"temperature": 0.2,
"max_tokens": 300,
},
timeout=30,
)
if resp.status_code == 401:
raise RuntimeError("Unauthorized. Check DEEPSEEK_API_KEY and account access.")
if resp.status_code == 404:
raise RuntimeError("Endpoint or model not found. Confirm model name in console/docs.")
if resp.status_code in RETRIABLE:
raise RuntimeError(f"Retryable status: {resp.status_code}")
resp.raise_for_status()
return resp.json()
async def chat_with_retries(messages, attempts=4):
backoff = 0.5
async with httpx.AsyncClient() as client:
for i in range(attempts):
try:
return await chat_once(client, messages)
except RuntimeError as e:
msg = str(e)
if "Retryable status" in msg and i < attempts - 1:
time.sleep(backoff)
backoff *= 2
continue
raise
몇 가지 실제적인 주의사항:
- 속도 제한: 병렬 테스트를 실행했을 때 429를 봤습니다. 지수 백오프가 도움이 되었지만, 작은 지터 (무작위 50-150ms)도 추가하여 천둥 무리를 피했습니다.
- 타임아웃 위생: 빠른 검사를 위해 더 짧은 연결/읽기 타임아웃을 설정했습니다 (5-10초) 및 큰 프롬프트를 위해 더 깁니다. 타임아웃은 모두 기본값으로 30초여야 하지 않습니다: 문제를 숨깁니다.
- 오류 페이로드: 것들이 실패할 때, JSON 본체에 로그에 표시할 수 있는 메시지가 포함되었습니다. 저는 여전히 그것을 내 자신의 예외로 감싸서 UI에 도달하는 것을 제어합니다.
코드베이스가 이미 OpenAI 스타일 스키마를 말하는 경우, 이것은 관리 가능합니다: 동일한 메시지 모양, 약간 다른 모서리. 주요한 것은 모델 이름에 대해 엄격하고 추측하지 않도록 2xx가 아닌 경우 전체 응답 본체를 기록하는 것입니다. 문서 방면에서, 저는 매개변수 이름 및 스트리밍 모양에 대한 공식 DeepSeek API 문서를 기울였습니다. 공급자가 익숙한 끝점을 사용할 때마다, 패리티를 가정하기 위해 유혹됩니다. 저는 문서를 먼저 확인하고 생각할 수 있는 것보다 클라이언트 간에 덜 복사하는 것을 배웠습니다.
누가 이것을 좋아할 수도 있습니다

- 채팅 완료를 위한 기존 Python 래퍼가 있는 경우, 마이그레이션 경로는 부드럽습니다.
- 스트리밍 및 간단한 재시도를 신경 쓰는 경우, 예측 가능하게 동작합니다.
- 매우 특정한 도구 (함수 호출 스키마, 추론 토큰 또는 배치 작업)가 필요한 경우, 문서를 주의 깊게 읽고 커밋하기 전에 하나의 좁은 작업으로 프로토타입해야 합니다.
여기서 긴 다단계 에이전트를 조정하려고하지 않았습니다. 저는 작은 일상적 사용 프롬프트, 마찰을 갉아먹는 종류에 초점을 맞췄습니다. 그것이 DeepSeek V4 API가 Python과 함께 충분히 견고해서 유지하기에 느껴진 곳입니다.





