shinychat 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {shinychat-0.1.0 → shinychat-0.2.0}/PKG-INFO +9 -8
  2. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/README.md +3 -3
  3. shinychat-0.2.0/pkg-py/src/shinychat/__init__.py +12 -0
  4. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/__version.py +16 -3
  5. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/_chat.py +87 -106
  6. shinychat-0.2.0/pkg-py/src/shinychat/_chat_normalize.py +315 -0
  7. shinychat-0.2.0/pkg-py/src/shinychat/_chat_normalize_chatlas.py +449 -0
  8. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/_chat_provider_types.py +11 -3
  9. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/_chat_types.py +9 -2
  10. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/_markdown_stream.py +9 -5
  11. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/playwright/_chat.py +42 -3
  12. shinychat-0.2.0/pkg-py/src/shinychat/types/__init__.py +10 -0
  13. shinychat-0.2.0/pkg-py/src/shinychat/www/GIT_VERSION +1 -0
  14. shinychat-0.2.0/pkg-py/src/shinychat/www/chat/chat.css +2 -0
  15. shinychat-0.2.0/pkg-py/src/shinychat/www/chat/chat.css.map +7 -0
  16. shinychat-0.2.0/pkg-py/src/shinychat/www/chat/chat.js +166 -0
  17. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/www/chat/chat.js.map +4 -4
  18. shinychat-0.2.0/pkg-py/src/shinychat/www/markdown-stream/markdown-stream.js +228 -0
  19. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/www/markdown-stream/markdown-stream.js.map +4 -4
  20. {shinychat-0.1.0 → shinychat-0.2.0}/pyproject.toml +6 -5
  21. shinychat-0.1.0/pkg-py/src/shinychat/__init__.py +0 -4
  22. shinychat-0.1.0/pkg-py/src/shinychat/_chat_normalize.py +0 -350
  23. shinychat-0.1.0/pkg-py/src/shinychat/types/__init__.py +0 -5
  24. shinychat-0.1.0/pkg-py/src/shinychat/www/GIT_VERSION +0 -1
  25. shinychat-0.1.0/pkg-py/src/shinychat/www/chat/chat.css +0 -2
  26. shinychat-0.1.0/pkg-py/src/shinychat/www/chat/chat.css.map +0 -7
  27. shinychat-0.1.0/pkg-py/src/shinychat/www/chat/chat.js +0 -87
  28. shinychat-0.1.0/pkg-py/src/shinychat/www/markdown-stream/markdown-stream.js +0 -149
  29. {shinychat-0.1.0 → shinychat-0.2.0}/.gitignore +0 -0
  30. {shinychat-0.1.0 → shinychat-0.2.0}/LICENSE +0 -0
  31. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/_chat_bookmark.py +0 -0
  32. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/_chat_tokenizer.py +0 -0
  33. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/_html_deps_py_shiny.py +0 -0
  34. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/_typing_extensions.py +0 -0
  35. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/_utils.py +0 -0
  36. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/express/__init__.py +0 -0
  37. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/playwright/__init__.py +0 -0
  38. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/py.typed +0 -0
  39. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/www/markdown-stream/markdown-stream.css +0 -0
  40. {shinychat-0.1.0 → shinychat-0.2.0}/pkg-py/src/shinychat/www/markdown-stream/markdown-stream.css.map +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinychat
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: An AI Chat interface for Shiny apps.
5
5
  Project-URL: Homepage, https://posit-dev.github.io/shinychat/
6
6
  Project-URL: Documentation, https://posit-dev.github.io/shinychat/py/
@@ -11,14 +11,15 @@ Author-email: Joe Cheng <joe@posit.co>, Carson Sievert <carson@posit.co>, Garric
11
11
  License: MIT
12
12
  License-File: LICENSE
13
13
  Requires-Python: >=3.9
14
+ Requires-Dist: chatlas>=0.12.0
14
15
  Requires-Dist: htmltools>=0.6.0
15
16
  Requires-Dist: shiny>=1.4.0
16
17
  Provides-Extra: providers
17
- Requires-Dist: anthropic; extra == 'providers'
18
- Requires-Dist: chatlas>=0.6.1; extra == 'providers'
19
- Requires-Dist: google-generativeai; (python_version >= '3.9') and extra == 'providers'
18
+ Requires-Dist: anthropic; (python_version >= '3.11') and extra == 'providers'
19
+ Requires-Dist: chatlas[mcp]>=0.11.1; extra == 'providers'
20
+ Requires-Dist: google-generativeai; extra == 'providers'
20
21
  Requires-Dist: langchain-core; extra == 'providers'
21
- Requires-Dist: ollama; extra == 'providers'
22
+ Requires-Dist: ollama>=0.4.0; extra == 'providers'
22
23
  Requires-Dist: openai; extra == 'providers'
23
24
  Requires-Dist: tokenizers; extra == 'providers'
24
25
  Provides-Extra: test
@@ -42,6 +43,8 @@ Description-Content-Type: text/markdown
42
43
 
43
44
  Chat UI component for [Shiny for Python](https://shiny.posit.co/py/).
44
45
 
46
+ **Note:** shinychat is automatically installed with Shiny for Python and available as `shiny.ui.Chat` and `shiny.express.ui.Chat`. For complete instructions about creating chatbots please see the [Shiny for Python documentation](https://shiny.posit.co/py/docs/genai-chatbots.html).
47
+
45
48
  ## Installation
46
49
 
47
50
  You can install shinychat from PyPI with:
@@ -58,8 +61,6 @@ uv pip install git+https://github.com/posit-dev/shinychat.git
58
61
 
59
62
  ## Example
60
63
 
61
- To run this example, you'll first need to create an OpenAI API key, and set it in your environment as `OPENAI_API_KEY`.
62
-
63
64
  ```r
64
65
  from shiny.express import render, ui
65
66
  from shinychat.express import Chat
@@ -67,7 +68,7 @@ from shinychat.express import Chat
67
68
  # Set some Shiny page options
68
69
  ui.page_opts(title="Hello Chat")
69
70
 
70
- # Create a chat instance, with an initial message
71
+ # Create a chat component, with an initial message
71
72
  chat = Chat(
72
73
  id="chat",
73
74
  messages=[
@@ -4,6 +4,8 @@
4
4
 
5
5
  Chat UI component for [Shiny for Python](https://shiny.posit.co/py/).
6
6
 
7
+ **Note:** shinychat is automatically installed with Shiny for Python and available as `shiny.ui.Chat` and `shiny.express.ui.Chat`. For complete instructions about creating chatbots please see the [Shiny for Python documentation](https://shiny.posit.co/py/docs/genai-chatbots.html).
8
+
7
9
  ## Installation
8
10
 
9
11
  You can install shinychat from PyPI with:
@@ -20,8 +22,6 @@ uv pip install git+https://github.com/posit-dev/shinychat.git
20
22
 
21
23
  ## Example
22
24
 
23
- To run this example, you'll first need to create an OpenAI API key, and set it in your environment as `OPENAI_API_KEY`.
24
-
25
25
  ```r
26
26
  from shiny.express import render, ui
27
27
  from shinychat.express import Chat
@@ -29,7 +29,7 @@ from shinychat.express import Chat
29
29
  # Set some Shiny page options
30
30
  ui.page_opts(title="Hello Chat")
31
31
 
32
- # Create a chat instance, with an initial message
32
+ # Create a chat component, with an initial message
33
33
  chat = Chat(
34
34
  id="chat",
35
35
  messages=[
@@ -0,0 +1,12 @@
1
+ from ._chat import Chat, chat_ui
2
+ from ._chat_normalize import message_content, message_content_chunk
3
+ from ._markdown_stream import MarkdownStream, output_markdown_stream
4
+
5
+ __all__ = [
6
+ "Chat",
7
+ "chat_ui",
8
+ "MarkdownStream",
9
+ "output_markdown_stream",
10
+ "message_content",
11
+ "message_content_chunk",
12
+ ]
@@ -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__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
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.0'
21
- __version_tuple__ = version_tuple = (0, 1, 0)
31
+ __version__ = version = '0.2.0'
32
+ __version_tuple__ = version_tuple = (0, 2, 0)
33
+
34
+ __commit_id__ = commit_id = None
@@ -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,
@@ -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 normalize_message, normalize_message_chunk
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
- A sequence of messages to display in the chat. A given message can be one of the
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
- The tokenizer to use for calculating token counts, which is required to impose
212
- `token_limits` in `.messages()`. If not provided, a default generic tokenizer
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
- input = self.user_input(transform=True)
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. The default behavior is
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
- The message format to return. The default value of `MISSING` means
503
- chat messages are returned as :class:`ChatMessage` objects (a dictionary
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
- Limit the conversation history based on token limits. If specified, only
513
- the most recent messages that fit within the token limits are returned. This
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
- Whether to return user input messages with transformation applied. This only
523
- matters if a `transform_user_input` was provided to the chat constructor.
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
- Whether to return assistant messages with transformation applied. This only
529
- matters if an `transform_assistant_response` was provided to the chat
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
@@ -636,7 +636,7 @@ class Chat:
636
636
  self._pending_messages.append((message, False, "append", None))
637
637
  return
638
638
 
639
- msg = normalize_message(message)
639
+ msg = message_content(message)
640
640
  msg = await self._transform_message(msg)
641
641
  if msg is None:
642
642
  return
@@ -753,7 +753,10 @@ class Chat:
753
753
  self._current_stream_id = stream_id
754
754
 
755
755
  # Normalize various message types into a ChatMessage()
756
- msg = normalize_message_chunk(message)
756
+ msg = message_content_chunk(message)
757
+
758
+ if is_tool_result(message):
759
+ await hide_corresponding_request(message)
757
760
 
758
761
  if operation == "replace":
759
762
  self._current_stream_message = (
@@ -1020,25 +1023,15 @@ class Chat:
1020
1023
  self, fn: TransformUserInput | TransformUserInputAsync | None = None
1021
1024
  ) -> None | Callable[[TransformUserInput | TransformUserInputAsync], None]:
1022
1025
  """
1023
- Transform user input.
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.
1026
+ Deprecated. User input transformation features will be removed in a future version.
1040
1027
  """
1041
1028
 
1029
+ warn_deprecated(
1030
+ "The `.transform_user_input` decorator is deprecated. "
1031
+ "User input transformation features will be removed in a future version. "
1032
+ "See here for more details: https://github.com/posit-dev/shinychat/pull/91"
1033
+ )
1034
+
1042
1035
  def _set_transform(fn: TransformUserInput | TransformUserInputAsync):
1043
1036
  self._transform_user = _utils.wrap_async(fn)
1044
1037
 
@@ -1062,31 +1055,15 @@ class Chat:
1062
1055
  fn: TransformAssistantResponseFunction | None = None,
1063
1056
  ) -> None | Callable[[TransformAssistantResponseFunction], None]:
1064
1057
  """
1065
- Transform assistant responses.
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.
1058
+ Deprecated. Assistant response transformation features will be removed in a future version.
1088
1059
  """
1089
1060
 
1061
+ warn_deprecated(
1062
+ "The `.transform_assistant_response` decorator is deprecated. "
1063
+ "Assistant response transformation features will be removed in a future version. "
1064
+ "See here for more details: https://github.com/posit-dev/shinychat/pull/91"
1065
+ )
1066
+
1090
1067
  def _set_transform(
1091
1068
  fn: TransformAssistantResponseFunction,
1092
1069
  ):
@@ -1303,6 +1280,14 @@ class Chat:
1303
1280
  2. Maintaining message state separately from `.messages()`.
1304
1281
 
1305
1282
  """
1283
+
1284
+ if transform:
1285
+ warn_deprecated(
1286
+ "`.user_input(transform=...)` is deprecated. "
1287
+ "User input transformation features will be removed in a future version. "
1288
+ "See here for more details: https://github.com/posit-dev/shinychat/pull/91"
1289
+ )
1290
+
1306
1291
  msg = self._latest_user_input()
1307
1292
  if msg is None:
1308
1293
  return None
@@ -1791,17 +1776,13 @@ def chat_ui(
1791
1776
  else:
1792
1777
  ui = TagList(content).render()
1793
1778
 
1794
- if role == "user":
1795
- tag_name = "shiny-user-message"
1796
- else:
1797
- tag_name = "shiny-chat-message"
1798
-
1799
1779
  message_tags.append(
1800
1780
  Tag(
1801
- tag_name,
1781
+ "shiny-chat-message",
1802
1782
  ui["dependencies"],
1803
1783
  content=ui["html"],
1804
1784
  icon=icon_attr,
1785
+ data_role=role,
1805
1786
  )
1806
1787
  )
1807
1788