- Introduce RuntimeModelResource and RuntimeKnowledgeBase classes to manage workflow resources. - Update AssistantConfig to include workflow_model_resources and workflow_knowledge_bases for better integration. - Refactor validation and processing logic in routes and services to accommodate workflow types. - Implement dynamic variable support for workflow assistants and enhance graph normalization. - Add ToolExecutor for reusable tool execution across different assistant types. - Update various services to ensure compatibility with new workflow features and improve error handling.
119 lines
4.5 KiB
Python
119 lines
4.5 KiB
Python
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()
|