aidial-sdk 0.44.0.dev3__tar.gz → 0.44.0.dev5__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 (62) hide show
  1. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/PKG-INFO +1 -1
  2. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/__init__.py +6 -0
  3. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/application.py +2 -0
  4. aidial_sdk-0.44.0.dev5/aidial_sdk/response_headers.py +95 -0
  5. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/pyproject.toml +1 -1
  6. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/LICENSE +0 -0
  7. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/README.md +0 -0
  8. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/_errors.py +0 -0
  9. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/_pydantic/__init__.py +0 -0
  10. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/_pydantic/_compat.py +0 -0
  11. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/_pydantic/_model_config.py +0 -0
  12. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/__init__.py +0 -0
  13. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/_cache.py +0 -0
  14. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/_types.py +0 -0
  15. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/base.py +0 -0
  16. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/choice.py +0 -0
  17. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/choice_base.py +0 -0
  18. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/chunks.py +0 -0
  19. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/enums.py +0 -0
  20. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/form.py +0 -0
  21. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/function_call.py +0 -0
  22. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/function_tool_call.py +0 -0
  23. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/request.py +0 -0
  24. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/response.py +0 -0
  25. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/chat_completion/stage.py +0 -0
  26. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/deployment/__init__.py +0 -0
  27. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/deployment/_headers.py +0 -0
  28. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/deployment/configuration.py +0 -0
  29. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/deployment/from_request_mixin.py +0 -0
  30. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/deployment/rate.py +0 -0
  31. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/deployment/tokenize.py +0 -0
  32. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/deployment/truncate_prompt.py +0 -0
  33. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/embeddings/__init__.py +0 -0
  34. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/embeddings/base.py +0 -0
  35. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/embeddings/request.py +0 -0
  36. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/embeddings/response.py +0 -0
  37. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/exceptions.py +0 -0
  38. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/header_propagator.py +0 -0
  39. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/py.typed +0 -0
  40. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/pydantic/v2.py +0 -0
  41. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/pydantic_v1/__init__.py +0 -0
  42. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/telemetry/__init__.py +0 -0
  43. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/telemetry/_context.py +0 -0
  44. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/telemetry/init.py +0 -0
  45. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/telemetry/types.py +0 -0
  46. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/__init__.py +0 -0
  47. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/_attachment.py +0 -0
  48. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/_cancel_scope.py +0 -0
  49. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/_content_stream.py +0 -0
  50. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/_disconnect_middleware.py +0 -0
  51. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/_indexed_list.py +0 -0
  52. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/_json_log_formatter.py +0 -0
  53. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/_logging.py +0 -0
  54. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/_reflection.py +0 -0
  55. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/env.py +0 -0
  56. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/errors.py +0 -0
  57. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/json.py +0 -0
  58. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/log_config.py +0 -0
  59. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/logging.py +0 -0
  60. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/merge_chunks.py +0 -0
  61. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/aidial_sdk/utils/pydantic.py +0 -0
  62. {aidial_sdk-0.44.0.dev3 → aidial_sdk-0.44.0.dev5}/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.44.0.dev3
3
+ Version: 0.44.0.dev5
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
@@ -1,5 +1,9 @@
1
1
  from aidial_sdk.application import DIALApp
2
2
  from aidial_sdk.exceptions import HTTPException
3
+ from aidial_sdk.response_headers import (
4
+ NonForwardableHeadersMiddleware,
5
+ strip_stale_content_headers,
6
+ )
3
7
  from aidial_sdk.utils.log_config import LogConfig, configure_root_logger
4
8
  from aidial_sdk.utils.logging import logger
5
9
 
@@ -7,6 +11,8 @@ __all__ = [
7
11
  "DIALApp",
8
12
  "HTTPException",
9
13
  "LogConfig",
14
+ "NonForwardableHeadersMiddleware",
10
15
  "configure_root_logger",
11
16
  "logger",
17
+ "strip_stale_content_headers",
12
18
  ]
@@ -34,6 +34,7 @@ from aidial_sdk.embeddings.request import Request as EmbeddingsRequest
34
34
  from aidial_sdk.exceptions import DeploymentNotFoundError
35
35
  from aidial_sdk.exceptions import HTTPException as DIALException
36
36
  from aidial_sdk.header_propagator import HeaderPropagator
37
+ from aidial_sdk.response_headers import NonForwardableHeadersMiddleware
37
38
  from aidial_sdk.telemetry.types import TelemetryConfig
38
39
  from aidial_sdk.utils._disconnect_middleware import DisconnectMiddleware
39
40
  from aidial_sdk.utils._reflection import get_method_implementation
@@ -136,6 +137,7 @@ class DIALApp(FastAPI):
136
137
  logging.getLogger("uvicorn.access").addFilter(PathFilter(path))
137
138
 
138
139
  self.add_middleware(DisconnectMiddleware)
140
+ self.add_middleware(NonForwardableHeadersMiddleware)
139
141
 
140
142
  self.add_exception_handler(
141
143
  ValidationError, pydantic_validation_exception_handler
@@ -0,0 +1,95 @@
1
+ """Response headers a DIAL component must not relay downstream.
2
+
3
+ A component sitting in front of an upstream API - a model adapter proxying the
4
+ vendor's response, an interceptor rebuilding it - receives the upstream's
5
+ response headers and tends to pass them on. Some of them describe the
6
+ **upstream hop**, not the response the component itself emits:
7
+
8
+ * hop-by-hop headers (RFC 9110 7.6.1). ``transfer-encoding`` is the harmful
9
+ one: the ASGI server computes its own ``content-length`` for the body the
10
+ component emits, and a message carrying both is rejected by a strict HTTP
11
+ parser (RFC 9112 6.2) - DIAL Core being one. A relayed ``connection: close``
12
+ tears down the *downstream* connection once the response is over.
13
+ * ``server`` and ``date``, which the ASGI server supplies itself. Both are
14
+ singleton fields (RFC 9110 5.6.6), so the upstream's copy duplicates them.
15
+
16
+ Which headers these are follows from the HTTP spec alone, never from what the
17
+ component does, so ``NonForwardableHeadersMiddleware`` strips them from every
18
+ response the app emits and takes no configuration:
19
+
20
+ * an app built on ``DIALApp`` has it installed already - nothing to do;
21
+ * an app built on plain ``FastAPI``/``Starlette`` installs it once::
22
+
23
+ app = FastAPI()
24
+ app.add_middleware(NonForwardableHeadersMiddleware)
25
+
26
+ It covers the whole app, including sub-apps mounted into it, so a passthrough
27
+ proxy mounted as one needs no strip of its own.
28
+
29
+ ``content-length`` and ``content-encoding`` are a different matter: they are
30
+ accurate for the upstream body and go stale only once a component reads and
31
+ rebuilds it. The middleware cannot tell a stale one from the correct one the
32
+ ASGI server has just computed, so the rebuilding code drops them itself, on the
33
+ upstream headers it is about to reuse::
34
+
35
+ body = await upstream_response.aread()
36
+ strip_stale_content_headers(upstream_response.headers)
37
+ return Response(content=body, headers=upstream_response.headers)
38
+ """
39
+
40
+ from collections.abc import MutableMapping
41
+
42
+ from starlette.types import ASGIApp, Message, Receive, Scope, Send
43
+
44
+ _HOP_BY_HOP_HEADERS = frozenset(
45
+ {
46
+ b"connection",
47
+ b"keep-alive",
48
+ b"proxy-authenticate",
49
+ b"proxy-authorization",
50
+ b"te",
51
+ b"trailer",
52
+ b"transfer-encoding",
53
+ b"upgrade",
54
+ }
55
+ )
56
+
57
+ _SERVER_MANAGED_HEADERS = frozenset({b"server", b"date"})
58
+
59
+ _NON_FORWARDABLE_HEADERS = _HOP_BY_HOP_HEADERS | _SERVER_MANAGED_HEADERS
60
+
61
+ _STALE_CONTENT_HEADERS = ("content-length", "content-encoding")
62
+
63
+
64
+ class NonForwardableHeadersMiddleware:
65
+ def __init__(self, app: ASGIApp) -> None:
66
+ self.app = app
67
+
68
+ async def __call__(
69
+ self, scope: Scope, receive: Receive, send: Send
70
+ ) -> None:
71
+ if scope["type"] != "http":
72
+ await self.app(scope, receive, send)
73
+ return
74
+
75
+ async def send_wrapper(message: Message) -> None:
76
+ if message["type"] == "http.response.start":
77
+ message["headers"] = [
78
+ (key, value)
79
+ for key, value in message["headers"]
80
+ if key.lower() not in _NON_FORWARDABLE_HEADERS
81
+ ]
82
+ await send(message)
83
+
84
+ await self.app(scope, receive, send_wrapper)
85
+
86
+
87
+ def strip_stale_content_headers(headers: MutableMapping[str, str]) -> None:
88
+ """Drop from `headers` the content headers describing the upstream body.
89
+
90
+ Call it on the upstream response headers once the body they describe has
91
+ been read and rebuilt, so that the framing and the content coding of the
92
+ new body are recomputed rather than inherited.
93
+ """
94
+ for header in _STALE_CONTENT_HEADERS:
95
+ headers.pop(header, None)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aidial-sdk"
3
- version = "0.44.0.dev3"
3
+ version = "0.44.0.dev5"
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"