feat: release v1.0.2 LeKiwi 语言控制与网站嵌入
web-platform-ci / Standalone decision service (no cloud credentials) (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
web-platform-ci / Standalone decision service (no cloud credentials) (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
集成服务器托管模型、自然语言移动与有界抓放、内置 LeKiwi URL 导入和双摄像头;同步部署契约与指定域名 iframe 白名单,保留原有物理安全、会话及调用预算防护。 更新 npm 包及锁文件版本、CHANGELOG 与发布文档。提交前 typecheck、120 项定向前端测试和 44 项后端测试通过(3 项可选跳过);真实 v2 云模型抓放仍待单独验收,不包含运行密钥或构建产物。
This commit is contained in:
+47
-19
@@ -9,6 +9,22 @@ SCHEMA = json.loads(
|
||||
(Path(__file__).resolve().parents[1] / "contracts/lekiwi-agent-v1.schema.json").read_text()
|
||||
)
|
||||
VERSION = "lekiwi-agent-v1"
|
||||
LANGUAGE_VERSION = "lekiwi-language-v2"
|
||||
LANGUAGE_SCHEMA = json.loads(
|
||||
(
|
||||
Path(__file__).resolve().parents[1] / "contracts/lekiwi-language-task-v2.schema.json"
|
||||
).read_text()
|
||||
)
|
||||
|
||||
|
||||
def schema_for(version):
|
||||
if version == VERSION:
|
||||
return SCHEMA
|
||||
if version == LANGUAGE_VERSION:
|
||||
return LANGUAGE_SCHEMA
|
||||
raise DecisionError("unsupported_version")
|
||||
|
||||
|
||||
PRECONDITIONS = dict(
|
||||
zip(
|
||||
SCHEMA["$defs"]["Plan"]["properties"]["steps"]["items"]["properties"]["skill"]["enum"],
|
||||
@@ -29,6 +45,8 @@ PRECONDITIONS = dict(
|
||||
)
|
||||
)
|
||||
SKILLS = list(PRECONDITIONS)
|
||||
LANGUAGE_PRECONDITIONS = {"stow": "scene-ready", "approach": "arm-safe", **PRECONDITIONS}
|
||||
LANGUAGE_SKILLS = list(LANGUAGE_PRECONDITIONS)
|
||||
|
||||
|
||||
class DecisionError(Exception):
|
||||
@@ -58,12 +76,12 @@ def loads(text):
|
||||
raise DecisionError("invalid_json") from exc
|
||||
|
||||
|
||||
def check(node, value):
|
||||
def check(node, value, schema=SCHEMA):
|
||||
def fail():
|
||||
raise DecisionError("contract_mismatch")
|
||||
|
||||
if "$ref" in node:
|
||||
return check(SCHEMA["$defs"][node["$ref"].removeprefix("#/$defs/")], value)
|
||||
return check(schema["$defs"][node["$ref"].removeprefix("#/$defs/")], value, schema)
|
||||
if "const" in node and (type(value) is not type(node["const"]) or value != node["const"]):
|
||||
fail()
|
||||
if "enum" in node and value not in node["enum"]:
|
||||
@@ -92,7 +110,7 @@ def check(node, value):
|
||||
if node.get("uniqueItems") and len({json.dumps(v) for v in value}) != len(value):
|
||||
fail()
|
||||
for item in value:
|
||||
check(node["items"], item)
|
||||
check(node["items"], item, schema)
|
||||
elif kind == "object":
|
||||
if not isinstance(value, dict):
|
||||
fail()
|
||||
@@ -102,20 +120,21 @@ def check(node, value):
|
||||
if node.get("additionalProperties") is False and value.keys() - props.keys():
|
||||
fail()
|
||||
for key in value.keys() & props.keys():
|
||||
check(props[key], value[key])
|
||||
check(props[key], value[key], schema)
|
||||
|
||||
|
||||
def validate(name, value):
|
||||
def validate(name, value, version=VERSION):
|
||||
try:
|
||||
if len(json.dumps(value, ensure_ascii=False, allow_nan=False)) > 65536:
|
||||
raise DecisionError("message_too_large", 413)
|
||||
check(SCHEMA["$defs"][name], value)
|
||||
schema = schema_for(version)
|
||||
check(schema["$defs"][name], value, schema)
|
||||
except (ValueError, TypeError, OverflowError, RecursionError) as exc:
|
||||
raise DecisionError("contract_mismatch") from exc
|
||||
return deepcopy(value)
|
||||
|
||||
|
||||
def output_schema(name):
|
||||
def output_schema(name, version=VERSION):
|
||||
"""Equivalent schema with explicit string types for strict API implementations."""
|
||||
|
||||
def expand(node):
|
||||
@@ -129,38 +148,47 @@ def output_schema(name):
|
||||
result["items"] = expand(node["items"])
|
||||
return result
|
||||
|
||||
return expand(SCHEMA["$defs"][name])
|
||||
return expand(schema_for(version)["$defs"][name])
|
||||
|
||||
|
||||
def remaining_skills(value):
|
||||
if not isinstance(value, list) or not value or value not in [SKILLS[i:] for i in range(11)]:
|
||||
def remaining_skills(value, version=VERSION):
|
||||
schema_for(version)
|
||||
skills = LANGUAGE_SKILLS if version == LANGUAGE_VERSION else SKILLS
|
||||
if (
|
||||
not isinstance(value, list)
|
||||
or not value
|
||||
or value not in [skills[i:] for i in range(len(skills))]
|
||||
):
|
||||
raise DecisionError("invalid_remaining_skills")
|
||||
return value
|
||||
|
||||
|
||||
def validate_plan(value, remaining):
|
||||
value = validate("Plan", value)
|
||||
if [step["skill"] for step in value["steps"]] != remaining_skills(remaining):
|
||||
def validate_plan(value, remaining, version=VERSION):
|
||||
value = validate("Plan", value, version)
|
||||
if [step["skill"] for step in value["steps"]] != remaining_skills(remaining, version):
|
||||
raise DecisionError("invalid_plan_order")
|
||||
if any(step["precondition"] != PRECONDITIONS[step["skill"]] for step in value["steps"]):
|
||||
preconditions = LANGUAGE_PRECONDITIONS if version == LANGUAGE_VERSION else PRECONDITIONS
|
||||
if any(step["precondition"] != preconditions[step["skill"]] for step in value["steps"]):
|
||||
raise DecisionError("invalid_precondition")
|
||||
return value
|
||||
|
||||
|
||||
def candidates(value):
|
||||
def candidates(value, version=VERSION):
|
||||
schema_for(version)
|
||||
skills = LANGUAGE_SKILLS if version == LANGUAGE_VERSION else SKILLS
|
||||
if (
|
||||
not isinstance(value, list)
|
||||
or not 1 <= len(value) <= 4
|
||||
or any(type(v) is not str or v not in [*SKILLS, "stop"] for v in value)
|
||||
or any(type(v) is not str or v not in [*skills, "stop"] for v in value)
|
||||
or len(set(value)) != len(value)
|
||||
):
|
||||
raise DecisionError("invalid_candidates")
|
||||
return value
|
||||
|
||||
|
||||
def validate_jev(value, choices):
|
||||
value = validate("JevDecision", value)
|
||||
if value["choice"] not in candidates(choices):
|
||||
def validate_jev(value, choices, version=VERSION):
|
||||
value = validate("JevDecision", value, version)
|
||||
if value["choice"] not in candidates(choices, version):
|
||||
raise DecisionError("invalid_choice")
|
||||
return value
|
||||
|
||||
|
||||
Reference in New Issue
Block a user