@mate-academy/prompt-client 2.0.1 → 2.1.0
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.
- package/README.md +25 -11
- package/dist/LLMPromptClient.errors.d.ts +4 -2
- package/dist/LLMPromptClient.errors.js +5 -3
- package/dist/LLMPromptClient.typedefs.d.ts +10 -3
- package/dist/providers/InMemory/InMemory.typedefs.d.ts +2 -0
- package/dist/providers/InMemory/InMemoryPrompt.client.d.ts +2 -0
- package/dist/providers/InMemory/InMemoryPrompt.client.js +11 -3
- package/dist/providers/Langfuse/Langfuse.constants.d.ts +1 -0
- package/dist/providers/Langfuse/Langfuse.constants.js +4 -0
- package/dist/providers/Langfuse/Langfuse.factory.js +2 -2
- package/dist/providers/Langfuse/LangfusePrompt.client.d.ts +2 -0
- package/dist/providers/Langfuse/LangfusePrompt.client.js +21 -5
- package/dist/providers/Langfuse/LangfusePrompt.d.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -102,18 +102,20 @@ Error model (only when NO `fallback` is provided):
|
|
|
102
102
|
existence probe).
|
|
103
103
|
- `LLMPromptFetchError` — infrastructure failure (network/HTTP) or a
|
|
104
104
|
wrong-shape prompt (a chat prompt fetched via `getPrompt`, or vice versa).
|
|
105
|
-
Both extend `LLMPromptError` and carry `promptName`
|
|
105
|
+
Both extend `LLMPromptError` and carry `promptName`. `cause` is set when an
|
|
106
|
+
underlying provider error is available.
|
|
106
107
|
|
|
107
108
|
`LLMPromptListError` (thrown only by `listPrompts`, see [Catalog](#catalog-for-codegen))
|
|
108
|
-
sits outside this hierarchy on purpose: it is **
|
|
109
|
-
prompt-scoped, so it extends `Error` directly and carries
|
|
110
|
-
instead of `promptName`. A broad `catch (error) {
|
|
111
|
-
LLMPromptError) ... }` will **not** catch it — handle it
|
|
112
|
-
`error.name === 'LLMPromptListError'`.
|
|
109
|
+
sits outside this hierarchy on purpose: it is **selection-scoped**, not
|
|
110
|
+
prompt-scoped, so it extends `Error` directly and carries the requested
|
|
111
|
+
`label` + `tag` plus `cause` instead of `promptName`. A broad `catch (error) {
|
|
112
|
+
if (error instanceof LLMPromptError) ... }` will **not** catch it — handle it
|
|
113
|
+
explicitly, or match on `error.name === 'LLMPromptListError'`.
|
|
113
114
|
|
|
114
|
-
With `fallback` set, `getPrompt` never rejects:
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
With `fallback` set, `getPrompt` never rejects: it resolves to the fallback text
|
|
116
|
+
with `version: 0` and `isFallback: true`, and logs the fallback resolution. If
|
|
117
|
+
the SDK throws instead of returning its fallback, the client also logs the
|
|
118
|
+
underlying fetch error before building the same fallback locally.
|
|
117
119
|
|
|
118
120
|
### Chat prompts
|
|
119
121
|
|
|
@@ -135,8 +137,9 @@ prompt.compile({ leadName: 'Maria' }); // substitutes {{var}} in each message's
|
|
|
135
137
|
Fallback is an `LLMPromptMessage[]` instead of a string, with the same
|
|
136
138
|
never-throw / `version: 0` / `isFallback: true` semantics as `getPrompt`.
|
|
137
139
|
Langfuse placeholder entries and messages with an unrecognized role are
|
|
138
|
-
|
|
139
|
-
gap is visible without breaking the call
|
|
140
|
+
filtered when the prompt is fetched. A warning is logged once at fetch time so
|
|
141
|
+
the gap is visible without breaking the call; `messages` and `compile()` then
|
|
142
|
+
use the filtered result.
|
|
140
143
|
|
|
141
144
|
### Catalog (for codegen)
|
|
142
145
|
|
|
@@ -146,6 +149,7 @@ Langfuse-prompt codegen (the gateway's `generateSnapshot`):
|
|
|
146
149
|
```typescript
|
|
147
150
|
const { promptNames, page, totalPages } = await promptClient.listPrompts({
|
|
148
151
|
label: 'production',
|
|
152
|
+
tag: 'catalog', // optional; omit to list every prompt carrying the label
|
|
149
153
|
page: 1, // default 1
|
|
150
154
|
pageSize: 100, // default 100
|
|
151
155
|
});
|
|
@@ -156,6 +160,12 @@ const record = await promptClient.getPromptRecord('chatAgent.instructions', {
|
|
|
156
160
|
// record.type === LLMPromptTypes.Text | LLMPromptTypes.Chat
|
|
157
161
|
```
|
|
158
162
|
|
|
163
|
+
`label` is required and `tag` narrows the same listing further — a prompt is
|
|
164
|
+
returned only when it carries **both**. The filter is applied by the provider,
|
|
165
|
+
never by widening the query and post-filtering names, so an unmatched tag
|
|
166
|
+
yields an empty `promptNames` page rather than the full label listing.
|
|
167
|
+
Pagination applies to the filtered set.
|
|
168
|
+
|
|
159
169
|
Both methods are uncached and never fall back — they **throw** on failure
|
|
160
170
|
(`LLMPromptListError` for `listPrompts`, `LLMPromptNotFoundError` /
|
|
161
171
|
`LLMPromptFetchError` for `getPromptRecord`) so a codegen run can fail loudly
|
|
@@ -267,6 +277,10 @@ never applies to `getPromptRecord` — a missing seed always throws
|
|
|
267
277
|
`LLMPromptNotFoundError`, since the catalog must report seeded truth for
|
|
268
278
|
codegen tests.
|
|
269
279
|
|
|
280
|
+
Seeds accept an optional `tags` array so `listPrompts({ label, tag })` filters
|
|
281
|
+
the same way it does against Langfuse. Tags have no default: a seed without
|
|
282
|
+
`tags` matches every tag-less listing and no tag-filtered one.
|
|
283
|
+
|
|
270
284
|
## Provider notes (Langfuse)
|
|
271
285
|
|
|
272
286
|
- `label` and `version` are mutually exclusive; when `version` is set the
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LLMPromptListSelection } from './LLMPromptClient.typedefs';
|
|
1
2
|
export declare class LLMPromptError extends Error {
|
|
2
3
|
readonly promptName: string;
|
|
3
4
|
readonly cause?: unknown | undefined;
|
|
@@ -11,6 +12,7 @@ export declare class LLMPromptFetchError extends LLMPromptError {
|
|
|
11
12
|
}
|
|
12
13
|
export declare class LLMPromptListError extends Error {
|
|
13
14
|
readonly label: string;
|
|
14
|
-
readonly
|
|
15
|
-
|
|
15
|
+
readonly tag?: string;
|
|
16
|
+
readonly cause?: unknown;
|
|
17
|
+
constructor(selection: LLMPromptListSelection, cause?: unknown);
|
|
16
18
|
}
|
|
@@ -25,9 +25,11 @@ class LLMPromptFetchError extends LLMPromptError {
|
|
|
25
25
|
}
|
|
26
26
|
exports.LLMPromptFetchError = LLMPromptFetchError;
|
|
27
27
|
class LLMPromptListError extends Error {
|
|
28
|
-
constructor(
|
|
29
|
-
super(`Failed to list LLM prompts for label: ${label}`
|
|
30
|
-
|
|
28
|
+
constructor(selection, cause) {
|
|
29
|
+
super(`Failed to list LLM prompts for label: ${selection.label}`
|
|
30
|
+
+ (selection.tag === undefined ? '' : `, tag: ${selection.tag}`));
|
|
31
|
+
this.label = selection.label;
|
|
32
|
+
this.tag = selection.tag;
|
|
31
33
|
this.cause = cause;
|
|
32
34
|
this.name = 'LLMPromptListError';
|
|
33
35
|
}
|
|
@@ -14,7 +14,7 @@ export interface LLMPromptMessage {
|
|
|
14
14
|
export interface LLMPrompt {
|
|
15
15
|
name: string;
|
|
16
16
|
version: number;
|
|
17
|
-
type: LLMPromptTypes;
|
|
17
|
+
type: LLMPromptTypes.Text;
|
|
18
18
|
prompt: string;
|
|
19
19
|
config: unknown;
|
|
20
20
|
isFallback: boolean;
|
|
@@ -28,7 +28,7 @@ export interface LLMPrompt {
|
|
|
28
28
|
export interface LLMChatPrompt {
|
|
29
29
|
name: string;
|
|
30
30
|
version: number;
|
|
31
|
-
type: LLMPromptTypes;
|
|
31
|
+
type: LLMPromptTypes.Chat;
|
|
32
32
|
messages: LLMPromptMessage[];
|
|
33
33
|
config: unknown;
|
|
34
34
|
isFallback: boolean;
|
|
@@ -60,8 +60,15 @@ interface GetChatPromptByLabelOptions extends GetChatPromptCommonOptions {
|
|
|
60
60
|
label?: string;
|
|
61
61
|
}
|
|
62
62
|
export type GetChatPromptOptions = GetChatPromptByVersionOptions | GetChatPromptByLabelOptions;
|
|
63
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Which prompts a catalog listing selects: a required label, narrowed further
|
|
65
|
+
* by an optional tag.
|
|
66
|
+
*/
|
|
67
|
+
export interface LLMPromptListSelection {
|
|
64
68
|
label: string;
|
|
69
|
+
tag?: string;
|
|
70
|
+
}
|
|
71
|
+
export interface ListPromptsOptions extends LLMPromptListSelection {
|
|
65
72
|
page?: number;
|
|
66
73
|
pageSize?: number;
|
|
67
74
|
}
|
|
@@ -8,12 +8,14 @@ export interface InMemorySeedPrompt {
|
|
|
8
8
|
version?: number;
|
|
9
9
|
config?: unknown;
|
|
10
10
|
labels?: string[];
|
|
11
|
+
tags?: string[];
|
|
11
12
|
}
|
|
12
13
|
export interface InMemorySeedChatPrompt {
|
|
13
14
|
messages: LLMPromptMessage[];
|
|
14
15
|
version?: number;
|
|
15
16
|
config?: unknown;
|
|
16
17
|
labels?: string[];
|
|
18
|
+
tags?: string[];
|
|
17
19
|
}
|
|
18
20
|
export interface InMemoryProviderOptions {
|
|
19
21
|
prompts?: Record<string, InMemorySeedPrompt>;
|
|
@@ -19,7 +19,9 @@ export declare class InMemoryPromptClient implements LLMPromptClient {
|
|
|
19
19
|
private buildFallbackChatPrompt;
|
|
20
20
|
private compileMessages;
|
|
21
21
|
private collectMatchingNames;
|
|
22
|
+
private matchesSelection;
|
|
22
23
|
private matchesLabel;
|
|
24
|
+
private matchesTag;
|
|
23
25
|
private buildTextPromptRecord;
|
|
24
26
|
private buildChatPromptRecord;
|
|
25
27
|
}
|
|
@@ -47,7 +47,7 @@ class InMemoryPromptClient {
|
|
|
47
47
|
throw new LLMPromptClient_errors_1.LLMPromptNotFoundError(name);
|
|
48
48
|
}
|
|
49
49
|
async listPrompts(options) {
|
|
50
|
-
const matchingNames = this.collectMatchingNames(options
|
|
50
|
+
const matchingNames = this.collectMatchingNames(options);
|
|
51
51
|
const page = options.page ?? LLMPromptClient_constants_1.DEFAULT_PROMPT_LIST_PAGE;
|
|
52
52
|
const pageSize = options.pageSize ?? LLMPromptClient_constants_1.DEFAULT_PROMPT_LIST_PAGE_SIZE;
|
|
53
53
|
const startIndex = (page - 1) * pageSize;
|
|
@@ -131,12 +131,12 @@ class InMemoryPromptClient {
|
|
|
131
131
|
content: (0, template_1.compileTemplateVariables)(message.content, variables),
|
|
132
132
|
}));
|
|
133
133
|
}
|
|
134
|
-
collectMatchingNames(
|
|
134
|
+
collectMatchingNames(selection) {
|
|
135
135
|
const matchingNames = [];
|
|
136
136
|
const seenNames = new Set();
|
|
137
137
|
const collect = (entries) => {
|
|
138
138
|
entries.forEach(([name, seedPrompt]) => {
|
|
139
|
-
if (seenNames.has(name) || !this.
|
|
139
|
+
if (seenNames.has(name) || !this.matchesSelection(seedPrompt, selection)) {
|
|
140
140
|
return;
|
|
141
141
|
}
|
|
142
142
|
matchingNames.push(name);
|
|
@@ -147,9 +147,17 @@ class InMemoryPromptClient {
|
|
|
147
147
|
collect([...this.chatPrompts.entries()]);
|
|
148
148
|
return matchingNames;
|
|
149
149
|
}
|
|
150
|
+
matchesSelection(seedPrompt, selection) {
|
|
151
|
+
return this.matchesLabel(seedPrompt.labels, selection.label)
|
|
152
|
+
&& this.matchesTag(seedPrompt.tags, selection.tag);
|
|
153
|
+
}
|
|
150
154
|
matchesLabel(labels, label) {
|
|
151
155
|
return (labels ?? [LLMPromptClient_constants_1.DEFAULT_PROMPT_LABEL]).includes(label);
|
|
152
156
|
}
|
|
157
|
+
matchesTag(tags, tag) {
|
|
158
|
+
const requestedTag = tag ?? undefined;
|
|
159
|
+
return requestedTag === undefined || (tags ?? []).includes(requestedTag);
|
|
160
|
+
}
|
|
153
161
|
buildTextPromptRecord(name, seedPrompt) {
|
|
154
162
|
return {
|
|
155
163
|
name,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const MILLISECONDS_PER_SECOND = 1000;
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createLangfusePromptManagement = void 0;
|
|
4
4
|
const client_1 = require("@langfuse/client");
|
|
5
|
+
const Langfuse_constants_1 = require("../../providers/Langfuse/Langfuse.constants");
|
|
5
6
|
const LangfusePrompt_client_1 = require("../../providers/Langfuse/LangfusePrompt.client");
|
|
6
|
-
const MILLISECONDS_PER_SECOND = 1000;
|
|
7
7
|
const createLangfusePromptManagement = (options, logger) => {
|
|
8
8
|
const client = new client_1.LangfuseClient({
|
|
9
9
|
publicKey: options.credentials.publicKey,
|
|
10
10
|
secretKey: options.credentials.secretKey,
|
|
11
11
|
baseUrl: options.credentials.baseUrl,
|
|
12
12
|
...(options.fetchTimeoutMs !== undefined
|
|
13
|
-
? { timeout: Math.ceil(options.fetchTimeoutMs / MILLISECONDS_PER_SECOND) }
|
|
13
|
+
? { timeout: Math.ceil(options.fetchTimeoutMs / Langfuse_constants_1.MILLISECONDS_PER_SECOND) }
|
|
14
14
|
: {}),
|
|
15
15
|
});
|
|
16
16
|
return {
|
|
@@ -14,6 +14,7 @@ export declare class LangfusePromptClient implements LLMPromptClient {
|
|
|
14
14
|
shutdown(): Promise<void>;
|
|
15
15
|
private fetchTextPrompt;
|
|
16
16
|
private fetchChatPrompt;
|
|
17
|
+
private reportFallbackFetchError;
|
|
17
18
|
private resolveSelectionOptions;
|
|
18
19
|
private resolveCacheTtlSeconds;
|
|
19
20
|
private buildTextFallbackPrompt;
|
|
@@ -24,6 +25,7 @@ export declare class LangfusePromptClient implements LLMPromptClient {
|
|
|
24
25
|
private ensureChatPrompt;
|
|
25
26
|
private reportPromptResolution;
|
|
26
27
|
private reportSkippedChatEntries;
|
|
28
|
+
private resolveListSelection;
|
|
27
29
|
private fetchRawPrompt;
|
|
28
30
|
private buildRequestOptions;
|
|
29
31
|
}
|
|
@@ -7,9 +7,9 @@ const LLMPromptClient_constants_1 = require("../../LLMPromptClient.constants");
|
|
|
7
7
|
const LLMPromptClient_errors_1 = require("../../LLMPromptClient.errors");
|
|
8
8
|
const Langfuse_helpers_1 = require("../../providers/Langfuse/Langfuse.helpers");
|
|
9
9
|
const LangfusePrompt_1 = require("../../providers/Langfuse/LangfusePrompt");
|
|
10
|
+
const Langfuse_constants_1 = require("../../providers/Langfuse/Langfuse.constants");
|
|
10
11
|
const LANGFUSE_TEXT_PROMPT_TYPE = 'text';
|
|
11
12
|
const LANGFUSE_CHAT_PROMPT_TYPE = 'chat';
|
|
12
|
-
const MILLISECONDS_PER_SECOND = 1000;
|
|
13
13
|
const FALLBACK_PROMPT_VERSION = 0;
|
|
14
14
|
class LangfusePromptClient {
|
|
15
15
|
constructor(client, providerOptions, logger) {
|
|
@@ -31,9 +31,10 @@ class LangfusePromptClient {
|
|
|
31
31
|
return new LangfusePrompt_1.LangfuseChatPrompt(nativePrompt);
|
|
32
32
|
}
|
|
33
33
|
async listPrompts(options) {
|
|
34
|
+
const selection = this.resolveListSelection(options);
|
|
34
35
|
try {
|
|
35
36
|
const promptListPage = await this.client.api.prompts.list({
|
|
36
|
-
|
|
37
|
+
...selection,
|
|
37
38
|
page: options.page ?? LLMPromptClient_constants_1.DEFAULT_PROMPT_LIST_PAGE,
|
|
38
39
|
limit: options.pageSize ?? LLMPromptClient_constants_1.DEFAULT_PROMPT_LIST_PAGE_SIZE,
|
|
39
40
|
}, this.buildRequestOptions());
|
|
@@ -46,9 +47,9 @@ class LangfusePromptClient {
|
|
|
46
47
|
catch (error) {
|
|
47
48
|
this.logger.error('Failed to list Langfuse prompts', {
|
|
48
49
|
error,
|
|
49
|
-
|
|
50
|
+
...selection,
|
|
50
51
|
});
|
|
51
|
-
throw new LLMPromptClient_errors_1.LLMPromptListError(
|
|
52
|
+
throw new LLMPromptClient_errors_1.LLMPromptListError(selection, error);
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
async getPromptRecord(name, options) {
|
|
@@ -88,6 +89,7 @@ class LangfusePromptClient {
|
|
|
88
89
|
}
|
|
89
90
|
catch (error) {
|
|
90
91
|
if (options?.fallback !== undefined) {
|
|
92
|
+
this.reportFallbackFetchError(name, error);
|
|
91
93
|
return this.buildTextFallbackPrompt(name, options.fallback, options);
|
|
92
94
|
}
|
|
93
95
|
throw this.classifyFetchError(name, error);
|
|
@@ -106,11 +108,18 @@ class LangfusePromptClient {
|
|
|
106
108
|
}
|
|
107
109
|
catch (error) {
|
|
108
110
|
if (options?.fallback !== undefined) {
|
|
111
|
+
this.reportFallbackFetchError(name, error);
|
|
109
112
|
return this.buildChatFallbackPrompt(name, options.fallback, options);
|
|
110
113
|
}
|
|
111
114
|
throw this.classifyFetchError(name, error);
|
|
112
115
|
}
|
|
113
116
|
}
|
|
117
|
+
reportFallbackFetchError(name, error) {
|
|
118
|
+
this.logger.error('Failed to fetch Langfuse prompt; using fallback', {
|
|
119
|
+
error,
|
|
120
|
+
promptName: name,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
114
123
|
resolveSelectionOptions(options) {
|
|
115
124
|
if (options?.version !== undefined) {
|
|
116
125
|
return { version: options.version };
|
|
@@ -209,6 +218,13 @@ class LangfusePromptClient {
|
|
|
209
218
|
});
|
|
210
219
|
}
|
|
211
220
|
}
|
|
221
|
+
resolveListSelection(options) {
|
|
222
|
+
const requestedTag = options.tag ?? undefined;
|
|
223
|
+
return {
|
|
224
|
+
label: options.label,
|
|
225
|
+
...(requestedTag === undefined ? {} : { tag: requestedTag }),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
212
228
|
async fetchRawPrompt(name, options) {
|
|
213
229
|
try {
|
|
214
230
|
return await this.client.api.prompts.get(name, { label: options.label }, this.buildRequestOptions());
|
|
@@ -224,7 +240,7 @@ class LangfusePromptClient {
|
|
|
224
240
|
: {}),
|
|
225
241
|
...(this.providerOptions.fetchTimeoutMs !== undefined
|
|
226
242
|
? {
|
|
227
|
-
timeoutInSeconds: Math.ceil(this.providerOptions.fetchTimeoutMs / MILLISECONDS_PER_SECOND),
|
|
243
|
+
timeoutInSeconds: Math.ceil(this.providerOptions.fetchTimeoutMs / Langfuse_constants_1.MILLISECONDS_PER_SECOND),
|
|
228
244
|
}
|
|
229
245
|
: {}),
|
|
230
246
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ChatPromptClient, type TextPromptClient } from '@langfuse/client';
|
|
2
2
|
import { LLMPromptTypes, type LLMChatPrompt, type LLMPrompt, type LLMPromptMessage } from '../../LLMPromptClient.typedefs';
|
|
3
3
|
export declare class LangfusePrompt implements LLMPrompt {
|
|
4
|
-
readonly nativePrompt
|
|
4
|
+
private readonly nativePrompt;
|
|
5
5
|
readonly type = LLMPromptTypes.Text;
|
|
6
6
|
constructor(nativePrompt: TextPromptClient);
|
|
7
7
|
get name(): string;
|
|
@@ -12,7 +12,7 @@ export declare class LangfusePrompt implements LLMPrompt {
|
|
|
12
12
|
compile(variables?: Record<string, string>): string;
|
|
13
13
|
}
|
|
14
14
|
export declare class LangfuseChatPrompt implements LLMChatPrompt {
|
|
15
|
-
readonly nativePrompt
|
|
15
|
+
private readonly nativePrompt;
|
|
16
16
|
readonly type = LLMPromptTypes.Chat;
|
|
17
17
|
constructor(nativePrompt: ChatPromptClient);
|
|
18
18
|
get name(): string;
|