DeepSeek은 2026년 8월 12일 V4 Pro의 프리뷰를 종료했습니다. 출시 보도에서는 코딩, 도구 사용, 여러 단계를 거쳐도 맥락을 유지하는 장기 작업 등 에이전트 워크플로우를 핵심 활용 사례로 다룹니다. 이 포지셔닝에서 함수 호출(function calling)은 필수 기능이지만, 출시 직후의 많은 가이드는 채팅 완성(chat completions) 수준에서 멈췄습니다.
이 글에서는 DeepSeek V4 Pro에서 도구 스키마를 정의하고, 표준 Python openai SDK로 첫 도구 호출을 실행하며, 프로덕션에 가까운 에이전트 루프를 구현합니다. 마지막으로 배포 전에 Apidog에서 요청과 응답을 검증하는 방법까지 다룹니다.
아직 DeepSeek API 키가 없다면 DeepSeek V4 API 사용 방법 가이드로 먼저 설정하세요.
세 줄 요약
-
deepseek-v4-pro는 OpenAI 스타일의 함수 호출을 지원합니다.tools배열을 보내면 모델이tool_calls를 반환하고, 실행 결과를role: "tool"메시지로 전달합니다. 표준openaiSDK를https://api.deepseek.com에 연결해 사용할 수 있습니다. - 에이전트 루프의 핵심은 단순합니다. 모델 호출 → 도구 실행 → 도구 결과 추가를 반복하고, 모델이 일반 답변을 반환하면 종료합니다.
- 접두사 캐싱(prefix caching)을 활용하려면 이전 메시지와
tools배열을 변경하지 마세요. 에이전트 루프에서 반복 입력 비용을 크게 줄일 수 있습니다. - 도구 호출 품질은 모델만으로 결정되지 않습니다. 프롬프트, 도구 설명, JSON 스키마, 오류 처리 로직을 실제 도구와 함께 테스트해야 합니다.
함수 호출이 V4 Pro의 핵심 사용 사례인 이유
DeepSeek V4 Pro의 사양은 에이전트 런타임 요구 사항과 잘 맞습니다.
| 사양 | DeepSeek V4 Pro |
|---|---|
| 아키텍처 | Sparse MoE: 총 1.6T 파라미터, 토큰당 49B 활성 파라미터 |
| 컨텍스트 윈도우 | 1M 토큰 |
| 최대 출력 | 384K 토큰 |
| 입력 가격 | $0.435/M 토큰(캐시 미스), $0.003625/M(캐시 히트) |
| 출력 가격 | $0.87/M 토큰 |
| 함수 호출 | OpenAI 호환 tools 배열 및 tool_calls 응답 |
| 다른 인터페이스 | Anthropic Messages 형식, DeepSeek Responses API |
긴 컨텍스트는 도구 결과 이력을 유지하는 데 유리하고, 접두사 캐싱은 여러 라운드로 이어지는 에이전트 작업의 비용을 낮춥니다. 공급자 비교가 필요하다면 모델은 OpenRouter에서 deepseek-v4-pro-0813로도 확인할 수 있습니다.
다만 도구 호출 성능은 하네스(harness)에 민감합니다. Hacker News 출시 토론에서도 프레임워크, 시스템 프롬프트, 스키마 스타일에 따라 같은 모델의 결과가 달라진다는 보고가 있었습니다.
즉, 벤치마크 결과만 보지 말고 실제 서비스에서 사용할 도구 정의와 입력으로 테스트해야 합니다.
DeepSeek 함수 호출 작동 방식
함수 호출은 모델이 직접 코드를 실행한다는 뜻이 아닙니다.
모델은 일반 텍스트 대신 다음과 같은 구조화된 요청을 반환합니다.
{
"name": "get_order",
"arguments": "{\"order_id\":\"ORD-10442\"}"
}
애플리케이션이 이 요청을 받아 실제 함수를 실행하고, 그 결과를 모델에 다시 전달합니다.
전체 흐름은 다음과 같습니다.
-
messages와tools배열을 API에 보냅니다. - 모델이 도구가 필요하다고 판단하면
tool_calls를 반환합니다. - 애플리케이션이 함수 이름과 인수를 검증합니다.
- 애플리케이션이 실제 API 또는 로컬 함수를 실행합니다.
- 실행 결과를
role: "tool"메시지로 추가합니다. - 모델이 추가 도구를 호출하거나 최종 답변을 반환합니다.
OpenAI 함수 호출을 사용해 봤다면 인터페이스가 익숙할 것입니다. 대부분의 경우 기본 URL과 모델 이름만 바꾸면 기존 코드를 포팅할 수 있습니다.
공식 DeepSeek 문서는 Anthropic 호환 Messages 엔드포인트와 Responses API도 다루지만, 이 글에서는 OpenAI 호환 Chat Completions 인터페이스를 사용합니다.
1단계: 클라이언트 설정
먼저 SDK를 설치하고 API 키를 환경 변수에 설정합니다.
pip install openai
export DEEPSEEK_API_KEY="sk-..."
Python 클라이언트를 초기화합니다.
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com",
)
이후 예제에서는 model="deepseek-v4-pro"를 사용합니다.
2단계: 도구 스키마 정의
온라인 스토어 지원 에이전트를 예로 들어 보겠습니다. 첫 번째 도구는 주문 정보를 조회합니다.
도구 정의에는 다음 세 가지가 필요합니다.
- 함수 이름
- 함수 설명
- 매개변수 JSON 스키마
tools = [
{
"type": "function",
"function": {
"name": "get_order",
"description": (
"ID로 고객 주문을 조회합니다. 주문 상태, 운송업체, "
"추적 번호, 예상 배송 날짜를 반환합니다. 사용자가 "
"주문의 위치나 상태를 물을 때 사용하십시오."
),
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "주문 ID입니다. 예: ORD-10442",
}
},
"required": ["order_id"],
"additionalProperties": False,
},
},
}
]
도구 설명은 단순한 문서가 아닙니다. 모델은 이 설명을 바탕으로 다음을 판단합니다.
- 언제 도구를 호출할지
- 여러 도구 중 무엇을 선택할지
- 어떤 인수를 생성할지
따라서 "주문을 가져옵니다"처럼 모호하게 작성하지 말고, 반환 데이터와 호출 조건을 구체적으로 적으세요.
이제 실제 주문 API 대신 사용할 스텁 함수를 만듭니다.
def get_order(order_id: str) -> dict:
"""실제 주문 서비스 호출을 대체하는 예제 스텁입니다."""
fake_db = {
"ORD-10442": {
"status": "shipped",
"carrier": "DHL",
"tracking_number": "4281337005",
"estimated_delivery": "2026-08-15",
},
"ORD-10587": {
"status": "processing",
"estimated_ship_date": "2026-08-14",
},
}
return fake_db.get(
order_id,
{"error": f"알 수 없는 주문 ID: {order_id}"}
)
3단계: 첫 도구 호출 수행
도구 없이는 답할 수 없는 질문을 보냅니다.
messages = [
{
"role": "system",
"content": "당신은 온라인 상점의 지원 에이전트입니다.",
},
{
"role": "user",
"content": "내 주문 ORD-10442는 어디에 있나요?",
},
]
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=messages,
tools=tools,
)
message = response.choices[0].message
print(message.tool_calls[0].function.name)
# get_order
print(message.tool_calls[0].function.arguments)
# {"order_id": "ORD-10442"}
이 시점에서 모델은 주문 상태를 추측해 답하지 않습니다. 대신 get_order를 실행하라고 요청합니다.
응답의 핵심 구조는 다음과 같습니다.
{
"choices": [
{
"message": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_f1c29a44",
"type": "function",
"function": {
"name": "get_order",
"arguments": "{\"order_id\": \"ORD-10442\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}
여기서 반드시 확인해야 할 항목은 세 가지입니다.
-
finish_reason이"tool_calls"인지 확인합니다. -
tool_call.id를 저장합니다. 결과를 되돌려 줄 때 같은 ID가 필요합니다. -
arguments는 JSON 객체가 아니라 JSON 문자열입니다.json.loads()로 파싱해야 합니다.
4단계: 함수 실행 및 결과 반환
모델이 요청한 함수를 실행한 뒤, 두 종류의 메시지를 대화 이력에 추가합니다.
-
tool_calls가 포함된 assistant 메시지 - 함수 실행 결과를 담은 tool 메시지
import json
tool_call = message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
result = get_order(**args)
messages.append(message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False),
})
이제 도구 결과를 포함한 대화 이력으로 다시 모델을 호출합니다.
final = client.chat.completions.create(
model="deepseek-v4-pro",
messages=messages,
tools=tools,
)
print(final.choices[0].message.content)
예상되는 응답은 다음과 비슷합니다.
귀하의 주문 ORD-10442는 DHL로 배송되었으며
2026년 8월 15일에 도착할 것으로 예상됩니다.
추적 번호는 4281337005입니다.
tool_call_id 연결은 엄격합니다. 하나의 assistant 메시지에 여러 tool_calls가 있다면, 다음 모델 호출 전에 모든 호출에 대응하는 tool 메시지를 추가해야 합니다.
5단계: 완전한 에이전트 루프 구현
실제 에이전트는 한 번의 도구 호출로 끝나지 않습니다.
예를 들어 다음과 같은 작업이 있을 수 있습니다.
- 주문 상태 조회
- 환불 정책 조회
- 고객에게 보낼 답변 작성
이를 처리하려면 모델이 일반 답변을 반환할 때까지 도구 호출 루프를 반복해야 합니다.
import json
TOOLS_BY_NAME = {
"get_order": get_order,
}
def run_agent(client, messages, tools, max_rounds=10):
"""최종 답변 또는 최대 라운드 도달 시까지 에이전트를 실행합니다."""
for _ in range(max_rounds):
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=messages,
tools=tools,
)
message = response.choices[0].message
messages.append(message)
# 도구 요청이 없으면 최종 답변입니다.
if not message.tool_calls:
return message.content
# 한 응답에 포함된 모든 도구 호출을 처리합니다.
for tool_call in message.tool_calls:
try:
fn = TOOLS_BY_NAME.get(tool_call.function.name)
if fn is None:
raise ValueError(
f"허용되지 않은 도구: {tool_call.function.name}"
)
args = json.loads(tool_call.function.arguments)
result = fn(**args)
except Exception as exc:
# 오류를 모델에 전달해 다음 라운드에서 수정하도록 합니다.
result = {
"error": str(exc),
}
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False),
})
raise RuntimeError(
f"에이전트가 {max_rounds} 라운드 안에 완료되지 않았습니다."
)
호출 방법은 다음과 같습니다.
answer = run_agent(
client=client,
messages=[
{
"role": "system",
"content": "당신은 온라인 상점의 지원 에이전트입니다.",
},
{
"role": "user",
"content": "내 주문 ORD-10442는 어디에 있나요?",
},
],
tools=tools,
)
print(answer)
max_rounds는 필수 안전장치입니다. 잘못된 도구 정의나 반복되는 오류 때문에 모델이 무한히 재시도하는 상황을 막고, 예측 불가능한 비용 증가를 방지합니다.
병렬 도구 호출 처리
사용자가 다음과 같이 요청할 수 있습니다.
ORD-10442와 ORD-10587의 상태를 비교해줘.
이 경우 모델은 여러 도구 호출을 한 응답에 반환할 수 있습니다.
{
"tool_calls": [
{
"id": "call_0_a7d1",
"type": "function",
"function": {
"name": "get_order",
"arguments": "{\"order_id\": \"ORD-10442\"}"
}
},
{
"id": "call_1_b3e9",
"type": "function",
"function": {
"name": "get_order",
"arguments": "{\"order_id\": \"ORD-10587\"}"
}
}
]
}
앞의 run_agent() 구현은 for tool_call in message.tool_calls: 루프를 사용하므로 모든 호출을 처리합니다.
도구가 네트워크 I/O 작업이라면 asyncio.gather() 또는 작업 큐를 사용해 병렬 실행을 고려할 수 있습니다. 단, 다음 모델 호출 전에 모든 도구 결과를 추가해야 합니다.
DeepSeek은 실행과 권한 경계를 애플리케이션 런타임에 둡니다. 이는 모델이 샌드박스 안에서 오케스트레이션 코드를 작성하는 GPT-5.6의 프로그래밍 방식 도구 호출과는 다른 접근입니다.
사고 모드와 도구 호출 함께 사용하기
V4 Pro는 사고 모드를 제공하며, 활성화하면 응답에 reasoning_content가 포함될 수 있습니다.
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=messages,
tools=tools,
extra_body={
"thinking": {
"type": "enabled"
}
},
)
message = response.choices[0].message
print(message.reasoning_content)
print(message.tool_calls)
사고 추적은 모델이 특정 도구를 선택한 이유를 파악할 때 유용합니다. 특히 다음 문제를 디버깅하는 데 도움이 됩니다.
- 모델이 잘못된 도구를 선택함
- 필요한 도구를 호출하지 않음
- 인수 형식을 잘못 생성함
- 도구 설명을 오해함
다만 운영 환경에서는 reasoning_content를 장기 대화 이력에 그대로 쌓지 않는 편이 좋습니다. 필요한 경우 로그 또는 관측성 시스템으로 별도 저장하고, assistant 메시지를 다시 전송하기 전에는 제거하는 방식을 고려하세요.
사고 모드와 요금 정책의 최신 내용은 공식 DeepSeek 문서에서 확인하세요.
오류 처리: 잘못된 도구 호출을 중단시키지 않기
에이전트 루프에서는 작은 오류가 여러 라운드로 증폭될 수 있습니다. 핵심 원칙은 다음과 같습니다.
도구 호출이 잘못되었다고 애플리케이션 전체를 중단하지 말고, 오류를 도구 결과로 반환해 모델이 수정하도록 하세요.
JSON 파싱뿐 아니라 JSON 스키마 검증도 추가하는 것이 좋습니다.
pip install jsonschema
import json
from jsonschema import ValidationError, validate
schema = tools[0]["function"]["parameters"]
try:
args = json.loads(tool_call.function.arguments)
validate(
instance=args,
schema=schema,
)
result = get_order(**args)
except (json.JSONDecodeError, ValidationError) as exc:
result = {
"error": f"잘못된 인수: {exc}",
"hint": (
"'ORD-10442'와 같은 order_id 문자열로 "
"get_order를 다시 호출하십시오."
),
}
hint는 단순한 오류 문자열보다 효과적입니다. 모델이 다음 라운드에서 무엇을 고쳐야 하는지 명확히 알 수 있기 때문입니다.
또한 도구 호출 실패를 보안 관점에서도 다뤄야 합니다.
- 모델이 호출할 수 있는 도구를 허용 목록으로 제한합니다.
- 도구별 API 키와 권한을 분리합니다.
- 삭제, 결제, 권한 변경 같은 작업에는 추가 확인을 둡니다.
- 모델이 생성한 인수를 신뢰하지 말고 서버에서 검증합니다.
특히 쓰기 작업을 수행하는 도구에서는 AI 에이전트를 위한 최소 권한 API 키 원칙을 적용해야 합니다.
배포 전에 Apidog로 도구 호출 테스트 및 디버그
에이전트의 도구는 결국 백엔드 API를 감싼 래퍼입니다. 백엔드 API의 스키마가 모호하거나 응답이 불안정하면, 모델의 도구 호출도 불안정해집니다.
Apidog를 사용하면 API 계약과 에이전트 하네스를 함께 검증할 수 있습니다.
1. 백엔드 API부터 명확하게 설계하기
예를 들어 주문 조회 API를 다음처럼 정의합니다.
GET /orders/{order_id}
도구의 JSON 스키마는 API 사양과 일치해야 합니다. API에서 order_id가 필수인데 도구 스키마에서 선택 항목으로 두면, 모델은 누락된 인수를 생성할 수 있습니다.
2. 백엔드 완성 전에도 Mock으로 에이전트 테스트하기
실제 주문 서비스가 아직 준비되지 않았다면 Apidog의 Mock 기능으로 예상 응답을 먼저 만들 수 있습니다.
{
"status": "shipped",
"carrier": "DHL",
"tracking_number": "4281337005",
"estimated_delivery": "2026-08-15"
}
이렇게 하면 백엔드 구현과 분리해서 다음 항목을 먼저 검증할 수 있습니다.
- 모델이 올바른 도구를 선택하는지
- 올바른
order_id를 생성하는지 - 도구 결과를 고객 친화적인 답변으로 바꾸는지
3. DeepSeek 요청의 원시 페이로드 확인하기
Apidog에서 같은 messages와 tools 요청 본문을 https://api.deepseek.com으로 보내고, 응답의 원시 JSON을 직접 확인하세요.
특히 다음 문제를 빠르게 찾을 수 있습니다.
-
properties중첩 위치 오류 -
required필드 누락 - JSON 인수의 이중 인코딩
- 잘못된
tool_call_id - 예상하지 못한
finish_reason
4. 대화를 회귀 테스트 시나리오로 만들기
도구 스키마를 바꿀 때마다 다음을 검증하는 테스트를 실행하세요.
- 주문 상태 질문에
get_order가 호출되는지 -
arguments가 유효한 JSON인지 -
order_id가 필수로 포함되는지 - 잘못된 주문 ID에서 안전한 오류 응답이 나오는지
- 두 주문 요청에서 모든
tool_calls결과가 반환되는지
AI 에이전트를 Apidog 테스트 하네스에 연결하기에서 더 깊은 테스트 패턴을 확인할 수 있습니다.
Apidog를 무료로 다운로드하고, Mock 서버와 테스트 시나리오를 활용해 도구 호출을 배포 전에 검증하세요.
에이전트 루프 비용과 접두사 캐싱
에이전트 루프는 매 라운드마다 기존 대화 이력을 다시 전송합니다.
10번째 라운드에서는 다음 데이터가 다시 입력에 포함될 수 있습니다.
- 시스템 프롬프트
- 도구 정의
- 사용자 요청
- 이전 9라운드의 assistant 메시지
- 이전 9라운드의 tool 결과
따라서 입력 토큰 비용은 에이전트 설계에서 중요한 요소입니다.
V4 Pro의 자동 접두사 캐싱은 이전 라운드와 동일한 접두사에 대해 캐시 히트 요율을 적용합니다. 사용량 정보에서 다음 필드를 확인하세요.
{
"prompt_cache_hit_tokens": 80000,
"prompt_cache_miss_tokens": 1200
}
캐시 효율을 높이려면 다음 규칙을 지키세요.
- 이전 메시지를 수정하지 않습니다.
-
tools배열의 순서와 내용을 라운드마다 동일하게 유지합니다. - 시스템 프롬프트를 매 요청마다 동적으로 바꾸지 않습니다.
- 반복되는 컨텍스트는 대화 앞부분에 배치합니다.
- 도구 설명과 스키마를 불필요하게 재생성하지 않습니다.
프롬프트 캐싱이란 무엇인가 가이드에서 캐시 동작과 설계 패턴을 더 자세히 확인할 수 있습니다.
deepseek-v4-flash의 가격이 매력적으로 보일 수 있지만, 여러 단계의 도구 호출과 재시도가 필요한 에이전트에서는 단순한 토큰 단가만으로 선택하지 마세요. 실제 도구 스키마와 작업 흐름에서 정확도, 재시도 횟수, 전체 비용을 함께 측정해야 합니다.
FAQ
도구 정의에도 토큰 비용이 발생하나요?
예. tools 배열은 요청 입력의 일부이므로 토큰 비용에 포함됩니다.
다만 도구 정의와 이전 메시지를 안정적으로 유지하면, 이후 라운드에서 접두사 캐시 히트 대상이 될 수 있습니다.
함수 호출과 구조화된 출력을 함께 사용할 수 있나요?
예. 일반적인 패턴은 다음과 같습니다.
- 도구 호출로 외부 데이터를 가져옵니다.
- 모델이 결과를 해석합니다.
- 최종 답변을 구조화된 출력 스키마에 맞춰 반환합니다.
이 방식은 후속 코드가 자연어 문장을 다시 파싱하지 않아도 되므로 API 응답을 다른 시스템으로 전달할 때 유용합니다.
마무리
DeepSeek V4 Pro의 함수 호출 흐름은 단순합니다.
- OpenAI 호환
tools스키마를 보냅니다. - 모델의
tool_calls를 받습니다. - 애플리케이션에서 도구를 실행합니다.
- 같은 ID를 가진
tool메시지로 결과를 반환합니다. - 모델이 최종 답변을 생성할 때까지 반복합니다.
중요한 것은 모델 호출 자체보다 하네스 품질입니다. 도구 설명을 구체적으로 작성하고, 인수를 검증하며, 최대 반복 횟수를 제한하고, 실제 API 계약과 도구 스키마를 함께 테스트하세요.
벤치마크만으로는 모델이 내 도구 스키마를 얼마나 잘 처리하는지 알 수 없습니다. 백엔드 API를 먼저 명확히 정의하고, Mock으로 빠르게 검증하며, Apidog에서 도구 호출 회귀 테스트를 유지하는 것이 안정적인 에이전트 배포의 핵심입니다.
Top comments (0)