Source code for zett_agent.providers.deepseek

from __future__ import annotations

from collections.abc import Mapping
from typing import TYPE_CHECKING

from ..json_types import JsonValue
from ..model import (
    DEFAULT_RETRY_OPTIONS,
    DEFAULT_TIMEOUT_OPTIONS,
    ModelRequest,
    ReasoningEffort,
    RetryOptions,
    TimeoutOptions,
)
from .anthropic_compatible import DEFAULT_MAX_TOKENS, AnthropicCompatibleProvider
from .openai_compatible import OpenAICompatibleProvider
from .wire import JsonObject

if TYPE_CHECKING:
    import httpx


[docs] class DeepSeekProvider(OpenAICompatibleProvider): """Stream DeepSeek through its OpenAI-compatible protocol. Args: model: DeepSeek model identifier accepted by the endpoint. api_key: API credential supplied by the application. transport: Optional HTTPX transport for custom routing or tests. base_url: API root; None uses the default DeepSeek v1 endpoint. temperature: Optional provider sampling temperature. response: Use DeepSeek's Responses-compatible endpoint instead of Chat Completions. retry: Model-owned retry/backoff policy; never restarts an emitted stream. timeout: How long the provider may stay silent before the request is given up on: connecting, waiting for the response headers, and between chunks of a response that has started. See :class:`~zett_agent.model.TimeoutOptions`; a timeout raises :class:`~zett_agent.providers.base.ProviderTimeoutError` and is not retried. send_prompt_cache_key: Declare the run's prompt cache key on every request. Off by default: DeepSeek caches context automatically and its compatibility endpoint does not document the field. Enable it for gateways that route on the key. extra_headers: HTTP headers sent on every request after the adapter's own, for gateways that route or bill on a header. A header given here replaces the SDK default of the same name. extra_query: URL query parameters sent on every request, such as the ``api-version`` an Azure-style endpoint requires. extra_body: JSON fields merged into every request body last, so they add a vendor option the adapter does not model and may override one it does, such as ``thinking``. Note: Cache-hit counters are normalized into ModelUsage. Reasoning deltas are emitted only when returned by the provider. Returned reasoning is retained in AssistantMessage and replayed as reasoning_content in later requests, which is required by DeepSeek thinking mode. Close with aclose(). Examples: Select the model explicitly rather than inferring it from the key:: model = DeepSeekProvider( model="deepseek-chat", api_key=os.environ["DEEPSEEK_API_KEY"], ) try: client = await create_agent(model) print((await client.run("Explain this function")).content) finally: await model.aclose() .. note:: Reasoning effort is mapped only for models whose endpoint supports the corresponding request fields. A selected effort does not guarantee that the server will return reasoning deltas. .. seealso:: :class:`~zett_agent.model.ReasoningEffort` lists provider-neutral levels, and :doc:`/learn/providers` covers shared provider lifecycle rules. """ def __init__( self, model: str, api_key: str, transport: httpx.AsyncBaseTransport | None = None, *, base_url: str | None = None, temperature: float | None = None, response: bool = False, retry: RetryOptions = DEFAULT_RETRY_OPTIONS, send_prompt_cache_key: bool = False, extra_headers: Mapping[str, str] | None = None, extra_query: Mapping[str, str] | None = None, extra_body: Mapping[str, JsonValue] | None = None, timeout: TimeoutOptions = DEFAULT_TIMEOUT_OPTIONS, ) -> None: super().__init__( model=model, api_key=api_key, base_url=base_url or ("https://api.deepseek.com" if response else "https://api.deepseek.com/v1"), transport=transport, temperature=temperature, response=response, retry=retry, send_prompt_cache_key=send_prompt_cache_key, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, ) self.provider_name = "deepseek"
[docs] def request_fields(self, request: ModelRequest) -> JsonObject: """Switch thinking on or off, and give the effort to the models that take one.""" if request.reasoning_effort == ReasoningEffort.OFF: return {"thinking": {"type": "disabled"}} fields: JsonObject = {"thinking": {"type": "enabled"}} if self.model.startswith("deepseek-v4"): fields["reasoning_effort"] = _v4_effort(request.reasoning_effort) return fields
def _v4_effort(effort: ReasoningEffort) -> str: """Map a provider-neutral level onto the three the V4 models know.""" match effort: case ReasoningEffort.MINIMAL | ReasoningEffort.LOW: return "low" case ReasoningEffort.XHIGH | ReasoningEffort.MAX | ReasoningEffort.ULTRA: return "max" case _: return "high"
[docs] class DeepSeekAnthropicProvider(AnthropicCompatibleProvider): """Stream DeepSeek through its Anthropic Messages protocol. Use it for a tool that speaks Messages, or to keep a conversation on the protocol the rest of the application uses. :class:`DeepSeekProvider` is the same service over Chat Completions. Args: model: DeepSeek model identifier, such as ``deepseek-v4-pro``. A name that starts with ``claude`` is accepted by the service and mapped to a DeepSeek model; any other name that is not DeepSeek's is a 400. api_key: API credential supplied by the application. transport: Optional HTTPX transport for custom routing or tests. base_url: API root; None uses ``https://api.deepseek.com/anthropic``. retry: Model-owned retry/backoff policy; never restarts an emitted stream. timeout: How long the provider may stay silent before the request is given up on. See :class:`~zett_agent.model.TimeoutOptions`. max_tokens: Output budget for one response. extra_headers: HTTP headers sent on every request after the adapter's own. extra_query: URL query parameters sent on every request. extra_body: JSON fields merged into every request body last. Note: DeepSeek ignores ``budget_tokens`` and ``cache_control``, so neither is sent. Thinking is switched on or off with ``thinking``, and the level goes in ``output_config.effort`` for the V4 models. Thinking blocks are sent back unchanged. Redacted thinking, documents and search results are not supported by the service. Examples: Stream a DeepSeek model over Messages:: model = DeepSeekAnthropicProvider( model="deepseek-v4-pro", api_key=os.environ["DEEPSEEK_API_KEY"], ) try: client = await create_agent(model) print((await client.run("Explain this function")).content) finally: await model.aclose() """ provider_name = "deepseek-anthropic" def __init__( self, model: str, api_key: str, transport: httpx.AsyncBaseTransport | None = None, *, base_url: str | None = None, retry: RetryOptions = DEFAULT_RETRY_OPTIONS, timeout: TimeoutOptions = DEFAULT_TIMEOUT_OPTIONS, max_tokens: int = DEFAULT_MAX_TOKENS, extra_headers: Mapping[str, str] | None = None, extra_query: Mapping[str, str] | None = None, extra_body: Mapping[str, JsonValue] | None = None, ) -> None: super().__init__( model=model, api_key=api_key, base_url=base_url or "https://api.deepseek.com/anthropic", transport=transport, retry=retry, timeout=timeout, max_tokens=max_tokens, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, )
[docs] def request_fields(self, request: ModelRequest) -> JsonObject: """Switch thinking on or off, without a token budget, and give the V4 models their level.""" if request.reasoning_effort == ReasoningEffort.OFF: return {"thinking": {"type": "disabled"}} fields: JsonObject = {"thinking": {"type": "enabled"}} if self.model.startswith("deepseek-v4"): fields["output_config"] = {"effort": _v4_effort(request.reasoning_effort)} return fields