ommlds 0.0.0.dev440__py3-none-any.whl → 0.0.0.dev480__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 (271) hide show
  1. ommlds/.omlish-manifests.json +332 -35
  2. ommlds/__about__.py +15 -9
  3. ommlds/_hacks/__init__.py +4 -0
  4. ommlds/_hacks/funcs.py +110 -0
  5. ommlds/_hacks/names.py +158 -0
  6. ommlds/_hacks/params.py +73 -0
  7. ommlds/_hacks/patches.py +0 -3
  8. ommlds/backends/anthropic/protocol/_marshal.py +2 -2
  9. ommlds/backends/anthropic/protocol/sse/_marshal.py +1 -1
  10. ommlds/backends/anthropic/protocol/sse/assemble.py +23 -7
  11. ommlds/backends/anthropic/protocol/sse/events.py +13 -0
  12. ommlds/backends/anthropic/protocol/types.py +30 -9
  13. ommlds/backends/google/protocol/__init__.py +3 -0
  14. ommlds/backends/google/protocol/_marshal.py +16 -0
  15. ommlds/backends/google/protocol/types.py +626 -0
  16. ommlds/backends/groq/_marshal.py +23 -0
  17. ommlds/backends/groq/protocol.py +249 -0
  18. ommlds/backends/mlx/generation.py +1 -1
  19. ommlds/backends/mlx/loading.py +58 -1
  20. ommlds/backends/ollama/__init__.py +0 -0
  21. ommlds/backends/ollama/protocol.py +170 -0
  22. ommlds/backends/openai/protocol/__init__.py +9 -28
  23. ommlds/backends/openai/protocol/_common.py +18 -0
  24. ommlds/backends/openai/protocol/_marshal.py +27 -0
  25. ommlds/backends/openai/protocol/chatcompletion/chunk.py +58 -31
  26. ommlds/backends/openai/protocol/chatcompletion/contentpart.py +49 -44
  27. ommlds/backends/openai/protocol/chatcompletion/message.py +55 -43
  28. ommlds/backends/openai/protocol/chatcompletion/request.py +114 -66
  29. ommlds/backends/openai/protocol/chatcompletion/response.py +71 -45
  30. ommlds/backends/openai/protocol/chatcompletion/responseformat.py +27 -20
  31. ommlds/backends/openai/protocol/chatcompletion/tokenlogprob.py +16 -7
  32. ommlds/backends/openai/protocol/completionusage.py +24 -15
  33. ommlds/backends/tavily/__init__.py +0 -0
  34. ommlds/backends/tavily/protocol.py +301 -0
  35. ommlds/backends/tinygrad/models/llama3/__init__.py +22 -14
  36. ommlds/backends/transformers/__init__.py +0 -0
  37. ommlds/backends/transformers/filecache.py +109 -0
  38. ommlds/backends/transformers/streamers.py +73 -0
  39. ommlds/cli/asyncs.py +30 -0
  40. ommlds/cli/backends/catalog.py +93 -0
  41. ommlds/cli/backends/configs.py +9 -0
  42. ommlds/cli/backends/inject.py +31 -36
  43. ommlds/cli/backends/injection.py +16 -0
  44. ommlds/cli/backends/types.py +46 -0
  45. ommlds/cli/content/__init__.py +0 -0
  46. ommlds/cli/content/messages.py +34 -0
  47. ommlds/cli/content/strings.py +42 -0
  48. ommlds/cli/inject.py +15 -32
  49. ommlds/cli/inputs/__init__.py +0 -0
  50. ommlds/cli/inputs/asyncs.py +32 -0
  51. ommlds/cli/inputs/sync.py +75 -0
  52. ommlds/cli/main.py +270 -110
  53. ommlds/cli/rendering/__init__.py +0 -0
  54. ommlds/cli/rendering/configs.py +9 -0
  55. ommlds/cli/rendering/inject.py +31 -0
  56. ommlds/cli/rendering/markdown.py +52 -0
  57. ommlds/cli/rendering/raw.py +73 -0
  58. ommlds/cli/rendering/types.py +21 -0
  59. ommlds/cli/secrets.py +21 -0
  60. ommlds/cli/sessions/base.py +1 -1
  61. ommlds/cli/sessions/chat/chat/__init__.py +0 -0
  62. ommlds/cli/sessions/chat/chat/ai/__init__.py +0 -0
  63. ommlds/cli/sessions/chat/chat/ai/configs.py +11 -0
  64. ommlds/cli/sessions/chat/chat/ai/inject.py +74 -0
  65. ommlds/cli/sessions/chat/chat/ai/injection.py +14 -0
  66. ommlds/cli/sessions/chat/chat/ai/rendering.py +70 -0
  67. ommlds/cli/sessions/chat/chat/ai/services.py +79 -0
  68. ommlds/cli/sessions/chat/chat/ai/tools.py +44 -0
  69. ommlds/cli/sessions/chat/chat/ai/types.py +28 -0
  70. ommlds/cli/sessions/chat/chat/state/__init__.py +0 -0
  71. ommlds/cli/sessions/chat/chat/state/configs.py +11 -0
  72. ommlds/cli/sessions/chat/chat/state/inject.py +36 -0
  73. ommlds/cli/sessions/chat/chat/state/inmemory.py +33 -0
  74. ommlds/cli/sessions/chat/chat/state/storage.py +52 -0
  75. ommlds/cli/sessions/chat/chat/state/types.py +38 -0
  76. ommlds/cli/sessions/chat/chat/user/__init__.py +0 -0
  77. ommlds/cli/sessions/chat/chat/user/configs.py +17 -0
  78. ommlds/cli/sessions/chat/chat/user/inject.py +62 -0
  79. ommlds/cli/sessions/chat/chat/user/interactive.py +31 -0
  80. ommlds/cli/sessions/chat/chat/user/oneshot.py +25 -0
  81. ommlds/cli/sessions/chat/chat/user/types.py +15 -0
  82. ommlds/cli/sessions/chat/configs.py +27 -0
  83. ommlds/cli/sessions/chat/driver.py +43 -0
  84. ommlds/cli/sessions/chat/inject.py +33 -65
  85. ommlds/cli/sessions/chat/phases/__init__.py +0 -0
  86. ommlds/cli/sessions/chat/phases/inject.py +27 -0
  87. ommlds/cli/sessions/chat/phases/injection.py +14 -0
  88. ommlds/cli/sessions/chat/phases/manager.py +29 -0
  89. ommlds/cli/sessions/chat/phases/types.py +29 -0
  90. ommlds/cli/sessions/chat/session.py +27 -0
  91. ommlds/cli/sessions/chat/tools/__init__.py +0 -0
  92. ommlds/cli/sessions/chat/tools/configs.py +22 -0
  93. ommlds/cli/sessions/chat/tools/confirmation.py +46 -0
  94. ommlds/cli/sessions/chat/tools/execution.py +66 -0
  95. ommlds/cli/sessions/chat/tools/fs/__init__.py +0 -0
  96. ommlds/cli/sessions/chat/tools/fs/configs.py +12 -0
  97. ommlds/cli/sessions/chat/tools/fs/inject.py +35 -0
  98. ommlds/cli/sessions/chat/tools/inject.py +88 -0
  99. ommlds/cli/sessions/chat/tools/injection.py +44 -0
  100. ommlds/cli/sessions/chat/tools/rendering.py +58 -0
  101. ommlds/cli/sessions/chat/tools/todo/__init__.py +0 -0
  102. ommlds/cli/sessions/chat/tools/todo/configs.py +12 -0
  103. ommlds/cli/sessions/chat/tools/todo/inject.py +31 -0
  104. ommlds/cli/sessions/chat/tools/weather/__init__.py +0 -0
  105. ommlds/cli/sessions/chat/tools/weather/configs.py +12 -0
  106. ommlds/cli/sessions/chat/tools/weather/inject.py +22 -0
  107. ommlds/cli/{tools/weather.py → sessions/chat/tools/weather/tools.py} +1 -1
  108. ommlds/cli/sessions/completion/configs.py +21 -0
  109. ommlds/cli/sessions/completion/inject.py +42 -0
  110. ommlds/cli/sessions/completion/session.py +35 -0
  111. ommlds/cli/sessions/embedding/configs.py +21 -0
  112. ommlds/cli/sessions/embedding/inject.py +42 -0
  113. ommlds/cli/sessions/embedding/session.py +33 -0
  114. ommlds/cli/sessions/inject.py +28 -11
  115. ommlds/cli/state/__init__.py +0 -0
  116. ommlds/cli/state/inject.py +28 -0
  117. ommlds/cli/{state.py → state/storage.py} +41 -24
  118. ommlds/minichain/__init__.py +84 -24
  119. ommlds/minichain/_marshal.py +49 -9
  120. ommlds/minichain/_typedvalues.py +2 -4
  121. ommlds/minichain/backends/catalogs/base.py +20 -1
  122. ommlds/minichain/backends/catalogs/simple.py +2 -2
  123. ommlds/minichain/backends/catalogs/strings.py +10 -8
  124. ommlds/minichain/backends/impls/anthropic/chat.py +65 -27
  125. ommlds/minichain/backends/impls/anthropic/names.py +10 -8
  126. ommlds/minichain/backends/impls/anthropic/protocol.py +109 -0
  127. ommlds/minichain/backends/impls/anthropic/stream.py +111 -43
  128. ommlds/minichain/backends/impls/duckduckgo/search.py +1 -1
  129. ommlds/minichain/backends/impls/dummy/__init__.py +0 -0
  130. ommlds/minichain/backends/impls/dummy/chat.py +69 -0
  131. ommlds/minichain/backends/impls/google/chat.py +114 -22
  132. ommlds/minichain/backends/impls/google/search.py +7 -2
  133. ommlds/minichain/backends/impls/google/stream.py +219 -0
  134. ommlds/minichain/backends/impls/google/tools.py +149 -0
  135. ommlds/minichain/backends/impls/groq/__init__.py +0 -0
  136. ommlds/minichain/backends/impls/groq/chat.py +75 -0
  137. ommlds/minichain/backends/impls/groq/names.py +48 -0
  138. ommlds/minichain/backends/impls/groq/protocol.py +143 -0
  139. ommlds/minichain/backends/impls/groq/stream.py +125 -0
  140. ommlds/minichain/backends/impls/llamacpp/chat.py +33 -18
  141. ommlds/minichain/backends/impls/llamacpp/completion.py +1 -1
  142. ommlds/minichain/backends/impls/llamacpp/format.py +4 -2
  143. ommlds/minichain/backends/impls/llamacpp/stream.py +37 -20
  144. ommlds/minichain/backends/impls/mistral.py +20 -5
  145. ommlds/minichain/backends/impls/mlx/chat.py +96 -22
  146. ommlds/minichain/backends/impls/ollama/__init__.py +0 -0
  147. ommlds/minichain/backends/impls/ollama/chat.py +199 -0
  148. ommlds/minichain/backends/impls/openai/chat.py +18 -8
  149. ommlds/minichain/backends/impls/openai/completion.py +10 -3
  150. ommlds/minichain/backends/impls/openai/embedding.py +10 -3
  151. ommlds/minichain/backends/impls/openai/format.py +131 -106
  152. ommlds/minichain/backends/impls/openai/names.py +31 -5
  153. ommlds/minichain/backends/impls/openai/stream.py +43 -25
  154. ommlds/minichain/backends/impls/tavily.py +66 -0
  155. ommlds/minichain/backends/impls/tinygrad/chat.py +23 -16
  156. ommlds/minichain/backends/impls/transformers/sentence.py +1 -1
  157. ommlds/minichain/backends/impls/transformers/tokens.py +1 -1
  158. ommlds/minichain/backends/impls/transformers/transformers.py +155 -34
  159. ommlds/minichain/backends/strings/parsing.py +1 -1
  160. ommlds/minichain/backends/strings/resolving.py +4 -1
  161. ommlds/minichain/chat/_marshal.py +16 -9
  162. ommlds/minichain/chat/choices/adapters.py +4 -4
  163. ommlds/minichain/chat/choices/services.py +1 -1
  164. ommlds/minichain/chat/choices/stream/__init__.py +0 -0
  165. ommlds/minichain/chat/choices/stream/adapters.py +35 -0
  166. ommlds/minichain/chat/choices/stream/joining.py +31 -0
  167. ommlds/minichain/chat/choices/stream/services.py +45 -0
  168. ommlds/minichain/chat/choices/stream/types.py +43 -0
  169. ommlds/minichain/chat/choices/types.py +2 -2
  170. ommlds/minichain/chat/history.py +3 -3
  171. ommlds/minichain/chat/messages.py +55 -19
  172. ommlds/minichain/chat/services.py +3 -3
  173. ommlds/minichain/chat/stream/_marshal.py +16 -0
  174. ommlds/minichain/chat/stream/joining.py +85 -0
  175. ommlds/minichain/chat/stream/services.py +15 -21
  176. ommlds/minichain/chat/stream/types.py +32 -19
  177. ommlds/minichain/chat/tools/execution.py +8 -7
  178. ommlds/minichain/chat/tools/ids.py +9 -15
  179. ommlds/minichain/chat/tools/parsing.py +17 -26
  180. ommlds/minichain/chat/transforms/base.py +29 -38
  181. ommlds/minichain/chat/transforms/metadata.py +30 -4
  182. ommlds/minichain/chat/transforms/services.py +9 -11
  183. ommlds/minichain/content/_marshal.py +44 -20
  184. ommlds/minichain/content/json.py +13 -0
  185. ommlds/minichain/content/materialize.py +14 -21
  186. ommlds/minichain/content/prepare.py +4 -0
  187. ommlds/minichain/content/transforms/interleave.py +1 -1
  188. ommlds/minichain/content/transforms/squeeze.py +1 -1
  189. ommlds/minichain/content/transforms/stringify.py +1 -1
  190. ommlds/minichain/json.py +20 -0
  191. ommlds/minichain/lib/code/__init__.py +0 -0
  192. ommlds/minichain/lib/code/prompts.py +6 -0
  193. ommlds/minichain/lib/fs/binfiles.py +108 -0
  194. ommlds/minichain/lib/fs/context.py +126 -0
  195. ommlds/minichain/lib/fs/errors.py +101 -0
  196. ommlds/minichain/lib/fs/suggestions.py +36 -0
  197. ommlds/minichain/lib/fs/tools/__init__.py +0 -0
  198. ommlds/minichain/lib/fs/tools/edit.py +104 -0
  199. ommlds/minichain/lib/fs/tools/ls.py +38 -0
  200. ommlds/minichain/lib/fs/tools/read.py +115 -0
  201. ommlds/minichain/lib/fs/tools/recursivels/__init__.py +0 -0
  202. ommlds/minichain/lib/fs/tools/recursivels/execution.py +40 -0
  203. ommlds/minichain/lib/todo/__init__.py +0 -0
  204. ommlds/minichain/lib/todo/context.py +54 -0
  205. ommlds/minichain/lib/todo/tools/__init__.py +0 -0
  206. ommlds/minichain/lib/todo/tools/read.py +44 -0
  207. ommlds/minichain/lib/todo/tools/write.py +335 -0
  208. ommlds/minichain/lib/todo/types.py +60 -0
  209. ommlds/minichain/llms/_marshal.py +25 -17
  210. ommlds/minichain/llms/types.py +4 -0
  211. ommlds/minichain/registries/globals.py +18 -4
  212. ommlds/minichain/resources.py +66 -43
  213. ommlds/minichain/search.py +1 -1
  214. ommlds/minichain/services/_marshal.py +46 -39
  215. ommlds/minichain/services/facades.py +3 -3
  216. ommlds/minichain/services/services.py +1 -1
  217. ommlds/minichain/standard.py +8 -0
  218. ommlds/minichain/stream/services.py +152 -38
  219. ommlds/minichain/stream/wrap.py +22 -24
  220. ommlds/minichain/tools/_marshal.py +1 -1
  221. ommlds/minichain/tools/execution/catalog.py +2 -1
  222. ommlds/minichain/tools/execution/context.py +34 -14
  223. ommlds/minichain/tools/execution/errors.py +15 -0
  224. ommlds/minichain/tools/execution/executors.py +8 -3
  225. ommlds/minichain/tools/execution/reflect.py +40 -5
  226. ommlds/minichain/tools/fns.py +46 -9
  227. ommlds/minichain/tools/jsonschema.py +14 -5
  228. ommlds/minichain/tools/reflect.py +54 -18
  229. ommlds/minichain/tools/types.py +33 -1
  230. ommlds/minichain/utils.py +27 -0
  231. ommlds/minichain/vectors/_marshal.py +11 -10
  232. ommlds/nanochat/LICENSE +21 -0
  233. ommlds/nanochat/__init__.py +0 -0
  234. ommlds/nanochat/rustbpe/LICENSE +21 -0
  235. ommlds/nanochat/tokenizers.py +406 -0
  236. ommlds/server/server.py +3 -3
  237. ommlds/specs/__init__.py +0 -0
  238. ommlds/specs/mcp/__init__.py +0 -0
  239. ommlds/specs/mcp/_marshal.py +23 -0
  240. ommlds/specs/mcp/protocol.py +266 -0
  241. ommlds/tools/git.py +27 -10
  242. ommlds/tools/ocr.py +8 -9
  243. ommlds/wiki/analyze.py +2 -2
  244. ommlds/wiki/text/mfh.py +1 -5
  245. ommlds/wiki/text/wtp.py +1 -3
  246. ommlds/wiki/utils/xml.py +5 -5
  247. {ommlds-0.0.0.dev440.dist-info → ommlds-0.0.0.dev480.dist-info}/METADATA +24 -21
  248. ommlds-0.0.0.dev480.dist-info/RECORD +427 -0
  249. ommlds/cli/backends/standard.py +0 -20
  250. ommlds/cli/sessions/chat/base.py +0 -42
  251. ommlds/cli/sessions/chat/interactive.py +0 -73
  252. ommlds/cli/sessions/chat/printing.py +0 -96
  253. ommlds/cli/sessions/chat/prompt.py +0 -143
  254. ommlds/cli/sessions/chat/state.py +0 -109
  255. ommlds/cli/sessions/chat/tools.py +0 -91
  256. ommlds/cli/sessions/completion/completion.py +0 -44
  257. ommlds/cli/sessions/embedding/embedding.py +0 -42
  258. ommlds/cli/tools/config.py +0 -13
  259. ommlds/cli/tools/inject.py +0 -64
  260. ommlds/minichain/chat/stream/adapters.py +0 -69
  261. ommlds/minichain/lib/fs/ls/execution.py +0 -32
  262. ommlds-0.0.0.dev440.dist-info/RECORD +0 -303
  263. /ommlds/{cli/tools → backends/google}/__init__.py +0 -0
  264. /ommlds/{minichain/lib/fs/ls → backends/groq}/__init__.py +0 -0
  265. /ommlds/{huggingface.py → backends/huggingface.py} +0 -0
  266. /ommlds/minichain/lib/fs/{ls → tools/recursivels}/rendering.py +0 -0
  267. /ommlds/minichain/lib/fs/{ls → tools/recursivels}/running.py +0 -0
  268. {ommlds-0.0.0.dev440.dist-info → ommlds-0.0.0.dev480.dist-info}/WHEEL +0 -0
  269. {ommlds-0.0.0.dev440.dist-info → ommlds-0.0.0.dev480.dist-info}/entry_points.txt +0 -0
  270. {ommlds-0.0.0.dev440.dist-info → ommlds-0.0.0.dev480.dist-info}/licenses/LICENSE +0 -0
  271. {ommlds-0.0.0.dev440.dist-info → ommlds-0.0.0.dev480.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,199 @@
1
+ import typing as ta
2
+
3
+ from omlish import check
4
+ from omlish import lang
5
+ from omlish import marshal as msh
6
+ from omlish import typedvalues as tv
7
+ from omlish.formats import json
8
+ from omlish.http import all as http
9
+ from omlish.io.buffers import DelimitingBuffer
10
+
11
+ from .....backends.ollama import protocol as pt
12
+ from ....chat.choices.services import ChatChoicesOutputs
13
+ from ....chat.choices.services import ChatChoicesRequest
14
+ from ....chat.choices.services import ChatChoicesResponse
15
+ from ....chat.choices.services import static_check_is_chat_choices_service
16
+ from ....chat.choices.stream.services import ChatChoicesStreamRequest
17
+ from ....chat.choices.stream.services import ChatChoicesStreamResponse
18
+ from ....chat.choices.stream.services import static_check_is_chat_choices_stream_service
19
+ from ....chat.choices.stream.types import AiChoiceDeltas
20
+ from ....chat.choices.stream.types import AiChoicesDeltas
21
+ from ....chat.choices.types import AiChoice
22
+ from ....chat.messages import AiMessage
23
+ from ....chat.messages import AnyAiMessage
24
+ from ....chat.messages import Message
25
+ from ....chat.messages import SystemMessage
26
+ from ....chat.messages import UserMessage
27
+ from ....chat.stream.types import ContentAiDelta
28
+ from ....models.configs import ModelName
29
+ from ....resources import UseResources
30
+ from ....standard import ApiUrl
31
+ from ....stream.services import StreamResponseSink
32
+ from ....stream.services import new_stream_response
33
+
34
+
35
+ ##
36
+
37
+
38
+ # @omlish-manifest $.minichain.backends.strings.manifests.BackendStringsManifest(
39
+ # [
40
+ # 'ChatChoicesService',
41
+ # 'ChatChoicesStreamService',
42
+ # ],
43
+ # 'ollama',
44
+ # )
45
+
46
+
47
+ ##
48
+
49
+
50
+ class BaseOllamaChatChoicesService(lang.Abstract):
51
+ DEFAULT_API_URL: ta.ClassVar[ApiUrl] = ApiUrl('http://localhost:11434/api')
52
+ DEFAULT_MODEL_NAME: ta.ClassVar[ModelName] = ModelName('llama3.2')
53
+
54
+ def __init__(
55
+ self,
56
+ *configs: ApiUrl | ModelName,
57
+ http_client: http.AsyncHttpClient | None = None,
58
+ ) -> None:
59
+ super().__init__()
60
+
61
+ self._http_client = http_client
62
+
63
+ with tv.consume(*configs) as cc:
64
+ self._api_url = cc.pop(self.DEFAULT_API_URL)
65
+ self._model_name = cc.pop(self.DEFAULT_MODEL_NAME)
66
+
67
+ #
68
+
69
+ ROLE_MAP: ta.ClassVar[ta.Mapping[type[Message], pt.Role]] = { # noqa
70
+ SystemMessage: 'system',
71
+ UserMessage: 'user',
72
+ AiMessage: 'assistant',
73
+ }
74
+
75
+ @classmethod
76
+ def _get_message_content(cls, m: Message) -> str | None:
77
+ if isinstance(m, (AiMessage, UserMessage, SystemMessage)):
78
+ return check.isinstance(m.c, str)
79
+ else:
80
+ raise TypeError(m)
81
+
82
+ @classmethod
83
+ def _build_request_messages(cls, mc_msgs: ta.Iterable[Message]) -> ta.Sequence[pt.Message]:
84
+ messages: list[pt.Message] = []
85
+ for m in mc_msgs:
86
+ messages.append(pt.Message(
87
+ role=cls.ROLE_MAP[type(m)],
88
+ content=cls._get_message_content(m),
89
+ ))
90
+ return messages
91
+
92
+
93
+ ##
94
+
95
+
96
+ # @omlish-manifest $.minichain.registries.manifests.RegistryManifest(
97
+ # name='ollama',
98
+ # type='ChatChoicesService',
99
+ # )
100
+ @static_check_is_chat_choices_service
101
+ class OllamaChatChoicesService(BaseOllamaChatChoicesService):
102
+ async def invoke(
103
+ self,
104
+ request: ChatChoicesRequest,
105
+ ) -> ChatChoicesResponse:
106
+ messages = self._build_request_messages(request.v)
107
+
108
+ a_req = pt.ChatRequest(
109
+ model=self._model_name.v,
110
+ messages=messages,
111
+ # tools=tools or None,
112
+ stream=False,
113
+ )
114
+
115
+ raw_request = msh.marshal(a_req)
116
+
117
+ async with http.manage_async_client(self._http_client) as http_client:
118
+ raw_response = await http_client.request(http.HttpRequest(
119
+ self._api_url.v.removesuffix('/') + '/chat',
120
+ data=json.dumps(raw_request).encode('utf-8'),
121
+ ))
122
+
123
+ json_response = json.loads(check.not_none(raw_response.data).decode('utf-8'))
124
+
125
+ resp = msh.unmarshal(json_response, pt.ChatResponse)
126
+
127
+ out: list[AnyAiMessage] = []
128
+ if resp.message.role == 'assistant':
129
+ out.append(AiMessage(
130
+ check.not_none(resp.message.content),
131
+ ))
132
+ else:
133
+ raise TypeError(resp.message.role)
134
+
135
+ return ChatChoicesResponse([
136
+ AiChoice(out),
137
+ ])
138
+
139
+
140
+ ##
141
+
142
+
143
+ # @omlish-manifest $.minichain.registries.manifests.RegistryManifest(
144
+ # name='ollama',
145
+ # type='ChatChoicesStreamService',
146
+ # )
147
+ @static_check_is_chat_choices_stream_service
148
+ class OllamaChatChoicesStreamService(BaseOllamaChatChoicesService):
149
+ READ_CHUNK_SIZE: ta.ClassVar[int] = -1
150
+
151
+ async def invoke(
152
+ self,
153
+ request: ChatChoicesStreamRequest,
154
+ ) -> ChatChoicesStreamResponse:
155
+ messages = self._build_request_messages(request.v)
156
+
157
+ a_req = pt.ChatRequest(
158
+ model=self._model_name.v,
159
+ messages=messages,
160
+ # tools=tools or None,
161
+ stream=True,
162
+ )
163
+
164
+ raw_request = msh.marshal(a_req)
165
+
166
+ http_request = http.HttpRequest(
167
+ self._api_url.v.removesuffix('/') + '/chat',
168
+ data=json.dumps(raw_request).encode('utf-8'),
169
+ )
170
+
171
+ async with UseResources.or_new(request.options) as rs:
172
+ http_client = await rs.enter_async_context(http.manage_async_client(self._http_client))
173
+ http_response = await rs.enter_async_context(await http_client.stream_request(http_request))
174
+
175
+ async def inner(sink: StreamResponseSink[AiChoicesDeltas]) -> ta.Sequence[ChatChoicesOutputs] | None:
176
+ db = DelimitingBuffer([b'\r', b'\n', b'\r\n'])
177
+ while True:
178
+ b = await http_response.stream.read1(self.READ_CHUNK_SIZE)
179
+ for l in db.feed(b):
180
+ if isinstance(l, DelimitingBuffer.Incomplete):
181
+ # FIXME: handle
182
+ return []
183
+
184
+ lj = json.loads(l.decode('utf-8'))
185
+ lp: pt.ChatResponse = msh.unmarshal(lj, pt.ChatResponse)
186
+
187
+ check.state(lp.message.role == 'assistant')
188
+ check.none(lp.message.tool_name)
189
+ check.state(not lp.message.tool_calls)
190
+
191
+ if (c := lp.message.content):
192
+ await sink.emit(AiChoicesDeltas([AiChoiceDeltas([ContentAiDelta(
193
+ c,
194
+ )])]))
195
+
196
+ if not b:
197
+ return []
198
+
199
+ return await new_stream_response(rs, inner)
@@ -14,10 +14,12 @@ TODO:
14
14
  import typing as ta
15
15
 
16
16
  from omlish import check
17
+ from omlish import marshal as msh
17
18
  from omlish import typedvalues as tv
18
19
  from omlish.formats import json
19
20
  from omlish.http import all as http
20
21
 
22
+ from .....backends.openai import protocol as pt
21
23
  from ....chat.choices.services import ChatChoicesRequest
22
24
  from ....chat.choices.services import ChatChoicesResponse
23
25
  from ....chat.choices.services import static_check_is_chat_choices_service
@@ -25,7 +27,8 @@ from ....models.configs import ModelName
25
27
  from ....standard import ApiKey
26
28
  from ....standard import DefaultOptions
27
29
  from .format import OpenaiChatRequestHandler
28
- from .names import MODEL_NAMES
30
+ from .format import build_mc_choices_response
31
+ from .names import CHAT_MODEL_NAMES
29
32
 
30
33
 
31
34
  ##
@@ -37,17 +40,23 @@ from .names import MODEL_NAMES
37
40
  # )
38
41
  @static_check_is_chat_choices_service
39
42
  class OpenaiChatChoicesService:
40
- DEFAULT_MODEL_NAME: ta.ClassVar[ModelName] = ModelName(check.not_none(MODEL_NAMES.default))
43
+ DEFAULT_MODEL_NAME: ta.ClassVar[ModelName] = ModelName(check.not_none(CHAT_MODEL_NAMES.default))
41
44
 
42
- def __init__(self, *configs: ApiKey | ModelName | DefaultOptions) -> None:
45
+ def __init__(
46
+ self,
47
+ *configs: ApiKey | ModelName | DefaultOptions,
48
+ http_client: http.AsyncHttpClient | None = None,
49
+ ) -> None:
43
50
  super().__init__()
44
51
 
52
+ self._http_client = http_client
53
+
45
54
  with tv.consume(*configs) as cc:
46
55
  self._model_name = cc.pop(self.DEFAULT_MODEL_NAME)
47
56
  self._api_key = ApiKey.pop_secret(cc, env='OPENAI_API_KEY')
48
57
  self._default_options: tv.TypedValues = DefaultOptions.pop(cc)
49
58
 
50
- def invoke(self, request: ChatChoicesRequest) -> ChatChoicesResponse:
59
+ async def invoke(self, request: ChatChoicesRequest) -> ChatChoicesResponse:
51
60
  # check.isinstance(request, ChatRequest)
52
61
 
53
62
  rh = OpenaiChatRequestHandler(
@@ -57,23 +66,24 @@ class OpenaiChatChoicesService:
57
66
  *request.options,
58
67
  override=True,
59
68
  ),
60
- model=MODEL_NAMES.resolve(self._model_name.v),
69
+ model=CHAT_MODEL_NAMES.resolve(self._model_name.v),
61
70
  mandatory_kwargs=dict(
62
71
  stream=False,
63
72
  ),
64
73
  )
65
74
 
66
- raw_request = rh.raw_request()
75
+ raw_request = msh.marshal(rh.oai_request())
67
76
 
68
- http_response = http.request(
77
+ http_response = await http.async_request(
69
78
  'https://api.openai.com/v1/chat/completions',
70
79
  headers={
71
80
  http.consts.HEADER_CONTENT_TYPE: http.consts.CONTENT_TYPE_JSON,
72
81
  http.consts.HEADER_AUTH: http.consts.format_bearer_auth_header(check.not_none(self._api_key).reveal()),
73
82
  },
74
83
  data=json.dumps(raw_request).encode('utf-8'),
84
+ client=self._http_client,
75
85
  )
76
86
 
77
87
  raw_response = json.loads(check.not_none(http_response.data).decode('utf-8'))
78
88
 
79
- return rh.build_response(raw_response)
89
+ return build_mc_choices_response(msh.unmarshal(raw_response, pt.ChatCompletionResponse))
@@ -23,13 +23,19 @@ from ....standard import ApiKey
23
23
  class OpenaiCompletionService:
24
24
  DEFAULT_MODEL_NAME: ta.ClassVar[str] = 'gpt-3.5-turbo-instruct'
25
25
 
26
- def __init__(self, *configs: Config) -> None:
26
+ def __init__(
27
+ self,
28
+ *configs: Config,
29
+ http_client: http.AsyncHttpClient | None = None,
30
+ ) -> None:
27
31
  super().__init__()
28
32
 
33
+ self._http_client = http_client
34
+
29
35
  with tv.consume(*configs) as cc:
30
36
  self._api_key = ApiKey.pop_secret(cc, env='OPENAI_API_KEY')
31
37
 
32
- def invoke(self, t: CompletionRequest) -> CompletionResponse:
38
+ async def invoke(self, t: CompletionRequest) -> CompletionResponse:
33
39
  raw_request = dict(
34
40
  model=self.DEFAULT_MODEL_NAME,
35
41
  prompt=t.v,
@@ -41,13 +47,14 @@ class OpenaiCompletionService:
41
47
  stream=False,
42
48
  )
43
49
 
44
- raw_response = http.request(
50
+ raw_response = await http.async_request(
45
51
  'https://api.openai.com/v1/completions',
46
52
  headers={
47
53
  http.consts.HEADER_CONTENT_TYPE: http.consts.CONTENT_TYPE_JSON,
48
54
  http.consts.HEADER_AUTH: http.consts.format_bearer_auth_header(check.not_none(self._api_key).reveal()),
49
55
  },
50
56
  data=json.dumps(raw_request).encode('utf-8'),
57
+ client=self._http_client,
51
58
  )
52
59
 
53
60
  response = json.loads(check.not_none(raw_response.data).decode('utf-8'))
@@ -22,25 +22,32 @@ from ....vectors.types import Vector
22
22
  class OpenaiEmbeddingService:
23
23
  model = 'text-embedding-3-small'
24
24
 
25
- def __init__(self, *configs: Config) -> None:
25
+ def __init__(
26
+ self,
27
+ *configs: Config,
28
+ http_client: http.AsyncHttpClient | None = None,
29
+ ) -> None:
26
30
  super().__init__()
27
31
 
32
+ self._http_client = http_client
33
+
28
34
  with tv.consume(*configs) as cc:
29
35
  self._api_key = ApiKey.pop_secret(cc, env='OPENAI_API_KEY')
30
36
 
31
- def invoke(self, request: EmbeddingRequest) -> EmbeddingResponse:
37
+ async def invoke(self, request: EmbeddingRequest) -> EmbeddingResponse:
32
38
  raw_request = dict(
33
39
  model=self.model,
34
40
  input=check.isinstance(request.v, str),
35
41
  )
36
42
 
37
- raw_response = http.request(
43
+ raw_response = await http.async_request(
38
44
  'https://api.openai.com/v1/embeddings',
39
45
  headers={
40
46
  http.consts.HEADER_CONTENT_TYPE: http.consts.CONTENT_TYPE_JSON,
41
47
  http.consts.HEADER_AUTH: http.consts.format_bearer_auth_header(check.not_none(self._api_key).reveal()),
42
48
  },
43
49
  data=json.dumps(raw_request).encode('utf-8'),
50
+ client=self._http_client,
44
51
  )
45
52
 
46
53
  response = json.loads(check.not_none(raw_response.data).decode('utf-8'))
@@ -2,74 +2,150 @@ import typing as ta
2
2
 
3
3
  from omlish import cached
4
4
  from omlish import check
5
- from omlish import lang
6
5
  from omlish import typedvalues as tv
7
6
  from omlish.formats import json
8
7
 
8
+ from .....backends.openai import protocol as pt
9
9
  from ....chat.choices.services import ChatChoicesResponse
10
10
  from ....chat.choices.types import AiChoice
11
+ from ....chat.choices.types import AiChoices
11
12
  from ....chat.choices.types import ChatChoicesOptions
12
13
  from ....chat.messages import AiMessage
14
+ from ....chat.messages import AnyAiMessage
13
15
  from ....chat.messages import Chat
14
- from ....chat.messages import Message
15
16
  from ....chat.messages import SystemMessage
16
- from ....chat.messages import ToolExecResultMessage
17
+ from ....chat.messages import ToolUseMessage
18
+ from ....chat.messages import ToolUseResultMessage
17
19
  from ....chat.messages import UserMessage
18
- from ....chat.stream.types import AiMessageDelta
20
+ from ....chat.stream.types import AiDelta
21
+ from ....chat.stream.types import ContentAiDelta
22
+ from ....chat.stream.types import PartialToolUseAiDelta
19
23
  from ....chat.tools.types import Tool
24
+ from ....content.json import JsonContent
20
25
  from ....content.prepare import prepare_content_str
26
+ from ....llms.types import MaxCompletionTokens
21
27
  from ....llms.types import MaxTokens
22
28
  from ....llms.types import Temperature
23
29
  from ....llms.types import TokenUsage
24
30
  from ....llms.types import TokenUsageOutput
25
- from ....tools.jsonschema import build_tool_spec_json_schema
26
- from ....tools.types import ToolExecRequest
31
+ from ....tools.jsonschema import build_tool_spec_params_json_schema
27
32
  from ....tools.types import ToolSpec
33
+ from ....tools.types import ToolUse
28
34
  from ....types import Option
29
35
 
30
36
 
31
37
  ##
32
38
 
33
39
 
34
- def build_request_message(m: Message) -> ta.Mapping[str, ta.Any]:
35
- if isinstance(m, SystemMessage):
36
- return dict(
37
- role='system',
38
- content=m.c,
39
- )
40
+ def build_oai_request_msgs(mc_chat: Chat) -> ta.Sequence[pt.ChatCompletionMessage]:
41
+ oai_msgs: list[pt.ChatCompletionMessage] = []
42
+
43
+ for mc_msg in mc_chat:
44
+ if isinstance(mc_msg, SystemMessage):
45
+ oai_msgs.append(pt.SystemChatCompletionMessage(
46
+ content=check.isinstance(mc_msg.c, str),
47
+ ))
40
48
 
41
- elif isinstance(m, AiMessage):
42
- return dict(
43
- role='assistant',
44
- content=check.isinstance(m.c, (str, None)),
45
- **(dict(tool_calls=[
46
- dict(
47
- id=te.id,
48
- function=dict(
49
- arguments=check.not_none(te.raw_args),
50
- name=te.name,
49
+ elif isinstance(mc_msg, AiMessage):
50
+ oai_msgs.append(pt.AssistantChatCompletionMessage(
51
+ content=check.isinstance(mc_msg.c, (str, None)),
52
+ ))
53
+
54
+ elif isinstance(mc_msg, ToolUseMessage):
55
+ oai_msgs.append(pt.AssistantChatCompletionMessage(
56
+ tool_calls=[pt.AssistantChatCompletionMessage.ToolCall(
57
+ id=check.not_none(mc_msg.tu.id),
58
+ function=pt.AssistantChatCompletionMessage.ToolCall.Function(
59
+ arguments=check.not_none(mc_msg.tu.raw_args),
60
+ name=mc_msg.tu.name,
51
61
  ),
52
- type='function',
53
- )
54
- for te in m.tool_exec_requests
55
- ]) if m.tool_exec_requests else {}),
56
- )
62
+ )],
63
+ ))
57
64
 
58
- elif isinstance(m, UserMessage):
59
- return dict(
60
- role='user',
61
- content=prepare_content_str(m.c),
62
- )
65
+ elif isinstance(mc_msg, UserMessage):
66
+ oai_msgs.append(pt.UserChatCompletionMessage(
67
+ content=prepare_content_str(mc_msg.c),
68
+ ))
69
+
70
+ elif isinstance(mc_msg, ToolUseResultMessage):
71
+ tc: str
72
+ if isinstance(mc_msg.tur.c, str):
73
+ tc = mc_msg.tur.c
74
+ elif isinstance(mc_msg.tur.c, JsonContent):
75
+ tc = json.dumps_compact(mc_msg.tur.c)
76
+ else:
77
+ raise TypeError(mc_msg.tur.c)
78
+ oai_msgs.append(pt.ToolChatCompletionMessage(
79
+ tool_call_id=check.not_none(mc_msg.tur.id),
80
+ content=tc,
81
+ ))
82
+
83
+ else:
84
+ raise TypeError(mc_msg)
85
+
86
+ return oai_msgs
87
+
88
+
89
+ #
90
+
91
+
92
+ def build_mc_ai_choice(oai_choice: pt.ChatCompletionResponseChoice) -> AiChoice:
93
+ cur: list[AnyAiMessage] = []
94
+
95
+ oai_msg = oai_choice.message
96
+
97
+ if (oai_c := oai_msg.content) is not None:
98
+ cur.append(AiMessage(check.isinstance(oai_c, str)))
99
+
100
+ for oai_tc in oai_msg.tool_calls or []:
101
+ cur.append(ToolUseMessage(ToolUse(
102
+ id=oai_tc.id,
103
+ name=oai_tc.function.name,
104
+ args=json.loads(oai_tc.function.arguments or '{}'),
105
+ raw_args=oai_tc.function.arguments,
106
+ )))
107
+
108
+ return AiChoice(cur)
63
109
 
64
- elif isinstance(m, ToolExecResultMessage):
65
- return dict(
66
- role='tool',
67
- tool_call_id=m.id,
68
- content=check.isinstance(m.c, str),
110
+
111
+ def build_mc_ai_choices(oai_resp: pt.ChatCompletionResponse) -> AiChoices:
112
+ return [
113
+ build_mc_ai_choice(oai_choice)
114
+ for oai_choice in oai_resp.choices
115
+ ]
116
+
117
+
118
+ def build_mc_choices_response(oai_resp: pt.ChatCompletionResponse) -> ChatChoicesResponse:
119
+ return ChatChoicesResponse(
120
+ build_mc_ai_choices(oai_resp),
121
+
122
+ tv.TypedValues(
123
+ *([TokenUsageOutput(TokenUsage(
124
+ input=tu.prompt_tokens,
125
+ output=tu.completion_tokens,
126
+ total=tu.total_tokens,
127
+ ))] if (tu := oai_resp.usage) is not None else []),
128
+ ),
129
+ )
130
+
131
+
132
+ def build_mc_ai_delta(delta: pt.ChatCompletionChunkChoiceDelta) -> AiDelta:
133
+ if delta.content is not None:
134
+ check.state(not delta.tool_calls)
135
+ return ContentAiDelta(delta.content)
136
+
137
+ elif delta.tool_calls is not None:
138
+ check.state(delta.content is None)
139
+ tc = check.single(delta.tool_calls)
140
+ tc_fn = check.not_none(tc.function)
141
+ return PartialToolUseAiDelta(
142
+ id=tc.id,
143
+ name=tc_fn.name,
144
+ raw_args=tc_fn.arguments,
69
145
  )
70
146
 
71
147
  else:
72
- raise TypeError(m)
148
+ raise ValueError(delta)
73
149
 
74
150
 
75
151
  ##
@@ -90,21 +166,15 @@ class OpenaiChatRequestHandler:
90
166
  self._model = model
91
167
  self._mandatory_kwargs = mandatory_kwargs
92
168
 
93
- ROLES_MAP: ta.ClassVar[ta.Mapping[type[Message], str]] = {
94
- SystemMessage: 'system',
95
- UserMessage: 'user',
96
- AiMessage: 'assistant',
97
- ToolExecResultMessage: 'tool',
98
- }
99
-
100
- DEFAULT_OPTIONS: ta.ClassVar[tv.TypedValues[Option]] = tv.TypedValues(
101
- Temperature(0.),
102
- MaxTokens(1024),
169
+ DEFAULT_OPTIONS: ta.ClassVar[tv.TypedValues[Option]] = tv.TypedValues[Option](
170
+ # Temperature(0.),
171
+ # MaxTokens(1024),
103
172
  )
104
173
 
105
174
  _OPTION_KWARG_NAMES_MAP: ta.ClassVar[ta.Mapping[str, type[ChatChoicesOptions]]] = dict(
106
175
  temperature=Temperature,
107
176
  max_tokens=MaxTokens,
177
+ max_completion_tokens=MaxCompletionTokens,
108
178
  )
109
179
 
110
180
  class _ProcessedOptions(ta.NamedTuple):
@@ -114,8 +184,8 @@ class OpenaiChatRequestHandler:
114
184
  @cached.function
115
185
  def _process_options(self) -> _ProcessedOptions:
116
186
  kwargs: dict = dict(
117
- temperature=0,
118
- max_tokens=1024,
187
+ # temperature=0,
188
+ # max_tokens=1024,
119
189
  )
120
190
 
121
191
  tools_by_name: dict[str, ToolSpec] = {}
@@ -139,71 +209,26 @@ class OpenaiChatRequestHandler:
139
209
  )
140
210
 
141
211
  @cached.function
142
- def raw_request(self) -> ta.Mapping[str, ta.Any]:
212
+ def oai_request(self) -> pt.ChatCompletionRequest:
143
213
  po = self._process_options()
144
214
 
145
- tools = [
146
- dict(
147
- type='function',
148
- function=build_tool_spec_json_schema(ts),
215
+ tools: list[pt.ChatCompletionRequestTool] = [
216
+ pt.ChatCompletionRequestTool(
217
+ function=pt.ChatCompletionRequestTool.Function(
218
+ name=check.not_none(ts.name),
219
+ description=prepare_content_str(ts.desc),
220
+ parameters=build_tool_spec_params_json_schema(ts),
221
+ ),
149
222
  )
150
223
  for ts in po.tools_by_name.values()
151
224
  ]
152
225
 
153
- return dict(
226
+ return pt.ChatCompletionRequest(
154
227
  model=self._model,
155
- messages=[
156
- build_request_message(m)
157
- for m in self._chat
158
- ],
228
+ messages=build_oai_request_msgs(self._chat),
159
229
  top_p=1,
160
- **lang.opt_kw(tools=tools),
230
+ tools=tools or None,
161
231
  frequency_penalty=0.0,
162
232
  presence_penalty=0.0,
163
233
  **po.kwargs,
164
234
  )
165
-
166
- def build_ai_message(self, message: ta.Mapping[str, ta.Any]) -> AiMessage:
167
- return AiMessage(
168
- message.get('content'),
169
- tool_exec_requests=[
170
- ToolExecRequest(
171
- id=tc['id'],
172
- name=tc['function']['name'],
173
- args=json.loads(tc['function']['arguments'] or '{}'),
174
- raw_args=tc['function']['arguments'],
175
- )
176
- for tc in message.get('tool_calls', [])
177
- ] or None,
178
- )
179
-
180
- def build_response(self, raw_response: ta.Mapping[str, ta.Any]) -> ChatChoicesResponse:
181
- return ChatChoicesResponse(
182
- [
183
- AiChoice(self.build_ai_message(choice['message']))
184
- for choice in raw_response['choices']
185
- ],
186
-
187
- tv.TypedValues(
188
- *([TokenUsageOutput(TokenUsage(
189
- input=tu['prompt_tokens'],
190
- output=tu['completion_tokens'],
191
- total=tu['total_tokens'],
192
- ))] if (tu := raw_response.get('usage')) is not None else []),
193
- ),
194
- )
195
-
196
- def build_ai_message_delta(self, delta: ta.Mapping[str, ta.Any]) -> AiMessageDelta:
197
- return AiMessageDelta(
198
- delta.get('content'),
199
- # FIXME:
200
- # tool_exec_requests=[
201
- # ToolExecRequest(
202
- # id=tc['id'],
203
- # spec=self._process_options().tools_by_name[tc['function']['name']],
204
- # args=json.loads(tc['function']['arguments'] or '{}'),
205
- # raw_args=tc['function']['arguments'],
206
- # )
207
- # for tc in message_or_delta.get('tool_calls', [])
208
- # ] or None,
209
- )