@agentunion/kite 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/cli.js +44 -5
- package/core/dependency_checker.py +250 -0
- package/core/env_checker.py +490 -0
- package/dependencies_lock.json +128 -0
- package/extensions/agents/assistant/server.py +33 -17
- package/extensions/channels/acp_channel/server.py +33 -17
- package/extensions/services/backup/entry.py +23 -16
- package/extensions/services/evol/auth_manager.py +443 -0
- package/extensions/services/evol/config.yaml +149 -0
- package/extensions/services/evol/config_loader.py +117 -0
- package/extensions/services/evol/entry.py +406 -0
- package/extensions/services/evol/evol_api.py +173 -0
- package/extensions/services/evol/evol_config.json5 +29 -0
- package/extensions/services/evol/migrate_tokens.py +122 -0
- package/extensions/services/evol/module.md +32 -0
- package/extensions/services/evol/pairing.py +250 -0
- package/extensions/services/evol/pairing_codes.jsonl +1 -0
- package/extensions/services/evol/relay.py +682 -0
- package/extensions/services/evol/relay_config.json5 +67 -0
- package/extensions/services/evol/routes/__init__.py +1 -0
- package/extensions/services/evol/routes/routes_management_ws.py +127 -0
- package/extensions/services/evol/routes/routes_rpc.py +89 -0
- package/extensions/services/evol/routes/routes_test.py +61 -0
- package/extensions/services/evol/server.py +875 -0
- package/extensions/services/evol/static/css/style.css +1200 -0
- package/extensions/services/evol/static/index.html +781 -0
- package/extensions/services/evol/static/index_evol.html +14 -0
- package/extensions/services/evol/static/js/app.js +6304 -0
- package/extensions/services/evol/static/js/auth.js +326 -0
- package/extensions/services/evol/static/js/dialog.js +285 -0
- package/extensions/services/evol/static/js/evol-app-fixed.js +50 -0
- package/extensions/services/evol/static/js/evol-app.js +1949 -0
- package/extensions/services/evol/static/js/evol-app.js.bak +1800 -0
- package/extensions/services/evol/static/js/kernel-client-example.js +228 -0
- package/extensions/services/evol/static/js/kernel-client.js +396 -0
- package/extensions/services/evol/static/js/main.js +141 -0
- package/extensions/services/evol/static/js/registry-tests.js +585 -0
- package/extensions/services/evol/static/js/stats.js +217 -0
- package/extensions/services/evol/static/js/token-manager.js +175 -0
- package/extensions/services/evol/static/pairing.html +248 -0
- package/extensions/services/evol/static/test_registry.html +262 -0
- package/extensions/services/evol/static/test_relay.html +462 -0
- package/extensions/services/evol/stats_manager.py +240 -0
- package/extensions/services/model_service/entry.py +23 -1
- package/extensions/services/proxy/.claude/settings.local.json +13 -0
- package/extensions/services/proxy/CHANGELOG_20260308.md +258 -0
- package/extensions/services/proxy/_fix_prints.py +133 -0
- package/extensions/services/proxy/_fix_prints2.py +87 -0
- package/extensions/services/proxy/agentcp/LICENCE +178 -0
- package/extensions/services/proxy/agentcp/README copy.md +85 -0
- package/extensions/services/proxy/agentcp/README.md +260 -0
- package/extensions/services/proxy/agentcp/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/agent.py +4 -0
- package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
- package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
- package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
- package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
- package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
- package/extensions/services/proxy/agentcp/base/client.py +112 -0
- package/extensions/services/proxy/agentcp/base/env.py +34 -0
- package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
- package/extensions/services/proxy/agentcp/base/log.py +98 -0
- package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
- package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
- package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
- package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/context/context.py +73 -0
- package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
- package/extensions/services/proxy/agentcp/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
- package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
- package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
- package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
- package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
- package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
- package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
- package/extensions/services/proxy/agentcp/hcp.py +299 -0
- package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
- package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
- package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
- package/extensions/services/proxy/agentcp/llm_server.py +172 -0
- package/extensions/services/proxy/agentcp/mermaid.py +210 -0
- package/extensions/services/proxy/agentcp/message.py +149 -0
- package/extensions/services/proxy/agentcp/metrics.py +256 -0
- package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
- package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
- package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
- package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
- package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
- package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
- package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
- package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
- package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
- package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
- package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
- package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
- package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
- package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
- package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
- package/extensions/services/proxy/agentcp/requirements.txt +7 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
- package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
- package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
- package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
- package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
- package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
- package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
- package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
- package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
- package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
- package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
- package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
- package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
- package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
- package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
- package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
- package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
- package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
- package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
- package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
- package/extensions/services/proxy/agentcp/workflow.py +203 -0
- package/extensions/services/proxy/console_auth.py +109 -0
- package/extensions/services/proxy/evol/__init__.py +1 -0
- package/extensions/services/proxy/evol/config.py +37 -0
- package/extensions/services/proxy/evol/http/__init__.py +1 -0
- package/extensions/services/proxy/evol/http/async_http.py +551 -0
- package/extensions/services/proxy/evol/log.py +28 -0
- package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
- package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
- package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +106 -0
- package/extensions/services/proxy/evol/presenter/configPresenter.py +1281 -0
- package/extensions/services/proxy/evol/presenter/userPresenter.py +477 -0
- package/extensions/services/proxy/evol/server/__init__.py +1 -0
- package/extensions/services/proxy/evol/server/claude_proxy_async.py +3430 -0
- package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
- package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
- package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
- package/extensions/services/proxy/evol/version.py +24 -0
- package/extensions/services/proxy/logs/websocket.log +260 -0
- package/extensions/services/proxy/main.py +240 -0
- package/extensions/services/proxy/requirements.txt +13 -0
- package/extensions/services/proxy/server.py +271 -0
- package/extensions/services/watchdog/entry.py +42 -16
- package/extensions/services/watchdog/module.md +1 -0
- package/extensions/services/watchdog/monitor.py +34 -4
- package/extensions/services/web/module.md +1 -1
- package/extensions/services/web/server.py +30 -18
- package/extensions/services/web/static/js/token-manager.js +10 -10
- package/kernel/entry.py +1 -1
- package/kernel/module.md +25 -1
- package/kernel/registry_store.py +2 -26
- package/kernel/rpc_router.py +36 -10
- package/kernel/server.py +106 -17
- package/kite_cli/commands/deps_install.py +67 -0
- package/kite_cli/commands/env_check.py +45 -0
- package/kite_cli/commands/prepare.py +49 -0
- package/kite_cli/commands/venv_setup.py +56 -0
- package/kite_cli/main.py +29 -1
- package/launcher/entry.py +306 -21
- package/launcher/module.md +9 -0
- package/launcher/module_scanner.py +11 -1
- package/main.py +4 -1
- package/package.json +8 -1
- package/python_version.json +4 -0
- package/requirements.txt +38 -0
- package/scripts/env-manager.js +328 -0
- package/scripts/python-env.js +79 -0
- package/scripts/scan_dependencies.py +461 -0
- package/scripts/setup-python-env.js +191 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# -*- coding:utf-8 -*-
|
|
2
|
+
# Copyright 2025 AgentUnion Inc.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
import asyncio
|
|
16
|
+
import json
|
|
17
|
+
import time
|
|
18
|
+
import traceback
|
|
19
|
+
import urllib.parse
|
|
20
|
+
|
|
21
|
+
import requests
|
|
22
|
+
from flask import Response
|
|
23
|
+
|
|
24
|
+
from agentcp.base.log import log_error, log_exception, log_info
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AttrDict(dict):
|
|
28
|
+
"""使用属性方式读取字典兼容 openai响应格式"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, *args, **kwargs):
|
|
31
|
+
super().__init__(*args, **kwargs)
|
|
32
|
+
# 递归转换所有字典类型子项
|
|
33
|
+
for key, value in self.items():
|
|
34
|
+
if isinstance(value, dict):
|
|
35
|
+
self[key] = AttrDict(value)
|
|
36
|
+
elif isinstance(value, list):
|
|
37
|
+
self[key] = [AttrDict(v) if isinstance(v, dict) else v for v in value]
|
|
38
|
+
|
|
39
|
+
def __getattr__(self, key):
|
|
40
|
+
if key in self:
|
|
41
|
+
return self[key]
|
|
42
|
+
raise AttributeError(f"{self.__class__.__name__}对象无属性{key}")
|
|
43
|
+
|
|
44
|
+
def model_dump(self, exclude_none: bool = True, **kwargs) -> dict:
|
|
45
|
+
"""兼容openai响应的 model_dump"""
|
|
46
|
+
|
|
47
|
+
def _serialize(obj):
|
|
48
|
+
if isinstance(obj, AttrDict):
|
|
49
|
+
return {k: _serialize(v) for k, v in obj.items() if not (exclude_none and v is None)}
|
|
50
|
+
elif isinstance(obj, list):
|
|
51
|
+
return [_serialize(item) for item in obj]
|
|
52
|
+
else:
|
|
53
|
+
return obj
|
|
54
|
+
|
|
55
|
+
return _serialize(self)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def format_date() -> str:
|
|
59
|
+
"""获取当前格式化时间"""
|
|
60
|
+
ts = time.time()
|
|
61
|
+
# 格式化输出(带毫秒)
|
|
62
|
+
formatted_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts))
|
|
63
|
+
millis = int(ts * 1000) % 1000
|
|
64
|
+
return f"{formatted_time}.{millis:03d}"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_message_type(messages: list) -> str:
|
|
68
|
+
"""获取消息类型"""
|
|
69
|
+
if len(messages) > 0:
|
|
70
|
+
msg = messages[0]
|
|
71
|
+
msg_type = msg.get("type")
|
|
72
|
+
return msg_type
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def parse_stream_url(url0: str) -> tuple:
|
|
77
|
+
"""
|
|
78
|
+
解析消息中流地址
|
|
79
|
+
如:
|
|
80
|
+
https://ts.agentunion.cn/api/text_stream/pull_text_stream?session_id=1831992075507204096&message_id=6
|
|
81
|
+
解析出独立的url和参数字典
|
|
82
|
+
"""
|
|
83
|
+
args = {}
|
|
84
|
+
array = url0.split("?")
|
|
85
|
+
if len(array) != 2:
|
|
86
|
+
return url0, args
|
|
87
|
+
url, args_str = array
|
|
88
|
+
for kv in args_str.split("&"):
|
|
89
|
+
arr = kv.split("=")
|
|
90
|
+
if len(arr) != 2:
|
|
91
|
+
continue
|
|
92
|
+
args[arr[0]] = arr[1]
|
|
93
|
+
return url, args
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_vaild_json(text):
|
|
97
|
+
try:
|
|
98
|
+
json_data = json.loads(text)
|
|
99
|
+
return json_data
|
|
100
|
+
except Exception:
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def fail_response(content) -> AttrDict:
|
|
105
|
+
# 构造失败默认结果(是否流式只是delta和message的区别)
|
|
106
|
+
return AttrDict(
|
|
107
|
+
{
|
|
108
|
+
"status": "error",
|
|
109
|
+
"code": 400,
|
|
110
|
+
"message": content,
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class LLMAgent:
|
|
116
|
+
def __init__(self, llm_agent, aid):
|
|
117
|
+
self.llm_agent_name = llm_agent # 大模型agent名称
|
|
118
|
+
self.aid = aid # 当前agent主体
|
|
119
|
+
self.msg = None # 当前agent主体
|
|
120
|
+
self.result = None
|
|
121
|
+
self.session_id = None # 当前会话id
|
|
122
|
+
self.result_type = (
|
|
123
|
+
None # 结果类型,如text, image, audio, video, file, form, add_friend, create_order, error, empt
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
async def chat_create(self, open_ai_message_body, trace_id: str = ""):
|
|
127
|
+
"""大模型agent对话"""
|
|
128
|
+
# 结果值为空
|
|
129
|
+
self.result = AttrDict({})
|
|
130
|
+
llm_message = {
|
|
131
|
+
"type": "llm",
|
|
132
|
+
"status": "success",
|
|
133
|
+
"timestamp": int(time.time() * 1000),
|
|
134
|
+
"content": open_ai_message_body,
|
|
135
|
+
"trace_id": trace_id,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# 3、发送消息并异步接收结果
|
|
139
|
+
async def reply_message_handler(reply_msg):
|
|
140
|
+
try:
|
|
141
|
+
# 解析大模型返回结果
|
|
142
|
+
msg_type, response = self.parse_message(reply_msg=reply_msg)
|
|
143
|
+
log_info(
|
|
144
|
+
f"[{format_date()}]: llm agent message parse result msg_type = {msg_type}, response = {response}"
|
|
145
|
+
)
|
|
146
|
+
self.result_type = msg_type
|
|
147
|
+
if msg_type == "error":
|
|
148
|
+
self.result = fail_response(response)
|
|
149
|
+
else:
|
|
150
|
+
self.result = response
|
|
151
|
+
except Exception as e:
|
|
152
|
+
self.result = fail_response(f"消息解析失败{str(e)}")
|
|
153
|
+
self.result_type = "error"
|
|
154
|
+
self.session_id = reply_msg.get("session_id", "")
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
# 3-1、向大模型agent发送消息
|
|
158
|
+
if self.session_id is None:
|
|
159
|
+
returned_session_id = self.aid.quick_send_message(
|
|
160
|
+
self.llm_agent_name, llm_message, lambda reply_msg: reply_message_handler(reply_msg)
|
|
161
|
+
)
|
|
162
|
+
# 捕获返回的session_id
|
|
163
|
+
if returned_session_id:
|
|
164
|
+
self.session_id = returned_session_id
|
|
165
|
+
else:
|
|
166
|
+
self.aid.add_message_handler(reply_message_handler, session_id=self.session_id)
|
|
167
|
+
self.aid.send_message(self.session_id, [self.llm_agent_name], llm_message)
|
|
168
|
+
# 3-2、异步等待结果(带超时)
|
|
169
|
+
# 4-2、轮询解析结果(最长等待10分钟)
|
|
170
|
+
timeout = 600 # 10分钟超时
|
|
171
|
+
start_time = time.time()
|
|
172
|
+
while len(self.result) == 0:
|
|
173
|
+
# log_info(f'reply_result = {self.result[1]}')
|
|
174
|
+
await asyncio.sleep(0.1) # 每0.1s检查一次
|
|
175
|
+
if time.time() - start_time > timeout:
|
|
176
|
+
return fail_response(f"服务商{self.aid.id}未响应,请检查网络或重启ModelGate客户端,如果问题依旧,请联系客服")
|
|
177
|
+
# 流式响应
|
|
178
|
+
if self.result_type == "text/event-stream":
|
|
179
|
+
return self.read_stream(self.result)
|
|
180
|
+
except Exception as e:
|
|
181
|
+
log_exception(f"{format_date()}消息处理异常: {str(e)}")
|
|
182
|
+
traceback.format_exc()
|
|
183
|
+
return fail_response(f"消息处理异常{str(e)}")
|
|
184
|
+
return self.result
|
|
185
|
+
|
|
186
|
+
def read_stream(self, content):
|
|
187
|
+
url = ""
|
|
188
|
+
try:
|
|
189
|
+
# 增加URL参数验证
|
|
190
|
+
url = content + "&agent_id=" + self.aid.id
|
|
191
|
+
target_response = requests.get(url, stream=True, verify=False, timeout=(60, 600), proxies={}) # 连接超时60秒,读取超时10分钟
|
|
192
|
+
|
|
193
|
+
def generate():
|
|
194
|
+
try:
|
|
195
|
+
for line in target_response.iter_lines():
|
|
196
|
+
if line:
|
|
197
|
+
# 检查是否为合法 SSE 格式(避免污染数据流)
|
|
198
|
+
chunk = urllib.parse.unquote_plus(line.decode("utf-8"))
|
|
199
|
+
# chunk = get_vaild_json(decoded_url)
|
|
200
|
+
key, value = chunk.split(":", 1)
|
|
201
|
+
key = key.strip()
|
|
202
|
+
value = value.strip()
|
|
203
|
+
if key == "event" and value == "done":
|
|
204
|
+
break
|
|
205
|
+
# yield f"event: done\n\n".encode('utf-8')
|
|
206
|
+
else:
|
|
207
|
+
json_data = get_vaild_json(value)
|
|
208
|
+
if json_data is None:
|
|
209
|
+
continue
|
|
210
|
+
# print(f"[llm agent message] {json_data}")
|
|
211
|
+
yield f"data: {json.dumps(json_data)}\n\n".encode("utf-8")
|
|
212
|
+
except requests.exceptions.Timeout:
|
|
213
|
+
# 返回超时错误(SSE 格式)
|
|
214
|
+
error_msg = {"status": "error", "message": json.dumps({"error": "目标服务器响应超时"})}
|
|
215
|
+
yield f"data: {error_msg}\n\n".encode("utf-8") # 编码为字节流
|
|
216
|
+
except requests.exceptions.RequestException as e:
|
|
217
|
+
# 返回其他请求错误(SSE 格式)
|
|
218
|
+
error_msg = {"status": "error", "message": json.dumps({"error": f"目标服务器请求失败: {str(e)}"})}
|
|
219
|
+
yield f"data: {error_msg}\n\n".encode("utf-8") # 编码为字节流
|
|
220
|
+
except Exception as e:
|
|
221
|
+
# 捕获其他异常并返回错误(SSE 格式)
|
|
222
|
+
error_msg = {"status": "error", "message": json.dumps({"error": f"处理过程中发生错误: {str(e)}"})}
|
|
223
|
+
yield f"data: {error_msg}\n\n".encode("utf-8")
|
|
224
|
+
|
|
225
|
+
# 返回流式响应
|
|
226
|
+
return Response(generate(), content_type="text/event-stream", status=target_response.status_code)
|
|
227
|
+
except requests.exceptions.Timeout:
|
|
228
|
+
log_error(f"请求超时: {url}")
|
|
229
|
+
return fail_response("流连接超时")
|
|
230
|
+
except requests.exceptions.RequestException as e:
|
|
231
|
+
log_error(f"{format_date()}连接异常: {str(e)}")
|
|
232
|
+
import traceback
|
|
233
|
+
traceback.format_exc()
|
|
234
|
+
return fail_response(f"连接异常{traceback.format_exc()}")
|
|
235
|
+
|
|
236
|
+
def parse_message(self, reply_msg) -> tuple:
|
|
237
|
+
"""解析llm agent消息结果"""
|
|
238
|
+
# 读取消息中的llm响应体
|
|
239
|
+
msg_type = get_message_type(messages=self.aid.get_content_array_from_message(reply_msg))
|
|
240
|
+
content = self.aid.get_content_from_message(reply_msg, message_type=msg_type)
|
|
241
|
+
if msg_type == "error":
|
|
242
|
+
return msg_type, content
|
|
243
|
+
|
|
244
|
+
if msg_type == "text/event-stream":
|
|
245
|
+
return msg_type, content
|
|
246
|
+
|
|
247
|
+
# 解析大模型返回
|
|
248
|
+
content_dict = json.loads(content)
|
|
249
|
+
return msg_type, AttrDict(content_dict)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# 在Python文件开头明确指定编码声明
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Copyright 2025 AgentUnion Inc.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# from flask import Flask, request, Response, jsonify
|
|
17
|
+
# import threading
|
|
18
|
+
# import socket
|
|
19
|
+
# import time
|
|
20
|
+
|
|
21
|
+
# from .llm_agent_utils import LLMAgent,AttrDict
|
|
22
|
+
# from flask import jsonify, make_response
|
|
23
|
+
# import logging
|
|
24
|
+
# log = logging.getLogger('werkzeug')
|
|
25
|
+
# log.setLevel(logging.ERROR)
|
|
26
|
+
# app = Flask(__name__)
|
|
27
|
+
# # app.logger.disabled = True
|
|
28
|
+
# actual_port = 0
|
|
29
|
+
|
|
30
|
+
# llm_aid_app_key_map = {}
|
|
31
|
+
# llm_app_key_aid_map = {}
|
|
32
|
+
# is_running = False
|
|
33
|
+
|
|
34
|
+
# @app.route('/<llm_aid>/chat/completions', methods=['POST']) # 添加methods参数指定POST方法
|
|
35
|
+
# async def llm_request(llm_aid):
|
|
36
|
+
# # 获取请求头并打印
|
|
37
|
+
# headers = dict(request.headers)
|
|
38
|
+
# if request.is_json:
|
|
39
|
+
# body = request.get_json()
|
|
40
|
+
# else:
|
|
41
|
+
# body = request.form.to_dict()
|
|
42
|
+
# global llm_app_key_aid_map
|
|
43
|
+
# auth_str:str = headers.get("Authorization")
|
|
44
|
+
# llm_app_key = auth_str.replace("Bearer ","")
|
|
45
|
+
# aid = llm_app_key_aid_map.get(llm_app_key)
|
|
46
|
+
# if aid is None:
|
|
47
|
+
# return make_response(jsonify({"error": "Unauthorized"}), 401)
|
|
48
|
+
# llm_agent = LLMAgent(llm_agent=llm_aid, aid = aid)
|
|
49
|
+
# response = await llm_agent.chat_create(body)
|
|
50
|
+
# # print(response.get("status",""))
|
|
51
|
+
# if isinstance(response, AttrDict) and response.get("status","") == 'error':
|
|
52
|
+
# # 如果是错误状态,可以进行特殊处理,例如记录日志或返回自定义错误信息
|
|
53
|
+
# return make_response(jsonify({"error": response.get('message', "未知错误")}), response.get('code', 400))
|
|
54
|
+
# return response
|
|
55
|
+
|
|
56
|
+
# @app.route('/', defaults={'path': ''}, methods=['OPTIONS', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
|
|
57
|
+
# @app.route('/<path:path>', methods=['OPTIONS', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
|
|
58
|
+
# async def proxy(path):
|
|
59
|
+
# try:
|
|
60
|
+
# if request.method == 'OPTIONS':
|
|
61
|
+
# # 返回 CORS 预检响应头
|
|
62
|
+
# response = Response()
|
|
63
|
+
# response.headers['Access-Control-Allow-Origin'] = '*'
|
|
64
|
+
# response.headers['Access-Control-Allow-Methods'] = 'POST, OPTIONS'
|
|
65
|
+
# response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
|
|
66
|
+
# return response
|
|
67
|
+
# elif request.method == 'POST':
|
|
68
|
+
# # 这个最终来自路由服务器,即模型名称对应一个地址和key
|
|
69
|
+
# # 校验请求数据
|
|
70
|
+
# # data = request.get_json()
|
|
71
|
+
# try:
|
|
72
|
+
# #return llm_request(llm_aid)
|
|
73
|
+
# llm_aid = path.split('/')[0]
|
|
74
|
+
# return await llm_request(llm_aid)
|
|
75
|
+
# except Exception as e:
|
|
76
|
+
# return jsonify({"error": f"{path}"}), 502
|
|
77
|
+
# elif request.method == 'GET':
|
|
78
|
+
# return jsonify({"result": "服务访问正常"}), 200
|
|
79
|
+
# else:
|
|
80
|
+
# return jsonify({"error": "Method not allowed"}), 405
|
|
81
|
+
|
|
82
|
+
# except Exception as e:
|
|
83
|
+
# return jsonify({"error": "Internal server error", "details": str(e)}), 500
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def add_llm_aid(aid):
|
|
87
|
+
# global llm_aid_app_key_map, llm_app_key_aid_map
|
|
88
|
+
# import hashlib
|
|
89
|
+
# if aid.id in llm_aid_app_key_map:
|
|
90
|
+
# llm_app_key = llm_aid_app_key_map[aid.id]
|
|
91
|
+
# else:
|
|
92
|
+
# llm_app_key = str(int(time.time())+actual_port)
|
|
93
|
+
# llm_app_key = hashlib.sha256(llm_app_key.encode()).hexdigest()
|
|
94
|
+
# llm_aid_app_key_map[aid.id] = llm_app_key
|
|
95
|
+
# llm_app_key_aid_map[llm_app_key] = aid
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def add_llm_api_key(aid,llm_app_key):
|
|
100
|
+
# global llm_aid_app_key_map, llm_app_key_aid_map
|
|
101
|
+
# llm_aid_app_key_map[aid.id] = llm_app_key
|
|
102
|
+
# llm_app_key_aid_map[llm_app_key] = aid
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_base_url(aid,llm_aid):
|
|
107
|
+
# global actual_port
|
|
108
|
+
# # 获取实际分配的端口号
|
|
109
|
+
# return "http://127.0.0.1:"+str(actual_port)+"/"+llm_aid
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
def get_llm_api_key(aid_str:str):
|
|
113
|
+
# 获取实际分配的端口号
|
|
114
|
+
pass
|
|
115
|
+
# global llm_aid_app_key_map,llm_app_key_aid_map
|
|
116
|
+
# if aid_str not in llm_aid_app_key_map:
|
|
117
|
+
# import secrets,hashlib
|
|
118
|
+
# llm_app_key = secrets.token_hex(16)
|
|
119
|
+
# llm_app_key = hashlib.sha256(llm_app_key.encode()).hexdigest()
|
|
120
|
+
# llm_aid_app_key_map[aid_str] = llm_app_key
|
|
121
|
+
# return llm_aid_app_key_map[aid_str]
|
|
122
|
+
|
|
123
|
+
def llm_server_is_running():
|
|
124
|
+
# global is_running
|
|
125
|
+
# return is_running
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
def __run_server():
|
|
129
|
+
# 端口设为0让系统自动分配
|
|
130
|
+
pass
|
|
131
|
+
# try:
|
|
132
|
+
# global actual_port
|
|
133
|
+
# if actual_port == 0:
|
|
134
|
+
# sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
135
|
+
# sock.bind(('', 0))
|
|
136
|
+
# actual_port = sock.getsockname()[1]
|
|
137
|
+
# sock.close()
|
|
138
|
+
# app.run(host='127.0.0.1', port = actual_port, debug=False)
|
|
139
|
+
# else:
|
|
140
|
+
# app.run(host='127.0.0.1', port = actual_port, debug=False)
|
|
141
|
+
|
|
142
|
+
# global is_running
|
|
143
|
+
# is_running = True
|
|
144
|
+
# except Exception as e:
|
|
145
|
+
# is_running = False
|
|
146
|
+
# #print(f"Flask服务启动失败,请检查端口占用后,重启服务")
|
|
147
|
+
|
|
148
|
+
def run_server(debug:bool = False,port:int = 0,llm_aid_app_key_map_h = {},llm_app_key_aid_map_h = {}):
|
|
149
|
+
# 创建并启动子线程运行Flask服务
|
|
150
|
+
pass
|
|
151
|
+
# app.logger.disabled = (not debug)
|
|
152
|
+
# global actual_port,llm_aid_app_key_map,llm_app_key_aid_map
|
|
153
|
+
# try:
|
|
154
|
+
# actual_port = int(port)
|
|
155
|
+
# except (ValueError, TypeError):
|
|
156
|
+
# actual_port = 0 # 如果转换失败,设置为默认值0
|
|
157
|
+
# llm_aid_app_key_map = llm_aid_app_key_map_h
|
|
158
|
+
# llm_app_key_aid_map = llm_app_key_aid_map_h
|
|
159
|
+
# server_thread = threading.Thread(target=__run_server)
|
|
160
|
+
# server_thread.daemon = True # 设置为守护线程,主线程退出时会自动结束
|
|
161
|
+
# server_thread.start()
|
|
162
|
+
# 主线程可以继续执行其他任务
|
|
163
|
+
|
|
164
|
+
# 添加一个关闭服务器的路由
|
|
165
|
+
# @app.route('/shutdown', methods=['POST'])
|
|
166
|
+
def shutdown_server():
|
|
167
|
+
pass
|
|
168
|
+
# func = request.environ.get('werkzeug.server.shutdown')
|
|
169
|
+
# if func is None:
|
|
170
|
+
# return jsonify({"status": "error", "message": "Not running with Werkzeug Server"})
|
|
171
|
+
# func()
|
|
172
|
+
# return jsonify({"status": "success", "message": "Server shutting down..."})
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright 2025 AgentUnion Inc.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from agentcp.base.log import log_error, log_info
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Mermaid:
|
|
22
|
+
def __init__(self, content):
|
|
23
|
+
self.mermaid_code = content
|
|
24
|
+
self.graph_type = None
|
|
25
|
+
self.graph_direction = None # 新增:存储图的方向
|
|
26
|
+
self.nodes = []
|
|
27
|
+
self.node_dict = {}
|
|
28
|
+
self.node_styles = {}
|
|
29
|
+
self.edges = []
|
|
30
|
+
self.parse_mermaid()
|
|
31
|
+
|
|
32
|
+
log_info(f"图类型:{self.graph_type}")
|
|
33
|
+
log_info(f"总共{len(self.nodes)}个节点,节点列表如下:\n\t{self.nodes}")
|
|
34
|
+
for node in self.node_dict.items():
|
|
35
|
+
log_info(f"节点映射:{node}")
|
|
36
|
+
for edge in self.edges:
|
|
37
|
+
log_info(f"边关系:{edge}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_file(cls, dir_path, file_name):
|
|
42
|
+
return cls(cls.read_mermaid_code(dir_path, file_name))
|
|
43
|
+
def read_mermaid_code(dir_path: str, file_name: str):
|
|
44
|
+
file_path = Path(dir_path) / f"{file_name}.mmd"
|
|
45
|
+
absolute_path = file_path.absolute()
|
|
46
|
+
log_info(f"读取Mermaid文件,绝对路径: {absolute_path}")
|
|
47
|
+
if file_path.is_file():
|
|
48
|
+
try:
|
|
49
|
+
with file_path.open('r', encoding='utf-8') as f:
|
|
50
|
+
content = f.read()
|
|
51
|
+
content = content.replace(' ', '').replace(u'\xa0', u'')
|
|
52
|
+
return content
|
|
53
|
+
except Exception as e:
|
|
54
|
+
log_error(f"读取文件错误: {e}")
|
|
55
|
+
return ""
|
|
56
|
+
else:
|
|
57
|
+
log_error(f"文件 {file_path} 未找到")
|
|
58
|
+
return ""
|
|
59
|
+
|
|
60
|
+
def parse_mermaid(self):
|
|
61
|
+
if not self.mermaid_code:
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
# 检查图类型和方向
|
|
65
|
+
for line in self.mermaid_code.split('\n'):
|
|
66
|
+
line = line.strip()
|
|
67
|
+
if not line or line.startswith('%'):
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
if line.startswith('graph'):
|
|
71
|
+
parts = line.split()
|
|
72
|
+
if len(parts) >= 2:
|
|
73
|
+
self.graph_type = 'graph'
|
|
74
|
+
self.graph_direction = parts[1] # 存储方向 (TD, LR, BT, RL等)
|
|
75
|
+
break
|
|
76
|
+
elif line.startswith(('pie', 'sequenceDiagram', 'gantt', 'classDiagram', 'stateDiagram')):
|
|
77
|
+
self.graph_type = line.split()[0]
|
|
78
|
+
log_error(f"不支持 {self.graph_type} 类型的图")
|
|
79
|
+
return
|
|
80
|
+
else:
|
|
81
|
+
log_error("无法识别的图类型")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
if self.graph_type != 'graph':
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
# 解析内容
|
|
88
|
+
for line in self.mermaid_code.split('\n'):
|
|
89
|
+
line = line.strip()
|
|
90
|
+
if not line or line.startswith(('%', 'classDef', 'linkStyle', 'style', 'graph')):
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
# 解析节点
|
|
94
|
+
if (any(x in line for x in ['[', '(', '{']) and
|
|
95
|
+
not any(x in line for x in ['-->', '--', '->'])) or \
|
|
96
|
+
(not any(x in line for x in ['-->', '--', '->']) and
|
|
97
|
+
line not in self.node_dict and
|
|
98
|
+
not line.startswith('graph')):
|
|
99
|
+
self.parse_node(line)
|
|
100
|
+
|
|
101
|
+
# 解析边关系
|
|
102
|
+
if any(x in line for x in ['-->', '--', '->']):
|
|
103
|
+
self.parse_edge(line)
|
|
104
|
+
|
|
105
|
+
def parse_node(self, line):
|
|
106
|
+
"""解析节点定义"""
|
|
107
|
+
node_name = ''
|
|
108
|
+
node_desc = ''
|
|
109
|
+
style_class = ''
|
|
110
|
+
|
|
111
|
+
# 提取样式类 (:::xxx)
|
|
112
|
+
if ':::' in line:
|
|
113
|
+
parts = line.split(':::')
|
|
114
|
+
line = parts[0].strip()
|
|
115
|
+
style_class = parts[1].strip()
|
|
116
|
+
|
|
117
|
+
# 花括号格式: A{描述}
|
|
118
|
+
if '{' in line and '}' in line:
|
|
119
|
+
start = line.find('{')
|
|
120
|
+
end = line.find('}')
|
|
121
|
+
node_name = line[:start].strip()
|
|
122
|
+
node_desc = line[start + 1:end].strip('" ')
|
|
123
|
+
|
|
124
|
+
# 方括号格式: A["描述"]
|
|
125
|
+
elif '[' in line and ']' in line:
|
|
126
|
+
start = line.find('[')
|
|
127
|
+
end = line.find(']')
|
|
128
|
+
node_name = line[:start].strip()
|
|
129
|
+
node_desc = line[start + 1:end].strip('" ')
|
|
130
|
+
|
|
131
|
+
# 圆括号格式: A(描述)
|
|
132
|
+
elif '(' in line and ')' in line:
|
|
133
|
+
start = line.find('(')
|
|
134
|
+
end = line.find(')')
|
|
135
|
+
node_name = line[:start].strip()
|
|
136
|
+
node_desc = line[start + 1:end].strip('" ')
|
|
137
|
+
|
|
138
|
+
# 无描述节点: A
|
|
139
|
+
else:
|
|
140
|
+
node_name = line.strip()
|
|
141
|
+
node_desc = node_name
|
|
142
|
+
|
|
143
|
+
if node_name and node_name not in self.node_dict:
|
|
144
|
+
self.node_dict[node_name] = node_desc or node_name
|
|
145
|
+
self.nodes.append(self.node_dict[node_name])
|
|
146
|
+
if style_class:
|
|
147
|
+
self.node_styles[node_name] = style_class
|
|
148
|
+
|
|
149
|
+
def parse_edge(self, line):
|
|
150
|
+
"""解析边关系"""
|
|
151
|
+
line = line.replace(' ', '')
|
|
152
|
+
|
|
153
|
+
# 处理 A-->|描述|B 格式
|
|
154
|
+
if '-->|' in line and '|' in line[line.find('-->|') + 3:]:
|
|
155
|
+
arrow_pos = line.find('-->|')
|
|
156
|
+
desc_start = arrow_pos + 4
|
|
157
|
+
desc_end = line.find('|', desc_start)
|
|
158
|
+
source = line[:arrow_pos]
|
|
159
|
+
target = line[desc_end + 1:]
|
|
160
|
+
description = line[desc_start:desc_end]
|
|
161
|
+
self.edges.append((source, description, target))
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
# 处理 A-->B 格式
|
|
165
|
+
if '-->' in line:
|
|
166
|
+
parts = line.split('-->')
|
|
167
|
+
if len(parts) == 2:
|
|
168
|
+
self.edges.append((parts[0], '', parts[1]))
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
# 处理 A--描述-->B 格式
|
|
172
|
+
if '--' in line and '-->' in line:
|
|
173
|
+
arrow_pos = line.find('-->')
|
|
174
|
+
source = line[:line.find('--')]
|
|
175
|
+
target = line[arrow_pos + 3:]
|
|
176
|
+
description = line[line.find('--') + 2:arrow_pos]
|
|
177
|
+
self.edges.append((source, description, target))
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
# 处理 A->B 格式
|
|
181
|
+
if '->' in line:
|
|
182
|
+
parts = line.split('->')
|
|
183
|
+
if len(parts) == 2:
|
|
184
|
+
self.edges.append((parts[0], '', parts[1]))
|
|
185
|
+
return
|
|
186
|
+
# 示例使用
|
|
187
|
+
if __name__ == "__main__":
|
|
188
|
+
#mermaid = Mermaid.from_file('.', 'workflow')
|
|
189
|
+
mmd = """
|
|
190
|
+
graph TD
|
|
191
|
+
%% ===== 节点定义 =====
|
|
192
|
+
Z[用户交互层User]:::user
|
|
193
|
+
A[任务拆解/推进/退出]:::Planner
|
|
194
|
+
B(个人助手):::PA
|
|
195
|
+
D[Agent选择 AgentSelector]:::selector
|
|
196
|
+
F[协作Agents]:::action
|
|
197
|
+
G[单步任务执行]:::process
|
|
198
|
+
Z -->|自然语言任务| B
|
|
199
|
+
A -->|任务推进| D
|
|
200
|
+
B -->|简单任务直接执行交付或复杂任务执行完成交付| Z
|
|
201
|
+
B -->|复杂任务| A
|
|
202
|
+
A -->|复杂任务完成所有步骤或者达成退出条件,交付| B
|
|
203
|
+
D -->|任务推进|G
|
|
204
|
+
G -->|调用| F
|
|
205
|
+
F -->|成功/失败| G
|
|
206
|
+
G -->|有失败任务未达到交付条件,重选Agent| D
|
|
207
|
+
G -->|执行成功后交付|A
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
mermaid2 = Mermaid(mmd)
|