mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-11 01:50:46 +08:00
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>
This commit is contained in:
parent
6307ade480
commit
d1bcc4debe
266 changed files with 57024 additions and 839 deletions
149
tests/test_openai_tools_schema_compat.py
Normal file
149
tests/test_openai_tools_schema_compat.py
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from oclaw.platform.llm.tool_schema import complete_openai_tools_wire_parameters
|
||||
|
||||
|
||||
def test_complete_tools_coerces_null_function_parameters_to_min_object_schema() -> None:
|
||||
raw = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "mcp__mcp-playwright__browser_click",
|
||||
"description": "click",
|
||||
"parameters": None,
|
||||
},
|
||||
}
|
||||
]
|
||||
out = complete_openai_tools_wire_parameters(raw)
|
||||
assert len(out) == 1
|
||||
fn = out[0]["function"]
|
||||
params = fn["parameters"]
|
||||
assert isinstance(params, dict)
|
||||
assert params.get("type") == "object"
|
||||
assert params.get("required") == []
|
||||
assert params.get("properties") == {}
|
||||
assert "additionalProperties" in params
|
||||
|
||||
|
||||
def test_complete_coerces_array_items_null_to_empty_object() -> None:
|
||||
raw = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "mcp__tusharemcp__adj_factor",
|
||||
"description": "x",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"codes": {"type": "array", "items": None, "description": "ids"},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
out = complete_openai_tools_wire_parameters(raw)
|
||||
codes = out[0]["function"]["parameters"]["properties"]["codes"]
|
||||
assert codes.get("type") == "array"
|
||||
assert codes.get("items") == {}
|
||||
|
||||
|
||||
def test_complete_drops_null_combo_keywords_and_fixes_lists() -> None:
|
||||
raw = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "demo",
|
||||
"description": "d",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {"anyOf": None, "description": "a"},
|
||||
"y": {"allOf": None},
|
||||
"z": {"prefixItems": None},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
out = complete_openai_tools_wire_parameters(raw)
|
||||
props = out[0]["function"]["parameters"]["properties"]
|
||||
assert "anyOf" not in props["x"]
|
||||
assert props["y"].get("allOf") == []
|
||||
assert props["z"].get("prefixItems") == []
|
||||
|
||||
|
||||
def test_complete_strips_null_entries_from_combo_arrays() -> None:
|
||||
raw = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "demo2",
|
||||
"description": "d",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {"anyOf": [None, {"type": "string"}]},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
out = complete_openai_tools_wire_parameters(raw)
|
||||
assert out[0]["function"]["parameters"]["properties"]["x"]["anyOf"] == [{"type": "string"}]
|
||||
|
||||
|
||||
def test_complete_fills_minimal_object_parameters_like_tier_minimal() -> None:
|
||||
raw = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "mcp__srv__ping",
|
||||
"description": "p",
|
||||
"parameters": {"type": "object", "additionalProperties": True},
|
||||
},
|
||||
}
|
||||
]
|
||||
out = complete_openai_tools_wire_parameters(raw)
|
||||
params = out[0]["function"]["parameters"]
|
||||
assert params.get("type") == "object"
|
||||
assert params.get("required") == []
|
||||
assert params.get("properties") == {}
|
||||
|
||||
|
||||
def test_complete_fills_object_type_when_null_with_properties() -> None:
|
||||
raw = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "x",
|
||||
"description": "",
|
||||
"parameters": {"type": None, "properties": {"a": {"type": "string"}}, "required": []},
|
||||
},
|
||||
}
|
||||
]
|
||||
out = complete_openai_tools_wire_parameters(raw)
|
||||
params = out[0]["function"]["parameters"]
|
||||
assert params.get("type") == "object"
|
||||
assert "a" in params.get("properties", {})
|
||||
|
||||
|
||||
def test_complete_removes_empty_anyof_after_null_strip() -> None:
|
||||
raw = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "demo3",
|
||||
"description": "d",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"x": {"anyOf": [None]}},
|
||||
"required": [],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
out = complete_openai_tools_wire_parameters(raw)
|
||||
assert "anyOf" not in out[0]["function"]["parameters"]["properties"]["x"]
|
||||
Loading…
Add table
Add a link
Reference in a new issue