lixi
|
8f1d1bc5f3
|
feat(seedance): 两个已开通模型的限制表与提交前本地预校验(23 测试)
账号实际接入 doubao-seedance-2-5-260628 与 doubao-seedance-2-0-260128,两者限制
差异不小,且同一组参数在一个上合法、在另一个上不合法:
| | 2.5 | 2.0 |
| 时长 | [4, 30] | [4, 15] |
| 分辨率 | 480p/720p/1080p | 480p/720p/1080p/4k |
| 图生视频 ratio | **只能 adaptive** | 可指定 |
| output_format | mp4/mov | 仅 mp4 |
超范围的请求会被厂商 400 拒绝——那要花一次往返、计入 RPM,并表现为一个失败的
任务。故在 capabilities.py 落一张限制表并在提交前本地预校验,错误信息直接指出
是哪个模型的哪条限制。
两处刻意的设计:
- **按模型族前缀匹配**而非完整 ID:尾部是日期版本(260628/260128),会随厂商
小版本更新而变,按前缀匹配使一次升级不必改表。
- **未知模型一律放行**(只记日志):账号新开通一个模型时不该因为这张表没更新
就用不了,否则「开通即可用」会退化成「开通还得改代码发版」。代价只是失去
本地预校验,厂商仍会兜底。
测试 100 → 123:限制表 23 项逐条对应官方文档的一条限制,另在适配器层加 4 项
端到端断言(超范围请求的 handler 直接 assert 不该被调用,确认请求根本没发出)。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
2026-09-20 15:12:06 +08:00 |
|
lixi
|
db87d66403
|
fix(ai): 修 ruff lint 三处错误——CI 由红转绿
CI / ai-gates (push) Successful in 31s
`4762358` 与 `86d63ab` 两次 CI 均因 `ruff check` 失败而红。
**根因是我的门禁自查方法错了**:用 `ruff check . | tail -1; echo $?` 读到的是
`tail` 的退出码而不是 ruff 的,于是把一次真实的 lint 失败读成了通过——先前声称
的「四道门禁全绿」是基于这个坏掉的检查得出的。已在 ci.yml 的 Gates 步骤就地注明
此坑,并改为逐条分开跑读各自退出码。
三处都是实际问题,非误报:
- `client.py:138` PYI034:`__enter__` 的返回注解硬编码为类名,应为 `Self`
(子类化后注解会失准)
- `test_video.py:58` C408:`dict()` 调用改字面量
- `test_layering.py:85` PIE810:两次 `endswith` 合并为一次元组调用
修复后在与 CI 同构的隔离 venv(同镜像源、同 requirements-dev.txt,实测 ruff
版本与本地一致 0.16.8)逐道复验:secret-scan / format / lint / pytest 四道
退出码均为 0,100 测试全过。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
2026-09-20 12:36:17 +08:00 |
|
lixi
|
86d63ab1cb
|
docs: 补正 4762358 的提交记录——该提交实际包含 AI 服务分层重构
CI / ai-gates (push) Failing after 31s
**这是一条记录补正,不含代码改动。**
4762358 的提交信息只写了「凭证扫描放行依赖注入传参 + 四仓口径更正」,但它实际
包含了 patbond-ai 的整个分层重构(22 个文件、约 1900 行增、约 700 行删)。
成因:前一次提交尝试被 pre-commit 钩子拦下(它正确地拦了 assembly.py 里
`api_key=volcengine.api_key` 这个形态),但 `git add -A` 已经把重构文件留在
暂存区;随后修规则的那次提交只 `git add` 了脚本,却把暂存区里既有的内容一并
提交了。提交前未核对 `git status` 的暂存清单——正是 git-workflow.md「禁止事项」
第一条要求逐一核对的那一步。
不用 amend 重写历史:dev 是共享分支,git-workflow.md 明确禁止对其 force push。
故以本条补正记录说明 4762358 的真实内容,其完整说明如下。
---
**refactor(ai): 按高内聚低耦合分层重构 + 集中配置(100 测试)**
原先 seedance.py 一个文件里混了 HTTP 传输、鉴权、厂商 DTO、状态映射与视频业务,
接第二个火山接口(图片/语音)时只能整段再抄一遍。按依赖单向向内重切四层:
config.py → assembly.py → providers/ → domain/
- **domain/generation.py**:与服务商无关的领域模型,对厂商与 httpx **零依赖**
——「换供应商不动上层」的支点。判断归属的准绳是「换一家供应商它还在吗」:
Retry/UserFacing 描述 Worker 与客户端该怎么做,故在领域层;expired 状态、
QuotaExceeded 这类码是火山特有的,故在适配层。GenerationResult 拒绝
「FAILED 但无 Failure」与「SUCCEEDED 但无 asset_url」——半个失败态放给上层
只会让它盲目重试或盲目放弃。
- **providers/base.py + registry.py**:能力抽象与 provider_code 分发,对具体
供应商零依赖。registry 拒绝重复注册(静默覆盖会让「哪个实例在处理任务」
取决于注册顺序)。
- **providers/volcengine/** 拆四块:client.py(HTTP 传输与鉴权,**不认识
「视频」**,以后接火山图片/语音接口复用它——这是拆它的全部理由;并修正连接
归属:只关自己创建的连接池)、errors.py(火山码 → 领域 Failure,补 message
透传)、models.py(厂商 DTO 与常量,base URL 从 config 收拢至此)、
video.py(仅视频业务)。
- **config.py**:集中配置。非法值响亮失败而不静默回落;厂商区间
[3600,259200] 启动时校验;密钥绝无默认值。另加 .env.sample。
- **assembly.py**:装配根,唯一同时知道配置与具体供应商的地方。缺凭证的供应商
被跳过而非让启动失败。
- **tests/test_layering.py**:用 AST 断言架构约束本身。分层的价值全在「换供应商
不动上层」,而这个性质只要有人无意加一行 import 就会悄悄失效——靠纪律守不住,
靠测试才守得住。写完它当即抓到一处真问题:厂商 base URL 硬编码在 config.py。
测试 58 → 100,全部离线。另两处新行为:succeeded 但缺 video_url 判为可重试失败;
asset_url 随 URL 给出 24 小时时效,使上层没有理由把厂商地址当永久存储。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
2026-09-20 12:19:50 +08:00 |
|
lixi
|
4762358f54
|
fix(security): 凭证扫描放行依赖注入传参 + 四仓口径更正
CI / ai-gates (push) Failing after 29s
`api_key=volcengine.api_key` 这类**依赖注入传参**被 KEY-ASSIGN 判为疑似凭证。
这是规则的真缺口而非代码问题:行里没有任何秘密,而做 DI 的代码到处都是这种
写法——绕过一次就得在每个调用点绕过,那等于把规则废掉。故改规则。
新增 ALLOW_REFERENCE,刻意收得很紧以免开后门:
- 值必须是**未加引号**的小写起首标识符引用(真密钥要么带引号,要么不是标识符形状)
- 只允许**恰好一个点且随即行尾**
- 连字符不在字符集内
对抗性夹具验证(三真三假):
- 拦下 `api_key = "sk-T3Blbk…"`(OpenAI 形态)
- 拦下 `secret_key: "AKID…"`(腾讯云形态)
- 拦下 `auth_token=eyJ….eyJ….Sfl…`(**三段式 JWT——两个点,故不落进放行**)
- 放行 `api_key=lowercase.reference` / `api_key = settings.api_key` / `api_token: config.authToken`
原 5 形态泄漏夹具复验仍 8 条命中,一条不少。
另更正口径:脚本头与 git-workflow.md 仍写「三仓同构」,实际已是四仓——
patbond-ai 是唯一持有 AI 服务商凭证的仓,漏同步它风险最大。四仓 md5 已一致
(4945940093b4b8413b7d3eec8bf6a5ec),四仓 --all 全绿。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
2026-09-20 12:18:18 +08:00 |
|
lixi
|
c6e8dddc8d
|
feat(seedance): 火山方舟视频生成客户端与错误分类(58 测试,全离线)
CI / ai-gates (push) Successful in 31s
按用户提供的三份官方文档适配 Seedance 视频生成:提交
(POST /contents/generations/tasks)与查询(GET .../tasks/{id})。
取证记录见 patbond-doc docs/development/providers/volcengine-seedance.md。
**开工前问的五项,两项与原假设不同:**
1. **不支持幂等键**。请求参数全集里没有任何幂等字段。故 ADR-023 §4 设想的
「把我方 idempotency_key 透传为厂商幂等头」对本供应商不成立,「提交超时后
不知道有没有扣费」只能由 max_attempts 兜住——如实登记,不假装解决。
两点减轻:提交是异步的(只入队即返回,超时概率低);且厂商支持 callback_url,
孤儿任务会通过回调把 ID 送回来,故回调在此供应商上还是**孤儿回收通道**,
不只是延迟优化——这是 ADR-023 §4「回调仅预留」之外的新论据,值得复评。
2. **产物是有效期 24 小时的 URL**(2.5 另有 100 次下载上限)。因此厂商 URL
**不能当存储用**:存进 media.assets.external_url 会在 24h 后变死链。
**这对挂起中的存储决策是硬约束**——无论选本地还是云,都必须在 24 小时内
把文件复制到我们自己的存储。
其余三项:查询接口只保留 7 天记录;无明确频率数值,但官方 SDK 示例用 10s 轮询
(我们的 3s/10s/30s 阶梯与之兼容)、客户端截止 30 分钟(建议据此把 ADR-023 §10
的视频客户端超时从 15~20 分钟对齐到 30 分钟);execution_expires_after 范围
[3600, 259200],实现取下限 1 小时(厂商默认 48h 对交互式应用过长,且无法更短)。
**两个文档自身的问题,实现按宽的那份:**
- **状态枚举不一致**:查询文档列 5 个(queued/running/cancelled/succeeded/
failed),创建文档的回调一节额外列 **expired**。只按查询文档实现会让超时
任务掉进未知分支。按 6 个实现,expired 视为**可重试**失败(是超时不是内容
问题),且厂商 expired 时不一定给 error.code 故单独分类。
- **QuotaExceeded 一码两义**:同一 code 既表示「排队任务数超限」(等几秒)
又表示「额度耗尽」(等到明天或充值)。按 code 一刀切必然有一半场景的文案
和出口是错的;只能按 message 判别,两条用例都在测试里钉住。
**顺带拿到两个免费收益**:last_frame_url 可直接作视频封面(iteration-4/03 R13
把缩略图列为视频成本项,实际免费);查询返回 ratio/resolution/framespersecond/
duration,**Worker 写产物时维度与时长都在手里、无需解码**——widthPx/heightPx
债在 AI 产物上可零成本修掉。
一处精度陷阱已处理:返回的 duration 是「帧数/24 向下取整」的约数(133 帧实际
5.54s 返回 5),且 duration 与 frames 只返回其一。故优先按 frames/fps 精确换算,
只有 duration 时退回整数秒并标注为约数,缺失时返回 None 而非编 0。
取消语义已查明:厂商**只支持取消 queued 状态**的任务,故我方取消已 running
的任务无法向厂商传导,**仍会计费**。ADR-023 §6 关于取消的说明据此可定稿。
测试 58 项(错误分类 32 + 客户端 26),全部离线用 httpx MockTransport,
夹具响应体逐字取自官方文档示例以免字段名对不上。
过程中防泄漏钩子拦下了测试夹具里的 `api_key="test-key"`——规则是对的(这正是
真实密钥最常见的泄漏形态),故改用 ALLOW 清单认可的占位词,未放宽规则。
未实现:产物转存(阻塞于存储决策)、HTTP 服务端点与 openapi-internal.yaml。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
2026-09-20 11:51:37 +08:00 |
|
lixi
|
6990709124
|
chore: 建仓门禁四件套——CI、防泄漏脚本与钩子、gitignore、职责边界 README
CI / ai-gates (push) Successful in 47s
patbond-ai 纳入发布语义(ADR-023 §12)。本仓是四仓中唯一直接持有 AI
服务商凭证的仓库,因此不得成为唯一没有防泄漏门禁的仓库——建仓即配齐,
不等代码落地。
- .gitea/workflows/ci.yml:ai-gates 作业,push(dev) + pull_request 双触发,
与另三仓同构。Secret scan 为第一个 step 且先于任何依赖安装。
pip 走腾讯云镜像(与 patbond-doc 装 mkdocs 同一策略)。
门禁命令 ruff format --check / ruff check / pytest,对应 flutter 仓的
dart format + analyze + test 三步。requirements*.txt 落地前只装工具本身,
使 CI 自建仓第一天即为绿;pytest 无测试时退出码 5 视为通过。
- scripts/check-secrets.sh:四仓同构副本,md5 640994c12bfa78b4512e872d709ed685。
已覆盖 AI 服务商密钥形态(sk-ant- / sk- / sk-proj- / sk-svcacct-)与
api_key/auth_token 实值赋值——本仓正是这些规则的主要适用对象。
- scripts/hooks/pre-commit:与另三仓同构。四仓 core.hooksPath 均已启用,
ADR-023 §12 登记的「防泄漏第一层从未生效」至此闭环。
实测验证:含 5 种真实凭证形态的 .env 夹具被钩子拦下,提交未产生。
- .gitignore:Python 产物与工具缓存;凭证载体(.env / credentials* / *.pem
/ *.key)一律排除,占位样例走 .env.sample(对应脚本的 DENY_NAME_OK)。
- README.md:职责边界(不访问数据库、不持有对象存储凭证、对上游同步异步
一律对外呈现为异步)、与 Java 侧的接口约定(REST 非 gRPC、独立
openapi-internal.yaml、服务身份 JWT 独立密钥对、请求体不含 userId)、
本地门禁命令与凭证纪律。细节以 ADR-023 为准。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
2026-09-18 16:42:34 +08:00 |
|
lixi
|
172f7d7537
|
first commit
|
2026-09-17 15:28:15 +08:00 |
|