|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +# Copyright (c) Alibaba, Inc. and its affiliates. |
| 3 | +"""SDK identification headers shared by the 1.x API and the 2.0 package. |
| 4 | +
|
| 5 | +``x-dashscope-sdk-client`` tells the backend which client and which |
| 6 | +functional area issued a request, and ``x-dashscope-sdk-session-id`` |
| 7 | +groups every request one interpreter process makes. |
| 8 | +
|
| 9 | +The client segment is process-wide *mutable* state: entry points that |
| 10 | +embed the SDK overwrite it (the ``dashscope`` CLI marks itself as |
| 11 | +``python-cli`` via ``set_sdk_client``), and every caller in both stacks |
| 12 | +must see the same value. Duplicating it would silently drop the override |
| 13 | +for whichever stack held the copy, so it lives here -- one source of |
| 14 | +truth, same reason as the session id in ``_shared._sdk_session``. |
| 15 | +
|
| 16 | +The 2.0 transport builds its own header in |
| 17 | +``dashscope.v2.transport._ident`` because it appends a trailing |
| 18 | +generation marker (``aigc`` -> ``aigc2``) that this unmarked 1.x shape |
| 19 | +must not carry. Sub-SDKs that predate the marker -- AgentStudio, whose |
| 20 | +``agentstudio`` tag the backend already reports on -- keep using this |
| 21 | +module. |
| 22 | +
|
| 23 | +``get_user_agent`` belongs here for the same reason as the two headers: |
| 24 | +it is the other half of the request identity, and it must agree with |
| 25 | +``get_sdk_headers`` on the version segment. It is the *1.x shape* |
| 26 | +(``dashscope/<version>; python/...; platform/...; processor/...``) and is |
| 27 | +deliberately not shared with the 2.0 transport's own |
| 28 | +``dashscope.v2.transport.http.default_user_agent()`` |
| 29 | +(``dashscope-v2-python/<version>``). Those are two distinct backend |
| 30 | +contracts, not two spellings of one value, so they must not be merged. |
| 31 | +
|
| 32 | +Only the stdlib is imported at module level; ``dashscope.version`` is |
| 33 | +pulled in lazily and defensively so this leaf stays below both stacks. |
| 34 | +""" |
| 35 | + |
| 36 | +import os |
| 37 | +import platform |
| 38 | +from typing import Dict |
| 39 | + |
| 40 | +from dashscope._shared._sdk_session import sdk_session_id |
| 41 | + |
| 42 | +_SDK_CLIENT = "python-sdk" |
| 43 | + |
| 44 | +# Shared with the 2.0 transport so one process reports one |
| 45 | +# x-dashscope-sdk-session-id across both stacks (see _shared._sdk_session; |
| 46 | +# acli deliberately keeps its own, it is a one-way sync copy). |
| 47 | +_SDK_SESSION_ID = sdk_session_id() |
| 48 | + |
| 49 | +__all__ = ["get_sdk_headers", "get_user_agent", "set_sdk_client"] |
| 50 | + |
| 51 | + |
| 52 | +def _sdk_version() -> str: |
| 53 | + try: |
| 54 | + from dashscope.version import __version__ |
| 55 | + except Exception: # pragma: no cover - defensive import guard |
| 56 | + return "0.0.0" |
| 57 | + return __version__ |
| 58 | + |
| 59 | + |
| 60 | +# Read once at import so the value is stable for the process lifetime. |
| 61 | +_VERSION = _sdk_version() |
| 62 | + |
| 63 | + |
| 64 | +def set_sdk_client(client: str) -> None: |
| 65 | + """Override the client identifier in the x-dashscope-sdk-client header. |
| 66 | +
|
| 67 | + Process-wide, used by entry points that embed the SDK (e.g. the |
| 68 | + ``dashscope`` CLI marks itself as ``python-cli``). |
| 69 | + """ |
| 70 | + global _SDK_CLIENT # pylint: disable=global-statement |
| 71 | + if client: |
| 72 | + _SDK_CLIENT = client |
| 73 | + |
| 74 | + |
| 75 | +def get_sdk_headers(module: str = "") -> Dict[str, str]: |
| 76 | + """Structured SDK identification headers for backend statistics. |
| 77 | +
|
| 78 | + Set DASHSCOPE_DISABLE_SDK_HEADERS=1 to omit them. |
| 79 | +
|
| 80 | + Args: |
| 81 | + module: Optional module name (e.g. agentstudio, rl, app). |
| 82 | + """ |
| 83 | + if os.environ.get("DASHSCOPE_DISABLE_SDK_HEADERS"): |
| 84 | + return {} |
| 85 | + parts = [_SDK_CLIENT, _VERSION] |
| 86 | + if module: |
| 87 | + parts.append(module) |
| 88 | + return { |
| 89 | + "x-dashscope-sdk-client": "/".join(parts), |
| 90 | + "x-dashscope-sdk-session-id": _SDK_SESSION_ID, |
| 91 | + } |
| 92 | + |
| 93 | + |
| 94 | +def get_user_agent() -> str: |
| 95 | + """The 1.x-shaped ``user-agent`` value. |
| 96 | +
|
| 97 | + Kept byte-identical to the pre-move implementation: the version |
| 98 | + segment is ``_VERSION`` here rather than a second read of |
| 99 | + ``dashscope.version``, so it can never drift from the one in |
| 100 | + ``get_sdk_headers``. |
| 101 | + """ |
| 102 | + try: |
| 103 | + platform_ = ( |
| 104 | + platform.platform().replace("\n", "").replace("\r", "").strip() |
| 105 | + ) |
| 106 | + except Exception: |
| 107 | + platform_ = "unknown" |
| 108 | + |
| 109 | + try: |
| 110 | + processor_ = ( |
| 111 | + platform.processor().replace("\n", "").replace("\r", "").strip() |
| 112 | + ) |
| 113 | + except Exception: |
| 114 | + processor_ = "unknown" |
| 115 | + |
| 116 | + ua = ( |
| 117 | + f"dashscope/{_VERSION}; python/{platform.python_version()}; " |
| 118 | + f"platform/{platform_}; " |
| 119 | + f"processor/{processor_}" |
| 120 | + ) |
| 121 | + return ua |
0 commit comments