echotools 2.3.16__tar.gz → 2.3.19__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 (182) hide show
  1. {echotools-2.3.16 → echotools-2.3.19}/PKG-INFO +1 -1
  2. {echotools-2.3.16 → echotools-2.3.19}/pyproject.toml +1 -1
  3. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/prompt/inject.py +6 -0
  4. echotools-2.3.19/src/echotools/exec/fncall/protocols/entml_thinking.py +141 -0
  5. {echotools-2.3.16 → echotools-2.3.19}/src/echotools.egg-info/PKG-INFO +1 -1
  6. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_entml_protocol.py +81 -7
  7. echotools-2.3.16/src/echotools/exec/fncall/protocols/entml_thinking.py +0 -53
  8. {echotools-2.3.16 → echotools-2.3.19}/README.md +0 -0
  9. {echotools-2.3.16 → echotools-2.3.19}/docs/README.md +0 -0
  10. {echotools-2.3.16 → echotools-2.3.19}/setup.cfg +0 -0
  11. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/__init__.py +0 -0
  12. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/cache/__init__.py +0 -0
  13. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/cache/list_cache.py +0 -0
  14. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/cache/memory_cache.py +0 -0
  15. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/config/__init__.py +0 -0
  16. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/config/base.py +0 -0
  17. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/config/center.py +0 -0
  18. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/config/center_notify.py +0 -0
  19. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/config/center_template.py +0 -0
  20. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/config/loader.py +0 -0
  21. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/config/merge.py +0 -0
  22. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/errors/__init__.py +0 -0
  23. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/errors/base.py +0 -0
  24. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/errors/classify.py +0 -0
  25. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/errors/common.py +0 -0
  26. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/errors/http.py +0 -0
  27. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/ids/__init__.py +0 -0
  28. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/ids/generator.py +0 -0
  29. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/io/__init__.py +0 -0
  30. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/io/io_utils.py +0 -0
  31. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/io/printstream.py +0 -0
  32. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/logger/__init__.py +0 -0
  33. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/logger/manager.py +0 -0
  34. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/retry/__init__.py +0 -0
  35. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/base/retry/retry.py +0 -0
  36. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/compat.py +0 -0
  37. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/dispatch/__init__.py +0 -0
  38. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/dispatch/candidate.py +0 -0
  39. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/dispatch/dispatcher.py +0 -0
  40. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
  41. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/dispatch/race.py +0 -0
  42. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/dispatch/selector.py +0 -0
  43. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/dispatch/usage.py +0 -0
  44. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/__init__.py +0 -0
  45. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
  46. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/parsers/stream.py +0 -0
  47. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
  48. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
  49. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
  50. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/prompt/history.py +0 -0
  51. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
  52. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
  53. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/prompt/templates.py +0 -0
  54. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
  55. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/protocols/entml.py +0 -0
  56. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
  57. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
  58. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/protocols/entml_thinking_parse.py +0 -0
  59. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
  60. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
  61. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/registry.py +0 -0
  62. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/shared/__init__.py +0 -0
  63. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/shared/coercion.py +0 -0
  64. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
  65. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/shared/normalization.py +0 -0
  66. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
  67. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
  68. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/keys.py +0 -0
  69. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/lifecycle/__init__.py +0 -0
  70. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/lifecycle/manager.py +0 -0
  71. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/lifecycle/updater.py +0 -0
  72. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
  73. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/process/__init__.py +0 -0
  74. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/process/port.py +0 -0
  75. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/protocol/__init__.py +0 -0
  76. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/protocol/base.py +0 -0
  77. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/terminal/__init__.py +0 -0
  78. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/terminal/conpty.py +0 -0
  79. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/terminal/local/__init__.py +0 -0
  80. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/terminal/local/core.py +0 -0
  81. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/terminal/local/proc.py +0 -0
  82. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/terminal/local/unix.py +0 -0
  83. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/terminal/local/win.py +0 -0
  84. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/terminal/sanitize.py +0 -0
  85. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/terminal/session/__init__.py +0 -0
  86. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/terminal/session/base.py +0 -0
  87. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/terminal/session/client.py +0 -0
  88. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/terminal/ssh.py +0 -0
  89. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/exec/terminal/tmux.py +0 -0
  90. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/events/__init__.py +0 -0
  91. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/events/bus.py +0 -0
  92. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/events/event.py +0 -0
  93. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/spinner/__init__.py +0 -0
  94. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/spinner/spinner.py +0 -0
  95. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/spinner/spinner_color.py +0 -0
  96. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/tracing/__init__.py +0 -0
  97. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/tracing/context.py +0 -0
  98. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/tracing/span.py +0 -0
  99. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/tracing/tracer.py +0 -0
  100. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/translate.py +0 -0
  101. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/watcher/__init__.py +0 -0
  102. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/watcher/file_watcher.py +0 -0
  103. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/__init__.py +0 -0
  104. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/application.py +0 -0
  105. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/broker.py +0 -0
  106. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/app.js +0 -0
  107. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/components/filezone.js +0 -0
  108. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
  109. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
  110. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
  111. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
  112. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/components/textinput.js +0 -0
  113. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
  114. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
  115. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
  116. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
  117. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/fonts/tailwind.css +0 -0
  118. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/inputbox.css +0 -0
  119. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/inputbox.js +0 -0
  120. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/style.css +0 -0
  121. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/input_box/voicegif.js +0 -0
  122. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/middleware.py +0 -0
  123. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/stats.py +0 -0
  124. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/media/web/utils.py +0 -0
  125. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/files/__init__.py +0 -0
  126. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/files/file_util.py +0 -0
  127. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/network/__init__.py +0 -0
  128. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/network/http_utils.py +0 -0
  129. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/plugin/__init__.py +0 -0
  130. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/plugin/base.py +0 -0
  131. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/plugin/discovery.py +0 -0
  132. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/plugin/registry.py +0 -0
  133. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/plugin/registry_ops.py +0 -0
  134. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/proxy/__init__.py +0 -0
  135. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/proxy/manager.py +0 -0
  136. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/runtime/__init__.py +0 -0
  137. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/runtime/collector.py +0 -0
  138. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/scheduler/__init__.py +0 -0
  139. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/scheduler/scheduler.py +0 -0
  140. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/sdk/__init__.py +0 -0
  141. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/plat/sdk/facade.py +0 -0
  142. {echotools-2.3.16 → echotools-2.3.19}/src/echotools/version.py +0 -0
  143. {echotools-2.3.16 → echotools-2.3.19}/src/echotools.egg-info/SOURCES.txt +0 -0
  144. {echotools-2.3.16 → echotools-2.3.19}/src/echotools.egg-info/dependency_links.txt +0 -0
  145. {echotools-2.3.16 → echotools-2.3.19}/src/echotools.egg-info/requires.txt +0 -0
  146. {echotools-2.3.16 → echotools-2.3.19}/src/echotools.egg-info/top_level.txt +0 -0
  147. {echotools-2.3.16 → echotools-2.3.19}/src/tests/bench_import.py +0 -0
  148. {echotools-2.3.16 → echotools-2.3.19}/src/tests/conftest.py +0 -0
  149. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_cache_retry.py +0 -0
  150. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_completion.py +0 -0
  151. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_config.py +0 -0
  152. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_config_extended.py +0 -0
  153. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_coverage_core.py +0 -0
  154. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_dispatch.py +0 -0
  155. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_dispatch_extended.py +0 -0
  156. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_dispatch_race.py +0 -0
  157. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_entml_thinking_parse.py +0 -0
  158. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_entml_tools.py +0 -0
  159. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_errors.py +0 -0
  160. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_errors_common.py +0 -0
  161. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_events.py +0 -0
  162. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_events_event.py +0 -0
  163. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_facade.py +0 -0
  164. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_fncall.py +0 -0
  165. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_ids.py +0 -0
  166. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_imports.py +0 -0
  167. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_io.py +0 -0
  168. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_keys.py +0 -0
  169. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_lifecycle.py +0 -0
  170. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_logger.py +0 -0
  171. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_merge.py +0 -0
  172. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_plugin.py +0 -0
  173. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_printstream.py +0 -0
  174. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_protocols.py +0 -0
  175. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_proxy.py +0 -0
  176. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_proxy_selector.py +0 -0
  177. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_runtime.py +0 -0
  178. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_scheduler.py +0 -0
  179. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_tracing.py +0 -0
  180. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_usage.py +0 -0
  181. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_web.py +0 -0
  182. {echotools-2.3.16 → echotools-2.3.19}/src/tests/test_web_broker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.16
3
+ Version: 2.3.19
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 = "2.3.16"
7
+ version = "2.3.19"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "docs/README.md"
10
10
  requires-python = ">=3.8"
@@ -21,6 +21,7 @@ from echotools.exec.fncall.protocols.entml import (
21
21
  format_entml_conversation_history,
22
22
  format_entml_current_user_message,
23
23
  )
24
+ from echotools.exec.fncall.protocols.entml_thinking import build_entml_thinking_section
24
25
  from echotools.exec.fncall.shared.loop_detect import detect_tool_loop
25
26
  from echotools.exec.fncall.shared.normalization import format_tool_descs
26
27
  from echotools.exec.protocol.base import ToolProtocol
@@ -108,12 +109,17 @@ def inject_fncall(
108
109
  if not tools:
109
110
  if protocol.id == "entml":
110
111
  sections: List[str] = []
112
+ thinking_section = build_entml_thinking_section(protocol_options)
113
+ if thinking_section:
114
+ sections.append(thinking_section)
111
115
  if history_text.strip():
112
116
  sections.append(format_entml_conversation_history(history_text))
113
117
  sections.append(format_entml_current_user_message(current_user_message))
114
118
  prompt = "\n\n".join(sections)
115
119
  else:
116
120
  prompt = build_no_tools_prompt(history_text, current_user_message)
121
+ if dump_prompt:
122
+ _maybe_dump_prompt(prompt, dump_dir)
117
123
  return [{"role": "user", "content": prompt}]
118
124
 
119
125
  prompt = build_tools_prompt(
@@ -0,0 +1,141 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Optional
4
+
5
+ _CANONICAL_MODES = frozenset({"off", "on", "auto"})
6
+
7
+ _OFF_ALIASES = frozenset(
8
+ {"off", "disabled", "disable", "false", "none", "no", "never"}
9
+ )
10
+ _ON_ALIASES = frozenset(
11
+ {
12
+ "on",
13
+ "enabled",
14
+ "enable",
15
+ "true",
16
+ "force",
17
+ "forced",
18
+ "required",
19
+ "must",
20
+ "static",
21
+ "thinking",
22
+ }
23
+ )
24
+ _AUTO_ALIASES = frozenset(
25
+ {"auto", "automatic", "adaptive", "interleaved"}
26
+ )
27
+
28
+ _THINKING_BLOCK_OPEN = "<entml:thinking>"
29
+ _THINKING_BLOCK_CLOSE = "</entml:thinking>"
30
+
31
+
32
+ def normalize_thinking_mode(mode: Any) -> Optional[str]:
33
+ """将外界声明归一化为 off | on | auto;无法识别时返回 None。"""
34
+ if mode is None:
35
+ return None
36
+ key = str(mode).strip().lower()
37
+ if not key:
38
+ return None
39
+ if key in _OFF_ALIASES:
40
+ return "off"
41
+ if key in _ON_ALIASES:
42
+ return "on"
43
+ if key in _AUTO_ALIASES:
44
+ return "auto"
45
+ if key in _CANONICAL_MODES:
46
+ return key
47
+ return None
48
+
49
+
50
+ def _policy_header(mode: str) -> str:
51
+ labels = {
52
+ "off": "off (forced no thinking)",
53
+ "on": "on (forced thinking)",
54
+ "auto": "auto (model decides)",
55
+ }
56
+ return (
57
+ f"The thinking_mode for this request is {labels[mode]}. "
58
+ f"Follow the rules below exactly."
59
+ )
60
+
61
+
62
+ def _prompt_off() -> list[str]:
63
+ return [
64
+ _policy_header("off"),
65
+ "",
66
+ "You must NOT output any thinking blocks in this response.",
67
+ f"Do not write `{_THINKING_BLOCK_OPEN}` or `{_THINKING_BLOCK_CLOSE}`.",
68
+ "Respond with only the visible answer text and any "
69
+ "`<entml:function_calls>` blocks if tools are needed.",
70
+ ]
71
+
72
+
73
+ def _prompt_on() -> list[str]:
74
+ return [
75
+ _policy_header("on"),
76
+ "",
77
+ "You MUST output a thinking block before any other content.",
78
+ "At the very start of your response, output:",
79
+ "",
80
+ _THINKING_BLOCK_OPEN,
81
+ "...your reasoning here...",
82
+ _THINKING_BLOCK_CLOSE,
83
+ "",
84
+ "Only after that block, output your visible reply and/or "
85
+ "`<entml:function_calls>`. Never skip the thinking block.",
86
+ ]
87
+
88
+
89
+ def _prompt_auto() -> list[str]:
90
+ return [
91
+ _policy_header("auto"),
92
+ "",
93
+ "Thinking blocks are optional but encouraged when they would help.",
94
+ "At the very start of your response, think carefully about whether "
95
+ f"a `{_THINKING_BLOCK_OPEN}` `{_THINKING_BLOCK_CLOSE}` block would be "
96
+ "appropriate and strongly prefer to output one if you are uncertain.",
97
+ "",
98
+ "After function results, you should strongly consider outputting a "
99
+ "thinking block before continuing. Example:",
100
+ "",
101
+ "<entml:function_calls>",
102
+ "...",
103
+ "</entml:function_calls>",
104
+ "",
105
+ "<function_results>",
106
+ "...",
107
+ "</function_results>",
108
+ "",
109
+ _THINKING_BLOCK_OPEN,
110
+ "...thinking about results...",
111
+ _THINKING_BLOCK_CLOSE,
112
+ ]
113
+
114
+
115
+ def build_entml_thinking_section(
116
+ protocol_options: Optional[Dict[str, Any]] = None,
117
+ ) -> str:
118
+ """按 thinking_mode 构建思考链指令块(off / on / auto 三套 prompt)。"""
119
+ opts = protocol_options or {}
120
+ mode = normalize_thinking_mode(opts.get("thinking_mode"))
121
+ max_thinking_length = opts.get("max_thinking_length")
122
+
123
+ if mode is None and max_thinking_length is None:
124
+ return ""
125
+
126
+ lines: list[str] = []
127
+ if mode is not None:
128
+ lines.append(f"<entml:thinking_mode>{mode}</entml:thinking_mode>")
129
+ if max_thinking_length is not None:
130
+ lines.append(
131
+ f"<entml:max_thinking_length>{int(max_thinking_length)}</entml:max_thinking_length>"
132
+ )
133
+
134
+ if mode == "off":
135
+ lines.extend([""] + _prompt_off())
136
+ elif mode == "on":
137
+ lines.extend([""] + _prompt_on())
138
+ elif mode == "auto":
139
+ lines.extend([""] + _prompt_auto())
140
+
141
+ return "\n".join(lines)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.16
3
+ Version: 2.3.19
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Author: nichengfuben
6
6
  License: MIT
@@ -6,7 +6,10 @@ import pytest
6
6
 
7
7
  from echotools.exec.fncall import get_protocol, inject_fncall
8
8
  from echotools.exec.fncall.protocols.entml_invoke import parse_entml_tool_calls
9
- from echotools.exec.fncall.protocols.entml_thinking import build_entml_thinking_section
9
+ from echotools.exec.fncall.protocols.entml_thinking import (
10
+ build_entml_thinking_section,
11
+ normalize_thinking_mode,
12
+ )
10
13
  from echotools.exec.fncall.protocols.entml_values import coerce_entml_parameter_value
11
14
  from echotools.exec.fncall.shared.coercion import _build_param_schema_index
12
15
 
@@ -54,13 +57,13 @@ def test_inject_with_thinking_options_only_when_declared() -> None:
54
57
  tools,
55
58
  proto,
56
59
  protocol_options={
57
- "thinking_mode": "interleaved",
60
+ "thinking_mode": "on",
58
61
  "max_thinking_length": 22000,
59
62
  },
60
63
  )[0]["content"]
61
- assert "<entml:thinking_mode>interleaved</entml:thinking_mode>" in with_opts
64
+ assert "<entml:thinking_mode>on</entml:thinking_mode>" in with_opts
62
65
  assert "<entml:max_thinking_length>22000</entml:max_thinking_length>" in with_opts
63
- assert "<entml:thinking>" in with_opts
66
+ assert "MUST output a thinking block" in with_opts
64
67
  assert "At the very start of your response" in with_opts
65
68
 
66
69
 
@@ -69,6 +72,76 @@ def test_build_entml_thinking_section_empty_without_options() -> None:
69
72
  assert build_entml_thinking_section({}) == ""
70
73
 
71
74
 
75
+ @pytest.mark.parametrize(
76
+ "raw,expected",
77
+ [
78
+ ("off", "off"),
79
+ ("disabled", "off"),
80
+ ("on", "on"),
81
+ ("enabled", "on"),
82
+ ("interleaved", "auto"),
83
+ ("auto", "auto"),
84
+ ("adaptive", "auto"),
85
+ ("bogus", None),
86
+ ],
87
+ )
88
+ def test_normalize_thinking_mode(raw, expected) -> None:
89
+ assert normalize_thinking_mode(raw) == expected
90
+
91
+
92
+ def test_thinking_prompt_off() -> None:
93
+ section = build_entml_thinking_section({"thinking_mode": "off"})
94
+ assert "<entml:thinking_mode>off</entml:thinking_mode>" in section
95
+ assert "forced no thinking" in section
96
+ assert "You must NOT output any thinking blocks" in section
97
+ assert "MUST output a thinking block" not in section
98
+
99
+
100
+ def test_thinking_prompt_on() -> None:
101
+ section = build_entml_thinking_section({"thinking_mode": "on"})
102
+ assert "<entml:thinking_mode>on</entml:thinking_mode>" in section
103
+ assert "forced thinking" in section
104
+ assert "MUST output a thinking block" in section
105
+ assert "<entml:thinking>" in section
106
+ assert "You must NOT output any thinking blocks" not in section
107
+
108
+
109
+ def test_thinking_prompt_auto() -> None:
110
+ section = build_entml_thinking_section({"thinking_mode": "auto"})
111
+ assert "<entml:thinking_mode>auto</entml:thinking_mode>" in section
112
+ assert "model decides" in section
113
+ assert "<function_results>" in section
114
+ assert "strongly prefer to output one if you are uncertain" in section
115
+ assert "MUST output a thinking block" not in section
116
+
117
+
118
+ def test_inject_no_tools_with_thinking_off() -> None:
119
+ proto = get_protocol("entml")
120
+ msgs = [{"role": "user", "content": "hi"}]
121
+ result = inject_fncall(
122
+ msgs,
123
+ [],
124
+ proto,
125
+ protocol_options={"thinking_mode": "off"},
126
+ )[0]["content"]
127
+ assert "<entml:thinking_mode>off</entml:thinking_mode>" in result
128
+ assert "You must NOT output any thinking blocks" in result
129
+ assert "<current_user_message>\nhi\n</current_user_message>" in result
130
+
131
+
132
+ def test_inject_no_tools_with_thinking_on() -> None:
133
+ proto = get_protocol("entml")
134
+ msgs = [{"role": "user", "content": "hi"}]
135
+ result = inject_fncall(
136
+ msgs,
137
+ [],
138
+ proto,
139
+ protocol_options={"thinking_mode": "on"},
140
+ )[0]["content"]
141
+ assert "<entml:thinking_mode>on</entml:thinking_mode>" in result
142
+ assert "MUST output a thinking block" in result
143
+
144
+
72
145
  def test_inject_with_history_entml_tags() -> None:
73
146
  proto = get_protocol("entml")
74
147
  tools = [
@@ -167,7 +240,7 @@ def test_entml_instruction_matches_spec_format() -> None:
167
240
  lang="en",
168
241
  current_user_message="pick one",
169
242
  protocol_options={
170
- "thinking_mode": "interleaved",
243
+ "thinking_mode": "on",
171
244
  "max_thinking_length": 22000,
172
245
  },
173
246
  )
@@ -179,9 +252,10 @@ def test_entml_instruction_matches_spec_format() -> None:
179
252
  assert '<entml:invoke name="$FUNCTION_NAME">' in prompt
180
253
  assert '<entml:parameter name="$PARAMETER_NAME">' in prompt
181
254
  assert "String and scalar parameters should be specified as is" in prompt
182
- assert "<entml:thinking_mode>interleaved</entml:thinking_mode>" in prompt
255
+ assert "<entml:thinking_mode>on</entml:thinking_mode>" in prompt
183
256
  assert "<entml:max_thinking_length>22000</entml:max_thinking_length>" in prompt
184
- assert "<function_results>" in prompt
257
+ assert "MUST output a thinking block" in prompt
258
+ assert "<function_results>" not in prompt
185
259
  assert "<entml:conversation_history>" not in prompt
186
260
  assert "<entml:history>" not in prompt
187
261
  assert "<functions>" not in prompt
@@ -1,53 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any, Dict, Optional
4
-
5
-
6
- def build_entml_thinking_section(
7
- protocol_options: Optional[Dict[str, Any]] = None,
8
- ) -> str:
9
- """仅当外界声明 thinking_mode / max_thinking_length 时构建思考链指令块。"""
10
- opts = protocol_options or {}
11
- thinking_mode = opts.get("thinking_mode")
12
- max_thinking_length = opts.get("max_thinking_length")
13
-
14
- if thinking_mode is None and max_thinking_length is None:
15
- return ""
16
-
17
- lines: list[str] = []
18
- if thinking_mode is not None:
19
- mode = str(thinking_mode).strip()
20
- if mode:
21
- lines.append(f"<entml:thinking_mode>{mode}</entml:thinking_mode>")
22
- if max_thinking_length is not None:
23
- lines.append(
24
- f"<entml:max_thinking_length>{int(max_thinking_length)}</entml:max_thinking_length>"
25
- )
26
-
27
- mode_lower = str(thinking_mode or "").strip().lower()
28
- if mode_lower in ("interleaved", "auto"):
29
- lines.extend(
30
- [
31
- "",
32
- "If the thinking_mode is interleaved or auto, then after function "
33
- "results you should strongly consider outputting a thinking block. "
34
- "Here is an example:",
35
- "",
36
- "<entml:function_calls>",
37
- "...",
38
- "</entml:function_calls>",
39
- "",
40
- "<function_results>",
41
- "...",
42
- "</function_results>",
43
- "",
44
- "<entml:thinking>",
45
- "...thinking about results",
46
- "</entml:thinking>",
47
- "At the very start of your response, think carefully about whether "
48
- "an `<entml:thinking>` `</entml:thinking>` block would be appropriate "
49
- "and strongly prefer to output a thinking block if you are uncertain.",
50
- ]
51
- )
52
-
53
- return "\n".join(lines)
File without changes
File without changes
File without changes