Register
Login
Resources
Docs Blog Datasets Glossary Case Studies Tutorials & Webinars
Product
Data Engine LLMs Platform Enterprise
Pricing Explore
Connect to our Discord channel

evaluatorHelpers.langfuse.test.ts 13 KB

You have to be logged in to leave a comment. Sign In
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
  1. import { renderPrompt } from '../src/evaluatorHelpers';
  2. import * as langfuseIntegration from '../src/integrations/langfuse';
  3. import type { Prompt } from '../src/types';
  4. jest.mock('../src/integrations/langfuse');
  5. function toPrompt(text: string): Prompt {
  6. return { raw: text, label: text };
  7. }
  8. describe('renderPrompt - Langfuse integration', () => {
  9. let mockGetPrompt: jest.MockedFunction<typeof langfuseIntegration.getPrompt>;
  10. beforeEach(() => {
  11. jest.clearAllMocks();
  12. mockGetPrompt = jest.mocked(langfuseIntegration.getPrompt);
  13. });
  14. describe('version-based prompts', () => {
  15. it('should handle langfuse:// prompt with version only', async () => {
  16. mockGetPrompt.mockResolvedValue('Hello from Langfuse v3');
  17. const prompt = toPrompt('langfuse://test-prompt:3:text');
  18. const result = await renderPrompt(prompt, { name: 'World' }, {});
  19. expect(mockGetPrompt).toHaveBeenCalledWith(
  20. 'test-prompt',
  21. { name: 'World' },
  22. 'text',
  23. 3,
  24. undefined,
  25. );
  26. expect(result).toBe('Hello from Langfuse v3');
  27. });
  28. it('should handle langfuse:// prompt with version and type', async () => {
  29. mockGetPrompt.mockResolvedValue('[{"role": "system", "content": "You are helpful"}]');
  30. const prompt = toPrompt('langfuse://chat-prompt:2:chat');
  31. const result = await renderPrompt(prompt, { topic: 'AI' }, {});
  32. expect(mockGetPrompt).toHaveBeenCalledWith(
  33. 'chat-prompt',
  34. { topic: 'AI' },
  35. 'chat',
  36. 2,
  37. undefined,
  38. );
  39. expect(result).toBe('[{"role": "system", "content": "You are helpful"}]');
  40. });
  41. it('should handle latest version keyword', async () => {
  42. mockGetPrompt.mockResolvedValue('Latest prompt content');
  43. const prompt = toPrompt('langfuse://my-prompt:latest:text');
  44. const result = await renderPrompt(prompt, {}, {});
  45. // With dual syntax support, 'latest' is now treated as a label
  46. expect(mockGetPrompt).toHaveBeenCalledWith('my-prompt', {}, 'text', undefined, 'latest');
  47. expect(result).toBe('Latest prompt content');
  48. });
  49. });
  50. describe('label-based prompts', () => {
  51. it('should handle langfuse:// prompt with label only', async () => {
  52. mockGetPrompt.mockResolvedValue('Production prompt');
  53. const prompt = toPrompt('langfuse://my-prompt@production');
  54. const result = await renderPrompt(prompt, { env: 'prod' }, {});
  55. expect(mockGetPrompt).toHaveBeenCalledWith(
  56. 'my-prompt',
  57. { env: 'prod' },
  58. 'text',
  59. undefined,
  60. 'production',
  61. );
  62. expect(result).toBe('Production prompt');
  63. });
  64. it('should handle langfuse:// prompt with label and type', async () => {
  65. mockGetPrompt.mockResolvedValue('[{"role": "user", "content": "Hello from staging"}]');
  66. const prompt = toPrompt('langfuse://chat-prompt@staging:chat');
  67. const result = await renderPrompt(prompt, { user: 'Alice' }, {});
  68. expect(mockGetPrompt).toHaveBeenCalledWith(
  69. 'chat-prompt',
  70. { user: 'Alice' },
  71. 'chat',
  72. undefined,
  73. 'staging',
  74. );
  75. expect(result).toBe('[{"role": "user", "content": "Hello from staging"}]');
  76. });
  77. it('should handle latest label', async () => {
  78. mockGetPrompt.mockResolvedValue('Latest version via label');
  79. const prompt = toPrompt('langfuse://test@latest');
  80. const result = await renderPrompt(prompt, {}, {});
  81. expect(mockGetPrompt).toHaveBeenCalledWith('test', {}, 'text', undefined, 'latest');
  82. expect(result).toBe('Latest version via label');
  83. });
  84. it('should handle custom environment labels', async () => {
  85. mockGetPrompt.mockResolvedValue('Development prompt');
  86. const prompt = toPrompt('langfuse://feature-prompt@dev');
  87. const result = await renderPrompt(prompt, { feature: 'new' }, {});
  88. expect(mockGetPrompt).toHaveBeenCalledWith(
  89. 'feature-prompt',
  90. { feature: 'new' },
  91. 'text',
  92. undefined,
  93. 'dev',
  94. );
  95. expect(result).toBe('Development prompt');
  96. });
  97. it('should handle tenant-specific labels', async () => {
  98. mockGetPrompt.mockResolvedValue('Tenant A specific prompt');
  99. const prompt = toPrompt('langfuse://multi-tenant@tenant-a:chat');
  100. const result = await renderPrompt(prompt, { tenant: 'A' }, {});
  101. expect(mockGetPrompt).toHaveBeenCalledWith(
  102. 'multi-tenant',
  103. { tenant: 'A' },
  104. 'chat',
  105. undefined,
  106. 'tenant-a',
  107. );
  108. expect(result).toBe('Tenant A specific prompt');
  109. });
  110. it('should handle experiment labels', async () => {
  111. mockGetPrompt.mockResolvedValue('Experiment B prompt');
  112. const prompt = toPrompt('langfuse://ab-test@experiment-b');
  113. const result = await renderPrompt(prompt, { variant: 'B' }, {});
  114. expect(mockGetPrompt).toHaveBeenCalledWith(
  115. 'ab-test',
  116. { variant: 'B' },
  117. 'text',
  118. undefined,
  119. 'experiment-b',
  120. );
  121. expect(result).toBe('Experiment B prompt');
  122. });
  123. });
  124. describe('edge cases', () => {
  125. it('should handle prompt ID with @ symbol correctly with improved parsing', async () => {
  126. mockGetPrompt.mockResolvedValue('Prompt with @ in ID');
  127. // Now the improved parsing should handle this correctly
  128. // 'email@support@production' should parse as:
  129. // id='email@support', label='production'
  130. const prompt = toPrompt('langfuse://email@support@production');
  131. const result = await renderPrompt(prompt, {}, {});
  132. expect(mockGetPrompt).toHaveBeenCalledWith(
  133. 'email@support',
  134. {},
  135. 'text',
  136. undefined,
  137. 'production',
  138. );
  139. expect(result).toBe('Prompt with @ in ID');
  140. });
  141. it('should handle prompt ID with multiple @ symbols', async () => {
  142. mockGetPrompt.mockResolvedValue('Complex prompt ID');
  143. // 'user@domain@example@staging' should parse as:
  144. // id='user@domain@example', label='staging'
  145. const prompt = toPrompt('langfuse://user@domain@example@staging:chat');
  146. const result = await renderPrompt(prompt, {}, {});
  147. expect(mockGetPrompt).toHaveBeenCalledWith(
  148. 'user@domain@example',
  149. {},
  150. 'chat',
  151. undefined,
  152. 'staging',
  153. );
  154. expect(result).toBe('Complex prompt ID');
  155. });
  156. it('should handle prompt ID with colon', async () => {
  157. mockGetPrompt.mockResolvedValue('Prompt with : in ID');
  158. const prompt = toPrompt('langfuse://namespace:prompt@staging:chat');
  159. const result = await renderPrompt(prompt, {}, {});
  160. expect(mockGetPrompt).toHaveBeenCalledWith(
  161. 'namespace:prompt',
  162. {},
  163. 'chat',
  164. undefined,
  165. 'staging',
  166. );
  167. expect(result).toBe('Prompt with : in ID');
  168. });
  169. it('should handle missing type (defaults to text)', async () => {
  170. mockGetPrompt.mockResolvedValue('Default text prompt');
  171. const prompt = toPrompt('langfuse://simple@prod');
  172. const result = await renderPrompt(prompt, {}, {});
  173. expect(mockGetPrompt).toHaveBeenCalledWith('simple', {}, 'text', undefined, 'prod');
  174. expect(result).toBe('Default text prompt');
  175. });
  176. it('should handle errors from Langfuse', async () => {
  177. mockGetPrompt.mockRejectedValue(new Error('Prompt not found'));
  178. const prompt = toPrompt('langfuse://non-existent@production');
  179. await expect(renderPrompt(prompt, {}, {})).rejects.toThrow('Prompt not found');
  180. });
  181. it('should handle mixed case labels', async () => {
  182. mockGetPrompt.mockResolvedValue('Mixed case label prompt');
  183. const prompt = toPrompt('langfuse://test@Production:chat');
  184. const result = await renderPrompt(prompt, {}, {});
  185. expect(mockGetPrompt).toHaveBeenCalledWith('test', {}, 'chat', undefined, 'Production');
  186. expect(result).toBe('Mixed case label prompt');
  187. });
  188. it('should handle label with hyphens and underscores', async () => {
  189. mockGetPrompt.mockResolvedValue('Complex label prompt');
  190. const prompt = toPrompt('langfuse://test@prod-v2_final');
  191. const result = await renderPrompt(prompt, {}, {});
  192. expect(mockGetPrompt).toHaveBeenCalledWith('test', {}, 'text', undefined, 'prod-v2_final');
  193. expect(result).toBe('Complex label prompt');
  194. });
  195. });
  196. describe('prompt type variations', () => {
  197. it('should handle text type with label', async () => {
  198. mockGetPrompt.mockResolvedValue('Text prompt with label');
  199. const prompt = toPrompt('langfuse://text-example@production:text');
  200. const result = await renderPrompt(prompt, {}, {});
  201. expect(mockGetPrompt).toHaveBeenCalledWith(
  202. 'text-example',
  203. {},
  204. 'text',
  205. undefined,
  206. 'production',
  207. );
  208. expect(result).toBe('Text prompt with label');
  209. });
  210. it('should handle chat type with label', async () => {
  211. const chatMessages = [
  212. { role: 'system', content: 'You are a helpful assistant' },
  213. { role: 'user', content: '{{question}}' },
  214. ];
  215. mockGetPrompt.mockResolvedValue(JSON.stringify(chatMessages));
  216. const prompt = toPrompt('langfuse://chat-example@staging:chat');
  217. const result = await renderPrompt(prompt, { question: 'What is AI?' }, {});
  218. expect(mockGetPrompt).toHaveBeenCalledWith(
  219. 'chat-example',
  220. { question: 'What is AI?' },
  221. 'chat',
  222. undefined,
  223. 'staging',
  224. );
  225. expect(result).toBe(JSON.stringify(chatMessages));
  226. });
  227. });
  228. describe('dual syntax support', () => {
  229. it('should auto-detect string as label with : syntax', async () => {
  230. mockGetPrompt.mockResolvedValue('Production prompt via colon');
  231. const prompt = toPrompt('langfuse://my-prompt:production');
  232. const result = await renderPrompt(prompt, { env: 'prod' }, {});
  233. expect(mockGetPrompt).toHaveBeenCalledWith(
  234. 'my-prompt',
  235. { env: 'prod' },
  236. 'text',
  237. undefined,
  238. 'production',
  239. );
  240. expect(result).toBe('Production prompt via colon');
  241. });
  242. it('should auto-detect string as label with : syntax and type', async () => {
  243. mockGetPrompt.mockResolvedValue('[{"role": "system", "content": "Staging chat"}]');
  244. const prompt = toPrompt('langfuse://chat-prompt:staging:chat');
  245. const result = await renderPrompt(prompt, { user: 'Bob' }, {});
  246. expect(mockGetPrompt).toHaveBeenCalledWith(
  247. 'chat-prompt',
  248. { user: 'Bob' },
  249. 'chat',
  250. undefined,
  251. 'staging',
  252. );
  253. expect(result).toBe('[{"role": "system", "content": "Staging chat"}]');
  254. });
  255. it('should still treat numeric values as versions', async () => {
  256. mockGetPrompt.mockResolvedValue('Version 3 prompt');
  257. const prompt = toPrompt('langfuse://my-prompt:3:text');
  258. const result = await renderPrompt(prompt, {}, {});
  259. expect(mockGetPrompt).toHaveBeenCalledWith('my-prompt', {}, 'text', 3, undefined);
  260. expect(result).toBe('Version 3 prompt');
  261. });
  262. it('should treat "latest" as label with : syntax', async () => {
  263. mockGetPrompt.mockResolvedValue('Latest via colon');
  264. const prompt = toPrompt('langfuse://my-prompt:latest');
  265. const result = await renderPrompt(prompt, {}, {});
  266. expect(mockGetPrompt).toHaveBeenCalledWith('my-prompt', {}, 'text', undefined, 'latest');
  267. expect(result).toBe('Latest via colon');
  268. });
  269. it('should handle complex label names with : syntax', async () => {
  270. mockGetPrompt.mockResolvedValue('Complex label via colon');
  271. const prompt = toPrompt('langfuse://test:prod-v2_final:chat');
  272. const result = await renderPrompt(prompt, {}, {});
  273. expect(mockGetPrompt).toHaveBeenCalledWith('test', {}, 'chat', undefined, 'prod-v2_final');
  274. expect(result).toBe('Complex label via colon');
  275. });
  276. it('should prefer @ syntax when both could apply', async () => {
  277. mockGetPrompt.mockResolvedValue('Explicit @ syntax wins');
  278. // Even though "123" could be a version, @ makes it explicitly a label
  279. const prompt = toPrompt('langfuse://my-prompt@123:chat');
  280. const result = await renderPrompt(prompt, {}, {});
  281. expect(mockGetPrompt).toHaveBeenCalledWith('my-prompt', {}, 'chat', undefined, '123');
  282. expect(result).toBe('Explicit @ syntax wins');
  283. });
  284. it('should handle tenant labels with : syntax', async () => {
  285. mockGetPrompt.mockResolvedValue('Tenant A via colon');
  286. const prompt = toPrompt('langfuse://multi-tenant:tenant-a');
  287. const result = await renderPrompt(prompt, { tenant: 'A' }, {});
  288. expect(mockGetPrompt).toHaveBeenCalledWith(
  289. 'multi-tenant',
  290. { tenant: 'A' },
  291. 'text',
  292. undefined,
  293. 'tenant-a',
  294. );
  295. expect(result).toBe('Tenant A via colon');
  296. });
  297. it('should handle experiment labels with : syntax', async () => {
  298. mockGetPrompt.mockResolvedValue('Experiment B via colon');
  299. const prompt = toPrompt('langfuse://ab-test:experiment-b:chat');
  300. const result = await renderPrompt(prompt, { variant: 'B' }, {});
  301. expect(mockGetPrompt).toHaveBeenCalledWith(
  302. 'ab-test',
  303. { variant: 'B' },
  304. 'chat',
  305. undefined,
  306. 'experiment-b',
  307. );
  308. expect(result).toBe('Experiment B via colon');
  309. });
  310. });
  311. });
Tip!

Press p or to see the previous file or, n or to see the next file

Comments

Loading...