ommlds 0.0.0.dev499__py3-none-any.whl → 0.0.0.dev503__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 (55) hide show
  1. ommlds/.omlish-manifests.json +20 -9
  2. ommlds/__about__.py +1 -1
  3. ommlds/backends/anthropic/protocol/sse/events.py +2 -0
  4. ommlds/backends/groq/clients.py +9 -0
  5. ommlds/cli/_dataclasses.py +22 -72
  6. ommlds/cli/backends/inject.py +20 -0
  7. ommlds/cli/backends/meta.py +47 -0
  8. ommlds/cli/sessions/chat/drivers/ai/tools.py +3 -7
  9. ommlds/cli/sessions/chat/facades/commands/base.py +1 -1
  10. ommlds/cli/sessions/chat/interfaces/textual/app.py +1 -1
  11. ommlds/minichain/__init__.py +47 -6
  12. ommlds/minichain/_dataclasses.py +533 -132
  13. ommlds/minichain/backends/impls/anthropic/names.py +3 -3
  14. ommlds/minichain/backends/impls/anthropic/stream.py +1 -1
  15. ommlds/minichain/backends/impls/cerebras/names.py +15 -0
  16. ommlds/minichain/backends/impls/cerebras/stream.py +1 -1
  17. ommlds/minichain/backends/impls/google/names.py +6 -0
  18. ommlds/minichain/backends/impls/google/stream.py +1 -1
  19. ommlds/minichain/backends/impls/groq/chat.py +2 -0
  20. ommlds/minichain/backends/impls/groq/stream.py +3 -1
  21. ommlds/minichain/backends/impls/ollama/chat.py +1 -1
  22. ommlds/minichain/backends/impls/openai/format.py +2 -1
  23. ommlds/minichain/backends/impls/openai/stream.py +33 -1
  24. ommlds/minichain/chat/messages.py +1 -1
  25. ommlds/minichain/chat/stream/joining.py +36 -12
  26. ommlds/minichain/chat/transforms/metadata.py +3 -3
  27. ommlds/minichain/content/standard.py +1 -1
  28. ommlds/minichain/content/transform/json.py +1 -1
  29. ommlds/minichain/content/transform/metadata.py +1 -1
  30. ommlds/minichain/content/transform/standard.py +2 -2
  31. ommlds/minichain/content/transform/strings.py +1 -1
  32. ommlds/minichain/content/transform/templates.py +1 -1
  33. ommlds/minichain/metadata.py +13 -16
  34. ommlds/minichain/resources.py +22 -1
  35. ommlds/minichain/services/README.md +154 -0
  36. ommlds/minichain/services/__init__.py +6 -2
  37. ommlds/minichain/services/_marshal.py +46 -10
  38. ommlds/minichain/services/_origclasses.py +11 -0
  39. ommlds/minichain/services/_typedvalues.py +8 -3
  40. ommlds/minichain/services/requests.py +73 -3
  41. ommlds/minichain/services/responses.py +73 -3
  42. ommlds/minichain/services/services.py +9 -0
  43. ommlds/minichain/stream/services.py +24 -1
  44. ommlds/minichain/tools/reflect.py +3 -3
  45. ommlds/minichain/wrappers/firstinwins.py +29 -2
  46. ommlds/minichain/wrappers/instrument.py +146 -0
  47. ommlds/minichain/wrappers/retry.py +93 -3
  48. ommlds/minichain/wrappers/services.py +26 -0
  49. {ommlds-0.0.0.dev499.dist-info → ommlds-0.0.0.dev503.dist-info}/METADATA +6 -6
  50. {ommlds-0.0.0.dev499.dist-info → ommlds-0.0.0.dev503.dist-info}/RECORD +54 -52
  51. ommlds/minichain/stream/wrap.py +0 -62
  52. {ommlds-0.0.0.dev499.dist-info → ommlds-0.0.0.dev503.dist-info}/WHEEL +0 -0
  53. {ommlds-0.0.0.dev499.dist-info → ommlds-0.0.0.dev503.dist-info}/entry_points.txt +0 -0
  54. {ommlds-0.0.0.dev499.dist-info → ommlds-0.0.0.dev503.dist-info}/licenses/LICENSE +0 -0
  55. {ommlds-0.0.0.dev499.dist-info → ommlds-0.0.0.dev503.dist-info}/top_level.txt +0 -0
@@ -178,14 +178,14 @@ class ToolReflector:
178
178
  for o in md.get_object_metadata(fn, type=_ToolSpecOverride):
179
179
  ts_ovr.update({
180
180
  k: v
181
- for k, v in dc.asdict(o).items()
181
+ for k, v in dc.shallow_asdict(o).items()
182
182
  if k != 'params'
183
183
  and v is not None
184
184
  })
185
185
  for op in (o.params or []):
186
186
  p_ovr_dct.setdefault(check.non_empty_str(op.name), {}).update({
187
187
  k: v
188
- for k, v in dc.asdict(op).items()
188
+ for k, v in dc.shallow_asdict(op).items()
189
189
  if v is not None
190
190
  })
191
191
 
@@ -286,7 +286,7 @@ class ToolReflector:
286
286
  if md_tp is not None:
287
287
  tp_kw.update({
288
288
  k: v
289
- for k, v in dc.asdict(md_tp).items()
289
+ for k, v in dc.shallow_asdict(md_tp).items()
290
290
  if v is not None
291
291
  })
292
292
 
@@ -17,16 +17,24 @@ from .services import WrappedRequest
17
17
  from .services import WrappedRequestV
18
18
  from .services import WrappedResponse
19
19
  from .services import WrappedResponseV
20
+ from .stream import WrappedStreamOutputT
21
+ from .stream import WrapperStreamService
20
22
 
21
23
 
22
24
  with lang.auto_proxy_import(globals()):
23
25
  import asyncio
24
26
 
25
27
 
28
+ AnyFirstInWinsService: ta.TypeAlias = ta.Union[
29
+ 'FirstInWinsService',
30
+ 'FirstInWinsStreamService',
31
+ ]
32
+
33
+
26
34
  ##
27
35
 
28
36
 
29
- @dc.dataclass(frozen=True)
37
+ @dc.dataclass()
30
38
  class FirstInWinsServiceCancelledError(Exception):
31
39
  e: BaseException
32
40
 
@@ -37,11 +45,14 @@ class FirstInWinsServiceExceptionGroup(ExceptionGroup):
37
45
 
38
46
  @dc.dataclass(frozen=True)
39
47
  class FirstInWinsServiceOutput(Output):
40
- first_in_wins_service: 'FirstInWinsService'
48
+ first_in_wins_service: AnyFirstInWinsService
41
49
  response_service: Service
42
50
  service_exceptions: ta.Mapping[Service, Exception] | None = None
43
51
 
44
52
 
53
+ ##
54
+
55
+
45
56
  class FirstInWinsService(
46
57
  MultiWrapperService[
47
58
  WrappedRequestV,
@@ -57,6 +68,22 @@ class FirstInWinsService(
57
68
  ##
58
69
 
59
70
 
71
+ class FirstInWinsStreamService(
72
+ WrapperStreamService[
73
+ WrappedRequestV,
74
+ WrappedOptionT,
75
+ WrappedResponseV,
76
+ WrappedOutputT,
77
+ WrappedStreamOutputT,
78
+ ],
79
+ lang.Abstract,
80
+ ):
81
+ pass
82
+
83
+
84
+ ##
85
+
86
+
60
87
  class AsyncioFirstInWinsService(
61
88
  FirstInWinsService[
62
89
  WrappedRequestV,
@@ -0,0 +1,146 @@
1
+ """
2
+ TODO:
3
+ - final stream outputs
4
+ """
5
+ import datetime
6
+ import typing as ta
7
+
8
+ from omlish import dataclasses as dc
9
+ from omlish import lang
10
+
11
+ from ..services.requests import Request
12
+ from ..services.responses import Response
13
+ from .services import WrappedOptionT
14
+ from .services import WrappedOutputT
15
+ from .services import WrappedRequest
16
+ from .services import WrappedRequestV
17
+ from .services import WrappedResponse
18
+ from .services import WrappedResponseV
19
+ from .services import WrappedService
20
+ from .services import WrapperService
21
+ from .stream import WrappedStreamOutputT
22
+ from .stream import WrappedStreamResponse
23
+ from .stream import WrappedStreamService
24
+ from .stream import WrapperStreamService
25
+
26
+
27
+ ##
28
+
29
+
30
+ @dc.dataclass(frozen=True, kw_only=True)
31
+ @dc.extra_class_params(default_repr_fn=lang.opt_or_just_repr)
32
+ class InstrumentedServiceEvent:
33
+ dt: datetime.datetime = dc.field(default_factory=lang.utcnow)
34
+
35
+ req: Request | None = None
36
+
37
+ resp: Response | None = None
38
+ exc: BaseException | None = None
39
+
40
+ stream_v: lang.Maybe[ta.Any] = lang.empty()
41
+
42
+
43
+ class InstrumentedServiceEventSink(ta.Protocol):
44
+ def __call__(self, ev: InstrumentedServiceEvent) -> ta.Awaitable[None]: ...
45
+
46
+
47
+ class ListInstrumentedServiceEventSink:
48
+ def __init__(self, lst: list[InstrumentedServiceEvent] | None = None) -> None:
49
+ super().__init__()
50
+
51
+ if lst is None:
52
+ lst = []
53
+ self._lst = lst
54
+
55
+ @property
56
+ def events(self) -> ta.Sequence[InstrumentedServiceEvent]:
57
+ return self._lst
58
+
59
+ async def __call__(self, ev: InstrumentedServiceEvent) -> None:
60
+ self._lst.append(ev)
61
+
62
+
63
+ ##
64
+
65
+
66
+ class InstrumentedService(
67
+ WrapperService[
68
+ WrappedRequestV,
69
+ WrappedOptionT,
70
+ WrappedResponseV,
71
+ WrappedOutputT,
72
+ ],
73
+ ):
74
+ def __init__(
75
+ self,
76
+ service: WrappedService,
77
+ sink: InstrumentedServiceEventSink | None = None,
78
+ ) -> None:
79
+ super().__init__(service)
80
+
81
+ if sink is None:
82
+ sink = ListInstrumentedServiceEventSink()
83
+ self._sink = sink
84
+
85
+ async def invoke(self, request: WrappedRequest) -> WrappedResponse:
86
+ await self._sink(InstrumentedServiceEvent(req=request))
87
+
88
+ try:
89
+ resp = await self._service.invoke(request)
90
+
91
+ except Exception as e: # noqa
92
+ await self._sink(InstrumentedServiceEvent(req=request, exc=e))
93
+
94
+ raise
95
+
96
+ await self._sink(InstrumentedServiceEvent(req=request, resp=resp))
97
+
98
+ return resp
99
+
100
+
101
+ ##
102
+
103
+
104
+ class InstrumentedStreamService(
105
+ WrapperStreamService[
106
+ WrappedRequestV,
107
+ WrappedOptionT,
108
+ WrappedResponseV,
109
+ WrappedOutputT,
110
+ WrappedStreamOutputT,
111
+ ],
112
+ ):
113
+ def __init__(
114
+ self,
115
+ service: WrappedStreamService,
116
+ sink: InstrumentedServiceEventSink | None = None,
117
+ ) -> None:
118
+ super().__init__(service)
119
+
120
+ if sink is None:
121
+ sink = ListInstrumentedServiceEventSink()
122
+ self._sink = sink
123
+
124
+ async def invoke(self, request: WrappedRequest) -> WrappedStreamResponse:
125
+ await self._sink(InstrumentedServiceEvent(req=request))
126
+
127
+ try:
128
+ resp = await self._service.invoke(request)
129
+
130
+ except Exception as e: # noqa
131
+ await self._sink(InstrumentedServiceEvent(req=request, exc=e))
132
+
133
+ raise
134
+
135
+ await self._sink(InstrumentedServiceEvent(req=request, resp=resp))
136
+
137
+ async def inner(sink): # noqa
138
+ async with resp.v as st:
139
+ async for v in st:
140
+ await self._sink(InstrumentedServiceEvent(req=request, resp=resp, stream_v=v))
141
+
142
+ await sink(v)
143
+
144
+ # return resp.with_v(inner())
145
+
146
+ raise NotImplementedError
@@ -1,14 +1,25 @@
1
1
  """
2
2
  TODO:
3
3
  - tenacity shit
4
- - exception filter
4
+ - exception filter - retryable vs not
5
+ - explicit RetryableError wrapper?
5
6
  - sleep
6
7
  - jitter
8
+ - log, on retry / on except callbacks, blah blah
9
+ - stream retry:
10
+ - failed to open
11
+ - failed during stream
12
+ - buffer and replay??
13
+ - accept death mid-stream?
14
+ - ** probably **: cannot sanely impose any nontrivial stream retry strat at this layer -
7
15
  """
8
16
  import typing as ta
9
17
 
10
18
  from omlish import dataclasses as dc
11
19
 
20
+ from ..resources import Resources
21
+ from ..stream.services import StreamResponseSink
22
+ from ..stream.services import new_stream_response
12
23
  from ..types import Output
13
24
  from .services import WrappedOptionT
14
25
  from .services import WrappedOutputT
@@ -18,22 +29,35 @@ from .services import WrappedResponse
18
29
  from .services import WrappedResponseV
19
30
  from .services import WrappedService
20
31
  from .services import WrapperService
32
+ from .stream import WrappedStreamOutputT
33
+ from .stream import WrappedStreamResponse
34
+ from .stream import WrappedStreamService
35
+ from .stream import WrapperStreamService
36
+
37
+
38
+ AnyRetryService: ta.TypeAlias = ta.Union[
39
+ 'RetryService',
40
+ 'RetryStreamService',
41
+ ]
21
42
 
22
43
 
23
44
  ##
24
45
 
25
46
 
26
- @dc.dataclass(frozen=True)
47
+ @dc.dataclass()
27
48
  class RetryServiceMaxRetriesExceededError(Exception):
28
49
  pass
29
50
 
30
51
 
31
52
  @dc.dataclass(frozen=True)
32
53
  class RetryServiceOutput(Output):
33
- retry_service: 'RetryService'
54
+ retry_service: AnyRetryService
34
55
  num_retries: int
35
56
 
36
57
 
58
+ ##
59
+
60
+
37
61
  class RetryService(
38
62
  WrapperService[
39
63
  WrappedRequestV,
@@ -76,3 +100,69 @@ class RetryService(
76
100
  ))
77
101
 
78
102
  raise RuntimeError # unreachable
103
+
104
+
105
+ ##
106
+
107
+
108
+ class RetryStreamService(
109
+ WrapperStreamService[
110
+ WrappedRequestV,
111
+ WrappedOptionT,
112
+ WrappedResponseV,
113
+ WrappedOutputT,
114
+ WrappedStreamOutputT,
115
+ ],
116
+ ):
117
+ DEFAULT_MAX_RETRIES: ta.ClassVar[int] = 3
118
+
119
+ def __init__(
120
+ self,
121
+ service: WrappedStreamService,
122
+ *,
123
+ max_retries: int | None = None,
124
+ ) -> None:
125
+ super().__init__(service)
126
+
127
+ if max_retries is None:
128
+ max_retries = self.DEFAULT_MAX_RETRIES
129
+ self._max_retries = max_retries
130
+
131
+ async def invoke(self, request: WrappedRequest) -> WrappedStreamResponse:
132
+ n = 0
133
+
134
+ while True:
135
+ try:
136
+ async with Resources.new() as rs:
137
+ in_resp = await self._service.invoke(request)
138
+ in_vs = await rs.enter_async_context(in_resp.v)
139
+
140
+ async def inner(sink: StreamResponseSink[WrappedResponseV]) -> ta.Sequence[WrappedOutputT] | None:
141
+ async for v in in_vs:
142
+ await sink.emit(v)
143
+
144
+ return in_vs.outputs
145
+
146
+ outs = [
147
+ *in_resp.outputs,
148
+ RetryServiceOutput(
149
+ retry_service=self,
150
+ num_retries=n,
151
+ ),
152
+ ]
153
+
154
+ # FIXME: ??
155
+ # if (ur := tv.as_collection(request.options).get(UseResources)) is not None:
156
+
157
+ return await new_stream_response(
158
+ rs,
159
+ inner,
160
+ outs,
161
+ )
162
+
163
+ except Exception as e: # noqa
164
+ if n < self._max_retries:
165
+ n += 1
166
+ continue
167
+
168
+ raise RetryServiceMaxRetriesExceededError from e
@@ -10,6 +10,9 @@ from ..types import Option
10
10
  from ..types import Output
11
11
 
12
12
 
13
+ P = ta.ParamSpec('P')
14
+
15
+
13
16
  WrappedRequestV = ta.TypeVar('WrappedRequestV')
14
17
  WrappedOptionT = ta.TypeVar('WrappedOptionT', bound=Option)
15
18
 
@@ -70,3 +73,26 @@ class MultiWrapperService(
70
73
  super().__init__()
71
74
 
72
75
  self._services = check.not_empty(services)
76
+
77
+
78
+ ##
79
+
80
+
81
+ def wrap_service(
82
+ wrapped: WrappedService,
83
+ wrapper: ta.Callable[
84
+ ta.Concatenate[
85
+ WrappedService,
86
+ P,
87
+ ],
88
+ WrapperService[
89
+ WrappedRequestV,
90
+ WrappedOptionT,
91
+ WrappedResponseV,
92
+ WrappedOutputT,
93
+ ],
94
+ ],
95
+ *args: P.args,
96
+ **kwargs: P.kwargs,
97
+ ) -> WrappedService:
98
+ return ta.cast(ta.Any, wrapper(wrapped, *args, **kwargs))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ommlds
3
- Version: 0.0.0.dev499
3
+ Version: 0.0.0.dev503
4
4
  Summary: ommlds
5
5
  Author: wrmsr
6
6
  License-Expression: BSD-3-Clause
@@ -14,9 +14,9 @@ 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.dev499
17
+ Requires-Dist: omlish==0.0.0.dev503
18
18
  Provides-Extra: all
19
- Requires-Dist: omdev==0.0.0.dev499; extra == "all"
19
+ Requires-Dist: omdev==0.0.0.dev503; 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"
@@ -32,13 +32,13 @@ Requires-Dist: regex>=2025.0; extra == "all"
32
32
  Requires-Dist: numpy>=1.26; extra == "all"
33
33
  Requires-Dist: pytesseract~=0.3; extra == "all"
34
34
  Requires-Dist: rapidocr-onnxruntime~=1.4; extra == "all"
35
- Requires-Dist: pillow~=12.0; extra == "all"
35
+ Requires-Dist: pillow~=12.1; extra == "all"
36
36
  Requires-Dist: ddgs~=9.10; extra == "all"
37
37
  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.dev499; extra == "omdev"
41
+ Requires-Dist: omdev==0.0.0.dev503; 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"
@@ -60,7 +60,7 @@ Provides-Extra: ocr
60
60
  Requires-Dist: pytesseract~=0.3; extra == "ocr"
61
61
  Requires-Dist: rapidocr-onnxruntime~=1.4; extra == "ocr"
62
62
  Provides-Extra: pillow
63
- Requires-Dist: pillow~=12.0; extra == "pillow"
63
+ Requires-Dist: pillow~=12.1; extra == "pillow"
64
64
  Provides-Extra: search
65
65
  Requires-Dist: ddgs~=9.10; extra == "search"
66
66
  Provides-Extra: wiki