点击停止后保留半段文字,并不是一个简单的字符串拼接问题。真正需要约束的是:哪个状态允许交付、谁能写入结果、迟到的事件还能不能改变结论。
这篇实现一个纯Python状态归约器:输入一个不可变状态和一个应用内规范事件,返回新状态。它不解析SSE、不连接任何模型,也不替你推断厂商的完成语义。适合在真正的协议适配层之后,学习部分结果如何隔离与验收。
环境与文件
Python 3.11及以上,标准库,无需pip安装、API Key或账户。建立partial-lab目录,保存下面三个文件:partial_state.py、cancel_probe.py、test_partial_state.py。进入该目录执行python -m unittest -v和python partial_state.py;另运行python cancel_probe.py观察真实本地任务取消。
输入合同与终态规则
每个事件必须是普通dict,只有attempt、kind两项;delta还必须有字符串text。多余字段、错误类型和不匹配attempt直接抛出固定错误码,原状态不变。attempt由调用方为每次生成创建,不是鉴权凭证。
kind支持delta,以及complete、local_cancel、transport_error、eof、rejected。此处eof表示缺少完成证据的流结束,complete只能由上游已核对的响应级完成条件产生。禁止看到一段文本done就直接产生整次complete。
采用第一个被接受终态冻结的应用政策:相同终态重放不改变计数;其他迟到终态或增量报already_terminal。它要求单个顺序消费者,不提供线程安全、数据库事务或远端请求幂等。
max_chars按Python len统计字符串长度,不能换算Token或实际内存字节。超长块整块丢弃,保留原前缀,进入limit_reached。max_events是本地允许接受的事件数,包含终态;若先耗尽,后来的complete也不能把结果提升为成品。生产接入应固定一次会话的上限,不要中途增大上限试图恢复已冻结状态。
完整状态机:partial_state.py
"""Normalized local events, NOT a vendor streaming-protocol adapter."""
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class State:
attempt: str
parts: tuple[str, ...] = ()
phase: str = "running"
chars: int = 0
events: int = 0
@property
def preview(self):
return "".join(self.parts)
def accepted_text(self):
if self.phase != "complete":
raise ValueError("incomplete_result")
return self.preview
TERMINALS = {"complete", "local_cancel", "transport_error", "eof", "rejected"}
def advance(state, event, *, max_chars=10000, max_events=1000):
# State is constructed by this process; serialized user states are not trusted.
if any(type(n) is not int or n < 1 for n in (max_chars, max_events)):
raise ValueError("invalid_limit")
if type(event) is not dict:
raise ValueError("invalid_event")
kind = event.get("kind")
if type(kind) is not str or kind not in TERMINALS | {"delta"}:
raise ValueError("invalid_kind")
keys = {"attempt", "kind", "text"} if kind == "delta" else {"attempt", "kind"}
if set(event) != keys or type(event["attempt"]) is not str:
raise ValueError("invalid_fields")
if event["attempt"] != state.attempt:
raise ValueError("wrong_attempt")
if kind == "delta" and type(event["text"]) is not str:
raise ValueError("invalid_text")
if state.phase != "running":
if kind == state.phase: # Repeated same terminal: no counter changes.
return state
raise ValueError("already_terminal")
if state.events >= max_events:
return replace(state, phase="limit_reached")
if kind == "delta":
size = len(event["text"])
if state.chars + size > max_chars:
# Preserve old prefix; discard the whole oversized chunk.
return replace(state, phase="limit_reached", events=state.events + 1)
return replace(state, parts=state.parts + (event["text"],),
chars=state.chars + size, events=state.events + 1)
return replace(state, phase=kind, events=state.events + 1)
def main():
for terminal in ("complete", "local_cancel", "eof"):
s = State("run-1")
for event in ({"attempt": "run-1", "kind": "delta", "text": "半段文字"},
{"attempt": "run-1", "kind": terminal}):
s = advance(s, event)
print(s.phase, repr(s.preview), s.phase == "complete")
if __name__ == "__main__":
main()
State只由本进程构造;如果接收用户上传或数据库反序列化的State,需要另做状态字段校验,不能把本例当成不可信状态的安全边界。parts采用tuple保留不可变快照,便于教学与测试;反复追加会复制元组,不适合未经测量就处理无限长流。默认10000字符、1000事件是示例政策值,不是任何模型的额度。
accepted_text只检查完整状态。空文本可以在协议层完成,因此本例允许complete得到空字符串;业务要求非空、JSON字段齐全或引用有效时,应另做校验。不要把这个函数当成“内容正确且可自动执行”的证明。
真正的本地取消:cancel_probe.py
仅向归约器传local_cancel并不能测试Task.cancel。下面另建协程,用Event确认已收到半段,然后请求取消、等待退出;正常路径用于对照。finally标记只证明本地清理代码运行过,没有创建网络连接。
"""Actual asyncio cancellation; no sockets, SDKs or model calls."""
import asyncio
async def probe(cancel=True):
ready, proceed = asyncio.Event(), asyncio.Event()
result = {"partial": "", "local_status": "running", "closed": False}
async def worker():
try:
result["partial"] += "已收到的半段"
ready.set()
await proceed.wait()
result["partial"] += ",后半段也收到了。"
result["local_status"] = "complete"
except asyncio.CancelledError:
result["local_status"] = "local_cancel"
raise
finally:
result["closed"] = True
task = asyncio.create_task(worker())
await ready.wait() # Synchronization, not a guessed sleep duration.
if cancel:
task.cancel()
else:
proceed.set()
try:
await task
except asyncio.CancelledError:
pass # The owner has awaited cancellation and recorded its status.
result["task_cancelled"] = task.cancelled()
result["remote_status"] = "unknown_no_network_in_this_demo"
return result
if __name__ == "__main__":
for cancel in (True, False):
print(asyncio.run(probe(cancel)))
完整回归:test_partial_state.py
import asyncio
import unittest
from partial_state import State, advance
from cancel_probe import probe
def event(kind, text=None, attempt="run-1"):
e = {"attempt": attempt, "kind": kind}
if text is not None:
e["text"] = text
return e
class StateTests(unittest.TestCase):
def setUp(self):
self.initial = State("run-1")
self.partial = advance(self.initial, event("delta", "你好"))
def test_complete_accepts_exact_text(self):
self.assertEqual(advance(self.partial, event("complete")).accepted_text(), "你好")
def test_noncomplete_never_accepted(self):
for kind in ("local_cancel", "eof", "transport_error", "rejected"):
with self.subTest(kind=kind):
s = advance(self.partial, event(kind))
self.assertEqual(s.preview, "你好")
with self.assertRaisesRegex(ValueError, "^incomplete_result$"):
s.accepted_text()
def test_first_terminal_wins(self):
for first, late in (("complete", "local_cancel"), ("local_cancel", "complete")):
s = advance(self.partial, event(first))
with self.assertRaisesRegex(ValueError, "^already_terminal$"):
advance(s, event(late))
self.assertEqual(s.phase, first)
def test_terminal_repeat_idempotent(self):
s = advance(self.partial, event("complete"))
self.assertIs(advance(s, event("complete")), s)
def test_late_delta_rejected(self):
s = advance(self.partial, event("eof"))
with self.assertRaisesRegex(ValueError, "already_terminal"):
advance(s, event("delta", "迟到"))
def test_wrong_attempt(self):
with self.assertRaisesRegex(ValueError, "wrong_attempt"):
advance(self.partial, event("delta", "旧请求", "run-0"))
def test_exact_char_limit_and_overflow(self):
s = advance(self.initial, event("delta", "你好"), max_chars=2)
self.assertEqual(s.chars, 2)
limited = advance(s, event("delta", "!"), max_chars=2)
self.assertEqual((limited.phase, limited.preview), ("limit_reached", "你好"))
def test_event_limit_can_block_completion(self):
s = advance(self.partial, event("complete"), max_events=1)
self.assertEqual((s.phase, s.events), ("limit_reached", 1))
def test_invalid_limits(self):
for value in (True, 0, -1, "2"):
with self.assertRaisesRegex(ValueError, "invalid_limit"):
advance(self.initial, event("complete"), max_events=value)
def test_invalid_event_shape(self):
for value in (None, [], {"attempt": "run-1", "kind": "complete", "extra": 1},
{"attempt": "run-1", "kind": "delta", "text": 9},
{"attempt": "run-1", "kind": []}):
with self.assertRaises(ValueError):
advance(self.partial, value)
self.assertEqual(self.partial.preview, "你好")
def test_empty_complete_is_transport_not_business_validation(self):
self.assertEqual(advance(self.initial, event("complete")).accepted_text(), "")
def test_immutable_snapshot(self):
later = advance(self.partial, event("delta", "!"))
self.assertEqual((self.initial.preview, self.partial.preview, later.preview),
("", "你好", "你好!"))
class CancellationTests(unittest.IsolatedAsyncioTestCase):
async def test_real_task_cancel_propagated(self):
s = await probe()
self.assertTrue(s["task_cancelled"])
self.assertEqual(s["local_status"], "local_cancel")
async def test_partial_and_finally_preserved(self):
s = await probe()
self.assertEqual(s["partial"], "已收到的半段")
self.assertTrue(s["closed"])
async def test_normal_completion(self):
s = await probe(False)
self.assertEqual(s["local_status"], "complete")
self.assertFalse(s["task_cancelled"])
self.assertTrue(s["closed"])
if __name__ == "__main__":
unittest.main()
运行结果与解释
本次在Python 3.11.9环境实际执行,15项测试通过。运行partial_state.py得到:
complete '半段文字' True
local_cancel '半段文字' False
eof '半段文字' False
运行cancel_probe.py,取消路径返回local_cancel、task_cancelled=True,保留“已收到的半段”;正常路径返回complete、task_cancelled=False。两条路径closed=True;remote_status都明确为unknown_no_network_in_this_demo。这不包含真实网络、中止计费或厂商兼容性测试。
接入顺序与错误排查
先让SDK适配器核对事件种类、响应级状态和拒答/截断,再发规范事件给advance。每次重试使用新的State和attempt。不要从异常分支finally里发送complete;也不要让新请求复用旧请求状态。
ValueError(incomplete_result):上游没有接受完整终态,允许显示带标记的预览,禁止按成品使用。already_terminal:检查是否重复消费者或取消/完成竞态;先保留已冻结状态,调查来源,不自动重置。wrong_attempt:旧请求或路由错误,不能改事件标识来强行接收。invalid_fields/invalid_text:协议适配器违反输入合同,应停止本轮处理并记录错误码,不回显敏感原文。limit_reached:本地政策终止,不能伪装为厂商限流,也不能自动切换成complete。
对真实SDK还要补响应关闭、超时、工具输出、多模态内容、资源释放和持久化策略。取消只说明本地观察结果;远端是否已处理、如何计费,必须查实际服务协议与可用证据,不能由本实验推断。
小练习与下一篇
增加一个测试:先收到两个delta,再收到transport_error,确认preview完整保留而accepted_text仍拒绝;再让相同complete事件重复两次,确认计数不增加。只改本地样本,不发起真实模型请求。
下一篇实现本地并发闸门,解释它和每分钟请求、Token配额的差别。欢迎在评论里给出去掉密钥和个人信息的事件序列,讨论该在哪个状态停止交付。
参考与事实边界
Python 3.11任务取消说明说明取消异常和清理传播;OpenAI流式响应指南将文本增量与响应完成事件分开。前者是本地协程语义,后者是一种厂商协议,不能互相替代。
本文为常青工程教学。10月30日OpenAI公告讨论网站训练偏好信号,原文仍说将在未来数月实施,与本课的流式停止无直接关系,因此没有把它包装为取消能力上新,也未据此声称任何聚合站已支持新能力。没有模型调用、计费实验或性能测量。
内容由AI辅助整理,示例代码经过本地离线验证;上线前仍需按实际SDK、协议和业务要求审查。
本文实现一个纯Python流式状态归约器,支持局部取消、终态冻结与部分结果交付。核心包括不可变状态机设计、local_cancel本地取消验证、15项离线单元测试覆盖delta/complete/transport_error等事件类型,以及max_chars/max_events本地策略约束。强调输入合同校验、单顺序消费者语义、终态幂等性与协议适配层职责分离,不依赖外部模型或网络。
364

被折叠的 条评论
为什么被折叠?



