ommlds 0.0.0.dev463__py3-none-any.whl → 0.0.0.dev465__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.

Files changed (81) hide show
  1. ommlds/.omlish-manifests.json +2 -2
  2. ommlds/cli/inject.py +14 -32
  3. ommlds/cli/main.py +34 -59
  4. ommlds/cli/sessions/{chat2 → chat}/backends/catalog.py +5 -5
  5. ommlds/cli/sessions/chat/backends/inject.py +37 -0
  6. ommlds/cli/sessions/chat/backends/injection.py +16 -0
  7. ommlds/cli/sessions/{chat2 → chat}/backends/types.py +3 -3
  8. ommlds/cli/sessions/chat/chat/ai/inject.py +78 -0
  9. ommlds/cli/sessions/chat/chat/ai/injection.py +14 -0
  10. ommlds/cli/sessions/{chat2 → chat}/chat/ai/rendering.py +13 -10
  11. ommlds/cli/sessions/chat/chat/ai/services.py +81 -0
  12. ommlds/cli/sessions/chat/chat/ai/tools.py +44 -0
  13. ommlds/cli/sessions/{chat2 → chat}/chat/ai/types.py +5 -5
  14. ommlds/cli/sessions/chat/chat/state/inject.py +40 -0
  15. ommlds/cli/sessions/{chat2 → chat}/chat/state/inmemory.py +1 -1
  16. ommlds/cli/sessions/{chat2 → chat}/chat/state/storage.py +2 -2
  17. ommlds/cli/sessions/{chat2 → chat}/chat/state/types.py +1 -1
  18. ommlds/cli/sessions/chat/chat/user/inject.py +61 -0
  19. ommlds/cli/sessions/{chat2 → chat}/chat/user/interactive.py +1 -1
  20. ommlds/cli/sessions/{chat2 → chat}/chat/user/oneshot.py +1 -1
  21. ommlds/cli/sessions/{chat2 → chat}/chat/user/types.py +1 -1
  22. ommlds/cli/sessions/{chat2 → chat}/configs.py +4 -1
  23. ommlds/cli/sessions/{chat2 → chat}/content/messages.py +6 -2
  24. ommlds/cli/sessions/{chat2 → chat}/content/strings.py +2 -2
  25. ommlds/cli/sessions/{chat2 → chat}/driver.py +2 -2
  26. ommlds/cli/sessions/chat/inject.py +47 -63
  27. ommlds/cli/sessions/chat/phases/inject.py +27 -0
  28. ommlds/cli/sessions/chat/phases/injection.py +14 -0
  29. ommlds/cli/sessions/{chat2/phases.py → chat/phases/manager.py} +2 -28
  30. ommlds/cli/sessions/chat/phases/types.py +29 -0
  31. ommlds/cli/sessions/chat/rendering/inject.py +32 -0
  32. ommlds/cli/sessions/{chat2 → chat}/rendering/markdown.py +2 -2
  33. ommlds/cli/sessions/{chat2 → chat}/rendering/raw.py +6 -6
  34. ommlds/cli/sessions/{chat2 → chat}/rendering/types.py +1 -1
  35. ommlds/cli/sessions/{chat2 → chat}/session.py +1 -1
  36. ommlds/cli/sessions/{chat2 → chat}/tools/confirmation.py +4 -4
  37. ommlds/cli/sessions/{chat2 → chat}/tools/execution.py +18 -5
  38. ommlds/cli/sessions/chat/tools/inject.py +145 -0
  39. ommlds/cli/sessions/chat/tools/injection.py +29 -0
  40. ommlds/cli/sessions/chat/tools/rendering.py +58 -0
  41. ommlds/cli/{tools → sessions/chat/tools}/weather.py +1 -1
  42. ommlds/cli/sessions/completion/configs.py +21 -0
  43. ommlds/cli/sessions/completion/inject.py +28 -0
  44. ommlds/cli/sessions/completion/{completion.py → session.py} +4 -9
  45. ommlds/cli/sessions/embedding/configs.py +21 -0
  46. ommlds/cli/sessions/embedding/inject.py +28 -0
  47. ommlds/cli/sessions/embedding/{embedding.py → session.py} +4 -9
  48. ommlds/cli/sessions/inject.py +27 -15
  49. ommlds/cli/state/inject.py +28 -0
  50. ommlds/minichain/backends/impls/anthropic/chat.py +4 -61
  51. ommlds/minichain/backends/impls/anthropic/protocol.py +109 -0
  52. ommlds/minichain/backends/impls/anthropic/stream.py +17 -17
  53. {ommlds-0.0.0.dev463.dist-info → ommlds-0.0.0.dev465.dist-info}/METADATA +3 -3
  54. {ommlds-0.0.0.dev463.dist-info → ommlds-0.0.0.dev465.dist-info}/RECORD +69 -60
  55. ommlds/cli/sessions/chat/base.py +0 -42
  56. ommlds/cli/sessions/chat/code.py +0 -125
  57. ommlds/cli/sessions/chat/interactive.py +0 -70
  58. ommlds/cli/sessions/chat/printing.py +0 -126
  59. ommlds/cli/sessions/chat/prompt.py +0 -161
  60. ommlds/cli/sessions/chat/state.py +0 -110
  61. ommlds/cli/sessions/chat/tools.py +0 -97
  62. ommlds/cli/sessions/chat2/_inject.py +0 -105
  63. ommlds/cli/sessions/chat2/chat/ai/services.py +0 -70
  64. ommlds/cli/sessions/chat2/inject.py +0 -143
  65. ommlds/cli/tools/config.py +0 -14
  66. ommlds/cli/tools/inject.py +0 -75
  67. /ommlds/cli/sessions/{chat2 → chat/backends}/__init__.py +0 -0
  68. /ommlds/cli/sessions/{chat2/backends → chat/chat}/__init__.py +0 -0
  69. /ommlds/cli/sessions/{chat2/chat → chat/chat/ai}/__init__.py +0 -0
  70. /ommlds/cli/sessions/{chat2/chat/ai → chat/chat/state}/__init__.py +0 -0
  71. /ommlds/cli/sessions/{chat2/chat/state → chat/chat/user}/__init__.py +0 -0
  72. /ommlds/cli/sessions/{chat2/chat/user → chat/content}/__init__.py +0 -0
  73. /ommlds/cli/sessions/{chat2/content → chat/phases}/__init__.py +0 -0
  74. /ommlds/cli/sessions/{chat2 → chat}/rendering/__init__.py +0 -0
  75. /ommlds/cli/sessions/{chat2 → chat}/tools/__init__.py +0 -0
  76. /ommlds/cli/{tools → state}/__init__.py +0 -0
  77. /ommlds/cli/{state.py → state/storage.py} +0 -0
  78. {ommlds-0.0.0.dev463.dist-info → ommlds-0.0.0.dev465.dist-info}/WHEEL +0 -0
  79. {ommlds-0.0.0.dev463.dist-info → ommlds-0.0.0.dev465.dist-info}/entry_points.txt +0 -0
  80. {ommlds-0.0.0.dev463.dist-info → ommlds-0.0.0.dev465.dist-info}/licenses/LICENSE +0 -0
  81. {ommlds-0.0.0.dev463.dist-info → ommlds-0.0.0.dev465.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,145 @@
1
+ import os
2
+ import typing as ta
3
+
4
+ from omlish import check
5
+ from omlish import inject as inj
6
+ from omlish import lang
7
+
8
+ from ..... import minichain as mc
9
+ from .injection import bind_tool_context_provider_to_key
10
+ from .injection import tool_catalog_entries
11
+ from .injection import tool_context_providers
12
+
13
+
14
+ with lang.auto_proxy_import(globals()):
15
+ from . import confirmation as _confirmation
16
+ from . import execution as _execution
17
+ from . import rendering as _rendering
18
+
19
+
20
+ ##
21
+
22
+
23
+ _TOOL_BINDERS: dict[str, ta.Callable[[], inj.Elements]] = {}
24
+
25
+
26
+ def _tool_binder(name: str) -> ta.Callable[[ta.Callable[[], inj.Elements]], ta.Callable[[], inj.Elements]]:
27
+ def inner(fn):
28
+ check.not_in(name, _TOOL_BINDERS)
29
+ _TOOL_BINDERS[name] = fn
30
+ return fn
31
+ return inner
32
+
33
+
34
+ #
35
+
36
+
37
+ @_tool_binder('weather')
38
+ def _bind_weather_tool() -> inj.Elements:
39
+ from .weather import WEATHER_TOOL
40
+
41
+ return inj.as_elements(
42
+ tool_catalog_entries().bind_item_consts(WEATHER_TOOL),
43
+ )
44
+
45
+
46
+ @_tool_binder('todo')
47
+ def _bind_todo_tools() -> inj.Elements:
48
+ from .....minichain.lib.todo.context import TodoContext
49
+ from .....minichain.lib.todo.tools.read import todo_read_tool
50
+ from .....minichain.lib.todo.tools.write import todo_write_tool
51
+
52
+ return inj.as_elements(
53
+ tool_catalog_entries().bind_item_consts(
54
+ todo_read_tool(),
55
+ todo_write_tool(),
56
+ ),
57
+
58
+ inj.bind(TodoContext()),
59
+ bind_tool_context_provider_to_key(TodoContext),
60
+ )
61
+
62
+
63
+ @_tool_binder('fs')
64
+ def _bind_fs_tools() -> inj.Elements:
65
+ from .....minichain.lib.fs.context import FsContext
66
+ from .....minichain.lib.fs.tools.ls import ls_tool
67
+ from .....minichain.lib.fs.tools.read import read_tool
68
+
69
+ return inj.as_elements(
70
+ tool_catalog_entries().bind_item_consts(
71
+ ls_tool(),
72
+ read_tool(),
73
+ ),
74
+
75
+ inj.bind(FsContext(
76
+ root_dir=os.getcwd(),
77
+ )),
78
+ bind_tool_context_provider_to_key(FsContext),
79
+ )
80
+
81
+
82
+ # if tools_config.enable_unsafe_tools_do_not_use_lol:
83
+ # from ...minichain.lib.bash import bash_tool
84
+ # els.append(bind_tool(bash_tool()))
85
+ #
86
+ # from ...minichain.lib.fs.tools.edit import edit_tool
87
+ # els.append(bind_tool(edit_tool()))
88
+
89
+
90
+ ##
91
+
92
+
93
+ def bind_tools(
94
+ *,
95
+ silent: bool = False,
96
+ dangerous_no_confirmation: bool = False,
97
+ enabled_tools: ta.Iterable[str] | None = None,
98
+ ) -> inj.Elements:
99
+ els: list[inj.Elemental] = []
100
+
101
+ #
102
+
103
+ els.append(inj.bind(mc.ToolCatalog, singleton=True))
104
+
105
+ #
106
+
107
+ els.append(tool_catalog_entries().bind_items_provider(singleton=True))
108
+
109
+ for etn in check.not_isinstance(enabled_tools or [], str):
110
+ els.append(_TOOL_BINDERS[etn]())
111
+
112
+ #
113
+
114
+ exec_stack = inj.wrapper_binder_helper(_execution.ToolUseExecutor)
115
+
116
+ els.append(exec_stack.push_bind(to_ctor=_execution.ToolUseExecutorImpl, singleton=True))
117
+
118
+ if not silent:
119
+ els.append(exec_stack.push_bind(to_ctor=_rendering.ResultRenderingToolUseExecutor, singleton=True))
120
+
121
+ if dangerous_no_confirmation:
122
+ els.append(exec_stack.push_bind(to_ctor=_rendering.ArgsRenderingToolUseExecutor, singleton=True))
123
+
124
+ els.extend([
125
+ inj.bind(_execution.ToolUseExecutor, to_key=exec_stack.top),
126
+ ])
127
+
128
+ #
129
+
130
+ if not dangerous_no_confirmation:
131
+ els.append(inj.bind(_confirmation.ToolExecutionConfirmation, to_ctor=_confirmation.InteractiveToolExecutionConfirmation, singleton=True)) # noqa
132
+
133
+ #
134
+
135
+ els.extend([
136
+ tool_context_providers().bind_items_provider(singleton=True),
137
+
138
+ inj.bind(_execution.ToolContextProvider, to_fn=lang.typed_lambda(tcps=_execution.ToolContextProviders)(
139
+ lambda tcps: _execution.ToolContextProvider(lambda: [tc for tcp in tcps for tc in tcp()]),
140
+ ), singleton=True),
141
+ ])
142
+
143
+ #
144
+
145
+ return inj.as_elements(*els)
@@ -0,0 +1,29 @@
1
+ import typing as ta
2
+
3
+ from omlish import inject as inj
4
+ from omlish import lang
5
+
6
+ from ..... import minichain as mc
7
+
8
+
9
+ with lang.auto_proxy_import(globals()):
10
+ from . import execution as _execution
11
+
12
+ ##
13
+
14
+
15
+ @lang.cached_function
16
+ def tool_catalog_entries() -> 'inj.ItemsBinderHelper[mc.ToolCatalogEntry]':
17
+ return inj.items_binder_helper[mc.ToolCatalogEntry](mc.ToolCatalogEntries)
18
+
19
+
20
+ @lang.cached_function
21
+ def tool_context_providers() -> 'inj.ItemsBinderHelper[_execution.ToolContextProvider]':
22
+ return inj.items_binder_helper[_execution.ToolContextProvider](_execution.ToolContextProviders)
23
+
24
+
25
+ def bind_tool_context_provider_to_key(key: ta.Any) -> inj.Elements:
26
+ return tool_context_providers().bind_item(to_fn=inj.KwargsTarget.of(
27
+ lambda v: _execution.ToolContextProvider(lambda: [v]),
28
+ v=key,
29
+ ), singleton=True)
@@ -0,0 +1,58 @@
1
+ import typing as ta
2
+
3
+ from ..... import minichain as mc
4
+ from ..rendering.types import ContentRendering
5
+ from .execution import ToolUseExecutor
6
+
7
+
8
+ ##
9
+
10
+
11
+ class ArgsRenderingToolUseExecutor(ToolUseExecutor):
12
+ def __init__(
13
+ self,
14
+ *,
15
+ wrapped: ToolUseExecutor,
16
+ renderer: ContentRendering,
17
+ ) -> None:
18
+ super().__init__()
19
+
20
+ self._wrapped = wrapped
21
+ self._renderer = renderer
22
+
23
+ async def execute_tool_use(
24
+ self,
25
+ use: 'mc.ToolUse',
26
+ *ctx_items: ta.Any,
27
+ ) -> 'mc.ToolUseResultMessage':
28
+ await self._renderer.render_content(mc.JsonContent(dict(
29
+ id=use.id,
30
+ name=use.name,
31
+ args=use.args,
32
+ )))
33
+
34
+ return await self._wrapped.execute_tool_use(use, *ctx_items)
35
+
36
+
37
+ class ResultRenderingToolUseExecutor(ToolUseExecutor):
38
+ def __init__(
39
+ self,
40
+ *,
41
+ wrapped: ToolUseExecutor,
42
+ renderer: ContentRendering,
43
+ ) -> None:
44
+ super().__init__()
45
+
46
+ self._wrapped = wrapped
47
+ self._renderer = renderer
48
+
49
+ async def execute_tool_use(
50
+ self,
51
+ use: 'mc.ToolUse',
52
+ *ctx_items: ta.Any,
53
+ ) -> 'mc.ToolUseResultMessage':
54
+ out = await self._wrapped.execute_tool_use(use, *ctx_items)
55
+
56
+ await self._renderer.render_content(out.tur.c)
57
+
58
+ return out
@@ -1,4 +1,4 @@
1
- from ... import minichain as mc
1
+ from ..... import minichain as mc
2
2
 
3
3
 
4
4
  ##
@@ -0,0 +1,21 @@
1
+ import dataclasses as dc
2
+
3
+ from .... import minichain as mc
4
+
5
+
6
+ ##
7
+
8
+
9
+ DEFAULT_COMPLETION_MODEL_BACKEND = 'openai'
10
+
11
+
12
+ ##
13
+
14
+
15
+ @dc.dataclass(frozen=True)
16
+ class CompletionConfig:
17
+ content: 'mc.Content'
18
+
19
+ _: dc.KW_ONLY
20
+
21
+ backend: str | None = None
@@ -0,0 +1,28 @@
1
+ from omlish import dataclasses as dc
2
+ from omlish import inject as inj
3
+ from omlish import lang
4
+
5
+ from ..base import Session
6
+ from .configs import CompletionConfig
7
+
8
+
9
+ with lang.auto_proxy_import(globals()):
10
+ from . import session as _session
11
+
12
+
13
+ ##
14
+
15
+
16
+ def bind_completion(cfg: CompletionConfig) -> inj.Elements:
17
+ els: list[inj.Elemental] = []
18
+
19
+ #
20
+
21
+ els.extend([
22
+ inj.bind(_session.CompletionSession.Config(**dc.asdict(cfg))),
23
+ inj.bind(Session, to_ctor=_session.CompletionSession, singleton=True),
24
+ ])
25
+
26
+ #
27
+
28
+ return inj.as_elements(*els)
@@ -5,22 +5,17 @@ from omlish import lang
5
5
 
6
6
  from .... import minichain as mc
7
7
  from ..base import Session
8
+ from .configs import DEFAULT_COMPLETION_MODEL_BACKEND
9
+ from .configs import CompletionConfig
8
10
 
9
11
 
10
12
  ##
11
13
 
12
14
 
13
- DEFAULT_COMPLETION_MODEL_BACKEND = 'openai'
14
-
15
-
16
15
  class CompletionSession(Session['CompletionSession.Config']):
17
16
  @dc.dataclass(frozen=True)
18
- class Config(Session.Config):
19
- content: mc.Content
20
-
21
- _: dc.KW_ONLY
22
-
23
- backend: str | None = None
17
+ class Config(Session.Config, CompletionConfig):
18
+ pass
24
19
 
25
20
  def __init__(
26
21
  self,
@@ -0,0 +1,21 @@
1
+ import dataclasses as dc
2
+
3
+ from .... import minichain as mc
4
+
5
+
6
+ ##
7
+
8
+
9
+ DEFAULT_EMBEDDING_MODEL_BACKEND = 'openai'
10
+
11
+
12
+ ##
13
+
14
+
15
+ @dc.dataclass(frozen=True)
16
+ class EmbeddingConfig:
17
+ content: 'mc.Content'
18
+
19
+ _: dc.KW_ONLY
20
+
21
+ backend: str | None = None
@@ -0,0 +1,28 @@
1
+ from omlish import dataclasses as dc
2
+ from omlish import inject as inj
3
+ from omlish import lang
4
+
5
+ from ..base import Session
6
+ from .configs import EmbeddingConfig
7
+
8
+
9
+ with lang.auto_proxy_import(globals()):
10
+ from . import session as _session
11
+
12
+
13
+ ##
14
+
15
+
16
+ def bind_embedding(cfg: EmbeddingConfig) -> inj.Elements:
17
+ els: list[inj.Elemental] = []
18
+
19
+ #
20
+
21
+ els.extend([
22
+ inj.bind(_session.EmbeddingSession.Config(**dc.asdict(cfg))),
23
+ inj.bind(Session, to_ctor=_session.EmbeddingSession, singleton=True),
24
+ ])
25
+
26
+ #
27
+
28
+ return inj.as_elements(*els)
@@ -5,22 +5,17 @@ from omlish.formats import json
5
5
 
6
6
  from .... import minichain as mc
7
7
  from ..base import Session
8
+ from .configs import DEFAULT_EMBEDDING_MODEL_BACKEND
9
+ from .configs import EmbeddingConfig
8
10
 
9
11
 
10
12
  ##
11
13
 
12
14
 
13
- DEFAULT_EMBEDDING_MODEL_BACKEND = 'openai'
14
-
15
-
16
15
  class EmbeddingSession(Session['EmbeddingSession.Config']):
17
16
  @dc.dataclass(frozen=True)
18
- class Config(Session.Config):
19
- content: mc.Content
20
-
21
- _: dc.KW_ONLY
22
-
23
- backend: str | None = None
17
+ class Config(Session.Config, EmbeddingConfig):
18
+ pass
24
19
 
25
20
  def __init__(
26
21
  self,
@@ -1,26 +1,38 @@
1
+ import typing as ta
2
+
1
3
  from omlish import inject as inj
4
+ from omlish import lang
5
+
2
6
 
3
- from .base import Session
4
- from .chat.base import ChatSession
5
- from .chat2.session import Chat2Session
7
+ with lang.auto_proxy_import(globals()):
8
+ from .chat import configs as _chat_cfgs
9
+ from .chat import inject as _chat_inj
10
+ from .completion import configs as _completion_cfgs
11
+ from .completion import inject as _completion_inj
12
+ from .embedding import configs as _embedding_cfgs
13
+ from .embedding import inject as _embedding_inj
6
14
 
7
15
 
8
16
  ##
9
17
 
10
18
 
11
- def bind_sessions(session_cfg: Session.Config) -> inj.Elements:
12
- els: list[inj.Elemental] = [
13
- inj.bind(session_cfg),
14
- inj.bind(session_cfg.configurable_cls, singleton=True),
15
- inj.bind(Session, to_key=session_cfg.configurable_cls),
16
- ]
19
+ def bind_sessions(cfg: ta.Any) -> inj.Elements:
20
+ els: list[inj.Elemental] = []
21
+
22
+ #
23
+
24
+ if isinstance(cfg, _chat_cfgs.ChatConfig):
25
+ els.append(_chat_inj.bind_chat(cfg))
26
+
27
+ elif isinstance(cfg, _completion_cfgs.CompletionConfig):
28
+ els.append(_completion_inj.bind_completion(cfg))
29
+
30
+ elif isinstance(cfg, _embedding_cfgs.EmbeddingConfig):
31
+ els.append(_embedding_inj.bind_embedding(cfg))
17
32
 
18
- if isinstance(session_cfg, ChatSession.Config):
19
- from .chat.inject import bind_chat_session
20
- els.append(bind_chat_session(session_cfg))
33
+ else:
34
+ raise TypeError(cfg)
21
35
 
22
- elif isinstance(session_cfg, Chat2Session.Config):
23
- from .chat2.inject import bind_chat
24
- els.append(bind_chat(session_cfg)) # noqa
36
+ #
25
37
 
26
38
  return inj.as_elements(*els)
@@ -0,0 +1,28 @@
1
+ import os.path
2
+
3
+ from omdev.home.paths import get_home_paths
4
+ from omlish import inject as inj
5
+ from omlish import lang
6
+
7
+
8
+ with lang.auto_proxy_import(globals()):
9
+ from . import storage as _storage
10
+
11
+
12
+ ##
13
+
14
+
15
+ def _provide_state_storage() -> '_storage.StateStorage':
16
+ state_dir = os.path.join(get_home_paths().state_dir, 'minichain', 'cli')
17
+ if not os.path.exists(state_dir):
18
+ os.makedirs(state_dir, exist_ok=True)
19
+ os.chmod(state_dir, 0o770) # noqa
20
+
21
+ state_file = os.path.join(state_dir, 'state.json')
22
+ return _storage.JsonFileStateStorage(state_file)
23
+
24
+
25
+ def bind_state() -> inj.Elements:
26
+ return inj.as_elements(
27
+ inj.bind(_provide_state_storage, singleton=True),
28
+ )
@@ -22,15 +22,14 @@ from ....chat.messages import AnyAiMessage
22
22
  from ....chat.messages import Message
23
23
  from ....chat.messages import SystemMessage
24
24
  from ....chat.messages import ToolUseMessage
25
- from ....chat.messages import ToolUseResultMessage
26
25
  from ....chat.messages import UserMessage
27
26
  from ....chat.tools.types import Tool
28
- from ....content.prepare import prepare_content_str
29
27
  from ....models.configs import ModelName
30
28
  from ....standard import ApiKey
31
- from ....tools.jsonschema import build_tool_spec_params_json_schema
32
29
  from ....tools.types import ToolUse
33
30
  from .names import MODEL_NAMES
31
+ from .protocol import build_protocol_chat_messages
32
+ from .protocol import build_protocol_tool
34
33
 
35
34
 
36
35
  ##
@@ -44,13 +43,6 @@ from .names import MODEL_NAMES
44
43
  class AnthropicChatChoicesService:
45
44
  DEFAULT_MODEL_NAME: ta.ClassVar[ModelName] = ModelName(check.not_none(MODEL_NAMES.default))
46
45
 
47
- ROLES_MAP: ta.ClassVar[ta.Mapping[type[Message], str]] = {
48
- SystemMessage: 'system',
49
- UserMessage: 'user',
50
- AiMessage: 'assistant',
51
- ToolUseMessage: 'assistant',
52
- }
53
-
54
46
  def __init__(
55
47
  self,
56
48
  *configs: ApiKey | ModelName,
@@ -78,62 +70,13 @@ class AnthropicChatChoicesService:
78
70
  *,
79
71
  max_tokens: int = 4096, # FIXME: ChatOption
80
72
  ) -> ChatChoicesResponse:
81
- messages: list[pt.Message] = []
82
- system: list[pt.Content] | None = None
83
- for i, m in enumerate(request.v):
84
- if isinstance(m, SystemMessage):
85
- if i != 0 or system is not None:
86
- raise Exception('Only supports one system message and must be first')
87
- system = [pt.Text(check.not_none(self._get_msg_content(m)))]
88
-
89
- elif isinstance(m, ToolUseResultMessage):
90
- messages.append(pt.Message(
91
- role='user',
92
- content=[pt.ToolResult(
93
- tool_use_id=check.not_none(m.tur.id),
94
- content=json.dumps_compact(msh.marshal(m.tur.c)) if not isinstance(m.tur.c, str) else m.tur.c,
95
- )],
96
- ))
97
-
98
- elif isinstance(m, AiMessage):
99
- # messages.append(pt.Message(
100
- # role=self.ROLES_MAP[type(m)], # noqa
101
- # content=[pt.Text(check.isinstance(self._get_msg_content(m), str))],
102
- # ))
103
- messages.append(pt.Message(
104
- role='assistant',
105
- content=[
106
- *([pt.Text(check.isinstance(m.c, str))] if m.c is not None else []),
107
- ],
108
- ))
109
-
110
- elif isinstance(m, ToolUseMessage):
111
- messages.append(pt.Message(
112
- role='assistant',
113
- content=[
114
- pt.ToolUse(
115
- id=check.not_none(m.tu.id),
116
- name=check.not_none(m.tu.name),
117
- input=m.tu.args,
118
- ),
119
- ],
120
- ))
121
-
122
- else:
123
- messages.append(pt.Message(
124
- role=self.ROLES_MAP[type(m)], # type: ignore[arg-type]
125
- content=[pt.Text(check.isinstance(self._get_msg_content(m), str))],
126
- ))
73
+ messages, system = build_protocol_chat_messages(request.v)
127
74
 
128
75
  tools: list[pt.ToolSpec] = []
129
76
  with tv.TypedValues(*request.options).consume() as oc:
130
77
  t: Tool
131
78
  for t in oc.pop(Tool, []):
132
- tools.append(pt.ToolSpec(
133
- name=check.not_none(t.spec.name),
134
- description=prepare_content_str(t.spec.desc),
135
- input_schema=build_tool_spec_params_json_schema(t.spec),
136
- ))
79
+ tools.append(build_protocol_tool(t))
137
80
 
138
81
  a_req = pt.MessagesRequest(
139
82
  model=MODEL_NAMES.resolve(self._model_name.v),
@@ -0,0 +1,109 @@
1
+ import typing as ta
2
+
3
+ from omlish import check
4
+ from omlish import marshal as msh
5
+ from omlish.formats import json
6
+
7
+ from .....backends.anthropic.protocol import types as pt
8
+ from ....chat.messages import AiMessage
9
+ from ....chat.messages import Message
10
+ from ....chat.messages import SystemMessage
11
+ from ....chat.messages import ToolUseMessage
12
+ from ....chat.messages import ToolUseResultMessage
13
+ from ....chat.messages import UserMessage
14
+ from ....chat.tools.types import Tool
15
+ from ....content.prepare import prepare_content_str
16
+ from ....tools.jsonschema import build_tool_spec_params_json_schema
17
+
18
+
19
+ ##
20
+
21
+
22
+ def get_message_content(m: Message) -> str | None:
23
+ if isinstance(m, AiMessage):
24
+ return check.isinstance(m.c, str)
25
+
26
+ elif isinstance(m, (UserMessage, SystemMessage)):
27
+ return check.isinstance(m.c, str)
28
+
29
+ else:
30
+ raise TypeError(m)
31
+
32
+
33
+ #
34
+
35
+
36
+ class BuiltChatMessages(ta.NamedTuple):
37
+ messages: list[pt.Message]
38
+ system: list[pt.Content] | None
39
+
40
+
41
+ ROLES_MAP: ta.Mapping[type[Message], str] = {
42
+ SystemMessage: 'system',
43
+ UserMessage: 'user',
44
+ AiMessage: 'assistant',
45
+ ToolUseMessage: 'assistant',
46
+ }
47
+
48
+
49
+ def build_protocol_chat_messages(msgs: ta.Iterable[Message]) -> BuiltChatMessages:
50
+ messages: list[pt.Message] = []
51
+ system: list[pt.Content] | None = None
52
+
53
+ for i, m in enumerate(msgs):
54
+ if isinstance(m, SystemMessage):
55
+ if i or system is not None:
56
+ raise Exception('Only supports one system message and must be first')
57
+ system = [pt.Text(check.not_none(get_message_content(m)))]
58
+
59
+ elif isinstance(m, ToolUseResultMessage):
60
+ messages.append(pt.Message(
61
+ role='user',
62
+ content=[pt.ToolResult(
63
+ tool_use_id=check.not_none(m.tur.id),
64
+ content=json.dumps_compact(msh.marshal(m.tur.c)) if not isinstance(m.tur.c, str) else m.tur.c,
65
+ )],
66
+ ))
67
+
68
+ elif isinstance(m, AiMessage):
69
+ # messages.append(pt.Message(
70
+ # role=ROLES_MAP[type(m)], # noqa
71
+ # content=[pt.Text(check.isinstance(get_message_content(m), str))],
72
+ # ))
73
+ messages.append(pt.Message(
74
+ role='assistant',
75
+ content=[
76
+ *([pt.Text(check.isinstance(m.c, str))] if m.c is not None else []),
77
+ ],
78
+ ))
79
+
80
+ elif isinstance(m, ToolUseMessage):
81
+ messages.append(pt.Message(
82
+ role='assistant',
83
+ content=[
84
+ pt.ToolUse(
85
+ id=check.not_none(m.tu.id),
86
+ name=check.not_none(m.tu.name),
87
+ input=m.tu.args,
88
+ ),
89
+ ],
90
+ ))
91
+
92
+ else:
93
+ messages.append(pt.Message(
94
+ role=ROLES_MAP[type(m)], # type: ignore[arg-type]
95
+ content=[pt.Text(check.isinstance(get_message_content(m), str))],
96
+ ))
97
+
98
+ return BuiltChatMessages(messages, system)
99
+
100
+
101
+ ##
102
+
103
+
104
+ def build_protocol_tool(t: Tool) -> pt.ToolSpec:
105
+ return pt.ToolSpec(
106
+ name=check.not_none(t.spec.name),
107
+ description=prepare_content_str(t.spec.desc),
108
+ input_schema=build_tool_spec_params_json_schema(t.spec),
109
+ )