feat(create_mcp): 添加Dify API错误处理和文件字段类型支持

- 引入DifyAPIError异常类,提供详细的API错误信息
- 实现对file和file-list字段类型的区分处理
- 添加网络请求异常和其他未知错误的捕获机制
- 改进文件上传逻辑,支持单文件和多文件类型
- 优化错误信息返回,提供更友好的用户提示

fix(create_mcp_utils): 修复文件参数处理逻辑

- 添加is_list字段标识多文件类型
- 改进文件上传失败的过滤机制
- 修复文件字段变量查找逻辑,使用映射提高查找效率

chore(config): 更新项目版本号至0.1.2

- 将PKG-INFO和pyproject.toml中的版本从0.1.1更新至0.1.2
- 更新默认配置中的app_sks值
This commit is contained in:
2026-01-09 15:25:45 +08:00
parent 9ea81fe3e7
commit 1a6ff54199
15 changed files with 490 additions and 62 deletions

View File

@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: lzwcai-demp-tool-server-dify-to-mcp-test
Version: 0.1.1
Version: 0.1.2
Summary: 这是一个Dify to MCP的集成工具通过Dify的API接口将Dify的模型部署到MCP平台并进行推理。
Requires-Python: >=3.10
Requires-Dist: httpx>=0.28.1

View File

@@ -19,7 +19,7 @@ def setup_mock_arguments():
# 默认配置
default_config = {
"base_url": "http://192.168.2.236:3001/v1",
"app_sks": ["app-Oo0QRJismgQADRSt8Bj0RXWB"],
"app_sks": ["app-IfJayK9uu5oTo54Rpr2AS7wl"],
"mode_type": "workflow",
"transport": "stdio"
}

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "lzwcai-demp-tool-server-dify-to-mcp-test"
version = "0.1.1"
version = "0.1.2"
description = "这是一个Dify to MCP的集成工具通过Dify的API接口将Dify的模型部署到MCP平台并进行推理。"
requires-python = ">=3.10"
dependencies = [

View File

@@ -17,6 +17,7 @@ from src.difyTaskCall.task_instance import TaskInstance
from src.utils.dify_workflow_schema import process_user_input_form, extract_file_fields
from src.create_mcp_utils import process_file_arguments
from src.utils.logger_config import get_logger
from src.workflow.workflow_server import DifyAPIError
# 使用统一的日志配置
logger = get_logger(__name__)
@@ -147,6 +148,7 @@ async def handle_call_tool(
processed_arguments = process_file_arguments(arguments, current_tool_file_fields, dify_api, name)
logger.info(f"工具 {name} 处理后的 arguments: {processed_arguments}")
try:
responses = dify_api.chat_message(
tool_sk,
inputs=processed_arguments,
@@ -163,13 +165,31 @@ async def handle_call_tool(
mcp_out = []
if outputs:
for _, v in outputs.items():
mcp_out.append(types.TextContent(type="text", text=v))
mcp_out.append(types.TextContent(type="text", text=str(v)))
else:
# 如果没有获取到 outputs返回错误信息
logger.warning(f"工具 {name} 未获取到 workflow_finished 事件或 outputs 为空")
mcp_out.append(types.TextContent(type="text", text="工具执行完成,但未返回输出结果"))
return mcp_out
except DifyAPIError as e:
# 捕获 Dify API 错误,直接返回给用户
logger.error(f"工具 {name} 调用 Dify API 失败: {e}")
error_message = f"API调用失败: {e.message}"
return [types.TextContent(type="text", text=error_message)]
except requests.exceptions.RequestException as e:
# 捕获网络请求错误
logger.error(f"工具 {name} 网络请求失败: {e}")
error_message = f"网络请求失败: {str(e)}"
return [types.TextContent(type="text", text=error_message)]
except Exception as e:
# 捕获其他未知错误
logger.error(f"工具 {name} 执行时发生未知错误: {e}", exc_info=True)
error_message = f"工具执行失败: {str(e)}"
return [types.TextContent(type="text", text=error_message)]
else:
raise ValueError(f"Unknown tool: {name}")

View File

@@ -44,7 +44,7 @@ def process_file_arguments(arguments, current_tool_file_fields, dify_api, tool_n
current_tool_file_fields (list): 当前工具的文件字段信息列表
数据结构: [{'variable': 'txt', 'label': '输入', 'required': True, 'max_length': 48,
'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['local_file', 'remote_url'],
'allowed_file_extensions': []}]
'allowed_file_extensions': [], 'is_list': False}]
dify_api: Dify API实例包含file_parameter_pretreatment方法
tool_name (str): 工具名称,用于日志记录
@@ -58,8 +58,9 @@ def process_file_arguments(arguments, current_tool_file_fields, dify_api, tool_n
logger.info(f"工具 {tool_name}: 无需处理文件字段 (arguments={bool(arguments)}, file_fields={len(current_tool_file_fields) if current_tool_file_fields else 0})")
return arguments
# 创建文件字段变量名的集合,用于快速查找
file_field_variables = {field['variable'] for field in current_tool_file_fields}
# 创建文件字段变量名到字段信息的映射,用于快速查找
file_field_map = {field['variable']: field for field in current_tool_file_fields}
file_field_variables = set(file_field_map.keys())
logger.info(f"工具 {tool_name} 的文件字段变量名: {file_field_variables}")
# 创建arguments的副本避免修改原始数据
@@ -101,12 +102,20 @@ def process_file_arguments(arguments, current_tool_file_fields, dify_api, tool_n
logger.error(f"工具 {tool_name}: 文件预处理失败,未返回有效结果")
continue
# 取第一个处理后的文件对象
processed_files_item = processed_files[0]
logger.info(f"工具 {tool_name}: 文件预处理完成upload_file_id: {processed_files_item.get('upload_file_id', 'N/A')}")
# 过滤掉上传失败的文件
valid_files = [f for f in processed_files if f.get('upload_file_id') and not f.get('upload_error')]
if not valid_files:
logger.error(f"工具 {tool_name}: 所有文件上传失败")
continue
logger.info(f"工具 {tool_name}: 文件预处理完成,成功上传 {len(valid_files)} 个文件")
# 获取当前字段的配置信息,判断是单文件还是多文件
field_config = file_field_map.get(arg_name, {})
is_list = field_config.get('is_list', False)
# 更新processed_arguments中的值
_update_processed_argument(processed_arguments, arg_name, arg_value, processed_files_item, tool_name)
_update_processed_argument(processed_arguments, arg_name, arg_value, valid_files, tool_name, is_list)
except Exception as e:
logger.error(f"工具 {tool_name}: 处理文件字段 {arg_name} 时发生错误: {str(e)}")
@@ -180,27 +189,34 @@ def _is_file_object(obj):
return any(key in obj for key in file_indicators)
def _update_processed_argument(processed_arguments, arg_name, original_value, processed_files, tool_name):
def _update_processed_argument(processed_arguments, arg_name, original_value, processed_files, tool_name, is_list=False):
"""
更新处理后的参数值
重要Dify API 的文件字段始终需要一个文件对象列表,即使只有一个文件
根据字段类型决定输出格式:
- file-list (is_list=True): 输出列表格式 []
- file (is_list=False): 输出对象格式 {}
Args:
processed_arguments (dict): 处理后的参数字典
arg_name (str): 参数名称
original_value: 原始参数值可以是字符串URL、文件对象或文件列表
processed_files: 处理后的文件对象(单个对象,不是列表
processed_files (list): 处理后的文件对象列表
tool_name (str): 工具名称
is_list (bool): 是否为多文件类型 (file-list=True, file=False)
"""
# 注意:processed_files 是单个文件对象,需要转换为列表
# 因为 Dify API 要求文件字段必须是列表格式
if processed_files:
# 始终将文件对象包装成列表
processed_arguments[arg_name] = [processed_files]
logger.info(f"工具 {tool_name}: 已更新文件字段 {arg_name} 为列表格式: {processed_arguments[arg_name]}")
else:
if not processed_files:
logger.warning(f"工具 {tool_name}: 文件处理后为空,保持原值")
return
if is_list:
# file-list 类型:使用完整列表
processed_arguments[arg_name] = processed_files
logger.info(f"工具 {tool_name}: 字段 {arg_name} 是 file-list 类型,输出 {len(processed_files)} 个文件")
else:
# file 类型:只取第一个文件对象
processed_arguments[arg_name] = processed_files[0]
logger.info(f"工具 {tool_name}: 字段 {arg_name} 是 file 类型,输出单个对象")
logger.info(f"工具 {tool_name}: 字段 {arg_name} 最终值: {processed_arguments[arg_name]}")

View File

@@ -247,7 +247,7 @@ def process_user_input_form(user_input_form):
if default_value is not None:
property_schema["default"] = str(default_value)
elif control_type == "file":
elif control_type in ["file", "file-list"]:
# 文件上传控件处理 - 简化版本仅支持remote_url
# 获取允许的文件类型
allowed_file_types = control_config.get("allowed_file_types", [])
@@ -259,16 +259,6 @@ def process_user_input_form(user_input_form):
"type": "object",
"description": label or f"文件上传字段: {variable}",
"properties": {
# "type": {
# "type": "string",
# "description": file_type_desc
# },
# "transfer_method": {
# "type": "string",
# "description": "文件传输方式",
# "enum": ["remote_url"],
# "default": "remote_url"
# },
"url": {
"type": "string",
"description": url_description
@@ -277,9 +267,13 @@ def process_user_input_form(user_input_form):
"required": ["url"]
}
# 处理文件数量限制
# 判断是单文件还是多文件
# file-list 类型或 max_length > 1 都视为多文件
actual_type = control_config.get("type", control_type)
max_length = control_config.get("max_length", 1)
if max_length > 1:
is_multi_file = actual_type == "file-list" or max_length > 1
if is_multi_file:
# 多文件上传场景
property_schema = {
"type": "array",
@@ -330,6 +324,7 @@ def extract_file_fields(user_input_form):
- allowed_file_types (list): 允许的文件类型
- allowed_file_upload_methods (list): 允许的上传方式
- allowed_file_extensions (list): 允许的文件扩展名
- is_list (bool): 是否为多文件类型 (file-list=True, file=False)
"""
file_fields = []
@@ -353,6 +348,11 @@ def extract_file_fields(user_input_form):
# 只处理type为file或file-list的字段
if control_type in ["file", "file-list"] or control_config.get("type") in ["file", "file-list"]:
# 判断是否为多文件类型
# 优先使用 control_config 中的 type其次使用 control_type
actual_type = control_config.get("type", control_type)
is_list = actual_type == "file-list"
# 提取文件字段的详细信息
file_field_info = {
"variable": control_config.get("variable", ""),
@@ -361,7 +361,8 @@ def extract_file_fields(user_input_form):
"max_length": control_config.get("max_length", 1),
"allowed_file_types": control_config.get("allowed_file_types", []),
"allowed_file_upload_methods": control_config.get("allowed_file_upload_methods", []),
"allowed_file_extensions": control_config.get("allowed_file_extensions", [])
"allowed_file_extensions": control_config.get("allowed_file_extensions", []),
"is_list": is_list # 新增:标识是否为多文件类型
}
# 只添加有效的字段必须有variable

View File

@@ -13,6 +13,27 @@ except ImportError:
logger = get_logger(__name__)
class DifyAPIError(Exception):
"""Dify API 错误异常类"""
def __init__(self, status_code: int, error_code: str, message: str, request_data: dict = None):
self.status_code = status_code
self.error_code = error_code
self.message = message
self.request_data = request_data
super().__init__(self.message)
def __str__(self):
return f"[{self.status_code}] {self.error_code}: {self.message}"
def to_dict(self):
return {
"status_code": self.status_code,
"error_code": self.error_code,
"message": self.message
}
def pinyin_to_camel(pinyin):
"""
将中文名称转换为工具名称
@@ -132,7 +153,21 @@ class WorkflowDifyAPI(ABC):
logger.error(f"Response content: {response.text}")
logger.error(f"Request data: {data}")
response.raise_for_status()
# 解析错误响应并抛出带有详细信息的异常
try:
error_data = response.json()
error_message = error_data.get("message", response.text)
error_code = error_data.get("code", "unknown_error")
except json.JSONDecodeError:
error_message = response.text
error_code = "unknown_error"
raise DifyAPIError(
status_code=response.status_code,
error_code=error_code,
message=error_message,
request_data=data
)
if response_mode == "streaming":
def stream_generator():
for line in response.iter_lines():
@@ -148,12 +183,14 @@ class WorkflowDifyAPI(ABC):
return response.json()
def upload_file(self, api_key, file_path, user="pp666"):
url = f"{self.dify_base_url}/files/upload"
headers = {"Authorization": f"Bearer {api_key}"}
files = {"file": open(file_path, "rb")}
data = {"user": user}
with open(file_path, "rb") as f:
files = {"file": f}
response = requests.post(url, headers=headers, files=files, data=data)
response.raise_for_status()
return response.json()
def upload_file_remote_url(self, file_url):

View File

@@ -113,3 +113,47 @@
2026-01-08 00:56:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief
2026-01-08 00:56:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:56:21 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 09:52:27 - mcp_services - INFO - [main.py:344] - MCP 服务器已关闭
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-08 10:00:45 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-08 10:00:45 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-08 10:00:45 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-08 10:00:45 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-08 10:00:45 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-08 10:02:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis
2026-01-08 10:02:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 10:02:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 10:02:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes
2026-01-08 10:02:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 10:02:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 10:02:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning
2026-01-08 10:02:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 10:02:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 10:22:23 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis
2026-01-08 10:22:23 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 10:22:24 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 10:57:20 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes
2026-01-08 10:57:20 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 10:57:20 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 11:05:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning
2026-01-08 11:05:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 11:05:48 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 11:13:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard
2026-01-08 11:13:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 11:13:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 11:34:55 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief
2026-01-08 11:34:55 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 11:34:56 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 11:59:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning
2026-01-08 11:59:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 11:59:37 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功