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:
@@ -2,8 +2,10 @@
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
@@ -19,19 +21,88 @@ async def main():
|
||||
|
||||
async def respond(request):
|
||||
body = await request.json()
|
||||
import json
|
||||
|
||||
if request.path == "/decisions":
|
||||
choices = {name: next(iter(q["criteria"])) for name, q in body["questions"].items()}
|
||||
if "grasp" in choices:
|
||||
evidence = json.loads(body["state"])["observation"]["evidence"]
|
||||
choices.update(
|
||||
grasp="secure"
|
||||
if evidence["secure"]
|
||||
else "uncertain"
|
||||
if any(v > 0.2 for v in evidence["fingerForces"])
|
||||
else "empty",
|
||||
diagnosis="none",
|
||||
recovery="continue",
|
||||
)
|
||||
if "noul" in choices:
|
||||
choices.update(noul="allow", score="good", reason="verified_progress")
|
||||
return web.json_response(
|
||||
{
|
||||
"answers": {
|
||||
name: {"choice": next(iter(q["criteria"]))}
|
||||
for name, q in body["questions"].items()
|
||||
},
|
||||
"answers": {name: {"choice": choice} for name, choice in choices.items()},
|
||||
"usage": {"input_tokens": 1},
|
||||
}
|
||||
)
|
||||
import json
|
||||
|
||||
value = '{"ok":true}' if '"ok"' in str(body) else json.dumps(plan_value())
|
||||
if '"action"' in json.dumps(body.get("text", {})):
|
||||
instruction = json.loads(body["input"])["instruction"]
|
||||
choices = {
|
||||
"前进0.5米": ("move", 0.5),
|
||||
"前进0.2米": ("move", 0.2),
|
||||
"后退0.2米": ("move", -0.2),
|
||||
"左转90度": ("turn", 90),
|
||||
"右转90度": ("turn", -90),
|
||||
"把方块搬到支撑台": ("pick_place", 0),
|
||||
"把方块搬到 B 区": ("pick_place", 0),
|
||||
"把方块搬到 (0.25,0.50) 米": ("pick_place", 0),
|
||||
}
|
||||
action, number = choices.get(instruction, ("clarify", 0))
|
||||
context = json.loads(body["input"])
|
||||
extra = {}
|
||||
if "sceneContext" in context:
|
||||
extra = dict(
|
||||
version="lekiwi-language-v2",
|
||||
objectId="none",
|
||||
targetId="none",
|
||||
position=[],
|
||||
supportId="none",
|
||||
)
|
||||
if action == "pick_place":
|
||||
extra.update(
|
||||
objectId="block",
|
||||
targetId="B" if "B" in instruction else "coordinates",
|
||||
position=[] if "B" in instruction else [0.25, 0.50],
|
||||
supportId="table",
|
||||
)
|
||||
value = json.dumps(
|
||||
{
|
||||
**extra,
|
||||
"action": action,
|
||||
"value": number,
|
||||
"summary": instruction
|
||||
if action != "clarify"
|
||||
else "请说明左转还是右转,一次一个动作。",
|
||||
}
|
||||
)
|
||||
else:
|
||||
value = '{"ok":true}' if '"ok"' in str(body) else json.dumps(plan_value())
|
||||
context = json.loads(body.get("input", "{}"))
|
||||
if context.get("observation", {}).get("version") == "lekiwi-language-v2":
|
||||
from decision_server.protocol import LANGUAGE_PRECONDITIONS
|
||||
|
||||
value = json.dumps(
|
||||
dict(
|
||||
version="lekiwi-language-v2",
|
||||
objectId="block",
|
||||
goalId="placement",
|
||||
summary="测试规划",
|
||||
steps=[
|
||||
dict(skill=s, precondition=LANGUAGE_PRECONDITIONS[s], onFailure="stop")
|
||||
for s in context["remaining"]
|
||||
],
|
||||
)
|
||||
)
|
||||
if request.path == "/chat/completions":
|
||||
return web.json_response(
|
||||
{"choices": [{"finish_reason": "stop", "message": {"content": value}}]}
|
||||
@@ -62,7 +133,26 @@ async def main():
|
||||
from decision_server import server
|
||||
|
||||
server.post = fake_post
|
||||
app = create_website_app("http://127.0.0.1:4180", development=True)
|
||||
from decision_server.connections import Connection
|
||||
from decision_server.hosted_budget import HostedBudget
|
||||
|
||||
quota_dir = tempfile.TemporaryDirectory()
|
||||
app = create_website_app(
|
||||
os.environ.get("CADWORLD_E2E_ORIGIN", "http://127.0.0.1:4180"),
|
||||
development=True,
|
||||
defaults={
|
||||
"llm": Connection(
|
||||
"responses", "https://api.deepseek.com", "deepseek-flash", "fixture-llm"
|
||||
),
|
||||
"jev": Connection(
|
||||
"openrouter-decisions",
|
||||
"https://openrouter.ai/api/alpha/decisions",
|
||||
"typesafe/jev-1.13",
|
||||
"fixture-jev",
|
||||
),
|
||||
},
|
||||
budget=HostedBudget(Path(quota_dir.name) / "budget.sqlite"),
|
||||
)
|
||||
catalog = app[CATALOG]
|
||||
catalog.models = {"fixture/structured": "Fixture structured model (not real)"}
|
||||
catalog.available = True
|
||||
@@ -73,12 +163,15 @@ async def main():
|
||||
catalog.refresh = refresh
|
||||
gateway = web.AppRunner(app, access_log=None, handler_cancellation=True)
|
||||
await gateway.setup()
|
||||
await web.TCPSite(gateway, "127.0.0.1", 8769).start()
|
||||
await web.TCPSite(
|
||||
gateway, "127.0.0.1", int(os.environ.get("CADWORLD_E2E_PORT", "8769"))
|
||||
).start()
|
||||
try:
|
||||
await asyncio.Event().wait()
|
||||
finally:
|
||||
await gateway.cleanup()
|
||||
await runner.cleanup()
|
||||
quota_dir.cleanup()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user