corespine 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- corespine-0.0.1/.github/workflows/ci.yml +32 -0
- corespine-0.0.1/.gitignore +5 -0
- corespine-0.0.1/CLAUDE.md +55 -0
- corespine-0.0.1/Makefile +52 -0
- corespine-0.0.1/PKG-INFO +79 -0
- corespine-0.0.1/README.md +56 -0
- corespine-0.0.1/deploy/.dockerignore +14 -0
- corespine-0.0.1/deploy/Dockerfile +45 -0
- corespine-0.0.1/deploy/README.md +33 -0
- corespine-0.0.1/docs/extending.md +145 -0
- corespine-0.0.1/docs/prd.md +70 -0
- corespine-0.0.1/docs/roadmap.md +76 -0
- corespine-0.0.1/examples/conformance_usage.py +72 -0
- corespine-0.0.1/examples/quickstart.py +83 -0
- corespine-0.0.1/pyproject.toml +63 -0
- corespine-0.0.1/src/corespine/__init__.py +69 -0
- corespine-0.0.1/src/corespine/config/__init__.py +1 -0
- corespine-0.0.1/src/corespine/config/env.py +77 -0
- corespine-0.0.1/src/corespine/conformance/__init__.py +1 -0
- corespine-0.0.1/src/corespine/conformance/harness.py +123 -0
- corespine-0.0.1/src/corespine/errors.py +81 -0
- corespine-0.0.1/src/corespine/llm/__init__.py +1 -0
- corespine-0.0.1/src/corespine/llm/provider.py +120 -0
- corespine-0.0.1/src/corespine/observability/__init__.py +1 -0
- corespine-0.0.1/src/corespine/observability/trace.py +90 -0
- corespine-0.0.1/src/corespine/py.typed +0 -0
- corespine-0.0.1/src/corespine/queue/__init__.py +1 -0
- corespine-0.0.1/src/corespine/queue/task_queue.py +93 -0
- corespine-0.0.1/src/corespine/seam/__init__.py +1 -0
- corespine-0.0.1/src/corespine/seam/registry.py +112 -0
- corespine-0.0.1/tests/test_conformance_bridge.py +126 -0
- corespine-0.0.1/tests/test_conformance_harness.py +121 -0
- corespine-0.0.1/tests/test_env.py +72 -0
- corespine-0.0.1/tests/test_errors.py +99 -0
- corespine-0.0.1/tests/test_provider.py +50 -0
- corespine-0.0.1/tests/test_registry.py +76 -0
- corespine-0.0.1/tests/test_task_queue.py +66 -0
- corespine-0.0.1/tests/test_trace.py +48 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up uv (+ Python ${{ matrix.python-version }})
|
|
18
|
+
uses: astral-sh/setup-uv@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
|
|
22
|
+
- name: Install (editable + dev)
|
|
23
|
+
run: uv pip install --system -e ".[dev]"
|
|
24
|
+
|
|
25
|
+
- name: Lint (ruff)
|
|
26
|
+
run: ruff check src tests
|
|
27
|
+
|
|
28
|
+
- name: Typecheck (mypy --strict)
|
|
29
|
+
run: mypy
|
|
30
|
+
|
|
31
|
+
- name: Test (pytest)
|
|
32
|
+
run: pytest -q
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# CLAUDE.md — corespine(宪章)
|
|
2
|
+
|
|
3
|
+
Spine 家族的 AI / 人类协作契约。先读家族 `../README.md` 与
|
|
4
|
+
`../docs/adr/0001-spine-family-boundaries-and-dependency-direction.md`,本文件是 corespine 的操作指南。
|
|
5
|
+
|
|
6
|
+
## 这是什么
|
|
7
|
+
|
|
8
|
+
**corespine —— Spine 家族的「薄」共享核**(ADR 0001 D3/D5)。只装 **domain-neutral 的底层原语**:
|
|
9
|
+
那些**既不属于 RAG、也不属于 agent** 的稳定地基。它被 `ragspine` / `agentspine` 等兄弟包各自依赖,
|
|
10
|
+
但**不**反向依赖任何一个,也**不**含任何它们的领域概念。
|
|
11
|
+
|
|
12
|
+
## 宪章(不可违背)
|
|
13
|
+
|
|
14
|
+
- **刻意地薄。** corespine 只放"极小且明显稳定"的原语。新增一块**必须先有证据**——
|
|
15
|
+
rule of three:当**两个以上**真实消费者都被证明在重复同一块稳定面,才把**恰好那块**提上来,
|
|
16
|
+
并记一条新 ADR。**痛了再抽,带着证据抽。** 不预先造大而全的框架。
|
|
17
|
+
- **零领域泄漏。** 任何 RAG-特定(chunking / retrieval / anti-fabrication / provenance)或
|
|
18
|
+
agent-特定(MCP / A2A / 工具循环 / 编排)的代码**一律不准进**。判据:这段代码换到一个完全
|
|
19
|
+
不同的后端引擎里还成立吗?不成立就不属于 corespine。
|
|
20
|
+
- **离线可跑、import-clean、零重依赖默认路径。** 核心只用标准库;真实后端(SDK / 数据库 / Redis)
|
|
21
|
+
经**可选 extra 延迟 import**,由各 app 在自己的缝里接,corespine 的 `dependencies` 永远为空。
|
|
22
|
+
- **机制,不是保证。** corespine 只提供**机制**(conformance 基座、缝注册表、隐私 trace 形状);
|
|
23
|
+
具体**不变量由各 app 自己绑**(ADR 0001 D6)。这里不准出现任何具体业务不变量。
|
|
24
|
+
|
|
25
|
+
## 缝的元模式(家族统一)
|
|
26
|
+
|
|
27
|
+
每条缝都长一个样:**Protocol + 离线确定性默认 + `make_*` / Registry 工厂 + 参数化 conformance**。
|
|
28
|
+
core 只 import Protocol,**绝不** import 任何 SDK。
|
|
29
|
+
|
|
30
|
+
## 模块地图(按文件夹定位)
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
src/corespine/
|
|
34
|
+
seam/registry.py Registry(name->factory) + make(spec) + entry-point 发现 + lazy_extra_import
|
|
35
|
+
observability/trace.py TraceSink 协议 + InProcessPrivacyTraceSink(只记 code/计数/耗时,拒绝正文)
|
|
36
|
+
llm/provider.py LLMProvider 协议 + 离线确定性 MockProvider
|
|
37
|
+
config/env.py load_from_env:PREFIX_* env -> frozen dataclass(范式同 ragspine from_env)
|
|
38
|
+
queue/task_queue.py TaskQueue 协议 + 同步内联 FakeQueue 默认
|
|
39
|
+
conformance/harness.py ConformanceSuite × InvariantPack:实现 × 不变量 笛卡尔积(机制,无具体不变量)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 跑(始终从包根)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv venv .venv
|
|
46
|
+
VIRTUAL_ENV="$(pwd)/.venv" uv pip install -e ".[dev]"
|
|
47
|
+
.venv/bin/python -m pytest -q # 期望 GREEN
|
|
48
|
+
.venv/bin/python -c "import corespine" # 期望 import-clean
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 约定
|
|
52
|
+
|
|
53
|
+
- Python **3.10+** 类型注解;import 顺序 **stdlib > 三方 > 本地**;简体中文 docstring/注释,匹配家族风格。
|
|
54
|
+
- **TDD**——测试即规格;**最小改动**——只改需求要求的部分。
|
|
55
|
+
- **深层、按领域分组**的布局:文件路径先定位职责,再读文件名。
|
corespine-0.0.1/Makefile
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# corespine — 常用开发 / CI 命令。
|
|
2
|
+
#
|
|
3
|
+
# `make` 或 `make help` 列出全部目标。目标默认使用项目 venv(.venv);
|
|
4
|
+
# 覆盖解释器: make test PYTHON=python3.12
|
|
5
|
+
#
|
|
6
|
+
# 始终从包根运行。
|
|
7
|
+
|
|
8
|
+
.DEFAULT_GOAL := help
|
|
9
|
+
PYTHON ?= .venv/bin/python
|
|
10
|
+
VENV ?= .venv
|
|
11
|
+
|
|
12
|
+
# ---- 环境 ----------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
.PHONY: install
|
|
15
|
+
install: ## 建 .venv(uv)并以 dev extra 可编辑安装(常规开发装法)
|
|
16
|
+
uv venv $(VENV)
|
|
17
|
+
VIRTUAL_ENV="$(CURDIR)/$(VENV)" uv pip install -e ".[dev]"
|
|
18
|
+
|
|
19
|
+
# ---- 质量门 --------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
.PHONY: ci
|
|
22
|
+
ci: lint typecheck test ## 本地 CI 门:lint + 类型检查 + 测试(与 GitHub Actions 同形)
|
|
23
|
+
|
|
24
|
+
.PHONY: test
|
|
25
|
+
test: ## 跑测试套件
|
|
26
|
+
$(PYTHON) -m pytest -q
|
|
27
|
+
|
|
28
|
+
.PHONY: lint
|
|
29
|
+
lint: ## ruff 静态检查(风格 + import 顺序 + 死代码)
|
|
30
|
+
$(PYTHON) -m ruff check src tests
|
|
31
|
+
|
|
32
|
+
.PHONY: typecheck
|
|
33
|
+
typecheck: ## mypy --strict 类型检查(出货代码 src)
|
|
34
|
+
$(PYTHON) -m mypy
|
|
35
|
+
|
|
36
|
+
.PHONY: fmt
|
|
37
|
+
fmt: ## ruff 自动格式化(src / tests / examples)
|
|
38
|
+
$(PYTHON) -m ruff format src tests examples
|
|
39
|
+
|
|
40
|
+
# ---- demo ----------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
.PHONY: demo
|
|
43
|
+
demo: ## 跑离线快速上手示例(期望末行 "corespine OK")
|
|
44
|
+
$(PYTHON) examples/quickstart.py
|
|
45
|
+
|
|
46
|
+
# ---- meta ----------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
.PHONY: help
|
|
49
|
+
help: ## 列出可用目标
|
|
50
|
+
@grep -hE '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) \
|
|
51
|
+
| sort \
|
|
52
|
+
| awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}'
|
corespine-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: corespine
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Spine 家族的薄共享核:domain-neutral 底层原语(缝注册表 / observability / LLM 缝 / config / queue / conformance 基座)。
|
|
5
|
+
Author: lin han
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Keywords: conformance,framework-free,llm-provider,observability,offline,registry,seam,task-queue
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# corespine
|
|
25
|
+
|
|
26
|
+
Spine 家族的**薄共享核**(见 [ADR 0001](../docs/adr/0001-spine-family-boundaries-and-dependency-direction.md))。
|
|
27
|
+
只装 **domain-neutral 的底层原语**——既不属于 RAG 也不属于 agent 的稳定地基,被 `ragspine` /
|
|
28
|
+
`agentspine` 兄弟包各自依赖,**不**含任何它们的领域概念。
|
|
29
|
+
|
|
30
|
+
> 刻意地薄。按证据(rule of three)增长,不预先造框架。详见 [`CLAUDE.md`](CLAUDE.md) 宪章。
|
|
31
|
+
|
|
32
|
+
## 缝的元模式
|
|
33
|
+
|
|
34
|
+
每条缝都长一个样,核心 import 零 SDK、离线可跑:
|
|
35
|
+
|
|
36
|
+
**Protocol + 离线确定性默认 + `Registry` / `make_*` 工厂 + 参数化 conformance**
|
|
37
|
+
|
|
38
|
+
## 里面有什么
|
|
39
|
+
|
|
40
|
+
| 模块 | 原语 |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `seam/registry.py` | `Registry`:name→factory 解析(大小写/留白/连字符不敏感)+ entry-point 自动发现(`corespine.<seam>` group)+ 未知 spec 列清可用名 + `lazy_extra_import`(缺 extra 给"pip install …"友好提示) |
|
|
43
|
+
| `observability/trace.py` | `TraceSink` 协议 + `InProcessPrivacyTraceSink`:只记 code/计数/耗时,**拒绝**任何携带正文(answer/value/text/content…)的载荷 |
|
|
44
|
+
| `llm/provider.py` | `LLMProvider` 协议 + 离线确定性 `MockProvider`(零网络、零 key、可复现) |
|
|
45
|
+
| `config/env.py` | `load_from_env`:把 `PREFIX_*` 环境变量读进一个 frozen dataclass(范式同 ragspine `from_env`) |
|
|
46
|
+
| `queue/task_queue.py` | `TaskQueue` 协议 + `FakeQueue`:同步内联执行 + 记录,离线/测试用 |
|
|
47
|
+
| `conformance/harness.py` | `ConformanceSuite` × `InvariantPack`:把"实现 × 不变量"绑成笛卡尔积逐格执行(**机制**,具体不变量由各 app 自己绑) |
|
|
48
|
+
|
|
49
|
+
## 本地开发(始终从包根)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv venv .venv
|
|
53
|
+
VIRTUAL_ENV="$(pwd)/.venv" uv pip install -e ".[dev]"
|
|
54
|
+
.venv/bin/python -m pytest -q
|
|
55
|
+
.venv/bin/python -c "import corespine"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 30 秒上手
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from corespine import Registry, MockProvider, InProcessPrivacyTraceSink, FakeQueue
|
|
62
|
+
|
|
63
|
+
# 缝:一个 spec 选实现(大小写/留白不敏感;找不到列清可用名;还能 entry-point 自动发现)
|
|
64
|
+
reg: Registry = Registry("llm")
|
|
65
|
+
reg.register("mock", lambda **kw: MockProvider(**kw))
|
|
66
|
+
provider = reg.make(" MOCK ")
|
|
67
|
+
# OpenAI chat-completions 规范:messages 进,OpenAI 形状的 ChatCompletion 出(确定性可复现)
|
|
68
|
+
out = provider.chat([{"role": "user", "content": "hello"}])
|
|
69
|
+
print(out.choices[0].message.content)
|
|
70
|
+
|
|
71
|
+
# 隐私 trace:只记元数据;塞正文会被直接拒绝(raise TraceError)
|
|
72
|
+
sink = InProcessPrivacyTraceSink()
|
|
73
|
+
sink.emit("retrieve", count=3, took_ms=12)
|
|
74
|
+
|
|
75
|
+
# 任务队列:同步内联执行
|
|
76
|
+
q = FakeQueue()
|
|
77
|
+
jid = q.enqueue(lambda p: {"doubled": p["n"] * 2}, {"n": 21})
|
|
78
|
+
print(q.get(jid).result) # {'doubled': 42}
|
|
79
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# corespine
|
|
2
|
+
|
|
3
|
+
Spine 家族的**薄共享核**(见 [ADR 0001](../docs/adr/0001-spine-family-boundaries-and-dependency-direction.md))。
|
|
4
|
+
只装 **domain-neutral 的底层原语**——既不属于 RAG 也不属于 agent 的稳定地基,被 `ragspine` /
|
|
5
|
+
`agentspine` 兄弟包各自依赖,**不**含任何它们的领域概念。
|
|
6
|
+
|
|
7
|
+
> 刻意地薄。按证据(rule of three)增长,不预先造框架。详见 [`CLAUDE.md`](CLAUDE.md) 宪章。
|
|
8
|
+
|
|
9
|
+
## 缝的元模式
|
|
10
|
+
|
|
11
|
+
每条缝都长一个样,核心 import 零 SDK、离线可跑:
|
|
12
|
+
|
|
13
|
+
**Protocol + 离线确定性默认 + `Registry` / `make_*` 工厂 + 参数化 conformance**
|
|
14
|
+
|
|
15
|
+
## 里面有什么
|
|
16
|
+
|
|
17
|
+
| 模块 | 原语 |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `seam/registry.py` | `Registry`:name→factory 解析(大小写/留白/连字符不敏感)+ entry-point 自动发现(`corespine.<seam>` group)+ 未知 spec 列清可用名 + `lazy_extra_import`(缺 extra 给"pip install …"友好提示) |
|
|
20
|
+
| `observability/trace.py` | `TraceSink` 协议 + `InProcessPrivacyTraceSink`:只记 code/计数/耗时,**拒绝**任何携带正文(answer/value/text/content…)的载荷 |
|
|
21
|
+
| `llm/provider.py` | `LLMProvider` 协议 + 离线确定性 `MockProvider`(零网络、零 key、可复现) |
|
|
22
|
+
| `config/env.py` | `load_from_env`:把 `PREFIX_*` 环境变量读进一个 frozen dataclass(范式同 ragspine `from_env`) |
|
|
23
|
+
| `queue/task_queue.py` | `TaskQueue` 协议 + `FakeQueue`:同步内联执行 + 记录,离线/测试用 |
|
|
24
|
+
| `conformance/harness.py` | `ConformanceSuite` × `InvariantPack`:把"实现 × 不变量"绑成笛卡尔积逐格执行(**机制**,具体不变量由各 app 自己绑) |
|
|
25
|
+
|
|
26
|
+
## 本地开发(始终从包根)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv venv .venv
|
|
30
|
+
VIRTUAL_ENV="$(pwd)/.venv" uv pip install -e ".[dev]"
|
|
31
|
+
.venv/bin/python -m pytest -q
|
|
32
|
+
.venv/bin/python -c "import corespine"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 30 秒上手
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from corespine import Registry, MockProvider, InProcessPrivacyTraceSink, FakeQueue
|
|
39
|
+
|
|
40
|
+
# 缝:一个 spec 选实现(大小写/留白不敏感;找不到列清可用名;还能 entry-point 自动发现)
|
|
41
|
+
reg: Registry = Registry("llm")
|
|
42
|
+
reg.register("mock", lambda **kw: MockProvider(**kw))
|
|
43
|
+
provider = reg.make(" MOCK ")
|
|
44
|
+
# OpenAI chat-completions 规范:messages 进,OpenAI 形状的 ChatCompletion 出(确定性可复现)
|
|
45
|
+
out = provider.chat([{"role": "user", "content": "hello"}])
|
|
46
|
+
print(out.choices[0].message.content)
|
|
47
|
+
|
|
48
|
+
# 隐私 trace:只记元数据;塞正文会被直接拒绝(raise TraceError)
|
|
49
|
+
sink = InProcessPrivacyTraceSink()
|
|
50
|
+
sink.emit("retrieve", count=3, took_ms=12)
|
|
51
|
+
|
|
52
|
+
# 任务队列:同步内联执行
|
|
53
|
+
q = FakeQueue()
|
|
54
|
+
jid = q.enqueue(lambda p: {"doubled": p["n"] * 2}, {"n": 21})
|
|
55
|
+
print(q.get(jid).result) # {'doubled': 42}
|
|
56
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# corespine 可复现测试镜像 —— 多阶段、基于 uv。
|
|
2
|
+
#
|
|
3
|
+
# 一键容器:`docker build` 装好包 + dev,`docker run` 默认跑整套 pytest 自检。
|
|
4
|
+
# 构建 / 运行命令见 deploy/README.md(构建上下文为仓库根)。
|
|
5
|
+
|
|
6
|
+
# ---- builder:在独立 venv 里装入包 + dev ----------------------------------------------
|
|
7
|
+
FROM python:3.12-slim AS builder
|
|
8
|
+
|
|
9
|
+
# 从官方镜像拷入 uv 静态二进制(免装 pip,构建可复现)。
|
|
10
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
|
11
|
+
|
|
12
|
+
ENV UV_LINK_MODE=copy \
|
|
13
|
+
VIRTUAL_ENV=/opt/venv \
|
|
14
|
+
PATH="/opt/venv/bin:$PATH"
|
|
15
|
+
|
|
16
|
+
WORKDIR /app
|
|
17
|
+
|
|
18
|
+
# 与宿主隔离的独立 venv。
|
|
19
|
+
RUN uv venv /opt/venv
|
|
20
|
+
|
|
21
|
+
# 先拷打包元数据 + 源码(可编辑安装需源码在场),再装 —— 与 `make install` 同形。
|
|
22
|
+
COPY pyproject.toml README.md ./
|
|
23
|
+
COPY src ./src
|
|
24
|
+
RUN uv pip install -e ".[dev]"
|
|
25
|
+
|
|
26
|
+
# ---- runtime:只带 venv + 跑测试 / demo 所需的源码 ------------------------------------
|
|
27
|
+
FROM python:3.12-slim AS runtime
|
|
28
|
+
|
|
29
|
+
ENV VIRTUAL_ENV=/opt/venv \
|
|
30
|
+
PATH="/opt/venv/bin:$PATH" \
|
|
31
|
+
PYTHONDONTWRITEBYTECODE=1 \
|
|
32
|
+
PYTHONUNBUFFERED=1
|
|
33
|
+
|
|
34
|
+
WORKDIR /app
|
|
35
|
+
|
|
36
|
+
COPY --from=builder /opt/venv /opt/venv
|
|
37
|
+
# 可编辑安装的 .pth 指向 /app/src,故运行阶段须保持同一绝对路径。
|
|
38
|
+
COPY pyproject.toml README.md ./
|
|
39
|
+
COPY src ./src
|
|
40
|
+
COPY tests ./tests
|
|
41
|
+
COPY examples ./examples
|
|
42
|
+
|
|
43
|
+
# 默认 CMD:跑测试套件(一键自检)。
|
|
44
|
+
# 改跑离线 demo:`docker run --rm corespine-test python examples/quickstart.py`
|
|
45
|
+
CMD ["python", "-m", "pytest", "-q"]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# deploy —— 一键测试容器
|
|
2
|
+
|
|
3
|
+
一个多阶段、基于 uv 的可复现镜像:`docker build` 装好 `corespine` + dev 依赖,
|
|
4
|
+
`docker run` 默认跑整套 `pytest` 自检。无需本机装 Python / uv。
|
|
5
|
+
|
|
6
|
+
> 构建上下文是**仓库根**(`Dockerfile` 用显式 `COPY` 取 `pyproject.toml` / `README.md` /
|
|
7
|
+
> `src` / `tests` / `examples`),`-f` 指到本目录的 Dockerfile。
|
|
8
|
+
|
|
9
|
+
## 构建
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 从仓库根运行
|
|
13
|
+
docker build -f deploy/Dockerfile -t corespine-test .
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## 运行
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 默认 CMD:跑整套测试(一键自检)
|
|
20
|
+
docker run --rm corespine-test
|
|
21
|
+
|
|
22
|
+
# 改跑离线快速上手 demo(期望末行 "corespine OK")
|
|
23
|
+
docker run --rm corespine-test python examples/quickstart.py
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`docker build` + `docker run` 两条命令即完成"构建镜像 → 跑通测试套件"的一键闭环。
|
|
27
|
+
|
|
28
|
+
## 说明
|
|
29
|
+
|
|
30
|
+
- **多阶段**:builder 阶段在 `/opt/venv` 建独立 venv 并 `uv pip install -e ".[dev]"`;
|
|
31
|
+
runtime 阶段只拷该 venv 与跑测试所需源码,镜像更小。
|
|
32
|
+
- 可编辑安装的链接指向 `/app/src`,故两个阶段都把源码放在同一绝对路径 `/app/src`。
|
|
33
|
+
- `.dockerignore` 收窄上下文(加速构建);因 Dockerfile 用显式 `COPY`,镜像内容不依赖它。
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# 扩展 corespine(给消费者 / 第三方)
|
|
2
|
+
|
|
3
|
+
> 本文是**操作指南**:第三方包如何在不改 corespine 一行代码的前提下,给某条缝补一个实现、
|
|
4
|
+
> 接一个真实后端、绑自己的不变量。机制源码见 [`seam/registry.py`](../src/corespine/seam/registry.py)
|
|
5
|
+
> 与 [`conformance/harness.py`](../src/corespine/conformance/harness.py);宪章见
|
|
6
|
+
> [`../CLAUDE.md`](../CLAUDE.md),增长判据见 [`roadmap.md`](roadmap.md),现状见 [`prd.md`](prd.md)。
|
|
7
|
+
|
|
8
|
+
corespine 只提供**机制**,扩展点全在缝上,三种方式互不耦合:
|
|
9
|
+
|
|
10
|
+
| 扩展方式 | 解决什么 | 关键 API | 章节 |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| entry-point 扩展一条缝 | 第三方装包即被 `make` 发现,无需改核心 | `Registry.make` / group `corespine.<seam>` | [一](#一用-entry-point-扩展一条缝) |
|
|
13
|
+
| 可选 extra + 延迟 import | 真实后端 SDK 只在选用时才 import,缺依赖给友好提示 | `lazy_extra_import` / `[project.optional-dependencies]` | [二](#二可选-extra-命名约定) |
|
|
14
|
+
| 给一条缝绑 conformance 不变量 | app 用自己的不变量逮住坏实现 | `InvariantPack` / `ConformanceSuite` | [三](#三给一条缝绑-conformance-不变量) |
|
|
15
|
+
|
|
16
|
+
## 一、用 entry-point 扩展一条缝
|
|
17
|
+
|
|
18
|
+
每条缝是一个 `Registry("<seam>")` 实例;`make(spec)` 找不到内置名时,**回落到
|
|
19
|
+
`importlib.metadata` 的 entry-point 自动发现**,group 命名固定为 **`corespine.<seam>`**。
|
|
20
|
+
于是第三方只需在自己的 `pyproject.toml` 声明一个 entry point,**装包即扩展**,corespine
|
|
21
|
+
核心一行不改。
|
|
22
|
+
|
|
23
|
+
### 第三方包的 pyproject 片段
|
|
24
|
+
|
|
25
|
+
假设某条缝叫 `vector_store`,你的包 `myadapter` 想注册一个名为 `pgvector` 的实现:
|
|
26
|
+
|
|
27
|
+
```toml
|
|
28
|
+
# myadapter/pyproject.toml
|
|
29
|
+
[project.entry-points."corespine.vector_store"]
|
|
30
|
+
# 名字(左) -> "模块:工厂可调用对象"(右);工厂签名为 (**kwargs) -> 实现实例
|
|
31
|
+
pgvector = "myadapter.pg:make_pgvector"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# myadapter/pg.py
|
|
36
|
+
def make_pgvector(**kwargs):
|
|
37
|
+
"""工厂:返回一个实现该缝 Protocol 的实例(此处省略真实细节)。"""
|
|
38
|
+
return PgVectorStore(**kwargs)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 装包后的调用示例
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from corespine import Registry
|
|
45
|
+
|
|
46
|
+
# 同一条缝名 "vector_store" 必须与 entry-point group 后缀一致。
|
|
47
|
+
registry: Registry = Registry("vector_store")
|
|
48
|
+
|
|
49
|
+
# 内置名找不到 → 自动扫 group "corespine.vector_store" 发现 myadapter 的 pgvector。
|
|
50
|
+
store = registry.make("pgvector", dsn="postgresql://...")
|
|
51
|
+
|
|
52
|
+
# 解析大小写 / 连字符 / 留白不敏感:"PG-Vector" / " pgvector " 都解析到同一项。
|
|
53
|
+
registry.names() # 列出【内置 + entry-point 发现】的全部可用名(字典序、去重)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
要点:
|
|
57
|
+
|
|
58
|
+
- group 名**必须**是 `corespine.<seam>`,`<seam>` 与 `Registry("<seam>")` 的入参完全一致;
|
|
59
|
+
- **内置注册优先于 entry-point**:同名时内置胜出(便于在测试里覆盖);
|
|
60
|
+
- 发现是**延迟**的:只在 `make` / `names` 解析时才扫,不在 import 期付出代价;
|
|
61
|
+
- 未知 spec 抛 `ValueError`,并**列清当前全部可用名**(绝不让人猜)。
|
|
62
|
+
|
|
63
|
+
## 二、可选 extra 命名约定
|
|
64
|
+
|
|
65
|
+
薄核宪章(ADR 0001 D5):核心 `dependencies` **永远为空**,默认路径零重依赖。真实后端的
|
|
66
|
+
SDK(Redis / OpenAI / …)由各 app 在自己的缝里经**可选 extra** 声明,并用
|
|
67
|
+
[`lazy_extra_import`](../src/corespine/seam/registry.py) **延迟 import** —— 选用该 adapter
|
|
68
|
+
时才 import,没装就给出可直接照做的安装指引。
|
|
69
|
+
|
|
70
|
+
### extra 命名约定
|
|
71
|
+
|
|
72
|
+
| 约定 | 示例 | 说明 |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| 一个真实后端 → 一个 extra,**以后端命名** | `[redis]` / `[openai]` | 名字即"装哪个后端",直观可猜 |
|
|
75
|
+
| extra 内只放该后端的 SDK | `redis = ["redis>=5"]` | 不夹带无关依赖,装得最小 |
|
|
76
|
+
| `lazy_extra_import` 的 `extra=` 与之同名 | `extra="redis"` | 缺依赖时提示 `pip install <pkg>[redis]` 即可修 |
|
|
77
|
+
|
|
78
|
+
```toml
|
|
79
|
+
# 你的包 pyproject.toml:真实后端依赖声明为可选 extra(以后端命名)
|
|
80
|
+
[project.optional-dependencies]
|
|
81
|
+
redis = ["redis>=5"]
|
|
82
|
+
openai = ["openai>=1.0"]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### lazy_extra_import 用法
|
|
86
|
+
|
|
87
|
+
`lazy_extra_import(module, *, pkg, extra)` 把裸 `ImportError` 翻译成
|
|
88
|
+
**"pip install <pkg>[<extra>]"** 的友好提示:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from corespine import lazy_extra_import
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def make_redis_queue(**kwargs):
|
|
95
|
+
# 只在真正构造该 adapter 时才 import;没装 redis 不影响核心离线默认路径。
|
|
96
|
+
redis = lazy_extra_import("redis", pkg="myadapter", extra="redis")
|
|
97
|
+
client = redis.Redis(**kwargs)
|
|
98
|
+
return RedisQueue(client)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
未装 `redis` 时调用 `make_redis_queue(...)` 会抛:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
ImportError: 缺少可选依赖 'redis':请先 `pip install myadapter[redis]` 再重试。
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
—— 而不是让调用方对着 `ModuleNotFoundError: No module named 'redis'` 自己猜该装哪个 extra。
|
|
108
|
+
|
|
109
|
+
## 三、给一条缝绑 conformance 不变量
|
|
110
|
+
|
|
111
|
+
corespine 的 conformance 是**机制,非保证**:harness 只负责"跑 实现 × 不变量 的笛卡尔积 +
|
|
112
|
+
报告哪个格子坏了",**具体不变量由各 app 自己绑**(ADR 0001 D6)。app 把自己的
|
|
113
|
+
[`InvariantPack`](../src/corespine/conformance/harness.py) 喂进
|
|
114
|
+
[`ConformanceSuite`](../src/corespine/conformance/harness.py) 即可。
|
|
115
|
+
|
|
116
|
+
最小骨架(完整可跑范例见 [`../examples/conformance_usage.py`](../examples/conformance_usage.py)):
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from corespine import ConformanceSuite, InvariantPack
|
|
120
|
+
|
|
121
|
+
# 1) 实现注册表:名字 -> 无参工厂(每格各新建实例,杜绝实现间状态串味)
|
|
122
|
+
impls = {"counter": Counter, "broken": BrokenCounter}
|
|
123
|
+
|
|
124
|
+
# 2) app 自己的不变量包(corespine 核心不含任何具体不变量)
|
|
125
|
+
pack = (
|
|
126
|
+
InvariantPack("counter-contract")
|
|
127
|
+
.add("first-add-returns-n", lambda c: assert_eq(c.add(3), 3))
|
|
128
|
+
.add("accumulates", lambda c: assert_eq((c.add(2), c.add(5)), (2, 7)))
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# 3) 绑成笛卡尔积,逐格跑
|
|
132
|
+
suite = ConformanceSuite(impls, pack)
|
|
133
|
+
for impl, invariant in suite.cases():
|
|
134
|
+
suite.check(impl, invariant) # 失败即抛,定位到具体格子
|
|
135
|
+
# 或:suite.run() 收集全部结果(不抛);suite.passed() 便捷判全过
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
要点:
|
|
139
|
+
|
|
140
|
+
- 不变量 = `(实现实例) -> None`,**通过则正常返回、违反则抛异常**;只验外部可观测行为;
|
|
141
|
+
- 每个格子都**新建实例**(工厂无参),杜绝实现间状态串味;
|
|
142
|
+
- `cases()` 返回 `(实现名, 不变量名)` 列表,可直接喂 `pytest.mark.parametrize`;
|
|
143
|
+
`ids()` 给对齐的可读 id(形如 `impl/invariant`);
|
|
144
|
+
- 这正是"敢放手让第三方填广度、却让脊柱不变量烂不掉"的落点:没过 conformance 的实现直接
|
|
145
|
+
CI 红,而非生产事故。
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# corespine PRD(现状与待做)
|
|
2
|
+
|
|
3
|
+
> 本文记**现状清单 + 可执行的待做 backlog**;**方向与增长判据**见
|
|
4
|
+
> [`roadmap.md`](roadmap.md),宪章见 [`../CLAUDE.md`](../CLAUDE.md)。
|
|
5
|
+
> corespine 刻意地薄:多数"待做"被**有意推迟**,需满足 rule of three 才动手(见 roadmap 五问)。
|
|
6
|
+
|
|
7
|
+
## 现状(已完成)
|
|
8
|
+
|
|
9
|
+
六条缝 + errors 缝均已落地,各带 Protocol + 离线确定性默认 + 工厂 + 参数化 conformance;
|
|
10
|
+
`make ci` 全绿(ruff + pytest)、import-clean、核心 `dependencies` 为空。
|
|
11
|
+
|
|
12
|
+
| 缝 | 公开面 | 离线默认 | 状态 |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| seam/registry | `Registry` / `lazy_extra_import` | 内置注册 + entry-point 发现 | ✅ |
|
|
15
|
+
| observability/trace | `TraceSink` / `TraceEvent` / `TraceError` / `FORBIDDEN_KEYS` | `InProcessPrivacyTraceSink`(拒正文) | ✅ |
|
|
16
|
+
| llm/provider | `LLMProvider.chat`(OpenAI chat-completions 规范:messages+tools 进、`ChatCompletion` 出) | `MockProvider`(确定性,OpenAI 形状) | ✅ |
|
|
17
|
+
| config/env | `load_from_env` / `env_key` | env→frozen dataclass(含 `X\|None`) | ✅ |
|
|
18
|
+
| queue/task_queue | `TaskQueue` / `JobStatus` | `FakeQueue`(同步内联) | ✅ |
|
|
19
|
+
| conformance/harness | `ConformanceSuite` / `InvariantPack` / `CaseResult` + `parametrize_kwargs` | 实现×不变量笛卡尔积 | ✅ |
|
|
20
|
+
| errors | `CorespineError` / `error_to_dict` / `ConfigError` / `SeamError` | 统一基类 + 任意异常归一 dict | ✅ |
|
|
21
|
+
|
|
22
|
+
测试覆盖含:解析归一、entry-point 发现、**内置优先于 entry-point**、未知名报错、缺 extra 友好提示、
|
|
23
|
+
trace 拒正文、Mock 确定性、env 类型转换 / 可选 / 缺失校验、queue 终态 / 幂等、conformance 笛卡尔积、
|
|
24
|
+
errors 基类 code / retryable / 归一 dict。`TraceError` 已改继承 `CorespineError`(code=`trace.forbidden`),
|
|
25
|
+
家族异常统一基类。
|
|
26
|
+
|
|
27
|
+
## 待做 backlog
|
|
28
|
+
|
|
29
|
+
### A. 可立即做(纯增量,不需新证据,charter-safe)
|
|
30
|
+
|
|
31
|
+
A 类已全部落地(✅),示例 / 文档见 `examples/` 与 `docs/`:
|
|
32
|
+
|
|
33
|
+
| # | 项 | 内容 | 落点 | 状态 |
|
|
34
|
+
|---|---|---|---|---|
|
|
35
|
+
| A1 | entry-point 端到端示例 | 一个"第三方装包即扩展"的最小可跑 demo + 文档(`pyproject` entry-points → `Registry.make` 发现) | example / 文档 | ✅ |
|
|
36
|
+
| A2 | 可选 extra 范式文档 | 把 `[redis]`/`[openai]` 等 extra 命名约定 + `lazy_extra_import` 用法写成一页 | 文档 | ✅ |
|
|
37
|
+
| A3 | conformance 使用范例 | 展示 app 如何用 `InvariantPack` 给某缝绑自己的不变量(机制示范,不含具体业务不变量) | example / 文档 | ✅ |
|
|
38
|
+
|
|
39
|
+
### B. 待证据(rule of three:≥2 个真实消费者重复同一稳定面才动)
|
|
40
|
+
|
|
41
|
+
> **watch list(单一消费者证据,暂不实现):** `import_string`(字符串→对象解析)、
|
|
42
|
+
> PEP 562 惰性子模块、并行 fan-out 执行器、资源限额——目前仅一个消费者出现过,证据不足
|
|
43
|
+
> rule of three,只观察不动手。方向口径见 [`roadmap.md`](roadmap.md) 增长五问。
|
|
44
|
+
|
|
45
|
+
| # | 项 | 触发条件 | 落点 | 状态 |
|
|
46
|
+
|---|---|---|---|---|
|
|
47
|
+
| B1 | trace 真实 sink(OTel 等) | ≥2 app 需要导出 trace | 可选 extra / contrib,**不进核心默认路径** | 待证据 |
|
|
48
|
+
| B2 | llm streaming / 批量 / token 计量 | ≥2 app 在缝上重复同一形状 | 先扩 Protocol(最小),实现走 extra | 待证据 |
|
|
49
|
+
| B3 | queue 真实后端 adapter(RQ/Celery)+ 重试/延迟 | ≥2 app 接同一类后端 | adapter 走 extra/contrib;协议扩不扩看证据 | 待证据 |
|
|
50
|
+
| B4 | conformance × pytest 集成助手 | ragspine + agentspine 都在重复 `cases()`→`parametrize` 胶水 | `harness.parametrize_kwargs`(纯标准库胶水,不把 pytest 引进核心) | ✅ |
|
|
51
|
+
| B5 | config 扩展转型(list / enum / 嵌套) | 出现真实通用配置项需要 | 核心(仅当确为通用) | 待证据 |
|
|
52
|
+
| B6 | 统一异常基类 `CorespineError` | 多个 app 需统一 `catch` corespine 错误 | 核心(`errors.py`:基类 + `error_to_dict` + `ConfigError`/`SeamError`) | ✅ |
|
|
53
|
+
| B7 | 稳定性契约(公开面冻结 / SemVer / deprecation) | 出现依赖其稳定性的外部消费者 | 流程 + 文档 | 待证据 |
|
|
54
|
+
|
|
55
|
+
### C. 家族前置(最重要,解锁 B 类证据)
|
|
56
|
+
|
|
57
|
+
| # | 项 | 说明 |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| C1 | ragspine / agentspine 真正依赖 corespine | rule-of-three 证据的**唯一来源**;在此之前 B 类多数不该动 |
|
|
60
|
+
| C2 | 跨包 conformance 验证 | 多 app 各绑不变量时,验证现有机制是否够用,反推增强 |
|
|
61
|
+
|
|
62
|
+
## 不做(护栏)
|
|
63
|
+
|
|
64
|
+
RAG-/agent-特定概念(chunking / retrieval / provenance;MCP / A2A / 工具循环 / 编排)、
|
|
65
|
+
预造框架、核心非空依赖、把具体业务不变量写进核心。详见 roadmap「明确不做」。
|
|
66
|
+
|
|
67
|
+
## 跟踪
|
|
68
|
+
|
|
69
|
+
- 每落一项 → 在家族 `docs/adr/` 记一条 ADR(编号接 0001),并回填本表状态。
|
|
70
|
+
- A 类可随时排期;B 类须先在 PR / ADR 里附上"≥2 消费者重复"的证据;C 类是 B 类的前置。
|