mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 04:40:45 +08:00
实现附件处理链路的统一引用化与可检索增强,避免大文件/多模态内容直接撑爆上下文并提升工具可用性。
本次补齐 text/image/video/archive 的标准化处理、会话级工具守卫、回放压缩、配置与文档对齐,并修复表格与流式输出相关体验问题。 Made-with: Cursor
This commit is contained in:
parent
9d2900db02
commit
37a2ef35f4
27 changed files with 3227 additions and 278 deletions
|
|
@ -123,3 +123,272 @@ def test_repeated_non_sql_tools_are_not_compacted(tmp_path: Path) -> None:
|
|||
assert payloads
|
||||
assert not any(bool(p.get("_history_compacted")) for p in payloads)
|
||||
|
||||
|
||||
def test_tabular_tools_blocked_without_tabular_ref(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "g4.sqlite"))
|
||||
sess = store.create_session("t")
|
||||
# A normal user turn without tabular_ref attachments.
|
||||
store.add_message(session_id=sess.id, role="user", content="analyze this file", attachments=[{"type": "text", "name": "x"}])
|
||||
|
||||
def _handler(_args):
|
||||
return {"ok": True, "rows": []}
|
||||
|
||||
reg = ToolRegistry(
|
||||
[
|
||||
ToolSpec(
|
||||
name="query_tabular_attachment",
|
||||
description="query table",
|
||||
parameters={"type": "object", "properties": {"table_id": {"type": "string"}}},
|
||||
handler=_handler,
|
||||
read_only=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
tool_uses = [LLMToolCall(id="c1", name="query_tabular_attachment", arguments={"table_id": "日志.xlsx"})]
|
||||
_, results = ToolExecutor().execute_tool_uses(
|
||||
ctx=ToolExecutionContext(store=store, tools=reg, session_id=sess.id),
|
||||
assistant_msg_id=1,
|
||||
tool_uses=tool_uses,
|
||||
)
|
||||
blocked, _ = results["c1"]
|
||||
assert not bool(blocked.get("ok"))
|
||||
assert str(blocked.get("error_code") or "") == "tabular_ref_missing"
|
||||
|
||||
|
||||
def test_tabular_tools_allowed_with_tabular_ref(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "g5.sqlite"))
|
||||
sess = store.create_session("t")
|
||||
store.add_message(
|
||||
session_id=sess.id,
|
||||
role="user",
|
||||
content="uploaded table",
|
||||
attachments=[{"type": "tabular_ref", "table_id": "a" * 64, "name": "ok.xlsx"}],
|
||||
)
|
||||
calls = {"n": 0}
|
||||
|
||||
def _handler(_args):
|
||||
calls["n"] += 1
|
||||
return {"ok": True, "rows": [{"x": 1}]}
|
||||
|
||||
reg = ToolRegistry(
|
||||
[
|
||||
ToolSpec(
|
||||
name="query_tabular_attachment",
|
||||
description="query table",
|
||||
parameters={"type": "object", "properties": {"table_id": {"type": "string"}}},
|
||||
handler=_handler,
|
||||
read_only=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
tool_uses = [LLMToolCall(id="c1", name="query_tabular_attachment", arguments={"table_id": "a" * 64})]
|
||||
_, results = ToolExecutor().execute_tool_uses(
|
||||
ctx=ToolExecutionContext(store=store, tools=reg, session_id=sess.id),
|
||||
assistant_msg_id=1,
|
||||
tool_uses=tool_uses,
|
||||
)
|
||||
ok_res, _ = results["c1"]
|
||||
assert bool(ok_res.get("ok"))
|
||||
assert calls["n"] == 1
|
||||
|
||||
|
||||
def test_text_tools_blocked_without_text_ref(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "g6.sqlite"))
|
||||
sess = store.create_session("t")
|
||||
store.add_message(session_id=sess.id, role="user", content="summarize", attachments=[{"type": "text", "name": "a.txt"}])
|
||||
|
||||
def _handler(_args):
|
||||
return {"ok": True, "rows": []}
|
||||
|
||||
reg = ToolRegistry(
|
||||
[
|
||||
ToolSpec(
|
||||
name="query_text_attachment",
|
||||
description="query text",
|
||||
parameters={"type": "object", "properties": {"text_id": {"type": "string"}}},
|
||||
handler=_handler,
|
||||
read_only=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
tool_uses = [LLMToolCall(id="c1", name="query_text_attachment", arguments={"text_id": "a" * 64})]
|
||||
_, results = ToolExecutor().execute_tool_uses(
|
||||
ctx=ToolExecutionContext(store=store, tools=reg, session_id=sess.id),
|
||||
assistant_msg_id=1,
|
||||
tool_uses=tool_uses,
|
||||
)
|
||||
blocked, _ = results["c1"]
|
||||
assert not bool(blocked.get("ok"))
|
||||
assert str(blocked.get("error_code") or "") == "text_ref_missing"
|
||||
|
||||
|
||||
def test_text_tools_allowed_with_text_ref(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "g7.sqlite"))
|
||||
sess = store.create_session("t")
|
||||
store.add_message(
|
||||
session_id=sess.id,
|
||||
role="user",
|
||||
content="uploaded long text",
|
||||
attachments=[{"type": "text_ref", "text_id": "b" * 64, "name": "long.txt"}],
|
||||
)
|
||||
calls = {"n": 0}
|
||||
|
||||
def _handler(_args):
|
||||
calls["n"] += 1
|
||||
return {"ok": True, "rows": [{"x": 1}]}
|
||||
|
||||
reg = ToolRegistry(
|
||||
[
|
||||
ToolSpec(
|
||||
name="query_text_attachment",
|
||||
description="query text",
|
||||
parameters={"type": "object", "properties": {"text_id": {"type": "string"}}},
|
||||
handler=_handler,
|
||||
read_only=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
tool_uses = [LLMToolCall(id="c1", name="query_text_attachment", arguments={"text_id": "b" * 64})]
|
||||
_, results = ToolExecutor().execute_tool_uses(
|
||||
ctx=ToolExecutionContext(store=store, tools=reg, session_id=sess.id),
|
||||
assistant_msg_id=1,
|
||||
tool_uses=tool_uses,
|
||||
)
|
||||
ok_res, _ = results["c1"]
|
||||
assert bool(ok_res.get("ok"))
|
||||
assert calls["n"] == 1
|
||||
|
||||
|
||||
def test_image_tools_blocked_without_image_ref(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "g8.sqlite"))
|
||||
sess = store.create_session("t")
|
||||
store.add_message(session_id=sess.id, role="user", content="analyze image", attachments=[{"type": "text", "name": "x"}])
|
||||
|
||||
def _handler(_args):
|
||||
return {"ok": True}
|
||||
|
||||
reg = ToolRegistry(
|
||||
[
|
||||
ToolSpec(
|
||||
name="query_image_attachment",
|
||||
description="query image",
|
||||
parameters={"type": "object", "properties": {"attachment_id": {"type": "string"}}},
|
||||
handler=_handler,
|
||||
read_only=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
tool_uses = [LLMToolCall(id="c1", name="query_image_attachment", arguments={"attachment_id": "abc"})]
|
||||
_, results = ToolExecutor().execute_tool_uses(
|
||||
ctx=ToolExecutionContext(store=store, tools=reg, session_id=sess.id),
|
||||
assistant_msg_id=1,
|
||||
tool_uses=tool_uses,
|
||||
)
|
||||
blocked, _ = results["c1"]
|
||||
assert not bool(blocked.get("ok"))
|
||||
assert str(blocked.get("error_code") or "") == "image_ref_missing"
|
||||
|
||||
|
||||
def test_image_tools_allowed_with_image_ref(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "g9.sqlite"))
|
||||
sess = store.create_session("t")
|
||||
store.add_message(
|
||||
session_id=sess.id,
|
||||
role="user",
|
||||
content="uploaded image",
|
||||
attachments=[{"type": "image_ref", "attachment_id": "abc", "mime": "image/png"}],
|
||||
)
|
||||
calls = {"n": 0}
|
||||
|
||||
def _handler(_args):
|
||||
calls["n"] += 1
|
||||
return {"ok": True}
|
||||
|
||||
reg = ToolRegistry(
|
||||
[
|
||||
ToolSpec(
|
||||
name="query_image_attachment",
|
||||
description="query image",
|
||||
parameters={"type": "object", "properties": {"attachment_id": {"type": "string"}}},
|
||||
handler=_handler,
|
||||
read_only=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
tool_uses = [LLMToolCall(id="c1", name="query_image_attachment", arguments={"attachment_id": "abc"})]
|
||||
_, results = ToolExecutor().execute_tool_uses(
|
||||
ctx=ToolExecutionContext(store=store, tools=reg, session_id=sess.id),
|
||||
assistant_msg_id=1,
|
||||
tool_uses=tool_uses,
|
||||
)
|
||||
ok_res, _ = results["c1"]
|
||||
assert bool(ok_res.get("ok"))
|
||||
assert calls["n"] == 1
|
||||
|
||||
|
||||
def test_video_tools_blocked_without_video_ref(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "g10.sqlite"))
|
||||
sess = store.create_session("t")
|
||||
store.add_message(session_id=sess.id, role="user", content="analyze video", attachments=[{"type": "text", "name": "x"}])
|
||||
|
||||
def _handler(_args):
|
||||
return {"ok": True}
|
||||
|
||||
reg = ToolRegistry(
|
||||
[
|
||||
ToolSpec(
|
||||
name="query_video_attachment",
|
||||
description="query video",
|
||||
parameters={"type": "object", "properties": {"attachment_id": {"type": "string"}}},
|
||||
handler=_handler,
|
||||
read_only=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
tool_uses = [LLMToolCall(id="c1", name="query_video_attachment", arguments={"attachment_id": "abc"})]
|
||||
_, results = ToolExecutor().execute_tool_uses(
|
||||
ctx=ToolExecutionContext(store=store, tools=reg, session_id=sess.id),
|
||||
assistant_msg_id=1,
|
||||
tool_uses=tool_uses,
|
||||
)
|
||||
blocked, _ = results["c1"]
|
||||
assert not bool(blocked.get("ok"))
|
||||
assert str(blocked.get("error_code") or "") == "video_ref_missing"
|
||||
|
||||
|
||||
def test_video_tools_allowed_with_video_ref(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "g11.sqlite"))
|
||||
sess = store.create_session("t")
|
||||
store.add_message(
|
||||
session_id=sess.id,
|
||||
role="user",
|
||||
content="uploaded video",
|
||||
attachments=[{"type": "video_ref", "attachment_id": "abc", "mime": "video/mp4"}],
|
||||
)
|
||||
calls = {"n": 0}
|
||||
|
||||
def _handler(_args):
|
||||
calls["n"] += 1
|
||||
return {"ok": True}
|
||||
|
||||
reg = ToolRegistry(
|
||||
[
|
||||
ToolSpec(
|
||||
name="query_video_attachment",
|
||||
description="query video",
|
||||
parameters={"type": "object", "properties": {"attachment_id": {"type": "string"}}},
|
||||
handler=_handler,
|
||||
read_only=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
tool_uses = [LLMToolCall(id="c1", name="query_video_attachment", arguments={"attachment_id": "abc"})]
|
||||
_, results = ToolExecutor().execute_tool_uses(
|
||||
ctx=ToolExecutionContext(store=store, tools=reg, session_id=sess.id),
|
||||
assistant_msg_id=1,
|
||||
tool_uses=tool_uses,
|
||||
)
|
||||
ok_res, _ = results["c1"]
|
||||
assert bool(ok_res.get("ok"))
|
||||
assert calls["n"] == 1
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue