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
@@ -1,26 +1,33 @@
1
1
  import typing as ta
2
2
 
3
3
  from omlish import check
4
- from omlish import lang
4
+ from omlish import marshal as msh
5
5
  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
9
  from omlish.io.buffers import DelimitingBuffer
10
10
 
11
+ from .....backends.anthropic.protocol import types as pt
12
+ from .....backends.anthropic.protocol.sse.events import AnthropicSseDecoderEvents
11
13
  from ....chat.choices.services import ChatChoicesOutputs
12
- from ....chat.messages import SystemMessage
13
- from ....chat.stream.services import ChatChoicesStreamRequest
14
- from ....chat.stream.services import ChatChoicesStreamResponse
15
- from ....chat.stream.services import static_check_is_chat_choices_stream_service
16
- from ....chat.stream.types import AiChoiceDelta
17
- from ....chat.stream.types import AiChoiceDeltas
14
+ from ....chat.choices.stream.services import ChatChoicesStreamRequest
15
+ from ....chat.choices.stream.services import ChatChoicesStreamResponse
16
+ from ....chat.choices.stream.services import static_check_is_chat_choices_stream_service
17
+ from ....chat.choices.stream.types import AiChoiceDeltas
18
+ from ....chat.choices.stream.types import AiChoicesDeltas
19
+ from ....chat.stream.types import ContentAiDelta
20
+ from ....chat.stream.types import PartialToolUseAiDelta
21
+ from ....chat.tools.types import Tool
18
22
  from ....configs import Config
19
23
  from ....resources import UseResources
20
24
  from ....standard import ApiKey
25
+ from ....stream.services import StreamResponseSink
21
26
  from ....stream.services import new_stream_response
22
27
  from .chat import AnthropicChatChoicesService
23
28
  from .names import MODEL_NAMES
29
+ from .protocol import build_protocol_chat_messages
30
+ from .protocol import build_protocol_tool
24
31
 
25
32
 
26
33
  ##
@@ -32,42 +39,46 @@ from .names import MODEL_NAMES
32
39
  # )
33
40
  @static_check_is_chat_choices_stream_service
34
41
  class AnthropicChatChoicesStreamService:
35
- def __init__(self, *configs: Config) -> None:
42
+ def __init__(
43
+ self,
44
+ *configs: Config,
45
+ http_client: http.AsyncHttpClient | None = None,
46
+ ) -> None:
36
47
  super().__init__()
37
48
 
49
+ self._http_client = http_client
50
+
38
51
  with tv.consume(*configs) as cc:
39
52
  self._model_name = cc.pop(AnthropicChatChoicesService.DEFAULT_MODEL_NAME)
40
53
  self._api_key = check.not_none(ApiKey.pop_secret(cc, env='ANTHROPIC_API_KEY'))
41
54
 
42
- READ_CHUNK_SIZE = 64 * 1024
55
+ READ_CHUNK_SIZE: ta.ClassVar[int] = -1
43
56
 
44
- def invoke(
57
+ async def invoke(
45
58
  self,
46
59
  request: ChatChoicesStreamRequest,
47
60
  *,
48
61
  max_tokens: int = 4096, # FIXME: ChatOption
49
62
  ) -> ChatChoicesStreamResponse:
50
- messages = []
51
- system: str | None = None
52
- for i, m in enumerate(request.v):
53
- if isinstance(m, SystemMessage):
54
- if i != 0 or system is not None:
55
- raise Exception('Only supports one system message and must be first')
56
- system = AnthropicChatChoicesService._get_msg_content(m) # noqa
57
- else:
58
- messages.append(dict(
59
- role=AnthropicChatChoicesService.ROLES_MAP[type(m)], # noqa
60
- content=check.isinstance(AnthropicChatChoicesService._get_msg_content(m), str), # noqa
61
- ))
62
-
63
- raw_request = dict(
63
+ messages, system = build_protocol_chat_messages(request.v)
64
+
65
+ tools: list[pt.ToolSpec] = []
66
+ with tv.TypedValues(*request.options).consume() as oc:
67
+ t: Tool
68
+ for t in oc.pop(Tool, []):
69
+ tools.append(build_protocol_tool(t))
70
+
71
+ a_req = pt.MessagesRequest(
64
72
  model=MODEL_NAMES.resolve(self._model_name.v),
65
- **lang.opt_kw(system=system),
73
+ system=system,
66
74
  messages=messages,
75
+ tools=tools or None,
67
76
  max_tokens=max_tokens,
68
77
  stream=True,
69
78
  )
70
79
 
80
+ raw_request = msh.marshal(a_req)
81
+
71
82
  http_request = http.HttpRequest(
72
83
  'https://api.anthropic.com/v1/messages',
73
84
  headers={
@@ -78,16 +89,19 @@ class AnthropicChatChoicesStreamService:
78
89
  data=json.dumps(raw_request).encode('utf-8'),
79
90
  )
80
91
 
81
- with UseResources.or_new(request.options) as rs:
82
- http_client = rs.enter_context(http.client())
83
- http_response = rs.enter_context(http_client.stream_request(http_request))
92
+ async with UseResources.or_new(request.options) as rs:
93
+ http_client = await rs.enter_async_context(http.manage_async_client(self._http_client))
94
+ http_response = await rs.enter_async_context(await http_client.stream_request(http_request))
95
+
96
+ async def inner(sink: StreamResponseSink[AiChoicesDeltas]) -> ta.Sequence[ChatChoicesOutputs] | None:
97
+ msg_start: AnthropicSseDecoderEvents.MessageStart | None = None
98
+ cbk_start: AnthropicSseDecoderEvents.ContentBlockStart | None = None
99
+ msg_stop: AnthropicSseDecoderEvents.MessageStop | None = None
84
100
 
85
- def yield_choices() -> ta.Generator[AiChoiceDeltas, None, ta.Sequence[ChatChoicesOutputs] | None]:
86
101
  db = DelimitingBuffer([b'\r', b'\n', b'\r\n'])
87
102
  sd = sse.SseDecoder()
88
103
  while True:
89
- # FIXME: read1 not on response stream protocol
90
- b = http_response.stream.read1(self.READ_CHUNK_SIZE) # type: ignore[attr-defined]
104
+ b = await http_response.stream.read1(self.READ_CHUNK_SIZE)
91
105
  for l in db.feed(b):
92
106
  if isinstance(l, DelimitingBuffer.Incomplete):
93
107
  # FIXME: handle
@@ -95,29 +109,83 @@ class AnthropicChatChoicesStreamService:
95
109
 
96
110
  # FIXME: https://docs.anthropic.com/en/docs/build-with-claude/streaming
97
111
  for so in sd.process_line(l):
98
- # FIXME: AnthropicSseMessageAssembler lol
99
- if isinstance(so, sse.SseEvent) and so.type == b'message':
112
+ if isinstance(so, sse.SseEvent):
100
113
  ss = so.data.decode('utf-8')
101
114
  if ss == '[DONE]':
102
115
  return []
103
116
 
104
- sj = json.loads(ss) # ChatCompletionChunk
117
+ dct = json.loads(ss)
118
+ check.equal(dct['type'], so.type.decode('utf-8'))
119
+ ae = msh.unmarshal(dct, AnthropicSseDecoderEvents.Event)
120
+
121
+ match ae:
122
+ case AnthropicSseDecoderEvents.MessageStart():
123
+ check.none(msg_start)
124
+ msg_start = ae
125
+ if msg_start.message.content:
126
+ raise NotImplementedError
127
+
128
+ case AnthropicSseDecoderEvents.ContentBlockStart():
129
+ check.not_none(msg_start)
130
+ check.none(cbk_start)
131
+ cbk_start = ae
132
+
133
+ if isinstance(ae.content_block, AnthropicSseDecoderEvents.ContentBlockStart.Text): # noqa
134
+ await sink.emit(AiChoicesDeltas([AiChoiceDeltas([ContentAiDelta(
135
+ ae.content_block.text,
136
+ )])]))
137
+
138
+ elif isinstance(ae.content_block, AnthropicSseDecoderEvents.ContentBlockStart.ToolUse): # noqa
139
+ await sink.emit(AiChoicesDeltas([AiChoiceDeltas([PartialToolUseAiDelta( # noqa
140
+ id=ae.content_block.id,
141
+ name=ae.content_block.name,
142
+ raw_args=ae.content_block.input,
143
+ )])]))
144
+
145
+ else:
146
+ raise TypeError(ae.content_block)
147
+
148
+ case AnthropicSseDecoderEvents.ContentBlockDelta():
149
+ check.not_none(cbk_start)
150
+
151
+ if isinstance(ae.delta, AnthropicSseDecoderEvents.ContentBlockDelta.TextDelta):
152
+ await sink.emit(AiChoicesDeltas([AiChoiceDeltas([ContentAiDelta(
153
+ ae.delta.text,
154
+ )])]))
155
+
156
+ elif isinstance(ae.delta, AnthropicSseDecoderEvents.ContentBlockDelta.InputJsonDelta): # noqa
157
+ await sink.emit(AiChoicesDeltas([AiChoiceDeltas([PartialToolUseAiDelta( # noqa
158
+ raw_args=ae.delta.partial_json,
159
+ )])]))
160
+
161
+ else:
162
+ raise TypeError(ae.delta)
163
+
164
+ case AnthropicSseDecoderEvents.ContentBlockStop():
165
+ check.not_none(cbk_start)
166
+ cbk_start = None
167
+
168
+ case AnthropicSseDecoderEvents.MessageDelta():
169
+ check.not_none(msg_start)
170
+ check.none(cbk_start)
105
171
 
106
- check.state(sj['object'] == 'chat.completion.chunk')
172
+ case AnthropicSseDecoderEvents.MessageStop():
173
+ check.not_none(msg_start)
174
+ check.none(msg_stop)
175
+ msg_stop = ae
107
176
 
108
- # FIXME: stop reason
109
- if not sj['choices']:
110
- continue
177
+ case AnthropicSseDecoderEvents.Ping():
178
+ pass
111
179
 
112
- yield [
113
- AiChoiceDelta(choice['delta'])
114
- for choice in sj['choices']
115
- ]
180
+ case _:
181
+ raise TypeError(ae)
116
182
 
117
183
  if not b:
184
+ check.not_none(msg_stop)
185
+ check.none(cbk_start)
118
186
  return []
119
187
 
120
188
  # raw_response = json.loads(check.not_none(http_response.data).decode('utf-8'))
121
189
  # return rh.build_response(raw_response)
122
190
 
123
- return new_stream_response(rs, yield_choices())
191
+ return await new_stream_response(rs, inner)
@@ -17,7 +17,7 @@ from ....search import static_check_is_search_service
17
17
  # )
18
18
  @static_check_is_search_service
19
19
  class DuckduckgoSearchService:
20
- def invoke(self, request: SearchRequest) -> SearchResponse:
20
+ async def invoke(self, request: SearchRequest) -> SearchResponse:
21
21
  dsch = ddgs.DDGS()
22
22
  res = dsch.text(request.v)
23
23
  return SearchResponse(SearchHits(
File without changes
@@ -0,0 +1,69 @@
1
+ # ruff: noqa: PERF402
2
+ import typing as ta
3
+
4
+ from omlish.text.lorem import LOREM
5
+
6
+ from ....chat.choices.services import ChatChoicesRequest
7
+ from ....chat.choices.services import ChatChoicesResponse
8
+ from ....chat.choices.services import static_check_is_chat_choices_service
9
+ from ....chat.choices.stream.services import ChatChoicesStreamRequest
10
+ from ....chat.choices.stream.services import ChatChoicesStreamResponse
11
+ from ....chat.choices.stream.services import static_check_is_chat_choices_stream_service
12
+ from ....chat.choices.stream.types import AiChoiceDeltas
13
+ from ....chat.choices.stream.types import AiChoicesDeltas
14
+ from ....chat.choices.types import AiChoice
15
+ from ....chat.choices.types import ChatChoicesOutputs
16
+ from ....chat.messages import AiMessage
17
+ from ....chat.stream.types import ContentAiDelta
18
+ from ....resources import UseResources
19
+ from ....stream.services import StreamResponseSink
20
+ from ....stream.services import new_stream_response
21
+
22
+
23
+ ##
24
+
25
+
26
+ # @omlish-manifest $.minichain.registries.manifests.RegistryManifest(
27
+ # name='dummy',
28
+ # type='ChatChoicesService',
29
+ # )
30
+ @static_check_is_chat_choices_service
31
+ class DummyChatChoicesService:
32
+ async def invoke(self, request: ChatChoicesRequest) -> ChatChoicesResponse:
33
+ return ChatChoicesResponse([AiChoice([AiMessage(LOREM)])])
34
+
35
+
36
+ ##
37
+
38
+
39
+ # @omlish-manifest $.minichain.registries.manifests.RegistryManifest(
40
+ # name='dummy',
41
+ # type='ChatChoicesStreamService',
42
+ # )
43
+ @static_check_is_chat_choices_stream_service
44
+ class DummyChatChoicesStreamService:
45
+ async def invoke(self, request: ChatChoicesStreamRequest) -> ChatChoicesStreamResponse:
46
+ async with UseResources.or_new(request.options) as rs:
47
+ async def inner(sink: StreamResponseSink[AiChoicesDeltas]) -> ta.Sequence[ChatChoicesOutputs]:
48
+ for s in LOREM:
49
+ await sink.emit(AiChoicesDeltas([
50
+ AiChoiceDeltas([
51
+ ContentAiDelta(s),
52
+ ]),
53
+ ]))
54
+
55
+ return []
56
+
57
+ return await new_stream_response(rs, inner)
58
+
59
+
60
+ ##
61
+
62
+
63
+ # @omlish-manifest $.minichain.backends.strings.manifests.BackendStringsManifest(
64
+ # [
65
+ # 'ChatChoicesService',
66
+ # 'ChatChoicesStreamService',
67
+ # ],
68
+ # 'dummy',
69
+ # )
@@ -4,21 +4,29 @@ https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models
4
4
  import typing as ta
5
5
 
6
6
  from omlish import check
7
+ from omlish import marshal as msh
7
8
  from omlish import typedvalues as tv
8
9
  from omlish.formats import json
9
10
  from omlish.http import all as http
10
11
 
12
+ from .....backends.google.protocol import types as pt
11
13
  from ....chat.choices.services import ChatChoicesRequest
12
14
  from ....chat.choices.services import ChatChoicesResponse
13
15
  from ....chat.choices.services import static_check_is_chat_choices_service
14
16
  from ....chat.choices.types import AiChoice
15
17
  from ....chat.messages import AiMessage
18
+ from ....chat.messages import AnyAiMessage
16
19
  from ....chat.messages import Message
17
20
  from ....chat.messages import SystemMessage
21
+ from ....chat.messages import ToolUseMessage
22
+ from ....chat.messages import ToolUseResultMessage
18
23
  from ....chat.messages import UserMessage
24
+ from ....chat.tools.types import Tool
19
25
  from ....models.configs import ModelName
20
26
  from ....standard import ApiKey
27
+ from ....tools.types import ToolUse
21
28
  from .names import MODEL_NAMES
29
+ from .tools import build_tool_spec_schema
22
30
 
23
31
 
24
32
  ##
@@ -32,9 +40,15 @@ from .names import MODEL_NAMES
32
40
  class GoogleChatChoicesService:
33
41
  DEFAULT_MODEL_NAME: ta.ClassVar[ModelName] = ModelName(check.not_none(MODEL_NAMES.default))
34
42
 
35
- def __init__(self, *configs: ApiKey | ModelName) -> None:
43
+ def __init__(
44
+ self,
45
+ *configs: ApiKey | ModelName,
46
+ http_client: http.AsyncHttpClient | None = None,
47
+ ) -> None:
36
48
  super().__init__()
37
49
 
50
+ self._http_client = http_client
51
+
38
52
  with tv.consume(*configs) as cc:
39
53
  self._model_name = cc.pop(self.DEFAULT_MODEL_NAME)
40
54
  self._api_key = ApiKey.pop_secret(cc, env='GEMINI_API_KEY')
@@ -52,43 +66,121 @@ class GoogleChatChoicesService:
52
66
  BASE_URL: ta.ClassVar[str] = 'https://generativelanguage.googleapis.com/v1beta/models'
53
67
 
54
68
  ROLES_MAP: ta.ClassVar[ta.Mapping[type[Message], str]] = {
55
- SystemMessage: 'system',
56
69
  UserMessage: 'user',
57
- AiMessage: 'assistant',
70
+ AiMessage: 'model',
71
+ ToolUseMessage: 'model',
58
72
  }
59
73
 
60
- def invoke(
74
+ async def invoke(
61
75
  self,
62
76
  request: ChatChoicesRequest,
63
77
  ) -> ChatChoicesResponse:
64
78
  key = check.not_none(self._api_key).reveal()
65
79
 
66
- req_dct = {
67
- 'contents': [
68
- {
69
- 'role': self.ROLES_MAP[type(m)],
70
- 'parts': [
71
- {
72
- 'text': self._get_msg_content(m),
73
- },
74
- ],
75
- }
76
- for m in request.v
77
- ],
78
- }
80
+ g_sys_content: pt.Content | None = None
81
+ g_contents: list[pt.Content] = []
82
+ for i, m in enumerate(request.v):
83
+ if isinstance(m, SystemMessage):
84
+ check.arg(i == 0)
85
+ check.none(g_sys_content)
86
+ g_sys_content = pt.Content(
87
+ parts=[pt.Part(
88
+ text=check.not_none(self._get_msg_content(m)),
89
+ )],
90
+ )
91
+
92
+ elif isinstance(m, ToolUseResultMessage):
93
+ tr_resp_val: pt.Value
94
+ if m.tur.c is None:
95
+ tr_resp_val = pt.NullValue() # type: ignore[unreachable]
96
+ elif isinstance(m.tur.c, str):
97
+ tr_resp_val = pt.StringValue(m.tur.c)
98
+ else:
99
+ raise TypeError(m.tur.c)
100
+ g_contents.append(pt.Content(
101
+ parts=[pt.Part(
102
+ function_response=pt.FunctionResponse(
103
+ id=m.tur.id,
104
+ name=m.tur.name,
105
+ response={
106
+ 'value': tr_resp_val,
107
+ },
108
+ ),
109
+ )],
110
+ ))
111
+
112
+ elif isinstance(m, AiMessage):
113
+ g_contents.append(pt.Content(
114
+ parts=[pt.Part(
115
+ text=check.not_none(self._get_msg_content(m)),
116
+ )],
117
+ role='model',
118
+ ))
119
+
120
+ elif isinstance(m, ToolUseMessage):
121
+ g_contents.append(pt.Content(
122
+ parts=[pt.Part(
123
+ function_call=pt.FunctionCall(
124
+ id=m.tu.id,
125
+ name=m.tu.name,
126
+ args=m.tu.args,
127
+ ),
128
+ )],
129
+ role='model',
130
+ ))
131
+
132
+ else:
133
+ g_contents.append(pt.Content(
134
+ parts=[pt.Part(
135
+ text=check.not_none(self._get_msg_content(m)),
136
+ )],
137
+ role=self.ROLES_MAP[type(m)], # type: ignore[arg-type]
138
+ ))
139
+
140
+ g_tools: list[pt.Tool] = []
141
+ with tv.TypedValues(*request.options).consume() as oc:
142
+ t: Tool
143
+ for t in oc.pop(Tool, []):
144
+ g_tools.append(pt.Tool(
145
+ function_declarations=[build_tool_spec_schema(t.spec)],
146
+ ))
147
+
148
+ g_req = pt.GenerateContentRequest(
149
+ contents=g_contents or None,
150
+ tools=g_tools or None,
151
+ system_instruction=g_sys_content,
152
+ )
153
+
154
+ req_dct = msh.marshal(g_req)
79
155
 
80
156
  model_name = MODEL_NAMES.resolve(self._model_name.v)
81
157
 
82
- resp = http.request(
158
+ resp = await http.async_request(
83
159
  f'{self.BASE_URL.rstrip("/")}/{model_name}:generateContent?key={key}',
84
160
  headers={'Content-Type': 'application/json'},
85
161
  data=json.dumps_compact(req_dct).encode('utf-8'),
86
162
  method='POST',
163
+ client=self._http_client,
87
164
  )
88
165
 
89
166
  resp_dct = json.loads(check.not_none(resp.data).decode('utf-8'))
90
167
 
91
- return ChatChoicesResponse([
92
- AiChoice(AiMessage(c['content']['parts'][0]['text']))
93
- for c in resp_dct['candidates']
94
- ])
168
+ g_resp = msh.unmarshal(resp_dct, pt.GenerateContentResponse)
169
+
170
+ ai_choices: list[AiChoice] = []
171
+ for c in g_resp.candidates or []:
172
+ out: list[AnyAiMessage] = []
173
+ for g_resp_part in check.not_none(check.not_none(c.content).parts):
174
+ if (g_txt := g_resp_part.text) is not None:
175
+ out.append(AiMessage(g_txt))
176
+ elif (g_fc := g_resp_part.function_call) is not None:
177
+ out.append(ToolUseMessage(ToolUse(
178
+ id=g_fc.id,
179
+ name=g_fc.name,
180
+ args=g_fc.args or {},
181
+ )))
182
+ else:
183
+ raise TypeError(g_resp_part)
184
+ ai_choices.append(AiChoice(out))
185
+
186
+ return ChatChoicesResponse(ai_choices)
@@ -82,13 +82,17 @@ class CseSearchService:
82
82
  self,
83
83
  cse_id: str | None = None,
84
84
  cse_api_key: str | None = None,
85
+ *,
86
+ http_client: http.AsyncHttpClient | None = None,
85
87
  ) -> None:
86
88
  super().__init__()
87
89
 
88
90
  self._cse_id = cse_id
89
91
  self._cse_api_key = cse_api_key
90
92
 
91
- def invoke(
93
+ self._http_client = http_client
94
+
95
+ async def invoke(
92
96
  self,
93
97
  request: SearchRequest,
94
98
  ) -> SearchResponse:
@@ -97,8 +101,9 @@ class CseSearchService:
97
101
  cx=check.non_empty_str(self._cse_id),
98
102
  q=request.v,
99
103
  ))
100
- resp = http.request(
104
+ resp = await http.async_request(
101
105
  f'https://www.googleapis.com/customsearch/v1?{qs}',
106
+ client=self._http_client,
102
107
  )
103
108
  out = check.not_none(resp.data)
104
109