Skip to content

Commit

Permalink
feat: Version mismatch checks (#3989)
Browse files Browse the repository at this point in the history
* Send server version in response headers and warn if clients diverge

* Fix logic for only showing minor version mismatch warnings once

* Move clients into utilities to avoid circular imports

* Don't call `close` in httpx client wrappers

* Ensure version warnings work for suffixes and missing server versions

* Do not await sync function
  • Loading branch information
anticorrelator authored Jul 25, 2024
1 parent 91f96ef commit 8454183
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 3 deletions.
5 changes: 3 additions & 2 deletions src/phoenix/experiments/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,16 @@
)
from phoenix.experiments.utils import get_dataset_experiments_url, get_experiment_url, get_func_name
from phoenix.trace.attributes import flatten
from phoenix.utilities.client import VersionedAsyncClient, VersionedClient
from phoenix.utilities.json import jsonify


def _phoenix_clients() -> Tuple[httpx.Client, httpx.AsyncClient]:
headers = get_env_client_headers()
return httpx.Client(
return VersionedClient(
base_url=get_base_url(),
headers=headers,
), httpx.AsyncClient(
), VersionedAsyncClient(
base_url=get_base_url(),
headers=headers,
)
Expand Down
4 changes: 4 additions & 0 deletions src/phoenix/server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
from phoenix.server.grpc_server import GrpcServer
from phoenix.server.telemetry import initialize_opentelemetry_tracer_provider
from phoenix.trace.schemas import Span
from phoenix.utilities.client import PHOENIX_SERVER_VERSION_HEADER

if TYPE_CHECKING:
from opentelemetry.trace import TracerProvider
Expand Down Expand Up @@ -168,8 +169,11 @@ async def dispatch(
request: Request,
call_next: RequestResponseEndpoint,
) -> Response:
from phoenix import __version__ as phoenix_version

response = await call_next(request)
response.headers["x-colab-notebook-cache-control"] = "no-cache"
response.headers[PHOENIX_SERVER_VERSION_HEADER] = phoenix_version
return response


Expand Down
3 changes: 2 additions & 1 deletion src/phoenix/session/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from phoenix.trace import Evaluations, TraceDataset
from phoenix.trace.dsl import SpanQuery
from phoenix.trace.otel import encode_span_to_otlp
from phoenix.utilities.client import VersionedClient

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -92,7 +93,7 @@ def __init__(
host = "127.0.0.1"
base_url = endpoint or get_env_collector_endpoint() or f"http://{host}:{get_env_port()}"
self._base_url = base_url if base_url.endswith("/") else base_url + "/"
self._client = httpx.Client(headers=headers)
self._client = VersionedClient(headers=headers)
weakref.finalize(self, self._client.close)
if warn_if_server_not_running:
self._warn_if_phoenix_is_not_running()
Expand Down
116 changes: 116 additions & 0 deletions src/phoenix/utilities/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import warnings
from typing import Any

import httpx

PHOENIX_SERVER_VERSION_HEADER = "x-phoenix-server-version"


class VersionedClient(httpx.Client):
"""
A httpx.Client wrapper that warns if there is a server/client version mismatch.
"""

def __init__(self, *args: Any, **kwargs: Any):
from phoenix import __version__ as phoenix_version

super().__init__(*args, **kwargs)
self._client_phoenix_version = phoenix_version
self._warned_on_minor_version_mismatch = False

def _check_version(self, response: httpx.Response) -> None:
server_version = response.headers.get(PHOENIX_SERVER_VERSION_HEADER)

if server_version is None:
warnings.warn(
"The Phoenix server has an unknown version and may have compatibility issues."
)
return

try:
client_major, client_minor, client_patch = map(
int, self._client_phoenix_version.split(".")
)
server_major, server_minor, server_patch = map(int, server_version.split("."))
if abs(server_major - client_major) >= 1:
warnings.warn(
f"⚠️⚠️ The Phoenix server ({server_version}) and client "
f"({self._client_phoenix_version}) versions are severely mismatched. Upgrade "
" either the client or server to ensure API compatibility ⚠️⚠️"
)
elif (
abs(server_minor - client_minor) >= 1 and not self._warned_on_minor_version_mismatch
):
self._warned_on_minor_version_mismatch = True
warnings.warn(
f"The Phoenix server ({server_version}) and client "
f"({self._client_phoenix_version}) versions are mismatched and may have "
"compatibility issues."
)
except ValueError:
# if either the client or server version includes a suffix e.g. "rc1", check for an
# exact version match of the version string
if self._client_phoenix_version != server_version:
warnings.warn(
f"The Phoenix server ({server_version}) and client "
f"({self._client_phoenix_version}) versions are mismatched and may have "
"compatibility issues."
)

def request(self, *args: Any, **kwargs: Any) -> httpx.Response:
response = super().request(*args, **kwargs)
self._check_version(response)
return response


class VersionedAsyncClient(httpx.AsyncClient):
"""
A httpx.Client wrapper that warns if there is a server/client version mismatch.
"""

def __init__(self, *args: Any, **kwargs: Any):
from phoenix import __version__ as phoenix_version

super().__init__(*args, **kwargs)
self._client_phoenix_version = phoenix_version
self._warned_on_minor_version_mismatch = False

def _check_version(self, response: httpx.Response) -> None:
server_version = response.headers.get(PHOENIX_SERVER_VERSION_HEADER)

if server_version is None:
return

try:
client_major, client_minor, client_patch = map(
int, self._client_phoenix_version.split(".")
)
server_major, server_minor, server_patch = map(int, server_version.split("."))
if abs(server_major - client_major) >= 1:
warnings.warn(
f"⚠️⚠️ The Phoenix server ({server_version}) and client "
f"({self._client_phoenix_version}) versions are severely mismatched. Upgrade "
" either the client or server to ensure API compatibility ⚠️⚠️"
)
elif (
abs(server_minor - client_minor) >= 1 and not self._warned_on_minor_version_mismatch
):
self._warned_on_minor_version_mismatch = True
warnings.warn(
f"The Phoenix server ({server_version}) and client "
f"({self._client_phoenix_version}) versions are mismatched and may have "
"compatibility issues."
)
except ValueError:
# if the version includes a suffix e.g. "rc1", check for an exact version match
if self._client_phoenix_version != server_version:
warnings.warn(
f"The Phoenix server ({server_version}) and client "
f"({self._client_phoenix_version}) versions are mismatched and may have "
"compatibility issues."
)

async def request(self, *args: Any, **kwargs: Any) -> httpx.Response:
response = await super().request(*args, **kwargs)
self._check_version(response)
return response

0 comments on commit 8454183

Please sign in to comment.