promptfuse 0.1.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.
- promptfuse-0.1.0/.github/workflows/ci.yml +20 -0
- promptfuse-0.1.0/.github/workflows/publish.yml +22 -0
- promptfuse-0.1.0/.gitignore +11 -0
- promptfuse-0.1.0/LICENSE +21 -0
- promptfuse-0.1.0/PKG-INFO +356 -0
- promptfuse-0.1.0/README.md +344 -0
- promptfuse-0.1.0/pyproject.toml +23 -0
- promptfuse-0.1.0/src/promptfuse/__init__.py +14 -0
- promptfuse-0.1.0/src/promptfuse/cache.py +217 -0
- promptfuse-0.1.0/src/promptfuse/chain.py +58 -0
- promptfuse-0.1.0/src/promptfuse/client.py +108 -0
- promptfuse-0.1.0/src/promptfuse/errors.py +17 -0
- promptfuse-0.1.0/src/promptfuse/models.py +259 -0
- promptfuse-0.1.0/src/promptfuse/registry.py +218 -0
- promptfuse-0.1.0/src/promptfuse/snapshot.py +105 -0
- promptfuse-0.1.0/src/promptfuse/stores/__init__.py +1 -0
- promptfuse-0.1.0/src/promptfuse/stores/memory.py +71 -0
- promptfuse-0.1.0/src/promptfuse/stores/protocol.py +61 -0
- promptfuse-0.1.0/src/promptfuse/stores/sqlite.py +198 -0
- promptfuse-0.1.0/src/promptfuse/stores/yaml.py +266 -0
- promptfuse-0.1.0/tests/test_cache.py +101 -0
- promptfuse-0.1.0/tests/test_client.py +127 -0
- promptfuse-0.1.0/tests/test_compile.py +95 -0
- promptfuse-0.1.0/tests/test_package.py +5 -0
- promptfuse-0.1.0/tests/test_registry.py +102 -0
- promptfuse-0.1.0/tests/test_sqlite.py +25 -0
- promptfuse-0.1.0/tests/test_store_contract.py +31 -0
- promptfuse-0.1.0/tests/test_yaml.py +70 -0
- promptfuse-0.1.0/uv.lock +225 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- run: uv sync --extra dev --frozen
|
|
20
|
+
- run: uv run pytest
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
pypi:
|
|
10
|
+
name: upload release to PyPI
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment: pypi
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write
|
|
15
|
+
contents: read
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: astral-sh/setup-uv@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.13"
|
|
21
|
+
- run: uv build
|
|
22
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
promptfuse-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Henry Fong
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: promptfuse
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local prompt registry with a Langfuse-compatible read API.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: pyyaml>=6
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# promptfuse
|
|
14
|
+
|
|
15
|
+
[](https://github.com/foongsy/promptfuse/actions/workflows/ci.yml)
|
|
16
|
+
|
|
17
|
+
Local prompt registry with a Langfuse-compatible prompt API. Prompts are stored in a YAML tree, in SQLite, or in SQLite with the YAML tree as the seed. `promptfuse` does not call Langfuse Cloud and does not send traces.
|
|
18
|
+
|
|
19
|
+
This document is the contract for the public API.
|
|
20
|
+
|
|
21
|
+
The prompt record and the fetch, label, cache, compile, and fallback rules match [Langfuse prompt management](https://langfuse.com/docs/prompt-management/data-model). Where Langfuse leaves a local-store choice open, this document defines it.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
Requires Python 3.10+.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install promptfuse
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Client
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from promptfuse import Promptfuse
|
|
35
|
+
|
|
36
|
+
client = Promptfuse(
|
|
37
|
+
yaml_dir="prompts", # optional YAML store
|
|
38
|
+
sqlite_path="prompts.db", # optional SQLite store
|
|
39
|
+
snapshot_path=None, # optional disk cache of successful reads
|
|
40
|
+
cache_ttl_seconds=60, # default; 0 disables the memory cache
|
|
41
|
+
)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
At least one of `yaml_dir` and `sqlite_path` is required. See [Stores](#stores) for which one is read and written.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
prompt = client.get_prompt("deep-agent/system")
|
|
48
|
+
text = prompt.compile(persona="analyst")
|
|
49
|
+
|
|
50
|
+
client.create_prompt(
|
|
51
|
+
name="deep-agent/system",
|
|
52
|
+
type="text",
|
|
53
|
+
prompt="You are a {{persona}} research assistant.",
|
|
54
|
+
labels=["production"],
|
|
55
|
+
config={"model": "gpt-4.1", "temperature": 0.2},
|
|
56
|
+
tags=["deep-agent"],
|
|
57
|
+
commit_message="initial system prompt",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
client.update_prompt(
|
|
61
|
+
name="deep-agent/system",
|
|
62
|
+
version=1,
|
|
63
|
+
new_labels=["staging"],
|
|
64
|
+
)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`get_prompt` follows [`GET /api/public/v2/prompts/{name}`](https://langfuse.com/docs/prompt-management/features/prompt-version-control). `create_prompt` and `update_prompt` follow the Langfuse Python SDK methods of the same names.
|
|
68
|
+
|
|
69
|
+
## Prompt client
|
|
70
|
+
|
|
71
|
+
`get_prompt`, `create_prompt`, and a fallback all return a prompt client.
|
|
72
|
+
|
|
73
|
+
| Attribute | Meaning |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `name` | Prompt name. Slashes are folders (`deep-agent/system`). |
|
|
76
|
+
| `version` | Integer. Store versions start at 1. |
|
|
77
|
+
| `type` | `"text"` or `"chat"`. Fixed for a name after the first version. |
|
|
78
|
+
| `prompt` | Text prompt: `str`. Chat prompt: list of messages. |
|
|
79
|
+
| `config` | JSON object versioned with this prompt. `{}` when omitted. |
|
|
80
|
+
| `labels` | Labels currently pointing at this version. |
|
|
81
|
+
| `tags` | String list. `[]` when omitted. |
|
|
82
|
+
| `commit_message` | Optional string. |
|
|
83
|
+
| `is_fallback` | `True` only for a client built from the `fallback` argument. |
|
|
84
|
+
| `variables` | `{{name}}` placeholders found in the prompt, in order. |
|
|
85
|
+
|
|
86
|
+
A text prompt's `prompt` is a string. A chat prompt's `prompt` is a list of either of:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
{"type": "message", "role": "system", "content": "You are a {{persona}}."}
|
|
90
|
+
{"type": "placeholder", "name": "chat_history"}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`role` is any string. `promptfuse` does not restrict it to `system`, `user`, or `assistant`.
|
|
94
|
+
|
|
95
|
+
`config` is an arbitrary JSON object. Typical keys are `model`, `temperature`, `max_tokens`, `response_format`, `tools`, and `tool_choice`. `promptfuse` stores and returns `config`. It does not call a model.
|
|
96
|
+
|
|
97
|
+
## Fetching
|
|
98
|
+
|
|
99
|
+
From [How label resolution works](https://langfuse.com/docs/prompt-management/features/prompt-version-control#label-resolution):
|
|
100
|
+
|
|
101
|
+
| Call | Result |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `get_prompt(name)` | Version labeled `production`. |
|
|
104
|
+
| `get_prompt(name, label="staging")` | Version labeled `staging`. |
|
|
105
|
+
| `get_prompt(name, version=3)` | Version 3, whatever its labels are. |
|
|
106
|
+
| `get_prompt(name, label=..., version=...)` | `InvalidPromptRequest`. `label` and `version` are mutually exclusive. |
|
|
107
|
+
|
|
108
|
+
A missing name, a missing label, or a missing version raises `PromptNotFound`. There is no silent fallback from `staging` to `production` or `latest`.
|
|
109
|
+
|
|
110
|
+
`label` defaults to `"production"` only when `version` is omitted. `type` defaults to `"text"`. Passing `type="chat"` for a text prompt, or the reverse, raises `PromptNotFound`. The default `type` does not coerce a chat prompt into text.
|
|
111
|
+
|
|
112
|
+
`latest` is a real label. It always points at the highest version of that name. Callers fetch it with `label="latest"`. They cannot assign it.
|
|
113
|
+
|
|
114
|
+
`cache_ttl_seconds` overrides the client default for that call. `fallback` is used only as described in [Fallback](#fallback).
|
|
115
|
+
|
|
116
|
+
## Creating a version
|
|
117
|
+
|
|
118
|
+
`create_prompt` appends a version. It does not edit an existing version.
|
|
119
|
+
|
|
120
|
+
- The first version of a name is `1`. The next is `max(version) + 1`.
|
|
121
|
+
- `type` must match every existing version of that name. A change raises `InvalidPromptRequest`.
|
|
122
|
+
- `labels` moves each listed label onto the new version and off any older version. Omitted labels stay where they are.
|
|
123
|
+
- `latest` always moves to the new version. An entry of `"latest"` in `labels` raises `InvalidPromptRequest`.
|
|
124
|
+
- `config`, `tags`, and `commit_message` belong to this version.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
client.create_prompt(
|
|
128
|
+
name="movie-critic",
|
|
129
|
+
type="text",
|
|
130
|
+
prompt="As a {{criticLevel}} movie critic, do you like {{movie}}?",
|
|
131
|
+
labels=["production"],
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The same call with `type="chat"` takes a message list:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
client.create_prompt(
|
|
139
|
+
name="movie-critic-chat",
|
|
140
|
+
type="chat",
|
|
141
|
+
prompt=[
|
|
142
|
+
{"role": "system", "content": "You are a {{criticLevel}} movie critic."},
|
|
143
|
+
{"type": "placeholder", "name": "chat_history"},
|
|
144
|
+
{"role": "user", "content": "What should I watch next?"},
|
|
145
|
+
],
|
|
146
|
+
labels=["production"],
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
A chat item with `role` and `content` is stored as `{"type": "message", "role": ..., "content": ...}`. `type` may be omitted on input. A placeholder must be `{"type": "placeholder", "name": "..."}` and must not include `role` or `content`.
|
|
151
|
+
|
|
152
|
+
## Moving labels
|
|
153
|
+
|
|
154
|
+
`update_prompt(name, version, new_labels)` replaces the labels on that version. Prompt text, `config`, `tags`, and `commit_message` stay as stored.
|
|
155
|
+
|
|
156
|
+
- Each name in `new_labels` is removed from every other version of this prompt, then pointed at `version`.
|
|
157
|
+
- Labels previously on `version` and absent from `new_labels` are removed.
|
|
158
|
+
- `"latest"` in `new_labels` raises `InvalidPromptRequest`. `latest` stays on the highest version.
|
|
159
|
+
- A version that does not exist raises `PromptNotFound`.
|
|
160
|
+
|
|
161
|
+
Rollback is this call. Repoint `production` at the earlier version:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
client.update_prompt(name="movie-critic", version=2, new_labels=["production"])
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Compile
|
|
168
|
+
|
|
169
|
+
`compile` substitutes `{{variable}}` the way the Langfuse Python SDK's `TemplateParser` does.
|
|
170
|
+
|
|
171
|
+
- The name is the text between `{{` and `}}`, with surrounding whitespace removed. `{{ persona }}` and `{{persona}}` are the same variable.
|
|
172
|
+
- A provided value is inserted with `str(value)`. `None` is inserted as an empty string.
|
|
173
|
+
- A variable with no matching keyword argument is left unchanged, including its original braces.
|
|
174
|
+
- `compile` does not require every variable to be present, and it does not reject extra keyword arguments. There is no `throw_on_incomplete_variables` switch.
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
prompt.compile(criticLevel="expert", movie="Dune 2")
|
|
178
|
+
# "As an expert movie critic, do you like Dune 2?"
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Text `compile(**kwargs)` returns a `str`.
|
|
182
|
+
|
|
183
|
+
Chat `compile(**kwargs)` returns a list of messages:
|
|
184
|
+
|
|
185
|
+
- A message gets `{{variable}}` substitution in `content`. The result is `{"role": ..., "content": ...}` with no `type` key.
|
|
186
|
+
- A placeholder whose name is a keyword argument must be a list of messages. Each dict in that list is copied, and its `content` is compiled when it is a string. A non-dict item, or a value that is not a list, is appended as `{"role": "NOT_GIVEN", "content": str(value)}`.
|
|
187
|
+
- A placeholder with no keyword argument stays `{"type": "placeholder", "name": ...}` in the result.
|
|
188
|
+
|
|
189
|
+
Prompt references are stored and returned as literal text. `promptfuse` does not expand `@@@langfusePrompt:name=...|version=1@@@` or `@@@langfusePrompt:name=...|label=production@@@`.
|
|
190
|
+
|
|
191
|
+
`get_langchain_prompt` is not part of this package.
|
|
192
|
+
|
|
193
|
+
## Cache
|
|
194
|
+
|
|
195
|
+
The memory cache matches the [Langfuse client cache](https://langfuse.com/docs/prompt-management/features/caching).
|
|
196
|
+
|
|
197
|
+
- The key is `(name, "label", label)` or `(name, "version", version)`. A label and a version pin do not share an entry.
|
|
198
|
+
- Default TTL is 60 seconds.
|
|
199
|
+
- A fresh entry is returned without reading the store.
|
|
200
|
+
- After the TTL, the stale client is returned immediately and the store is read in the background. A failed refresh keeps the stale client.
|
|
201
|
+
- `cache_ttl_seconds=0` skips the memory cache and reads the store on every call.
|
|
202
|
+
|
|
203
|
+
The cache stores the resolved client, so a label move is visible on the next read after the TTL, not at the moment the label changes. Use `cache_ttl_seconds=0` when a call must see the store immediately. `label="latest"` with a TTL of 0 is the usual development setting.
|
|
204
|
+
|
|
205
|
+
## Fallback
|
|
206
|
+
|
|
207
|
+
`fallback` matches [guaranteed availability](https://langfuse.com/docs/prompt-management/features/guaranteed-availability).
|
|
208
|
+
|
|
209
|
+
`get_prompt` raises if the memory cache has neither a fresh nor a stale client and every store read fails, and `fallback` was not passed.
|
|
210
|
+
|
|
211
|
+
`fallback` is used only in that case. A successful store read ignores `fallback`. A stale cache hit ignores `fallback` and does not build a fallback client.
|
|
212
|
+
|
|
213
|
+
A string fallback returns a text client. A list fallback returns a chat client. The client uses the requested `name`, `version=0`, `labels=[]`, `config={}`, `tags=[]`, `commit_message=None`, and `is_fallback=True`. `compile` still runs on the fallback body.
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
prompt = client.get_prompt("movie-critic", fallback="Do you like {{movie}}?")
|
|
217
|
+
prompt.is_fallback # True only when the stores and the cache could not answer
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Stores
|
|
221
|
+
|
|
222
|
+
YAML and SQLite store the same prompt record. The memory cache sits in front of both.
|
|
223
|
+
|
|
224
|
+
| Constructor | Source of truth | Writes |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| `yaml_dir` only | YAML | YAML |
|
|
227
|
+
| `sqlite_path` only | SQLite | SQLite |
|
|
228
|
+
| both | SQLite, when it can be opened | SQLite |
|
|
229
|
+
|
|
230
|
+
A healthy SQLite database that does not contain the prompt raises `PromptNotFound`. It does not continue into YAML. YAML is consulted when SQLite cannot be opened or a query fails, and when SQLite was not configured.
|
|
231
|
+
|
|
232
|
+
### YAML
|
|
233
|
+
|
|
234
|
+
```text
|
|
235
|
+
prompts/
|
|
236
|
+
├── index.yaml
|
|
237
|
+
└── deep-agent/
|
|
238
|
+
└── system/
|
|
239
|
+
├── v1.yaml
|
|
240
|
+
└── v2.yaml
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The directory under `yaml_dir` is the prompt name. `deep-agent/system/v2.yaml` is version 2 of `deep-agent/system`. The file stem must be `v` plus the integer version. Names must not contain `..` or escape `yaml_dir`.
|
|
244
|
+
|
|
245
|
+
Version files are immutable. A label change edits `index.yaml` only.
|
|
246
|
+
|
|
247
|
+
```yaml
|
|
248
|
+
# prompts/deep-agent/system/v1.yaml
|
|
249
|
+
name: deep-agent/system
|
|
250
|
+
version: 1
|
|
251
|
+
type: text
|
|
252
|
+
prompt: |
|
|
253
|
+
You are a {{persona}} research assistant.
|
|
254
|
+
config:
|
|
255
|
+
model: gpt-4.1
|
|
256
|
+
temperature: 0.2
|
|
257
|
+
tags: ["deep-agent", "system"]
|
|
258
|
+
commit_message: initial system prompt
|
|
259
|
+
created_at: "2026-09-28T00:00:00Z"
|
|
260
|
+
updated_at: "2026-09-28T00:00:00Z"
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`name` and `version` in the file must match the path. `type` is `text` or `chat`. For `chat`, `prompt` is the message list. `config`, `tags`, `commit_message`, `created_at`, and `updated_at` may be omitted.
|
|
264
|
+
|
|
265
|
+
```yaml
|
|
266
|
+
# prompts/index.yaml
|
|
267
|
+
prompts:
|
|
268
|
+
deep-agent/system:
|
|
269
|
+
development: 1
|
|
270
|
+
staging: 1
|
|
271
|
+
production: 1
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Index values are integers. A label points at one version. `latest` must not appear in the index; the highest `vN.yaml` for that name is `latest`. A version file does not list its labels.
|
|
275
|
+
|
|
276
|
+
`create_prompt` writes `vN.yaml` and updates the index. `update_prompt` updates the index only. Neither rewrites an existing `vN.yaml`.
|
|
277
|
+
|
|
278
|
+
### SQLite
|
|
279
|
+
|
|
280
|
+
Two tables:
|
|
281
|
+
|
|
282
|
+
```sql
|
|
283
|
+
CREATE TABLE prompt_version (
|
|
284
|
+
name TEXT NOT NULL,
|
|
285
|
+
version INTEGER NOT NULL,
|
|
286
|
+
type TEXT NOT NULL,
|
|
287
|
+
prompt TEXT NOT NULL, -- JSON: a string or a message list
|
|
288
|
+
config TEXT NOT NULL, -- JSON object
|
|
289
|
+
tags TEXT NOT NULL, -- JSON array of strings
|
|
290
|
+
commit_message TEXT,
|
|
291
|
+
created_at TEXT NOT NULL,
|
|
292
|
+
updated_at TEXT NOT NULL,
|
|
293
|
+
PRIMARY KEY (name, version)
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
CREATE TABLE prompt_label (
|
|
297
|
+
name TEXT NOT NULL,
|
|
298
|
+
label TEXT NOT NULL,
|
|
299
|
+
version INTEGER NOT NULL,
|
|
300
|
+
PRIMARY KEY (name, label),
|
|
301
|
+
FOREIGN KEY (name, version) REFERENCES prompt_version (name, version)
|
|
302
|
+
);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
`prompt_version` rows are inserted, never updated. `prompt_label` is the only mutable data. `latest` is a row in `prompt_label`, moved when a version is inserted.
|
|
306
|
+
|
|
307
|
+
### Using both
|
|
308
|
+
|
|
309
|
+
`import_yaml()` copies the YAML tree into SQLite.
|
|
310
|
+
|
|
311
|
+
- A YAML version that is not in SQLite is inserted.
|
|
312
|
+
- A YAML version that is already in SQLite with the same body, `type`, `config`, `tags`, and `commit_message` is left as stored.
|
|
313
|
+
- A YAML version whose stored body differs raises `InvalidPromptRequest`. Versions stay immutable.
|
|
314
|
+
- Labels in `index.yaml` replace the SQLite label rows for names present in the index. SQLite versions that exist only in SQLite are kept.
|
|
315
|
+
- `latest` is recomputed as the highest version after the import.
|
|
316
|
+
|
|
317
|
+
`Promptfuse(..., import_yaml=True)` runs `import_yaml()` once at construction. The default is `False`. Import overwrites SQLite label pointers for the imported names, including labels that were moved at runtime.
|
|
318
|
+
|
|
319
|
+
### Disk snapshot
|
|
320
|
+
|
|
321
|
+
`snapshot_path` is a JSON file of prompt clients from successful store reads. It is a cache. It is not edited by hand and it is not a third source of truth.
|
|
322
|
+
|
|
323
|
+
On a store outage, `get_prompt` uses the snapshot entry for that cache key when the memory cache is empty. A missing snapshot entry then uses YAML if SQLite failed and `yaml_dir` is set. A snapshot hit sets `is_fallback` to `False`.
|
|
324
|
+
|
|
325
|
+
The snapshot is updated after a successful SQLite or YAML read. A fallback client is not written to the snapshot.
|
|
326
|
+
|
|
327
|
+
## Errors
|
|
328
|
+
|
|
329
|
+
| Exception | When |
|
|
330
|
+
|---|---|
|
|
331
|
+
| `PromptNotFound` | Unknown name, label, or version. `type` does not match the stored prompt. SQLite is healthy and the row is absent. |
|
|
332
|
+
| `InvalidPromptRequest` | `label` and `version` both set. `type` changes on `create_prompt`. `"latest"` is assigned by the caller or written in `index.yaml`. A version file disagrees with its path. `import_yaml()` finds a body that differs from SQLite. The YAML path escapes `yaml_dir`. |
|
|
333
|
+
| `PromptStoreError` | SQLite cannot be opened or a query fails, and snapshot, YAML, and `fallback` do not produce a client. |
|
|
334
|
+
|
|
335
|
+
`PromptNotFound` and `InvalidPromptRequest` are not retried through the seed files when SQLite is healthy.
|
|
336
|
+
|
|
337
|
+
## Out of scope
|
|
338
|
+
|
|
339
|
+
- Langfuse Cloud, API keys, tracing, and linking a prompt to a generation
|
|
340
|
+
- Expanding `@@@langfusePrompt:...@@@` references
|
|
341
|
+
- `get_langchain_prompt`
|
|
342
|
+
- Protected labels
|
|
343
|
+
- Postgres and other database engines
|
|
344
|
+
- A network server
|
|
345
|
+
|
|
346
|
+
## References
|
|
347
|
+
|
|
348
|
+
- [Prompt data model](https://langfuse.com/docs/prompt-management/data-model)
|
|
349
|
+
- [Version control and label resolution](https://langfuse.com/docs/prompt-management/features/prompt-version-control)
|
|
350
|
+
- [Variables](https://langfuse.com/docs/prompt-management/features/variables)
|
|
351
|
+
- [Message placeholders](https://langfuse.com/docs/prompt-management/features/message-placeholders)
|
|
352
|
+
- [Prompt config](https://langfuse.com/docs/prompt-management/features/config)
|
|
353
|
+
- [Client cache](https://langfuse.com/docs/prompt-management/features/caching)
|
|
354
|
+
- [Guaranteed availability](https://langfuse.com/docs/prompt-management/features/guaranteed-availability)
|
|
355
|
+
- [Folders](https://langfuse.com/docs/prompt-management/features/folders)
|
|
356
|
+
- Compile rules follow `TemplateParser` in the Langfuse Python SDK (`langfuse/model.py`): brace text is stripped, missing variables stay literal, and `None` becomes an empty string.
|