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.
- {echotools-2.3.29 → echotools-2.3.31}/PKG-INFO +1 -1
- {echotools-2.3.29 → echotools-2.3.31}/pyproject.toml +1 -1
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/prompt/history_format.py +17 -2
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/prompt/inject.py +15 -2
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/prompt/prompt_helpers.py +38 -6
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/prompt/templates.py +9 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml.py +7 -1
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_thinking_history.py +23 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools.egg-info/PKG-INFO +1 -1
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_entml_protocol.py +83 -0
- {echotools-2.3.29 → echotools-2.3.31}/README.md +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/docs/README.md +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/setup.cfg +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/cache/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/cache/list_cache.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/cache/memory_cache.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/base.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/center.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/center_notify.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/center_template.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/loader.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/config/merge.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/errors/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/errors/base.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/errors/classify.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/errors/common.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/errors/http.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/ids/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/ids/generator.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/io/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/io/io_utils.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/io/printstream.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/logger/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/logger/manager.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/retry/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/base/retry/retry.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/compat.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/candidate.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/dispatcher.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/race.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/selector.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/dispatch/usage.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/parsers/stream.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/prompt/history.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_thinking.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_thinking_parse.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/registry.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/shared/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/shared/coercion.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/shared/normalization.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/keys.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/lifecycle/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/lifecycle/manager.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/lifecycle/updater.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/process/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/process/port.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/protocol/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/protocol/base.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/conpty.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/local/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/local/core.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/local/proc.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/local/unix.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/local/win.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/sanitize.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/session/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/session/base.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/session/client.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/ssh.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/terminal/tmux.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/console/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/console/charmap.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/console/spinner.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/console/ui.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/events/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/events/bus.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/events/event.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/spinner/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/spinner/spinner.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/spinner/spinner_color.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/tracing/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/tracing/context.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/tracing/span.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/tracing/tracer.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/translate.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/watcher/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/watcher/file_watcher.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/application.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/broker.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/app.js +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/filezone.js +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/textinput.js +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/fonts/tailwind.css +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/inputbox.css +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/inputbox.js +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/style.css +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/voicegif.js +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/middleware.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/stats.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/utils.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/files/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/files/file_util.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/network/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/network/http_utils.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/plugin/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/plugin/base.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/plugin/discovery.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/plugin/registry.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/plugin/registry_ops.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/proxy/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/proxy/manager.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/runtime/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/runtime/collector.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/scheduler/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/scheduler/scheduler.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/sdk/__init__.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/plat/sdk/facade.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools/version.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools.egg-info/SOURCES.txt +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools.egg-info/dependency_links.txt +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools.egg-info/requires.txt +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/echotools.egg-info/top_level.txt +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/bench_import.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/conftest.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_cache_retry.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_completion.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_config.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_config_extended.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_console.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_coverage_core.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_dispatch.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_dispatch_extended.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_dispatch_race.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_entml_thinking_history.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_entml_thinking_parse.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_entml_tools.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_errors.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_errors_common.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_events.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_events_event.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_facade.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_fncall.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_ids.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_imports.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_io.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_keys.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_lifecycle.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_logger.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_merge.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_plugin.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_printstream.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_protocols.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_proxy.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_proxy_selector.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_runtime.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_scheduler.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_tracing.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_usage.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_web.py +0 -0
- {echotools-2.3.29 → echotools-2.3.31}/src/tests/test_web_broker.py +0 -0
|
@@ -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
|
-
|
|
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,
|
|
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
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
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>")
|
{echotools-2.3.29 → echotools-2.3.31}/src/echotools/exec/fncall/protocols/entml_thinking_history.py
RENAMED
|
@@ -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],
|
|
@@ -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
|
|
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.29 → echotools-2.3.31}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/filezone.js
RENAMED
|
File without changes
|
{echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/motionkit.js
RENAMED
|
File without changes
|
{echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/sendbutton.js
RENAMED
|
File without changes
|
{echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/sortablelist.css
RENAMED
|
File without changes
|
{echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/sortablelist.js
RENAMED
|
File without changes
|
{echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/textinput.js
RENAMED
|
File without changes
|
{echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/components/voiceinput.js
RENAMED
|
File without changes
|
{echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/fonts/inter-500.woff2
RENAMED
|
File without changes
|
{echotools-2.3.29 → echotools-2.3.31}/src/echotools/media/web/input_box/fonts/inter-600.woff2
RENAMED
|
File without changes
|
{echotools-2.3.29 → echotools-2.3.31}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|