
1. 把 CAD 参数级生成放进 CI先解决请求出口和 Key 注入如果你正在把 CAD 参数级生成塞进 GitHub Actions 或 GitLab CI大概率会遇到几个很具体的问题脚本在本地能跑到了 Runner 里却因为请求出口不稳定、模型名写死、JSON 参数漂移而失败或者把 Key 塞进 workflow 后才发现每次生成的板厚、孔径、圆角、约束关系都散落在日志里没有统一归档。近期围绕 1.5B 量级 CAD 生成模型的讨论核心并不是“画一个好看的曲面”而是把 CAD 意图落到可校验的参数树上。参数级输出的好处是它天然适合 CI因为你可以用 JSON Schema、数值范围和回归快照来断言结果。这篇内容围绕一个可跟做的 CI 集成路径展开先到 TaoToken 官网 获取 API Key再把请求出口统一填成https://taotoken.net/api最后在 CI 里完成 CAD 参数生成、测试命令执行和生成结果归档。整个流程不依赖本地图形界面所有命令都可以在 CI Runner 或读者自己的终端里执行。需要提前明确三个配置事实TaoToken 的 Base URL 是https://taotoken.net/api这个地址用于工具和 SDK 的请求出口不加 UTM 参数。API Key 用占位符YOUR_API_KEY表示实际使用时请从 CI Secret 或本地环境变量注入。Claude Code、Codex、CC Switch 的配置方式不同不能把ANTHROPIC_*环境变量套到 Codex 的config.toml上。下面的目标很明确让每次 PR 或手动触发 CI 时都能通过 TaoToken 调用模型生成一份 CAD 参数 JSON跑完 Schema 与范围校验并把原始响应、参数文件、manifest 一起归档。2. 在 TaoToken 创建 Key 并做最小连通性验证第一步不是直接写 workflow而是先确认请求出口可用。到 TaoToken 官网 进入控制台相关入口创建 API Key。也可以直接打开 API Keys 页面创建 Key。创建完成后把 Key 保存到本地环境变量或 CI Secret 中命名建议用TAOTOKEN_API_KEY。本地最小验证可以用 Python OpenAI 兼容 SDK。先安装依赖python -m venv .venv source .venv/bin/activate pip install openai jsonschema pytest export TAOTOKEN_API_KEYYOUR_API_KEY export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_MODELYOUR_MODEL然后运行一个最小脚本确认 Key、Base URL 和模型名三者匹配import os from openai import OpenAI client OpenAI( base_urlos.environ[TAOTOKEN_BASE_URL], api_keyos.environ[TAOTOKEN_API_KEY], ) resp client.chat.completions.create( modelos.environ[TAOTOKEN_MODEL], messages[ {role: system, content: 你只输出 JSON不要输出解释。}, {role: user, content: 输出一个 CAD 参数测试对象外径 80内径 30厚度 6。}, ], temperature0, timeout60, ) print(resp.choices[0].message.content)如果这里返回 401优先检查TAOTOKEN_API_KEY是否被正确注入以及请求头是否为 Bearer 形式。如果返回模型不存在或 404请到 模型对话 里确认当前可用模型名再写入TAOTOKEN_MODEL。不要凭记忆写死模型名也不要把 Claude Code 的ANTHROPIC_MODEL直接复制到 Codex 配置里。为了让 CAD 参数生成在 CI 里可重复建议把输入规格整理成 fixtures JSON。例如{ part_type: flange, unit: mm, outer_diameter: 80, inner_diameter: 30, thickness: 6, holes: { count: 4, diameter: 6, pattern: equally_spaced }, constraints: [ 孔中心圆直径等于外径减 2 倍边距, 所有孔均布在中心圆上, 厚度大于 0 且小于外径的 0.2 倍 ] }模型的任务不是画图而是把自然语言规格和约束转成结构化参数。输出可以约定为{ part_type: flange, unit: mm, parameters: { outer_diameter: 80, inner_diameter: 30, thickness: 6, hole_count: 4, hole_diameter: 6, hole_pattern: equally_spaced }, constraints: [ { name: hole_pcd, expression: outer_diameter - 2 * edge_margin, value: 68 } ], operations: [ create_cylinder, cut_inner_cylinder, cut_holes, apply_fillet ] }这个 JSON 就是后续测试和归档的核心产物。3. GitHub Actions 配置片段让 1.5B CAD 参数生成随 PR 跑起来建议在仓库里建立如下目录避免把生成逻辑散落在 workflow 内联脚本里cad-ci/ fixtures/ cad_specs/ flange_001.json baseline/ flange_001.params.json prompts/ cad_param_prompt.txt schemas/ cad_params.schema.json scripts/ generate_cad_params.py write_manifest.py diff_params.py tests/ test_cad_params.py artifacts/ .gitkeep .github/ workflows/ cad-param-ci.ymlgenerate_cad_params.py负责读取输入规格、拼接 prompt、调用 TaoToken、校验 JSON 并写入artifacts/。示例import argparse import hashlib import json import os import pathlib import sys import time from openai import OpenAI BASE_URL os.environ.get(TAOTOKEN_BASE_URL, https://taotoken.net/api) API_KEY os.environ[TAOTOKEN_API_KEY] MODEL os.environ.get(TAOTOKEN_MODEL, YOUR_MODEL) client OpenAI(base_urlBASE_URL, api_keyAPI_KEY) PROMPT_TEMPLATE 你是 CAD 参数化生成器。 请根据输入规格输出严格 JSON字段必须符合 - part_type: string - unit: string - parameters: object - constraints: array - operations: array 不要输出 Markdown不要输出解释不要输出代码块。 输入规格 {spec} def load_json(path: str) - dict: return json.loads(pathlib.Path(path).read_text(encodingutf-8)) def save_json(path: str, data: dict) - None: p pathlib.Path(path) p.parent.mkdir(parentsTrue, exist_okTrue) p.write_text(json.dumps(data, ensure_asciiFalse, indent2), encodingutf-8) def prompt_hash(text: str) - str: return hashlib.sha256(text.encode(utf-8)).hexdigest()[:16] def generate(spec: dict) - dict: prompt PROMPT_TEMPLATE.format(specjson.dumps(spec, ensure_asciiFalse)) last_error None for attempt in range(3): try: resp client.chat.completions.create( modelMODEL, messages[ {role: system, content: 只输出合法 JSON。}, {role: user, content: prompt}, ], temperature0, response_format{type: json_object}, timeout90, ) content resp.choices[0].message.content return json.loads(content) except Exception as exc: last_error exc time.sleep(2 ** attempt) raise RuntimeError(fCAD 参数生成失败: {last_error}) def main() - int: parser argparse.ArgumentParser() parser.add_argument(--input, requiredTrue) parser.add_argument(--output, requiredTrue) args parser.parse_args() spec load_json(args.input) result generate(spec) save_json(args.output, result) print(json.dumps({ input: args.input, output: args.output, model: MODEL, base_url: BASE_URL, prompt_hash: prompt_hash(json.dumps(spec, ensure_asciiFalse)), }, ensure_asciiFalse)) return 0 if __name__ __main__: sys.exit(main())workflow 可以这样写name: cad-param-ci on: pull_request: paths: - cad-ci/** workflow_dispatch: env: TAOTOKEN_BASE_URL: https://taotoken.net/api jobs: generate-and-validate: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-pythonv5 with: python-version: 3.11 - name: Install deps run: | pip install openai jsonschema pytest - name: Generate CAD params env: TAOTOKEN_API_KEY: ${{ secrets.TAOTOKEN_API_KEY }} TAOTOKEN_MODEL: ${{ vars.TAOTOKEN_MODEL }} run: | python cad-ci/scripts/generate_cad_params.py \ --input cad-ci/fixtures/cad_specs/flange_001.json \ --output cad-ci/artifacts/flange_001.params.json - name: Validate schema and ranges run: | pytest -q cad-ci/tests/test_cad_params.py - name: Write manifest run: | python cad-ci/scripts/write_manifest.py \ --artifacts cad-ci/artifacts \ --out cad-ci/artifacts/manifest.json - uses: actions/upload-artifactv4 with: name: cad-param-artifacts path: cad-ci/artifacts/这段配置里TAOTOKEN_API_KEY来自仓库 SecretTAOTOKEN_MODEL来自仓库变量。不要把 Key 写进 YAML也不要提交.env。Base URL 统一使用https://taotoken.net/api所有请求出口都从这里走。4. 测试命令与生成结果归档把参数级输出变成可回归快照CI 里最值得投入的部分不是“调用一次模型”而是让生成结果可验证。对于 CAD 参数级生成建议至少做四类断言JSON Schema 校验字段是否齐全类型是否正确。数值范围校验外径、内径、厚度、孔数、孔径是否落在合理区间。约束一致性校验孔中心圆、边距、均布关系是否被表达。回归快照校验与基线 JSON 对比允许少量浮点误差。tests/test_cad_params.py示例import json import pathlib import jsonschema import pytest SCHEMA { type: object, required: [part_type, unit, parameters, constraints, operations], properties: { part_type: {type: string}, unit: {type: string}, parameters: { type: object, required: [outer_diameter, inner_diameter, thickness, hole_count, hole_diameter], properties: { outer_diameter: {type: number, minimum: 10}, inner_diameter: {type: number, minimum: 0}, thickness: {type: number, minimum: 0.5}, hole_count: {type: integer, minimum: 1}, hole_diameter: {type: number, minimum: 0.5}, }, }, constraints: {type: array}, operations: {type: array}, }, } def load_params(): path pathlib.Path(cad-ci/artifacts/flange_001.params.json) return json.loads(path.read_text(encodingutf-8)) def test_schema(): jsonschema.validate(load_params(), SCHEMA) def test_ranges(): data load_params() p data[parameters] assert p[outer_diameter] p[inner_diameter] assert p[thickness] p[outer_diameter] * 0.2 assert p[hole_count] 3 assert p[hole_diameter] p[outer_diameter] - p[inner_diameter] def test_constraints_not_empty(): data load_params() assert isinstance(data[constraints], list) assert len(data[constraints]) 1 def test_operations_contain_cut_holes(): data load_params() assert any(hole in op.lower() for op in data[operations])测试命令可以在本地和 CI 中一致执行pytest -q cad-ci/tests/test_cad_params.py -k schema or ranges or constraints如果需要做回归对比可以用diff_params.py比较基线import json import pathlib import sys def load(path): return json.loads(pathlib.Path(path).read_text(encodingutf-8)) def main(current_path, baseline_path, tol0.01): current load(current_path) baseline load(baseline_path) cur_p current[parameters] base_p baseline[parameters] failures [] for key in base_p: if key not in cur_p: failures.append(f缺少参数: {key}) continue if isinstance(base_p[key], (int, float)) and isinstance(cur_p[key], (int, float)): if abs(cur_p[key] - base_p[key]) tol: failures.append(f{key}: {cur_p[key]} ! {base_p[key]}) elif cur_p[key] ! base_p[key]: failures.append(f{key}: {cur_p[key]} ! {base_p[key]}) if failures: print(\n.join(failures)) sys.exit(1) print(参数回归通过) if __name__ __main__: main(sys.argv[1], sys.argv[2])执行命令python cad-ci/scripts/diff_params.py \ cad-ci/artifacts/flange_001.params.json \ cad-ci/fixtures/baseline/flange_001.params.json归档部分建议至少保存flange_001.params.json模型返回并解析后的参数。raw_response.txt原始响应文本方便排查 JSON 漂移。manifest.json模型名、Base URL、prompt hash、Git SHA、时间戳。pytest.log测试输出。manifest 脚本可以写成import argparse import datetime import hashlib import json import os import pathlib def sha256_file(path): h hashlib.sha256() h.update(pathlib.Path(path).read_bytes()) return h.hexdigest()[:16] def main(): parser argparse.ArgumentParser() parser.add_argument(--artifacts, requiredTrue) parser.add_argument(--out, requiredTrue) args parser.parse_args() artifacts pathlib.Path(args.artifacts) files [] for p in sorted(artifacts.glob(*)): if p.is_file() and p.name ! manifest.json: files.append({ name: p.name, sha256_16: sha256_file(p), size: p.stat().st_size, }) manifest { model: os.environ.get(TAOTOKEN_MODEL, YOUR_MODEL), base_url: os.environ.get(TAOTOKEN_BASE_URL, https://taotoken.net/api), git_sha: os.environ.get(GITHUB_SHA, local), created_at: datetime.datetime.utcnow().isoformat() Z, files: files, } pathlib.Path(args.out).write_text( json.dumps(manifest, ensure_asciiFalse, indent2), encodingutf-8, ) print(json.dumps(manifest, ensure_asciiFalse, indent2)) if __name__ __main__: main()这样每次 CI 结束后cad-param-artifacts里都有一份可追溯的参数生成记录。5. CI Runner 上的 Claude Code、Codex 与 CC Switch 配置虽然 CAD 参数生成主要走 Python 脚本但在本地开发或 CI 排障时很多人会用 Claude Code、Codex 或 CC Switch 辅助检查 prompt、日志和配置。这里必须把配置方式分开。Claude Code 使用settings.json和ANTHROPIC_*环境变量。示例{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: YOUR_CLAUDE_MODEL } }注意ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN、ANTHROPIC_MODEL只用于 Claude Code 这一侧。不要把这些变量写进 Codex 的配置。Codex 使用config.toml配置形式不同。示例model YOUR_CODEX_MODEL model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chatCodex 侧的关键是model_provider、base_url和env_key而不是ANTHROPIC_*。如果你在 CI Runner 上同时装了 Claude Code 和 Codex建议把 API Key 统一放在TAOTOKEN_API_KEY再按工具要求映射。CC Switch 三件套通常可以理解为供应商名称TaoToken-CI Base URLhttps://taotoken.net/api API KeyYOUR_API_KEY在 CC Switch 中新建或切换配置时把这三项填好再选择对应的 Claude Code 或 Codex profile。Claude Code 文档可以参考Claude Code 文档。如果只是跑 CAD 参数生成流水线不需要在 CI 里启动交互式工具只需要保证 Python 脚本使用的https://taotoken.net/api和 Key 可用即可。6. 常见报错排查401、404、超时、JSON 漂移CI 集成里最常见的四类问题可以按下面顺序排查。第一类401 或鉴权失败。检查 Secret 是否注入到当前 step环境变量名是否为TAOTOKEN_API_KEY。如果是手动 curl确认请求头是Authorization: Bearer $TAOTOKEN_API_KEY。不要在 YAML 里明文写 Key。需要新建 Key 时到 创建 Key 处理。第二类404 或模型不可用。通常是TAOTOKEN_MODEL写错或者模型名与当前账号可用范围不匹配。到 模型对话 里确认模型名再更新 CI 变量。不要把 Claude Code 的模型名填到 Codex 配置里也不要反向复制。第三类超时或间歇性失败。CI Runner 出网波动、模型排队、单次生成时间过长都会导致超时。建议设置timeout90失败重试 3 次指数退避。对于 CAD 参数生成单次请求不应无限重试否则会掩盖 prompt 或 Schema 问题。第四类JSON 漂移。模型有时会返回 Markdown 代码块、注释或多余字段。处理方式包括在 prompt 中要求“只输出合法 JSON”使用response_format{type: json_object}解析失败时保留原始响应用jsonschema严格校验把失败样本写入 artifacts方便下一次调整 prompt。可以在 workflow 里加一个失败时上传日志的步骤- name: Upload failed artifacts if: failure() uses: actions/upload-artifactv4 with: name: cad-param-failed path: cad-ci/artifacts/这样即使流水线失败也能拿到原始输出和 manifest而不是只看到一行红色报错。7. 文末 CTA模型对话 → Coding Plan → 创建 Key → Claude Code 文档把 1.5B CAD 参数生成放进 CI关键不是一次调用成功而是把请求出口、Key 注入、测试断言和归档做成可重复流程。推荐按下面路径完成接入先到 模型对话 确认可用模型和参数级输出效果。如果要把这类生成任务长期放进流水线可以查看 Coding Plan。然后到 创建 Key 生成YOUR_API_KEY。需要配置 Claude Code 时参考 Claude Code 文档。统一请求出口仍然是https://taotoken.net/api。更多入口可以从 TaoToken 官网 进入。把 Key 放进 Secret把 Base URL 放进环境变量把 CAD 参数 JSON 放进 artifacts你的 CI 就具备了可追溯、可回归、可排障的 CAD 参数级生成能力。