@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 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` + `cause`.
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 **label-scoped**, not
109
- prompt-scoped, so it extends `Error` directly and carries `label` + `cause`
110
- instead of `promptName`. A broad `catch (error) { if (error instanceof
111
- LLMPromptError) ... }` will **not** catch it — handle it explicitly, or match on
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: on any failure it resolves to
115
- the fallback text with `version: 0` and `isFallback: true`, and the logger
116
- receives a warning.
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
- silently skipped from `messages`/`compile()`, with a warning logged so the
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 cause?: unknown | undefined;
15
- constructor(label: string, cause?: unknown | undefined);
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(label, cause) {
29
- super(`Failed to list LLM prompts for label: ${label}`);
30
- this.label = label;
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
- export interface ListPromptsOptions {
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.label);
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(label) {
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.matchesLabel(seedPrompt.labels, label)) {
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;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MILLISECONDS_PER_SECOND = void 0;
4
+ exports.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
- label: options.label,
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
- label: options.label,
50
+ ...selection,
50
51
  });
51
- throw new LLMPromptClient_errors_1.LLMPromptListError(options.label, error);
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: TextPromptClient;
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: ChatPromptClient;
15
+ private readonly nativePrompt;
16
16
  readonly type = LLMPromptTypes.Chat;
17
17
  constructor(nativePrompt: ChatPromptClient);
18
18
  get name(): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mate-academy/prompt-client",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Provider-agnostic LLM prompt management client (Langfuse, InMemory)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",