Skip to content

AI功能配置系统详细指南 ​

本文档详细说明X-Wukong Mini Backend的AI功能配置逻辑、工作流程和设计模式。


目录 ​


一、AI功能配置系统架构 ​

1. 核心数据模型(model/ai_function.go) ​

AIFunction - AI功能配置表 ​

这是整个系统的核心配置实体,包含以下关键字段:

功能标识

  • function_key: 功能唯一标识(如 ai_topic_evaluation)
  • function_name: 功能显示名称
  • description: 功能描述

提示词系统

  • prompt_template: 发送给AI的完整提示词模板,支持变量占位符
    • 例如: {{subject}}, {{title}}
    • 包含详细的指令、格式要求、示例等
  • user_prompt_template: 展示给用户的简化版引导语
    • 更简洁,便于用户理解
    • 同样支持变量占位符

字段配置系统

  • input_fields: JSONB格式的输入字段配置数组
    • 定义用户需要提供的输入参数
    • 包含验证规则
  • output_fields: JSONB格式的输出字段配置数组
    • 定义AI返回数据的结构
    • 支持预览字段标记

输出格式控制

  • output_format: 支持4种类型
    • json_object: 结构化对象(默认)
    • json_array: 数组列表
    • plain_text: 纯文本
    • markdown: Markdown格式

模型配置

  • model_id: AI模型ID(外键)
  • model_config: JSONB格式的模型参数覆盖
    • 可覆盖默认的temperature、max_tokens等参数

状态管理

  • status: 状态(0=未启动,1=启用,2=禁用)
  • sort: 排序值

AIFieldConfig - 字段配置结构 ​

灵活的字段定义系统,支持以下属性:

基础属性

go
type AIFieldConfig struct {
    FieldKey     string                 // 字段标识(编程用)
    FieldName    string                 // 字段显示名称(用户可见)
    FieldType    string                 // 字段类型
    IsRequired   bool                   // 是否必填
    DefaultValue interface{}            // 默认值
    Validation   AIFieldValidation      // 验证规则
    Description  string                 // 字段描述
    Extra        map[string]interface{} // 额外配置(扩展点)
}

支持的6种字段类型

  1. string - 字符串
  2. int - 整数
  3. float - 浮点数
  4. bool - 布尔值
  5. array - 数组
  6. object - 对象

验证规则(AIFieldValidation)

go
type AIFieldValidation struct {
    MinLength int      // 最小长度(字符串/数组)
    MaxLength int      // 最大长度(字符串/数组)
    Pattern   string   // 正则表达式(字符串)
    Options   []string // 枚举选项(字符串)
    Min       *float64 // 数值最小值
    Max       *float64 // 数值最大值
}

扩展配置(Extra字段)

Extra 是一个 map[string]interface{},支持任意自定义配置,常见用途:

  • 预览控制: preview_visible: true
  • 数组结构定义: item_schema, item_type
  • UI展示配置: display_type, unit, icon, display_section
  • 数量控制: expected_count, min_count, max_count

其他相关模型 ​

AICallLog - AI调用日志

  • 记录每次AI调用的完整信息
  • 包含输入、输出、耗时、Token消耗等

AIProvider - AI服务提供商

  • 配置OpenAI或其他AI服务的连接信息
  • 支持多个提供商配置

AIModel - AI模型配置

  • 定义可用的AI模型
  • 关联到具体的提供商

2. 核心服务逻辑(service/ai_function.go) ​

缓存机制 ​

设计目标: 减少数据库查询,提高性能

实现方式:

go
var (
    aiConfigCache       = make(map[string]*model.AIFunction)
    aiConfigCacheMux    sync.RWMutex
    aiConfigCacheTime   = time.Now()
    cacheExpireDuration = 10 * time.Minute
)

缓存策略:

  1. 首次访问: 从数据库加载 → 存入缓存
  2. 后续访问:
    • 缓存未过期(10分钟内)→ 直接返回缓存
    • 缓存已过期 → 重新从数据库加载
  3. 并发安全: 使用读写锁(RWMutex)保护缓存访问

手动刷新:

  • RefreshAIConfigCache(): 清空缓存,强制重新加载

二、CallAI完整执行流程 ​

流程概览 ​

用户请求
  ↓
[阶段1: 准备阶段]
  → 获取AI功能配置(缓存/数据库)
  → 验证输入数据
  → 构建AI提示词
  ↓
[阶段2: AI调用阶段]
  → 获取模型配置
  → 构建OpenAI请求
  → 调用OpenAI API
  ↓
[阶段3: 响应处理阶段]
  → 解析AI原始响应
  → 处理输出数据(根据output_format)
  → 特殊功能增强(如期刊数据增强)
  → 记录调用日志
  → 创建用户历史记录
  ↓
[阶段4: 预览模式处理]
  → 判断用户权限状态
  → 生成预览数据(字段过滤)
  → 计算付费控制字段
  ↓
返回响应给用户

阶段1: 准备阶段(第138-157行) ​

1.1 获取AI功能配置 ​

go
function := GetAIFunctionByKey(req.FunctionKey)
expect.PBMC(function == nil, code.PARAMETER_ERROR,
    fmt.Sprintf("AI功能 '%s' 不存在或已禁用", req.FunctionKey))
  • 通过 function_key 查询配置
  • 先查缓存,未命中则查数据库
  • 配置不存在或已禁用时返回错误

1.2 验证输入数据 ​

go
err = validateAIInputData(req.InputData, function.InputFields)

根据 input_fields 配置进行三层验证:

  1. 必填检查: 检查必填字段是否存在
  2. 类型验证: 验证字段类型是否匹配
  3. 值验证: 验证字段值是否符合规则

详细验证逻辑见三、输入验证体系

1.3 构建AI提示词 ​

go
prompt, err := buildAIPrompt(function.PromptTemplate, req.InputData)

处理步骤:

  1. 使用模板引擎替换占位符
    • {{subject}}→ 用户输入的专业名称
    • {{title}} → 用户输入的选题名称
  2. 清理输入,防止注入攻击
    • 调用 utils.SanitizeInput()

示例转换:

模板: "我的专业是{{subject}},请评估选题:{{title}}"
输入: {"subject": "工商管理", "title": "数字化转型研究"}
输出: "我的专业是工商管理,请评估选题:数字化转型研究"

阶段2: AI调用阶段(第156-178行) ​

2.1 获取模型配置 ​

go
modelConfig, err := getAIEffectiveModelConfig(function)

配置合并逻辑:

默认配置:
  - Model: "gpt-3.5-turbo"
  - Temperature: 0.7
  - MaxTokens: 2000
  - TopP: 1.0
  - Timeout: 60
  - RetryTimes: 3

↓ 合并 function.ModelConfig

最终配置:
  - 如果 function.ModelConfig.Model != "",使用自定义值
  - 如果 function.ModelConfig.Temperature > 0,使用自定义值
  - 其他参数同理

2.2 构建OpenAI请求 ​

go
openaiReq := &utils.OpenAIRequest{
    Model:            modelConfig.Model,
    Temperature:      modelConfig.Temperature,
    MaxTokens:        modelConfig.MaxTokens,
    TopP:             modelConfig.TopP,
    FrequencyPenalty: modelConfig.FrequencyPenalty,
    PresencePenalty:  modelConfig.PresencePenalty,
    Messages: []openai.ChatCompletionMessage{
        {
            Role:    openai.ChatMessageRoleUser,
            Content: prompt,
        },
    },
}

2.3 调用OpenAI API ​

go
openaiResp, err = utils.CallOpenAI(ctx, openaiReq)
callDuration := int(time.Since(startTime).Milliseconds())
  • 发送HTTP请求到OpenAI API
  • 记录调用耗时
  • 处理超时、重试等

阶段3: 响应处理阶段(第194-302行) ​

3.1 处理调用失败场景 ​

go
if err != nil || openaiResp.Error != nil {
    errorMsg := ""
    if err != nil {
        errorMsg = err.Error()
    } else if openaiResp.Error != nil {
        errorMsg = openaiResp.Error.Message
    }

    resp.ErrorMessage = errorMsg
    callLog.Status = "error"
    callLog.ErrorMessage = errorMsg
}
  • 记录错误信息到日志
  • 返回错误响应给用户

3.2 处理调用成功场景 ​

步骤1: 提取原始响应

go
rawResponse := ""
if len(openaiResp.Choices) > 0 {
    rawResponse = openaiResp.Choices[0].Message.Content
}

步骤2: 处理输出数据

go
outputData, err := processAIOutputData(rawResponse,
    function.OutputFields, function.OutputFormat)

根据 output_format 进行不同处理:

格式处理方式
json_object解析为JSON对象 map[string]interface{}
json_array解析为JSON数组 []interface{}
plain_text直接返回原始文本 string
markdown直接返回Markdown文本 string

步骤3: 特殊功能增强(针对智能投稿期刊)

go
if req.FunctionKey == "ai_journal_submission" {
    enrichedData, enrichErr := enrichJournalDataWithDetails(outputData)
    if enrichErr != nil {
        // 处理错误
    } else {
        outputData = enrichedData
    }
}

详见案例2: 智能投稿期刊

步骤4: 记录调用日志

go
callLog.ResponseRaw = rawResponse
callLog.TokensUsed = openaiResp.Usage.TotalTokens
callLog.Status = "success"
callLog.InputData = json.Marshal(req.InputData)
callLog.OutputData = json.Marshal(outputData)

err = callLog.Create()

保存到 ai_call_logs 表,包含:

  • 输入数据
  • 输出数据
  • 提示词
  • 原始响应
  • Token消耗
  • 调用耗时
  • 状态

步骤5: 创建用户历史记录

go
history, historyErr := CreateAIFunctionUsage(
    req.UserInfo.Uuid,
    req.FunctionKey,
    req.InputData,
    outputData,
    callLog.ID,
    req.UserInfo,
    req.OrgInfo
)

保存到 ai_function_usage 表,用于:

  • 用户历史记录查询
  • 预览/完整状态管理
  • 付费控制
  • 去重检查

阶段4: 预览模式处理(第284-298行) ​

4.1 判断预览状态 ​

go
resp.IsPreview = history.ViewStatus == conf.ViewStatusPreview

ViewStatus取值:

  • ViewStatusPreview: 预览模式(需要付费或升级会员才能查看完整内容)
  • ViewStatusFull: 完整模式(可以查看所有内容)

判断逻辑(在CreateAIFunctionUsage中):

  1. 如果功能完全免费(Price = 0)→ Full
  2. 如果用户是会员且会员免费(VipPrice = 0)→ Full
  3. 其他情况 → Preview

4.2 生成预览数据 ​

go
if resp.IsPreview {
    previewData := getPreviewData(
        history.OutputData,
        history.FunctionKey,
        history.PreviewCount
    )
    var previewInterface interface{}
    err := json.Unmarshal(previewData, &previewInterface)
    if err == nil {
        resp.OutputData = previewInterface
    }
}

getPreviewData逻辑:

  1. 获取AI功能配置(读取 output_fields)
  2. 遍历配置字段,过滤出 extra.preview_visible = true 的字段
  3. 构建只包含预览字段的数据对象
  4. 如果字段是数组,可能截取前 previewCount 条

详见service/ai_function_usage.go:375-461

4.3 计算付费控制字段 ​

go
fillPaymentFields(resp, req.FunctionKey, req.UserInfo, req.OrgInfo)

计算的字段:

  • show_payment_buttons: 是否显示付费按钮
  • payment_price: 需要支付的金额(根据会员状态可能是原价或折扣价)
  • show_free_trial_tip: 是否显示免费试用提示
  • remaining_free_times: 剩余免费次数
  • member_tip_text: 会员提示文本(如"会员免费"、"会员享折扣")

四种场景:

场景PriceVipPrice会员状态显示按钮价格提示
完全免费00-❌0-
会员免费>00是会员❌0会员免费
会员免费>00非会员✅Price会员免费
会员折扣>0>0是会员✅VipPrice会员享折扣
会员折扣>0>0非会员✅Price会员享折扣
无优惠>0>0(=Price)-✅Price-

三、输入验证体系 ​

validateAIInputData 完整逻辑 ​

go
func validateAIInputData(inputData map[string]interface{},
    fields []model.AIFieldConfig) error {

    for _, field := range fields {
        value, exists := inputData[field.FieldKey]

        // 第一层: 必填检查
        if field.IsRequired && (!exists || tool.IsEmpty(value)) {
            return fmt.Errorf("必填字段 '%s' 缺失或为空", field.FieldName)
        }

        if !exists {
            continue // 非必填字段不存在,跳过后续验证
        }

        // 第二层: 类型验证
        if err := validateAIFieldType(value, field); err != nil {
            return fmt.Errorf("字段 '%s' %v", field.FieldName, err)
        }

        // 第三层: 值验证
        if err := validateAIFieldValue(value, field); err != nil {
            return fmt.Errorf("字段 '%s' %v", field.FieldName, err)
        }
    }

    return nil
}

第一层: 必填检查 ​

规则:

如果 field.IsRequired = true 且 (字段不存在 OR 值为空)
  → 返回错误

空值判断:

  • nil
  • 空字符串 ""
  • 空数组 []
  • 空对象 {}

第二层: 类型验证(validateAIFieldType) ​

string类型 ​

go
if _, ok := value.(string); !ok {
    return fmt.Errorf("类型错误,期望字符串类型")
}

int类型 ​

go
switch v := value.(type) {
case int, int8, int16, int32, int64:
    // 合法的整数类型
case float64:
    // JSON反序列化数字默认为float64,需检查是否为整数
    if v != float64(int64(v)) {
        return fmt.Errorf("类型错误,期望整数类型")
    }
default:
    return fmt.Errorf("类型错误,期望整数类型")
}

注意: Go的JSON反序列化会将所有数字解析为 float64,需要特殊处理。

float类型 ​

go
switch value.(type) {
case float32, float64, int, int8, int16, int32, int64:
    // 合法的数字类型
default:
    return fmt.Errorf("类型错误,期望数字类型")
}

bool类型 ​

go
if _, ok := value.(bool); !ok {
    return fmt.Errorf("类型错误,期望布尔类型")
}

array类型 ​

go
switch value.(type) {
case []interface{}, []string, []int, []float64:
    // 合法的数组类型
default:
    return fmt.Errorf("类型错误,期望数组类型")
}

object类型 ​

go
if _, ok := value.(map[string]interface{}); !ok {
    return fmt.Errorf("类型错误,期望对象类型")
}

第三层: 值验证(validateAIFieldValue) ​

字符串验证 ​

长度验证:

go
if field.FieldType == "string" {
    if str, ok := value.(string); ok {
        strLen := len([]rune(str)) // 使用rune计算,支持中文

        if field.Validation.MinLength > 0 && strLen < field.Validation.MinLength {
            return fmt.Errorf("长度不能少于%d个字符", field.Validation.MinLength)
        }

        if field.Validation.MaxLength > 0 && strLen > field.Validation.MaxLength {
            return fmt.Errorf("长度不能超过%d个字符", field.Validation.MaxLength)
        }
    }
}

关键点: 使用 []rune(str) 而不是 len(str),正确计算中文字符数。

正则表达式验证:

go
if field.Validation.Pattern != "" && field.FieldType == "string" {
    if str, ok := value.(string); ok {
        matched, err := regexp.MatchString(field.Validation.Pattern, str)
        if err != nil {
            return fmt.Errorf("正则表达式验证失败: %v", err)
        }
        if !matched {
            return fmt.Errorf("格式不符合要求")
        }
    }
}

枚举值验证:

go
if len(field.Validation.Options) > 0 && field.FieldType == "string" {
    if str, ok := value.(string); ok {
        found := false
        for _, option := range field.Validation.Options {
            if str == option {
                found = true
                break
            }
        }
        if !found {
            return fmt.Errorf("值必须是以下选项之一: %v", field.Validation.Options)
        }
    }
}

数值验证 ​

范围验证:

go
if field.FieldType == "int" || field.FieldType == "float" {
    var numValue float64

    // 类型转换为float64
    switch v := value.(type) {
    case int:
        numValue = float64(v)
    case int8, int16, int32, int64:
        numValue = float64(v)
    case float32, float64:
        numValue = float64(v)
    default:
        return fmt.Errorf("无法验证数值范围")
    }

    // 验证范围
    if field.Validation.Min != nil && numValue < *field.Validation.Min {
        return fmt.Errorf("数值不能小于%.2f", *field.Validation.Min)
    }

    if field.Validation.Max != nil && numValue > *field.Validation.Max {
        return fmt.Errorf("数值不能大于%.2f", *field.Validation.Max)
    }
}

数组验证 ​

元素数量验证:

go
if field.FieldType == "array" {
    var arrayLen int

    switch arr := value.(type) {
    case []interface{}:
        arrayLen = len(arr)
    case []string:
        arrayLen = len(arr)
    case []int:
        arrayLen = len(arr)
    case []float64:
        arrayLen = len(arr)
    default:
        return fmt.Errorf("无法验证数组长度")
    }

    if field.Validation.MinLength > 0 && arrayLen < field.Validation.MinLength {
        return fmt.Errorf("数组长度不能少于%d个元素", field.Validation.MinLength)
    }

    if field.Validation.MaxLength > 0 && arrayLen > field.Validation.MaxLength {
        return fmt.Errorf("数组长度不能超过%d个元素", field.Validation.MaxLength)
    }
}

验证示例 ​

配置:

json
{
  "field_key": "subject",
  "field_name": "专业名称",
  "field_type": "string",
  "is_required": true,
  "validation": {
    "min_length": 2,
    "max_length": 100,
    "pattern": "^[\\u4e00-\\u9fa5a-zA-Z]+$"
  }
}

验证过程:

输入验证结果
未提供字段❌ 必填字段 '专业名称' 缺失或为空
""❌ 必填字段 '专业名称' 缺失或为空
"计"❌ 字段 '专业名称' 长度不能少于2个字符
123❌ 字段 '专业名称' 类型错误,期望字符串类型
"计算机123"❌ 字段 '专业名称' 格式不符合要求
"计算机科学"✅ 验证通过
"Computer Science"✅ 验证通过

四、输出字段配置的灵活性 ​

Extra 字段的多种用途 ​

extra 是一个 map[string]interface{},提供了强大的扩展能力。

1. 预览控制 ​

用途: 标记字段在预览模式下是否可见

配置示例:

json
{
  "field_key": "overall_score",
  "field_name": "综合评分",
  "field_type": "int",
  "extra": {
    "preview_visible": true
  }
}

作用机制:

  • getPreviewData 函数会检查此标记
  • 只有 preview_visible: true 的字段才会包含在预览数据中
  • 未标记的字段默认不在预览中显示

2. 数组元素结构定义 ​

用途: 定义数组字段中每个元素的结构

配置示例:

json
{
  "field_key": "topics",
  "field_name": "生成的选题列表",
  "field_type": "array",
  "extra": {
    "item_type": "object",
    "item_schema": {
      "title": "选题名称",
      "reason": "推荐理由",
      "innovation": "创新性说明",
      "logic_structure": "逻辑结构"
    }
  }
}

作用:

  • 文档化数组元素的结构
  • 前端可以根据 item_schema 渲染UI
  • 提示AI按照此结构生成数据

3. UI展示配置 ​

用途: 指导前端如何渲染字段

配置示例:

json
{
  "field_key": "overall_score",
  "field_name": "综合评分",
  "field_type": "int",
  "extra": {
    "display_type": "circular_progress",  // 显示为圆形进度环
    "unit": "分",                         // 单位
    "display_section": "综合评价",        // 所属分组
    "highlight_color": "yellow",          // 高亮颜色
    "icon": "💡"                          // 图标
  }
}

display_type 常见值:

  • circular_progress: 圆形进度环(常用于总分)
  • progress_bar: 横向进度条(常用于分项评分)
  • text: 普通文本
  • badge: 徽章样式
  • list: 列表展示

前端渲染示例:

jsx
// 根据 display_type 选择组件
switch (field.extra.display_type) {
  case 'circular_progress':
    return <CircularProgress value={data.overall_score} unit={field.extra.unit} />
  case 'progress_bar':
    return <ProgressBar value={data.feasibility_score} />
  default:
    return <Text>{data[field.field_key]}</Text>
}

4. 数量期望配置 ​

用途: 提示AI生成特定数量的结果

配置示例:

json
{
  "field_key": "risks_and_challenges",
  "field_name": "风险与挑战",
  "field_type": "array",
  "extra": {
    "expected_count": 4,    // 期望生成4个
    "min_count": 3,         // 至少3个
    "max_count": 6          // 最多6个
  }
}

在提示词中的应用:

请列出研究过程中可能遇到的主要风险和挑战(3-6个,建议4个)

5. 字段关系定义 ​

用途: 定义嵌套对象的结构

配置示例:

json
{
  "field_key": "current_status",
  "field_name": "现状概况",
  "field_type": "object",
  "extra": {
    "item_schema": {
      "type": "object",
      "properties": {
        "introduction": {
          "type": "string",
          "description": "研究现状的概括性导语"
        },
        "trends": {
          "type": "array",
          "description": "主要研究趋势列表",
          "items": {
            "type": "string"
          }
        }
      }
    }
  }
}

数据结构:

json
{
  "current_status": {
    "introduction": "该领域研究现状的概括...",
    "trends": [
      "研究趋势1",
      "研究趋势2",
      "研究趋势3"
    ]
  }
}

6. 枚举值约束 ​

用途: 限制数组元素的title字段只能是特定值

配置示例:

json
{
  "field_key": "feasibility_analysis",
  "field_name": "选题可行性分析",
  "field_type": "array",
  "extra": {
    "item_schema": {
      "type": "object",
      "properties": {
        "title": {
          "type": "string",
          "enum": ["优势", "挑战"],
          "description": "分析维度标题:优势或挑战"
        },
        "content": {
          "type": "string",
          "description": "该维度的详细分析内容"
        }
      }
    },
    "expected_count": 2
  }
}

提示AI:

请从"优势"和"挑战"两个维度分析选题可行性,每个维度包含:
- title: 必须是"优势"或"挑战"
- content: 详细分析内容

Extra配置最佳实践 ​

✅ 推荐做法 ​

  1. 文档化所有extra字段

    json
    // 在配置中添加注释说明
    "extra": {
      "preview_visible": true,        // 预览模式可见
      "display_type": "progress_bar", // UI组件类型
      "unit": "分"                    // 显示单位
    }
  2. 使用统一的命名规范

    • 使用 snake_case: preview_visible, item_schema
    • 避免使用驼峰: previewVisible
  3. 提供合理的默认值

    go
    // 在代码中处理 extra 字段时提供默认值
    previewVisible := false
    if extra != nil {
        if v, ok := extra["preview_visible"].(bool); ok {
            previewVisible = v
        }
    }

❌ 避免的做法 ​

  1. 不要在extra中存储业务数据

    json
    // ❌ 错误
    "extra": {
      "user_score": 85  // 业务数据应该在 default_value 或数据表中
    }
  2. 不要使用过于复杂的嵌套

    json
    // ❌ 过于复杂
    "extra": {
      "config": {
        "display": {
          "mobile": {
            "type": "compact"
          }
        }
      }
    }
    
    // ✅ 扁平化
    "extra": {
      "mobile_display_type": "compact"
    }

五、典型案例流程 ​

案例1: 智能选题测评(ai_topic_evaluation) ​

功能概述 ​

对学术选题进行全面评估,包括可行性、创新性、价值性等多维度分析。

配置结构 ​

输入字段(input_fields):

json
[
  {
    "field_key": "subject",
    "field_name": "专业名称",
    "field_type": "string",
    "is_required": true,
    "validation": {
      "min_length": 2,
      "max_length": 100
    }
  },
  {
    "field_key": "title",
    "field_name": "选题名称",
    "field_type": "string",
    "is_required": true,
    "validation": {
      "min_length": 5,
      "max_length": 500
    }
  }
]

输出字段(output_fields): 12个字段

预览可见字段(7个):

  1. title - 选题名称(回显)
  2. overall_score - 综合评分
  3. feasibility_score - 可行性得分
  4. innovation_score - 创新性得分
  5. value_score - 价值性得分
  6. overall_evaluation - 综合评价
  7. feasibility_analysis - 选题可行性分析

完整版字段(5个,预览不可见): 8. innovation_analysis - 选题创新性分析 9. value_analysis - 选题价值性分析 10. background_significance - 背景与意义 11. risks_and_challenges - 风险与挑战 12. current_status - 现状概况

执行流程详解 ​

步骤1: 用户提交请求

json
{
  "function_key": "ai_topic_evaluation",
  "input_data": {
    "subject": "工商管理",
    "title": "数字化转型对中小企业创新能力的影响研究"
  }
}

步骤2: 验证输入

  • 检查 subject 和 title 都存在且为字符串
  • 验证 subject 长度在 2-100 之间
  • 验证 title 长度在 5-500 之间

步骤3: 构建提示词

我是一名在读的博士生,我的专业是工商管理,我正在开展学术期刊论文选题工作,
我初步拟定的选题名称是:数字化转型对中小企业创新能力的影响研究。

请你充当我的学术指导教师,从以下几个方面对该选题进行全面评估:
...(详细的评估要求)

请严格按照以下 JSON 格式输出:
{
  "title": "选题名称",
  "overall_score": 85,
  "feasibility_score": 78,
  ...
}

步骤4: 调用GPT-4o

  • Model: gpt-4o
  • Temperature: 0.7
  • MaxTokens: 6000

步骤5: 解析AI响应 AI返回完整的JSON对象,包含所有12个字段。

步骤6: 判断用户权限

场景A: 会员用户或免费功能

go
// CreateAIFunctionUsage 中的判断逻辑
if functionConfig.Price == 0 ||
   (functionConfig.VipPrice == 0 && isVip) {
    viewStatus = conf.ViewStatusFull
}

返回完整数据(12个字段):

json
{
  "id": 123,
  "is_preview": false,
  "view_status": 2,
  "output_data": {
    "title": "数字化转型对中小企业创新能力的影响研究",
    "overall_score": 85,
    "feasibility_score": 78,
    "innovation_score": 90,
    "value_score": 88,
    "overall_evaluation": "该选题具有较强的学术价值...",
    "feasibility_analysis": [...],
    "innovation_analysis": [...],      // ✅ 包含
    "value_analysis": [...],           // ✅ 包含
    "background_significance": "...",  // ✅ 包含
    "risks_and_challenges": [...],     // ✅ 包含
    "current_status": {...}            // ✅ 包含
  }
}

场景B: 普通用户且需付费

go
viewStatus = conf.ViewStatusPreview

步骤7: 生成预览数据

go
previewData := getPreviewData(history.OutputData,
    history.FunctionKey, history.PreviewCount)

getPreviewData执行过程:

  1. 加载 ai_topic_evaluation 的 output_fields 配置
  2. 遍历12个字段配置
  3. 过滤出 extra.preview_visible = true 的字段
  4. 从完整数据中提取这7个字段
  5. 如果字段是数组(如 feasibility_analysis),检查是否需要截取

返回预览数据(7个字段):

json
{
  "id": 123,
  "is_preview": true,
  "view_status": 1,
  "output_data": {
    "title": "数字化转型对中小企业创新能力的影响研究",
    "overall_score": 85,
    "feasibility_score": 78,
    "innovation_score": 90,
    "value_score": 88,
    "overall_evaluation": "该选题具有较强的学术价值...",
    "feasibility_analysis": [...]
    // ❌ innovation_analysis 不包含
    // ❌ value_analysis 不包含
    // ❌ background_significance 不包含
    // ❌ risks_and_challenges 不包含
    // ❌ current_status 不包含
  },
  "show_payment_buttons": true,
  "payment_price": 500,
  "remaining_free_times": 2,
  "member_tip_text": "会员用户免费查看"
}

步骤8: 计算付费信息

go
fillPaymentFields(resp, req.FunctionKey, req.UserInfo, req.OrgInfo)

假设配置为:

  • Price: 500(普通用户价格)
  • VipPrice: 0(会员免费)
  • FreeTimes: 3(免费次数)

对于普通用户:

  • show_payment_buttons: true
  • payment_price: 500
  • remaining_free_times: 2(已使用1次)
  • member_tip_text: "会员用户免费查看"

案例2: 智能投稿期刊(ai_journal_submission) ​

功能概述 ​

根据研究内容和方向,推荐适合投稿的学术期刊,并提供详细的期刊信息。

特殊处理逻辑 ​

此功能与其他AI功能的不同之处在于需要数据增强:AI只返回期刊名称和评分,需要通过RPC服务获取期刊的完整详情。

执行流程 ​

步骤1-5: 与普通功能相同

步骤6: AI返回原始数据

json
{
  "journals": [
    {
      "journal_name": "管理世界",
      "match_score": 95,
      "difficulty_score": 85,
      "recommend_score": 92,
      "recommend_reason": "《管理世界》是中国管理学顶级期刊...",
      "submission_advice": "建议突出研究的理论贡献..."
    },
    {
      "journal_name": "中国工业经济",
      "match_score": 93,
      "difficulty_score": 80,
      "recommend_score": 90,
      "recommend_reason": "...",
      "submission_advice": "..."
    }
    // ... 更多期刊
  ]
}

步骤7: 数据增强(enrichJournalDataWithDetails)

go
if req.FunctionKey == "ai_journal_submission" {
    enrichedData, enrichErr := enrichJournalDataWithDetails(outputData)
    if enrichErr != nil {
        // 增强失败,返回错误
        expect.PBM(true, callLog.ErrorMessage)
    } else {
        outputData = enrichedData
    }
}

enrichJournalDataWithDetails 执行过程:

7.1 解析输入数据

go
var dataArray []interface{}
json.Unmarshal(outputData, &dataArray)

7.2 判断数据类型 检查第一条数据:

  • 如果包含 journal_id → 按ID查询(用于历史记录场景)
  • 如果只有 journal_name → 按名称查询(用于AI调用场景)

7.3 提取查询参数

go
names := []string{"管理世界", "中国工业经济", ...}

7.4 调用RPC服务

go
journalDetails := journal.JournalCnListByNames(names)

返回期刊详情:

go
[]journals.JournalInfo{
    {
        JournalId: 123,
        CNJournalName: "管理世界",
        ImpactFactor: 8.5,
        Partition: "T1",
        CNIssn: "1002-5502",
        Host: "中国社会科学院",
        SubmissionUrl: "http://...",
        // ... 更多字段
    },
    ...
}

7.5 构建期刊映射

go
nameToDetail := map[string]journals.JournalInfo{
    "管理世界": {...},
    "中国工业经济": {...},
}

7.6 合并AI评分与期刊详情

go
for _, item := range dataArray {
    name := item["journal_name"]
    detail := nameToDetail[name]

    merged := map[string]interface{}{
        // AI评分数据
        "journal_name": item["journal_name"],
        "journal_id": detail.JournalId,
        "match_score": item["match_score"],
        "difficulty_score": item["difficulty_score"],
        "recommend_score": item["recommend_score"],
        "recommend_reason": item["recommend_reason"],
        "submission_advice": item["submission_advice"],

        // 完整期刊详情
        "journal_detail": detail,
    }

    enrichedResult = append(enrichedResult, merged)
}

7.7 按推荐度排序

go
sort.Slice(enrichedResult, func(i, j int) bool {
    scoreI := enrichedResult[i]["recommend_score"].(float64)
    scoreJ := enrichedResult[j]["recommend_score"].(float64)
    return scoreI > scoreJ
})

7.8 限制返回数量

go
if len(enrichedResult) > 10 {
    enrichedResult = enrichedResult[:10]
}

步骤8: 返回增强后的数据

json
{
  "journals": [
    {
      "journal_name": "管理世界",
      "journal_id": 123,
      "match_score": 95,
      "difficulty_score": 85,
      "recommend_score": 92,
      "recommend_reason": "《管理世界》是中国管理学顶级期刊...",
      "submission_advice": "建议突出研究的理论贡献...",
      "journal_detail": {
        "journal_id": 123,
        "cn_journal_name": "管理世界",
        "impact_factor": 8.5,
        "partition": "T1",
        "cn_issn": "1002-5502",
        "host": "中国社会科学院",
        "submission_url": "http://...",
        "review_cycle": "3-6个月",
        "acceptance_rate": "8%",
        // ... 完整期刊信息
      }
    },
    // ... 最多10条
  ]
}

数据增强的优势 ​

  1. 分离关注点: AI专注于推荐和评分,期刊详情由专业数据库提供
  2. 数据一致性: 期刊详情实时更新,无需AI重新训练
  3. 降低Token消耗: AI不需要生成详细的期刊信息
  4. 提高准确性: 期刊数据来自权威数据库,更准确可靠

案例3: 复刻学术选题(ai_topic_replication) ​

功能概述 ​

根据用户提供的参考文献,生成模仿其理论/方法的学术选题。

配置结构 ​

输入字段:

json
[
  {
    "field_key": "subject",
    "field_name": "专业",
    "field_type": "string",
    "is_required": true
  },
  {
    "field_key": "reference",
    "field_name": "参考文献",
    "field_type": "string",
    "is_required": true,
    "validation": {
      "min_length": 50
    }
  }
]

输出字段: 数组格式

json
{
  "field_key": "topics",
  "field_name": "生成的复刻选题列表",
  "field_type": "array",
  "extra": {
    "item_type": "object",
    "item_schema": {
      "title": "选题名称",
      "innovation": "创新性与新颖性",
      "theory_method": "模仿的理论/方法",
      "logic_structure": "逻辑结构"
    },
    "expected_count": 5,
    "min_count": 5
  }
}

AI输出示例 ​

json
{
  "topics": [
    {
      "title": "数字化转型对中小企业创新能力的影响研究",
      "innovation": "将数字化转型理论应用于中国中小企业情境,填补该领域实证研究空白",
      "theory_method": "采用参考文献中的双差分模型(DID)和中介效应分析框架,模仿其实证研究方法论",
      "logic_structure": "现象观察→理论推导→假设提出→实证检验→机制分析→稳健性检验,完整复刻原文逻辑链条"
    },
    {
      "title": "供应链数字化对企业绩效的影响机制研究",
      "innovation": "从供应链视角拓展数字化研究边界,结合中国制造业转型升级背景",
      "theory_method": "借鉴参考文献的资源基础理论框架和PSM-DID方法,构建类似因果推断模型",
      "logic_structure": "问题提出→文献综述→理论框架→研究假设→数据与方法→实证结果→结论与建议"
    },
    // ... 至少5个选题
  ]
}

提示词构建关键点 ​

item_schema的应用:

请生成至少5个复刻学术选题,每个选题必须包含以下4个部分:

1. title: 选题名称(清晰、具体、学术性强)
2. innovation: 创新性与新颖性说明(如何在模仿中创新)
3. theory_method: 模仿的理论/方法(具体说明复刻了参考文献的哪些理论或方法)
4. logic_structure: 逻辑结构(论文的章节结构或研究流程)

请严格按照以下JSON格式输出:
{
  "topics": [
    {
      "title": "...",
      "innovation": "...",
      "theory_method": "...",
      "logic_structure": "..."
    }
  ]
}

数组字段的处理 ​

前端渲染:

jsx
// 遍历topics数组
data.topics.map((topic, index) => (
  <div key={index} className="topic-card">
    <h3>{index + 1}. {topic.title}</h3>
    <div className="topic-section">
      <h4>创新性与新颖性</h4>
      <p>{topic.innovation}</p>
    </div>
    <div className="topic-section">
      <h4>模仿的理论/方法</h4>
      <p>{topic.theory_method}</p>
    </div>
    <div className="topic-section">
      <h4>逻辑结构</h4>
      <p>{topic.logic_structure}</p>
    </div>
  </div>
))

六、配置系统的设计优势 ​

1. 配置驱动,灵活扩展 ​

无需修改代码:

  • 新增AI功能只需在数据库插入一条配置记录
  • 修改功能只需更新数据库记录
  • 启用/禁用功能只需修改 status 字段

示例:

sql
-- 新增一个AI功能
INSERT INTO ai_functions (
    function_key, function_name, description,
    prompt_template, input_fields, output_fields,
    output_format, model_config, status, sort
) VALUES (
    'ai_paper_outline',
    '智能论文大纲',
    '根据选题生成论文大纲',
    '...',  -- 提示词模板
    '[...]'::jsonb,  -- 输入字段配置
    '[...]'::jsonb,  -- 输出字段配置
    'json_object',
    '{...}'::jsonb,  -- 模型配置
    1,
    20
);

扩展性:

  • 通过 extra 字段添加任意自定义配置
  • 不需要修改数据库结构
  • 向后兼容,旧功能不受影响

2. 类型安全的验证体系 ​

编译时类型检查:

go
type AIFieldConfig struct {
    FieldKey     string                 // ✅ 编译时检查
    FieldName    string                 // ✅ 编译时检查
    FieldType    string                 // ✅ 编译时检查
    IsRequired   bool                   // ✅ 编译时检查
    Validation   AIFieldValidation      // ✅ 编译时检查
    Extra        map[string]interface{} // ⚠️ 运行时检查
}

运行时验证:

  • 输入数据类型验证
  • 输入数据值验证(长度、范围、格式)
  • 详细的错误消息

防御性编程:

go
// 安全地访问 extra 字段
previewVisible := false
if extra != nil {
    if v, ok := extra["preview_visible"].(bool); ok {
        previewVisible = v
    }
}

3. 分离的模板系统 ​

两级模板:

模板用途受众内容
prompt_template发送给AIAI模型完整详细,包含指令、格式、示例
user_prompt_template展示给用户终端用户简洁易懂,引导用户输入

示例对比:

prompt_template (约3000字):

bash
我是一名在读的博士生,我的专业是{{subject}},我正在开展学术期刊论文选题工作,
我初步拟定的选题名称是:{{title}}。

请你充当我的学术指导教师,从以下几个方面对该选题进行全面评估:

**评分维度(百分制):**
1. 综合评分:对选题的整体学术价值进行综合评价
2. 可行性:评估研究的可实施性(数据获取、研究方法、时间安排等)
... (详细的评估要求和格式说明)

user_prompt_template (约30字):

bash
我的专业是{{subject}},请为我评估以下选题的学术价值与研究潜力:{{title}}

优势:

  • 用户界面简洁友好
  • AI收到完整详细的指令
  • 两者独立修改,互不影响

4. 多层输出控制 ​

三层控制机制:

第一层: output_format 控制整体格式

go
switch outputFormat {
case "json_array":
    return []interface{}{...}
case "json_object":
    return map[string]interface{}{...}
case "plain_text":
    return "..."
case "markdown":
    return "# ..."
}

第二层: output_fields 定义字段结构

json
[
  {
    "field_key": "title",
    "field_type": "string"
  },
  {
    "field_key": "topics",
    "field_type": "array"
  }
]

第三层: extra.preview_visible 控制预览字段

json
{
  "field_key": "overall_score",
  "extra": {
    "preview_visible": true
  }
}

协同工作:

bash
完整数据 (12个字段)
  ↓
[output_fields 定义结构]
  ↓
预览模式判断
  ↓
[extra.preview_visible 过滤]
  ↓
预览数据 (7个字段)

5. 缓存优化性能 ​

缓存策略:

  • 缓存时长: 10分钟
  • 缓存键: function_key
  • 并发安全: sync.RWMutex

性能对比:

场景无缓存有缓存
首次请求查询数据库 (~5ms)查询数据库 (~5ms)
10分钟内重复请求查询数据库 (~5ms)读取缓存 (~0.01ms)
高并发场景 (100 QPS)500ms总耗时1ms总耗时

缓存命中率估算:

  • 假设每个功能平均每分钟被调用10次
  • 10分钟内缓存命中率: 99.9%
  • 极大减少数据库压力

缓存刷新:

go
// 手动刷新缓存(管理后台调用)
service.RefreshAIConfigCache()

// 或者修改配置后自动刷新
func UpdateAIFunction(function *model.AIFunction) error {
    err := function.Update(nil)
    if err == nil {
        // 清理缓存,下次请求时重新加载
        service.RefreshAIConfigCache()
    }
    return err
}

6. 完整的日志和历史 ​

两层日志系统:

ai_call_logs (调用日志):

  • 记录每次AI调用的技术细节
  • 用于调试、监控、计费
  • 包含完整的请求和响应

ai_function_usage (用户历史):

  • 记录用户的使用记录
  • 用于历史查询、预览管理、去重检查
  • 包含预览状态和付费信息

日志用途:

用途ai_call_logsai_function_usage
技术调试✅❌
Token计费✅❌
用户历史❌✅
预览管理❌✅
去重检查❌✅
性能监控✅❌

查询示例:

技术人员查询某功能的平均Token消耗:

sql
SELECT
    function_key,
    AVG(tokens_used) as avg_tokens,
    AVG(call_duration) as avg_duration_ms
FROM ai_call_logs
WHERE function_key = 'ai_topic_evaluation'
    AND status = 'success'
    AND created_at >= NOW() - INTERVAL '7 days'
GROUP BY function_key;

用户查询自己的使用历史:

sql
SELECT
    id,
    function_key,
    created_at,
    view_status
FROM ai_function_usage
WHERE uuid = 'user-uuid-xxx'
ORDER BY created_at DESC
LIMIT 20;

七、关键设计模式 ​

1. 策略模式(输出格式处理) ​

问题: 不同的AI功能需要不同的输出格式处理逻辑

解决方案: 根据 output_format 选择不同的处理策略

实现:

go
func processAIOutputData(rawResponse string,
    outputFields []model.AIFieldConfig,
    outputFormat string) (interface{}, error) {

    cleanResponse := strings.TrimSpace(rawResponse)

    switch outputFormat {
    case "json_array":
        return processJSONArray(cleanResponse)
    case "json_object":
        return processJSONObject(cleanResponse, outputFields)
    case "plain_text":
        return processPlainText(cleanResponse)
    case "markdown":
        return processMarkdown(cleanResponse)
    default:
        return processJSONObject(cleanResponse, outputFields)
    }
}

优势:

  • 新增格式类型只需添加新的 case
  • 各策略独立实现,互不影响
  • 易于测试和维护

UML类图:

                    ┌───────────────────────┐
                    │  AIOutputProcessor    │
                    ├───────────────────────┤
                    │ +Process(format)      │
                    └───────────┬───────────┘
                                │
                 ┌──────────────┼──────────────┐
                 │              │              │
        ┌────────▼────────┐    │    ┌────────▼────────┐
        │ JSONArrayHandler│    │    │JSONObjectHandler│
        ├─────────────────┤    │    ├─────────────────┤
        │ +Process()      │    │    │ +Process()      │
        └─────────────────┘    │    └─────────────────┘
                               │
                  ┌────────────▼──────────┐
                  │  PlainTextHandler     │
                  │  MarkdownHandler      │
                  └───────────────────────┘

2. 模板方法模式(CallAI流程) ​

问题: AI调用有标准流程,但某些功能需要特殊处理

解决方案: 定义标准流程框架,允许子步骤定制

标准流程(CallAI):

go
func CallAI(ctx context.Context, req *form.AICallReq) (*response.AICallResp, error) {
    // 步骤1: 准备阶段(标准)
    function := GetAIFunctionByKey(req.FunctionKey)
    validateAIInputData(req.InputData, function.InputFields)
    prompt := buildAIPrompt(function.PromptTemplate, req.InputData)

    // 步骤2: 调用阶段(标准)
    openaiResp := utils.CallOpenAI(ctx, openaiReq)

    // 步骤3: 处理阶段(可定制)
    outputData := processAIOutputData(rawResponse, function.OutputFields, function.OutputFormat)

    // 步骤4: 特殊处理(扩展点)
    if req.FunctionKey == "ai_journal_submission" {
        outputData = enrichJournalDataWithDetails(outputData)
    }

    // 步骤5: 记录日志(标准)
    callLog.Create()
    history := CreateAIFunctionUsage(...)

    return resp, nil
}

扩展点:

  • processAIOutputData: 可根据 output_format 定制
  • 特殊处理: 可针对特定功能插入定制逻辑

优势:

  • 大部分功能走标准流程,无需重复代码
  • 特殊功能可以插入定制逻辑
  • 保持代码的一致性和可维护性

UML序列图:

3. 装饰器模式(数据增强) ​

问题: 期刊推荐功能需要在AI数据基础上添加详情信息

解决方案: 不修改原始数据结构,装饰性地添加信息

实现:

go
// AI原始数据(核心对象)
type AIJournalData struct {
    JournalName     string
    MatchScore      float64
    RecommendScore  float64
    RecommendReason string
}

// 装饰器函数
func enrichJournalDataWithDetails(aiData interface{}) (interface{}, error) {
    // 1. 解析AI数据
    journals := parseAIJournals(aiData)

    // 2. 获取期刊详情(装饰)
    details := fetchJournalDetails(journals)

    // 3. 合并数据(返回装饰后的对象)
    return mergeAIDataWithDetails(journals, details), nil
}

// 装饰后的数据(增强对象)
type EnrichedJournalData struct {
    // AI数据(原始)
    JournalName     string
    MatchScore      float64
    RecommendScore  float64
    RecommendReason string

    // 期刊详情(装饰)
    JournalDetail   JournalInfo
}

关键特点:

  • 不修改AI返回的原始数据
  • 通过包装方式添加新信息
  • 原始数据和装饰数据可以独立演化

优势:

  • 职责分离: AI负责推荐,数据库负责详情
  • 灵活组合: 可以多次装饰(如再添加用户评论)
  • 易于测试: AI数据和详情数据可以分别测试

装饰链示例:

AI原始数据
  ↓
enrichJournalDataWithDetails (装饰器1)
  ↓
enrichWithUserReviews (装饰器2,可选)
  ↓
enrichWithSubmissionStats (装饰器3,可选)
  ↓
最终数据

4. 工厂模式(模型配置) ​

问题: 不同的AI功能可能使用不同的模型配置

解决方案: 使用工厂方法根据功能配置动态创建模型配置对象

实现:

go
// 抽象产品
type ModelConfig interface {
    GetModel() string
    GetTemperature() float32
    GetMaxTokens() int
}

// 具体产品
type AIModelConfig struct {
    Model            string
    Temperature      float32
    MaxTokens        int
    TopP             float32
    FrequencyPenalty float32
    PresencePenalty  float32
    Timeout          int
    RetryTimes       int
}

// 工厂方法
func getAIEffectiveModelConfig(function *model.AIFunction) (*model.AIModelConfig, error) {
    // 创建默认配置
    baseConfig := &model.AIModelConfig{
        Model:       "gpt-3.5-turbo",
        Temperature: 0.7,
        MaxTokens:   2000,
        TopP:        1.0,
        Timeout:     60,
        RetryTimes:  3,
    }

    // 根据功能配置覆盖
    if function.ModelConfig.Model != "" {
        baseConfig.Model = function.ModelConfig.Model
    }
    if function.ModelConfig.Temperature > 0 {
        baseConfig.Temperature = function.ModelConfig.Temperature
    }
    if function.ModelConfig.MaxTokens > 0 {
        baseConfig.MaxTokens = function.ModelConfig.MaxTokens
    }

    return baseConfig, nil
}

使用场景:

场景1: 使用默认配置

go
// function.ModelConfig 为空
config := getAIEffectiveModelConfig(function)
// config = {Model: "gpt-3.5-turbo", Temperature: 0.7, ...}

场景2: 使用GPT-4

go
// function.ModelConfig = {Model: "gpt-4"}
config := getAIEffectiveModelConfig(function)
// config = {Model: "gpt-4", Temperature: 0.7, ...}

场景3: 高温度创造性任务

go
// function.ModelConfig = {Model: "gpt-4", Temperature: 1.2}
config := getAIEffectiveModelConfig(function)
// config = {Model: "gpt-4", Temperature: 1.2, ...}

优势:

  • 统一创建逻辑,避免重复代码
  • 易于添加新的配置选项
  • 可以实现配置的继承和覆盖

5. 观察者模式(缓存更新) ​

问题: 配置更新后需要通知缓存清理

解决方案: 当配置变更时,通知所有观察者(缓存管理器)

实现思路:

go
// 主题(配置管理)
type AIFunctionManager struct {
    observers []CacheObserver
}

func (m *AIFunctionManager) UpdateFunction(function *model.AIFunction) error {
    err := function.Update(nil)
    if err == nil {
        // 通知所有观察者
        m.notifyObservers(function.FunctionKey)
    }
    return err
}

// 观察者接口
type CacheObserver interface {
    OnConfigChanged(functionKey string)
}

// 具体观察者(缓存管理器)
type AIConfigCacheManager struct {}

func (c *AIConfigCacheManager) OnConfigChanged(functionKey string) {
    // 清理特定功能的缓存
    aiConfigCacheMux.Lock()
    delete(aiConfigCache, functionKey)
    aiConfigCacheMux.Unlock()
}

优势:

  • 配置管理和缓存管理解耦
  • 可以添加多个观察者(如日志记录器、指标收集器)
  • 易于扩展

总结 ​

AI功能配置系统通过以下关键设计实现了高度的灵活性和可扩展性:

  1. 配置驱动: 所有功能通过数据库配置,无需修改代码
  2. 分层验证: 三层验证体系确保数据安全和类型正确
  3. 模板系统: 分离的提示词模板满足不同受众需求
  4. 预览控制: 灵活的字段级预览机制支持付费模式
  5. 缓存优化: 智能缓存大幅提升性能
  6. 设计模式: 运用多种设计模式保持代码的清晰和可维护性

文档版本: v1.0最后更新: 2025-10-22维护者: X-Wukong研发团队