✨ feat: 灵台引擎 Python 绑定与消息路径接入(WP-15/WP-03 交界)
- engine/bindings:nanobind 官方薄绑定包 hunjing-lingtai(LingtaiEngine RAII、HjStatus→LingtaiError 映射、HJ_ABI_VERSION 导出);scikit-build-core 构建,直接编译引擎源码自包含 - backend 以 editable 路径依赖接入;typings/lingtai.pyi 类型存根(mypy strict 全绿) - app/core/lingtai.py:懒初始化单例 + LingtaiUnavailable fail-closed(禁止宿主第二实现降级) - 迁移 3:message.parser_version;post_message 的 markdown 输入经 hj_transform 规范化为 canonical blocks(展开 code/mermaid/math 并标注 parser_version,契约第 4 节) - 测试 29 项全绿:消息展开、Python/C++ golden 字节一致、引擎注入失败时消息路径显式 500(fail-closed 验证)
This commit is contained in:
1 parent
127191c889
commit
491136c770
15 files changed
+314
-4
No files matched your search
@@ -0,0 +1,22 @@
|
||||
cmake_minimum_required(VERSION 3.18...3.30)
|
||||
|
||||
project(lingtai_bindings LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
find_package(Python COMPONENTS Interpreter Development REQUIRED)
|
||||
find_package(nanobind CONFIG REQUIRED)
|
||||
|
||||
# 直接编译引擎源码(自包含,不依赖 dylib 安装路径)
|
||||
nanobind_add_module(lingtai
|
||||
src/lingtai.cpp
|
||||
../src/lingtai.cpp
|
||||
../src/transform.cpp)
|
||||
|
||||
target_include_directories(lingtai PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../src)
|
||||
|
||||
install(TARGETS lingtai DESTINATION .)
|
||||
@@ -0,0 +1,12 @@
|
||||
[build-system]
|
||||
requires = ["scikit-build-core>=0.10", "nanobind>=2.4"]
|
||||
build-backend = "scikit_build_core.build"
|
||||
|
||||
[project]
|
||||
name = "hunjing-lingtai"
|
||||
version = "0.1.0"
|
||||
description = "灵台引擎(Lingtai Engine)官方 Python 薄绑定(ADR-016)"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[tool.scikit-build]
|
||||
build-dir = "build/{wheel_tag}"
|
||||
@@ -0,0 +1,88 @@
|
||||
// 灵台引擎官方 Python 绑定(nanobind,ADR-016)。
|
||||
// 薄封装:类型转换、句柄 RAII 与错误映射;不复制业务语义(契约 lingtai-ffi 第 8 节)。
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include <nanobind/nanobind.h>
|
||||
#include <nanobind/stl/string.h>
|
||||
|
||||
#include "hunjing/lingtai.h"
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
namespace {
|
||||
|
||||
struct LingtaiError : std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
struct PyEngine {
|
||||
HjEngine *handle = nullptr;
|
||||
|
||||
PyEngine() {
|
||||
const HjStatus st = hj_engine_new(HJ_ABI_VERSION, &handle);
|
||||
if (st != HJ_OK) {
|
||||
throw LingtaiError(std::string("灵台引擎初始化失败:") + hj_last_error());
|
||||
}
|
||||
}
|
||||
|
||||
PyEngine(const PyEngine &) = delete;
|
||||
PyEngine &operator=(const PyEngine &) = delete;
|
||||
|
||||
~PyEngine() {
|
||||
if (handle != nullptr) {
|
||||
hj_engine_free(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const char *status_name(HjStatus st) {
|
||||
switch (st) {
|
||||
case HJ_OK: return "HJ_OK";
|
||||
case HJ_ERR_INVALID_ARGUMENT: return "HJ_ERR_INVALID_ARGUMENT";
|
||||
case HJ_ERR_OUT_OF_MEMORY: return "HJ_ERR_OUT_OF_MEMORY";
|
||||
case HJ_ERR_UNSUPPORTED_ABI: return "HJ_ERR_UNSUPPORTED_ABI";
|
||||
case HJ_ERR_HANDLE_INVALID: return "HJ_ERR_HANDLE_INVALID";
|
||||
case HJ_ERR_REENTRANCY: return "HJ_ERR_REENTRANCY";
|
||||
case HJ_ERR_PARSE_FAILED: return "HJ_ERR_PARSE_FAILED";
|
||||
case HJ_ERR_LIMIT_EXCEEDED: return "HJ_ERR_LIMIT_EXCEEDED";
|
||||
case HJ_ERR_POLICY_DENIED: return "HJ_ERR_POLICY_DENIED";
|
||||
case HJ_ERR_SIGNATURE_INVALID: return "HJ_ERR_SIGNATURE_INVALID";
|
||||
case HJ_ERR_INDEX_CORRUPT: return "HJ_ERR_INDEX_CORRUPT";
|
||||
case HJ_ERR_BUFFER_TOO_SMALL: return "HJ_ERR_BUFFER_TOO_SMALL";
|
||||
case HJ_ERR_INTERNAL: return "HJ_ERR_INTERNAL";
|
||||
}
|
||||
return "HJ_ERR_UNKNOWN";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NB_MODULE(lingtai, m) {
|
||||
m.doc() = "灵台引擎(Lingtai Engine)官方 Python 绑定";
|
||||
|
||||
m.attr("HJ_ABI_VERSION") = HJ_ABI_VERSION;
|
||||
m.def("abi_version", [] { return hj_abi_version(); }, "引擎构建时的 ABI major");
|
||||
m.def("transform_version", [] { return std::string(hj_transform_version()); },
|
||||
"Markdown 解析器版本");
|
||||
|
||||
nb::exception<LingtaiError>(m, "LingtaiError");
|
||||
|
||||
nb::class_<PyEngine>(m, "LingtaiEngine")
|
||||
.def(nb::init<>())
|
||||
.def(
|
||||
"transform_markdown",
|
||||
[](PyEngine &self, const std::string &input) -> std::string {
|
||||
HjBuffer *buf = nullptr;
|
||||
const HjStatus st =
|
||||
hj_transform_markdown(self.handle, input.data(), input.size(), &buf);
|
||||
if (st != HJ_OK) {
|
||||
throw LingtaiError(std::string(status_name(st)) + ": " + hj_last_error());
|
||||
}
|
||||
std::string out(static_cast<const char *>(hj_buffer_data(buf)), hj_buffer_len(buf));
|
||||
hj_buffer_free(buf);
|
||||
return out;
|
||||
},
|
||||
nb::arg("input"),
|
||||
"Markdown → canonical ContentBlock JSON(确定性、键序固定)");
|
||||
}
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "hunjing-lingtai"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
Reference in new issue
Block a user