Skip to content

Commit 1951c37

Browse files
committed
Emit llm-tool-call-request frame when the LLM requests a tool call
1 parent 5b86363 commit 1951c37

File tree

4 files changed

+39
-30
lines changed

4 files changed

+39
-30
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ All notable changes to this project will be documented in this file. This change
2222
- **Transport In (twilio, async and microphone**: Moved params parsing to use malli schemas, see the schemas defined [here](./src/simulflow/transport/in.clj).
2323
- **Examples**: Added example of local microphone AI agent with interruption capability. See [here](./examples/src/simulflow_examples/local_w_interruption_support.clj)
2424
- **Transport In (twilio, async and microphone)**: Added muting support. When transports receive `mute-input-start`, the input isn't processed further and processing resumes when `mute-input-stop` frame is received. Useful for guided conversations where you don't want the user to speak over the bot, during initial greetings or during function calls.
25+
- **User Context Aggregator**: Now emits `frame/llm-tool-call-request` when a the llm requests a tool call. This frame can be used to make the agent say something while the tool handler is called, or trigger a mute filter while executing tool call.
2526

2627
### Changed
2728
- Moved most of the llm logic from [openai processor](./src/simulflow/processors/openai.clj) to an utils folder to be used by multiple processors like [gemini](./src/simulflow/processors/google.clj)

src/simulflow/frame.clj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,9 @@
419419
::scenario-context-update
420420
::speak-frame
421421
::mute-input-start
422-
::mute-input-stop})
422+
::mute-input-stop
423+
::llm-tool-call-request
424+
::llm-tool-call-result})
423425

424426
(defn system-frame?
425427
"Returns true if the frame is a system frame that should be processed immediately"

src/simulflow/processors/llm_context_aggregator.clj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,12 @@
181181
(frame/llm-context-messages-append? frame)
182182
,(let [{new-messages :messages
183183
opts :properties} (:frame/data frame)
184-
nc (update-in (:llm/context state) [:messages] into new-messages)]
184+
nc (update-in (:llm/context state) [:messages] into new-messages)
185+
tool-request-message (first (filter #(seq (:tool_calls %)) new-messages))]
185186
[(assoc state :llm/context nc) (cond-> {}
186187
;; if it's a tool call, send to the tool-caller to obtain the result
187-
(:tool-call? opts) (assoc :tool-write [(frame/llm-context nc)])
188+
(:tool-call? opts) (assoc :tool-write [(frame/llm-context nc)]
189+
:sys-out [(frame/llm-tool-call-request tool-request-message)])
188190
;; send new context further to the LLM
189191
(:run-llm? opts) (assoc :out [(frame/llm-context nc)]))])
190192

@@ -289,7 +291,7 @@
289291
:function-arguments nil
290292
:tool-call-id nil)
291293

292-
id "assistant-context-assembler"]
294+
id :assistant-context-assembler]
293295
(cond
294296

295297
(frame/llm-full-response-start? frame)

test/simulflow/processors/llm_context_aggregator_test.clj

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -258,32 +258,36 @@
258258
(get-in (first out) [:frame/data :messages]))))
259259

260260
;; Case 2: Append with tool call
261-
(let [[new-state {:keys [out tool-write]}]
262-
(sut/context-aggregator-transform
263-
state nil
264-
(frame/llm-context-messages-append
265-
{:messages (conj new-messages tool-request)
266-
:properties {:tool-call? true
267-
:run-llm? false}}))]
268-
269-
;; Check state update
270-
(is (= [{:role :system :content "Initial context"}
271-
{:role :user :content "Hello"}
272-
{:role :assistant :content "Hi there"}
273-
tool-request]
274-
(get-in new-state [:llm/context :messages])))
275-
276-
;; Check if message was sent to tool-write channel
277-
(is (= 1 (count tool-write)))
278-
(is (frame/llm-context? (first tool-write)))
279-
(is (= [{:role :system :content "Initial context"}
280-
{:role :user :content "Hello"}
281-
{:role :assistant :content "Hi there"}
282-
tool-request]
283-
(get-in (first tool-write) [:frame/data :messages])))
284-
285-
;; Verify no message sent to out channel when only tool-call? is true
286-
(is (nil? out)))
261+
(testing "Handles tool-call request messages correctly"
262+
(let [[new-state {:keys [out tool-write sys-out]}]
263+
(sut/context-aggregator-transform
264+
state nil
265+
(frame/llm-context-messages-append
266+
{:messages (conj new-messages tool-request)
267+
:properties {:tool-call? true
268+
:run-llm? false}}))
269+
[tool-call-request-frame] sys-out]
270+
271+
;; Check state update
272+
(is (= [{:role :system :content "Initial context"}
273+
{:role :user :content "Hello"}
274+
{:role :assistant :content "Hi there"}
275+
tool-request]
276+
(get-in new-state [:llm/context :messages])))
277+
278+
;; Check if message was sent to tool-write channel
279+
(is (= 1 (count tool-write)))
280+
(is (frame/llm-context? (first tool-write)))
281+
(is (= [{:role :system :content "Initial context"}
282+
{:role :user :content "Hello"}
283+
{:role :assistant :content "Hi there"}
284+
tool-request]
285+
(get-in (first tool-write) [:frame/data :messages])))
286+
287+
;; Verify no message sent to out channel when only tool-call? is true
288+
(is (nil? out))
289+
(is (frame/llm-tool-call-request? tool-call-request-frame))
290+
(is (= (:frame/data tool-call-request-frame) tool-request))))
287291

288292
;; Case 3: Append with both tool call and run-llm
289293
(let [[new-state {:keys [out tool-write]}]

0 commit comments

Comments
 (0)