aidial-sdk 0.41.0.dev4__tar.gz → 0.42.0.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/PKG-INFO +30 -1
  2. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/README.md +29 -0
  3. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/application.py +107 -36
  4. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/deployment/_headers.py +3 -0
  5. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/deployment/from_request_mixin.py +26 -3
  6. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/pyproject.toml +1 -1
  7. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/LICENSE +0 -0
  8. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/__init__.py +0 -0
  9. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/_errors.py +0 -0
  10. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/_pydantic/__init__.py +0 -0
  11. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/_pydantic/_compat.py +0 -0
  12. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/_pydantic/_model_config.py +0 -0
  13. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/__init__.py +0 -0
  14. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/_cache.py +0 -0
  15. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/_types.py +0 -0
  16. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/base.py +0 -0
  17. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/choice.py +0 -0
  18. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/choice_base.py +0 -0
  19. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/chunks.py +0 -0
  20. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/enums.py +0 -0
  21. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/form.py +0 -0
  22. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/function_call.py +0 -0
  23. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/function_tool_call.py +0 -0
  24. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/request.py +0 -0
  25. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/response.py +0 -0
  26. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/chat_completion/stage.py +0 -0
  27. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/deployment/__init__.py +0 -0
  28. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/deployment/configuration.py +0 -0
  29. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/deployment/rate.py +0 -0
  30. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/deployment/tokenize.py +0 -0
  31. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/deployment/truncate_prompt.py +0 -0
  32. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/embeddings/__init__.py +0 -0
  33. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/embeddings/base.py +0 -0
  34. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/embeddings/request.py +0 -0
  35. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/embeddings/response.py +0 -0
  36. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/exceptions.py +0 -0
  37. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/header_propagator.py +0 -0
  38. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/py.typed +0 -0
  39. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/pydantic/v2.py +0 -0
  40. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/pydantic_v1/__init__.py +0 -0
  41. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/telemetry/__init__.py +0 -0
  42. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/telemetry/init.py +0 -0
  43. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/telemetry/types.py +0 -0
  44. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/__init__.py +0 -0
  45. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/_attachment.py +0 -0
  46. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/_cancel_scope.py +0 -0
  47. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/_content_stream.py +0 -0
  48. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/_disconnect_middleware.py +0 -0
  49. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/_indexed_list.py +0 -0
  50. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/_json_log_formatter.py +0 -0
  51. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/_logging.py +0 -0
  52. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/_reflection.py +0 -0
  53. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/env.py +0 -0
  54. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/errors.py +0 -0
  55. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/json.py +0 -0
  56. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/log_config.py +0 -0
  57. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/logging.py +0 -0
  58. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/merge_chunks.py +0 -0
  59. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/pydantic.py +0 -0
  60. {aidial_sdk-0.41.0.dev4 → aidial_sdk-0.42.0.dev1}/aidial_sdk/utils/streaming.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aidial-sdk
3
- Version: 0.41.0.dev4
3
+ Version: 0.42.0.dev1
4
4
  Summary: Framework to create applications and model adapters for AI DIAL
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -61,6 +61,7 @@ Description-Content-Type: text/markdown
61
61
  - [Echo application example](#echo-application-example)
62
62
  - [Run](#run)
63
63
  - [Check](#check)
64
+ - [Endpoints](#endpoints)
64
65
  - [Development](#development)
65
66
  - [Development Environment](#development-environment)
66
67
  - [Setup](#setup)
@@ -177,6 +178,34 @@ You will see the JSON response as:
177
178
 
178
179
  ---
179
180
 
181
+ ### Endpoints
182
+
183
+ Every deployment is exposed under two base paths:
184
+
185
+ |Base path|Description|
186
+ |---|---|
187
+ |`/openai/deployments/{deployment_name}`|The DIAL API path. The deployment name is a part of the path.|
188
+ |`/openai/v1`|The Azure OpenAI v1 API path. The deployment name comes from the request headers.|
189
+
190
+ The following endpoints are served under each of them:
191
+
192
+ - `POST <base path>/chat/completions`
193
+ - `POST <base path>/rate`
194
+ - `POST <base path>/tokenize`
195
+ - `POST <base path>/truncate_prompt`
196
+ - `GET <base path>/configuration`
197
+ - `POST <base path>/embeddings`
198
+
199
+ `request.deployment_id` is resolved from the first of the following sources that is set:
200
+
201
+ 1. the `X-DIAL-OVERRIDE-NAME` header,
202
+ 2. the deployment name from the path, for the `/openai/deployments/{deployment_name}` requests,
203
+ 3. the `X-DIAL-DEPLOYMENT-ID` header, which DIAL Core sets when it calls the `/openai/v1` endpoints.
204
+
205
+ An `/openai/v1` request with none of the headers set fails with `500 Internal Server Error`.
206
+
207
+ ---
208
+
180
209
  ## Development
181
210
 
182
211
  ### Development Environment
@@ -22,6 +22,7 @@
22
22
  - [Echo application example](#echo-application-example)
23
23
  - [Run](#run)
24
24
  - [Check](#check)
25
+ - [Endpoints](#endpoints)
25
26
  - [Development](#development)
26
27
  - [Development Environment](#development-environment)
27
28
  - [Setup](#setup)
@@ -138,6 +139,34 @@ You will see the JSON response as:
138
139
 
139
140
  ---
140
141
 
142
+ ### Endpoints
143
+
144
+ Every deployment is exposed under two base paths:
145
+
146
+ |Base path|Description|
147
+ |---|---|
148
+ |`/openai/deployments/{deployment_name}`|The DIAL API path. The deployment name is a part of the path.|
149
+ |`/openai/v1`|The Azure OpenAI v1 API path. The deployment name comes from the request headers.|
150
+
151
+ The following endpoints are served under each of them:
152
+
153
+ - `POST <base path>/chat/completions`
154
+ - `POST <base path>/rate`
155
+ - `POST <base path>/tokenize`
156
+ - `POST <base path>/truncate_prompt`
157
+ - `GET <base path>/configuration`
158
+ - `POST <base path>/embeddings`
159
+
160
+ `request.deployment_id` is resolved from the first of the following sources that is set:
161
+
162
+ 1. the `X-DIAL-OVERRIDE-NAME` header,
163
+ 2. the deployment name from the path, for the `/openai/deployments/{deployment_name}` requests,
164
+ 3. the `X-DIAL-DEPLOYMENT-ID` header, which DIAL Core sets when it calls the `/openai/v1` endpoints.
165
+
166
+ An `/openai/v1` request with none of the headers set fails with `500 Internal Server Error`.
167
+
168
+ ---
169
+
141
170
  ## Development
142
171
 
143
172
  ### Development Environment
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  import re
3
3
  import warnings
4
- from collections.abc import Callable, Coroutine
4
+ from collections.abc import Callable, Coroutine, Iterator
5
5
  from typing import Any, Literal, TypeVar
6
6
 
7
7
  from fastapi import FastAPI, HTTPException, Request
@@ -20,12 +20,16 @@ from aidial_sdk.chat_completion.response import (
20
20
  Response as ChatCompletionResponse,
21
21
  )
22
22
  from aidial_sdk.deployment.configuration import ConfigurationRequest
23
- from aidial_sdk.deployment.from_request_mixin import FromRequestMixin
23
+ from aidial_sdk.deployment.from_request_mixin import (
24
+ FromRequestMixin,
25
+ resolve_deployment_id,
26
+ )
24
27
  from aidial_sdk.deployment.rate import RateRequest
25
28
  from aidial_sdk.deployment.tokenize import TokenizeRequest
26
29
  from aidial_sdk.deployment.truncate_prompt import TruncatePromptRequest
27
30
  from aidial_sdk.embeddings.base import Embeddings
28
31
  from aidial_sdk.embeddings.request import Request as EmbeddingsRequest
32
+ from aidial_sdk.exceptions import DeploymentNotFoundError
29
33
  from aidial_sdk.exceptions import HTTPException as DIALException
30
34
  from aidial_sdk.header_propagator import HeaderPropagator
31
35
  from aidial_sdk.telemetry.types import TelemetryConfig
@@ -45,6 +49,8 @@ configure_sdk_logger()
45
49
 
46
50
  RequestType = TypeVar("RequestType", bound=FromRequestMixin)
47
51
 
52
+ Handler = Callable[[Request], Coroutine[Any, Any, Response]]
53
+
48
54
 
49
55
  def _interpolate_deployment_id(deployment_id: str, path_params: dict) -> str:
50
56
  result = deployment_id
@@ -70,6 +76,7 @@ class PathFilter(logging.Filter):
70
76
  class DIALApp(FastAPI):
71
77
  _allow_extra_request_fields: bool
72
78
  _dial_url: str | None
79
+ _v1_handlers: dict[str, dict[str, Handler]]
73
80
 
74
81
  def __init__(
75
82
  self,
@@ -95,6 +102,7 @@ class DIALApp(FastAPI):
95
102
 
96
103
  self._allow_extra_request_fields = allow_extra_request_fields
97
104
  self._dial_url = dial_url
105
+ self._v1_handlers = {}
98
106
 
99
107
  self.configure_telemetry(telemetry_config)
100
108
 
@@ -152,6 +160,13 @@ class DIALApp(FastAPI):
152
160
  methods=["POST"],
153
161
  )
154
162
 
163
+ self._add_v1_route(
164
+ deployment_name,
165
+ "embeddings",
166
+ self._embeddings(None, impl),
167
+ methods=["POST"],
168
+ )
169
+
155
170
  return self
156
171
 
157
172
  def add_chat_completion(
@@ -161,60 +176,115 @@ class DIALApp(FastAPI):
161
176
  *,
162
177
  heartbeat_interval: float | None = None,
163
178
  ) -> "DIALApp":
164
- self.add_api_route(
165
- f"/openai/deployments/{deployment_name}/chat/completions",
179
+ for endpoint, handler, methods in self._chat_completion_handlers(
180
+ deployment_name, impl, heartbeat_interval
181
+ ):
182
+ self.add_api_route(
183
+ f"/openai/deployments/{deployment_name}/{endpoint}",
184
+ handler,
185
+ methods=methods,
186
+ )
187
+
188
+ for endpoint, handler, methods in self._chat_completion_handlers(
189
+ None, impl, heartbeat_interval
190
+ ):
191
+ self._add_v1_route(
192
+ deployment_name, endpoint, handler, methods=methods
193
+ )
194
+
195
+ return self
196
+
197
+ def _chat_completion_handlers(
198
+ self,
199
+ deployment_id: str | None,
200
+ impl: ChatCompletion,
201
+ heartbeat_interval: float | None,
202
+ ) -> Iterator[tuple[str, Handler, list[str]]]:
203
+ yield (
204
+ "chat/completions",
166
205
  self._chat_completion(
167
- deployment_name,
168
- impl,
169
- heartbeat_interval=heartbeat_interval,
206
+ deployment_id, impl, heartbeat_interval=heartbeat_interval
170
207
  ),
171
- methods=["POST"],
208
+ ["POST"],
172
209
  )
173
210
 
174
- self.add_api_route(
175
- f"/openai/deployments/{deployment_name}/rate",
176
- self._rate_response(deployment_name, impl),
177
- methods=["POST"],
178
- )
211
+ yield "rate", self._rate_response(deployment_id, impl), ["POST"]
179
212
 
180
213
  if endpoint_impl := get_method_implementation(impl, "tokenize"):
181
- self.add_api_route(
182
- f"/openai/deployments/{deployment_name}/tokenize",
214
+ yield (
215
+ "tokenize",
183
216
  self._endpoint_factory(
184
- deployment_name, endpoint_impl, "tokenize", TokenizeRequest
217
+ deployment_id, endpoint_impl, "tokenize", TokenizeRequest
185
218
  ),
186
- methods=["POST"],
219
+ ["POST"],
187
220
  )
188
221
 
189
222
  if endpoint_impl := get_method_implementation(impl, "truncate_prompt"):
190
- self.add_api_route(
191
- f"/openai/deployments/{deployment_name}/truncate_prompt",
223
+ yield (
224
+ "truncate_prompt",
192
225
  self._endpoint_factory(
193
- deployment_name,
226
+ deployment_id,
194
227
  endpoint_impl,
195
228
  "truncate_prompt",
196
229
  TruncatePromptRequest,
197
230
  ),
198
- methods=["POST"],
231
+ ["POST"],
199
232
  )
200
233
 
201
234
  if endpoint_impl := get_method_implementation(impl, "configuration"):
202
- self.add_api_route(
203
- f"/openai/deployments/{deployment_name}/configuration",
235
+ yield (
236
+ "configuration",
204
237
  self._endpoint_factory(
205
- deployment_name,
238
+ deployment_id,
206
239
  endpoint_impl,
207
240
  "configuration",
208
241
  ConfigurationRequest,
209
242
  ),
210
- methods=["GET"],
243
+ ["GET"],
211
244
  )
212
245
 
213
- return self
246
+ def _add_v1_route(
247
+ self,
248
+ deployment_name: str,
249
+ endpoint: str,
250
+ handler: Handler,
251
+ *,
252
+ methods: list[str],
253
+ ) -> None:
254
+ """The /openai/v1 endpoints are shared by all the deployments of
255
+ the application, so they are dispatched by the deployment id
256
+ resolved from the request headers."""
257
+
258
+ if endpoint not in self._v1_handlers:
259
+ self._v1_handlers[endpoint] = {}
260
+ self.add_api_route(
261
+ f"/openai/v1/{endpoint}",
262
+ self._v1_dispatcher(endpoint),
263
+ methods=methods,
264
+ )
265
+
266
+ self._v1_handlers[endpoint][deployment_name] = handler
267
+
268
+ def _v1_dispatcher(self, endpoint: str) -> Handler:
269
+ async def _handler(original_request: Request) -> Response:
270
+ deployment_id = resolve_deployment_id(
271
+ original_request.headers, None
272
+ )
273
+ set_log_deployment(deployment_id)
274
+
275
+ handler = self._v1_handlers[endpoint].get(deployment_id)
276
+ if handler is None:
277
+ raise DeploymentNotFoundError(
278
+ f"The deployment {deployment_id!r} doesn't provide the {endpoint!r} endpoint"
279
+ )
280
+
281
+ return await handler(original_request)
282
+
283
+ return _handler
214
284
 
215
285
  def _endpoint_factory(
216
286
  self,
217
- deployment_id: str,
287
+ deployment_id: str | None,
218
288
  endpoint_impl: Callable[[RequestType], Coroutine[Any, Any, Any]],
219
289
  endpoint: Literal["tokenize", "truncate_prompt", "configuration"],
220
290
  request_type: type["RequestType"],
@@ -242,7 +312,7 @@ class DIALApp(FastAPI):
242
312
 
243
313
  return _handler
244
314
 
245
- def _rate_response(self, deployment_id: str, impl: ChatCompletion):
315
+ def _rate_response(self, deployment_id: str | None, impl: ChatCompletion):
246
316
  async def _handler(original_request: Request):
247
317
  request = await self._parse_request(
248
318
  RateRequest, original_request, deployment_id
@@ -257,15 +327,16 @@ class DIALApp(FastAPI):
257
327
  self,
258
328
  request: type[RequestType],
259
329
  original_request: Request,
260
- deployment_id: str,
330
+ deployment_id: str | None,
261
331
  ) -> RequestType:
262
- interpolated_deployment_id = _interpolate_deployment_id(
263
- deployment_id, original_request.path_params
264
- )
265
- set_log_deployment(interpolated_deployment_id)
332
+ if deployment_id is not None:
333
+ deployment_id = _interpolate_deployment_id(
334
+ deployment_id, original_request.path_params
335
+ )
336
+ set_log_deployment(deployment_id)
266
337
 
267
338
  ret = await request.from_request(
268
- original_request, interpolated_deployment_id, self._dial_url
339
+ original_request, deployment_id, self._dial_url
269
340
  )
270
341
  if not self._allow_extra_request_fields:
271
342
  model_validate_extra_fields(ret)
@@ -273,7 +344,7 @@ class DIALApp(FastAPI):
273
344
 
274
345
  def _chat_completion(
275
346
  self,
276
- deployment_id: str,
347
+ deployment_id: str | None,
277
348
  impl: ChatCompletion,
278
349
  *,
279
350
  heartbeat_interval: float | None,
@@ -316,7 +387,7 @@ class DIALApp(FastAPI):
316
387
 
317
388
  return _handler
318
389
 
319
- def _embeddings(self, deployment_id: str, impl: Embeddings):
390
+ def _embeddings(self, deployment_id: str | None, impl: Embeddings):
320
391
  async def _handler(original_request: Request):
321
392
  request = await self._parse_request(
322
393
  EmbeddingsRequest, original_request, deployment_id
@@ -1,6 +1,9 @@
1
1
  DIAL_CONVERSATION_ID = "X-CONVERSATION-ID"
2
2
  DIAL_JOB_TITLE = "X-JOB-TITLE"
3
3
 
4
+ DIAL_OVERRIDE_NAME = "X-DIAL-OVERRIDE-NAME"
5
+ DIAL_DEPLOYMENT_ID = "X-DIAL-DEPLOYMENT-ID"
6
+
4
7
  DIAL_UPSTREAM_ENDPOINT = "X-UPSTREAM-ENDPOINT"
5
8
  DIAL_UPSTREAM_KEY = "X-UPSTREAM-KEY"
6
9
  DIAL_UPSTREAM_EXTRA_DATA = "X-UPSTREAM-EXTRA-DATA"
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import warnings
3
3
  from abc import ABC, abstractmethod
4
+ from collections.abc import Mapping
4
5
  from typing import Any, TypeVar
5
6
  from urllib.parse import urljoin
6
7
 
@@ -21,7 +22,9 @@ from aidial_sdk.deployment._headers import (
21
22
  DIAL_CACHE_BREAKPOINT_PATH,
22
23
  DIAL_CACHE_EXTRA_METADATA,
23
24
  DIAL_CONVERSATION_ID,
25
+ DIAL_DEPLOYMENT_ID,
24
26
  DIAL_JOB_TITLE,
27
+ DIAL_OVERRIDE_NAME,
25
28
  DIAL_UPSTREAM_ENDPOINT,
26
29
  DIAL_UPSTREAM_EXTRA_DATA,
27
30
  DIAL_UPSTREAM_KEY,
@@ -33,13 +36,33 @@ from aidial_sdk.utils.pydantic import ExtraAllowModel
33
36
  T = TypeVar("T", bound="FromRequestMixin")
34
37
 
35
38
 
39
+ def resolve_deployment_id(
40
+ headers: Mapping[str, str], deployment_id: str | None
41
+ ) -> str:
42
+ """The deployment id of a request comes either from the path it was
43
+ sent to or, for the deployment-agnostic paths, from its headers."""
44
+
45
+ effective_deployment_id = (
46
+ headers.get(DIAL_OVERRIDE_NAME)
47
+ or deployment_id
48
+ or headers.get(DIAL_DEPLOYMENT_ID)
49
+ )
50
+
51
+ if effective_deployment_id is None:
52
+ raise InternalServerError(
53
+ f"The request headers are missing {DIAL_DEPLOYMENT_ID} header."
54
+ )
55
+
56
+ return effective_deployment_id
57
+
58
+
36
59
  class FromRequestMixin(ABC, ExtraAllowModel):
37
60
  @classmethod
38
61
  @abstractmethod
39
62
  async def from_request(
40
63
  cls: type[T],
41
64
  request: fastapi.Request,
42
- deployment_id: str,
65
+ deployment_id: str | None,
43
66
  base_url: str | None,
44
67
  ) -> T:
45
68
  pass
@@ -176,7 +199,7 @@ class FromRequestDeploymentMixin(FromRequestMixin):
176
199
  async def from_request(
177
200
  cls,
178
201
  request: fastapi.Request,
179
- deployment_id: StrictStr,
202
+ deployment_id: StrictStr | None,
180
203
  base_url: str | None,
181
204
  ):
182
205
  headers = request.headers.mutablecopy()
@@ -212,7 +235,7 @@ class FromRequestDeploymentMixin(FromRequestMixin):
212
235
  bearer_token_secret=(
213
236
  SecretStr(bearer_token) if bearer_token else None
214
237
  ),
215
- deployment_id=deployment_id,
238
+ deployment_id=resolve_deployment_id(headers, deployment_id),
216
239
  api_version=request.query_params.get("api-version"),
217
240
  headers=headers,
218
241
  original_request=request,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aidial-sdk"
3
- version = "0.41.0.dev4"
3
+ version = "0.42.0.dev1"
4
4
  description = "Framework to create applications and model adapters for AI DIAL"
5
5
  authors = [{ name = "EPAM RAIL", email = "SpecialEPM-DIALDevTeam@epam.com" }]
6
6
  license = "Apache-2.0"