from __future__ import annotations import unittest from models import AssistantConfig, RuntimeModelResource from services.pipecat.service_factory import config_with_resource from services.node_specs import normalize_graph, validate_graph from services.runtime_variables import DynamicVariableStore, prepare_dynamic_config from services.workflow_engine import WorkflowEngine def valid_graph(): return { "specVersion": 3, "settings": {"globalPrompt": "服务 {{customer}}"}, "nodes": [ {"id": "start", "type": "start", "data": {"name": "Start"}}, { "id": "agent", "type": "agent", "data": {"name": "Agent", "prompt": "处理订单", "contextPolicy": "fresh"}, }, {"id": "end", "type": "end", "data": {"name": "End", "scope": "flow"}}, ], "edges": [ { "id": "begin", "source": "start", "target": "agent", "data": {"mode": "always", "priority": 0}, }, { "id": "paid", "source": "agent", "target": "end", "data": { "mode": "expression", "priority": 10, "expression": { "combinator": "and", "rules": [ {"variable": "order_status", "operator": "eq", "value": "paid"} ], }, }, }, ], } class WorkflowGraphTests(unittest.TestCase): def test_voice_resource_creates_isolated_runtime_config(self): base = AssistantConfig(type="workflow", asr="default", voice="default") asr = RuntimeModelResource( id="asr_1", capability="ASR", interface_type="openai-asr", values={"modelId": "sensevoice", "language": "zh", "apiUrl": "https://asr.test"}, secrets={"apiKey": "secret"}, ) resolved = config_with_resource(base, asr) self.assertEqual(resolved.asr, "sensevoice") self.assertEqual(resolved.stt_api_key, "secret") self.assertEqual(base.asr, "default") def test_v2_start_prompt_is_preserved_in_synthetic_agent(self): graph = normalize_graph( { "nodes": [ {"id": "s", "type": "startCall", "data": {"prompt": "询问需求"}}, {"id": "e", "type": "endCall", "data": {"prompt": "再见"}}, ], "edges": [ {"id": "done", "source": "s", "target": "e", "data": {"condition": "完成"}} ], } ) migrated = next(node for node in graph["nodes"] if node["type"] == "agent") self.assertEqual(migrated["data"]["prompt"], "询问需求") self.assertEqual(validate_graph(graph), []) def test_rejects_automatic_cycle_and_duplicate_priorities(self): graph = valid_graph() graph["nodes"].insert(1, {"id": "action", "type": "action", "data": {}}) graph["edges"] = [ {"id": "a", "source": "start", "target": "action", "data": {"mode": "always", "priority": 0}}, {"id": "b", "source": "action", "target": "start", "data": {"mode": "always", "priority": 0}}, {"id": "c", "source": "agent", "target": "end", "data": {"mode": "always", "priority": 10}}, ] errors = validate_graph(graph) self.assertTrue(any("无等待循环" in error for error in errors)) def test_expression_routes_using_session_variables(self): cfg = prepare_dynamic_config( AssistantConfig( type="workflow", graph=valid_graph(), dynamic_variable_definitions={ "customer": {"type": "string", "default": "王先生"}, "order_status": {"type": "string", "default": "pending"}, }, ), {}, assistant_id="asst_test", ) store = DynamicVariableStore.from_config(cfg) engine = WorkflowEngine(cfg.graph) self.assertIsNone(engine.deterministic_edge("agent", store, include_default=False)) store.assign("order_status", "paid") self.assertEqual( engine.deterministic_edge("agent", store, include_default=False)["target"], "end", ) self.assertIn("王先生", engine.prompt_for("agent", store)) if __name__ == "__main__": unittest.main()