实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
271 lines
9.0 KiB
Python
271 lines
9.0 KiB
Python
"""终态费用事实与一次外发边界;使用内存事务替身,不访问数据库或模型。"""
|
||
|
||
import asyncio
|
||
import hashlib
|
||
from contextlib import contextmanager
|
||
from copy import deepcopy
|
||
from decimal import Decimal
|
||
from types import SimpleNamespace
|
||
|
||
import pytest
|
||
|
||
import muse.任务运行.调用结算 as 结算模块
|
||
from muse.任务运行.接口 import 模型结果, 模型请求
|
||
from muse.任务运行.模型 import 状态冲突
|
||
from muse.任务运行.调用治理 import 受控发送
|
||
from muse.任务运行.调用结算 import 结算调用, 调用结算请求
|
||
from muse.共享.调用身份 import 用途
|
||
|
||
|
||
class 游标:
|
||
def __init__(self, row):
|
||
self.row = row
|
||
|
||
def fetchone(self):
|
||
return self.row
|
||
|
||
|
||
class 账本:
|
||
用途 = 用途.评测
|
||
|
||
def __init__(self):
|
||
self.task = {
|
||
"author_id": "author",
|
||
"state": "cancelled",
|
||
"last_sequence": 8,
|
||
"accounting_revision": 2,
|
||
}
|
||
self.call = {
|
||
"call_id": "call",
|
||
"task_id": "task",
|
||
"attempt_id": "attempt",
|
||
"account_id": "account",
|
||
"state": "unknown",
|
||
"actual_amount": None,
|
||
}
|
||
self.receipts = {}
|
||
self.statements = []
|
||
|
||
@contextmanager
|
||
def 连接(self):
|
||
yield self
|
||
|
||
@contextmanager
|
||
def transaction(self):
|
||
原 = deepcopy((self.task, self.call, self.receipts))
|
||
try:
|
||
yield self
|
||
except Exception:
|
||
self.task, self.call, self.receipts = 原
|
||
raise
|
||
|
||
|
||
class 存储替身:
|
||
def __init__(self, db, purpose):
|
||
self.db = db
|
||
|
||
def 任务行(self, task, **kwargs):
|
||
assert task == "task"
|
||
return self.db.task
|
||
|
||
def 查询(self, sql, args=()):
|
||
self.db.statements.append(sql)
|
||
if sql.startswith("SELECT request_hash"):
|
||
return 游标(self.db.receipts.get(args[1]))
|
||
if "SELECT * FROM {s}.muse_budget_reservation" in sql:
|
||
return 游标(self.db.call if args[1] == "call" else None)
|
||
if sql.startswith("SELECT accounting_revision"):
|
||
return 游标(self.db.task)
|
||
if sql.startswith("INSERT INTO {s}.muse_call_settlement"):
|
||
self.db.receipts[args[1]] = {"request_hash": args[3], "receipt": args[4].obj}
|
||
return 游标(None)
|
||
raise AssertionError(sql)
|
||
|
||
|
||
class 预算替身:
|
||
def __init__(self, db, account):
|
||
self.db = db
|
||
|
||
def 结算于(self, conn, call, amount, *, 回执ID):
|
||
if self.db.call["state"] != "unknown":
|
||
raise 状态冲突("不能改写已结费用")
|
||
self.db.call.update(state="settled", actual_amount=amount, receipt_id=回执ID)
|
||
self.db.task["accounting_revision"] += 1
|
||
|
||
|
||
def 请求(**changes):
|
||
statement = "作者核对账单2026-09-17第12行,实际费用0.15美元。"
|
||
return 调用结算请求(
|
||
**{
|
||
"command_id": "settle-1",
|
||
"expected_revision": 2,
|
||
"amount": "0.15",
|
||
"currency": "USD",
|
||
"source_reference": "账单第12行",
|
||
"source_statement": statement,
|
||
"source_hash": hashlib.sha256(statement.encode()).hexdigest(),
|
||
**changes,
|
||
}
|
||
)
|
||
|
||
|
||
@pytest.mark.case_id("NC-O02-SETTLE-TERMINAL")
|
||
def test_取消任务结费幂等且不修改执行状态(monkeypatch):
|
||
db = 账本()
|
||
monkeypatch.setattr(结算模块, "任务存储", 存储替身)
|
||
monkeypatch.setattr(结算模块, "预算管理", 预算替身)
|
||
receipt = 结算调用(db, "task", "call", "author", 请求())
|
||
assert receipt["author_id"] == "author"
|
||
assert receipt["accounting_revision"] == 3 and receipt["execution_final_sequence"] == 8
|
||
assert db.task["state"] == "cancelled" and db.task["last_sequence"] == 8
|
||
assert db.call["actual_amount"] == Decimal("0.15")
|
||
assert 结算调用(db, "task", "call", "author", 请求()) == receipt
|
||
with pytest.raises(状态冲突, match="不能改变"):
|
||
结算调用(db, "task", "call", "author", 请求(amount="0.16"))
|
||
assert not any(
|
||
"muse_attempt" in s or "muse_step" in s or "muse_task_event" in s for s in db.statements
|
||
)
|
||
|
||
|
||
@pytest.mark.case_id("NC-O02-SETTLE-SCOPE")
|
||
def test_错作者错调用和旧账目版本不能结算(monkeypatch):
|
||
db = 账本()
|
||
monkeypatch.setattr(结算模块, "任务存储", 存储替身)
|
||
monkeypatch.setattr(结算模块, "预算管理", 预算替身)
|
||
for call, author, req in [
|
||
("call", "other", 请求()),
|
||
("other", "author", 请求()),
|
||
("call", "author", 请求(expected_revision=1)),
|
||
]:
|
||
with pytest.raises(状态冲突):
|
||
结算调用(db, "task", call, author, req)
|
||
assert db.call["state"] == "unknown" and not db.receipts
|
||
|
||
|
||
@pytest.mark.case_id("NC-O02-SETTLE-EVIDENCE")
|
||
def test_零费用缺明确确认和伪造来源哈希均拒绝():
|
||
with pytest.raises(状态冲突, match="零费用"):
|
||
请求(amount="0")
|
||
with pytest.raises(状态冲突, match="来源哈希"):
|
||
请求(source_hash="0" * 64)
|
||
with pytest.raises(状态冲突, match="提供方认证"):
|
||
请求(source_kind="provider_receipt")
|
||
assert 请求(amount="0", zero_cost_confirmed=True).amount == "0.000000"
|
||
|
||
|
||
class 发送预算:
|
||
def __init__(self, 允许=True):
|
||
self.允许 = 允许
|
||
self.events = []
|
||
|
||
def 预留(self, *args, **kwargs):
|
||
self.events.append("reserved")
|
||
|
||
def 标记已发送(self, *args, **kwargs):
|
||
kwargs["发送复检"](None)
|
||
self.events.append("sent")
|
||
return SimpleNamespace(允许外发=self.允许)
|
||
|
||
def 取消预留(self, *args):
|
||
self.events.append("release")
|
||
|
||
def 结算(self, call, amount, *, 回执ID):
|
||
self.events.append(("settle", amount, 回执ID))
|
||
return SimpleNamespace(状态="unknown" if amount is None else "settled")
|
||
|
||
|
||
class 宿主:
|
||
def __init__(self, failure=None):
|
||
self.calls = 0
|
||
self.failure = failure
|
||
|
||
def 准备(self, request):
|
||
return self
|
||
|
||
async def 调用(self):
|
||
self.calls += 1
|
||
if self.failure:
|
||
raise self.failure
|
||
return 模型结果("completed", '{"ok": true}', "model", None, "response")
|
||
|
||
|
||
def _模型请求():
|
||
return 模型请求("call", "provider", "model", "probe", "probe", {}, 64, 60, thinking="off")
|
||
|
||
|
||
@pytest.mark.case_id("NC-O02-GOVERNED-CANCEL")
|
||
def test_发送取消不推定免费():
|
||
budget, host = 发送预算(), 宿主(asyncio.CancelledError())
|
||
pricing = SimpleNamespace(金额=lambda _: Decimal("0.1"))
|
||
with pytest.raises(asyncio.CancelledError):
|
||
asyncio.run(
|
||
受控发送(budget, None, _模型请求(), "extractor", host, pricing, 发送复检=lambda _: None)
|
||
)
|
||
assert host.calls == 1 and budget.events[-1] == ("settle", None, "call")
|
||
|
||
|
||
@pytest.mark.case_id("NC-O02-GOVERNED-ONCE")
|
||
def test_已发送调用不会再次外发():
|
||
budget, host = 发送预算(False), 宿主()
|
||
with pytest.raises(状态冲突):
|
||
asyncio.run(
|
||
受控发送(budget, None, _模型请求(), "extractor", host, None, 发送复检=lambda _: None)
|
||
)
|
||
assert host.calls == 0
|
||
|
||
|
||
@pytest.mark.case_id("NC-O02-GOVERNED-PRICING")
|
||
def test_计价失败仍登记未知费用():
|
||
budget, host = 发送预算(), 宿主()
|
||
|
||
def broken(_):
|
||
raise ValueError("rate missing")
|
||
|
||
result, settlement = asyncio.run(
|
||
受控发送(
|
||
budget,
|
||
None,
|
||
_模型请求(),
|
||
"extractor",
|
||
host,
|
||
SimpleNamespace(金额=broken),
|
||
发送复检=lambda _: None,
|
||
)
|
||
)
|
||
assert result.响应ID == "response" and settlement.状态 == "unknown"
|
||
assert budget.events[-1] == ("settle", None, "response")
|
||
|
||
|
||
@pytest.mark.case_id("NC-O02-HTTP-CONTRACT")
|
||
def test_HTTP结算验证材料哈希并返回认证作者(monkeypatch):
|
||
from dataclasses import asdict
|
||
|
||
from fastapi import FastAPI
|
||
from fastapi.testclient import TestClient
|
||
|
||
from muse.任务运行.接口 import 任务服务
|
||
from muse.接入.http.作者会话 import 要求作者
|
||
from muse.接入.http.路由.任务运行 import 路由
|
||
|
||
db = 账本()
|
||
monkeypatch.setattr(结算模块, "任务存储", 存储替身)
|
||
monkeypatch.setattr(结算模块, "预算管理", 预算替身)
|
||
service = 任务服务(db, None)
|
||
app = FastAPI()
|
||
app.state.装配 = SimpleNamespace(任务运行=service)
|
||
app.dependency_overrides[要求作者] = lambda: "author"
|
||
app.include_router(路由)
|
||
with TestClient(app) as client:
|
||
body = asdict(请求())
|
||
response = client.post("/api/v1/tasks/task/calls/call/settlements", json=body)
|
||
assert response.status_code == 200, response.text
|
||
assert response.json()["author_id"] == "author"
|
||
assert response.json()["amount"] == "0.150000"
|
||
assert response.json()["state"] == "cancelled"
|
||
assert (
|
||
client.post(
|
||
"/api/v1/tasks/task/calls/call/settlements", json={**body, "author_id": "other"}
|
||
).status_code
|
||
== 422
|
||
)
|