dv-pipecat-flows 0.0.22.dev916__tar.gz → 0.0.22.dev1116__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dv_pipecat_flows-0.0.22.dev916/src/dv_pipecat_flows.egg-info → dv_pipecat_flows-0.0.22.dev1116}/PKG-INFO +1 -1
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116/src/dv_pipecat_flows.egg-info}/PKG-INFO +1 -1
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/adapters.py +6 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/manager.py +32 -117
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/uv.lock +277 -1149
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.claude/skills/loki-logs/SKILL.md +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.claude/skills/loki-logs/query-reference.md +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.gitattributes +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.gitignore +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.pre-commit-config.yaml +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.python-version +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.readthedocs.yaml +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/AGENTS.md +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/CHANGELOG.md +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/CLAUDE.md +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/CONTRIBUTING.md +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/LICENSE +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/MANIFEST.in +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/README.md +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/dev-requirements.txt +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/docker-compose.dev.yml +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/.eslintrc.json +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/.prettierrc +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/css/tailwind.css +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/examples/food_ordering.json +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/examples/movie_explorer.json +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/examples/patient_intake.json +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/examples/restaurant_reservation.json +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/examples/travel_planner.json +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/favicon.png +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/favicon.svg +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/index.html +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/editor/canvas.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/editor/editorState.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/editor/sidePanel.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/editor/toolbar.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/main.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/baseNode.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/endNode.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/flowNode.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/functionNode.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/index.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/mergeNode.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/startNode.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/types.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/utils/export.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/utils/helpers.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/utils/import.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/utils/validation.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/jsdoc.json +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/package-lock.json +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/package.json +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/postcss.config.cjs +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/public/favicon.png +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/public/favicon.svg +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/tailwind.config.cjs +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/vercel.json +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/vite.config.js +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/env.example +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/images/food-ordering-flow.png +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/pipecat-flows.png +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/pipecat_upgrade.md +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/pyproject.toml +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/remote-asterisk-code/README.md +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/remote-asterisk-code/extensions.conf +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/remote-asterisk-code/rtp.conf +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/requirements.txt +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/scripts/check-pypi-package.py +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/scripts/fix-ruff.sh +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/scripts/pre-commit.sh +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/setup.cfg +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/dv_pipecat_flows.egg-info/SOURCES.txt +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/dv_pipecat_flows.egg-info/dependency_links.txt +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/dv_pipecat_flows.egg-info/requires.txt +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/dv_pipecat_flows.egg-info/top_level.txt +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/__init__.py +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/actions.py +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/data_extractor.py +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/exceptions.py +0 -0
- {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/types.py +0 -0
{dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/adapters.py
RENAMED
|
@@ -672,6 +672,12 @@ def create_adapter(llm, context_aggregator) -> LLMAdapter:
|
|
|
672
672
|
llm_type = type(llm).__name__
|
|
673
673
|
llm_class = type(llm)
|
|
674
674
|
|
|
675
|
+
if llm_type == "OpenAIResponsesLLMService":
|
|
676
|
+
# Responses API service uses universal LLMContext — should have been caught
|
|
677
|
+
# by the LLMContextAggregatorPair check above, but handle explicitly as a fallback
|
|
678
|
+
logger.debug("Creating universal adapter for OpenAI Responses API service")
|
|
679
|
+
return UniversalLLMAdapter()
|
|
680
|
+
|
|
675
681
|
if llm_type == "OpenAILLMService":
|
|
676
682
|
logger.debug("Creating OpenAI adapter")
|
|
677
683
|
return OpenAIAdapter()
|
{dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/manager.py
RENAMED
|
@@ -154,9 +154,6 @@ class FlowManager:
|
|
|
154
154
|
self._initialized = False
|
|
155
155
|
self._context_aggregator = context_aggregator
|
|
156
156
|
self._pending_transition: Optional[Dict[str, Any]] = None
|
|
157
|
-
self._transition_lock = asyncio.Lock()
|
|
158
|
-
self._transition_watchdog_task: Optional[asyncio.Task] = None
|
|
159
|
-
self._transition_attempt_count: int = 0
|
|
160
157
|
self._pending_function_results: Dict[str, Dict[str, Any]] = {} # Store function results for extraction
|
|
161
158
|
self._context_strategy = context_strategy or ContextStrategyConfig(
|
|
162
159
|
strategy=ContextStrategy.APPEND
|
|
@@ -572,7 +569,6 @@ class FlowManager:
|
|
|
572
569
|
"result": result,
|
|
573
570
|
}
|
|
574
571
|
self._pending_transition = transition_info
|
|
575
|
-
self._start_transition_watchdog()
|
|
576
572
|
|
|
577
573
|
properties = FunctionCallResultProperties(
|
|
578
574
|
run_llm=False, # Don't run LLM until transition completes
|
|
@@ -666,121 +662,19 @@ class FlowManager:
|
|
|
666
662
|
return metadata if metadata else None
|
|
667
663
|
|
|
668
664
|
async def _check_and_execute_transition(self) -> None:
|
|
669
|
-
"""Check if all functions are complete and execute transition if so.
|
|
670
|
-
|
|
671
|
-
Uses asyncio.shield to protect the transition from cancellation by
|
|
672
|
-
the aggregator's task management during user interruptions. A lock
|
|
673
|
-
prevents concurrent execution if both the on_context_updated callback
|
|
674
|
-
and the watchdog try to run simultaneously.
|
|
675
|
-
"""
|
|
665
|
+
"""Check if all functions are complete and execute transition if so."""
|
|
676
666
|
if not self._pending_transition:
|
|
677
667
|
return
|
|
678
668
|
|
|
679
669
|
# Check if all function calls are complete using Pipecat's state
|
|
680
670
|
assistant_aggregator = self._context_aggregator.assistant()
|
|
681
671
|
if not assistant_aggregator.has_function_calls_in_progress:
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
if not self._pending_transition:
|
|
689
|
-
return
|
|
690
|
-
|
|
691
|
-
transition_info = self._pending_transition
|
|
692
|
-
try:
|
|
693
|
-
await asyncio.shield(self._execute_transition(transition_info))
|
|
694
|
-
# Transition succeeded — clear pending state and watchdog
|
|
695
|
-
self._pending_transition = None
|
|
696
|
-
self._cancel_transition_watchdog()
|
|
697
|
-
except asyncio.CancelledError:
|
|
698
|
-
# asyncio.shield caught cancellation from the aggregator's
|
|
699
|
-
# task management during an interruption. The shielded inner
|
|
700
|
-
# coroutine continues running independently. The watchdog
|
|
701
|
-
# will verify completion or retry.
|
|
702
|
-
logger.warning(
|
|
703
|
-
"Transition execution shielded from cancellation; "
|
|
704
|
-
"watchdog will ensure completion"
|
|
705
|
-
)
|
|
706
|
-
except Exception as e:
|
|
707
|
-
# Hard failure — clear pending transition to avoid infinite retry
|
|
708
|
-
logger.error(f"Transition execution failed: {e}")
|
|
709
|
-
self._pending_transition = None
|
|
710
|
-
self._cancel_transition_watchdog()
|
|
711
|
-
|
|
712
|
-
async def _transition_watchdog(self) -> None:
|
|
713
|
-
"""Safety net that retries pending transitions if they don't complete.
|
|
714
|
-
|
|
715
|
-
Waits a short interval then checks if _pending_transition is still set.
|
|
716
|
-
If so, the on_context_updated path failed (stuck or cancelled) and we
|
|
717
|
-
retry the transition with the transition_message stripped to avoid
|
|
718
|
-
duplicate TTS output. Max 2 retries.
|
|
719
|
-
"""
|
|
720
|
-
max_attempts = 2
|
|
721
|
-
interval = 2.0
|
|
722
|
-
try:
|
|
723
|
-
while (
|
|
724
|
-
self._pending_transition
|
|
725
|
-
and self._transition_attempt_count < max_attempts
|
|
726
|
-
):
|
|
727
|
-
await asyncio.sleep(interval)
|
|
728
|
-
|
|
729
|
-
if not self._pending_transition:
|
|
730
|
-
logger.debug("Transition watchdog: transition completed normally")
|
|
731
|
-
return
|
|
732
|
-
|
|
733
|
-
self._transition_attempt_count += 1
|
|
734
|
-
logger.warning(
|
|
735
|
-
f"Transition watchdog: pending transition still active, "
|
|
736
|
-
f"attempt {self._transition_attempt_count}/{max_attempts}"
|
|
737
|
-
)
|
|
738
|
-
|
|
739
|
-
transition_info = self._pending_transition
|
|
740
|
-
try:
|
|
741
|
-
# Strip transition_message on retry to avoid duplicate TTS
|
|
742
|
-
retry_info = dict(transition_info)
|
|
743
|
-
if isinstance(retry_info.get("result"), dict):
|
|
744
|
-
retry_info["result"] = {
|
|
745
|
-
k: v
|
|
746
|
-
for k, v in retry_info["result"].items()
|
|
747
|
-
if k != "transition_message"
|
|
748
|
-
}
|
|
749
|
-
async with self._transition_lock:
|
|
750
|
-
if not self._pending_transition:
|
|
751
|
-
return
|
|
752
|
-
await self._execute_transition(retry_info)
|
|
753
|
-
self._pending_transition = None
|
|
754
|
-
logger.info("Transition watchdog: retry succeeded")
|
|
755
|
-
return
|
|
756
|
-
except Exception as e:
|
|
757
|
-
logger.error(f"Transition watchdog: retry failed: {e}")
|
|
758
|
-
|
|
759
|
-
if self._pending_transition:
|
|
760
|
-
logger.error(
|
|
761
|
-
f"Transition watchdog: exhausted {max_attempts} attempts, "
|
|
762
|
-
f"giving up on transition for "
|
|
763
|
-
f"{self._pending_transition.get('function_name')}"
|
|
764
|
-
)
|
|
765
|
-
self._pending_transition = None
|
|
766
|
-
except asyncio.CancelledError:
|
|
767
|
-
pass
|
|
768
|
-
finally:
|
|
769
|
-
self._transition_attempt_count = 0
|
|
770
|
-
|
|
771
|
-
def _start_transition_watchdog(self) -> None:
|
|
772
|
-
"""Start (or restart) the transition watchdog task."""
|
|
773
|
-
self._cancel_transition_watchdog()
|
|
774
|
-
self._transition_attempt_count = 0
|
|
775
|
-
self._transition_watchdog_task = asyncio.ensure_future(
|
|
776
|
-
self._transition_watchdog()
|
|
777
|
-
)
|
|
778
|
-
|
|
779
|
-
def _cancel_transition_watchdog(self) -> None:
|
|
780
|
-
"""Cancel any running transition watchdog."""
|
|
781
|
-
if self._transition_watchdog_task and not self._transition_watchdog_task.done():
|
|
782
|
-
self._transition_watchdog_task.cancel()
|
|
783
|
-
self._transition_watchdog_task = None
|
|
672
|
+
transition_info = self._pending_transition
|
|
673
|
+
self._pending_transition = None
|
|
674
|
+
try:
|
|
675
|
+
await self._execute_transition(transition_info)
|
|
676
|
+
except Exception as e:
|
|
677
|
+
logger.error(f"Transition execution failed: {e}")
|
|
784
678
|
|
|
785
679
|
async def _execute_transition(self, transition_info: Dict[str, Any]) -> None:
|
|
786
680
|
"""Execute the stored transition."""
|
|
@@ -798,11 +692,12 @@ class FlowManager:
|
|
|
798
692
|
transition_message = result.get("transition_message")
|
|
799
693
|
|
|
800
694
|
try:
|
|
801
|
-
#
|
|
695
|
+
# Queue transition_message TTS without waiting for completion.
|
|
696
|
+
# Using _execute_actions would block on _ongoing_actions_finished_event.wait()
|
|
697
|
+
# which deadlocks if the user interrupts (ActionFinishedFrame gets dropped).
|
|
802
698
|
if transition_message:
|
|
803
699
|
logger.debug(f"Speaking transition message: {transition_message[:50]}...")
|
|
804
|
-
|
|
805
|
-
await self._execute_actions(pre_actions=[transition_action])
|
|
700
|
+
await self._task.queue_frame(TTSSpeakFrame(text=transition_message))
|
|
806
701
|
|
|
807
702
|
if next_node: # Function-returned next node (consolidated function)
|
|
808
703
|
if isinstance(next_node, str): # Static flow
|
|
@@ -1143,7 +1038,6 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
1143
1038
|
# - Normal transition flow (already cleared in _check_and_execute_transition)
|
|
1144
1039
|
# - Direct calls to set_node/set_node_from_config
|
|
1145
1040
|
self._pending_transition = None
|
|
1146
|
-
self._cancel_transition_watchdog()
|
|
1147
1041
|
|
|
1148
1042
|
# Stop idle processor during node transition to prevent timeout interruptions
|
|
1149
1043
|
await self._task.queue_frame(StopUserIdleProcessorFrame())
|
|
@@ -1241,12 +1135,23 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
1241
1135
|
standard_functions, original_configs=functions_list
|
|
1242
1136
|
)
|
|
1243
1137
|
|
|
1138
|
+
# Collect mirror_context messages from tts_say pre-actions
|
|
1139
|
+
mirror_messages = []
|
|
1140
|
+
for action in node_config.get("pre_actions", []):
|
|
1141
|
+
if (
|
|
1142
|
+
action.get("type") == "tts_say"
|
|
1143
|
+
and action.get("mirror_context", True)
|
|
1144
|
+
and action.get("text")
|
|
1145
|
+
):
|
|
1146
|
+
mirror_messages.append({"role": "assistant", "content": action["text"]})
|
|
1147
|
+
|
|
1244
1148
|
# Update LLM context
|
|
1245
1149
|
await self._update_llm_context(
|
|
1246
1150
|
role_messages=node_config.get("role_messages"),
|
|
1247
1151
|
task_messages=node_config["task_messages"],
|
|
1248
1152
|
functions=formatted_tools,
|
|
1249
1153
|
strategy=node_config.get("context_strategy"),
|
|
1154
|
+
mirror_messages=mirror_messages,
|
|
1250
1155
|
)
|
|
1251
1156
|
logger.debug("Updated LLM context")
|
|
1252
1157
|
|
|
@@ -1292,6 +1197,7 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
1292
1197
|
task_messages: List[dict],
|
|
1293
1198
|
functions: List[dict],
|
|
1294
1199
|
strategy: Optional[ContextStrategyConfig] = None,
|
|
1200
|
+
mirror_messages: Optional[List[dict]] = None,
|
|
1295
1201
|
) -> None:
|
|
1296
1202
|
"""Update LLM context with new messages and functions.
|
|
1297
1203
|
|
|
@@ -1300,6 +1206,9 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
1300
1206
|
task_messages: Task messages to add to context.
|
|
1301
1207
|
functions: New functions to make available.
|
|
1302
1208
|
strategy: Optional context update configuration.
|
|
1209
|
+
mirror_messages: Optional assistant messages from tts_say pre-actions
|
|
1210
|
+
with mirror_context=True. These are appended after task_messages
|
|
1211
|
+
so the LLM sees what was spoken to the user.
|
|
1303
1212
|
|
|
1304
1213
|
Raises:
|
|
1305
1214
|
FlowError: If context update fails.
|
|
@@ -1349,6 +1258,12 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
1349
1258
|
# Add task messages
|
|
1350
1259
|
messages.extend(task_messages)
|
|
1351
1260
|
|
|
1261
|
+
# Add mirror messages (tts_say text mirrored as assistant context)
|
|
1262
|
+
# Placed after task_messages so the LLM sees: [system instructions] + [what was spoken]
|
|
1263
|
+
if mirror_messages:
|
|
1264
|
+
messages.extend(mirror_messages)
|
|
1265
|
+
logger.debug(f"Added {len(mirror_messages)} mirror message(s) to LLM context")
|
|
1266
|
+
|
|
1352
1267
|
# For first node or RESET/RESET_WITH_SUMMARY strategy, use update frame
|
|
1353
1268
|
frame_type = (
|
|
1354
1269
|
LLMMessagesUpdateFrame
|