echotools 2.3.2__tar.gz → 2.3.4__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 (150) hide show
  1. {echotools-2.3.2 → echotools-2.3.4}/PKG-INFO +2 -3
  2. {echotools-2.3.2 → echotools-2.3.4}/pyproject.toml +3 -4
  3. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/prompt/history.py +49 -1
  4. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/prompt/inject.py +33 -16
  5. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/protocols/entml.py +33 -3
  6. {echotools-2.3.2 → echotools-2.3.4}/src/echotools.egg-info/PKG-INFO +2 -3
  7. {echotools-2.3.2 → echotools-2.3.4}/README.md +0 -0
  8. {echotools-2.3.2 → echotools-2.3.4}/setup.cfg +0 -0
  9. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/__init__.py +0 -0
  10. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/_version.py +0 -0
  11. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/cache/__init__.py +0 -0
  12. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/cache/list_cache.py +0 -0
  13. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/cache/memory_cache.py +0 -0
  14. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/config/__init__.py +0 -0
  15. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/config/base.py +0 -0
  16. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/config/center.py +0 -0
  17. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/config/loader.py +0 -0
  18. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/config/merge.py +0 -0
  19. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/dispatch/__init__.py +0 -0
  20. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/dispatch/candidate.py +0 -0
  21. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/dispatch/dispatcher.py +0 -0
  22. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/dispatch/proxy_selector.py +0 -0
  23. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/dispatch/selector.py +0 -0
  24. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/dispatch/usage.py +0 -0
  25. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/errors/__init__.py +0 -0
  26. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/errors/base.py +0 -0
  27. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/errors/classify.py +0 -0
  28. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/errors/common.py +0 -0
  29. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/errors/http.py +0 -0
  30. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/events/__init__.py +0 -0
  31. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/events/bus.py +0 -0
  32. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/events/event.py +0 -0
  33. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/files/__init__.py +0 -0
  34. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/files/file_util.py +0 -0
  35. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/__init__.py +0 -0
  36. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/parsers/__init__.py +0 -0
  37. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/parsers/stream.py +0 -0
  38. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/parsers/xml_parser.py +0 -0
  39. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/prompt/__init__.py +0 -0
  40. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/prompt/templates.py +0 -0
  41. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/protocols/__init__.py +0 -0
  42. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/registry.py +0 -0
  43. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/shared/__init__.py +0 -0
  44. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/shared/coercion.py +0 -0
  45. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/shared/loop_detect.py +0 -0
  46. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/shared/normalization.py +0 -0
  47. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/fncall/shared/xml_helpers.py +0 -0
  48. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/ids/__init__.py +0 -0
  49. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/ids/generator.py +0 -0
  50. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/io/__init__.py +0 -0
  51. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/io/io_utils.py +0 -0
  52. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/io/printstream.py +0 -0
  53. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/keys.py +0 -0
  54. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/lifecycle/__init__.py +0 -0
  55. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/lifecycle/manager.py +0 -0
  56. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/lifecycle/updater.py +0 -0
  57. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/logger/__init__.py +0 -0
  58. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/logger/manager.py +0 -0
  59. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/network/__init__.py +0 -0
  60. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/network/http_utils.py +0 -0
  61. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/plugin/__init__.py +0 -0
  62. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/plugin/base.py +0 -0
  63. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/plugin/discovery.py +0 -0
  64. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/plugin/registry.py +0 -0
  65. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/process/__init__.py +0 -0
  66. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/process/port.py +0 -0
  67. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/protocol/__init__.py +0 -0
  68. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/protocol/base.py +0 -0
  69. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/proxy/__init__.py +0 -0
  70. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/proxy/manager.py +0 -0
  71. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/py.typed +0 -0
  72. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/retry/__init__.py +0 -0
  73. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/retry/retry.py +0 -0
  74. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/runtime/__init__.py +0 -0
  75. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/runtime/collector.py +0 -0
  76. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/scheduler/__init__.py +0 -0
  77. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/scheduler/scheduler.py +0 -0
  78. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/sdk/__init__.py +0 -0
  79. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/sdk/facade.py +0 -0
  80. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/terminal/__init__.py +0 -0
  81. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/terminal/conpty.py +0 -0
  82. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/terminal/local.py +0 -0
  83. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/terminal/sanitize.py +0 -0
  84. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/terminal/session.py +0 -0
  85. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/terminal/ssh.py +0 -0
  86. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/terminal/tmux.py +0 -0
  87. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/tracing/__init__.py +0 -0
  88. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/tracing/context.py +0 -0
  89. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/tracing/span.py +0 -0
  90. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/tracing/tracer.py +0 -0
  91. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/translate.py +0 -0
  92. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/watcher/__init__.py +0 -0
  93. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/watcher/file_watcher.py +0 -0
  94. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/__init__.py +0 -0
  95. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/application.py +0 -0
  96. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/broker.py +0 -0
  97. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/app.js +0 -0
  98. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/file-zone/file-zone.js +0 -0
  99. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/fonts/inter-400.woff2 +0 -0
  100. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/fonts/inter-500.woff2 +0 -0
  101. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/fonts/inter-600.woff2 +0 -0
  102. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/fonts/tailwind.min.css +0 -0
  103. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/input-box.css +0 -0
  104. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/input-box.js +0 -0
  105. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/motion-kit/motion-kit.js +0 -0
  106. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/send-button/send-button.js +0 -0
  107. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/sortable-list/sortable-list.css +0 -0
  108. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/sortable-list/sortable-list.js +0 -0
  109. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/style.css +0 -0
  110. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/text-input/text-input.js +0 -0
  111. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/input_box/voice-input/voice-input.js +0 -0
  112. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/middleware.py +0 -0
  113. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/stats.py +0 -0
  114. {echotools-2.3.2 → echotools-2.3.4}/src/echotools/web/utils.py +0 -0
  115. {echotools-2.3.2 → echotools-2.3.4}/src/echotools.egg-info/SOURCES.txt +0 -0
  116. {echotools-2.3.2 → echotools-2.3.4}/src/echotools.egg-info/dependency_links.txt +0 -0
  117. {echotools-2.3.2 → echotools-2.3.4}/src/echotools.egg-info/requires.txt +0 -0
  118. {echotools-2.3.2 → echotools-2.3.4}/src/echotools.egg-info/top_level.txt +0 -0
  119. {echotools-2.3.2 → echotools-2.3.4}/tests/test_cache_retry.py +0 -0
  120. {echotools-2.3.2 → echotools-2.3.4}/tests/test_completion.py +0 -0
  121. {echotools-2.3.2 → echotools-2.3.4}/tests/test_config.py +0 -0
  122. {echotools-2.3.2 → echotools-2.3.4}/tests/test_config_extended.py +0 -0
  123. {echotools-2.3.2 → echotools-2.3.4}/tests/test_coverage_core.py +0 -0
  124. {echotools-2.3.2 → echotools-2.3.4}/tests/test_dispatch.py +0 -0
  125. {echotools-2.3.2 → echotools-2.3.4}/tests/test_dispatch_extended.py +0 -0
  126. {echotools-2.3.2 → echotools-2.3.4}/tests/test_dispatch_race.py +0 -0
  127. {echotools-2.3.2 → echotools-2.3.4}/tests/test_errors.py +0 -0
  128. {echotools-2.3.2 → echotools-2.3.4}/tests/test_errors_common.py +0 -0
  129. {echotools-2.3.2 → echotools-2.3.4}/tests/test_events.py +0 -0
  130. {echotools-2.3.2 → echotools-2.3.4}/tests/test_events_event.py +0 -0
  131. {echotools-2.3.2 → echotools-2.3.4}/tests/test_facade.py +0 -0
  132. {echotools-2.3.2 → echotools-2.3.4}/tests/test_fncall.py +0 -0
  133. {echotools-2.3.2 → echotools-2.3.4}/tests/test_ids.py +0 -0
  134. {echotools-2.3.2 → echotools-2.3.4}/tests/test_imports.py +0 -0
  135. {echotools-2.3.2 → echotools-2.3.4}/tests/test_io.py +0 -0
  136. {echotools-2.3.2 → echotools-2.3.4}/tests/test_keys.py +0 -0
  137. {echotools-2.3.2 → echotools-2.3.4}/tests/test_lifecycle.py +0 -0
  138. {echotools-2.3.2 → echotools-2.3.4}/tests/test_logger.py +0 -0
  139. {echotools-2.3.2 → echotools-2.3.4}/tests/test_merge.py +0 -0
  140. {echotools-2.3.2 → echotools-2.3.4}/tests/test_plugin.py +0 -0
  141. {echotools-2.3.2 → echotools-2.3.4}/tests/test_printstream.py +0 -0
  142. {echotools-2.3.2 → echotools-2.3.4}/tests/test_protocols.py +0 -0
  143. {echotools-2.3.2 → echotools-2.3.4}/tests/test_proxy.py +0 -0
  144. {echotools-2.3.2 → echotools-2.3.4}/tests/test_proxy_selector.py +0 -0
  145. {echotools-2.3.2 → echotools-2.3.4}/tests/test_runtime.py +0 -0
  146. {echotools-2.3.2 → echotools-2.3.4}/tests/test_scheduler.py +0 -0
  147. {echotools-2.3.2 → echotools-2.3.4}/tests/test_tracing.py +0 -0
  148. {echotools-2.3.2 → echotools-2.3.4}/tests/test_usage.py +0 -0
  149. {echotools-2.3.2 → echotools-2.3.4}/tests/test_web.py +0 -0
  150. {echotools-2.3.2 → echotools-2.3.4}/tests/test_web_broker.py +0 -0
@@ -1,15 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.2
3
+ Version: 2.3.4
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Author: nichengfuben
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/nichengfuben/echotools
8
8
  Project-URL: Repository, https://github.com/nichengfuben/echotools
9
9
  Project-URL: Issues, https://github.com/nichengfuben/echotools/issues
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: MIT License
13
12
  Classifier: Programming Language :: Python :: 3
14
13
  Classifier: Programming Language :: Python :: 3.8
15
14
  Classifier: Programming Language :: Python :: 3.9
@@ -4,16 +4,15 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "echotools"
7
- version = "2.3.2"
7
+ version = "2.3.4"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
11
- license = { text = "MIT" }
11
+ license = "MIT"
12
12
  authors = [{ name = "nichengfuben" }]
13
13
  classifiers = [
14
14
  "Development Status :: 4 - Beta",
15
15
  "Intended Audience :: Developers",
16
- "License :: OSI Approved :: MIT License",
17
16
  "Programming Language :: Python :: 3",
18
17
  "Programming Language :: Python :: 3.8",
19
18
  "Programming Language :: Python :: 3.9",
@@ -136,4 +135,4 @@ exclude_lines = [
136
135
  "def __repr__",
137
136
  "if TYPE_CHECKING:",
138
137
  "raise NotImplementedError",
139
- ]
138
+ ]
@@ -36,6 +36,45 @@ _TOOL_CALL_MARKER_RE = re.compile(
36
36
  r"|<invoke\s+name=",
37
37
  )
38
38
 
39
+ # entml 标签清理正则
40
+ _ENTML_FNCALL_BLOCK_RE = re.compile(
41
+ r"<entml:function_calls\b[^>]*>.*?</entml:function_calls>",
42
+ re.DOTALL,
43
+ )
44
+ _ENTML_INVOKE_RE = re.compile(
45
+ r'<entml:invoke\s+name="[^"]+"\s*>.*?</entml:invoke>',
46
+ re.DOTALL,
47
+ )
48
+ _ENTML_PARAMETERS_RE = re.compile(
49
+ r'<entml:parameters>.*?</entml:parameters>',
50
+ re.DOTALL,
51
+ )
52
+ _ENTML_PARAM_RE = re.compile(
53
+ r'<entml:parameter\s+name="[^"]+"\s*>.*?</entml:parameter>',
54
+ re.DOTALL,
55
+ )
56
+ # 清理任意 entml:* 标签
57
+ _ENTML_ANY_TAG_RE = re.compile(
58
+ r'<entml:[a-zA-Z_][\w]*\b[^>]*>.*?</entml:[a-zA-Z_][\w]*>',
59
+ re.DOTALL,
60
+ )
61
+
62
+
63
+ def _clean_entml_tags(content: str) -> str:
64
+ """移除内容中的所有 entml 标签及其内部内容。"""
65
+ if not content:
66
+ return content
67
+ cleaned = content
68
+ cleaned = _ENTML_FNCALL_BLOCK_RE.sub("", cleaned)
69
+ cleaned = _ENTML_INVOKE_RE.sub("", cleaned)
70
+ cleaned = _ENTML_PARAMETERS_RE.sub("", cleaned)
71
+ cleaned = _ENTML_PARAM_RE.sub("", cleaned)
72
+ cleaned = _ENTML_ANY_TAG_RE.sub("", cleaned)
73
+ # 清理多余空行
74
+ lines = [line for line in cleaned.splitlines() if line.strip()]
75
+ return "\n".join(lines)
76
+
77
+
39
78
  # ---------------------------------------------------------------------------
40
79
  # 渲染工具调用 / 工具结果
41
80
  # ---------------------------------------------------------------------------
@@ -335,6 +374,7 @@ def _format_conversation_history(
335
374
  protocol: 可选的 ToolProtocol 实例。提供时使用
336
375
  protocol.format_assistant_tool_calls() 渲染工具调用,
337
376
  确保历史中的工具调用格式与 LLM 指令中的格式一致。
377
+ 同时使用 protocol.clean_tags() 清理 user 消息中的 entml 标签。
338
378
 
339
379
  始终渲染所有消息角色(包括 assistant tool_calls 和 tool results),
340
380
  不区分客户端类型。
@@ -346,11 +386,19 @@ def _format_conversation_history(
346
386
  seen_assistant_keys: Set[Tuple[str, Tuple[Tuple[str, str], ...]]] = set()
347
387
  parts: List[Tuple[str, bool]] = []
348
388
 
389
+ # 获取清理函数
390
+ clean_fn = None
391
+ if protocol and hasattr(protocol, 'clean_tags'):
392
+ clean_fn = protocol.clean_tags
393
+
349
394
  for m in messages:
350
395
  role: str = m.get("role") or "user"
351
396
  content_str = normalize_content(m.get("content", ""))
352
397
 
353
398
  if role == "user":
399
+ # 清理用户消息中的 entml 标签
400
+ if clean_fn:
401
+ content_str = clean_fn(content_str)
354
402
  parts.append((f"<user>\n{content_str}\n</user>", False))
355
403
 
356
404
  elif role == "assistant":
@@ -371,7 +419,7 @@ def _format_conversation_history(
371
419
  content_str and _TOOL_CALL_MARKER_RE.search(content_str)
372
420
  )
373
421
  if not content_has_markers:
374
- if protocol is not None:
422
+ if protocol is not None and hasattr(protocol, 'format_assistant_tool_calls'):
375
423
  blocks.append(protocol.format_assistant_tool_calls(tcs))
376
424
  else:
377
425
  for tc in tcs:
@@ -50,7 +50,9 @@ def inject_fncall(
50
50
  dump_dir: Optional[str] = None,
51
51
  ) -> List[Dict[str, Any]]:
52
52
  """注入工具定义为单条 user 消息。
53
-
53
+
54
+ 无论是否有 tools,都构建包含历史的 prompt 返回单条 user 消息。
55
+
54
56
  Args:
55
57
  messages: 消息列表。
56
58
  tools: 工具定义。
@@ -64,26 +66,18 @@ def inject_fncall(
64
66
  Returns:
65
67
  单条 user 消息列表。
66
68
  """
67
- if not tools:
68
- return list(messages)
69
69
  normalized = _normalize_messages(list(messages))
70
- loop_warning = ""
71
- if loop_detection_threshold > 0:
72
- result = detect_tool_loop(normalized, loop_detection_threshold)
73
- if result.is_looping:
74
- logger.debug(
75
- "检测到工具循环(%d 次)", result.repeat_count
76
- )
77
- loop_warning = result.suggestion
70
+
71
+ # 提取最后一条 user 消息和历史
78
72
  last_user_idx: Optional[int] = None
79
73
  for i in range(len(normalized) - 1, -1, -1):
80
74
  if (normalized[i].get("role") or "user") == "user":
81
75
  last_user_idx = i
82
76
  break
77
+
83
78
  if last_user_idx is not None:
84
79
  history_messages = (
85
- normalized[:last_user_idx]
86
- + normalized[last_user_idx + 1 :]
80
+ normalized[:last_user_idx] + normalized[last_user_idx + 1:]
87
81
  )
88
82
  current_user_message = normalize_content(
89
83
  normalized[last_user_idx].get("content", "")
@@ -91,13 +85,34 @@ def inject_fncall(
91
85
  else:
92
86
  history_messages = normalized
93
87
  current_user_message = ""
88
+
89
+ # 清理当前用户消息中的 entml 标签
90
+ if hasattr(protocol, 'clean_tags'):
91
+ current_user_message = protocol.clean_tags(current_user_message)
92
+
93
+ # 构建历史文本(传入 protocol 以便清理 user 消息中的 entml 标签)
94
+ history_text = _format_conversation_history(
95
+ history_messages, protocol=protocol
96
+ ).strip()
97
+
98
+ # 如果没有 tools,直接拼历史 + 当前消息,不注入工具定义
99
+ if not tools:
100
+ prompt = f"<conversation_history>\n{history_text}\n</conversation_history>\n\n<current_user_message>\n{current_user_message}\n</current_user_message>"
101
+ return [{"role": "user", "content": prompt}]
102
+
103
+ # 有 tools:走原有逻辑
104
+ loop_warning = ""
105
+ if loop_detection_threshold > 0:
106
+ result = detect_tool_loop(normalized, loop_detection_threshold)
107
+ if result.is_looping:
108
+ logger.debug("检测到工具循环(%d 次)", result.repeat_count)
109
+ loop_warning = result.suggestion
110
+
94
111
  if hasattr(protocol, 'format_tool_descs'):
95
112
  tool_descs = protocol.format_tool_descs(tools)
96
113
  else:
97
114
  tool_descs = format_tool_descs(tools)
98
- history_text = _format_conversation_history(
99
- history_messages, protocol=protocol
100
- ).strip()
115
+
101
116
  prompt = protocol.render_prompt(
102
117
  tool_descs=tool_descs,
103
118
  lang=lang,
@@ -106,6 +121,8 @@ def inject_fncall(
106
121
  loop_warning=loop_warning,
107
122
  current_user_message=current_user_message,
108
123
  )
124
+
109
125
  if dump_prompt:
110
126
  _maybe_dump_prompt(prompt, dump_dir)
127
+
111
128
  return [{"role": "user", "content": prompt}]
@@ -75,6 +75,29 @@ _PARAMETERS_RE = re.compile(
75
75
  r'<entml:parameters>([\s\S]*?)</entml:parameters>',
76
76
  re.DOTALL,
77
77
  )
78
+ # 用于解析 <entml:parameters> 内的子标签
79
+ _SUB_TAG_RE = re.compile(
80
+ r'<([^>]+)>([\s\S]*?)</\1>',
81
+ re.DOTALL,
82
+ )
83
+
84
+
85
+ def _parse_sub_tags(content: str, schema_index: Optional[Dict[str, Any]] = None, func_name: str = "") -> Dict[str, Any]:
86
+ """解析 <entml:parameters> 内的子标签,返回参数字典。"""
87
+ args: Dict[str, Any] = {}
88
+ for m in _SUB_TAG_RE.finditer(content):
89
+ pname = m.group(1).strip()
90
+ pval = m.group(2).strip()
91
+ pschema = schema_index.get(func_name, {}).get(pname, {}) if schema_index else {}
92
+ if pschema:
93
+ args[pname] = _coerce_param_value(pval, pschema)
94
+ else:
95
+ try:
96
+ args[pname] = json.loads(pval)
97
+ except json.JSONDecodeError:
98
+ args[pname] = pval
99
+ return args
100
+
78
101
 
79
102
  # ---------------------------------------------------------------------------
80
103
  # Entml 协议
@@ -179,11 +202,18 @@ class EntmlProtocol(ToolProtocol):
179
202
  # Try <entml:parameters>{json}</entml:parameters> format first
180
203
  params_m = _PARAMETERS_RE.search(body)
181
204
  if params_m:
182
- params_json = params_m.group(1).strip()
205
+ params_content = params_m.group(1).strip()
183
206
  try:
184
- args = json.loads(params_json)
207
+ # 尝试 JSON 解析
208
+ args = json.loads(params_content)
209
+ if not isinstance(args, dict):
210
+ args = {"value": args}
185
211
  except json.JSONDecodeError:
186
- args = {"value": params_json}
212
+ # 不是 JSON,尝试解析子标签
213
+ args = _parse_sub_tags(params_content, schema_index, name)
214
+ # 如果子标签解析也没结果,整个当作值
215
+ if not args:
216
+ args = {"value": params_content}
187
217
  else:
188
218
  # Try <entml:parameter name="...">value</entml:parameter> format
189
219
  for param_m in _PARAM_RE.finditer(body):
@@ -1,15 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.2
3
+ Version: 2.3.4
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Author: nichengfuben
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/nichengfuben/echotools
8
8
  Project-URL: Repository, https://github.com/nichengfuben/echotools
9
9
  Project-URL: Issues, https://github.com/nichengfuben/echotools/issues
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: MIT License
13
12
  Classifier: Programming Language :: Python :: 3
14
13
  Classifier: Programming Language :: Python :: 3.8
15
14
  Classifier: Programming Language :: Python :: 3.9
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes