Skip to content

Commit aed1391

Browse files
committed
Add support for dynamic transition functions + fix tts-say
Modified-by: Ovi Stoica <ovidiu.stoica1094@gmail.com>
1 parent e3cec1f commit aed1391

File tree

7 files changed

+48
-21
lines changed

7 files changed

+48
-21
lines changed

src/voice_fn/processors/deepgram.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ https://developers.deepgram.com/docs/understanding-end-of-speech-detection#using
169169
keep-alive #(loop []
170170
(when @alive?
171171
(a/<!! (a/timeout 3000))
172-
(t/log! {:level :debug :id :deepgram} "Sending keep-alive message")
172+
(t/log! {:level :trace :id :deepgram} "Sending keep-alive message")
173173
(ws/send! ws-conn keep-alive-payload)
174174
(recur)))]
175175
((flow/futurize write-to-ws :exec :io))

src/voice_fn/processors/elevenlabs.clj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@
157157
keep-alive #(loop []
158158
(when @alive?
159159
(a/<!! (a/timeout 3000))
160-
(t/log! {:level :debug :id :elevenlabs} "Sending keep-alive message")
160+
(t/log! {:level :trace :id :elevenlabs} "Sending keep-alive message")
161161
(ws/send! ws-conn keep-alive-message)
162162
(recur)))]
163163
((flow/futurize write-to-ws :exec :io))
@@ -192,5 +192,7 @@
192192
[(assoc state :audio/acc attempt)]))
193193
(cond
194194
(frame/speak-frame? msg)
195-
[state {:ws-write [msg]}]
195+
(do
196+
(t/log! {:id :elevenlabs :level :debug} ["SPEAK" (:frame/data msg)])
197+
[state {:ws-write [msg]}])
196198
:else [state])))}))

src/voice_fn/processors/llm_context_aggregator.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ S: Start, E: End, T: Transcription, I: Interim, X: Text
234234
;; function, to wait for the new context
235235
;; messages from the new scenario node
236236
:properties {:run-llm? (nil? transition-cb)
237-
:on-update transition-cb}}))
237+
:on-update #(transition-cb args)}}))
238238
(frame/llm-tool-call-result
239239
{:request tool-call-msg
240240
:result {:role :tool

src/voice_fn/processors/openai.clj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
(def openai-completions-url "https://api.openai.com/v1/chat/completions")
1515

1616
(defn stream-openai-chat-completion
17-
[{:keys [api-key messages tools model]}]
17+
[{:keys [api-key messages tools model]
18+
:or {model "gpt-4o-mini"}}]
1819
(:body (request/sse-request {:request {:url openai-completions-url
1920
:headers {"Authorization" (str "Bearer " api-key)
2021
"Content-Type" "application/json"}
@@ -23,8 +24,9 @@
2324
:body (u/json-str (cond-> {:messages messages
2425
:stream true
2526
:model model}
26-
tools (assoc :tools tools)))}
27+
(pos? (count tools)) (assoc :tools tools)))}
2728
:params {:stream/close? true}})))
29+
2830
(defn normal-chat-completion
2931
[{:keys [api-key messages tools model]}]
3032
(http/request {:url openai-completions-url
@@ -36,7 +38,7 @@
3638
:body (u/json-str (cond-> {:messages messages
3739
:stream true
3840
:model model}
39-
tools (assoc :tools tools)))}))
41+
(pos? (count tools)) (assoc :tools tools)))}))
4042

4143
(comment
4244
(require '[voice-fn.secrets :refer [secret]])

src/voice_fn/scenario_manager.clj

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
[:or
1818
[:map
1919
[:type (schema/flex-enum ["tts-say"])]
20-
[:text :string]]
20+
[:text [:or :string [:vector :string]]]]
2121
[:map
2222
[:type (schema/flex-enum ["end-conversation"])]]
2323
[:map
@@ -50,7 +50,8 @@
5050
vals
5151
(mapcat :functions)
5252
(keep (fn [f] (get-in f [:function :transition-to])))
53-
(remove nil?))
53+
(remove #(or (nil? %)
54+
(fn? %))))
5455
invalid-transition (first (remove nodes transitions))]
5556
(when invalid-transition
5657
(format "Unreachable node: %s" invalid-transition))))}
@@ -60,7 +61,8 @@
6061
vals
6162
(mapcat :functions)
6263
(keep (fn [f] (get-in f [:function :transition-to])))
63-
(remove nil?))]
64+
(remove #(or (nil? %)
65+
(fn? %))))]
6466
(every? defined-nodes transitions)))]])
6567

6668
(defprotocol Scenario
@@ -79,8 +81,13 @@
7981
[scenario tool]
8082
(let [fndef (:function tool)
8183
handler (:handler fndef)
82-
next-node (:transition-to fndef)
83-
cb #(set-node scenario next-node)]
84+
85+
next-node-or-fn (:transition-to fndef)
86+
cb (if (fn? next-node-or-fn)
87+
(fn [args]
88+
(let [next-node (next-node-or-fn args)]
89+
(set-node scenario next-node)))
90+
(fn [_] (set-node scenario next-node-or-fn)))]
8491
(cond-> tool
8592
true (update-in [:function] dissoc :transition-to)
8693
true (assoc-in [:function :transition-cb] cb)
@@ -96,10 +103,14 @@
96103
initialized? (atom false)
97104
tts-action? #(contains? #{:tts-say "tts-say"} (:type %))
98105
end-action? #(contains? #{:end-conversation "end-conversation"} (:type %))
99-
handle-action #(cond
100-
(tts-action? %) (flow/inject flow flow-in-coord [(frame/speak-frame (:text %))])
101-
(end-action? %) (flow/stop flow)
102-
:else ((:handler %)))]
106+
handle-action (fn [a]
107+
(cond
108+
(tts-action? a)
109+
(flow/inject flow flow-in-coord (if (coll? (:text a))
110+
(mapv #(frame/speak-frame %) (:text a))
111+
[(frame/speak-frame (:text a))]))
112+
(end-action? a) (flow/stop flow)
113+
:else ((:handler a))))]
103114
(reify Scenario
104115
(current-node [_] @current-node)
105116
(set-node [this node-id]
@@ -113,11 +124,10 @@
113124
(doseq [a (:post-actions node)] (handle-action a)))
114125
(reset! current-node node-id)
115126
(try
116-
(t/log! "Sending new scenario")
117-
(doseq [a (:pre-actions node)] (handle-action a))
118127
(flow/inject flow flow-in-coord [(frame/scenario-context-update {:messages append-context
119128
:tools tools
120129
:properties {:run-llm? (if (boolean? (:run-llm? node)) (:run-llm? node) true)}})])
130+
(doseq [a (:pre-actions node)] (handle-action a))
121131

122132
(catch Exception e
123133
(t/log! :error e)))))

src/voice_fn/schema.clj

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,13 +509,25 @@
509509
[:handler [:=> [:cat :map] :any]]
510510
[:transition-cb {:optional true} [:=> :cat :nil]]]]]))
511511

512+
(def TransitionTo
513+
[:or
514+
:keyword
515+
[:and
516+
[:=> [:cat :map] :any]
517+
[:fn {:error/message "Transition function must return a valid node keyword"}
518+
(fn [f]
519+
(try
520+
(let [result (f {})]
521+
(keyword? result))
522+
(catch Exception _ false)))]]])
523+
512524
(def LLMTransitionToolDefinition
513525
(mu/merge LLMFunctionToolDefinitionWithHandling
514526
[:map {:closed true}
515527
[:function
516528
[:map
517529
[:handler {:optional true} [:=> [:cat :map] :any]]
518-
[:transition-to :keyword]]]]))
530+
[:transition-to TransitionTo]]]]))
519531

520532
(def RegisteredFunctions [:map-of :string [:map
521533
[:tool [:=> [:cat :map] :any]]

src/voice_fn/transport.clj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[clojure.core.async.flow :as flow]
55
[taoensso.telemere :as t]
66
[uncomplicate.clojure-sound.core :refer [open! read! start! stop! write!]]
7-
[uncomplicate.clojure-sound.sampled :refer [audio-format line line-info]]
7+
[uncomplicate.clojure-sound.sampled :refer [audio-format flush! line line-info]]
88
[uncomplicate.commons.core :refer [close!]]
99
[voice-fn.frame :as frame]
1010
[voice-fn.schema :as schema]
@@ -14,7 +14,7 @@
1414
[voice-fn.utils.core :as u])
1515
(:import
1616
(java.util Arrays)
17-
(javax.sound.sampled AudioFormat AudioSystem DataLine$Info TargetDataLine)))
17+
(javax.sound.sampled AudioFormat AudioSystem DataLine$Info)))
1818

1919
(def AsyncOutputProcessorSchema
2020
[:map
@@ -233,6 +233,7 @@
233233
(when (= transition ::flow/stop)
234234
(when-let [line (::speaker-line state)]
235235
(stop! line)
236+
(flush! line)
236237
(close! line))
237238
(doseq [port (concat (vals in-ports) (vals out-ports))]
238239
(a/close! port))))

0 commit comments

Comments
 (0)