@mate-academy/prompt-client 2.0.0 → 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,11 +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
- With `fallback` set, `getPrompt` never rejects: on any failure it resolves to
108
- the fallback text with `version: 0` and `isFallback: true`, and the logger
109
- receives a warning.
108
+ `LLMPromptListError` (thrown only by `listPrompts`, see [Catalog](#catalog-for-codegen))
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'`.
114
+
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.
110
119
 
111
120
  ### Chat prompts
112
121
 
@@ -128,8 +137,9 @@ prompt.compile({ leadName: 'Maria' }); // substitutes {{var}} in each message's
128
137
  Fallback is an `LLMPromptMessage[]` instead of a string, with the same
129
138
  never-throw / `version: 0` / `isFallback: true` semantics as `getPrompt`.
130
139
  Langfuse placeholder entries and messages with an unrecognized role are
131
- silently skipped from `messages`/`compile()`, with a warning logged so the
132
- 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.
133
143
 
134
144
  ### Catalog (for codegen)
135
145
 
@@ -139,6 +149,7 @@ Langfuse-prompt codegen (the gateway's `generateSnapshot`):
139
149
  ```typescript
140
150
  const { promptNames, page, totalPages } = await promptClient.listPrompts({
141
151
  label: 'production',
152
+ tag: 'catalog', // optional; omit to list every prompt carrying the label
142
153
  page: 1, // default 1
143
154
  pageSize: 100, // default 100
144
155
  });
@@ -149,6 +160,12 @@ const record = await promptClient.getPromptRecord('chatAgent.instructions', {
149
160
  // record.type === LLMPromptTypes.Text | LLMPromptTypes.Chat
150
161
  ```
151
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
+
152
169
  Both methods are uncached and never fall back — they **throw** on failure
153
170
  (`LLMPromptListError` for `listPrompts`, `LLMPromptNotFoundError` /
154
171
  `LLMPromptFetchError` for `getPromptRecord`) so a codegen run can fail loudly
@@ -260,6 +277,10 @@ never applies to `getPromptRecord` — a missing seed always throws
260
277
  `LLMPromptNotFoundError`, since the catalog must report seeded truth for
261
278
  codegen tests.
262
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
+
263
284
  ## Provider notes (Langfuse)
264
285
 
265
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,10 @@ 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
+ const FALLBACK_PROMPT_VERSION = 0;
13
14
  class LangfusePromptClient {
14
15
  constructor(client, providerOptions, logger) {
15
16
  this.client = client;
@@ -30,9 +31,10 @@ class LangfusePromptClient {
30
31
  return new LangfusePrompt_1.LangfuseChatPrompt(nativePrompt);
31
32
  }
32
33
  async listPrompts(options) {
34
+ const selection = this.resolveListSelection(options);
33
35
  try {
34
36
  const promptListPage = await this.client.api.prompts.list({
35
- label: options.label,
37
+ ...selection,
36
38
  page: options.page ?? LLMPromptClient_constants_1.DEFAULT_PROMPT_LIST_PAGE,
37
39
  limit: options.pageSize ?? LLMPromptClient_constants_1.DEFAULT_PROMPT_LIST_PAGE_SIZE,
38
40
  }, this.buildRequestOptions());
@@ -45,9 +47,9 @@ class LangfusePromptClient {
45
47
  catch (error) {
46
48
  this.logger.error('Failed to list Langfuse prompts', {
47
49
  error,
48
- label: options.label,
50
+ ...selection,
49
51
  });
50
- throw new LLMPromptClient_errors_1.LLMPromptListError(options.label, error);
52
+ throw new LLMPromptClient_errors_1.LLMPromptListError(selection, error);
51
53
  }
52
54
  }
53
55
  async getPromptRecord(name, options) {
@@ -87,6 +89,7 @@ class LangfusePromptClient {
87
89
  }
88
90
  catch (error) {
89
91
  if (options?.fallback !== undefined) {
92
+ this.reportFallbackFetchError(name, error);
90
93
  return this.buildTextFallbackPrompt(name, options.fallback, options);
91
94
  }
92
95
  throw this.classifyFetchError(name, error);
@@ -105,11 +108,18 @@ class LangfusePromptClient {
105
108
  }
106
109
  catch (error) {
107
110
  if (options?.fallback !== undefined) {
111
+ this.reportFallbackFetchError(name, error);
108
112
  return this.buildChatFallbackPrompt(name, options.fallback, options);
109
113
  }
110
114
  throw this.classifyFetchError(name, error);
111
115
  }
112
116
  }
117
+ reportFallbackFetchError(name, error) {
118
+ this.logger.error('Failed to fetch Langfuse prompt; using fallback', {
119
+ error,
120
+ promptName: name,
121
+ });
122
+ }
113
123
  resolveSelectionOptions(options) {
114
124
  if (options?.version !== undefined) {
115
125
  return { version: options.version };
@@ -128,7 +138,7 @@ class LangfusePromptClient {
128
138
  buildTextFallbackPrompt(name, fallback, options) {
129
139
  return new client_1.TextPromptClient({
130
140
  name,
131
- version: options?.version ?? 0,
141
+ version: FALLBACK_PROMPT_VERSION,
132
142
  labels: this.resolveFallbackLabels(options),
133
143
  tags: [],
134
144
  config: {},
@@ -143,7 +153,7 @@ class LangfusePromptClient {
143
153
  buildChatFallbackPrompt(name, fallback, options) {
144
154
  return new client_1.ChatPromptClient({
145
155
  name,
146
- version: options?.version ?? 0,
156
+ version: FALLBACK_PROMPT_VERSION,
147
157
  labels: this.resolveFallbackLabels(options),
148
158
  tags: [],
149
159
  config: {},
@@ -208,6 +218,13 @@ class LangfusePromptClient {
208
218
  });
209
219
  }
210
220
  }
221
+ resolveListSelection(options) {
222
+ const requestedTag = options.tag ?? undefined;
223
+ return {
224
+ label: options.label,
225
+ ...(requestedTag === undefined ? {} : { tag: requestedTag }),
226
+ };
227
+ }
211
228
  async fetchRawPrompt(name, options) {
212
229
  try {
213
230
  return await this.client.api.prompts.get(name, { label: options.label }, this.buildRequestOptions());
@@ -223,7 +240,7 @@ class LangfusePromptClient {
223
240
  : {}),
224
241
  ...(this.providerOptions.fetchTimeoutMs !== undefined
225
242
  ? {
226
- timeoutInSeconds: Math.ceil(this.providerOptions.fetchTimeoutMs / MILLISECONDS_PER_SECOND),
243
+ timeoutInSeconds: Math.ceil(this.providerOptions.fetchTimeoutMs / Langfuse_constants_1.MILLISECONDS_PER_SECOND),
227
244
  }
228
245
  : {}),
229
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.0",
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",