Skip to content

Commit 52088cf

Browse files
authored
bug: Use repr() for chat tool calls when exporting markdown (#30)
1 parent 6e3a58e commit 52088cf

File tree

5 files changed

+35
-14
lines changed

5 files changed

+35
-14
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
77
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88
-->
99

10+
## [Development]
11+
12+
### New features
13+
14+
### Bug fixes
15+
16+
* Update formatting of exported markdown to use `repr()` instead of `str()` when exporting tool call results. (#30)
17+
1018
## [0.3.0] - 2024-12-20
1119

1220
### New features
@@ -23,4 +31,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2331

2432
## [0.2.0] - 2024-12-11
2533

26-
First stable release of `chatlas`, see the website to learn more <https://posit-dev.github.io/chatlas/>
34+
First stable release of `chatlas`, see the website to learn more <https://posit-dev.github.io/chatlas/>

chatlas/_chat.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -948,11 +948,11 @@ def export(
948948
is_html = filename.suffix == ".html"
949949

950950
# Get contents from each turn
951-
contents = ""
951+
content_arr: list[str] = []
952952
for turn in turns:
953953
turn_content = "\n\n".join(
954954
[
955-
str(content)
955+
str(content).strip()
956956
for content in turn.contents
957957
if include == "all" or isinstance(content, ContentText)
958958
]
@@ -963,7 +963,8 @@ def export(
963963
turn_content = f"<shiny-{msg_type}-message content='{content_attr}'></shiny-{msg_type}-message>"
964964
else:
965965
turn_content = f"## {turn.role.capitalize()}\n\n{turn_content}"
966-
contents += f"{turn_content}\n\n"
966+
content_arr.append(turn_content)
967+
contents = "\n\n".join(content_arr)
967968

968969
# Shiny chat message components requires container elements
969970
if is_html:

chatlas/_content.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import json
44
from dataclasses import dataclass
5+
from pprint import pformat
56
from typing import Any, Literal, Optional
67

78
ImageContentTypes = Literal[
@@ -154,7 +155,7 @@ def __str__(self):
154155
args_str = self._arguments_str()
155156
func_call = f"{self.name}({args_str})"
156157
comment = f"# tool request ({self.id})"
157-
return f"\n```python\n{comment}\n{func_call}\n```\n"
158+
return f"```python\n{comment}\n{func_call}\n```\n"
158159

159160
def _repr_markdown_(self):
160161
return self.__str__()
@@ -195,10 +196,20 @@ class ContentToolResult(Content):
195196
value: Any = None
196197
error: Optional[str] = None
197198

199+
def _get_value_and_language(self) -> tuple[str, str]:
200+
if self.error:
201+
return f"Tool calling failed with error: '{self.error}'", ""
202+
try:
203+
json_val = json.loads(self.value)
204+
return pformat(json_val, indent=2, sort_dicts=False), "python"
205+
except: # noqa: E722
206+
return str(self.value), ""
207+
198208
def __str__(self):
199209
comment = f"# tool result ({self.id})"
200-
val = self.get_final_value()
201-
return f"""\n```python\n{comment}\n"{val}"\n```\n"""
210+
value, language = self._get_value_and_language()
211+
212+
return f"""```{language}\n{comment}\n{value}\n```"""
202213

203214
def _repr_markdown_(self):
204215
return self.__str__()
@@ -211,9 +222,8 @@ def __repr__(self, indent: int = 0):
211222
return res + ">"
212223

213224
def get_final_value(self) -> str:
214-
if self.error:
215-
return f"Tool calling failed with error: '{self.error}'"
216-
return str(self.value)
225+
value, _language = self._get_value_and_language()
226+
return value
217227

218228

219229
@dataclass
@@ -236,7 +246,7 @@ def __str__(self):
236246
return json.dumps(self.value, indent=2)
237247

238248
def _repr_markdown_(self):
239-
return f"""\n```json\n{self.__str__()}\n```\n"""
249+
return f"""```json\n{self.__str__()}\n```"""
240250

241251
def __repr__(self, indent: int = 0):
242252
return " " * indent + f"<ContentJson value={self.value}>"

tests/__snapshots__/test_chat.ambr

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
<shiny-user-message content='What&apos;s 1 + 1? What&apos;s 1 + 2?'></shiny-user-message>
1818

1919
<shiny-chat-message content='2 3'></shiny-chat-message>
20-
21-
2220
</shiny-chat-messages></shiny-chat-container>
2321
<br><br>
2422
<details><summary>System prompt</summary>

tests/test_provider_openai.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
23
from chatlas import ChatOpenAI
34

45
from .conftest import (
@@ -21,7 +22,10 @@ def test_openai_simple_request():
2122
chat.chat("What is 1 + 1?")
2223
turn = chat.get_last_turn()
2324
assert turn is not None
24-
assert turn.tokens == (27, 2)
25+
assert turn.tokens is not None
26+
assert len(turn.tokens) == 2
27+
assert turn.tokens[0] == 27
28+
# Not testing turn.tokens[1] because it's not deterministic. Typically 1 or 2.
2529
assert turn.finish_reason == "stop"
2630

2731

0 commit comments

Comments
 (0)