规则引擎DSL设计分析.md 19 KB

规则引擎 DSL 设计分析

分析时间: 2026-02-12
目的: 验证当前数据库设计是否支持规则引擎DSL需求


一、DSL代码示例分析

1.1 示例代码

@_call_verify_func_int(expected_output_id)
def calculate_score(标准分_ids: list[id]) -> str:
    return sum(标准分_ids)

@_call_verify_func_notnone()
def build_one_five_section(one_two_ch: id, one_tree_ch: id, calculate_score_callback: ref, prompt: str) -> str:
    one_two_ch_str = _call_export_resource_str(one_two_ch)
    one_tree_ch_str = _call_export_resource_str(one_tree_ch)
    return _call_ai_assistant(system=prompt, user='%s\n%s\n标准分:%s' % (one_two_ch_str, one_tree_ch_str, calculate_score_callback()))

@_call_verify_func_notnone()
def build_one_full_chapter(one_two_ch: id, one_tree_ch: id, one_four_ch: id, one_five_ch_callback: ref):
    one_two_ch_str = _call_export_resource_str(one_two_ch)
    one_tree_ch_str = _call_export_resource_str(one_tree_ch)
    one_four_ch_str = _call_export_resource_str(one_four_ch)
    return '%s\n%s\n%s\n%s' % (one_two_ch_str, one_tree_ch_str, one_four_ch_str, one_five_ch_callback())
    
# 参数配置
one_two_ch_id=12578
one_tree_ch_id=12389
one_four_ch_id=13456
score_expected_output_id=120234989
standard_item1_score_id=234808
standard_item2_score_id=234874
standard_item3_score_id=2234
standard_item4_score_id=12398
section_prompt='从原文生成100以内字数的摘要,并说明标准分'

# 执行链
score_ref = &calculate_score([standard_item1_score_id, standard_item2_score_id, standard_item3_score_id, standard_item4_score_id], expected_output_id=score_expected_output_id)
section_ref = &build_one_five_section(one_two_ch_id, one_tree_ch_id, score_ref, prompt=section_prompt)
one_full_chapter = build_one_full_chapter(one_two_ch_id, one_tree_ch_id, one_four_ch_id, section_ref)

return one_full_chapter

1.2 核心特性提取

特性 说明 示例
函数定义 支持自定义函数,带装饰器 @_call_verify_func_int
参数类型 id, list[id], ref, str 标准分_ids: list[id]
内置函数 导出资源、AI调用 _call_export_resource_str()
函数引用 创建和调用函数引用 &calculate_score(...)
依赖链 函数间依赖关系 score_ref → section_ref → result
输出验证 验证输出类型和非空 expected_output_id

二、当前数据库设计分析

2.1 RULE节点属性(当前设计)

-- 规则节点属性
node_properties:
- rule_type: VARCHAR        # 规则类型(direct_entity/extraction/llm/aggregate)
- action_type: VARCHAR      # 动作类型(use_entity_value/regex_extract/llm_generate)
- description: TEXT         # 规则描述
- action_config: JSONB      # 动作配置(JSON格式)
- dsl_content: TEXT         # DSL内容(预留字段)
- last_output_text: TEXT    # 最后输出文本
- last_output_json: JSONB   # 最后输出JSON
- last_run_status: VARCHAR  # 最后运行状态
- last_run_time: TIMESTAMP  # 最后运行时间
- last_run_error: TEXT      # 最后运行错误

2.2 RULE关系(当前设计)

-- 规则关系
PROJECT --HAS_RULE--> RULE          # 项目包含规则
RULE --FOR_ELEMENT--> ELEMENT       # 规则对应要素
RULE --INPUT_FROM--> ENTITY         # 规则输入来源(实体)
RULE --INPUT_FROM--> ATTACHMENT     # 规则输入来源(附件)
RULE --INPUT_FROM--> VALUE          # 规则输入来源(其他值)

2.3 INPUT_FROM边属性(当前设计)

-- 输入边属性
edge_properties:
- input_key: VARCHAR        # 输入参数名(如 'entity')
- input_type: VARCHAR       # 输入类型(entity_ref/attachment_ref/value_ref)
- input_name: VARCHAR       # 输入名称(显示用)
- fixed_value: TEXT         # 固定值(可选)

三、DSL需求 vs 当前设计对比

3.1 ✅ 已支持的特性

DSL特性 当前设计 说明
节点ID引用 ✅ 支持 INPUT_FROM 边 + input_type='entity_ref'
输入参数 ✅ 支持 edge_properties.input_key
输出存储 ✅ 支持 last_output_text, last_output_json
规则描述 ✅ 支持 description 字段
执行状态 ✅ 支持 last_run_status, last_run_time

3.2 ❌ 缺失的特性

DSL特性 当前设计 问题
函数定义 ❌ 不支持 没有存储函数定义的结构
函数引用 ❌ 不支持 没有 RULE --DEPENDS_ON--> RULE 关系
参数类型 ❌ 不完整 只有 input_type,没有详细类型定义
装饰器/验证 ❌ 不支持 没有存储验证规则的字段
DSL代码 ⚠️ 部分支持 dsl_content 字段,但未定义结构
执行顺序 ❌ 不支持 没有依赖关系的拓扑排序机制
输出验证 ❌ 不支持 没有 expected_output_id 机制

四、设计改进方案

4.1 方案A:扩展当前设计(推荐)

核心思路: 在当前图数据库基础上扩展,支持DSL特性

4.1.1 新增节点类型

-- 不需要新增节点类型,RULE节点足够

4.1.2 新增关系类型

INSERT INTO edge_types (type_code, type_name, from_node_type, to_node_type, description) VALUES
('DEPENDS_ON', '依赖规则', 'RULE', 'RULE', '规则依赖关系(用于函数引用)'),
('VALIDATES_TO', '验证输出', 'RULE', 'VALUE', '规则验证输出到指定要素值');

4.1.3 扩展RULE节点属性

-- 新增属性
ALTER TABLE node_properties ADD COLUMN IF NOT EXISTS prop_int BIGINT;

-- 规则节点新增属性
node_properties:
- rule_type: VARCHAR           # 规则类型
  - 'function_def'             # 函数定义
  - 'direct_entity'            # 直接引用实体
  - 'extraction'               # 正则提取
  - 'llm'                      # LLM生成
  - 'aggregate'                # 聚合计算

- function_name: VARCHAR       # 函数名(如 'calculate_score')
- function_params: JSONB       # 函数参数定义
  {
    "标准分_ids": {"type": "list[id]", "required": true},
    "expected_output_id": {"type": "id", "required": false}
  }

- function_body: TEXT          # 函数体(DSL代码)
- return_type: VARCHAR         # 返回类型(str/int/float/json)

- validators: JSONB            # 验证器配置
  [
    {"type": "verify_int", "target_id": 120234989},
    {"type": "verify_notnone"}
  ]

- execution_order: INT         # 执行顺序(拓扑排序后的序号)

4.1.4 扩展INPUT_FROM边属性

-- 输入边属性扩展
edge_properties:
- input_key: VARCHAR           # 参数名(如 'one_two_ch')
- input_type: VARCHAR          # 输入类型
  - 'id'                       # 单个节点ID
  - 'list[id]'                 # 节点ID列表
  - 'ref'                      # 函数引用
  - 'str'                      # 字符串
  - 'int'                      # 整数
  
- input_value_type: VARCHAR    # 值类型(node_ref/rule_ref/literal)
- input_value: TEXT            # 字面值(如 prompt字符串)
- input_value_json: JSONB      # 复杂值(如ID列表)
- sort_order: INT              # 参数顺序

4.1.5 数据示例

-- 示例:calculate_score 函数
INSERT INTO nodes (id, node_type, node_key, name, status) VALUES
(700, 'RULE', 'func:calculate_score', 'calculate_score函数', 'active');

INSERT INTO node_properties (node_id, prop_key, prop_value, prop_json) VALUES
(700, 'rule_type', 'function_def', NULL),
(700, 'function_name', 'calculate_score', NULL),
(700, 'function_params', NULL, '{
  "标准分_ids": {"type": "list[id]", "required": true}
}'),
(700, 'function_body', 'return sum(标准分_ids)', NULL),
(700, 'return_type', 'int', NULL),
(700, 'validators', NULL, '[{"type": "verify_int", "target_id": 120234989}]');

-- 输入:标准分ID列表
INSERT INTO edges (id, edge_type, from_node_id, to_node_id, sort_order) VALUES
(2000, 'INPUT_FROM', 700, 234808, 1),
(2001, 'INPUT_FROM', 700, 234874, 2),
(2002, 'INPUT_FROM', 700, 2234, 3),
(2003, 'INPUT_FROM', 700, 12398, 4);

INSERT INTO edge_properties (edge_id, prop_key, prop_value) VALUES
(2000, 'input_key', '标准分_ids'),
(2000, 'input_type', 'list[id]'),
(2000, 'input_value_type', 'node_ref'),
(2001, 'input_key', '标准分_ids'),
(2001, 'input_type', 'list[id]'),
(2001, 'input_value_type', 'node_ref'),
(2002, 'input_key', '标准分_ids'),
(2002, 'input_type', 'list[id]'),
(2002, 'input_value_type', 'node_ref'),
(2003, 'input_key', '标准分_ids'),
(2003, 'input_type', 'list[id]'),
(2003, 'input_value_type', 'node_ref');

-- 示例:build_one_five_section 函数
INSERT INTO nodes (id, node_type, node_key, name, status) VALUES
(701, 'RULE', 'func:build_one_five_section', 'build_one_five_section函数', 'active');

INSERT INTO node_properties (node_id, prop_key, prop_value, prop_json) VALUES
(701, 'rule_type', 'function_def', NULL),
(701, 'function_name', 'build_one_five_section', NULL),
(701, 'function_params', NULL, '{
  "one_two_ch": {"type": "id", "required": true},
  "one_tree_ch": {"type": "id", "required": true},
  "calculate_score_callback": {"type": "ref", "required": true},
  "prompt": {"type": "str", "required": true}
}'),
(701, 'function_body', 'one_two_ch_str = _call_export_resource_str(one_two_ch)\none_tree_ch_str = _call_export_resource_str(one_tree_ch)\nreturn _call_ai_assistant(system=prompt, user=''%s\\n%s\\n标准分:%s'' % (one_two_ch_str, one_tree_ch_str, calculate_score_callback()))', NULL),
(701, 'return_type', 'str', NULL),
(701, 'validators', NULL, '[{"type": "verify_notnone"}]');

-- 输入1: one_two_ch (节点ID)
INSERT INTO edges (id, edge_type, from_node_id, to_node_id, sort_order) VALUES
(2010, 'INPUT_FROM', 701, 12578, 1);

INSERT INTO edge_properties (edge_id, prop_key, prop_value) VALUES
(2010, 'input_key', 'one_two_ch'),
(2010, 'input_type', 'id'),
(2010, 'input_value_type', 'node_ref');

-- 输入2: one_tree_ch (节点ID)
INSERT INTO edges (id, edge_type, from_node_id, to_node_id, sort_order) VALUES
(2011, 'INPUT_FROM', 701, 12389, 2);

INSERT INTO edge_properties (edge_id, prop_key, prop_value) VALUES
(2011, 'input_key', 'one_tree_ch'),
(2011, 'input_type', 'id'),
(2011, 'input_value_type', 'node_ref');

-- 输入3: calculate_score_callback (函数引用)
INSERT INTO edges (id, edge_type, from_node_id, to_node_id, sort_order) VALUES
(2012, 'DEPENDS_ON', 701, 700, 3);

INSERT INTO edge_properties (edge_id, prop_key, prop_value) VALUES
(2012, 'input_key', 'calculate_score_callback'),
(2012, 'input_type', 'ref'),
(2012, 'input_value_type', 'rule_ref');

-- 输入4: prompt (字符串字面值)
INSERT INTO edge_properties (edge_id, prop_key, prop_value) VALUES
(2013, 'input_key', 'prompt'),
(2013, 'input_type', 'str'),
(2013, 'input_value_type', 'literal'),
(2013, 'input_value', '从原文生成100以内字数的摘要,并说明标准分');

4.2 方案B:独立DSL存储(备选)

核心思路: 将DSL代码作为整体存储,运行时解析

优点

  • 实现简单,直接存储DSL代码
  • 灵活性高,不受数据库结构限制

缺点

  • 无法利用图数据库的关系查询能力
  • 难以可视化规则依赖关系
  • 难以做规则影响分析

实现方式

-- 只需使用 dsl_content 字段
INSERT INTO node_properties (node_id, prop_key, prop_value) VALUES
(700, 'dsl_content', '完整的DSL代码...');

五、推荐方案:方案A(扩展设计)

5.1 优势

  1. 保留图数据库优势: 可以查询规则依赖关系、影响分析
  2. 可视化友好: 前端可以展示规则DAG图
  3. 增量执行: 只执行受影响的规则
  4. 调试友好: 可以追踪每个规则的输入输出
  5. 兼容性好: 与现有设计无缝集成

5.2 实现步骤

Step 1: 更新数据库Schema

-- 1. 新增关系类型
INSERT INTO edge_types (type_code, type_name, from_node_type, to_node_type, description) VALUES
('DEPENDS_ON', '依赖规则', 'RULE', 'RULE', '规则依赖关系'),
('VALIDATES_TO', '验证输出', 'RULE', 'VALUE', '规则验证输出');

-- 2. 新增属性定义
INSERT INTO property_definitions (owner_type, target_type, prop_key, prop_name, data_type, required) VALUES
('node', 'RULE', 'function_name', '函数名', 'string', false),
('node', 'RULE', 'function_params', '函数参数', 'json', false),
('node', 'RULE', 'function_body', '函数体', 'string', false),
('node', 'RULE', 'return_type', '返回类型', 'string', false),
('node', 'RULE', 'validators', '验证器', 'json', false),
('node', 'RULE', 'execution_order', '执行顺序', 'number', false),

('edge', 'INPUT_FROM', 'input_value_type', '值类型', 'string', false),
('edge', 'INPUT_FROM', 'input_value', '字面值', 'string', false),
('edge', 'INPUT_FROM', 'input_value_json', '复杂值', 'json', false);

Step 2: 后端实现规则解析器

/**
 * DSL规则解析器
 */
@Service
public class RuleDSLParser {
    
    /**
     * 解析DSL代码,创建规则节点和关系
     */
    public Long parseDSL(String dslCode, Long projectId) {
        // 1. 解析函数定义
        List<FunctionDef> functions = parseFunctions(dslCode);
        
        // 2. 为每个函数创建RULE节点
        Map<String, Long> functionNodeMap = new HashMap<>();
        for (FunctionDef func : functions) {
            Long ruleId = createRuleNode(func, projectId);
            functionNodeMap.put(func.getName(), ruleId);
        }
        
        // 3. 创建依赖关系(DEPENDS_ON边)
        for (FunctionDef func : functions) {
            createDependencies(func, functionNodeMap);
        }
        
        // 4. 拓扑排序,计算执行顺序
        calculateExecutionOrder(functionNodeMap.values());
        
        return functionNodeMap.get("main"); // 返回主函数ID
    }
}

Step 3: 后端实现规则执行器

/**
 * DSL规则执行器
 */
@Service
public class RuleDSLExecutor {
    
    /**
     * 执行规则(按依赖顺序)
     */
    public Object executeRule(Long ruleId) {
        // 1. 获取规则节点
        Rule rule = ruleService.getById(ruleId);
        
        // 2. 获取依赖规则(DEPENDS_ON边)
        List<Rule> dependencies = getDependencies(ruleId);
        
        // 3. 按execution_order排序
        dependencies.sort(Comparator.comparing(Rule::getExecutionOrder));
        
        // 4. 递归执行依赖规则
        Map<String, Object> context = new HashMap<>();
        for (Rule dep : dependencies) {
            Object result = executeRule(dep.getId());
            context.put(dep.getFunctionName(), result);
        }
        
        // 5. 执行当前规则
        Object result = executeFunctionBody(rule, context);
        
        // 6. 验证输出
        validateOutput(rule, result);
        
        // 7. 保存输出
        saveOutput(ruleId, result);
        
        return result;
    }
    
    /**
     * 执行函数体
     */
    private Object executeFunctionBody(Rule rule, Map<String, Object> context) {
        // 根据 rule_type 选择执行器
        switch (rule.getRuleType()) {
            case "function_def":
                return executePythonFunction(rule, context);
            case "direct_entity":
                return executeDirectEntity(rule);
            case "llm":
                return executeLLM(rule, context);
            default:
                throw new UnsupportedOperationException("Unknown rule type: " + rule.getRuleType());
        }
    }
}

Step 4: 前端规则配置界面

<template>
  <div class="rule-dsl-editor">
    <!-- DSL代码编辑器 -->
    <CodeEditor
      v-model="dslCode"
      language="python"
      @save="saveDSL"
    />
    
    <!-- 规则依赖图 -->
    <RuleDependencyGraph
      :rules="parsedRules"
      @node-click="selectRule"
    />
    
    <!-- 规则详情 -->
    <RuleDetail
      v-if="selectedRule"
      :rule="selectedRule"
      @execute="executeRule"
    />
  </div>
</template>

六、接口设计更新

6.1 新增接口

// 1. DSL解析接口
POST /api/v1/projects/{projectId}/rules/parse-dsl
{
  "dslCode": "完整的DSL代码..."
}

Response:
{
  "code": 200,
  "data": {
    "mainRuleId": 700,
    "functions": [
      {"id": 700, "name": "calculate_score", "order": 1},
      {"id": 701, "name": "build_one_five_section", "order": 2},
      {"id": 702, "name": "build_one_full_chapter", "order": 3}
    ]
  }
}

// 2. 规则依赖查询接口
GET /api/v1/rules/{ruleId}/dependencies

Response:
{
  "code": 200,
  "data": {
    "ruleId": 701,
    "dependencies": [
      {"id": 700, "name": "calculate_score", "type": "ref"}
    ],
    "dependents": [
      {"id": 702, "name": "build_one_full_chapter", "type": "ref"}
    ]
  }
}

// 3. 规则执行接口(支持依赖链)
POST /api/v1/rules/{ruleId}/execute
{
  "context": {
    "one_two_ch_id": 12578,
    "one_tree_ch_id": 12389,
    "section_prompt": "从原文生成100以内字数的摘要"
  }
}

Response:
{
  "code": 200,
  "data": {
    "ruleId": 702,
    "output": "完整章节内容...",
    "executionTrace": [
      {"ruleId": 700, "name": "calculate_score", "output": 93.33, "duration": 10},
      {"ruleId": 701, "name": "build_one_five_section", "output": "摘要内容...", "duration": 2500},
      {"ruleId": 702, "name": "build_one_full_chapter", "output": "完整内容...", "duration": 100}
    ]
  }
}

七、总结

7.1 当前设计评估

维度 评分 说明
基础支持 ⭐⭐⭐⭐ 节点、关系、属性基础完善
DSL支持 ⭐⭐ 缺少函数引用、依赖关系
扩展性 ⭐⭐⭐⭐⭐ 图数据库架构易于扩展
实现难度 ⭐⭐⭐ 需要扩展但不复杂

7.2 改进建议

推荐采用方案A(扩展当前设计)

需要新增:

  1. 关系类型:DEPENDS_ON(规则依赖)
  2. 节点属性:function_name, function_params, function_body, return_type, validators, execution_order
  3. 边属性:input_value_type, input_value, input_value_json

实现优先级:

  • P0: 基础函数定义和执行(Week 4)
  • P1: 函数引用和依赖链(Week 4)
  • P2: DSL解析器和可视化(Week 5)

7.3 兼容性

完全兼容当前设计

  • 不需要修改现有表结构
  • 只需新增关系类型和属性定义
  • 现有的 direct_entityextraction 等规则类型继续有效

7.4 下一步行动

  1. 更新 init_mock_new.sql,添加新的关系类型和属性定义
  2. 实现 RuleDSLParser 解析器
  3. 实现 RuleDSLExecutor 执行器
  4. 前端实现DSL编辑器和依赖图可视化
  5. 编写测试用例验证DSL功能