
1. 小程序后端拿不到文案问题通常不在模型而在 Key 放错位置个人小程序想在微信生态里被 AI 调用第一道坎往往不是模型能力而是 Key 放在哪。很多人在小程序onLoad里直接wx.request打大模型接口本地调试通了真机一测先是「request 合法域名校验失败」把域名加进后台白名单后又发现前端包里的 Key 只要反编译一次就暴露。更稳的最小路径其实只有一条小程序前端不碰 Key请求先给自己的云函数或自建后端由后端带着 Key 去请求模型。这篇就用「文案生成」这个最典型的场景把这条链路拆成可以直接照做的步骤。如果你还没有可用的 Key先去TaoToken官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentwx-miniapp-copy-key-1 注册并在控制台创建一个后面所有的请求地址都会统一用https://taotoken.net/api这个 Base URL。整条链路可以压成四步在 TaoToken 官网拿到 Key存进云函数环境变量或后端配置中心绝不写进小程序代码小程序页面只负责收集「商品名、卖点、语气、字数」这类结构化字段wx.cloud.callFunction调用自己的云函数云函数读取环境变量里的 Key向https://taotoken.net/api发一次对话请求拿回结构化文案云函数把结果写进日志含 usage把干净的 JSON 返回给小程序渲染。之所以强调「后端拿 Key」是因为小程序前端本质上是一个会被下发到用户手机上的 JS 包。你写在里面的任何字符串用微信开发者工具的反编译或抓包工具都能看到甚至不用反编译代理抓包就能看到Authorization头。文案生成这种功能一旦被人扒到 Key别人可以拿你的额度刷任意内容账号被封是小事账单才是大事。第二道坎是网络可达性。小程序的wx.request走的是微信自己的域名校验体系你需要在 mp 后台把域名加进 request 合法域名而且要求 HTTPS 已备案。把模型服务域名直接加进去运营上很麻烦而云函数或你自己的服务器作为唯一出口只需要管好一个出口以后换供应商也不用再动小程序后台配置。这就是「接入微信生态最小步骤」的真正含义不是把模型塞进小程序而是让小程序只认识自己的后端。2. 在 TaoToken 官网完成注册与创建 Key这一步和普通的开发者平台没区别但顺序要对否则容易在后面调试时把「Key 没生效」误判成「代码写错了」。打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentwx-miniapp-copy-key-2 注册并登录进入控制台。控制台里你需要关注三个位置API Keys 页面https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentwx-miniapp-copy-key-2 在这里新建一个 Key命名建议带上用途比如miniapp-copy-prod方便日后按项目回收模型列表确认你要用于文案生成的模型 ID 是什么文案场景通常不需要最贵的模型先选一个性价比高的跑通链路调用示例页面会给出该 Key 对应的调用方式和 Base URL本文统一用https://taotoken.net/api实际路径以控制台展示为准。拿到 Key 之后第一件事不是写代码而是先在本地用一条 curl 验收确认 Key 和 Base URL 的组合是通的curl -s https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer YOUR_API_KEY \ -d { model: 你的模型ID, messages: [ {role: system, content: 你是一个小程序文案助手只输出 JSON。}, {role: user, content: 为「手冲咖啡挂耳包」写 3 条朋友圈文案每条不超过 30 字。} ], temperature: 0.7 }如果返回的是带choices的 JSON说明链路没问题可以开始往下接。如果返回 401八成是 Key 复制时带了空格返回 404通常是路径写成了/chat/completions而漏了/v1或者 Base URL 末尾多了一个斜杠。这些错误在小程序里会被wx.cloud.callFunction的错误包装吞掉很难定位所以务必先在命令行跑通。3. 云函数侧把 Key 放进环境变量而不是源码接下来写小程序侧的调用。这里用微信云开发的云函数举例自建后端Express、Flask、FastAPI逻辑完全一样只是把入口换成 HTTP 路由。先在云函数目录里初始化# 在小程序项目根目录 mkdir -p cloudfunctions/genCopy cd cloudfunctions/genCopy npm init -y npm install wx-server-sdk云函数的入口文件注意 Key 是从环境变量读取的// cloudfunctions/genCopy/index.js const cloud require(wx-server-sdk) cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) // 在云开发控制台 - 云函数 - 配置 - 环境变量 中添加 // TAOTOKEN_API_KEY YOUR_API_KEY const API_KEY process.env.TAOTOKEN_API_KEY const BASE_URL https://taotoken.net/api exports.main async (event) { const { productName, sellingPoints [], tone 口语化, count 3 } event if (!productName) { return { ok: false, code: PARAM_MISSING, msg: productName 不能为空 } } if (!API_KEY) { return { ok: false, code: KEY_MISSING, msg: 请在云函数环境变量中配置 TAOTOKEN_API_KEY } } const userPrompt [ 商品名称${productName}, 核心卖点${sellingPoints.join(、) || 无}, 语气要求${tone}, 生成数量${count} 条, 每条不超过 40 个汉字不要出现绝对化用词不要编造功效。 ].join(\n) const startedAt Date.now() const resp await fetch(${BASE_URL}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${API_KEY} }, body: JSON.stringify({ model: process.env.TAOTOKEN_MODEL || 你的模型ID, messages: [ { role: system, content: SYSTEM_PROMPT }, { role: user, content: userPrompt } ], temperature: 0.7, max_tokens: 600, response_format: { type: json_object } }) }) const costMs Date.now() - startedAt const raw await resp.text() if (!resp.ok) { console.error([genCopy] upstream error, resp.status, raw.slice(0, 500)) return { ok: false, code: UPSTREAM_${resp.status}, msg: 文案服务暂时不可用 } } let payload try { payload JSON.parse(raw) } catch (e) { console.error([genCopy] bad json, raw.slice(0, 500)) return { ok: false, code: BAD_JSON, msg: 返回内容不是合法 JSON } } const usage payload.usage || {} const content payload.choices?.[0]?.message?.content || // Token 日志排查成本和限流的第一手依据 console.log(JSON.stringify({ tag: genCopy, model: payload.model, prompt_tokens: usage.prompt_tokens, completion_tokens: usage.completion_tokens, total_tokens: usage.total_tokens, cost_ms: costMs, product: productName })) let parsed try { parsed JSON.parse(content) } catch (e) { return { ok: false, code: MODEL_NOT_JSON, msg: 模型未按格式返回, raw: content.slice(0, 200) } } return { ok: true, data: parsed, usage } }这份代码里有三个关键设计值得单独说明。第一API_KEY只在云端出现。小程序端调用时传的是业务参数不是凭证。哪怕攻击者反编译了小程序包他也只能看到云函数的调用名拿不到 Key。第二所有上游错误都被收敛成了业务错误码。小程序端只需要根据code展示不同的提示文案不需要理解 HTTP 状态码。UPSTREAM_401意味着 Key 失效或被回收UPSTREAM_429意味着触发了限流这两类问题处理方式完全不同。第三console.log里专门打了 usage。这是最容易被忽略、但后期最救命的一段代码。没有 Token 日志你无法回答「为什么这个月消耗突然涨了」这种问题。4. 小程序端只收集参数、只渲染结果页面侧的代码要做的事非常少。核心是拒绝诱惑不要在onLoad里内联任何请求逻辑全部走云函数。// pages/copy/index.js Page({ data: { productName: , sellingPoints: , tone: 口语化, loading: false, copies: [] }, onInput(e) { this.setData({ [e.currentTarget.dataset.field]: e.detail.value }) }, async onGenerate() { if (this.data.loading) return if (!this.data.productName.trim()) { wx.showToast({ title: 请先填写商品名, icon: none }) return } this.setData({ loading: true }) try { const res await wx.cloud.callFunction({ name: genCopy, data: { productName: this.data.productName.trim(), sellingPoints: this.data.sellingPoints.split(/[,、\n]/).filter(Boolean), tone: this.data.tone, count: 3 } }) const result res.result || {} if (!result.ok) { wx.showToast({ title: result.msg || 生成失败, icon: none }) return } this.setData({ copies: result.data.copies || [] }) } catch (err) { // 云函数调用失败网络、超时、函数未部署 console.error([callFunction] fail, err) wx.showToast({ title: 网络异常请稍后重试, icon: none }) } finally { this.setData({ loading: false }) } } })有一个细节经常踩wx.cloud.callFunction的默认超时时间不宽裕而文案生成属于「用户可感知的等待」一旦模型排队很容易触发超时。处理方式有两条一是把云函数超时时间在控制台里调到 20 秒以上二是在小程序端加wx.showLoading并考虑做流式或异步任务。对个人小程序来说先把超时调到 20 秒体验就能从「经常失败」变成「慢但可用」。另外给按钮加防重复提交是必须的。loading标志看起来土但能挡住至少一半的重复计费。5. 文案提示词模板把「好看」变成「可解析」文案生成的提示词如果只写「帮我写几条文案」返回结果会随模型心情变化今天给你编号列表明天给你加一段「希望这些文案对你有帮助」。小程序要渲染就必须让输出结构化。下面这套模板在个人小程序场景里比较稳可以直接改字段复用# System 你是小程序内的文案生成器。你的唯一任务是把输入的商品信息转换成可直接展示的短文案。 硬性规则 1. 只输出 JSON不要任何解释、前后缀、Markdown 代码块标记。 2. JSON 结构固定为{copies: [{title: string, body: string, tags: string[]}]} 3. title 不超过 12 个汉字body 不超过 40 个汉字。 4. tags 输出 2-4 个不带 # 号。 5. 禁止出现「最」「第一」「国家级」「治疗」「百分百」等违规或绝对化表述。 6. 禁止编造商品不具备的功效禁止出现具体价格与折扣。 # User 商品名称{{productName}} 核心卖点{{sellingPoints}} 语气{{tone}} 数量{{count}} # 输出示例 {copies:[{title:一杯醒神,body:挂耳现磨办公室三分钟出杯。,tags:[手冲,挂耳咖啡,办公提神]}]}几个实践过的调整点给出输出示例few-shot比只写规则有效得多。模型会模仿示例的字段命名和长度返回格式的稳定性明显提升。response_format: {type: json_object}要配上。但它只是「强约束」不能保证百分之百合规所以后端仍然必须 try/catch 一次JSON.parse解析失败时给用户降级文案而不是直接报错白屏。合规词表要显式写进系统提示。小程序是审核场景文案里出现绝对化用语被打回的时候你不可能逐条去猜是哪句。要数量不如要质量。一次要 3 条、max_tokens给 600比一次要 10 条、返回被截断要划算得多。截断会直接导致 JSON 解析失败。想给用户更多选择就再加一次生成而不是拉长单次输出。如果业务上有多种文案风格不要去改系统提示而是把风格当成参数注入 user 提示例如把「语气口语化」换成「语气小红书种草风多用短句和emoji」。这样系统提示保持稳定返回结构不会因为风格切换而漂移。6. Token 日志从「能不能用」到「能不能长期用」文案生成跑通之后真正决定这个功能能不能留在线上的是成本可控性。你需要一条能按调用查看记录的链路最简单的方式就是前面那段console.log。建议在日志里固定保留这些字段字段作用排查什么问题model实际命中的模型 ID配置写错、被降级到别的模型prompt_tokens输入长度提示词是不是越来越长completion_tokens输出长度是否存在被截断total_tokens单次消耗估算单用户成本cost_ms端到端耗时用户等待体验、超时风险product业务标识哪个功能在烧 Token有了这张表两件事会变得很清楚。第一如果某天prompt_tokens突然翻了几倍多半是有人把提示词模板改长了或者把历史对话全量塞进了上下文。第二如果completion_tokens长期贴着max_tokens上限说明你在持续为被截断的输出付费应该回头收紧长度要求。在云开发控制台的日志里按tag: genCopy过滤就能得到一张按时间排序的调用清单。个人小程序早期的量不大这套纯文本日志足够用等量起来之后再把同样的字段写到自己的日志服务里做聚合。还有一个容易被忽略的点限流要从日志里提前发现而不是等用户投诉。如果日志里开始密集出现UPSTREAM_429说明瞬时并发超过了额度这时候与其去调大重试不如在小程序端加一层队列或者把生成动作从「点一下生成一次」改成「每天免费 N 次」。7. 顺带把你本地的调试工具也换成同一个入口文案后端调通之后很多人会发现一个尴尬情况后端用的是 TaoToken而本地写代码时用的还是另一套配置两边模型行为不一致调提示词的时候来回对不上。把本地工具统一到同一个 Base URL能省掉大量「为什么线上和本地不一样」的排查。Claude Code走的是settings.json通过环境变量注入{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: 你的模型ID } }配置文件放在项目根目录的.claude/settings.json或者用户级的~/.claude/settings.json。改完之后重启会话用/status一类的命令确认当前生效的 Base URL。完整说明可以看 https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentwx-miniapp-copy-key-3 。Codex走的是config.toml和上一条完全是两套体系不要混用model 你的模型ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api/v1 env_key TAOTOKEN_API_KEY wire_api chat这里最容易犯的错就是把ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN这套变量名配到 Codex 上。Codex 读的是config.toml里的model_providers段和env_key指定的环境变量变量名对不上它会一声不吭地回落到默认供应商你在终端里看到的报错和配置毫无关系很容易怀疑到 Key 头上。简单记Claude Code 认ANTHROPIC_*Codex 认config.toml。如果你同时用多个工具、多个供应商来回改配置文件很烦可以用 CC Switch 这类切换工具统一管理。它需要维护的其实就是「三件套」Base URL统一https://taotoken.net/apiCodex 的base_url按控制台示例可带/v1API Key填YOUR_API_KEY对应的真实 Key模型 ID从模型列表里选文案场景和代码场景可以分开指定。三件套对齐之后再从切换器里切回默认配置就能立刻判断某个问题到底出在工具端还是服务端。这比在聊天里反复猜「是不是额度没了」高效得多。8. 上线前的排障清单把下面这张表过一遍基本能覆盖个人小程序接入文案生成时会遇到的大部分问题。「request 合法域名校验失败」—— 你的请求没有走云函数而是在小程序端直接wx.request。改法是把请求移到云函数里小程序只callFunction。「云函数执行超时」—— 在控制台把genCopy的超时时间调到 20 秒以上同时在前端加wx.showLoading。如果还是超时检查是不是max_tokens给得太大导致输出很长。「返回 401 / KEY_MISSING」—— 环境变量没配置或者配置在了错误的函数上。云函数的环境变量是函数级的新建的副本不会自动继承。「返回 429」—— 瞬时并发过高。加客户端队列或降低单用户频率不要盲目加重试重试会放大并发。「MODEL_NOT_JSON」—— 模型没有严格按格式输出。检查系统提示里的 JSON 结构描述和示例是否一致必要时在 user 提示末尾再强调一次「只输出 JSON」。「文案里出现违规词」—— 在系统提示中显式列出禁用词类别并在后端做一次关键词兜底过滤命中就替换或重新生成一次。「用户连点按钮产生多条记录」—— 前端loading标志 后端幂等键两者都要有。「线上和本地效果不一致」—— 先确认两边命中的model是否相同再看temperature是否一致。日志里的model字段就是为了这个存在的。9. 把这条链路固定成模板回到最初的问题个人小程序如何被微信 AI 调用最小步骤到底是什么。答案不是某个神奇插件而是一条职责清晰的链路——小程序负责交互云函数负责持 Key 和转发https://taotoken.net/api负责出文案日志负责让你知道花了多少钱。这条链路一旦跑通后面换模型、加风格、做多语言都只是改提示词和参数不再需要动小程序结构。如果你还没开始建议按这个顺序走一遍先去 https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentwx-miniapp-copy-key-4 直接试一次对话确认返回格式是你想要的再考虑 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentwx-miniapp-copy-key-4 这类适合持续调用的方案然后在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentwx-miniapp-copy-key-4 创建正式 Key填进云函数环境变量最后照着 https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentwx-miniapp-copy-key-4 把本地调试工具也对齐到同一个入口。每一步都验收通过再进下一步比一次性全配好再排错要快得多。