feat: add DeepSeek extreme reasoning tier
This commit is contained in:
@@ -13,6 +13,8 @@ def settings(tmp_path: Path) -> Settings:
|
||||
return Settings(
|
||||
model_api_base_url="https://model.example.test",
|
||||
model_api_key="model-secret",
|
||||
deepseek_api_base_url="https://api.deepseek.example.test",
|
||||
deepseek_api_key="deepseek-secret",
|
||||
openwebui_forward_jwt_secret="identity-secret-with-at-least-32-bytes", # noqa: S106
|
||||
internal_provider_key="provider-secret-with-at-least-32-bytes",
|
||||
internal_gateway_key="gateway-secret-with-at-least-32-bytes",
|
||||
|
||||
@@ -962,3 +962,59 @@ def test_tool_events_render_one_friendly_completed_card() -> None:
|
||||
assert 'name="执行命令"' in rendered
|
||||
assert "2 passed" in rendered
|
||||
assert 'done="false"' not in rendered
|
||||
|
||||
|
||||
async def test_extreme_tier_preserves_reasoning_state_across_tool_turns(settings) -> None:
|
||||
provider = ScriptedProvider(
|
||||
[
|
||||
{
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": None,
|
||||
"reasoning_content": "provider-private-state",
|
||||
"tool_calls": [tool_call("1", "read_file", {"path": "notes.txt"})],
|
||||
},
|
||||
"finish_reason": "tool_calls",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"choices": [
|
||||
{
|
||||
"message": {"role": "assistant", "content": "检查完成"},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
)
|
||||
registry = RecordingRegistry()
|
||||
store = RuntimeStore(settings.database_url)
|
||||
await store.initialize()
|
||||
|
||||
async def callback(event_type, payload):
|
||||
return None
|
||||
|
||||
try:
|
||||
answer = await AgentLoop(provider, registry, store, max_tool_output_chars=10_000).run(
|
||||
spec=get_model_spec("work-extreme"),
|
||||
messages=[{"role": "user", "content": "读取 notes.txt 并检查内容"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
chat_id="deepseek-reasoning",
|
||||
callback=callback,
|
||||
)
|
||||
finally:
|
||||
await store.close()
|
||||
|
||||
assert answer == "检查完成"
|
||||
assert provider.requests[0]["provider"] == "deepseek"
|
||||
assert provider.requests[0]["reasoning_effort"] == "max"
|
||||
assistant_turn = next(
|
||||
message
|
||||
for message in provider.requests[1]["messages"]
|
||||
if message.get("role") == "assistant" and message.get("tool_calls")
|
||||
)
|
||||
assert assistant_turn["reasoning_content"] == "provider-private-state"
|
||||
|
||||
@@ -3,7 +3,7 @@ from agent_platform.bootstrap import _model_payload
|
||||
|
||||
def test_bootstrap_models_override_provider_ids_and_are_public() -> None:
|
||||
models = _model_payload()
|
||||
assert len(models) == 6
|
||||
assert len(models) == 8
|
||||
assert all(model["base_model_id"] is None for model in models)
|
||||
assert all(
|
||||
model["access_grants"] == [{"principal_type": "user", "principal_id": "*", "permission": "read"}]
|
||||
|
||||
@@ -6,3 +6,8 @@ import pytest
|
||||
def test_internal_secrets_must_be_at_least_32_bytes(settings) -> None:
|
||||
with pytest.raises(RuntimeError, match="at least 32 bytes"):
|
||||
replace(settings, internal_gateway_key="too-short").validate_gateway()
|
||||
|
||||
|
||||
def test_runtime_requires_deepseek_key_for_extreme_tier(settings) -> None:
|
||||
with pytest.raises(RuntimeError, match="DEEPSEEK_API_KEY"):
|
||||
replace(settings, deepseek_api_key="").validate_runtime()
|
||||
|
||||
@@ -6,11 +6,18 @@ def test_fixed_public_model_matrix() -> None:
|
||||
"chat-light",
|
||||
"chat-medium",
|
||||
"chat-high",
|
||||
"chat-extreme",
|
||||
"work-light",
|
||||
"work-medium",
|
||||
"work-high",
|
||||
"work-extreme",
|
||||
}
|
||||
assert get_model_spec("chat-light").provider_model == "ChatGPT-5.6:Luna"
|
||||
assert get_model_spec("work-medium").provider_model == "ChatGPT-5.6:Terra"
|
||||
assert get_model_spec("work-high").provider_model == "ChatGPT-5.6:Sol"
|
||||
extreme = get_model_spec("work-extreme")
|
||||
assert extreme.provider == "deepseek"
|
||||
assert extreme.provider_model == "deepseek-v4-pro"
|
||||
assert extreme.thinking_enabled is True
|
||||
assert extreme.reasoning_effort == "max"
|
||||
assert {item["id"] for item in openai_model_list()["data"]} == set(MODEL_SPECS)
|
||||
|
||||
+44
-1
@@ -21,10 +21,15 @@ def disable_retry_sleep(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def _complete_with_handler(
|
||||
settings: Settings,
|
||||
handler: Callable[[httpx.Request], httpx.Response | Awaitable[httpx.Response]],
|
||||
**kwargs,
|
||||
) -> dict:
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
|
||||
provider = ModelProvider(settings, client)
|
||||
return await provider.complete(model="test-model", messages=[{"role": "user", "content": "hello"}])
|
||||
return await provider.complete(
|
||||
model="test-model",
|
||||
messages=[{"role": "user", "content": "hello"}],
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
async def test_complete_retries_transient_gateway_response(settings: Settings) -> None:
|
||||
@@ -158,3 +163,41 @@ async def test_complete_aggregates_streamed_tool_call(settings: Settings) -> Non
|
||||
}
|
||||
]
|
||||
assert result["usage"]["total_tokens"] == 7
|
||||
|
||||
|
||||
async def test_deepseek_route_enables_thinking_and_uses_separate_credentials(settings: Settings) -> None:
|
||||
body = (
|
||||
'data: {"choices":[{"index":0,"delta":{"role":"assistant",'
|
||||
'"reasoning_content":"private state","tool_calls":[{"index":0,"id":"call-1",'
|
||||
'"type":"function","function":{"name":"ping","arguments":"{}"}}]},'
|
||||
'"finish_reason":"tool_calls"}]}\n\n'
|
||||
"data: [DONE]\n\n"
|
||||
)
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
assert str(request.url) == "https://api.deepseek.example.test/v1/chat/completions"
|
||||
assert request.headers["Authorization"] == "Bearer deepseek-secret"
|
||||
payload = json.loads(request.content)
|
||||
assert payload["model"] == "test-model"
|
||||
assert payload["thinking"] == {"type": "enabled"}
|
||||
assert payload["reasoning_effort"] == "max"
|
||||
assert payload["max_tokens"] == 16_384
|
||||
return httpx.Response(
|
||||
200,
|
||||
request=request,
|
||||
headers={"Content-Type": "text/event-stream"},
|
||||
content=body,
|
||||
)
|
||||
|
||||
result = await _complete_with_handler(
|
||||
settings,
|
||||
handler,
|
||||
provider="deepseek",
|
||||
thinking_enabled=True,
|
||||
reasoning_effort="max",
|
||||
max_output_tokens=16_384,
|
||||
)
|
||||
|
||||
message = result["choices"][0]["message"]
|
||||
assert message["reasoning_content"] == "private state"
|
||||
assert message["tool_calls"][0]["function"]["name"] == "ping"
|
||||
|
||||
@@ -13,6 +13,7 @@ class FakeModelProvider:
|
||||
def __init__(self) -> None:
|
||||
self.completed = []
|
||||
self.forwarded = []
|
||||
self.forward_options = []
|
||||
|
||||
async def complete(self, **kwargs):
|
||||
self.completed.append(kwargs)
|
||||
@@ -21,8 +22,9 @@ class FakeModelProvider:
|
||||
"usage": {"total_tokens": 10},
|
||||
}
|
||||
|
||||
async def forward(self, payload):
|
||||
async def forward(self, payload, **kwargs):
|
||||
self.forwarded.append(payload)
|
||||
self.forward_options.append(kwargs)
|
||||
request = httpx.Request("POST", "https://model.example.test/v1/chat/completions")
|
||||
if payload.get("stream"):
|
||||
return httpx.Response(
|
||||
@@ -83,7 +85,7 @@ def test_model_list_requires_internal_key(settings) -> None:
|
||||
headers={"Authorization": f"Bearer {settings.internal_provider_key}"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()["data"]) == 6
|
||||
assert len(response.json()["data"]) == 8
|
||||
|
||||
|
||||
def test_chat_maps_provider_model_and_work_upgrade_is_one_way(settings, identity_jwt) -> None:
|
||||
@@ -99,6 +101,20 @@ def test_chat_maps_provider_model_and_work_upgrade_is_one_way(settings, identity
|
||||
assert provider.forwarded[0]["model"] == "ChatGPT-5.6:Luna"
|
||||
assert chat.json()["model"] == "chat-light"
|
||||
|
||||
extreme_chat = client.post(
|
||||
"/v1/chat/completions",
|
||||
headers=headers(settings, identity_jwt, "extreme-chat"),
|
||||
json={"model": "chat-extreme", "messages": [{"role": "user", "content": "think"}]},
|
||||
)
|
||||
assert extreme_chat.status_code == 200
|
||||
assert provider.forwarded[-1]["model"] == "deepseek-v4-pro"
|
||||
assert provider.forward_options[-1] == {
|
||||
"provider": "deepseek",
|
||||
"thinking_enabled": True,
|
||||
"reasoning_effort": "max",
|
||||
"max_output_tokens": 16_384,
|
||||
}
|
||||
|
||||
work = client.post(
|
||||
"/v1/chat/completions",
|
||||
headers=headers(settings, identity_jwt),
|
||||
@@ -107,6 +123,16 @@ def test_chat_maps_provider_model_and_work_upgrade_is_one_way(settings, identity
|
||||
assert work.status_code == 200
|
||||
assert provider.completed[-1]["model"] == "ChatGPT-5.6:Sol"
|
||||
|
||||
extreme_work = client.post(
|
||||
"/v1/chat/completions",
|
||||
headers=headers(settings, identity_jwt, "extreme-work"),
|
||||
json={"model": "work-extreme", "messages": [{"role": "user", "content": "do it carefully"}]},
|
||||
)
|
||||
assert extreme_work.status_code == 200
|
||||
assert provider.completed[-1]["model"] == "deepseek-v4-pro"
|
||||
assert provider.completed[-1]["provider"] == "deepseek"
|
||||
assert provider.completed[-1]["reasoning_effort"] == "max"
|
||||
|
||||
downgrade = client.post(
|
||||
"/v1/chat/completions",
|
||||
headers=headers(settings, identity_jwt),
|
||||
|
||||
Reference in New Issue
Block a user