将附件/多模态查询与编辑工具从 generalist 迁移到 public。

同时移除 network_ops 的转发壳文件,统一工具入口并更新对应测试引用,降低重复维护成本。

Made-with: Cursor
This commit is contained in:
oliver 2026-05-01 04:13:43 +08:00
parent 5d67303d22
commit 1c71a29ec1
10 changed files with 84 additions and 126 deletions

View file

@ -1,6 +0,0 @@
from __future__ import annotations
from oclaw.runtime.tools.experts.generalist.image_query import query_image_attachment_tool
__all__ = ["query_image_attachment_tool"]

View file

@ -1,10 +0,0 @@
from __future__ import annotations
from oclaw.runtime.tools.experts.generalist.tabular_query import (
analyze_tabular_attachment_full_scan_tool,
query_tabular_attachment_tool,
run_tabular_sql_tool,
)
__all__ = ["query_tabular_attachment_tool", "run_tabular_sql_tool", "analyze_tabular_attachment_full_scan_tool"]

View file

@ -1,6 +0,0 @@
from __future__ import annotations
from oclaw.runtime.tools.experts.generalist.text_query import query_text_attachment_tool
__all__ = ["query_text_attachment_tool"]

View file

@ -1,6 +0,0 @@
from __future__ import annotations
from oclaw.runtime.tools.experts.generalist.video_query import query_video_attachment_tool
__all__ = ["query_video_attachment_tool"]

View file

@ -47,13 +47,11 @@ def image_edit_tool() -> ToolSpec:
client_kwargs["base_url"] = base_url
client = OpenAI(**client_kwargs)
# OpenAI SDK expects a file-like object for edits.
img_file = io.BytesIO(blob)
img_file.name = "input.png" # type: ignore[attr-defined]
b64_out: str | None = None
try:
# Preferred: image edit endpoint (if supported by the gateway/model).
resp = client.images.edit( # type: ignore[attr-defined]
model=model,
image=img_file,
@ -63,7 +61,6 @@ def image_edit_tool() -> ToolSpec:
data0 = resp.data[0] if getattr(resp, "data", None) else None
b64_out = getattr(data0, "b64_json", None) if data0 is not None else None
except Exception:
# Fallback: generate a new image from prompt (still returns an image, but not true edit).
try:
resp = client.images.generate( # type: ignore[attr-defined]
model=model,
@ -126,4 +123,3 @@ def image_edit_tool() -> ToolSpec:
__all__ = ["image_edit_tool"]

View file

@ -72,4 +72,3 @@ def query_image_attachment_tool() -> ToolSpec:
__all__ = ["query_image_attachment_tool"]

View file

@ -38,4 +38,3 @@ def query_text_attachment_tool() -> ToolSpec:
__all__ = ["query_text_attachment_tool"]

View file

@ -1,10 +1,10 @@
from __future__ import annotations
import io
import json
import os
import subprocess
import tempfile
import io
import json
from pathlib import Path
from typing import Any
@ -144,7 +144,6 @@ def query_video_attachment_tool() -> ToolSpec:
if p is None:
return {"ok": False, "error": "attachment_not_found"}
# Basic metadata (no heavy deps). Prefer ffprobe if present.
if task == "meta":
fp = _ffprobe_json(p)
norm = _normalized_video_meta(fp)
@ -163,8 +162,6 @@ def query_video_attachment_tool() -> ToolSpec:
"note": "Use task=transcript to extract audio transcript (requires ffmpeg + OpenAI key).",
}
# transcript: extract audio then transcribe via OpenAI, store as text chunks.
if task == "transcript":
if not _ffmpeg_exists():
return {
"ok": False,
@ -199,11 +196,9 @@ def query_video_attachment_tool() -> ToolSpec:
model = str(args.get("model") or os.getenv("OPENAI_AUDIO_TRANSCRIPTION_MODEL") or OPENAI_DEFAULT_AUDIO_TRANSCRIPTION_MODEL).strip()
prompt = str(args.get("prompt") or "").strip()
# OpenAI SDK expects a file-like object with a name.
wav_bytes = wav.read_bytes()
f = io.BytesIO(wav_bytes)
f.name = "audio.wav" # type: ignore[attr-defined]
# Best-effort: different gateways may accept different param names; keep it minimal.
try:
resp = client.audio.transcriptions.create( # type: ignore[attr-defined]
model=model,
@ -217,7 +212,6 @@ def query_video_attachment_tool() -> ToolSpec:
if not text.strip():
return {"ok": False, "error": "empty_transcript"}
# Persist transcript as a long text document so the model can query evidence by text_id.
name = str(meta.name if meta else p.name)
text_name = f"{name}.transcript.txt"
cfg_chunk_size, cfg_chunk_overlap = _video_transcript_chunk_defaults()
@ -275,4 +269,3 @@ def query_video_attachment_tool() -> ToolSpec:
__all__ = ["query_video_attachment_tool"]

View file

@ -36,7 +36,7 @@ def query_tabular_attachment_tool() -> ToolSpec:
columns=cols,
limit=int(args.get("limit") or 50),
offset=int(args.get("offset") or 0),
where_contains=where_contains, # {"column":"...", "keyword":"..."}
where_contains=where_contains,
sheet=sheet,
)
@ -147,4 +147,3 @@ def analyze_tabular_attachment_full_scan_tool() -> ToolSpec:
__all__ = ["query_tabular_attachment_tool", "run_tabular_sql_tool", "analyze_tabular_attachment_full_scan_tool"]

View file

@ -1,6 +1,6 @@
from __future__ import annotations
import oclaw.runtime.tools.experts.generalist.video_query as vq
import oclaw.runtime.tools.public.query_video_attachment_tool as vq
def test_normalized_video_meta_extracts_top_level_fields() -> None: