Skip to content

Commit 39e2ff6

Browse files
authored
fix: message_content() and message_content_chunk() now accept ChatMessage() as input (#132)
* fix: message_content() and message_content_chunk() now accept ChatMessage() as input * Update changelog
1 parent d92f8ff commit 39e2ff6

File tree

3 files changed

+52
-2
lines changed

3 files changed

+52
-2
lines changed

pkg-py/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [UNRELEASED]
9+
10+
### Improvements
11+
12+
* `message_content()` and `message_content_chunk()` can now take `ChatMessage()` as input. (#)
13+
814
## [0.2.1] - 2025-09-10
915

1016
### New features

pkg-py/src/shinychat/_chat_normalize.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ def message_content(message):
4444
"""
4545
if isinstance(message, (str, HTML)) or message is None:
4646
return ChatMessage(content=message)
47+
if isinstance(message, ChatMessage):
48+
return message
4749
if isinstance(message, dict):
4850
if "content" not in message:
4951
raise ValueError("Message dictionary must have a 'content' key")
@@ -90,6 +92,8 @@ def message_content_chunk(chunk):
9092
"""
9193
if isinstance(chunk, (str, HTML)) or chunk is None:
9294
return ChatMessage(content=chunk)
95+
if isinstance(chunk, ChatMessage):
96+
return chunk
9397
if isinstance(chunk, dict):
9498
if "content" not in chunk:
9599
raise ValueError("Chunk dictionary must have a 'content' key")

pkg-py/tests/pytest/test_chat.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,15 +181,55 @@ def generate_content(token_count: int) -> str:
181181

182182

183183
def test_string_normalization():
184-
m = message_content_chunk("Hello world!")
184+
m = message_content("Hello world!")
185185
assert m.content == "Hello world!"
186186
assert m.role == "assistant"
187+
mc = message_content_chunk("Hello world!")
188+
assert mc.content == "Hello world!"
189+
assert mc.role == "assistant"
187190

188191

189192
def test_dict_normalization():
190-
m = message_content_chunk({"content": "Hello world!", "role": "assistant"})
193+
m = message_content({"content": "Hello world!", "role": "assistant"})
191194
assert m.content == "Hello world!"
192195
assert m.role == "assistant"
196+
mc = message_content_chunk({"content": "Hello world!"})
197+
assert mc.content == "Hello world!"
198+
assert mc.role == "assistant"
199+
200+
201+
def test_chat_message_normalization():
202+
m = message_content(ChatMessage(content="Hello world!", role="assistant"))
203+
assert m.content == "Hello world!"
204+
assert m.role == "assistant"
205+
mc = message_content_chunk(ChatMessage(content="Hello world!"))
206+
assert mc.content == "Hello world!"
207+
assert mc.role == "assistant"
208+
209+
210+
def test_tagifiable_normalization():
211+
from shiny.ui import HTML, div
212+
213+
# Interpreted as markdown (without escaping)
214+
m = message_content("Hello <span>world</span>!")
215+
assert m.content == "Hello <span>world</span>!"
216+
assert m.role == "assistant"
217+
218+
# Interpreted as HTML (without escaping)
219+
m = message_content(HTML("Hello <span>world</span>!"))
220+
assert (
221+
m.content
222+
== "\n\n````````{=html}\nHello <span>world</span>!\n````````\n\n"
223+
)
224+
assert m.role == "assistant"
225+
226+
# Interpreted as HTML (if top-level object is tag-like, inner string contents get escaped)
227+
m = message_content(div("Hello <span>world</span>!"))
228+
assert (
229+
m.content
230+
== "\n\n````````{=html}\n<div>Hello &lt;span&gt;world&lt;/span&gt;!</div>\n````````\n\n"
231+
)
232+
assert m.role == "assistant"
193233

194234

195235
def test_langchain_normalization():

0 commit comments

Comments
 (0)