F2: delegate prompt tasks to native Reasoner or OpenCode backends
This commit is contained in:
@@ -1,22 +1,24 @@
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
from models import AssistantConfig, RuntimeTool, RuntimeToolBinding
|
||||
from models import AssistantConfig, RuntimeTaskBackend
|
||||
from services.brains.prompt_tasks import PromptTaskBridge
|
||||
from services.tool_executor import ToolExecutor
|
||||
from services.runtime_variables import DynamicVariableStore
|
||||
|
||||
|
||||
class PromptTaskBridgeTest(unittest.IsolatedAsyncioTestCase):
|
||||
def config(self, **kwargs):
|
||||
return AssistantConfig(task_backend=RuntimeTaskBackend(id="backend_1", type="opencode"), **kwargs)
|
||||
|
||||
async def test_receipt_and_single_terminal_notification(self):
|
||||
cfg = AssistantConfig(conversation_id="conv_1", dynamic_variables={"system__agent_id": "asst_1"})
|
||||
cfg = self.config(conversation_id="conv_1", dynamic_variables={"system__agent_id": "asst_1"})
|
||||
runtime = SimpleNamespace(session_id="conv_1", transport=object(), tool_executor_factory=None, call_end=SimpleNamespace(ending=False), queue_frame=AsyncMock(), llm=SimpleNamespace(queue_frame=AsyncMock()))
|
||||
detach = Mock()
|
||||
service = SimpleNamespace(subscribe=Mock(return_value=detach), submit=AsyncMock(return_value={"id": "task_1", "status": "queued"}))
|
||||
bridge = PromptTaskBridge(cfg, runtime, ToolExecutor(DynamicVariableStore({})), service)
|
||||
tool = RuntimeTool(id="tool_task", name="coding", function_name="coding_task", type="task", definition={"type": "task", "config": {}})
|
||||
binding = RuntimeToolBinding(toolId=tool.id, argumentBindings={"instruction": {"source": "model"}})
|
||||
functions = bridge.functions(tool, binding)
|
||||
functions = bridge.functions()
|
||||
self.assertEqual(functions[0][0].name, "delegate_task")
|
||||
callback = AsyncMock()
|
||||
await functions[0][1](SimpleNamespace(arguments={"instruction": "修复测试"}, tool_call_id="call_1", result_callback=callback))
|
||||
self.assertEqual(callback.await_args.args[0]["status"], "accepted")
|
||||
@@ -31,22 +33,39 @@ class PromptTaskBridgeTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_test_runtime_never_submits_a_real_task(self):
|
||||
runtime = SimpleNamespace(session_id="conv_1", transport=None, tool_executor_factory=object())
|
||||
service = SimpleNamespace(submit=AsyncMock())
|
||||
bridge = PromptTaskBridge(AssistantConfig(), runtime, ToolExecutor(DynamicVariableStore({})), service)
|
||||
tool = RuntimeTool(id="tool_task", name="coding", function_name="coding_task", type="task", definition={"type": "task", "config": {}})
|
||||
bridge = PromptTaskBridge(self.config(), runtime, ToolExecutor(DynamicVariableStore({})), service)
|
||||
callback = AsyncMock()
|
||||
await bridge.functions(tool, None)[0][1](SimpleNamespace(result_callback=callback))
|
||||
await bridge.functions()[0][1](SimpleNamespace(result_callback=callback))
|
||||
service.submit.assert_not_awaited()
|
||||
self.assertEqual(callback.await_args.args[0]["status"], "error")
|
||||
|
||||
async def test_test_runtime_never_controls_an_existing_real_task(self):
|
||||
runtime = SimpleNamespace(session_id="conv_1", transport=None, tool_executor_factory=object())
|
||||
service = SimpleNamespace(get=AsyncMock(), cancel=AsyncMock(), send_input=AsyncMock())
|
||||
bridge = PromptTaskBridge(AssistantConfig(), runtime, ToolExecutor(DynamicVariableStore({})), service)
|
||||
tool = RuntimeTool(id="tool_task", name="coding", function_name="coding_task", type="task", definition={"type": "task", "config": {}})
|
||||
for schema, handler in bridge.functions(tool, None)[1:]:
|
||||
bridge = PromptTaskBridge(self.config(), runtime, ToolExecutor(DynamicVariableStore({})), service)
|
||||
for schema, handler in bridge.functions()[1:]:
|
||||
callback = AsyncMock()
|
||||
await handler(SimpleNamespace(function_name=schema.name, arguments={"task_id": "real_task", "message": "yes"}, result_callback=callback))
|
||||
self.assertEqual(callback.await_args.args[0]["status"], "error")
|
||||
service.get.assert_not_awaited()
|
||||
service.cancel.assert_not_awaited()
|
||||
service.send_input.assert_not_awaited()
|
||||
|
||||
async def test_bound_instruction_sources_and_snapshot_ignore_model_overrides(self):
|
||||
cases = [({"source": "literal", "value": "固定任务"}, "固定任务"),
|
||||
({"source": "template", "value": "检查 {{project}}"}, "检查 demo"),
|
||||
({"source": "variable", "variable_name": "project"}, "demo")]
|
||||
for source, expected in cases:
|
||||
with self.subTest(source=source):
|
||||
cfg = self.config(conversation_id="conv_1", dynamic_variables={"system__agent_id": "asst_1"})
|
||||
cfg.task_backend.instruction_source = source
|
||||
runtime = SimpleNamespace(session_id="conv_1", transport=None, tool_executor_factory=None)
|
||||
service = SimpleNamespace(submit=AsyncMock(return_value={"id": "task_1"}))
|
||||
store = DynamicVariableStore({"project": "demo"})
|
||||
bridge = PromptTaskBridge(cfg, runtime, ToolExecutor(store), service)
|
||||
schema, handler = bridge.functions()[0]
|
||||
self.assertEqual(schema.to_default_dict()["parameters"]["properties"], {})
|
||||
callback = AsyncMock()
|
||||
await handler(SimpleNamespace(arguments={"instruction": "unauthorized override"}, tool_call_id="call_1", result_callback=callback))
|
||||
self.assertEqual(service.submit.await_args.kwargs["instruction"], expected)
|
||||
self.assertEqual(service.submit.await_args.kwargs["variable_state"]["values"], store.values)
|
||||
|
||||
Reference in New Issue
Block a user