fix(workflow): prevent fixed speech continuation
This commit is contained in:
@@ -42,6 +42,7 @@ from services.action_runtime import (
|
||||
ActionStatus,
|
||||
)
|
||||
from services.action_stage import ActionStageRunner, ActionStageSpec, StageAction
|
||||
from services.fixed_speech import fixed_speech_context_message
|
||||
from services.knowledge import search as search_knowledge
|
||||
from services.message_policy import (
|
||||
MESSAGE_COMPLETION_POLICIES,
|
||||
@@ -794,9 +795,9 @@ class WorkflowBrain(BaseBrain):
|
||||
source="workflow-edge-transition",
|
||||
node_id=str(edge.get("target") or "") or None,
|
||||
)
|
||||
context_messages.append(
|
||||
{"role": "assistant", "content": content}
|
||||
)
|
||||
context_message = fixed_speech_context_message(content)
|
||||
if context_message is not None:
|
||||
context_messages.append(context_message)
|
||||
return await self._resolve_path(
|
||||
str(edge.get("target") or ""),
|
||||
leading_messages=context_messages,
|
||||
@@ -828,10 +829,11 @@ class WorkflowBrain(BaseBrain):
|
||||
if triggering_user_message
|
||||
else {"role": "user", "content": triggering_user_text}
|
||||
)
|
||||
agent_messages = [
|
||||
current_user_message,
|
||||
*context_messages,
|
||||
]
|
||||
# Fixed speech is represented by system facts. Put those
|
||||
# facts before the triggering user turn so RESET contexts
|
||||
# still end with the real user input rather than a control
|
||||
# message.
|
||||
agent_messages = [*context_messages, current_user_message]
|
||||
return self._agent_config(node_id, agent_messages)
|
||||
if node_type == "end":
|
||||
await self._enter_end(node_id)
|
||||
@@ -871,9 +873,9 @@ class WorkflowBrain(BaseBrain):
|
||||
source="workflow-edge-transition",
|
||||
node_id=target_id or None,
|
||||
)
|
||||
context_messages.append(
|
||||
{"role": "assistant", "content": content}
|
||||
)
|
||||
context_message = fixed_speech_context_message(content)
|
||||
if context_message is not None:
|
||||
context_messages.append(context_message)
|
||||
node_id = str(edge.get("target") or "")
|
||||
raise RuntimeError("工作流连续自动跳转超过安全上限")
|
||||
|
||||
@@ -974,9 +976,9 @@ class WorkflowBrain(BaseBrain):
|
||||
dict(message) for message in continuation.context_messages
|
||||
]
|
||||
if result.speech:
|
||||
context_messages.append(
|
||||
{"role": "assistant", "content": result.speech}
|
||||
)
|
||||
context_message = fixed_speech_context_message(result.speech)
|
||||
if context_message is not None:
|
||||
context_messages.append(context_message)
|
||||
|
||||
if not self._engine.has_outgoing(continuation.node_id):
|
||||
self._state.enter(
|
||||
|
||||
@@ -12,6 +12,20 @@ from services.brains.base import BrainRuntime
|
||||
from services.runtime_variables import DynamicVariableStore
|
||||
|
||||
|
||||
FIXED_SPEECH_CONTEXT_MARKER = "[会话事实:以下固定消息已向用户播报]"
|
||||
|
||||
|
||||
def fixed_speech_context_message(content: str) -> dict[str, str] | None:
|
||||
"""Keep deterministic speech in context without inviting assistant continuation."""
|
||||
text = content.strip()
|
||||
if not text:
|
||||
return None
|
||||
return {
|
||||
"role": "system",
|
||||
"content": f"{FIXED_SPEECH_CONTEXT_MARKER}\n{text}",
|
||||
}
|
||||
|
||||
|
||||
class FixedSpeechOutput:
|
||||
"""Display and synthesize fixed speech without waiting for playback."""
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ from services.brains.dify_llm import (
|
||||
normalize_api_base,
|
||||
)
|
||||
from services.brains.workflow_brain import ConfiguredFlowManager, WorkflowBrain
|
||||
from services.fixed_speech import FIXED_SPEECH_CONTEXT_MARKER
|
||||
from services.runtime_variables import prepare_dynamic_config
|
||||
from services.action_runtime import ActionOutcome, ActionStatus
|
||||
from services.workflow.models import (
|
||||
@@ -1723,7 +1724,10 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
{
|
||||
"id": "agent1",
|
||||
"type": "agent",
|
||||
"data": {"prompt": "收集基本信息"},
|
||||
"data": {
|
||||
"prompt": "收集基本信息",
|
||||
"entryMode": "generate",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "middle",
|
||||
@@ -1846,6 +1850,21 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
if manager.current_node == "agent1":
|
||||
break
|
||||
self.assertEqual(manager.current_node, "agent1")
|
||||
self.assertEqual(
|
||||
manager.configs[-1]["task_messages"],
|
||||
[
|
||||
{
|
||||
"role": "system",
|
||||
"content": f"{FIXED_SPEECH_CONTEXT_MARKER}\n欢迎使用。",
|
||||
}
|
||||
],
|
||||
)
|
||||
self.assertFalse(
|
||||
any(
|
||||
message["role"] == "assistant"
|
||||
for message in manager.configs[-1]["task_messages"]
|
||||
)
|
||||
)
|
||||
|
||||
# The user-turn processor must return while the second Message is
|
||||
# still waiting for its transport playback boundary.
|
||||
@@ -1868,8 +1887,13 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(
|
||||
manager.configs[-1]["task_messages"],
|
||||
[
|
||||
{
|
||||
"role": "system",
|
||||
"content": (
|
||||
f"{FIXED_SPEECH_CONTEXT_MARKER}\n现在进入信息确认。"
|
||||
),
|
||||
},
|
||||
{"role": "user", "content": "基本信息已经收集完成"},
|
||||
{"role": "assistant", "content": "现在进入信息确认。"},
|
||||
],
|
||||
)
|
||||
|
||||
@@ -2550,7 +2574,14 @@ class WorkflowBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
for frame in worker.frames
|
||||
if isinstance(frame, LLMMessagesAppendFrame)
|
||||
and frame.messages
|
||||
== [{"role": "assistant", "content": "正在为你结束流程"}]
|
||||
== [
|
||||
{
|
||||
"role": "system",
|
||||
"content": (
|
||||
f"{FIXED_SPEECH_CONTEXT_MARKER}\n正在为你结束流程"
|
||||
),
|
||||
}
|
||||
]
|
||||
]
|
||||
self.assertTrue(transition_context_frames)
|
||||
transition_events = [
|
||||
|
||||
Reference in New Issue
Block a user