diff --git a/CLAUDE.md b/CLAUDE.md index 94cc546..dbf5fa0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,7 +9,7 @@ - **核心目标**: PolyGateway = 统一的大语言模型(LLM/VLM/OCR,音频预留)调度与中转库。治理单位是**一次模型调用**:请求封装、多源多账号、限流、错误分类与重试、熔断、Redis 响应缓存、流式看门狗、遥测(含成本)、结构化输出策略。全组件端口化可插拔。 - **架构权威文档**: `research-wiki/ARCHITECTURE.md`(架构单一事实源,含 D1-D14 决策及讨论过程、子系统设计、三项目迁移验收标准;**不受 400 行设计文档限制**,以无歧义传达既有讨论为准绳)。开发顺序见 `research-wiki/ROADMAP.md`;`research-wiki/designs/` 仅存放每次实现具体功能的设计文档。 - **参考项目**: `reference/` 下三个项目是本库的需求来源与代码蓝本(**只读,勿改**;M4 起"只读"指工作区文件与 main 检出不变——迁移实施经 `git worktree` 在 `~/Projects/m4-worktrees/` 的 feature 分支进行,worktree 的 git 操作会写 `reference/*/.git` 元数据,属预期);库必须能按 ARCHITECTURE.md §11 被它们迁移接入,否则即边界缺口。 -- **技术栈**: Python 3.11+,核心仅依赖 `httpx` + `pydantic`,其余(redis/sqlite/postgres/json_repair/openai)一律 optional extras。conda 环境 `PolyGateway`。 +- **技术栈**: Python 3.12+,核心仅依赖 `httpx` + `pydantic`,其余(redis/sqlite/postgres/json_repair/openai)一律 optional extras。conda 环境 `PolyGateway`。 ## 2. 常用命令 diff --git a/README.md b/README.md index 98ec21c..d811b27 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ pip install --extra-index-url https://gitea.iomgaa.online/api/packages/iomgaa/py | `structured` | json-repair | 结构化输出的修复策略 | | `sdk` | openai | 可选的 SDK transport(默认手写 httpx,不需要) | -要求 Python ≥ 3.11。 +要求 Python ≥ 3.12。 ## 快速开始 @@ -461,7 +461,7 @@ graph LR ## 开发 ```bash -conda create -n PolyGateway python=3.11 && conda activate PolyGateway +conda create -n PolyGateway python=3.12 && conda activate PolyGateway make install # editable 安装(dev + 全部 extras) make test # pytest + 覆盖率(目标 ≥80%) make lint # ruff + import-linter diff --git a/pyproject.toml b/pyproject.toml index f171ad8..19698e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ description = "PolyGateway:实验室统一的大语言模型(LLM/VLM/OCR) # registry 包页面的正文只认这一项:缺了页面就是一片空白(1.1.2 的教训,twine 会警告 # long_description missing 但不阻塞上传)。README 在打包时被固化进产物,发布后再改无效。 readme = "README.md" -requires-python = ">=3.11" +requires-python = ">=3.12" dependencies = [ "httpx>=0.27", "pydantic>=2.8", @@ -56,7 +56,7 @@ markers = [ ] [tool.ruff] -target-version = "py311" +target-version = "py312" line-length = 100 [tool.ruff.lint] diff --git a/src/polygateway/client.py b/src/polygateway/client.py index dac5f5c..94c07db 100644 --- a/src/polygateway/client.py +++ b/src/polygateway/client.py @@ -13,7 +13,7 @@ import hashlib import json import random import time -from typing import TYPE_CHECKING, Any, Literal, TypeVar +from typing import TYPE_CHECKING, Any, Literal from polygateway.backends.memory.breaker import InMemoryGate from polygateway.backends.memory.cache import InMemoryCache @@ -63,8 +63,6 @@ if TYPE_CHECKING: SourceConfig, ) -_T = TypeVar("_T") - def _guard_thinking( sources: list[SourceConfig], @@ -442,7 +440,7 @@ def _build_structured( return None, None -async def gather_bounded(aws: Iterable[Awaitable[_T]], *, concurrency: int) -> list[_T]: +async def gather_bounded[T](aws: Iterable[Awaitable[T]], *, concurrency: int) -> list[T]: """有界并发 gather(D5 便利函数,替代 VT 手搓 semaphore+gather 样板)。 语义与 `asyncio.gather` 默认一致: 结果保序、首个异常上抛;仅增加并发上限。 @@ -451,7 +449,7 @@ async def gather_bounded(aws: Iterable[Awaitable[_T]], *, concurrency: int) -> l raise ValueError("concurrency 必须 ≥ 1") sem = asyncio.Semaphore(concurrency) - async def _run(aw: Awaitable[_T]) -> _T: + async def _run(aw: Awaitable[T]) -> T: async with sem: return await aw diff --git a/src/polygateway/streaming.py b/src/polygateway/streaming.py index 6a64820..ff315bb 100644 --- a/src/polygateway/streaming.py +++ b/src/polygateway/streaming.py @@ -14,13 +14,11 @@ from __future__ import annotations import asyncio import contextlib import time -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING if TYPE_CHECKING: from collections.abc import AsyncIterator -_T = TypeVar("_T") - class StreamLivenessTimeout(Exception): # noqa: N818 — 三项目冻结的公共名 """流活性超时异常。 @@ -38,14 +36,14 @@ class StreamLivenessTimeout(Exception): # noqa: N818 — 三项目冻结的公 super().__init__(f"流活性超时({kind}, elapsed={elapsed_s:.1f}s)") -async def _anext_within( - it: AsyncIterator[_T], +async def _anext_within[T]( + it: AsyncIterator[T], timeout_s: float, *, kind: str, start: float, first: bool, -) -> _T: +) -> T: """限时取下一项;本层 deadline 触发抛 StreamLivenessTimeout(kind)。 上游自抛的 TimeoutError 用 cm.expired() 区分,原样上抛不误吞。 @@ -59,13 +57,13 @@ async def _anext_within( raise StreamLivenessTimeout(kind, time.monotonic() - start, not first) from None -async def stream_with_liveness_timeouts( - source: AsyncIterator[_T], +async def stream_with_liveness_timeouts[T]( + source: AsyncIterator[T], *, ttft_s: float, inter_token_s: float, total_s: float, -) -> AsyncIterator[_T]: +) -> AsyncIterator[T]: """逐项产出 source,并施加三层活性超时。 关键实现: 超时**只包裹单次 __anext__**,绝不包裹 yield——否则总时长