aidial-sdk 0.47.0.dev10__tar.gz → 0.48.0.dev2__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.
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/PKG-INFO +1 -1
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/application.py +53 -30
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/__init__.py +12 -5
- aidial_sdk-0.47.0.dev10/aidial_sdk/translators/messages_to_chat_completions/core_client.py → aidial_sdk-0.48.0.dev2/aidial_sdk/translators/messages_to_chat_completions/backend/http.py +29 -1
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/chat_completions/app.py +32 -42
- aidial_sdk-0.48.0.dev2/aidial_sdk/utils/_interpolation.py +55 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/pyproject.toml +1 -2
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/LICENSE +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/README.md +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/__init__.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/_errors.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/_pydantic/__init__.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/_pydantic/_compat.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/_pydantic/_model_config.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/__init__.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/_cache.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/_types.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/base.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/choice.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/choice_base.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/chunks.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/enums.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/form.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/function_call.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/function_tool_call.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/request.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/response.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/stage.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/deployment/__init__.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/deployment/_headers.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/deployment/configuration.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/deployment/from_request_mixin.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/deployment/rate.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/deployment/tokenize.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/deployment/truncate_prompt.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/embeddings/__init__.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/embeddings/base.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/embeddings/request.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/embeddings/response.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/exceptions.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/header_propagator.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/py.typed +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/pydantic/v2.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/pydantic_v1/__init__.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/response_headers.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/telemetry/__init__.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/telemetry/_context.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/telemetry/init.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/telemetry/types.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/chat_completions/cache_breakpoints.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/chat_completions/dial_extensions.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/chat_completions/from_chat_completions.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/chat_completions/reasoning.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/chat_completions/streaming.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/chat_completions/system_turns.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/chat_completions/to_chat_completions.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/common.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/errors.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/settings.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/sse_stream.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/translators/messages_to_chat_completions/translation_state.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/__init__.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/_attachment.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/_cancel_scope.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/_content_stream.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/_disconnect_middleware.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/_indexed_list.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/_json_log_formatter.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/_logging.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/_reflection.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/env.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/errors.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/json.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/log_config.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/logging.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/merge_chunks.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/pydantic.py +0 -0
- {aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/streaming.py +0 -0
|
@@ -37,6 +37,10 @@ from aidial_sdk.header_propagator import HeaderPropagator
|
|
|
37
37
|
from aidial_sdk.response_headers import NonForwardableHeadersMiddleware
|
|
38
38
|
from aidial_sdk.telemetry.types import TelemetryConfig
|
|
39
39
|
from aidial_sdk.utils._disconnect_middleware import DisconnectMiddleware
|
|
40
|
+
from aidial_sdk.utils._interpolation import (
|
|
41
|
+
interpolate_deployment_id,
|
|
42
|
+
match_deployment_id,
|
|
43
|
+
)
|
|
40
44
|
from aidial_sdk.utils._reflection import get_method_implementation
|
|
41
45
|
from aidial_sdk.utils.env import env_float, env_var_list
|
|
42
46
|
from aidial_sdk.utils.log_config import configure_sdk_logger
|
|
@@ -59,16 +63,6 @@ RequestType = TypeVar("RequestType", bound=FromRequestMixin)
|
|
|
59
63
|
Handler = Callable[[Request], Coroutine[Any, Any, Response]]
|
|
60
64
|
|
|
61
65
|
|
|
62
|
-
def _interpolate_deployment_id(deployment_id: str, path_params: dict) -> str:
|
|
63
|
-
result = deployment_id
|
|
64
|
-
for key, value in path_params.items():
|
|
65
|
-
# Replace {key} or {key:type} patterns with actual value
|
|
66
|
-
# Pattern matches {key} or {key:int}, {key:path}, etc.
|
|
67
|
-
pattern = r"\{" + re.escape(key) + r"(?::[^}]*)?\}"
|
|
68
|
-
result = re.sub(pattern, str(value), result)
|
|
69
|
-
return result
|
|
70
|
-
|
|
71
|
-
|
|
72
66
|
class PathFilter(logging.Filter):
|
|
73
67
|
path: str
|
|
74
68
|
|
|
@@ -83,7 +77,20 @@ class PathFilter(logging.Filter):
|
|
|
83
77
|
class DIALApp(FastAPI):
|
|
84
78
|
_allow_extra_request_fields: bool
|
|
85
79
|
_dial_url: str | None
|
|
86
|
-
|
|
80
|
+
|
|
81
|
+
_generic_handlers: dict[str, dict[str, Handler]]
|
|
82
|
+
"""
|
|
83
|
+
Request handlers for endpoints that do not allow to distinguish deployments in the path.
|
|
84
|
+
A dictionary from such a deployment-id-less endpoint to the handlers for each deployment:
|
|
85
|
+
|
|
86
|
+
/openai/v1/chat/completions:
|
|
87
|
+
gpt-5.4: handler1
|
|
88
|
+
gpt-5.5: handler2
|
|
89
|
+
/openai/v1/embeddings:
|
|
90
|
+
ada-embeddings-2024-10-10: handler3
|
|
91
|
+
...
|
|
92
|
+
"""
|
|
93
|
+
|
|
87
94
|
_reset_otel_context: Callable[[], AbstractContextManager[None]]
|
|
88
95
|
|
|
89
96
|
def __init__(
|
|
@@ -110,7 +117,7 @@ class DIALApp(FastAPI):
|
|
|
110
117
|
|
|
111
118
|
self._allow_extra_request_fields = allow_extra_request_fields
|
|
112
119
|
self._dial_url = dial_url
|
|
113
|
-
self.
|
|
120
|
+
self._generic_handlers = {}
|
|
114
121
|
self._reset_otel_context = nullcontext
|
|
115
122
|
|
|
116
123
|
self.configure_telemetry(telemetry_config)
|
|
@@ -181,9 +188,9 @@ class DIALApp(FastAPI):
|
|
|
181
188
|
methods=["POST"],
|
|
182
189
|
)
|
|
183
190
|
|
|
184
|
-
self.
|
|
191
|
+
self._add_generic_route(
|
|
185
192
|
deployment_name,
|
|
186
|
-
"embeddings",
|
|
193
|
+
"/openai/v1/embeddings",
|
|
187
194
|
self._embeddings(None, impl),
|
|
188
195
|
methods=["POST"],
|
|
189
196
|
)
|
|
@@ -209,8 +216,11 @@ class DIALApp(FastAPI):
|
|
|
209
216
|
for endpoint, handler, methods in self._chat_completion_handlers(
|
|
210
217
|
None, impl, heartbeat_interval
|
|
211
218
|
):
|
|
212
|
-
self.
|
|
213
|
-
deployment_name,
|
|
219
|
+
self._add_generic_route(
|
|
220
|
+
deployment_name,
|
|
221
|
+
f"/openai/v1/{endpoint}",
|
|
222
|
+
handler,
|
|
223
|
+
methods=methods,
|
|
214
224
|
)
|
|
215
225
|
|
|
216
226
|
return self
|
|
@@ -264,7 +274,7 @@ class DIALApp(FastAPI):
|
|
|
264
274
|
["GET"],
|
|
265
275
|
)
|
|
266
276
|
|
|
267
|
-
def
|
|
277
|
+
def _add_generic_route(
|
|
268
278
|
self,
|
|
269
279
|
deployment_name: str,
|
|
270
280
|
endpoint: str,
|
|
@@ -272,28 +282,25 @@ class DIALApp(FastAPI):
|
|
|
272
282
|
*,
|
|
273
283
|
methods: list[str],
|
|
274
284
|
) -> None:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
resolved from the request headers."""
|
|
278
|
-
|
|
279
|
-
if endpoint not in self._v1_handlers:
|
|
280
|
-
self._v1_handlers[endpoint] = {}
|
|
285
|
+
if endpoint not in self._generic_handlers:
|
|
286
|
+
self._generic_handlers[endpoint] = {}
|
|
281
287
|
self.add_api_route(
|
|
282
|
-
|
|
283
|
-
self.
|
|
288
|
+
endpoint,
|
|
289
|
+
self._generic_dispatcher(endpoint),
|
|
284
290
|
methods=methods,
|
|
285
291
|
)
|
|
292
|
+
self._generic_handlers[endpoint][deployment_name] = handler
|
|
286
293
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
def _v1_dispatcher(self, endpoint: str) -> Handler:
|
|
294
|
+
def _generic_dispatcher(self, endpoint: str) -> Handler:
|
|
290
295
|
async def _handler(original_request: Request) -> Response:
|
|
291
296
|
deployment_id = get_upstream_deployment_id(
|
|
292
297
|
original_request.headers, None
|
|
293
298
|
)
|
|
294
299
|
set_log_deployment(deployment_id)
|
|
295
300
|
|
|
296
|
-
handler = self.
|
|
301
|
+
handler = self._get_handler(
|
|
302
|
+
endpoint, deployment_id, original_request
|
|
303
|
+
)
|
|
297
304
|
if handler is None:
|
|
298
305
|
raise DeploymentNotFoundError(
|
|
299
306
|
f"The deployment {deployment_id!r} doesn't provide the {endpoint!r} endpoint"
|
|
@@ -303,6 +310,22 @@ class DIALApp(FastAPI):
|
|
|
303
310
|
|
|
304
311
|
return _handler
|
|
305
312
|
|
|
313
|
+
def _get_handler(
|
|
314
|
+
self, endpoint: str, deployment_id: str, request: Request
|
|
315
|
+
) -> Handler | None:
|
|
316
|
+
handlers = self._generic_handlers[endpoint]
|
|
317
|
+
|
|
318
|
+
if (handler := handlers.get(deployment_id)) is not None:
|
|
319
|
+
return handler
|
|
320
|
+
|
|
321
|
+
for name, handler in handlers.items():
|
|
322
|
+
params = match_deployment_id(name, deployment_id)
|
|
323
|
+
if params is not None:
|
|
324
|
+
request.scope["path_params"] = params
|
|
325
|
+
return handler
|
|
326
|
+
|
|
327
|
+
return None
|
|
328
|
+
|
|
306
329
|
def _endpoint_factory(
|
|
307
330
|
self,
|
|
308
331
|
deployment_id: str | None,
|
|
@@ -351,7 +374,7 @@ class DIALApp(FastAPI):
|
|
|
351
374
|
deployment_id: str | None,
|
|
352
375
|
) -> RequestType:
|
|
353
376
|
if deployment_id is not None:
|
|
354
|
-
deployment_id =
|
|
377
|
+
deployment_id = interpolate_deployment_id(
|
|
355
378
|
deployment_id, original_request.path_params
|
|
356
379
|
)
|
|
357
380
|
set_log_deployment(deployment_id)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import httpx
|
|
2
1
|
from fastapi import FastAPI
|
|
3
2
|
|
|
4
3
|
from aidial_sdk._pydantic import PYDANTIC_V2
|
|
@@ -9,10 +8,18 @@ if not PYDANTIC_V2:
|
|
|
9
8
|
"install pydantic>=2 and set PYDANTIC_V2=True"
|
|
10
9
|
)
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
try:
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
from aidial_sdk.translators.messages_to_chat_completions.chat_completions.app import (
|
|
15
|
+
ClientFactory,
|
|
16
|
+
create_anthropic_api_app,
|
|
17
|
+
)
|
|
18
|
+
except ModuleNotFoundError as e:
|
|
19
|
+
raise ImportError(
|
|
20
|
+
"The Anthropic Messages API translator requires the translators extra: "
|
|
21
|
+
'pip install "aidial-sdk[translators]"'
|
|
22
|
+
) from e
|
|
16
23
|
|
|
17
24
|
__all__ = ["ClientFactory", "create_anthropic_api_app", "mount_translator"]
|
|
18
25
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
1
2
|
from urllib.parse import quote
|
|
2
3
|
|
|
3
4
|
import httpx
|
|
4
5
|
from fastapi import Request
|
|
5
|
-
from openai import AsyncOpenAI, Omit, omit
|
|
6
|
+
from openai import AsyncOpenAI, AsyncStream, Omit, omit
|
|
7
|
+
from openai.types.chat import ChatCompletion, ChatCompletionChunk
|
|
6
8
|
|
|
7
9
|
from aidial_sdk.translators.messages_to_chat_completions.settings import (
|
|
8
10
|
get_dial_api_version,
|
|
@@ -62,3 +64,29 @@ def core_headers(request: Request) -> dict[str, str | Omit]:
|
|
|
62
64
|
}
|
|
63
65
|
)
|
|
64
66
|
return headers
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class ChatClientHttp:
|
|
71
|
+
client: AsyncOpenAI
|
|
72
|
+
core_headers: dict[str, str | Omit]
|
|
73
|
+
|
|
74
|
+
async def create_stream(
|
|
75
|
+
self, body: dict
|
|
76
|
+
) -> AsyncStream[ChatCompletionChunk]:
|
|
77
|
+
return await self.client.post(
|
|
78
|
+
"/chat/completions",
|
|
79
|
+
cast_to=ChatCompletion,
|
|
80
|
+
body=body,
|
|
81
|
+
options={"headers": self.core_headers},
|
|
82
|
+
stream=True,
|
|
83
|
+
stream_cls=AsyncStream[ChatCompletionChunk],
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
async def create_block(self, body: dict) -> ChatCompletion:
|
|
87
|
+
return await self.client.post(
|
|
88
|
+
"/chat/completions",
|
|
89
|
+
cast_to=ChatCompletion,
|
|
90
|
+
body=body,
|
|
91
|
+
options={"headers": self.core_headers},
|
|
92
|
+
)
|
|
@@ -7,9 +7,14 @@ import httpx
|
|
|
7
7
|
from anthropic.types.beta.message_create_params import MessageCreateParams
|
|
8
8
|
from fastapi import FastAPI, Request
|
|
9
9
|
from fastapi.responses import JSONResponse, Response, StreamingResponse
|
|
10
|
-
from openai import AsyncStream
|
|
11
|
-
from openai.types.chat import ChatCompletion, ChatCompletionChunk
|
|
12
10
|
|
|
11
|
+
from aidial_sdk.deployment.from_request_mixin import get_client_deployment_id
|
|
12
|
+
from aidial_sdk.translators.messages_to_chat_completions.backend.http import (
|
|
13
|
+
ChatClientHttp,
|
|
14
|
+
core_headers,
|
|
15
|
+
core_legacy_client,
|
|
16
|
+
core_v1_client,
|
|
17
|
+
)
|
|
13
18
|
from aidial_sdk.translators.messages_to_chat_completions.chat_completions.from_chat_completions import (
|
|
14
19
|
to_anthropic_response,
|
|
15
20
|
)
|
|
@@ -23,11 +28,6 @@ from aidial_sdk.translators.messages_to_chat_completions.common import (
|
|
|
23
28
|
not_found,
|
|
24
29
|
with_debug_logging,
|
|
25
30
|
)
|
|
26
|
-
from aidial_sdk.translators.messages_to_chat_completions.core_client import (
|
|
27
|
-
core_headers,
|
|
28
|
-
core_legacy_client,
|
|
29
|
-
core_v1_client,
|
|
30
|
-
)
|
|
31
31
|
from aidial_sdk.translators.messages_to_chat_completions.errors import (
|
|
32
32
|
AnthropicErrorType,
|
|
33
33
|
AnthropicHTTPError,
|
|
@@ -45,13 +45,10 @@ ClientT = TypeVar("ClientT")
|
|
|
45
45
|
ClientFactory = Callable[[Request], Awaitable[ClientT]] | ClientT
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
async def
|
|
49
|
-
request: Request,
|
|
50
|
-
get_client: ClientFactory[httpx.AsyncClient],
|
|
51
|
-
legacy: bool,
|
|
48
|
+
async def handle_anthropic_messages(
|
|
49
|
+
request: Request, client: ChatClientHttp
|
|
52
50
|
) -> Response:
|
|
53
|
-
|
|
54
|
-
translation_state = TranslationState()
|
|
51
|
+
deployment = get_client_deployment_id(request.headers)
|
|
55
52
|
|
|
56
53
|
try:
|
|
57
54
|
req: MessageCreateParams = await request.json()
|
|
@@ -61,7 +58,7 @@ async def _handle_messages(
|
|
|
61
58
|
f"Request body is not valid JSON: {e}",
|
|
62
59
|
) from e
|
|
63
60
|
|
|
64
|
-
|
|
61
|
+
translation_state = TranslationState()
|
|
65
62
|
chat_request = to_chat_completions_request(
|
|
66
63
|
req, deployment, translation_state
|
|
67
64
|
)
|
|
@@ -70,38 +67,14 @@ async def _handle_messages(
|
|
|
70
67
|
if log.isEnabledFor(logging.DEBUG):
|
|
71
68
|
log.debug(f"Translated request to Core: {json.dumps(body)}")
|
|
72
69
|
|
|
73
|
-
http_client = (
|
|
74
|
-
get_client
|
|
75
|
-
if isinstance(get_client, httpx.AsyncClient)
|
|
76
|
-
else await get_client(request)
|
|
77
|
-
)
|
|
78
|
-
client = (
|
|
79
|
-
core_legacy_client(dial_url, deployment, http_client)
|
|
80
|
-
if legacy
|
|
81
|
-
else core_v1_client(dial_url, http_client)
|
|
82
|
-
)
|
|
83
|
-
headers = core_headers(request)
|
|
84
|
-
|
|
85
70
|
if chat_request.stream:
|
|
86
|
-
events = await client.
|
|
87
|
-
"/chat/completions",
|
|
88
|
-
cast_to=ChatCompletion,
|
|
89
|
-
body=body,
|
|
90
|
-
options={"headers": headers},
|
|
91
|
-
stream=True,
|
|
92
|
-
stream_cls=AsyncStream[ChatCompletionChunk],
|
|
93
|
-
)
|
|
71
|
+
events = await client.create_stream(body)
|
|
94
72
|
return StreamingResponse(
|
|
95
73
|
translate_stream(events, deployment, translation_state),
|
|
96
74
|
media_type="text/event-stream",
|
|
97
75
|
)
|
|
98
76
|
|
|
99
|
-
response = await client.
|
|
100
|
-
"/chat/completions",
|
|
101
|
-
cast_to=ChatCompletion,
|
|
102
|
-
body=body,
|
|
103
|
-
options={"headers": headers},
|
|
104
|
-
)
|
|
77
|
+
response = await client.create_block(body)
|
|
105
78
|
message = to_anthropic_response(response, deployment, translation_state)
|
|
106
79
|
return JSONResponse(
|
|
107
80
|
content=message.model_dump(mode="json", exclude_unset=True)
|
|
@@ -113,17 +86,34 @@ def create_anthropic_api_app(
|
|
|
113
86
|
) -> FastAPI:
|
|
114
87
|
app = FastAPI()
|
|
115
88
|
|
|
89
|
+
async def _chat_client(request: Request, *, legacy: bool) -> ChatClientHttp:
|
|
90
|
+
http_client = (
|
|
91
|
+
await get_client(request) if callable(get_client) else get_client
|
|
92
|
+
)
|
|
93
|
+
dial_url = get_dial_url()
|
|
94
|
+
if legacy:
|
|
95
|
+
deployment_id = get_client_deployment_id(request.headers)
|
|
96
|
+
client = core_legacy_client(dial_url, deployment_id, http_client)
|
|
97
|
+
else:
|
|
98
|
+
client = core_v1_client(dial_url, http_client)
|
|
99
|
+
|
|
100
|
+
return ChatClientHttp(client, core_headers(request))
|
|
101
|
+
|
|
116
102
|
@app.post("/anthropic/v1/messages")
|
|
117
103
|
@with_debug_logging
|
|
118
104
|
@translator_error_handler
|
|
119
105
|
async def _handle_v1_messages(request: Request) -> Response:
|
|
120
|
-
return await
|
|
106
|
+
return await handle_anthropic_messages(
|
|
107
|
+
request, await _chat_client(request, legacy=False)
|
|
108
|
+
)
|
|
121
109
|
|
|
122
110
|
@app.post("/legacy/anthropic/v1/messages")
|
|
123
111
|
@with_debug_logging
|
|
124
112
|
@translator_error_handler
|
|
125
113
|
async def _handle_legacy_messages(request: Request) -> Response:
|
|
126
|
-
return await
|
|
114
|
+
return await handle_anthropic_messages(
|
|
115
|
+
request, await _chat_client(request, legacy=True)
|
|
116
|
+
)
|
|
127
117
|
|
|
128
118
|
app.add_api_route(
|
|
129
119
|
"/{full_path:path}",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Deployment names may be templates, e.g. "app-{idx}" or "app-{idx:int}",
|
|
2
|
+
in the same syntax Starlette uses for the path params."""
|
|
3
|
+
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from functools import cache
|
|
6
|
+
from re import Match
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from starlette.convertors import Convertor
|
|
10
|
+
from starlette.routing import compile_path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@cache
|
|
14
|
+
def _compile_path_template(
|
|
15
|
+
name: str,
|
|
16
|
+
) -> tuple[Callable[[str], Match[str] | None], str, dict[str, Convertor[Any]]]:
|
|
17
|
+
# compile_path reads a pattern with no leading slash as a *host* pattern,
|
|
18
|
+
# which it truncates at ":", so the name is compiled as a path instead.
|
|
19
|
+
regex, path_format, convertors = compile_path("/" + name)
|
|
20
|
+
|
|
21
|
+
def matcher(deployment_id: str) -> Match[str] | None:
|
|
22
|
+
return regex.match("/" + deployment_id)
|
|
23
|
+
|
|
24
|
+
return matcher, path_format.removeprefix("/"), convertors
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def match_deployment_id(
|
|
28
|
+
template: str, deployment_id: str
|
|
29
|
+
) -> dict[str, Any] | None:
|
|
30
|
+
"""Matches a deployment id against a deployment name template
|
|
31
|
+
(e.g. "app-{idx}"), returning the path params it resolves to,
|
|
32
|
+
or None if the name isn't a template or doesn't match."""
|
|
33
|
+
|
|
34
|
+
if "{" not in template:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
matcher, _path_format, convertors = _compile_path_template(template)
|
|
38
|
+
|
|
39
|
+
if (match := matcher(deployment_id)) is None:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
key: convertors[key].convert(value)
|
|
44
|
+
for key, value in match.groupdict().items()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def interpolate_deployment_id(deployment_id: str, path_params: dict) -> str:
|
|
49
|
+
"""Fills a deployment name template (e.g. "app-{idx:int}") with the
|
|
50
|
+
path params the request it came in on was routed by."""
|
|
51
|
+
|
|
52
|
+
_matcher, path_format, convertors = _compile_path_template(deployment_id)
|
|
53
|
+
|
|
54
|
+
# the format drops the convertor types, so "app-{idx:int}" -> "app-{idx}"
|
|
55
|
+
return path_format.format(**path_params) if convertors else deployment_id
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aidial-sdk"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.48.0.dev2"
|
|
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"
|
|
@@ -52,7 +52,6 @@ aiointercept = "^0.1.7"
|
|
|
52
52
|
requests = "^2.33"
|
|
53
53
|
responses = "^0.25.3"
|
|
54
54
|
httpx2 = "^2.12.0" # required for Starlette.TestClient
|
|
55
|
-
openai = "^2.46.0" # third-party logger for log-takeover tests
|
|
56
55
|
|
|
57
56
|
[tool.poetry.group.lint.dependencies]
|
|
58
57
|
pyright = "1.1.385"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/choice_base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/function_call.py
RENAMED
|
File without changes
|
{aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/chat_completion/function_tool_call.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/deployment/from_request_mixin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aidial_sdk-0.47.0.dev10 → aidial_sdk-0.48.0.dev2}/aidial_sdk/utils/_disconnect_middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|