echotools 2.3.6__tar.gz → 2.3.7__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.6 → echotools-2.3.7}/PKG-INFO +1 -1
- {echotools-2.3.6 → echotools-2.3.7}/pyproject.toml +1 -1
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/__init__.py +4 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/parsers/xml_parser.py +4 -3
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/protocols/entml.py +2 -1
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/shared/__init__.py +4 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/shared/normalization.py +88 -1
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools.egg-info/PKG-INFO +1 -1
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_fncall.py +31 -0
- {echotools-2.3.6 → echotools-2.3.7}/README.md +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/setup.cfg +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/_version.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/cache/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/cache/list_cache.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/cache/memory_cache.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/config/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/config/base.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/config/center.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/config/loader.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/config/merge.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/dispatch/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/dispatch/candidate.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/dispatch/dispatcher.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/dispatch/proxy_selector.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/dispatch/selector.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/dispatch/usage.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/errors/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/errors/base.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/errors/classify.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/errors/common.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/errors/http.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/events/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/events/bus.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/events/event.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/files/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/files/file_util.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/parsers/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/parsers/stream.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/prompt/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/prompt/history.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/prompt/inject.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/prompt/templates.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/protocols/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/registry.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/shared/coercion.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/shared/loop_detect.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/fncall/shared/xml_helpers.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/ids/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/ids/generator.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/io/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/io/io_utils.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/io/printstream.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/keys.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/lifecycle/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/lifecycle/manager.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/lifecycle/updater.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/logger/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/logger/manager.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/network/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/network/http_utils.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/plugin/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/plugin/base.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/plugin/discovery.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/plugin/registry.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/process/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/process/port.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/protocol/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/protocol/base.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/proxy/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/proxy/manager.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/py.typed +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/retry/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/retry/retry.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/runtime/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/runtime/collector.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/scheduler/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/scheduler/scheduler.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/sdk/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/sdk/facade.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/spinner/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/spinner/spinner.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/terminal/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/terminal/conpty.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/terminal/local.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/terminal/sanitize.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/terminal/session.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/terminal/ssh.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/terminal/tmux.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/tracing/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/tracing/context.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/tracing/span.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/tracing/tracer.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/translate.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/watcher/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/watcher/file_watcher.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/__init__.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/application.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/broker.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/app.js +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/file_zone/file-zone.js +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/fonts/inter-400.woff2 +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/fonts/inter-500.woff2 +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/fonts/inter-600.woff2 +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/fonts/tailwind.min.css +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/input-box.css +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/input-box.js +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/motion_kit/motion-kit.js +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/send_button/send-button.js +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/sortable_list/sortable-list.css +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/sortable_list/sortable-list.js +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/style.css +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/text_input/text-input.js +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/voice_input/voice-input.js +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/middleware.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/stats.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/utils.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools.egg-info/SOURCES.txt +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools.egg-info/dependency_links.txt +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools.egg-info/requires.txt +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/src/echotools.egg-info/top_level.txt +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_cache_retry.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_completion.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_config.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_config_extended.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_coverage_core.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_dispatch.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_dispatch_extended.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_dispatch_race.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_errors.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_errors_common.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_events.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_events_event.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_facade.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_ids.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_imports.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_io.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_keys.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_lifecycle.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_logger.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_merge.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_plugin.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_printstream.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_protocols.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_proxy.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_proxy_selector.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_runtime.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_scheduler.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_tracing.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_usage.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_web.py +0 -0
- {echotools-2.3.6 → echotools-2.3.7}/tests/test_web_broker.py +0 -0
|
@@ -16,6 +16,8 @@ from echotools.fncall.shared.loop_detect import (
|
|
|
16
16
|
from echotools.fncall.shared.normalization import (
|
|
17
17
|
format_tool_descs,
|
|
18
18
|
normalize_content,
|
|
19
|
+
normalize_tool_call,
|
|
20
|
+
normalize_tool_calls,
|
|
19
21
|
)
|
|
20
22
|
from echotools.protocol.base import (
|
|
21
23
|
ToolProtocol,
|
|
@@ -30,6 +32,8 @@ __all__ = [
|
|
|
30
32
|
"FncallStreamParser",
|
|
31
33
|
"format_tool_descs",
|
|
32
34
|
"normalize_content",
|
|
35
|
+
"normalize_tool_call",
|
|
36
|
+
"normalize_tool_calls",
|
|
33
37
|
"detect_tool_loop",
|
|
34
38
|
"LoopDetectionResult",
|
|
35
39
|
"ToolProtocol",
|
|
@@ -14,6 +14,7 @@ from echotools.fncall.shared.coercion import (
|
|
|
14
14
|
_build_param_schema_index,
|
|
15
15
|
_coerce_param_value,
|
|
16
16
|
)
|
|
17
|
+
from echotools.fncall.shared.normalization import normalize_tool_calls
|
|
17
18
|
from echotools.fncall.shared.xml_helpers import (
|
|
18
19
|
_PROVIDER_BLOCK_RE,
|
|
19
20
|
_PROVIDER_INVOKE_RE,
|
|
@@ -208,7 +209,7 @@ def parse_fncall(
|
|
|
208
209
|
clean = _FUNC_RE.sub("", clean)
|
|
209
210
|
clean = clean.strip()
|
|
210
211
|
|
|
211
|
-
return clean, calls
|
|
212
|
+
return clean, normalize_tool_calls(calls, tools)
|
|
212
213
|
|
|
213
214
|
def parse_fncall_xml(
|
|
214
215
|
xml: str,
|
|
@@ -262,7 +263,7 @@ def parse_fncall_xml(
|
|
|
262
263
|
except Exception as exc:
|
|
263
264
|
logger.warning("parse_fncall_xml 解析失败: %s", exc)
|
|
264
265
|
|
|
265
|
-
return tool_calls
|
|
266
|
+
return normalize_tool_calls(tool_calls, tools)
|
|
266
267
|
|
|
267
268
|
|
|
268
269
|
def parse_fncall_managed_xml(
|
|
@@ -319,4 +320,4 @@ def parse_fncall_managed_xml(
|
|
|
319
320
|
except Exception as exc:
|
|
320
321
|
logger.warning("parse_fncall_managed_xml 解析失败: %s", exc)
|
|
321
322
|
|
|
322
|
-
return tool_calls
|
|
323
|
+
return normalize_tool_calls(tool_calls, tools)
|
|
@@ -18,6 +18,7 @@ from echotools.fncall.shared.coercion import (
|
|
|
18
18
|
_build_param_schema_index,
|
|
19
19
|
_coerce_param_value,
|
|
20
20
|
)
|
|
21
|
+
from echotools.fncall.shared.normalization import normalize_tool_calls
|
|
21
22
|
from echotools.logger.manager import get_logger
|
|
22
23
|
from echotools.protocol.base import ToolProtocol
|
|
23
24
|
|
|
@@ -245,7 +246,7 @@ class EntmlProtocol(ToolProtocol):
|
|
|
245
246
|
if tool_calls:
|
|
246
247
|
clean = _BLOCK_RE.sub("", text).strip()
|
|
247
248
|
|
|
248
|
-
return (clean, tool_calls)
|
|
249
|
+
return (clean, normalize_tool_calls(tool_calls, tools))
|
|
249
250
|
|
|
250
251
|
def parse_fragment(
|
|
251
252
|
self,
|
|
@@ -11,12 +11,16 @@ from echotools.fncall.shared.loop_detect import (
|
|
|
11
11
|
from echotools.fncall.shared.normalization import (
|
|
12
12
|
format_tool_descs,
|
|
13
13
|
normalize_content,
|
|
14
|
+
normalize_tool_call,
|
|
15
|
+
normalize_tool_calls,
|
|
14
16
|
)
|
|
15
17
|
from echotools.ids.generator import uuid7
|
|
16
18
|
|
|
17
19
|
__all__ = [
|
|
18
20
|
"normalize_content",
|
|
19
21
|
"format_tool_descs",
|
|
22
|
+
"normalize_tool_call",
|
|
23
|
+
"normalize_tool_calls",
|
|
20
24
|
"detect_tool_loop",
|
|
21
25
|
"LoopDetectionResult",
|
|
22
26
|
"uuid7",
|
|
@@ -5,8 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import ast
|
|
8
9
|
import json
|
|
9
|
-
from typing import Any, List
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from echotools.fncall.shared.coercion import _build_param_schema_index
|
|
13
|
+
from echotools.logger.manager import get_logger
|
|
14
|
+
|
|
15
|
+
logger = get_logger(__name__)
|
|
10
16
|
|
|
11
17
|
_LT = chr(60)
|
|
12
18
|
_GT = chr(62)
|
|
@@ -168,3 +174,84 @@ def format_tool_descs(tools):
|
|
|
168
174
|
parts.append(chr(10).join(lines))
|
|
169
175
|
|
|
170
176
|
return (chr(10) + chr(10)).join(parts)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _try_parse_relaxed_literal(text: str) -> Optional[Any]:
|
|
180
|
+
"""仅对 array/object 形态尝试 JSON 或 Python literal 解析。"""
|
|
181
|
+
stripped = text.strip()
|
|
182
|
+
if not stripped or stripped[0] not in "[{" or stripped[-1] not in "]}":
|
|
183
|
+
return None
|
|
184
|
+
try:
|
|
185
|
+
return json.loads(stripped)
|
|
186
|
+
except json.JSONDecodeError:
|
|
187
|
+
pass
|
|
188
|
+
try:
|
|
189
|
+
return ast.literal_eval(stripped)
|
|
190
|
+
except (ValueError, SyntaxError):
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _normalize_value(value: Any, schema: Optional[Dict[str, Any]] = None) -> Any:
|
|
195
|
+
if isinstance(value, str):
|
|
196
|
+
parsed = _try_parse_relaxed_literal(value)
|
|
197
|
+
if parsed is not None:
|
|
198
|
+
value = parsed
|
|
199
|
+
if isinstance(value, list):
|
|
200
|
+
item_schema = (schema or {}).get("items") if schema else None
|
|
201
|
+
if isinstance(item_schema, dict):
|
|
202
|
+
return [_normalize_value(item, item_schema) for item in value]
|
|
203
|
+
return [_normalize_value(item) for item in value]
|
|
204
|
+
if isinstance(value, dict):
|
|
205
|
+
props = (schema or {}).get("properties") or {}
|
|
206
|
+
return {
|
|
207
|
+
key: _normalize_value(
|
|
208
|
+
val,
|
|
209
|
+
props.get(key) if isinstance(props.get(key), dict) else None,
|
|
210
|
+
)
|
|
211
|
+
for key, val in value.items()
|
|
212
|
+
}
|
|
213
|
+
return value
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def normalize_tool_call(
|
|
217
|
+
tc: Dict[str, Any],
|
|
218
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
219
|
+
) -> Dict[str, Any]:
|
|
220
|
+
"""将 tool call arguments 中的 Python 字面量字符串还原为合法 JSON 结构。"""
|
|
221
|
+
func = tc.get("function") or {}
|
|
222
|
+
name = str(func.get("name") or "")
|
|
223
|
+
raw_args = func.get("arguments", "{}")
|
|
224
|
+
try:
|
|
225
|
+
args = json.loads(raw_args) if isinstance(raw_args, str) else dict(raw_args)
|
|
226
|
+
except (json.JSONDecodeError, TypeError, ValueError):
|
|
227
|
+
logger.warning("normalize_tool_call: invalid arguments JSON for %s", name)
|
|
228
|
+
return tc
|
|
229
|
+
|
|
230
|
+
schema_index = _build_param_schema_index(tools) if tools else {}
|
|
231
|
+
param_schemas = schema_index.get(name, {})
|
|
232
|
+
|
|
233
|
+
if isinstance(args, dict):
|
|
234
|
+
args = {
|
|
235
|
+
key: _normalize_value(
|
|
236
|
+
val,
|
|
237
|
+
param_schemas.get(key) if param_schemas else None,
|
|
238
|
+
)
|
|
239
|
+
for key, val in args.items()
|
|
240
|
+
}
|
|
241
|
+
else:
|
|
242
|
+
args = _normalize_value(args)
|
|
243
|
+
|
|
244
|
+
normalized = dict(tc)
|
|
245
|
+
normalized["function"] = dict(func)
|
|
246
|
+
normalized["function"]["arguments"] = json.dumps(args, ensure_ascii=False)
|
|
247
|
+
return normalized
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def normalize_tool_calls(
|
|
251
|
+
tool_calls: Optional[List[Dict[str, Any]]],
|
|
252
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
253
|
+
) -> List[Dict[str, Any]]:
|
|
254
|
+
"""批量规范化 OpenAI 格式 tool_calls。"""
|
|
255
|
+
if not tool_calls:
|
|
256
|
+
return []
|
|
257
|
+
return [normalize_tool_call(tc, tools) for tc in tool_calls]
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
4
|
+
|
|
3
5
|
import pytest
|
|
4
6
|
|
|
5
7
|
from echotools.fncall import get_protocol, inject_fncall, list_protocols
|
|
8
|
+
from echotools.fncall.shared.normalization import normalize_tool_call
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
@pytest.mark.parametrize(
|
|
@@ -43,6 +46,34 @@ def test_get_protocol_platform_mapping() -> None:
|
|
|
43
46
|
assert proto.id == "entml"
|
|
44
47
|
|
|
45
48
|
|
|
49
|
+
def test_normalize_tool_call_python_literal() -> None:
|
|
50
|
+
tools = [
|
|
51
|
+
{
|
|
52
|
+
"type": "function",
|
|
53
|
+
"function": {
|
|
54
|
+
"name": "run",
|
|
55
|
+
"parameters": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"properties": {
|
|
58
|
+
"items": {"type": "array", "items": {"type": "string"}},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
tc = {
|
|
65
|
+
"id": "call_0001",
|
|
66
|
+
"type": "function",
|
|
67
|
+
"function": {
|
|
68
|
+
"name": "run",
|
|
69
|
+
"arguments": json.dumps({"items": "['a', 'b']"}),
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
out = normalize_tool_call(tc, tools)
|
|
73
|
+
args = json.loads(out["function"]["arguments"])
|
|
74
|
+
assert args["items"] == ["a", "b"]
|
|
75
|
+
|
|
76
|
+
|
|
46
77
|
def test_inject_with_tools_and_dump(tmp_path) -> None:
|
|
47
78
|
proto = get_protocol("entml")
|
|
48
79
|
tools = [
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.6 → echotools-2.3.7}/src/echotools/web/input_box/sortable_list/sortable-list.css
RENAMED
|
File without changes
|
{echotools-2.3.6 → echotools-2.3.7}/src/echotools/web/input_box/sortable_list/sortable-list.js
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
|