mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +08:00
90 lines
3.4 KiB
Python
90 lines
3.4 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from plyngent.lmproto.deepseek import DeepseekResponsesClient
|
||
|
|
from plyngent.lmproto.openai import Response, ResponsesCreateParam, response_output_text
|
||
|
|
from plyngent.lmproto.openai_compatible.config import OpenAIConfig
|
||
|
|
|
||
|
|
|
||
|
|
def _sample_response_body() -> bytes:
|
||
|
|
return (
|
||
|
|
b'{"id":"resp_1","object":"response","created_at":1,"model":"deepseek-v4-flash",'
|
||
|
|
b'"status":"completed","output":['
|
||
|
|
b'{"id":"msg_1","type":"message","role":"assistant","status":"completed",'
|
||
|
|
b'"content":[{"type":"output_text","text":"hello from deepseek","annotations":[]}]}'
|
||
|
|
b"]}"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_client_kind_and_base_url() -> None:
|
||
|
|
client = DeepseekResponsesClient(OpenAIConfig(access_key_or_token="sk", base_url="https://api.deepseek.com"))
|
||
|
|
assert client.kind == "responses"
|
||
|
|
assert client.session.base_url == "https://api.deepseek.com"
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_client_responses_create(monkeypatch: pytest.MonkeyPatch) -> None:
|
||
|
|
client = DeepseekResponsesClient(OpenAIConfig(access_key_or_token="sk", base_url="https://api.deepseek.com"))
|
||
|
|
|
||
|
|
class _Resp:
|
||
|
|
status_code = 200
|
||
|
|
|
||
|
|
@property
|
||
|
|
def content(self) -> bytes:
|
||
|
|
return _sample_response_body()
|
||
|
|
|
||
|
|
async def fake_post(path: str, **kwargs: object) -> _Resp:
|
||
|
|
assert path == "/responses"
|
||
|
|
assert kwargs.get("stream") is False
|
||
|
|
return _Resp()
|
||
|
|
|
||
|
|
monkeypatch.setattr(client.session, "post", fake_post)
|
||
|
|
result = await client.responses(ResponsesCreateParam(model="deepseek-v4-flash", input="hi"))
|
||
|
|
assert isinstance(result, Response)
|
||
|
|
assert response_output_text(result) == "hello from deepseek"
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_client_responses_stream_stops_at_terminal(monkeypatch: pytest.MonkeyPatch) -> None:
|
||
|
|
"""DeepSeek sends no ``[DONE]``: the stream must stop at a terminal event."""
|
||
|
|
client = DeepseekResponsesClient(OpenAIConfig(access_key_or_token="sk", base_url="https://api.deepseek.com"))
|
||
|
|
|
||
|
|
class _Resp:
|
||
|
|
status_code = 200
|
||
|
|
|
||
|
|
async def iter_lines(self):
|
||
|
|
yield (
|
||
|
|
b'data: {"type":"response.output_text.delta","delta":"hel",'
|
||
|
|
b'"item_id":"m","output_index":0,"content_index":0,"sequence_number":1}'
|
||
|
|
)
|
||
|
|
# Terminal event carries the full response (no [DONE] follows).
|
||
|
|
yield (
|
||
|
|
b'data: {"type":"response.completed","sequence_number":2,'
|
||
|
|
b'"response":{"id":"resp_1","object":"response","created_at":1,'
|
||
|
|
b'"model":"deepseek-v4-flash","status":"completed","output":[]}}'
|
||
|
|
)
|
||
|
|
# Anything after the terminal event must not be consumed.
|
||
|
|
yield b'data: {"type":"response.output_text.delta","delta":"ignored"}'
|
||
|
|
|
||
|
|
def close(self) -> None:
|
||
|
|
return None
|
||
|
|
|
||
|
|
async def fake_post(path: str, **kwargs: object) -> _Resp:
|
||
|
|
assert path == "/responses"
|
||
|
|
assert kwargs.get("stream") is True
|
||
|
|
return _Resp()
|
||
|
|
|
||
|
|
monkeypatch.setattr(client.session, "post", fake_post)
|
||
|
|
stream = await client.responses(
|
||
|
|
ResponsesCreateParam(model="deepseek-v4-flash", input="hi"),
|
||
|
|
stream=True,
|
||
|
|
)
|
||
|
|
events = [event async for event in stream]
|
||
|
|
assert [event.type for event in events] == [
|
||
|
|
"response.output_text.delta",
|
||
|
|
"response.completed",
|
||
|
|
]
|
||
|
|
assert isinstance(events[1].response, dict)
|
||
|
|
assert events[1].response["status"] == "completed"
|