返回博客

API 与 SDK指南

用 API 从一条提示词生成 HTML 工具(Python)

一个 Python 脚本,将一条提示词变成可用的 tool.html,通过 finish_reason 捕捉被截断的回答,并打印 token 和成本。

作者:CheapTokens Team发布于 6 分钟阅读

一段检查 finish_reason 并将 tool.html 保存在标题“一条提示词,一个 HTML 工具”旁边的代码片段
本页内容
  1. “给我构建一个工具”的提示词需要什么
  2. 脚本:请求、检查、保存
  3. 为什么 max_tokens 决定页面能否工作
  4. 打开并查看页面
  5. 一次生成的成本
  6. 如果它不工作

你可以通过一次 API 调用得到一个可用的单文件工具:发送一个结构良好的提示词,确保 max_tokens 足够大以容纳整个页面,检查 finish_reason 是否为 stop,并将回复保存为 tool.html。下面是实现这一点的脚本,以及一个你可以为自己的工具改造的提示词模板。

思路很简单,DeepSeek 刚刚展示了它。它发布了一组用于 DeepSeek Harness 中 Creator Mode 的七个演示提示词(该页面,检查于 2026 年 11 月 3 日):一个 3D 地标画廊、一个海报工作台、一个视频编辑工作台、一个论文阅读器、一个对话统计面板以及另外几个。Creator Mode 位于 Harness 应用内部。如果你想在自己的脚本中、用自己的密钥实现同样的“输入提示词,输出工具”流程,对于单文件场景,普通的聊天补全就足够了。如果你确实使用 Harness 本身,将其连接到 CheapTokens 是一份单独的简短指南。

“给我构建一个工具”的提示词需要什么#

读几个这样的演示提示词,一个模式就会显现出来。每一个都说明了工具是什么、屏幕上有什么、控件做什么、初始状态是什么,以及它不能做什么。跳过其中任何一项,模型就会用自己的猜测填补空白,而那通常不是你的意图。

下面是模板,共五个部分:

text
What it is: <one sentence: the tool and who uses it>
Layout: <what is on the screen, top to bottom>
Controls: <every input and button, and what each one does>
Defaults: <what the page shows before the user touches anything>
Constraints: <single HTML file, no external requests, no libraries, size of the page>

以及一个填写好的版本。假设你经常换算烹饪计量单位,并且厌倦了搜索转换器:

text
What it is: a unit converter for cooking, for someone standing in a kitchen with a phone.
Layout: a title, one number field, two dropdowns (from, to), a large result line under them.
Controls: the dropdowns hold cup, tablespoon, teaspoon, ml, oz, g (flour) and g (sugar). Typing a number updates the result immediately. A swap button exchanges the two dropdowns.
Defaults: 1 cup to ml, result already shown.
Constraints: one HTML file with inline CSS and JavaScript, no external libraries, fonts or network requests, works on a narrow phone screen, dark text on a light background.

将其保存为 prompt.txt,放在脚本旁边。“Constraints”这一行承担了大部分重量:如果没有“no external requests”,模型喜欢引入一个 CDN 库,然后页面在离线时就会出错。

前后对比:一行提示词与同一提示词分成五个部分
同一请求写成一行和分成五个部分:它是什么、布局、控件、默认值、约束

脚本:请求、检查、保存#

CheapTokens API 在 https://api.cheaptokens.dev/v1 处兼容 OpenAI Chat Completions,因此 openai 包只需两处更改即可使用:基础 URL 和密钥。对于整个页面,我们使用 deepseek-ai/DeepSeek-V4-Flash-0731:它每次回答最多允许 32,768 个输出 token,并具有 380K 上下文窗口。完整的 API 详情见 CheapTokens 文档。

安装该包,将你的密钥放入环境变量,并从价格表设置两个价格(脚本用它们来打印成本):

bash
pip install openai
export CHEAPTOKENS_API_KEY="your-key"
export PRICE_IN_PER_M="<input price per 1M from the table>"
export PRICE_OUT_PER_M="<output price per 1M from the table>"

然后是 make_tool.py:

python
import os
import re
import sys

from openai import OpenAI

MODEL = 'deepseek-ai/DeepSeek-V4-Flash-0731'
PRICE_IN = float(os.environ['PRICE_IN_PER_M'])    # USD per 1M input tokens (from the table)
PRICE_OUT = float(os.environ['PRICE_OUT_PER_M'])  # USD per 1M output tokens (from the table)
FENCE = '`' * 3

client = OpenAI(
    base_url='https://api.cheaptokens.dev/v1',
    api_key=os.environ['CHEAPTOKENS_API_KEY'],
)

prompt = open('prompt.txt', encoding='utf-8').read()

resp = client.chat.completions.create(
    model=MODEL,
    max_tokens=16000,
    messages=[
        {'role': 'system', 'content': 'You write one self-contained HTML file. Reply with the file only, no commentary.'},
        {'role': 'user', 'content': prompt},
    ],
)

choice = resp.choices[0]
if choice.finish_reason == 'length':
    sys.exit('Cut off at max_tokens: the page is unfinished. Raise max_tokens or shrink the request.')

text = choice.message.content or ''
m = re.search(FENCE + r'(?:html)?\s*(.*?)' + FENCE, text, re.DOTALL)
html = (m.group(1) if m else text).strip()
if '</html>' not in html.lower():
    sys.exit('The reply has no closing </html>; not saving it.')

with open('tool.html', 'w', encoding='utf-8') as f:
    f.write(html)

u = resp.usage
cost = (u.prompt_tokens * PRICE_IN + u.completion_tokens * PRICE_OUT) / 1_000_000
print(f'saved tool.html, {u.prompt_tokens} in / {u.completion_tokens} out, about ${cost:.6f}')

步骤的顺序很重要,而且很容易弄错:

排序

将脚本的步骤按运行顺序排列

  1. 用宽松的 max_tokens 发送提示词
  2. 在触碰文本之前检查 finish_reason
  3. 如果有代码围栏,从代码围栏中提取 HTML
  4. 确认结尾的 </html> 存在
  5. 保存 tool.html 并打印用量
流水线:提示词、API 调用、finish_reason 检查、tool.html
脚本做了什么,从提示词到保存的文件

为什么 max_tokens 决定页面能否工作#

一个完整的交互页面是一个很长的回答。HTML、CSS 和 JavaScript 加起来很快,而模型按这个顺序编写它们:如果回答被截断,你首先丢失的是脚本的结尾,而这恰恰是让按钮工作的部分。浏览器可能仍会打开页面。它只是什么都不做。

这就是为什么脚本在查看文本之前先检查 finish_reason。stop 表示模型自行完成。length 表示它在文件中间撞到了 max_tokens 上限。这个问题的通用版本在 AI 回答被截断?修复 finish_reason length 中有所介绍;这里的规则很简单:永远不要保存 finish_reason 为 length 的页面。

如果你确实看到了它:

  • 提高 max_tokens(DeepSeek-V4-Flash 每次回答最多允许 32,768);
  • 或者要求更少:更少的控件、一个屏幕、没有动画;
  • 在 GLM-5.3-Flash(zai-org/GLM-5.3-Flash)上,上限是 8,192 个输出 token,因此该模型适合小型工具,例如上面的转换器。

第二项检查,即文本中的 </html>,会捕捉更隐蔽的失败:模型礼貌地结束回复,却没有完成文件。设置 max_tokens 也不会让模型写更多:它只是一个上限,你只为实际产生的 token 付费(更多内容见削减输出 token)。

快速检查

脚本打印了 finish_reason = length。你该怎么办?

显示答案

答案: 不要保存它;提高 max_tokens 或缩小请求

length 表示回答在上限处被截断,因此脚本的结尾缺失。更低的上限会让截断来得更早。

打开并查看页面#

双击打开 tool.html,或者从终端打开:macOS 上使用 open tool.html,Linux 上使用 xdg-open tool.html,Windows 上使用 start tool.html。

这是模型为你编写的代码,所以在信任它之前花一分钟。打开浏览器的开发者工具(F12),查看 Network 标签并重新加载:在“no external requests”约束下,它应该保持为空。然后从提示词的“Controls”部分逐个点击每个控件一次。

如果有问题,不要从头重新生成。在提示词中针对具体问题添加一句话(“the swap button must also swap the two values”),然后再次运行。DeepSeek 自己的页面指出,它的一些演示经过了几轮对话的完善,因此第二遍是正常的。

自己试试试一试:对于转换器,你期望在 Network 标签中看到什么?显示答案

什么都没有:页面是一个单文件,CSS 和 JavaScript 都内联,因此首次加载后没有进一步的请求。如果你看到对 CDN 的请求,说明模型忽略了约束;用大写将其添加到提示词中并再次运行。

一次生成的成本#

假设提示词大约 600 个输入 token,完成的页面大约 6,000 个输出 token(这是对中型工具的假设;你自己的 usage 行会显示真实数字)。在 DeepSeek-V4-Flash 上运行一次是 $0.00061,在 GLM-5.3-Flash 上是 $0.00025。两者都是当前价格表中的实时数字,并且总的来说,将输入和输出加在一起计算,两者都比 OpenRouter 便宜。

模型 费用 OpenRouter
GLM-5.3-Flash $0.00025 $0.00052
DeepSeek-V4-Flash $0.00061 $0.00088
MiniMax-M2.7 $0.00093 $0.0112

总计 600 个输入 token 和 6,000 个输出 token。每 100 万 token 的价格为当前价格,会随我们的资费一同变动;OpenRouter 列显示相同工作负载的参考价格。

在实践中,你不会第一次就得到正确的工具,所以要以迭代的思维来考虑:对提示词做十次调整就是那一行的十倍,对于你本来要手写的工具来说仍然是一笔小数目。几乎全部成本都在输出侧,即页面本身,这就是为什么缩小要求(更少的控件)会缩短回答和账单。

如果它不工作#

你看到什么 可能原因 该怎么做
来自 API 的 401 或 404 密钥、基础 URL 或模型 ID 错误 检查这三项,见 API 401 或 404 错误
Cut off at max_tokens 页面长度超过上限 提高 max_tokens 或要求更少
The reply has no closing </html> 模型提前停止或添加了文本 再次运行;在提示词中添加“end with </html>”
页面打开但按钮无反应 JavaScript 错误 打开控制台(F12),将红色错误粘贴到后续提示词中

如果你希望将模型的回复解析为数据而不是页面,提取技巧与 LLM 回复上的 json.JSONDecodeError 中相同。刚接触 API?从你的第一个 API 请求开始。