ommlds 0.0.0.dev503__py3-none-any.whl → 0.0.0.dev505__py3-none-any.whl
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.
- ommlds/.omlish-manifests.json +5 -5
- ommlds/__about__.py +1 -1
- ommlds/backends/anthropic/protocol/_dataclasses.py +16 -16
- ommlds/backends/cerebras/_dataclasses.py +42 -42
- ommlds/backends/google/protocol/_dataclasses.py +64 -64
- ommlds/backends/groq/_dataclasses.py +36 -36
- ommlds/backends/ollama/_dataclasses.py +28 -28
- ommlds/backends/openai/protocol/_dataclasses.py +88 -88
- ommlds/backends/tavily/_dataclasses.py +16 -16
- ommlds/cli/_dataclasses.py +212 -43
- ommlds/cli/sessions/chat/interfaces/textual/app.py +34 -0
- ommlds/cli/sessions/chat/interfaces/textual/configs.py +1 -1
- ommlds/cli/sessions/chat/interfaces/textual/inject.py +14 -0
- ommlds/cli/sessions/chat/interfaces/textual/inputhistory.py +174 -0
- ommlds/cli/sessions/chat/interfaces/textual/widgets/input.py +42 -8
- ommlds/minichain/_dataclasses.py +361 -343
- ommlds/minichain/backends/impls/cerebras/stream.py +39 -52
- ommlds/minichain/backends/impls/google/chat.py +11 -82
- ommlds/minichain/backends/impls/google/protocol.py +105 -0
- ommlds/minichain/backends/impls/google/stream.py +49 -132
- ommlds/minichain/backends/impls/groq/stream.py +40 -53
- ommlds/minichain/backends/impls/openai/stream.py +40 -87
- ommlds/minichain/http/__init__.py +0 -0
- ommlds/minichain/http/stream.py +195 -0
- {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/METADATA +6 -6
- {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/RECORD +30 -26
- {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/WHEEL +0 -0
- {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/entry_points.txt +0 -0
- {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/licenses/LICENSE +0 -0
- {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/top_level.txt +0 -0
|
@@ -6,21 +6,18 @@ from omlish import typedvalues as tv
|
|
|
6
6
|
from omlish.formats import json
|
|
7
7
|
from omlish.http import all as http
|
|
8
8
|
from omlish.http import sse
|
|
9
|
-
from omlish.io.buffers import DelimitingBuffer
|
|
10
9
|
|
|
11
10
|
from .....backends.groq import protocol as pt
|
|
12
11
|
from .....backends.groq.clients import REQUIRED_HTTP_HEADERS
|
|
13
|
-
from ....chat.choices.services import ChatChoicesOutputs
|
|
14
12
|
from ....chat.choices.stream.services import ChatChoicesStreamRequest
|
|
15
13
|
from ....chat.choices.stream.services import ChatChoicesStreamResponse
|
|
16
14
|
from ....chat.choices.stream.services import static_check_is_chat_choices_stream_service
|
|
17
15
|
from ....chat.choices.stream.types import AiChoicesDeltas
|
|
18
16
|
from ....chat.tools.types import Tool
|
|
19
17
|
from ....configs import Config
|
|
20
|
-
from ....
|
|
18
|
+
from ....http.stream import BytesHttpStreamResponseBuilder
|
|
19
|
+
from ....http.stream import SimpleSseLinesHttpStreamResponseHandler
|
|
21
20
|
from ....standard import ApiKey
|
|
22
|
-
from ....stream.services import StreamResponseSink
|
|
23
|
-
from ....stream.services import new_stream_response
|
|
24
21
|
from .chat import GroqChatChoicesService
|
|
25
22
|
from .names import MODEL_NAMES
|
|
26
23
|
from .protocol import build_gq_request_messages
|
|
@@ -50,6 +47,35 @@ class GroqChatChoicesStreamService:
|
|
|
50
47
|
self._model_name = cc.pop(GroqChatChoicesService.DEFAULT_MODEL_NAME)
|
|
51
48
|
self._api_key = ApiKey.pop_secret(cc, env='GROQ_API_KEY')
|
|
52
49
|
|
|
50
|
+
URL: ta.ClassVar[str] = 'https://api.groq.com/openai/v1/chat/completions'
|
|
51
|
+
|
|
52
|
+
def _process_sse(self, so: sse.SseDecoderOutput) -> ta.Sequence[AiChoicesDeltas | None]:
|
|
53
|
+
if not (isinstance(so, sse.SseEvent) and so.type == b'message'):
|
|
54
|
+
return []
|
|
55
|
+
|
|
56
|
+
ss = so.data.decode('utf-8')
|
|
57
|
+
if ss == '[DONE]':
|
|
58
|
+
return [None]
|
|
59
|
+
|
|
60
|
+
sj = json.loads(ss) # ChatCompletionChunk
|
|
61
|
+
|
|
62
|
+
check.state(sj['object'] == 'chat.completion.chunk')
|
|
63
|
+
|
|
64
|
+
ccc = msh.unmarshal(sj, pt.ChatCompletionChunk)
|
|
65
|
+
|
|
66
|
+
# FIXME: stop reason
|
|
67
|
+
if not ccc.choices:
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
if any(choice.finish_reason for choice in ccc.choices):
|
|
71
|
+
check.state(all(choice.finish_reason for choice in ccc.choices))
|
|
72
|
+
return [None]
|
|
73
|
+
|
|
74
|
+
return [AiChoicesDeltas([
|
|
75
|
+
build_mc_ai_choice_deltas(choice.delta)
|
|
76
|
+
for choice in ccc.choices
|
|
77
|
+
])]
|
|
78
|
+
|
|
53
79
|
READ_CHUNK_SIZE: ta.ClassVar[int] = -1
|
|
54
80
|
|
|
55
81
|
async def invoke(self, request: ChatChoicesStreamRequest) -> ChatChoicesStreamResponse:
|
|
@@ -69,7 +95,7 @@ class GroqChatChoicesStreamService:
|
|
|
69
95
|
raw_request = msh.marshal(gq_request)
|
|
70
96
|
|
|
71
97
|
http_request = http.HttpRequest(
|
|
72
|
-
|
|
98
|
+
self.URL,
|
|
73
99
|
headers={
|
|
74
100
|
http.consts.HEADER_CONTENT_TYPE: http.consts.CONTENT_TYPE_JSON,
|
|
75
101
|
http.consts.HEADER_AUTH: http.consts.format_bearer_auth_header(check.not_none(self._api_key).reveal()),
|
|
@@ -78,50 +104,11 @@ class GroqChatChoicesStreamService:
|
|
|
78
104
|
data=json.dumps(raw_request).encode('utf-8'),
|
|
79
105
|
)
|
|
80
106
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
http_response
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
b = await http_response.stream.read1(self.READ_CHUNK_SIZE)
|
|
90
|
-
for l in db.feed(b):
|
|
91
|
-
if isinstance(l, DelimitingBuffer.Incomplete):
|
|
92
|
-
# FIXME: handle
|
|
93
|
-
raise TypeError(l)
|
|
94
|
-
|
|
95
|
-
# FIXME: https://platform.openai.com/docs/guides/function-calling?api-mode=responses#streaming
|
|
96
|
-
for so in sd.process_line(l):
|
|
97
|
-
if isinstance(so, sse.SseEvent) and so.type == b'message':
|
|
98
|
-
ss = so.data.decode('utf-8')
|
|
99
|
-
if ss == '[DONE]':
|
|
100
|
-
return []
|
|
101
|
-
|
|
102
|
-
sj = json.loads(ss) # ChatCompletionChunk
|
|
103
|
-
|
|
104
|
-
check.state(sj['object'] == 'chat.completion.chunk')
|
|
105
|
-
|
|
106
|
-
ccc = msh.unmarshal(sj, pt.ChatCompletionChunk)
|
|
107
|
-
|
|
108
|
-
# FIXME: stop reason
|
|
109
|
-
if not ccc.choices:
|
|
110
|
-
continue
|
|
111
|
-
|
|
112
|
-
if any(choice.finish_reason for choice in ccc.choices):
|
|
113
|
-
check.state(all(choice.finish_reason for choice in ccc.choices))
|
|
114
|
-
break
|
|
115
|
-
|
|
116
|
-
await sink.emit(AiChoicesDeltas([
|
|
117
|
-
build_mc_ai_choice_deltas(choice.delta)
|
|
118
|
-
for choice in ccc.choices
|
|
119
|
-
]))
|
|
120
|
-
|
|
121
|
-
if not b:
|
|
122
|
-
return []
|
|
123
|
-
|
|
124
|
-
# raw_response = json.loads(check.not_none(http_response.data).decode('utf-8'))
|
|
125
|
-
# return rh.build_response(raw_response)
|
|
126
|
-
|
|
127
|
-
return await new_stream_response(rs, inner)
|
|
107
|
+
return await BytesHttpStreamResponseBuilder(
|
|
108
|
+
self._http_client,
|
|
109
|
+
lambda http_response: SimpleSseLinesHttpStreamResponseHandler(self._process_sse).as_lines().as_bytes(),
|
|
110
|
+
read_chunk_size=self.READ_CHUNK_SIZE,
|
|
111
|
+
).new_stream_response(
|
|
112
|
+
http_request,
|
|
113
|
+
request.options,
|
|
114
|
+
)
|
|
@@ -4,16 +4,13 @@ https://platform.openai.com/docs/api-reference/responses-streaming
|
|
|
4
4
|
import typing as ta
|
|
5
5
|
|
|
6
6
|
from omlish import check
|
|
7
|
-
from omlish import dataclasses as dc
|
|
8
7
|
from omlish import marshal as msh
|
|
9
8
|
from omlish import typedvalues as tv
|
|
10
9
|
from omlish.formats import json
|
|
11
10
|
from omlish.http import all as http
|
|
12
11
|
from omlish.http import sse
|
|
13
|
-
from omlish.io.buffers import DelimitingBuffer
|
|
14
12
|
|
|
15
13
|
from .....backends.openai import protocol as pt
|
|
16
|
-
from ....chat.choices.services import ChatChoicesOutputs
|
|
17
14
|
from ....chat.choices.stream.services import ChatChoicesStreamRequest
|
|
18
15
|
from ....chat.choices.stream.services import ChatChoicesStreamResponse
|
|
19
16
|
from ....chat.choices.stream.services import static_check_is_chat_choices_stream_service
|
|
@@ -21,12 +18,11 @@ from ....chat.choices.stream.types import AiChoiceDeltas
|
|
|
21
18
|
from ....chat.choices.stream.types import AiChoicesDeltas
|
|
22
19
|
from ....chat.choices.stream.types import ChatChoicesStreamOption
|
|
23
20
|
from ....configs import Config
|
|
21
|
+
from ....http.stream import BytesHttpStreamResponseBuilder
|
|
22
|
+
from ....http.stream import SimpleSseLinesHttpStreamResponseHandler
|
|
24
23
|
from ....resources import ResourcesOption
|
|
25
|
-
from ....resources import UseResources
|
|
26
24
|
from ....standard import ApiKey
|
|
27
25
|
from ....stream.services import StreamOption
|
|
28
|
-
from ....stream.services import StreamResponseSink
|
|
29
|
-
from ....stream.services import new_stream_response
|
|
30
26
|
from .chat import OpenaiChatChoicesService
|
|
31
27
|
from .format import OpenaiChatRequestHandler
|
|
32
28
|
from .format import build_mc_ai_delta
|
|
@@ -36,12 +32,6 @@ from .names import CHAT_MODEL_NAMES
|
|
|
36
32
|
##
|
|
37
33
|
|
|
38
34
|
|
|
39
|
-
@dc.dataclass()
|
|
40
|
-
class OpenaiChatChoicesStreamServiceError(Exception):
|
|
41
|
-
status: int
|
|
42
|
-
data: ta.Any | None = None
|
|
43
|
-
|
|
44
|
-
|
|
45
35
|
# @omlish-manifest $.minichain.registries.manifests.RegistryManifest(
|
|
46
36
|
# name='openai',
|
|
47
37
|
# type='ChatChoicesStreamService',
|
|
@@ -61,11 +51,38 @@ class OpenaiChatChoicesStreamService:
|
|
|
61
51
|
self._model_name = cc.pop(OpenaiChatChoicesService.DEFAULT_MODEL_NAME)
|
|
62
52
|
self._api_key = ApiKey.pop_secret(cc, env='OPENAI_API_KEY')
|
|
63
53
|
|
|
54
|
+
URL: ta.ClassVar[str] = 'https://api.openai.com/v1/chat/completions'
|
|
55
|
+
|
|
56
|
+
def _process_sse(self, so: sse.SseDecoderOutput) -> ta.Sequence[AiChoicesDeltas | None]:
|
|
57
|
+
if not (isinstance(so, sse.SseEvent) and so.type == b'message'):
|
|
58
|
+
return []
|
|
59
|
+
|
|
60
|
+
ss = so.data.decode('utf-8')
|
|
61
|
+
if ss == '[DONE]':
|
|
62
|
+
return [None]
|
|
63
|
+
|
|
64
|
+
sj = json.loads(ss) # ChatCompletionChunk
|
|
65
|
+
|
|
66
|
+
check.state(sj['object'] == 'chat.completion.chunk')
|
|
67
|
+
|
|
68
|
+
ccc = msh.unmarshal(sj, pt.ChatCompletionChunk)
|
|
69
|
+
|
|
70
|
+
# FIXME: stop reason
|
|
71
|
+
if not ccc.choices:
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
if any(choice.finish_reason for choice in ccc.choices):
|
|
75
|
+
check.state(all(choice.finish_reason for choice in ccc.choices))
|
|
76
|
+
return [None]
|
|
77
|
+
|
|
78
|
+
return [AiChoicesDeltas([
|
|
79
|
+
AiChoiceDeltas([build_mc_ai_delta(choice.delta)])
|
|
80
|
+
for choice in ccc.choices
|
|
81
|
+
])]
|
|
82
|
+
|
|
64
83
|
READ_CHUNK_SIZE: ta.ClassVar[int] = -1
|
|
65
84
|
|
|
66
85
|
async def invoke(self, request: ChatChoicesStreamRequest) -> ChatChoicesStreamResponse:
|
|
67
|
-
# check.isinstance(request, ChatRequest)
|
|
68
|
-
|
|
69
86
|
rh = OpenaiChatRequestHandler(
|
|
70
87
|
request.v,
|
|
71
88
|
*[
|
|
@@ -85,7 +102,7 @@ class OpenaiChatChoicesStreamService:
|
|
|
85
102
|
raw_request = msh.marshal(rh.oai_request())
|
|
86
103
|
|
|
87
104
|
http_request = http.HttpRequest(
|
|
88
|
-
|
|
105
|
+
self.URL,
|
|
89
106
|
headers={
|
|
90
107
|
http.consts.HEADER_CONTENT_TYPE: http.consts.CONTENT_TYPE_JSON,
|
|
91
108
|
http.consts.HEADER_AUTH: http.consts.format_bearer_auth_header(check.not_none(self._api_key).reveal()),
|
|
@@ -93,75 +110,11 @@ class OpenaiChatChoicesStreamService:
|
|
|
93
110
|
data=json.dumps(raw_request).encode('utf-8'),
|
|
94
111
|
)
|
|
95
112
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
http_response
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
except Exception as e: # noqa
|
|
105
|
-
data = e
|
|
106
|
-
try:
|
|
107
|
-
data_obj = json.loads(data.decode())
|
|
108
|
-
except Exception as e: # noqa
|
|
109
|
-
pass
|
|
110
|
-
else:
|
|
111
|
-
data = data_obj
|
|
112
|
-
raise OpenaiChatChoicesStreamServiceError(http_response.status, data)
|
|
113
|
-
|
|
114
|
-
async def inner(sink: StreamResponseSink[AiChoicesDeltas]) -> ta.Sequence[ChatChoicesOutputs]:
|
|
115
|
-
db = DelimitingBuffer([b'\r', b'\n', b'\r\n'])
|
|
116
|
-
sd = sse.SseDecoder()
|
|
117
|
-
|
|
118
|
-
# bs = []
|
|
119
|
-
# ls = []
|
|
120
|
-
# sos = []
|
|
121
|
-
|
|
122
|
-
while True:
|
|
123
|
-
b = await http_response.stream.read1(self.READ_CHUNK_SIZE)
|
|
124
|
-
# bs.append(b)
|
|
125
|
-
|
|
126
|
-
for l in db.feed(b):
|
|
127
|
-
# ls.append(l)
|
|
128
|
-
|
|
129
|
-
if isinstance(l, DelimitingBuffer.Incomplete):
|
|
130
|
-
# FIXME: handle
|
|
131
|
-
raise TypeError(l)
|
|
132
|
-
|
|
133
|
-
# FIXME: https://platform.openai.com/docs/guides/function-calling?api-mode=responses#streaming
|
|
134
|
-
for so in sd.process_line(l):
|
|
135
|
-
# sos.append(so)
|
|
136
|
-
|
|
137
|
-
if isinstance(so, sse.SseEvent) and so.type == b'message':
|
|
138
|
-
ss = so.data.decode('utf-8')
|
|
139
|
-
if ss == '[DONE]':
|
|
140
|
-
return []
|
|
141
|
-
|
|
142
|
-
sj = json.loads(ss) # ChatCompletionChunk
|
|
143
|
-
|
|
144
|
-
check.state(sj['object'] == 'chat.completion.chunk')
|
|
145
|
-
|
|
146
|
-
ccc = msh.unmarshal(sj, pt.ChatCompletionChunk)
|
|
147
|
-
|
|
148
|
-
# FIXME: stop reason
|
|
149
|
-
if not ccc.choices:
|
|
150
|
-
continue
|
|
151
|
-
|
|
152
|
-
if any(choice.finish_reason for choice in ccc.choices):
|
|
153
|
-
check.state(all(choice.finish_reason for choice in ccc.choices))
|
|
154
|
-
break
|
|
155
|
-
|
|
156
|
-
await sink.emit(AiChoicesDeltas([
|
|
157
|
-
AiChoiceDeltas([build_mc_ai_delta(choice.delta)])
|
|
158
|
-
for choice in ccc.choices
|
|
159
|
-
]))
|
|
160
|
-
|
|
161
|
-
if not b:
|
|
162
|
-
return []
|
|
163
|
-
|
|
164
|
-
# raw_response = json.loads(check.not_none(http_response.data).decode('utf-8'))
|
|
165
|
-
# return rh.build_response(raw_response)
|
|
166
|
-
|
|
167
|
-
return await new_stream_response(rs, inner)
|
|
113
|
+
return await BytesHttpStreamResponseBuilder(
|
|
114
|
+
self._http_client,
|
|
115
|
+
lambda http_response: SimpleSseLinesHttpStreamResponseHandler(self._process_sse).as_lines().as_bytes(),
|
|
116
|
+
read_chunk_size=self.READ_CHUNK_SIZE,
|
|
117
|
+
).new_stream_response(
|
|
118
|
+
http_request,
|
|
119
|
+
request.options,
|
|
120
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- better pipeline composition lol
|
|
4
|
+
"""
|
|
5
|
+
import typing as ta
|
|
6
|
+
|
|
7
|
+
from omlish import check
|
|
8
|
+
from omlish import dataclasses as dc
|
|
9
|
+
from omlish import lang
|
|
10
|
+
from omlish.http import all as http
|
|
11
|
+
from omlish.http import sse
|
|
12
|
+
from omlish.io.buffers import DelimitingBuffer
|
|
13
|
+
|
|
14
|
+
from ..resources import UseResources
|
|
15
|
+
from ..stream.services import StreamResponse
|
|
16
|
+
from ..stream.services import StreamResponseSink
|
|
17
|
+
from ..stream.services import new_stream_response
|
|
18
|
+
from ..types import Option
|
|
19
|
+
from ..types import Output
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dc.dataclass()
|
|
26
|
+
@dc.extra_class_params(default_repr_fn=lang.opt_repr)
|
|
27
|
+
class HttpStreamResponseError(Exception):
|
|
28
|
+
response: http.BaseHttpResponse
|
|
29
|
+
|
|
30
|
+
data: bytes | None = None
|
|
31
|
+
data_exception: Exception | None = None
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
async def from_response(cls, response: http.AsyncStreamHttpResponse) -> 'HttpStreamResponseError':
|
|
35
|
+
data: bytes | None = None
|
|
36
|
+
data_exception: Exception | None = None
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
data = await response.stream.readall()
|
|
40
|
+
except Exception as de: # noqa
|
|
41
|
+
data_exception = de
|
|
42
|
+
|
|
43
|
+
return HttpStreamResponseError(
|
|
44
|
+
response,
|
|
45
|
+
data=data,
|
|
46
|
+
data_exception=data_exception,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class HttpStreamResponseHandler(lang.Abstract):
|
|
54
|
+
def start(self) -> ta.Sequence[Output]:
|
|
55
|
+
return ()
|
|
56
|
+
|
|
57
|
+
def finish(self) -> ta.Sequence[Output]:
|
|
58
|
+
return ()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class BytesHttpStreamResponseHandler(HttpStreamResponseHandler, lang.Abstract):
|
|
65
|
+
def process_bytes(self, data: bytes) -> ta.Iterable:
|
|
66
|
+
return ()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class BytesHttpStreamResponseBuilder:
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
http_client: http.AsyncHttpClient | None,
|
|
73
|
+
handling: ta.Callable[[http.AsyncStreamHttpResponse], BytesHttpStreamResponseHandler],
|
|
74
|
+
*,
|
|
75
|
+
read_chunk_size: int = -1,
|
|
76
|
+
) -> None:
|
|
77
|
+
super().__init__()
|
|
78
|
+
|
|
79
|
+
self._http_client = http_client
|
|
80
|
+
self._handling = handling
|
|
81
|
+
self._read_chunk_size = read_chunk_size
|
|
82
|
+
|
|
83
|
+
async def new_stream_response(
|
|
84
|
+
self,
|
|
85
|
+
http_request: http.HttpRequest,
|
|
86
|
+
options: ta.Sequence[Option],
|
|
87
|
+
) -> StreamResponse:
|
|
88
|
+
async with UseResources.or_new(options) as rs:
|
|
89
|
+
http_client = await rs.enter_async_context(http.manage_async_client(self._http_client))
|
|
90
|
+
http_response = await rs.enter_async_context(await http_client.stream_request(http_request))
|
|
91
|
+
|
|
92
|
+
if http_response.status != 200:
|
|
93
|
+
raise await HttpStreamResponseError.from_response(http_response)
|
|
94
|
+
|
|
95
|
+
handler = self._handling(http_response)
|
|
96
|
+
|
|
97
|
+
async def inner(sink: StreamResponseSink) -> ta.Sequence | None:
|
|
98
|
+
while True:
|
|
99
|
+
b = await http_response.stream.read1(self._read_chunk_size)
|
|
100
|
+
|
|
101
|
+
for v in handler.process_bytes(b):
|
|
102
|
+
if v is None:
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
await sink.emit(v)
|
|
106
|
+
|
|
107
|
+
if not b:
|
|
108
|
+
break
|
|
109
|
+
|
|
110
|
+
return handler.finish()
|
|
111
|
+
|
|
112
|
+
return await new_stream_response(
|
|
113
|
+
rs,
|
|
114
|
+
inner,
|
|
115
|
+
handler.start(),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
##
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class LinesHttpStreamResponseHandler(HttpStreamResponseHandler, lang.Abstract):
|
|
123
|
+
def process_line(self, line: bytes) -> ta.Iterable:
|
|
124
|
+
return ()
|
|
125
|
+
|
|
126
|
+
def as_bytes(self) -> BytesHttpStreamResponseHandler:
|
|
127
|
+
return LinesBytesHttpStreamResponseHandler(self)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class LinesBytesHttpStreamResponseHandler(BytesHttpStreamResponseHandler):
|
|
131
|
+
def __init__(self, handler: LinesHttpStreamResponseHandler) -> None:
|
|
132
|
+
super().__init__()
|
|
133
|
+
|
|
134
|
+
self._handler = handler
|
|
135
|
+
|
|
136
|
+
self._db = DelimitingBuffer([b'\r', b'\n', b'\r\n'])
|
|
137
|
+
|
|
138
|
+
def start(self) -> ta.Sequence[Output]:
|
|
139
|
+
return self._handler.start()
|
|
140
|
+
|
|
141
|
+
def process_bytes(self, data: bytes) -> ta.Iterable:
|
|
142
|
+
for o in self._db.feed(data):
|
|
143
|
+
if isinstance(o, bytes):
|
|
144
|
+
yield from self._handler.process_line(o)
|
|
145
|
+
|
|
146
|
+
else:
|
|
147
|
+
raise TypeError(o)
|
|
148
|
+
|
|
149
|
+
def finish(self) -> ta.Sequence[Output]:
|
|
150
|
+
check.state(self._db.is_closed)
|
|
151
|
+
|
|
152
|
+
return self._handler.finish()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
##
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class SseHttpStreamResponseHandler(HttpStreamResponseHandler, lang.Abstract):
|
|
159
|
+
def process_sse(self, so: sse.SseDecoderOutput) -> ta.Iterable:
|
|
160
|
+
return ()
|
|
161
|
+
|
|
162
|
+
def as_lines(self) -> LinesHttpStreamResponseHandler:
|
|
163
|
+
return SseLinesHttpStreamResponseHandler(self)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class SseLinesHttpStreamResponseHandler(LinesHttpStreamResponseHandler):
|
|
167
|
+
def __init__(self, handler: SseHttpStreamResponseHandler) -> None:
|
|
168
|
+
super().__init__()
|
|
169
|
+
|
|
170
|
+
self._handler = handler
|
|
171
|
+
|
|
172
|
+
self._sd = sse.SseDecoder()
|
|
173
|
+
|
|
174
|
+
def start(self) -> ta.Sequence[Output]:
|
|
175
|
+
return self._handler.start()
|
|
176
|
+
|
|
177
|
+
def process_line(self, line: bytes) -> ta.Iterable:
|
|
178
|
+
for so in self._sd.process_line(line):
|
|
179
|
+
yield from self._handler.process_sse(so)
|
|
180
|
+
|
|
181
|
+
def finish(self) -> ta.Sequence[Output]:
|
|
182
|
+
return self._handler.finish()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
#
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class SimpleSseLinesHttpStreamResponseHandler(SseHttpStreamResponseHandler):
|
|
189
|
+
def __init__(self, fn: ta.Callable[[sse.SseDecoderOutput], ta.Iterable]) -> None:
|
|
190
|
+
super().__init__()
|
|
191
|
+
|
|
192
|
+
self._fn = fn
|
|
193
|
+
|
|
194
|
+
def process_sse(self, so: sse.SseDecoderOutput) -> ta.Iterable:
|
|
195
|
+
return self._fn(so)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ommlds
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev505
|
|
4
4
|
Summary: ommlds
|
|
5
5
|
Author: wrmsr
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -14,14 +14,14 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
14
14
|
Requires-Python: >=3.13
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
|
-
Requires-Dist: omlish==0.0.0.
|
|
17
|
+
Requires-Dist: omlish==0.0.0.dev505
|
|
18
18
|
Provides-Extra: all
|
|
19
|
-
Requires-Dist: omdev==0.0.0.
|
|
19
|
+
Requires-Dist: omdev==0.0.0.dev505; extra == "all"
|
|
20
20
|
Requires-Dist: llama-cpp-python~=0.3; extra == "all"
|
|
21
21
|
Requires-Dist: mlx~=0.30; sys_platform == "darwin" and extra == "all"
|
|
22
22
|
Requires-Dist: mlx-lm~=0.29; sys_platform == "darwin" and extra == "all"
|
|
23
23
|
Requires-Dist: tiktoken~=0.12; extra == "all"
|
|
24
|
-
Requires-Dist: tinygrad~=0.
|
|
24
|
+
Requires-Dist: tinygrad~=0.12; extra == "all"
|
|
25
25
|
Requires-Dist: tokenizers~=0.22; extra == "all"
|
|
26
26
|
Requires-Dist: torch~=2.9; extra == "all"
|
|
27
27
|
Requires-Dist: transformers~=4.57; extra == "all"
|
|
@@ -38,13 +38,13 @@ Requires-Dist: mwparserfromhell~=0.7; extra == "all"
|
|
|
38
38
|
Requires-Dist: wikitextparser~=0.56; extra == "all"
|
|
39
39
|
Requires-Dist: lxml>=5.3; python_version < "3.13" and extra == "all"
|
|
40
40
|
Provides-Extra: omdev
|
|
41
|
-
Requires-Dist: omdev==0.0.0.
|
|
41
|
+
Requires-Dist: omdev==0.0.0.dev505; extra == "omdev"
|
|
42
42
|
Provides-Extra: backends
|
|
43
43
|
Requires-Dist: llama-cpp-python~=0.3; extra == "backends"
|
|
44
44
|
Requires-Dist: mlx~=0.30; sys_platform == "darwin" and extra == "backends"
|
|
45
45
|
Requires-Dist: mlx-lm~=0.29; sys_platform == "darwin" and extra == "backends"
|
|
46
46
|
Requires-Dist: tiktoken~=0.12; extra == "backends"
|
|
47
|
-
Requires-Dist: tinygrad~=0.
|
|
47
|
+
Requires-Dist: tinygrad~=0.12; extra == "backends"
|
|
48
48
|
Requires-Dist: tokenizers~=0.22; extra == "backends"
|
|
49
49
|
Requires-Dist: torch~=2.9; extra == "backends"
|
|
50
50
|
Requires-Dist: transformers~=4.57; extra == "backends"
|