Skip to content

Commit f838f16

Browse files
feat(py): pass pathlib.Path object directly without.read_text() (#44)
Co-authored-by: Barret Schloerke <[email protected]>
1 parent 100c300 commit f838f16

File tree

4 files changed

+69
-45
lines changed

4 files changed

+69
-45
lines changed

pkg-py/docs/index.qmd

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Currently, querychat uses DuckDB for its SQL engine. It's extremely fast and has
5959

6060
### Provide a greeting (recommended)
6161

62-
When the querychat UI first appears, you will usually want it to greet the user with some basic instructions. By default, these instructions are auto-generated every time a user arrives; this is slow, wasteful, and unpredictable. Instead, you should create a file called `greeting.md`, and when calling `querychat.init`, pass `greeting=Path("greeting.md").read_text()`.
62+
When the querychat UI first appears, you will usually want it to greet the user with some basic instructions. By default, these instructions are auto-generated every time a user arrives; this is slow, wasteful, and unpredictable. Instead, you should create a file called `greeting.md`, and when calling `querychat.init`, pass `greeting=Path("greeting.md")`.
6363

6464
You can provide suggestions to the user by using the `<span class="suggestion"> </span>` tag.
6565

@@ -141,7 +141,7 @@ which you can then pass via:
141141
querychat_config = querychat.init(
142142
titanic,
143143
"titanic",
144-
data_description=Path("data_description.md").read_text()
144+
data_description=Path("data_description.md")
145145
)
146146
```
147147

@@ -163,7 +163,7 @@ querychat_config = querychat.init(
163163
)
164164
```
165165

166-
You can also put these instructions in a separate file and use `Path("instructions.md").read_text()` to load them, as we did for `data_description` above.
166+
You can also put these instructions in a separate file and use `Path("instructions.md")` to load them, as we did for `data_description` above.
167167

168168
**Warning:** It is not 100% guaranteed that the LLM will always—or in many cases, ever—obey your instructions, and it can be difficult to predict which instructions will be a problem. So be sure to test extensively each time you change your instructions, and especially, if you change the model you use.
169169

pkg-py/examples/app-database-sqlite.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
from pathlib import Path
22

33
import chatlas
4+
import querychat as qc
45
from seaborn import load_dataset
56
from shiny import App, render, ui
67
from sqlalchemy import create_engine
78

8-
import querychat as qc
9-
109
# Load titanic data and create SQLite database
1110
db_path = Path(__file__).parent / "titanic.db"
1211
engine = create_engine("sqlite:///" + str(db_path))
@@ -17,8 +16,8 @@
1716
titanic = load_dataset("titanic")
1817
titanic.to_sql("titanic", engine, if_exists="replace", index=False)
1918

20-
greeting = (Path(__file__).parent / "greeting.md").read_text()
21-
data_desc = (Path(__file__).parent / "data_description.md").read_text()
19+
greeting = Path(__file__).parent / "greeting.md"
20+
data_desc = Path(__file__).parent / "data_description.md"
2221

2322
# 1. Configure querychat
2423

pkg-py/examples/app-dataframe-pandas.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
from pathlib import Path
22

33
import chatlas
4+
import querychat as qc
45
from seaborn import load_dataset
56
from shiny import App, render, ui
67

7-
import querychat as qc
8-
98
titanic = load_dataset("titanic")
109

11-
greeting = (Path(__file__).parent / "greeting.md").read_text()
12-
data_desc = (Path(__file__).parent / "data_description.md").read_text()
10+
greeting = Path(__file__).parent / "greeting.md"
11+
data_desc = Path(__file__).parent / "data_description.md"
1312

1413
# 1. Configure querychat
1514

pkg-py/src/querychat/querychat.py

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

33
import re
44
import sys
5+
from dataclasses import dataclass
56
from functools import partial
67
from pathlib import Path
78
from typing import TYPE_CHECKING, Any, Callable, Optional, Protocol, Union
@@ -23,22 +24,16 @@ class CreateChatCallback(Protocol):
2324
def __call__(self, system_prompt: str) -> chatlas.Chat: ...
2425

2526

27+
@dataclass
2628
class QueryChatConfig:
2729
"""
2830
Configuration class for querychat.
2931
"""
3032

31-
def __init__(
32-
self,
33-
data_source: DataSource,
34-
system_prompt: str,
35-
greeting: Optional[str],
36-
create_chat_callback: CreateChatCallback,
37-
):
38-
self.data_source = data_source
39-
self.system_prompt = system_prompt
40-
self.greeting = greeting
41-
self.create_chat_callback = create_chat_callback
33+
data_source: DataSource
34+
system_prompt: str
35+
greeting: Optional[str]
36+
create_chat_callback: CreateChatCallback
4237

4338

4439
class QueryChat:
@@ -140,8 +135,9 @@ def __getitem__(self, key: str) -> Any:
140135

141136
def system_prompt(
142137
data_source: DataSource,
143-
data_description: Optional[str] = None,
144-
extra_instructions: Optional[str] = None,
138+
*,
139+
data_description: Optional[str | Path] = None,
140+
extra_instructions: Optional[str | Path] = None,
145141
categorical_threshold: int = 10,
146142
prompt_path: Optional[Path] = None,
147143
) -> str:
@@ -179,15 +175,27 @@ def system_prompt(
179175

180176
prompt_text = prompt_path.read_text()
181177

178+
data_description_str: str | None = (
179+
data_description.read_text()
180+
if isinstance(data_description, Path)
181+
else data_description
182+
)
183+
184+
extra_instructions_str: str | None = (
185+
extra_instructions.read_text()
186+
if isinstance(extra_instructions, Path)
187+
else extra_instructions
188+
)
189+
182190
return chevron.render(
183191
prompt_text,
184192
{
185193
"db_engine": data_source.db_engine,
186194
"schema": data_source.get_schema(
187195
categorical_threshold=categorical_threshold,
188196
),
189-
"data_description": data_description,
190-
"extra_instructions": extra_instructions,
197+
"data_description": data_description_str,
198+
"extra_instructions": extra_instructions_str,
191199
},
192200
)
193201

@@ -232,11 +240,10 @@ def df_to_html(df: IntoFrame, maxrows: int = 5) -> str:
232240
def init(
233241
data_source: IntoFrame | sqlalchemy.Engine,
234242
table_name: str,
235-
/,
236243
*,
237-
greeting: Optional[str] = None,
238-
data_description: Optional[str] = None,
239-
extra_instructions: Optional[str] = None,
244+
greeting: Optional[str | Path] = None,
245+
data_description: Optional[str | Path] = None,
246+
extra_instructions: Optional[str | Path] = None,
240247
prompt_path: Optional[Path] = None,
241248
system_prompt_override: Optional[str] = None,
242249
create_chat_callback: Optional[CreateChatCallback] = None,
@@ -254,12 +261,18 @@ def init(
254261
SQL queries (usually the variable name of the data frame, but it doesn't
255262
have to be). If a data_source is a SQLAlchemy engine, the table_name is
256263
the name of the table in the database to query against.
257-
greeting : str, optional
258-
A string in Markdown format, containing the initial message
259-
data_description : str, optional
260-
Description of the data in plain text or Markdown
261-
extra_instructions : str, optional
262-
Additional instructions for the chat model
264+
greeting : str | Path, optional
265+
A string in Markdown format, containing the initial message.
266+
If a pathlib.Path object is passed,
267+
querychat will read the contents of the path into a string with `.read_text()`.
268+
data_description : str | Path, optional
269+
Description of the data in plain text or Markdown.
270+
If a pathlib.Path object is passed,
271+
querychat will read the contents of the path into a string with `.read_text()`.
272+
extra_instructions : str | Path, optional
273+
Additional instructions for the chat model.
274+
If a pathlib.Path object is passed,
275+
querychat will read the contents of the path into a string with `.read_text()`.
263276
prompt_path : Path, optional
264277
Path to a custom prompt file. If not provided, the default querychat
265278
template will be used. This should be a Markdown file that contains the
@@ -305,14 +318,22 @@ def init(
305318
file=sys.stderr,
306319
)
307320

308-
# Create the system prompt, or use the override
309-
_system_prompt = system_prompt_override or system_prompt(
310-
data_source_obj,
311-
data_description,
312-
extra_instructions,
313-
prompt_path=prompt_path,
321+
# quality of life improvement to do the Path.read_text() for user or pass along the string
322+
greeting_str: str | None = (
323+
greeting.read_text() if isinstance(greeting, Path) else greeting
314324
)
315325

326+
# Create the system prompt, or use the override
327+
if isinstance(system_prompt_override, Path):
328+
system_prompt_ = system_prompt_override.read_text()
329+
else:
330+
system_prompt_ = system_prompt_override or system_prompt(
331+
data_source_obj,
332+
data_description=data_description,
333+
extra_instructions=extra_instructions,
334+
prompt_path=prompt_path,
335+
)
336+
316337
# Default chat function if none provided
317338
create_chat_callback = create_chat_callback or partial(
318339
chatlas.ChatOpenAI,
@@ -321,8 +342,8 @@ def init(
321342

322343
return QueryChatConfig(
323344
data_source=data_source_obj,
324-
system_prompt=_system_prompt,
325-
greeting=greeting,
345+
system_prompt=system_prompt_,
346+
greeting=greeting_str,
326347
create_chat_callback=create_chat_callback,
327348
)
328349

@@ -354,7 +375,12 @@ def mod_ui() -> ui.TagList:
354375
)
355376

356377

357-
def sidebar(id: str, width: int = 400, height: str = "100%", **kwargs) -> ui.Sidebar:
378+
def sidebar(
379+
id: str,
380+
width: int = 400,
381+
height: str = "100%",
382+
**kwargs,
383+
) -> ui.Sidebar:
358384
"""
359385
Create a sidebar containing the querychat UI.
360386

0 commit comments

Comments
 (0)