1. 从 Function Calling 到 Skill:OpenClaw 到底解决了什么问题
如果你用过 OpenAI 的 Function Calling,大概率经历过这种场景:为了让模型能查个天气、读个文件,你得先写一大段 JSON Schema,把参数名、类型、必填项、枚举值全部钉死。模型能做的事情,完全被你定义的接口边界框住。一旦用户问了一个你没预定义参数的问题,模型只能干瞪眼,或者硬塞一个不匹配的参数进去,然后你的后端代码报错。
OpenClaw(社区里常叫“龙虾”)的核心机制,恰恰是在这个基础上往前走了一步。它没有抛弃 Function Calling,而是把 Function Calling 降级成了底层能力,在上面加了一层用 Markdown 写的 Skill(技能)定义。你可以理解为:Function Calling 是螺丝刀、扳手这些原子工具,而 Skill 是一本写给模型看的操作手册,告诉它“遇到什么场景,该拿哪些工具,按什么顺序用”。
这个区别听起来抽象,落到实际开发里差别很大。传统 Function Calling 是代码驱动——模型只负责填参数,执行逻辑全在你的 Python 或 JS 里写死。OpenClaw 的 Skill 是模型驱动——模型读完 Markdown 说明书后,自己决定调用哪个底层工具(比如 exec 执行命令、file_read 读文件),甚至能在遇到报错时根据说明书里的提示自己重试。
适合谁看这篇?如果你已经写过 Function Calling 的 demo,但觉得每次加一个新能力都要改代码、重新部署很烦;或者你在做 Agent 类产品,想让模型处理更开放的任务,那 OpenClaw 这套 Skill + exec 的组合值得你花时间理解。下面我会从机制对比讲到可复制的 Skill 定义,再到 exec 调用的验证步骤,最后把常见的报错排查一遍。
2. TaoToken 前置准备:让 OpenClaw 的 Skill 跑起来需要什么
OpenClaw 本身是一个 Agent 框架,它要调用大模型来“读”你的 Skill 说明书,然后决定怎么执行。所以第一步不是写 Skill,而是先把模型接入配好。这里我用 TaoToken 作为模型接入层来演示,因为它兼容 OpenAI 的接口格式,OpenClaw 底层走 Function Calling 时能直接对接。
你需要准备三样东西:Base URL、API Key、Model ID。这三个是任何 OpenAI 兼容接口的标配,缺一不可。很多人在配 OpenClaw 时卡住,就是因为只填了 Key 没填 Base URL,或者 Model ID 写成了带前缀的别名。
先拿 API Key。打开 TaoToken 的 API Keys 页面(https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys),创建一个新 Key。注意创建后立刻复制,页面刷新后就看不到了。这个 Key 的格式通常是sk-开头的一串字符。
Base URL 用https://taotoken.net/api,注意结尾不要带斜杠,也不要自己加/v1,OpenClaw 或 OpenAI SDK 会自动拼接路径。Model ID 根据你实际要用的模型填,比如gpt-4o、claude-3-5-sonnet这类。如果你不确定有哪些可用,可以去模型对话页面(https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=chat)先试一下,能正常对话说明模型可用。
配好这三个之后,OpenClaw 的配置文件里通常长这样(以环境变量方式为例):
export OPENAI_API_KEY="sk-你的key" export OPENAI_BASE_URL="https://taotoken.net/api" export OPENCLAW_MODEL="gpt-4o"如果你用的是 Claude Code 或者 Cline 这类工具来辅助开发 OpenClaw 的 Skill,它们的配置逻辑是一样的。Claude Code 的 settings.json 里需要写全三件套:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的key", "ANTHROPIC_MODEL": "claude-3-5-sonnet" } }注意 Claude Code 用的是ANTHROPIC_BASE_URL而不是OPENAI_BASE_URL,但地址是同一个。这个坑我见过不少人踩,配了半天发现变量名写错了。
3. 可复制的 Skill 定义:用 Markdown 写一份 GitHub 操作手册
OpenClaw 的 Skill 本质上是一个 Markdown 文件,放在项目的skills/目录下。文件名通常就是技能名,比如github.md。模型在启动时会读取这个目录下所有 Markdown 文件的内容,作为系统提示的一部分。
下面是一份可以直接复制使用的 GitHub Skill 定义。我把它拆成几个部分:技能描述、可用工具、操作步骤、错误处理。
# GitHub 操作技能 ## 描述 你可以使用 `gh` 命令行工具来操作 GitHub。当用户要求查看 PR、合并分支、创建 issue 时,使用本技能。 ## 可用工具 - exec: 执行 shell 命令 - file_read: 读取本地文件 - file_write: 写入本地文件 ## 操作步骤 ### 查看 PR 列表 1. 执行 `gh pr list --state open` 2. 解析输出,提取 PR 编号、标题、作者 3. 用表格形式返回给用户 ### 合并 PR 1. 先执行 `gh pr view <编号>` 确认 PR 状态 2. 如果状态是 open 且没有冲突,执行 `gh pr merge <编号> --squash` 3. 如果报错提示有冲突,返回冲突文件列表,不要强行合并 ### 创建 Issue 1. 执行 `gh issue create --title "<标题>" --body "<内容>"` 2. 返回创建的 issue 链接 ## 错误处理 - 如果 `gh` 命令不存在,提示用户安装 GitHub CLI - 如果报错 `not logged in`,提示用户执行 `gh auth login` - 如果报错 `rate limit`,等待 60 秒后重试一次 - 任何命令执行失败,先读取 stderr 输出,再决定是否重试这份 Markdown 里没有一行 JSON Schema。模型读到的是一段自然语言描述,它自己理解“查看 PR 列表”该怎么做。底层它还是会调用 exec 这个 Function Calling 工具,但调用什么命令、加什么参数,是模型根据 Markdown 里的步骤自己决定的。
对比一下传统 Function Calling 的写法,你要定义get_pr_list、merge_pr、create_issue三个函数,每个都要写 parameters schema。而 Skill 方式下,你只暴露一个exec工具,剩下的交给 Markdown 说明书。这就是“用自然语言编程”指挥 Function Calling 的意思。
写 Skill 有几个实用技巧。第一,步骤要具体到命令级别,不要写“调用 GitHub API”,要写gh pr list。第二,错误处理要写清楚什么错该重试、什么错该放弃。第三,工具列表要明确,告诉模型它有哪些原子能力可用。第四,Markdown 里的标题层级要清晰,模型对##和###的解析很敏感。
4. 验证 exec 调用:从请求到成功结果的完整链路
Skill 写好了,怎么验证模型真的会按说明书去调用 exec?我建议用一个最小可复现的请求来测。下面这段 Python 代码模拟 OpenClaw 的调用逻辑,你可以直接跑。
import os from openai import OpenAI client = OpenAI( api_key=os.environ["OPENAI_API_KEY"], base_url=os.environ["OPENAI_BASE_URL"] ) # 读取 Skill 文件内容 with open("skills/github.md", "r") as f: skill_content = f.read() # 定义底层 exec 工具(Function Calling 格式) tools = [ { "type": "function", "function": { "name": "exec", "description": "执行 shell 命令并返回输出", "parameters": { "type": "object", "properties": { "command": { "type": "string", "description": "要执行的命令" } }, "required": ["command"] } } } ] response = client.chat.completions.create( model=os.environ["OPENCLAW_MODEL"], messages=[ {"role": "system", "content": f"你可以使用以下技能:\n\n{skill_content}"}, {"role": "user", "content": "帮我看看现在有哪些打开的 PR"} ], tools=tools, tool_choice="auto" ) print(response.choices[0].message)跑这段代码,你会看到模型返回的tool_calls里,function.name是exec,arguments里是{"command": "gh pr list --state open"}。这说明模型读懂了 Markdown 里的“执行gh pr list --state open”这一步,并且自己把它翻译成了 exec 调用。
接下来你要做的是真正执行这个命令,把结果塞回对话,让模型继续处理。这一步是 OpenClaw 框架帮你做的,但理解它有助于排查问题:
import subprocess import json tool_call = response.choices[0].message.tool_calls[0] args = json.loads(tool_call.function.arguments) result = subprocess.run(args["command"], shell=True, capture_output=True, text=True) # 把执行结果返回给模型 follow_up = client.chat.completions.create( model=os.environ["OPENCLAW_MODEL"], messages=[ {"role": "system", "content": f"你可以使用以下技能:\n\n{skill_content}"}, {"role": "user", "content": "帮我看看现在有哪些打开的 PR"}, response.choices[0].message, { "role": "tool", "tool_call_id": tool_call.id, "content": result.stdout or result.stderr } ], tools=tools ) print(follow_up.choices[0].message.content)如果一切正常,最后你会看到模型把gh pr list的输出整理成了表格,返回给用户。这就是从 Skill 定义到 exec 调用再到结果整理的完整链路。
实测下来,模型对 Markdown 里步骤的遵循程度,跟你的描述清晰度直接相关。如果你写“查看 PR”,模型可能调用gh pr list也可能调用gh api。如果你写“执行gh pr list --state open”,模型基本不会跑偏。所以 Skill 写得越具体,exec 调用的准确率越高。
5. 常见报错排查:401、local proxy failed、reading choices 怎么解
配 OpenClaw + TaoToken 的过程中,有几个报错出现频率特别高。我按实际遇到的顺序列一下,你对照着排查。
401 Unauthorized。这个最常见,原因通常是 Key 没填对或者 Base URL 写错了。先检查OPENAI_API_KEY是不是完整的sk-开头字符串,有没有多余空格。再检查OPENAI_BASE_URL是不是https://taotoken.net/api,注意不要写成https://taotoken.net/api/v1,有些 SDK 会自动加/v1,你手动加了就变成/v1/v1,直接 404 或 401。如果你用的是 Claude Code,检查变量名是不是ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL,别跟 OpenAI 的混了。
local proxy failed。这个报错通常出现在你本地开了某些网络工具,但配置没生效或者端口冲突。OpenClaw 底层走 HTTP 请求,如果系统代理设置指向了一个不存在的端口,就会报这个。排查方法是先关掉所有代理设置,用curl https://taotoken.net/api直接测一下能不能通。如果 curl 能通但 OpenClaw 报错,检查 OpenClaw 的配置文件里有没有硬编码的 proxy 设置。
reading choices 报错。完整报错通常是Error reading choices: ...或者choices is undefined。这说明模型返回的响应结构跟你代码里解析的不一致。常见原因是 Base URL 配错了,请求打到了非 OpenAI 兼容的接口上,返回了 HTML 而不是 JSON。另一个原因是 Model ID 写错了,接口返回了错误信息,但你的代码直接去读choices[0],就报 undefined。排查方法是在请求后先打印完整 response,看看返回的 JSON 结构对不对。
OAuth 相关报错。如果你用 Claude Code 接入,可能会遇到OAuth token expired或invalid_grant。这是因为 Claude Code 默认走 OAuth 登录流程,但用 API Key 接入时不需要 OAuth。你需要在 settings.json 里明确配置ANTHROPIC_API_KEY,并且确保没有残留的 OAuth token 文件。删掉~/.claude/下的 token 缓存,重新用 API Key 方式登录。
exec 调用返回空结果。模型调用了 exec,但result.stdout是空的。先确认命令本身在终端里能跑通,比如gh pr list需要你先gh auth login。如果命令没问题但 OpenClaw 里跑不出来,检查 OpenClaw 执行命令时的工作目录是不是你预期的目录。有些框架默认在项目根目录执行,而你的gh配置在用户目录下,就会找不到。
Skill 没被加载。模型完全无视你的 Markdown,直接瞎调工具。检查 Skill 文件是不是放在 OpenClaw 配置的 skills 目录下,文件名是不是.md结尾。有些框架要求 Skill 文件必须有特定的 frontmatter,比如---\nname: github\n---,缺了就不加载。另外检查文件编码,UTF-8 不带 BOM,有些框架对 BOM 头敏感。
6. 继续深入:把 Skill 和 exec 用顺手的几个建议
Skill 机制最大的好处是迭代快。你想给 Agent 加一个新能力,不用改代码、不用重新部署,写一个 Markdown 文件丢进 skills 目录就行。模型下次启动就会读到。这对于快速试错特别友好。
但要注意,Skill 不是越多越好。每个 Skill 都会占用系统提示的 token,Skill 太多会挤占对话上下文,模型反而容易混淆。我的做法是按业务域拆分,比如github.md、file-ops.md、web-search.md,每个文件聚焦一个场景。不要把所有能力塞进一个巨大的 Markdown 里。
exec 工具是把双刃剑。它给了模型极大的灵活性,但也意味着模型可以执行任意 shell 命令。在生产环境里,你需要在 exec 外面包一层白名单或者沙箱,限制可执行的命令范围。OpenClaw 本身提供了一些权限控制配置,建议至少把rm -rf、curl | bash这类危险命令拦掉。
如果你想让模型长期跑编码或 Agent 任务,可以考虑用 Coding Plan(https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding-plan),它在长上下文和工具调用稳定性上做了优化,适合 OpenClaw 这种需要多轮 exec 调用的场景。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc,里面有各语言 SDK 的配置示例,配的时候对照着看能少踩很多坑。
最后说一个我自己的经验:写 Skill 的时候,把模型当成一个刚入职的实习生。你给实习生的操作手册越具体、越有例子、越说明什么情况该怎么办,他上手就越快。Markdown 里的每一步都写清楚命令和预期输出,模型执行 exec 的准确率会明显提升。反过来,如果你只写“处理 GitHub 相关操作”,模型就只能靠猜,结果往往不是你想要的。