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.
Files changed (80) hide show
  1. {dv_pipecat_flows-0.0.22.dev916/src/dv_pipecat_flows.egg-info → dv_pipecat_flows-0.0.22.dev1116}/PKG-INFO +1 -1
  2. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116/src/dv_pipecat_flows.egg-info}/PKG-INFO +1 -1
  3. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/adapters.py +6 -0
  4. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/manager.py +32 -117
  5. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/uv.lock +277 -1149
  6. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.claude/skills/loki-logs/SKILL.md +0 -0
  7. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.claude/skills/loki-logs/query-reference.md +0 -0
  8. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.gitattributes +0 -0
  9. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.gitignore +0 -0
  10. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.pre-commit-config.yaml +0 -0
  11. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.python-version +0 -0
  12. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/.readthedocs.yaml +0 -0
  13. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/AGENTS.md +0 -0
  14. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/CHANGELOG.md +0 -0
  15. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/CLAUDE.md +0 -0
  16. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/CONTRIBUTING.md +0 -0
  17. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/LICENSE +0 -0
  18. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/MANIFEST.in +0 -0
  19. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/README.md +0 -0
  20. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/dev-requirements.txt +0 -0
  21. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/docker-compose.dev.yml +0 -0
  22. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/.eslintrc.json +0 -0
  23. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/.prettierrc +0 -0
  24. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/css/tailwind.css +0 -0
  25. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/examples/food_ordering.json +0 -0
  26. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/examples/movie_explorer.json +0 -0
  27. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/examples/patient_intake.json +0 -0
  28. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/examples/restaurant_reservation.json +0 -0
  29. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/examples/travel_planner.json +0 -0
  30. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/favicon.png +0 -0
  31. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/favicon.svg +0 -0
  32. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/index.html +0 -0
  33. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/editor/canvas.js +0 -0
  34. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/editor/editorState.js +0 -0
  35. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/editor/sidePanel.js +0 -0
  36. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/editor/toolbar.js +0 -0
  37. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/main.js +0 -0
  38. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/baseNode.js +0 -0
  39. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/endNode.js +0 -0
  40. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/flowNode.js +0 -0
  41. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/functionNode.js +0 -0
  42. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/index.js +0 -0
  43. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/mergeNode.js +0 -0
  44. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/nodes/startNode.js +0 -0
  45. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/types.js +0 -0
  46. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/utils/export.js +0 -0
  47. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/utils/helpers.js +0 -0
  48. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/utils/import.js +0 -0
  49. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/js/utils/validation.js +0 -0
  50. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/jsdoc.json +0 -0
  51. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/package-lock.json +0 -0
  52. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/package.json +0 -0
  53. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/postcss.config.cjs +0 -0
  54. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/public/favicon.png +0 -0
  55. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/public/favicon.svg +0 -0
  56. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/tailwind.config.cjs +0 -0
  57. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/vercel.json +0 -0
  58. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/editor/vite.config.js +0 -0
  59. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/env.example +0 -0
  60. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/images/food-ordering-flow.png +0 -0
  61. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/pipecat-flows.png +0 -0
  62. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/pipecat_upgrade.md +0 -0
  63. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/pyproject.toml +0 -0
  64. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/remote-asterisk-code/README.md +0 -0
  65. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/remote-asterisk-code/extensions.conf +0 -0
  66. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/remote-asterisk-code/rtp.conf +0 -0
  67. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/requirements.txt +0 -0
  68. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/scripts/check-pypi-package.py +0 -0
  69. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/scripts/fix-ruff.sh +0 -0
  70. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/scripts/pre-commit.sh +0 -0
  71. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/setup.cfg +0 -0
  72. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/dv_pipecat_flows.egg-info/SOURCES.txt +0 -0
  73. {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
  74. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/dv_pipecat_flows.egg-info/requires.txt +0 -0
  75. {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
  76. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/__init__.py +0 -0
  77. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/actions.py +0 -0
  78. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/data_extractor.py +0 -0
  79. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/exceptions.py +0 -0
  80. {dv_pipecat_flows-0.0.22.dev916 → dv_pipecat_flows-0.0.22.dev1116}/src/pipecat_flows/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dv-pipecat-flows
3
- Version: 0.0.22.dev916
3
+ Version: 0.0.22.dev1116
4
4
  Summary: Conversation Flow management for Pipecat AI applications
5
5
  License: BSD 2-Clause License
6
6
  Project-URL: Source, https://github.com/pipecat-ai/pipecat-flows
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dv-pipecat-flows
3
- Version: 0.0.22.dev916
3
+ Version: 0.0.22.dev1116
4
4
  Summary: Conversation Flow management for Pipecat AI applications
5
5
  License: BSD 2-Clause License
6
6
  Project-URL: Source, https://github.com/pipecat-ai/pipecat-flows
@@ -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()
@@ -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
- if self._transition_lock.locked():
683
- # Another transition execution is already in progress
684
- return
685
-
686
- async with self._transition_lock:
687
- # Re-check after acquiring lock (another path may have completed it)
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
- # Execute transition_message as a pre_action (same behavior as node pre_actions)
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
- transition_action = {"type": "tts_say", "text": transition_message}
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