echotools 1.0.36__tar.gz → 2.0.0__tar.gz
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.
- {echotools-1.0.36 → echotools-2.0.0}/PKG-INFO +1 -1
- {echotools-1.0.36 → echotools-2.0.0}/pyproject.toml +1 -1
- echotools-2.0.0/src/echotools/fncall/protocols/__init__.py +14 -0
- echotools-1.0.36/src/echotools/fncall/protocols/antml.py → echotools-2.0.0/src/echotools/fncall/protocols/entml.py +35 -35
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/registry.py +16 -7
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/protocol/base.py +1 -6
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools.egg-info/PKG-INFO +1 -1
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools.egg-info/SOURCES.txt +1 -7
- echotools-1.0.36/src/echotools/fncall/protocols/__init__.py +0 -30
- echotools-1.0.36/src/echotools/fncall/protocols/bracket.py +0 -158
- echotools-1.0.36/src/echotools/fncall/protocols/custom.py +0 -51
- echotools-1.0.36/src/echotools/fncall/protocols/dsml.py +0 -244
- echotools-1.0.36/src/echotools/fncall/protocols/nous.py +0 -147
- echotools-1.0.36/src/echotools/fncall/protocols/original.py +0 -377
- echotools-1.0.36/src/echotools/fncall/protocols/xml.py +0 -251
- {echotools-1.0.36 → echotools-2.0.0}/README.md +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/setup.cfg +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/_version.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/cache/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/cache/list_cache.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/cache/memory_cache.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/config/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/config/base.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/config/center.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/config/loader.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/config/merge.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/dispatch/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/dispatch/candidate.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/dispatch/dispatcher.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/dispatch/proxy_selector.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/dispatch/selector.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/dispatch/usage.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/errors/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/errors/base.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/errors/classify.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/errors/common.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/errors/http.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/events/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/events/bus.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/events/event.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/files/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/files/file_util.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/parsers/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/parsers/stream.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/parsers/xml_parser.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/prompt/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/prompt/history.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/prompt/inject.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/prompt/templates.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/shared/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/shared/coercion.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/shared/loop_detect.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/shared/normalization.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/shared/xml_helpers.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/ids/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/ids/generator.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/io/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/io/io_utils.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/keys.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/lifecycle/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/lifecycle/manager.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/lifecycle/updater.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/logger/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/logger/manager.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/network/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/network/http_utils.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/plugin/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/plugin/base.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/plugin/discovery.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/plugin/registry.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/process/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/process/port.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/protocol/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/proxy/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/proxy/manager.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/py.typed +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/retry/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/retry/retry.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/runtime/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/runtime/collector.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/scheduler/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/scheduler/scheduler.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/sdk/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/sdk/facade.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/terminal/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/terminal/conpty.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/terminal/local.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/terminal/sanitize.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/terminal/session.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/terminal/ssh.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/tracing/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/tracing/context.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/tracing/span.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/tracing/tracer.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/translate.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/watcher/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/watcher/file_watcher.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/__init__.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/application.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/broker.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/app.js +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/file-zone/file-zone.js +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/fonts/inter-400.woff2 +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/fonts/inter-500.woff2 +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/fonts/inter-600.woff2 +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/fonts/tailwind.min.css +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/input-box.css +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/input-box.js +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/motion-kit/motion-kit.js +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/send-button/send-button.js +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/sortable-list/sortable-list.css +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/sortable-list/sortable-list.js +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/style.css +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/text-input/text-input.js +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/voice-input/voice-input.js +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/middleware.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/stats.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/utils.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools.egg-info/dependency_links.txt +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools.egg-info/requires.txt +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/src/echotools.egg-info/top_level.txt +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_cache_retry.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_completion.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_config.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_config_extended.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_coverage_core.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_dispatch.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_dispatch_extended.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_dispatch_race.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_errors.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_errors_common.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_events.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_events_event.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_facade.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_fncall.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_ids.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_imports.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_io.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_keys.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_lifecycle.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_logger.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_merge.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_plugin.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_protocols.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_proxy.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_proxy_selector.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_runtime.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_scheduler.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_tracing.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_usage.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_web.py +0 -0
- {echotools-1.0.36 → echotools-2.0.0}/tests/test_web_broker.py +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""协议注册 — echotools 仅内置 entml 标记语言。"""
|
|
2
|
+
|
|
3
|
+
from echotools.protocol.base import register_protocol
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _register_all() -> None:
|
|
7
|
+
from echotools.fncall.protocols.entml import EntmlProtocol
|
|
8
|
+
|
|
9
|
+
register_protocol(EntmlProtocol())
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_register_all()
|
|
13
|
+
|
|
14
|
+
__all__ = ["_register_all"]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Entropy ML (entml) (entml) 协议实现。
|
|
2
2
|
|
|
3
|
-
使用 <
|
|
4
|
-
<
|
|
3
|
+
使用 <entml:function_calls> 作为触发标记,
|
|
4
|
+
<entml:invoke name="..."> 作为调用格式。
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
@@ -36,13 +36,13 @@ All functions are defined inside a `<functions>` wrapper block. Each function is
|
|
|
36
36
|
|
|
37
37
|
When calling tools, respond with ONLY the following XML block format:
|
|
38
38
|
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
<
|
|
42
|
-
</
|
|
43
|
-
</
|
|
39
|
+
<entml:function_calls>
|
|
40
|
+
<entml:invoke name="tool_name">
|
|
41
|
+
<entml:parameters>{"param_name": "value"}</entml:parameters>
|
|
42
|
+
</entml:invoke>
|
|
43
|
+
</entml:function_calls>
|
|
44
44
|
|
|
45
|
-
Multiple invocations can be stacked inside one `<
|
|
45
|
+
Multiple invocations can be stacked inside one `<entml:function_calls>` block for parallel execution.
|
|
46
46
|
|
|
47
47
|
## Function Call Instructions
|
|
48
48
|
|
|
@@ -58,40 +58,40 @@ If you intend to call multiple tools and there are no dependencies between the c
|
|
|
58
58
|
# ---------------------------------------------------------------------------
|
|
59
59
|
|
|
60
60
|
_BLOCK_RE = re.compile(
|
|
61
|
-
r"<
|
|
61
|
+
r"<entml:function_calls\b[^>]*>([\s\S]*?)</entml:function_calls>",
|
|
62
62
|
re.DOTALL,
|
|
63
63
|
)
|
|
64
64
|
_INVOKE_RE = re.compile(
|
|
65
|
-
r'<
|
|
65
|
+
r'<entml:invoke\s+name="([^"]+)">\s*([\s\S]*?)\s*</entml:invoke>',
|
|
66
66
|
re.DOTALL,
|
|
67
67
|
)
|
|
68
68
|
_PARAM_RE = re.compile(
|
|
69
|
-
r'<
|
|
69
|
+
r'<entml:parameter\s+name="([^"]+)">\s*([\s\S]*?)\s*</entml:parameter>',
|
|
70
70
|
re.DOTALL,
|
|
71
71
|
)
|
|
72
72
|
_PARAMETERS_RE = re.compile(
|
|
73
|
-
r'<
|
|
73
|
+
r'<entml:parameters>([\s\S]*?)</entml:parameters>',
|
|
74
74
|
re.DOTALL,
|
|
75
75
|
)
|
|
76
76
|
|
|
77
77
|
# ---------------------------------------------------------------------------
|
|
78
|
-
#
|
|
78
|
+
# Entml 协议
|
|
79
79
|
# ---------------------------------------------------------------------------
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
class
|
|
83
|
-
"""
|
|
82
|
+
class EntmlProtocol(ToolProtocol):
|
|
83
|
+
"""Entropy ML (entml) 格式工具调用协议适配器。
|
|
84
84
|
|
|
85
|
-
使用 <
|
|
85
|
+
使用 <entml:function_calls><entml:invoke name="...">...</entml:invoke>
|
|
86
86
|
作为触发标记和调用格式。
|
|
87
87
|
"""
|
|
88
88
|
|
|
89
89
|
@property
|
|
90
90
|
def id(self) -> str:
|
|
91
|
-
return "
|
|
91
|
+
return "entml"
|
|
92
92
|
|
|
93
|
-
_TRIGGER = "<
|
|
94
|
-
_END_TAG = "</
|
|
93
|
+
_TRIGGER = "<entml:function_calls>"
|
|
94
|
+
_END_TAG = "</entml:function_calls>"
|
|
95
95
|
|
|
96
96
|
def get_trigger_tags(self) -> List[str]:
|
|
97
97
|
return [self._TRIGGER]
|
|
@@ -134,13 +134,13 @@ class AntmlProtocol(ToolProtocol):
|
|
|
134
134
|
|
|
135
135
|
return prompt
|
|
136
136
|
|
|
137
|
-
_TRIGGER_PREFIX = "<
|
|
137
|
+
_TRIGGER_PREFIX = "<entml:function_calls"
|
|
138
138
|
|
|
139
139
|
def detect_start(self, buffer: str) -> Tuple[bool, int]:
|
|
140
|
-
"""检测 buffer 中是否包含 ``<
|
|
140
|
+
"""检测 buffer 中是否包含 ``<entml:function_calls...>`` 触发标记。
|
|
141
141
|
|
|
142
|
-
容忍变体(如 ``<
|
|
143
|
-
``<
|
|
142
|
+
容忍变体(如 ``<entml:function_calls >`` 或带属性),只要前缀
|
|
143
|
+
``<entml:function_calls`` 后跟任意字符并以 ``>`` 闭合即视为触发。
|
|
144
144
|
"""
|
|
145
145
|
pos = buffer.find(self._TRIGGER_PREFIX)
|
|
146
146
|
if pos < 0:
|
|
@@ -171,10 +171,10 @@ class AntmlProtocol(ToolProtocol):
|
|
|
171
171
|
name = invoke_m.group(1).strip()
|
|
172
172
|
body = invoke_m.group(2)
|
|
173
173
|
|
|
174
|
-
# Extract parameters - support both <
|
|
174
|
+
# Extract parameters - support both <entml:parameter> and <entml:parameters>
|
|
175
175
|
args: Dict[str, Any] = {}
|
|
176
176
|
|
|
177
|
-
# Try <
|
|
177
|
+
# Try <entml:parameters>{json}</entml:parameters> format first
|
|
178
178
|
params_m = _PARAMETERS_RE.search(body)
|
|
179
179
|
if params_m:
|
|
180
180
|
params_json = params_m.group(1).strip()
|
|
@@ -183,7 +183,7 @@ class AntmlProtocol(ToolProtocol):
|
|
|
183
183
|
except json.JSONDecodeError:
|
|
184
184
|
args = {"value": params_json}
|
|
185
185
|
else:
|
|
186
|
-
# Try <
|
|
186
|
+
# Try <entml:parameter name="...">value</entml:parameter> format
|
|
187
187
|
for param_m in _PARAM_RE.finditer(body):
|
|
188
188
|
pname = param_m.group(1).strip()
|
|
189
189
|
pval = param_m.group(2).strip()
|
|
@@ -220,19 +220,19 @@ class AntmlProtocol(ToolProtocol):
|
|
|
220
220
|
fragment: str,
|
|
221
221
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
222
222
|
) -> List[Dict[str, Any]]:
|
|
223
|
-
"""将已知的完整
|
|
223
|
+
"""将已知的完整 entml 片段直接解析为 tool_calls 列表。"""
|
|
224
224
|
_, tool_calls = self.parse(fragment, tools)
|
|
225
225
|
return tool_calls
|
|
226
226
|
|
|
227
227
|
def clean_tags(self, content: str) -> str:
|
|
228
|
-
"""从响应文本中移除 <
|
|
228
|
+
"""从响应文本中移除 <entml:function_calls> 标签残留。"""
|
|
229
229
|
return _BLOCK_RE.sub("", content).strip()
|
|
230
230
|
|
|
231
231
|
def format_assistant_tool_calls(
|
|
232
232
|
self,
|
|
233
233
|
tool_calls: List[Dict[str, Any]],
|
|
234
234
|
) -> str:
|
|
235
|
-
"""将 tool_call 对象列表渲染为
|
|
235
|
+
"""将 tool_call 对象列表渲染为 entml 格式。"""
|
|
236
236
|
if not tool_calls:
|
|
237
237
|
return ""
|
|
238
238
|
|
|
@@ -242,12 +242,12 @@ class AntmlProtocol(ToolProtocol):
|
|
|
242
242
|
name = fn.get("name", "")
|
|
243
243
|
args = fn.get("arguments", "{}")
|
|
244
244
|
parts.append(
|
|
245
|
-
f'<
|
|
246
|
-
f"<
|
|
247
|
-
f"</
|
|
245
|
+
f'<entml:invoke name="{name}">'
|
|
246
|
+
f"<entml:parameters>{args}</entml:parameters>"
|
|
247
|
+
f"</entml:invoke>"
|
|
248
248
|
)
|
|
249
|
-
return f"<
|
|
249
|
+
return f"<entml:function_calls>{''.join(parts)}</entml:function_calls>"
|
|
250
250
|
|
|
251
251
|
def supports_streaming(self) -> bool:
|
|
252
|
-
"""
|
|
252
|
+
"""entml 协议支持流式检测。"""
|
|
253
253
|
return True
|
|
@@ -20,19 +20,28 @@ _registered = False
|
|
|
20
20
|
_mapping_logged: set = set()
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
_custom_factory = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def set_custom_protocol_factory(factory) -> None:
|
|
27
|
+
"""由 Provider-Fncall-Util 等插件注入 custom 协议工厂。"""
|
|
28
|
+
global _custom_factory
|
|
29
|
+
_custom_factory = factory
|
|
30
|
+
|
|
31
|
+
|
|
23
32
|
def _get_custom_protocol(
|
|
24
33
|
prompt_en: str = "", prompt_zh: str = ""
|
|
25
34
|
) -> ToolProtocol:
|
|
26
|
-
"""获取或创建 custom
|
|
35
|
+
"""获取或创建 custom 协议(由 fncall 插件提供)。"""
|
|
27
36
|
global _custom_instance
|
|
28
37
|
if _custom_instance is not None:
|
|
29
38
|
return _custom_instance
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
if _custom_factory is not None:
|
|
40
|
+
_custom_instance = _custom_factory(prompt_en, prompt_zh)
|
|
41
|
+
return _custom_instance
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"custom 协议需要 Provider-Fncall-Util 插件;请安装并启用 fncall 插件"
|
|
34
44
|
)
|
|
35
|
-
return _custom_instance
|
|
36
45
|
|
|
37
46
|
|
|
38
47
|
def _ensure_registered() -> None:
|
|
@@ -49,7 +58,7 @@ def _ensure_registered() -> None:
|
|
|
49
58
|
def get_protocol(
|
|
50
59
|
protocol_id: str = "",
|
|
51
60
|
*,
|
|
52
|
-
default_protocol: str = "
|
|
61
|
+
default_protocol: str = "entml",
|
|
53
62
|
custom_prompt_en: str = "",
|
|
54
63
|
custom_prompt_zh: str = "",
|
|
55
64
|
platform_id: str = "",
|
|
@@ -44,13 +44,7 @@ src/echotools/fncall/prompt/history.py
|
|
|
44
44
|
src/echotools/fncall/prompt/inject.py
|
|
45
45
|
src/echotools/fncall/prompt/templates.py
|
|
46
46
|
src/echotools/fncall/protocols/__init__.py
|
|
47
|
-
src/echotools/fncall/protocols/
|
|
48
|
-
src/echotools/fncall/protocols/bracket.py
|
|
49
|
-
src/echotools/fncall/protocols/custom.py
|
|
50
|
-
src/echotools/fncall/protocols/dsml.py
|
|
51
|
-
src/echotools/fncall/protocols/nous.py
|
|
52
|
-
src/echotools/fncall/protocols/original.py
|
|
53
|
-
src/echotools/fncall/protocols/xml.py
|
|
47
|
+
src/echotools/fncall/protocols/entml.py
|
|
54
48
|
src/echotools/fncall/shared/__init__.py
|
|
55
49
|
src/echotools/fncall/shared/coercion.py
|
|
56
50
|
src/echotools/fncall/shared/loop_detect.py
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"""协议注册。
|
|
2
|
-
|
|
3
|
-
导入时自动注册所有内置协议。
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from echotools.protocol.base import register_protocol
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def _register_all() -> None:
|
|
10
|
-
"""注册所有内置协议。"""
|
|
11
|
-
from echotools.fncall.protocols.antml import AntmlProtocol
|
|
12
|
-
from echotools.fncall.protocols.bracket import BracketProtocol
|
|
13
|
-
from echotools.fncall.protocols.dsml import DsmlProtocol
|
|
14
|
-
from echotools.fncall.protocols.nous import NousProtocol
|
|
15
|
-
from echotools.fncall.protocols.original import OriginalProtocol
|
|
16
|
-
from echotools.fncall.protocols.xml import XmlProtocol
|
|
17
|
-
|
|
18
|
-
register_protocol(XmlProtocol())
|
|
19
|
-
register_protocol(AntmlProtocol())
|
|
20
|
-
register_protocol(OriginalProtocol())
|
|
21
|
-
register_protocol(BracketProtocol())
|
|
22
|
-
register_protocol(NousProtocol())
|
|
23
|
-
register_protocol(DsmlProtocol())
|
|
24
|
-
# custom 协议按需创建,不在此注册
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# 模块导入时自动注册
|
|
28
|
-
_register_all()
|
|
29
|
-
|
|
30
|
-
__all__ = ["_register_all"]
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import re
|
|
3
|
-
from typing import List, Tuple
|
|
4
|
-
|
|
5
|
-
from echotools.fncall.prompt.templates import (
|
|
6
|
-
_HISTORY_CLARIFY_EN,
|
|
7
|
-
_HISTORY_CLARIFY_ZH,
|
|
8
|
-
)
|
|
9
|
-
from echotools.fncall.shared.coercion import (
|
|
10
|
-
_build_param_schema_index,
|
|
11
|
-
_coerce_param_value,
|
|
12
|
-
)
|
|
13
|
-
from echotools.protocol.base import ToolProtocol
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class BracketProtocol(ToolProtocol):
|
|
17
|
-
@property
|
|
18
|
-
def id(self) -> str:
|
|
19
|
-
return "bracket"
|
|
20
|
-
|
|
21
|
-
_TRIGGER = "[function_calls]"
|
|
22
|
-
_END_TAG = "[/function_calls]"
|
|
23
|
-
_BLOCK_RE = re.compile(r"\[function_calls\]([\s\S]*?)\[/function_calls\]", re.DOTALL)
|
|
24
|
-
_CALL_RE = re.compile(r"\[call:([^\]]+)\]([\s\S]*?)\[/call\]", re.DOTALL)
|
|
25
|
-
# Fallback for incorrect [ToolName]{...}[/ToolName] format — only when body starts with {
|
|
26
|
-
_SIMPLE_CALL_RE = re.compile(r"\[([A-Za-z_][A-Za-z0-9_]*)\](\{[\s\S]*?)\[/\1\]", re.DOTALL)
|
|
27
|
-
|
|
28
|
-
def get_trigger_tags(self) -> List[str]:
|
|
29
|
-
return [self._TRIGGER]
|
|
30
|
-
|
|
31
|
-
def render_prompt(self, tool_descs, lang, user_system_prompt="", history_text="", loop_warning="", current_user_message=""):
|
|
32
|
-
instruction = f"""## Available Tools
|
|
33
|
-
You can invoke the following developer tools. Tool names are case-sensitive.
|
|
34
|
-
Use only the exact tool names listed below. Do not rename, camelCase, translate, shorten, or invent tool names.
|
|
35
|
-
|
|
36
|
-
{tool_descs}
|
|
37
|
-
|
|
38
|
-
## Tool Invocation Format
|
|
39
|
-
|
|
40
|
-
When calling tools, you MUST respond with ONLY this exact bracket format:
|
|
41
|
-
|
|
42
|
-
[function_calls]
|
|
43
|
-
[call:exact_tool_name]{{"argument_name":"argument_value"}}[/call]
|
|
44
|
-
[/function_calls]
|
|
45
|
-
|
|
46
|
-
Rules:
|
|
47
|
-
1. Always wrap tool calls in [function_calls]...[/function_calls]
|
|
48
|
-
2. Use [call:tool_name]...[/call] for each invocation (note the colon before tool name)
|
|
49
|
-
3. Arguments must be valid JSON inside the [call] tags
|
|
50
|
-
4. Tool names are case-sensitive — use exact names from the list above
|
|
51
|
-
5. Do NOT use [ToolName]{{...}}[/ToolName] format — that is incorrect
|
|
52
|
-
6. Do NOT output plain text between [function_calls] tags
|
|
53
|
-
|
|
54
|
-
Example correct invocation:
|
|
55
|
-
[function_calls]
|
|
56
|
-
[call:Bash]{{"command":"echo hello"}}[/call]
|
|
57
|
-
[/function_calls]
|
|
58
|
-
|
|
59
|
-
Tool results will be provided in a corresponding result block."""
|
|
60
|
-
|
|
61
|
-
sections = [instruction]
|
|
62
|
-
if user_system_prompt and user_system_prompt.strip():
|
|
63
|
-
sections.append(f"<user_system_prompt>\n{user_system_prompt.strip()}\n</user_system_prompt>")
|
|
64
|
-
if history_text:
|
|
65
|
-
clarify = _HISTORY_CLARIFY_ZH if lang == "zh" else _HISTORY_CLARIFY_EN
|
|
66
|
-
sections.append(f"<conversation_history>\n{clarify}\n\n{history_text}\n</conversation_history>")
|
|
67
|
-
if loop_warning:
|
|
68
|
-
sections.append(f"<loop_warning>\n{loop_warning}\n</loop_warning>")
|
|
69
|
-
if current_user_message:
|
|
70
|
-
sections.append(f"<current_user_message>\n{current_user_message}\n</current_user_message>")
|
|
71
|
-
|
|
72
|
-
return "\n\n".join(sections)
|
|
73
|
-
|
|
74
|
-
def detect_start(self, buffer: str) -> Tuple[bool, int]:
|
|
75
|
-
pos = buffer.find(self._TRIGGER)
|
|
76
|
-
return (pos >= 0, pos if pos >= 0 else -1)
|
|
77
|
-
|
|
78
|
-
def parse(self, text, tools=None):
|
|
79
|
-
tool_calls = []
|
|
80
|
-
schema_index = _build_param_schema_index(tools) if tools else None
|
|
81
|
-
|
|
82
|
-
for block_m in self._BLOCK_RE.finditer(text):
|
|
83
|
-
block_body = block_m.group(1)
|
|
84
|
-
block_had_correct_calls = False
|
|
85
|
-
|
|
86
|
-
# Try correct [call:name]{...}[/call] format first
|
|
87
|
-
for call_m in self._CALL_RE.finditer(block_body):
|
|
88
|
-
name = call_m.group(1).strip()
|
|
89
|
-
args_raw = call_m.group(2).strip()
|
|
90
|
-
args = self._parse_args(args_raw, name, schema_index)
|
|
91
|
-
tool_calls.append({
|
|
92
|
-
"id": f"call_{len(tool_calls):04d}",
|
|
93
|
-
"type": "function",
|
|
94
|
-
"function": {"name": name, "arguments": args},
|
|
95
|
-
})
|
|
96
|
-
block_had_correct_calls = True
|
|
97
|
-
|
|
98
|
-
# Fallback: if no correct calls in this block, try simplified [ToolName]{...}[/ToolName]
|
|
99
|
-
if not block_had_correct_calls:
|
|
100
|
-
for simple_m in self._SIMPLE_CALL_RE.finditer(block_body):
|
|
101
|
-
name = simple_m.group(1).strip()
|
|
102
|
-
# Skip if it looks like a block tag
|
|
103
|
-
if name.lower() in ('function_calls', 'call'):
|
|
104
|
-
continue
|
|
105
|
-
args_raw = simple_m.group(2).strip()
|
|
106
|
-
# Only treat as fallback if body looks like JSON or plain text
|
|
107
|
-
args = self._parse_args(args_raw, name, schema_index)
|
|
108
|
-
tool_calls.append({
|
|
109
|
-
"id": f"call_{len(tool_calls):04d}",
|
|
110
|
-
"type": "function",
|
|
111
|
-
"function": {"name": name, "arguments": args},
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
clean = text
|
|
115
|
-
if tool_calls:
|
|
116
|
-
clean = self._BLOCK_RE.sub("", text).strip()
|
|
117
|
-
|
|
118
|
-
return (clean, tool_calls)
|
|
119
|
-
|
|
120
|
-
def _parse_args(self, args_raw, func_name, schema_index):
|
|
121
|
-
"""Parse and optionally coerce arguments."""
|
|
122
|
-
try:
|
|
123
|
-
args = json.loads(args_raw)
|
|
124
|
-
if not isinstance(args, dict):
|
|
125
|
-
args = {"value": args_raw}
|
|
126
|
-
except json.JSONDecodeError:
|
|
127
|
-
args = {"value": args_raw}
|
|
128
|
-
|
|
129
|
-
# Apply schema coercion
|
|
130
|
-
if schema_index and func_name in schema_index:
|
|
131
|
-
coerced = {}
|
|
132
|
-
for k, v in args.items():
|
|
133
|
-
pschema = schema_index[func_name].get(k, {})
|
|
134
|
-
coerced[k] = _coerce_param_value(
|
|
135
|
-
json.dumps(v) if not isinstance(v, str) else v,
|
|
136
|
-
pschema,
|
|
137
|
-
)
|
|
138
|
-
args = coerced
|
|
139
|
-
|
|
140
|
-
return json.dumps(args, ensure_ascii=False)
|
|
141
|
-
|
|
142
|
-
def parse_fragment(self, fragment, tools=None):
|
|
143
|
-
_, tool_calls = self.parse(fragment, tools)
|
|
144
|
-
return tool_calls
|
|
145
|
-
|
|
146
|
-
def clean_tags(self, content):
|
|
147
|
-
return self._BLOCK_RE.sub("", content).strip()
|
|
148
|
-
|
|
149
|
-
def format_assistant_tool_calls(self, tool_calls):
|
|
150
|
-
calls = []
|
|
151
|
-
for tc in tool_calls:
|
|
152
|
-
fn = tc.get("function", {})
|
|
153
|
-
calls.append(f"[call:{fn.get('name', '')}]{fn.get('arguments', '{}')}[/call]")
|
|
154
|
-
joined = "\n".join(calls)
|
|
155
|
-
return f"[function_calls]\n{joined}\n[/function_calls]"
|
|
156
|
-
|
|
157
|
-
def supports_streaming(self):
|
|
158
|
-
return True
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
from typing import List, Tuple
|
|
2
|
-
|
|
3
|
-
from echotools.protocol.base import ToolProtocol
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class CustomProtocol(ToolProtocol):
|
|
7
|
-
"""用户自定义 prompt 协议。
|
|
8
|
-
|
|
9
|
-
不解析响应,依赖平台原生 tool_calls。
|
|
10
|
-
仅注入用户提供的 prompt 模板。
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
def __init__(self, prompt_en: str = "", prompt_zh: str = ""):
|
|
14
|
-
self._prompt_en = prompt_en
|
|
15
|
-
self._prompt_zh = prompt_zh
|
|
16
|
-
|
|
17
|
-
@property
|
|
18
|
-
def id(self) -> str:
|
|
19
|
-
return "custom"
|
|
20
|
-
|
|
21
|
-
def get_trigger_tags(self) -> List[str]:
|
|
22
|
-
return []
|
|
23
|
-
|
|
24
|
-
def render_prompt(self, tool_descs, lang, user_system_prompt="", history_text="", loop_warning="", current_user_message=""):
|
|
25
|
-
template = self._prompt_en if lang == "en" else self._prompt_zh
|
|
26
|
-
if not template:
|
|
27
|
-
return ""
|
|
28
|
-
|
|
29
|
-
# Simple template substitution
|
|
30
|
-
prompt = template
|
|
31
|
-
prompt = prompt.replace("{tool_descs}", tool_descs)
|
|
32
|
-
prompt = prompt.replace("{history_text}", history_text)
|
|
33
|
-
prompt = prompt.replace("{loop_warning}", loop_warning)
|
|
34
|
-
prompt = prompt.replace("{current_user_message}", current_user_message)
|
|
35
|
-
prompt = prompt.replace("{user_system_prompt}", user_system_prompt)
|
|
36
|
-
return prompt
|
|
37
|
-
|
|
38
|
-
def detect_start(self, buffer: str) -> Tuple[bool, int]:
|
|
39
|
-
return (False, -1)
|
|
40
|
-
|
|
41
|
-
def parse(self, text, tools=None):
|
|
42
|
-
return (text, [])
|
|
43
|
-
|
|
44
|
-
def parse_fragment(self, fragment, tools=None):
|
|
45
|
-
return []
|
|
46
|
-
|
|
47
|
-
def clean_tags(self, content):
|
|
48
|
-
return content.strip()
|
|
49
|
-
|
|
50
|
-
def supports_streaming(self):
|
|
51
|
-
return False
|