欢迎光临
我们一直在努力

AI 应用里的多个 Skill 怎么设计:从工具 Skill 到阅读 Skill

本文为作者原创,首发于掘金,现同步发布到 CSDN。 内容整理自 AI Mind 项目的真实开发过程。 GitHub:https://github.com/HWYD/ai-mind 对应代码版本:v0.0.8

AI Mind 是一个基于 Next.js 持续迭代的 AI Chat 项目,项目从本地大模型聊天起步,逐步扩展流式协议、工具调用、MCP、Skill Runtime 和 Agent 等能力。

如果这篇文章或 AI Mind 项目对你有所帮助,也欢迎到 GitHub 给项目点个 Star,这会是对我继续整理后续版本复盘很大的鼓励。

当 AI 应用里不止一种能力时,所有工具都堆在一起会让 Runtime 越来越难维护。本文复盘 AI Mind 如何从最初的工具 Skill 扩展到 Reader Skill,让不同任务由不同 Skill 承接,并为后续 MCP 和 Agent 做边界准备。

在 v0.0.7 里,我已经给 AI Mind 落下了第一个正式工具Skill:utility-skill。

那一版的重点,是证明一件事:

在 Multi-Tool Runtime 之上,是否真的能再长出一层更稳定的能力模式。

但只有一个 Skill,其实还不够。

因为单 Skill 最多只能证明:

  • 这套结构能跑
  • Runtime 能感知 Skill
  • Tool 可以被 Skill 收口

它还证明不了另一件更关键的事:

当系统开始进入多 Skill 阶段时,这层抽象到底是不是真的成立。

所以到了 v0.0.8,我真正要回答的问题就变成了:

  • 第二个 Skill 应该是什么
  • 什么样的 Skill 才值得进入正式版本
  • 多 Skill Runtime 的边界应该怎么收
  • 前端又该怎么把这种能力模式切换表达出来
  • 这篇文章想讲的,就是我如何从最初的 writer-skill 设想,最后收敛到了 reader-skill,并让 AI Mind 真正迈出“从单 Skill 到多 Skill”的第一步。

    在这里插入图片描述

    为什么多 Skill 是这一版必须面对的问题

    如果项目一直只有一个 utility-skill,那 Skill Runtime 很容易停留在一个比较尴尬的状态:

    • 看起来像是做出了一层新抽象
    • 但又很难证明它不是一次性的特殊 case

    因为只有一个 Skill 时,你很难回答这些问题:

    • Skill 之间的边界能不能真正拉开
    • Runtime 是否能根据不同 Skill 暴露不同 Tool 子集
    • 自动模式下的路由是否还有意义
    • 前端是否需要为不同 Skill 提供更明确的交互入口

    换句话说,单 Skill 更像是在证明“这套机制存在”,而多 Skill 才开始证明“这套机制成立”。

    所以 v0.0.8 的重点,不是再多做一个功能,而是:

    让第二个 Skill 真正成为一个有独立边界、有独立 Tool 价值的能力模式。

    为什么最开始想到的是 writer-skill

    一开始我最自然想到的第二个 Skill,其实是 writer-skill。

    这个方向表面上看很合理:

    • 它和 utility-skill 差异足够大
    • 用户很容易理解“写作模式”
    • 前端做模式切换时,也很容易有感知

    所以我最初尝试的方向是:

    • 做一个 writer-skill
    • 再配一个偏结构整理的 Tool
    • 让模型在“改写、总结、整理、生成标题”这类任务上走另一条路径

    从想法上说,这条线没有问题。

    真正的问题出在落地后。

    很快我就发现,写作整理类任务和 utility-skill 最大的不同在于:

    它们里有很大一部分,其实本来就是大模型原生就会做的事情。

    比如:

    • 润色一段话
    • 把几句话改写得更自然
    • 整理成一段通顺表达
    • 概括几个点

    这些任务里,模型往往会直接回答,而不是老老实实触发 Tool。

    也就是说,writer-skill 可以命中,但 Tool 的独特价值却不够稳定。

    为什么我最后放弃了 writer-skill

    最后让我决定止损的,不是某一个 bug,而是一个越来越明确的判断:

    第二个正式 Skill,最好补的是模型没有的能力,而不是模型已经比较擅长的能力。

    writer-skill 的问题主要有三个。

    1. 写作整理很多时候是模型原生能力

    写作并不是不能做成 Skill,而是它很难在当前阶段承担“证明多 Skill Runtime 成立”的任务。

    因为一旦用户的需求是:

    • 改写
    • 润色
    • 概括
    • 整理成更自然的一段话

    模型会天然倾向于自己直接写。

    这意味着:

    • Skill 也许命中了
    • 但 Tool 不一定会稳定触发

    2. Tool 没形成“非它不可”的能力差

    如果一个 Tool 提供的只是:

    • 换个结构
    • 换个格式
    • 帮你整理一下语序

    那它很容易被模型直接绕过去。

    因为模型会判断:

    我自己直接写一段,往往比调用一个结构整理 Tool 更简单。

    这和 calculator、datetime、unit-convert 完全不一样。

    后者一旦不用 Tool,模型就很容易答错;而前者即使不用 Tool,模型也很可能还能答得不错。

    3. 第二个 Skill 不应该只是“多一种说话风格”

    这是这轮最重要的取舍。

    我最后越来越明确地意识到:

    多 Skill 的关键,不是“多几个模式名字”,也不是“多几段 Prompt”。

    真正值得留下来的 Skill,应该满足至少一条:

    • 它组织了一组边界清晰的 Tool
    • 它补的是模型原本拿不到的能力
    • 它能明显改变 Runtime 的可用能力范围

    writer-skill 在当前阶段没有足够强地满足这些条件。

    所以我最后放弃它,并不是因为写作不重要,而是因为它不适合当前作为“第二个正式 Skill”。

    为什么第二个 Skill 最终变成了 reader-skill

    我最后把第二个 Skill 改成了 reader-skill。

    因为这时我更想验证的是:

    Skill Runtime 是否能承载一类模型本身完全拿不到的信息能力。

    reader-skill 对应的正是这类场景:

    • 实时天气
    • 本地文本文件

    它们有一个共同点:

    没有 Tool,就没有能力来源。

    这和写作场景最大的区别在于:

    • 没有天气 Tool,模型就拿不到实时天气
    • 没有本地文件读取 Tool,模型就看不到你的项目文件

    这时候 Tool 不再是“可选增强”,而是“能力成立的前提”。

    于是 reader-skill 的价值就变得非常明确:

    • 它不是在给模型增加一种风格
    • 而是在给模型接入一类新的上下文来源

    这就让第二个 Skill 终于拥有了足够清晰的独立边界。

    这版 reader-skill 是怎么收边界的

    reader-skill 这一版我只落了两个 Tool,而且每个 Tool 都故意收得很小。

    1. city-weather

    用途非常单一:

    • 查询指定城市的实时天气

    它只收一个参数:

    • city

    数据源我也没有做得很重,而是直接用了轻量的 wttr.in。

    这背后的考虑很简单:

    • 这版的重点不是做一个完整天气系统
    • 而是验证“实时信息如何进入 Skill Runtime”

    也就是说,city-weather 的价值不在于“做得多强”,而在于它非常直接地证明了:

    没有外部 Tool,模型就是拿不到这部分实时信息。

    2. local-text-read

    local-text-read 同样只做一件事:

    • 读取项目根目录下的直接文本文件

    它也只收一个参数:

    • filename

    而且我给它加了很强的边界限制:

    • 只允许根目录直接文件
    • 不允许子目录
    • 不允许绝对路径
    • 不允许 ../
    • 只允许文本类文件

    这也是我这一版很看重的一点:

    Tool 的价值不只是“能做什么”,还包括“它不会越界做什么”。

    如果第二个 Skill 要证明它是一种正式能力模式,那它不仅要有能力来源,也要有稳定边界。

    多 Skill Runtime 这一版真正收敛了什么

    到 v0.0.8,项目里的 Skill 边界终于开始变清楚了。

    现在可以比较明确地把它们分成两类:

    utility-skill

    负责确定性实用任务:

    • 计算
    • 时间日期
    • 单位换算
    • 文本转换

    对应 Tool:

    • calculator
    • datetime
    • unit-convert
    • text-transform

    reader-skill

    负责外部上下文获取:

    • 实时天气
    • 本地文件读取

    对应 Tool:

    • city-weather
    • local-text-read

    这时候 Skill 才真正不再只是“一个标签”,而是:

    • 当前属于哪一种能力模式
    • 当前允许模型使用哪些 Tool
    • 当前回答主要建立在哪一类能力来源上

    多 Skill 链路图

    #mermaid-svg-k8Nxw42crXNgmtMc{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-k8Nxw42crXNgmtMc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-k8Nxw42crXNgmtMc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-k8Nxw42crXNgmtMc .error-icon{fill:#552222;}#mermaid-svg-k8Nxw42crXNgmtMc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-k8Nxw42crXNgmtMc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-k8Nxw42crXNgmtMc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-k8Nxw42crXNgmtMc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-k8Nxw42crXNgmtMc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-k8Nxw42crXNgmtMc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-k8Nxw42crXNgmtMc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-k8Nxw42crXNgmtMc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-k8Nxw42crXNgmtMc .marker.cross{stroke:#333333;}#mermaid-svg-k8Nxw42crXNgmtMc svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-k8Nxw42crXNgmtMc p{margin:0;}#mermaid-svg-k8Nxw42crXNgmtMc .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-k8Nxw42crXNgmtMc .cluster-label text{fill:#333;}#mermaid-svg-k8Nxw42crXNgmtMc .cluster-label span{color:#333;}#mermaid-svg-k8Nxw42crXNgmtMc .cluster-label span p{background-color:transparent;}#mermaid-svg-k8Nxw42crXNgmtMc .label text,#mermaid-svg-k8Nxw42crXNgmtMc span{fill:#333;color:#333;}#mermaid-svg-k8Nxw42crXNgmtMc .node rect,#mermaid-svg-k8Nxw42crXNgmtMc .node circle,#mermaid-svg-k8Nxw42crXNgmtMc .node ellipse,#mermaid-svg-k8Nxw42crXNgmtMc .node polygon,#mermaid-svg-k8Nxw42crXNgmtMc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-k8Nxw42crXNgmtMc .rough-node .label text,#mermaid-svg-k8Nxw42crXNgmtMc .node .label text,#mermaid-svg-k8Nxw42crXNgmtMc .image-shape .label,#mermaid-svg-k8Nxw42crXNgmtMc .icon-shape .label{text-anchor:middle;}#mermaid-svg-k8Nxw42crXNgmtMc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-k8Nxw42crXNgmtMc .rough-node .label,#mermaid-svg-k8Nxw42crXNgmtMc .node .label,#mermaid-svg-k8Nxw42crXNgmtMc .image-shape .label,#mermaid-svg-k8Nxw42crXNgmtMc .icon-shape .label{text-align:center;}#mermaid-svg-k8Nxw42crXNgmtMc .node.clickable{cursor:pointer;}#mermaid-svg-k8Nxw42crXNgmtMc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-k8Nxw42crXNgmtMc .arrowheadPath{fill:#333333;}#mermaid-svg-k8Nxw42crXNgmtMc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-k8Nxw42crXNgmtMc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-k8Nxw42crXNgmtMc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-k8Nxw42crXNgmtMc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-k8Nxw42crXNgmtMc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-k8Nxw42crXNgmtMc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-k8Nxw42crXNgmtMc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-k8Nxw42crXNgmtMc .cluster text{fill:#333;}#mermaid-svg-k8Nxw42crXNgmtMc .cluster span{color:#333;}#mermaid-svg-k8Nxw42crXNgmtMc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-k8Nxw42crXNgmtMc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-k8Nxw42crXNgmtMc rect.text{fill:none;stroke-width:0;}#mermaid-svg-k8Nxw42crXNgmtMc .icon-shape,#mermaid-svg-k8Nxw42crXNgmtMc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-k8Nxw42crXNgmtMc .icon-shape p,#mermaid-svg-k8Nxw42crXNgmtMc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-k8Nxw42crXNgmtMc .icon-shape .label rect,#mermaid-svg-k8Nxw42crXNgmtMc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-k8Nxw42crXNgmtMc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-k8Nxw42crXNgmtMc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-k8Nxw42crXNgmtMc :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    utility

    reader

    未命中

    用户请求

    /api/chat

    是否显式传入 skill

    直接命中对应 Skill

    轻量规则路由

    命中 utility / reader ?

    utility-skill

    reader-skill

    普通聊天链路

    allowedTools 过滤 ToolRegistry

    模型在当前 Tool 子集里决定是否调用 Tool

    Runtime 执行 Tool

    流式返回 reasoning / tool / text

    这一版里我刻意没有做的,是:

    • 模型自主 Skill 路由
    • 多 Skill 编排
    • 更复杂的 Agent 化链路

    因为我想先证明的不是“系统越来越聪明”,而是:

    多 Skill Runtime 在结构上已经开始稳定成立。

    为什么前端也要一起进入正式组件基线

    这版还有一个我认为非常值得一起写进去的变化:

    前端开始正式进入 shadcn/ui 基线阶段。

    原因其实也很现实。

    当输入区开始同时出现:

    • 模型选择器
    • Skill 模式切换
    • 深度思考开关
    • 推理过程面板
    • Tool 卡片

    如果继续完全靠手写样式往前堆,界面会越来越像几套东西拼在一起。

    所以这一版我顺手做了前端统一收口:

    • 正式接入 shadcn/ui
    • 使用 Radix
    • 图标统一为 lucide-react
    • 主题走 cssVariables
    • 当前基线切到 radix-vega

    这一轮已经统一下来的区域包括:

    • 输入区控制条
    • 顶部错误条
    • 推理过程面板
    • Tool 卡片
    • 空状态

    而且我还补了几项比较细的交互收口:

    • 输入框上下边距收紧
    • 推理面板上下边距收紧
    • Tool 状态色区分:
      • 完成:绿
      • 执行中:蓝
      • 失败:红
    • 实用 和 读取 模式下的提示文案分开

    这一点对我来说很重要,因为它说明:

    多 Skill Runtime 的成立,不只是后端 Runtime 的问题,也是前端表达能力的一部分。

    在这里插入图片描述

    这版最重要的工程结论

    如果要我用几句话总结 v0.0.8,我最想留下的是这三点:

    1. 不是所有 Skill 都值得进入正式版本

    有些 Skill 看起来方向对,但它不一定适合当前版本的验证目标。

    writer-skill 就属于这种情况:

    • 它不是完全没价值
    • 但它不适合当前承担“证明第二个 Skill 成立”的任务

    2. 第二个 Skill 最好补的是模型缺失的能力

    如果 Tool 补的是模型原本就会做的事情,那它就很容易被绕过。

    但如果 Tool 补的是模型完全拿不到的上下文,那 Skill 的价值会立刻清晰很多。

    这也是为什么 reader-skill 比 writer-skill 更适合当前阶段。

    3. 多 Skill 的成立,不只是 Runtime 的事,也是 UI 的事

    一旦系统开始真正区分:

    • 自动 / 实用 / 读取
    • reasoning / tool / text
    • 不同 Tool 状态

    那前端也必须同步给出更统一、更稳定的表达方式。

    这也是为什么这版里,我没有把 UI 统一看成“顺手做的样式活”。

    它其实也是版本收敛的一部分。

    这一版之后,我更清楚了一件事

    如果说 v0.0.7 证明的是:

    Tool Runtime 之上可以长出第一层 Skill Runtime。

    那么 v0.0.8 证明的就是:

    多 Skill 不是多几个不同名字的 Prompt,而是第二个 Skill 是否真的打开了一块新的能力边界。

    对现在的 AI Mind 来说,这块边界已经开始变得清楚:

    • utility-skill:确定性实用任务
    • reader-skill:外部上下文获取

    这也让整个 Runtime Skeleton 比之前更像一个会继续长大的系统,而不是一组不断堆叠的局部功能。

    后面会往哪走

    如果继续沿这条线往后走,我更关心的是:

    • reader-skill 的稳定性继续收口
    • 网页读取 / MCP 能力怎么接入
    • 更高层的 Agent Runtime 什么时候开始真正有必要

    但至少在 v0.0.8 这个点上,我已经比较确认:

    第二个 Skill 终于不是一个“看起来像 Skill 的名字”,而是一块真正成立的能力模式。

    最后

    这个项目还会继续沿着:

    • reader-skill 稳定性收口
    • MCP服务设计
    • Agent Runtime

    这些方向继续往前走。

    项目地址

    👉 GitHub:https://github.com/HWYD/ai-mind

    如果这篇文章或者 AI Mind 项目对你有所帮助,也欢迎给项目点个 Star。你的支持会是我持续更新这个系列、继续整理项目实现过程和设计复盘的很大动力。

    赞(0)
    未经允许不得转载:171主机测评 » AI 应用里的多个 Skill 怎么设计:从工具 Skill 到阅读 Skill
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址