使用 Kimi K3.1 Pro API 构建图像转 CAD 智能体
图像转 CAD 智能体能把零件图片转成可编辑的 CAD 模型。
为什么 Kimi K3.1 Pro 与 CadQuery 适配图像转 CAD
Kimi K3.1 Pro 能读取四张图像,并对其自身代码的产物作出反应。我前面提到的同一篇 Moonshot 博客报告称,在BenchCAD 的 Vision2Code 任务上,使用工具得分为 92.7%,不使用工具为 72.8%,该任务从渲染视图编写 CAD 代码并计分。分数来自 1,000 个用例子集、在 max 努力级别下得到,且使用工具的成绩基于 Claude Code harness,因此部分得分可能归功于该 harness。
该基准还将四个视图并入一张图片,并在评分前把所有零件缩放到相同尺寸。我的做法是发送四个独立视图并提供一个真实尺寸,设置为 high 努力级别,因此把 Moonshot 的数字当作动手测试的理由,而非结果本身。当我运行这些试验时,开源权重尚不可下载。
之所以选择 CadQuery 作为输出格式,是因为 CadQuery 脚本本身就是参数化模型。一个孔对应一个包含直径与位置的调用,因此后续编辑只需改一个数,实体即可重建。STL 文件只有三角面,没有 slot_width 可改。
智能体能看到的与被隐藏的
Kimi 接收四张参考图、60 mm 的宽度信息,以及一段系统提示。该提示固定单位为毫米与坐标轴,把零件在 X 与 Y 居中、底面位于 Z=0,并要求给出命名尺寸以及在 result 中输出最终实体。
之后,Kimi 只知道工具报告的结果。Kimi API 不会运行 CadQuery,因此四个工具都在应用内实现:
-
execute_cad在无网络的 Docker 容器中运行程序,并将结果保存为候选项 -
inspect_cad报告有效性、实体数量、包围盒、体积以及圆柱面 -
render_cad用与参考相同的四个相机视角渲染候选项 -
submit_cad用一个候选项结束一个阶段
Kimi 从未见过参考代码或任何分数。评测器只会在智能体完成后读取已保存的 STEP 文件,因此分数既不能引导也不会中止智能体。
分数从不回流到 Kimi。作者供图。
在 Python 中设置 Kimi K3.1 Pro API
要在 Python 中使用 Kimi K3.1 Pro API,您需要四样东西。我在 Linux x86_64 上用以下版本完成了测试:
-
一枚已充值至少 1 美元的 Kimi API 密钥,参考 K3.1 Pro 快速上手
-
Python 3.12.3 与
openai3.39.0(OpenAI SDK) -
cadquery2.8.0(用于构建与检查实体) -
Docker 29.9.2(用于沙箱)
下面的代码片段摘自该 GitHub 仓库的完整项目,您将很快克隆它。
安装 CadQuery 并构建 Docker 沙箱
CadQuery 的安装指南推荐使用 conda,不过在 Python 3.12 上用 pip 也可行。克隆仓库并完成设置:
git clone https://github.com/KhalidAbdelaty/Kimi_K3.1_Pro_API_Tutorial.git
cd Kimi_K3.1_Pro_API_Tutorial
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\Activate.ps1
pip install -r requirements.txt
cp .env.example .env # then add your MOONSHOT_API_KEY
docker build -t cad-sandbox:2.8.0 sandbox/
该沙箱镜像安装相同版本的 CadQuery,并以普通用户身份运行。按 Docker 资源限制文档,Docker 默认不设 CPU 或内存限制,因此由运行器设置。仓库首页列出了运行与评分实验的命令,包括无需 API 密钥或 Docker 的路径。
在 Kimi 的代码进入容器之前,先尝试自己“破坏”它。我的测试程序尝试读取 API 密钥、访问网络、并在其临时文件夹之外写入,这三次尝试均被阻止。

沙箱中被拦截的三项操作。作者供图。
发出您的第一条 Kimi K3.1 Pro API 调用
这就是把 OpenAI SDK 指向 Moonshot 端点的常规用法。每次请求都要设置 reasoning_effort,因为留空会得到 max,而且您无法关闭“思考”。
client = OpenAI(api_key=os.environ["MOONSHOT_API_KEY"], base_url="https://api.moonshot.ai/v1")
response = client.chat.completions.create(
model="kimi-k3.1-pro",
reasoning_effort="high", # low, high, or max; the API default is max
max_completion_tokens=2048, # reasoning and answer share this cap
messages=[{"role": "user", "content": "In one sentence, what does a STEP file store?"}],
)
print(response.choices[0].message.content)
print("usage:", response.usage.model_dump(exclude_none=True))
不要设置 temperature、top_p、n,以及两个惩罚项;快速上手文档将它们列为固定值。即便是这样一句话的回答,完成的绝大部分 tokens 也花在推理上,因此设置上限时要考虑这一点。

一次请求确认密钥与模型。作者供图。
如果您对 Kimi API 还不熟悉,我之前的Kimi K3 教程涵盖了流式输出、工具调用、缓存与视觉输入。
创建参考 CAD 模型与四个测试视图
参考 CAD 模型是您的“答案标准”,每次评分都能与之精确对比。我在第一次请求前就把它锁定了——从相机到评分规则——看到结果后也未作任何更改。
定义支架尺寸
支架是 L 形,竖板与底板在后侧相连。原点位于底面中心,X 沿宽度方向、前方向为 −Y、Z 轴向上:
|
特征 |
数值 |
展示给 Kimi? |
|
总体宽度(X) |
60 mm |
是 |
|
底板深度(Y) |
40 mm |
否 |
|
总体高度(Z) |
45 mm |
否 |
|
板厚 |
6 mm |
否 |
|
安装孔 |
2 × Ø6.5 mm,间距 34 mm,位于 Y = −3 mm |
否 |
|
竖板槽 |
宽 6 mm,端到端长度 20 mm,中心位于 X = 0,Z = 25.5 mm |
否 |
我把孔排与槽都放在各自面的中线处,并采用 6.5 mm 孔径(常见的 M6 间隙孔尺寸),因为真实支架通常如此。这一选择最终成为本支架的薄弱点,我们稍后会解释原因。
在构建函数内,w、dep、h 与 t分别为宽度、深度、高度与厚度。它把两块板连接起来并切出这些特征:
base = cq.Workplane("XY").box(w, dep, t, centered=(True, True, False))
upright = cq.Workplane("XY").center(0, dep / 2 - t / 2).box(w, t, h, centered=(True, True, False))
holes = cq.Workplane("XY").pushPoints([(-17, -3), (17, -3)]).circle(3.25).extrude(t)
slot = (cq.Workplane("XZ", origin=(0, dep / 2 - t / 2, 0)).center(0, 25.5)
.slot2D(20, 6, 90) # end-to-end length, width, angle
.extrude(t, both=True))
result = base.union(upright).cut(holes).cut(slot)
脚本会在导出 STEP 文件前检查是否得到一个尺寸正确的有效实体。
用固定相机渲染四个视图
一个小型 NumPy 渲染器以固定取景绘制正视、俯视、右视与等轴测视图,参考与每个候选都使用相同取景。在 512 像素下,一个像素约对应 0.19 mm,但从图像读数的误差可能超过该值。固定取景还能让过大或放错位置的候选显得“不对劲”,而不是被重新居中。

发送给 Kimi 的四张图片。作者供图。
在运行智能体前确定评测规则
评测器对导出的实体进行测量,因此参数名无法“误导”它。它会切片每个 STEP 文件以找到孔、槽与两处板厚,并在真实尺寸的 0.5 mm 体素网格上计算体素 IoU——两实体公共体积的占比。公差与像素尺度一致:板厚、孔径与槽宽为 ±0.5 mm,从图像读得的长度与位置为 ±1 到 ±1.5 mm。
仅靠 IoU 不够。我认为它是个“钝”指标,但由于没有更简单的方法比较两个实体,我仍在使用它。一个没有孔和槽的纯 L 形仍能拿到 0.96 以上,而把正确零件整体放大 20% 后,只要两者缩放到相同尺寸,IoU 也能是完美的 1.0。
因此这里每个得分都配有 13 项通过/未通过检查。即便 1.0 也只代表在该网格上两实体匹配,而非每微米都一致,这些数字都不构成制造保证。
用 Kimi K3.1 Pro 从图像生成 CadQuery 代码
第一个候选来自一条无工具的请求,并按返回内容原样运行。
向 Kimi K3.1 Pro API 发送四个参考视图
Kimi 的视觉输入在 content 数组中接受 base64 data URL,不接受公共图片 URL。系统提示包含我前述的取景与命名规则,用户消息携带四张 PNG 与如下提示:
These four images show one mechanical part, a bracket. Each view uses orthographic
projection, and all four use the same scale. The camera for each view is written at the
top of the image, and a small axis triad sits in the lower-left corner.
The overall width of the part along X is 60 mm. No other dimension is given, so estimate
the rest from the images.
Write one CadQuery program that rebuilds this part as an editable parametric model.
Return the complete program in a single python code block.
请求本身很简短:
images = [image_part(path) for path in view_paths] # four PNGs as base64 data URLs
messages.append({"role": "user", "content": images + [{"type": "text", "text": BASELINE_PROMPT}]})
response = client.chat.completions.create(
model="kimi-k3.1-pro", reasoning_effort="high",
max_completion_tokens=32_768, messages=messages,
)
要在付费前检查输入大小,可将同一消息发送到token 预估端点,其估算值与账单几乎完全一致。四张 512 像素图像的输入不足 2,000 个 tokens。
在沙箱中运行第一段 CadQuery 程序
运行器把代码写入临时文件夹,并用 docker run 命令启动一次性容器:
cmd = ["docker", "run", "--rm", "--network", "none", "--read-only",
"--tmpfs", "/tmp:size=64m", "--cap-drop", "ALL",
"--security-opt", "no-new-privileges",
"--memory", "1g", "--cpus", "1", "--pids-limit", "128",
"-v", f"{work}:/work", "cad-sandbox:2.8.0"]
subprocess.run(cmd, capture_output=True, text=True, timeout=60)
在第一次试验中,Kimi 的回复用了约一分钟。其程序为隐藏参考中的每个尺寸都命了名,从 bracket_depth = 40.0 到 slot_center_z = 25.5 皆如此。它生成了有效实体并通过所有检查。

同一相机、同一比例、形状匹配。作者供图。
第一次就完美让我检查是否“泄露”了信息,但 PNG 不含元数据,任何提示里也没有隐藏数值。Kimi 的代码解释了结果。正如我在定义支架时提到的,这些特征位于设计师通常会放置的位置,Kimi 亦如此。

Kimi 通过中点推导特征位置。作者供图。
用 Kimi K3.1 Pro 构建自检式工具调用智能体
一个工具调用智能体以循环运行:Kimi 请求调用工具,您的代码执行它,并将结果作为工具消息返回,直到 Kimi 完成。

三个阶段,均有硬性限制。作者供图。
定义用于执行、检查与渲染 CAD 的工具
下面是 execute_cad 的模式。它用固定格式返回简短结果;当程序失败时,包含回溯的末几行:
{"type": "function", "function": {
"name": "execute_cad",
"description": "Run a complete CadQuery program in an isolated sandbox. Returns a "
"candidate id, whether it ran, and a short error if it did not.",
"parameters": {"type": "object", "properties": {
"code": {"type": "string",
"description": "The complete program. It must assign the final solid to result."}},
"required": ["code"]}}}
render_cad 会在工具消息中直接返回四张图片,这与我们Agentic Vision 教程在 Gemini 中采用的“再看一眼”模式相同。Kimi 文档没有这种示例,因此将其视为“按观察到的行为”处理。在三次试验中,role: "tool" 消息都携带了 image_url 部分,且模型能读取。
在工具循环中保留完整的 assistant 消息
这里有三点很重要,前两点来自 Kimi 的思考型模型指南与快速上手:
-
每一轮都回传完整的 assistant 消息,包括
reasoning_content与tool_calls -
每个工具调用添加一条工具消息,并带上对应的
tool_call_id -
用
model_dump()序列化消息,因为 OpenAI SDK 没有类型化的reasoning_content字段
下面的循环遵循了这三点:
message = response.choices[0].message.model_dump(exclude_none=True)
messages.append(message) # all of it, or the next turn quietly loses its reasoning
for call in message.get("tool_calls") or []:
content = run_tool(call["function"]["name"], json.loads(call["function"]["arguments"]))
messages.append({"role": "tool", "tool_call_id": call["id"], "content": content})
不要指望 API 在这里帮您抓错。把历史里的 reasoning_content 移除后,请求仍会成功,尽管文档要求该字段,且删除它可能改变后续轮次的延续方式。相比静默出错,我更希望返回 400。
预算写在循环的代码里,Kimi 无法更改。一旦 3 次修复用尽,execute_cad 将返回错误,而 submit_cad 会拒绝任何非“单一有效实体”的提交。
修复阶段发生了什么
几乎没发生什么——我差点因此删掉本节。我原以为修复循环会承担大部分工作,而 Kimi 几乎没用它。它检查了第一个候选、渲染了图像,写了一段自信的总结称其与四个参考视图匹配,然后提交了最初那份程序。
这段总结并非总是正确。在一次试验中,inspect_cad 报告孔半径为 3.0 mm,即孔径 6.0 mm,而参考为 6.5 mm,Kimi 仍然提交。半毫米在这些视图中不足 3 个像素(我自己肉眼看也不敢肯定),且反馈中没有指出正确直径是多少。

工具报告的半径为 3.0 mm。作者供图。
用 Kimi K3.1 Pro 编辑参数化 CAD 模型
由于修复循环几乎闲置,设计修改成了更能说明问题的测试。尽管这是一次温和的测试,如果一次修改会同时移动两个特征,难度会更大。
让 Kimi 加宽槽
编辑继续在同一会话中进行,并以 Kimi 提交的候选为起点,而非我的参考代码:
Increase the width of the vertical slot in the upright plate from 6 mm to 9 mm.
Keep the slot's center position and its end-to-end length as they are in your model.
Do not change the mounting holes, the plate thickness, or the outer dimensions.
Kimi 使用了 3 次允许程序中的一次。差异只是一行,将 slot_width = 6.0 改成 slot_width = 9.0,随后一轮检查与渲染,再提交。
在导出的几何体上检查编辑
代码里出现新数值并不能证明实体已改变。评测器在编辑前后逐体素比较了两个实体,只忽略允许由更宽槽占据的空间:
- 槽宽为 9 mm,长度与中心不变
- 孔、板厚与外形尺寸不变
- 槽区外没有任何材料变化
每次编辑检查都全部通过。
编辑之所以干净,是因为 slot2D() 将长度视为端到端,因此槽仍为 20 mm 长,而直线段变短。若程序存的是两段圆弧圆心距,槽就会长出 3 mm。

只有槽发生了变化。作者供图。
Kimi K3.1 Pro CAD 智能体有多准?
在三次试验中,智能体都在我预先设定的公差内通过了所有隐藏检查。下表作为回顾:
|
检查项 |
三次试验结果 |
|
隐藏几何检查通过数 |
每次均为 13/13 |
|
与参考的体素 IoU(0.5 mm 网格) |
0.998 至 1.0 |
|
孔径(真实值 6.5 mm) |
两次为 6.5 mm,一次为 6.0 mm(Kimi 的复核放行了) |
|
已用修复次数(允许 3 次) |
0 |
|
编辑检查通过数 |
每次均为 8/8 |
三次试验不构成成功率,只说明在一个支架上结果具有稳定性。修复阶段出现的 6.0 mm 孔之所以通过,仅因为刚好卡在 ±0.5 mm 孔径公差上;若更严格,那次会失败。
自我纠错无事可做
要验证循环是否能改进模型,应进行“故障注入”测试,即从一个带已知错误的程序启动智能体。这与本次试验分开报告为宜。
每次试验的 token 成本与运行时间
每次试验包含 6 次请求与 7 次工具调用,费用为 6.3 至 7.3 美分。这里仅计 API 费用,渲染与 Docker 在我的机器上运行。
可按 usage 字段与 公布费率为 kimi-k3.1-pro 逐条计费。每百万 tokens:输入 $2.00,输出 $10.00,缓存命中 $0.20,5 分钟缓存写入 $2.00。
|
阶段 |
请求数 |
成本占比 |
|
首次请求 |
1 |
约一半 |
|
修复复核 |
2 |
约四分之一 |
|
编辑 |
3 |
约四分之一 |
首次请求成本最高,因为 Kimi 在这一轮进行推理。输出占账单过半,缓存写入占 23% 到 35%,而缓存命中几乎不花钱。以下展示三个输入字段如何相加,依据我先前链接的 Chat Completions 参考;如果您的追踪器已拆分缓存写入,可跳过。
uncached = u.prompt_tokens - cached - written # the three parts sum to prompt_tokens
cost = (uncached * 2.00 + cached * 0.20 + written * 2.00 + u.completion_tokens * 10.00) / 1e6
推理 tokens 已计入 completion_tokens,不要重复计费。5 分钟缓存写入与未缓存输入同价,因此提示缓存只有在前缀被读回时才划算。工具循环每轮都会重发同一历史,这里缓存把输入费用降到原价的 40% 以下。
一次试验约耗时三分钟,主要在等待,因为客户端按我将在 FAQ 中说明的限速,每隔 21 秒发送一次请求。
基于图像的 AI CAD 生成有哪些不足
主要限制是图像无法承载所有尺寸信息。我的测试还增加了几点:
- 渲染无法展示小于一个像素的细节。如果某个尺寸重要,请用文本给出或用工具测量。
- 该支架容易猜。若孔不在中线、或尺寸怪异,就能测试 Kimi 是在读像素还是在套设计习惯。
- Docker 标志只能拦截它们指明的内容。它们确实拦下了“设置”小节中的异常程序,但模型代码仍可能用满整整一分钟 CPU。我们的容器中保障 AI 智能体安全指南介绍了下一层防护。
- 通过这些检查并不代表强度合格。载荷、材料、真实零件所需的圆角内角、螺栓配合以及公差叠加等均不在本测试范围内。
“易”这个特性更多是我测试的限制,而非 Kimi 的限制。
结语
我们构建了一个智能体:它能从四张图像编写参数化 CadQuery 代码、用工具检查其实体,并按要求编辑一个特征。对于这个支架,我开篇问题的答案是肯定的。三次试验均在首次请求内通过我设定的公差,而槽的编辑没有影响到零件的其他部分。
我无法为修复循环作背书,因为 Kimi 从未发现需要修复的内容。那次 0.5 mm 的误差之所以被它的复核放行,是因为它看到的是像素、而非真实直径。我不建议将结果泛化到尺寸怪异的零件,或真实照片。
值得借鉴的是方法论:隐藏参考,在第一次请求前冻结评分规则,并对导出的实体而非代码评分。Kimi 三次都称其候选匹配,只有评测器能判断两次“正确”、一次“接近”。我会保留这个评测器,而“故障注入”将是我首先要做的扩展。
若要进一步了解如何把模型连接到您自己的工具,我们的Developing AI Systems with the OpenAI API 课程涵盖了与本智能体相同 SDK 的函数调用。
常见问题解答
Kimi K3.1 Pro API 支持哪些图像格式与尺寸?
以 base64 data URL 发送 PNG、JPEG、GIF、WebP、BMP、HEIC 与 HEIF 均可,SVG 即便编码也会被拒绝,因此请把 SVG 图纸的 XML 作为文本发送。单个请求可携带任意数量的图像(总大小不超过 100 MB),我之前链接的视觉指南建议分辨率不超过 4096×2160。我建议避免发送动图 GIF 与 WebP,Kimi 可能会将其解码并按视频计费。
Kimi 的 3D Make 能替代这样的 CAD 智能体吗?
就发布博文来看,并不适合参数化工作。3D Make 位于 Kimi 应用与网站中,您可在其中编辑模型并下载以供 3D 打印。该博文未提及任何 API 访问或 STEP 导出,因此若要能更改 slot_width ,仍需要您自建工具循环。
为什么我的 Kimi K3.1 Pro 智能体会遇到 HTTP 429 错误?
先看 error.type,因为 Kimi 将 429 用于三种不同问题。rate_limit_reached_error 表示超过了您所在档位的限速,该限速从充值 1 美元时每分钟 3 次请求,提升到 10 美元时的每分钟 200 次。exceeded_current_quota_error 表示余额用尽,而 engine_overloaded_error 表示服务器繁忙,应退避重试,而非充值。
如何降低 Kimi K3.1 Pro CAD 智能体的成本?
默认 5 分钟缓存已适合此循环,因为每次命中都会免费续期,而我的请求间隔为 21 秒。若人工在轮次间审阅渲染内容、间隔可能超过 5 分钟,可参考上下文缓存指南将 TTL 设为 1 小时;其每百万 tokens 4 美元的写入成本,在两次命中后就能回本。若担心成本,我会跳过 Highspeed,因为它将价格翻倍,速度仅提升 2 到 3 倍。
CAD 重建应使用 high 还是 max 的推理强度?
建议从 high 开始,我在全文中都采用了它。若您尝试 max (我在本支架上未尝试),请开启流式响应,因为当非流式请求 900 秒内无输出时,网关会返回 504。完成上限默认为 131,072 个 tokens,包含推理,我设置的 32,768 上限可将单次回复的输出成本控制在约 33 美分以内。

我是一名数据工程师兼社区建设者,专注于数据管道、云端与 AI 工具链,同时为 DataCamp 和新兴开发者撰写实用且高影响力的教程。
假设您有四张金属安装支架的照片——那种用于把搁板固定到墙上的 L 形支架——但没有对应的计算机辅助设计(CAD)文件。工程师之所以编辑 CAD 文件并将其发送到加工车间,是因为 CAD 文件能存储精确尺寸。您可以在 CAD 文件里移动一个孔或加宽一个槽,仍然能得到尺寸正确的零件;而仅凭图片做不到。
您需要的是一段简短的 Python 程序,基于已命名的尺寸(如
hole_diameter = 6.5)构建该支架,并将其保存为 STEP 文件——CAD 工具通用的标准格式。Moonshot 的技术博客称 Kimi K3.1 Pro 擅长此类空间任务,于是我做了一个测试:Kimi K3.1 Pro 能否把四张图片变成可编辑的 CAD 模型、用工具自检其结果、并在不破坏其他部分的前提下更改一个特征?为公平起见,我用 CadQuery(一个用于构建 CAD 模型的 Python 库)亲自做了支架。我用它渲染了四张图片,并把原始 CAD 文件隔离,不让 Kimi 接触。在这篇 Kimi K3.1 Pro API 教程里,Kimi 将根据这些图片编写程序,而我的代码会运行它并测量其构建结果。
您不需要 CAD 背景也能跟上进度,但代码假定您熟悉 Python 和 Docker;几何细节出现时我会解释。我们将涵盖:
我们的Kimi K3.1 Pro 概览介绍了发布信息、基准测试、定价与访问方式,这里我将专注于智能体。
要点速览
我用
reasoning_effort="high"设置,让 Kimi K3.1 Pro 三次把四张金属支架图片转成可编辑 3D 模型;除了一处小差异,三次结果一致: