Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/yetibot/core/commands/grok.clj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
(let [prompt match
_ (info "grok: generating text for prompt:" prompt)
text (xai/generate-text prompt)
footer "\n\nSent via grok-4.6"]
footer (format "\n\nSent via grok-4.6 | Cost: $%.3f" (xai/cost-per-prompt))]
{:result/value (str text footer)
:result/data {:prompt prompt :response text}})
(catch Exception e
Expand Down
2 changes: 1 addition & 1 deletion src/yetibot/core/commands/image.clj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
id (store-image! image)
base-url (gemini/yetibot-base-url)
image-url (format "%s/generated-images/%s.png" base-url id)
footer "\n\nSent via grok-imagine-image-2.0"]
footer (format "\n\nSent via grok-imagine-image-2.0 | Cost: $%.3f" (xai/cost-per-image))]
(info "image: image generated successfully, serving at" image-url)
{:result/value (str image-url footer)
:result/data {:id id :prompt match :url image-url}})
Expand Down
25 changes: 24 additions & 1 deletion src/yetibot/core/util/xai.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,36 @@
[yetibot.core.config :refer [get-config]]))

(s/def ::key string?)
(s/def ::config (s/keys :req-un [::key]))
(s/def ::cost-per-image (s/or :string string? :number number?))
(s/def ::cost-per-prompt (s/or :string string? :number number?))
(s/def ::config (s/keys :req-un [::key] :opt-un [::cost-per-image ::cost-per-prompt]))

(def config (or (:value (get-config ::config [:xai])) {}))

(defn configured? []
(some? (:key config)))

(defn- parse-number
"Parse a string to a number, returning the number unchanged if it's already a number.
Returns nil if parsing fails."
[v]
(if (string? v)
(try
(Double/parseDouble v)
(catch Exception _ nil))
v))

(def ^:private default-cost-per-image 0.04)
(def ^:private default-cost-per-prompt 0.01)

(defn cost-per-image []
(or (parse-number (:cost-per-image config))
default-cost-per-image))

(defn cost-per-prompt []
(or (parse-number (:cost-per-prompt config))
default-cost-per-prompt))

(defn generate-image
"Call the xAI API to generate an image from a text prompt."
[prompt]
Expand Down
3 changes: 2 additions & 1 deletion test/yetibot/core/test/commands/grok_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@

(fact "it generates text and returns response with model footer when configured"
(g/grok-cmd {:match "what is 2+2" :chat-source {}})
=> (contains {:result/value "4\n\nSent via grok-4.6"
=> (contains {:result/value "4\n\nSent via grok-4.6 | Cost: $0.010"
:result/data {:prompt "what is 2+2" :response "4"}})
(provided
(xai/configured?) => true
(xai/cost-per-prompt) => 0.01
(xai/generate-text "what is 2+2") => "4")))
3 changes: 2 additions & 1 deletion test/yetibot/core/test/commands/image_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@

(fact "it generates an image and returns URL with model footer when configured"
(img/image-cmd {:match "image space kitty" :chat-source {}})
=> (contains {:result/value #"http://localhost:3003/generated-images/grok123.png\n\nSent via grok-imagine-image-2.0"
=> (contains {:result/value #"http://localhost:3003/generated-images/grok123.png\n\nSent via grok-imagine-image-2.0 \| Cost: \$0.040"
:result/data {:id "grok123" :prompt "image space kitty" :url "http://localhost:3003/generated-images/grok123.png"}})
(provided
(xai/configured?) => true
(xai/cost-per-image) => 0.04
(yetibot.core.util.image-input/extract-images "image space kitty" {}) => {:prompt "space kitty" :image-urls []}
(xai/generate-image "space kitty") => {:data "grokbytes" :mime-type "image/jpeg"}
(yetibot.core.webapp.routes.images/store-image! {:data "grokbytes" :mime-type "image/jpeg"}) => "grok123"
Expand Down
22 changes: 22 additions & 0 deletions test/yetibot/core/test/util/xai_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,28 @@
(with-redefs [xai/config {}]
(xai/configured?)) => false))

(facts "about xai cost-per-image"
(fact "it returns default if not overridden in config"
(with-redefs [xai/config {:key "secret-key"}]
(xai/cost-per-image)) => 0.04)
(fact "it returns the configured number value"
(with-redefs [xai/config {:key "secret-key" :cost-per-image 0.055}]
(xai/cost-per-image)) => 0.055)
(fact "it returns the parsed string value from config"
(with-redefs [xai/config {:key "secret-key" :cost-per-image "0.065"}]
(xai/cost-per-image)) => 0.065))

(facts "about xai cost-per-prompt"
(fact "it returns default if not overridden in config"
(with-redefs [xai/config {:key "secret-key"}]
(xai/cost-per-prompt)) => 0.01)
(fact "it returns the configured number value"
(with-redefs [xai/config {:key "secret-key" :cost-per-prompt 0.025}]
(xai/cost-per-prompt)) => 0.025)
(fact "it returns the parsed string value from config"
(with-redefs [xai/config {:key "secret-key" :cost-per-prompt "0.035"}]
(xai/cost-per-prompt)) => 0.035))

(facts "about xai generate-image"
(fact "it generates an image successfully when API returns valid data"
(with-redefs [xai/config {:key "secret-key"}]
Expand Down