diff --git a/Makefile b/Makefile index 731c625..13a2429 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,27 @@ -PORT = 5001 -IMAGE_NAME = ai-dial-analytics-realtime -ARGS = +PORT ?= 5001 +IMAGE_NAME ?= ai-dial-analytics-realtime +DEV_PYTHON ?= 3.10 +ARGS ?= -.PHONY: all build serve docker_build docker_serve lint format test test_all docs clean help +.PHONY: all install build serve docker_build docker_serve lint format test test_all docs clean help all: build +install: + poetry env use python$(DEV_PYTHON) + poetry install + + build: poetry build serve: poetry install --only main - poetry run uvicorn aidial_analytics_realtime.app:app --port=$(PORT) --env-file .env + poetry run uvicorn aidial_analytics_realtime.app:app --port=$(PORT) --reload --env-file .env docker_build: diff --git a/README.md b/README.md index 69ab4b0..c5edd8f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Realtime analytics server for [AI DIAL](https://epam-rail.com). The service cons Refer to [Documentation](https://github.com/epam/ai-dial/blob/main/docs/tutorials/realtime-analytics.md) to learn how to configure AI DAL Core and other necessary components. -# Usage +## Usage Check the [AI DIAL Core](https://github.com/epam/ai-dial-core) documentation to configure the way to send the logs to the instance of the realtime analytics server. @@ -25,17 +25,18 @@ The realtime analytics server analyzes the logs stream in the realtime and write |user_hash| The unique hash for the user. | |price| The calculated price of the request. | |number_request_messages| The total number of messages in history for this request. | -|chat_id| The unique ID of this convestation. | +|chat_id| The unique ID of this conversation. | |prompt_tokens| The number of tokens in the prompt including conversation history and the current message | |completion_tokens| The number of completion tokens generated for this request | - -# Configuration +## Configuration Copy `.env.example` to `.env` and customize it for your environment. ### Connection to the InfluxDB + You need to specify the connection options to the InfluxDB instance using the environment variables: + |Variable|Description| |---|---| |INFLUX_URL|Url to the InfluxDB to write the analytics data | @@ -46,6 +47,7 @@ You need to specify the connection options to the InfluxDB instance using the en You can follow the [InfluxDB documentation](https://docs.influxdata.com/influxdb/v2/get-started/) to setup InfluxDB locally and acquire the required configuration parameters. ### Other configuration + Also, following environment valuables can be used to configure the service behavior: |Variable|Default|Description| @@ -53,6 +55,7 @@ Also, following environment valuables can be used to configure the service behav |MODEL_RATES| {} | Specifies per-token price rates for models in JSON format| |TOPIC_MODEL| ./topic_model | Specifies the name or path for the topic model. If the model is specified by name, it will be downloaded from, the [Huggingface]( https://huggingface.co/).| |TOPIC_EMBEDDINGS_MODEL| None | Specifies the name or path for the embeddings model used with the topic model. If the model is specified by name, it will be downloaded from, the [Huggingface]( https://huggingface.co/). If None, the name will be used from the topic model config.| +|LOG_LEVEL|INFO|Log level. Use DEBUG for dev purposes and INFO in prod| Example of the MODEL_RATES configuration: ```json @@ -84,29 +87,28 @@ Example of the MODEL_RATES configuration: } ``` +## Developer environment -______ -# Developer environment - -This project uses [Python>=3.11](https://www.python.org/downloads/) and [Poetry>=1.6.1](https://python-poetry.org/) as a dependency manager. +This project uses [Python>=3.11](https://www.python.org/downloads/) and [Poetry>=1.6.1](https://python-poetry.org/) as a dependency manager. Check out Poetry's [documentation on how to install it](https://python-poetry.org/docs/#installation) on your system before proceeding. To install requirements: -``` +```sh poetry install ``` This will install all requirements for running the package, linting, formatting and tests. -# Build +## Build To build the wheel packages run: + ```sh make build ``` -# Run +## Run To run the development server locally run: @@ -116,21 +118,23 @@ make serve The server will be running as http://localhost:5001 -# Docker +## Docker To build the docker image run: + ```sh make docker_build ``` To run the server locally from the docker image run: + ```sh make docker_serve ``` The server will be running as http://localhost:5001 -# Lint +## Lint Run the linting before committing: @@ -144,7 +148,7 @@ To auto-fix formatting issues run: make format ``` -# Test +## Test Run unit tests locally: @@ -152,7 +156,7 @@ Run unit tests locally: make test ``` -# Clean +## Clean To remove the virtual environment and build artifacts: diff --git a/aidial_analytics_realtime/analytics.py b/aidial_analytics_realtime/analytics.py index 9ecda3f..b54abb2 100644 --- a/aidial_analytics_realtime/analytics.py +++ b/aidial_analytics_realtime/analytics.py @@ -1,8 +1,8 @@ +import logging from datetime import datetime from decimal import Decimal from enum import Enum from logging import Logger -from typing import Awaitable, Callable from uuid import uuid4 from influxdb_client import Point @@ -14,8 +14,12 @@ get_chat_completion_response_contents, get_embeddings_request_contents, ) +from aidial_analytics_realtime.influx_writer import InfluxWriterAsync from aidial_analytics_realtime.rates import RatesCalculator from aidial_analytics_realtime.topic_model import TopicModel +from aidial_analytics_realtime.utils.concurrency import make_async +from aidial_analytics_realtime.utils.log_config import with_prefix +from aidial_analytics_realtime.utils.timer import Timer identifier = LanguageIdentifier.from_modelstring(model, norm_probs=True) @@ -25,7 +29,7 @@ class RequestType(Enum): EMBEDDING = 2 -def detect_lang( +async def detect_lang( logger: Logger, request: dict, response: dict, request_type: RequestType ) -> str: match request_type: @@ -42,20 +46,29 @@ def detect_lang( case _: assert_never(request_type) - return to_string(detect_lang_by_text(text)) + return to_string(await detect_lang_by_text(logger, text)) -def detect_lang_by_text(text: str) -> str | None: +async def detect_lang_by_text(logger: logging.Logger, text: str) -> str | None: text = text.strip() if not text: return None + logger = with_prefix(logger, "[langid]") + try: - lang, prob = identifier.classify(text) + + def _task(text: str): + with Timer(logger.info): + return identifier.classify(text) + + lang, prob = await make_async(_task, text) + if prob > 0.998: return lang - except Exception: + except Exception as e: + logger.error(f"error: {str(e)}") pass return None @@ -69,7 +82,7 @@ def build_execution_path(path: list | None): return "undefined" if not path else "/".join(map(to_string, path)) -def make_point( +async def make_point( logger: Logger, deployment: str, model: str, @@ -105,16 +118,21 @@ def make_point( response_content = "\n".join(response_contents) if chat_id: - topic = topic_model.get_topic_by_text( - "\n\n".join(request_contents + response_contents) + topic = to_string( + await topic_model.get_topic_by_text( + logger, + "\n\n".join(request_contents + response_contents), + ) ) case RequestType.EMBEDDING: request_contents = get_embeddings_request_contents(logger, request) request_content = "\n".join(request_contents) if chat_id: - topic = topic_model.get_topic_by_text( - "\n\n".join(request_contents) + topic = to_string( + await topic_model.get_topic_by_text( + logger, "\n\n".join(request_contents) + ) ) case _: assert_never(request_type) @@ -156,7 +174,7 @@ def make_point( ( "undefined" if not chat_id - else detect_lang(logger, request, response, request_type) + else await detect_lang(logger, request, response, request_type) ), ) .tag("upstream", to_string(upstream_url)) @@ -232,7 +250,7 @@ def make_rate_point( return point -async def parse_usage_per_model(response: dict): +def parse_usage_per_model(response: dict) -> list[dict]: statistics = response.get("statistics") if statistics is None: return [] @@ -249,7 +267,7 @@ async def parse_usage_per_model(response: dict): async def on_message( logger: Logger, - influx_writer: Callable[[Point], Awaitable[None]], + influx_writer: InfluxWriterAsync, deployment: str, model: str, project_id: str, @@ -268,11 +286,9 @@ async def on_message( trace: dict | None, execution_path: list | None, ): - logger.info(f"Chat completion response length {len(response)}") - - usage_per_model = await parse_usage_per_model(response) + usage_per_model = parse_usage_per_model(response) if token_usage is not None: - point = make_point( + point = await make_point( logger, deployment, model, @@ -292,9 +308,9 @@ async def on_message( trace, execution_path, ) - await influx_writer(point) + await influx_writer(logger, point) elif len(usage_per_model) == 0: - point = make_point( + point = await make_point( logger, deployment, model, @@ -314,9 +330,9 @@ async def on_message( trace, execution_path, ) - await influx_writer(point) + await influx_writer(logger, point) else: - point = make_point( + point = await make_point( logger, deployment, model, @@ -336,10 +352,10 @@ async def on_message( trace, execution_path, ) - await influx_writer(point) + await influx_writer(logger, point) for usage in usage_per_model: - point = make_point( + point = await make_point( logger, deployment, usage["model"], @@ -359,4 +375,4 @@ async def on_message( trace, execution_path, ) - await influx_writer(point) + await influx_writer(logger, point) diff --git a/aidial_analytics_realtime/app.py b/aidial_analytics_realtime/app.py index b36426a..f954b64 100644 --- a/aidial_analytics_realtime/app.py +++ b/aidial_analytics_realtime/app.py @@ -1,10 +1,15 @@ +import asyncio import contextlib import json import logging import re +import uuid from datetime import datetime +import aiohttp +import starlette.requests import uvicorn +from aidial_sdk.telemetry.init import TelemetryConfig, init_telemetry from fastapi import Depends, FastAPI, Request from fastapi.responses import JSONResponse @@ -21,7 +26,13 @@ from aidial_analytics_realtime.time import parse_time from aidial_analytics_realtime.topic_model import TopicModel from aidial_analytics_realtime.universal_api_utils import merge -from aidial_analytics_realtime.utils.log_config import configure_loggers, logger +from aidial_analytics_realtime.utils.concurrency import cpu_task_executor +from aidial_analytics_realtime.utils.log_config import ( + app_logger, + configure_loggers, + with_prefix, +) +from aidial_analytics_realtime.utils.timer import Timer RATE_PATTERN = r"/v1/(.+?)/rate" CHAT_COMPLETION_PATTERN = r"/openai/deployments/(.+?)/chat/completions" @@ -31,24 +42,28 @@ @contextlib.asynccontextmanager async def lifespan(app: FastAPI): influx_client, influx_writer = create_influx_writer() - async with influx_client: - app.dependency_overrides[InfluxWriterAsync] = lambda: influx_writer + with cpu_task_executor: + async with influx_client: + app.dependency_overrides[InfluxWriterAsync] = lambda: influx_writer - topic_model = TopicModel() - app.dependency_overrides[TopicModel] = lambda: topic_model + topic_model = TopicModel() + app.dependency_overrides[TopicModel] = lambda: topic_model - rates_calculator = RatesCalculator() - app.dependency_overrides[RatesCalculator] = lambda: rates_calculator + rates_calculator = RatesCalculator() + app.dependency_overrides[RatesCalculator] = lambda: rates_calculator - yield + yield app = FastAPI(lifespan=lifespan) +init_telemetry(app, TelemetryConfig()) + configure_loggers() async def on_rate_message( + logger: logging.Logger, deployment: str, project_id: str, chat_id: str, @@ -59,7 +74,7 @@ async def on_rate_message( response: dict, influx_writer: InfluxWriterAsync, ): - logger.info(f"Rate message length {len(request) + len(response)}") + app_logger.info(f"Rate message length {len(request) + len(response)}") request_body = json.loads(request["body"]) point = make_rate_point( deployment, @@ -70,10 +85,11 @@ async def on_rate_message( timestamp, request_body, ) - await influx_writer(point) + await influx_writer(logger, point) async def on_chat_completion_message( + logger: logging.Logger, deployment: str, project_id: str, chat_id: str, @@ -95,10 +111,9 @@ async def on_chat_completion_message( return request_body = json.loads(request["body"]) - stream = request_body.get("stream", False) - model = request_body.get("model", deployment) + stream = bool(request_body.get("stream")) + model = request_body.get("model") or deployment - response_body = None if stream: body = response["body"] chunks = body.split("\n\ndata: ") @@ -149,6 +164,7 @@ async def on_chat_completion_message( async def on_embedding_message( + logger: logging.Logger, deployment: str, project_id: str, chat_id: str, @@ -193,6 +209,7 @@ async def on_embedding_message( async def on_log_message( + logger: logging.Logger, message: dict, influx_writer: InfluxWriterAsync, topic_model: TopicModel, @@ -205,20 +222,19 @@ async def on_log_message( chat_id = message["chat"]["id"] user_hash = message["user"]["id"] user_title = message["user"]["title"] - upstream_url = ( - response["upstream_uri"] if "upstream_uri" in response else "" - ) + upstream_url = response.get("upstream_uri") or "" timestamp = parse_time(request["time"]) - token_usage = message.get("token_usage", None) - trace = message.get("trace", None) - parent_deployment = message.get("parent_deployment", None) - execution_path = message.get("execution_path", None) - deployment = message.get("deployment", "") + token_usage = message.get("token_usage") + trace = message.get("trace") + parent_deployment = message.get("parent_deployment") + execution_path = message.get("execution_path") + deployment = message.get("deployment") or "" if re.search(RATE_PATTERN, uri): await on_rate_message( + logger, deployment, project_id, chat_id, @@ -232,6 +248,7 @@ async def on_log_message( elif re.search(CHAT_COMPLETION_PATTERN, uri): await on_chat_completion_message( + logger, deployment, project_id, chat_id, @@ -252,6 +269,7 @@ async def on_log_message( elif re.search(EMBEDDING_PATTERN, uri): await on_embedding_message( + logger, deployment, project_id, chat_id, @@ -271,7 +289,7 @@ async def on_log_message( ) else: - logger.warning(f"Unsupported message type: {uri!r}") + app_logger.warning(f"Unsupported message type: {uri!r}") @app.post("/data") @@ -283,26 +301,85 @@ async def on_log_messages( ): data = await request.json() - statuses = [] - for idx, item in enumerate(data): - try: - await on_log_message( - json.loads(item["message"]), - influx_writer, - topic_model, - rates_calculator, - ) - except Exception as e: - logging.exception(f"Error processing message #{idx}") - statuses.append({"status": "error", "error": str(e)}) - else: - statuses.append({"status": "success"}) + n = len(data) + app_logger.info(f"number of messages: {n}") + + statuses: list[dict] = [] + + request_id = str(uuid.uuid4()) + + request_logger = with_prefix(app_logger, f"[{request_id}]") + + async with Timer(request_logger.info): + for i, item in enumerate(data, start=1): + message_logger = with_prefix(request_logger, f"[{i}/{n}]") + message = json.loads(item["message"]) + + time = (message.get("request") or {})["time"] + message_logger.info(f"request.time: {time}") + + async with Timer(message_logger.info): + status = await process_message( + message_logger, + message, + influx_writer, + topic_model, + rates_calculator, + ) + + statuses.append(status) + + if request_logger.isEnabledFor(logging.DEBUG): + request_logger.debug(f"response: {json.dumps(statuses)}") # Returning 200 code even if processing of some messages has failed, # since the log broker that sends the messages may decide to retry the failed requests. return JSONResponse(content=statuses, status_code=200) +async def process_message( + logger: logging.Logger, + message: dict, + influx_writer: InfluxWriterAsync, + topic_model: TopicModel, + rates_calculator: RatesCalculator, +) -> dict: + try: + await on_log_message( + logger, + message, + influx_writer, + topic_model, + rates_calculator, + ) + logger.info("success") + return {"status": "success"} + except starlette.requests.ClientDisconnect as e: + logger.error("disconnect error") + return { + "status": "error", + "error": str(e), + "reason": "client disconnect", + } + except aiohttp.ClientConnectionError as e: + logger.error("connection error") + return { + "status": "error", + "error": str(e), + "reason": "connection error", + } + except asyncio.TimeoutError as e: + logger.error("timed-out") + return { + "status": "error", + "error": str(e), + "reason": "timeout", + } + except Exception as e: + logger.exception("caught exception") + return {"status": "error", "error": str(e)} + + @app.get("/health") def health(): return {"status": "ok"} diff --git a/aidial_analytics_realtime/dial.py b/aidial_analytics_realtime/dial.py index 0f5410b..bf2758b 100644 --- a/aidial_analytics_realtime/dial.py +++ b/aidial_analytics_realtime/dial.py @@ -1,53 +1,72 @@ from logging import Logger -from typing import List +from typing import Iterator, List def get_chat_completion_request_contents( logger: Logger, request: dict ) -> List[str]: - return [ - content - for message in request["messages"] - for content in _get_chat_completion_message_contents(logger, message) - ] + return list(_chat_completion_request_contents(logger, request)) def get_chat_completion_response_contents( logger: Logger, response: dict ) -> List[str]: - message = response["choices"][0]["message"] - return _get_chat_completion_message_contents(logger, message) + return list(_chat_completion_response_contents(logger, response)) def get_embeddings_request_contents(logger: Logger, request: dict) -> List[str]: + return list(_embeddings_request_contents(logger, request)) + + +def _chat_completion_request_contents( + logger: Logger, request: dict +) -> Iterator[str]: + for message in request["messages"]: + yield from _chat_completion_message_contents(logger, message) + + +def _chat_completion_response_contents( + logger: Logger, response: dict +) -> Iterator[str]: + message = response["choices"][0]["message"] + yield from _chat_completion_message_contents(logger, message) + + +def _embeddings_request_contents( + logger: Logger, request: dict +) -> Iterator[str]: inp = request.get("input") if isinstance(inp, str): - return [inp] + yield from _non_empty_string(inp) elif isinstance(inp, list): - return [i for i in inp if isinstance(i, str)] + for i in inp: + if isinstance(i, str): + yield from _non_empty_string(i) else: logger.warning(f"Unexpected type of embeddings input: {type(inp)}") - return [] -def _get_chat_completion_message_contents( +def _chat_completion_message_contents( logger: Logger, message: dict -) -> List[str]: +) -> Iterator[str]: content = message.get("content") if content is None: - return [] + return elif isinstance(content, str): - return [content] + yield from _non_empty_string(content) elif isinstance(content, list): - ret: List[str] = [] for content_part in content: if isinstance(content_part, dict): if content_part.get("type") == "text" and ( - text := content_part.get("content") + text := content_part.get("text") ): - ret.extend(text) - return ret + yield from _non_empty_string(text) else: logger.warning(f"Unexpected message content type: {type(content)}") - return [] + + +def _non_empty_string(value: str) -> Iterator[str]: + value = value.strip() + if value: + yield value diff --git a/aidial_analytics_realtime/influx_writer.py b/aidial_analytics_realtime/influx_writer.py index a1da8a7..5db984d 100644 --- a/aidial_analytics_realtime/influx_writer.py +++ b/aidial_analytics_realtime/influx_writer.py @@ -1,10 +1,14 @@ import os +from logging import Logger from typing import Awaitable, Callable, Tuple from influxdb_client import Point from influxdb_client.client.influxdb_client_async import InfluxDBClientAsync -InfluxWriterAsync = Callable[[Point], Awaitable[None]] +from aidial_analytics_realtime.utils.log_config import with_prefix +from aidial_analytics_realtime.utils.timer import Timer + +InfluxWriterAsync = Callable[[Logger, Point], Awaitable[None]] def create_influx_writer() -> Tuple[InfluxDBClientAsync, InfluxWriterAsync]: @@ -18,7 +22,10 @@ def create_influx_writer() -> Tuple[InfluxDBClientAsync, InfluxWriterAsync]: ) influx_write_api = client.write_api() - async def influx_writer_impl(record: Point): - await influx_write_api.write(bucket=influx_bucket, record=record) + async def influx_writer_impl(logger: Logger, record: Point): + logger = with_prefix(logger, "[influx]") + with Timer(logger.info): + await influx_write_api.write(bucket=influx_bucket, record=record) + logger.debug(f"record: {str(record)}") return client, influx_writer_impl diff --git a/aidial_analytics_realtime/topic_model.py b/aidial_analytics_realtime/topic_model.py index b0d6c22..dd03b01 100644 --- a/aidial_analytics_realtime/topic_model.py +++ b/aidial_analytics_realtime/topic_model.py @@ -1,7 +1,12 @@ +import logging import os from bertopic import BERTopic +from aidial_analytics_realtime.utils.concurrency import make_async +from aidial_analytics_realtime.utils.log_config import with_prefix +from aidial_analytics_realtime.utils.timer import Timer + class TopicModel: def __init__( @@ -14,18 +19,33 @@ def __init__( topic_embeddings_model_name = os.environ.get( "TOPIC_EMBEDDINGS_MODEL", None ) + assert topic_model_name is not None self.model = BERTopic.load( topic_model_name, topic_embeddings_model_name ) + + # Disable tqdm progress bar for batch encoding + self.model.verbose = False self.model.transform(["test"]) # Make sure the model is loaded - def get_topic_by_text(self, text): - topics, _ = self.model.transform([text]) - topic = self.model.get_topic_info(topics[0]) + async def get_topic_by_text( + self, logger: logging.Logger, text: str + ) -> str | None: + return await make_async(self._get_topic_by_text, logger, text) + + def _get_topic_by_text( + self, logger: logging.Logger, text: str + ) -> str | None: + if not text: + return None + + with Timer(with_prefix(logger, "[topic]").info): + topics, _ = self.model.transform([text]) + topic = self.model.get_topic_info(topics[0]) - if "GeneratedName" in topic: - # "GeneratedName" is an expected name for the human readable topic representation - return topic["GeneratedName"][0][0][0] + if "GeneratedName" in topic: + # "GeneratedName" is an expected name for the human readable topic representation + return topic["GeneratedName"][0][0][0] # type: ignore - return topic["Name"][0] + return topic["Name"][0] # type: ignore diff --git a/aidial_analytics_realtime/utils/concurrency.py b/aidial_analytics_realtime/utils/concurrency.py new file mode 100644 index 0000000..a160070 --- /dev/null +++ b/aidial_analytics_realtime/utils/concurrency.py @@ -0,0 +1,15 @@ +import asyncio +from concurrent.futures import ThreadPoolExecutor +from typing import Callable, ParamSpec, TypeVar + +_T = TypeVar("_T") +_P = ParamSpec("_P") + +cpu_task_executor = ThreadPoolExecutor() + + +async def make_async( + func: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs +) -> _T: + loop = asyncio.get_event_loop() + return await loop.run_in_executor(cpu_task_executor, func, *args) # type: ignore diff --git a/aidial_analytics_realtime/utils/log_config.py b/aidial_analytics_realtime/utils/log_config.py index 652a2f4..6ec4562 100644 --- a/aidial_analytics_realtime/utils/log_config.py +++ b/aidial_analytics_realtime/utils/log_config.py @@ -1,21 +1,24 @@ import logging +import os import sys +from typing import Callable +from typing_extensions import override from uvicorn.logging import DefaultFormatter -logger = logging.getLogger("app") +app_logger = logging.getLogger("app") +LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO") def configure_loggers(): # Delegate uvicorn logs to the root logger # to achieve uniform log formatting - for name, log in logging.getLogger().manager.loggerDict.items(): - if isinstance(log, logging.Logger) and name.startswith("uvicorn"): - log.handlers = [] - log.propagate = True + uvicorn_logger = logging.getLogger("uvicorn") + uvicorn_logger.handlers = [] + uvicorn_logger.propagate = True - # Setting up log levels - logger.setLevel(logging.DEBUG) + # Setting log levels for the analytics application + app_logger.setLevel(LOG_LEVEL) # Configuring the root logger root = logging.getLogger() @@ -37,3 +40,26 @@ def configure_loggers(): handler = logging.StreamHandler(sys.stderr) handler.setFormatter(formatter) root.addHandler(handler) + + +class _MessageHookLogger(logging.LoggerAdapter): + _on_message: Callable[[str], str] + + def __init__( + self, logger: logging.Logger, on_message: Callable[[str], str] + ): + super().__init__(logger) + self._on_message = on_message + + @override + def process(self, msg, kwargs): + return self._on_message(msg), kwargs + + +def with_prefix(logger: logging.Logger, prefix: str) -> logging.Logger: + def on_message(msg: str) -> str: + if msg and msg[0].isalnum(): + return f"{prefix} {msg}" + return f"{prefix}{msg}" + + return _MessageHookLogger(logger, on_message) # type: ignore diff --git a/aidial_analytics_realtime/utils/timer.py b/aidial_analytics_realtime/utils/timer.py new file mode 100644 index 0000000..0db302e --- /dev/null +++ b/aidial_analytics_realtime/utils/timer.py @@ -0,0 +1,52 @@ +import asyncio +import time +from datetime import datetime +from typing import Callable + + +class Timer: + start: float + format: str + printer: Callable[[str], None] + + def __init__( + self, + printer: Callable[[str], None] = print, + *, + format: str = "{time}", + ): + self.format = format + self.printer = printer + + def stop(self) -> float: + return time.perf_counter() - self.start + + def __str__(self) -> str: + return f"{self.stop():.3f}s" + + def __enter__(self): + self._enter() + return self + + async def __aenter__(self): + await asyncio.sleep(0) + self._enter() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self._exit() + + async def __aexit__(self, exc_type, exc_val, exc_tb): + self._exit() + await asyncio.sleep(0) + + def _enter(self): + self.start = time.perf_counter() + self.printer(f"enter {self._now()}") + + def _exit(self): + self.printer(f"exit {self._now()}") + self.printer(self.format.format(time=self)) + + def _now(self): + return datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") diff --git a/poetry.lock b/poetry.lock index 90cc896..33f1106 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,37 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +[[package]] +name = "aidial-sdk" +version = "0.16.0" +description = "Framework to create applications and model adapters for AI DIAL" +optional = false +python-versions = "<4.0,>=3.8.1" +files = [ + {file = "aidial_sdk-0.16.0-py3-none-any.whl", hash = "sha256:76bfa50fd08bfabedd572f06974c68cca9dc18b5c38a8d00bf5d59e1f61cb2d9"}, + {file = "aidial_sdk-0.16.0.tar.gz", hash = "sha256:eddb1f00949bd0e4263c18be03df7b80093ce8caf7e4ed46a550f3a790e01875"}, +] + +[package.dependencies] +fastapi = ">=0.51,<1.0" +opentelemetry-api = {version = ">=1.22.0,<2.0.0", optional = true, markers = "extra == \"telemetry\""} +opentelemetry-exporter-otlp-proto-grpc = {version = ">=1.22.0,<2.0.0", optional = true, markers = "extra == \"telemetry\""} +opentelemetry-exporter-prometheus = {version = ">=0.43b0", optional = true, markers = "extra == \"telemetry\""} +opentelemetry-instrumentation-aiohttp-client = {version = ">=0.43b0", optional = true, markers = "extra == \"telemetry\""} +opentelemetry-instrumentation-fastapi = {version = ">=0.43b0", optional = true, markers = "extra == \"telemetry\""} +opentelemetry-instrumentation-httpx = {version = ">=0.43b0", optional = true, markers = "extra == \"telemetry\""} +opentelemetry-instrumentation-logging = {version = ">=0.43b0", optional = true, markers = "extra == \"telemetry\""} +opentelemetry-instrumentation-requests = {version = ">=0.43b0", optional = true, markers = "extra == \"telemetry\""} +opentelemetry-instrumentation-system-metrics = {version = ">=0.43b0", optional = true, markers = "extra == \"telemetry\""} +opentelemetry-instrumentation-urllib = {version = ">=0.43b0", optional = true, markers = "extra == \"telemetry\""} +opentelemetry-sdk = {version = ">=1.22.0,<2.0.0", optional = true, markers = "extra == \"telemetry\""} +prometheus-client = {version = ">=0.17.1,<=0.21", optional = true, markers = "extra == \"telemetry\""} +pydantic = ">=1.10,<3" +uvicorn = ">=0.19,<1.0" +wrapt = ">=1.10,<2" + +[package.extras] +telemetry = ["opentelemetry-api (>=1.22.0,<2.0.0)", "opentelemetry-exporter-otlp-proto-grpc (>=1.22.0,<2.0.0)", "opentelemetry-exporter-prometheus (>=0.43b0)", "opentelemetry-instrumentation-aiohttp-client (>=0.43b0)", "opentelemetry-instrumentation-fastapi (>=0.43b0)", "opentelemetry-instrumentation-httpx (>=0.43b0)", "opentelemetry-instrumentation-logging (>=0.43b0)", "opentelemetry-instrumentation-requests (>=0.43b0)", "opentelemetry-instrumentation-system-metrics (>=0.43b0)", "opentelemetry-instrumentation-urllib (>=0.43b0)", "opentelemetry-sdk (>=1.22.0,<2.0.0)", "prometheus-client (>=0.17.1,<=0.21)"] + [[package]] name = "aiohttp" version = "3.9.4" @@ -146,6 +178,23 @@ files = [ [package.extras] test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] +[[package]] +name = "asgiref" +version = "3.8.1" +description = "ASGI specs, helper code, and adapters" +optional = false +python-versions = ">=3.8" +files = [ + {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, + {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} + +[package.extras] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + [[package]] name = "async-timeout" version = "4.0.3" @@ -471,6 +520,23 @@ files = [ {file = "Cython-0.29.37.tar.gz", hash = "sha256:f813d4a6dd94adee5d4ff266191d1d95bf6d4164a4facc535422c021b2504cfb"}, ] +[[package]] +name = "deprecated" +version = "1.2.15" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +files = [ + {file = "Deprecated-1.2.15-py2.py3-none-any.whl", hash = "sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320"}, + {file = "deprecated-1.2.15.tar.gz", hash = "sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "jinja2 (>=3.0.3,<3.1.0)", "setuptools", "sphinx (<2)", "tox"] + [[package]] name = "distlib" version = "0.3.8" @@ -669,6 +735,90 @@ smb = ["smbprotocol"] ssh = ["paramiko"] tqdm = ["tqdm"] +[[package]] +name = "googleapis-common-protos" +version = "1.66.0" +description = "Common protobufs used in Google APIs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, + {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, +] + +[package.dependencies] +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] + +[[package]] +name = "grpcio" +version = "1.68.1" +description = "HTTP/2-based RPC framework" +optional = false +python-versions = ">=3.8" +files = [ + {file = "grpcio-1.68.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:d35740e3f45f60f3c37b1e6f2f4702c23867b9ce21c6410254c9c682237da68d"}, + {file = "grpcio-1.68.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:d99abcd61760ebb34bdff37e5a3ba333c5cc09feda8c1ad42547bea0416ada78"}, + {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:f8261fa2a5f679abeb2a0a93ad056d765cdca1c47745eda3f2d87f874ff4b8c9"}, + {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0feb02205a27caca128627bd1df4ee7212db051019a9afa76f4bb6a1a80ca95e"}, + {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:919d7f18f63bcad3a0f81146188e90274fde800a94e35d42ffe9eadf6a9a6330"}, + {file = "grpcio-1.68.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:963cc8d7d79b12c56008aabd8b457f400952dbea8997dd185f155e2f228db079"}, + {file = "grpcio-1.68.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ccf2ebd2de2d6661e2520dae293298a3803a98ebfc099275f113ce1f6c2a80f1"}, + {file = "grpcio-1.68.1-cp310-cp310-win32.whl", hash = "sha256:2cc1fd04af8399971bcd4f43bd98c22d01029ea2e56e69c34daf2bf8470e47f5"}, + {file = "grpcio-1.68.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2e743e51cb964b4975de572aa8fb95b633f496f9fcb5e257893df3be854746"}, + {file = "grpcio-1.68.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:55857c71641064f01ff0541a1776bfe04a59db5558e82897d35a7793e525774c"}, + {file = "grpcio-1.68.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4b177f5547f1b995826ef529d2eef89cca2f830dd8b2c99ffd5fde4da734ba73"}, + {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:3522c77d7e6606d6665ec8d50e867f13f946a4e00c7df46768f1c85089eae515"}, + {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d1fae6bbf0816415b81db1e82fb3bf56f7857273c84dcbe68cbe046e58e1ccd"}, + {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:298ee7f80e26f9483f0b6f94cc0a046caf54400a11b644713bb5b3d8eb387600"}, + {file = "grpcio-1.68.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cbb5780e2e740b6b4f2d208e90453591036ff80c02cc605fea1af8e6fc6b1bbe"}, + {file = "grpcio-1.68.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ddda1aa22495d8acd9dfbafff2866438d12faec4d024ebc2e656784d96328ad0"}, + {file = "grpcio-1.68.1-cp311-cp311-win32.whl", hash = "sha256:b33bd114fa5a83f03ec6b7b262ef9f5cac549d4126f1dc702078767b10c46ed9"}, + {file = "grpcio-1.68.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f20ebec257af55694d8f993e162ddf0d36bd82d4e57f74b31c67b3c6d63d8b2"}, + {file = "grpcio-1.68.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8829924fffb25386995a31998ccbbeaa7367223e647e0122043dfc485a87c666"}, + {file = "grpcio-1.68.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3aed6544e4d523cd6b3119b0916cef3d15ef2da51e088211e4d1eb91a6c7f4f1"}, + {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:4efac5481c696d5cb124ff1c119a78bddbfdd13fc499e3bc0ca81e95fc573684"}, + {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ab2d912ca39c51f46baf2a0d92aa265aa96b2443266fc50d234fa88bf877d8e"}, + {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c87ce2a97434dffe7327a4071839ab8e8bffd0054cc74cbe971fba98aedd60"}, + {file = "grpcio-1.68.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e4842e4872ae4ae0f5497bf60a0498fa778c192cc7a9e87877abd2814aca9475"}, + {file = "grpcio-1.68.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:255b1635b0ed81e9f91da4fcc8d43b7ea5520090b9a9ad9340d147066d1d3613"}, + {file = "grpcio-1.68.1-cp312-cp312-win32.whl", hash = "sha256:7dfc914cc31c906297b30463dde0b9be48e36939575eaf2a0a22a8096e69afe5"}, + {file = "grpcio-1.68.1-cp312-cp312-win_amd64.whl", hash = "sha256:a0c8ddabef9c8f41617f213e527254c41e8b96ea9d387c632af878d05db9229c"}, + {file = "grpcio-1.68.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:a47faedc9ea2e7a3b6569795c040aae5895a19dde0c728a48d3c5d7995fda385"}, + {file = "grpcio-1.68.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:390eee4225a661c5cd133c09f5da1ee3c84498dc265fd292a6912b65c421c78c"}, + {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:66a24f3d45c33550703f0abb8b656515b0ab777970fa275693a2f6dc8e35f1c1"}, + {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c08079b4934b0bf0a8847f42c197b1d12cba6495a3d43febd7e99ecd1cdc8d54"}, + {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8720c25cd9ac25dd04ee02b69256d0ce35bf8a0f29e20577427355272230965a"}, + {file = "grpcio-1.68.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:04cfd68bf4f38f5bb959ee2361a7546916bd9a50f78617a346b3aeb2b42e2161"}, + {file = "grpcio-1.68.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c28848761a6520c5c6071d2904a18d339a796ebe6b800adc8b3f474c5ce3c3ad"}, + {file = "grpcio-1.68.1-cp313-cp313-win32.whl", hash = "sha256:77d65165fc35cff6e954e7fd4229e05ec76102d4406d4576528d3a3635fc6172"}, + {file = "grpcio-1.68.1-cp313-cp313-win_amd64.whl", hash = "sha256:a8040f85dcb9830d8bbb033ae66d272614cec6faceee88d37a88a9bd1a7a704e"}, + {file = "grpcio-1.68.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:eeb38ff04ab6e5756a2aef6ad8d94e89bb4a51ef96e20f45c44ba190fa0bcaad"}, + {file = "grpcio-1.68.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a3869a6661ec8f81d93f4597da50336718bde9eb13267a699ac7e0a1d6d0bea"}, + {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:2c4cec6177bf325eb6faa6bd834d2ff6aa8bb3b29012cceb4937b86f8b74323c"}, + {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12941d533f3cd45d46f202e3667be8ebf6bcb3573629c7ec12c3e211d99cfccf"}, + {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80af6f1e69c5e68a2be529990684abdd31ed6622e988bf18850075c81bb1ad6e"}, + {file = "grpcio-1.68.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e8dbe3e00771bfe3d04feed8210fc6617006d06d9a2679b74605b9fed3e8362c"}, + {file = "grpcio-1.68.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:83bbf5807dc3ee94ce1de2dfe8a356e1d74101e4b9d7aa8c720cc4818a34aded"}, + {file = "grpcio-1.68.1-cp38-cp38-win32.whl", hash = "sha256:8cb620037a2fd9eeee97b4531880e439ebfcd6d7d78f2e7dcc3726428ab5ef63"}, + {file = "grpcio-1.68.1-cp38-cp38-win_amd64.whl", hash = "sha256:52fbf85aa71263380d330f4fce9f013c0798242e31ede05fcee7fbe40ccfc20d"}, + {file = "grpcio-1.68.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:cb400138e73969eb5e0535d1d06cae6a6f7a15f2cc74add320e2130b8179211a"}, + {file = "grpcio-1.68.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a1b988b40f2fd9de5c820f3a701a43339d8dcf2cb2f1ca137e2c02671cc83ac1"}, + {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:96f473cdacfdd506008a5d7579c9f6a7ff245a9ade92c3c0265eb76cc591914f"}, + {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:37ea3be171f3cf3e7b7e412a98b77685eba9d4fd67421f4a34686a63a65d99f9"}, + {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ceb56c4285754e33bb3c2fa777d055e96e6932351a3082ce3559be47f8024f0"}, + {file = "grpcio-1.68.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dffd29a2961f3263a16d73945b57cd44a8fd0b235740cb14056f0612329b345e"}, + {file = "grpcio-1.68.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:025f790c056815b3bf53da850dd70ebb849fd755a4b1ac822cb65cd631e37d43"}, + {file = "grpcio-1.68.1-cp39-cp39-win32.whl", hash = "sha256:1098f03dedc3b9810810568060dea4ac0822b4062f537b0f53aa015269be0a76"}, + {file = "grpcio-1.68.1-cp39-cp39-win_amd64.whl", hash = "sha256:334ab917792904245a028f10e803fcd5b6f36a7b2173a820c0b5b076555825e1"}, + {file = "grpcio-1.68.1.tar.gz", hash = "sha256:44a8502dd5de653ae6a73e2de50a401d84184f0331d0ac3daeb044e66d5c5054"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.68.1)"] + [[package]] name = "h11" version = "0.14.0" @@ -784,6 +934,29 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] +[[package]] +name = "importlib-metadata" +version = "8.5.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + [[package]] name = "influxdb-client" version = "1.37.0" @@ -1407,6 +1580,299 @@ files = [ {file = "nvidia_nvtx_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:65f4d98982b31b60026e0e6de73fbdfc09d08a96f4656dd3665ca616a11e1e82"}, ] +[[package]] +name = "opentelemetry-api" +version = "1.29.0" +description = "OpenTelemetry Python API" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_api-1.29.0-py3-none-any.whl", hash = "sha256:5fcd94c4141cc49c736271f3e1efb777bebe9cc535759c54c936cca4f1b312b8"}, + {file = "opentelemetry_api-1.29.0.tar.gz", hash = "sha256:d04a6cf78aad09614f52964ecb38021e248f5714dc32c2e0d8fd99517b4d69cf"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +importlib-metadata = ">=6.0,<=8.5.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.29.0" +description = "OpenTelemetry Protobuf encoding" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_exporter_otlp_proto_common-1.29.0-py3-none-any.whl", hash = "sha256:a9d7376c06b4da9cf350677bcddb9618ed4b8255c3f6476975f5e38274ecd3aa"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.29.0.tar.gz", hash = "sha256:e7c39b5dbd1b78fe199e40ddfe477e6983cb61aa74ba836df09c3869a3e3e163"}, +] + +[package.dependencies] +opentelemetry-proto = "1.29.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.29.0" +description = "OpenTelemetry Collector Protobuf over gRPC Exporter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_exporter_otlp_proto_grpc-1.29.0-py3-none-any.whl", hash = "sha256:5a2a3a741a2543ed162676cf3eefc2b4150e6f4f0a193187afb0d0e65039c69c"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.29.0.tar.gz", hash = "sha256:3d324d07d64574d72ed178698de3d717f62a059a93b6b7685ee3e303384e73ea"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +googleapis-common-protos = ">=1.52,<2.0" +grpcio = ">=1.63.2,<2.0.0" +opentelemetry-api = ">=1.15,<2.0" +opentelemetry-exporter-otlp-proto-common = "1.29.0" +opentelemetry-proto = "1.29.0" +opentelemetry-sdk = ">=1.29.0,<1.30.0" + +[[package]] +name = "opentelemetry-exporter-prometheus" +version = "0.50b0" +description = "Prometheus Metric Exporter for OpenTelemetry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_exporter_prometheus-0.50b0-py3-none-any.whl", hash = "sha256:b44561b44ada38a223b622425e5df71ef1e933b2777f7f399990c333b9464805"}, + {file = "opentelemetry_exporter_prometheus-0.50b0.tar.gz", hash = "sha256:5c5be644e959a0980919778a367d9603892128443489a5b2ced15c90e1733892"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-sdk = ">=1.29.0,<1.30.0" +prometheus-client = ">=0.5.0,<1.0.0" + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.50b0" +description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation-0.50b0-py3-none-any.whl", hash = "sha256:b8f9fc8812de36e1c6dffa5bfc6224df258841fb387b6dfe5df15099daa10630"}, + {file = "opentelemetry_instrumentation-0.50b0.tar.gz", hash = "sha256:7d98af72de8dec5323e5202e46122e5f908592b22c6d24733aad619f07d82979"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.4,<2.0" +opentelemetry-semantic-conventions = "0.50b0" +packaging = ">=18.0" +wrapt = ">=1.0.0,<2.0.0" + +[[package]] +name = "opentelemetry-instrumentation-aiohttp-client" +version = "0.50b0" +description = "OpenTelemetry aiohttp client instrumentation" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_aiohttp_client-0.50b0-py3-none-any.whl", hash = "sha256:bf235d022a8c551c99db71475e0c9bdef858f634ab92ba65a5a967fbec9c4301"}, + {file = "opentelemetry_instrumentation_aiohttp_client-0.50b0.tar.gz", hash = "sha256:417da9e483e89d1805fa42fb1f7f2053561a09bcab2251a9005b8013f644bfb0"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.50b0" +opentelemetry-semantic-conventions = "0.50b0" +opentelemetry-util-http = "0.50b0" +wrapt = ">=1.0.0,<2.0.0" + +[package.extras] +instruments = ["aiohttp (>=3.0,<4.0)"] + +[[package]] +name = "opentelemetry-instrumentation-asgi" +version = "0.50b0" +description = "ASGI instrumentation for OpenTelemetry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_asgi-0.50b0-py3-none-any.whl", hash = "sha256:2ba1297f746e55dec5a17fe825689da0613662fb25c004c3965a6c54b1d5be22"}, + {file = "opentelemetry_instrumentation_asgi-0.50b0.tar.gz", hash = "sha256:3ca4cb5616ae6a3e8ce86e7d5c360a8d8cc8ed722cf3dc8a5e44300774e87d49"}, +] + +[package.dependencies] +asgiref = ">=3.0,<4.0" +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.50b0" +opentelemetry-semantic-conventions = "0.50b0" +opentelemetry-util-http = "0.50b0" + +[package.extras] +instruments = ["asgiref (>=3.0,<4.0)"] + +[[package]] +name = "opentelemetry-instrumentation-fastapi" +version = "0.50b0" +description = "OpenTelemetry FastAPI Instrumentation" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_fastapi-0.50b0-py3-none-any.whl", hash = "sha256:8f03b738495e4705fbae51a2826389c7369629dace89d0f291c06ffefdff5e52"}, + {file = "opentelemetry_instrumentation_fastapi-0.50b0.tar.gz", hash = "sha256:16b9181682136da210295def2bb304a32fb9bdee9a935cdc9da43567f7c1149e"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.50b0" +opentelemetry-instrumentation-asgi = "0.50b0" +opentelemetry-semantic-conventions = "0.50b0" +opentelemetry-util-http = "0.50b0" + +[package.extras] +instruments = ["fastapi (>=0.58,<1.0)"] + +[[package]] +name = "opentelemetry-instrumentation-httpx" +version = "0.50b0" +description = "OpenTelemetry HTTPX Instrumentation" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_httpx-0.50b0-py3-none-any.whl", hash = "sha256:27acd41a9e70384d0978d58f492e5c16fc7a1b2363d5992b5bd0a27a3df7b68e"}, + {file = "opentelemetry_instrumentation_httpx-0.50b0.tar.gz", hash = "sha256:0072d1d39552449c08a45a7a0db0cd6af32c85205bd97267b2a272fc56a9b438"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.50b0" +opentelemetry-semantic-conventions = "0.50b0" +opentelemetry-util-http = "0.50b0" +wrapt = ">=1.0.0,<2.0.0" + +[package.extras] +instruments = ["httpx (>=0.18.0)"] + +[[package]] +name = "opentelemetry-instrumentation-logging" +version = "0.50b0" +description = "OpenTelemetry Logging instrumentation" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_logging-0.50b0-py3-none-any.whl", hash = "sha256:b33e301e1533f9d3b1bd5bf0ed2d938378b17cdd470c9dc04c61307357f6b302"}, + {file = "opentelemetry_instrumentation_logging-0.50b0.tar.gz", hash = "sha256:7f5f9e28f7ccdac9b9a5dc09830e4a70331c59184f7788bff499f44d1393225a"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.50b0" + +[[package]] +name = "opentelemetry-instrumentation-requests" +version = "0.50b0" +description = "OpenTelemetry requests instrumentation" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_requests-0.50b0-py3-none-any.whl", hash = "sha256:2c60a890988d6765de9230004d0af9071b3b2e1ddba4ca3b631cfb8a1722208d"}, + {file = "opentelemetry_instrumentation_requests-0.50b0.tar.gz", hash = "sha256:f8088c76f757985b492aad33331d21aec2f99c197472a57091c2e986a4b7ec8b"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.50b0" +opentelemetry-semantic-conventions = "0.50b0" +opentelemetry-util-http = "0.50b0" + +[package.extras] +instruments = ["requests (>=2.0,<3.0)"] + +[[package]] +name = "opentelemetry-instrumentation-system-metrics" +version = "0.50b0" +description = "OpenTelemetry System Metrics Instrumentation" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_system_metrics-0.50b0-py3-none-any.whl", hash = "sha256:3457ea6dd0b4c5a0353df106ed8883eec94093a316448b71cf75b53d64b17596"}, + {file = "opentelemetry_instrumentation_system_metrics-0.50b0.tar.gz", hash = "sha256:1da218c6df465aac6ad3a5e4e657d7c298fc934783beae83e35ec71a2855cdf4"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.11,<2.0" +opentelemetry-instrumentation = "0.50b0" +psutil = ">=5.9.0,<7" + +[package.extras] +instruments = ["psutil (>=5)"] + +[[package]] +name = "opentelemetry-instrumentation-urllib" +version = "0.50b0" +description = "OpenTelemetry urllib instrumentation" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_urllib-0.50b0-py3-none-any.whl", hash = "sha256:55024940fd41fbdd5a6ab5b6397660900b7a75e23f9ff7f61b4ae1279710a3ec"}, + {file = "opentelemetry_instrumentation_urllib-0.50b0.tar.gz", hash = "sha256:af3e9710635c3f8a5ec38adc772dfef0c1022d0196007baf4b74504e920b5d31"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.50b0" +opentelemetry-semantic-conventions = "0.50b0" +opentelemetry-util-http = "0.50b0" + +[[package]] +name = "opentelemetry-proto" +version = "1.29.0" +description = "OpenTelemetry Python Proto" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_proto-1.29.0-py3-none-any.whl", hash = "sha256:495069c6f5495cbf732501cdcd3b7f60fda2b9d3d4255706ca99b7ca8dec53ff"}, + {file = "opentelemetry_proto-1.29.0.tar.gz", hash = "sha256:3c136aa293782e9b44978c738fff72877a4b78b5d21a64e879898db7b2d93e5d"}, +] + +[package.dependencies] +protobuf = ">=5.0,<6.0" + +[[package]] +name = "opentelemetry-sdk" +version = "1.29.0" +description = "OpenTelemetry Python SDK" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_sdk-1.29.0-py3-none-any.whl", hash = "sha256:173be3b5d3f8f7d671f20ea37056710217959e774e2749d984355d1f9391a30a"}, + {file = "opentelemetry_sdk-1.29.0.tar.gz", hash = "sha256:b0787ce6aade6ab84315302e72bd7a7f2f014b0fb1b7c3295b88afe014ed0643"}, +] + +[package.dependencies] +opentelemetry-api = "1.29.0" +opentelemetry-semantic-conventions = "0.50b0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.50b0" +description = "OpenTelemetry Semantic Conventions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_semantic_conventions-0.50b0-py3-none-any.whl", hash = "sha256:e87efba8fdb67fb38113efea6a349531e75ed7ffc01562f65b802fcecb5e115e"}, + {file = "opentelemetry_semantic_conventions-0.50b0.tar.gz", hash = "sha256:02dc6dbcb62f082de9b877ff19a3f1ffaa3c306300fa53bfac761c4567c83d38"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +opentelemetry-api = "1.29.0" + +[[package]] +name = "opentelemetry-util-http" +version = "0.50b0" +description = "Web util for OpenTelemetry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_util_http-0.50b0-py3-none-any.whl", hash = "sha256:21f8aedac861ffa3b850f8c0a6c373026189eb8630ac6e14a2bf8c55695cc090"}, + {file = "opentelemetry_util_http-0.50b0.tar.gz", hash = "sha256:dc4606027e1bc02aabb9533cc330dd43f874fca492e4175c31d7154f341754af"}, +] + [[package]] name = "packaging" version = "23.2" @@ -1628,6 +2094,70 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "prometheus-client" +version = "0.21.0" +description = "Python client for the Prometheus monitoring system." +optional = false +python-versions = ">=3.8" +files = [ + {file = "prometheus_client-0.21.0-py3-none-any.whl", hash = "sha256:4fa6b4dd0ac16d58bb587c04b1caae65b8c5043e85f778f42f5f632f6af2e166"}, + {file = "prometheus_client-0.21.0.tar.gz", hash = "sha256:96c83c606b71ff2b0a433c98889d275f51ffec6c5e267de37c7a2b5c9aa9233e"}, +] + +[package.extras] +twisted = ["twisted"] + +[[package]] +name = "protobuf" +version = "5.29.1" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "protobuf-5.29.1-cp310-abi3-win32.whl", hash = "sha256:22c1f539024241ee545cbcb00ee160ad1877975690b16656ff87dde107b5f110"}, + {file = "protobuf-5.29.1-cp310-abi3-win_amd64.whl", hash = "sha256:1fc55267f086dd4050d18ef839d7bd69300d0d08c2a53ca7df3920cc271a3c34"}, + {file = "protobuf-5.29.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:d473655e29c0c4bbf8b69e9a8fb54645bc289dead6d753b952e7aa660254ae18"}, + {file = "protobuf-5.29.1-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5ba1d0e4c8a40ae0496d0e2ecfdbb82e1776928a205106d14ad6985a09ec155"}, + {file = "protobuf-5.29.1-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ee1461b3af56145aca2800e6a3e2f928108c749ba8feccc6f5dd0062c410c0d"}, + {file = "protobuf-5.29.1-cp38-cp38-win32.whl", hash = "sha256:50879eb0eb1246e3a5eabbbe566b44b10348939b7cc1b267567e8c3d07213853"}, + {file = "protobuf-5.29.1-cp38-cp38-win_amd64.whl", hash = "sha256:027fbcc48cea65a6b17028510fdd054147057fa78f4772eb547b9274e5219331"}, + {file = "protobuf-5.29.1-cp39-cp39-win32.whl", hash = "sha256:5a41deccfa5e745cef5c65a560c76ec0ed8e70908a67cc8f4da5fce588b50d57"}, + {file = "protobuf-5.29.1-cp39-cp39-win_amd64.whl", hash = "sha256:012ce28d862ff417fd629285aca5d9772807f15ceb1a0dbd15b88f58c776c98c"}, + {file = "protobuf-5.29.1-py3-none-any.whl", hash = "sha256:32600ddb9c2a53dedc25b8581ea0f1fd8ea04956373c0c07577ce58d312522e0"}, + {file = "protobuf-5.29.1.tar.gz", hash = "sha256:683be02ca21a6ffe80db6dd02c0b5b2892322c59ca57fd6c872d652cb80549cb"}, +] + +[[package]] +name = "psutil" +version = "6.1.0" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"}, + {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"}, + {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, + {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"}, + {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"}, + {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, + {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, + {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, +] + +[package.extras] +dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] + [[package]] name = "pycodestyle" version = "2.11.1" @@ -2845,6 +3375,80 @@ platformdirs = ">=3.9.1,<5" docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +[[package]] +name = "wrapt" +version = "1.17.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.8" +files = [ + {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e185ec6060e301a7e5f8461c86fb3640a7beb1a0f0208ffde7a65ec4074931df"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb90765dd91aed05b53cd7a87bd7f5c188fcd95960914bae0d32c5e7f899719d"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:879591c2b5ab0a7184258274c42a126b74a2c3d5a329df16d69f9cee07bba6ea"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fce6fee67c318fdfb7f285c29a82d84782ae2579c0e1b385b7f36c6e8074fffb"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0698d3a86f68abc894d537887b9bbf84d29bcfbc759e23f4644be27acf6da301"}, + {file = "wrapt-1.17.0-cp310-cp310-win32.whl", hash = "sha256:69d093792dc34a9c4c8a70e4973a3361c7a7578e9cd86961b2bbf38ca71e4e22"}, + {file = "wrapt-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:f28b29dc158ca5d6ac396c8e0a2ef45c4e97bb7e65522bfc04c989e6fe814575"}, + {file = "wrapt-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74bf625b1b4caaa7bad51d9003f8b07a468a704e0644a700e936c357c17dd45a"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f2a28eb35cf99d5f5bd12f5dd44a0f41d206db226535b37b0c60e9da162c3ed"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81b1289e99cf4bad07c23393ab447e5e96db0ab50974a280f7954b071d41b489"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2939cd4a2a52ca32bc0b359015718472d7f6de870760342e7ba295be9ebaf9"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a9653131bda68a1f029c52157fd81e11f07d485df55410401f745007bd6d339"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4e4b4385363de9052dac1a67bfb535c376f3d19c238b5f36bddc95efae15e12d"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bdf62d25234290db1837875d4dceb2151e4ea7f9fff2ed41c0fde23ed542eb5b"}, + {file = "wrapt-1.17.0-cp311-cp311-win32.whl", hash = "sha256:5d8fd17635b262448ab8f99230fe4dac991af1dabdbb92f7a70a6afac8a7e346"}, + {file = "wrapt-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:92a3d214d5e53cb1db8b015f30d544bc9d3f7179a05feb8f16df713cecc2620a"}, + {file = "wrapt-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:89fc28495896097622c3fc238915c79365dd0ede02f9a82ce436b13bd0ab7569"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875d240fdbdbe9e11f9831901fb8719da0bd4e6131f83aa9f69b96d18fae7504"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ed16d95fd142e9c72b6c10b06514ad30e846a0d0917ab406186541fe68b451"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b956061b8db634120b58f668592a772e87e2e78bc1f6a906cfcaa0cc7991c1"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:daba396199399ccabafbfc509037ac635a6bc18510ad1add8fd16d4739cdd106"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d63f4d446e10ad19ed01188d6c1e1bb134cde8c18b0aa2acfd973d41fcc5ada"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a5e7cc39a45fc430af1aefc4d77ee6bad72c5bcdb1322cfde852c15192b8bd4"}, + {file = "wrapt-1.17.0-cp312-cp312-win32.whl", hash = "sha256:0a0a1a1ec28b641f2a3a2c35cbe86c00051c04fffcfcc577ffcdd707df3f8635"}, + {file = "wrapt-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c34f6896a01b84bab196f7119770fd8466c8ae3dfa73c59c0bb281e7b588ce7"}, + {file = "wrapt-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:714c12485aa52efbc0fc0ade1e9ab3a70343db82627f90f2ecbc898fdf0bb181"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da427d311782324a376cacb47c1a4adc43f99fd9d996ffc1b3e8529c4074d393"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba1739fb38441a27a676f4de4123d3e858e494fac05868b7a281c0a383c098f4"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e711fc1acc7468463bc084d1b68561e40d1eaa135d8c509a65dd534403d83d7b"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:140ea00c87fafc42739bd74a94a5a9003f8e72c27c47cd4f61d8e05e6dec8721"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73a96fd11d2b2e77d623a7f26e004cc31f131a365add1ce1ce9a19e55a1eef90"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0b48554952f0f387984da81ccfa73b62e52817a4386d070c75e4db7d43a28c4a"}, + {file = "wrapt-1.17.0-cp313-cp313-win32.whl", hash = "sha256:498fec8da10e3e62edd1e7368f4b24aa362ac0ad931e678332d1b209aec93045"}, + {file = "wrapt-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd136bb85f4568fffca995bd3c8d52080b1e5b225dbf1c2b17b66b4c5fa02838"}, + {file = "wrapt-1.17.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17fcf043d0b4724858f25b8826c36e08f9fb2e475410bece0ec44a22d533da9b"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a557d97f12813dc5e18dad9fa765ae44ddd56a672bb5de4825527c847d6379"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0229b247b0fc7dee0d36176cbb79dbaf2a9eb7ecc50ec3121f40ef443155fb1d"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8425cfce27b8b20c9b89d77fb50e368d8306a90bf2b6eef2cdf5cd5083adf83f"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c900108df470060174108012de06d45f514aa4ec21a191e7ab42988ff42a86c"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e547b447073fc0dbfcbff15154c1be8823d10dab4ad401bdb1575e3fdedff1b"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:914f66f3b6fc7b915d46c1cc424bc2441841083de01b90f9e81109c9759e43ab"}, + {file = "wrapt-1.17.0-cp313-cp313t-win32.whl", hash = "sha256:a4192b45dff127c7d69b3bdfb4d3e47b64179a0b9900b6351859f3001397dabf"}, + {file = "wrapt-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4f643df3d4419ea3f856c5c3f40fec1d65ea2e89ec812c83f7767c8730f9827a"}, + {file = "wrapt-1.17.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:69c40d4655e078ede067a7095544bcec5a963566e17503e75a3a3e0fe2803b13"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f495b6754358979379f84534f8dd7a43ff8cff2558dcdea4a148a6e713a758f"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baa7ef4e0886a6f482e00d1d5bcd37c201b383f1d314643dfb0367169f94f04c"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fc931382e56627ec4acb01e09ce66e5c03c384ca52606111cee50d931a342d"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8f8909cdb9f1b237786c09a810e24ee5e15ef17019f7cecb207ce205b9b5fcce"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ad47b095f0bdc5585bced35bd088cbfe4177236c7df9984b3cc46b391cc60627"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:948a9bd0fb2c5120457b07e59c8d7210cbc8703243225dbd78f4dfc13c8d2d1f"}, + {file = "wrapt-1.17.0-cp38-cp38-win32.whl", hash = "sha256:5ae271862b2142f4bc687bdbfcc942e2473a89999a54231aa1c2c676e28f29ea"}, + {file = "wrapt-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:f335579a1b485c834849e9075191c9898e0731af45705c2ebf70e0cd5d58beed"}, + {file = "wrapt-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d751300b94e35b6016d4b1e7d0e7bbc3b5e1751e2405ef908316c2a9024008a1"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7264cbb4a18dc4acfd73b63e4bcfec9c9802614572025bdd44d0721983fc1d9c"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33539c6f5b96cf0b1105a0ff4cf5db9332e773bb521cc804a90e58dc49b10578"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30970bdee1cad6a8da2044febd824ef6dc4cc0b19e39af3085c763fdec7de33"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc7f729a72b16ee21795a943f85c6244971724819819a41ddbaeb691b2dd85ad"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6ff02a91c4fc9b6a94e1c9c20f62ea06a7e375f42fe57587f004d1078ac86ca9"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dfb7cff84e72e7bf975b06b4989477873dcf160b2fd89959c629535df53d4e0"}, + {file = "wrapt-1.17.0-cp39-cp39-win32.whl", hash = "sha256:2399408ac33ffd5b200480ee858baa58d77dd30e0dd0cab6a8a9547135f30a88"}, + {file = "wrapt-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:4f763a29ee6a20c529496a20a7bcb16a73de27f5da6a843249c7047daf135977"}, + {file = "wrapt-1.17.0-py3-none-any.whl", hash = "sha256:d2c63b93548eda58abf5188e505ffed0229bf675f7c3090f8e36ad55b8cbc371"}, + {file = "wrapt-1.17.0.tar.gz", hash = "sha256:16187aa2317c731170a88ef35e8937ae0f533c402872c1ee5e6d079fcf320801"}, +] + [[package]] name = "yarl" version = "1.9.4" @@ -2948,7 +3552,26 @@ files = [ idna = ">=2.0" multidict = ">=4.0" +[[package]] +name = "zipp" +version = "3.21.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + [metadata] lock-version = "2.0" python-versions = "~3.10" -content-hash = "dc76c9e99ca22324db445bf61b63efe3194a1eb719c81da65b53723eb01cf73b" +content-hash = "9da70f53698193e6477e810c60537bb556f68188467b86db26264dde8c6cf85e" diff --git a/pyproject.toml b/pyproject.toml index 313bbd3..577caf7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,8 @@ python-dotenv = "^1.0.0" python-dateutil = "^2.8.2" transformers = "4.36.0" pydantic = "^1.10.14" +aidial-sdk = { version = "0.16.0", extras = ["telemetry"] } + [[tool.poetry.source]] name = "pypi" @@ -101,3 +103,6 @@ exclude = [ markers = [ "with_external: marks tests may require external resources, like the download models (deselect with '-m \"not with_external\"')", ] +filterwarnings = [ + "ignore::DeprecationWarning:bertopic._bertopic:17" +] diff --git a/tests/influx_writer_mock.py b/tests/influx_writer_mock.py deleted file mode 100644 index 3c51e47..0000000 --- a/tests/influx_writer_mock.py +++ /dev/null @@ -1,6 +0,0 @@ -class InfluxWriterMock: - def __init__(self): - self.points = [] - - async def __call__(self, record): - self.points.append(str(record)) diff --git a/tests/mocks.py b/tests/mocks.py new file mode 100644 index 0000000..c3ec45e --- /dev/null +++ b/tests/mocks.py @@ -0,0 +1,11 @@ +class InfluxWriterMock: + def __init__(self): + self.points = [] + + async def __call__(self, logger, record): + self.points.append(str(record)) + + +class TopicModelMock: + async def get_topic_by_text(self, logger, text): + return text or None diff --git a/tests/test_app.py b/tests/test_app.py index 83fb2e6..1d0695a 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -4,18 +4,13 @@ from fastapi.testclient import TestClient import aidial_analytics_realtime.app as app -from tests.influx_writer_mock import InfluxWriterMock - - -class TestTopicModel: - def get_topic_by_text(self, text): - return "TestTopic" +from tests.mocks import InfluxWriterMock, TopicModelMock def test_chat_completion_plain_text(): write_api_mock = InfluxWriterMock() app.app.dependency_overrides[app.InfluxWriterAsync] = lambda: write_api_mock - app.app.dependency_overrides[app.TopicModel] = lambda: TestTopicModel() + app.app.dependency_overrides[app.TopicModel] = lambda: TopicModelMock() client = TestClient(app.app) response = client.post( @@ -38,7 +33,7 @@ def test_chat_completion_plain_text(): { "messages": [ {"role": "system", "content": ""}, - {"role": "user", "content": "Hi!"}, + {"role": "user", "content": "ping"}, ], "model": "gpt-4", "max_tokens": 2000, @@ -50,7 +45,7 @@ def test_chat_completion_plain_text(): }, "response": { "status": "200", - "body": 'data: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"Hi"},"finish_reason":null}]}\n\ndata: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":189,"prompt_tokens":22,"total_tokens":211}}\n\ndata: [DONE]\n', + "body": 'data: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"pong"},"finish_reason":null}]}\n\ndata: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":189,"prompt_tokens":22,"total_tokens":211}}\n\ndata: [DONE]\n', }, } ) @@ -72,7 +67,7 @@ def test_chat_completion_plain_text(): { "messages": [ {"role": "system", "content": ""}, - {"role": "user", "content": "Hi!"}, + {"role": "user", "content": "ping"}, ], "model": "gpt-4", "max_tokens": 2000, @@ -84,7 +79,7 @@ def test_chat_completion_plain_text(): }, "response": { "status": "200", - "body": 'data: {"id":"chatcmpl-2","object":"chat.completion.chunk","created":1700828102,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"Hi"},"finish_reason":null}]}\n\ndata: {"id":"chatcmpl-2","object":"chat.completion.chunk","created":1700828102,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":189,"prompt_tokens":22,"total_tokens":211}}\n\ndata: [DONE]\n', + "body": 'data: {"id":"chatcmpl-2","object":"chat.completion.chunk","created":1700828102,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"po"},"finish_reason":null}]}\n\ndata: {"id":"chatcmpl-2","object":"chat.completion.chunk","created":1700828102,"model":"gpt-4","choices":[{"index":0,"delta":{"content":"ng"},"finish_reason":null}]}\n\ndata: {"id":"chatcmpl-2","object":"chat.completion.chunk","created":1700828102,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":189,"prompt_tokens":22,"total_tokens":211}}\n\ndata: [DONE]\n', }, } ) @@ -93,15 +88,15 @@ def test_chat_completion_plain_text(): ) assert response.status_code == 200 assert write_api_mock.points == [ - 'analytics,core_parent_span_id=undefined,core_span_id=undefined,deployment=gpt-4,execution_path=undefined,language=undefined,model=gpt-4,parent_deployment=undefined,project_id=PROJECT-KEY,response_id=chatcmpl-1,title=undefined,topic=TestTopic,trace_id=undefined,upstream=undefined chat_id="chat-1",completion_tokens=189i,deployment_price=0,number_request_messages=2i,price=0,prompt_tokens=22i,user_hash="undefined" 1692214959997000000', - 'analytics,core_parent_span_id=undefined,core_span_id=undefined,deployment=gpt-4,execution_path=undefined,language=undefined,model=gpt-4,parent_deployment=undefined,project_id=PROJECT-KEY-2,response_id=chatcmpl-2,title=undefined,topic=TestTopic,trace_id=undefined,upstream=undefined chat_id="chat-2",completion_tokens=189i,deployment_price=0,number_request_messages=2i,price=0,prompt_tokens=22i,user_hash="undefined" 1700796820390000000', + 'analytics,core_parent_span_id=undefined,core_span_id=undefined,deployment=gpt-4,execution_path=undefined,language=undefined,model=gpt-4,parent_deployment=undefined,project_id=PROJECT-KEY,response_id=chatcmpl-1,title=undefined,topic=ping\\n\\npong,trace_id=undefined,upstream=undefined chat_id="chat-1",completion_tokens=189i,deployment_price=0,number_request_messages=2i,price=0,prompt_tokens=22i,user_hash="undefined" 1692214959997000000', + 'analytics,core_parent_span_id=undefined,core_span_id=undefined,deployment=gpt-4,execution_path=undefined,language=undefined,model=gpt-4,parent_deployment=undefined,project_id=PROJECT-KEY-2,response_id=chatcmpl-2,title=undefined,topic=ping\\n\\npong,trace_id=undefined,upstream=undefined chat_id="chat-2",completion_tokens=189i,deployment_price=0,number_request_messages=2i,price=0,prompt_tokens=22i,user_hash="undefined" 1700796820390000000', ] def test_chat_completion_list_content(): write_api_mock = InfluxWriterMock() app.app.dependency_overrides[app.InfluxWriterAsync] = lambda: write_api_mock - app.app.dependency_overrides[app.TopicModel] = lambda: TestTopicModel() + app.app.dependency_overrides[app.TopicModel] = lambda: TopicModelMock() client = TestClient(app.app) response = client.post( @@ -132,7 +127,7 @@ def test_chat_completion_list_content(): } ], }, - {"role": "user", "content": "Hi!"}, + {"role": "user", "content": "ping"}, ], "model": "gpt-4", "max_tokens": 2000, @@ -144,7 +139,7 @@ def test_chat_completion_list_content(): }, "response": { "status": "200", - "body": 'data: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"Hi"},"finish_reason":null}]}\n\ndata: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":189,"prompt_tokens":22,"total_tokens":211}}\n\ndata: [DONE]\n', + "body": 'data: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"pong"},"finish_reason":null}]}\n\ndata: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":189,"prompt_tokens":22,"total_tokens":211}}\n\ndata: [DONE]\n', }, } ) @@ -153,14 +148,14 @@ def test_chat_completion_list_content(): ) assert response.status_code == 200 assert write_api_mock.points == [ - 'analytics,core_parent_span_id=undefined,core_span_id=undefined,deployment=gpt-4,execution_path=undefined,language=undefined,model=gpt-4,parent_deployment=undefined,project_id=PROJECT-KEY,response_id=chatcmpl-1,title=undefined,topic=TestTopic,trace_id=undefined,upstream=undefined chat_id="chat-1",completion_tokens=189i,deployment_price=0,number_request_messages=2i,price=0,prompt_tokens=22i,user_hash="undefined" 1692214959997000000', + 'analytics,core_parent_span_id=undefined,core_span_id=undefined,deployment=gpt-4,execution_path=undefined,language=undefined,model=gpt-4,parent_deployment=undefined,project_id=PROJECT-KEY,response_id=chatcmpl-1,title=undefined,topic=act\\ as\\ a\\ helpful\\ assistant\\n\\nping\\n\\npong,trace_id=undefined,upstream=undefined chat_id="chat-1",completion_tokens=189i,deployment_price=0,number_request_messages=2i,price=0,prompt_tokens=22i,user_hash="undefined" 1692214959997000000', ] def test_chat_completion_none_content(): write_api_mock = InfluxWriterMock() app.app.dependency_overrides[app.InfluxWriterAsync] = lambda: write_api_mock - app.app.dependency_overrides[app.TopicModel] = lambda: TestTopicModel() + app.app.dependency_overrides[app.TopicModel] = lambda: TopicModelMock() client = TestClient(app.app) response = client.post( @@ -219,7 +214,7 @@ def test_chat_completion_none_content(): }, "response": { "status": "200", - "body": 'data: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"Hi"},"finish_reason":null}]}\n\ndata: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":189,"prompt_tokens":22,"total_tokens":211}}\n\ndata: [DONE]\n', + "body": 'data: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"5"},"finish_reason":null}]}\n\ndata: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":189,"prompt_tokens":22,"total_tokens":211}}\n\ndata: [DONE]\n', }, } ) @@ -228,14 +223,14 @@ def test_chat_completion_none_content(): ) assert response.status_code == 200 assert write_api_mock.points == [ - 'analytics,core_parent_span_id=undefined,core_span_id=undefined,deployment=gpt-4,execution_path=undefined,language=undefined,model=gpt-4,parent_deployment=undefined,project_id=PROJECT-KEY,response_id=chatcmpl-1,title=undefined,topic=TestTopic,trace_id=undefined,upstream=undefined chat_id="chat-1",completion_tokens=189i,deployment_price=0,number_request_messages=4i,price=0,prompt_tokens=22i,user_hash="undefined" 1692214959997000000', + 'analytics,core_parent_span_id=undefined,core_span_id=undefined,deployment=gpt-4,execution_path=undefined,language=undefined,model=gpt-4,parent_deployment=undefined,project_id=PROJECT-KEY,response_id=chatcmpl-1,title=undefined,topic=what\'s\\ the\\ weather\\ like?\\n\\nIt\'s\\ sunny\\ today.\\n\\n2+3\\=?\\n\\n5,trace_id=undefined,upstream=undefined chat_id="chat-1",completion_tokens=189i,deployment_price=0,number_request_messages=4i,price=0,prompt_tokens=22i,user_hash="undefined" 1692214959997000000', ] def test_embeddings_plain_text(): write_api_mock: app.InfluxWriterAsync = InfluxWriterMock() app.app.dependency_overrides[app.InfluxWriterAsync] = lambda: write_api_mock - app.app.dependency_overrides[app.TopicModel] = lambda: TestTopicModel() + app.app.dependency_overrides[app.TopicModel] = lambda: TopicModelMock() client = TestClient(app.app) response = client.post( @@ -303,7 +298,7 @@ def test_embeddings_plain_text(): assert response.status_code == 200 assert len(write_api_mock.points) == 1 assert re.match( - 'analytics,core_parent_span_id=20e7e64715abbe97,core_span_id=9ade2b6fef0a716d,deployment=text-embedding-3-small,execution_path=undefined/b/c,language=undefined,model=text-embedding-3-small,parent_deployment=assistant,project_id=PROJECT-KEY,response_id=(.+?),title=undefined,topic=TestTopic,trace_id=5dca3d6ed5d22b6ab574f27a6ab5ec14,upstream=undefined chat_id="chat-1",completion_tokens=0i,deployment_price=0.001,number_request_messages=2i,price=0.001,prompt_tokens=2i,user_hash="undefined" 1692214959997000000', + r'analytics,core_parent_span_id=20e7e64715abbe97,core_span_id=9ade2b6fef0a716d,deployment=text-embedding-3-small,execution_path=undefined/b/c,language=undefined,model=text-embedding-3-small,parent_deployment=assistant,project_id=PROJECT-KEY,response_id=(.+?),title=undefined,topic=fish\\n\\ncat,trace_id=5dca3d6ed5d22b6ab574f27a6ab5ec14,upstream=undefined chat_id="chat-1",completion_tokens=0i,deployment_price=0.001,number_request_messages=2i,price=0.001,prompt_tokens=2i,user_hash="undefined" 1692214959997000000', write_api_mock.points[0], ) @@ -311,7 +306,7 @@ def test_embeddings_plain_text(): def test_embeddings_tokens(): write_api_mock: app.InfluxWriterAsync = InfluxWriterMock() app.app.dependency_overrides[app.InfluxWriterAsync] = lambda: write_api_mock - app.app.dependency_overrides[app.TopicModel] = lambda: TestTopicModel() + app.app.dependency_overrides[app.TopicModel] = lambda: TopicModelMock() client = TestClient(app.app) response = client.post( @@ -381,7 +376,7 @@ def test_embeddings_tokens(): assert response.status_code == 200 assert len(write_api_mock.points) == 1 assert re.match( - 'analytics,core_parent_span_id=20e7e64715abbe97,core_span_id=9ade2b6fef0a716d,deployment=text-embedding-3-small,execution_path=undefined/b/c,language=undefined,model=text-embedding-3-small,parent_deployment=assistant,project_id=PROJECT-KEY,response_id=(.+?),title=undefined,topic=TestTopic,trace_id=5dca3d6ed5d22b6ab574f27a6ab5ec14,upstream=undefined chat_id="chat-1",completion_tokens=0i,deployment_price=0.001,number_request_messages=2i,price=0.001,prompt_tokens=2i,user_hash="undefined" 1692214959997000000', + r'analytics,core_parent_span_id=20e7e64715abbe97,core_span_id=9ade2b6fef0a716d,deployment=text-embedding-3-small,execution_path=undefined/b/c,language=undefined,model=text-embedding-3-small,parent_deployment=assistant,project_id=PROJECT-KEY,response_id=(.+?),title=undefined,topic=undefined,trace_id=5dca3d6ed5d22b6ab574f27a6ab5ec14,upstream=undefined chat_id="chat-1",completion_tokens=0i,deployment_price=0.001,number_request_messages=2i,price=0.001,prompt_tokens=2i,user_hash="undefined" 1692214959997000000', write_api_mock.points[0], ) @@ -389,7 +384,7 @@ def test_embeddings_tokens(): def test_data_request_with_new_format(): write_api_mock: app.InfluxWriterAsync = InfluxWriterMock() app.app.dependency_overrides[app.InfluxWriterAsync] = lambda: write_api_mock - app.app.dependency_overrides[app.TopicModel] = lambda: TestTopicModel() + app.app.dependency_overrides[app.TopicModel] = lambda: TopicModelMock() client = TestClient(app.app) response = client.post( @@ -425,7 +420,7 @@ def test_data_request_with_new_format(): { "messages": [ {"role": "system", "content": ""}, - {"role": "user", "content": "Hi!"}, + {"role": "user", "content": "ping"}, ], "model": "gpt-4", "max_tokens": 2000, @@ -437,7 +432,7 @@ def test_data_request_with_new_format(): }, "response": { "status": "200", - "body": 'data: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"Hi"},"finish_reason":null}]}\n\ndata: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":189,"prompt_tokens":22,"total_tokens":211}}\n\ndata: [DONE]\n', + "body": 'data: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"pong"},"finish_reason":null}]}\n\ndata: {"id":"chatcmpl-1","object":"chat.completion.chunk","created":1692214960,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":189,"prompt_tokens":22,"total_tokens":211}}\n\ndata: [DONE]\n', }, } ) @@ -469,7 +464,7 @@ def test_data_request_with_new_format(): { "messages": [ {"role": "system", "content": ""}, - {"role": "user", "content": "Hi!"}, + {"role": "user", "content": "ping"}, ], "model": "gpt-4", "max_tokens": 2000, @@ -481,7 +476,7 @@ def test_data_request_with_new_format(): }, "response": { "status": "200", - "body": 'data: {"id":"chatcmpl-2","object":"chat.completion.chunk","created":1700828102,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"Hi"},"finish_reason":null}]}\n\ndata: {"id":"chatcmpl-2","object":"chat.completion.chunk","created":1700828102,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":189,"prompt_tokens":22,"total_tokens":211}}\n\ndata: [DONE]\n', + "body": 'data: {"id":"chatcmpl-2","object":"chat.completion.chunk","created":1700828102,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"pong"},"finish_reason":null}]}\n\ndata: {"id":"chatcmpl-2","object":"chat.completion.chunk","created":1700828102,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":189,"prompt_tokens":22,"total_tokens":211}}\n\ndata: [DONE]\n', }, } ) @@ -490,15 +485,15 @@ def test_data_request_with_new_format(): ) assert response.status_code == 200 assert write_api_mock.points == [ - 'analytics,core_parent_span_id=20e7e64715abbe97,core_span_id=9ade2b6fef0a716d,deployment=gpt-4,execution_path=undefined/b/c,language=undefined,model=gpt-4,parent_deployment=assistant,project_id=PROJECT-KEY,response_id=chatcmpl-1,title=undefined,topic=TestTopic,trace_id=5dca3d6ed5d22b6ab574f27a6ab5ec14,upstream=undefined chat_id="chat-1",completion_tokens=40i,deployment_price=0.001,number_request_messages=2i,price=0.001,prompt_tokens=30i,user_hash="undefined" 1692214959997000000', - 'analytics,core_parent_span_id=undefined,core_span_id=20e7e64715abbe97,deployment=gpt-4,execution_path=a/b/c,language=undefined,model=gpt-4,parent_deployment=undefined,project_id=PROJECT-KEY-2,response_id=chatcmpl-2,title=undefined,topic=TestTopic,trace_id=5dca3d6ed5d22b6ab574f27a6ab5ec14,upstream=undefined chat_id="chat-2",completion_tokens=40i,deployment_price=0,number_request_messages=2i,price=0.005,prompt_tokens=30i,user_hash="undefined" 1700796820390000000', + 'analytics,core_parent_span_id=20e7e64715abbe97,core_span_id=9ade2b6fef0a716d,deployment=gpt-4,execution_path=undefined/b/c,language=undefined,model=gpt-4,parent_deployment=assistant,project_id=PROJECT-KEY,response_id=chatcmpl-1,title=undefined,topic=ping\\n\\npong,trace_id=5dca3d6ed5d22b6ab574f27a6ab5ec14,upstream=undefined chat_id="chat-1",completion_tokens=40i,deployment_price=0.001,number_request_messages=2i,price=0.001,prompt_tokens=30i,user_hash="undefined" 1692214959997000000', + 'analytics,core_parent_span_id=undefined,core_span_id=20e7e64715abbe97,deployment=gpt-4,execution_path=a/b/c,language=undefined,model=gpt-4,parent_deployment=undefined,project_id=PROJECT-KEY-2,response_id=chatcmpl-2,title=undefined,topic=ping\\n\\npong,trace_id=5dca3d6ed5d22b6ab574f27a6ab5ec14,upstream=undefined chat_id="chat-2",completion_tokens=40i,deployment_price=0,number_request_messages=2i,price=0.005,prompt_tokens=30i,user_hash="undefined" 1700796820390000000', ] def test_rate_request(): write_api_mock = InfluxWriterMock() app.app.dependency_overrides[app.InfluxWriterAsync] = lambda: write_api_mock - app.app.dependency_overrides[app.TopicModel] = lambda: TestTopicModel() + app.app.dependency_overrides[app.TopicModel] = lambda: TopicModelMock() client = TestClient(app.app) response = client.post( diff --git a/tests/test_huggingface_model.py b/tests/test_huggingface_model.py index c880963..f5b651e 100644 --- a/tests/test_huggingface_model.py +++ b/tests/test_huggingface_model.py @@ -4,7 +4,7 @@ from fastapi.testclient import TestClient import aidial_analytics_realtime.app as app -from tests.influx_writer_mock import InfluxWriterMock +from tests.mocks import InfluxWriterMock @pytest.mark.with_external