☰
使用 Claude Skills 构建专业级 AI Agent:TaoToken 统一 Key 接入与 Server Actions 实战
2026/10/3 16:13:10 网站建设 项目流程

1. 为什么要在 Next.js 里用 Claude Skills 搭 AI Agent

Claude Skills 是一套基于文件系统的可复用能力包,它把「领域知识 + 标准流程 + 可执行脚本」打包成一个目录,让模型在需要时自动加载。放到 Next.js + TypeScript 项目里,它解决的是一个很具体的工程问题:AI Agent 的提示词散落在各个 Server Actions 里,改一处漏一处,新人接手根本不知道业务规则藏在哪。

我试过把校验逻辑、鉴权顺序、返回结构全塞进一个 800 行的 prompt 字符串,结果就是每次加字段都要重新调一遍,模型还经常漏掉 Zod 校验。Claude Skills 的思路是把这些规则外置成SKILL.md加脚本文件,Server Actions 只负责调用,规则由 Skill 统一维护。

这套方案适合谁?三类人最明显。第一类是正在用 Next.js App Router 做全栈项目、已经写了十几个 Server Actions 的开发者,规则重复到想吐。第二类是想把 AI 能力接进内部系统、但不想让业务逻辑和模型调用耦合在一起的团队。第三类是已经在用 Claude Code 或 Cline 做日常编码,想把项目规范沉淀下来的人。

核心检索词先明确:Claude Skills 是持久化、自动触发、支持代码执行的智能模块,和一次性 Prompt 的区别在于它按需渐进加载。元数据约 100 tokens 常驻,指令在触发时加载,资源文件用到才读,所以不会一上来就吃掉上下文窗口。

在 Next.js 场景下,Agent 的调用点通常是 Server Actions。用户提交表单,Action 里做鉴权、校验、调模型、写库、返回结果。如果每个 Action 都自己拼 prompt,维护成本会指数上升。把「怎么调模型、用什么模型、返回什么结构」抽成 Skill,Action 只关心业务输入输出,这才是可维护的路径。

而模型调用这一层,我用 TaoToken 做统一 Key 和 API 通道。原因是 Skills 本身不绑定供应商,但 Server Actions 里总得有个 base URL 和 key。TaoToken 提供兼容 Anthropic 风格的接口,把鉴权和路由收口到一处,换模型或加通道时不用改业务代码。官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,注意 API 地址不带 UTM 参数。

下面按「目录结构 → 环境变量 → Server Action 调用 → 本地验证 → 排障」的顺序走一遍,每一步都能直接复制。

2. TaoToken 前置准备:Key、Base URL 与 Skills 目录结构

在写 Server Action 之前,先把两件事定下来:模型通道的凭证,以及 Skills 在项目里的存放位置。这两件事没定,后面代码会反复改。

先说 TaoToken 这边。进入控制台创建 API Key,路径是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。创建后你会拿到一串 key,形如sk-开头。这个 key 只显示一次,复制到本地.env.local。如果你还没决定用哪个模型,可以先去模型对话页 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 试一下返回格式,确认通道通了再写代码。

Base URL 统一用https://taotoken.net/api。注意这里不加任何查询参数,鉴权靠x-api-key或Authorization头。模型 ID 按你实际选的填,比如claude-sonnet-4-5这类。三个要素记牢:Base URL、Key、Model ID,后面配置文件里三件套缺一不可。

然后是 Skills 目录。Claude Code 默认从~/.claude/skills/读取,但项目级 Skill 更适合放在仓库里,方便 Git 共享。我推荐的结构是项目根目录下建skills/,每个 Skill 一个子目录,目录名用 kebab-case。这样团队成员 clone 下来就能用,不需要每人手动配。

一个可复制的目录长这样:

my-nextjs-app/ ├── app/ │ ├── api/ │ │ └── agent/ │ │ └── route.ts │ └── actions/ │ └── generate.ts ├── skills/ │ └── nextjs-agent-writer/ │ ├── SKILL.md │ ├── validate-input.ts │ ├── build-prompt.ts │ └── examples/ │ └── sample-action.ts ├── lib/ │ └── taotoken.ts ├── .env.local └── package.json

SKILL.md是入口,里面写 Description、Triggers、Instructions。validate-input.ts和build-prompt.ts是可执行脚本,Skill 触发时可以调用。examples/放参考代码,模型按需读取,不占常驻上下文。

SKILL.md的内容我一般这样写,重点是 Triggers 要具体,别写「生成代码」这种泛词:

# Next.js Agent Writer Skill ## Description 为 Next.js App Router 项目生成符合团队规范的 Server Action 与 API Route, 包含 Zod 校验、鉴权检查、统一错误返回结构。 ## Triggers - "生成 Server Action" - "写一个带校验的 API Route" - "调用模型并返回结构化结果" ## Instructions 1. 所有 Server Action 必须包含 `'use server'` 指令 2. 输入必须用 Zod 校验,schema 定义在文件顶部 3. 调用模型前必须检查 session,未登录返回 401 4. 模型调用统一走 lib/taotoken.ts 导出的 client 5. 返回结构固定为 { ok: boolean, data?: T, error?: string } 6. 禁止在客户端组件中直接调用模型接口 ## Code Execution - validate-input.ts:校验传入对象是否符合 schema - build-prompt.ts:根据业务类型拼接系统提示词

环境变量文件.env.local里放三件套:

TAOTOKEN_API_KEY=sk-你的key TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_MODEL=claude-sonnet-4-5

注意.env.local要进.gitignore,别把 key 提交上去。团队协作时用.env.example占位,真实值走部署平台的密钥管理。

到这里前置就绪。下一步是把lib/taotoken.ts写出来,作为所有 Server Actions 的统一出口。

3. 可复制配置:lib/taotoken.ts 与 Server Actions 调用示例

这一节是全文的核心,所有代码都能直接复制进项目。先写模型客户端封装,再写 Server Action,最后写 API Route 作为备选。

lib/taotoken.ts的职责是收口 base URL、key、model,并暴露一个callModel函数。这样以后换模型只改一个文件:

// lib/taotoken.ts const BASE_URL = process.env.TAOTOKEN_BASE_URL ?? 'https://taotoken.net/api'; const API_KEY = process.env.TAOTOKEN_API_KEY; const DEFAULT_MODEL = process.env.TAOTOKEN_MODEL ?? 'claude-sonnet-4-5'; export type ModelMessage = { role: 'user' | 'assistant'; content: string; }; export type ModelResult = { ok: boolean; text?: string; error?: string; }; export async function callModel( messages: ModelMessage[], model: string = DEFAULT_MODEL ): Promise<ModelResult> { if (!API_KEY) { return { ok: false, error: 'TAOTOKEN_API_KEY 未配置' }; } try { const res = await fetch(`${BASE_URL}/v1/messages`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-api-key': API_KEY, 'anthropic-version': '2023-06-01', }, body: JSON.stringify({ model, max_tokens: 1024, messages, }), }); if (!res.ok) { const detail = await res.text(); return { ok: false, error: `HTTP ${res.status}: ${detail.slice(0, 200)}` }; } const json = await res.json(); const text = json?.content?.[0]?.text ?? ''; return { ok: true, text }; } catch (err) { return { ok: false, error: (err as Error).message }; } }

注意anthropic-version头,走 Anthropic 兼容接口时通常需要带上。如果你的通道对头部有额外要求,以控制台文档为准,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

接下来是 Server Action。放在app/actions/generate.ts,用'use server'标记,输入用 Zod 校验,鉴权用你项目里已有的auth():

// app/actions/generate.ts 'use server'; import { z } from 'zod'; import { auth } from '@/auth'; import { callModel } from '@/lib/taotoken'; const inputSchema = z.object({ topic: z.string().min(2).max(200), tone: z.enum(['formal', 'casual']).default('formal'), }); export type GenerateState = { ok: boolean; data?: string; error?: string; }; export async function generateDraft( raw: unknown ): Promise<GenerateState> { const session = await auth(); if (!session?.user) { return { ok: false, error: '未登录' }; } const parsed = inputSchema.safeParse(raw); if (!parsed.success) { return { ok: false, error: parsed.error.issues[0]?.message ?? '参数错误' }; } const { topic, tone } = parsed.data; const systemHint = tone === 'formal' ? '请用正式书面语输出。' : '请用轻松口语化语气输出。'; const result = await callModel([ { role: 'user', content: `${systemHint}\n主题:${topic}` }, ]); if (!result.ok) { return { ok: false, error: result.error }; } return { ok: true, data: result.text }; }

这个 Action 的结构就是 Skill 里 Instructions 要求的:先鉴权、再校验、再调模型、返回固定结构。如果你把SKILL.md放进项目,Claude Code 在生成新 Action 时会自动套用这套模板,不用每次手写。

再给一个 API Route 版本,适合需要被外部系统调用的场景:

// app/api/agent/route.ts import { NextRequest } from 'next/server'; import { z } from 'zod'; import { callModel } from '@/lib/taotoken'; const bodySchema = z.object({ prompt: z.string().min(1).max(2000), }); export async function POST(req: NextRequest) { const json = await req.json().catch(() => null); const parsed = bodySchema.safeParse(json); if (!parsed.success) { return Response.json({ ok: false, error: '参数错误' }, { status: 400 }); } const result = await callModel([{ role: 'user', content: parsed.data.prompt }]); if (!result.ok) { return Response.json({ ok: false, error: result.error }, { status: 502 }); } return Response.json({ ok: true, data: result.text }); }

如果你用的是 Claude Code 做本地开发,可以在项目根目录建.claude/settings.json,把 Skill 目录指过去,这样模型在写代码时能读到你的规范:

{ "skills": { "paths": ["./skills"] }, "env": { "TAOTOKEN_BASE_URL": "https://taotoken.net/api" } }

注意 settings.json 里不要写 key,key 走环境变量。这个文件可以提交到仓库,团队共享路径配置。

到这里配置层就完整了:一个客户端封装、一个 Server Action、一个 API Route、一个 Skill 目录、一个 settings 文件。下一步是本地跑起来验证。

4. 本地验证:从 curl 到页面调用的完整链路

配置写完不验证,等于没写。我习惯分三层验证:先 curl 打通道,再跑 Server Action 单测,最后在页面里点一次。

第一层,curl 验证 TaoToken 通道是否通。这一步能排除 key 错误、base URL 拼错、模型 ID 不存在等问题:

curl -s https://taotoken.net/api/v1/messages \ -H "content-type: application/json" \ -H "x-api-key: $TAOTOKEN_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-sonnet-4-5", "max_tokens": 128, "messages": [{"role": "user", "content": "只回复两个字:通了"}] }'

正常返回里会有content数组,第一项text字段是模型输出。如果返回 401,说明 key 不对或没带上;返回 404,多半是路径拼错,注意是/api/v1/messages而不是/v1/messages单独用。

第二层,写一个临时脚本直接调callModel,验证封装逻辑。在项目根目录建scripts/check.ts,用tsx跑:

// scripts/check.ts import { callModel } from '../lib/taotoken'; async function main() { const res = await callModel([{ role: 'user', content: '返回 JSON:{"ok":true}' }]); console.log(JSON.stringify(res, null, 2)); } main();

运行npx tsx scripts/check.ts,如果输出{ ok: true, text: '...' },说明封装没问题。这一步能提前发现环境变量没加载的问题,因为tsx默认不读.env.local,你需要用dotenv或node --env-file=.env.local。

第三层,页面调用。建一个最简单的表单页,把 Server Action 接上去:

// app/page.tsx import { generateDraft } from './actions/generate'; export default function Page() { async function action(formData: FormData) { 'use server'; const topic = String(formData.get('topic') ?? ''); const res = await generateDraft({ topic, tone: 'casual' }); console.log('action result:', res); } return ( <form action={action}> <input name="topic" placeholder="输入主题" /> <button type="submit">生成</button> </form> ); }

提交后看终端日志,如果打印出{ ok: true, data: '...' },整条链路就通了。如果打印{ ok: false, error: '未登录' },说明auth()没拿到 session,这是预期行为,你需要先登录或临时注释掉鉴权检查做验证。

验证通过后,把 Skill 目录接进 Claude Code 试一次。在对话里输入「生成一个带 Zod 校验的 Server Action」,观察它是否按SKILL.md的 Instructions 输出。如果它没读 Skill,检查.claude/settings.json的路径是否正确,以及SKILL.md的 Triggers 是否匹配你的措辞。

实测下来,Triggers 写得太泛会导致 Skill 不触发,写得太窄又只对特定句子生效。我的经验是每个 Skill 配 3 到 5 个 Triggers,覆盖同义表达,比如「生成 Server Action」「写 Action」「新增 action 文件」都列上。

5. 常见报错排查:401、local proxy failed、reading choices、OAuth

这一节按真实报错来,每个都给出定位方法和修复动作。这些错误我在接入过程中基本都踩过一遍。

401 Unauthorized。最常见,三种原因。一是 key 没带上,检查请求头里有没有x-api-key。二是 key 复制时带了空格或换行,重新从控制台复制一次。三是环境变量没加载,Next.js 里.env.local只在服务端生效,客户端组件读不到,确认你的调用发生在 Server Action 或 Route Handler 里。修复后重新 curl 一次确认。

local proxy failed。这个报错通常出现在本地开发时请求发不出去,多半是 base URL 写成了相对路径,或者fetch在服务端运行时被某个中间件拦截。检查TAOTOKEN_BASE_URL是不是完整的https://taotoken.net/api,不要写成/api。另外确认没有在next.config.js里配了会改写请求的 rewrites 规则。

reading 'choices'。这个报错说明代码按 OpenAI 的返回结构去读choices[0],但实际返回的是 Anthropic 风格的content[0].text。两种结构不一样,别混用。如果你走的是 Anthropic 兼容接口,就按json.content[0].text读;如果走 OpenAI 兼容接口,才用choices。检查lib/taotoken.ts里的解析逻辑,和实际返回结构对齐。

OAuth / authentication_error。如果你在 Claude Code 里配置了自定义通道,但启动时仍走默认 OAuth 流程,会报鉴权失败。这时候需要检查~/.claude/settings.json或项目级.claude/settings.json里的环境变量是否覆盖了默认端点。三件套要写全:Base URL、Key、Model ID。缺任何一个都可能回落到默认鉴权。配置片段参考:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的key", "ANTHROPIC_MODEL": "claude-sonnet-4-5" } }

注意这里 key 直接写在 settings 里只适合本地临时调试,正式项目还是走环境变量注入。

Skill 不触发。不是报错但很常见。检查SKILL.md的 Triggers 是否和你的输入匹配,以及 Skill 目录是否在配置的搜索路径下。Claude Code 里可以用/skills命令查看已加载的 Skill 列表,确认你的 Skill 在列。

Zod 校验通过但模型返回空。多半是max_tokens设太小,或者 prompt 里要求了结构化输出但模型没按格式返回。把max_tokens调到 1024 以上,并在 prompt 里明确「只返回 JSON,不要额外解释」。

排障时如果拿不准返回结构,直接去模型对话页 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 手动发一次请求,看原始返回长什么样,比在代码里猜快得多。接入细节以文档为准:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

6. 把 Skill 沉淀成团队资产:Key 管理与长期编码路径

代码跑通只是第一步,真正省时间的是把 Skill 变成团队共享资产。这一节讲怎么管 Key、怎么共享 Skill、以及长期编码场景下怎么选通道。

Key 管理上,本地开发用.env.local,部署环境用平台密钥管理。不要在代码里硬编码,也不要把 key 写进settings.json提交。团队里每个人用自己的 key,或者用统一的服务账号 key 但限制额度。TaoToken 控制台可以创建多个 key,按项目或按人区分,方便排查用量。创建入口在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

Skill 共享走 Git。把skills/目录提交到仓库,新成员 clone 下来就能用。如果团队用 Claude Code,可以在 README 里写清楚 Skill 目录位置和触发方式。更规范的做法是给每个 Skill 写一个简短的README.md,说明它解决什么问题、Triggers 有哪些、依赖哪些脚本。

长期编码和 Agent 场景,如果调用量大、需要稳定通道,可以看 Coding Plan 页面 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,按套餐选比按量付费更可控。如果只是偶尔验证模型输出,用模型对话页就够了。

还有一个实践细节:Skill 的 Instructions 要定期更新。项目规范变了,Skill 不更新,模型就会按旧规则生成代码。我一般把 Skill 更新和代码 review 绑在一起,改规范的 PR 里必须同步改SKILL.md。

最后说一个我踩过的坑。一开始我把所有规则都塞进一个 Skill,结果 Triggers 互相冲突,模型经常加载错。后来拆成三个:一个管 Server Action 模板,一个管 API Route 模板,一个管错误处理规范。每个 Skill 职责单一,触发准确率明显提升。Skill 不是越大越好,按职责拆开反而更好维护。

如果你还没开始,建议先从一个最小的 Skill 做起:只写 Server Action 的鉴权和校验规则,跑通后再加脚本和示例。通道这边先把 key 建好、curl 打通,再写代码。顺序对了,后面基本不会卡。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询