✨ feat: 灵台引擎 gRPC 独立服务形态(ADR-017 双形态落地)
- proto(engine/proto/liushen/v1):LingtaiEngine 九方法——GetEngineInfo(版本协商/能力位)、TransformMarkdown、RebuildExport(UNIMPLEMENTED 明示随 WP-03)、OpenStream 双向流、CheckBudget、RerankRecall、EvaluatePolicy、ValidateUrl、CheckStateTransition;RunStatus 枚举与契约一致 - 服务端参考实现(engine/server/lingtai_server.py):进程内直链 nanobind 引擎核心——双形态同核同 golden;开发档 127.0.0.1 明文(生产 mTLS,文案明示);状态转换表按 agent-runtime 第 3 节 - 集成测试 6 项:进程内随机端口起服——版本协商、双形态 golden(服务输出==进程内引擎输出,ADR-017 核心不变量首次实证)、双向流 envelope 序号单调、决策表/URL/状态机、RebuildExport UNIMPLEMENTED 明确拒绝(不伪造) - Makefile:lingtai-proto(stub 再生)与 lingtai-server(启动服务)目标 - 注:C++ 原生二进制(hunjing-lingtai-server)随交叉编译矩阵交付;Python 参考实现保证 proto 与语义先行锁定 - 测试 102 项全绿(+6);ruff/mypy 干净
This commit is contained in:
1 parent
a39cec5287
commit
9dafb8fe72
10 files changed
+1141
No files matched your search
@@ -0,0 +1,126 @@
|
||||
"""gRPC 服务形态集成测试:双形态 golden 一致(ADR-017)。
|
||||
|
||||
在进程内启动服务器(随机端口),验证:
|
||||
- GetEngineInfo 版本协商
|
||||
- TransformMarkdown 与进程内 lingtai.transform_markdown 输出一致
|
||||
- OpenStream 双向流 envelope 键序与序号单调
|
||||
- EvaluatePolicy / ValidateUrl / CheckStateTransition / RerankRecall 行为
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import grpc
|
||||
import pytest
|
||||
|
||||
_SERVER_ROOT = Path(__file__).resolve().parents[2] / "engine" / "server"
|
||||
sys.path.insert(0, str(_SERVER_ROOT / "generated"))
|
||||
_SERVER_MODULE = _SERVER_ROOT / "lingtai_server.py"
|
||||
|
||||
import liushen.v1.liushen_pb2 as pb # noqa: E402
|
||||
import liushen.v1.liushen_pb2_grpc as pb_grpc # noqa: E402
|
||||
|
||||
from app.core import lingtai # noqa: E402
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
async def grpc_channel():
|
||||
import importlib.util as _ilu
|
||||
|
||||
from grpc import aio as grpc_aio
|
||||
|
||||
_spec = _ilu.spec_from_file_location(
|
||||
"lingtai_server",
|
||||
_SERVER_MODULE,
|
||||
)
|
||||
_mod = _ilu.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(_mod)
|
||||
LingtaiEngineServicer = _mod.LingtaiEngineServicer
|
||||
|
||||
server = grpc_aio.server()
|
||||
pb_grpc.add_LingtaiEngineServicer_to_server(LingtaiEngineServicer(), server)
|
||||
port = server.add_insecure_port("127.0.0.1:0")
|
||||
await server.start()
|
||||
channel = grpc_aio.insecure_channel(f"127.0.0.1:{port}")
|
||||
yield channel
|
||||
await channel.close()
|
||||
await server.stop(0)
|
||||
|
||||
|
||||
async def test_engine_info(grpc_channel) -> None:
|
||||
stub = pb_grpc.LingtaiEngineStub(grpc_channel)
|
||||
info = await stub.GetEngineInfo(pb.GetEngineInfoRequest())
|
||||
assert info.abi_major == 1
|
||||
assert info.parser_version == "hunjing-markdown-v1"
|
||||
assert "transform" in info.capabilities
|
||||
|
||||
|
||||
async def test_transform_dual_form_golden(grpc_channel) -> None:
|
||||
"""双形态一致:服务输出 == 进程内引擎输出(ADR-017 核心不变量)。"""
|
||||
import json
|
||||
|
||||
stub = pb_grpc.LingtaiEngineStub(grpc_channel)
|
||||
markdown = "说明\n```mermaid\nA-->B\n```\n$$\nz\n$$\n"
|
||||
resp = await stub.TransformMarkdown(pb.TransformMarkdownRequest(markdown=markdown))
|
||||
served = json.loads(resp.canonical_json)
|
||||
inproc = lingtai.transform_markdown(markdown)
|
||||
assert served == inproc # 服务形态与进程内输出一致
|
||||
|
||||
|
||||
async def test_open_stream_envelope(grpc_channel) -> None:
|
||||
import json
|
||||
|
||||
stub = pb_grpc.LingtaiEngineStub(grpc_channel)
|
||||
|
||||
async def frames():
|
||||
yield pb.StreamFrame(block_started="block_01")
|
||||
yield pb.StreamFrame(text_delta="你")
|
||||
yield pb.StreamFrame(text_delta="好")
|
||||
|
||||
events = []
|
||||
async for ev in stub.OpenStream(frames()):
|
||||
events.append(json.loads(ev.event_json))
|
||||
assert len(events) == 2
|
||||
assert all(e["type"] == "content.block.delta" for e in events)
|
||||
seqs = [e["sequence"] for e in events]
|
||||
assert seqs == sorted(seqs)
|
||||
assert events[-1]["data"]["delta"]["text"] == "好"
|
||||
|
||||
|
||||
async def test_policy_and_state(grpc_channel) -> None:
|
||||
stub = pb_grpc.LingtaiEngineStub(grpc_channel)
|
||||
# 决策表
|
||||
assert (await stub.EvaluatePolicy(pb.EvaluatePolicyRequest(
|
||||
autonomy_level=1, risk_level=0))).decision == 0
|
||||
assert (await stub.EvaluatePolicy(pb.EvaluatePolicyRequest(
|
||||
autonomy_level=0, risk_level=2))).decision == 2
|
||||
# URL 白名单
|
||||
assert (await stub.ValidateUrl(pb.ValidateUrlRequest(
|
||||
url="https://ok"))).allowed is True
|
||||
assert (await stub.ValidateUrl(pb.ValidateUrlRequest(
|
||||
url="file:///etc"))).allowed is False
|
||||
# 状态机
|
||||
assert (await stub.CheckStateTransition(pb.CheckStateTransitionRequest(
|
||||
**{"from": pb.RUN_STATUS_QUEUED}, to=pb.RUN_STATUS_RUNNING))).valid is True
|
||||
assert (await stub.CheckStateTransition(pb.CheckStateTransitionRequest(
|
||||
**{"from": pb.RUN_STATUS_COMPLETED}, to=pb.RUN_STATUS_RUNNING))).valid is False
|
||||
|
||||
|
||||
async def test_rerank(grpc_channel) -> None:
|
||||
import json
|
||||
|
||||
stub = pb_grpc.LingtaiEngineStub(grpc_channel)
|
||||
resp = await stub.RerankRecall(pb.RerankRecallRequest(
|
||||
candidates_json='{"query_terms":["魂镜"],"candidates":['
|
||||
'{"id":"a","text":"魂镜 系统"},{"id":"b","text":"无"}]}'
|
||||
))
|
||||
ranked = json.loads(resp.ranked_json)["ranked"]
|
||||
assert ranked[0] == {"id": "a", "score": 10}
|
||||
assert ranked[1] == {"id": "b", "score": 0}
|
||||
|
||||
|
||||
async def test_rebuild_export_unimplemented(grpc_channel) -> None:
|
||||
stub = pb_grpc.LingtaiEngineStub(grpc_channel)
|
||||
with pytest.raises(grpc.aio.AioRpcError) as exc:
|
||||
await stub.RebuildExport(pb.RebuildExportRequest(canonical_json="{}"))
|
||||
assert exc.value.code() == grpc.StatusCode.UNIMPLEMENTED
|
||||
Reference in new issue
Block a user