extensions.ts 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. import { codeBlock } from 'common-tags'
  2. import OpenAI from 'openai'
  3. import { expect } from 'vitest'
  4. expect.extend({
  5. async toMatchCriteria(received: string, criteria: string) {
  6. const openAiKey = process.env.OPENAI_API_KEY
  7. const openai = new OpenAI({ apiKey: openAiKey })
  8. const model = 'gpt-4o-2024-05-13'
  9. const completionResponse = await openai.chat.completions.create({
  10. model,
  11. messages: [
  12. {
  13. role: 'system',
  14. content: codeBlock`
  15. You are a test runner. Your job is to evaluate whether 'Received' adheres to the test 'Criteria'.
  16. You must output JSON, specifically an object containing a "pass" boolean and "reason" string:
  17. - \`{ "pass": true, "reason": "<reason>" }\` if 'Received' adheres to the test 'Criteria'
  18. - \`{ "pass": false, "reason": "<reason>" }\` if 'Received' does not adhere to the test 'Criteria'
  19. The "reason" must explain exactly which part of 'Received' did or did not pass the test 'Criteria'.
  20. `,
  21. },
  22. {
  23. role: 'user',
  24. content: codeBlock`
  25. Received:
  26. ${received}
  27. Criteria:
  28. ${criteria}
  29. `,
  30. },
  31. ],
  32. max_tokens: 256,
  33. temperature: 0,
  34. response_format: {
  35. type: 'json_object',
  36. },
  37. stream: false,
  38. })
  39. const [choice] = completionResponse.choices
  40. if (!choice.message.content) {
  41. throw new Error('LLM evaluator returned invalid response')
  42. }
  43. const { pass, reason }: { pass?: boolean; reason?: string } = JSON.parse(choice.message.content)
  44. if (pass === undefined) {
  45. throw new Error('LLM evaluator returned invalid response')
  46. }
  47. return {
  48. message: () =>
  49. codeBlock`
  50. ${this.utils.matcherHint('toMatchCriteria', received, criteria, {
  51. comment: `evaluated by LLM '${model}'`,
  52. isNot: this.isNot,
  53. promise: this.promise,
  54. })}
  55. ${reason}
  56. `,
  57. pass,
  58. }
  59. },
  60. })