ommlds 0.0.0.dev456__py3-none-any.whl → 0.0.0.dev485__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 (212) hide show
  1. ommlds/.omlish-manifests.json +314 -33
  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/__init__.py +13 -1
  9. ommlds/backends/anthropic/protocol/_dataclasses.py +1625 -0
  10. ommlds/backends/anthropic/protocol/sse/assemble.py +22 -6
  11. ommlds/backends/anthropic/protocol/sse/events.py +13 -0
  12. ommlds/backends/google/protocol/__init__.py +13 -0
  13. ommlds/backends/google/protocol/_dataclasses.py +5997 -0
  14. ommlds/backends/google/protocol/types.py +5 -1
  15. ommlds/backends/groq/__init__.py +7 -0
  16. ommlds/backends/groq/_dataclasses.py +3901 -0
  17. ommlds/backends/groq/_marshal.py +23 -0
  18. ommlds/backends/groq/protocol.py +249 -0
  19. ommlds/backends/llamacpp/logging.py +4 -1
  20. ommlds/backends/mlx/caching.py +7 -3
  21. ommlds/backends/mlx/cli.py +10 -7
  22. ommlds/backends/mlx/generation.py +18 -16
  23. ommlds/backends/mlx/limits.py +10 -6
  24. ommlds/backends/mlx/loading.py +65 -5
  25. ommlds/backends/ollama/__init__.py +7 -0
  26. ommlds/backends/ollama/_dataclasses.py +3458 -0
  27. ommlds/backends/ollama/protocol.py +170 -0
  28. ommlds/backends/openai/protocol/__init__.py +15 -1
  29. ommlds/backends/openai/protocol/_dataclasses.py +7708 -0
  30. ommlds/backends/tavily/__init__.py +7 -0
  31. ommlds/backends/tavily/_dataclasses.py +1734 -0
  32. ommlds/backends/tavily/protocol.py +301 -0
  33. ommlds/backends/tinygrad/models/llama3/__init__.py +22 -14
  34. ommlds/backends/transformers/__init__.py +14 -0
  35. ommlds/backends/transformers/filecache.py +109 -0
  36. ommlds/backends/transformers/streamers.py +73 -0
  37. ommlds/cli/__init__.py +7 -0
  38. ommlds/cli/_dataclasses.py +2562 -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/messages.py +34 -0
  46. ommlds/cli/content/strings.py +42 -0
  47. ommlds/cli/inject.py +15 -32
  48. ommlds/cli/inputs/__init__.py +0 -0
  49. ommlds/cli/inputs/asyncs.py +32 -0
  50. ommlds/cli/inputs/sync.py +75 -0
  51. ommlds/cli/main.py +267 -128
  52. ommlds/cli/rendering/__init__.py +0 -0
  53. ommlds/cli/rendering/configs.py +9 -0
  54. ommlds/cli/rendering/inject.py +31 -0
  55. ommlds/cli/rendering/markdown.py +52 -0
  56. ommlds/cli/rendering/raw.py +73 -0
  57. ommlds/cli/rendering/types.py +21 -0
  58. ommlds/cli/secrets.py +21 -0
  59. ommlds/cli/sessions/base.py +1 -1
  60. ommlds/cli/sessions/chat/chat/__init__.py +0 -0
  61. ommlds/cli/sessions/chat/chat/ai/__init__.py +0 -0
  62. ommlds/cli/sessions/chat/chat/ai/configs.py +11 -0
  63. ommlds/cli/sessions/chat/chat/ai/inject.py +74 -0
  64. ommlds/cli/sessions/chat/chat/ai/injection.py +14 -0
  65. ommlds/cli/sessions/chat/chat/ai/rendering.py +70 -0
  66. ommlds/cli/sessions/chat/chat/ai/services.py +79 -0
  67. ommlds/cli/sessions/chat/chat/ai/tools.py +44 -0
  68. ommlds/cli/sessions/chat/chat/ai/types.py +28 -0
  69. ommlds/cli/sessions/chat/chat/state/__init__.py +0 -0
  70. ommlds/cli/sessions/chat/chat/state/configs.py +11 -0
  71. ommlds/cli/sessions/chat/chat/state/inject.py +36 -0
  72. ommlds/cli/sessions/chat/chat/state/inmemory.py +33 -0
  73. ommlds/cli/sessions/chat/chat/state/storage.py +52 -0
  74. ommlds/cli/sessions/chat/chat/state/types.py +38 -0
  75. ommlds/cli/sessions/chat/chat/user/__init__.py +0 -0
  76. ommlds/cli/sessions/chat/chat/user/configs.py +17 -0
  77. ommlds/cli/sessions/chat/chat/user/inject.py +62 -0
  78. ommlds/cli/sessions/chat/chat/user/interactive.py +31 -0
  79. ommlds/cli/sessions/chat/chat/user/oneshot.py +25 -0
  80. ommlds/cli/sessions/chat/chat/user/types.py +15 -0
  81. ommlds/cli/sessions/chat/configs.py +27 -0
  82. ommlds/cli/sessions/chat/driver.py +43 -0
  83. ommlds/cli/sessions/chat/inject.py +33 -65
  84. ommlds/cli/sessions/chat/phases/__init__.py +0 -0
  85. ommlds/cli/sessions/chat/phases/inject.py +27 -0
  86. ommlds/cli/sessions/chat/phases/injection.py +14 -0
  87. ommlds/cli/sessions/chat/phases/manager.py +29 -0
  88. ommlds/cli/sessions/chat/phases/types.py +29 -0
  89. ommlds/cli/sessions/chat/session.py +27 -0
  90. ommlds/cli/sessions/chat/tools/__init__.py +0 -0
  91. ommlds/cli/sessions/chat/tools/configs.py +22 -0
  92. ommlds/cli/sessions/chat/tools/confirmation.py +46 -0
  93. ommlds/cli/sessions/chat/tools/execution.py +66 -0
  94. ommlds/cli/sessions/chat/tools/fs/__init__.py +0 -0
  95. ommlds/cli/sessions/chat/tools/fs/configs.py +12 -0
  96. ommlds/cli/sessions/chat/tools/fs/inject.py +35 -0
  97. ommlds/cli/sessions/chat/tools/inject.py +88 -0
  98. ommlds/cli/sessions/chat/tools/injection.py +44 -0
  99. ommlds/cli/sessions/chat/tools/rendering.py +58 -0
  100. ommlds/cli/sessions/chat/tools/todo/__init__.py +0 -0
  101. ommlds/cli/sessions/chat/tools/todo/configs.py +12 -0
  102. ommlds/cli/sessions/chat/tools/todo/inject.py +31 -0
  103. ommlds/cli/sessions/chat/tools/weather/__init__.py +0 -0
  104. ommlds/cli/sessions/chat/tools/weather/configs.py +12 -0
  105. ommlds/cli/sessions/chat/tools/weather/inject.py +22 -0
  106. ommlds/cli/{tools/weather.py → sessions/chat/tools/weather/tools.py} +1 -1
  107. ommlds/cli/sessions/completion/configs.py +21 -0
  108. ommlds/cli/sessions/completion/inject.py +42 -0
  109. ommlds/cli/sessions/completion/session.py +35 -0
  110. ommlds/cli/sessions/embedding/configs.py +21 -0
  111. ommlds/cli/sessions/embedding/inject.py +42 -0
  112. ommlds/cli/sessions/embedding/session.py +33 -0
  113. ommlds/cli/sessions/inject.py +28 -11
  114. ommlds/cli/state/__init__.py +0 -0
  115. ommlds/cli/state/inject.py +28 -0
  116. ommlds/cli/{state.py → state/storage.py} +41 -24
  117. ommlds/minichain/__init__.py +46 -17
  118. ommlds/minichain/_dataclasses.py +15401 -0
  119. ommlds/minichain/backends/catalogs/base.py +20 -1
  120. ommlds/minichain/backends/catalogs/simple.py +2 -2
  121. ommlds/minichain/backends/catalogs/strings.py +10 -8
  122. ommlds/minichain/backends/impls/anthropic/chat.py +31 -65
  123. ommlds/minichain/backends/impls/anthropic/names.py +3 -4
  124. ommlds/minichain/backends/impls/anthropic/protocol.py +109 -0
  125. ommlds/minichain/backends/impls/anthropic/stream.py +53 -31
  126. ommlds/minichain/backends/impls/duckduckgo/search.py +5 -1
  127. ommlds/minichain/backends/impls/dummy/__init__.py +0 -0
  128. ommlds/minichain/backends/impls/dummy/chat.py +69 -0
  129. ommlds/minichain/backends/impls/google/chat.py +9 -2
  130. ommlds/minichain/backends/impls/google/search.py +6 -1
  131. ommlds/minichain/backends/impls/google/stream.py +122 -32
  132. ommlds/minichain/backends/impls/groq/__init__.py +0 -0
  133. ommlds/minichain/backends/impls/groq/chat.py +75 -0
  134. ommlds/minichain/backends/impls/groq/names.py +48 -0
  135. ommlds/minichain/backends/impls/groq/protocol.py +143 -0
  136. ommlds/minichain/backends/impls/groq/stream.py +125 -0
  137. ommlds/minichain/backends/impls/huggingface/repos.py +1 -5
  138. ommlds/minichain/backends/impls/llamacpp/chat.py +15 -3
  139. ommlds/minichain/backends/impls/llamacpp/completion.py +7 -3
  140. ommlds/minichain/backends/impls/llamacpp/stream.py +38 -19
  141. ommlds/minichain/backends/impls/mistral.py +9 -2
  142. ommlds/minichain/backends/impls/mlx/chat.py +100 -23
  143. ommlds/minichain/backends/impls/ollama/__init__.py +0 -0
  144. ommlds/minichain/backends/impls/ollama/chat.py +199 -0
  145. ommlds/minichain/backends/impls/openai/chat.py +14 -7
  146. ommlds/minichain/backends/impls/openai/completion.py +9 -2
  147. ommlds/minichain/backends/impls/openai/embedding.py +9 -2
  148. ommlds/minichain/backends/impls/openai/format.py +115 -109
  149. ommlds/minichain/backends/impls/openai/names.py +31 -5
  150. ommlds/minichain/backends/impls/openai/stream.py +33 -27
  151. ommlds/minichain/backends/impls/sentencepiece/tokens.py +9 -6
  152. ommlds/minichain/backends/impls/tavily.py +66 -0
  153. ommlds/minichain/backends/impls/tinygrad/chat.py +17 -14
  154. ommlds/minichain/backends/impls/tokenizers/tokens.py +9 -6
  155. ommlds/minichain/backends/impls/transformers/sentence.py +5 -2
  156. ommlds/minichain/backends/impls/transformers/tokens.py +10 -7
  157. ommlds/minichain/backends/impls/transformers/transformers.py +139 -20
  158. ommlds/minichain/backends/strings/parsing.py +1 -1
  159. ommlds/minichain/backends/strings/resolving.py +4 -1
  160. ommlds/minichain/chat/choices/stream/__init__.py +0 -0
  161. ommlds/minichain/chat/choices/stream/adapters.py +35 -0
  162. ommlds/minichain/chat/choices/stream/joining.py +31 -0
  163. ommlds/minichain/chat/choices/stream/services.py +45 -0
  164. ommlds/minichain/chat/choices/stream/types.py +43 -0
  165. ommlds/minichain/chat/stream/_marshal.py +4 -4
  166. ommlds/minichain/chat/stream/joining.py +85 -0
  167. ommlds/minichain/chat/stream/services.py +15 -15
  168. ommlds/minichain/chat/stream/types.py +24 -18
  169. ommlds/minichain/llms/types.py +4 -0
  170. ommlds/minichain/registries/globals.py +18 -4
  171. ommlds/minichain/resources.py +28 -3
  172. ommlds/minichain/search.py +1 -1
  173. ommlds/minichain/standard.py +8 -0
  174. ommlds/minichain/stream/services.py +19 -16
  175. ommlds/minichain/tools/reflect.py +5 -1
  176. ommlds/nanochat/LICENSE +21 -0
  177. ommlds/nanochat/__init__.py +0 -0
  178. ommlds/nanochat/rustbpe/LICENSE +21 -0
  179. ommlds/nanochat/tokenizers.py +406 -0
  180. ommlds/specs/__init__.py +0 -0
  181. ommlds/specs/mcp/__init__.py +0 -0
  182. ommlds/specs/mcp/_marshal.py +23 -0
  183. ommlds/specs/mcp/clients.py +146 -0
  184. ommlds/specs/mcp/protocol.py +371 -0
  185. ommlds/tools/git.py +13 -6
  186. ommlds/tools/ocr.py +1 -8
  187. ommlds/wiki/analyze.py +2 -2
  188. ommlds/wiki/text/mfh.py +1 -5
  189. ommlds/wiki/text/wtp.py +1 -3
  190. ommlds/wiki/utils/xml.py +5 -5
  191. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/METADATA +22 -19
  192. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/RECORD +198 -95
  193. ommlds/cli/backends/standard.py +0 -20
  194. ommlds/cli/sessions/chat/base.py +0 -42
  195. ommlds/cli/sessions/chat/code.py +0 -129
  196. ommlds/cli/sessions/chat/interactive.py +0 -71
  197. ommlds/cli/sessions/chat/printing.py +0 -97
  198. ommlds/cli/sessions/chat/prompt.py +0 -151
  199. ommlds/cli/sessions/chat/state.py +0 -110
  200. ommlds/cli/sessions/chat/tools.py +0 -100
  201. ommlds/cli/sessions/completion/completion.py +0 -44
  202. ommlds/cli/sessions/embedding/embedding.py +0 -42
  203. ommlds/cli/tools/config.py +0 -14
  204. ommlds/cli/tools/inject.py +0 -75
  205. ommlds/minichain/backends/impls/openai/format2.py +0 -210
  206. ommlds/minichain/chat/stream/adapters.py +0 -80
  207. /ommlds/{huggingface.py → backends/huggingface.py} +0 -0
  208. /ommlds/cli/{tools → content}/__init__.py +0 -0
  209. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/WHEEL +0 -0
  210. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/entry_points.txt +0 -0
  211. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/licenses/LICENSE +0 -0
  212. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/top_level.txt +0 -0
@@ -22,9 +22,15 @@ 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
 
@@ -34,13 +40,14 @@ class OpenaiEmbeddingService:
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,31 +2,33 @@ 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
- from ....chat.messages import AiChat
13
13
  from ....chat.messages import AiMessage
14
14
  from ....chat.messages import AnyAiMessage
15
15
  from ....chat.messages import Chat
16
- from ....chat.messages import Message
17
16
  from ....chat.messages import SystemMessage
18
17
  from ....chat.messages import ToolUseMessage
19
18
  from ....chat.messages import ToolUseResultMessage
20
19
  from ....chat.messages import UserMessage
21
- from ....chat.stream.types import ContentAiChoiceDelta
20
+ from ....chat.stream.types import AiDelta
21
+ from ....chat.stream.types import ContentAiDelta
22
+ from ....chat.stream.types import PartialToolUseAiDelta
22
23
  from ....chat.tools.types import Tool
23
24
  from ....content.json import JsonContent
24
25
  from ....content.prepare import prepare_content_str
26
+ from ....llms.types import MaxCompletionTokens
25
27
  from ....llms.types import MaxTokens
26
28
  from ....llms.types import Temperature
27
29
  from ....llms.types import TokenUsage
28
30
  from ....llms.types import TokenUsageOutput
29
- from ....tools.jsonschema import build_tool_spec_json_schema
31
+ from ....tools.jsonschema import build_tool_spec_params_json_schema
30
32
  from ....tools.types import ToolSpec
31
33
  from ....tools.types import ToolUse
32
34
  from ....types import Option
@@ -35,61 +37,115 @@ from ....types import Option
35
37
  ##
36
38
 
37
39
 
38
- def build_request_messages(chat: Chat) -> ta.Sequence[ta.Mapping[str, ta.Any]]:
39
- out: list[dict[str, ta.Any]] = []
40
+ def build_oai_request_msgs(mc_chat: Chat) -> ta.Sequence[pt.ChatCompletionMessage]:
41
+ oai_msgs: list[pt.ChatCompletionMessage] = []
40
42
 
41
- for m in chat:
42
- if isinstance(m, SystemMessage):
43
- out.append(dict(
44
- role='system',
45
- content=m.c,
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),
46
47
  ))
47
48
 
48
- elif isinstance(m, AiMessage):
49
- out.append(dict(
50
- role='assistant',
51
- content=check.isinstance(m.c, (str, None)),
49
+ elif isinstance(mc_msg, AiMessage):
50
+ oai_msgs.append(pt.AssistantChatCompletionMessage(
51
+ content=check.isinstance(mc_msg.c, (str, None)),
52
52
  ))
53
53
 
54
- elif isinstance(m, ToolUseMessage):
55
- out.append(dict(
56
- role='assistant',
57
- tool_calls=[
58
- dict(
59
- id=m.tu.id,
60
- function=dict(
61
- arguments=check.not_none(m.tu.raw_args),
62
- name=m.tu.name,
63
- ),
64
- type='function',
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,
65
61
  ),
66
- ],
62
+ )],
67
63
  ))
68
64
 
69
- elif isinstance(m, UserMessage):
70
- out.append(dict(
71
- role='user',
72
- content=prepare_content_str(m.c),
65
+ elif isinstance(mc_msg, UserMessage):
66
+ oai_msgs.append(pt.UserChatCompletionMessage(
67
+ content=prepare_content_str(mc_msg.c),
73
68
  ))
74
69
 
75
- elif isinstance(m, ToolUseResultMessage):
70
+ elif isinstance(mc_msg, ToolUseResultMessage):
76
71
  tc: str
77
- if isinstance(m.tur.c, str):
78
- tc = m.tur.c
79
- elif isinstance(m.tur.c, JsonContent):
80
- tc = json.dumps_compact(m.tur.c)
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)
81
76
  else:
82
- raise TypeError(m.tur.c)
83
- out.append(dict(
84
- role='tool',
85
- tool_call_id=m.tur.id,
77
+ raise TypeError(mc_msg.tur.c)
78
+ oai_msgs.append(pt.ToolChatCompletionMessage(
79
+ tool_call_id=check.not_none(mc_msg.tur.id),
86
80
  content=tc,
87
81
  ))
88
82
 
89
83
  else:
90
- raise TypeError(m)
84
+ raise TypeError(mc_msg)
91
85
 
92
- return out
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)
109
+
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,
145
+ )
146
+
147
+ else:
148
+ raise ValueError(delta)
93
149
 
94
150
 
95
151
  ##
@@ -110,22 +166,15 @@ class OpenaiChatRequestHandler:
110
166
  self._model = model
111
167
  self._mandatory_kwargs = mandatory_kwargs
112
168
 
113
- ROLES_MAP: ta.ClassVar[ta.Mapping[type[Message], str]] = {
114
- SystemMessage: 'system',
115
- UserMessage: 'user',
116
- AiMessage: 'assistant',
117
- ToolUseMessage: 'assistant',
118
- ToolUseResultMessage: 'tool',
119
- }
120
-
121
169
  DEFAULT_OPTIONS: ta.ClassVar[tv.TypedValues[Option]] = tv.TypedValues[Option](
122
- Temperature(0.),
123
- MaxTokens(1024),
170
+ # Temperature(0.),
171
+ # MaxTokens(1024),
124
172
  )
125
173
 
126
174
  _OPTION_KWARG_NAMES_MAP: ta.ClassVar[ta.Mapping[str, type[ChatChoicesOptions]]] = dict(
127
175
  temperature=Temperature,
128
176
  max_tokens=MaxTokens,
177
+ max_completion_tokens=MaxCompletionTokens,
129
178
  )
130
179
 
131
180
  class _ProcessedOptions(ta.NamedTuple):
@@ -135,8 +184,8 @@ class OpenaiChatRequestHandler:
135
184
  @cached.function
136
185
  def _process_options(self) -> _ProcessedOptions:
137
186
  kwargs: dict = dict(
138
- temperature=0,
139
- max_tokens=1024,
187
+ # temperature=0,
188
+ # max_tokens=1024,
140
189
  )
141
190
 
142
191
  tools_by_name: dict[str, ToolSpec] = {}
@@ -160,69 +209,26 @@ class OpenaiChatRequestHandler:
160
209
  )
161
210
 
162
211
  @cached.function
163
- def raw_request(self) -> ta.Mapping[str, ta.Any]:
212
+ def oai_request(self) -> pt.ChatCompletionRequest:
164
213
  po = self._process_options()
165
214
 
166
- tools = [
167
- dict(
168
- type='function',
169
- 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
+ ),
170
222
  )
171
223
  for ts in po.tools_by_name.values()
172
224
  ]
173
225
 
174
- return dict(
226
+ return pt.ChatCompletionRequest(
175
227
  model=self._model,
176
- messages=build_request_messages(self._chat),
228
+ messages=build_oai_request_msgs(self._chat),
177
229
  top_p=1,
178
- **lang.opt_kw(tools=tools),
230
+ tools=tools or None,
179
231
  frequency_penalty=0.0,
180
232
  presence_penalty=0.0,
181
233
  **po.kwargs,
182
234
  )
183
-
184
- def build_ai_chat(self, message: ta.Mapping[str, ta.Any]) -> AiChat:
185
- out: list[AnyAiMessage] = []
186
- if (c := message.get('content')) is not None:
187
- out.append(AiMessage(c))
188
- for tc in message.get('tool_calls', []):
189
- out.append(ToolUseMessage(
190
- ToolUse(
191
- id=tc['id'],
192
- name=tc['function']['name'],
193
- args=json.loads(tc['function']['arguments'] or '{}'),
194
- raw_args=tc['function']['arguments'],
195
- ),
196
- ))
197
- return out
198
-
199
- def build_response(self, raw_response: ta.Mapping[str, ta.Any]) -> ChatChoicesResponse:
200
- return ChatChoicesResponse(
201
- [
202
- AiChoice(self.build_ai_chat(choice['message']))
203
- for choice in raw_response['choices']
204
- ],
205
-
206
- tv.TypedValues(
207
- *([TokenUsageOutput(TokenUsage(
208
- input=tu['prompt_tokens'],
209
- output=tu['completion_tokens'],
210
- total=tu['total_tokens'],
211
- ))] if (tu := raw_response.get('usage')) is not None else []),
212
- ),
213
- )
214
-
215
- def build_ai_choice_delta(self, delta: ta.Mapping[str, ta.Any]) -> ContentAiChoiceDelta:
216
- return ContentAiChoiceDelta(
217
- check.not_none(delta.get('content')),
218
- # FIXME:
219
- # tool_exec_requests=[
220
- # ToolUse(
221
- # id=tc['id'],
222
- # spec=self._process_options().tools_by_name[tc['function']['name']],
223
- # args=json.loads(tc['function']['arguments'] or '{}'),
224
- # raw_args=tc['function']['arguments'],
225
- # )
226
- # for tc in message_or_delta.get('tool_calls', [])
227
- # ] or None,
228
- )
@@ -30,10 +30,12 @@ _GPT_MODEL_NAMES = [
30
30
  'gpt-5-chat-latest',
31
31
  'gpt-5-mini',
32
32
  'gpt-5-nano',
33
+
34
+ 'gpt-5.1',
33
35
  ]
34
36
 
35
37
 
36
- MODEL_NAMES = ModelNameCollection(
38
+ CHAT_MODEL_NAMES = ModelNameCollection(
37
39
  default='gpt',
38
40
  aliases={
39
41
  **{
@@ -46,8 +48,8 @@ MODEL_NAMES = ModelNameCollection(
46
48
  for n in _GPT_MODEL_NAMES
47
49
  },
48
50
 
49
- 'gpt': 'gpt-4o',
50
- 'gpt-mini': 'gpt-4o-mini',
51
+ 'gpt': 'gpt-5.1',
52
+ 'gpt-mini': 'gpt-5-mini',
51
53
 
52
54
  #
53
55
 
@@ -61,11 +63,35 @@ MODEL_NAMES = ModelNameCollection(
61
63
 
62
64
 
63
65
  # @omlish-manifest
64
- _BACKEND_STRINGS_MANIFEST = BackendStringsManifest(
66
+ _CHAT_BACKEND_STRINGS_MANIFEST = BackendStringsManifest(
65
67
  [
66
68
  'ChatChoicesService',
67
69
  'ChatChoicesStreamService',
68
70
  ],
69
71
  'openai',
70
- model_names=MODEL_NAMES,
72
+ model_names=CHAT_MODEL_NAMES,
73
+ )
74
+
75
+
76
+ ##
77
+
78
+
79
+ # @omlish-manifest
80
+ _COMPLETION_BACKEND_STRINGS_MANIFEST = BackendStringsManifest(
81
+ [
82
+ 'CompletionService',
83
+ ],
84
+ 'openai',
85
+ )
86
+
87
+
88
+ ##
89
+
90
+
91
+ # @omlish-manifest
92
+ _EMBEDDING_BACKEND_STRINGS_MANIFEST = BackendStringsManifest(
93
+ [
94
+ 'EmbeddingService',
95
+ ],
96
+ 'openai',
71
97
  )
@@ -11,14 +11,14 @@ from omlish.http import all as http
11
11
  from omlish.http import sse
12
12
  from omlish.io.buffers import DelimitingBuffer
13
13
 
14
- from .....backends.openai.protocol.chatcompletion.chunk import ChatCompletionChunk
14
+ from .....backends.openai import protocol as pt
15
15
  from ....chat.choices.services import ChatChoicesOutputs
16
- from ....chat.stream.services import ChatChoicesStreamRequest
17
- from ....chat.stream.services import ChatChoicesStreamResponse
18
- from ....chat.stream.services import static_check_is_chat_choices_stream_service
19
- from ....chat.stream.types import AiChoiceDeltas
20
- from ....chat.stream.types import AiChoicesDeltas
21
- from ....chat.stream.types import ChatChoicesStreamOption
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.stream.types import ChatChoicesStreamOption
22
22
  from ....configs import Config
23
23
  from ....resources import ResourcesOption
24
24
  from ....resources import UseResources
@@ -28,7 +28,8 @@ from ....stream.services import StreamResponseSink
28
28
  from ....stream.services import new_stream_response
29
29
  from .chat import OpenaiChatChoicesService
30
30
  from .format import OpenaiChatRequestHandler
31
- from .names import MODEL_NAMES
31
+ from .format import build_mc_ai_delta
32
+ from .names import CHAT_MODEL_NAMES
32
33
 
33
34
 
34
35
  ##
@@ -40,14 +41,20 @@ from .names import MODEL_NAMES
40
41
  # )
41
42
  @static_check_is_chat_choices_stream_service
42
43
  class OpenaiChatChoicesStreamService:
43
- def __init__(self, *configs: Config) -> None:
44
+ def __init__(
45
+ self,
46
+ *configs: Config,
47
+ http_client: http.AsyncHttpClient | None = None,
48
+ ) -> None:
44
49
  super().__init__()
45
50
 
51
+ self._http_client = http_client
52
+
46
53
  with tv.consume(*configs) as cc:
47
54
  self._model_name = cc.pop(OpenaiChatChoicesService.DEFAULT_MODEL_NAME)
48
55
  self._api_key = ApiKey.pop_secret(cc, env='OPENAI_API_KEY')
49
56
 
50
- READ_CHUNK_SIZE = 64 * 1024
57
+ READ_CHUNK_SIZE: ta.ClassVar[int] = -1
51
58
 
52
59
  async def invoke(self, request: ChatChoicesStreamRequest) -> ChatChoicesStreamResponse:
53
60
  # check.isinstance(request, ChatRequest)
@@ -59,16 +66,16 @@ class OpenaiChatChoicesStreamService:
59
66
  for o in request.options
60
67
  if not isinstance(o, (ChatChoicesStreamOption, StreamOption, ResourcesOption))
61
68
  ],
62
- model=MODEL_NAMES.resolve(self._model_name.v),
69
+ model=CHAT_MODEL_NAMES.resolve(self._model_name.v),
63
70
  mandatory_kwargs=dict(
64
71
  stream=True,
65
- stream_options=dict(
72
+ stream_options=pt.ChatCompletionRequest.StreamOptions(
66
73
  include_usage=True,
67
74
  ),
68
75
  ),
69
76
  )
70
77
 
71
- raw_request = rh.raw_request()
78
+ raw_request = msh.marshal(rh.oai_request())
72
79
 
73
80
  http_request = http.HttpRequest(
74
81
  'https://api.openai.com/v1/chat/completions',
@@ -80,15 +87,14 @@ class OpenaiChatChoicesStreamService:
80
87
  )
81
88
 
82
89
  async with UseResources.or_new(request.options) as rs:
83
- http_client = rs.enter_context(http.client())
84
- http_response = rs.enter_context(http_client.stream_request(http_request))
90
+ http_client = await rs.enter_async_context(http.manage_async_client(self._http_client))
91
+ http_response = await rs.enter_async_context(await http_client.stream_request(http_request))
85
92
 
86
93
  async def inner(sink: StreamResponseSink[AiChoicesDeltas]) -> ta.Sequence[ChatChoicesOutputs]:
87
94
  db = DelimitingBuffer([b'\r', b'\n', b'\r\n'])
88
95
  sd = sse.SseDecoder()
89
96
  while True:
90
- # FIXME: read1 not on response stream protocol
91
- b = http_response.stream.read1(self.READ_CHUNK_SIZE) # type: ignore[attr-defined]
97
+ b = await http_response.stream.read1(self.READ_CHUNK_SIZE)
92
98
  for l in db.feed(b):
93
99
  if isinstance(l, DelimitingBuffer.Incomplete):
94
100
  # FIXME: handle
@@ -105,20 +111,20 @@ class OpenaiChatChoicesStreamService:
105
111
 
106
112
  check.state(sj['object'] == 'chat.completion.chunk')
107
113
 
108
- ccc = msh.unmarshal(sj, ChatCompletionChunk) # noqa
109
- # print(ccc)
114
+ ccc = msh.unmarshal(sj, pt.ChatCompletionChunk)
110
115
 
111
116
  # FIXME: stop reason
112
- if not sj['choices']:
117
+ if not ccc.choices:
113
118
  continue
114
119
 
115
- if any(choice['delta'] for choice in sj['choices']):
116
- await sink.emit(AiChoicesDeltas([
117
- AiChoiceDeltas(
118
- [rh.build_ai_choice_delta(choice['delta'])] if choice['delta'] else [],
119
- )
120
- for choice in sj['choices']
121
- ]))
120
+ if any(choice.finish_reason for choice in ccc.choices):
121
+ check.state(all(choice.finish_reason for choice in ccc.choices))
122
+ break
123
+
124
+ await sink.emit(AiChoicesDeltas([
125
+ AiChoiceDeltas([build_mc_ai_delta(choice.delta)])
126
+ for choice in ccc.choices
127
+ ]))
122
128
 
123
129
  if not b:
124
130
  return []
@@ -1,23 +1,26 @@
1
1
  import typing as ta
2
2
 
3
- import sentencepiece as spm
4
-
5
3
  from omlish import check
4
+ from omlish import lang
6
5
 
7
6
  from .... import tokens as tks
8
7
 
9
8
 
9
+ with lang.auto_proxy_import(globals()):
10
+ import sentencepiece as spm
11
+
12
+
10
13
  ##
11
14
 
12
15
 
13
- def build_vocab(spm_tokenizer: spm.SentencePieceProcessor) -> tks.Vocab:
16
+ def build_vocab(spm_tokenizer: 'spm.SentencePieceProcessor') -> tks.Vocab:
14
17
  return tks.Vocab([
15
18
  (ta.cast(tks.Token, i), tks.TokenStr(spm_tokenizer.id_to_piece(i))) # noqa
16
19
  for i in range(spm_tokenizer.get_piece_size()) # noqa
17
20
  ])
18
21
 
19
22
 
20
- def build_specials(spm_tokenizer: spm.SentencePieceProcessor) -> tks.SpecialTokens:
23
+ def build_specials(spm_tokenizer: 'spm.SentencePieceProcessor') -> tks.SpecialTokens:
21
24
  # FIXME
22
25
  return tks.SpecialTokens([])
23
26
 
@@ -28,7 +31,7 @@ def build_specials(spm_tokenizer: spm.SentencePieceProcessor) -> tks.SpecialToke
28
31
  class SentencepieceTokenizer(tks.BaseTokenizer):
29
32
  def __init__(
30
33
  self,
31
- spm_tokenizer: spm.SentencePieceProcessor,
34
+ spm_tokenizer: 'spm.SentencePieceProcessor',
32
35
  ) -> None:
33
36
  self._spm_tokenizer = check.isinstance(spm_tokenizer, spm.SentencePieceProcessor)
34
37
 
@@ -38,7 +41,7 @@ class SentencepieceTokenizer(tks.BaseTokenizer):
38
41
  )
39
42
 
40
43
  @property
41
- def spm_tokenizer(self) -> spm.SentencePieceProcessor:
44
+ def spm_tokenizer(self) -> 'spm.SentencePieceProcessor':
42
45
  return self._spm_tokenizer
43
46
 
44
47
  #
@@ -0,0 +1,66 @@
1
+ from omlish import check
2
+ from omlish import marshal as msh
3
+ from omlish import typedvalues as tv
4
+ from omlish.formats import json
5
+ from omlish.http import all as http
6
+
7
+ from ....backends.tavily import protocol as pt
8
+ from ...search import SearchHit
9
+ from ...search import SearchHits
10
+ from ...search import SearchRequest
11
+ from ...search import SearchResponse
12
+ from ...search import static_check_is_search_service
13
+ from ...standard import ApiKey
14
+
15
+
16
+ ##
17
+
18
+
19
+ # @omlish-manifest $.minichain.registries.manifests.RegistryManifest(
20
+ # name='tavily',
21
+ # type='SearchService',
22
+ # )
23
+ @static_check_is_search_service
24
+ class TavilySearchService:
25
+ def __init__(
26
+ self,
27
+ *configs: ApiKey,
28
+ http_client: http.AsyncHttpClient | None = None,
29
+ ) -> None:
30
+ super().__init__()
31
+
32
+ self._http_client = http_client
33
+
34
+ with tv.consume(*configs) as cc:
35
+ self._api_key = ApiKey.pop_secret(cc, env='TAVILY_API_KEY')
36
+
37
+ async def invoke(self, request: SearchRequest) -> SearchResponse:
38
+ pt_request = pt.SearchRequest(
39
+ query=request.v,
40
+ )
41
+
42
+ raw_request = msh.marshal(pt_request)
43
+
44
+ http_response = await http.async_request(
45
+ 'https://api.tavily.com/search',
46
+ headers={
47
+ http.consts.HEADER_CONTENT_TYPE: http.consts.CONTENT_TYPE_JSON,
48
+ http.consts.HEADER_AUTH: http.consts.format_bearer_auth_header(check.not_none(self._api_key).reveal()),
49
+ },
50
+ data=json.dumps(raw_request).encode('utf-8'),
51
+ client=self._http_client,
52
+ )
53
+
54
+ raw_response = json.loads(check.not_none(http_response.data).decode('utf-8'))
55
+
56
+ pt_response = msh.unmarshal(raw_response, pt.SearchResponse)
57
+
58
+ return SearchResponse(SearchHits(
59
+ l=[
60
+ SearchHit(
61
+ title=r.title,
62
+ url=r.url,
63
+ )
64
+ for r in pt_response.results or []
65
+ ],
66
+ ))