Skip to content

Commit 125ed78

Browse files
committed
feat: implement workflow and dispatch rule registries with system workflows and rules
- Added WorkflowRegistry to manage workflow registration and loading - Created DispatchRuleRegistry to handle dynamic dispatch rule management - Implemented system workflows for games, chat, and system commands - Added dispatch rules for various interaction types (prefix, keyword, regex) - Created new workflow factories for game and system workflows - Introduced new blocks for dice rolling, gacha simulation, and help generation - Enhanced main.py with container initialization and workflow/rule loading
1 parent 98447ea commit 125ed78

File tree

19 files changed

+765
-16
lines changed

19 files changed

+765
-16
lines changed

data/dispatch_rules/chat.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# 聊天相关的调度规则
2+
# 每个规则包含:
3+
# - type: 规则类型 (prefix/keyword/regex)
4+
# - workflow: 要执行的工作流 (格式: groupId:workflowId)
5+
# - 其他特定类型的参数
6+
7+
# 前缀匹配规则示例
8+
- type: prefix
9+
workflow: chat:normal
10+
prefix: "/chat"
11+
description: "普通聊天,使用默认参数"
12+
13+
# 关键词匹配规则示例
14+
- type: keyword
15+
workflow: chat:creative
16+
keywords:
17+
- "创意"
18+
- "发散"
19+
- "brainstorm"
20+
description: "创意聊天,使用更高的温度参数"
21+
22+
# 正则匹配规则示例
23+
- type: regex
24+
workflow: chat:roleplay
25+
pattern: "^扮演(.+?)[::]"
26+
description: "角色扮演聊天,自动提取角色设定"

data/dispatch_rules/game.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# 游戏相关的调度规则
2+
# 这些规则用于处理各种游戏互动
3+
4+
# 骰子
5+
- type: regex
6+
workflow: game:dice
7+
pattern: "^[.。]roll\\s*(\\d+)?d(\\d+)"
8+
description: "骰子游戏,支持 XdY 格式"
9+
10+
# 抽卡
11+
- type: keyword
12+
workflow: game:gacha
13+
keywords:
14+
- "抽卡"
15+
- "十连"
16+
- "单抽"
17+
description: "抽卡模拟器"

data/dispatch_rules/system.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# 系统相关的调度规则
2+
# 这些规则主要用于系统管理和控制
3+
4+
# 帮助命令
5+
- type: prefix
6+
workflow: system:help
7+
prefix: "/help"
8+
description: "显示帮助信息"
9+
10+
# 系统状态
11+
- type: prefix
12+
workflow: system:status
13+
prefix: "/status"
14+
description: "显示系统状态"
15+
16+
# 管理员命令
17+
- type: regex
18+
workflow: system:admin
19+
pattern: "^/(ban|unban|kick|mute) \\S+"
20+
description: "管理员命令,需要管理员权限"
21+
22+
# 系统设置
23+
- type: prefix
24+
workflow: system:settings
25+
prefix: "/settings"
26+
description: "修改系统设置"

framework/workflow_dispatcher/dispatch_rule.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from abc import ABC, abstractmethod
2-
from typing import Type
2+
from typing import Type, Callable
33
from framework.im.message import IMMessage
44
from framework.ioc.container import DependencyContainer
55
from framework.workflow_executor.workflow import Workflow
@@ -10,12 +10,12 @@ class DispatchRule(ABC):
1010
用于定义如何根据消息内容选择合适的工作流进行处理。
1111
"""
1212

13-
def __init__(self, workflow_factory):
13+
def __init__(self, workflow_factory: Callable[[DependencyContainer], Workflow]):
1414
"""
1515
初始化调度规则。
1616
1717
Args:
18-
workflow_factory: 用于构造工作流的工厂对象
18+
workflow_factory: 用于构造工作流的工厂函数
1919
"""
2020
self.workflow_factory = workflow_factory
2121

@@ -39,7 +39,7 @@ def get_workflow(self, container: DependencyContainer) -> Workflow:
3939
Returns:
4040
Workflow: 工作流实例
4141
"""
42-
return self.workflow_factory.create_workflow(container)
42+
return self.workflow_factory(container)
4343

4444
def __str__(self) -> str:
4545
return self.__class__.__name__
@@ -48,7 +48,7 @@ def __str__(self) -> str:
4848
class PrefixMatchRule(DispatchRule):
4949
"""根据消息前缀匹配的规则"""
5050

51-
def __init__(self, prefix: str, workflow_factory):
51+
def __init__(self, prefix: str, workflow_factory: Callable[[DependencyContainer], Workflow]):
5252
super().__init__(workflow_factory)
5353
self.prefix = prefix
5454

@@ -62,7 +62,7 @@ def __str__(self) -> str:
6262
class KeywordMatchRule(DispatchRule):
6363
"""根据关键词匹配的规则"""
6464

65-
def __init__(self, keywords: list[str], workflow_factory):
65+
def __init__(self, keywords: list[str], workflow_factory: Callable[[DependencyContainer], Workflow]):
6666
super().__init__(workflow_factory)
6767
self.keywords = keywords
6868

@@ -76,7 +76,7 @@ def __str__(self) -> str:
7676
class RegexMatchRule(DispatchRule):
7777
"""根据正则表达式匹配的规则"""
7878

79-
def __init__(self, pattern: str, workflow_factory):
79+
def __init__(self, pattern: str, workflow_factory: Callable[[DependencyContainer], Workflow]):
8080
super().__init__(workflow_factory)
8181
import re
8282
self.pattern = re.compile(pattern)
@@ -91,7 +91,7 @@ def __str__(self) -> str:
9191
class FallbackMatchRule(DispatchRule):
9292
"""默认的兜底规则,总是匹配"""
9393

94-
def __init__(self, workflow_factory):
94+
def __init__(self, workflow_factory: Callable[[DependencyContainer], Workflow]):
9595
super().__init__(workflow_factory)
9696

9797
def match(self, message: IMMessage) -> bool:
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from typing import List
2+
from framework.workflow_dispatcher.dispatch_rule import DispatchRule, PrefixMatchRule, KeywordMatchRule, RegexMatchRule
3+
from framework.workflow_executor.workflow_registry import WorkflowRegistry
4+
from framework.ioc.container import DependencyContainer
5+
from framework.logger import get_logger
6+
import os
7+
from ruamel.yaml import YAML
8+
9+
class DispatchRuleRegistry:
10+
"""调度规则注册表,管理调度规则的加载和注册"""
11+
12+
def __init__(self, container: DependencyContainer):
13+
self.container = container
14+
self.workflow_registry = container.resolve(WorkflowRegistry)
15+
self.rules: List[DispatchRule] = []
16+
self.logger = get_logger("DispatchRuleRegistry")
17+
18+
19+
def register(self, rule: DispatchRule):
20+
"""注册一个调度规则"""
21+
self.rules.append(rule)
22+
self.logger.info(f"Registered dispatch rule: {rule}")
23+
24+
def load_rules(self, container: DependencyContainer, rules_dir: str = "data/dispatch_rules"):
25+
"""从指定目录加载所有调度规则"""
26+
if not os.path.exists(rules_dir):
27+
os.makedirs(rules_dir)
28+
29+
yaml = YAML(typ='safe')
30+
31+
for file_name in os.listdir(rules_dir):
32+
if not file_name.endswith('.yaml'):
33+
continue
34+
35+
file_path = os.path.join(rules_dir, file_name)
36+
try:
37+
with open(file_path, 'r', encoding='utf-8') as f:
38+
rules_data = yaml.load(f)
39+
40+
if not isinstance(rules_data, list):
41+
self.logger.warning(f"Invalid rules file {file_name}, expected list of rules")
42+
continue
43+
44+
for rule_data in rules_data:
45+
rule = self._create_rule(rule_data, container)
46+
if rule:
47+
self.register(rule)
48+
49+
except Exception as e:
50+
self.logger.error(f"Failed to load rules from {file_path}: {str(e)}")
51+
52+
def _create_rule(self, rule_data: dict) -> DispatchRule:
53+
"""从规则数据创建调度规则实例"""
54+
rule_type = rule_data.get('type')
55+
workflow_name = rule_data.get('workflow')
56+
57+
58+
if not rule_type or not workflow_name:
59+
raise ValueError("Rule must specify 'type' and 'workflow'")
60+
61+
# 获取工作流构建器
62+
workflow_builder = self.workflow_registry.get(workflow_name)
63+
if not workflow_builder:
64+
raise ValueError(f"Workflow {workflow_name} not found")
65+
66+
# 根据规则类型创建相应的规则实例
67+
if rule_type == 'prefix':
68+
prefix = rule_data.get('prefix')
69+
if not prefix:
70+
raise ValueError("Prefix rule must specify 'prefix'")
71+
return PrefixMatchRule(prefix, workflow_builder)
72+
73+
elif rule_type == 'keyword':
74+
keywords = rule_data.get('keywords')
75+
if not keywords or not isinstance(keywords, list):
76+
raise ValueError("Keyword rule must specify 'keywords' as list")
77+
return KeywordMatchRule(keywords, workflow_builder)
78+
79+
elif rule_type == 'regex':
80+
pattern = rule_data.get('pattern')
81+
if not pattern:
82+
raise ValueError("Regex rule must specify 'pattern'")
83+
return RegexMatchRule(pattern, workflow_builder)
84+
85+
else:
86+
raise ValueError(f"Unknown rule type: {rule_type}")
87+
88+
def get_rules(self) -> List[DispatchRule]:
89+
"""获取所有已注册的规则"""
90+
return self.rules.copy()

framework/workflow_dispatcher/workflow_dispatcher.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,46 @@
55
from framework.logger import get_logger
66
from framework.workflow_dispatcher.dispatch_rule import DispatchRule, FallbackMatchRule
77
from framework.workflow_executor.executor import WorkflowExecutor
8+
from framework.workflow_executor.workflow_registry import WorkflowRegistry
9+
from framework.workflow_dispatcher.dispatch_rule_registry import DispatchRuleRegistry
10+
from framework.workflow_executor.system_workflows import register_system_workflows
811

912

1013
class WorkflowDispatcher:
14+
"""工作流调度器"""
1115
def __init__(self, container: DependencyContainer):
1216
self.container = container
1317
self.logger = get_logger("WorkflowDispatcher")
14-
self.dispatch_rules: List[DispatchRule] = []
18+
19+
# 从容器获取注册表
20+
self.workflow_registry = container.resolve(WorkflowRegistry)
21+
self.dispatch_registry = container.resolve(DispatchRuleRegistry)
22+
23+
# 初始化默认的兜底规则
1524
self.__init_fallback()
1625

1726
def __init_fallback(self):
1827
"""初始化默认的兜底规则"""
1928
from framework.workflows.default.factory import DefaultWorkflowFactory
2029
fallback_factory = DefaultWorkflowFactory()
21-
self.dispatch_rules.append(FallbackMatchRule(fallback_factory))
30+
self.dispatch_registry.register(FallbackMatchRule(fallback_factory.create_workflow))
2231
self.logger.info("Registered fallback dispatch rule")
2332

2433
def register_rule(self, rule: DispatchRule):
2534
"""注册一个调度规则"""
26-
self.dispatch_rules.append(rule)
35+
self.dispatch_registry.register(rule)
2736
self.logger.info(f"Registered dispatch rule: {rule}")
2837

2938
async def dispatch(self, source: IMAdapter, message: IMMessage):
3039
"""
3140
根据消息内容选择第一个匹配的规则进行处理
3241
"""
33-
for rule in self.dispatch_rules:
42+
for rule in self.dispatch_registry.get_rules():
3443
if rule.match(message):
3544
self.logger.debug(f"Matched rule {rule}, executing workflow")
3645
with self.container.scoped() as scoped_container:
3746
scoped_container.register(IMAdapter, source)
3847
scoped_container.register(IMMessage, message)
39-
# 此处构造出实际的 workflow 对象,使用的容器是注入了当前上下文的 container
4048
workflow = rule.get_workflow(scoped_container)
4149
executor = WorkflowExecutor(workflow)
4250
return await executor.run()

framework/workflow_executor/system_blocks.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
LLMToMessage,
99
StoreMemory
1010
)
11+
from framework.workflows.blocks.game.dice import DiceRoll
12+
from framework.workflows.blocks.game.gacha import GachaSimulator
13+
from framework.workflows.blocks.system.help import GenerateHelp
1114

1215
def register_system_blocks(registry: BlockRegistry):
1316
"""注册系统自带的 block"""
@@ -21,4 +24,11 @@ def register_system_blocks(registry: BlockRegistry):
2124
registry.register("construct_llm_message", "internal", ConstructLLMMessage)
2225
registry.register("llm_chat", "internal", LLMChat)
2326
registry.register("llm_to_message", "internal", LLMToMessage)
24-
registry.register("store_memory", "internal", StoreMemory)
27+
registry.register("store_memory", "internal", StoreMemory)
28+
29+
# 游戏相关 blocks
30+
registry.register("dice_roll", "game", DiceRoll)
31+
registry.register("gacha_simulator", "game", GachaSimulator)
32+
33+
# 系统相关 blocks
34+
registry.register("generate_help", "system", GenerateHelp)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from framework.workflow_executor.workflow_registry import WorkflowRegistry
2+
from framework.workflows.game.factory import GameWorkflowFactory
3+
from framework.workflows.system.factory import SystemWorkflowFactory
4+
from framework.workflows.default.factory import DefaultWorkflowFactory
5+
6+
def register_system_workflows(registry: WorkflowRegistry):
7+
"""注册系统自带的工作流"""
8+
9+
# 游戏相关工作流
10+
registry.register("game", "dice", GameWorkflowFactory.create_dice_workflow)
11+
registry.register("game", "gacha", GameWorkflowFactory.create_gacha_workflow)
12+
13+
# 系统相关工作流
14+
registry.register("system", "help", SystemWorkflowFactory.create_help_workflow)
15+
registry.register("system", "admin", SystemWorkflowFactory.create_help_workflow) # 暂时用help代替
16+
registry.register("system", "status", SystemWorkflowFactory.create_help_workflow) # 暂时用help代替
17+
registry.register("system", "settings", SystemWorkflowFactory.create_help_workflow) # 暂时用help代替
18+
19+
# 聊天相关工作流
20+
registry.register("chat", "normal", DefaultWorkflowFactory.create_workflow)
21+
registry.register("chat", "creative", DefaultWorkflowFactory.create_workflow) # 暂时用默认的
22+
registry.register("chat", "roleplay", DefaultWorkflowFactory.create_workflow) # 暂时用默认的
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from typing import Dict, Type, Optional
2+
from framework.workflow_executor.workflow import Workflow
3+
from framework.workflow_executor.builder import WorkflowBuilder
4+
from framework.ioc.container import DependencyContainer
5+
from framework.logger import get_logger
6+
import os
7+
8+
class WorkflowRegistry:
9+
"""工作流注册表,管理工作流的注册和获取"""
10+
11+
def __init__(self, container: DependencyContainer):
12+
self._workflows: Dict[str, Type[WorkflowBuilder]] = {}
13+
self.logger = get_logger("WorkflowRegistry")
14+
self.container = container
15+
16+
17+
def register(self, group_id: str, workflow_id: str, workflow_builder: Type[WorkflowBuilder]):
18+
"""注册一个工作流"""
19+
full_name = f"{group_id}:{workflow_id}"
20+
if full_name in self._workflows:
21+
self.logger.warning(f"Workflow {full_name} already registered, overwriting")
22+
self._workflows[full_name] = workflow_builder
23+
self.logger.info(f"Registered workflow: {full_name}")
24+
25+
def get(self, name: str) -> Optional[Type[WorkflowBuilder]]:
26+
"""获取工作流构建器"""
27+
return self._workflows.get(name)
28+
29+
def load_workflows(self, workflows_dir: str = "data/workflows"):
30+
"""从指定目录加载所有工作流定义"""
31+
if not os.path.exists(workflows_dir):
32+
os.makedirs(workflows_dir)
33+
34+
for file_name in os.listdir(workflows_dir):
35+
if not file_name.endswith('.yaml'):
36+
continue
37+
38+
file_path = os.path.join(workflows_dir, file_name)
39+
try:
40+
workflow = WorkflowBuilder.load_from_yaml(file_path, self.container)
41+
# 从文件名解析 group_id 和 workflow_id
42+
name_without_ext = os.path.splitext(file_name)[0]
43+
if ':' not in name_without_ext:
44+
self.logger.warning(f"Invalid workflow file name {file_name}, skipping")
45+
continue
46+
47+
group_id, workflow_id = name_without_ext.split(':', 1)
48+
self.register(group_id, workflow_id, workflow)
49+
50+
except Exception as e:
51+
self.logger.error(f"Failed to load workflow from {file_path}: {str(e)}")
52+
53+
def create_workflow(self, name: str) -> Optional[Workflow]:
54+
"""创建工作流实例"""
55+
builder = self.get(name)
56+
if not builder:
57+
return None
58+
59+
return builder.build()

0 commit comments

Comments
 (0)