ommlds 0.0.0.dev475__py3-none-any.whl → 0.0.0.dev477__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.
- ommlds/.omlish-manifests.json +85 -1
- ommlds/__about__.py +1 -1
- ommlds/backends/groq/_marshal.py +23 -0
- ommlds/backends/groq/protocol.py +249 -0
- ommlds/cli/{sessions/chat/backends → backends}/catalog.py +35 -3
- ommlds/cli/backends/configs.py +9 -0
- ommlds/cli/backends/inject.py +31 -36
- ommlds/cli/{sessions/chat/backends → backends}/injection.py +1 -1
- ommlds/cli/{sessions/chat/backends → backends}/types.py +11 -1
- ommlds/cli/{sessions/chat/content → content}/messages.py +1 -1
- ommlds/cli/{sessions/chat/content → content}/strings.py +1 -1
- ommlds/cli/inject.py +0 -6
- ommlds/cli/inputs/asyncs.py +32 -0
- ommlds/cli/{sessions/chat/chat/user/inputs.py → inputs/sync.py} +0 -30
- ommlds/cli/main.py +267 -113
- ommlds/cli/rendering/__init__.py +0 -0
- ommlds/cli/rendering/configs.py +9 -0
- ommlds/cli/{sessions/chat/rendering → rendering}/inject.py +4 -5
- ommlds/cli/{sessions/chat/rendering → rendering}/markdown.py +1 -1
- ommlds/cli/{sessions/chat/rendering → rendering}/raw.py +1 -1
- ommlds/cli/{sessions/chat/rendering → rendering}/types.py +1 -1
- ommlds/cli/secrets.py +21 -0
- ommlds/cli/sessions/base.py +1 -1
- ommlds/cli/sessions/chat/chat/ai/configs.py +11 -0
- ommlds/cli/sessions/chat/chat/ai/inject.py +7 -11
- ommlds/cli/sessions/chat/chat/ai/rendering.py +4 -4
- ommlds/cli/sessions/chat/chat/ai/services.py +2 -2
- ommlds/cli/sessions/chat/chat/state/configs.py +11 -0
- ommlds/cli/sessions/chat/chat/state/inject.py +6 -10
- ommlds/cli/sessions/chat/chat/state/inmemory.py +1 -2
- ommlds/cli/sessions/chat/chat/state/storage.py +1 -2
- ommlds/cli/sessions/chat/chat/state/types.py +1 -1
- ommlds/cli/sessions/chat/chat/user/configs.py +17 -0
- ommlds/cli/sessions/chat/chat/user/inject.py +13 -19
- ommlds/cli/sessions/chat/chat/user/interactive.py +3 -3
- ommlds/cli/sessions/chat/configs.py +15 -26
- ommlds/cli/sessions/chat/inject.py +18 -35
- ommlds/cli/sessions/chat/session.py +1 -1
- ommlds/cli/sessions/chat/tools/configs.py +22 -0
- ommlds/cli/sessions/chat/tools/fs/__init__.py +0 -0
- ommlds/cli/sessions/chat/tools/fs/configs.py +12 -0
- ommlds/cli/sessions/chat/tools/fs/inject.py +35 -0
- ommlds/cli/sessions/chat/tools/inject.py +17 -74
- ommlds/cli/sessions/chat/tools/injection.py +15 -0
- ommlds/cli/sessions/chat/tools/rendering.py +1 -1
- ommlds/cli/sessions/chat/tools/todo/__init__.py +0 -0
- ommlds/cli/sessions/chat/tools/todo/configs.py +12 -0
- ommlds/cli/sessions/chat/tools/todo/inject.py +31 -0
- ommlds/cli/sessions/chat/tools/weather/__init__.py +0 -0
- ommlds/cli/sessions/chat/tools/weather/configs.py +12 -0
- ommlds/cli/sessions/chat/tools/weather/inject.py +22 -0
- ommlds/cli/sessions/chat/tools/{weather.py → weather/tools.py} +1 -1
- ommlds/cli/sessions/completion/configs.py +2 -2
- ommlds/cli/sessions/completion/inject.py +14 -0
- ommlds/cli/sessions/completion/session.py +7 -11
- ommlds/cli/sessions/embedding/configs.py +2 -2
- ommlds/cli/sessions/embedding/inject.py +14 -0
- ommlds/cli/sessions/embedding/session.py +7 -11
- ommlds/cli/state/storage.py +1 -1
- ommlds/minichain/backends/catalogs/strings.py +1 -1
- ommlds/minichain/backends/impls/groq/__init__.py +0 -0
- ommlds/minichain/backends/impls/groq/chat.py +75 -0
- ommlds/minichain/backends/impls/groq/names.py +48 -0
- ommlds/minichain/backends/impls/groq/protocol.py +143 -0
- ommlds/minichain/backends/impls/groq/stream.py +125 -0
- ommlds/minichain/backends/impls/openai/chat.py +3 -3
- ommlds/minichain/backends/impls/openai/names.py +27 -3
- ommlds/minichain/backends/impls/openai/stream.py +2 -2
- ommlds/minichain/chat/stream/joining.py +1 -0
- ommlds/minichain/tools/reflect.py +5 -1
- ommlds/wiki/utils/xml.py +5 -5
- {ommlds-0.0.0.dev475.dist-info → ommlds-0.0.0.dev477.dist-info}/METADATA +5 -5
- {ommlds-0.0.0.dev475.dist-info → ommlds-0.0.0.dev477.dist-info}/RECORD +80 -58
- ommlds/cli/backends/standard.py +0 -20
- ommlds/cli/main2.py +0 -220
- ommlds/cli/sessions/chat/backends/inject.py +0 -53
- /ommlds/{cli/sessions/chat/backends → backends/groq}/__init__.py +0 -0
- /ommlds/cli/{sessions/chat/content → content}/__init__.py +0 -0
- /ommlds/cli/{sessions/chat/rendering → inputs}/__init__.py +0 -0
- {ommlds-0.0.0.dev475.dist-info → ommlds-0.0.0.dev477.dist-info}/WHEEL +0 -0
- {ommlds-0.0.0.dev475.dist-info → ommlds-0.0.0.dev477.dist-info}/entry_points.txt +0 -0
- {ommlds-0.0.0.dev475.dist-info → ommlds-0.0.0.dev477.dist-info}/licenses/LICENSE +0 -0
- {ommlds-0.0.0.dev475.dist-info → ommlds-0.0.0.dev477.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from omlish import inject as inj
|
|
2
|
+
|
|
3
|
+
from ..injection import ToolSetBinder
|
|
4
|
+
from ..injection import bind_tool_context_provider_to_key
|
|
5
|
+
from ..injection import tool_catalog_entries
|
|
6
|
+
from .configs import TodoToolSetConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def bind_todo_tools(cfg: TodoToolSetConfig) -> inj.Elements:
|
|
13
|
+
from ......minichain.lib.todo.context import TodoContext
|
|
14
|
+
from ......minichain.lib.todo.tools.read import todo_read_tool
|
|
15
|
+
from ......minichain.lib.todo.tools.write import todo_write_tool
|
|
16
|
+
|
|
17
|
+
return inj.as_elements(
|
|
18
|
+
tool_catalog_entries().bind_item_consts(
|
|
19
|
+
todo_read_tool(),
|
|
20
|
+
todo_write_tool(),
|
|
21
|
+
),
|
|
22
|
+
|
|
23
|
+
inj.bind(TodoContext()),
|
|
24
|
+
bind_tool_context_provider_to_key(TodoContext),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
TODO_TOOL_SET_BINDER = ToolSetBinder(TodoToolSetConfig, bind_todo_tools)
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from omlish import inject as inj
|
|
2
|
+
|
|
3
|
+
from ..injection import ToolSetBinder
|
|
4
|
+
from ..injection import tool_catalog_entries
|
|
5
|
+
from .configs import WeatherToolSetConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def bind_weather_tools(cfg: WeatherToolSetConfig) -> inj.Elements:
|
|
12
|
+
from .tools import WEATHER_TOOL
|
|
13
|
+
|
|
14
|
+
return inj.as_elements(
|
|
15
|
+
tool_catalog_entries().bind_item_consts(WEATHER_TOOL),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
WEATHER_TOOL_SET_BINDER = ToolSetBinder(WeatherToolSetConfig, bind_weather_tools)
|
|
@@ -2,11 +2,15 @@ from omlish import dataclasses as dc
|
|
|
2
2
|
from omlish import inject as inj
|
|
3
3
|
from omlish import lang
|
|
4
4
|
|
|
5
|
+
from ...backends.configs import BackendConfig
|
|
6
|
+
from ...backends.types import DefaultBackendName
|
|
5
7
|
from ..base import Session
|
|
8
|
+
from .configs import DEFAULT_BACKEND
|
|
6
9
|
from .configs import CompletionConfig
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
with lang.auto_proxy_import(globals()):
|
|
13
|
+
from ...backends import inject as _backends
|
|
10
14
|
from . import session as _session
|
|
11
15
|
|
|
12
16
|
|
|
@@ -25,4 +29,14 @@ def bind_completion(cfg: CompletionConfig) -> inj.Elements:
|
|
|
25
29
|
|
|
26
30
|
#
|
|
27
31
|
|
|
32
|
+
els.extend([
|
|
33
|
+
_backends.bind_backends(BackendConfig(
|
|
34
|
+
backend=cfg.backend,
|
|
35
|
+
)),
|
|
36
|
+
|
|
37
|
+
inj.bind(DefaultBackendName, to_const=DEFAULT_BACKEND),
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
|
|
28
42
|
return inj.as_elements(*els)
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import dataclasses as dc
|
|
2
|
-
|
|
3
1
|
from omlish import check
|
|
4
|
-
from omlish import
|
|
2
|
+
from omlish import dataclasses as dc
|
|
5
3
|
|
|
6
4
|
from .... import minichain as mc
|
|
5
|
+
from ...backends.types import CompletionServiceBackendProvider
|
|
7
6
|
from ..base import Session
|
|
8
|
-
from .configs import DEFAULT_COMPLETION_MODEL_BACKEND
|
|
9
7
|
from .configs import CompletionConfig
|
|
10
8
|
|
|
11
9
|
|
|
@@ -21,19 +19,17 @@ class CompletionSession(Session['CompletionSession.Config']):
|
|
|
21
19
|
self,
|
|
22
20
|
config: Config,
|
|
23
21
|
*,
|
|
24
|
-
|
|
22
|
+
service_provider: CompletionServiceBackendProvider,
|
|
25
23
|
) -> None:
|
|
26
24
|
super().__init__(config)
|
|
27
25
|
|
|
28
|
-
self.
|
|
26
|
+
self._service_provider = service_provider
|
|
29
27
|
|
|
30
28
|
async def run(self) -> None:
|
|
31
29
|
prompt = check.isinstance(self._config.content, str)
|
|
32
30
|
|
|
33
31
|
mdl: mc.CompletionService
|
|
34
|
-
async with
|
|
35
|
-
mc.CompletionService,
|
|
36
|
-
self._config.backend or DEFAULT_COMPLETION_MODEL_BACKEND,
|
|
37
|
-
)) as mdl:
|
|
32
|
+
async with self._service_provider.provide_backend() as mdl:
|
|
38
33
|
response = await mdl.invoke(mc.CompletionRequest(prompt))
|
|
39
|
-
|
|
34
|
+
|
|
35
|
+
print(response.v.strip())
|
|
@@ -2,11 +2,15 @@ from omlish import dataclasses as dc
|
|
|
2
2
|
from omlish import inject as inj
|
|
3
3
|
from omlish import lang
|
|
4
4
|
|
|
5
|
+
from ...backends.configs import BackendConfig
|
|
6
|
+
from ...backends.types import DefaultBackendName
|
|
5
7
|
from ..base import Session
|
|
8
|
+
from .configs import DEFAULT_BACKEND
|
|
6
9
|
from .configs import EmbeddingConfig
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
with lang.auto_proxy_import(globals()):
|
|
13
|
+
from ...backends import inject as _backends
|
|
10
14
|
from . import session as _session
|
|
11
15
|
|
|
12
16
|
|
|
@@ -25,4 +29,14 @@ def bind_embedding(cfg: EmbeddingConfig) -> inj.Elements:
|
|
|
25
29
|
|
|
26
30
|
#
|
|
27
31
|
|
|
32
|
+
els.extend([
|
|
33
|
+
_backends.bind_backends(BackendConfig(
|
|
34
|
+
backend=cfg.backend,
|
|
35
|
+
)),
|
|
36
|
+
|
|
37
|
+
inj.bind(DefaultBackendName, to_const=DEFAULT_BACKEND),
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
|
|
28
42
|
return inj.as_elements(*els)
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import dataclasses as dc
|
|
2
|
-
|
|
3
|
-
from omlish import lang
|
|
1
|
+
from omlish import dataclasses as dc
|
|
4
2
|
from omlish.formats import json
|
|
5
3
|
|
|
6
4
|
from .... import minichain as mc
|
|
5
|
+
from ...backends.types import EmbeddingServiceBackendProvider
|
|
7
6
|
from ..base import Session
|
|
8
|
-
from .configs import DEFAULT_EMBEDDING_MODEL_BACKEND
|
|
9
7
|
from .configs import EmbeddingConfig
|
|
10
8
|
|
|
11
9
|
|
|
@@ -21,17 +19,15 @@ class EmbeddingSession(Session['EmbeddingSession.Config']):
|
|
|
21
19
|
self,
|
|
22
20
|
config: Config,
|
|
23
21
|
*,
|
|
24
|
-
|
|
22
|
+
service_provider: EmbeddingServiceBackendProvider,
|
|
25
23
|
) -> None:
|
|
26
24
|
super().__init__(config)
|
|
27
25
|
|
|
28
|
-
self.
|
|
26
|
+
self._service_provider = service_provider
|
|
29
27
|
|
|
30
28
|
async def run(self) -> None:
|
|
31
29
|
mdl: mc.EmbeddingService
|
|
32
|
-
async with
|
|
33
|
-
mc.EmbeddingService,
|
|
34
|
-
self._config.backend or DEFAULT_EMBEDDING_MODEL_BACKEND,
|
|
35
|
-
)) as mdl:
|
|
30
|
+
async with self._service_provider.provide_backend() as mdl:
|
|
36
31
|
response = await mdl.invoke(mc.EmbeddingRequest(self._config.content))
|
|
37
|
-
|
|
32
|
+
|
|
33
|
+
print(json.dumps_compact(list(map(float, response.v))))
|
ommlds/cli/state/storage.py
CHANGED
|
@@ -39,7 +39,7 @@ class BackendStringBackendCatalog(BackendCatalog):
|
|
|
39
39
|
|
|
40
40
|
al: list = list(rs.args or [])
|
|
41
41
|
|
|
42
|
-
# FIXME: lol
|
|
42
|
+
# FIXME: lol - move *into* local model classes as an injected dep?
|
|
43
43
|
if al and isinstance(al[0], ModelRepo):
|
|
44
44
|
[mr] = al
|
|
45
45
|
mrr = check.not_none(self._model_repo_resolver)
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
from omlish import check
|
|
4
|
+
from omlish import marshal as msh
|
|
5
|
+
from omlish import typedvalues as tv
|
|
6
|
+
from omlish.formats import json
|
|
7
|
+
from omlish.http import all as http
|
|
8
|
+
|
|
9
|
+
from .....backends.groq import protocol as pt
|
|
10
|
+
from ....chat.choices.services import ChatChoicesRequest
|
|
11
|
+
from ....chat.choices.services import ChatChoicesResponse
|
|
12
|
+
from ....chat.choices.services import static_check_is_chat_choices_service
|
|
13
|
+
from ....chat.tools.types import Tool
|
|
14
|
+
from ....models.configs import ModelName
|
|
15
|
+
from ....standard import ApiKey
|
|
16
|
+
from ....standard import DefaultOptions
|
|
17
|
+
from .names import MODEL_NAMES
|
|
18
|
+
from .protocol import build_gq_request_messages
|
|
19
|
+
from .protocol import build_gq_request_tool
|
|
20
|
+
from .protocol import build_mc_choices_response
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# @omlish-manifest $.minichain.registries.manifests.RegistryManifest(
|
|
27
|
+
# name='groq',
|
|
28
|
+
# type='ChatChoicesService',
|
|
29
|
+
# )
|
|
30
|
+
@static_check_is_chat_choices_service
|
|
31
|
+
class GroqChatChoicesService:
|
|
32
|
+
DEFAULT_MODEL_NAME: ta.ClassVar[ModelName] = ModelName(check.not_none(MODEL_NAMES.default))
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
*configs: ApiKey | ModelName | DefaultOptions,
|
|
37
|
+
http_client: http.AsyncHttpClient | None = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
super().__init__()
|
|
40
|
+
|
|
41
|
+
self._http_client = http_client
|
|
42
|
+
|
|
43
|
+
with tv.consume(*configs) as cc:
|
|
44
|
+
self._model_name = cc.pop(self.DEFAULT_MODEL_NAME)
|
|
45
|
+
self._api_key = ApiKey.pop_secret(cc, env='GROQ_API_KEY')
|
|
46
|
+
self._default_options: tv.TypedValues = DefaultOptions.pop(cc)
|
|
47
|
+
|
|
48
|
+
async def invoke(self, request: ChatChoicesRequest) -> ChatChoicesResponse:
|
|
49
|
+
tools: list[pt.ChatCompletionRequest.Tool] = []
|
|
50
|
+
with tv.TypedValues(*request.options).consume() as oc:
|
|
51
|
+
t: Tool
|
|
52
|
+
for t in oc.pop(Tool, []):
|
|
53
|
+
tools.append(build_gq_request_tool(t))
|
|
54
|
+
|
|
55
|
+
gq_request = pt.ChatCompletionRequest(
|
|
56
|
+
messages=build_gq_request_messages(request.v),
|
|
57
|
+
model=MODEL_NAMES.resolve(self._model_name.v),
|
|
58
|
+
tools=tools or None,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
raw_request = msh.marshal(gq_request)
|
|
62
|
+
|
|
63
|
+
http_response = await http.async_request(
|
|
64
|
+
'https://api.groq.com/openai/v1/chat/completions',
|
|
65
|
+
headers={
|
|
66
|
+
http.consts.HEADER_CONTENT_TYPE: http.consts.CONTENT_TYPE_JSON,
|
|
67
|
+
http.consts.HEADER_AUTH: http.consts.format_bearer_auth_header(check.not_none(self._api_key).reveal()),
|
|
68
|
+
},
|
|
69
|
+
data=json.dumps(raw_request).encode('utf-8'),
|
|
70
|
+
client=self._http_client,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
raw_response = json.loads(check.not_none(http_response.data).decode('utf-8'))
|
|
74
|
+
|
|
75
|
+
return build_mc_choices_response(msh.unmarshal(raw_response, pt.ChatCompletionResponse))
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
https://console.groq.com/docs/models
|
|
3
|
+
|
|
4
|
+
curl -X GET "https://api.groq.com/openai/v1/models" \
|
|
5
|
+
-H "Authorization: Bearer $GROQ_API_KEY" \
|
|
6
|
+
-H "Content-Type: application/json"
|
|
7
|
+
|
|
8
|
+
"compound-beta",
|
|
9
|
+
"compound-beta-mini",
|
|
10
|
+
"gemma2-9b-it",
|
|
11
|
+
"llama-3.1-8b-instant",
|
|
12
|
+
"llama-3.3-70b-versatile",
|
|
13
|
+
"meta-llama/llama-4-maverick-17b-128e-instruct",
|
|
14
|
+
"meta-llama/llama-4-scout-17b-16e-instruct",
|
|
15
|
+
"meta-llama/llama-guard-4-12b",
|
|
16
|
+
"moonshotai/kimi-k2-instruct",
|
|
17
|
+
"openai/gpt-oss-120b",
|
|
18
|
+
"openai/gpt-oss-20b",
|
|
19
|
+
"qwen/qwen3-32b",
|
|
20
|
+
"""
|
|
21
|
+
from ....models.names import ModelNameCollection
|
|
22
|
+
from ...strings.manifests import BackendStringsManifest
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
MODEL_NAMES = ModelNameCollection(
|
|
29
|
+
default='gpt-oss-120b',
|
|
30
|
+
aliases={
|
|
31
|
+
'gpt-oss-120b': 'openai/gpt-oss-120b',
|
|
32
|
+
'openai/gpt-oss-120b': None,
|
|
33
|
+
|
|
34
|
+
'gpt-oss-20b': 'openai/gpt-oss-20b',
|
|
35
|
+
'openai/gpt-oss-20b': None,
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# @omlish-manifest
|
|
41
|
+
_BACKEND_STRINGS_MANIFEST = BackendStringsManifest(
|
|
42
|
+
[
|
|
43
|
+
'ChatChoicesService',
|
|
44
|
+
'ChatChoicesStreamService',
|
|
45
|
+
],
|
|
46
|
+
'groq',
|
|
47
|
+
model_names=MODEL_NAMES,
|
|
48
|
+
)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
|
|
3
|
+
from omlish import check
|
|
4
|
+
from omlish.formats import json
|
|
5
|
+
|
|
6
|
+
from .....backends.groq import protocol as pt
|
|
7
|
+
from ....chat.choices.services import ChatChoicesResponse
|
|
8
|
+
from ....chat.choices.types import AiChoice
|
|
9
|
+
from ....chat.messages import AiMessage
|
|
10
|
+
from ....chat.messages import AnyAiMessage
|
|
11
|
+
from ....chat.messages import Chat
|
|
12
|
+
from ....chat.messages import SystemMessage
|
|
13
|
+
from ....chat.messages import ToolUseMessage
|
|
14
|
+
from ....chat.messages import ToolUseResultMessage
|
|
15
|
+
from ....chat.messages import UserMessage
|
|
16
|
+
from ....chat.stream.types import AiChoiceDelta
|
|
17
|
+
from ....chat.stream.types import AiChoiceDeltas
|
|
18
|
+
from ....chat.stream.types import ContentAiChoiceDelta
|
|
19
|
+
from ....chat.stream.types import ToolUseAiChoiceDelta
|
|
20
|
+
from ....chat.tools.types import Tool
|
|
21
|
+
from ....content.prepare import prepare_content_str
|
|
22
|
+
from ....tools.jsonschema import build_tool_spec_params_json_schema
|
|
23
|
+
from ....tools.types import ToolUse
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_gq_request_messages(chat: Chat) -> list[pt.ChatCompletionRequest.Message]:
|
|
30
|
+
gq_msgs: list[pt.ChatCompletionRequest.Message] = []
|
|
31
|
+
|
|
32
|
+
for _, g in itertools.groupby(chat, lambda mc_m: isinstance(mc_m, AnyAiMessage)):
|
|
33
|
+
mc_msgs = list(g)
|
|
34
|
+
|
|
35
|
+
if isinstance(mc_msgs[0], AnyAiMessage):
|
|
36
|
+
tups: list[tuple[AiMessage | None, list[ToolUseMessage]]] = []
|
|
37
|
+
for mc_msg in mc_msgs:
|
|
38
|
+
if isinstance(mc_msg, AiMessage):
|
|
39
|
+
tups.append((mc_msg, []))
|
|
40
|
+
|
|
41
|
+
elif isinstance(mc_msg, ToolUseMessage):
|
|
42
|
+
if not tups:
|
|
43
|
+
tups.append((None, []))
|
|
44
|
+
tups[-1][1].append(mc_msg)
|
|
45
|
+
|
|
46
|
+
else:
|
|
47
|
+
raise TypeError(mc_msg)
|
|
48
|
+
|
|
49
|
+
for mc_ai_msg, mc_tu_msgs in tups:
|
|
50
|
+
gq_msgs.append(pt.ChatCompletionRequest.AssistantMessage(
|
|
51
|
+
content=check.isinstance(mc_ai_msg.c, str) if mc_ai_msg is not None else None,
|
|
52
|
+
tool_calls=[
|
|
53
|
+
pt.ChatCompletionRequest.AssistantMessage.ToolCall(
|
|
54
|
+
function=pt.ChatCompletionRequest.AssistantMessage.ToolCall.Function(
|
|
55
|
+
name=mc_tu_msg.tu.name,
|
|
56
|
+
arguments=check.not_none(mc_tu_msg.tu.raw_args),
|
|
57
|
+
),
|
|
58
|
+
id=check.not_none(mc_tu_msg.tu.id),
|
|
59
|
+
)
|
|
60
|
+
for mc_tu_msg in mc_tu_msgs
|
|
61
|
+
] if mc_tu_msgs else None,
|
|
62
|
+
))
|
|
63
|
+
|
|
64
|
+
else:
|
|
65
|
+
for mc_msg in mc_msgs:
|
|
66
|
+
if isinstance(mc_msg, SystemMessage):
|
|
67
|
+
gq_msgs.append(pt.ChatCompletionRequest.SystemMessage(
|
|
68
|
+
content=check.isinstance(mc_msg.c, str),
|
|
69
|
+
))
|
|
70
|
+
|
|
71
|
+
elif isinstance(mc_msg, UserMessage):
|
|
72
|
+
gq_msgs.append(pt.ChatCompletionRequest.UserMessage(
|
|
73
|
+
content=check.isinstance(mc_msg.c, str),
|
|
74
|
+
))
|
|
75
|
+
|
|
76
|
+
elif isinstance(mc_msg, ToolUseResultMessage):
|
|
77
|
+
gq_msgs.append(pt.ChatCompletionRequest.ToolMessage(
|
|
78
|
+
tool_call_id=check.not_none(mc_msg.tur.id),
|
|
79
|
+
content=check.isinstance(mc_msg.tur.c, str),
|
|
80
|
+
))
|
|
81
|
+
|
|
82
|
+
else:
|
|
83
|
+
raise TypeError(mc_msg)
|
|
84
|
+
|
|
85
|
+
return gq_msgs
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def build_gq_request_tool(t: Tool) -> pt.ChatCompletionRequest.Tool:
|
|
89
|
+
return pt.ChatCompletionRequest.Tool(
|
|
90
|
+
function=pt.ChatCompletionRequest.Tool.Function(
|
|
91
|
+
name=check.not_none(t.spec.name),
|
|
92
|
+
description=prepare_content_str(t.spec.desc),
|
|
93
|
+
parameters=build_tool_spec_params_json_schema(t.spec),
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def build_mc_choices_response(gq_resp: pt.ChatCompletionResponse) -> ChatChoicesResponse:
|
|
99
|
+
def build_choice(gq_choice: pt.ChatCompletionResponse.Choice) -> AiChoice:
|
|
100
|
+
gq_msg = gq_choice.message
|
|
101
|
+
|
|
102
|
+
lst: list[AnyAiMessage] = []
|
|
103
|
+
|
|
104
|
+
if gq_msg.content is not None:
|
|
105
|
+
lst.append(AiMessage(
|
|
106
|
+
check.isinstance(gq_msg.content, str),
|
|
107
|
+
))
|
|
108
|
+
|
|
109
|
+
for gq_tc in gq_msg.tool_calls or []:
|
|
110
|
+
lst.append(ToolUseMessage(ToolUse(
|
|
111
|
+
id=gq_tc.id,
|
|
112
|
+
name=gq_tc.function.name,
|
|
113
|
+
args=json.loads(gq_tc.function.arguments or '{}'),
|
|
114
|
+
raw_args=gq_tc.function.arguments,
|
|
115
|
+
)))
|
|
116
|
+
|
|
117
|
+
return AiChoice(lst)
|
|
118
|
+
|
|
119
|
+
return ChatChoicesResponse(list(map(build_choice, gq_resp.choices)))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def build_mc_ai_choice_deltas(delta: pt.ChatCompletionChunk.Choice.Delta) -> AiChoiceDeltas:
|
|
123
|
+
if delta.role in (None, 'assistant'):
|
|
124
|
+
lst: list[AiChoiceDelta] = []
|
|
125
|
+
|
|
126
|
+
if delta.content is not None:
|
|
127
|
+
lst.append(ContentAiChoiceDelta(delta.content))
|
|
128
|
+
|
|
129
|
+
for tc in delta.tool_calls or []:
|
|
130
|
+
tc_fn = check.not_none(tc.function)
|
|
131
|
+
lst.append(ToolUseAiChoiceDelta(
|
|
132
|
+
id=tc.id,
|
|
133
|
+
name=check.not_none(tc_fn.name),
|
|
134
|
+
args=json.loads(tc_fn.arguments or '{}'),
|
|
135
|
+
))
|
|
136
|
+
|
|
137
|
+
return AiChoiceDeltas(lst)
|
|
138
|
+
|
|
139
|
+
elif delta.channel in ('analysis', 'commentary'):
|
|
140
|
+
return AiChoiceDeltas([])
|
|
141
|
+
|
|
142
|
+
else:
|
|
143
|
+
raise ValueError(delta)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
from omlish import check
|
|
4
|
+
from omlish import marshal as msh
|
|
5
|
+
from omlish import typedvalues as tv
|
|
6
|
+
from omlish.formats import json
|
|
7
|
+
from omlish.http import all as http
|
|
8
|
+
from omlish.http import sse
|
|
9
|
+
from omlish.io.buffers import DelimitingBuffer
|
|
10
|
+
|
|
11
|
+
from .....backends.groq import protocol as pt
|
|
12
|
+
from ....chat.choices.services import ChatChoicesOutputs
|
|
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 AiChoicesDeltas
|
|
17
|
+
from ....chat.tools.types import Tool
|
|
18
|
+
from ....configs import Config
|
|
19
|
+
from ....resources import UseResources
|
|
20
|
+
from ....standard import ApiKey
|
|
21
|
+
from ....stream.services import StreamResponseSink
|
|
22
|
+
from ....stream.services import new_stream_response
|
|
23
|
+
from .chat import GroqChatChoicesService
|
|
24
|
+
from .names import MODEL_NAMES
|
|
25
|
+
from .protocol import build_gq_request_messages
|
|
26
|
+
from .protocol import build_gq_request_tool
|
|
27
|
+
from .protocol import build_mc_ai_choice_deltas
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# @omlish-manifest $.minichain.registries.manifests.RegistryManifest(
|
|
34
|
+
# name='groq',
|
|
35
|
+
# type='ChatChoicesStreamService',
|
|
36
|
+
# )
|
|
37
|
+
@static_check_is_chat_choices_stream_service
|
|
38
|
+
class GroqChatChoicesStreamService:
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
*configs: Config,
|
|
42
|
+
http_client: http.AsyncHttpClient | None = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
super().__init__()
|
|
45
|
+
|
|
46
|
+
self._http_client = http_client
|
|
47
|
+
|
|
48
|
+
with tv.consume(*configs) as cc:
|
|
49
|
+
self._model_name = cc.pop(GroqChatChoicesService.DEFAULT_MODEL_NAME)
|
|
50
|
+
self._api_key = ApiKey.pop_secret(cc, env='GROQ_API_KEY')
|
|
51
|
+
|
|
52
|
+
READ_CHUNK_SIZE: ta.ClassVar[int] = -1
|
|
53
|
+
|
|
54
|
+
async def invoke(self, request: ChatChoicesStreamRequest) -> ChatChoicesStreamResponse:
|
|
55
|
+
tools: list[pt.ChatCompletionRequest.Tool] = []
|
|
56
|
+
with tv.TypedValues(*request.options).consume() as oc:
|
|
57
|
+
t: Tool
|
|
58
|
+
for t in oc.pop(Tool, []):
|
|
59
|
+
tools.append(build_gq_request_tool(t))
|
|
60
|
+
|
|
61
|
+
gq_request = pt.ChatCompletionRequest(
|
|
62
|
+
messages=build_gq_request_messages(request.v),
|
|
63
|
+
model=MODEL_NAMES.resolve(self._model_name.v),
|
|
64
|
+
tools=tools or None,
|
|
65
|
+
stream=True,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
raw_request = msh.marshal(gq_request)
|
|
69
|
+
|
|
70
|
+
http_request = http.HttpRequest(
|
|
71
|
+
'https://api.groq.com/openai/v1/chat/completions',
|
|
72
|
+
headers={
|
|
73
|
+
http.consts.HEADER_CONTENT_TYPE: http.consts.CONTENT_TYPE_JSON,
|
|
74
|
+
http.consts.HEADER_AUTH: http.consts.format_bearer_auth_header(check.not_none(self._api_key).reveal()),
|
|
75
|
+
},
|
|
76
|
+
data=json.dumps(raw_request).encode('utf-8'),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
async with UseResources.or_new(request.options) as rs:
|
|
80
|
+
http_client = await rs.enter_async_context(http.manage_async_client(self._http_client))
|
|
81
|
+
http_response = await rs.enter_async_context(await http_client.stream_request(http_request))
|
|
82
|
+
|
|
83
|
+
async def inner(sink: StreamResponseSink[AiChoicesDeltas]) -> ta.Sequence[ChatChoicesOutputs]:
|
|
84
|
+
db = DelimitingBuffer([b'\r', b'\n', b'\r\n'])
|
|
85
|
+
sd = sse.SseDecoder()
|
|
86
|
+
while True:
|
|
87
|
+
b = await http_response.stream.read1(self.READ_CHUNK_SIZE)
|
|
88
|
+
for l in db.feed(b):
|
|
89
|
+
if isinstance(l, DelimitingBuffer.Incomplete):
|
|
90
|
+
# FIXME: handle
|
|
91
|
+
return []
|
|
92
|
+
|
|
93
|
+
# FIXME: https://platform.openai.com/docs/guides/function-calling?api-mode=responses#streaming
|
|
94
|
+
for so in sd.process_line(l):
|
|
95
|
+
if isinstance(so, sse.SseEvent) and so.type == b'message':
|
|
96
|
+
ss = so.data.decode('utf-8')
|
|
97
|
+
if ss == '[DONE]':
|
|
98
|
+
return []
|
|
99
|
+
|
|
100
|
+
sj = json.loads(ss) # ChatCompletionChunk
|
|
101
|
+
|
|
102
|
+
check.state(sj['object'] == 'chat.completion.chunk')
|
|
103
|
+
|
|
104
|
+
ccc = msh.unmarshal(sj, pt.ChatCompletionChunk)
|
|
105
|
+
|
|
106
|
+
# FIXME: stop reason
|
|
107
|
+
if not ccc.choices:
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
if any(choice.finish_reason for choice in ccc.choices):
|
|
111
|
+
check.state(all(choice.finish_reason for choice in ccc.choices))
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
await sink.emit(AiChoicesDeltas([
|
|
115
|
+
build_mc_ai_choice_deltas(choice.delta)
|
|
116
|
+
for choice in ccc.choices
|
|
117
|
+
]))
|
|
118
|
+
|
|
119
|
+
if not b:
|
|
120
|
+
return []
|
|
121
|
+
|
|
122
|
+
# raw_response = json.loads(check.not_none(http_response.data).decode('utf-8'))
|
|
123
|
+
# return rh.build_response(raw_response)
|
|
124
|
+
|
|
125
|
+
return await new_stream_response(rs, inner)
|