echotools 2.3.19__tar.gz → 2.3.22__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {echotools-2.3.19 → echotools-2.3.22}/PKG-INFO +1 -1
- {echotools-2.3.19 → echotools-2.3.22}/pyproject.toml +1 -1
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml_thinking.py +22 -18
- echotools-2.3.22/src/echotools/exec/fncall/protocols/entml_thinking_history.py +133 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools.egg-info/PKG-INFO +1 -1
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools.egg-info/SOURCES.txt +2 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_entml_protocol.py +24 -7
- echotools-2.3.22/src/tests/test_entml_thinking_history.py +71 -0
- {echotools-2.3.19 → echotools-2.3.22}/README.md +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/docs/README.md +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/setup.cfg +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/cache/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/cache/list_cache.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/cache/memory_cache.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/config/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/config/base.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/config/center.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/config/center_notify.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/config/center_template.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/config/loader.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/config/merge.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/errors/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/errors/base.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/errors/classify.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/errors/common.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/errors/http.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/ids/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/ids/generator.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/io/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/io/io_utils.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/io/printstream.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/logger/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/logger/manager.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/retry/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/base/retry/retry.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/compat.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/dispatch/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/dispatch/candidate.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/dispatch/dispatcher.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/dispatch/race.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/dispatch/selector.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/dispatch/usage.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/parsers/stream.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/prompt/history.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/prompt/inject.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/prompt/templates.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml_thinking_parse.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/registry.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/shared/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/shared/coercion.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/shared/normalization.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/keys.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/lifecycle/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/lifecycle/manager.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/lifecycle/updater.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/process/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/process/port.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/protocol/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/protocol/base.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/terminal/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/terminal/conpty.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/terminal/local/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/terminal/local/core.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/terminal/local/proc.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/terminal/local/unix.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/terminal/local/win.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/terminal/sanitize.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/terminal/session/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/terminal/session/base.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/terminal/session/client.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/terminal/ssh.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/terminal/tmux.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/events/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/events/bus.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/events/event.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/spinner/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/spinner/spinner.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/spinner/spinner_color.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/tracing/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/tracing/context.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/tracing/span.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/tracing/tracer.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/translate.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/watcher/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/watcher/file_watcher.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/application.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/broker.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/app.js +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/filezone.js +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/textinput.js +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/fonts/tailwind.css +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/inputbox.css +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/inputbox.js +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/style.css +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/voicegif.js +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/middleware.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/stats.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/utils.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/files/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/files/file_util.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/network/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/network/http_utils.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/plugin/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/plugin/base.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/plugin/discovery.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/plugin/registry.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/plugin/registry_ops.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/proxy/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/proxy/manager.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/runtime/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/runtime/collector.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/scheduler/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/scheduler/scheduler.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/sdk/__init__.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/plat/sdk/facade.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools/version.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools.egg-info/dependency_links.txt +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools.egg-info/requires.txt +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/echotools.egg-info/top_level.txt +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/bench_import.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/conftest.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_cache_retry.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_completion.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_config.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_config_extended.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_coverage_core.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_dispatch.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_dispatch_extended.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_dispatch_race.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_entml_thinking_parse.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_entml_tools.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_errors.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_errors_common.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_events.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_events_event.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_facade.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_fncall.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_ids.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_imports.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_io.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_keys.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_lifecycle.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_logger.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_merge.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_plugin.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_printstream.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_protocols.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_proxy.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_proxy_selector.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_runtime.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_scheduler.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_tracing.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_usage.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_web.py +0 -0
- {echotools-2.3.19 → echotools-2.3.22}/src/tests/test_web_broker.py +0 -0
|
@@ -47,9 +47,23 @@ def normalize_thinking_mode(mode: Any) -> Optional[str]:
|
|
|
47
47
|
return None
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def parse_max_thinking_length(value: Any) -> Optional[int]:
|
|
51
|
+
"""仅当显式传入正整数时返回长度;否则为 None(不注入标签)。"""
|
|
52
|
+
if value is None:
|
|
53
|
+
return None
|
|
54
|
+
if isinstance(value, str) and not value.strip():
|
|
55
|
+
return None
|
|
56
|
+
try:
|
|
57
|
+
parsed = int(value)
|
|
58
|
+
except (TypeError, ValueError):
|
|
59
|
+
return None
|
|
60
|
+
if parsed <= 0:
|
|
61
|
+
return None
|
|
62
|
+
return parsed
|
|
63
|
+
|
|
64
|
+
|
|
50
65
|
def _policy_header(mode: str) -> str:
|
|
51
66
|
labels = {
|
|
52
|
-
"off": "off (forced no thinking)",
|
|
53
67
|
"on": "on (forced thinking)",
|
|
54
68
|
"auto": "auto (model decides)",
|
|
55
69
|
}
|
|
@@ -59,17 +73,6 @@ def _policy_header(mode: str) -> str:
|
|
|
59
73
|
)
|
|
60
74
|
|
|
61
75
|
|
|
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
76
|
def _prompt_on() -> list[str]:
|
|
74
77
|
return [
|
|
75
78
|
_policy_header("on"),
|
|
@@ -115,10 +118,13 @@ def _prompt_auto() -> list[str]:
|
|
|
115
118
|
def build_entml_thinking_section(
|
|
116
119
|
protocol_options: Optional[Dict[str, Any]] = None,
|
|
117
120
|
) -> str:
|
|
118
|
-
"""按 thinking_mode 构建思考链指令块(
|
|
121
|
+
"""按 thinking_mode 构建思考链指令块(on / auto);off 时不注入任何内容。"""
|
|
119
122
|
opts = protocol_options or {}
|
|
120
123
|
mode = normalize_thinking_mode(opts.get("thinking_mode"))
|
|
121
|
-
max_thinking_length = opts.get("max_thinking_length")
|
|
124
|
+
max_thinking_length = parse_max_thinking_length(opts.get("max_thinking_length"))
|
|
125
|
+
|
|
126
|
+
if mode == "off":
|
|
127
|
+
return ""
|
|
122
128
|
|
|
123
129
|
if mode is None and max_thinking_length is None:
|
|
124
130
|
return ""
|
|
@@ -128,12 +134,10 @@ def build_entml_thinking_section(
|
|
|
128
134
|
lines.append(f"<entml:thinking_mode>{mode}</entml:thinking_mode>")
|
|
129
135
|
if max_thinking_length is not None:
|
|
130
136
|
lines.append(
|
|
131
|
-
f"<entml:max_thinking_length>{
|
|
137
|
+
f"<entml:max_thinking_length>{max_thinking_length}</entml:max_thinking_length>"
|
|
132
138
|
)
|
|
133
139
|
|
|
134
|
-
if mode == "
|
|
135
|
-
lines.extend([""] + _prompt_off())
|
|
136
|
-
elif mode == "on":
|
|
140
|
+
if mode == "on":
|
|
137
141
|
lines.extend([""] + _prompt_on())
|
|
138
142
|
elif mode == "auto":
|
|
139
143
|
lines.extend([""] + _prompt_auto())
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Mapping, Optional
|
|
4
|
+
|
|
5
|
+
REASONING_KEYS = ("reasoning", "reasoning_content", "reasoning_details")
|
|
6
|
+
HISTORY_FLAG_KEYS = (
|
|
7
|
+
"include_thinking_in_history",
|
|
8
|
+
"pass_thinking",
|
|
9
|
+
"include_thinking",
|
|
10
|
+
"interleaved_history",
|
|
11
|
+
)
|
|
12
|
+
_THINKING_BLOCK_TYPES = frozenset({"thinking", "reasoning", "redacted_thinking"})
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def parse_interleaved_history(
|
|
16
|
+
body: Mapping[str, Any],
|
|
17
|
+
extra: Optional[Mapping[str, Any]] = None,
|
|
18
|
+
thinking: Any = None,
|
|
19
|
+
) -> bool:
|
|
20
|
+
"""解析是否将历史 assistant 思考链传给模型(Entropy interleaved_history)。"""
|
|
21
|
+
extra = extra or {}
|
|
22
|
+
if thinking is None:
|
|
23
|
+
thinking = body.get("thinking")
|
|
24
|
+
for key in ("interleaved_history", "include_in_history"):
|
|
25
|
+
if key in body:
|
|
26
|
+
return bool(body[key])
|
|
27
|
+
if key in extra:
|
|
28
|
+
return bool(extra[key])
|
|
29
|
+
if isinstance(thinking, dict):
|
|
30
|
+
for key in ("interleaved_history", "include_in_history"):
|
|
31
|
+
if key in thinking:
|
|
32
|
+
return bool(thinking[key])
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _thinking_from_content_block(block: Any) -> str:
|
|
37
|
+
if not isinstance(block, dict):
|
|
38
|
+
return ""
|
|
39
|
+
btype = str(block.get("type", "")).strip().lower()
|
|
40
|
+
if btype in ("thinking", "redacted_thinking"):
|
|
41
|
+
val = block.get("thinking") or block.get("data")
|
|
42
|
+
return str(val).strip() if val else ""
|
|
43
|
+
if btype == "reasoning":
|
|
44
|
+
val = block.get("text") or block.get("reasoning")
|
|
45
|
+
return str(val).strip() if val else ""
|
|
46
|
+
return ""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def extract_reasoning_text(msg: Mapping[str, Any]) -> str:
|
|
50
|
+
"""从 assistant 消息字段或 Entropy/Anthropic 内容块提取思考文本。"""
|
|
51
|
+
for key in ("reasoning", "reasoning_content"):
|
|
52
|
+
val = msg.get(key)
|
|
53
|
+
if isinstance(val, str) and val.strip():
|
|
54
|
+
return val.strip()
|
|
55
|
+
|
|
56
|
+
details = msg.get("reasoning_details")
|
|
57
|
+
if isinstance(details, list):
|
|
58
|
+
parts: List[str] = []
|
|
59
|
+
for item in details:
|
|
60
|
+
if not isinstance(item, dict):
|
|
61
|
+
continue
|
|
62
|
+
text = item.get("text")
|
|
63
|
+
if text:
|
|
64
|
+
parts.append(str(text))
|
|
65
|
+
if parts:
|
|
66
|
+
return "".join(parts)
|
|
67
|
+
|
|
68
|
+
content = msg.get("content")
|
|
69
|
+
if isinstance(content, list):
|
|
70
|
+
parts = []
|
|
71
|
+
for block in content:
|
|
72
|
+
text = _thinking_from_content_block(block)
|
|
73
|
+
if text:
|
|
74
|
+
parts.append(text)
|
|
75
|
+
if parts:
|
|
76
|
+
return "\n".join(parts)
|
|
77
|
+
return ""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _strip_thinking_blocks(content: List[Any]) -> List[Any]:
|
|
81
|
+
kept: List[Any] = []
|
|
82
|
+
for block in content:
|
|
83
|
+
if isinstance(block, dict) and str(block.get("type", "")).lower() in _THINKING_BLOCK_TYPES:
|
|
84
|
+
continue
|
|
85
|
+
kept.append(block)
|
|
86
|
+
return kept
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _collapse_text_content(blocks: List[Any]) -> Any:
|
|
90
|
+
if len(blocks) == 1:
|
|
91
|
+
only = blocks[0]
|
|
92
|
+
if isinstance(only, dict) and only.get("type") == "text":
|
|
93
|
+
return only.get("text", "")
|
|
94
|
+
return blocks
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _strip_reasoning_from_message(msg: Dict[str, Any]) -> Dict[str, Any]:
|
|
98
|
+
out = dict(msg)
|
|
99
|
+
for key in REASONING_KEYS:
|
|
100
|
+
out.pop(key, None)
|
|
101
|
+
content = out.get("content")
|
|
102
|
+
if isinstance(content, list):
|
|
103
|
+
stripped = _strip_thinking_blocks(content)
|
|
104
|
+
out["content"] = _collapse_text_content(stripped) if stripped else ""
|
|
105
|
+
return out
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _normalize_message_with_reasoning(msg: Dict[str, Any]) -> Dict[str, Any]:
|
|
109
|
+
out = dict(msg)
|
|
110
|
+
if out.get("role") != "assistant":
|
|
111
|
+
for key in REASONING_KEYS:
|
|
112
|
+
out.pop(key, None)
|
|
113
|
+
return out
|
|
114
|
+
|
|
115
|
+
text = extract_reasoning_text(out)
|
|
116
|
+
if text:
|
|
117
|
+
out["reasoning"] = text
|
|
118
|
+
out.setdefault("reasoning_content", text)
|
|
119
|
+
return out
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def apply_thinking_history_policy(
|
|
123
|
+
messages: List[Dict[str, Any]],
|
|
124
|
+
include: bool,
|
|
125
|
+
) -> List[Dict[str, Any]]:
|
|
126
|
+
"""按 interleaved_history 保留或剥离历史思考链。
|
|
127
|
+
|
|
128
|
+
include=True: 保留 reasoning 字段与 content 中的 thinking 块。
|
|
129
|
+
include=False: 仅保留可见回复,剥离思考链。
|
|
130
|
+
"""
|
|
131
|
+
if include:
|
|
132
|
+
return [_normalize_message_with_reasoning(dict(m)) for m in messages]
|
|
133
|
+
return [_strip_reasoning_from_message(dict(m)) for m in messages]
|
|
@@ -58,6 +58,7 @@ src/echotools/exec/fncall/protocols/entml.py
|
|
|
58
58
|
src/echotools/exec/fncall/protocols/entml_invoke.py
|
|
59
59
|
src/echotools/exec/fncall/protocols/entml_patterns.py
|
|
60
60
|
src/echotools/exec/fncall/protocols/entml_thinking.py
|
|
61
|
+
src/echotools/exec/fncall/protocols/entml_thinking_history.py
|
|
61
62
|
src/echotools/exec/fncall/protocols/entml_thinking_parse.py
|
|
62
63
|
src/echotools/exec/fncall/protocols/entml_tools.py
|
|
63
64
|
src/echotools/exec/fncall/protocols/entml_values.py
|
|
@@ -151,6 +152,7 @@ src/tests/test_dispatch.py
|
|
|
151
152
|
src/tests/test_dispatch_extended.py
|
|
152
153
|
src/tests/test_dispatch_race.py
|
|
153
154
|
src/tests/test_entml_protocol.py
|
|
155
|
+
src/tests/test_entml_thinking_history.py
|
|
154
156
|
src/tests/test_entml_thinking_parse.py
|
|
155
157
|
src/tests/test_entml_tools.py
|
|
156
158
|
src/tests/test_errors.py
|
|
@@ -72,6 +72,17 @@ def test_build_entml_thinking_section_empty_without_options() -> None:
|
|
|
72
72
|
assert build_entml_thinking_section({}) == ""
|
|
73
73
|
|
|
74
74
|
|
|
75
|
+
def test_parse_max_thinking_length() -> None:
|
|
76
|
+
from echotools.exec.fncall.protocols.entml_thinking import parse_max_thinking_length
|
|
77
|
+
|
|
78
|
+
assert parse_max_thinking_length(None) is None
|
|
79
|
+
assert parse_max_thinking_length("") is None
|
|
80
|
+
assert parse_max_thinking_length(" ") is None
|
|
81
|
+
assert parse_max_thinking_length(0) is None
|
|
82
|
+
assert parse_max_thinking_length(22000) == 22000
|
|
83
|
+
assert parse_max_thinking_length("22000") == 22000
|
|
84
|
+
|
|
85
|
+
|
|
75
86
|
@pytest.mark.parametrize(
|
|
76
87
|
"raw,expected",
|
|
77
88
|
[
|
|
@@ -90,11 +101,16 @@ def test_normalize_thinking_mode(raw, expected) -> None:
|
|
|
90
101
|
|
|
91
102
|
|
|
92
103
|
def test_thinking_prompt_off() -> None:
|
|
93
|
-
|
|
94
|
-
assert
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
104
|
+
assert build_entml_thinking_section({"thinking_mode": "off"}) == ""
|
|
105
|
+
assert build_entml_thinking_section(
|
|
106
|
+
{"thinking_mode": "off", "max_thinking_length": 22000}
|
|
107
|
+
) == ""
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_thinking_prompt_on_without_max_length() -> None:
|
|
111
|
+
section = build_entml_thinking_section({"thinking_mode": "on"})
|
|
112
|
+
assert "<entml:thinking_mode>on</entml:thinking_mode>" in section
|
|
113
|
+
assert "<entml:max_thinking_length>" not in section
|
|
98
114
|
|
|
99
115
|
|
|
100
116
|
def test_thinking_prompt_on() -> None:
|
|
@@ -124,8 +140,9 @@ def test_inject_no_tools_with_thinking_off() -> None:
|
|
|
124
140
|
proto,
|
|
125
141
|
protocol_options={"thinking_mode": "off"},
|
|
126
142
|
)[0]["content"]
|
|
127
|
-
assert "<entml:thinking_mode>
|
|
128
|
-
assert "
|
|
143
|
+
assert "<entml:thinking_mode>" not in result
|
|
144
|
+
assert "<entml:max_thinking_length>" not in result
|
|
145
|
+
assert "You must NOT output any thinking blocks" not in result
|
|
129
146
|
assert "<current_user_message>\nhi\n</current_user_message>" in result
|
|
130
147
|
|
|
131
148
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from echotools.exec.fncall.protocols.entml_thinking_history import (
|
|
4
|
+
apply_thinking_history_policy,
|
|
5
|
+
extract_reasoning_text,
|
|
6
|
+
parse_interleaved_history,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_parse_interleaved_history_from_thinking_object() -> None:
|
|
11
|
+
body = {"thinking": {"mode": "on", "interleaved_history": True}}
|
|
12
|
+
assert parse_interleaved_history(body) is True
|
|
13
|
+
assert parse_interleaved_history({"thinking": {"interleaved_history": False}}) is False
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_parse_interleaved_history_top_level() -> None:
|
|
17
|
+
assert parse_interleaved_history({"interleaved_history": True}) is True
|
|
18
|
+
assert parse_interleaved_history({}, extra={"interleaved_history": True}) is True
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_strip_reasoning_fields_when_disabled() -> None:
|
|
22
|
+
msgs = [
|
|
23
|
+
{
|
|
24
|
+
"role": "assistant",
|
|
25
|
+
"content": "answer",
|
|
26
|
+
"reasoning": "thought",
|
|
27
|
+
"reasoning_content": "thought",
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
out = apply_thinking_history_policy(msgs, False)
|
|
31
|
+
assert out[0]["content"] == "answer"
|
|
32
|
+
assert "reasoning" not in out[0]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_strip_entropy_thinking_blocks_when_disabled() -> None:
|
|
36
|
+
msgs = [
|
|
37
|
+
{
|
|
38
|
+
"role": "assistant",
|
|
39
|
+
"content": [
|
|
40
|
+
{"type": "thinking", "thinking": "step 1"},
|
|
41
|
+
{"type": "text", "text": "hello"},
|
|
42
|
+
],
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
out = apply_thinking_history_policy(msgs, False)
|
|
46
|
+
assert out[0]["content"] == "hello"
|
|
47
|
+
assert "reasoning" not in out[0]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_preserve_entropy_thinking_blocks_when_enabled() -> None:
|
|
51
|
+
msgs = [
|
|
52
|
+
{
|
|
53
|
+
"role": "assistant",
|
|
54
|
+
"content": [
|
|
55
|
+
{"type": "thinking", "thinking": "step 1"},
|
|
56
|
+
{"type": "text", "text": "hello"},
|
|
57
|
+
],
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
out = apply_thinking_history_policy(msgs, True)
|
|
61
|
+
assert out[0]["reasoning"] == "step 1"
|
|
62
|
+
assert out[0]["reasoning_content"] == "step 1"
|
|
63
|
+
assert out[0]["content"][0]["type"] == "thinking"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_extract_reasoning_text_from_blocks() -> None:
|
|
67
|
+
msg = {
|
|
68
|
+
"role": "assistant",
|
|
69
|
+
"content": [{"type": "thinking", "thinking": "a"}, {"type": "text", "text": "b"}],
|
|
70
|
+
}
|
|
71
|
+
assert extract_reasoning_text(msg) == "a"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{echotools-2.3.19 → echotools-2.3.22}/src/echotools/exec/fncall/protocols/entml_thinking_parse.py
RENAMED
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/filezone.js
RENAMED
|
File without changes
|
{echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/motionkit.js
RENAMED
|
File without changes
|
{echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/sendbutton.js
RENAMED
|
File without changes
|
{echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/sortablelist.css
RENAMED
|
File without changes
|
{echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/sortablelist.js
RENAMED
|
File without changes
|
{echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/textinput.js
RENAMED
|
File without changes
|
{echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/components/voiceinput.js
RENAMED
|
File without changes
|
{echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/fonts/inter-500.woff2
RENAMED
|
File without changes
|
{echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/fonts/inter-600.woff2
RENAMED
|
File without changes
|
{echotools-2.3.19 → echotools-2.3.22}/src/echotools/media/web/input_box/fonts/inter400.woff2
RENAMED
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|