Compare commits
6 Commits
fb61ae27cf
...
research-f
| Author | SHA1 | Date | |
|---|---|---|---|
| bf3274788b | |||
| d71458669a | |||
| 0d48341b73 | |||
| 557361632c | |||
| 9c597c9b0d | |||
| a1012e61bf |
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -1,2 +0,0 @@
|
||||
{
|
||||
}
|
||||
68
README.md
68
README.md
@@ -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
BIN
doct/模板.docx
Normal file
Binary file not shown.
BIN
doct/模板.pdf
Normal file
BIN
doct/模板.pdf
Normal file
Binary file not shown.
BIN
doct/首页和尾页.docx
Normal file
BIN
doct/首页和尾页.docx
Normal file
Binary file not shown.
BIN
doct/首页和尾页.pdf
Normal file
BIN
doct/首页和尾页.pdf
Normal file
Binary file not shown.
@@ -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
|
||||
@@ -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/)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[console_scripts]
|
||||
lzwcai-demp-tool-server-dify-to-mcp = src.create_mcp:run_main
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
src
|
||||
@@ -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()
|
||||
@@ -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"]
|
||||
@@ -1,4 +0,0 @@
|
||||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
Binary file not shown.
@@ -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)),
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
@@ -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}")
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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}")
|
||||
Binary file not shown.
@@ -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,
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
@@ -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()
|
||||
@@ -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}
|
||||
Binary file not shown.
Binary file not shown.
@@ -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()
|
||||
@@ -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
|
||||
@@ -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) |
|
||||
@@ -1,2 +0,0 @@
|
||||
# 此文件用于确保 logs 目录被 Git 跟踪
|
||||
# 日志文件会自动生成在此目录中
|
||||
@@ -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'}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[console_scripts]
|
||||
lzwcai-demp-tool-server-dify-to-mcp-test = src.create_mcp:run_main
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
src
|
||||
@@ -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()
|
||||
@@ -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"]
|
||||
@@ -1,4 +0,0 @@
|
||||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
Binary file not shown.
@@ -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)),
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
@@ -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}")
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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'
|
||||
Binary file not shown.
@@ -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,
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
@@ -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("日志配置测试完成!")
|
||||
@@ -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()
|
||||
@@ -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}
|
||||
@@ -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() # 取消注释并配置后运行
|
||||
Binary file not shown.
Binary file not shown.
@@ -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")
|
||||
@@ -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
|
||||
@@ -1,12 +0,0 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: lzwcai-mcp-api-converter
|
||||
Version: 0.2.5
|
||||
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
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[console_scripts]
|
||||
lzwcai-mcp-api-converter = lzwcai_mcp_api_converter.src.create_mcp:run_main
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
lzwcai_mcp_api_converter
|
||||
@@ -1,96 +0,0 @@
|
||||
2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================
|
||||
2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 17:57:40
|
||||
2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO
|
||||
2026-03-18 17:57:40 - 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
|
||||
2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False
|
||||
2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True
|
||||
2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份
|
||||
2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================
|
||||
2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================
|
||||
2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 17:57:52
|
||||
2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO
|
||||
2026-03-18 17:57:52 - 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
|
||||
2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False
|
||||
2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True
|
||||
2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份
|
||||
2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================
|
||||
2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================
|
||||
2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 17:58:11
|
||||
2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO
|
||||
2026-03-18 17:58:11 - 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
|
||||
2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False
|
||||
2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True
|
||||
2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份
|
||||
2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================
|
||||
2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================
|
||||
2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 17:58:59
|
||||
2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO
|
||||
2026-03-18 17:58:59 - 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
|
||||
2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False
|
||||
2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True
|
||||
2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份
|
||||
2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================
|
||||
2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================
|
||||
2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 17:59:12
|
||||
2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO
|
||||
2026-03-18 17:59:12 - 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
|
||||
2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False
|
||||
2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True
|
||||
2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份
|
||||
2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================
|
||||
2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================
|
||||
2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:04:50
|
||||
2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO
|
||||
2026-03-18 18:04:50 - 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
|
||||
2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False
|
||||
2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True
|
||||
2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份
|
||||
2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================
|
||||
2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================
|
||||
2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:06:12
|
||||
2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO
|
||||
2026-03-18 18:06:12 - 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
|
||||
2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False
|
||||
2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True
|
||||
2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份
|
||||
2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================
|
||||
2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================
|
||||
2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:10:29
|
||||
2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO
|
||||
2026-03-18 18:10:29 - 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
|
||||
2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False
|
||||
2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True
|
||||
2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份
|
||||
2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================
|
||||
2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================
|
||||
2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:10:40
|
||||
2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO
|
||||
2026-03-18 18:10:40 - 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
|
||||
2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False
|
||||
2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True
|
||||
2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份
|
||||
2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================
|
||||
2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================
|
||||
2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:11:20
|
||||
2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO
|
||||
2026-03-18 18:11:20 - 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
|
||||
2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False
|
||||
2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True
|
||||
2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份
|
||||
2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================
|
||||
2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================
|
||||
2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:13:17
|
||||
2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO
|
||||
2026-03-18 18:13:17 - 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
|
||||
2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False
|
||||
2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True
|
||||
2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份
|
||||
2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================
|
||||
2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================
|
||||
2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:16:52
|
||||
2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO
|
||||
2026-03-18 18:16:52 - 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
|
||||
2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False
|
||||
2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True
|
||||
2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份
|
||||
2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{}
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user