|
2 | 2 | (:require [clojure.test :refer [deftest is testing use-fixtures]] |
3 | 3 | [dscloj.core :as dscloj])) |
4 | 4 |
|
5 | | -(def api-key-available? |
6 | | - "Check if OPENAI_API_KEY is set in environment" |
7 | | - (not (nil? (System/getenv "OPENAI_API_KEY")))) |
| 5 | +(def openrouter-api-key |
| 6 | + "OpenRouter API key for live integration tests." |
| 7 | + (System/getenv "OPENROUTER_API_KEY")) |
| 8 | + |
| 9 | +(def openrouter-model |
| 10 | + "Cheap, deterministic default model for live integration tests. |
| 11 | + CI may override this with OPENROUTER_MODEL." |
| 12 | + (or (System/getenv "OPENROUTER_MODEL") |
| 13 | + "openai/gpt-4o-mini")) |
| 14 | + |
| 15 | +(defn- api-key-available? [] |
| 16 | + (boolean (seq openrouter-api-key))) |
| 17 | + |
| 18 | +(defn- ci? [] |
| 19 | + (= "true" (System/getenv "CI"))) |
8 | 20 |
|
9 | 21 | (defn setup-test-provider |
10 | | - "Fixture to register test provider and skip tests if API key is not available" |
| 22 | + "Register OpenRouter provider when OPENROUTER_API_KEY is available. |
| 23 | +
|
| 24 | + These tests intentionally keep one live provider smoke. Unit tests already cover |
| 25 | + DSCloj parsing/validation in detail; this verifies the generated prompt still |
| 26 | + works against a real chat-completions model without making DSCloj responsible |
| 27 | + for testing every litellm provider." |
11 | 28 | [f] |
12 | | - (if api-key-available? |
| 29 | + (cond |
| 30 | + (api-key-available?) |
13 | 31 | (do |
14 | | - ;; Register test provider |
15 | | - (dscloj/register-provider! :test-openai |
16 | | - {:provider :openai |
17 | | - :model "gpt-3.5-turbo" |
18 | | - :config {:api-key (System/getenv "OPENAI_API_KEY")}}) |
| 32 | + (dscloj/register-provider! :test-openrouter |
| 33 | + {:provider :openrouter |
| 34 | + :model openrouter-model |
| 35 | + :config {:api-key openrouter-api-key}}) |
19 | 36 | (f)) |
20 | | - (println "Skipping integration tests: OPENAI_API_KEY not set"))) |
| 37 | + |
| 38 | + (ci?) |
| 39 | + (throw (ex-info "OPENROUTER_API_KEY is required in CI for integration tests" {})) |
| 40 | + |
| 41 | + :else |
| 42 | + (println "Skipping integration tests: OPENROUTER_API_KEY not set"))) |
21 | 43 |
|
22 | 44 | (use-fixtures :once setup-test-provider) |
23 | 45 |
|
24 | | -(deftest ^:integration basic-qa-integration-test |
25 | | - (testing "Basic Q&A with OpenAI API" |
26 | | - (when api-key-available? |
27 | | - (let [qa-module {:inputs [{:name :question |
28 | | - :spec :string |
29 | | - :description "A question to answer"}] |
30 | | - :outputs [{:name :answer |
31 | | - :spec :string |
32 | | - :description "The answer to the question"}] |
33 | | - :instructions "Answer the question accurately and concisely."} |
34 | | - ;; Use registered provider |
35 | | - result (dscloj/predict :test-openai |
36 | | - qa-module |
37 | | - {:question "What is 2+2? Reply with just the number."} |
38 | | - {:temperature 0.0})] |
| 46 | +(deftest ^:integration live-structured-output-integration-test |
| 47 | + (testing "Live OpenRouter model can follow DSCloj structured output format" |
| 48 | + (when (api-key-available?) |
| 49 | + (let [module {:inputs [{:name :question |
| 50 | + :spec :string |
| 51 | + :description "A deterministic arithmetic question"}] |
| 52 | + :outputs [{:name :answer |
| 53 | + :spec :string |
| 54 | + :description "The answer as text"} |
| 55 | + {:name :numeric_answer |
| 56 | + :spec :int |
| 57 | + :description "The answer as an integer"} |
| 58 | + {:name :is_correct |
| 59 | + :spec :boolean |
| 60 | + :description "Whether the numeric answer is correct"}] |
| 61 | + :instructions (str "Answer the arithmetic question. " |
| 62 | + "Use 4 for numeric_answer. " |
| 63 | + "Use True for is_correct.")} |
| 64 | + result (dscloj/predict :test-openrouter |
| 65 | + module |
| 66 | + {:question "What is 2+2?"} |
| 67 | + {:temperature 0.0 |
| 68 | + :max-tokens 80})] |
39 | 69 | (is (map? result)) |
40 | 70 | (is (contains? result :answer)) |
41 | 71 | (is (string? (:answer result))) |
42 | | - (is (re-find #"4" (:answer result))))))) |
43 | | - |
44 | | -(deftest ^:integration boolean-output-integration-test |
45 | | - (testing "Boolean output parsing with OpenAI API" |
46 | | - (when api-key-available? |
47 | | - (let [validator-module {:inputs [{:name :statement |
48 | | - :spec :string |
49 | | - :description "A statement to verify"}] |
50 | | - :outputs [{:name :is_true |
51 | | - :spec :boolean |
52 | | - :description "Whether the statement is true or false"}] |
53 | | - :instructions "Determine if the statement is true or false."} |
54 | | - result (dscloj/predict :test-openai |
55 | | - validator-module |
56 | | - {:statement "The Earth orbits around the Sun."} |
57 | | - {:temperature 0.0})] |
58 | | - (is (map? result)) |
59 | | - (is (contains? result :is_true)) |
60 | | - (is (boolean? (:is_true result))) |
61 | | - (is (true? (:is_true result))))))) |
62 | | - |
63 | | -(deftest ^:integration multiple-outputs-integration-test |
64 | | - (testing "Multiple outputs with different types" |
65 | | - (when api-key-available? |
66 | | - (let [analyzer-module {:inputs [{:name :text |
67 | | - :spec :string |
68 | | - :description "Text to analyze"}] |
69 | | - :outputs [{:name :word_count |
70 | | - :spec :int |
71 | | - :description "Number of words"} |
72 | | - {:name :has_punctuation |
73 | | - :spec :boolean |
74 | | - :description "Whether text has punctuation"} |
75 | | - {:name :summary |
76 | | - :spec :string |
77 | | - :description "Brief summary"}] |
78 | | - :instructions "Analyze the text and provide word count, check for punctuation, and give a brief summary."} |
79 | | - result (dscloj/predict :test-openai |
80 | | - analyzer-module |
81 | | - {:text "Hello, world! This is a test."} |
82 | | - {:temperature 0.0})] |
83 | | - (is (map? result)) |
84 | | - (is (contains? result :word_count)) |
85 | | - (is (contains? result :has_punctuation)) |
86 | | - (is (contains? result :summary)) |
87 | | - (is (number? (:word_count result))) |
88 | | - (is (boolean? (:has_punctuation result))) |
89 | | - (is (string? (:summary result))) |
90 | | - (is (true? (:has_punctuation result))))))) |
| 72 | + (is (= 4 (:numeric_answer result))) |
| 73 | + (is (true? (:is_correct result))))))) |
0 commit comments