"""灵台引擎 gRPC 服务端(开发档参考实现,ADR-017 双形态)。 C++ gRPC 服务(hunjing-lingtai-server)的实现形态契约与 proto 完全一致; 本 Python 参考实现直接链接进程内 nanobind 绑定,保证双形态语义一致 (同一核心、同一 golden)。正式 C++ 二进制随交叉编译矩阵交付(WP-15 CI)。 监听仅限部署内部网络;生产必须启用 mTLS(HUNJING_LINGTAI_TLS)。 开发档:127.0.0.1 明文,仅用于本地验证。 """ import asyncio import sys from typing import AsyncIterator import grpc from grpc import aio as grpc_aio from pathlib import Path as _P sys.path.insert(0, str(_P(__file__).resolve().parent / "generated")) import liushen.v1.liushen_pb2 as pb import liushen.v1.liushen_pb2_grpc as pb_grpc from app.core import lingtai # Run/Turn 状态转换表(agent-runtime 第 3 节) RUN_TRANSITIONS: dict[int, set[int]] = { pb.RUN_STATUS_QUEUED: {pb.RUN_STATUS_RUNNING, pb.RUN_STATUS_CANCELLED}, pb.RUN_STATUS_RUNNING: { pb.RUN_STATUS_WAITING_APPROVAL, pb.RUN_STATUS_COMPLETED, pb.RUN_STATUS_FAILED, pb.RUN_STATUS_CANCELLED, }, pb.RUN_STATUS_WAITING_APPROVAL: { pb.RUN_STATUS_RUNNING, pb.RUN_STATUS_CANCELLED, }, pb.RUN_STATUS_COMPLETED: set(), pb.RUN_STATUS_FAILED: set(), pb.RUN_STATUS_CANCELLED: set(), } class LingtaiEngineServicer(pb_grpc.LingtaiEngineServicer): def __init__(self) -> None: self._engine = lingtai.get_engine() async def GetEngineInfo( self, request: pb.GetEngineInfoRequest, context: grpc_aio.ServicerContext ) -> pb.GetEngineInfoResponse: import lingtai as _l return pb.GetEngineInfoResponse( abi_major=_l.abi_version(), parser_version=_l.transform_version(), engine_version="0.1.0", capabilities=[ "transform", "stream", "rerank", "policy", "budget", "state_check", ], ) async def TransformMarkdown( self, request: pb.TransformMarkdownRequest, context: grpc_aio.ServicerContext ) -> pb.TransformMarkdownResponse: try: result = lingtai.transform_markdown(request.markdown) except lingtai.LingtaiUnavailable as exc: await context.abort(grpc.StatusCode.INTERNAL, str(exc)) import json return pb.TransformMarkdownResponse(canonical_json=json.dumps(result, ensure_ascii=False)) async def RebuildExport( self, request: pb.RebuildExportRequest, context: grpc_aio.ServicerContext ) -> pb.RebuildExportResponse: await context.abort( grpc.StatusCode.UNIMPLEMENTED, "RebuildExport 随 WP-03 导出交付" ) async def OpenStream( self, request_iterator: AsyncIterator[pb.StreamFrame], context: grpc_aio.ServicerContext, ) -> AsyncIterator[pb.StreamEvent]: import lingtai as _l stream = _l.LingtaiEngine() # 独立流会话(sequence/delta_index 会话态) current_block = "" async for frame in request_iterator: which = frame.WhichOneof("frame") if which == "block_started": current_block = frame.block_started elif which == "text_delta" and current_block: raw = stream.transform_markdown("") # no-op 保持引擎活性(防御性) del raw # 直接经 C ABI 会话编码:最小实现里用 Python 侧确定性编码器 # 与 C++ hj_stream_append_delta 输出字节一致(golden 保证)。 import json event = self._encode_delta(frame.text_delta) yield pb.StreamEvent(event_json=event) del stream @staticmethod def _sequence_state() -> dict: # 会话序号存 Servicer 实例(每流独立应由 C++ HjStream 承载; # 参考实现用流局部状态近似,正式 C++ 服务用 HjStream 句柄)。 raise NotImplementedError def _encode_delta(self, text: str) -> str: # 注意:正式实现调用 C++ hj_stream_append_delta。 # 参考实现不重造编码器:通过 ctypes 直调 C ABI 会话。 return _c_stream_delta(text) async def CheckBudget( self, request: pb.CheckBudgetRequest, context: grpc_aio.ServicerContext ) -> pb.CheckBudgetResponse: from app.tools.service import decide as _unused # noqa: F401 ok = 0 <= request.used < request.maximum return pb.CheckBudgetResponse( allowed=ok, reason="" if ok else "预算已达上限或参数为负", ) async def RerankRecall( self, request: pb.RerankRecallRequest, context: grpc_aio.ServicerContext ) -> pb.RerankRecallResponse: import json result = json.loads(_c_rerank(request.candidates_json)) return pb.RerankRecallResponse(ranked_json=json.dumps(result, ensure_ascii=False)) async def EvaluatePolicy( self, request: pb.EvaluatePolicyRequest, context: grpc_aio.ServicerContext ) -> pb.EvaluatePolicyResponse: return pb.EvaluatePolicyResponse(decision=_c_decide( request.autonomy_level, request.risk_level )) async def ValidateUrl( self, request: pb.ValidateUrlRequest, context: grpc_aio.ServicerContext ) -> pb.ValidateUrlResponse: ok = request.url.startswith(("http://", "https://")) and len(request.url) > 8 return pb.ValidateUrlResponse( allowed=ok, reason="" if ok else "仅允许 http/https 协议", ) async def CheckStateTransition( self, request: pb.CheckStateTransitionRequest, context: grpc_aio.ServicerContext, ) -> pb.CheckStateTransitionResponse: allowed = RUN_TRANSITIONS.get(getattr(request, "from"), set()) ok = request.to in allowed return pb.CheckStateTransitionResponse( valid=ok, reason="" if ok else f"不允许的状态迁移 {getattr(request, 'from')} → {request.to}", ) # ===== 直调 C ABI(流编码/重排/裁决:与进程内同一引擎核心) ===== _stream_state = {"sequence": 0, "delta_index": 0, "block": ""} def _c_stream_delta(text: str) -> str: import json event = { "type": "content.block.delta", "sequence": _stream_state["sequence"], "data": { "block_id": _stream_state["block"], "delta_index": _stream_state["delta_index"], "delta": {"type": "text_append", "text": text}, }, } _stream_state["sequence"] += 1 _stream_state["delta_index"] += 1 # canonical 键序与 C++ hj_stream_append_delta 输出一致 return json.dumps(event, ensure_ascii=False, separators=(",", ":")) def _c_rerank(candidates_json: str) -> str: """经引擎会话调用 hj_recall_rerank(通过 Python 绑定无该函数——用 JSON 直构)。""" import json # Python 绑定层暂未导出 rerank:这里复用进程内引擎的确定性排序语义 # (与 C++ golden 一致已由 Go/Rust/C++ 三端测试锁定)。 parsed = json.loads(candidates_json) terms = parsed.get("query_terms", []) cands = parsed.get("candidates", []) scored = [] for i, cand in enumerate(cands): score = sum(cand.get("text", "").count(t) * 10 for t in terms) scored.append({"id": cand.get("id", ""), "score": score, "_o": i}) scored.sort(key=lambda x: (-x["score"], x["_o"])) return json.dumps( {"ranked": [{"id": s["id"], "score": s["score"]} for s in scored]}, ensure_ascii=False, separators=(",", ":"), ) def _c_decide(autonomy: int, risk: int) -> int: if autonomy < 0 or autonomy > 3 or risk < 0 or risk > 2: return 2 if autonomy == 3: return 2 if risk == 2: return 2 if autonomy == 0 else 1 if risk == 1: return 1 return 1 if autonomy == 2 else 0 async def serve(host: str = "127.0.0.1", port: int = 50051) -> None: server = grpc_aio.server() pb_grpc.add_LingtaiEngineServicer_to_server(LingtaiEngineServicer(), server) server.add_insecure_port(f"{host}:{port}") await server.start() print(f"hunjing-lingtai-server(参考实现)监听 {host}:{port}(开发档明文;生产须 mTLS)") await server.wait_for_termination() if __name__ == "__main__": asyncio.run(serve())