diff --git a/core/executor_docker.py b/core/executor_docker.py index 0e36662..41f40ed 100644 --- a/core/executor_docker.py +++ b/core/executor_docker.py @@ -47,13 +47,13 @@ import json # canceller 侧线程 poll cancel_check 的间隔;单测可 patch 此常量加速 _CANCEL_POLL_INTERVAL_S = 0.2 -from tools.base import format_timeout_result +from tools.output import format_timeout_result from .executor import ExecCtx, Executor, ToolResult from .executor_host import HostExecutor from .pysyntax import precheck_python from .sandbox import SandboxPool -from tools.base import compact_tool_output +from tools.output import compact_tool_output # write/edit 走配额 gate;read/glob/grep 不消耗磁盘,放行 diff --git a/tests/test_timeout_partial.py b/tests/test_timeout_partial.py index de394ed..de49935 100644 --- a/tests/test_timeout_partial.py +++ b/tests/test_timeout_partial.py @@ -4,7 +4,7 @@ from __future__ import annotations import tempfile import unittest -from tools.base import format_timeout_result +from tools.output import format_timeout_result from tools.run_python import RunPythonTool diff --git a/tests/test_tool_output_compaction.py b/tests/test_tool_output_compaction.py index 1602ebd..01a1834 100644 --- a/tests/test_tool_output_compaction.py +++ b/tests/test_tool_output_compaction.py @@ -1,7 +1,7 @@ import unittest import tempfile -from tools.base import compact_tool_output +from tools.output import compact_tool_output from tools.run_python import RunPythonTool diff --git a/tools/base.py b/tools/base.py index 2f91162..22ed1bb 100644 --- a/tools/base.py +++ b/tools/base.py @@ -16,52 +16,6 @@ class FileOutOfBounds(Exception): """agent 给的文件路径解析后落在 user_root 之外(防 `../` 读到别人/系统文件)。""" -def compact_tool_output( - text: str, - *, - max_chars: int = 8_000, - head_chars: int = 4_000, - tail_chars: int = 2_000, -) -> str: - """压缩长工具输出,保留头尾和截断说明。""" - if len(text) <= max_chars: - return text - head_chars = max(0, min(head_chars, max_chars)) - tail_chars = max(0, min(tail_chars, max_chars - head_chars)) - removed = len(text) - head_chars - tail_chars - return ( - text[:head_chars] - + f"\n[... truncated, {removed} chars omitted ...]\n" - + (text[-tail_chars:] if tail_chars else "") - ) - - -_TIMEOUT_HINT = ( - "若是批量循环(检索 / PDF 抽取 / 下载等):① 据上面已完成的部分**续跑剩下的**,别整批重跑;" - "② 每完成一项就落盘 / 打印进度,超时也不丢;③ 大批量 / 大下载改用 background=true 后台跑," - "再用 check_process 取结果;④ 单次别塞太多项。" -) - - -def format_timeout_result(stdout: str, stderr: str, timeout_s: int) -> str: - """run_python / shell 超时结果:带上超时前已捕获的部分输出 + 续跑提示。 - - 进程被 kill 前跑出的 stdout/stderr 本已捕获(subprocess 标准行为:TimeoutExpired - 带 .stdout/.stderr、docker 路径 kill 后 communicate() 续读),旧实现直接丢弃只回一句 - 超时 → 模型看不到「跑到哪了」、整批重来(工具失败面板 #8:批量检索 / PDF 抽取 / 下载 - 超时反复重跑,累计最多)。这里把部分输出一并返回,让模型据此续跑未完成的。""" - parts = [] - if stdout and stdout.strip(): - parts.append(f"[stdout]\n{stdout.rstrip()}") - if stderr and stderr.strip(): - parts.append(f"[stderr]\n{stderr.rstrip()}") - parts.append( - f"[Error] command timed out after {timeout_s}s(进程已被杀,上方为超时前的部分输出)。" - ) - parts.append(_TIMEOUT_HINT) - return "\n".join(parts) - - class Tool(ABC): name: str = "" description: str = "" diff --git a/tools/check_process.py b/tools/check_process.py index 7e1f858..86188ef 100644 --- a/tools/check_process.py +++ b/tools/check_process.py @@ -12,7 +12,8 @@ from typing import Optional from core import procs -from .base import Tool, compact_tool_output +from .base import Tool +from .output import compact_tool_output class CheckProcessTool(Tool): diff --git a/tools/look_at_image.py b/tools/look_at_image.py index bf784d4..3b69a10 100644 --- a/tools/look_at_image.py +++ b/tools/look_at_image.py @@ -16,7 +16,8 @@ from uuid import UUID from core.ark_client import ArkConfig from core.storage.usage import record_vision_usage -from .base import Tool, compact_tool_output +from .base import Tool +from .output import compact_tool_output from .image_ref import load_image_as_data_url from .media_common import ark_chat_with_retry, extract_chat_answer, record_usage_safe diff --git a/tools/output.py b/tools/output.py new file mode 100644 index 0000000..811241b --- /dev/null +++ b/tools/output.py @@ -0,0 +1,48 @@ +"""工具输出处理原语(从 tools/base.py 析出,2026-07-23——base 只留 Tool 基类与路径边界)。""" +from __future__ import annotations + + +def compact_tool_output( + text: str, + *, + max_chars: int = 8_000, + head_chars: int = 4_000, + tail_chars: int = 2_000, +) -> str: + """压缩长工具输出,保留头尾和截断说明。""" + if len(text) <= max_chars: + return text + head_chars = max(0, min(head_chars, max_chars)) + tail_chars = max(0, min(tail_chars, max_chars - head_chars)) + removed = len(text) - head_chars - tail_chars + return ( + text[:head_chars] + + f"\n[... truncated, {removed} chars omitted ...]\n" + + (text[-tail_chars:] if tail_chars else "") + ) + + +_TIMEOUT_HINT = ( + "若是批量循环(检索 / PDF 抽取 / 下载等):① 据上面已完成的部分**续跑剩下的**,别整批重跑;" + "② 每完成一项就落盘 / 打印进度,超时也不丢;③ 大批量 / 大下载改用 background=true 后台跑," + "再用 check_process 取结果;④ 单次别塞太多项。" +) + + +def format_timeout_result(stdout: str, stderr: str, timeout_s: int) -> str: + """run_python / shell 超时结果:带上超时前已捕获的部分输出 + 续跑提示。 + + 进程被 kill 前跑出的 stdout/stderr 本已捕获(subprocess 标准行为:TimeoutExpired + 带 .stdout/.stderr、docker 路径 kill 后 communicate() 续读),旧实现直接丢弃只回一句 + 超时 → 模型看不到「跑到哪了」、整批重来(工具失败面板 #8:批量检索 / PDF 抽取 / 下载 + 超时反复重跑,累计最多)。这里把部分输出一并返回,让模型据此续跑未完成的。""" + parts = [] + if stdout and stdout.strip(): + parts.append(f"[stdout]\n{stdout.rstrip()}") + if stderr and stderr.strip(): + parts.append(f"[stderr]\n{stderr.rstrip()}") + parts.append( + f"[Error] command timed out after {timeout_s}s(进程已被杀,上方为超时前的部分输出)。" + ) + parts.append(_TIMEOUT_HINT) + return "\n".join(parts) diff --git a/tools/read_document.py b/tools/read_document.py index d61aad2..2155908 100644 --- a/tools/read_document.py +++ b/tools/read_document.py @@ -18,7 +18,8 @@ from uuid import UUID from core.ark_client import ArkConfig from core.storage.usage import record_vision_usage -from .base import Tool, compact_tool_output +from .base import Tool +from .output import compact_tool_output from .image_ref import _CONTAINER_ROOT, load_pdf_as_data_url, resolve_in_root from .media_common import ark_chat_with_retry, extract_chat_answer, record_usage_safe diff --git a/tools/run_python.py b/tools/run_python.py index 27502ca..3adb8d3 100644 --- a/tools/run_python.py +++ b/tools/run_python.py @@ -18,7 +18,8 @@ from typing import Optional from core import procs from core.pysyntax import precheck_python -from .base import Tool, compact_tool_output, format_timeout_result +from .base import Tool +from .output import compact_tool_output, format_timeout_result _SENSITIVE_PATTERNS = ("API_KEY", "TOKEN", "SECRET", "PASSWORD", "PRIVATE_KEY") # 刻意放行的例外:research skill 在 sandbox 里直连 paper_server,这把只读文献库 key diff --git a/tools/shell.py b/tools/shell.py index 964f05c..4bf16da 100644 --- a/tools/shell.py +++ b/tools/shell.py @@ -11,7 +11,8 @@ from typing import Optional from core import procs -from .base import Tool, compact_tool_output +from .base import Tool +from .output import compact_tool_output class ShellTool(Tool):