使用 Flask 构建基于 Dify 的企业资金投向与客户分类评估系统

发布于:2025-07-04 ⋅ 阅读:(19) ⋅ 点赞:(0)


前言

在金融、银行等对公业务中,准确判断企业的经营范围与其资金投向、客户分类是否匹配至关重要。本文将介绍如何使用 Python 和 Dify(一个强大的 LLM 应用开发平台)构建一个自动化的评估服务。

我们将通过 Flask 搭建一个 Web 接口,接收用户的输入信息,调用 Dify 提供的 API 来分析企业的资金投向和客户分类是否合理,并返回结构化结果。


一、🧩 技术栈

  • Python 3.10+
  • Flask:用于构建 Web 后端服务
  • Requests:用于调用 Dify API
  • JSON:处理数据解析和格式化输出
  • Dify AI 平台:提供大模型推理能力
  • 前端模板引擎 Jinja2:渲染评估表单页面

二、📦 项目结构概览

main_by_dify_v2.py          # 主程序文件
templates/
└── evaluation_form.html   # 表单页面模板

三、 🔧 核心功能模块说明

1 配置参数

API_URL = "http://localhost/v1/chat-messages"
API_KEY = "app-***"  # 替换为你的API密钥

[API_URL]是 Dify 提供的接口地址。
[API_KEY]是你在 Dify 平台上创建应用时获取的访问令牌。

2 请求封装函数

[ask_dify]该函数负责向 Dify 发送请求并处理响应:

def ask_dify(question: str, user_id: str = "user_123") -> Dict[str, Any]:
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json"
    }
    payload = {
        "inputs": {},
        "query": question,
        "response_mode": "blocking",
        "user": user_id
    }

    response = requests.post(API_URL, json=payload, headers=headers, timeout=30)
    response.raise_for_status()
    data = response.json()

    if 'answer' not in data:
        raise ValueError("API response missing 'answer' field")

    data_dict = json.loads(data['answer'])
    required_fields = ["funding_direction", "customer_category"]
    for field in required_fields:
        if field not in data_dict:
            raise KeyError(f"API response missing required field: {field}")

    return {
        'status': 'success',
        'message': '评估完成',
        'funding_evaluation': data_dict["funding_direction"],
        'category_evaluation': data_dict["customer_category"]
    }

✅ 功能说明:

  • 使用 requests 发起 POST 请求
  • 设置超时时间防止挂起
  • 对响应进行 JSON 解析
  • 校验关键字段是否存在

3 Prompt 构造函数

generate_prompt

构造符合要求的提示词内容,确保大模型能返回结构化结果:

def generate_prompt(customer_info: str, funding_direction: str, customer_category: str) -> str:
    return f"""
    #角色
    你是一名对公业务专家
    #核心任务
    基于经营范围,分析客户的资金投向与行内类别是否正确。
    不用输出think过程

    经营范围为: {json.dumps(customer_info)}
    资金投向为: {json.dumps(funding_direction)}
    行内类别为: {json.dumps(customer_category)}
    #输出规范

    {{
        "funding_direction": "分析资金投向是否正确,如果不正确,请给出分析原因", 
        "customer_category": "分析行内类别是否正确,如果不正确,请给出分析原因"
    }}
    """

4 Flask 路由定义

🏠 首页路由 /
@app.route('/')
def index() -> str:
    return render_template('evaluation_form.html')

渲染 HTML 页面,提供用户输入表单。

📥 评估接口 /evaluate
@app.route('/evaluate', methods=['POST'])
def evaluate_route() -> Any:
    try:
        data = request.get_json()
        prompt = generate_prompt(
            data['customer_info'],
            data['funding_direction'],
            data['customer_category']
        )
        result = ask_dify(prompt)
        return jsonify(result)

    except ValueError as e:
        return jsonify({'status': 'error', 'message': str(e)}), 400
    except Exception as e:
        return jsonify({'status': 'error', 'message': f"评估失败: {str(e)}"}), 500

处理用户提交的数据,生成提示词并调用 Dify 接口,返回结构化评估结果。

🧪 健康检查 /health
@app.route('/health', methods=['GET'])
def health_check() -> Dict[str, str]:
    return {"status": "healthy"}

用于监控服务运行状态。


🛠️ 运行方式

python main_by_dify_v2.py --port 5000 --host 0.0.0.0 --debug

启动后访问 http://localhost:5000 即可打开评估表单页面。


🧪 示例请求体(JSON)

{
  "customer_info": "计算机软件开发与销售",
  "funding_direction": "投资人工智能研发",
  "customer_category": "科技型企业"
}

💡 可扩展性建议

  • 将代码拆分为多个模块(如 routes.py, services.py, utils.py
  • 添加单元测试(推荐使用 pytest
  • 支持异步响应模式(Dify 支持 streaming 和 async 模式)
  • 使用 gunicorn + nginx 部署生产环境
  • 引入 Redis 缓存历史评估结果

📝 总结

通过本文我们实现了一个基于 Flask 和 Dify 的企业资金投向与客户分类评估系统。它具备以下特点:

  • 接口清晰、响应结构化
  • 支持 JSON 输入输出
  • 易于扩展和维护
  • 利用大模型能力自动化评估复杂业务逻辑

未来你可以根据业务需求进一步扩展此系统,例如支持多语言、增强安全机制、引入审计日志等。


📌 完整代码
main_by_dify_v2

import requests
from flask import Flask, render_template, request, jsonify
import logging
import json
from typing import Dict, Any, Optional
import argparse

# 配置常量
API_URL = "http://0.0.0.0/v1/chat-messages"
API_KEY = "app-***"  # 替换为你的API密钥

# 初始化 Flask 应用
app = Flask(__name__)
app.logger.setLevel(logging.INFO)


def ask_dify(question: str, user_id: str = "user_123") -> Dict[str, Any]:
    """
    向Dify API发送查询并处理响应

    Args:
        question: 要查询的问题
        user_id: 用户标识符

    Returns:
        包含评估结果的字典

    Raises:
        Exception: 当API请求失败或响应格式不正确时
    """
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json"
    }
    payload = {
        "inputs": {},
        "query": question,
        "response_mode": "blocking",
        "user": user_id
    }

    try:
        response = requests.post(API_URL, json=payload, headers=headers, timeout=1000)
        response.raise_for_status()  # 检查HTTP错误

        data = response.json()
        app.logger.debug("Received API response: %s", data)

        if 'answer' not in data:
            raise ValueError("API response missing 'answer' field")

        data_dict = json.loads(data['answer'])

        required_fields = ["funding_direction", "customer_category"]
        for field in required_fields:
            if field not in data_dict:
                raise KeyError(f"API response missing required field: {field}")

        return {
            'status': 'success',
            'message': '评估完成',
            'funding_evaluation': data_dict["funding_direction"],
            'category_evaluation': data_dict["customer_category"]
        }

    except requests.exceptions.RequestException as e:
        app.logger.error("API request failed: %s", str(e))
        raise Exception(f"API请求失败: {str(e)}")
    except json.JSONDecodeError as e:
        app.logger.error("Failed to parse API response: %s", str(e))
        raise Exception("API响应解析失败")


def generate_prompt(customer_info: str, funding_direction: str, customer_category: str) -> str:
    """
    生成用于评估的提示文本

    Args:
        customer_info: 客户经营范围信息
        funding_direction: 资金投向
        customer_category: 客户分类

    Returns:
        格式化后的提示文本
    """
    return f"""
    #角色
    你是一名对公业务专家
    #核心任务
    基于经营范围,分析客户的资金投向与行内类别是否正确。
    不用输出think过程

    经营范围为: {customer_info}
    资金投向为: {funding_direction}
    行内类别为: {customer_category}
    #输出规范

    {{
        "funding_direction": "分析资金投向是否正确,如果不正确,请给出分析原因", 
        "customer_category": "分析行内类别是否正确,如果不正确,请给出分析原因"
    }}
    """


@app.route('/')
def index() -> str:
    """显示评估表单页面"""
    return render_template('evaluation_form.html')


@app.route('/evaluate', methods=['POST'])
def evaluate_route() -> Any:
    """
    处理评估请求的后端接口

    Returns:
        JSON响应: 包含评估结果或错误信息
    """
    try:
        data = request.get_json()
        if not data:
            raise ValueError("请求体为空或不是有效的JSON")

        required_fields = ['customer_info', 'funding_direction', 'customer_category']
        for field in required_fields:
            if field not in data:
                raise ValueError(f"缺少必要字段: {field}")

        app.logger.info("Processing evaluation request for customer: %s",
                        data.get('customer_info', 'unknown'))

        prompt = generate_prompt(
            data['customer_info'],
            data['funding_direction'],
            data['customer_category']
        )

        result = ask_dify(prompt)
        return jsonify(result)

    except ValueError as e:
        app.logger.warning("Invalid request: %s", str(e))
        return jsonify({
            'status': 'error',
            'message': str(e)
        }), 400
    except Exception as e:
        app.logger.error("Evaluation failed: %s", str(e), exc_info=True)
        return jsonify({
            'status': 'error',
            'message': f"评估失败: {str(e)}"
        }), 500


@app.route('/health', methods=['GET'])
def health_check() -> Dict[str, str]:
    """健康检查接口"""
    return {"status": "healthy"}


def parse_args() -> argparse.Namespace:
    """解析命令行参数"""
    parser = argparse.ArgumentParser(description="Run the evaluation service")
    parser.add_argument('--port', type=int, default=5006, help="Port to run the server on")
    parser.add_argument('--host', default='0.0.0.0', help="Host to bind the server to")
    parser.add_argument('--debug', action='store_true', help="Run in debug mode")
    return parser.parse_args()


if __name__ == '__main__':
    args = parse_args()
    app.run(host=args.host, port=args.port, debug=args.debug)

evaluation_form.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户价值评估</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

    <style>
        * {
            box-sizing: border-box;
            font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
        }
        body {
            background: linear-gradient(135deg, #f5f7fa 0%, #e4edf9 100%);
            min-height: 100vh;
            margin: 0;
            padding: 20px;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .evaluation-card {
            width: 100%;
            max-width: 900px;
            background: white;
            border-radius: 16px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
            overflow: hidden;
        }
        .card-header {
            background: linear-gradient(to right, #3498db, #2c3e50);
            color: white;
            padding: 25px 40px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
        }
        .card-title {
            margin: 0;
            font-size: 28px;
            font-weight: 600;
            letter-spacing: 0.5px;
        }
        .card-subtitle {
            margin: 8px 0 0;
            opacity: 0.9;
            font-weight: 400;
            font-size: 16px;
        }
        .card-body {
            padding: 40px;
        }
        .form-grid {
            display: grid;
            grid-template-columns: 150px 1fr;
            gap: 25px;
            align-items: center;
        }
        .form-label {
            font-weight: 600;
            font-size: 16px;
            color: #2c3e50;
            text-align: right;
            padding-right: 10px;
        }
        .form-control-group {
            display: flex;
            flex-direction: column;
            gap: 5px;
        }
        .text-input, .textarea-input {
            width: 100%;
            padding: 14px;
            border: 1px solid #dce4ec;
            border-radius: 8px;
            font-size: 15px;
            transition: all 0.3s ease;
        }
        .text-input:focus, .textarea-input:focus {
            border-color: #3498db;
            outline: none;
            box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
        }
        .text-input::placeholder, .textarea-input::placeholder {
            color: #a0aec0;
        }
        .textarea-input {
            height: 120px;
            resize: vertical;
            line-height: 1.5;
        }
        .evaluation-result {
            margin-top: 35px;
            display: none;
        }
        .result-container {
            background: #f8f9fa;
            border-left: 4px solid #3498db;
            border-radius: 0 6px 6px 0;
            padding: 20px;
            margin-top: 10px;
        }
        .result-title {
            display: block;
            font-weight: 600;
            margin-bottom: 8px;
            color: #2c3e50;
        }

        /* 添加CSS样式 */
.result-container {
    width: 100%; /* 宽度自适应容器 */
    max-width: 600px; /* 建议在银行系统设置固定宽度 */
    max-height: 300px; /* 控制最大高度 */
    overflow-y: auto; /* 垂直滚动条 */
    border: 1px solid #e0e0e0; /* 银行系统常见的浅灰色边框 */
    border-radius: 4px;
    background-color: #f8f9fa; /* 银行常用的浅灰背景 */
    margin-top: 8px;
    padding: 12px;
    box-shadow: inset 0 1px 2px rgba(0,0,0,0.05); /* 内阴影增强可读性 */
}

.result-content {
    white-space: pre-wrap; /* 保留空白同时自动换行 */
    word-wrap: break-word; /* 强制长单词换行 */
    word-break: break-word; /* 支持中文换行 */
    font-family: 'SimSun', 'Microsoft YaHei', sans-serif; /* 银行常用字体 */
    font-size: 14px;
    line-height: 1.6;
    color: #333; /* 银行系统常用深灰文字 */
}
        .card-footer {
            padding: 0 40px 30px;
            display: flex;
            justify-content: center;
        }
        .evaluate-button {
            background: linear-gradient(to right, #3498db, #2c3e50);
            color: white;
            border: none;
            padding: 14px 50px;
            font-size: 18px;
            font-weight: 500;
            border-radius: 8px;
            cursor: pointer;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        .evaluate-button:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
        }
        .evaluate-button:active {
            transform: translateY(1px);
        }
        .evaluate-button i {
            font-size: 20px;
        }
        .feedback-message {
            text-align: center;
            margin-top: 20px;
            padding: 15px;
            border-radius: 8px;
            font-size: 16px;
        }
        .success { background-color: #dff0d8; color: #3c763d; border: 1px solid #d6e9c6; }
        .error { background-color: #f2dede; color: #a94442; border: 1px solid #ebccd1; }
        .processing { background-color: #d9edf7; color: #31708f; border: 1px solid #bce8f1; }
    </style>
</head>
<body>
    <div class="evaluation-card">
        <div class="card-header">
            <h1 class="card-title">客户评估助手</h1>
            <p class="card-subtitle">基于大模型的智能分析与风险评估</p>
        </div>

        <div class="card-body">
            <form class="form-grid" id="evaluationForm">
                <!-- 第1行:客户名称 -->
                <label class="form-label" for="customerName">客户名称</label>
                <div class="form-control-group">
                    <input type="text" class="text-input" id="customerName" placeholder="请输入客户名称">
                </div>

                <!-- 第2行:客户资料 -->
                <label class="form-label" for="customerInfo">客户资料</label>
                <div class="form-control-group">
                    <textarea class="textarea-input" id="customerInfo" placeholder="请输入客户资料"></textarea>
                </div>

                <!-- 第3行:资金投向 -->
                <label class="form-label" for="fundingDirection">资金投向</label>
                <div class="form-control-group">
                    <input type="text" class="text-input" id="fundingDirection" placeholder="请输入资金投向">

                    <!-- 资金投向评估结果 -->
                    <div class="evaluation-result" id="fundingResult">
                        <span class="result-title">大模型评估结果:</span>
                        <div class="result-container">
                          <!--  <input type="textarea" class="text-input" id="fundingEvaluation" readonly>-->
                             <div class="result-content" id="fundingEvaluation"></div>
                        </div>
                    </div>
                </div>

                <!-- 第4行:客户类别 -->
                <label class="form-label" for="customerCategory">客户类别</label>
                <div class="form-control-group">
                    <input type="text" class="text-input" id="customerCategory" placeholder="请输入客户类别">

                    <!-- 客户类别评估结果 -->
                    <div class="evaluation-result" id="categoryResult">
                        <span class="result-title">大模型评估结果:</span>
                        <div class="result-container">
                         <!--   <input type="text" class="text-input" id="categoryEvaluation" readonly>-->
                               <div class="result-content" id="categoryEvaluation"></div>
                        </div>
                    </div>
                </div>

                <!-- 反馈消息 -->
                <div class="feedback-message" id="formFeedback"></div>
            </form>
        </div>

        <div class="card-footer">
            <button class="evaluate-button" id="evaluateBtn">
                <i>📊</i> 开始评估
            </button>
        </div>
    </div>

    <script>
        $(document).ready(function() {
            $('#evaluateBtn').click(function() {
                // 收集表单数据
                const formData = {
                    customer_name: $('#customerName').val().trim(),
                    customer_info: $('#customerInfo').val().trim(),
                    funding_direction: $('#fundingDirection').val().trim(),
                    customer_category: $('#customerCategory').val().trim()
                };

                // 表单验证
                const feedback = $('#formFeedback');
                let isValid = true;

                feedback.removeClass().text('');

                // 清除之前的错误标记
                $('.text-input, .textarea-input').css('border-color', '#dce4ec');

                // 检查客户名称
                if (!formData.customer_name) {
                    $('#customerName').css('border-color', '#e74c3c');
                    showFeedback('请填写客户名称', 'error');
                    isValid = false;
                }

                // 检查客户资料
                if (!formData.customer_info) {
                    $('#customerInfo').css('border-color', '#e74c3c');
                    if (!isValid) showFeedback('请填写所有必填项', 'error');
                    else showFeedback('请填写客户资料', 'error');
                    isValid = false;
                }

                // 检查资金投向
                if (!formData.funding_direction) {
                    $('#fundingDirection').css('border-color', '#e74c3c');
                    if (!isValid) showFeedback('请填写所有必填项', 'error');
                    isValid = false;
                }

                // 检查客户类别
                if (!formData.customer_category) {
                    $('#customerCategory').css('border-color', '#e74c3c');
                    if (!isValid) showFeedback('请填写所有必填项', 'error');
                    isValid = false;
                }

                if (!isValid) return;

                // 显示处理中状态
                showFeedback('正在使用大模型分析中,请稍候...', 'processing');

                // 发送评估请求
                $.ajax({
                    type: "POST",
                    url: "/evaluate",
                    contentType: "application/json",
                    data: JSON.stringify(formData),
                    success: function(response) {
                        // 显示评估结果
                        $('#fundingEvaluation').val(response.funding_evaluation);
                        document.getElementById('fundingEvaluation').innerText = response.funding_evaluation;
                        $('#categoryEvaluation').val(response.category_evaluation);
                        document.getElementById('categoryEvaluation').innerText = response.category_evaluation;

                        // 显示结果区域
                        $('#fundingResult').show();
                        $('#categoryResult').show();

                        // 成功反馈
                        showFeedback(response.message + ' ✅', 'success');
                    },
                    error: function(xhr) {
                        const errorMsg = xhr.responseJSON?.error || '评估请求失败,请重试';
                        showFeedback('错误: ' + errorMsg, 'error');
                    }
                });
            });

            // 输入框改变时移除错误标记
            $('.text-input, .textarea-input').on('input', function() {
                $(this).css('border-color', '#dce4ec');
            });

            function showFeedback(message, type) {
                const feedback = $('#formFeedback');
                feedback.text(message).removeClass().addClass('feedback-message ' + type);
            }
        });
    </script>
</body>
</html>

网站公告

今日签到

点亮在社区的每一天
去签到