mirror of
https://github.com/deepseek-ai/deepseek-recipe.git
synced 2026-10-28 17:17:08 +08:00
Co-authored-by: xinchengxx <xinchengxx@deepseek.com> Co-authored-by: zhu-he <hezhu@deepseek.com>
154 lines
5.3 KiB
Python
154 lines
5.3 KiB
Python
"""Smoke tests for the FastAPI example and its inference callback."""
|
|
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from deepseek_recipe import (
|
|
ConversionOptions,
|
|
InferenceChunk,
|
|
ModelVersion,
|
|
SpecialToken,
|
|
WebSearchBehavior,
|
|
)
|
|
from fastapi.testclient import TestClient
|
|
|
|
SPEC = importlib.util.spec_from_file_location(
|
|
"recipe_server_example", Path(__file__).resolve().parents[1] / "main.py"
|
|
)
|
|
SERVER = importlib.util.module_from_spec(SPEC)
|
|
SPEC.loader.exec_module(SERVER)
|
|
|
|
PROTOCOL_REQUESTS = [
|
|
(
|
|
"/v1/chat/completions",
|
|
{"messages": [{"role": "user", "content": "hi"}]},
|
|
"data: [DONE]",
|
|
),
|
|
("/v1/responses", {"input": "hi"}, "event: response.completed"),
|
|
(
|
|
"/v1/messages",
|
|
{"messages": [{"role": "user", "content": "hi"}]},
|
|
"event: message_stop",
|
|
),
|
|
]
|
|
|
|
|
|
def response_text(path, body):
|
|
if path == "/v1/chat/completions":
|
|
return body["choices"][0]["message"]["content"]
|
|
if path == "/v1/responses":
|
|
return "".join(
|
|
content["text"]
|
|
for item in body["output"]
|
|
if item["type"] == "message"
|
|
for content in item["content"]
|
|
if content["type"] == "output_text"
|
|
)
|
|
return "".join(item["text"] for item in body["content"] if item["type"] == "text")
|
|
|
|
|
|
@pytest.mark.parametrize("path,payload,terminal", PROTOCOL_REQUESTS)
|
|
@pytest.mark.parametrize("stream", [False, True])
|
|
def test_example_server(path, payload, terminal, stream):
|
|
with TestClient(SERVER.app) as client:
|
|
response = client.post(
|
|
path, json={"model": "deepseek-flash", "stream": stream, **payload}
|
|
)
|
|
assert response.status_code == 200, response.text
|
|
assert "cache-control" not in response.headers
|
|
if stream:
|
|
assert response.headers["content-type"].startswith("text/event-stream")
|
|
assert "Hello " in response.text and "world!" in response.text
|
|
assert terminal in response.text
|
|
else:
|
|
assert response.headers["content-type"] == "application/json"
|
|
assert response_text(path, response.json()) == "Hello world!"
|
|
|
|
|
|
def test_custom_inference():
|
|
async def infer(request):
|
|
assert "hi" in request.prompt
|
|
assert request.inference_options.temperature == 0.25
|
|
assert request.images == []
|
|
assert request.image_token_adjustment == 0
|
|
yield InferenceChunk.text(SpecialToken.ThinkingEnd.default_str() + "Custom answer")
|
|
|
|
options = SERVER.conversion_options().with_model_alias(
|
|
"custom-model", ModelVersion.DeepseekV41Flash
|
|
)
|
|
with TestClient(SERVER.create_app(infer, options=options)) as client:
|
|
response = client.post(
|
|
"/v1/responses",
|
|
json={
|
|
"model": "custom-model",
|
|
"input": "hi",
|
|
"temperature": 0.25,
|
|
"reasoning": {"effort": "none"},
|
|
},
|
|
)
|
|
assert response.status_code == 200, response.text
|
|
assert response.json()["model"] == "custom-model"
|
|
assert response_text("/v1/responses", response.json()) == "Custom answer"
|
|
|
|
|
|
def test_invalid_request():
|
|
with TestClient(SERVER.app) as client:
|
|
response = client.post("/v1/chat/completions", json={})
|
|
assert response.status_code == 400
|
|
assert response.headers["content-type"] == "application/json"
|
|
assert "cache-control" not in response.headers
|
|
assert response.json()["error"]["type"] == "invalid_request_error"
|
|
|
|
|
|
def test_model_version_selects_the_prompt_encoding():
|
|
def body(model):
|
|
return json.dumps({
|
|
"model": model,
|
|
"messages": [
|
|
{"role": "system", "content": "Be brief."},
|
|
{"role": "user", "content": "hi"},
|
|
],
|
|
}).encode()
|
|
|
|
system_token = SpecialToken.System.default_str()
|
|
|
|
# V4.1 writes the system token; V4 writes no system token.
|
|
v41 = SERVER.prepare_request("chat_completions", body("deepseek-flash"))
|
|
v4 = SERVER.prepare_request("chat_completions", body("deepseek-v4-pro"))
|
|
assert system_token in v41.prompt
|
|
assert system_token not in v4.prompt
|
|
|
|
|
|
def test_web_search_conversion_options():
|
|
messages = {
|
|
"model": "deepseek-flash",
|
|
"stream": False,
|
|
"messages": [{"role": "user", "content": "hi"}],
|
|
"tools": [{"type": "web_search_20250305", "name": "web_search"}],
|
|
}
|
|
responses = {
|
|
"model": "deepseek-flash",
|
|
"stream": False,
|
|
"input": "hi",
|
|
"tools": [{"type": "web_search"}],
|
|
}
|
|
|
|
# The default options reject Messages web search and drop Responses web search.
|
|
with TestClient(SERVER.app) as client:
|
|
assert client.post("/v1/messages", json=messages).status_code == 400
|
|
assert client.post("/v1/responses", json=responses).status_code == 200
|
|
|
|
options = (
|
|
SERVER.conversion_options()
|
|
.with_responses_web_search(WebSearchBehavior.Reject)
|
|
.with_messages_web_search(WebSearchBehavior.Drop)
|
|
)
|
|
with TestClient(SERVER.create_app(options=options)) as client:
|
|
accepted = client.post("/v1/messages", json=messages)
|
|
rejected = client.post("/v1/responses", json=responses)
|
|
assert accepted.status_code == 200, accepted.text
|
|
assert response_text("/v1/messages", accepted.json()) == "Hello world!"
|
|
assert rejected.status_code == 400
|