Skip to content

Commit d9417a8

Browse files
committed
Sentence assembler: Use correct channel for outputting speak frames
1 parent fcb155b commit d9417a8

File tree

3 files changed

+46
-48
lines changed

3 files changed

+46
-48
lines changed

examples/src/simulflow_examples/local.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@
127127

128128
;; Assemble sentence by sentence for fast speech
129129
[[:llm :out] [:llm-sentence-assembler :in]]
130-
[[:llm-sentence-assembler :out] [:tts :in]]
130+
[[:llm-sentence-assembler :sys-out] [:tts :sys-in]]
131131

132132
[[:tts :out] [:audio-splitter :in]]
133133
[[:audio-splitter :out] [:transport-out :in]]

src/simulflow/processors/llm_context_aggregator.clj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@
361361
(if sentence
362362
(do
363363
(t/log! {:level :trace :id :sentence-assembler} ["New sentence assembled: " sentence])
364-
[(assoc state ::accumulator accumulator) {:out [(frame/speak-frame sentence)]}])
364+
[(assoc state ::accumulator accumulator) (frame/send (frame/speak-frame sentence))])
365365
[(assoc state ::accumulator accumulator)]))
366366

367367
;; Drop LLM chunks during interruption
@@ -370,14 +370,14 @@
370370

371371
:else [state {}]))
372372

373-
(defn- llm-sentence-assembler-impl
373+
(defn llm-sentence-assembler-fn
374374
"Takes in llm-text-chunk frames and returns a full sentence. Useful for
375375
generating speech sentence by sentence, instead of waiting for the full LLM message."
376376
([] {:ins {:in "Channel for llm text chunks"
377377
:sys-in "Channel for system messages"}
378378
:outs {:out "Channel for assembled speak frames"
379379
:sys-out "Channel for system frames"}})
380-
([_] {::accumulator nil})
380+
([_] {::accumulator ""})
381381
([state _transition]
382382
state)
383383
([state in msg]
@@ -425,4 +425,4 @@
425425
(def llm-sentence-assembler
426426
"Takes in llm-text-chunk frames and returns a full sentence. Useful for
427427
generating speech sentence by sentence, instead of waiting for the full LLM message."
428-
(flow/process #'llm-sentence-assembler-impl))
428+
(flow/process #'llm-sentence-assembler-fn))

test/simulflow/processors/llm_context_aggregator_test.clj

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
(ns simulflow.processors.llm-context-aggregator-test
22
(:require
33
[clojure.core.async :as a]
4-
[clojure.string :as str]
54
[clojure.test :refer [deftest is testing]]
65
[simulflow.frame :as frame]
76
[simulflow.mock-data :as mock]
8-
[simulflow.processors.llm-context-aggregator :as sut]
9-
[taoensso.telemere :as t]))
7+
[simulflow.processors.llm-context-aggregator :as sut]))
108

119
(deftest concat-context-test
1210
(testing "concat-context"
@@ -537,13 +535,13 @@
537535
;; Use real assemble-sentence function, no mocking
538536
(let [initial-state {::sut/accumulator "Hello"}
539537
text-chunk (frame/llm-text-chunk " world.")
540-
[new-state {:keys [out]}] (sut/llm-sentence-assembler-transform initial-state :in text-chunk)]
538+
[new-state {:keys [sys-out]}] (sut/llm-sentence-assembler-transform initial-state :in text-chunk)]
541539

542540
;; Should complete sentence and reset accumulator
543541
(is (= "" (::sut/accumulator new-state)))
544-
(is (= 1 (count out)))
545-
(is (frame/speak-frame? (first out)))
546-
(is (= "Hello world." (:frame/data (first out))))))
542+
(is (= 1 (count sys-out)))
543+
(is (frame/speak-frame? (first sys-out)))
544+
(is (= "Hello world." (:frame/data (first sys-out))))))
547545

548546
(testing "accumulates partial sentences"
549547
;; Test with text that doesn't end a sentence
@@ -574,22 +572,22 @@
574572
(testing "completes sentences with exclamation marks"
575573
(let [initial-state {::sut/accumulator "Hello world"}
576574
text-chunk (frame/llm-text-chunk "!")
577-
[new-state {:keys [out]}] (sut/llm-sentence-assembler-transform initial-state :in text-chunk)]
575+
[new-state {:keys [sys-out]}] (sut/llm-sentence-assembler-transform initial-state :in text-chunk)]
578576

579577
(is (= "" (::sut/accumulator new-state)))
580-
(is (= 1 (count out)))
581-
(is (frame/speak-frame? (first out)))
582-
(is (= "Hello world!" (:frame/data (first out))))))
578+
(is (= 1 (count sys-out)))
579+
(is (frame/speak-frame? (first sys-out)))
580+
(is (= "Hello world!" (:frame/data (first sys-out))))))
583581

584582
(testing "completes sentences with question marks"
585583
(let [initial-state {::sut/accumulator "How are you"}
586584
text-chunk (frame/llm-text-chunk "?")
587-
[new-state {:keys [out]}] (sut/llm-sentence-assembler-transform initial-state :in text-chunk)]
585+
[new-state {:keys [sys-out]}] (sut/llm-sentence-assembler-transform initial-state :in text-chunk)]
588586

589587
(is (= "" (::sut/accumulator new-state)))
590-
(is (= 1 (count out)))
591-
(is (frame/speak-frame? (first out)))
592-
(is (= "How are you?" (:frame/data (first out)))))))
588+
(is (= 1 (count sys-out)))
589+
(is (frame/speak-frame? (first sys-out)))
590+
(is (= "How are you?" (:frame/data (first sys-out)))))))
593591

594592
(testing "interruption handling"
595593
(testing "control-interrupt-start clears accumulator and sets interrupted state"
@@ -686,13 +684,13 @@
686684

687685
;; 4. After resuming, text chunks are processed again
688686
(let [new-text-chunk (frame/llm-text-chunk "New sentence.")
689-
[final-state {:keys [out]}] (sut/llm-sentence-assembler-transform
690-
resumed-state :in new-text-chunk)]
687+
[final-state {:keys [sys-out]}] (sut/llm-sentence-assembler-transform
688+
resumed-state :in new-text-chunk)]
691689
(is (= "" (::sut/accumulator final-state)))
692690
(is (= false (:pipeline/interrupted? final-state)))
693-
(is (= 1 (count out)))
694-
(is (frame/speak-frame? (first out)))
695-
(is (= "New sentence." (:frame/data (first out)))))))))
691+
(is (= 1 (count sys-out)))
692+
(is (frame/speak-frame? (first sys-out)))
693+
(is (= "New sentence." (:frame/data (first sys-out)))))))))
696694

697695
(testing "multiple interrupt commands are idempotent"
698696
(let [initial-state {::sut/accumulator "some text"}
@@ -785,38 +783,38 @@
785783
(let [initial-state {::sut/accumulator ""}
786784

787785
;; Build up a sentence word by word
788-
[state1 out1] (sut/llm-sentence-assembler-transform
789-
initial-state :in (frame/llm-text-chunk "The U.S.A. is"))
790-
[state2 out2] (sut/llm-sentence-assembler-transform
791-
state1 :in (frame/llm-text-chunk " a great"))
792-
[state3 {:keys [out]}] (sut/llm-sentence-assembler-transform
793-
state2 :in (frame/llm-text-chunk " country!"))]
786+
[state1 sys-out1] (sut/llm-sentence-assembler-transform
787+
initial-state :in (frame/llm-text-chunk "The U.S.A. is"))
788+
[state2 sys-out2] (sut/llm-sentence-assembler-transform
789+
state1 :in (frame/llm-text-chunk " a great"))
790+
[state3 {:keys [sys-out]}] (sut/llm-sentence-assembler-transform
791+
state2 :in (frame/llm-text-chunk " country!"))]
794792

795793
;; First two chunks should accumulate (no sentence end detected)
796-
(is (nil? out1))
797-
(is (nil? out2))
794+
(is (nil? sys-out1))
795+
(is (nil? sys-out2))
798796

799797
;; Third chunk with exclamation should complete sentence
800-
(is (= 1 (count out)))
801-
(is (frame/speak-frame? (first out)))
802-
(is (= "The U.S.A. is a great country!" (:frame/data (first out))))
798+
(is (= 1 (count sys-out)))
799+
(is (frame/speak-frame? (first sys-out)))
800+
(is (= "The U.S.A. is a great country!" (:frame/data (first sys-out))))
803801
(is (= "" (::sut/accumulator state3)))))
804802

805803
(testing "handles multiple sentence types"
806804
;; Test period
807-
(let [[_state {:keys [out]}] (sut/llm-sentence-assembler-transform
808-
{::sut/accumulator "Hello"} :in (frame/llm-text-chunk " world."))]
809-
(is (= 1 (count out)))
810-
(is (= "Hello world." (:frame/data (first out)))))
805+
(let [[_state {:keys [sys-out]}] (sut/llm-sentence-assembler-transform
806+
{::sut/accumulator "Hello"} :in (frame/llm-text-chunk " world."))]
807+
(is (= 1 (count sys-out)))
808+
(is (= "Hello world." (:frame/data (first sys-out)))))
811809

812810
;; Test question mark
813-
(let [[_state {:keys [out]}] (sut/llm-sentence-assembler-transform
814-
{::sut/accumulator "How are"} :in (frame/llm-text-chunk " you?"))]
815-
(is (= 1 (count out)))
816-
(is (= "How are you?" (:frame/data (first out)))))
811+
(let [[_state {:keys [sys-out]}] (sut/llm-sentence-assembler-transform
812+
{::sut/accumulator "How are"} :in (frame/llm-text-chunk " you?"))]
813+
(is (= 1 (count sys-out)))
814+
(is (= "How are you?" (:frame/data (first sys-out)))))
817815

818816
;; Test exclamation
819-
(let [[_state {:keys [out]}] (sut/llm-sentence-assembler-transform
820-
{::sut/accumulator "Great"} :in (frame/llm-text-chunk " job!"))]
821-
(is (= 1 (count out)))
822-
(is (= "Great job!" (:frame/data (first out)))))))))
817+
(let [[_state {:keys [sys-out]}] (sut/llm-sentence-assembler-transform
818+
{::sut/accumulator "Great"} :in (frame/llm-text-chunk " job!"))]
819+
(is (= 1 (count sys-out)))
820+
(is (= "Great job!" (:frame/data (first sys-out)))))))))

0 commit comments

Comments
 (0)