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.
Files changed (30) hide show
  1. ommlds/.omlish-manifests.json +5 -5
  2. ommlds/__about__.py +1 -1
  3. ommlds/backends/anthropic/protocol/_dataclasses.py +16 -16
  4. ommlds/backends/cerebras/_dataclasses.py +42 -42
  5. ommlds/backends/google/protocol/_dataclasses.py +64 -64
  6. ommlds/backends/groq/_dataclasses.py +36 -36
  7. ommlds/backends/ollama/_dataclasses.py +28 -28
  8. ommlds/backends/openai/protocol/_dataclasses.py +88 -88
  9. ommlds/backends/tavily/_dataclasses.py +16 -16
  10. ommlds/cli/_dataclasses.py +212 -43
  11. ommlds/cli/sessions/chat/interfaces/textual/app.py +34 -0
  12. ommlds/cli/sessions/chat/interfaces/textual/configs.py +1 -1
  13. ommlds/cli/sessions/chat/interfaces/textual/inject.py +14 -0
  14. ommlds/cli/sessions/chat/interfaces/textual/inputhistory.py +174 -0
  15. ommlds/cli/sessions/chat/interfaces/textual/widgets/input.py +42 -8
  16. ommlds/minichain/_dataclasses.py +361 -343
  17. ommlds/minichain/backends/impls/cerebras/stream.py +39 -52
  18. ommlds/minichain/backends/impls/google/chat.py +11 -82
  19. ommlds/minichain/backends/impls/google/protocol.py +105 -0
  20. ommlds/minichain/backends/impls/google/stream.py +49 -132
  21. ommlds/minichain/backends/impls/groq/stream.py +40 -53
  22. ommlds/minichain/backends/impls/openai/stream.py +40 -87
  23. ommlds/minichain/http/__init__.py +0 -0
  24. ommlds/minichain/http/stream.py +195 -0
  25. {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/METADATA +6 -6
  26. {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/RECORD +30 -26
  27. {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/WHEEL +0 -0
  28. {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/entry_points.txt +0 -0
  29. {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/licenses/LICENSE +0 -0
  30. {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 ....resources import UseResources
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
- 'https://api.groq.com/openai/v1/chat/completions',
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
- async with UseResources.or_new(request.options) as rs:
82
- http_client = await rs.enter_async_context(http.manage_async_client(self._http_client))
83
- http_response = await rs.enter_async_context(await http_client.stream_request(http_request))
84
-
85
- async def inner(sink: StreamResponseSink[AiChoicesDeltas]) -> ta.Sequence[ChatChoicesOutputs]:
86
- db = DelimitingBuffer([b'\r', b'\n', b'\r\n'])
87
- sd = sse.SseDecoder()
88
- while True:
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
- 'https://api.openai.com/v1/chat/completions',
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
- async with UseResources.or_new(request.options) as rs:
97
- http_client = await rs.enter_async_context(http.manage_async_client(self._http_client))
98
- http_response = await rs.enter_async_context(await http_client.stream_request(http_request))
99
-
100
- if http_response.status != 200:
101
- data: ta.Any
102
- try:
103
- data = await http_response.stream.readall()
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.dev503
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.dev503
17
+ Requires-Dist: omlish==0.0.0.dev505
18
18
  Provides-Extra: all
19
- Requires-Dist: omdev==0.0.0.dev503; extra == "all"
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.11; extra == "all"
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.dev503; extra == "omdev"
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.11; extra == "backends"
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"