dv-pipecat-flows 0.0.0.dev2093__tar.gz → 0.0.0.dev2098__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.0.dev2093/src/dv_pipecat_flows.egg-info → dv_pipecat_flows-0.0.0.dev2098}/PKG-INFO +1 -1
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098/src/dv_pipecat_flows.egg-info}/PKG-INFO +1 -1
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/manager.py +86 -9
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/processors/speak_interruption_guard.py +8 -8
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/router_mode.py +5 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/types.py +4 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/.agents/skills/loki-logs/SKILL.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/.agents/skills/loki-logs/query-reference.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/.claude/skills/loki-logs/SKILL.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/.claude/skills/loki-logs/query-reference.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/.gitattributes +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/.gitignore +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/.pre-commit-config.yaml +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/.python-version +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/.readthedocs.yaml +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/AGENTS.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/CHANGELOG.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/CLAUDE.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/CONTRIBUTING.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/LICENSE +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/MANIFEST.in +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/README.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/dev-requirements.txt +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/docker-compose.dev.yml +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/.eslintrc.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/.prettierrc +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/css/tailwind.css +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/examples/food_ordering.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/examples/movie_explorer.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/examples/patient_intake.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/examples/restaurant_reservation.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/examples/travel_planner.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/favicon.png +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/favicon.svg +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/index.html +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/editor/canvas.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/editor/editorState.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/editor/sidePanel.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/editor/toolbar.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/main.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/nodes/baseNode.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/nodes/endNode.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/nodes/flowNode.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/nodes/functionNode.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/nodes/index.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/nodes/mergeNode.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/nodes/startNode.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/types.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/utils/export.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/utils/helpers.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/utils/import.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/utils/validation.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/jsdoc.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/package-lock.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/package.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/postcss.config.cjs +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/public/favicon.png +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/public/favicon.svg +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/tailwind.config.cjs +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/vercel.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/vite.config.js +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/engine_primitives_plan.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/env.example +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/.gitignore +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/README.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/RESULTS.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/UNIFIED_KB_BENCHMARKS.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/chunkers.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/contracts.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/datagen/generate_corpus.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/datagen/generate_dataset.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/datagen/generate_hybrid.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/datagen/templates/insurance.yaml +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/datasets/.gitignore +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/datasets/v1_hybrid/manifest.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/datasets/v1_retrieval/manifest.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/datasets/v1_retrieval_long/manifest.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/datasets/v1_synthetic/manifest.json +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/runners/run_filter_pick.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/runners/run_filter_scaling.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/runners/run_hybrid.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/runners/run_latency.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/runners/run_rerank.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/runners/run_retrieval.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/service/API.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/service/__init__.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/service/app.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/service/jobs.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/images/food-ordering-flow.png +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/pipecat-flows.png +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/pipecat_upgrade.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/pyproject.toml +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/remote-asterisk-code/README.md +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/remote-asterisk-code/extensions.conf +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/remote-asterisk-code/rtp.conf +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/requirements.txt +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/scripts/check-pypi-package.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/scripts/fix-ruff.sh +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/scripts/pre-commit.sh +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/setup.cfg +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/dv_pipecat_flows.egg-info/SOURCES.txt +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/dv_pipecat_flows.egg-info/dependency_links.txt +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/dv_pipecat_flows.egg-info/requires.txt +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/dv_pipecat_flows.egg-info/top_level.txt +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/__init__.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/actions.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/adapters.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/condition_evaluator.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/exceptions.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/flow_validator.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/processors/__init__.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/processors/router_mode_guard.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/processors/user_turn_observer.py +0 -0
- {dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/uv.lock +0 -0
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/manager.py
RENAMED
|
@@ -78,6 +78,7 @@ from pipecat_flows.exceptions import (
|
|
|
78
78
|
RouterProviderError,
|
|
79
79
|
)
|
|
80
80
|
from pipecat_flows.router_mode import (
|
|
81
|
+
BACK_BRANCH_ID,
|
|
81
82
|
REPROMPT_IN_PLACE,
|
|
82
83
|
UNCLEAR_BRANCH_ID,
|
|
83
84
|
build_router_response_schema,
|
|
@@ -218,6 +219,12 @@ class FlowManager:
|
|
|
218
219
|
# backwards-compatible if a downstream installs an older engine.
|
|
219
220
|
self._user_turn_observer: Optional[Any] = None
|
|
220
221
|
|
|
222
|
+
self._current_node_entered_ts: float = 0.0
|
|
223
|
+
self._previous_node_entered_ts: float = 0.0
|
|
224
|
+
|
|
225
|
+
self._current_llm_router_name: Optional[str] = None
|
|
226
|
+
self._previous_llm_router_name: Optional[str] = None
|
|
227
|
+
|
|
221
228
|
# Set up static or dynamic mode
|
|
222
229
|
if flow_config:
|
|
223
230
|
warnings.warn(
|
|
@@ -967,6 +974,24 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
967
974
|
|
|
968
975
|
# Case 1: bare string (Speak / single-target Action).
|
|
969
976
|
if isinstance(auto_transition, str):
|
|
977
|
+
if node_config.get("interruptible", False):
|
|
978
|
+
observer = self._user_turn_observer
|
|
979
|
+
user_barged_in = (
|
|
980
|
+
observer is not None
|
|
981
|
+
and observer.last_user_stopped_ts
|
|
982
|
+
>= self._current_node_entered_ts
|
|
983
|
+
)
|
|
984
|
+
if user_barged_in and self._current_llm_router_name:
|
|
985
|
+
next_cfg = self._nodes.get(auto_transition) or {}
|
|
986
|
+
if next_cfg.get("llm_mode") != "router":
|
|
987
|
+
logger.info(
|
|
988
|
+
f"Interruptible Speak '{node_config.get('name')}': "
|
|
989
|
+
f"caller barged in; natural-next "
|
|
990
|
+
f"'{auto_transition}' is not a Router; routing "
|
|
991
|
+
f"to most-recent Router "
|
|
992
|
+
f"'{self._current_llm_router_name}' instead"
|
|
993
|
+
)
|
|
994
|
+
return self._current_llm_router_name
|
|
970
995
|
return auto_transition
|
|
971
996
|
|
|
972
997
|
return None
|
|
@@ -1001,7 +1026,7 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
1001
1026
|
# and drops the OpenAILLMContextFrame upstream of the main LLM.
|
|
1002
1027
|
current_cfg = self._nodes.get(self._current_node) or {}
|
|
1003
1028
|
needs_deferred_flush = False
|
|
1004
|
-
if current_cfg.get("
|
|
1029
|
+
if not current_cfg.get("interruptible", False):
|
|
1005
1030
|
needs_deferred_flush = await self._exit_speak_guard(
|
|
1006
1031
|
current_cfg, target
|
|
1007
1032
|
)
|
|
@@ -1030,15 +1055,15 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
1030
1055
|
node_config = self._nodes.get(self._current_node) or {}
|
|
1031
1056
|
return bool(node_config.get("suppress_llm_text"))
|
|
1032
1057
|
|
|
1033
|
-
def
|
|
1034
|
-
"""True when the current node has ``
|
|
1035
|
-
:class:`pipecat_flows.processors.SpeakInterruptionGuard`
|
|
1036
|
-
interruption cascade and the
|
|
1058
|
+
def is_speak_interruptible(self) -> bool:
|
|
1059
|
+
"""True when the current node has ``interruptible`` set, so
|
|
1060
|
+
:class:`pipecat_flows.processors.SpeakInterruptionGuard` lets the
|
|
1061
|
+
interruption cascade through and the caller can barge in.
|
|
1037
1062
|
"""
|
|
1038
1063
|
if not getattr(self, "_current_node", None):
|
|
1039
1064
|
return False
|
|
1040
1065
|
node_config = self._nodes.get(self._current_node) or {}
|
|
1041
|
-
return bool(node_config.get("
|
|
1066
|
+
return bool(node_config.get("interruptible", False))
|
|
1042
1067
|
|
|
1043
1068
|
async def _enter_speak_guard(self, node_config: Dict[str, Any]) -> None:
|
|
1044
1069
|
"""Reset the user aggregator's recency timers so the
|
|
@@ -1141,8 +1166,9 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
1141
1166
|
# `_run_router_node` captures a fresh entry_ts — the next iteration
|
|
1142
1167
|
# correctly waits for a NEW user reply instead of reclassifying the
|
|
1143
1168
|
# stale one that triggered the previous UNCLEAR.
|
|
1144
|
-
timeout_s = float(node_config.get("router_wait_for_user_timeout", 15.0))
|
|
1145
1169
|
observer = self._user_turn_observer
|
|
1170
|
+
|
|
1171
|
+
timeout_s = float(node_config.get("router_wait_for_user_timeout", 15.0))
|
|
1146
1172
|
if observer is not None and timeout_s > 0:
|
|
1147
1173
|
entry_ts = time.monotonic()
|
|
1148
1174
|
if observer.last_user_stopped_ts >= entry_ts:
|
|
@@ -1206,6 +1232,40 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
1206
1232
|
f"lowerer fills it"
|
|
1207
1233
|
)
|
|
1208
1234
|
|
|
1235
|
+
back_routing_enabled = (
|
|
1236
|
+
self._previous_llm_router_name is not None
|
|
1237
|
+
and self._previous_llm_router_name != node_id
|
|
1238
|
+
)
|
|
1239
|
+
if back_routing_enabled:
|
|
1240
|
+
prev_cfg = self._nodes.get(self._previous_llm_router_name) or {}
|
|
1241
|
+
prev_descriptions = prev_cfg.get("router_branch_descriptions") or {}
|
|
1242
|
+
prev_desc_block = "\n".join(
|
|
1243
|
+
f" - {bid}: {desc}" for bid, desc in prev_descriptions.items()
|
|
1244
|
+
) or " (previous router branches unavailable)"
|
|
1245
|
+
branches = list(branches) + [
|
|
1246
|
+
{"id": BACK_BRANCH_ID, "next": self._previous_llm_router_name}
|
|
1247
|
+
]
|
|
1248
|
+
system_prompt = (
|
|
1249
|
+
system_prompt
|
|
1250
|
+
+ "\n\n## Re-routing to previous Router\n"
|
|
1251
|
+
+ f"In addition to your branches above, you may pick "
|
|
1252
|
+
+ f"`{BACK_BRANCH_ID}` if the caller's reply is clearly NOT "
|
|
1253
|
+
+ f"answering this Router's question and instead contradicts "
|
|
1254
|
+
+ f"or changes the prior intent. The previous Router "
|
|
1255
|
+
+ f"(`{self._previous_llm_router_name}`) can re-classify into:\n"
|
|
1256
|
+
+ prev_desc_block
|
|
1257
|
+
+ "\n\nOnly pick this when the caller's reply belongs at the "
|
|
1258
|
+
+ "previous decision level — otherwise prefer your own branches."
|
|
1259
|
+
)
|
|
1260
|
+
|
|
1261
|
+
# Log the FULL router system prompt actually being used at runtime, so we
|
|
1262
|
+
# can confirm what the classifier sees (branches/extract/reasoning) rather
|
|
1263
|
+
# than just the sha1 hash logged after the pick below.
|
|
1264
|
+
logger.info(
|
|
1265
|
+
f"Router '{node_id}' system prompt ({len(system_prompt)} chars):\n"
|
|
1266
|
+
f"{system_prompt}"
|
|
1267
|
+
)
|
|
1268
|
+
|
|
1209
1269
|
schema = build_router_response_schema(branches, extract)
|
|
1210
1270
|
valid_branch_ids = [b["id"] for b in branches if isinstance(b, dict) and b.get("id")]
|
|
1211
1271
|
valid_branch_ids.append(UNCLEAR_BRANCH_ID)
|
|
@@ -1350,6 +1410,16 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
1350
1410
|
f"router_unclear observer failed for {node_id}: {observer_exc!r}"
|
|
1351
1411
|
)
|
|
1352
1412
|
|
|
1413
|
+
if back_routing_enabled and branch_id == BACK_BRANCH_ID:
|
|
1414
|
+
back_msg = node_config.get("router_back_transition_message") or ""
|
|
1415
|
+
if back_msg:
|
|
1416
|
+
await self._task.queue_frame(TTSSpeakFrame(text=back_msg))
|
|
1417
|
+
logger.info(
|
|
1418
|
+
f"Router '{node_id}' picked back-branch -> "
|
|
1419
|
+
f"{self._previous_llm_router_name} (msg={back_msg!r})"
|
|
1420
|
+
)
|
|
1421
|
+
return self._previous_llm_router_name
|
|
1422
|
+
|
|
1353
1423
|
target = pick_branch_target(
|
|
1354
1424
|
branch_id=branch_id,
|
|
1355
1425
|
node_config=node_config,
|
|
@@ -1541,6 +1611,13 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
1541
1611
|
if not self._initialized:
|
|
1542
1612
|
raise FlowTransitionError(f"{self.__class__.__name__} must be initialized first")
|
|
1543
1613
|
|
|
1614
|
+
self._previous_node_entered_ts = self._current_node_entered_ts
|
|
1615
|
+
self._current_node_entered_ts = time.monotonic()
|
|
1616
|
+
|
|
1617
|
+
if node_config.get("llm_mode") == "router":
|
|
1618
|
+
self._previous_llm_router_name = self._current_llm_router_name
|
|
1619
|
+
self._current_llm_router_name = node_id
|
|
1620
|
+
|
|
1544
1621
|
# Tool-log every transition so operators see the graph traversal on
|
|
1545
1622
|
# the same timeline as on-call API tool calls. Capture state BEFORE
|
|
1546
1623
|
# any of the node-setup logic runs so previous_node_id reflects the
|
|
@@ -1584,7 +1661,7 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
1584
1661
|
# Latch the guard's reactive flag (read off ``_current_node``)
|
|
1585
1662
|
# BEFORE pre_actions execute, so the Speak's tts_say plays under
|
|
1586
1663
|
# protection. Reset the aggregator's recency timers in lockstep.
|
|
1587
|
-
if node_config.get("
|
|
1664
|
+
if not node_config.get("interruptible", False):
|
|
1588
1665
|
self._current_node = node_id
|
|
1589
1666
|
await self._enter_speak_guard(node_config)
|
|
1590
1667
|
|
|
@@ -1873,7 +1950,7 @@ In all of these cases, you can provide a `name` in your new node's config for de
|
|
|
1873
1950
|
# still latched on ``_current_node`` — clear it so the pipeline
|
|
1874
1951
|
# doesn't strand with every cancel signal being eaten.
|
|
1875
1952
|
try:
|
|
1876
|
-
if node_config.get("
|
|
1953
|
+
if not node_config.get("interruptible", False) and self._current_node == node_id:
|
|
1877
1954
|
self._current_node = None
|
|
1878
1955
|
except Exception:
|
|
1879
1956
|
pass
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
-
"""Swallows interruption-cascade frames while the current node is
|
|
7
|
+
"""Swallows interruption-cascade frames while the current node is non-interruptible.
|
|
8
8
|
|
|
9
9
|
Speak / Action / Transfer / End nodes play scripted TTS that must land in full.
|
|
10
10
|
The pipeline-wide ``InterruptionFrame`` cascade (cancel-task and flush the
|
|
11
11
|
output audio queue in ``MediaSender``) would truncate them — so this processor
|
|
12
12
|
drops the cancel signal in both directions when the FlowManager reports
|
|
13
|
-
``
|
|
13
|
+
``is_speak_interruptible()`` is False.
|
|
14
14
|
|
|
15
15
|
User transcripts still pass through, so they accumulate in
|
|
16
16
|
``LLMUserContextAggregator._aggregation`` and the FlowManager flushes (or
|
|
@@ -49,35 +49,35 @@ _UPSTREAM_DROP_TYPES: Tuple[Type[Frame], ...] = (InterruptionTaskFrame,)
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
class SpeakInterruptionGuard(FrameProcessor):
|
|
52
|
-
"""Drops interruption-cascade frames while ``
|
|
52
|
+
"""Drops interruption-cascade frames while ``is_interruptible_fn()`` returns False.
|
|
53
53
|
|
|
54
54
|
Same holder-callback pattern as :class:`RouterModeGuard` so the manager can
|
|
55
55
|
flip state via ``_current_node`` without the processor importing
|
|
56
56
|
FlowManager directly.
|
|
57
57
|
"""
|
|
58
58
|
|
|
59
|
-
def __init__(self,
|
|
59
|
+
def __init__(self, is_interruptible_fn: Callable[[], bool], **kwargs) -> None:
|
|
60
60
|
super().__init__(**kwargs)
|
|
61
|
-
self.
|
|
61
|
+
self._is_interruptible_fn = is_interruptible_fn
|
|
62
62
|
|
|
63
63
|
async def process_frame(self, frame: Frame, direction: FrameDirection) -> None:
|
|
64
64
|
await super().process_frame(frame, direction)
|
|
65
65
|
|
|
66
|
-
if
|
|
66
|
+
if self._is_interruptible_fn():
|
|
67
67
|
await self.push_frame(frame, direction)
|
|
68
68
|
return
|
|
69
69
|
|
|
70
70
|
if direction == FrameDirection.DOWNSTREAM and isinstance(frame, _DOWNSTREAM_DROP_TYPES):
|
|
71
71
|
logger.debug(
|
|
72
72
|
f"SpeakInterruptionGuard dropping {type(frame).__name__} downstream "
|
|
73
|
-
f"during
|
|
73
|
+
f"during non-interruptible node"
|
|
74
74
|
)
|
|
75
75
|
return
|
|
76
76
|
|
|
77
77
|
if direction == FrameDirection.UPSTREAM and isinstance(frame, _UPSTREAM_DROP_TYPES):
|
|
78
78
|
logger.debug(
|
|
79
79
|
f"SpeakInterruptionGuard dropping {type(frame).__name__} upstream "
|
|
80
|
-
f"during
|
|
80
|
+
f"during non-interruptible node"
|
|
81
81
|
)
|
|
82
82
|
return
|
|
83
83
|
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/router_mode.py
RENAMED
|
@@ -44,6 +44,11 @@ logger = logging.getLogger(__name__)
|
|
|
44
44
|
# Sentinel returned by the LLM when none of the explicit branches match.
|
|
45
45
|
UNCLEAR_BRANCH_ID = "__unclear__"
|
|
46
46
|
|
|
47
|
+
# Synthetic branch ID injected at runtime when a Router has an upstream LLM
|
|
48
|
+
# Router available, letting the classifier pick "re-route to previous Router"
|
|
49
|
+
# without the author wiring an explicit edge.
|
|
50
|
+
BACK_BRANCH_ID = "__back_to_previous_router__"
|
|
51
|
+
|
|
47
52
|
# JSON Schema scalar type translation for ExtractSpec.type values.
|
|
48
53
|
_EXTRACT_TYPE_TO_JSONSCHEMA = {
|
|
49
54
|
"string": "string",
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/types.py
RENAMED
|
@@ -573,6 +573,10 @@ class NodeConfig(NodeConfigRequired, total=False):
|
|
|
573
573
|
router_exhausted_node: str
|
|
574
574
|
"""Target when ``router_max_unclear`` is exceeded. Typically Transfer or End."""
|
|
575
575
|
|
|
576
|
+
skip_wait_if_silent: bool
|
|
577
|
+
"""If true and no user transcript has landed since this node was entered,
|
|
578
|
+
skip the wait + classifier call and route via ``router_unclear_node``."""
|
|
579
|
+
|
|
576
580
|
# Router-Condition (consumed when auto_transition is the sentinel
|
|
577
581
|
# ``"__condition__"`` AND ``conditions`` is set)
|
|
578
582
|
conditions: List[Condition]
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/.agents/skills/loki-logs/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/.claude/skills/loki-logs/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/examples/food_ordering.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/editor/canvas.js
RENAMED
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/editor/editorState.js
RENAMED
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/editor/sidePanel.js
RENAMED
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/editor/toolbar.js
RENAMED
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/nodes/baseNode.js
RENAMED
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/nodes/endNode.js
RENAMED
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/nodes/flowNode.js
RENAMED
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/nodes/functionNode.js
RENAMED
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/nodes/mergeNode.js
RENAMED
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/nodes/startNode.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/utils/helpers.js
RENAMED
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/js/utils/validation.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/editor/tailwind.config.cjs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/UNIFIED_KB_BENCHMARKS.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/datasets/.gitignore
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/runners/run_hybrid.py
RENAMED
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/runners/run_latency.py
RENAMED
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/runners/run_rerank.py
RENAMED
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/runners/run_retrieval.py
RENAMED
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/evals/kb/service/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/images/food-ordering-flow.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/remote-asterisk-code/README.md
RENAMED
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/remote-asterisk-code/rtp.conf
RENAMED
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/scripts/check-pypi-package.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/__init__.py
RENAMED
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/actions.py
RENAMED
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/adapters.py
RENAMED
|
File without changes
|
|
File without changes
|
{dv_pipecat_flows-0.0.0.dev2093 → dv_pipecat_flows-0.0.0.dev2098}/src/pipecat_flows/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|