oclaw/tests/test_oclaw_router.py
oliver d1bcc4debe feat: multimodal image clients, workspaces, Tushare skill, test fixes
- Replace monolithic image_message_client with HTTP/OCR/legacy modules; tighten OpenAI transport + tool schemas for multimodal downgrade to OCR specialist path.
- Add image/stock workspace prompts (META/SOUL/ROLE_SYSTEM); register experts; tweak specialist agent/direct loop/query_image_attachment.
- Add bundled runtime/skills/tushare-finance (references, api_client, SKILL metadata).
- Document OCR-related env vars; admin chat tweaks; README; weixin_install Ensure-OfficialPluginRuntimeDeps helper.
- Tests: multimodal downgrade + OCR coverage, strict tool pairing in attachment replay guard, workspace contract skips _internal/_system dirs, router/trace/prompt guards.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 04:41:42 +08:00

131 lines
4.1 KiB
Python

from __future__ import annotations
from oclaw.platform.llm.chat_models import LLMResponse
from oclaw.runtime.router import decide_route
from oclaw.runtime.types import StandardMessage, normalize_interaction_mode, normalize_requested_specialist
def _msg(text: str, attachments: list[dict] | None = None) -> StandardMessage:
return StandardMessage(
session_id="s1",
tenant_id="t1",
user_id="u1",
role="user",
channel="admin_chat",
text=text,
attachments=attachments or [],
metadata={},
)
def test_router_async_for_multi_step_send_flow() -> None:
d = decide_route(_msg("帮我总结并发送到项目群"))
assert d.mode == "async_task"
assert d.reason == "multi_step_send_flow"
def test_router_sync_for_short_default_message() -> None:
d = decide_route(_msg("你好,今天怎么样"))
assert d.mode == "sync_direct"
assert d.reason == "default_sync"
class _RouterJsonModel:
def __init__(self, content: str) -> None:
self._content = content
def chat(self, messages, tools, *, on_token=None):
return LLMResponse(content=self._content, tool_calls=[])
def test_router_llm_json_mode_uses_model() -> None:
class Store:
def get_setting(self, key: str) -> str:
if key == "AIA_OCLAW_ROUTER_MODE":
return "llm_json"
return ""
d = decide_route(
_msg("short"),
store=Store(),
model=_RouterJsonModel('{"mode":"async_task","reason":"user asked for batch"}'),
)
assert d.mode == "async_task"
assert "batch" in d.reason
def test_router_llm_json_invalid_json_falls_back_to_rule() -> None:
class Store:
def get_setting(self, key: str) -> str:
if key == "AIA_OCLAW_ROUTER_MODE":
return "llm_json"
return ""
d = decide_route(_msg("你好,今天怎么样"), store=Store(), model=_RouterJsonModel("not json"))
assert d.mode == "sync_direct"
assert d.reason == "default_sync"
def test_router_llm_json_tolerates_extra_dynamic_agent_fields() -> None:
class Store:
def get_setting(self, key: str) -> str:
if key == "AIA_OCLAW_ROUTER_MODE":
return "llm_json"
return ""
d = decide_route(
_msg("plan this"),
store=Store(),
model=_RouterJsonModel('{"mode":"sync_direct","reason":"ok","dynamic_agent":{"name":"x"}}'),
)
assert d.mode == "sync_direct"
assert d.reason == "ok"
def test_router_skill_signal_from_metadata() -> None:
msg = StandardMessage(
session_id="s1",
tenant_id="t1",
user_id="u1",
role="user",
channel="admin_chat",
text="你好",
attachments=[],
metadata={"skills_total": 4},
)
d = decide_route(msg)
assert d.skill_signal == "skills=4"
def test_interaction_mode_normalization_supports_legacy_values() -> None:
assert normalize_interaction_mode("comprehensive") == "comprehensive"
assert normalize_interaction_mode("expert") == "expert"
assert normalize_interaction_mode("specialist") == "expert"
assert normalize_interaction_mode("composite") == "comprehensive"
def test_requested_specialist_normalization_defaults_to_generalist() -> None:
assert normalize_requested_specialist("ops") == "ops"
# "stock" is a dynamic specialist discovered from runtime workspaces.
assert normalize_requested_specialist("stock") == "stock"
assert normalize_requested_specialist("image") == "image"
# Unknown ids fall back to generalist.
assert normalize_requested_specialist("memory") == "memory"
assert normalize_requested_specialist("unknown") == "generalist"
def test_router_carries_interaction_mode_and_requested_specialist() -> None:
msg = StandardMessage(
session_id="s1",
tenant_id="t1",
user_id="u1",
role="user",
channel="admin_chat",
text="你好",
attachments=[],
metadata={"interaction_mode": "expert", "selected_specialist": "ops"},
)
d = decide_route(msg)
assert d.interaction_mode == "expert"
assert d.requested_specialist == "ops"