Two open atoms of #83390 (DeepSeek "This response_format type is unavailable now"): * `_call_fallback_candidate_sync/_async` only special-cased auth errors, so when the primary aux provider failed (timeout, rate limit, payment) and the fallback landed on a provider that rejects `json_schema`, the 400 re-raised and the whole task died — the primary-path rung from #89589 never applied there. Both fallback paths now retry once without `response_format`. * Every structured aux call (titles, kanban decomposer, goal judge, plugin structured calls) paid a guaranteed-fail request on providers that lack `json_schema` before the retry. A provider profile can now declare `unsupported_response_formats` (DeepSeek: json_schema, per https://api-docs.deepseek.com/guides/json_mode) and the recovery ladder remembers any route that rejected a type once (host:port scoped), so `_build_call_kwargs` — shared by the primary and fallback paths — omits the field before the first request. Dropping rather than downgrading to json_object matches the end state the retry already produced; json_object needs a JSON-mentioning prompt and some relays return empty content under it. New logic lives in agent/auxiliary_structured_output.py; the facade only gains the fallback rung next to the predicate it uses. tests/agent/conftest.py resets the process-level memo per test. Fixes #83390, #105191. Closes duplicates #84976, #88830, #102849, #113064. Co-authored-by: Legion-is-life <Legion-is-life@users.noreply.github.com>
57 lines
2.8 KiB
Python
57 lines
2.8 KiB
Python
"""DeepSeek provider profile.
|
|
|
|
V4 defaults to thinking ON when ``extra_body.thinking`` is unset, and then
|
|
requires ``reasoning_content`` to be echoed back on later turns (HTTP 400 after
|
|
the first tool call otherwise). This profile sets ``thinking`` explicitly and
|
|
maps effort onto DeepSeek's ``reasoning_effort``; V3 models are left untouched.
|
|
Retired ``deepseek-chat``/``deepseek-reasoner`` IDs are remapped in
|
|
``hermes_cli.model_normalize`` before reaching here.
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
from agent.reasoning_effort import DEEPSEEK_V4_EFFORTS, DEEPSEEK_V4_OVERRIDES, thinking_toggle_extras
|
|
from providers import register_provider
|
|
from providers.base import ProviderProfile
|
|
|
|
|
|
# Version-less canonical ids for thinking-capable DeepSeek models. The 2026-09 Flash
|
|
# refresh dropped the ``v<N>`` marker from the public id: ``GET /v1/models`` reports
|
|
# ``deepseek-flash`` and the API accepts it directly, so the generation check in
|
|
# ``build_api_kwargs_extras`` cannot recognise it.
|
|
_THINKING_CAPABLE_IDS: frozenset[str] = frozenset({"deepseek-flash"})
|
|
|
|
|
|
class DeepSeekProfile(ProviderProfile):
|
|
"""DeepSeek — extra_body.thinking + top-level reasoning_effort."""
|
|
|
|
def build_api_kwargs_extras(
|
|
self, *, reasoning_config: dict | None = None, model: str | None = None, **context
|
|
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
m = (model or "").strip().lower()
|
|
# v4+ only; v3 excluded. Version-less canonicals (``deepseek-flash``) carry the
|
|
# same thinking-mode contract but no ``v<N>`` prefix, so consult the id set too —
|
|
# missing them makes Hermes omit ``thinking``, so the server defaults to on and
|
|
# the user's thinking toggle / effort setting is silently ignored.
|
|
versioned_v4_plus = m.startswith("deepseek-v") and not m.startswith("deepseek-v3")
|
|
if not versioned_v4_plus and m not in _THINKING_CAPABLE_IDS:
|
|
return {}, {}
|
|
# Always set thinking explicitly (default enabled, matching the API default)
|
|
# to avoid the reasoning_content echo trap on subsequent turns.
|
|
return thinking_toggle_extras(
|
|
reasoning_config, DEEPSEEK_V4_EFFORTS, DEEPSEEK_V4_OVERRIDES, always_emit_toggle=True
|
|
)
|
|
|
|
|
|
deepseek = DeepSeekProfile(
|
|
name="deepseek", aliases=("deepseek-chat", "deep-seek"), env_vars=("DEEPSEEK_API_KEY",), display_name="DeepSeek",
|
|
description="DeepSeek — native DeepSeek API", signup_url="https://platform.deepseek.com/",
|
|
fallback_models=("deepseek-v4-pro", "deepseek-flash"), base_url="https://api.deepseek.com/v1",
|
|
default_aux_model="deepseek-flash",
|
|
# Native API implements only ``json_object`` (https://api-docs.deepseek.com/guides/json_mode);
|
|
# ``json_schema`` is a guaranteed HTTP 400 "This response_format type is unavailable now".
|
|
unsupported_response_formats=("json_schema",),
|
|
)
|
|
|
|
register_provider(deepseek)
|