shinychat 0.1.0__py3-none-any.whl → 0.2.1__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.
- shinychat/__init__.py +9 -1
- shinychat/__version.py +16 -3
- shinychat/_chat.py +101 -133
- shinychat/_chat_normalize.py +252 -287
- shinychat/_chat_normalize_chatlas.py +449 -0
- shinychat/_chat_provider_types.py +11 -3
- shinychat/_chat_types.py +17 -6
- shinychat/_markdown_stream.py +9 -5
- shinychat/playwright/_chat.py +42 -3
- shinychat/types/__init__.py +6 -1
- shinychat/www/GIT_VERSION +1 -1
- shinychat/www/chat/chat.css +1 -1
- shinychat/www/chat/chat.css.map +2 -2
- shinychat/www/chat/chat.js +101 -22
- shinychat/www/chat/chat.js.map +4 -4
- shinychat/www/markdown-stream/markdown-stream.js +125 -46
- shinychat/www/markdown-stream/markdown-stream.js.map +4 -4
- {shinychat-0.1.0.dist-info → shinychat-0.2.1.dist-info}/METADATA +9 -8
- shinychat-0.2.1.dist-info/RECORD +31 -0
- shinychat-0.1.0.dist-info/RECORD +0 -30
- {shinychat-0.1.0.dist-info → shinychat-0.2.1.dist-info}/WHEEL +0 -0
- {shinychat-0.1.0.dist-info → shinychat-0.2.1.dist-info}/licenses/LICENSE +0 -0
shinychat/__init__.py
CHANGED
@@ -1,4 +1,12 @@
|
|
1
1
|
from ._chat import Chat, chat_ui
|
2
|
+
from ._chat_normalize import message_content, message_content_chunk
|
2
3
|
from ._markdown_stream import MarkdownStream, output_markdown_stream
|
3
4
|
|
4
|
-
__all__ = [
|
5
|
+
__all__ = [
|
6
|
+
"Chat",
|
7
|
+
"chat_ui",
|
8
|
+
"MarkdownStream",
|
9
|
+
"output_markdown_stream",
|
10
|
+
"message_content",
|
11
|
+
"message_content_chunk",
|
12
|
+
]
|
shinychat/__version.py
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
# file generated by setuptools-scm
|
2
2
|
# don't change, don't track in version control
|
3
3
|
|
4
|
-
__all__ = [
|
4
|
+
__all__ = [
|
5
|
+
"__version__",
|
6
|
+
"__version_tuple__",
|
7
|
+
"version",
|
8
|
+
"version_tuple",
|
9
|
+
"__commit_id__",
|
10
|
+
"commit_id",
|
11
|
+
]
|
5
12
|
|
6
13
|
TYPE_CHECKING = False
|
7
14
|
if TYPE_CHECKING:
|
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
|
|
9
16
|
from typing import Union
|
10
17
|
|
11
18
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
19
|
+
COMMIT_ID = Union[str, None]
|
12
20
|
else:
|
13
21
|
VERSION_TUPLE = object
|
22
|
+
COMMIT_ID = object
|
14
23
|
|
15
24
|
version: str
|
16
25
|
__version__: str
|
17
26
|
__version_tuple__: VERSION_TUPLE
|
18
27
|
version_tuple: VERSION_TUPLE
|
28
|
+
commit_id: COMMIT_ID
|
29
|
+
__commit_id__: COMMIT_ID
|
19
30
|
|
20
|
-
__version__ = version = '0.1
|
21
|
-
__version_tuple__ = version_tuple = (0,
|
31
|
+
__version__ = version = '0.2.1'
|
32
|
+
__version_tuple__ = version_tuple = (0, 2, 1)
|
33
|
+
|
34
|
+
__commit_id__ = commit_id = None
|
shinychat/_chat.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import inspect
|
4
|
+
import warnings
|
4
5
|
from contextlib import asynccontextmanager
|
5
6
|
from typing import (
|
6
7
|
TYPE_CHECKING,
|
@@ -21,7 +22,7 @@ from weakref import WeakValueDictionary
|
|
21
22
|
|
22
23
|
from htmltools import (
|
23
24
|
HTML,
|
24
|
-
|
25
|
+
HTMLDependency,
|
25
26
|
Tag,
|
26
27
|
TagAttrValue,
|
27
28
|
TagChild,
|
@@ -52,9 +53,10 @@ from ._chat_bookmark import (
|
|
52
53
|
is_chatlas_chat_client,
|
53
54
|
set_chatlas_state,
|
54
55
|
)
|
55
|
-
from ._chat_normalize import
|
56
|
+
from ._chat_normalize import message_content, message_content_chunk
|
57
|
+
from ._chat_normalize_chatlas import hide_corresponding_request, is_tool_result
|
56
58
|
from ._chat_provider_types import (
|
57
|
-
AnthropicMessage,
|
59
|
+
AnthropicMessage, # pyright: ignore[reportAttributeAccessIssue]
|
58
60
|
GoogleMessage,
|
59
61
|
LangChainMessage,
|
60
62
|
OllamaMessage,
|
@@ -182,21 +184,7 @@ class Chat:
|
|
182
184
|
A unique identifier for the chat session. In Shiny Core, make sure this id
|
183
185
|
matches a corresponding :func:`~shiny.ui.chat_ui` call in the UI.
|
184
186
|
messages
|
185
|
-
|
186
|
-
following:
|
187
|
-
|
188
|
-
* A string, which is interpreted as markdown and rendered to HTML on the client.
|
189
|
-
* To prevent interpreting as markdown, mark the string as
|
190
|
-
:class:`~shiny.ui.HTML`.
|
191
|
-
* A UI element (specifically, a :class:`~shiny.ui.TagChild`).
|
192
|
-
* This includes :class:`~shiny.ui.TagList`, which take UI elements
|
193
|
-
(including strings) as children. In this case, strings are still
|
194
|
-
interpreted as markdown as long as they're not inside HTML.
|
195
|
-
* A dictionary with `content` and `role` keys. The `content` key can contain a
|
196
|
-
content as described above, and the `role` key can be "assistant" or "user".
|
197
|
-
|
198
|
-
**NOTE:** content may include specially formatted **input suggestion** links
|
199
|
-
(see `.append_message()` for more information).
|
187
|
+
Deprecated. Use `chat.ui(messages=...)` instead.
|
200
188
|
on_error
|
201
189
|
How to handle errors that occur in response to user input. When `"unhandled"`,
|
202
190
|
the app will stop running when an error occurs. Otherwise, a notification
|
@@ -208,11 +196,8 @@ class Chat:
|
|
208
196
|
* `"sanitize"`: Sanitize the error message before displaying it to the user.
|
209
197
|
* `"unhandled"`: Do not display any error message to the user.
|
210
198
|
tokenizer
|
211
|
-
|
212
|
-
|
213
|
-
is attempted to be loaded from the tokenizers library. A specific tokenizer
|
214
|
-
may also be provided by following the `TokenEncoding` (tiktoken or tozenizers)
|
215
|
-
protocol (e.g., `tiktoken.encoding_for_model("gpt-4o")`).
|
199
|
+
Deprecated. Token counting and message trimming features will be removed in a
|
200
|
+
future version.
|
216
201
|
"""
|
217
202
|
|
218
203
|
def __init__(
|
@@ -226,6 +211,17 @@ class Chat:
|
|
226
211
|
if not isinstance(id, str):
|
227
212
|
raise TypeError("`id` must be a string.")
|
228
213
|
|
214
|
+
if messages:
|
215
|
+
warn_deprecated(
|
216
|
+
"`Chat(messages=...)` is deprecated. Use `.ui(messages=...)` instead."
|
217
|
+
)
|
218
|
+
|
219
|
+
if tokenizer is not None:
|
220
|
+
warn_deprecated(
|
221
|
+
"`Chat(tokenizer=...)` is deprecated. "
|
222
|
+
"This is only relevant for `.messages(token_limits=...)` which is also now deprecated."
|
223
|
+
)
|
224
|
+
|
229
225
|
self.id = resolve_id(id)
|
230
226
|
self.user_input_id = ResolvedId(f"{self.id}_user_input")
|
231
227
|
self._transform_user: TransformUserInputAsync | None = None
|
@@ -381,7 +377,9 @@ class Chat:
|
|
381
377
|
"A on_user_submit function should not take more than 1 argument"
|
382
378
|
)
|
383
379
|
elif len(fn_params) == 1:
|
384
|
-
|
380
|
+
with warnings.catch_warnings():
|
381
|
+
warnings.simplefilter("ignore")
|
382
|
+
input = self.user_input(transform=True)
|
385
383
|
# The line immediately below handles the possibility of input
|
386
384
|
# being transformed to None. Technically, input should never be
|
387
385
|
# None at this point (since the handler should be suspended).
|
@@ -486,48 +484,22 @@ class Chat:
|
|
486
484
|
"""
|
487
485
|
Reactively read chat messages
|
488
486
|
|
489
|
-
Obtain chat messages within a reactive context.
|
490
|
-
intended for passing messages along to a model for response generation where
|
491
|
-
you typically want to:
|
492
|
-
|
493
|
-
1. Cap the number of tokens sent in a single request (i.e., `token_limits`).
|
494
|
-
2. Apply user input transformations (i.e., `transform_user`), if any.
|
495
|
-
3. Not apply assistant response transformations (i.e., `transform_assistant`)
|
496
|
-
since these are predominantly for display purposes (i.e., the model shouldn't
|
497
|
-
concern itself with how the responses are displayed).
|
487
|
+
Obtain chat messages within a reactive context.
|
498
488
|
|
499
489
|
Parameters
|
500
490
|
----------
|
501
491
|
format
|
502
|
-
|
503
|
-
|
504
|
-
with `content` and `role` keys). Other supported formats include:
|
505
|
-
|
506
|
-
* `"anthropic"`: Anthropic message format.
|
507
|
-
* `"google"`: Google message (aka content) format.
|
508
|
-
* `"langchain"`: LangChain message format.
|
509
|
-
* `"openai"`: OpenAI message format.
|
510
|
-
* `"ollama"`: Ollama message format.
|
492
|
+
Deprecated. Provider-specific message formatting will be removed in a future
|
493
|
+
version.
|
511
494
|
token_limits
|
512
|
-
|
513
|
-
|
514
|
-
is useful for avoiding "exceeded token limit" errors when sending messages
|
515
|
-
to the relevant model, while still providing the most recent context available.
|
516
|
-
A specified value must be a tuple of two integers. The first integer is the
|
517
|
-
maximum number of tokens that can be sent to the model in a single request.
|
518
|
-
The second integer is the amount of tokens to reserve for the model's response.
|
519
|
-
Note that token counts based on the `tokenizer` provided to the `Chat`
|
520
|
-
constructor.
|
495
|
+
Deprecated. Token counting and message trimming features will be removed in
|
496
|
+
a future version.
|
521
497
|
transform_user
|
522
|
-
|
523
|
-
|
524
|
-
The default value of `"all"` means all user input messages are transformed.
|
525
|
-
The value of `"last"` means only the last user input message is transformed.
|
526
|
-
The value of `"none"` means no user input messages are transformed.
|
498
|
+
Deprecated. Message transformation features will be removed in a future
|
499
|
+
version.
|
527
500
|
transform_assistant
|
528
|
-
|
529
|
-
|
530
|
-
constructor.
|
501
|
+
Deprecated. Message transformation features will be removed in a future
|
502
|
+
version.
|
531
503
|
|
532
504
|
Note
|
533
505
|
----
|
@@ -541,6 +513,34 @@ class Chat:
|
|
541
513
|
A tuple of chat messages.
|
542
514
|
"""
|
543
515
|
|
516
|
+
if not isinstance(format, MISSING_TYPE):
|
517
|
+
warn_deprecated(
|
518
|
+
"`.messages(format=...)` is deprecated. "
|
519
|
+
"Provider-specific message formatting will be removed in a future version. "
|
520
|
+
"See here for more details: https://github.com/posit-dev/shinychat/pull/91"
|
521
|
+
)
|
522
|
+
|
523
|
+
if token_limits is not None:
|
524
|
+
warn_deprecated(
|
525
|
+
"`.messages(token_limits=...)` is deprecated. "
|
526
|
+
"Token counting and message trimming features will be removed in a future version. "
|
527
|
+
"See here for more details: https://github.com/posit-dev/shinychat/pull/91"
|
528
|
+
)
|
529
|
+
|
530
|
+
if transform_user != "all":
|
531
|
+
warn_deprecated(
|
532
|
+
"`.messages(transform_user=...)` is deprecated. "
|
533
|
+
"Message transformation features will be removed in a future version. "
|
534
|
+
"See here for more details: https://github.com/posit-dev/shinychat/pull/91"
|
535
|
+
)
|
536
|
+
|
537
|
+
if transform_assistant:
|
538
|
+
warn_deprecated(
|
539
|
+
"`.messages(transform_assistant=...)` is deprecated. "
|
540
|
+
"Message transformation features will be removed in a future version. "
|
541
|
+
"See here for more details: https://github.com/posit-dev/shinychat/pull/91"
|
542
|
+
)
|
543
|
+
|
544
544
|
messages = self._messages()
|
545
545
|
|
546
546
|
# Anthropic requires a user message first and no system messages
|
@@ -595,6 +595,7 @@ class Chat:
|
|
595
595
|
* A dictionary with `content` and `role` keys. The `content` key can contain
|
596
596
|
content as described above, and the `role` key can be "assistant" or
|
597
597
|
"user".
|
598
|
+
* More generally, any type registered with :func:`shinychat.message_content`.
|
598
599
|
|
599
600
|
**NOTE:** content may include specially formatted **input suggestion** links
|
600
601
|
(see note below).
|
@@ -636,7 +637,7 @@ class Chat:
|
|
636
637
|
self._pending_messages.append((message, False, "append", None))
|
637
638
|
return
|
638
639
|
|
639
|
-
msg =
|
640
|
+
msg = message_content(message)
|
640
641
|
msg = await self._transform_message(msg)
|
641
642
|
if msg is None:
|
642
643
|
return
|
@@ -753,7 +754,10 @@ class Chat:
|
|
753
754
|
self._current_stream_id = stream_id
|
754
755
|
|
755
756
|
# Normalize various message types into a ChatMessage()
|
756
|
-
msg =
|
757
|
+
msg = message_content_chunk(message)
|
758
|
+
|
759
|
+
if is_tool_result(message):
|
760
|
+
await hide_corresponding_request(message)
|
757
761
|
|
758
762
|
if operation == "replace":
|
759
763
|
self._current_stream_message = (
|
@@ -826,6 +830,7 @@ class Chat:
|
|
826
830
|
* A dictionary with `content` and `role` keys. The `content` key can contain
|
827
831
|
content as described above, and the `role` key can be "assistant" or
|
828
832
|
"user".
|
833
|
+
* More generally, any type registered with :func:`shinychat.message_content_chunk`.
|
829
834
|
|
830
835
|
**NOTE:** content may include specially formatted **input suggestion** links
|
831
836
|
(see note below).
|
@@ -1020,25 +1025,15 @@ class Chat:
|
|
1020
1025
|
self, fn: TransformUserInput | TransformUserInputAsync | None = None
|
1021
1026
|
) -> None | Callable[[TransformUserInput | TransformUserInputAsync], None]:
|
1022
1027
|
"""
|
1023
|
-
|
1024
|
-
|
1025
|
-
Use this method as a decorator on a function (`fn`) that transforms user input
|
1026
|
-
before storing it in the chat messages returned by `.messages()`. This is
|
1027
|
-
useful for implementing RAG workflows, like taking a URL and scraping it for
|
1028
|
-
text before sending it to the model.
|
1029
|
-
|
1030
|
-
Parameters
|
1031
|
-
----------
|
1032
|
-
fn
|
1033
|
-
A function to transform user input before storing it in the chat
|
1034
|
-
`.messages()`. If `fn` returns `None`, the user input is effectively
|
1035
|
-
ignored, and `.on_user_submit()` callbacks are suspended until more input is
|
1036
|
-
submitted. This behavior is often useful to catch and handle errors that
|
1037
|
-
occur during transformation. In this case, the transform function should
|
1038
|
-
append an error message to the chat (via `.append_message()`) to inform the
|
1039
|
-
user of the error.
|
1028
|
+
Deprecated. User input transformation features will be removed in a future version.
|
1040
1029
|
"""
|
1041
1030
|
|
1031
|
+
warn_deprecated(
|
1032
|
+
"The `.transform_user_input` decorator is deprecated. "
|
1033
|
+
"User input transformation features will be removed in a future version. "
|
1034
|
+
"See here for more details: https://github.com/posit-dev/shinychat/pull/91"
|
1035
|
+
)
|
1036
|
+
|
1042
1037
|
def _set_transform(fn: TransformUserInput | TransformUserInputAsync):
|
1043
1038
|
self._transform_user = _utils.wrap_async(fn)
|
1044
1039
|
|
@@ -1062,31 +1057,15 @@ class Chat:
|
|
1062
1057
|
fn: TransformAssistantResponseFunction | None = None,
|
1063
1058
|
) -> None | Callable[[TransformAssistantResponseFunction], None]:
|
1064
1059
|
"""
|
1065
|
-
|
1066
|
-
|
1067
|
-
Use this method as a decorator on a function (`fn`) that transforms assistant
|
1068
|
-
responses before displaying them in the chat. This is useful for post-processing
|
1069
|
-
model responses before displaying them to the user.
|
1070
|
-
|
1071
|
-
Parameters
|
1072
|
-
----------
|
1073
|
-
fn
|
1074
|
-
A function that takes a string and returns either a string,
|
1075
|
-
:class:`shiny.ui.HTML`, or `None`. If `fn` returns a string, it gets
|
1076
|
-
interpreted and parsed as a markdown on the client (and the resulting HTML
|
1077
|
-
is then sanitized). If `fn` returns :class:`shiny.ui.HTML`, it will be
|
1078
|
-
displayed as-is. If `fn` returns `None`, the response is effectively ignored.
|
1079
|
-
|
1080
|
-
Note
|
1081
|
-
----
|
1082
|
-
When doing an `.append_message_stream()`, `fn` gets called on every chunk of the
|
1083
|
-
response (thus, it should be performant), and can optionally access more
|
1084
|
-
information (i.e., arguments) about the stream. The 1st argument (required)
|
1085
|
-
contains the accumulated content, the 2nd argument (optional) contains the
|
1086
|
-
current chunk, and the 3rd argument (optional) is a boolean indicating whether
|
1087
|
-
this chunk is the last one in the stream.
|
1060
|
+
Deprecated. Assistant response transformation features will be removed in a future version.
|
1088
1061
|
"""
|
1089
1062
|
|
1063
|
+
warn_deprecated(
|
1064
|
+
"The `.transform_assistant_response` decorator is deprecated. "
|
1065
|
+
"Assistant response transformation features will be removed in a future version. "
|
1066
|
+
"See here for more details: https://github.com/posit-dev/shinychat/pull/91"
|
1067
|
+
)
|
1068
|
+
|
1090
1069
|
def _set_transform(
|
1091
1070
|
fn: TransformAssistantResponseFunction,
|
1092
1071
|
):
|
@@ -1303,6 +1282,14 @@ class Chat:
|
|
1303
1282
|
2. Maintaining message state separately from `.messages()`.
|
1304
1283
|
|
1305
1284
|
"""
|
1285
|
+
|
1286
|
+
if transform:
|
1287
|
+
warn_deprecated(
|
1288
|
+
"`.user_input(transform=...)` is deprecated. "
|
1289
|
+
"User input transformation features will be removed in a future version. "
|
1290
|
+
"See here for more details: https://github.com/posit-dev/shinychat/pull/91"
|
1291
|
+
)
|
1292
|
+
|
1306
1293
|
msg = self._latest_user_input()
|
1307
1294
|
if msg is None:
|
1308
1295
|
return None
|
@@ -1603,7 +1590,9 @@ class ChatExpress(Chat):
|
|
1603
1590
|
def ui(
|
1604
1591
|
self,
|
1605
1592
|
*,
|
1606
|
-
messages: Optional[
|
1593
|
+
messages: Optional[
|
1594
|
+
Iterable[str | TagChild | ChatMessageDict | ChatMessage | Any]
|
1595
|
+
] = None,
|
1607
1596
|
placeholder: str = "Enter a message...",
|
1608
1597
|
width: CssUnit = "min(680px, 100%)",
|
1609
1598
|
height: CssUnit = "auto",
|
@@ -1705,12 +1694,14 @@ class ChatExpress(Chat):
|
|
1705
1694
|
def chat_ui(
|
1706
1695
|
id: str,
|
1707
1696
|
*,
|
1708
|
-
messages: Optional[
|
1697
|
+
messages: Optional[
|
1698
|
+
Iterable[str | TagChild | ChatMessageDict | ChatMessage | Any]
|
1699
|
+
] = None,
|
1709
1700
|
placeholder: str = "Enter a message...",
|
1710
1701
|
width: CssUnit = "min(680px, 100%)",
|
1711
1702
|
height: CssUnit = "auto",
|
1712
1703
|
fill: bool = True,
|
1713
|
-
icon_assistant: HTML | Tag | TagList
|
1704
|
+
icon_assistant: Optional[HTML | Tag | TagList] = None,
|
1714
1705
|
**kwargs: TagAttrValue,
|
1715
1706
|
) -> Tag:
|
1716
1707
|
"""
|
@@ -1737,6 +1728,7 @@ def chat_ui(
|
|
1737
1728
|
interpreted as markdown as long as they're not inside HTML.
|
1738
1729
|
* A dictionary with `content` and `role` keys. The `content` key can contain a
|
1739
1730
|
content as described above, and the `role` key can be "assistant" or "user".
|
1731
|
+
* More generally, any type registered with :func:`shinychat.message_content`.
|
1740
1732
|
|
1741
1733
|
**NOTE:** content may include specially formatted **input suggestion** links
|
1742
1734
|
(see :method:`~shiny.ui.Chat.append_message` for more info).
|
@@ -1770,38 +1762,14 @@ def chat_ui(
|
|
1770
1762
|
if messages is None:
|
1771
1763
|
messages = []
|
1772
1764
|
for x in messages:
|
1773
|
-
|
1774
|
-
content: TagChild = None
|
1775
|
-
if not isinstance(x, dict):
|
1776
|
-
content = x
|
1777
|
-
else:
|
1778
|
-
if "content" not in x:
|
1779
|
-
raise ValueError(
|
1780
|
-
"Each message dictionary must have a 'content' key."
|
1781
|
-
)
|
1782
|
-
|
1783
|
-
content = x["content"]
|
1784
|
-
if "role" in x:
|
1785
|
-
role = x["role"]
|
1786
|
-
|
1787
|
-
# `content` is most likely a string, so avoid overhead in that case
|
1788
|
-
# (it's also important that we *don't escape HTML* here).
|
1789
|
-
if isinstance(content, str):
|
1790
|
-
ui: RenderedHTML = {"html": content, "dependencies": []}
|
1791
|
-
else:
|
1792
|
-
ui = TagList(content).render()
|
1793
|
-
|
1794
|
-
if role == "user":
|
1795
|
-
tag_name = "shiny-user-message"
|
1796
|
-
else:
|
1797
|
-
tag_name = "shiny-chat-message"
|
1798
|
-
|
1765
|
+
msg = message_content(x)
|
1799
1766
|
message_tags.append(
|
1800
1767
|
Tag(
|
1801
|
-
|
1802
|
-
|
1803
|
-
content=
|
1768
|
+
"shiny-chat-message",
|
1769
|
+
*[HTMLDependency(**d) for d in msg.html_deps],
|
1770
|
+
content=msg.content,
|
1804
1771
|
icon=icon_attr,
|
1772
|
+
data_role=msg.role,
|
1805
1773
|
)
|
1806
1774
|
)
|
1807
1775
|
|