echotools 2.3.29__tar.gz → 2.3.31__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 (188) hide show
  1. {echotools-2.3.29 → echotools-2.3.31}/PKG-INFO +1 -1
  2. {echotools-2.3.29 → echotools-2.3.31}/pyproject.toml +1 -1
  3. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/prompt/history_format.py +17 -2
  4. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/prompt/inject.py +15 -2
  5. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/prompt/prompt_helpers.py +38 -6
  6. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/prompt/templates.py +9 -0
  7. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml.py +7 -1
  8. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_thinking_history.py +23 -0
  9. {echotools-2.3.29 → echotools-2.3.31}/src/echotools.egg-info/PKG-INFO +1 -1
  10. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_entml_protocol.py +83 -0
  11. {echotools-2.3.29 → echotools-2.3.31}/README.md +0 -0
  12. {echotools-2.3.29 → echotools-2.3.31}/docs/README.md +0 -0
  13. {echotools-2.3.29 → echotools-2.3.31}/setup.cfg +0 -0
  14. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/__init__.py +0 -0
  15. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/cache/__init__.py +0 -0
  16. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/cache/list_cache.py +0 -0
  17. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/cache/memory_cache.py +0 -0
  18. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/__init__.py +0 -0
  19. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/base.py +0 -0
  20. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/center.py +0 -0
  21. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/center_notify.py +0 -0
  22. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/center_template.py +0 -0
  23. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/loader.py +0 -0
  24. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/merge.py +0 -0
  25. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/errors/__init__.py +0 -0
  26. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/errors/base.py +0 -0
  27. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/errors/classify.py +0 -0
  28. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/errors/common.py +0 -0
  29. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/errors/http.py +0 -0
  30. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/ids/__init__.py +0 -0
  31. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/ids/generator.py +0 -0
  32. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/io/__init__.py +0 -0
  33. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/io/io_utils.py +0 -0
  34. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/io/printstream.py +0 -0
  35. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/logger/__init__.py +0 -0
  36. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/logger/manager.py +0 -0
  37. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/retry/__init__.py +0 -0
  38. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/retry/retry.py +0 -0
  39. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/compat.py +0 -0
  40. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/__init__.py +0 -0
  41. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/candidate.py +0 -0
  42. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/dispatcher.py +0 -0
  43. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
  44. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/race.py +0 -0
  45. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/selector.py +0 -0
  46. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/usage.py +0 -0
  47. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/__init__.py +0 -0
  48. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
  49. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/parsers/stream.py +0 -0
  50. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
  51. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
  52. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
  53. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/prompt/history.py +0 -0
  54. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
  55. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
  56. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
  57. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_thinking.py +0 -0
  58. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_thinking_parse.py +0 -0
  59. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
  60. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
  61. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/registry.py +0 -0
  62. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/shared/__init__.py +0 -0
  63. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/shared/coercion.py +0 -0
  64. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
  65. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/shared/normalization.py +0 -0
  66. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
  67. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
  68. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/keys.py +0 -0
  69. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/lifecycle/__init__.py +0 -0
  70. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/lifecycle/manager.py +0 -0
  71. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/lifecycle/updater.py +0 -0
  72. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
  73. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/process/__init__.py +0 -0
  74. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/process/port.py +0 -0
  75. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/protocol/__init__.py +0 -0
  76. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/protocol/base.py +0 -0
  77. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/__init__.py +0 -0
  78. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/conpty.py +0 -0
  79. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/local/__init__.py +0 -0
  80. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/local/core.py +0 -0
  81. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/local/proc.py +0 -0
  82. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/local/unix.py +0 -0
  83. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/local/win.py +0 -0
  84. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/sanitize.py +0 -0
  85. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/session/__init__.py +0 -0
  86. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/session/base.py +0 -0
  87. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/session/client.py +0 -0
  88. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/ssh.py +0 -0
  89. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/tmux.py +0 -0
  90. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/console/__init__.py +0 -0
  91. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/console/charmap.py +0 -0
  92. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/console/spinner.py +0 -0
  93. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/console/ui.py +0 -0
  94. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/events/__init__.py +0 -0
  95. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/events/bus.py +0 -0
  96. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/events/event.py +0 -0
  97. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/spinner/__init__.py +0 -0
  98. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/spinner/spinner.py +0 -0
  99. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/spinner/spinner_color.py +0 -0
  100. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/tracing/__init__.py +0 -0
  101. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/tracing/context.py +0 -0
  102. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/tracing/span.py +0 -0
  103. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/tracing/tracer.py +0 -0
  104. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/translate.py +0 -0
  105. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/watcher/__init__.py +0 -0
  106. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/watcher/file_watcher.py +0 -0
  107. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/__init__.py +0 -0
  108. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/application.py +0 -0
  109. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/broker.py +0 -0
  110. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/app.js +0 -0
  111. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/filezone.js +0 -0
  112. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
  113. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
  114. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
  115. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
  116. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/textinput.js +0 -0
  117. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
  118. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
  119. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
  120. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
  121. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/fonts/tailwind.css +0 -0
  122. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/inputbox.css +0 -0
  123. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/inputbox.js +0 -0
  124. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/style.css +0 -0
  125. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/voicegif.js +0 -0
  126. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/middleware.py +0 -0
  127. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/stats.py +0 -0
  128. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/utils.py +0 -0
  129. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/files/__init__.py +0 -0
  130. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/files/file_util.py +0 -0
  131. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/network/__init__.py +0 -0
  132. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/network/http_utils.py +0 -0
  133. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/plugin/__init__.py +0 -0
  134. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/plugin/base.py +0 -0
  135. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/plugin/discovery.py +0 -0
  136. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/plugin/registry.py +0 -0
  137. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/plugin/registry_ops.py +0 -0
  138. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/proxy/__init__.py +0 -0
  139. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/proxy/manager.py +0 -0
  140. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/runtime/__init__.py +0 -0
  141. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/runtime/collector.py +0 -0
  142. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/scheduler/__init__.py +0 -0
  143. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/scheduler/scheduler.py +0 -0
  144. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/sdk/__init__.py +0 -0
  145. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/sdk/facade.py +0 -0
  146. {echotools-2.3.29 → echotools-2.3.31}/src/echotools/version.py +0 -0
  147. {echotools-2.3.29 → echotools-2.3.31}/src/echotools.egg-info/SOURCES.txt +0 -0
  148. {echotools-2.3.29 → echotools-2.3.31}/src/echotools.egg-info/dependency_links.txt +0 -0
  149. {echotools-2.3.29 → echotools-2.3.31}/src/echotools.egg-info/requires.txt +0 -0
  150. {echotools-2.3.29 → echotools-2.3.31}/src/echotools.egg-info/top_level.txt +0 -0
  151. {echotools-2.3.29 → echotools-2.3.31}/src/tests/bench_import.py +0 -0
  152. {echotools-2.3.29 → echotools-2.3.31}/src/tests/conftest.py +0 -0
  153. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_cache_retry.py +0 -0
  154. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_completion.py +0 -0
  155. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_config.py +0 -0
  156. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_config_extended.py +0 -0
  157. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_console.py +0 -0
  158. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_coverage_core.py +0 -0
  159. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_dispatch.py +0 -0
  160. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_dispatch_extended.py +0 -0
  161. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_dispatch_race.py +0 -0
  162. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_entml_thinking_history.py +0 -0
  163. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_entml_thinking_parse.py +0 -0
  164. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_entml_tools.py +0 -0
  165. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_errors.py +0 -0
  166. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_errors_common.py +0 -0
  167. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_events.py +0 -0
  168. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_events_event.py +0 -0
  169. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_facade.py +0 -0
  170. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_fncall.py +0 -0
  171. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_ids.py +0 -0
  172. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_imports.py +0 -0
  173. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_io.py +0 -0
  174. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_keys.py +0 -0
  175. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_lifecycle.py +0 -0
  176. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_logger.py +0 -0
  177. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_merge.py +0 -0
  178. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_plugin.py +0 -0
  179. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_printstream.py +0 -0
  180. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_protocols.py +0 -0
  181. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_proxy.py +0 -0
  182. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_proxy_selector.py +0 -0
  183. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_runtime.py +0 -0
  184. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_scheduler.py +0 -0
  185. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_tracing.py +0 -0
  186. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_usage.py +0 -0
  187. {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_web.py +0 -0
  188. {echotools-2.3.29 → echotools-2.3.31}/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.29
3
+ Version: 2.3.31
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.29"
7
+ version = "2.3.31"
8
8
  description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
9
9
  readme = "docs/README.md"
10
10
  requires-python = ">=3.8"
@@ -15,9 +15,22 @@ from .prompt_helpers import (
15
15
  )
16
16
 
17
17
 
18
+ def history_contains_tool_calls(messages: List[Dict[str, Any]]) -> bool:
19
+ """历史消息中是否包含 assistant tool_calls 或 tool 结果。"""
20
+ for m in messages:
21
+ role = m.get("role") or "user"
22
+ if role == "assistant" and m.get("tool_calls"):
23
+ return True
24
+ if role == "tool":
25
+ return True
26
+ return False
27
+
28
+
18
29
  def _format_conversation_history(
19
30
  messages: List[Dict[str, Any]],
20
31
  protocol: Optional[Any] = None,
32
+ *,
33
+ include_thinking_in_history: bool = False,
21
34
  ) -> str:
22
35
  if not messages:
23
36
  return ""
@@ -48,14 +61,16 @@ def _format_conversation_history(
48
61
  tool_msgs.append(messages[j])
49
62
  j += 1
50
63
  block = format_assistant_block_with_results(
51
- m, tool_msgs, protocol, call_id_to_name, seen_assistant_keys
64
+ m, tool_msgs, protocol, call_id_to_name, seen_assistant_keys,
65
+ include_thinking_in_history=include_thinking_in_history,
52
66
  )
53
67
  if block:
54
68
  parts.append((block, False))
55
69
  i = j
56
70
  else:
57
71
  block = format_assistant_block(
58
- m, content_str, protocol, call_id_to_name, seen_assistant_keys
72
+ m, content_str, protocol, call_id_to_name, seen_assistant_keys,
73
+ include_thinking_in_history=include_thinking_in_history,
59
74
  )
60
75
  if block:
61
76
  parts.append((block, False))
@@ -12,6 +12,7 @@ from echotools.exec.fncall.prompt.history import (
12
12
  )
13
13
  from echotools.exec.fncall.prompt.history_format import (
14
14
  _format_conversation_history,
15
+ history_contains_tool_calls,
15
16
  )
16
17
  from echotools.exec.fncall.prompt.prompt_helpers import (
17
18
  build_no_tools_prompt,
@@ -22,6 +23,10 @@ from echotools.exec.fncall.protocols.entml import (
22
23
  format_entml_current_user_message,
23
24
  )
24
25
  from echotools.exec.fncall.protocols.entml_thinking import build_entml_thinking_section
26
+ from echotools.exec.fncall.protocols.entml_thinking_history import (
27
+ apply_thinking_history_policy,
28
+ parse_include_thinking_in_history,
29
+ )
25
30
  from echotools.exec.fncall.shared.loop_detect import detect_tool_loop
26
31
  from echotools.exec.fncall.shared.normalization import format_tool_descs
27
32
  from echotools.exec.protocol.base import ToolProtocol
@@ -57,6 +62,7 @@ def build_tools_prompt(
57
62
  current_user_message: str,
58
63
  loop_detection_threshold: int,
59
64
  protocol_options: Optional[Dict[str, Any]] = None,
65
+ history_has_tool_calls: bool = False,
60
66
  ) -> str:
61
67
  loop_warning = ""
62
68
  if loop_detection_threshold > 0:
@@ -73,6 +79,8 @@ def build_tools_prompt(
73
79
  if protocol_options is not None and protocol.id == "entml"
74
80
  else {}
75
81
  )
82
+ if protocol.id == "entml":
83
+ extra["history_has_tool_calls"] = history_has_tool_calls
76
84
  return protocol.render_prompt(
77
85
  tool_descs=tool_descs,
78
86
  lang=lang,
@@ -96,14 +104,18 @@ def inject_fncall(
96
104
  protocol_options: Optional[Dict[str, Any]] = None,
97
105
  ) -> List[Dict[str, Any]]:
98
106
  """注入工具定义,返回单条 user 消息(含历史)。"""
99
- normalized = _normalize_messages(list(messages))
107
+ include_history = parse_include_thinking_in_history(protocol_options)
108
+ prepared = apply_thinking_history_policy(list(messages), include_history)
109
+ normalized = _normalize_messages(prepared)
100
110
  history_messages, current_user_message = split_last_user_message(normalized)
101
111
 
102
112
  if hasattr(protocol, 'clean_tags'):
103
113
  current_user_message = protocol.clean_tags(current_user_message)
104
114
 
105
115
  history_text = _format_conversation_history(
106
- history_messages, protocol=protocol
116
+ history_messages,
117
+ protocol=protocol,
118
+ include_thinking_in_history=include_history,
107
119
  ).strip()
108
120
 
109
121
  if not tools:
@@ -125,6 +137,7 @@ def inject_fncall(
125
137
  prompt = build_tools_prompt(
126
138
  protocol, tools, normalized, lang, user_system_prompt,
127
139
  history_text, current_user_message, loop_detection_threshold, protocol_options,
140
+ history_has_tool_calls=history_contains_tool_calls(history_messages),
128
141
  )
129
142
  if dump_prompt:
130
143
  _maybe_dump_prompt(prompt, dump_dir)
@@ -15,9 +15,37 @@ from .history import (
15
15
  _try_convert_user_to_tool,
16
16
  )
17
17
 
18
+ from echotools.exec.fncall.protocols.entml_thinking_history import (
19
+ extract_reasoning_text,
20
+ format_entml_thinking_history_block,
21
+ )
22
+
18
23
  logger = get_logger(__name__)
19
24
 
20
25
 
26
+ def _assistant_history_content_blocks(
27
+ m: Dict[str, Any],
28
+ content_str: str,
29
+ protocol: Optional[Any],
30
+ include_thinking_in_history: bool,
31
+ ) -> List[str]:
32
+ """构建 assistant 历史块正文(可选前置 entml:thinking)。"""
33
+ blocks: List[str] = []
34
+ if (
35
+ include_thinking_in_history
36
+ and protocol is not None
37
+ and getattr(protocol, "id", None) == "entml"
38
+ ):
39
+ reasoning = extract_reasoning_text(m)
40
+ if reasoning:
41
+ thinking_block = format_entml_thinking_history_block(reasoning)
42
+ if thinking_block:
43
+ blocks.append(thinking_block)
44
+ if content_str:
45
+ blocks.append(content_str)
46
+ return blocks
47
+
48
+
21
49
  def split_last_user_message(
22
50
  normalized: List[Dict[str, Any]],
23
51
  ) -> Tuple[List[Dict[str, Any]], str]:
@@ -52,11 +80,13 @@ def format_assistant_block(
52
80
  protocol: Optional[Any],
53
81
  call_id_to_name: Dict[str, str],
54
82
  seen_assistant_keys: Set[Tuple[str, Tuple[Tuple[str, str], ...]]],
83
+ *,
84
+ include_thinking_in_history: bool = False,
55
85
  ) -> Optional[str]:
56
86
  tcs: List[Dict[str, Any]] = m.get("tool_calls") or []
57
- blocks: List[str] = []
58
- if content_str:
59
- blocks.append(content_str)
87
+ blocks = _assistant_history_content_blocks(
88
+ m, content_str, protocol, include_thinking_in_history
89
+ )
60
90
  for tc in tcs:
61
91
  cid = tc.get("id") or ""
62
92
  fn_name = (tc.get("function") or {}).get("name") or ""
@@ -87,6 +117,8 @@ def format_assistant_block_with_results(
87
117
  protocol: Optional[Any],
88
118
  call_id_to_name: Dict[str, str],
89
119
  seen_assistant_keys: Set[Tuple[str, Tuple[Tuple[str, str], ...]]],
120
+ *,
121
+ include_thinking_in_history: bool = False,
90
122
  ) -> Optional[str]:
91
123
  tcs: List[Dict[str, Any]] = m.get("tool_calls") or []
92
124
  content_str = normalize_content(m.get("content", ""))
@@ -102,9 +134,9 @@ def format_assistant_block_with_results(
102
134
  for tmsg in tool_msgs
103
135
  }
104
136
 
105
- blocks: List[str] = []
106
- if content_str:
107
- blocks.append(content_str)
137
+ blocks = _assistant_history_content_blocks(
138
+ m, content_str, protocol, include_thinking_in_history
139
+ )
108
140
 
109
141
  for tc in tcs:
110
142
  if protocol is not None and hasattr(protocol, "format_assistant_tool_calls"):
@@ -118,6 +118,15 @@ _HISTORY_CLARIFY_EN = (
118
118
  "The user's latest message follows below."
119
119
  )
120
120
 
121
+ _HISTORY_TOOL_INVOKE_REMINDER_EN = (
122
+ "Reminder — tool notation in the conversation history above: prior tool "
123
+ "calls appear as compact pseudocode (e.g. `[function_name(arg=\"value\")]` "
124
+ "and optional `→ Result:` / `→ Error:` lines). That transcript is for "
125
+ "context only; it is not parsed and must not be copied. To invoke a tool "
126
+ "in your reply, output a valid `<entml:invoke>` block exactly as specified "
127
+ "in the tool instructions at the top of this prompt."
128
+ )
129
+
121
130
  _HISTORY_CLARIFY_ZH = (
122
131
  "以下是已完成的交互记录。"
123
132
  "此处展示的所有工具调用及其结果均已执行完毕,请勿重复调用。"
@@ -10,7 +10,10 @@ from __future__ import annotations
10
10
  import json as _json
11
11
  from typing import Any, Dict, List, Optional, Tuple
12
12
 
13
- from echotools.exec.fncall.prompt.templates import _HISTORY_CLARIFY_EN
13
+ from echotools.exec.fncall.prompt.templates import (
14
+ _HISTORY_CLARIFY_EN,
15
+ _HISTORY_TOOL_INVOKE_REMINDER_EN,
16
+ )
14
17
  from echotools.exec.fncall.protocols.entml_invoke import (
15
18
  format_entml_tool_calls,
16
19
  parse_entml_tool_calls,
@@ -117,6 +120,7 @@ class EntmlProtocol(ToolProtocol):
117
120
  loop_warning: str = "",
118
121
  current_user_message: str = "",
119
122
  protocol_options: Optional[Dict[str, Any]] = None,
123
+ history_has_tool_calls: bool = False,
120
124
  ) -> str:
121
125
  if tool_descs:
122
126
  sections: List[str] = [
@@ -138,6 +142,8 @@ class EntmlProtocol(ToolProtocol):
138
142
  sections.append(
139
143
  format_entml_conversation_history(history_text, _HISTORY_CLARIFY_EN)
140
144
  )
145
+ if tool_descs and history_has_tool_calls:
146
+ sections.append(_HISTORY_TOOL_INVOKE_REMINDER_EN)
141
147
 
142
148
  if loop_warning:
143
149
  sections.append(f"<loop_warning>\n{loop_warning}\n</loop_warning>")
@@ -11,6 +11,29 @@ HISTORY_FLAG_KEYS = (
11
11
  )
12
12
  _THINKING_BLOCK_TYPES = frozenset({"thinking", "reasoning", "redacted_thinking"})
13
13
 
14
+ _THINKING_BLOCK_OPEN = "<entml:thinking>"
15
+ _THINKING_BLOCK_CLOSE = "</entml:thinking>"
16
+
17
+
18
+ def format_entml_thinking_history_block(text: str) -> str:
19
+ """将历史 assistant 思考链格式化为 entml 块(供 conversation_history 使用)。"""
20
+ stripped = (text or "").strip()
21
+ if not stripped:
22
+ return ""
23
+ return f"{_THINKING_BLOCK_OPEN}\n{stripped}\n{_THINKING_BLOCK_CLOSE}"
24
+
25
+
26
+ def parse_include_thinking_in_history(
27
+ protocol_options: Optional[Mapping[str, Any]],
28
+ ) -> bool:
29
+ """从 protocol_options 解析是否在历史中渲染 assistant 思考链。"""
30
+ if not protocol_options:
31
+ return False
32
+ for key in HISTORY_FLAG_KEYS:
33
+ if key in protocol_options:
34
+ return bool(protocol_options[key])
35
+ return False
36
+
14
37
 
15
38
  def parse_interleaved_history(
16
39
  body: Mapping[str, Any],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echotools
3
- Version: 2.3.29
3
+ Version: 2.3.31
4
4
  Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
5
5
  Author: nichengfuben
6
6
  License: MIT
@@ -207,6 +207,49 @@ def test_entml_history_clarify_always_english() -> None:
207
207
  content = inject_fncall(msgs, tools, proto, lang="zh")[0]["content"]
208
208
  assert "The following is a transcript of completed interactions." in content
209
209
  assert "以下是已完成的交互记录" not in content
210
+ assert "Reminder — tool notation in the conversation history above" not in content
211
+
212
+
213
+ def test_entml_history_tool_invoke_reminder_when_tools_in_history() -> None:
214
+ proto = get_protocol("entml")
215
+ tools = [
216
+ {
217
+ "type": "function",
218
+ "function": {
219
+ "name": "search",
220
+ "description": "Search",
221
+ "parameters": {
222
+ "type": "object",
223
+ "properties": {"query": {"type": "string"}},
224
+ },
225
+ },
226
+ }
227
+ ]
228
+ msgs = [
229
+ {"role": "user", "content": "find docs"},
230
+ {
231
+ "role": "assistant",
232
+ "content": "",
233
+ "tool_calls": [{
234
+ "id": "call_1",
235
+ "type": "function",
236
+ "function": {
237
+ "name": "search",
238
+ "arguments": json.dumps({"query": "docs"}),
239
+ },
240
+ }],
241
+ },
242
+ {"role": "tool", "tool_call_id": "call_1", "content": "found 3"},
243
+ {"role": "user", "content": "summarize"},
244
+ ]
245
+ content = inject_fncall(msgs, tools, proto)[0]["content"]
246
+ assert "[search(query=\"docs\")]" in content
247
+ assert "Reminder — tool notation in the conversation history above" in content
248
+ assert "output a valid `<entml:invoke>` block" in content
249
+ hist_idx = content.index("<entml:conversation_history>")
250
+ reminder_idx = content.index("Reminder — tool notation")
251
+ user_idx = content.index("<current_user_message>")
252
+ assert hist_idx < reminder_idx < user_idx
210
253
 
211
254
 
212
255
  @pytest.mark.parametrize(
@@ -426,3 +469,43 @@ def test_entml_parse_schema_coercion() -> None:
426
469
  calls = parse_entml_tool_calls(sample, tools, schema_index)
427
470
  args = json.loads(calls[0]["function"]["arguments"])
428
471
  assert args == {"count": 7, "enabled": True, "tags": ["x", "y"]}
472
+
473
+
474
+ def test_inject_include_thinking_in_history() -> None:
475
+ """protocol_options.include_thinking_in_history 应在历史中渲染 entml:thinking。"""
476
+ proto = get_protocol("entml")
477
+ tools = [
478
+ {
479
+ "type": "function",
480
+ "function": {
481
+ "name": "get_weather",
482
+ "description": "weather",
483
+ "parameters": {
484
+ "type": "object",
485
+ "properties": {"city": {"type": "string"}},
486
+ "required": ["city"],
487
+ },
488
+ },
489
+ }
490
+ ]
491
+ msgs = [
492
+ {"role": "user", "content": "查北京天气"},
493
+ {
494
+ "role": "assistant",
495
+ "reasoning": "应先调用 get_weather 获取实时数据。",
496
+ "content": "我来查一下。",
497
+ },
498
+ {"role": "user", "content": "那上海呢?"},
499
+ ]
500
+ plain = inject_fncall(msgs, tools, proto)[0]["content"]
501
+ assert "应先调用 get_weather" not in plain
502
+
503
+ with_history = inject_fncall(
504
+ msgs,
505
+ tools,
506
+ proto,
507
+ protocol_options={"include_thinking_in_history": True},
508
+ )[0]["content"]
509
+ assert "<entml:thinking>" in with_history
510
+ assert "应先调用 get_weather 获取实时数据。" in with_history
511
+ assert "那上海呢?" in with_history
File without changes
File without changes
File without changes