feat: simplify SOP to immediate-effect configuration
This commit is contained in:
@@ -12,8 +12,9 @@ var allowedNodeTypes = map[string]bool{"start": true, "message": true, "question
|
||||
var allowedConditionOperators = map[string]bool{"equals": true, "not_equals": true, "contains": true, "greater_than": true, "less_than": true, "exists": true, "not_exists": true, "in": true}
|
||||
|
||||
type ValidationContext struct {
|
||||
Fields []model.ScenarioField
|
||||
PublishedKnowledgeCardIDs map[uint64]bool
|
||||
Fields []model.ScenarioField
|
||||
KnowledgeItems []model.KnowledgeItem
|
||||
KnowledgeRelations []model.KnowledgeRelation
|
||||
}
|
||||
|
||||
func ValidateGraph(startNodeKey string, nodes []model.SOPNode, edges []model.SOPEdge) []string {
|
||||
@@ -134,6 +135,23 @@ func ValidateForPublish(startNodeKey string, nodes []model.SOPNode, edges []mode
|
||||
for _, field := range context.Fields {
|
||||
fieldMap[field.FieldKey] = field
|
||||
}
|
||||
knowledgeByKey := make(map[string]model.KnowledgeItem, len(context.KnowledgeItems))
|
||||
knowledgeTypes := make(map[string]bool)
|
||||
knowledgeIDs := make(map[uint64]bool, len(context.KnowledgeItems))
|
||||
for _, item := range context.KnowledgeItems {
|
||||
if item.Status != "active" {
|
||||
continue
|
||||
}
|
||||
knowledgeByKey[item.ItemKey] = item
|
||||
knowledgeTypes[item.Type] = true
|
||||
knowledgeIDs[item.ID] = true
|
||||
}
|
||||
relationTypes := make(map[string]bool)
|
||||
for _, relation := range context.KnowledgeRelations {
|
||||
if knowledgeIDs[relation.FromKnowledgeID] && knowledgeIDs[relation.ToKnowledgeID] {
|
||||
relationTypes[relation.RelationType] = true
|
||||
}
|
||||
}
|
||||
collected := map[string]bool{}
|
||||
nodeMap := make(map[string]model.SOPNode, len(nodes))
|
||||
adjacency := make(map[string][]string)
|
||||
@@ -174,14 +192,38 @@ func ValidateForPublish(startNodeKey string, nodes []model.SOPNode, edges []mode
|
||||
collected[fieldKey] = true
|
||||
}
|
||||
case "knowledge":
|
||||
cardID := uint64FromJSON(config["knowledge_card_id"])
|
||||
if cardID == 0 || !context.PublishedKnowledgeCardIDs[cardID] {
|
||||
problems = append(problems, fmt.Sprintf("节点“%s”没有关联已发布的知识卡", node.Title))
|
||||
selector, ok := config["knowledge_selector"].(map[string]interface{})
|
||||
if !ok {
|
||||
problems = append(problems, fmt.Sprintf("节点“%s”没有配置知识选择器", node.Title))
|
||||
break
|
||||
}
|
||||
validateKnowledgeSelector(node, selector, knowledgeByKey, knowledgeTypes, relationTypes, &problems)
|
||||
if collection, ok := config["knowledge_collection"].(map[string]interface{}); ok {
|
||||
if keys, ok := collection["context_field_keys"].([]interface{}); ok {
|
||||
for _, value := range keys {
|
||||
if key, ok := value.(string); ok {
|
||||
if _, exists := fieldMap[key]; !exists {
|
||||
problems = append(problems, fmt.Sprintf("节点“%s”引用的采集字段 %s 不存在", node.Title, key))
|
||||
} else {
|
||||
collected[key] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if steps, ok := collection["steps"].([]interface{}); ok {
|
||||
for _, raw := range steps {
|
||||
if step, ok := raw.(map[string]interface{}); ok {
|
||||
if key, ok := step["field_key"].(string); ok && key != "" {
|
||||
collected[key] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, field := range context.Fields {
|
||||
if field.Required && !collected[field.FieldKey] {
|
||||
if field.Required && field.SourcePath == "" && !collected[field.FieldKey] {
|
||||
problems = append(problems, fmt.Sprintf("必填字段“%s”没有对应的采集节点", field.FieldName))
|
||||
}
|
||||
}
|
||||
@@ -312,20 +354,61 @@ func isDefaultCondition(value []byte) bool {
|
||||
return ok && len(object) == 0
|
||||
}
|
||||
|
||||
func uint64FromJSON(value interface{}) uint64 {
|
||||
switch typed := value.(type) {
|
||||
case float64:
|
||||
if typed > 0 {
|
||||
return uint64(typed)
|
||||
}
|
||||
case uint64:
|
||||
return typed
|
||||
case int:
|
||||
if typed > 0 {
|
||||
return uint64(typed)
|
||||
func validateKnowledgeSelector(node model.SOPNode, selector map[string]interface{}, items map[string]model.KnowledgeItem, availableTypes, availableRelations map[string]bool, problems *[]string) {
|
||||
derivedField, _ := selector["derived_field"].(string)
|
||||
answerField, _ := selector["answer_field"].(string)
|
||||
keys, keysValid := selectorStrings(selector, "knowledge_keys")
|
||||
types, typesValid := selectorStrings(selector, "knowledge_types")
|
||||
relations, relationsValid := selectorStrings(selector, "relation_types")
|
||||
if !keysValid || !typesValid || !relationsValid {
|
||||
*problems = append(*problems, fmt.Sprintf("节点“%s”的知识选择器必须使用字符串数组", node.Title))
|
||||
return
|
||||
}
|
||||
if derivedField == "" && answerField == "" && len(keys) == 0 {
|
||||
*problems = append(*problems, fmt.Sprintf("节点“%s”没有配置派生知识字段或固定知识 key", node.Title))
|
||||
}
|
||||
allowedTypes := make(map[string]bool, len(types))
|
||||
for _, itemType := range types {
|
||||
allowedTypes[itemType] = true
|
||||
if !availableTypes[itemType] {
|
||||
*problems = append(*problems, fmt.Sprintf("节点“%s”引用的知识类型 %s 不存在", node.Title, itemType))
|
||||
}
|
||||
}
|
||||
return 0
|
||||
for _, key := range keys {
|
||||
item, exists := items[key]
|
||||
if !exists {
|
||||
*problems = append(*problems, fmt.Sprintf("节点“%s”引用的知识 key %s 不存在或未启用", node.Title, key))
|
||||
continue
|
||||
}
|
||||
if len(allowedTypes) > 0 && !allowedTypes[item.Type] {
|
||||
*problems = append(*problems, fmt.Sprintf("节点“%s”的知识 key %s 不属于允许的根类型", node.Title, key))
|
||||
}
|
||||
}
|
||||
for _, relationType := range relations {
|
||||
if !availableRelations[relationType] {
|
||||
*problems = append(*problems, fmt.Sprintf("节点“%s”引用的知识关系类型 %s 不存在", node.Title, relationType))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func selectorStrings(selector map[string]interface{}, key string) ([]string, bool) {
|
||||
raw, exists := selector[key]
|
||||
if !exists || raw == nil {
|
||||
return nil, true
|
||||
}
|
||||
values, ok := raw.([]interface{})
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
result := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
text, ok := value.(string)
|
||||
if !ok || text == "" {
|
||||
return nil, false
|
||||
}
|
||||
result = append(result, text)
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
func canReachType(start, nodeType string, nodes map[string]model.SOPNode, adjacency map[string][]string) bool {
|
||||
|
||||
Reference in New Issue
Block a user