Compare commits

...

16 Commits

Author SHA1 Message Date
bf3274788b feat: 提交一下 2026-06-12 14:37:44 +08:00
d71458669a ```
docs: 删除项目文档和配置文件

移除主 README.md 文件,包含项目概述、安装指南和配置示例

移除 lzwcai_mcp_sqlexecutor 模块的完整文档、配置文件和初始化文件

删除 businessQueries.json 配置模板和 .python-version 版本指定文件

更新 .gitignore 文件以清理 Python 缓存文件
```
2026-06-11 18:56:15 +08:00
0d48341b73 docs(agile-db): 移除过时的 AgileDB 技能实现计划文档
移除 .kilo/plans/lzwcai-agile-db-skill.md 文件,该文件包含
AgileDB 数据库操作技能的详细实现计划,包括数据源管理、
数据库与表管理、表数据操作和 SQL 执行等场景化工作流指导。
同时移除对应的技能压缩包文件。
2026-06-11 18:55:33 +08:00
557361632c feat(.kilo): 新增 AgileDB 数据库操作技能
新增 lzwcai-agile-db 技能文件,为 AI Agent 提供 AgileDB 数据库操作的场景化工作流指导。
该技能支持数据源浏览、表数据 CRUD、SQL 执行和 AI 生成表结构等功能,包含完整的工具列表和使用场景。
2026-06-11 18:51:49 +08:00
9c597c9b0d feat: 添加数据库管理平台MCP Server
新增lzwcai-mcp-agile-db项目,提供数据库管理、表操作、数据CRUD、
API密钥管理、技能与工具管理等功能。

包含33个工具:
- 数据源管理:创建、更新、删除数据源
- 数据库与表管理:表结构操作、数据查询等
- API密钥管理:密钥创建、权限管理等
- 技能与工具管理:SQL工具创建、配置更新等
- 数据导入和SQL执行功能

添加了完整的README文档说明安装使用方法,
以及Python 3.12版本支持和基本项目结构。
2026-06-11 09:53:40 +08:00
a1012e61bf feat: 新增userId追踪支持并更新版本至0.1.12
- 自动为生成的输入JSON Schema添加userId字段
- 从工具调用参数中提取userId并传递至API请求
- 新增userId日志打印与调试信息
- 修复API客户端无用的f-string日志调用
- 清理无用的类型导入项
2026-05-25 14:42:46 +08:00
fb61ae27cf feat: 新增HTML转URL工具服务,优化API转换器配置
- 新增lzwcai_mcpskills_visual2url项目,提供本地HTML文件、HTML代码转可访问URL的MCP工具
- 重构lzwcai_mcp_api_converter配置加载逻辑,取消内存缓存复用,强制拉取最新配置并新增缓存降级容错机制
- 升级lzwcai_mcp_api_converter至0.2.5、项目模板至0.1.3
- 更新各示例项目的环境配置参数与模板工具配置
2026-05-25 11:48:12 +08:00
992d97c0a4 feat(core): 改进API响应处理支持纯文本JSON解析
当API返回content-type为text/plain但实际内容是JSON格式时,
现在会尝试解析为JSON对象而不是直接返回文本,
提高API响应的兼容性

fix(create_mcp): 移除硬编码的输出schema定义

移除了工具注册中硬编码的outputSchema定义,
让系统使用更灵活的配置方式

chore(release): 更新版本号至0.2.4
2026-03-06 18:02:41 +08:00
8703a61198 feat(api-converter): 更新API配置并调整持久化令牌策略
- 替换api_config_9p04kww1pu.json为新的api_config_w8kgb73ib3.json配置文件,
  包含登录和单据查询两个API接口

- 修改AuthService类中的persist_token默认值为False,调整令牌持久化策略

- 移除旧的包信息文件、依赖文件和日志文件

- 更新API配置以支持金蝶K3Cloud系统的登录和单据查询功能
2026-03-06 16:19:07 +08:00
3c9fba36e9 feat(main): 修改 workflow extraContext 参数名称
将 workflow[extraContext] 参数名更改为 workflow_extraContext,
以避免方括号在某些系统中可能引起的解析问题。

同时更新了版本号从 0.1.7 到 0.1.8
2026-02-09 20:10:21 +08:00
32bc05376f feat(main): 修改工具输入schema以支持workflow[extraContext]字段
- 将params字段替换为workflow[extraContext]字段用于接收工作流额外上下文参数
- 更新描述信息,明确字段用途为接收工作流额外上下文参数(如环境变量等)
- 修改处理逻辑,提取workflow[extraContext]字段并合并到inputs中
- 当workflow[extraContext]为字典类型时合并到inputs,否则保留为独立字段
- 更新日志信息中的字段名称引用

chore(pyproject): 更新版本号至0.1.7
2026-02-09 19:44:51 +08:00
1b850913e7 feat(workflow): 添加 params 参数支持并优化工具调用处理
- 在输入 schema 中添加 params 字段,允许接收任何类型的额外参数
- 修改 handle_call_tool 函数,提取并合并 params 到 inputs
- 支持字典类型 params 的自动合并和其他类型 params 的保留
- 更新版本号从 0.1.5 到 0.1.6
2026-02-09 18:51:26 +08:00
a50aa307ab feat(schema-converter): 新增文件上传类型支持并优化schema转换逻辑
- 新增 file 和 fileList 类型用于单文件和多文件上传功能
- 添加文件配置信息处理,包括 accept、typeCategories、uploadMode 等参数
- 为文件类型添加 x-file-type、x-accept、x-type-categories 等扩展属性
- 优化默认值处理逻辑,排除文件类型设置默认值的情况

refactor(main): 重构工具配置处理优先级

- 将 schema 获取优先级调整为先从 sqlParams 转换,再使用 inputJsonSchema
- 增强错误处理机制,当 sqlParams 转换失败时自动回退到 inputJsonSchema
- 当两种方式都失败时提供空 schema 作为兜底方案
- 改进日志记录,增加调试信息和警告提示

chore: 更新项目版本号和测试配置

- 更新 pyproject.toml 中的版本号从 0.1.3 到 0.1.5
- 修改测试用的 workflowId 和 workflowExecuteKey 环境变量
2026-02-07 20:17:10 +08:00
e18c661368 feat(api-converter): 添加进销存采购订单API配置并实现本地缓存机制
新增api_config_9p04kww1pu.json配置文件,包含进销存采购订单相关的四个核心
API接口(查询列表、新建、详情、编辑),完善了load_api_configs函数,
增加本地文件缓存机制,支持从本地文件加载配置并在配置变更时同步保存,
优化refresh_api_configs函数以同步清理本地文件缓存。

BREAKING CHANGE: API配置方式调整,引入本地缓存机制可能影响原有部署流程
2026-02-07 15:48:01 +08:00
41c3d7a8fd fix(pyproject): 降低Python版本要求从3.13到3.10
修改了pyproject.toml文件中的Python版本要求,将最低支持版本从3.13降级到3.10,
同时更新了分类器中对应的Python版本声明,以提高项目的兼容性。
2026-01-28 09:36:17 +08:00
5107fdb74c chore(general): 更新项目配置文件
- 添加必要的配置项
- 优化现有设置
- 确保环境兼容性
2026-01-28 09:35:48 +08:00
296 changed files with 0 additions and 40623 deletions

View File

@@ -1,2 +0,0 @@
{
}

View File

@@ -1,68 +0,0 @@
# lzwcai-mcp-server-package
MCP (Model Context Protocol) 服务器工具集,为 AI 助手提供企业级业务能力扩展。
## 📦 包含模块
| 模块 | 版本 | 说明 |
|------|------|------|
| [lzwcai-mcp-iot](./lzwcai_mcp_iot) | 0.3.3 | IoT 设备控制服务器,支持设备查询、定位和控制 |
| [lzwcai-mcp-sqlexecutor](./lzwcai_mcp_sqlexecutor) | 0.1.8 | SQL 查询执行服务器,支持动态工具生成 |
| [lzwcai-mcp-api-converter](./lzwcai_mcp_api_converter) | 0.1.30 | API 转换服务器,将业务 API 转换为 MCP 工具 |
| [lzwcai-demp-tool-server-dify-to-mcp](./lzwcai_demp_tool_server_dify_to_mcp) | 0.1.4 | Dify 集成工具,将 Dify 模型部署到 MCP |
| [lzwcai-demp-tool-server-dify-to-mcp-test](./lzwcai_demp_tool_server_dify_to_mcp_test) | 0.1.0 | Dify 集成工具测试版 |
## 🚀 快速安装
```bash
# IoT 设备控制
pip install lzwcai-mcp-iot
# SQL 查询执行
pip install lzwcai-mcp-sqlexecutor
# API 转换器
pip install lzwcai-mcp-api-converter
# Dify 集成
pip install lzwcai-demp-tool-server-dify-to-mcp
```
## <20> 打包 与发布
```bash
# 进入子模块目录
cd lzwcai_mcp_iot
# 使用 uv 打包
uv build
# 上传到管理端技能广场
# 将 dist/ 目录下的 .tar.gz 文件上传至技能广场
```
## 🔧 MCP 客户端配置示例
```json
{
"mcpServers": {
"iot": {
"command": "lzwcai-mcp-iot"
},
"sql": {
"command": "lzwcai-mcp-sqlexecutor"
},
"api": {
"command": "lzwcai-mcp-api-converter"
}
}
}
```
## 📄 许可证
专有软件 - 版权所有 © LZWCAI开发团队
## 📧 联系方式
- 邮箱dev@lzwcai.com

BIN
doct/模板.docx Normal file

Binary file not shown.

BIN
doct/模板.pdf Normal file

Binary file not shown.

BIN
doct/首页和尾页.docx Normal file

Binary file not shown.

BIN
doct/首页和尾页.pdf Normal file

Binary file not shown.

View File

@@ -1,12 +0,0 @@
Metadata-Version: 2.4
Name: lzwcai-demp-tool-server-dify-to-mcp
Version: 0.1.0
Summary: 这是一个Dify to MCP的集成工具通过Dify的API接口将Dify的模型部署到MCP平台并进行推理。
Requires-Python: >=3.10
Requires-Dist: httpx>=0.28.1
Requires-Dist: mcp>=1.1.2
Requires-Dist: omegaconf>=2.3.0
Requires-Dist: pip>=24.3.1
Requires-Dist: python-dotenv>=1.0.1
Requires-Dist: requests
Requires-Dist: pypinyin>=0.54.0

View File

@@ -1,37 +0,0 @@
# lzwcai-mcp-server-package
#### 介绍
lzwcai-mcp-server-package
#### 软件架构
软件架构说明
#### 安装教程
1. xxxx
2. xxxx
3. xxxx
#### 使用说明
1. xxxx
2. xxxx
3. xxxx
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

View File

@@ -1,12 +0,0 @@
Metadata-Version: 2.4
Name: lzwcai-demp-tool-server-dify-to-mcp
Version: 0.1.4
Summary: 这是一个Dify to MCP的集成工具通过Dify的API接口将Dify的模型部署到MCP平台并进行推理。
Requires-Python: >=3.10
Requires-Dist: httpx>=0.28.1
Requires-Dist: mcp>=1.1.2
Requires-Dist: omegaconf>=2.3.0
Requires-Dist: pip>=24.3.1
Requires-Dist: python-dotenv>=1.0.1
Requires-Dist: requests
Requires-Dist: pypinyin>=0.54.0

View File

@@ -1,23 +0,0 @@
README.md
pyproject.toml
setup.cfg
lzwcai_demp_tool_server_dify_to_mcp.egg-info/PKG-INFO
lzwcai_demp_tool_server_dify_to_mcp.egg-info/SOURCES.txt
lzwcai_demp_tool_server_dify_to_mcp.egg-info/dependency_links.txt
lzwcai_demp_tool_server_dify_to_mcp.egg-info/entry_points.txt
lzwcai_demp_tool_server_dify_to_mcp.egg-info/requires.txt
lzwcai_demp_tool_server_dify_to_mcp.egg-info/top_level.txt
src/__init__.py
src/create_mcp.py
src/create_mcp_util.py
src/chat/__init__.py
src/chat/chat_server.py
src/completion/completion_server.py
src/completion/test.py
src/core/__init__.py
src/core/core_server.py
src/difyTaskCall/task_instance.py
src/utils/tool_translation.py
src/utils/translator.py
src/workflow/__init__.py
src/workflow/workflow_server.py

View File

@@ -1,2 +0,0 @@
[console_scripts]
lzwcai-demp-tool-server-dify-to-mcp = src.create_mcp:run_main

View File

@@ -1,7 +0,0 @@
httpx>=0.28.1
mcp>=1.1.2
omegaconf>=2.3.0
pip>=24.3.1
python-dotenv>=1.0.1
requests
pypinyin>=0.54.0

View File

@@ -1,61 +0,0 @@
#!/usr/bin/env python3
"""
主入口文件
用于启动 Dify MCP 服务器,并配置命令行参数
"""
import os
import sys
# Mock 配置参数
def setup_mock_arguments():
"""
设置模拟命令行参数
这些参数可以根据实际需求进行修改
"""
# 默认配置
default_config = {
"base_url": "http://192.168.2.236:3001/v1",
"app_sks": ["app-YFHByB4whARWVqXN2LcuPudq"],
"mode_type": "workflow",
"transport": "stdio"
}
# 如果没有提供命令行参数,则添加默认参数
if len(sys.argv) == 1:
sys.argv.extend([
"--base-url", default_config["base_url"],
"--app-sks", *default_config["app_sks"],
"--mode-type", default_config["mode_type"]
])
return default_config
def main():
"""
主函数:设置命令行参数并启动服务器
"""
# 设置模拟命令行参数
config = setup_mock_arguments()
# 导入并运行 MCP 服务器
try:
from src.create_mcp import run_main
# 获取传输模式
transport_mode = config.get("transport", "stdio")
# 运行服务器(不输出额外信息,避免干扰 STDIO 通信)
run_main(transport=transport_mode)
except ImportError as e:
print(f"[ERROR] 导入错误: {e}", file=sys.stderr)
print("请确保已正确安装所有依赖包", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"[ERROR] 运行错误: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -1,32 +0,0 @@
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "lzwcai-demp-tool-server-dify-to-mcp"
version = "0.1.4"
description = "这是一个Dify to MCP的集成工具通过Dify的API接口将Dify的模型部署到MCP平台并进行推理。"
requires-python = ">=3.10"
dependencies = [
"httpx>=0.28.1",
"mcp>=1.1.2",
"omegaconf>=2.3.0",
"pip>=24.3.1",
"python-dotenv>=1.0.1",
"requests",
"pypinyin>=0.54.0",
]
[tool.setuptools]
packages = {find = {where = ["."], include = ["src*"]}}
include-package-data = true
[project.scripts]
lzwcai-demp-tool-server-dify-to-mcp = "src.create_mcp:run_main"
[tool.hatch.build.targets.wheel]
packages = ["src"]
[tool.setuptools.package-data]
"*" = ["*.env"]
"src" = ["**/*.env"]

View File

@@ -1,4 +0,0 @@
[egg_info]
tag_build =
tag_date = 0

View File

@@ -1,7 +0,0 @@
class ChatDifyAPI:
def __init__(self, base_url: str, app_sks: str):
self.base_url = base_url
self.app_sks = app_sks
def process_task(self, task_id: str, **kwargs):
pass

View File

@@ -1,212 +0,0 @@
import requests
from abc import ABC
import logging
import json
import re
import pypinyin
logger = logging.getLogger(__name__)
def pinyin_to_camel(pinyin):
"""
将拼音列表转换为驼峰命名
例如: ['ni', 'hao', 'a'] -> 'NiHaoA'
所有非字母数字字符会被替换为下划线
"""
# 使用正则表达式将所有非字母数字字符(包括所有标点符号)替换为下划线
cleaned = re.sub(r'[^\w\s]', '_', pinyin)
# 将空格也替换为下划线
cleaned = re.sub(r'\s+', '_', cleaned)
# 移除连续的下划线并去除首尾下划线
cleaned = re.sub(r'_+', '_', cleaned).strip('_')
# 转换为拼音并生成驼峰命名
pinyin_list = pypinyin.lazy_pinyin(cleaned)
return "tool_" + "".join(word.capitalize() for word in pinyin_list)
class CompletionDifyAPI(ABC):
def __init__(self, base_url: str, dify_app_sks: list, user="pp666"):
# dify configs
self.dify_base_url = base_url
self.dify_app_sks = dify_app_sks
self.user = user
# dify app infos
dify_app_infos = []
dify_app_params = []
dify_app_metas = []
for key in self.dify_app_sks:
dify_app_infos.append(self.get_app_info(key))
dify_app_params.append(self.get_app_parameters(key))
dify_app_metas.append(self.get_app_meta(key))
self.dify_app_infos = dify_app_infos
self.dify_app_params = dify_app_params
self.dify_app_metas = dify_app_metas
self.dify_app_names = [x["name"] for x in dify_app_infos]
def chat_message(
self,
api_key,
inputs={},
response_mode="streaming",
conversation_id=None,
userId="pp666",
files=None,
):
url = f"{self.dify_base_url}/completion-messages"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {
"inputs": inputs,
"response_mode": response_mode,
"user": userId,
}
if conversation_id:
data["conversation_id"] = conversation_id
if response_mode == "streaming":
response = requests.post(url, headers=headers, json=data, stream=True)
# 处理流式响应
full_answer = ""
for line in response.iter_lines():
if line:
# 跳过 "data:" 前缀
decoded_line = line.decode("utf-8")
if decoded_line.startswith("data:"):
try:
json_str = decoded_line[5:].strip()
data = json.loads(json_str)
if data.get("event") == "message" and "answer" in data:
# 累积完整答案
full_answer += data["answer"]
# 这里也可以选择处理每个部分响应,例如返回生成器
# yield data
except json.JSONDecodeError:
logger.warning(f"无法解析JSON数据: {decoded_line}")
# 创建一个符合非流式响应格式的结果
response_data = {"answer": full_answer}
# 处理可能包含代码块的数据
processed_data = self.process_answer_code_block(response_data)
return processed_data
else:
response = requests.post(url, headers=headers, json=data)
response_data = response.json()
# 处理可能包含代码块的数据
processed_data = self.process_answer_code_block(response_data)
return processed_data
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}
response = requests.post(url, headers=headers, files=files, data=data)
response.raise_for_status()
return response.json()
def stop_response(self, api_key, task_id, user="pp666"):
url = f"{self.dify_base_url}/chat-messages/{task_id}/stop"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {"user": user}
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
return response.json()
def get_app_info(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/info"
headers = {"Authorization": f"Bearer {api_key}"}
# params = {"user": user}
response = requests.get(url, headers=headers)
response.raise_for_status()
response_map = response.json()
# 翻译工具名称
from src.utils.tool_translation import TranslationService
tool_name = response_map.get("name")
if tool_name:
# translated_name = TranslationService.translate_tool_name(tool_name)
translated_name = pinyin_to_camel(tool_name)
response_map["name"] = translated_name
# 翻译工具描述
# tool_description = response_map.get("description")
# if tool_description:
# translated_description = TranslationService.translate_tool_description(
# tool_description
# )
# response_map["description"] = (
# f"{tool_description} ({translated_description})"
# )
return response_map
def get_app_parameters(self, api_key, user="pp666"):
return {
"user_input_form": [
{"string": {"variable": "query", "label": "查询内容", "required": True}}
]
}
def get_app_meta(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/meta"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json()
@staticmethod
def process_answer_code_block(data):
try:
# 获取answer字段
answer = data.get("answer", "")
# 构造符合workflow_finished格式的输出
formatted_response = [
{"event": "workflow_finished", "data": {"outputs": {"result": answer}}}
]
# 尝试处理可能的代码块
if answer.startswith("```") and answer.endswith("```"):
try:
# 移除代码块标记并解析JSON
code_content = answer.strip("```").strip()
json_data = json.loads(code_content)
# 如果包含description字段用它替换answer
if "description" in json_data:
formatted_response[0]["data"]["outputs"]["result"] = json_data[
"description"
]
except json.JSONDecodeError:
# 如果不是有效的JSON保留原始代码块内容
pass
return formatted_response
except Exception as e:
logger.warning(f"处理答案代码块时出错: {str(e)}")
# 发生错误时返回符合格式的基础响应
return [
{
"event": "workflow_finished",
"data": {
"outputs": {
"error": str(e),
"fallback": data.get("answer", str(data)),
}
},
}
]

View File

@@ -1,104 +0,0 @@
import requests
from abc import ABC
import logging
import json
logger = logging.getLogger(__name__)
res = {
"event": "message",
"task_id": "49c9ea1b-7b43-475b-a680-d769fb238a45",
"id": "432ab98e-5e36-4a29-abe5-e01281c3678c",
"message_id": "432ab98e-5e36-4a29-abe5-e01281c3678c",
"mode": "completion",
"answer": '```\n{\n "description": "该API的具体功能描述暂时不明确因为提供的API信息 \'今天打老虎啊按时啊啊\' 并不是有效的API名称或描述。请提供正确的API名称和相关输入输出信息以便我能为其补充完善的API描述。"\n}\n```',
"metadata": {
"usage": {
"prompt_tokens": 73,
"prompt_unit_price": "0.0",
"prompt_price_unit": "0.0",
"prompt_price": "0.0",
"completion_tokens": 61,
"completion_unit_price": "0.0",
"completion_price_unit": "0.0",
"completion_price": "0.0",
"total_tokens": 134,
"total_price": "0.0",
"currency": "USD",
"latency": 1.896302318200469,
}
},
"created_at": 1747233054,
}
def process_answer_code_block(data):
try:
# 获取answer字段
answer = data.get("answer", "")
# 检查answer是否是代码块格式
if answer.startswith("```") and answer.endswith("```"):
# 移除代码块标记并解析JSON
code_content = answer.strip("```").strip()
json_data = json.loads(code_content)
# 获取description字段
if "description" in json_data:
return json_data["description"]
# 如果不是预期格式则返回原始answer
return data.get("answer", data)
except Exception as e:
logger.warning(f"处理答案代码块时出错: {str(e)}")
# 发生错误时返回原始数据
return data.get("answer", data)
def chat_message_test(
api_key,
inputs={},
response_mode="blocking",
conversation_id=None,
userId="pp666",
files=None,
):
url = "https://ops.lzwcai.com/v1/completion-messages"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {
"inputs": inputs,
"response_mode": response_mode,
"user": userId,
}
if conversation_id:
data["conversation_id"] = conversation_id
if response_mode == "streaming":
response = requests.post(
url, headers=headers, json=data, stream=response_mode == "streaming"
)
return response
else:
response = requests.post(url, headers=headers, json=data)
return response.json()
if __name__ == "__main__":
print("开始执行主程序")
try:
print("准备调用chat_message方法")
res = chat_message_test(
api_key="app-Ppemii3c0ROPoLvRwskgZ7Il",
inputs={"query": "今天打老虎啊按时啊啊"},
response_mode="streaming",
userId="abc-123",
)
print("chat_message方法调用完成")
# 打印响应内容
print("响应内容:", res)
# print(process_answer_code_block(res))
except Exception as e:
print(f"执行过程中出现错误: {e}")

View File

@@ -1,172 +0,0 @@
import requests
from abc import ABC
import logging
import json
import re
import pypinyin
logger = logging.getLogger(__name__)
def pinyin_to_camel(pinyin):
"""
将拼音列表转换为驼峰命名
例如: ['ni', 'hao', 'a'] -> 'NiHaoA'
所有非字母数字字符会被替换为下划线
"""
# 使用正则表达式将所有非字母数字字符(包括所有标点符号)替换为下划线
cleaned = re.sub(r'[^\w\s]', '_', pinyin)
# 将空格也替换为下划线
cleaned = re.sub(r'\s+', '_', cleaned)
# 移除连续的下划线并去除首尾下划线
cleaned = re.sub(r'_+', '_', cleaned).strip('_')
# 转换为拼音并生成驼峰命名
pinyin_list = pypinyin.lazy_pinyin(cleaned)
return "tool_" + "".join(word.capitalize() for word in pinyin_list)
class DifyAPI(ABC):
def __init__(self, base_url: str, dify_app_sks: list, user="pp666"):
# dify configs
self.dify_base_url = base_url
self.dify_app_sks = dify_app_sks
self.user = user
# dify app infos
dify_app_infos = []
dify_app_params = []
dify_app_metas = []
for key in self.dify_app_sks:
dify_app_infos.append(self.get_app_info(key))
dify_app_params.append(self.get_app_parameters(key))
dify_app_metas.append(self.get_app_meta(key))
print("dify_app_params", dify_app_params)
self.dify_app_infos = dify_app_infos
self.dify_app_params = dify_app_params
self.dify_app_metas = dify_app_metas
self.dify_app_names = [x["name"] for x in dify_app_infos]
def chat_message(
self,
api_key,
inputs={},
response_mode="streaming",
conversation_id=None,
user="pp666",
files=None,
):
url = f"{self.dify_base_url}/workflows/run"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {
"inputs": inputs,
"response_mode": response_mode,
"user": user,
}
logger.info("Sending data to Dify API: %s", data)
logger.info("Sending headers to Dify API: %s", headers)
logger.info("Sending url to Dify API: %s", url)
if conversation_id:
data["conversation_id"] = conversation_id
if files:
files_data = []
for file_info in files:
file_path = file_info.get("path")
transfer_method = file_info.get("transfer_method")
if transfer_method == "local_file":
files_data.append(("file", open(file_path, "rb")))
elif transfer_method == "remote_url":
pass
response = requests.post(
url,
headers=headers,
data=data,
files=files_data,
stream=response_mode == "streaming",
)
else:
response = requests.post(
url, headers=headers, json=data, stream=response_mode == "streaming"
)
response.raise_for_status()
if response_mode == "streaming":
for line in response.iter_lines():
if line:
if line.startswith(b"data:"):
try:
json_data = json.loads(line[5:].decode("utf-8"))
yield json_data
except json.JSONDecodeError:
print(f"Error decoding JSON: {line}")
else:
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}
response = requests.post(url, headers=headers, files=files, data=data)
response.raise_for_status()
return response.json()
def stop_response(self, api_key, task_id, user="pp666"):
url = f"{self.dify_base_url}/chat-messages/{task_id}/stop"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {"user": user}
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
return response.json()
def get_app_info(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/info"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
from src.utils.tool_translation import TranslationService
response_map = response.json()
# 翻译工具名称
# tool_name = response_map.get("name")
# translated_name = TranslationService.translate_tool_name(tool_name)
# response_map["name"] = translated_name
# # 翻译工具描述
# tool_description = response_map.get("description")
# translated_description = TranslationService.translate_tool_description(
# tool_description
# )
# response_map["description"] = translated_description
tool_name = response_map.get("name")
if tool_name:
# translated_name = TranslationService.translate_tool_name(tool_name)
translated_name = pinyin_to_camel(tool_name)
response_map["name"] = translated_name
return response_map
def get_app_parameters(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/parameters"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json()
def get_app_meta(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/meta"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json()

View File

@@ -1,318 +0,0 @@
import asyncio
import json
import os
import logging
import argparse
from abc import ABC
import mcp.server.stdio
import mcp.types as types
import requests
from mcp.server import NotificationOptions, Server
from mcp.server.models import InitializationOptions
from omegaconf import OmegaConf
# from src.workflow.workflow_server import WorkflowDifyAPI
from src.difyTaskCall.task_instance import TaskInstance
# 配置日志记录
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
def parse_arguments():
parser = argparse.ArgumentParser(description="Dify MCP服务器配置")
parser.add_argument(
"--base-url",
type=str,
help="API基础URL",
default="http://192.168.11.24:3001/v1",
)
parser.add_argument(
"--app-sks",
nargs="+",
help="应用秘钥列表",
default=["app-d7s00CJ2NY4LJzUEiZsVDnPN"],
)
parser.add_argument(
"--mode-type",
type=str,
help="Dify应用模式类型 (workflow, chat, completion)",
default="workflow",
choices=["workflow", "chat", "completion"],
)
return parser.parse_args()
def get_app_info(base_url=None, app_sks=None, mode_type=None):
# 获取命令行参数
args = parse_arguments()
# 命令行参数优先,其次是函数参数,最后是默认值
if args.base_url is not None:
base_url = args.base_url
if base_url is None:
base_url = "http://192.168.11.24:3001/v1"
if args.app_sks is not None:
app_sks = args.app_sks
if app_sks is None:
app_sks = ["app-d7s00CJ2NY4LJzUEiZsVDnPN"]
# 确保 app_sks 始终是列表类型
if isinstance(app_sks, str):
# 如果是字符串,转换为列表
app_sks = [app_sks]
if args.mode_type is not None:
mode_type = args.mode_type
if mode_type is None:
mode_type = "workflow"
return base_url, app_sks, mode_type
# return "https://dempdify.lzwcai.com/v1", ["app-X6wAy5nkvWB3hR69cgvIjC3r"], "workflow"
# 初始化服务器和Dify API
base_url, dify_app_sks, dify_app_mode_type = get_app_info()
server = Server("dify_mcp_server")
task_instance = TaskInstance(base_url, dify_app_sks, dify_app_mode_type)
dify_api = task_instance.get_task_instance(dify_app_mode_type)
def process_user_input_form(user_input_form):
"""
处理Dify应用的用户输入表单转换为JSON Schema格式
参数:
user_input_form: Dify应用的用户输入表单配置
返回:
处理后的inputSchema字典
"""
inputSchema = dict(
type="object",
properties={},
required=[],
)
property_num = len(user_input_form)
if property_num > 0:
for j in range(property_num):
param = user_input_form[j]
param_type = list(param.keys())[0]
param_info = param[param_type]
property_name = param_info["variable"]
# 根据不同控件类型处理
if param_type == "text-input":
inputSchema["properties"][property_name] = {
"type": "string",
"description": param_info["label"],
}
if "default" in param_info:
inputSchema["properties"][property_name]["default"] = param_info[
"default"
]
elif param_type == "paragraph":
inputSchema["properties"][property_name] = {
"type": "string",
"description": param_info["label"],
"format": "paragraph",
}
if "default" in param_info:
inputSchema["properties"][property_name]["default"] = param_info[
"default"
]
elif param_type == "select":
inputSchema["properties"][property_name] = {
"type": "string",
"description": param_info["label"],
"enum": param_info["options"],
}
if "default" in param_info:
inputSchema["properties"][property_name]["default"] = param_info[
"default"
]
elif param_type == "file_upload":
# 文件上传控件处理
file_type_schema = {
"type": "object",
"description": param_info["label"],
"properties": {
"file_url": {"type": "string", "description": "文件URL"},
"file_name": {"type": "string", "description": "文件名称"},
},
"required": ["file_url"],
}
# 处理图片上传配置
if "image" in param_info and param_info["image"]["enabled"]:
image_config = param_info["image"]
file_type_schema["properties"]["type"] = {
"type": "string",
"description": "文件类型支持png、jpg、jpeg、webp、gif",
"enum": ["png", "jpg", "jpeg", "webp", "gif"],
}
# 处理数量限制
number_limits = image_config.get("number_limits", 3)
if number_limits > 1:
# 如果允许多个文件,则使用数组
inputSchema["properties"][property_name] = {
"type": "array",
"description": param_info["label"],
"items": file_type_schema,
"maxItems": number_limits,
}
else:
# 如果只允许单个文件
inputSchema["properties"][property_name] = file_type_schema
else:
# 如果没有特定的图片配置,使用一般文件配置
inputSchema["properties"][property_name] = file_type_schema
else:
# 默认处理为字符串类型
inputSchema["properties"][property_name] = {
"type": "string",
"description": param_info["label"],
}
# 处理必填字段
if param_info.get("required", False):
inputSchema["required"].append(property_name)
# 添加必填的userId参数支持数字或字符串类型
# inputSchema["properties"]["userId"] = dict(
# oneOf=[{"type": "number"}, {"type": "string"}],
# description="您的员工ID用于识别您的员工身份",
# )
# inputSchema["required"].append("userId")
return inputSchema
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""
列出可用的工具
返回:
工具列表每个工具都使用JSON Schema验证其参数
"""
tools = []
tool_names = dify_api.dify_app_names
tool_infos = dify_api.dify_app_infos
tool_params = dify_api.dify_app_params
tool_num = len(tool_names)
for i in range(tool_num):
# 加载每个工具的应用信息
app_info = tool_infos[i]
# 加载每个工具的应用参数
app_param = tool_params[i]
# 处理用户输入表单
inputSchema = process_user_input_form(app_param["user_input_form"])
tools.append(
types.Tool(
name=app_info["name"],
description=app_info["description"],
inputSchema=inputSchema,
)
)
return tools
@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
"""
调用工具处理请求
参数:
name: 工具名称
arguments: 工具参数
返回:
处理结果列表
"""
tool_names = dify_api.dify_app_names
if name in tool_names:
tool_idx = tool_names.index(name)
tool_sk = dify_api.dify_app_sks[tool_idx]
responses = dify_api.chat_message(
tool_sk,
inputs=arguments,
userId=arguments.get("userId", "pp666"),
)
for res in responses:
if res["event"] == "workflow_finished":
outputs = res["data"]["outputs"]
mcp_out = []
for _, v in outputs.items():
mcp_out.append(types.TextContent(type="text", text=v))
return mcp_out
else:
raise ValueError(f"Unknown tool: {name}")
def run_main(transport="stdio"):
"""
主函数使用stdin/stdout流运行服务器
"""
if transport == "stdio":
import anyio
from mcp.server.stdio import stdio_server
async def arun():
async with stdio_server() as streams:
await server.run(
streams[0],
streams[1],
InitializationOptions(
server_name="dify_mcp_server",
server_version="0.0.6",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
anyio.run(arun)
else:
from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Mount, Route
sse = SseServerTransport("/messages/")
async def handle_sse(request):
async with sse.connect_sse(
request.scope, request.receive, request._send
) as streams:
await server.run(
streams[0], streams[1], server.create_initialization_options()
)
return Response()
starlette_app = Starlette(
debug=True,
routes=[
Route("/sse", endpoint=handle_sse, methods=["GET"]),
Mount("/messages/", app=sse.handle_post_message),
],
)
import uvicorn
uvicorn.run(starlette_app, host="0.0.0.0", port=8080, log_level="info")
if __name__ == "__main__":
run_main()

View File

@@ -1,373 +0,0 @@
import json
from typing import Dict, List, Any, Tuple, Optional
import logging
from pathlib import Path
# 常量定义
DEFAULT_NUMBER_LIMITS = 3 # 默认文件数量限制
def process_user_input_form(
user_input_form: List[Dict[str, Any]],
) -> Tuple[Dict[str, Any], List[str]]:
"""
处理Dify用户输入表单生成对应的JSON Schema properties和required列表
参数:
user_input_form: Dify应用的用户输入表单配置
返回:
properties: 表单字段的properties字典
required: 必填字段列表
"""
properties = {}
required = []
if not user_input_form:
return properties, required
for param in user_input_form:
try:
# 直接获取字典的第一个键,而不是通过list转换
param_type = next(iter(param))
param_info = param[param_type]
property_name = param_info["variable"]
properties[property_name] = {
"type": param_type,
"description": param_info["label"],
}
if param_info.get("required", False):
required.append(property_name)
except (KeyError, StopIteration) as e:
logging.warning(f"处理用户输入表单项时出错: {e}, 跳过此项")
continue
return properties, required
def process_file_upload(file_upload: Optional[Dict[str, Any]]) -> Dict[str, Any]:
"""
处理Dify文件上传配置生成对应的JSON Schema properties
设计为通用实现,支持任意文件类型配置
参数:
file_upload: Dify应用的文件上传配置
返回:
file_properties: 文件上传的properties字典
"""
# 检查是否存在文件上传配置
if not file_upload:
return {}
# 收集所有启用的文件类型信息
enabled_types = []
max_items = 0
supported_transfer_methods = set()
file_type_configs = {}
for file_type, config in file_upload.items():
if not config.get("enabled", False):
continue
enabled_types.append(file_type)
number_limits = config.get("number_limits", DEFAULT_NUMBER_LIMITS)
max_items += number_limits
type_transfer_methods = config.get("transfer_methods", [])
supported_transfer_methods.update(type_transfer_methods)
# 存储每种文件类型的详细配置
file_type_configs[file_type] = {
"number_limits": number_limits,
"transfer_methods": type_transfer_methods,
}
# 如果没有启用的文件类型,返回空字典
if not enabled_types:
return {}
# 构建文件项的JSON Schema
file_item_schema = _build_file_item_schema(
enabled_types, supported_transfer_methods, file_type_configs
)
# 构建最终的files属性
file_properties = {
"files": {
"type": "array",
"items": file_item_schema,
"description": "支持多种文件类型的文件列表",
"maxItems": max_items,
}
}
return file_properties
def _build_file_item_schema(
enabled_types: List[str],
supported_transfer_methods: set,
file_type_configs: Dict[str, Dict[str, Any]],
) -> Dict[str, Any]:
"""
构建文件项的JSON Schema
参数:
enabled_types: 启用的文件类型列表
supported_transfer_methods: 支持的传输方式集合
file_type_configs: 每种文件类型的配置信息
返回:
file_item_schema: 文件项的JSON Schema
"""
file_item_schema = {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": enabled_types,
"description": "文件类型",
},
"transfer_method": {
"type": "string",
"enum": list(supported_transfer_methods),
"description": "传输方式",
},
},
}
# 添加条件属性
if "remote_url" in supported_transfer_methods:
file_item_schema["properties"]["url"] = {
"type": "string",
"format": "uri",
"description": "文件URL (当传输方式为remote_url时使用)",
}
if "local_file" in supported_transfer_methods:
file_item_schema["properties"]["upload_file_id"] = {
"type": "string",
"description": "上传文件ID (当传输方式为local_file时使用)",
}
# 添加条件验证逻辑
file_item_schema["allOf"] = []
# 为每种文件类型添加验证规则
for file_type, type_config in file_type_configs.items():
type_methods = type_config["transfer_methods"]
# 基本验证
file_item_schema["allOf"].append(
{
"if": {"properties": {"type": {"const": file_type}}},
"then": {
"properties": {
"transfer_method": {
"enum": type_methods,
"description": f"{file_type}类型支持的传输方式: {', '.join(type_methods)}",
}
}
},
}
)
# 为每种传输方法添加类型特定的验证
_add_transfer_method_validations(file_item_schema, file_type, type_methods)
return file_item_schema
def _add_transfer_method_validations(
file_item_schema: Dict[str, Any], file_type: str, type_methods: List[str]
) -> None:
"""
为每种传输方法添加类型特定的验证
参数:
file_item_schema: 文件项JSON Schema
file_type: 文件类型
type_methods: 该类型支持的传输方法列表
"""
for method in type_methods:
if method == "remote_url":
file_item_schema["allOf"].append(
{
"if": {
"properties": {
"type": {"const": file_type},
"transfer_method": {"const": "remote_url"},
},
"required": ["type", "transfer_method"],
},
"then": {"required": ["url"]},
}
)
elif method == "local_file":
file_item_schema["allOf"].append(
{
"if": {
"properties": {
"type": {"const": file_type},
"transfer_method": {"const": "local_file"},
},
"required": ["type", "transfer_method"],
},
"then": {"required": ["upload_file_id"]},
}
)
def convert_dify_params_to_schema(tool_params: Dict[str, Any]) -> Dict[str, Any]:
"""
将用户输入表单和文件上传配置组合成新的结构
参数:
tool_params: 包含user_input_form和file_upload的参数字典
返回:
组合后的结构
"""
# 参数验证
if not isinstance(tool_params, dict):
raise TypeError("tool_params 必须是字典类型")
# 处理用户输入表单
properties, required = process_user_input_form(
tool_params.get("user_input_form", [])
)
# 处理文件上传配置
file_properties = process_file_upload(tool_params.get("file_upload"))
# 创建新的结构
result = {
"inputs": {"type": "object", "properties": properties, "required": required},
# "userId": {
# "oneOf": [{"type": "number"}, {"type": "string"}],
# "description": "您的员工ID用于识别您的员工身份",
# "required": True,
# },
}
# 如果有文件上传配置添加files字段
if file_properties:
result["files"] = file_properties.get("files", {})
return result
def finalize_schema_structure(mock_result: Dict[str, Any]) -> Dict[str, Any]:
"""
将mock_result构建为符合要求的map_mock_result格式
参数:
mock_result: 通过convert_dify_params_to_schema函数获取的结果
返回:
构建后的map_mock_result字典
"""
# 确定required字段
# required_fields = ["userId"]
required_fields = []
# 只有当inputs的required有值时才添加inputs到顶层required
if (
mock_result.get("inputs", {}).get("required")
and len(mock_result["inputs"]["required"]) > 0
):
required_fields.append("inputs")
# 如果有文件上传也可以考虑添加files到required
if "files" in mock_result:
# 可以根据需求决定是否将files添加到required
# required_fields.append("files")
pass
return {
"type": "object",
"properties": mock_result,
"required": required_fields,
}
def create_json_file(data: Dict[str, Any], filename: str = "output.json") -> None:
"""
将数据保存为JSON文件
参数:
data: 要保存的数据
filename: 保存的文件名
"""
try:
# 获取mock数据
mock_result = convert_dify_params_to_schema(data)
# 使用封装的方法构建map_mock_result
map_mock_result = finalize_schema_structure(mock_result)
# 确保目标目录存在
output_path = Path(filename)
output_path.parent.mkdir(parents=True, exist_ok=True)
# 将结果写入JSON文件
with open(filename, "w", encoding="utf-8") as f:
json.dump(map_mock_result, f, ensure_ascii=False, indent=4)
logging.info(f"已成功将数据保存至 {filename}")
print(f"已成功将数据保存至 {filename}")
except Exception as e:
error_msg = f"保存JSON文件时出错: {e}"
logging.error(error_msg)
raise IOError(error_msg)
if __name__ == "__main__":
# 配置日志
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
# 示例数据
api_data = {
"opening_statement": "",
"suggested_questions": [],
"suggested_questions_after_answer": {"enabled": False},
"speech_to_text": {"enabled": False},
"text_to_speech": {"enabled": False, "language": "", "voice": ""},
"retriever_resource": {"enabled": False},
"annotation_reply": {"enabled": False},
"more_like_this": {"enabled": False},
"user_input_form": [
{
"paragraph": {
"label": "文案内容",
"max_length": 33024,
"options": [],
"required": True,
"type": "paragraph",
"variable": "content",
}
}
],
"sensitive_word_avoidance": {"enabled": False},
"file_upload": {
"image": {
"enabled": False,
"number_limits": 3,
"transfer_methods": ["local_file", "remote_url"],
}
},
"system_parameters": {"image_file_size_limit": "10"},
}
try:
create_json_file(api_data)
except Exception as e:
logging.error(f"程序执行失败: {e}")

View File

@@ -1,53 +0,0 @@
from abc import ABC
# class WorkflowDifyAPI和chatDifyApi和completionDifyApi
# dify_app_mode_type workflow, chat, completion
class TaskInstance(ABC):
def __init__(self, base_url, dify_app_sks, dify_app_mode_type):
self.base_url = base_url
self.dify_app_sks = dify_app_sks
self.dify_app_mode_type = dify_app_mode_type
def get_task_instance(self, task_id: str):
"""
根据dify_app_mode_type返回相应的API实例
Args:
task_id: 任务ID
Returns:
返回对应的API实例
Raises:
ValueError: 当dify_app_mode_type无效时抛出异常
"""
from src.workflow.workflow_server import WorkflowDifyAPI
from src.completion.completion_server import CompletionDifyAPI
from src.chat.chat_server import ChatDifyAPI
# 使用字典映射提高代码灵活性和可维护性
api_classes = {
"workflow": WorkflowDifyAPI,
"chat": ChatDifyAPI,
"completion": CompletionDifyAPI,
}
# 检查mode_type是否有效
if self.dify_app_mode_type.lower() not in api_classes:
supported_types = ", ".join(api_classes.keys())
raise ValueError(
f"不支持的dify_app_mode_type: {self.dify_app_mode_type},支持的类型: {supported_types}"
)
# 获取对应的API类
api_class = api_classes[self.dify_app_mode_type.lower()]
# 这里假设所有API类都接受相同的参数集
# 如果各API类构造函数参数不同需要针对每种类型单独处理
return api_class(
self.base_url,
self.dify_app_sks,
)

View File

@@ -1,153 +0,0 @@
import os
import sys
from typing import Dict, Any, Optional
# 导入翻译函数
from .translator import translate
class TranslationService:
"""翻译服务类,用于处理各种翻译需求"""
@staticmethod
def create_prompt(
content: str,
target_lang: str,
use_case: str,
style: str,
prompt_type: str = "general",
keep_terms_desc: str = "核心术语",
) -> str:
"""
创建翻译提示
Args:
content: 待翻译内容
target_lang: 目标语言
use_case: 使用场景
style: 翻译风格
prompt_type: 提示类型,可选值: "general", "tool_name", "tool_description"
keep_terms_desc: 保留术语的描述
Returns:
str: 格式化的翻译提示
"""
if prompt_type == "tool_name":
keep_terms_desc = "核心术语(如 小写,词语需要用下划线连接)"
elif prompt_type == "tool_description":
keep_terms_desc = "核心术语(这是一段话)"
return f"""
角色:专业本地化翻译专家
任务:将以下内容翻译为{target_lang}(目标用途:{use_case}
要求:
1. 仅返回译文,不含解释或原文;
2. 保留{keep_terms_desc}
3. 符合{style}风格;
4. 特殊符号保持原样。
示例输出格式:
Translated Text
待翻译内容:
{content}
"""
@staticmethod
def translate_text(
content: str,
target_lang: str,
use_case: str = "",
style: str = "正式且符合技术品牌调性",
prompt_type: str = "general",
) -> Dict[str, Any]:
"""
翻译文本
Args:
content: 待翻译内容
target_lang: 目标语言
use_case: 使用场景,默认为空
style: 翻译风格,默认为"正式且符合技术品牌调性"
prompt_type: 提示类型,可选值: "general", "tool_name", "tool_description"
Returns:
Dict: 包含翻译结果的字典
"""
prompt = TranslationService.create_prompt(
content=content,
target_lang=target_lang,
use_case=use_case,
style=style,
prompt_type=prompt_type,
)
try:
result = translate(prompt, target_lang)
return result
except Exception as e:
print(f"翻译出错: {str(e)}")
return {"translated_text": "", "error": str(e)}
@staticmethod
def translate_tool_name(
name: str,
target_lang: str = "英语",
use_case: str = "工具名称",
style: str = "正式且符合技术品牌调性,大模型能理解",
) -> str:
"""
翻译工具名称的便捷方法
Returns:
str: 翻译后的工具名称
"""
result = TranslationService.translate_text(
content=name,
target_lang=target_lang,
use_case=use_case,
style=style,
prompt_type="tool_name",
)
return result.get("translated_text", "")
@staticmethod
def translate_tool_description(
description: str,
target_lang: str = "英语",
use_case: str = "工具描述",
style: str = "正式且符合技术品牌调性,大模型能理解",
) -> str:
"""
翻译工具描述的便捷方法
Returns:
str: 翻译后的工具描述
"""
result = TranslationService.translate_text(
content=description,
target_lang=target_lang,
use_case=use_case,
style=style,
prompt_type="tool_description",
)
return result.get("translated_text", "")
def translation_example():
"""翻译功能使用示例"""
# 示例1: 翻译工具名称
tool_name = "万川AI新媒体平台【测试环境】"
translated_name = TranslationService.translate_tool_name(tool_name)
print(f"工具名称翻译: {translated_name}")
# 示例2: 翻译工具描述
description = "21日辛柏青发布讣告宣布妻子朱媛媛抗癌五年后离世。此前在一次路演现场当观众问及朱媛媛时辛柏青2秒停顿藏着"
translated_desc = TranslationService.translate_tool_description(description)
print(f"工具描述翻译: {translated_desc}")
if __name__ == "__main__":
translation_example()

View File

@@ -1,64 +0,0 @@
import os
import requests
import json
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# ========== 模型相关 ==========
# 从.env文件获取模型API配置
BASE_URL = os.getenv("BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1")
API_KEY = os.getenv("OPENAI_API_KEY", "sk-c5a912a6bc8e4c9cbdbdf68232352a03")
TEMPERATURE = float(os.getenv("MODEL_TEMPERATURE", "0.7"))
def translate(content, target_language):
"""
翻译文本内容到目标语言
:param content: 要翻译的内容
:param target_language: 目标语言,如'en'(英语), 'zh'(中文), 'ja'(日语), 'fr'(法语)等
:return: 翻译后的内容,如果翻译失败则返回原文和错误信息
"""
if not content or not target_language:
return {"error": "内容或目标语言不能为空", "translated_text": content}
# 确保API密钥已设置
if not API_KEY:
return {"error": "API密钥未设置请检查.env文件", "translated_text": content}
try:
# 构建API请求头
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}",
}
# 构建翻译提示
prompt = f"请将以下内容翻译成{target_language},只返回翻译结果,不要包含任何解释或原文:\n\n{content}"
# 构建API请求体
data = {
"model": "qwen-max", # 使用通义千问模型,可以根据实际需要更改
"messages": [{"role": "user", "content": prompt}],
"temperature": TEMPERATURE,
}
# 发送API请求
response = requests.post(
f"{BASE_URL}/chat/completions", headers=headers, json=data
)
# 解析响应
if response.status_code == 200:
result = response.json()
translated_text = result["choices"][0]["message"]["content"].strip()
return {"translated_text": translated_text}
else:
error_message = (
f"翻译失败,状态码: {response.status_code}, 响应: {response.text}"
)
return {"error": error_message, "translated_text": content}
except Exception as e:
return {"error": f"翻译过程中发生错误: {str(e)}", "translated_text": content}

View File

@@ -1,175 +0,0 @@
import requests
from abc import ABC
import logging
import json
import re
logger = logging.getLogger(__name__)
import pypinyin
def pinyin_to_camel(pinyin):
"""
将拼音列表转换为驼峰命名
例如: ['ni', 'hao', 'a'] -> 'NiHaoA'
所有非字母数字字符会被替换为下划线
"""
# 使用正则表达式将所有非字母数字字符(包括所有标点符号)替换为下划线
cleaned = re.sub(r'[^\w\s]', '_', pinyin)
# 将空格也替换为下划线
cleaned = re.sub(r'\s+', '_', cleaned)
# 移除连续的下划线并去除首尾下划线
cleaned = re.sub(r'_+', '_', cleaned).strip('_')
# 转换为拼音并生成驼峰命名
pinyin_list = pypinyin.lazy_pinyin(cleaned)
return "tool_" + "".join(word.capitalize() for word in pinyin_list)
class WorkflowDifyAPI(ABC):
def __init__(self, base_url: str, dify_app_sks: list, user="pp666"):
# dify configs
self.dify_base_url = base_url
self.dify_app_sks = dify_app_sks
self.user = user
# dify app infos
dify_app_infos = []
dify_app_params = []
dify_app_metas = []
for key in self.dify_app_sks:
dify_app_infos.append(self.get_app_info(key))
dify_app_params.append(self.get_app_parameters(key))
dify_app_metas.append(self.get_app_meta(key))
self.dify_app_infos = dify_app_infos
self.dify_app_params = dify_app_params
self.dify_app_metas = dify_app_metas
self.dify_app_names = [x["name"] for x in dify_app_infos]
def chat_message(
self,
api_key,
inputs={},
response_mode="streaming",
conversation_id=None,
userId="pp666",
files=None,
):
url = f"{self.dify_base_url}/workflows/run"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {
"inputs": inputs,
"response_mode": response_mode,
"user": userId,
}
logger.info("Sending data to Dify API: %s", data)
logger.info("Sending headers to Dify API: %s", headers)
logger.info("Sending url to Dify API: %s", url)
if conversation_id:
data["conversation_id"] = conversation_id
if files:
files_data = []
for file_info in files:
file_path = file_info.get("path")
transfer_method = file_info.get("transfer_method")
if transfer_method == "local_file":
files_data.append(("file", open(file_path, "rb")))
elif transfer_method == "remote_url":
pass
response = requests.post(
url,
headers=headers,
data=data,
files=files_data,
stream=response_mode == "streaming",
)
else:
response = requests.post(
url, headers=headers, json=data, stream=response_mode == "streaming"
)
response.raise_for_status()
if response_mode == "streaming":
for line in response.iter_lines():
if line:
if line.startswith(b"data:"):
try:
json_data = json.loads(line[5:].decode("utf-8"))
yield json_data
except json.JSONDecodeError:
print(f"Error decoding JSON: {line}")
else:
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}
response = requests.post(url, headers=headers, files=files, data=data)
response.raise_for_status()
return response.json()
def stop_response(self, api_key, task_id, user="pp666"):
url = f"{self.dify_base_url}/chat-messages/{task_id}/stop"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {"user": user}
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
return response.json()
def get_app_info(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/info"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
from src.utils.tool_translation import TranslationService
response_map = response.json()
# 翻译工具名称
tool_name = response_map.get("name")
if tool_name:
# translated_name = TranslationService.translate_tool_name(tool_name)
translated_name = pinyin_to_camel(tool_name)
response_map["name"] = translated_name
# 翻译工具描述
# tool_description = response_map.get("description")
# if tool_description:
# translated_description = TranslationService.translate_tool_description(
# tool_description
# )
# response_map["description"] = (
# f"{tool_description} ({translated_description})"
# )
return response_map
def get_app_parameters(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/parameters"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json()
def get_app_meta(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/meta"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json()

View File

@@ -1,12 +0,0 @@
Metadata-Version: 2.4
Name: lzwcai-demp-tool-server-dify-to-mcp-test
Version: 0.0.15
Summary: 这是一个Dify to MCP的集成工具通过Dify的API接口将Dify的模型部署到MCP平台并进行推理。
Requires-Python: >=3.10
Requires-Dist: httpx>=0.28.1
Requires-Dist: mcp>=1.1.2
Requires-Dist: omegaconf>=2.3.0
Requires-Dist: pip>=24.3.1
Requires-Dist: python-dotenv>=1.0.1
Requires-Dist: requests
Requires-Dist: pypinyin>=0.54.0

View File

@@ -1,267 +0,0 @@
# Dify Workflow API 文档
## 概述
Workflow 应用无会话支持,适合用于翻译、文章写作、总结等 AI 场景。
**Base URL:** `http://192.168.2.236:3001/v1`
## 认证
所有 API 请求需在 HTTP Header 中包含 API-Key:
```
Authorization: Bearer {API_KEY}
```
## 1. 执行 Workflow
**接口:** `POST /workflows/run`
### 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| inputs | object | ✓ | 允许传入 App 定义的各变量值。inputs 参数包含了多组键值对,每组的键对应一个特定变量,每组的值则是该变量的具体值 |
| response_mode | string | ✓ | 返回响应模式:`streaming`(流式,推荐)或`blocking`(阻塞,Cloudflare 限制 100 秒超时) |
| user | string | ✓ | 用户标识,用于定义终端用户的身份,方便检索、统计。需保证用户标识在应用内唯一 |
### 文件列表类型变量
| 字段 | 类型 | 说明 |
|------|------|------|
| type | string | 文件类型:document/image/audio/video/custom |
| transfer_method | string | 传递方式:remote_url(图片地址)或local_file(上传文件) |
| url | string | 图片地址(仅当传递方式为 remote_url 时) |
| upload_file_id | string | 上传文件 ID(仅当传递方式为 local_file 时) |
**支持的文件类型:**
- **document**: TXT, MD, PDF, HTML, XLSX, DOCX, CSV, PPTX, XML, EPUB
- **image**: JPG, PNG, GIF, WEBP, SVG
- **audio**: MP3, WAV, M4A, WEBM, AMR
- **video**: MP4, MOV, MPEG
### 响应格式
#### CompletionResponse(阻塞模式)
| 字段 | 类型 | 说明 |
|------|------|------|
| workflow_run_id | string | workflow 执行 ID |
| task_id | string | 任务 ID,用于请求跟踪和停止响应接口 |
| data.id | string | workflow 执行 ID |
| data.workflow_id | string | 关联 Workflow ID |
| data.status | string | 执行状态:running/succeeded/failed/stopped |
| data.outputs | json | 可选,输出内容 |
| data.error | string | 可选,错误原因 |
| data.elapsed_time | float | 可选,耗时(秒) |
| data.total_tokens | int | 可选,总使用 tokens |
| data.total_steps | int | 总步数,默认 0 |
| data.created_at | timestamp | 开始时间 |
| data.finished_at | timestamp | 结束时间 |
#### ChunkCompletionResponse(流式模式)
**事件类型:**
**1. workflow_started**
| 字段 | 类型 | 说明 |
|------|------|------|
| task_id | string | 任务 ID |
| workflow_run_id | string | workflow 执行 ID |
| event | string | 固定为 workflow_started |
| data.id | string | workflow 执行 ID |
| data.workflow_id | string | 关联 Workflow ID |
| data.sequence_number | int | 自增序号,从 1 开始 |
| data.created_at | timestamp | 开始时间 |
**2. node_started**
| 字段 | 类型 | 说明 |
|------|------|------|
| data.node_id | string | 节点 ID |
| data.node_type | string | 节点类型 |
| data.title | string | 节点名称 |
| data.index | int | 执行序号 |
| data.predecessor_node_id | string | 前置节点 ID |
| data.inputs | object | 节点中所有使用到的前置节点变量内容 |
**3. node_finished**
| 字段 | 类型 | 说明 |
|------|------|------|
| data.node_id | string | 节点 ID |
| data.status | string | 执行状态:running/succeeded/failed/stopped |
| data.outputs | json | 可选,输出内容 |
| data.error | string | 可选,错误原因 |
| data.elapsed_time | float | 可选,耗时(秒) |
| data.execution_metadata.total_tokens | int | 可选,总使用 tokens |
| data.execution_metadata.total_price | decimal | 可选,总费用 |
| data.execution_metadata.currency | string | 可选,货币(USD/RMB) |
**4. workflow_finished**
| 字段 | 类型 | 说明 |
|------|------|------|
| data.status | string | 执行状态:running/succeeded/failed/stopped |
| data.outputs | json | 可选,输出内容 |
| data.error | string | 可选,错误原因 |
| data.total_tokens | int | 可选,总使用 tokens |
| data.finished_at | timestamp | 结束时间 |
**5. tts_message** - TTS 音频流事件(Mp3 格式,base64 编码)
| 字段 | 类型 | 说明 |
|------|------|------|
| task_id | string | 任务 ID |
| message_id | string | 消息唯一 ID |
| audio | string | Base64 编码的音频内容 |
| created_at | int | 创建时间戳 |
**6. tts_message_end** - TTS 音频流结束
**7. ping** - 每 10 秒心跳保活
### 错误码
| 状态码 | 错误码 | 说明 |
|--------|--------|------|
| 400 | invalid_param | 传入参数异常 |
| 400 | app_unavailable | App 配置不可用 |
| 400 | provider_not_initialize | 无可用模型凭据配置 |
| 400 | provider_quota_exceeded | 模型调用额度不足 |
| 400 | workflow_request_error | workflow 执行失败 |
| 500 | - | 服务内部异常 |
---
## 2. 查询 Workflow 执行状态
**接口:** `GET /workflows/run/:workflow_run_id`
### 响应字段
| 字段 | 类型 | 说明 |
|------|------|------|
| id | string | workflow 执行 ID |
| workflow_id | string | 关联的 Workflow ID |
| status | string | 执行状态:running/succeeded/failed/stopped |
| inputs | json | 任务输入内容 |
| outputs | json | 任务输出内容 |
| error | string | 错误原因 |
| total_steps | int | 任务执行总步数 |
| total_tokens | int | 任务执行总 tokens |
| created_at | timestamp | 任务开始时间 |
| finished_at | timestamp | 任务结束时间 |
| elapsed_time | float | 耗时(秒) |
---
## 3. 停止 Workflow 执行
**接口:** `POST /workflows/tasks/:task_id/stop`
### 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| user | string | ✓ | 用户标识,必须和发送消息接口传入 user 保持一致 |
---
## 4. 上传文件
**接口:** `POST /files/upload`
### 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| file | file | ✓ | 要上传的文件 |
| user | string | ✓ | 用户标识 |
### 响应字段
| 字段 | 类型 | 说明 |
|------|------|------|
| id | uuid | ID |
| name | string | 文件名 |
| size | int | 文件大小(byte) |
| extension | string | 文件后缀 |
| mime_type | string | 文件 mime-type |
| created_by | uuid | 上传人 ID |
| created_at | timestamp | 上传时间 |
### 错误码
| 状态码 | 错误码 | 说明 |
|--------|--------|------|
| 400 | no_file_uploaded | 必须提供文件 |
| 413 | file_too_large | 文件太大 |
| 415 | unsupported_file_type | 不支持的文件类型 |
| 503 | s3_connection_failed | 无法连接到 S3 服务 |
---
## 5. 获取 Workflow 日志
**接口:** `GET /workflows/logs`
### 查询参数
| 参数 | 类型 | 说明 | 默认值 |
|------|------|------|--------|
| keyword | string | 关键字 | - |
| status | string | 执行状态:succeeded/failed/stopped | - |
| page | int | 当前页码 | 1 |
| limit | int | 每页条数 | 20 |
### 响应字段
| 字段 | 类型 | 说明 |
|------|------|------|
| page | int | 当前页码 |
| limit | int | 每页条数 |
| total | int | 总条数 |
| has_more | bool | 是否还有更多数据 |
| data[].workflow_run.id | string | 标识 |
| data[].workflow_run.status | string | 执行状态 |
| data[].workflow_run.elapsed_time | float | 耗时(秒) |
| data[].workflow_run.total_tokens | int | 消耗的 token 数量 |
---
## 6. 获取应用信息
**接口:** `GET /info`
### 响应字段
| 字段 | 类型 | 说明 |
|------|------|------|
| name | string | 应用名称 |
| description | string | 应用描述 |
| tags | array[string] | 应用标签 |
---
## 7. 获取应用参数
**接口:** `GET /parameters`
### 响应字段
| 字段 | 类型 | 说明 |
|------|------|------|
| user_input_form[].text-input.label | string | 控件展示标签名 |
| user_input_form[].text-input.variable | string | 控件 ID |
| user_input_form[].text-input.required | bool | 是否必填 |
| user_input_form[].text-input.default | string | 默认值 |
| file_upload.image.enabled | bool | 是否开启 |
| file_upload.image.number_limits | int | 图片数量限制,默认 3 |
| file_upload.image.transfer_methods | array[string] | 传递方式:remote_url/local_file |
| system_parameters.file_size_limit | int | 文档上传大小限制(MB) |
| system_parameters.image_file_size_limit | int | 图片文件上传大小限制(MB) |
| system_parameters.audio_file_size_limit | int | 音频文件上传大小限制(MB) |
| system_parameters.video_file_size_limit | int | 视频文件上传大小限制(MB) |

View File

@@ -1,2 +0,0 @@
# 此文件用于确保 logs 目录被 Git 跟踪
# 日志文件会自动生成在此目录中

View File

@@ -1,156 +0,0 @@
2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================
2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 16:05:23
2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO
2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log
2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False
2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True
2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份
2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================
2026-01-19 16:05:23 - __main__ - INFO - [main.py:50] - ================================================================================
2026-01-19 16:05:23 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动
2026-01-19 16:05:23 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log
2026-01-19 16:05:23 - __main__ - INFO - [main.py:53] - ================================================================================
2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.168.11.24:3001/v1/parameters
2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-RM7IGxvdq48ixu1JXFiUZh76'}
2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'}
2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:328] - /parameters API 响应状态码: 200
2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:333] - /parameters API 响应数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'file': {'variable': 'contact_file', 'label': 'contact_file', 'type': 'file', 'max_length': 4096, 'required': True, 'options': [], 'allowed_file_upload_methods': ['remote_url'], 'allowed_file_types': ['document'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}}
2026-01-19 16:05:25 - __main__ - INFO - [main.py:68] - 传输模式: stdio
2026-01-19 16:05:25 - __main__ - INFO - [main.py:69] - 配置参数: {'base_url': 'http://192.168.11.24:3001/v1', 'app_sks': ['app-RM7IGxvdq48ixu1JXFiUZh76'], 'mode_type': 'workflow', 'transport': 'stdio'}
2026-01-19 16:05:26 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
2026-01-19 16:05:26 - src.create_mcp - INFO - [create_mcp.py:104] - 工具 tool_HeTongShenChaGongZuoLiu 的 parameters 数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'file': {'variable': 'contact_file', 'label': 'contact_file', 'type': 'file', 'max_length': 4096, 'required': True, 'options': [], 'allowed_file_upload_methods': ['remote_url'], 'allowed_file_types': ['document'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}}
2026-01-19 16:05:26 - src.create_mcp - INFO - [create_mcp.py:110] - 工具 tool_HeTongShenChaGongZuoLiu 提取的文件字段: [{'variable': 'contact_file', 'label': 'contact_file', 'required': True, 'max_length': 4096, 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['remote_url'], 'allowed_file_extensions': [], 'is_list': False}]
2026-01-19 16:06:14 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest
2026-01-19 16:06:14 - src.create_mcp - INFO - [create_mcp.py:144] - 工具 tool_HeTongShenChaGongZuoLiu 的文件字段信息: [{'variable': 'contact_file', 'label': 'contact_file', 'required': True, 'max_length': 4096, 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['remote_url'], 'allowed_file_extensions': [], 'is_list': False}]
2026-01-19 16:06:14 - src.create_mcp - INFO - [create_mcp.py:145] - 工具 tool_HeTongShenChaGongZuoLiu 调用前的 arguments: {'contact_file': [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}]}
2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:64] - 工具 tool_HeTongShenChaGongZuoLiu 的文件字段变量名: {'contact_file'}
2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:73] - 工具 tool_HeTongShenChaGongZuoLiu: 发现文件字段 contact_file值: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}]
2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:168] - 工具 tool_HeTongShenChaGongZuoLiu: 处理文件列表: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}]
2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:85] - 工具 tool_HeTongShenChaGongZuoLiu: 准备处理文件列表: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}]
2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:322] - 从URL http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx 提取的文件扩展名: docx
2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:333] - URL http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx 匹配文件类型: document
2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:96] - 工具 tool_HeTongShenChaGongZuoLiu: 自动识别文件类型为 document
2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:242] - 开始上传远程文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx
2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:400] - 开始处理文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx
2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:86] - 正在下载文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx (尝试 1/3)
2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:100] - 文件下载成功: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx (大小: 20445 字节)
2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:189] - 准备上传文件: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx (大小: 0.02 MB)
2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:220] - 文件上传成功: eb25b2b233a8719f680d4b0b9c5a80e7.docx (ID: e2ec6720-9cf9-4a64-aa31-102e787a343b)
2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:417] - 已清理临时文件: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx
2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:255] - 文件上传成功 - ID: e2ec6720-9cf9-4a64-aa31-102e787a343b, 名称: eb25b2b233a8719f680d4b0b9c5a80e7.docx, 大小: 20445 bytes
2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:274] - 文件预处理完成,共处理 1 个文件
2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:111] - 工具 tool_HeTongShenChaGongZuoLiu: 文件预处理完成,成功上传 1 个文件
2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:219] - 工具 tool_HeTongShenChaGongZuoLiu: 字段 contact_file 是 file 类型,输出单个对象
2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:221] - 工具 tool_HeTongShenChaGongZuoLiu: 字段 contact_file 最终值: {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'}
2026-01-19 16:06:14 - src.create_mcp - INFO - [create_mcp.py:149] - 工具 tool_HeTongShenChaGongZuoLiu 处理后的 arguments: {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'}}
2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:131] - Sending data to Dify API: {'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'}}, 'response_mode': 'streaming', 'user': 'pp666'}
2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:132] - Sending headers to Dify API: {'Authorization': 'Bearer app-RM7IGxvdq48ixu1JXFiUZh76', 'Content-Type': 'application/json'}
2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:133] - Sending url to Dify API: http://192.168.11.24:3001/v1/workflows/run
2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:148] - Response1:{'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'}}, 'response_mode': 'streaming', 'user': 'pp666'} 400 BAD REQUEST
2026-01-19 16:06:14 - src.workflow.workflow_server - ERROR - [workflow_server.py:152] - API request failed with status 400
2026-01-19 16:06:14 - src.workflow.workflow_server - ERROR - [workflow_server.py:153] - Response content: {"code": "invalid_param", "message": "File validation failed for file: eb25b2b233a8719f680d4b0b9c5a80e7.docx", "status": 400}
2026-01-19 16:06:14 - src.workflow.workflow_server - ERROR - [workflow_server.py:154] - Request data: {'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'}}, 'response_mode': 'streaming', 'user': 'pp666'}
2026-01-19 16:06:14 - src.create_mcp - ERROR - [create_mcp.py:178] - 工具 tool_HeTongShenChaGongZuoLiu 调用 Dify API 失败: [400] invalid_param: File validation failed for file: eb25b2b233a8719f680d4b0b9c5a80e7.docx
2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================
2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 16:21:31
2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO
2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log
2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False
2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True
2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份
2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================
2026-01-19 16:21:31 - __main__ - INFO - [main.py:50] - ================================================================================
2026-01-19 16:21:31 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动
2026-01-19 16:21:31 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log
2026-01-19 16:21:31 - __main__ - INFO - [main.py:53] - ================================================================================
2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.168.11.24:3001/v1/parameters
2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-RM7IGxvdq48ixu1JXFiUZh76'}
2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'}
2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:328] - /parameters API 响应状态码: 200
2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:333] - /parameters API 响应数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'file': {'variable': 'contact_file', 'label': 'contact_file', 'type': 'file', 'max_length': 4096, 'required': True, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['document'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}}
2026-01-19 16:21:33 - __main__ - INFO - [main.py:68] - 传输模式: stdio
2026-01-19 16:21:33 - __main__ - INFO - [main.py:69] - 配置参数: {'base_url': 'http://192.168.11.24:3001/v1', 'app_sks': ['app-RM7IGxvdq48ixu1JXFiUZh76'], 'mode_type': 'workflow', 'transport': 'stdio'}
2026-01-19 16:21:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
2026-01-19 16:21:37 - src.create_mcp - INFO - [create_mcp.py:104] - 工具 tool_HeTongShenChaGongZuoLiu 的 parameters 数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'file': {'variable': 'contact_file', 'label': 'contact_file', 'type': 'file', 'max_length': 4096, 'required': True, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['document'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}}
2026-01-19 16:21:37 - src.create_mcp - INFO - [create_mcp.py:110] - 工具 tool_HeTongShenChaGongZuoLiu 提取的文件字段: [{'variable': 'contact_file', 'label': 'contact_file', 'required': True, 'max_length': 4096, 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_extensions': [], 'is_list': False}]
2026-01-19 16:21:46 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest
2026-01-19 16:21:46 - src.create_mcp - INFO - [create_mcp.py:144] - 工具 tool_HeTongShenChaGongZuoLiu 的文件字段信息: [{'variable': 'contact_file', 'label': 'contact_file', 'required': True, 'max_length': 4096, 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_extensions': [], 'is_list': False}]
2026-01-19 16:21:46 - src.create_mcp - INFO - [create_mcp.py:145] - 工具 tool_HeTongShenChaGongZuoLiu 调用前的 arguments: {'contact_file': [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}]}
2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:64] - 工具 tool_HeTongShenChaGongZuoLiu 的文件字段变量名: {'contact_file'}
2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:73] - 工具 tool_HeTongShenChaGongZuoLiu: 发现文件字段 contact_file值: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}]
2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:168] - 工具 tool_HeTongShenChaGongZuoLiu: 处理文件列表: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}]
2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:85] - 工具 tool_HeTongShenChaGongZuoLiu: 准备处理文件列表: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}]
2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:322] - 从URL http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx 提取的文件扩展名: docx
2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:333] - URL http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx 匹配文件类型: document
2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:96] - 工具 tool_HeTongShenChaGongZuoLiu: 自动识别文件类型为 document
2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:242] - 开始上传远程文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx
2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:400] - 开始处理文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx
2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:86] - 正在下载文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx (尝试 1/3)
2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:100] - 文件下载成功: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx (大小: 20445 字节)
2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:189] - 准备上传文件: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx (大小: 0.02 MB)
2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:220] - 文件上传成功: eb25b2b233a8719f680d4b0b9c5a80e7.docx (ID: c09a25e0-9d92-4b8d-b098-a59d6be10921)
2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:417] - 已清理临时文件: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx
2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:255] - 文件上传成功 - ID: c09a25e0-9d92-4b8d-b098-a59d6be10921, 名称: eb25b2b233a8719f680d4b0b9c5a80e7.docx, 大小: 20445 bytes
2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:274] - 文件预处理完成,共处理 1 个文件
2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:111] - 工具 tool_HeTongShenChaGongZuoLiu: 文件预处理完成,成功上传 1 个文件
2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:219] - 工具 tool_HeTongShenChaGongZuoLiu: 字段 contact_file 是 file 类型,输出单个对象
2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:221] - 工具 tool_HeTongShenChaGongZuoLiu: 字段 contact_file 最终值: {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'c09a25e0-9d92-4b8d-b098-a59d6be10921'}
2026-01-19 16:21:46 - src.create_mcp - INFO - [create_mcp.py:149] - 工具 tool_HeTongShenChaGongZuoLiu 处理后的 arguments: {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'c09a25e0-9d92-4b8d-b098-a59d6be10921'}}
2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:131] - Sending data to Dify API: {'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'c09a25e0-9d92-4b8d-b098-a59d6be10921'}}, 'response_mode': 'streaming', 'user': 'pp666'}
2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:132] - Sending headers to Dify API: {'Authorization': 'Bearer app-RM7IGxvdq48ixu1JXFiUZh76', 'Content-Type': 'application/json'}
2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:133] - Sending url to Dify API: http://192.168.11.24:3001/v1/workflows/run
2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:148] - Response1:{'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'c09a25e0-9d92-4b8d-b098-a59d6be10921'}}, 'response_mode': 'streaming', 'user': 'pp666'} 200 OK
2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================
2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 22:09:39
2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO
2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log
2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False
2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True
2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份
2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================
2026-01-19 22:09:39 - __main__ - INFO - [main.py:50] - ================================================================================
2026-01-19 22:09:39 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动
2026-01-19 22:09:39 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log
2026-01-19 22:09:39 - __main__ - INFO - [main.py:53] - ================================================================================
2026-01-19 22:09:54 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.167.30.8:3001/v1/parameters
2026-01-19 22:09:54 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-0dlxPuFY8HBXo6PpoeStQCjA'}
2026-01-19 22:09:54 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'}
2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================
2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 22:09:55
2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO
2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log
2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False
2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True
2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份
2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================
2026-01-19 22:09:55 - __main__ - INFO - [main.py:50] - ================================================================================
2026-01-19 22:09:55 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动
2026-01-19 22:09:55 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log
2026-01-19 22:09:55 - __main__ - INFO - [main.py:53] - ================================================================================
2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================
2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 22:10:01
2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO
2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log
2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False
2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True
2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份
2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================
2026-01-19 22:10:01 - __main__ - INFO - [main.py:50] - ================================================================================
2026-01-19 22:10:01 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动
2026-01-19 22:10:01 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log
2026-01-19 22:10:01 - __main__ - INFO - [main.py:53] - ================================================================================
2026-01-19 22:10:06 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.167.30.8:3001/v1/parameters
2026-01-19 22:10:06 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-0dlxPuFY8HBXo6PpoeStQCjA'}
2026-01-19 22:10:06 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'}
2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:328] - /parameters API 响应状态码: 200
2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:333] - /parameters API 响应数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'paragraph': {'variable': 'contentText', 'label': '订单内容', 'type': 'paragraph', 'max_length': 48000, 'required': False, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['image', 'document'], 'allowed_file_extensions': []}}, {'file': {'variable': 'contentFile', 'label': '文件内容', 'type': 'file', 'max_length': 5, 'required': False, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['document', 'image'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}}
2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.167.30.8:3001/v1/parameters
2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:328] - /parameters API 响应状态码: 200
2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-0dlxPuFY8HBXo6PpoeStQCjA'}
2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:333] - /parameters API 响应数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'paragraph': {'variable': 'contentText', 'label': '订单内容', 'type': 'paragraph', 'max_length': 48000, 'required': False, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['image', 'document'], 'allowed_file_extensions': []}}, {'file': {'variable': 'contentFile', 'label': '文件内容', 'type': 'file', 'max_length': 5, 'required': False, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['document', 'image'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}}
2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'}
2026-01-19 22:10:13 - __main__ - INFO - [main.py:68] - 传输模式: stdio
2026-01-19 22:10:13 - __main__ - INFO - [main.py:69] - 配置参数: {'base_url': 'http://192.167.30.8:3001/v1', 'app_sks': ['app-0dlxPuFY8HBXo6PpoeStQCjA'], 'mode_type': 'workflow', 'transport': 'stdio'}
2026-01-19 22:10:17 - __main__ - INFO - [main.py:68] - 传输模式: stdio
2026-01-19 22:10:17 - __main__ - INFO - [main.py:69] - 配置参数: {'base_url': 'http://192.167.30.8:3001/v1', 'app_sks': ['app-0dlxPuFY8HBXo6PpoeStQCjA'], 'mode_type': 'workflow', 'transport': 'stdio'}

View File

@@ -1,12 +0,0 @@
Metadata-Version: 2.4
Name: lzwcai-demp-tool-server-dify-to-mcp-test
Version: 0.1.2
Summary: 这是一个Dify to MCP的集成工具通过Dify的API接口将Dify的模型部署到MCP平台并进行推理。
Requires-Python: >=3.10
Requires-Dist: httpx>=0.28.1
Requires-Dist: mcp>=1.1.2
Requires-Dist: omegaconf>=2.3.0
Requires-Dist: pip>=24.3.1
Requires-Dist: python-dotenv>=1.0.1
Requires-Dist: requests
Requires-Dist: pypinyin>=0.54.0

View File

@@ -1,27 +0,0 @@
README.md
pyproject.toml
setup.cfg
lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/PKG-INFO
lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/SOURCES.txt
lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/dependency_links.txt
lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/entry_points.txt
lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/requires.txt
lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/top_level.txt
src/__init__.py
src/create_mcp.py
src/create_mcp_update.py
src/create_mcp_utils.py
src/chat/__init__.py
src/chat/chat_server.py
src/completion/completion_server.py
src/completion/test.py
src/core/__init__.py
src/core/core_server.py
src/difyTaskCall/task_instance.py
src/utils/dify_workflow_schema.py
src/utils/logger_config.py
src/utils/tool_translation.py
src/utils/translator.py
src/utils/upload_file.py
src/workflow/__init__.py
src/workflow/workflow_server.py

View File

@@ -1,2 +0,0 @@
[console_scripts]
lzwcai-demp-tool-server-dify-to-mcp-test = src.create_mcp:run_main

View File

@@ -1,7 +0,0 @@
httpx>=0.28.1
mcp>=1.1.2
omegaconf>=2.3.0
pip>=24.3.1
python-dotenv>=1.0.1
requests
pypinyin>=0.54.0

View File

@@ -1,86 +0,0 @@
#!/usr/bin/env python3
"""
主入口文件
用于启动 Dify MCP 服务器,并配置命令行参数
"""
import os
import sys
import logging
# 导入日志配置
from src.utils.logger_config import setup_logging, get_logger
# Mock 配置参数
def setup_mock_arguments():
"""
设置模拟命令行参数
这些参数可以根据实际需求进行修改
"""
# 默认配置
default_config = {
"base_url": "http://192.168.2.236:3001/v1",
"app_sks": ["app-IfJayK9uu5oTo54Rpr2AS7wl"],
"mode_type": "workflow",
"transport": "stdio"
}
# 如果没有提供命令行参数,则添加默认参数
if len(sys.argv) == 1:
sys.argv.extend([
"--base-url", default_config["base_url"],
"--app-sks", *default_config["app_sks"],
"--mode-type", default_config["mode_type"]
])
return default_config
def main():
"""
主函数:设置命令行参数并启动服务器
"""
# 初始化日志系统MCP模式下禁用控制台输出避免干扰stdio通信
try:
log_file_path = setup_logging(
log_level=logging.INFO,
console_output=False, # MCP模式下禁用控制台输出
file_output=True
)
logger = get_logger(__name__)
logger.info("=" * 80)
logger.info("Dify MCP 服务器启动")
logger.info(f"日志文件: {log_file_path}")
logger.info("=" * 80)
except Exception as e:
# 如果日志初始化失败使用stderr输出错误
print(f"[ERROR] 日志系统初始化失败: {e}", file=sys.stderr)
# 设置模拟命令行参数
config = setup_mock_arguments()
# 导入并运行 MCP 服务器
try:
from src.create_mcp import run_main
# 获取传输模式
transport_mode = config.get("transport", "stdio")
logger.info(f"传输模式: {transport_mode}")
logger.info(f"配置参数: {config}")
# 运行服务器(不输出额外信息,避免干扰 STDIO 通信)
run_main(transport=transport_mode)
except ImportError as e:
logger.error(f"导入错误: {e}", exc_info=True)
print(f"[ERROR] 导入错误: {e}", file=sys.stderr)
print("请确保已正确安装所有依赖包", file=sys.stderr)
sys.exit(1)
except Exception as e:
logger.error(f"运行错误: {e}", exc_info=True)
print(f"[ERROR] 运行错误: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -1,32 +0,0 @@
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "lzwcai-demp-tool-server-dify-to-mcp-test"
version = "0.1.2"
description = "这是一个Dify to MCP的集成工具通过Dify的API接口将Dify的模型部署到MCP平台并进行推理。"
requires-python = ">=3.10"
dependencies = [
"httpx>=0.28.1",
"mcp>=1.1.2",
"omegaconf>=2.3.0",
"pip>=24.3.1",
"python-dotenv>=1.0.1",
"requests",
"pypinyin>=0.54.0",
]
[tool.setuptools]
packages = {find = {where = ["."], include = ["src*"]}}
include-package-data = true
[project.scripts]
lzwcai-demp-tool-server-dify-to-mcp-test = "src.create_mcp:run_main"
[tool.hatch.build.targets.wheel]
packages = ["src"]
[tool.setuptools.package-data]
"*" = ["*.env"]
"src" = ["**/*.env"]

View File

@@ -1,4 +0,0 @@
[egg_info]
tag_build =
tag_date = 0

View File

@@ -1,7 +0,0 @@
class ChatDifyAPI:
def __init__(self, base_url: str, app_sks: str):
self.base_url = base_url
self.app_sks = app_sks
def process_task(self, task_id: str, **kwargs):
pass

View File

@@ -1,203 +0,0 @@
import requests
from abc import ABC
import logging
import json
import pypinyin
from src.utils.logger_config import get_logger
logger = get_logger(__name__)
def pinyin_to_camel(pinyin):
"""
将拼音列表转换为驼峰命名
例如: ['ni', 'hao', 'a'] -> 'NiHaoA'
"""
pinyin_list = pypinyin.lazy_pinyin(pinyin)
return "tool_" + "".join(word.capitalize() for word in pinyin_list)
class CompletionDifyAPI(ABC):
def __init__(self, base_url: str, dify_app_sks: list, user="pp666"):
# dify configs
self.dify_base_url = base_url
self.dify_app_sks = dify_app_sks
self.user = user
# dify app infos
dify_app_infos = []
dify_app_params = []
dify_app_metas = []
for key in self.dify_app_sks:
dify_app_infos.append(self.get_app_info(key))
dify_app_params.append(self.get_app_parameters(key))
dify_app_metas.append(self.get_app_meta(key))
self.dify_app_infos = dify_app_infos
self.dify_app_params = dify_app_params
self.dify_app_metas = dify_app_metas
self.dify_app_names = [x["name"] for x in dify_app_infos]
def chat_message(
self,
api_key,
inputs={},
response_mode="streaming",
conversation_id=None,
userId="pp666",
files=None,
):
url = f"{self.dify_base_url}/completion-messages"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {
"inputs": inputs,
"response_mode": response_mode,
"user": userId,
}
if conversation_id:
data["conversation_id"] = conversation_id
if response_mode == "streaming":
response = requests.post(url, headers=headers, json=data, stream=True)
# 处理流式响应
full_answer = ""
for line in response.iter_lines():
if line:
# 跳过 "data:" 前缀
decoded_line = line.decode("utf-8")
if decoded_line.startswith("data:"):
try:
json_str = decoded_line[5:].strip()
data = json.loads(json_str)
if data.get("event") == "message" and "answer" in data:
# 累积完整答案
full_answer += data["answer"]
# 这里也可以选择处理每个部分响应,例如返回生成器
# yield data
except json.JSONDecodeError:
logger.warning(f"无法解析JSON数据: {decoded_line}")
# 创建一个符合非流式响应格式的结果
response_data = {"answer": full_answer}
# 处理可能包含代码块的数据
processed_data = self.process_answer_code_block(response_data)
return processed_data
else:
response = requests.post(url, headers=headers, json=data)
response_data = response.json()
# 处理可能包含代码块的数据
processed_data = self.process_answer_code_block(response_data)
return processed_data
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}
response = requests.post(url, headers=headers, files=files, data=data)
response.raise_for_status()
return response.json()
def stop_response(self, api_key, task_id, user="pp666"):
url = f"{self.dify_base_url}/chat-messages/{task_id}/stop"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {"user": user}
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
return response.json()
def get_app_info(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/info"
headers = {"Authorization": f"Bearer {api_key}"}
# params = {"user": user}
response = requests.get(url, headers=headers)
response.raise_for_status()
response_map = response.json()
# 翻译工具名称
from src.utils.tool_translation import TranslationService
tool_name = response_map.get("name")
if tool_name:
# translated_name = TranslationService.translate_tool_name(tool_name)
translated_name = pinyin_to_camel(tool_name)
response_map["name"] = translated_name
# 翻译工具描述
# tool_description = response_map.get("description")
# if tool_description:
# translated_description = TranslationService.translate_tool_description(
# tool_description
# )
# response_map["description"] = (
# f"{tool_description} ({translated_description})"
# )
return response_map
def get_app_parameters(self, api_key, user="pp666"):
return {
"user_input_form": [
{"string": {"variable": "query", "label": "查询内容", "required": True}}
]
}
def get_app_meta(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/meta"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json()
@staticmethod
def process_answer_code_block(data):
try:
# 获取answer字段
answer = data.get("answer", "")
# 构造符合workflow_finished格式的输出
formatted_response = [
{"event": "workflow_finished", "data": {"outputs": {"result": answer}}}
]
# 尝试处理可能的代码块
if answer.startswith("```") and answer.endswith("```"):
try:
# 移除代码块标记并解析JSON
code_content = answer.strip("```").strip()
json_data = json.loads(code_content)
# 如果包含description字段用它替换answer
if "description" in json_data:
formatted_response[0]["data"]["outputs"]["result"] = json_data[
"description"
]
except json.JSONDecodeError:
# 如果不是有效的JSON保留原始代码块内容
pass
return formatted_response
except Exception as e:
logger.warning(f"处理答案代码块时出错: {str(e)}")
# 发生错误时返回符合格式的基础响应
return [
{
"event": "workflow_finished",
"data": {
"outputs": {
"error": str(e),
"fallback": data.get("answer", str(data)),
}
},
}
]

View File

@@ -1,104 +0,0 @@
import requests
from abc import ABC
import logging
import json
logger = logging.getLogger(__name__)
res = {
"event": "message",
"task_id": "49c9ea1b-7b43-475b-a680-d769fb238a45",
"id": "432ab98e-5e36-4a29-abe5-e01281c3678c",
"message_id": "432ab98e-5e36-4a29-abe5-e01281c3678c",
"mode": "completion",
"answer": '```\n{\n "description": "该API的具体功能描述暂时不明确因为提供的API信息 \'今天打老虎啊按时啊啊\' 并不是有效的API名称或描述。请提供正确的API名称和相关输入输出信息以便我能为其补充完善的API描述。"\n}\n```',
"metadata": {
"usage": {
"prompt_tokens": 73,
"prompt_unit_price": "0.0",
"prompt_price_unit": "0.0",
"prompt_price": "0.0",
"completion_tokens": 61,
"completion_unit_price": "0.0",
"completion_price_unit": "0.0",
"completion_price": "0.0",
"total_tokens": 134,
"total_price": "0.0",
"currency": "USD",
"latency": 1.896302318200469,
}
},
"created_at": 1747233054,
}
def process_answer_code_block(data):
try:
# 获取answer字段
answer = data.get("answer", "")
# 检查answer是否是代码块格式
if answer.startswith("```") and answer.endswith("```"):
# 移除代码块标记并解析JSON
code_content = answer.strip("```").strip()
json_data = json.loads(code_content)
# 获取description字段
if "description" in json_data:
return json_data["description"]
# 如果不是预期格式则返回原始answer
return data.get("answer", data)
except Exception as e:
logger.warning(f"处理答案代码块时出错: {str(e)}")
# 发生错误时返回原始数据
return data.get("answer", data)
def chat_message_test(
api_key,
inputs={},
response_mode="blocking",
conversation_id=None,
userId="pp666",
files=None,
):
url = "https://ops.lzwcai.com/v1/completion-messages"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {
"inputs": inputs,
"response_mode": response_mode,
"user": userId,
}
if conversation_id:
data["conversation_id"] = conversation_id
if response_mode == "streaming":
response = requests.post(
url, headers=headers, json=data, stream=response_mode == "streaming"
)
return response
else:
response = requests.post(url, headers=headers, json=data)
return response.json()
if __name__ == "__main__":
print("开始执行主程序")
try:
print("准备调用chat_message方法")
res = chat_message_test(
api_key="app-Ppemii3c0ROPoLvRwskgZ7Il",
inputs={"query": "今天打老虎啊按时啊啊"},
response_mode="streaming",
userId="abc-123",
)
print("chat_message方法调用完成")
# 打印响应内容
print("响应内容:", res)
# print(process_answer_code_block(res))
except Exception as e:
print(f"执行过程中出现错误: {e}")

View File

@@ -1,213 +0,0 @@
import requests
from abc import ABC
import logging
import json
# 导入 pypinyin 用于中文转拼音
try:
import pypinyin
except ImportError:
pypinyin = None
logging.warning("pypinyin 模块未安装,将使用简化的命名方式")
logger = logging.getLogger(__name__)
def pinyin_to_camel(pinyin):
"""
将中文名称转换为工具名称
处理逻辑:
1. 如果安装了 pypinyin将中文转换为拼音然后转为驼峰命名
2. 如果未安装 pypinyin将所有非字母数字字符替换为下划线
3. 所有符号都会被替换成下划线
示例:
"你好啊" -> "tool_NiHaoA" (有pypinyin)
"测试-工具" -> "tool_测试_工具" (无pypinyin)
"Hello World!" -> "tool_Hello_World_" (无pypinyin)
Args:
pinyin: 输入的字符串(可能包含中文、英文、符号等)
Returns:
str: 格式化后的工具名称,以 "tool_" 开头
"""
import re
if pypinyin is None:
# 如果 pypinyin 未安装,使用简化的命名方式
# 将所有非字母数字字符(包括空格、符号等)替换为下划线
cleaned = re.sub(r'[^\w]', '_', str(pinyin))
# 移除连续的下划线
cleaned = re.sub(r'_+', '_', cleaned)
# 移除首尾的下划线
cleaned = cleaned.strip('_')
return "tool_" + cleaned if cleaned else "tool_unnamed"
# 使用 pypinyin 转换中文为拼音
pinyin_list = pypinyin.lazy_pinyin(pinyin)
# 处理每个拼音单词
processed_words = []
for word in pinyin_list:
# 将所有非字母数字字符替换为下划线
cleaned_word = re.sub(r'[^\w]', '_', word)
# 移除连续的下划线
cleaned_word = re.sub(r'_+', '_', cleaned_word)
# 移除首尾的下划线
cleaned_word = cleaned_word.strip('_')
if cleaned_word:
# 首字母大写(驼峰命名)
processed_words.append(cleaned_word.capitalize())
# 拼接所有单词
result = "".join(processed_words) if processed_words else "Unnamed"
return "tool_" + result
class DifyAPI(ABC):
def __init__(self, base_url: str, dify_app_sks: list, user="pp666"):
# dify configs
self.dify_base_url = base_url
self.dify_app_sks = dify_app_sks
self.user = user
# dify app infos
dify_app_infos = []
dify_app_params = []
dify_app_metas = []
for key in self.dify_app_sks:
dify_app_infos.append(self.get_app_info(key))
dify_app_params.append(self.get_app_parameters(key))
dify_app_metas.append(self.get_app_meta(key))
logger.info(f"Dify应用参数: {dify_app_params}")
self.dify_app_infos = dify_app_infos
self.dify_app_params = dify_app_params
self.dify_app_metas = dify_app_metas
self.dify_app_names = [x["name"] for x in dify_app_infos]
def chat_message(
self,
api_key,
inputs={},
response_mode="streaming",
conversation_id=None,
user="pp666",
files=None,
):
url = f"{self.dify_base_url}/workflows/run"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {
"inputs": inputs,
"response_mode": response_mode,
"user": user,
}
logger.info("Sending data to Dify API: %s", data)
logger.info("Sending headers to Dify API: %s", headers)
logger.info("Sending url to Dify API: %s", url)
if conversation_id:
data["conversation_id"] = conversation_id
if files:
files_data = []
for file_info in files:
file_path = file_info.get("path")
transfer_method = file_info.get("transfer_method")
if transfer_method == "local_file":
files_data.append(("file", open(file_path, "rb")))
elif transfer_method == "remote_url":
pass
response = requests.post(
url,
headers=headers,
data=data,
files=files_data,
stream=response_mode == "streaming",
)
else:
response = requests.post(
url, headers=headers, json=data, stream=response_mode == "streaming"
)
response.raise_for_status()
if response_mode == "streaming":
for line in response.iter_lines():
if line:
if line.startswith(b"data:"):
try:
json_data = json.loads(line[5:].decode("utf-8"))
yield json_data
except json.JSONDecodeError:
logger.error(f"JSON解码错误: {line}")
else:
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}
response = requests.post(url, headers=headers, files=files, data=data)
response.raise_for_status()
return response.json()
def stop_response(self, api_key, task_id, user="pp666"):
url = f"{self.dify_base_url}/chat-messages/{task_id}/stop"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {"user": user}
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
return response.json()
def get_app_info(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/info"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
from src.utils.tool_translation import TranslationService
response_map = response.json()
# 翻译工具名称
# tool_name = response_map.get("name")
# translated_name = TranslationService.translate_tool_name(tool_name)
# response_map["name"] = translated_name
# # 翻译工具描述
# tool_description = response_map.get("description")
# translated_description = TranslationService.translate_tool_description(
# tool_description
# )
# response_map["description"] = translated_description
tool_name = response_map.get("name")
if tool_name:
# translated_name = TranslationService.translate_tool_name(tool_name)
translated_name = pinyin_to_camel(tool_name)
response_map["name"] = translated_name
return response_map
def get_app_parameters(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/parameters"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json()
def get_app_meta(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/meta"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json()

View File

@@ -1,253 +0,0 @@
import asyncio
import json
import os
import logging
import argparse
from abc import ABC
import mcp.server.stdio
import mcp.types as types
import requests
from mcp.server import NotificationOptions, Server
from mcp.server.models import InitializationOptions
from omegaconf import OmegaConf
# from src.workflow.workflow_server import WorkflowDifyAPI
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__)
def parse_arguments():
parser = argparse.ArgumentParser(description="Dify MCP服务器配置")
parser.add_argument(
"--base-url",
type=str,
help="API基础URL",
default="http://192.168.2.236:3001/v1",
)
parser.add_argument(
"--app-sks",
nargs="+",
help="应用秘钥列表",
default=["app-RBS0TuYEnqm8Q1cRQingkuhf"],
)
parser.add_argument(
"--mode-type",
type=str,
help="Dify应用模式类型 (workflow, chat, completion)",
default="workflow",
choices=["workflow", "chat", "completion"],
)
return parser.parse_args()
def get_app_info(base_url=None, app_sks=None, mode_type=None):
# 获取命令行参数
args = parse_arguments()
# 命令行参数优先,其次是函数参数,最后是默认值
if args.base_url is not None:
base_url = args.base_url
if base_url is None:
base_url = "http://192.168.2.236:3001/v1"
if args.app_sks is not None:
app_sks = args.app_sks
if app_sks is None:
app_sks = ["app-RBS0TuYEnqm8Q1cRQingkuhf"]
if args.mode_type is not None:
mode_type = args.mode_type
if mode_type is None:
mode_type = "workflow"
return base_url, app_sks, mode_type
# 初始化服务器和Dify API
base_url, dify_app_sks, dify_app_mode_type = get_app_info()
server = Server("dify_mcp_server")
task_instance = TaskInstance(base_url, dify_app_sks, dify_app_mode_type)
dify_api = task_instance.get_task_instance(dify_app_mode_type)
# 创建工具
file_config = {
"file_fields": {}, # 字典key为工具名称value为该工具的文件字段列表
"file_type_dicts": {} # 字典key为工具名称value为该工具的文件类型字典
}
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""
列出可用的工具
返回:
工具列表每个工具都使用JSON Schema验证其参数
"""
tools = []
tool_names = dify_api.dify_app_names
tool_infos = dify_api.dify_app_infos
tool_params = dify_api.dify_app_params
tool_num = len(tool_names)
for i in range(tool_num):
# 加载每个工具的应用信息
app_info = tool_infos[i]
# 加载每个工具的应用参数
app_param = tool_params[i]
# 记录 parameters API 返回的原始数据
logger.info(f"工具 {app_info['name']} 的 parameters 数据: {app_param}")
# 处理用户输入表单
inputSchema = process_user_input_form(app_param["user_input_form"])
# 提取所有文件字段并存储到全局字典中
tool_file_fields = extract_file_fields(app_param["user_input_form"])
logger.info(f"工具 {app_info['name']} 提取的文件字段: {tool_file_fields}")
file_config["file_fields"][app_info["name"]] = tool_file_fields
tools.append(
types.Tool(
name=app_info["name"],
description=app_info["description"],
inputSchema=inputSchema,
)
)
return tools
@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
"""
调用工具处理请求
参数:
name: 工具名称
arguments: 工具参数
返回:
处理结果列表
"""
tool_names = dify_api.dify_app_names
if name in tool_names:
tool_idx = tool_names.index(name)
tool_sk = dify_api.dify_app_sks[tool_idx]
# 获取当前工具的文件字段信息
current_tool_file_fields = file_config["file_fields"].get(name, [])
logger.info(f"工具 {name} 的文件字段信息: {current_tool_file_fields}")
logger.info(f"工具 {name} 调用前的 arguments: {arguments}")
# 使用工具函数处理文件字段
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,
)
# 初始化 outputs 变量,避免未定义错误
outputs = {}
for res in responses:
if res["event"] == "workflow_finished":
outputs = res["data"]["outputs"]
break # 找到 workflow_finished 事件后退出循环
# 构建 MCP 输出
mcp_out = []
if outputs:
for _, v in outputs.items():
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}")
def run_main(transport="stdio"):
"""
主函数使用stdin/stdout流运行服务器
"""
if transport == "stdio":
import anyio
from mcp.server.stdio import stdio_server
async def arun():
async with stdio_server() as streams:
await server.run(
streams[0],
streams[1],
InitializationOptions(
server_name="dify_mcp_server",
server_version="0.0.6",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
anyio.run(arun)
else:
from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Mount, Route
sse = SseServerTransport("/messages/")
async def handle_sse(request):
async with sse.connect_sse(
request.scope, request.receive, request._send
) as streams:
await server.run(
streams[0], streams[1], server.create_initialization_options()
)
return Response()
starlette_app = Starlette(
debug=True,
routes=[
Route("/sse", endpoint=handle_sse, methods=["GET"]),
Mount("/messages/", app=sse.handle_post_message),
],
)
import uvicorn
uvicorn.run(starlette_app, host="0.0.0.0", port=8080, log_level="info")
if __name__ == "__main__":
run_main()

View File

@@ -1,320 +0,0 @@
import asyncio
import json
import os
import logging
import argparse
from abc import ABC
import mcp.server.stdio
import mcp.types as types
import requests
from mcp.server import NotificationOptions, Server
from mcp.server.models import InitializationOptions
from omegaconf import OmegaConf
# from src.workflow.workflow_server import WorkflowDifyAPI
from src.difyTaskCall.task_instance import TaskInstance
from src.utils.dify_workflow_schema import process_user_input_form
# 配置日志记录
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
def parse_arguments():
parser = argparse.ArgumentParser(description="Dify MCP服务器配置")
parser.add_argument(
"--base-url",
type=str,
help="API基础URL",
default="http://192.168.2.236:3001/v1",
)
parser.add_argument(
"--app-sks",
nargs="+",
help="应用秘钥列表",
default=["app-XaRWpeL2Yfdguc5ul3ScXvPE"],
)
parser.add_argument(
"--mode-type",
type=str,
help="Dify应用模式类型 (workflow, chat, completion)",
default="workflow",
choices=["workflow", "chat", "completion"],
)
return parser.parse_args()
def get_app_info(base_url=None, app_sks=None, mode_type=None):
# 获取命令行参数
args = parse_arguments()
# 命令行参数优先,其次是函数参数,最后是默认值
if args.base_url is not None:
base_url = args.base_url
if base_url is None:
base_url = "http://192.168.2.236:3001/v1"
if args.app_sks is not None:
app_sks = args.app_sks
if app_sks is None:
app_sks = ["app-XaRWpeL2Yfdguc5ul3ScXvPE"]
if args.mode_type is not None:
mode_type = args.mode_type
if mode_type is None:
mode_type = "workflow"
return base_url, app_sks, mode_type
# 初始化服务器和Dify API
base_url, dify_app_sks, dify_app_mode_type = get_app_info()
server = Server("dify_mcp_server")
task_instance = TaskInstance(base_url, dify_app_sks, dify_app_mode_type)
dify_api = task_instance.get_task_instance(dify_app_mode_type)
def process_user_input_form1(user_input_form):
"""
处理Dify应用的用户输入表单转换为JSON Schema格式
参数:
user_input_form: Dify应用的用户输入表单配置
返回:
处理后的inputSchema字典
"""
inputSchema = dict(
type="object",
properties={},
required=[],
)
property_num = len(user_input_form)
if property_num > 0:
for j in range(property_num):
param = user_input_form[j]
param_type = list(param.keys())[0]
param_info = param[param_type]
property_name = param_info["variable"]
# 根据不同控件类型处理
if param_type == "text-input":
inputSchema["properties"][property_name] = {
"type": "string",
"description": param_info["label"],
}
if "default" in param_info:
inputSchema["properties"][property_name]["default"] = param_info[
"default"
]
elif param_type == "paragraph":
inputSchema["properties"][property_name] = {
"type": "string",
"description": param_info["label"],
"format": "paragraph",
}
if "default" in param_info:
inputSchema["properties"][property_name]["default"] = param_info[
"default"
]
elif param_type == "select":
inputSchema["properties"][property_name] = {
"type": "string",
"description": param_info["label"],
"enum": param_info["options"],
}
if "default" in param_info:
inputSchema["properties"][property_name]["default"] = param_info[
"default"
]
elif param_type == "file_upload":
# 文件上传控件处理
file_type_schema = {
"type": "object",
"description": param_info["label"],
"properties": {
"file_url": {"type": "string", "description": "文件URL"},
"file_name": {"type": "string", "description": "文件名称"},
},
"required": ["file_url"],
}
# 处理图片上传配置
if "image" in param_info and param_info["image"]["enabled"]:
image_config = param_info["image"]
file_type_schema["properties"]["type"] = {
"type": "string",
"description": "文件类型支持png、jpg、jpeg、webp、gif",
"enum": ["png", "jpg", "jpeg", "webp", "gif"],
}
# 处理数量限制
number_limits = image_config.get("number_limits", 3)
if number_limits > 1:
# 如果允许多个文件,则使用数组
inputSchema["properties"][property_name] = {
"type": "array",
"description": param_info["label"],
"items": file_type_schema,
"maxItems": number_limits,
}
else:
# 如果只允许单个文件
inputSchema["properties"][property_name] = file_type_schema
else:
# 如果没有特定的图片配置,使用一般文件配置
inputSchema["properties"][property_name] = file_type_schema
else:
# 默认处理为字符串类型
inputSchema["properties"][property_name] = {
"type": "string",
"description": param_info["label"],
}
# 处理必填字段
if param_info.get("required", False):
inputSchema["required"].append(property_name)
# 添加必填的userId参数支持数字或字符串类型
inputSchema["properties"]["userId"] = dict(
oneOf=[{"type": "number"}, {"type": "string"}],
description="您的员工ID用于识别您的员工身份",
)
inputSchema["required"].append("userId")
return inputSchema
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""
列出可用的工具
返回:
工具列表每个工具都使用JSON Schema验证其参数
"""
tools = []
tool_names = dify_api.dify_app_names
tool_infos = dify_api.dify_app_infos
tool_params = dify_api.dify_app_params
tool_num = len(tool_names)
for i in range(tool_num):
# 加载每个工具的应用信息
app_info = tool_infos[i]
# 加载每个工具的应用参数
app_param = tool_params[i]
# 处理用户输入表单
inputSchema = process_user_input_form(app_param["user_input_form"])
tools.append(
types.Tool(
name=app_info["name"],
description=app_info["description"],
inputSchema=inputSchema,
)
)
return tools
@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
"""
调用工具处理请求
参数:
name: 工具名称
arguments: 工具参数
返回:
处理结果列表
"""
tool_names = dify_api.dify_app_names
if name in tool_names:
tool_idx = tool_names.index(name)
tool_sk = dify_api.dify_app_sks[tool_idx]
# 提取files参数并创建不包含files的inputs对象
files = arguments.get("files", None) if arguments else None
inputs = {k: v for k, v in (arguments or {}).items() if k != "files"}
responses = dify_api.chat_message(
tool_sk,
inputs=inputs,
userId=arguments.get("userId", "pp666") if arguments else "pp666",
files=files,
)
for res in responses:
if res["event"] == "workflow_finished":
outputs = res["data"]["outputs"]
mcp_out = []
for _, v in outputs.items():
mcp_out.append(types.TextContent(type="text", text=v))
return mcp_out
else:
raise ValueError(f"Unknown tool: {name}")
def run_main(transport="stdio"):
"""
主函数使用stdin/stdout流运行服务器
"""
if transport == "stdio":
import anyio
from mcp.server.stdio import stdio_server
async def arun():
async with stdio_server() as streams:
await server.run(
streams[0],
streams[1],
InitializationOptions(
server_name="dify_mcp_server",
server_version="0.0.6",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
anyio.run(arun)
else:
from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Mount, Route
sse = SseServerTransport("/messages/")
async def handle_sse(request):
async with sse.connect_sse(
request.scope, request.receive, request._send
) as streams:
await server.run(
streams[0], streams[1], server.create_initialization_options()
)
return Response()
starlette_app = Starlette(
debug=True,
routes=[
Route("/sse", endpoint=handle_sse, methods=["GET"]),
Mount("/messages/", app=sse.handle_post_message),
],
)
import uvicorn
uvicorn.run(starlette_app, host="0.0.0.0", port=8080, log_level="info")
if __name__ == "__main__":
run_main()

View File

@@ -1,342 +0,0 @@
"""
MCP创建工具的辅助函数模块
包含文件字段处理、参数预处理等功能
"""
import logging
import re
from urllib.parse import urlparse
import os
from src.utils.logger_config import get_logger
logger = get_logger(__name__)
file_type_details = {
"document": {
"extensions": "TXT, MD, MARKDOWN, PDF, HTML, XLSX, XLS, DOCX, CSV, EML, MSG, PPTX, PPT, XML, EPUB",
"description": "文档文件"
},
"image": {
"extensions": "JPG, JPEG, PNG, GIF, WEBP, SVG",
"description": "图片文件"
},
"audio": {
"extensions": "MP3, M4A, WAV, WEBM, AMR",
"description": "音频文件"
},
"video": {
"extensions": "MP4, MOV, MPEG, MPGA",
"description": "视频文件"
},
"custom": {
"extensions": "",
"description": "其他文件类型"
}
}
def process_file_arguments(arguments, current_tool_file_fields, dify_api, tool_name):
"""
处理arguments中的文件类型字段
Args:
arguments (dict): 工具调用的参数字典
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': [], 'is_list': False}]
dify_api: Dify API实例包含file_parameter_pretreatment方法
tool_name (str): 工具名称,用于日志记录
Returns:
dict: 处理后的arguments字典
Raises:
Exception: 当文件处理过程中发生严重错误时抛出
"""
if not arguments or not current_tool_file_fields:
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_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的副本避免修改原始数据
processed_arguments = arguments.copy()
# 处理arguments中的文件字段
for arg_name, arg_value in arguments.items():
# 检查参数名是否是文件字段
if arg_name in file_field_variables:
logger.info(f"工具 {tool_name}: 发现文件字段 {arg_name},值: {arg_value}")
# 检查参数值是否包含文件信息
files_to_process = _extract_files_from_argument(arg_value, arg_name, tool_name)
if not files_to_process:
logger.info(f"工具 {tool_name}: 字段 {arg_name} 不是文件格式,跳过处理")
continue
# 调用文件预处理方法
try:
logger.info(f"工具 {tool_name}: 准备处理文件列表: {files_to_process}")
# 为每个文件对象添加必要的字段
for file_obj in files_to_process:
# 设置传输方式为 remote_url从URL下载并上传
if 'transfer_method' not in file_obj:
file_obj['transfer_method'] = 'remote_url'
# 自动识别文件类型
if 'type' not in file_obj and 'url' in file_obj:
file_obj['type'] = get_file_type_from_url(file_obj['url'])
logger.info(f"工具 {tool_name}: 自动识别文件类型为 {file_obj['type']}")
# 调用文件预处理下载文件并上传到Dify获取upload_file_id
processed_files = dify_api.file_parameter_pretreatment(files_to_process)
if not processed_files or len(processed_files) == 0:
logger.error(f"工具 {tool_name}: 文件预处理失败,未返回有效结果")
continue
# 过滤掉上传失败的文件
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, valid_files, tool_name, is_list)
except Exception as e:
logger.error(f"工具 {tool_name}: 处理文件字段 {arg_name} 时发生错误: {str(e)}")
# 继续执行,不中断整个流程
continue
return processed_arguments
def _extract_files_from_argument(arg_value, arg_name, tool_name):
"""
从参数值中提取文件信息
Args:
arg_value: 参数值可以是字符串URL、文件对象或文件列表
arg_name (str): 参数名称
tool_name (str): 工具名称
Returns:
list: 文件列表如果不是文件格式则返回None
"""
# 情况1: 单个字符串URL
if isinstance(arg_value, str):
# 检查是否是有效的URL
if arg_value.startswith(('http://', 'https://')):
file_obj = {'url': arg_value}
logger.info(f"工具 {tool_name}: 将字符串URL转换为文件对象: {file_obj}")
return [file_obj]
else:
logger.warning(f"工具 {tool_name}: 字符串不是有效的URL: {arg_value}")
return None
# 情况2: 单个文件对象
if isinstance(arg_value, dict) and _is_file_object(arg_value):
files_to_process = [arg_value]
logger.info(f"工具 {tool_name}: 处理单个文件对象: {files_to_process}")
return files_to_process
# 情况3: 文件列表
elif isinstance(arg_value, list) and len(arg_value) > 0:
# 检查列表中是否包含文件对象或URL字符串
valid_files = []
for item in arg_value:
if isinstance(item, dict) and _is_file_object(item):
valid_files.append(item)
elif isinstance(item, str) and item.startswith(('http://', 'https://')):
valid_files.append({'url': item})
if valid_files:
logger.info(f"工具 {tool_name}: 处理文件列表: {valid_files}")
return valid_files
return None
def _is_file_object(obj):
"""
判断对象是否是文件对象
Args:
obj (dict): 要检查的对象
Returns:
bool: 如果是文件对象返回True否则返回False
"""
if not isinstance(obj, dict):
return False
# 检查是否包含文件对象的关键字段
file_indicators = ['type', 'transfer_method', 'url', 'upload_file_id', 'file_url', 'file_name']
return any(key in obj for key in file_indicators)
def _update_processed_argument(processed_arguments, arg_name, original_value, processed_files, tool_name, is_list=False):
"""
更新处理后的参数值
根据字段类型决定输出格式:
- file-list (is_list=True): 输出列表格式 []
- file (is_list=False): 输出对象格式 {}
Args:
processed_arguments (dict): 处理后的参数字典
arg_name (str): 参数名称
original_value: 原始参数值可以是字符串URL、文件对象或文件列表
processed_files (list): 处理后的文件对象列表
tool_name (str): 工具名称
is_list (bool): 是否为多文件类型 (file-list=True, file=False)
"""
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]}")
def validate_file_field_configuration(file_fields, tool_name):
"""
验证文件字段配置的有效性
Args:
file_fields (list): 文件字段配置列表
tool_name (str): 工具名称
Returns:
bool: 配置是否有效
"""
if not file_fields:
return True
for field in file_fields:
if not isinstance(field, dict):
logger.warning(f"工具 {tool_name}: 文件字段配置不是字典格式: {field}")
return False
if 'variable' not in field or not field['variable']:
logger.warning(f"工具 {tool_name}: 文件字段缺少variable字段: {field}")
return False
return True
def get_file_field_summary(file_fields, tool_name):
"""
获取文件字段的摘要信息
Args:
file_fields (list): 文件字段配置列表
tool_name (str): 工具名称
Returns:
dict: 文件字段摘要信息
"""
if not file_fields:
return {
'count': 0,
'variables': [],
'required_count': 0,
'optional_count': 0
}
variables = [field.get('variable', '') for field in file_fields if field.get('variable')]
required_count = sum(1 for field in file_fields if field.get('required', False))
optional_count = len(file_fields) - required_count
summary = {
'count': len(file_fields),
'variables': variables,
'required_count': required_count,
'optional_count': optional_count
}
logger.info(f"工具 {tool_name} 文件字段摘要: {summary}")
return summary
def get_file_type_from_url(url):
"""
根据URL地址返回文件类型
Args:
url (str): 文件的URL地址
Returns:
str: 文件类型 ('document', 'image', 'audio', 'video', 'custom')
Examples:
>>> get_file_type_from_url("http://example.com/file.pdf")
'document'
>>> get_file_type_from_url("http://example.com/image.jpg")
'image'
>>> get_file_type_from_url("http://example.com/video.mp4")
'video'
"""
if not url or not isinstance(url, str):
logger.warning(f"无效的URL: {url}")
return 'custom'
try:
# 解析URL获取路径
parsed_url = urlparse(url)
path = parsed_url.path
# 从路径中提取文件扩展名
file_extension = os.path.splitext(path)[1].lower().lstrip('.')
# 如果没有扩展名,尝试从查询参数中提取
if not file_extension:
# 使用正则表达式匹配常见的文件扩展名模式
extension_pattern = r'\.([a-zA-Z0-9]{2,5})(?:\?|$|&)'
match = re.search(extension_pattern, url)
if match:
file_extension = match.group(1).lower()
logger.info(f"从URL {url} 提取的文件扩展名: {file_extension}")
# 根据扩展名判断文件类型
for file_type, details in file_type_details.items():
if file_type == 'custom':
continue
# 将支持的扩展名转换为小写列表
supported_extensions = [ext.strip().lower() for ext in details['extensions'].split(',')]
if file_extension in supported_extensions:
logger.info(f"URL {url} 匹配文件类型: {file_type}")
return file_type
# 如果没有匹配到任何类型返回custom
logger.info(f"URL {url} 未匹配到已知文件类型返回custom")
return 'custom'
except Exception as e:
logger.error(f"解析URL {url} 时发生错误: {str(e)}")
return 'custom'

View File

@@ -1,53 +0,0 @@
from abc import ABC
# class WorkflowDifyAPI和chatDifyApi和completionDifyApi
# dify_app_mode_type workflow, chat, completion
class TaskInstance(ABC):
def __init__(self, base_url, dify_app_sks, dify_app_mode_type):
self.base_url = base_url
self.dify_app_sks = dify_app_sks
self.dify_app_mode_type = dify_app_mode_type
def get_task_instance(self, task_id: str):
"""
根据dify_app_mode_type返回相应的API实例
Args:
task_id: 任务ID
Returns:
返回对应的API实例
Raises:
ValueError: 当dify_app_mode_type无效时抛出异常
"""
from src.workflow.workflow_server import WorkflowDifyAPI
from src.completion.completion_server import CompletionDifyAPI
from src.chat.chat_server import ChatDifyAPI
# 使用字典映射提高代码灵活性和可维护性
api_classes = {
"workflow": WorkflowDifyAPI,
"chat": ChatDifyAPI,
"completion": CompletionDifyAPI,
}
# 检查mode_type是否有效
if self.dify_app_mode_type.lower() not in api_classes:
supported_types = ", ".join(api_classes.keys())
raise ValueError(
f"不支持的dify_app_mode_type: {self.dify_app_mode_type},支持的类型: {supported_types}"
)
# 获取对应的API类
api_class = api_classes[self.dify_app_mode_type.lower()]
# 这里假设所有API类都接受相同的参数集
# 如果各API类构造函数参数不同需要针对每种类型单独处理
return api_class(
self.base_url,
self.dify_app_sks,
)

View File

@@ -1,400 +0,0 @@
import json
import logging
# 获取日志器
logger = logging.getLogger(__name__)
data={
"user_input_form": [
{
"file": {
"variable": "files",
"label": "files",
"type": "file",
"max_length": 48,
"required": True,
"options": [],
"allowed_file_upload_methods": [
"local_file",
"remote_url"
],
"allowed_file_types": [
"image",
"document"
],
"allowed_file_extensions": []
}
}
],
}
data2={
"user_input_form": [
{
"paragraph": {
"label": "产品名称",
"max_length": 33024,
"options": [],
"required": True,
"type": "paragraph",
"variable": "name"
}
},
{
"paragraph": {
"label": "补充描述",
"max_length": 33024,
"options": [],
"required": False,
"type": "paragraph",
"variable": "desc"
}
}
],
}
data3={
"user_input_form": [
{
"file": {
"allowed_file_extensions": [],
"allowed_file_types": [
"document","image"
],
"allowed_file_upload_methods": [
"local_file",
"remote_url"
],
"label": "输入",
"max_length": 48,
"options": [],
"required": True,
"type": "file",
"variable": "txt"
}
}
]
}
def generate_file_type_description(allowed_file_types):
"""
根据allowed_file_types生成文件类型描述
参数:
allowed_file_types (list): 允许的文件类型列表
返回:
str: 生成的文件类型描述
"""
# 定义各种文件类型的具体格式和中文描述
file_type_details = {
"document": {
"extensions": "TXT, MD, MARKDOWN, PDF, HTML, XLSX, XLS, DOCX, CSV, EML, MSG, PPTX, PPT, XML, EPUB",
"description": "文档文件"
},
"image": {
"extensions": "JPG, JPEG, PNG, GIF, WEBP, SVG",
"description": "图片文件"
},
"audio": {
"extensions": "MP3, M4A, WAV, WEBM, AMR",
"description": "音频文件"
},
"video": {
"extensions": "MP4, MOV, MPEG, MPGA",
"description": "视频文件"
},
"custom": {
"extensions": "",
"description": "其他文件类型"
}
}
if not allowed_file_types or len(allowed_file_types) == 0:
return "请提供有效的文件URL地址"
# 生成描述
descriptions = []
all_extensions = []
for file_type in allowed_file_types:
if file_type in file_type_details:
detail = file_type_details[file_type]
if detail["extensions"]:
descriptions.append(f"{detail['description']}({detail['extensions']})")
all_extensions.extend(detail["extensions"].split(", "))
else:
descriptions.append(detail["description"])
if descriptions:
if len(descriptions) == 1:
return f"请提供{descriptions[0]}的URL地址"
else:
return f"请提供文件URL地址支持的文件类型{' | '.join(descriptions)}"
else:
return "请提供有效的文件URL地址"
def process_user_input_form(user_input_form):
"""
处理Dify应用的用户输入表单转换为JSON Schema格式
支持的控件类型:
- text-input (object): 文本输入控件
* label (string): 控件展示标签名
* variable (string): 控件 ID
* required (bool): 是否必填
* default (string): 默认值
- paragraph (object): 段落文本输入控件
* label (string): 控件展示标签名
* variable (string): 控件 ID
* required (bool): 是否必填
* default (string): 默认值
- select (object): 下拉控件
* label (string): 控件展示标签名
* variable (string): 控件 ID
* required (bool): 是否必填
* default (string): 默认值
* options (array[string]): 选项值
- file (object): 文件上传控件 (支持复杂的文件处理逻辑)
参数:
user_input_form (array[object]): 用户输入表单配置
返回:
dict: 处理后的inputSchema字典符合JSON Schema规范
"""
# 初始化基础schema结构
inputSchema = {
"type": "object",
"properties": {},
"required": [],
}
# 如果没有用户输入表单配置,跳过处理
if not user_input_form or len(user_input_form) == 0:
pass
else:
# 遍历处理每个表单控件
for form_item in user_input_form:
# 检查form_item是否为None或空
if not form_item or not isinstance(form_item, dict):
continue
# 获取控件类型和配置信息
control_type = list(form_item.keys())[0]
logger.debug(f"处理控件类型: {control_type}")
control_config = form_item[control_type]
# 检查control_config是否为None
if not control_config or not isinstance(control_config, dict):
continue
# 提取控件基础属性
variable = control_config.get("variable", "")
label = control_config.get("label", "")
required = control_config.get("required", False)
default_value = control_config.get("default")
# 跳过没有variable的无效控件
if not variable:
continue
# 根据控件类型进行相应处理
property_schema = None
if control_type == "text-input":
# 文本输入控件处理
property_schema = {
"type": "string",
"description": label or f"文本输入字段: {variable}",
}
# 设置默认值
if default_value is not None:
property_schema["default"] = str(default_value)
elif control_type == "paragraph":
# 段落文本输入控件处理
property_schema = {
"type": "string",
"description": label or f"段落文本字段: {variable}",
"format": "textarea", # 标识为多行文本输入
}
# 设置默认值
if default_value is not None:
property_schema["default"] = str(default_value)
elif control_type == "select":
# 下拉控件处理
options = control_config.get("options", [])
property_schema = {
"type": "string",
"description": label or f"下拉选择字段: {variable}",
}
# 设置选项枚举值
if options and len(options) > 0:
property_schema["enum"] = options
# 设置默认值
if default_value is not None:
property_schema["default"] = str(default_value)
elif control_type in ["file", "file-list"]:
# 文件上传控件处理 - 简化版本仅支持remote_url
# 获取允许的文件类型
allowed_file_types = control_config.get("allowed_file_types", [])
# 生成动态的URL描述
url_description = generate_file_type_description(allowed_file_types)
file_schema = {
"type": "object",
"description": label or f"文件上传字段: {variable}",
"properties": {
"url": {
"type": "string",
"description": url_description
}
},
"required": ["url"]
}
# 判断是单文件还是多文件
# file-list 类型或 max_length > 1 都视为多文件
actual_type = control_config.get("type", control_type)
max_length = control_config.get("max_length", 1)
is_multi_file = actual_type == "file-list" or max_length > 1
if is_multi_file:
# 多文件上传场景
property_schema = {
"type": "array",
"description": label or f"多文件上传字段: {variable}",
"items": file_schema,
"maxItems": max_length,
"minItems": 1 if required else 0
}
else:
# 单文件上传场景
property_schema = file_schema
else:
# 未知控件类型的默认处理
property_schema = {
"type": "string",
"description": label or f"未知类型字段: {variable} (类型: {control_type})",
}
if default_value is not None:
property_schema["default"] = str(default_value)
# 将处理后的属性添加到schema中
if property_schema:
inputSchema["properties"][variable] = property_schema
# 处理必填字段约束
if required:
inputSchema["required"].append(variable)
return inputSchema
def extract_file_fields(user_input_form):
"""
从用户输入表单中提取所有type为file的字段信息
参数:
user_input_form (array[object]): 用户输入表单配置
返回:
list: 包含所有file类型字段信息的列表每个元素包含
- variable (str): 字段变量名
- label (str): 字段标签
- required (bool): 是否必填
- max_length (int): 最大文件数量
- allowed_file_types (list): 允许的文件类型
- allowed_file_upload_methods (list): 允许的上传方式
- allowed_file_extensions (list): 允许的文件扩展名
- is_list (bool): 是否为多文件类型 (file-list=True, file=False)
"""
file_fields = []
# 如果没有用户输入表单配置,返回空列表
if not user_input_form or len(user_input_form) == 0:
return file_fields
# 遍历处理每个表单控件
for form_item in user_input_form:
# 检查form_item是否为None或空
if not form_item or not isinstance(form_item, dict):
continue
# 获取控件类型和配置信息
control_type = list(form_item.keys())[0]
control_config = form_item[control_type]
# 检查control_config是否为None
if not control_config or not isinstance(control_config, dict):
continue
# 只处理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", ""),
"label": control_config.get("label", ""),
"required": control_config.get("required", False),
"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", []),
"is_list": is_list # 新增:标识是否为多文件类型
}
# 只添加有效的字段必须有variable
if file_field_info["variable"]:
file_fields.append(file_field_info)
return file_fields
if __name__ == "__main__":
# run_main()
result = process_user_input_form(data3["user_input_form"])
print("开始生成 Schema...", result)
# 保存到当前目录下的JSON文件
output_file = "process_user_input_form_output.json"
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
# print(f"结果已保存到: {output_file}")
# # 测试新的extract_file_fields方法
# print("\n=== 测试 extract_file_fields 方法 ===")
# # 测试data包含file字段
# file_fields_data = extract_file_fields(data["user_input_form"])
# print("data中的file字段:", file_fields_data)
# # 测试data2不包含file字段
# file_fields_data2 = extract_file_fields(data2["user_input_form"])
# print("data2中的file字段:", file_fields_data2)
# 测试data3包含file字段
# file_fields_data3 = extract_file_fields(data3["user_input_form"])
# print("data3中的file字段:", file_fields_data3)

View File

@@ -1,552 +0,0 @@
"""
统一日志配置模块
这个模块提供了整个项目的统一日志配置和管理功能,确保所有组件使用一致的日志格式和输出方式。
主要功能:
1. 统一的日志格式配置
2. 支持控制台和文件双重输出
3. 日志文件轮转管理
4. MCP模式下的特殊处理禁用控制台输出
5. 便捷的日志器获取接口
6. 丰富的日志工具函数
设计特点:
- 单例模式确保配置一致性
- 支持动态配置调整
- 异常安全的编码处理
- 详细的调试信息记录
作者: lzwcai
版本: 1.0.0
"""
import logging
import logging.handlers
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional
class LoggerConfig:
"""
日志配置管理器
这个类采用单例模式管理整个项目的日志配置。
它提供了统一的日志格式、文件轮转、编码处理等功能。
主要特性:
- 单例模式:确保全局日志配置一致
- 双重输出:同时支持控制台和文件输出
- 文件轮转:自动管理日志文件大小和数量
- 编码安全:正确处理中文字符
- MCP兼容支持MCP模式下的特殊需求
配置参数:
DEFAULT_LOG_LEVEL: 默认日志级别INFO
DEFAULT_LOG_FORMAT: 日志格式模板
DEFAULT_DATE_FORMAT: 时间格式
LOG_FILE_NAME: 日志文件名
MAX_LOG_SIZE: 单个日志文件最大大小10MB
BACKUP_COUNT: 保留的备份文件数量5个
"""
# ==================== 默认配置常量 ====================
# 默认日志级别INFO级别平衡了信息量和性能
DEFAULT_LOG_LEVEL = logging.INFO
# 默认日志格式:包含时间、模块名、级别、文件位置、消息内容
DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s"
# 默认时间格式:标准的年-月-日 时:分:秒格式
DEFAULT_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
# ==================== 日志文件配置 ====================
# 日志文件名:使用项目名称作为前缀
LOG_FILE_NAME = "lzwcai_demp_tool_server_dify_to_mcp_test.log"
# 单个日志文件最大大小10MB
MAX_LOG_SIZE = 10 * 1024 * 1024 # 10MB
# 保留的备份文件数量5个总共约50MB的日志存储
BACKUP_COUNT = 5
# ==================== 单例模式状态 ====================
# 初始化标志:确保只初始化一次
_initialized = False
# 日志文件路径:记录当前使用的日志文件路径
_log_file_path = None
@classmethod
def setup_logging(
cls,
log_level: int = DEFAULT_LOG_LEVEL,
log_file: Optional[str] = None,
console_output: bool = True,
file_output: bool = True
) -> str:
"""
设置项目统一日志配置
这是日志系统的核心初始化方法,负责配置整个项目的日志输出。
采用单例模式,确保在整个应用生命周期中只初始化一次。
配置流程:
1. 检查是否已经初始化(单例模式)
2. 确定日志文件路径(自动或手动指定)
3. 创建必要的目录结构
4. 配置根日志器和处理器
5. 设置日志格式化器
6. 添加控制台和文件处理器
7. 记录初始化信息
特殊处理:
- MCP模式下通常禁用控制台输出避免干扰stdio通信
- Windows系统下的UTF-8编码处理
- 日志文件的自动轮转管理
参数:
log_level: 日志级别DEBUG, INFO, WARNING, ERROR, CRITICAL
log_file: 日志文件路径None时使用默认路径
console_output: 是否输出到控制台MCP模式下通常为False
file_output: 是否输出到文件通常为True
返回:
str: 实际使用的日志文件路径
注意事项:
- 这个方法是线程安全的
- 重复调用会直接返回已配置的路径
- 日志文件会自动创建必要的目录
"""
# 单例模式检查:如果已经初始化,直接返回
if cls._initialized:
return cls._log_file_path
# ==================== 日志文件路径配置 ====================
if log_file is None:
# 自动确定日志文件路径:项目根目录/logs/ + 默认文件名
project_root = cls._get_project_root()
logs_dir = project_root / "logs"
logs_dir.mkdir(parents=True, exist_ok=True)
log_file = logs_dir / cls.LOG_FILE_NAME
else:
# 使用指定的日志文件路径
log_file = Path(log_file)
# 确保日志目录存在(递归创建)
log_file.parent.mkdir(parents=True, exist_ok=True)
cls._log_file_path = str(log_file)
# ==================== 根日志器配置 ====================
# 配置根日志器,这样可以捕获所有模块的日志
root_logger = logging.getLogger()
root_logger.setLevel(log_level)
# 清除根日志器上现有的处理器,避免重复配置
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
# ==================== 日志格式化器 ====================
# 创建统一的日志格式化器
formatter = logging.Formatter(
fmt=cls.DEFAULT_LOG_FORMAT, # 日志格式模板
datefmt=cls.DEFAULT_DATE_FORMAT # 时间格式
)
# ==================== 控制台处理器配置 ====================
if console_output:
# 控制台输出处理器支持彩色输出和UTF-8编码
import io
# 处理Windows系统的编码问题
if hasattr(sys.stdout, 'buffer'):
# 在Windows上强制使用UTF-8编码避免中文乱码
# errors='replace'确保即使有编码问题也不会崩溃
console_stream = io.TextIOWrapper(
sys.stdout.buffer,
encoding='utf-8',
errors='replace'
)
else:
# Unix/Linux系统通常默认支持UTF-8
console_stream = sys.stdout
# 创建控制台处理器
console_handler = logging.StreamHandler(console_stream)
console_handler.setLevel(log_level)
console_handler.setFormatter(formatter)
root_logger.addHandler(console_handler)
# ==================== 文件处理器配置 ====================
if file_output:
# 文件输出处理器,支持自动轮转
file_handler = logging.handlers.RotatingFileHandler(
filename=cls._log_file_path, # 日志文件路径
maxBytes=cls.MAX_LOG_SIZE, # 单文件最大大小
backupCount=cls.BACKUP_COUNT, # 备份文件数量
encoding='utf-8' # 文件编码
)
file_handler.setLevel(log_level)
file_handler.setFormatter(formatter)
root_logger.addHandler(file_handler)
# ==================== 初始化完成标记 ====================
# 标记为已初始化,防止重复配置
cls._initialized = True
# ==================== 记录初始化信息 ====================
# 获取当前模块的日志器并记录初始化信息
logger = logging.getLogger(__name__)
logger.info("=" * 80)
logger.info(f"日志系统初始化完成 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
logger.info(f"日志级别: {logging.getLevelName(log_level)}")
logger.info(f"日志文件: {cls._log_file_path}")
logger.info(f"控制台输出: {console_output}")
logger.info(f"文件输出: {file_output}")
logger.info(f"文件轮转: 最大{cls.MAX_LOG_SIZE // (1024*1024)}MB, 保留{cls.BACKUP_COUNT}个备份")
logger.info("=" * 80)
return cls._log_file_path
@classmethod
def _get_project_root(cls) -> Path:
"""
获取项目根目录
这个方法通过向上遍历目录树来查找项目根目录。
它会寻找常见的项目标识文件来确定根目录位置。
查找策略:
1. 从当前文件所在目录开始向上查找
2. 寻找项目标识文件pyproject.toml, setup.py, main.py
3. 找到任一标识文件的目录即为项目根目录
4. 如果都找不到,使用当前文件的上级目录作为备选
返回:
Path: 项目根目录的路径对象
注意事项:
- 这个方法假设项目结构相对标准
- 在特殊的部署环境中可能需要调整
- 备选方案确保总是返回有效路径
"""
# 从当前文件向上查找项目根目录
current_path = Path(__file__).parent
# 向上遍历目录树
while current_path.parent != current_path: # 避免到达文件系统根目录
# 检查常见的项目标识文件
if (current_path / "pyproject.toml").exists() or \
(current_path / "setup.py").exists() or \
(current_path / "main.py").exists():
return current_path
current_path = current_path.parent
# 备选方案:如果找不到标识文件,使用预设的相对路径
# 这个路径基于当前的项目结构utils -> src -> 项目根
return Path(__file__).parent.parent.parent
@classmethod
def get_logger(cls, name: str) -> logging.Logger:
"""
获取配置好的日志器
这是获取日志器的标准方法,确保返回的日志器使用统一的配置。
如果日志系统尚未初始化,会自动进行初始化。
参数:
name: 日志器名称,通常使用模块的 __name__ 变量
返回:
logging.Logger: 配置好的日志器实例
使用示例:
logger = LoggerConfig.get_logger(__name__)
logger.info("这是一条信息日志")
特性:
- 自动初始化首次调用时自动配置日志系统MCP模式下禁用控制台输出
- 层次化命名支持Python日志器的层次化命名
- 统一配置:所有日志器使用相同的格式和输出配置
"""
# 检查是否已初始化,未初始化则使用默认配置初始化
# 重要在MCP模式下禁用控制台输出避免干扰stdio通信
if not cls._initialized:
cls.setup_logging(console_output=False, file_output=True)
# 返回指定名称的日志器
return logging.getLogger(name)
# ==================== 日志工具方法 ====================
@classmethod
def log_function_entry(cls, logger: logging.Logger, func_name: str, **kwargs):
"""
记录函数入口日志
用于调试和性能分析,记录函数被调用时的参数信息。
通常在DEBUG级别输出不会影响生产环境的性能。
参数:
logger: 日志器实例
func_name: 函数名称
**kwargs: 函数参数(键值对形式)
使用示例:
LoggerConfig.log_function_entry(logger, "process_data", user_id=123, action="login")
"""
args_str = ", ".join([f"{k}={v}" for k, v in kwargs.items()])
logger.debug(f"进入函数 {func_name}({args_str})")
@classmethod
def log_function_exit(cls, logger: logging.Logger, func_name: str, result=None):
"""
记录函数出口日志
与log_function_entry配对使用记录函数执行完成和返回值。
有助于跟踪函数执行流程和调试返回值问题。
参数:
logger: 日志器实例
func_name: 函数名称
result: 函数返回值(可选)
使用示例:
LoggerConfig.log_function_exit(logger, "process_data", result={"status": "success"})
"""
if result is not None:
logger.debug(f"退出函数 {func_name},返回值: {result}")
else:
logger.debug(f"退出函数 {func_name}")
@classmethod
def log_api_request(cls, logger: logging.Logger, method: str, url: str, **kwargs):
"""
记录API请求日志
标准化API请求的日志记录包含HTTP方法、URL和请求参数。
有助于API调用的监控和调试。
参数:
logger: 日志器实例
method: HTTP方法GET, POST, PUT, DELETE等
url: 请求URL
**kwargs: 请求参数(可选)
使用示例:
LoggerConfig.log_api_request(logger, "POST", "https://api.example.com/users",
headers={"Authorization": "Bearer xxx"})
"""
logger.info(f"API请求 - {method} {url}")
if kwargs:
logger.debug(f"请求参数: {kwargs}")
@classmethod
def log_api_response(cls, logger: logging.Logger, status_code: int, response_time: float = None):
"""
记录API响应日志
记录API响应的状态码和响应时间用于性能监控和问题诊断。
参数:
logger: 日志器实例
status_code: HTTP状态码
response_time: 响应时间(秒,可选)
使用示例:
LoggerConfig.log_api_response(logger, 200, 0.156)
"""
if response_time:
logger.info(f"API响应 - 状态码: {status_code}, 响应时间: {response_time:.3f}s")
else:
logger.info(f"API响应 - 状态码: {status_code}")
@classmethod
def log_error_with_context(cls, logger: logging.Logger, error: Exception, context: str = ""):
"""
记录带上下文的错误日志
提供丰富的错误信息记录,包含异常类型、错误消息、上下文信息和详细堆栈。
这是错误处理的标准方法。
参数:
logger: 日志器实例
error: 异常对象
context: 错误发生的上下文描述(可选)
使用示例:
try:
risky_operation()
except Exception as e:
LoggerConfig.log_error_with_context(logger, e, "处理用户请求时")
"""
if context:
logger.error(f"错误发生在 {context}: {type(error).__name__}: {str(error)}")
else:
logger.error(f"错误: {type(error).__name__}: {str(error)}")
# 记录详细的异常堆栈信息仅在DEBUG级别显示
logger.debug("错误详情:", exc_info=True)
# ==================== 便捷函数 ====================
def get_logger(name: str) -> logging.Logger:
"""
获取日志器的便捷函数
这是LoggerConfig.get_logger的简化版本提供更简洁的调用方式。
推荐在模块级别使用这个函数获取日志器。
参数:
name: 日志器名称,通常使用 __name__
返回:
logging.Logger: 配置好的日志器实例
使用示例:
logger = get_logger(__name__)
"""
return LoggerConfig.get_logger(name)
def setup_logging(**kwargs) -> str:
"""
设置日志的便捷函数
这是LoggerConfig.setup_logging的简化版本支持所有相同的参数。
参数:
**kwargs: 传递给LoggerConfig.setup_logging的所有参数
返回:
str: 日志文件路径
使用示例:
log_file = setup_logging(log_level=logging.DEBUG, console_output=False)
"""
return LoggerConfig.setup_logging(**kwargs)
# ==================== 装饰器 ====================
def log_function_calls(logger: Optional[logging.Logger] = None):
"""
函数调用日志装饰器
这个装饰器自动记录函数的调用和返回,包括参数和返回值。
主要用于调试和性能分析在生产环境中通常设置为DEBUG级别。
特性:
- 自动记录函数入口和出口
- 记录函数参数kwargs
- 记录返回值
- 自动处理异常并记录错误上下文
- 支持自定义日志器或自动获取
参数:
logger: 可选的日志器实例None时自动获取函数所在模块的日志器
返回:
装饰器函数
使用示例:
@log_function_calls()
def process_user_data(user_id, action="login"):
# 函数实现
return {"status": "success"}
# 或者指定日志器
@log_function_calls(logger=my_logger)
def another_function():
pass
注意事项:
- 会记录所有kwargs参数注意不要记录敏感信息
- 返回值也会被记录,大对象可能影响性能
- 异常会被重新抛出,不会被吞掉
"""
def decorator(func):
def wrapper(*args, **kwargs):
nonlocal logger
# 如果没有提供日志器,自动获取函数所在模块的日志器
if logger is None:
logger = get_logger(func.__module__)
func_name = func.__name__
# 记录函数入口只记录kwargs避免记录过多信息
LoggerConfig.log_function_entry(logger, func_name, **kwargs)
try:
# 执行原函数
result = func(*args, **kwargs)
# 记录函数出口和返回值
LoggerConfig.log_function_exit(logger, func_name, result)
return result
except Exception as e:
# 记录异常信息并重新抛出
LoggerConfig.log_error_with_context(logger, e, f"函数 {func_name}")
raise
return wrapper
return decorator
# ==================== 测试代码 ====================
if __name__ == "__main__":
"""
日志配置测试代码
这个测试代码演示了日志系统的基本功能,包括:
1. 日志系统初始化
2. 不同级别的日志输出
3. 日志文件路径获取
4. 装饰器功能测试
运行方式:
python -m src.utils.logger_config
"""
# 初始化日志系统DEBUG级别同时输出到控制台和文件
log_file = setup_logging(log_level=logging.DEBUG)
test_logger = get_logger(__name__)
test_logger.info("开始测试日志配置...")
# 测试不同级别的日志输出
test_logger.debug("这是一个调试消息 - 用于开发调试")
test_logger.info("这是一个信息消息 - 记录重要信息")
test_logger.warning("这是一个警告消息 - 提醒注意事项")
test_logger.error("这是一个错误消息 - 记录错误情况")
# 测试工具方法
LoggerConfig.log_api_request(test_logger, "GET", "https://api.example.com/test")
LoggerConfig.log_api_response(test_logger, 200, 0.123)
# 测试装饰器
@log_function_calls()
def test_function(param1, param2="default"):
"""测试函数"""
return {"result": "success", "param1": param1}
# 调用测试函数
result = test_function("test_value", param2="custom")
# 输出日志文件位置
test_logger.info(f"日志文件位置: {log_file}")
test_logger.info("日志配置测试完成!")

View File

@@ -1,153 +0,0 @@
import os
import sys
from typing import Dict, Any, Optional
# 导入翻译函数
from .translator import translate
class TranslationService:
"""翻译服务类,用于处理各种翻译需求"""
@staticmethod
def create_prompt(
content: str,
target_lang: str,
use_case: str,
style: str,
prompt_type: str = "general",
keep_terms_desc: str = "核心术语",
) -> str:
"""
创建翻译提示
Args:
content: 待翻译内容
target_lang: 目标语言
use_case: 使用场景
style: 翻译风格
prompt_type: 提示类型,可选值: "general", "tool_name", "tool_description"
keep_terms_desc: 保留术语的描述
Returns:
str: 格式化的翻译提示
"""
if prompt_type == "tool_name":
keep_terms_desc = "核心术语(如 小写,词语需要用下划线连接)"
elif prompt_type == "tool_description":
keep_terms_desc = "核心术语(这是一段话)"
return f"""
角色:专业本地化翻译专家
任务:将以下内容翻译为{target_lang}(目标用途:{use_case}
要求:
1. 仅返回译文,不含解释或原文;
2. 保留{keep_terms_desc}
3. 符合{style}风格;
4. 特殊符号保持原样。
示例输出格式:
Translated Text
待翻译内容:
{content}
"""
@staticmethod
def translate_text(
content: str,
target_lang: str,
use_case: str = "",
style: str = "正式且符合技术品牌调性",
prompt_type: str = "general",
) -> Dict[str, Any]:
"""
翻译文本
Args:
content: 待翻译内容
target_lang: 目标语言
use_case: 使用场景,默认为空
style: 翻译风格,默认为"正式且符合技术品牌调性"
prompt_type: 提示类型,可选值: "general", "tool_name", "tool_description"
Returns:
Dict: 包含翻译结果的字典
"""
prompt = TranslationService.create_prompt(
content=content,
target_lang=target_lang,
use_case=use_case,
style=style,
prompt_type=prompt_type,
)
try:
result = translate(prompt, target_lang)
return result
except Exception as e:
print(f"翻译出错: {str(e)}")
return {"translated_text": "", "error": str(e)}
@staticmethod
def translate_tool_name(
name: str,
target_lang: str = "英语",
use_case: str = "工具名称",
style: str = "正式且符合技术品牌调性,大模型能理解",
) -> str:
"""
翻译工具名称的便捷方法
Returns:
str: 翻译后的工具名称
"""
result = TranslationService.translate_text(
content=name,
target_lang=target_lang,
use_case=use_case,
style=style,
prompt_type="tool_name",
)
return result.get("translated_text", "")
@staticmethod
def translate_tool_description(
description: str,
target_lang: str = "英语",
use_case: str = "工具描述",
style: str = "正式且符合技术品牌调性,大模型能理解",
) -> str:
"""
翻译工具描述的便捷方法
Returns:
str: 翻译后的工具描述
"""
result = TranslationService.translate_text(
content=description,
target_lang=target_lang,
use_case=use_case,
style=style,
prompt_type="tool_description",
)
return result.get("translated_text", "")
def translation_example():
"""翻译功能使用示例"""
# 示例1: 翻译工具名称
tool_name = "万川AI新媒体平台【测试环境】"
translated_name = TranslationService.translate_tool_name(tool_name)
print(f"工具名称翻译: {translated_name}")
# 示例2: 翻译工具描述
description = "21日辛柏青发布讣告宣布妻子朱媛媛抗癌五年后离世。此前在一次路演现场当观众问及朱媛媛时辛柏青2秒停顿藏着"
translated_desc = TranslationService.translate_tool_description(description)
print(f"工具描述翻译: {translated_desc}")
if __name__ == "__main__":
translation_example()

View File

@@ -1,64 +0,0 @@
import os
import requests
import json
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# ========== 模型相关 ==========
# 从.env文件获取模型API配置
BASE_URL = os.getenv("BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1")
API_KEY = os.getenv("OPENAI_API_KEY", "sk-c5a912a6bc8e4c9cbdbdf68232352a03")
TEMPERATURE = float(os.getenv("MODEL_TEMPERATURE", "0.7"))
def translate(content, target_language):
"""
翻译文本内容到目标语言
:param content: 要翻译的内容
:param target_language: 目标语言,如'en'(英语), 'zh'(中文), 'ja'(日语), 'fr'(法语)等
:return: 翻译后的内容,如果翻译失败则返回原文和错误信息
"""
if not content or not target_language:
return {"error": "内容或目标语言不能为空", "translated_text": content}
# 确保API密钥已设置
if not API_KEY:
return {"error": "API密钥未设置请检查.env文件", "translated_text": content}
try:
# 构建API请求头
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}",
}
# 构建翻译提示
prompt = f"请将以下内容翻译成{target_language},只返回翻译结果,不要包含任何解释或原文:\n\n{content}"
# 构建API请求体
data = {
"model": "qwen-max", # 使用通义千问模型,可以根据实际需要更改
"messages": [{"role": "user", "content": prompt}],
"temperature": TEMPERATURE,
}
# 发送API请求
response = requests.post(
f"{BASE_URL}/chat/completions", headers=headers, json=data
)
# 解析响应
if response.status_code == 200:
result = response.json()
translated_text = result["choices"][0]["message"]["content"].strip()
return {"translated_text": translated_text}
else:
error_message = (
f"翻译失败,状态码: {response.status_code}, 响应: {response.text}"
)
return {"error": error_message, "translated_text": content}
except Exception as e:
return {"error": f"翻译过程中发生错误: {str(e)}", "translated_text": content}

View File

@@ -1,461 +0,0 @@
"""
Dify文件上传工具 - 优化版
主要功能:
- 从URL下载文件并自动上传到Dify API
- 支持常见文件类型JPG、PNG、GIF、PDF、DOCX、TXT等
- 自动处理文件大小检查、MIME类型识别、临时文件清理
使用方法:
from upload_file import upload_file_from_url
result = upload_file_from_url(
file_url="http://example.com/image.jpg",
base_url="http://192.168.2.236:3001/v1",
api_key="app-QdfDKqHAI3dlB6tvnibuh6rv"
)
file_id = result['id'] # 获取上传后的文件ID
"""
import os
import tempfile
import requests
import logging
from urllib.parse import urlparse, unquote
from typing import Optional
# 获取模块级别的logger避免影响全局日志配置
logger = logging.getLogger(__name__)
# 常用MIME类型映射
MIME_TYPE_MAP = {
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
'.webp': 'image/webp',
'.svg': 'image/svg+xml',
'.pdf': 'application/pdf',
'.txt': 'text/plain',
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
}
def download_file_from_url(
url: str,
download_dir: Optional[str] = None,
filename: Optional[str] = None,
timeout: int = 30,
max_retries: int = 3
) -> str:
"""
从URL下载文件到本地并返回文件路径
Args:
url: 文件的URL地址
download_dir: 下载目录如果为None则使用系统临时目录
filename: 指定文件名如果为None则从URL中提取
timeout: 请求超时时间(秒)
max_retries: 最大重试次数
Returns:
str: 下载后的本地文件路径
Raises:
Exception: 下载失败时抛出异常
"""
# 设置下载目录
if download_dir is None:
download_dir = tempfile.gettempdir()
# 确保下载目录存在
os.makedirs(download_dir, exist_ok=True)
# 提取文件名
if filename is None:
filename = _extract_filename_from_url(url)
# 构建完整的文件路径
file_path = os.path.join(download_dir, filename)
# 下载文件(带重试机制)
for attempt in range(max_retries):
try:
logger.info(f"正在下载文件: {url} (尝试 {attempt + 1}/{max_retries})")
# 发送GET请求下载文件
response = requests.get(url, timeout=timeout, stream=True)
response.raise_for_status()
# 写入文件
with open(file_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
# 验证文件是否下载成功
if os.path.exists(file_path) and os.path.getsize(file_path) > 0:
logger.info(f"文件下载成功: {file_path} (大小: {os.path.getsize(file_path)} 字节)")
return file_path
else:
raise Exception("下载的文件为空或不存在")
except requests.exceptions.Timeout:
error_msg = f"请求超时 (超过 {timeout} 秒)"
logger.warning(f"{error_msg}, 尝试 {attempt + 1}/{max_retries}")
if attempt == max_retries - 1:
raise Exception(f"下载失败:{error_msg}")
except requests.exceptions.RequestException as e:
error_msg = f"请求异常: {str(e)}"
logger.warning(f"{error_msg}, 尝试 {attempt + 1}/{max_retries}")
if attempt == max_retries - 1:
raise Exception(f"下载失败:{error_msg}")
except Exception as e:
error_msg = f"下载过程中发生错误: {str(e)}"
logger.warning(f"{error_msg}, 尝试 {attempt + 1}/{max_retries}")
if attempt == max_retries - 1:
raise Exception(f"下载失败:{error_msg}")
raise Exception("下载失败:已达到最大重试次数")
def _extract_filename_from_url(url: str) -> str:
"""
从URL中提取文件名
Args:
url: 文件URL
Returns:
str: 提取的文件名
"""
try:
# 解析URL
parsed_url = urlparse(url)
path = unquote(parsed_url.path)
# 从路径中提取文件名
filename = os.path.basename(path)
# 如果没有找到文件名或文件名为空,使用默认名称
if not filename or filename == '/':
filename = "downloaded_file"
# 移除查询参数(如果文件名中包含)
if '?' in filename:
filename = filename.split('?')[0]
return filename
except Exception as e:
logger.warning(f"无法从URL提取文件名: {str(e)}, 使用默认文件名")
return "downloaded_file"
def upload_file_to_dify(
file_path: str,
base_url: str,
api_key: str,
user: str = "default_user",
verify_ssl: bool = False
) -> dict:
"""
上传文件到Dify API
Args:
file_path: 本地文件路径
base_url: Dify API基础URL (例如: http://192.168.2.236:3001/v1)
api_key: API密钥
user: 用户标识
verify_ssl: 是否验证SSL证书
Returns:
dict: 上传响应结果
Raises:
Exception: 上传失败时抛出异常
"""
# 检查文件是否存在
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件不存在: {file_path}")
# 检查文件大小
file_size = os.path.getsize(file_path)
file_size_mb = file_size / (1024 * 1024)
logger.info(f"准备上传文件: {file_path} (大小: {file_size_mb:.2f} MB)")
# 检查文件大小是否超过限制
if file_size_mb > 10:
logger.warning(f"文件大小 {file_size_mb:.2f} MB 可能超过服务器限制 (10 MB)")
upload_url = f"{base_url}/files/upload"
headers = {"Authorization": f"Bearer {api_key}"}
try:
with open(file_path, "rb") as f:
# 获取文件扩展名和MIME类型
file_ext = os.path.splitext(file_path)[1].lower()
mime_type = MIME_TYPE_MAP.get(file_ext, "application/octet-stream")
# 构建文件上传数据
files = {"file": (os.path.basename(file_path), f, mime_type)}
data = {"user": user}
# 发送上传请求
response = requests.post(
upload_url,
headers=headers,
files=files,
data=data,
verify=verify_ssl
)
# 检查响应
if response.status_code == 201:
result = response.json()
logger.info(f"文件上传成功: {result.get('name', 'unknown')} (ID: {result.get('id', 'unknown')})")
return result
else:
error_msg = f"上传失败 (状态码: {response.status_code}): {response.text}"
logger.error(error_msg)
raise requests.exceptions.HTTPError(error_msg)
except requests.exceptions.RequestException as e:
logger.error(f"上传文件请求失败: {str(e)}")
raise
except Exception as e:
logger.error(f"上传文件失败: {str(e)}")
raise
def check_app_config(base_url: str, api_key: str):
"""
检查Dify应用的文件上传配置
Args:
base_url: Dify API基础URL
api_key: API密钥
"""
try:
# 获取应用参数配置
config_url = f"{base_url}/parameters"
headers = {"Authorization": f"Bearer {api_key}"}
response = requests.get(config_url, headers=headers, verify=False)
response.raise_for_status()
config = response.json()
logger.info(f"应用配置获取成功")
# 检查文件上传配置
file_upload = config.get("file_upload", {})
if file_upload.get("enabled", False):
logger.info("✓ 文件上传功能已启用")
logger.info(f" - 允许的文件类型: {file_upload.get('allowed_file_types', [])}")
logger.info(f" - 允许的文件扩展名: {file_upload.get('allowed_file_extensions', [])}")
logger.info(f" - 文件大小限制: {file_upload.get('fileUploadConfig', {}).get('image_file_size_limit', 'N/A')} MB")
else:
logger.warning("✗ 文件上传功能未启用")
return config
except Exception as e:
logger.error(f"检查应用配置失败: {str(e)}")
return None
def test_download_and_upload():
"""
测试下载和上传功能的示例
"""
# 测试用的文件URL
file_url = "http://192.168.2.236:9000/lzwcai/upload/2025-07-29/34b28da03f3c43b0921ba1b76857bbc0/34b28da03f3c43b0921ba1b76857bbc0.JPG?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minio%2F20250729%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250729T075242Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=80f58d37c3bd52fb2b25efa36b5df0d73c8da7c773e7a7066dd6563710c619d6"
# 上传API配置
base_url = "http://192.168.2.236:3001/v1"
upload_url = f"{base_url}/files/upload"
api_key = "app-QdfDKqHAI3dlB6tvnibuh6rv"
headers = {"Authorization": f"Bearer {api_key}"}
user = "abc-123"
try:
logger.info("开始测试下载和上传流程...")
# 0. 检查应用配置
logger.info("检查Dify应用配置...")
config = check_app_config(base_url, api_key)
if not config:
logger.error("无法获取应用配置,终止测试")
return
# 1. 下载文件
logger.info(f"正在下载文件: {file_url}")
file_path = download_file_from_url(file_url)
logger.info(f"文件下载成功,保存路径: {file_path}")
# 2. 上传文件
logger.info(f"正在上传文件到: {upload_url}")
# 检查文件大小
file_size = os.path.getsize(file_path)
file_size_mb = file_size / (1024 * 1024)
logger.info(f"文件大小: {file_size_mb:.2f} MB")
# 检查文件大小是否超过限制通常为10MB对于图片
if file_size_mb > 10:
logger.warning(f"文件大小 {file_size_mb:.2f} MB 可能超过服务器限制")
with open(file_path, "rb") as f:
# 获取文件扩展名来确定MIME类型
file_ext = os.path.splitext(file_path)[1].lower()
mime_type = "image/jpeg" if file_ext in ['.jpg', '.jpeg'] else "application/octet-stream"
# 构建文件上传数据指定正确的MIME类型
files = {"file": (os.path.basename(file_path), f, mime_type)}
data = {"user": user}
# 不要在headers中设置Content-Type让requests自动处理multipart/form-data
upload_headers = headers.copy()
if "Content-Type" in upload_headers:
del upload_headers["Content-Type"]
# 添加SSL验证跳过选项用于自签名证书
response = requests.post(upload_url, headers=upload_headers, files=files, data=data, verify=False)
# 打印详细的响应信息用于调试
logger.info(f"响应状态码: {response.status_code}")
logger.info(f"响应头: {response.headers}")
logger.info(f"响应内容: {response.text}")
response.raise_for_status()
result = response.json()
logger.info(f"文件上传成功,响应: {result}")
# 3. 清理临时文件
try:
os.remove(file_path)
logger.info(f"已清理临时文件: {file_path}")
except Exception as e:
logger.warning(f"清理临时文件失败: {str(e)}")
return result
except Exception as e:
logger.error(f"测试失败: {str(e)}")
raise
def upload_file_from_url(
file_url: str,
base_url: str,
api_key: str,
user: str = "default_user",
verify_ssl: bool = False
) -> dict:
"""
从URL下载文件并上传到Dify API - 一站式解决方案
这是一个优化后的方法只需要提供URL地址、base_url和api_key就能自动完成下载和上传。
支持常见的文件类型JPG、PNG、GIF、PDF、DOCX、TXT等。
自动处理文件大小检查、MIME类型识别、临时文件清理等。
Args:
file_url: 要下载的文件URL
base_url: Dify API基础URL (例如: http://192.168.2.236:3001/v1)
api_key: API密钥 (例如: app-QdfDKqHAI3dlB6tvnibuh6rv)
user: 用户标识 (可选,默认为 default_user)
verify_ssl: 是否验证SSL证书 (可选,默认为 False)
Returns:
dict: 上传成功后的结果包含文件ID、名称、大小等信息
示例: {
'id': 'a239b623-40a8-482c-859f-bb8368d5b1fe',
'name': 'example.jpg',
'size': 495240,
'extension': 'jpg',
'mime_type': 'image/jpeg',
'created_by': '92c4b250-e0e7-4123-900d-f5c2187679a2',
'created_at': 1753777420
}
使用示例:
result = upload_file_from_url(
file_url="http://example.com/image.jpg",
base_url="http://192.168.2.236:3001/v1",
api_key="app-QdfDKqHAI3dlB6tvnibuh6rv"
)
file_id = result['id'] # 获取上传后的文件ID
Raises:
Exception: 下载或上传失败时抛出异常
"""
temp_file_path = None
try:
logger.info(f"开始处理文件: {file_url}")
# 1. 下载文件到临时目录
temp_file_path = download_file_from_url(file_url)
# 2. 上传文件到Dify (复用已有的上传函数)
result = upload_file_to_dify(temp_file_path, base_url, api_key, user, verify_ssl)
return result
except Exception as e:
logger.error(f"处理文件失败: {str(e)}")
raise
finally:
# 清理临时文件
if temp_file_path and os.path.exists(temp_file_path):
try:
os.remove(temp_file_path)
logger.info(f"已清理临时文件: {temp_file_path}")
except Exception as e:
logger.warning(f"清理临时文件失败: {str(e)}")
def test_upload_functionality():
"""
测试文件上传功能的示例
注意:这个函数包含示例配置,实际使用时请替换为真实的配置
"""
# 示例配置 - 实际使用时请替换为真实的配置
file_url = "https://example.com/test-image.jpg" # 替换为实际的文件URL
base_url = "http://localhost:3001/v1" # 替换为实际的Dify API地址
api_key = "your-api-key-here" # 替换为实际的API密钥
try:
logger.info("=== 开始测试文件上传功能 ===")
# 检查应用配置
logger.info("检查Dify应用配置...")
config = check_app_config(base_url, api_key)
if not config:
logger.warning("无法获取应用配置,但继续测试上传功能")
# 调用上传方法
result = upload_file_from_url(file_url, base_url, api_key)
logger.info("=== 上传成功!结果如下 ===")
logger.info(f"文件ID: {result.get('id')}")
logger.info(f"文件名: {result.get('name')}")
logger.info(f"文件大小: {result.get('size')} 字节")
logger.info(f"文件类型: {result.get('mime_type')}")
logger.info(f"扩展名: {result.get('extension')}")
return result
except Exception as e:
logger.error(f"测试失败: {str(e)}")
raise
if __name__ == "__main__":
# 运行测试 - 注意需要先配置正确的URL和API密钥
print("警告:测试函数包含示例配置,请先修改为实际配置后再运行")
# test_upload_functionality() # 取消注释并配置后运行

View File

@@ -1,352 +0,0 @@
import requests
from abc import ABC
import logging
import json
from src.utils.logger_config import get_logger
# 导入 pypinyin 用于中文转拼音
try:
import pypinyin
except ImportError:
pypinyin = None
logging.warning("pypinyin 模块未安装,将使用简化的命名方式")
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):
"""
将中文名称转换为工具名称
处理逻辑:
1. 如果安装了 pypinyin将中文转换为拼音然后转为驼峰命名
2. 如果未安装 pypinyin将所有非字母数字字符替换为下划线
3. 所有符号都会被替换成下划线
示例:
"你好啊" -> "tool_NiHaoA" (有pypinyin)
"测试-工具" -> "tool_测试_工具" (无pypinyin)
"Hello World!" -> "tool_Hello_World_" (无pypinyin)
Args:
pinyin: 输入的字符串(可能包含中文、英文、符号等)
Returns:
str: 格式化后的工具名称,以 "tool_" 开头
"""
import re
if pypinyin is None:
# 如果 pypinyin 未安装,使用简化的命名方式
# 将所有非字母数字字符(包括空格、符号等)替换为下划线
cleaned = re.sub(r'[^\w]', '_', str(pinyin))
# 移除连续的下划线
cleaned = re.sub(r'_+', '_', cleaned)
# 移除首尾的下划线
cleaned = cleaned.strip('_')
return "tool_" + cleaned if cleaned else "tool_unnamed"
# 使用 pypinyin 转换中文为拼音
pinyin_list = pypinyin.lazy_pinyin(pinyin)
# 处理每个拼音单词
processed_words = []
for word in pinyin_list:
# 将所有非字母数字字符替换为下划线
cleaned_word = re.sub(r'[^\w]', '_', word)
# 移除连续的下划线
cleaned_word = re.sub(r'_+', '_', cleaned_word)
# 移除首尾的下划线
cleaned_word = cleaned_word.strip('_')
if cleaned_word:
# 首字母大写(驼峰命名)
processed_words.append(cleaned_word.capitalize())
# 拼接所有单词
result = "".join(processed_words) if processed_words else "Unnamed"
return "tool_" + result
class WorkflowDifyAPI(ABC):
def __init__(self, base_url: str, dify_app_sks: list, user="pp666"):
# dify configs
self.dify_base_url = base_url
self.dify_app_sks = dify_app_sks
self.user = user
# dify app infos
dify_app_infos = []
dify_app_params = []
dify_app_metas = []
for key in self.dify_app_sks:
dify_app_infos.append(self.get_app_info(key))
dify_app_params.append(self.get_app_parameters(key))
dify_app_metas.append(self.get_app_meta(key))
self.dify_app_infos = dify_app_infos
self.dify_app_params = dify_app_params
self.dify_app_metas = dify_app_metas
self.dify_app_names = [x["name"] for x in dify_app_infos]
def chat_message(
self,
api_key,
inputs={},
response_mode="streaming",
conversation_id=None,
userId="pp666",
files=None,
):
url = f"{self.dify_base_url}/workflows/run"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {
"inputs": inputs,
"response_mode": response_mode,
"user": userId,
}
logger.info("Sending data to Dify API: %s", data)
logger.info("Sending headers to Dify API: %s", headers)
logger.info("Sending url to Dify API: %s", url)
if conversation_id:
data["conversation_id"] = conversation_id
if files:
files_data = self.file_parameter_pretreatment(files)
if files_data and len(files_data) > 0:
data["inputs"]["files"] = files_data[0]
# For workflow API, we send files data in the JSON payload, not as multipart files
response = requests.post(
url, headers=headers, json=data, stream=response_mode == "streaming"
)
else:
response = requests.post(
url, headers=headers, json=data, stream=response_mode == "streaming"
)
logger.info(f"Response1:{data} {response.status_code} {response.reason}")
# Add debugging for error responses
if response.status_code != 200:
logger.error(f"API request failed with status {response.status_code}")
logger.error(f"Response content: {response.text}")
logger.error(f"Request data: {data}")
# 解析错误响应并抛出带有详细信息的异常
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():
if line:
if line.startswith(b"data:"):
try:
json_data = json.loads(line[5:].decode("utf-8"))
yield json_data
except json.JSONDecodeError:
logger.error(f"Error decoding JSON: {line}")
return stream_generator()
else:
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}"}
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):
from src.utils.upload_file import upload_file_from_url
base_url = self.dify_base_url
api_key = self.dify_app_sks[0]
return upload_file_from_url(file_url, base_url, api_key)
def file_parameter_pretreatment(self, files):
"""
文件参数预处理方法
传入的"files"数据结构是这样的: [
{
"type": "image",
"transfer_method": "remote_url",
"url": "http://example.com/image.jpg"
}
]
处理逻辑:
1. 遍历files列表中的每个文件对象
2. 对于transfer_method为"remote_url"的文件调用upload_file_remote_url方法
3. 将返回的对象的id字段存入原对象的upload_file_id字段
4. 设置transfer_method为"local_file"因为已经上传到Dify服务器
5. 返回处理好的files列表
Args:
files (list): 文件列表每个元素包含type、transfer_method、url等字段
Returns:
list: 处理后的文件列表每个文件对象包含upload_file_id和transfer_method字段
"""
if not files or not isinstance(files, list):
logger.warning("文件参数为空或格式不正确")
return files
processed_files = []
for file_obj in files:
# 创建文件对象的副本,避免修改原始数据
processed_file = file_obj.copy()
# 检查是否需要处理远程URL文件
if (processed_file.get("transfer_method") == "remote_url" and
processed_file.get("url")):
try:
logger.info(f"开始上传远程文件: {processed_file['url']}")
# 调用upload_file_remote_url方法下载文件并上传到Dify
upload_result = self.upload_file_remote_url(processed_file["url"])
# 将返回的对象的id存入upload_file_id字段
if upload_result and "id" in upload_result:
processed_file["upload_file_id"] = upload_result["id"]
# 修改transfer_method为local_file因为文件已经上传到Dify服务器
processed_file["transfer_method"] = "local_file"
# 移除url字段因为已经不需要了
processed_file.pop("url", None)
logger.info(f"文件上传成功 - ID: {upload_result['id']}, "
f"名称: {upload_result.get('name', 'N/A')}, "
f"大小: {upload_result.get('size', 'N/A')} bytes")
else:
logger.error(f"文件上传失败未获取到有效的文件ID响应: {upload_result}")
processed_file["upload_error"] = "未获取到有效的文件ID"
except Exception as e:
logger.error(f"文件上传过程中发生错误: {str(e)}", exc_info=True)
# 记录错误信息,但继续处理其他文件
processed_file["upload_error"] = str(e)
elif processed_file.get("transfer_method") == "local_file":
# 如果已经是local_file确保有upload_file_id
if not processed_file.get("upload_file_id"):
logger.warning("local_file类型的文件缺少upload_file_id字段")
processed_files.append(processed_file)
logger.info(f"文件预处理完成,共处理 {len(processed_files)} 个文件")
return processed_files
def stop_response(self, api_key, task_id, user="pp666"):
url = f"{self.dify_base_url}/chat-messages/{task_id}/stop"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
data = {"user": user}
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
return response.json()
def get_app_info(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/info"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
response_map = response.json()
# 翻译工具名称
tool_name = response_map.get("name")
if tool_name:
# translated_name = TranslationService.translate_tool_name(tool_name)
translated_name = pinyin_to_camel(tool_name)
response_map["name"] = translated_name
# 翻译工具描述
# tool_description = response_map.get("description")
# if tool_description:
# translated_description = TranslationService.translate_tool_description(
# tool_description
# )
# response_map["description"] = (
# f"{tool_description} ({translated_description})"
# )
return response_map
def get_app_parameters(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/parameters"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
logger.info(f"调用 /parameters API: {url}")
logger.info(f"请求头: {headers}")
logger.info(f"请求参数: {params}")
response = requests.get(url, headers=headers, params=params)
logger.info(f"/parameters API 响应状态码: {response.status_code}")
response.raise_for_status()
response_data = response.json()
logger.info(f"/parameters API 响应数据: {response_data}")
return response_data
def get_app_meta(self, api_key, user="pp666"):
url = f"{self.dify_base_url}/meta"
headers = {"Authorization": f"Bearer {api_key}"}
params = {"user": user}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json()
if __name__ == "__main__":
dify_api = WorkflowDifyAPI(
"https://ops.lzwcai.com/v1",
["app-ZmLuBlRmViseUdOonqLyNSku", "app-AHjfp8k4nawQSJi0us8x3J5Q"],
)
dify_api.upload_file_remote_url("http://192.168.2.236:9000/lzwcai/upload/2025-07-29/34b28da03f3c43b0921ba1b76857bbc0/34b28da03f3c43b0921ba1b76857bbc0.JPG?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minio%2F20250729%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250729T075242Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=80f58d37c3bd52fb2b25efa36b5df0d73c8da7c773e7a7066dd6563710c619d6")

View File

@@ -1,12 +0,0 @@
Metadata-Version: 2.4
Name: lzwcai-mcp-dyntoolapi
Version: 0.1.27
Summary: 基于FastMCP框架的动态API工具服务器自动将企业业务API配置转换为MCP协议工具支持多种传输方式、企业认证和参数验证为AI助手提供标准化的业务接口访问能力。
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: dynaconf>=3.2.11
Requires-Dist: httpx>=0.28.1
Requires-Dist: jinja2==3.1.6
Requires-Dist: mcp[cli]>=1.8.0
Requires-Dist: requests>=2.31.0
Requires-Dist: pypinyin>=0.54.0

View File

@@ -1,12 +0,0 @@
Metadata-Version: 2.4
Name: lzwcai-mcp-api-converter
Version: 0.2.0
Summary: 基于FastMCP框架的动态API工具服务器自动将企业业务API配置转换为MCP协议工具支持多种传输方式、企业认证和参数验证为AI助手提供标准化的业务接口访问能力。
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: dynaconf>=3.2.11
Requires-Dist: httpx>=0.28.1
Requires-Dist: jinja2==3.1.6
Requires-Dist: mcp[cli]>=1.8.0
Requires-Dist: requests>=2.31.0
Requires-Dist: pypinyin>=0.54.0

View File

@@ -1,25 +0,0 @@
pyproject.toml
setup.cfg
lzwcai_mcp_api_converter/__init__.py
lzwcai_mcp_api_converter.egg-info/PKG-INFO
lzwcai_mcp_api_converter.egg-info/SOURCES.txt
lzwcai_mcp_api_converter.egg-info/dependency_links.txt
lzwcai_mcp_api_converter.egg-info/entry_points.txt
lzwcai_mcp_api_converter.egg-info/requires.txt
lzwcai_mcp_api_converter.egg-info/top_level.txt
lzwcai_mcp_api_converter/src/__init__.py
lzwcai_mcp_api_converter/src/api_config.json
lzwcai_mcp_api_converter/src/create_mcp.py
lzwcai_mcp_api_converter/src/business/__init__.py
lzwcai_mcp_api_converter/src/business/business_util.py
lzwcai_mcp_api_converter/src/business/get_business_api.py
lzwcai_mcp_api_converter/src/core/__init__.py
lzwcai_mcp_api_converter/src/core/api_auth_service.py
lzwcai_mcp_api_converter/src/core/api_base.py
lzwcai_mcp_api_converter/src/core/core_server.py
lzwcai_mcp_api_converter/src/core/get_auth.py
lzwcai_mcp_api_converter/src/core/plugin_base.py
lzwcai_mcp_api_converter/src/util/__init__.py
lzwcai_mcp_api_converter/src/util/api_helper.py
lzwcai_mcp_api_converter/src/util/logger_config.py
lzwcai_mcp_api_converter/src/util/nested_value.py

View File

@@ -1,2 +0,0 @@
[console_scripts]
lzwcai-mcp-api-converter = lzwcai_mcp_api_converter.src.create_mcp:run_main

View File

@@ -1,6 +0,0 @@
dynaconf>=3.2.11
httpx>=0.28.1
jinja2==3.1.6
mcp[cli]>=1.8.0
requests>=2.31.0
pypinyin>=0.54.0

View File

@@ -1 +0,0 @@
lzwcai_mcp_api_converter

View File

@@ -1,37 +0,0 @@
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2025-12-30 11:48:23
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_api_converter\lzwcai_mcp_api_converter.log
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:277] - 开始初始化 MCP 服务器
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:116] - 配置模式: memory
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:124] - 使用内存模式加载配置(多租户支持)
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:135] - 使用环境变量提供的businessUuid: u9ua9ss2l8c
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:139] - 租户配置变量名: businessu9ua9ss2l8c
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:147] - 内存中没有租户 u9ua9ss2l8c 的配置,开始从业务平台获取...
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:168] - 从环境变量bizSysApiIds获取到API IDs: [1970386761072058369, 1970386761185304578, 1970386761583763457, 1970386761420185602]
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:175] - 调用get_business_api_config获取配置API IDs: [1970386761072058369, 1970386761185304578, 1970386761583763457, 1970386761420185602]
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.business.get_business_api - INFO - [get_business_api.py:193] - 开始获取 4 个API的详情...
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.business.get_business_api - INFO - [get_business_api.py:93] - 成功获取 4 个API详情
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.business.get_business_api - INFO - [get_business_api.py:197] - 开始映射为配置格式...
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.business.get_business_api - INFO - [get_business_api.py:158] - 成功映射 4 个API到配置格式
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.business.get_business_api - INFO - [get_business_api.py:159] - 服务名称: lzwcai_mcp_api_converter
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.business.get_business_api - INFO - [get_business_api.py:160] - 域名URL: https://erp.166bg.com/api
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.business.get_business_api - INFO - [get_business_api.py:161] - 描述: 定时任务列表、定时任务详情、任务列表、任务统计列表(按状态)
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.business.get_business_api - INFO - [get_business_api.py:200] - [SUCCESS] 成功生成API配置包含 4 个API
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:177] - 成功获取业务API配置包含 4 个API配置
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:181] - 配置已存储到内存变量: businessu9ua9ss2l8c
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:182] - 当前内存中共有 1 个租户配置
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:292] - 服务器配置 - 名称: lzwcai-mcp-dyntoolapi, 版本: 1.0.0
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.core.api_base - INFO - [api_base.py:262] - 开始处理 4 个API配置
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.core.api_base - INFO - [api_base.py:317] - API配置处理完成成功处理 4 个配置
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.core.api_base - INFO - [api_base.py:389] - ApiBase初始化完成共处理 4 个API配置
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:307] - API 基础服务初始化完成,共 4 个API配置
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:506] - 注册API工具插件
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:509] - API工具插件注册完成
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:779] - 配置文件监控功能已禁用如需启用请设置环境变量ENABLE_CONFIG_WATCH=true
2025-12-30 11:48:23 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:578] - 启动STDIO传输模式
2025-12-30 11:48:24 - lzwcai_mcp_api_converter.src.create_mcp - INFO - [create_mcp.py:391] - 返回工具列表,共 4 个工具

View File

@@ -1 +0,0 @@
lzwc19781970385781825785858token={"authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMTAwMDAwMDEiLCJsb2dpbl91c2VyX2tleSI6IjJmNmViMWVkYTk3MGRlNzI1OTM1YTczNzY5YWZmODJmZDE3MmFmMGIiLCJhYmJyIjoiXHU3MDc1XHU2Y2ZkXHU0ZTA3XHU1ZGRkIiwiYXVkIjoiIiwiZXhwIjoxNzY3MzQ4OTQxLCJpYXQiOjE3NjY3NDQxNDEsImlzcyI6IiIsImp0aSI6IjUyOTIyNzc0ZTdmZDA3MjZkNGEyY2FkMTgyYzEzNjM4IiwibmJmIjoxNzY2NzQ0MTQxLCJzdWIiOiIifQ.S8cvKtUfojJu0JvA1aPgd6H9y5ccd7XOa7UHMqZzn5w"}

Some files were not shown because too many files have changed in this diff Show More