fix: isolate startup tools and preview dialogs

This commit is contained in:
Xin Wang
2026-08-02 00:07:35 +08:00
parent 0331f8cd07
commit 479a516546
10 changed files with 202 additions and 49 deletions

View File

@@ -246,6 +246,55 @@ class DifyLLMServiceTests(unittest.IsolatedAsyncioTestCase):
class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
async def test_startup_only_tool_is_not_registered_with_llm(self):
startup_tool = RuntimeTool(
id="opening_message",
name="开场确认",
function_name="show_message",
type="client",
)
conversation_tool = RuntimeTool(
id="lookup_order",
name="查询订单",
function_name="lookup_order",
type="http",
)
cfg = AssistantConfig(
type="prompt",
tools=[startup_tool, conversation_tool],
llm_tool_ids=[conversation_tool.id],
startup={
"actions": [
{
"id": "opening_message",
"phase": "opening",
"tool_id": startup_tool.id,
"required": True,
}
]
},
)
brain = build_brain(cfg)
llm = FakeLLM()
visible_schemas = []
await brain.setup(
cfg,
BrainRuntime(
context=LLMContext(messages=[]),
llm=llm,
queue_frame=noop_queue_frame,
set_system_prompt=lambda _prompt: None,
set_tools=visible_schemas.extend,
call_end=FakeCallEnd(),
),
)
self.assertIn(startup_tool.id, brain._tool_by_id)
self.assertNotIn(startup_tool.function_name, llm.functions)
self.assertIn(conversation_tool.function_name, llm.functions)
self.assertEqual(len(visible_schemas), 1)
async def test_preflight_runs_multiple_server_tools_in_order(self):
tools = [
RuntimeTool(

View File

@@ -6,14 +6,19 @@ from types import SimpleNamespace
from fastapi import HTTPException
from routes.assistants import _validate_startup_actions
from schemas import AssistantUpsert
from services.config_resolver import _runtime_tool_ids
def startup_body(*, phase: str = "opening") -> AssistantUpsert:
def startup_body(
*,
phase: str = "opening",
bind_tool: bool = False,
) -> AssistantUpsert:
return AssistantUpsert(
name="启动动作测试",
type="prompt",
runtimeMode="pipeline",
toolIds=["tool_message"],
toolIds=["tool_message"] if bind_tool else [],
startup={
"executionMode": "sequential",
"actions": [
@@ -45,20 +50,52 @@ class FakeSession:
self.tool = tool
async def get(self, _model, tool_id):
return self.tool if tool_id == self.tool.id else None
return self.tool if self.tool is not None and tool_id == self.tool.id else None
class StartupActionValidationTests(unittest.IsolatedAsyncioTestCase):
def test_runtime_pool_includes_bound_and_startup_only_tools(self):
self.assertEqual(
_runtime_tool_ids(
["conversation_tool", "shared_tool"],
{
"actions": [
{"tool_id": "opening_tool"},
{"toolId": "shared_tool"},
]
},
),
["conversation_tool", "shared_tool", "opening_tool"],
)
def test_realtime_rejects_startup_actions(self):
with self.assertRaisesRegex(ValueError, "Realtime"):
AssistantUpsert(
name="Realtime 启动动作",
type="prompt",
runtimeMode="realtime",
toolIds=["tool_message"],
startup=startup_body().startup,
)
async def test_startup_tool_does_not_need_conversation_binding(self):
tool = SimpleNamespace(
id="tool_message",
name="重要提示",
function_name="show_message",
type="client",
status="active",
definition={
"config": {
"wait_for_response": True,
"response_wait_mode": "session",
}
},
)
body = startup_body(bind_tool=False)
await _validate_startup_actions(FakeSession(tool), body)
self.assertEqual(body.tool_ids, [])
async def test_show_message_requires_session_wait(self):
tool = SimpleNamespace(
id="tool_message",