ommlds 0.0.0.dev465__py3-none-any.whl → 0.0.0.dev467__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.
Potentially problematic release.
This version of ommlds might be problematic. Click here for more details.
- ommlds/.omlish-manifests.json +22 -6
- ommlds/backends/google/protocol/types.py +4 -1
- ommlds/cli/sessions/chat/backends/catalog.py +1 -1
- ommlds/cli/sessions/chat/chat/ai/services.py +5 -7
- ommlds/minichain/__init__.py +6 -0
- ommlds/minichain/backends/impls/anthropic/stream.py +17 -0
- ommlds/minichain/backends/impls/google/stream.py +105 -20
- ommlds/minichain/backends/impls/mlx/chat.py +95 -21
- ommlds/minichain/backends/impls/openai/chat.py +2 -2
- ommlds/minichain/backends/impls/openai/format.py +108 -104
- ommlds/minichain/backends/impls/openai/stream.py +14 -13
- ommlds/minichain/chat/stream/adapters.py +5 -50
- ommlds/minichain/chat/stream/joining.py +96 -0
- ommlds/minichain/chat/stream/types.py +20 -4
- {ommlds-0.0.0.dev465.dist-info → ommlds-0.0.0.dev467.dist-info}/METADATA +3 -3
- {ommlds-0.0.0.dev465.dist-info → ommlds-0.0.0.dev467.dist-info}/RECORD +20 -20
- ommlds/minichain/backends/impls/openai/format2.py +0 -210
- {ommlds-0.0.0.dev465.dist-info → ommlds-0.0.0.dev467.dist-info}/WHEEL +0 -0
- {ommlds-0.0.0.dev465.dist-info → ommlds-0.0.0.dev467.dist-info}/entry_points.txt +0 -0
- {ommlds-0.0.0.dev465.dist-info → ommlds-0.0.0.dev467.dist-info}/licenses/LICENSE +0 -0
- {ommlds-0.0.0.dev465.dist-info → ommlds-0.0.0.dev467.dist-info}/top_level.txt +0 -0
|
@@ -2,23 +2,24 @@ 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
|
|
20
|
+
from ....chat.stream.types import AiChoiceDelta
|
|
21
21
|
from ....chat.stream.types import ContentAiChoiceDelta
|
|
22
|
+
from ....chat.stream.types import PartialToolUseAiChoiceDelta
|
|
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,7 +27,7 @@ from ....llms.types import MaxTokens
|
|
|
26
27
|
from ....llms.types import Temperature
|
|
27
28
|
from ....llms.types import TokenUsage
|
|
28
29
|
from ....llms.types import TokenUsageOutput
|
|
29
|
-
from ....tools.jsonschema import
|
|
30
|
+
from ....tools.jsonschema import build_tool_spec_params_json_schema
|
|
30
31
|
from ....tools.types import ToolSpec
|
|
31
32
|
from ....tools.types import ToolUse
|
|
32
33
|
from ....types import Option
|
|
@@ -35,61 +36,115 @@ from ....types import Option
|
|
|
35
36
|
##
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
def
|
|
39
|
-
|
|
39
|
+
def build_oai_request_msgs(mc_chat: Chat) -> ta.Sequence[pt.ChatCompletionMessage]:
|
|
40
|
+
oai_msgs: list[pt.ChatCompletionMessage] = []
|
|
40
41
|
|
|
41
|
-
for
|
|
42
|
-
if isinstance(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
content=m.c,
|
|
42
|
+
for mc_msg in mc_chat:
|
|
43
|
+
if isinstance(mc_msg, SystemMessage):
|
|
44
|
+
oai_msgs.append(pt.SystemChatCompletionMessage(
|
|
45
|
+
content=check.isinstance(mc_msg.c, str),
|
|
46
46
|
))
|
|
47
47
|
|
|
48
|
-
elif isinstance(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
content=check.isinstance(m.c, (str, None)),
|
|
48
|
+
elif isinstance(mc_msg, AiMessage):
|
|
49
|
+
oai_msgs.append(pt.AssistantChatCompletionMessage(
|
|
50
|
+
content=check.isinstance(mc_msg.c, (str, None)),
|
|
52
51
|
))
|
|
53
52
|
|
|
54
|
-
elif isinstance(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
arguments=check.not_none(m.tu.raw_args),
|
|
62
|
-
name=m.tu.name,
|
|
63
|
-
),
|
|
64
|
-
type='function',
|
|
53
|
+
elif isinstance(mc_msg, ToolUseMessage):
|
|
54
|
+
oai_msgs.append(pt.AssistantChatCompletionMessage(
|
|
55
|
+
tool_calls=[pt.AssistantChatCompletionMessage.ToolCall(
|
|
56
|
+
id=check.not_none(mc_msg.tu.id),
|
|
57
|
+
function=pt.AssistantChatCompletionMessage.ToolCall.Function(
|
|
58
|
+
arguments=check.not_none(mc_msg.tu.raw_args),
|
|
59
|
+
name=mc_msg.tu.name,
|
|
65
60
|
),
|
|
66
|
-
],
|
|
61
|
+
)],
|
|
67
62
|
))
|
|
68
63
|
|
|
69
|
-
elif isinstance(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
content=prepare_content_str(m.c),
|
|
64
|
+
elif isinstance(mc_msg, UserMessage):
|
|
65
|
+
oai_msgs.append(pt.UserChatCompletionMessage(
|
|
66
|
+
content=prepare_content_str(mc_msg.c),
|
|
73
67
|
))
|
|
74
68
|
|
|
75
|
-
elif isinstance(
|
|
69
|
+
elif isinstance(mc_msg, ToolUseResultMessage):
|
|
76
70
|
tc: str
|
|
77
|
-
if isinstance(
|
|
78
|
-
tc =
|
|
79
|
-
elif isinstance(
|
|
80
|
-
tc = json.dumps_compact(
|
|
71
|
+
if isinstance(mc_msg.tur.c, str):
|
|
72
|
+
tc = mc_msg.tur.c
|
|
73
|
+
elif isinstance(mc_msg.tur.c, JsonContent):
|
|
74
|
+
tc = json.dumps_compact(mc_msg.tur.c)
|
|
81
75
|
else:
|
|
82
|
-
raise TypeError(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
tool_call_id=m.tur.id,
|
|
76
|
+
raise TypeError(mc_msg.tur.c)
|
|
77
|
+
oai_msgs.append(pt.ToolChatCompletionMessage(
|
|
78
|
+
tool_call_id=check.not_none(mc_msg.tur.id),
|
|
86
79
|
content=tc,
|
|
87
80
|
))
|
|
88
81
|
|
|
89
82
|
else:
|
|
90
|
-
raise TypeError(
|
|
83
|
+
raise TypeError(mc_msg)
|
|
91
84
|
|
|
92
|
-
return
|
|
85
|
+
return oai_msgs
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
#
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def build_mc_ai_choice(oai_choice: pt.ChatCompletionResponseChoice) -> AiChoice:
|
|
92
|
+
cur: list[AnyAiMessage] = []
|
|
93
|
+
|
|
94
|
+
oai_msg = oai_choice.message
|
|
95
|
+
|
|
96
|
+
if (oai_c := oai_msg.content) is not None:
|
|
97
|
+
cur.append(AiMessage(check.isinstance(oai_c, str)))
|
|
98
|
+
|
|
99
|
+
for oai_tc in oai_msg.tool_calls or []:
|
|
100
|
+
cur.append(ToolUseMessage(ToolUse(
|
|
101
|
+
id=oai_tc.id,
|
|
102
|
+
name=oai_tc.function.name,
|
|
103
|
+
args=json.loads(oai_tc.function.arguments or '{}'),
|
|
104
|
+
raw_args=oai_tc.function.arguments,
|
|
105
|
+
)))
|
|
106
|
+
|
|
107
|
+
return AiChoice(cur)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def build_mc_ai_choices(oai_resp: pt.ChatCompletionResponse) -> AiChoices:
|
|
111
|
+
return [
|
|
112
|
+
build_mc_ai_choice(oai_choice)
|
|
113
|
+
for oai_choice in oai_resp.choices
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def build_mc_choices_response(oai_resp: pt.ChatCompletionResponse) -> ChatChoicesResponse:
|
|
118
|
+
return ChatChoicesResponse(
|
|
119
|
+
build_mc_ai_choices(oai_resp),
|
|
120
|
+
|
|
121
|
+
tv.TypedValues(
|
|
122
|
+
*([TokenUsageOutput(TokenUsage(
|
|
123
|
+
input=tu.prompt_tokens,
|
|
124
|
+
output=tu.completion_tokens,
|
|
125
|
+
total=tu.total_tokens,
|
|
126
|
+
))] if (tu := oai_resp.usage) is not None else []),
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def build_mc_ai_choice_delta(delta: pt.ChatCompletionChunkChoiceDelta) -> AiChoiceDelta:
|
|
132
|
+
if delta.content is not None:
|
|
133
|
+
check.state(not delta.tool_calls)
|
|
134
|
+
return ContentAiChoiceDelta(delta.content)
|
|
135
|
+
|
|
136
|
+
elif delta.tool_calls is not None:
|
|
137
|
+
check.state(delta.content is None)
|
|
138
|
+
tc = check.single(delta.tool_calls)
|
|
139
|
+
tc_fn = check.not_none(tc.function)
|
|
140
|
+
return PartialToolUseAiChoiceDelta(
|
|
141
|
+
id=tc.id,
|
|
142
|
+
name=tc_fn.name,
|
|
143
|
+
raw_args=tc_fn.arguments,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
else:
|
|
147
|
+
raise ValueError(delta)
|
|
93
148
|
|
|
94
149
|
|
|
95
150
|
##
|
|
@@ -110,14 +165,6 @@ class OpenaiChatRequestHandler:
|
|
|
110
165
|
self._model = model
|
|
111
166
|
self._mandatory_kwargs = mandatory_kwargs
|
|
112
167
|
|
|
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
168
|
DEFAULT_OPTIONS: ta.ClassVar[tv.TypedValues[Option]] = tv.TypedValues[Option](
|
|
122
169
|
Temperature(0.),
|
|
123
170
|
MaxTokens(1024),
|
|
@@ -160,69 +207,26 @@ class OpenaiChatRequestHandler:
|
|
|
160
207
|
)
|
|
161
208
|
|
|
162
209
|
@cached.function
|
|
163
|
-
def
|
|
210
|
+
def oai_request(self) -> pt.ChatCompletionRequest:
|
|
164
211
|
po = self._process_options()
|
|
165
212
|
|
|
166
|
-
tools = [
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
213
|
+
tools: list[pt.ChatCompletionRequestTool] = [
|
|
214
|
+
pt.ChatCompletionRequestTool(
|
|
215
|
+
function=pt.ChatCompletionRequestTool.Function(
|
|
216
|
+
name=check.not_none(ts.name),
|
|
217
|
+
description=prepare_content_str(ts.desc),
|
|
218
|
+
parameters=build_tool_spec_params_json_schema(ts),
|
|
219
|
+
),
|
|
170
220
|
)
|
|
171
221
|
for ts in po.tools_by_name.values()
|
|
172
222
|
]
|
|
173
223
|
|
|
174
|
-
return
|
|
224
|
+
return pt.ChatCompletionRequest(
|
|
175
225
|
model=self._model,
|
|
176
|
-
messages=
|
|
226
|
+
messages=build_oai_request_msgs(self._chat),
|
|
177
227
|
top_p=1,
|
|
178
|
-
|
|
228
|
+
tools=tools or None,
|
|
179
229
|
frequency_penalty=0.0,
|
|
180
230
|
presence_penalty=0.0,
|
|
181
231
|
**po.kwargs,
|
|
182
232
|
)
|
|
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
|
-
)
|
|
@@ -11,7 +11,7 @@ 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
|
|
14
|
+
from .....backends.openai import protocol as pt
|
|
15
15
|
from ....chat.choices.services import ChatChoicesOutputs
|
|
16
16
|
from ....chat.stream.services import ChatChoicesStreamRequest
|
|
17
17
|
from ....chat.stream.services import ChatChoicesStreamResponse
|
|
@@ -28,6 +28,7 @@ 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 .format import build_mc_ai_choice_delta
|
|
31
32
|
from .names import MODEL_NAMES
|
|
32
33
|
|
|
33
34
|
|
|
@@ -62,13 +63,13 @@ class OpenaiChatChoicesStreamService:
|
|
|
62
63
|
model=MODEL_NAMES.resolve(self._model_name.v),
|
|
63
64
|
mandatory_kwargs=dict(
|
|
64
65
|
stream=True,
|
|
65
|
-
stream_options=
|
|
66
|
+
stream_options=pt.ChatCompletionRequest.StreamOptions(
|
|
66
67
|
include_usage=True,
|
|
67
68
|
),
|
|
68
69
|
),
|
|
69
70
|
)
|
|
70
71
|
|
|
71
|
-
raw_request = rh.
|
|
72
|
+
raw_request = msh.marshal(rh.oai_request())
|
|
72
73
|
|
|
73
74
|
http_request = http.HttpRequest(
|
|
74
75
|
'https://api.openai.com/v1/chat/completions',
|
|
@@ -105,20 +106,20 @@ class OpenaiChatChoicesStreamService:
|
|
|
105
106
|
|
|
106
107
|
check.state(sj['object'] == 'chat.completion.chunk')
|
|
107
108
|
|
|
108
|
-
ccc = msh.unmarshal(sj, ChatCompletionChunk)
|
|
109
|
-
# print(ccc)
|
|
109
|
+
ccc = msh.unmarshal(sj, pt.ChatCompletionChunk)
|
|
110
110
|
|
|
111
111
|
# FIXME: stop reason
|
|
112
|
-
if not
|
|
112
|
+
if not ccc.choices:
|
|
113
113
|
continue
|
|
114
114
|
|
|
115
|
-
if any(choice
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
if any(choice.finish_reason for choice in ccc.choices):
|
|
116
|
+
check.state(all(choice.finish_reason for choice in ccc.choices))
|
|
117
|
+
break
|
|
118
|
+
|
|
119
|
+
await sink.emit(AiChoicesDeltas([
|
|
120
|
+
AiChoiceDeltas([build_mc_ai_choice_delta(choice.delta)])
|
|
121
|
+
for choice in ccc.choices
|
|
122
|
+
]))
|
|
122
123
|
|
|
123
124
|
if not b:
|
|
124
125
|
return []
|
|
@@ -1,22 +1,14 @@
|
|
|
1
|
-
from omlish import check
|
|
2
1
|
from omlish import dataclasses as dc
|
|
3
|
-
from omlish import lang
|
|
4
2
|
|
|
5
3
|
from ...services import Response
|
|
6
|
-
from ...tools.types import ToolUse
|
|
7
4
|
from ..choices.services import ChatChoicesRequest
|
|
8
5
|
from ..choices.services import static_check_is_chat_choices_service
|
|
9
6
|
from ..choices.types import AiChoice
|
|
10
7
|
from ..choices.types import AiChoices
|
|
11
|
-
from
|
|
12
|
-
from ..messages import AnyAiMessage
|
|
13
|
-
from ..messages import ToolUseMessage
|
|
8
|
+
from .joining import AiChoiceDeltaJoiner
|
|
14
9
|
from .services import ChatChoicesOutputs
|
|
15
10
|
from .services import ChatChoicesStreamOutputs
|
|
16
11
|
from .services import ChatChoicesStreamService
|
|
17
|
-
from .types import AiChoiceDelta
|
|
18
|
-
from .types import ContentAiChoiceDelta
|
|
19
|
-
from .types import ToolUseAiChoiceDelta
|
|
20
12
|
|
|
21
13
|
|
|
22
14
|
##
|
|
@@ -31,50 +23,13 @@ class ChatChoicesStreamServiceChatChoicesService:
|
|
|
31
23
|
AiChoices,
|
|
32
24
|
ChatChoicesOutputs | ChatChoicesStreamOutputs,
|
|
33
25
|
]:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def add(l: list[list[str] | ToolUse], d: AiChoiceDelta) -> None:
|
|
37
|
-
if isinstance(d, ContentAiChoiceDelta):
|
|
38
|
-
s = check.isinstance(d.c, str)
|
|
39
|
-
if l and isinstance(l[-1], list):
|
|
40
|
-
l[-1].append(s)
|
|
41
|
-
else:
|
|
42
|
-
l.append([s])
|
|
43
|
-
|
|
44
|
-
elif isinstance(d, ToolUseAiChoiceDelta):
|
|
45
|
-
l.append(d.tu)
|
|
46
|
-
|
|
47
|
-
else:
|
|
48
|
-
raise TypeError(d)
|
|
26
|
+
joiner = AiChoiceDeltaJoiner()
|
|
49
27
|
|
|
50
28
|
async with (resp := await self.service.invoke(request)).v as it: # noqa
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
async for i, cs in lang.async_enumerate(it):
|
|
54
|
-
if i == 0:
|
|
55
|
-
for c in cs.choices:
|
|
56
|
-
choice_lsts.append(l := [])
|
|
57
|
-
for d in c.deltas:
|
|
58
|
-
add(l, d)
|
|
59
|
-
|
|
60
|
-
else:
|
|
61
|
-
for l, c in zip(choice_lsts, cs.choices, strict=True):
|
|
62
|
-
for d in c.deltas:
|
|
63
|
-
add(l, d)
|
|
29
|
+
async for cs in it:
|
|
30
|
+
joiner.add(cs.choices)
|
|
64
31
|
|
|
65
32
|
# check.state(resp_v.is_done)
|
|
66
33
|
|
|
67
|
-
ret: list[AiChoice] = []
|
|
68
|
-
for cl in choice_lsts:
|
|
69
|
-
cc: list[AnyAiMessage] = []
|
|
70
|
-
for e in cl:
|
|
71
|
-
if isinstance(e, list):
|
|
72
|
-
cc.append(AiMessage(''.join(e)))
|
|
73
|
-
elif isinstance(e, ToolUse):
|
|
74
|
-
cc.append(ToolUseMessage(e))
|
|
75
|
-
else:
|
|
76
|
-
raise TypeError(e)
|
|
77
|
-
ret.append(AiChoice(cc))
|
|
78
|
-
|
|
79
34
|
# FIXME: outputs lol
|
|
80
|
-
return Response(
|
|
35
|
+
return Response([AiChoice(ms) for ms in joiner.build()])
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
from omlish import check
|
|
4
|
+
from omlish.formats import json
|
|
5
|
+
|
|
6
|
+
from ...tools.types import ToolUse
|
|
7
|
+
from ..messages import AiChat
|
|
8
|
+
from ..messages import AiMessage
|
|
9
|
+
from ..messages import AnyAiMessage
|
|
10
|
+
from ..messages import ToolUseMessage
|
|
11
|
+
from .types import AiChoiceDelta
|
|
12
|
+
from .types import AiChoiceDeltas
|
|
13
|
+
from .types import ContentAiChoiceDelta
|
|
14
|
+
from .types import PartialToolUseAiChoiceDelta
|
|
15
|
+
from .types import ToolUseAiChoiceDelta
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AiChoiceDeltaJoiner:
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
super().__init__()
|
|
24
|
+
|
|
25
|
+
self._seq = 0
|
|
26
|
+
self._channels: list[AiChoiceDeltaJoiner._Channel] = []
|
|
27
|
+
|
|
28
|
+
class _Channel(ta.NamedTuple):
|
|
29
|
+
deltas: list[AiChoiceDelta]
|
|
30
|
+
messages: list[AnyAiMessage]
|
|
31
|
+
|
|
32
|
+
def _build_joined(self, deltas: ta.Sequence[AiChoiceDelta]) -> AnyAiMessage:
|
|
33
|
+
dty = check.single(set(map(type, check.not_empty(deltas))))
|
|
34
|
+
|
|
35
|
+
if dty is ContentAiChoiceDelta:
|
|
36
|
+
cds = ta.cast(ta.Sequence[ContentAiChoiceDelta], deltas)
|
|
37
|
+
return AiMessage(''.join(check.isinstance(cd.c, str) for cd in cds))
|
|
38
|
+
|
|
39
|
+
elif dty is ToolUseAiChoiceDelta:
|
|
40
|
+
raise TypeError(dty)
|
|
41
|
+
|
|
42
|
+
elif dty is PartialToolUseAiChoiceDelta:
|
|
43
|
+
tds = ta.cast(ta.Sequence[PartialToolUseAiChoiceDelta], deltas)
|
|
44
|
+
for td in ta.cast(ta.Sequence[PartialToolUseAiChoiceDelta], deltas)[1:]:
|
|
45
|
+
check.none(td.id)
|
|
46
|
+
check.none(td.name)
|
|
47
|
+
|
|
48
|
+
ra = ''.join(filter(None, (td.raw_args for td in tds)))
|
|
49
|
+
|
|
50
|
+
return ToolUseMessage(ToolUse(
|
|
51
|
+
id=tds[0].id,
|
|
52
|
+
name=check.non_empty_str(tds[0].name),
|
|
53
|
+
args=json.loads(ra),
|
|
54
|
+
raw_args=ra,
|
|
55
|
+
))
|
|
56
|
+
|
|
57
|
+
else:
|
|
58
|
+
raise TypeError(dty)
|
|
59
|
+
|
|
60
|
+
def _join_one(self, chan: _Channel) -> None:
|
|
61
|
+
if not chan.deltas:
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
chan.messages.append(self._build_joined(chan.deltas))
|
|
65
|
+
chan.deltas.clear()
|
|
66
|
+
|
|
67
|
+
def _add_to(self, chan: _Channel, d: AiChoiceDelta) -> None:
|
|
68
|
+
if chan.deltas and type(chan.deltas[0]) is not type(d):
|
|
69
|
+
self._join_one(chan)
|
|
70
|
+
|
|
71
|
+
if isinstance(d, ToolUseAiChoiceDelta):
|
|
72
|
+
chan.messages.append(ToolUseMessage(ToolUse(
|
|
73
|
+
id=d.id,
|
|
74
|
+
name=check.not_none(d.name),
|
|
75
|
+
args=d.args or {},
|
|
76
|
+
)))
|
|
77
|
+
|
|
78
|
+
else:
|
|
79
|
+
chan.deltas.append(d)
|
|
80
|
+
|
|
81
|
+
def add(self, choices: ta.Sequence[AiChoiceDeltas]) -> None:
|
|
82
|
+
if not self._seq:
|
|
83
|
+
check.empty(self._channels)
|
|
84
|
+
self._channels.extend(self._Channel([], []) for _ in range(len(choices)))
|
|
85
|
+
|
|
86
|
+
for chan, c in zip(self._channels, choices, strict=True):
|
|
87
|
+
for d in c.deltas:
|
|
88
|
+
self._add_to(chan, d)
|
|
89
|
+
|
|
90
|
+
self._seq += 1
|
|
91
|
+
|
|
92
|
+
def build(self) -> list[AiChat]:
|
|
93
|
+
for chan in self._channels:
|
|
94
|
+
self._join_one(chan)
|
|
95
|
+
|
|
96
|
+
return [list(chan.messages) for chan in self._channels]
|
|
@@ -6,7 +6,6 @@ from omlish import marshal as msh
|
|
|
6
6
|
|
|
7
7
|
from ...content.types import Content
|
|
8
8
|
from ...stream.services import StreamOptions
|
|
9
|
-
from ...tools.types import ToolUse
|
|
10
9
|
from ...types import Option
|
|
11
10
|
from ...types import Output
|
|
12
11
|
from ..choices.types import ChatChoicesOptions
|
|
@@ -43,14 +42,31 @@ class AiChoiceDelta(lang.Sealed, lang.Abstract):
|
|
|
43
42
|
pass
|
|
44
43
|
|
|
45
44
|
|
|
45
|
+
#
|
|
46
|
+
|
|
47
|
+
|
|
46
48
|
@dc.dataclass(frozen=True)
|
|
47
49
|
class ContentAiChoiceDelta(AiChoiceDelta, lang.Final):
|
|
48
50
|
c: Content
|
|
49
51
|
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
#
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
|
57
|
+
class AnyToolUseAiChoiceDelta(AiChoiceDelta, lang.Abstract):
|
|
58
|
+
id: str | None = None
|
|
59
|
+
name: str | None = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
|
63
|
+
class ToolUseAiChoiceDelta(AnyToolUseAiChoiceDelta, lang.Final):
|
|
64
|
+
args: ta.Mapping[str, ta.Any] | None = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
|
68
|
+
class PartialToolUseAiChoiceDelta(AnyToolUseAiChoiceDelta, lang.Final):
|
|
69
|
+
raw_args: ta.Any | None = None
|
|
54
70
|
|
|
55
71
|
|
|
56
72
|
#
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ommlds
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev467
|
|
4
4
|
Summary: ommlds
|
|
5
5
|
Author: wrmsr
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -14,8 +14,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
14
14
|
Requires-Python: >=3.13
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
|
-
Requires-Dist: omdev==0.0.0.
|
|
18
|
-
Requires-Dist: omlish==0.0.0.
|
|
17
|
+
Requires-Dist: omdev==0.0.0.dev467
|
|
18
|
+
Requires-Dist: omlish==0.0.0.dev467
|
|
19
19
|
Provides-Extra: all
|
|
20
20
|
Requires-Dist: llama-cpp-python~=0.3; extra == "all"
|
|
21
21
|
Requires-Dist: mlx~=0.29; extra == "all"
|