refactor(tools): 输出处理原语移出 base.py——base 只留 Tool 基类与路径边界
compact_tool_output / format_timeout_result 是「工具输出处理」关注点,与 Tool ABC / FileOutOfBounds 路径安全混装在 base.py(审查小项)。析出 tools/output.py,8 处引用(5 工具 + executor_docker + 2 测试)同步改。 318 测试全过。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
8038fb7491
commit
5a5ebfbe24
|
|
@ -47,13 +47,13 @@ import json
|
||||||
# canceller 侧线程 poll cancel_check 的间隔;单测可 patch 此常量加速
|
# canceller 侧线程 poll cancel_check 的间隔;单测可 patch 此常量加速
|
||||||
_CANCEL_POLL_INTERVAL_S = 0.2
|
_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 import ExecCtx, Executor, ToolResult
|
||||||
from .executor_host import HostExecutor
|
from .executor_host import HostExecutor
|
||||||
from .pysyntax import precheck_python
|
from .pysyntax import precheck_python
|
||||||
from .sandbox import SandboxPool
|
from .sandbox import SandboxPool
|
||||||
from tools.base import compact_tool_output
|
from tools.output import compact_tool_output
|
||||||
|
|
||||||
|
|
||||||
# write/edit 走配额 gate;read/glob/grep 不消耗磁盘,放行
|
# write/edit 走配额 gate;read/glob/grep 不消耗磁盘,放行
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from tools.base import format_timeout_result
|
from tools.output import format_timeout_result
|
||||||
from tools.run_python import RunPythonTool
|
from tools.run_python import RunPythonTool
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from tools.base import compact_tool_output
|
from tools.output import compact_tool_output
|
||||||
from tools.run_python import RunPythonTool
|
from tools.run_python import RunPythonTool
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,52 +16,6 @@ class FileOutOfBounds(Exception):
|
||||||
"""agent 给的文件路径解析后落在 user_root 之外(防 `../` 读到别人/系统文件)。"""
|
"""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):
|
class Tool(ABC):
|
||||||
name: str = ""
|
name: str = ""
|
||||||
description: str = ""
|
description: str = ""
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ from typing import Optional
|
||||||
|
|
||||||
from core import procs
|
from core import procs
|
||||||
|
|
||||||
from .base import Tool, compact_tool_output
|
from .base import Tool
|
||||||
|
from .output import compact_tool_output
|
||||||
|
|
||||||
|
|
||||||
class CheckProcessTool(Tool):
|
class CheckProcessTool(Tool):
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ from uuid import UUID
|
||||||
from core.ark_client import ArkConfig
|
from core.ark_client import ArkConfig
|
||||||
from core.storage.usage import record_vision_usage
|
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 .image_ref import load_image_as_data_url
|
||||||
from .media_common import ark_chat_with_retry, extract_chat_answer, record_usage_safe
|
from .media_common import ark_chat_with_retry, extract_chat_answer, record_usage_safe
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -18,7 +18,8 @@ from uuid import UUID
|
||||||
from core.ark_client import ArkConfig
|
from core.ark_client import ArkConfig
|
||||||
from core.storage.usage import record_vision_usage
|
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 .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
|
from .media_common import ark_chat_with_retry, extract_chat_answer, record_usage_safe
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ from typing import Optional
|
||||||
from core import procs
|
from core import procs
|
||||||
from core.pysyntax import precheck_python
|
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")
|
_SENSITIVE_PATTERNS = ("API_KEY", "TOKEN", "SECRET", "PASSWORD", "PRIVATE_KEY")
|
||||||
# 刻意放行的例外:research skill 在 sandbox 里直连 paper_server,这把只读文献库 key
|
# 刻意放行的例外:research skill 在 sandbox 里直连 paper_server,这把只读文献库 key
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ from typing import Optional
|
||||||
|
|
||||||
from core import procs
|
from core import procs
|
||||||
|
|
||||||
from .base import Tool, compact_tool_output
|
from .base import Tool
|
||||||
|
from .output import compact_tool_output
|
||||||
|
|
||||||
|
|
||||||
class ShellTool(Tool):
|
class ShellTool(Tool):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue