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.
Files changed (154) hide show
  1. {echotools-1.0.36 → echotools-2.0.0}/PKG-INFO +1 -1
  2. {echotools-1.0.36 → echotools-2.0.0}/pyproject.toml +1 -1
  3. echotools-2.0.0/src/echotools/fncall/protocols/__init__.py +14 -0
  4. echotools-1.0.36/src/echotools/fncall/protocols/antml.py → echotools-2.0.0/src/echotools/fncall/protocols/entml.py +35 -35
  5. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/registry.py +16 -7
  6. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/protocol/base.py +1 -6
  7. {echotools-1.0.36 → echotools-2.0.0}/src/echotools.egg-info/PKG-INFO +1 -1
  8. {echotools-1.0.36 → echotools-2.0.0}/src/echotools.egg-info/SOURCES.txt +1 -7
  9. echotools-1.0.36/src/echotools/fncall/protocols/__init__.py +0 -30
  10. echotools-1.0.36/src/echotools/fncall/protocols/bracket.py +0 -158
  11. echotools-1.0.36/src/echotools/fncall/protocols/custom.py +0 -51
  12. echotools-1.0.36/src/echotools/fncall/protocols/dsml.py +0 -244
  13. echotools-1.0.36/src/echotools/fncall/protocols/nous.py +0 -147
  14. echotools-1.0.36/src/echotools/fncall/protocols/original.py +0 -377
  15. echotools-1.0.36/src/echotools/fncall/protocols/xml.py +0 -251
  16. {echotools-1.0.36 → echotools-2.0.0}/README.md +0 -0
  17. {echotools-1.0.36 → echotools-2.0.0}/setup.cfg +0 -0
  18. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/__init__.py +0 -0
  19. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/_version.py +0 -0
  20. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/cache/__init__.py +0 -0
  21. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/cache/list_cache.py +0 -0
  22. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/cache/memory_cache.py +0 -0
  23. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/config/__init__.py +0 -0
  24. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/config/base.py +0 -0
  25. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/config/center.py +0 -0
  26. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/config/loader.py +0 -0
  27. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/config/merge.py +0 -0
  28. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/dispatch/__init__.py +0 -0
  29. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/dispatch/candidate.py +0 -0
  30. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/dispatch/dispatcher.py +0 -0
  31. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/dispatch/proxy_selector.py +0 -0
  32. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/dispatch/selector.py +0 -0
  33. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/dispatch/usage.py +0 -0
  34. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/errors/__init__.py +0 -0
  35. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/errors/base.py +0 -0
  36. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/errors/classify.py +0 -0
  37. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/errors/common.py +0 -0
  38. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/errors/http.py +0 -0
  39. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/events/__init__.py +0 -0
  40. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/events/bus.py +0 -0
  41. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/events/event.py +0 -0
  42. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/files/__init__.py +0 -0
  43. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/files/file_util.py +0 -0
  44. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/__init__.py +0 -0
  45. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/parsers/__init__.py +0 -0
  46. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/parsers/stream.py +0 -0
  47. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/parsers/xml_parser.py +0 -0
  48. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/prompt/__init__.py +0 -0
  49. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/prompt/history.py +0 -0
  50. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/prompt/inject.py +0 -0
  51. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/prompt/templates.py +0 -0
  52. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/shared/__init__.py +0 -0
  53. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/shared/coercion.py +0 -0
  54. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/shared/loop_detect.py +0 -0
  55. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/shared/normalization.py +0 -0
  56. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/fncall/shared/xml_helpers.py +0 -0
  57. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/ids/__init__.py +0 -0
  58. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/ids/generator.py +0 -0
  59. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/io/__init__.py +0 -0
  60. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/io/io_utils.py +0 -0
  61. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/keys.py +0 -0
  62. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/lifecycle/__init__.py +0 -0
  63. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/lifecycle/manager.py +0 -0
  64. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/lifecycle/updater.py +0 -0
  65. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/logger/__init__.py +0 -0
  66. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/logger/manager.py +0 -0
  67. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/network/__init__.py +0 -0
  68. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/network/http_utils.py +0 -0
  69. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/plugin/__init__.py +0 -0
  70. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/plugin/base.py +0 -0
  71. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/plugin/discovery.py +0 -0
  72. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/plugin/registry.py +0 -0
  73. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/process/__init__.py +0 -0
  74. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/process/port.py +0 -0
  75. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/protocol/__init__.py +0 -0
  76. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/proxy/__init__.py +0 -0
  77. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/proxy/manager.py +0 -0
  78. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/py.typed +0 -0
  79. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/retry/__init__.py +0 -0
  80. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/retry/retry.py +0 -0
  81. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/runtime/__init__.py +0 -0
  82. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/runtime/collector.py +0 -0
  83. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/scheduler/__init__.py +0 -0
  84. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/scheduler/scheduler.py +0 -0
  85. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/sdk/__init__.py +0 -0
  86. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/sdk/facade.py +0 -0
  87. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/terminal/__init__.py +0 -0
  88. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/terminal/conpty.py +0 -0
  89. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/terminal/local.py +0 -0
  90. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/terminal/sanitize.py +0 -0
  91. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/terminal/session.py +0 -0
  92. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/terminal/ssh.py +0 -0
  93. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/tracing/__init__.py +0 -0
  94. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/tracing/context.py +0 -0
  95. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/tracing/span.py +0 -0
  96. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/tracing/tracer.py +0 -0
  97. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/translate.py +0 -0
  98. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/watcher/__init__.py +0 -0
  99. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/watcher/file_watcher.py +0 -0
  100. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/__init__.py +0 -0
  101. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/application.py +0 -0
  102. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/broker.py +0 -0
  103. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/app.js +0 -0
  104. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/file-zone/file-zone.js +0 -0
  105. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/fonts/inter-400.woff2 +0 -0
  106. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/fonts/inter-500.woff2 +0 -0
  107. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/fonts/inter-600.woff2 +0 -0
  108. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/fonts/tailwind.min.css +0 -0
  109. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/input-box.css +0 -0
  110. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/input-box.js +0 -0
  111. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/motion-kit/motion-kit.js +0 -0
  112. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/send-button/send-button.js +0 -0
  113. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/sortable-list/sortable-list.css +0 -0
  114. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/sortable-list/sortable-list.js +0 -0
  115. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/style.css +0 -0
  116. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/text-input/text-input.js +0 -0
  117. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/input_box/voice-input/voice-input.js +0 -0
  118. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/middleware.py +0 -0
  119. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/stats.py +0 -0
  120. {echotools-1.0.36 → echotools-2.0.0}/src/echotools/web/utils.py +0 -0
  121. {echotools-1.0.36 → echotools-2.0.0}/src/echotools.egg-info/dependency_links.txt +0 -0
  122. {echotools-1.0.36 → echotools-2.0.0}/src/echotools.egg-info/requires.txt +0 -0
  123. {echotools-1.0.36 → echotools-2.0.0}/src/echotools.egg-info/top_level.txt +0 -0
  124. {echotools-1.0.36 → echotools-2.0.0}/tests/test_cache_retry.py +0 -0
  125. {echotools-1.0.36 → echotools-2.0.0}/tests/test_completion.py +0 -0
  126. {echotools-1.0.36 → echotools-2.0.0}/tests/test_config.py +0 -0
  127. {echotools-1.0.36 → echotools-2.0.0}/tests/test_config_extended.py +0 -0
  128. {echotools-1.0.36 → echotools-2.0.0}/tests/test_coverage_core.py +0 -0
  129. {echotools-1.0.36 → echotools-2.0.0}/tests/test_dispatch.py +0 -0
  130. {echotools-1.0.36 → echotools-2.0.0}/tests/test_dispatch_extended.py +0 -0
  131. {echotools-1.0.36 → echotools-2.0.0}/tests/test_dispatch_race.py +0 -0
  132. {echotools-1.0.36 → echotools-2.0.0}/tests/test_errors.py +0 -0
  133. {echotools-1.0.36 → echotools-2.0.0}/tests/test_errors_common.py +0 -0
  134. {echotools-1.0.36 → echotools-2.0.0}/tests/test_events.py +0 -0
  135. {echotools-1.0.36 → echotools-2.0.0}/tests/test_events_event.py +0 -0
  136. {echotools-1.0.36 → echotools-2.0.0}/tests/test_facade.py +0 -0
  137. {echotools-1.0.36 → echotools-2.0.0}/tests/test_fncall.py +0 -0
  138. {echotools-1.0.36 → echotools-2.0.0}/tests/test_ids.py +0 -0
  139. {echotools-1.0.36 → echotools-2.0.0}/tests/test_imports.py +0 -0
  140. {echotools-1.0.36 → echotools-2.0.0}/tests/test_io.py +0 -0
  141. {echotools-1.0.36 → echotools-2.0.0}/tests/test_keys.py +0 -0
  142. {echotools-1.0.36 → echotools-2.0.0}/tests/test_lifecycle.py +0 -0
  143. {echotools-1.0.36 → echotools-2.0.0}/tests/test_logger.py +0 -0
  144. {echotools-1.0.36 → echotools-2.0.0}/tests/test_merge.py +0 -0
  145. {echotools-1.0.36 → echotools-2.0.0}/tests/test_plugin.py +0 -0
  146. {echotools-1.0.36 → echotools-2.0.0}/tests/test_protocols.py +0 -0
  147. {echotools-1.0.36 → echotools-2.0.0}/tests/test_proxy.py +0 -0
  148. {echotools-1.0.36 → echotools-2.0.0}/tests/test_proxy_selector.py +0 -0
  149. {echotools-1.0.36 → echotools-2.0.0}/tests/test_runtime.py +0 -0
  150. {echotools-1.0.36 → echotools-2.0.0}/tests/test_scheduler.py +0 -0
  151. {echotools-1.0.36 → echotools-2.0.0}/tests/test_tracing.py +0 -0
  152. {echotools-1.0.36 → echotools-2.0.0}/tests/test_usage.py +0 -0
  153. {echotools-1.0.36 → echotools-2.0.0}/tests/test_web.py +0 -0
  154. {echotools-1.0.36 → echotools-2.0.0}/tests/test_web_broker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 1.0.36
3
+ Version: 2.0.0
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Author: nichengfuben
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "echotools"
7
- version = "1.0.36"
7
+ version = "2.0.0"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -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
- """Anthropic ML (antml) 协议实现。
1
+ """Entropy ML (entml) (entml) 协议实现。
2
2
 
3
- 使用 <antml:function_calls> 作为触发标记,
4
- <antml:invoke name="..."> 作为调用格式。
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
- <antml:function_calls>
40
- <antml:invoke name="tool_name">
41
- <antml:parameters>{"param_name": "value"}</antml:parameters>
42
- </antml:invoke>
43
- </antml:function_calls>
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 `<antml:function_calls>` block for parallel execution.
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"<antml:function_calls\b[^>]*>([\s\S]*?)</antml:function_calls>",
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'<antml:invoke\s+name="([^"]+)">\s*([\s\S]*?)\s*</antml:invoke>',
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'<antml:parameter\s+name="([^"]+)">\s*([\s\S]*?)\s*</antml:parameter>',
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'<antml:parameters>([\s\S]*?)</antml:parameters>',
73
+ r'<entml:parameters>([\s\S]*?)</entml:parameters>',
74
74
  re.DOTALL,
75
75
  )
76
76
 
77
77
  # ---------------------------------------------------------------------------
78
- # Antml 协议
78
+ # Entml 协议
79
79
  # ---------------------------------------------------------------------------
80
80
 
81
81
 
82
- class AntmlProtocol(ToolProtocol):
83
- """Anthropic ML 格式工具调用协议适配器。
82
+ class EntmlProtocol(ToolProtocol):
83
+ """Entropy ML (entml) 格式工具调用协议适配器。
84
84
 
85
- 使用 <antml:function_calls><antml:invoke name="...">...</antml:invoke>
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 "antml"
91
+ return "entml"
92
92
 
93
- _TRIGGER = "<antml:function_calls>"
94
- _END_TAG = "</antml:function_calls>"
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 = "<antml:function_calls"
137
+ _TRIGGER_PREFIX = "<entml:function_calls"
138
138
 
139
139
  def detect_start(self, buffer: str) -> Tuple[bool, int]:
140
- """检测 buffer 中是否包含 ``<antml:function_calls...>`` 触发标记。
140
+ """检测 buffer 中是否包含 ``<entml:function_calls...>`` 触发标记。
141
141
 
142
- 容忍变体(如 ``<antml:function_calls >`` 或带属性),只要前缀
143
- ``<antml:function_calls`` 后跟任意字符并以 ``>`` 闭合即视为触发。
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 <antml:parameter> and <antml:parameters>
174
+ # Extract parameters - support both <entml:parameter> and <entml:parameters>
175
175
  args: Dict[str, Any] = {}
176
176
 
177
- # Try <antml:parameters>{json}</antml:parameters> format first
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 <antml:parameter name="...">value</antml:parameter> format
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
- """将已知的完整 antml 片段直接解析为 tool_calls 列表。"""
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
- """从响应文本中移除 <antml:function_calls> 标签残留。"""
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 对象列表渲染为 antml 格式。"""
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'<antml:invoke name="{name}">'
246
- f"<antml:parameters>{args}</antml:parameters>"
247
- f"</antml:invoke>"
245
+ f'<entml:invoke name="{name}">'
246
+ f"<entml:parameters>{args}</entml:parameters>"
247
+ f"</entml:invoke>"
248
248
  )
249
- return f"<antml:function_calls>{''.join(parts)}</antml:function_calls>"
249
+ return f"<entml:function_calls>{''.join(parts)}</entml:function_calls>"
250
250
 
251
251
  def supports_streaming(self) -> bool:
252
- """antml 协议支持流式检测。"""
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
- from echotools.fncall.protocols.custom import CustomProtocol
31
-
32
- _custom_instance = CustomProtocol(
33
- prompt_en=prompt_en, prompt_zh=prompt_zh
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 = "xml",
61
+ default_protocol: str = "entml",
53
62
  custom_prompt_en: str = "",
54
63
  custom_prompt_zh: str = "",
55
64
  platform_id: str = "",
@@ -18,12 +18,7 @@ __all__ = [
18
18
  logger = get_logger(__name__)
19
19
 
20
20
  VALID_PROTOCOL_IDS = (
21
- "xml",
22
- "antml",
23
- "original",
24
- "bracket",
25
- "nous",
26
- "dsml",
21
+ "entml",
27
22
  "custom",
28
23
  )
29
24
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 1.0.36
3
+ Version: 2.0.0
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Author: nichengfuben
6
6
  License: MIT
@@ -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/antml.py
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