|
466 | 466 | (when-not (string/blank? @received-msgs*) |
467 | 467 | (add-to-history! {:role "assistant" :content [{:type :text :text @received-msgs*}]}) |
468 | 468 | (reset! received-msgs* "")) |
469 | | - (run! (fn do-tool-call [{:keys [id name arguments] :as tool-call}] |
470 | | - (let [approved?* (promise) |
471 | | - details (f.tools/tool-call-details-before-invocation name arguments) |
472 | | - summary (f.tools/tool-call-summary all-tools name arguments) |
473 | | - origin (tool-name->origin name all-tools) |
474 | | - approval (f.tools/approval all-tools name arguments db config behavior) |
475 | | - ask? (= :ask approval)] |
476 | | - ;; assert: In :preparing or :stopped |
477 | | - ;; Inform client the tool is about to run and store approval promise |
478 | | - (when-not (#{:stopped} (:status (get-tool-call-state @db* chat-id id))) |
479 | | - (transition-tool-call! db* chat-ctx id :tool-run |
480 | | - {:approved?* approved?* |
481 | | - :name name |
482 | | - :origin (tool-name->origin name all-tools) |
483 | | - :arguments arguments |
484 | | - :manual-approval ask? |
485 | | - :details details |
486 | | - :summary summary})) |
487 | | - ;; assert: In: :check-approval or :stopped or :rejected |
488 | | - (when-not (#{:stopped :rejected} (:status (get-tool-call-state @db* chat-id id))) |
489 | | - (case approval |
490 | | - :ask (transition-tool-call! db* chat-ctx id :approval-ask |
491 | | - {:state :running |
492 | | - :text "Waiting for tool call approval"}) |
493 | | - :allow (transition-tool-call! db* chat-ctx id :approval-allow |
494 | | - {:reason {:code :user-config-allow |
495 | | - :text "Tool call allowed by user config"}}) |
496 | | - :deny (transition-tool-call! db* chat-ctx id :approval-deny |
497 | | - {:reason {:code :user-config-deny |
498 | | - :text "Tool call denied by user config"}}) |
499 | | - (logger/warn logger-tag "Unknown value of approval in config" |
500 | | - {:approval approval :tool-call-id id}))) |
501 | | - ;; Execute each tool call concurrently |
502 | | - (if @approved?* ;TODO: Should there be a timeout here? If so, what would be the state transitions? |
503 | | - ;; assert: In :execution-approved or :stopped |
| 469 | + (let [any-rejected-tool-call?* (atom false)] |
| 470 | + (run! (fn do-tool-call [{:keys [id name arguments] :as tool-call}] |
| 471 | + (let [approved?* (promise) |
| 472 | + details (f.tools/tool-call-details-before-invocation name arguments) |
| 473 | + summary (f.tools/tool-call-summary all-tools name arguments) |
| 474 | + origin (tool-name->origin name all-tools) |
| 475 | + approval (f.tools/approval all-tools name arguments db config behavior) |
| 476 | + ask? (= :ask approval)] |
| 477 | + ;; assert: In :preparing or :stopped |
| 478 | + ;; Inform client the tool is about to run and store approval promise |
504 | 479 | (when-not (#{:stopped} (:status (get-tool-call-state @db* chat-id id))) |
505 | | - (assert-chat-not-stopped! chat-ctx) |
506 | | - (let [;; Since a future starts executing immediately, |
507 | | - ;; we need to delay the future so that the set-call-future action, |
508 | | - ;; used implicitly in the transition-tool-call! on the :execution-start event, |
509 | | - ;; can activate the future only *after* the state transition to :executing. |
510 | | - delayed-future |
511 | | - (delay |
512 | | - (future |
513 | | - ;; assert: In :executing |
514 | | - (let [result (f.tools/call-tool! name arguments behavior chat-id db* config messenger metrics) |
515 | | - details (f.tools/tool-call-details-after-invocation name arguments details result) |
516 | | - {:keys [start-time]} (get-tool-call-state @db* chat-id id)] |
517 | | - (add-to-history! {:role "tool_call" |
518 | | - :content (assoc tool-call |
519 | | - :details details |
520 | | - :summary summary |
521 | | - :origin origin)}) |
522 | | - (add-to-history! {:role "tool_call_output" |
523 | | - :content (assoc tool-call |
524 | | - :error (:error result) |
525 | | - :output result |
526 | | - :details details |
527 | | - :summary summary |
528 | | - :origin origin)}) |
529 | | - (transition-tool-call! db* chat-ctx id :execution-end |
530 | | - {:origin origin |
531 | | - :name name |
532 | | - :arguments arguments |
533 | | - :error (:error result) |
534 | | - :outputs (:contents result) |
535 | | - :total-time-ms (- (System/currentTimeMillis) start-time) |
536 | | - :details details |
537 | | - :summary summary}))))] |
538 | | - (transition-tool-call! db* chat-ctx id :execution-start |
539 | | - {:delayed-future delayed-future |
540 | | - :origin origin |
541 | | - :name name |
542 | | - :arguments arguments |
543 | | - :start-time (System/currentTimeMillis) |
544 | | - :details details |
545 | | - :summary summary}))) |
546 | | - ;; assert: In :rejected state |
547 | | - (let [tool-call-state (get-tool-call-state @db* chat-id id) |
548 | | - {:keys [code text]} (:decision-reason tool-call-state)] |
549 | | - (add-to-history! {:role "tool_call" :content tool-call}) |
550 | | - (add-to-history! {:role "tool_call_output" |
551 | | - :content (assoc tool-call :output {:error true |
552 | | - :contents [{:text text |
553 | | - :type :text}]})}) |
554 | | - (transition-tool-call! db* chat-ctx id :send-reject |
555 | | - {:origin origin |
| 480 | + (transition-tool-call! db* chat-ctx id :tool-run |
| 481 | + {:approved?* approved?* |
556 | 482 | :name name |
| 483 | + :origin (tool-name->origin name all-tools) |
557 | 484 | :arguments arguments |
558 | | - :reason code |
| 485 | + :manual-approval ask? |
559 | 486 | :details details |
560 | | - :summary summary}))))) |
561 | | - tool-calls) |
562 | | - (assert-chat-not-stopped! chat-ctx) |
563 | | - ;; Wait for all tool calls with futures to complete before returning |
564 | | - (run! deref (filter some? (map :future (vals (get-active-tool-calls @db* chat-id))))) |
565 | | - (send-content! chat-ctx :system {:type :progress :state :running :text "Generating"}) |
566 | | - {:new-messages (get-in @db* [:chats chat-id :messages])}) |
| 487 | + :summary summary})) |
| 488 | + ;; assert: In: :check-approval or :stopped or :rejected |
| 489 | + (when-not (#{:stopped :rejected} (:status (get-tool-call-state @db* chat-id id))) |
| 490 | + (case approval |
| 491 | + :ask (transition-tool-call! db* chat-ctx id :approval-ask |
| 492 | + {:state :running |
| 493 | + :text "Waiting for tool call approval"}) |
| 494 | + :allow (transition-tool-call! db* chat-ctx id :approval-allow |
| 495 | + {:reason {:code :user-config-allow |
| 496 | + :text "Tool call allowed by user config"}}) |
| 497 | + :deny (transition-tool-call! db* chat-ctx id :approval-deny |
| 498 | + {:reason {:code :user-config-deny |
| 499 | + :text "Tool call rejected by user config"}}) |
| 500 | + (logger/warn logger-tag "Unknown value of approval in config" |
| 501 | + {:approval approval :tool-call-id id}))) |
| 502 | + ;; Execute each tool call concurrently |
| 503 | + (if @approved?* ;TODO: Should there be a timeout here? If so, what would be the state transitions? |
| 504 | + ;; assert: In :execution-approved or :stopped |
| 505 | + (when-not (#{:stopped} (:status (get-tool-call-state @db* chat-id id))) |
| 506 | + (assert-chat-not-stopped! chat-ctx) |
| 507 | + (let [;; Since a future starts executing immediately, |
| 508 | + ;; we need to delay the future so that the set-call-future action, |
| 509 | + ;; used implicitly in the transition-tool-call! on the :execution-start event, |
| 510 | + ;; can activate the future only *after* the state transition to :executing. |
| 511 | + delayed-future |
| 512 | + (delay |
| 513 | + (future |
| 514 | + ;; assert: In :executing |
| 515 | + (let [result (f.tools/call-tool! name arguments behavior chat-id db* config messenger metrics) |
| 516 | + details (f.tools/tool-call-details-after-invocation name arguments details result) |
| 517 | + {:keys [start-time]} (get-tool-call-state @db* chat-id id)] |
| 518 | + (add-to-history! {:role "tool_call" |
| 519 | + :content (assoc tool-call |
| 520 | + :details details |
| 521 | + :summary summary |
| 522 | + :origin origin)}) |
| 523 | + (add-to-history! {:role "tool_call_output" |
| 524 | + :content (assoc tool-call |
| 525 | + :error (:error result) |
| 526 | + :output result |
| 527 | + :details details |
| 528 | + :summary summary |
| 529 | + :origin origin)}) |
| 530 | + (transition-tool-call! db* chat-ctx id :execution-end |
| 531 | + {:origin origin |
| 532 | + :name name |
| 533 | + :arguments arguments |
| 534 | + :error (:error result) |
| 535 | + :outputs (:contents result) |
| 536 | + :total-time-ms (- (System/currentTimeMillis) start-time) |
| 537 | + :details details |
| 538 | + :summary summary}))))] |
| 539 | + (transition-tool-call! db* chat-ctx id :execution-start |
| 540 | + {:delayed-future delayed-future |
| 541 | + :origin origin |
| 542 | + :name name |
| 543 | + :arguments arguments |
| 544 | + :start-time (System/currentTimeMillis) |
| 545 | + :details details |
| 546 | + :summary summary}))) |
| 547 | + ;; assert: In :rejected state |
| 548 | + (let [tool-call-state (get-tool-call-state @db* chat-id id) |
| 549 | + {:keys [code text]} (:decision-reason tool-call-state)] |
| 550 | + (add-to-history! {:role "tool_call" :content tool-call}) |
| 551 | + (add-to-history! {:role "tool_call_output" |
| 552 | + :content (assoc tool-call :output {:error true |
| 553 | + :contents [{:text text |
| 554 | + :type :text}]})}) |
| 555 | + (reset! any-rejected-tool-call?* true) |
| 556 | + (transition-tool-call! db* chat-ctx id :send-reject |
| 557 | + {:origin origin |
| 558 | + :name name |
| 559 | + :arguments arguments |
| 560 | + :reason code |
| 561 | + :details details |
| 562 | + :summary summary}))))) |
| 563 | + tool-calls) |
| 564 | + (assert-chat-not-stopped! chat-ctx) |
| 565 | + ;; Wait for all tool calls with futures to complete before returning |
| 566 | + (->> (vals (get-active-tool-calls @db* chat-id)) |
| 567 | + (map :future) |
| 568 | + (filter some?) |
| 569 | + (run! deref)) |
| 570 | + (if @any-rejected-tool-call?* |
| 571 | + (do |
| 572 | + (send-content! chat-ctx :system |
| 573 | + {:type :text |
| 574 | + :text "Tell ECA what to do differently for the rejected tool"}) |
| 575 | + (finish-chat-prompt! :idle chat-ctx) |
| 576 | + nil) |
| 577 | + (do |
| 578 | + (send-content! chat-ctx :system {:type :progress :state :running :text "Generating"}) |
| 579 | + {:new-messages (get-in @db* [:chats chat-id :messages])})))) |
567 | 580 | :on-reason (fn [{:keys [status id text external-id]}] |
568 | 581 | (assert-chat-not-stopped! chat-ctx) |
569 | 582 | (case status |
|
727 | 740 | :messenger messenger}] |
728 | 741 | (transition-tool-call! db* chat-ctx tool-call-id :user-reject |
729 | 742 | {:reason {:code :user-choice-deny |
730 | | - :text "Tool call denied by user choice"}}))) |
| 743 | + :text "Tool call rejected by user choice"}}))) |
731 | 744 |
|
732 | 745 | (defn query-context |
733 | 746 | [{:keys [query contexts chat-id]} |
|
0 commit comments