Harness 的实践:使用skill search 提高 skill 调用准确度

最近给自己的 Agent 做了一次关键的改造。表面上看,只是新增了一个 Skill Search 工具,但真正改变的,是 Agent 的工作方式。 以前 Skill 的调用非常不稳定。明明已经很明确告诉它"搜索天气"“搜索新闻”,也提供了相应的工具,但它就是不能自动触发。尝试优化提示词、改进 Skill 描述,做了很多尝试,效果都不理想。 后来看到 Claude Code 的源码,里面有一个 Tool Search 机制:每次使用工具之前,先查询一下有没有可用工具。受这个启发,我做了一个 Skill Search 工具,希望在 Agent 触发能力边界时——比如需要搜索网络、写脚本、调用外部命令时——先检查有没有现成 Skill 可以用,而不是从头造轮子。 核心问题 原来的 Skill 调用机制是这样的:把所有 Skill 的名字和描述都塞进系统提示词里,然后告诉模型"如果当前任务适合某个 Skill,就优先使用"。 这个思路一开始是成立的,模型确实"看得到"这些 Skill,也能在某些场景下主动调用。但真正用久了之后,问题就暴露出来了。 只有显式调用才稳定。当直接输入 /web-search 或明确指定某个 Skill 时,没问题——因为这时候不是模型在判断,而是用户替它做了决策。但真实使用中,用户更常见的表达是: “帮我搜一下这个话题” “去网上查一下最近有什么信息” “看看有没有现成工具能做这件事” “帮我处理一下这个文件” 这些输入对人类来说已经足够明确,大家都知道这时候应该优先检查 Skill。但模型不是每次都这么做。很多时候,明明已经有现成 Skill,它还是直接跳过去,自己动手。 根本原因不是 Skill 描述不够好,而是机制本身有问题。 原来的调用流程太长了:模型先看用户输入,再判断是否需要执行任务,再回忆提示词里有没有对应 Skill,再匹配描述,最后才决定要不要调用。只要其中任何一步松掉,它就会进入更省事的路径:自己来做。 随着 Skill 越来越多,问题还会继续恶化。Skill 越多,提示词越长,模型越容易漂移。这些 Skill 只是混在上下文里的动态信息,对模型来说不是强制执行的流程,只是可能参考的背景材料。 改造方案 核心上做了三件事。 1. 从隐式到显式 把 Skill 从"提示词中的隐式能力"变成"运行时中的显式能力"。新增真正的 skill_search,让 Agent 在面对非纯文本任务时,不是默认自己动手,而是先查有没有现成 Skill。 ...

2026-06-12 · 2 min · 424 words

我用 AI 给 Obsidian 写了一个"LLM-wiki"插件

起因 前几天,Karpathy 发了一条推。 他讲自己怎么用 LLM 管个人知识库,用了一个词:编译(Compile)。意思是,把原始资料"编译"成结构化知识。Obsidian Vault 是代码仓库,LLM 就是编译器。 我看完愣了一下——这不就是我最近几个月一直在折腾的事吗?我一直在做一个流程,把 raw 资料变成 wiki 条目,只是一直没找到一个好的词来概括。现在有了:知识编译。 然后我就做了一个程序员会做的事:把它做成了 Obsidian 插件。 思路 传统管理知识库的方式,大家应该都不陌生:你积累了 500 篇笔记,然后加标签、建链接、分类整理,再然后……三个月后放弃了,笔记库慢慢腐烂。 编译方式不一样。你只管把原始资料丢进 raw/ 目录,LLM 负责读取、提炼、交叉引用,自动产出结构化的 wiki 页面。你只需要做两件事:喂料,提问。 这跟写代码一模一样。raw/ 是源码,wiki/ 是编译产物,index.md 是目录清单,log.md 是构建日志,编译器是 LLM。你不会手动把 .java 文件逐行翻译成 .class——同理,你也不应该手动给 500 篇笔记加标签。 三层目录,各管各的 先看一下目录结构: Vault/ ├── raw/ # 原始资料,人类添加,LLM 自动归类 │ ├── tech/ # 技术文章、论文、教程 │ ├── work/ # 工作相关文档 │ ├── reading/ # 读书笔记、播客笔记 │ ├── general/ # 其他内容 │ └── assets/ # 图片附件 │ ├── wiki/ # 编译产物,完全由 LLM 维护 │ ├── summaries/ # 每篇源文件的结构化摘要 │ ├── concepts/ # 概念页面(跨源综合) │ ├── entities/ # 人物/工具/框架页面 │ ├── comparisons/ # 对比分析 │ └── analysis/ # 深度分析(从好的问答中沉淀) │ ├── legacy/ # 已有的旧笔记库,冻结存档 ├── drafts/ # 碎片想法,人类专属 ├── CLAUDE.md # LLM 的"编译规范" ├── index.md # Wiki 主索引 └── log.md # 操作日志 我定了一个很严格的 ownership 规则: ...

2026-04-07 · 3 min · 591 words

5个ADK开发者必须掌握的Agent Skill设计模式

名词解释 ADK: Agent Development Kit(Agent 开发工具包) 最近使用 AI 写代码用到了很多 SKILL,但是每个人写的 SKILL 都不同,没有一个公共的标准或者模式,最近看到Google Cloud 技术团队的一篇文章,很有启发,翻译成中文,大家可以参考一下。 说到SKILL.md,开发者往往过于关注格式——怎么写YAML、怎么组织目录、怎么遵循规范。但现在已经有30多个Agent工具(比如Claude Code、Gemini CLI、Cursor)都采用了相同的布局,格式问题基本上已经解决了。 现在的挑战是内容设计。规范解释了怎么打包一个Skill,但对Skill内部的逻辑结构完全没有指导。比如,一个封装FastAPI约定的Skill,和一个四步文档生成流水线,虽然它们的SKILL.md文件看起来一模一样,但运作方式完全不同。 通过研究整个生态系统中Skill的构建方式——从Anthropic的代码库到Vercel和Google的内部规范——我们发现了五种反复出现的设计模式,可以帮助开发者构建更好的Agent。 作者 @Saboo_Shubham_ 和 @lavinigam 本文将介绍每种模式,并附带可用的ADK代码示例: Tool Wrapper:让你的Agent瞬间成为任何库的专家 Generator:从可复用模板生成结构化文档 Reviewer:按严重程度对代码进行清单式评分 Inversion:Agent在行动前先采访你 Pipeline:通过检查点强制执行严格的多步工作流 模式1:Tool Wrapper Tool Wrapper让你的Agent按需获取特定库的上下文。你不是把API约定硬编码到系统提示词里,而是把它们打包成一个Skill。你的Agent只在真正用到该技术时才加载这些上下文。 这是最简单的实现模式。SKILL.md文件监听用户提示词中的特定库关键词,动态加载references/目录下的内部文档,并将这些规则视为绝对真理。这正是你将团队内部编码规范或特定框架最佳实践直接分发到开发者工作流中的机制。 下面是一个Tool Wrapper示例,教Agent如何编写FastAPI代码。注意指令如何明确要求Agent只在开始审查或编写代码时才加载conventions.md文件: # skills/api-expert/SKILL.md --- name: api-expert description: FastAPI开发最佳实践和规范。在构建、审查或调试FastAPI应用、REST API或Pydantic模型时使用。 metadata: pattern: tool-wrapper domain: fastapi --- 你是FastAPI开发专家。将这些约定应用到用户的代码或问题中。 ## 核心约定 加载'references/conventions.md'获取完整的FastAPI最佳实践列表。 ## 审查代码时 1. 加载约定参考文件 2. 将用户代码与每个约定进行比对 3. 对每个违反项,引用具体规则并建议修复方案 ## 编写代码时 1. 加载约定参考文件 2. 严格遵循每个约定 3. 为所有函数签名添加类型注解 4. 对依赖注入使用Annotated风格 模式2:Generator Tool Wrapper是应用知识,而Generator则强制产生一致的输出。如果你苦于Agent每次运行都生成不同的文档结构,Generator通过填空式流程解决了这个问题。 ...

2026-03-19 · 2 min · 261 words

Codex 最佳实践指南

一、引言 最近在用 OpenAI 的 Codex APP,官方发布了一份最佳实践指南。我仔细读了一遍,发现里面有很多实用的建议,特别是对于那些刚开始用 AI 编程工具的人。 总结整理了一下这些经验,分享给大家。如果你也在用 Codex(对于其它AI 编程工具也适用),或者对 AI 编程感兴趣,这篇文章应该能帮到你。 二、核心四要素 ![Codex最佳实践封面设计 (1)](Codex最佳实践封面设计 (1).webp) 想让 Codex 准确完成任务,每次提问最好包含这四个部分: 目标:你要改什么或建什么 上下文:相关文件、文档、错误信息(用 @ 提及) 约束:代码规范、安全要求、团队约定 完成标准:测试通过、行为改变、bug 复现消失 任务越复杂,越要提供详细信息。一次说清楚,可以减少很多返工。 三、七个实用技巧 ![生成 Codex 技巧插图](生成 Codex 技巧插图.webp) 1. 复杂任务先规划 遇到复杂或模糊的任务,先让 Codex 做规划再动手。 可以用 /plan 模式,让它先问清问题再执行。如果你只有个大致想法,可以让它先采访你,把模糊的想法变得具体。 对于大型项目,可以用 PLANS.md 模板来管理(建议使用 planning-with-files skill) 。 2. 创建 AI 使用说明书 可以使用 /init 命令初始化 AGENTS.md 文件,然后再根据自己的需要更新。 在项目中创建 AGENTS.md 文件,写上: 项目结构和重要目录 运行、构建、测试命令 代码规范和审查标准 “完成"的定义 这样 Codex 会自动读取,不用重复说明。 3. 配置个人设置 在 ~/.codex/config.toml 中设置: ...

2026-03-13 · 1 min · 172 words

Git Worktree:多分支并行开发的利器

Git Worktree:多分支并行开发的利器 Git 的分支功能很强大,但实际开发中经常遇到一个问题:需要在同一时间处理多个任务,比如主分支有紧急 Bug 要修,同时又想在一个新分支上做重构。这时传统的 git checkout 会让你陷入 stash、切换、再 pop 的循环,环境重建也很麻烦。 [ Git 2.6 引入了 git worktree 命令,它允许一个 Git 仓库对应多个工作目录,每个目录可以检出不同的分支,实现真正的并行开发。 什么是 Worktree? 简单说,Worktree 就是一个仓库的多个「分店」。它们共享同一个 .git 目录(包含所有分支历史、提交记录),但拥有独立的文件系统和工作环境。 仓库.git(共享) ├── 主工作树(main 分支) ├── ../feature/(feature 分支) └── ../bugfix/(bugfix 分支) 这样,你可以在不同目录同时编辑不同分支,互不干扰。 核心区别 特性 普通分支 Worktree 工作目录 单一目录,切换分支需 checkout 多个独立目录同时存在 环境隔离 切换分支需重建 node_modules 等 各树独立环境 并行开发 需要 stash 保存变更 无需 stash,直接并行 存储开销 只占指针空间 共享 .git,只复制工作文件 这些差异让 worktree 特别适合一台机器上并行开发多个功能。 基本使用 1. 创建 Worktree 在主仓库执行: # 基于现有分支创建 git worktree add ../feature-branch feature-branch # 创建新分支并检出 git worktree add ../new-feature -b new-feature 创建后,你得到一个新目录 ../feature-branch/,里面是 feature-branch 分支的完整工作树,主目录保持不变。 ...

2026-03-06 · 3 min · 481 words

markdown中code生成图片的实现

前几天写了《markdown 生成头条文章的一个思路》,周末就试了试。 先回顾一下思路,大致流程如下: 这里的三个关键点是: 提取code 把code 转换为html 把html 生成图片 code 替换成图片 第一个很简单,只有用正则表达式就可以解决: _fenced_code_block_re = re.compile(r''' (?:\n+|\A\n?) ^```\s*?([\w+-]+)?\s*?\n # opening fence, $1 = optional lang (.*?) # $2 = code block content ^```[ \t]*\n # closing fence ''', re.M | re.X | re.S) 这个正则来自 python-markdown2: https://github.com/trentm/python-markdown2 这个正则只匹配了 ``` 样式的代码,对于前边有四个空格的并没有做处理(也不想做处理,还是严格一点好)。 第二个也不麻烦,只需要把提取出的code 放到html 中,下面是一个html模板: <html> <head> <link rel="stylesheet" href="http://media.gusibi.mobi/highlight/static/styles/atom-one-dark.css"> <script src="http://media.gusibi.mobi/highlight/static/highlight.site.pack.js"></script> <script>hljs.initHighlightingOnLoad();</script> </head> <body style="width: 640px;"> <pre> <code class="{{.Language}}">{{.Code}}</code> </pre> </body> </html>` 这里有一个点是渲染html 页面的时候, 由于加载html 页面的工具都是get请求,这里我们需要先把code 数据保存起来。所以请求code 的html 页面分成了两步。 ...

2019-06-15 · 2 min · 260 words

markdown中code生成图片的思路

最近在头条上写东西,遇到了一个比较烦的事情—编辑器不支持代码。这对于一个像我这样使用代码凑字数的人来说实在不是一个好的消息。但是等头条改进编辑器太遥远了,只能自己自足实现一个替代方案了–把代码替换成图片。 一段代码的时候,我随手截图,简单完成了; 两段代码的时候,我随手随手截图,也完成了; 三段代码的时候,我随手随手随手截图,强忍着完成了; 等我发现代码越来越多的时候,不能忍了。 懒惰是程序员的美德,不能再花费时间干这些事情了。我觉得要写个程序,把markdown 中的代码自动生成图片。 考虑了一下,大概需要做的工作是: 把markdown 中 “ ” 包换的代码提取出来(也可以使用工具先把markdown 转换成html 再解析html 取出code 把每一段code 分别生成图片 把图片对应的代码替换掉 想想还是很简单的。那就开始吧。 但是到第二步的时候遇到了问题,code 如何生成图片,生成什么样的图片? 首先code 需要保持原有的样式,如果能高亮那就更好了(嗯,高亮 生成图片的时候是把code 作为文字使用PIL(我使用python)写在背景上么,图片大小是多少,高亮怎么实现 算了,还是先把code 生成html,然后截取html页面吧。(这样html 还能使用 highlight.js 来实现高亮) 如何动态生成包含code 的html 页面呢? 如何把截取html 页面呢? 动态生成包含code 的html 页面有两个思路: 使用post 请求,把code 写入数据库(或者文件),然后返回id,再使用id GET 请求获取页面(需要存储,两次请求) 压缩code,把code 作为url参数,使用GET请求获取页面(可能会造成url太长的错误) 那如何截取html呢? 如果是python,可以使用pyqt,渲染html页面,截取webview。 如果使用node,可以使用 html2canvas。 大致流程如下: 哎,这一篇没有代码,就凑不了多少字。 最后,感谢女朋友支持和包容,比❤️ 也可以在公号输入以下关键字获取历史文章:公号&小程序 | 设计模式 | 并发&协程 内推时间

2019-06-13 · 1 min · 59 words

Python 生成便签图片

最近有文字转图片的需求,但是不太想下载 APP,就使用 Python Pillow 实现了一个,效果如下: PIL 提供了 PIL.ImageDraw.ImageDraw.text 方法,可以方便的把文字写到图片上,简单示例如下: from PIL import Image, ImageDraw, ImageFont # get an image base = Image.open('Pillow/Tests/images/hopper.png').convert('RGBA') # make a blank image for the text, initialized to transparent text color txt = Image.new('RGBA', base.size, (255,255,255,0)) # get a font fnt = ImageFont.truetype('Pillow/Tests/fonts/FreeMono.ttf', 40) # get a drawing context d = ImageDraw.Draw(txt) # draw text, half opacity d.text((10,10), "Hello", font=fnt, fill=(255,255,255,128)) # draw text, full opacity d.text((10,60), "World", font=fnt, fill=(255,255,255,255)) out = Image.alpha_composite(base, txt) out.show() 为什么要计算文字的宽高呢?把文字直接写到背景图不可以么? ...

2018-07-08 · 3 min · 469 words

微信公号DIY:MongoDB 简易ORM & 公号记账数据库设计

前两篇 微信公号DIY 系列: 微信公号DIY:一小时搭建微信聊天机器人 微信公号DIY:训练聊天机器人&公号变身图片上传工具 介绍了如何使用搭建&训练聊天机器人以及让公号支持图片上传到七牛,把公号变成一个七牛图片上传客户端。这一篇将继续开发公号,让公号变成一个更加实用的工具账本(理财从记账开始)。 代码: 项目代码已上传至github,地址为gusibi/momo:https://github.com/gusibi/momo HUGOMORE42 账本功能 账本是一个功能比较简单应用,公号内只需要支持: 记账(记账,修改金额,取消记账) 账单统计(提供数据和图片形式的统计功能) 当然后台管理功能就比较多了,这个以后再介绍。 对于数据存储,我选择的是MongoDB(选MongoDB的原因是,之前没用过,想试一下),我们先看下MongoDB和关系型数据库的不同。 MongoDB 什么是MongoDB ? MongoDB 是由C++语言编写的,是一个开放源代码的面向文档的数据库,易于开发和缩放。 mongo和传统关系数据库的最本质的区别在那里呢?MongoDB 是文档模型。 关系模型和文档模型的区别在哪里? 关系模型需要你把一个数据对象,拆分成零部件,然后存到各个相应的表里,需要的是最后把它拼起来。举例子来说,假设我们要做一个CRM应用,那么要管理客户的基本信息,包括客户名字、地址、电话等。由于每个客户可能有多个电话,那么按照第三范式,我们会把电话号码用单独的一个表来存储,并在显示客户信息的时候通过关联把需要的信息取回来。 而MongoDB的文档模式,与这个模式大不相同。由于我们的存储单位是一个文档,可以支持数组和嵌套文档,所以很多时候你直接用一个这样的文档就可以涵盖这个客户相关的所有个人信息。关系型数据库的关联功能不一定就是它的优势,而是它能够工作的必要条件。 而在MongoDB里面,利用富文档的性质,很多时候,关联是个伪需求,可以通过合理建模来避免做关联。 MongoDB 概念解析 在mongodb中基本的概念是文档、集合、数据库,下表是MongoDB和关系型数据库概念对比: SQL术语/概念 MongoDB术语/概念 解释/说明 database database 数据库 table collection 数据库表/集合 row document 数据记录行/文档 column field 数据字段/域 index index 索引 table joins 表连接,MongoDB不支持 primary key primary key 主键,MongoDB自动将_id字段设置为主键 通过下图实例,我们也可以更直观的的了解Mongo中的一些概念: 接下来,我从使用的角度来介绍下如何使用 python 如何使用MongoDB,在这个过程中,我会实现一个简单的MongoDB的ORM,同时也会解释一下涉及到的概念。 简易 Python MongoDB ORM python 使用 mongodb 首先,需要确认已经安装了 PyMongo,如果没有安装,使用以下命令安装: pip install pymongo # 或者 easy_install pymongo 详细安装步骤参考: PyMongo Installing / Upgrading ...

2017-07-16 · 4 min · 659 words

微信公号DIY:训练聊天机器人&公号变身图片上传工具

上一篇 一小时搭建微信聊天机器人 介绍了如何搭建一个可用的聊天机器人,但是和机器人聊完你会发现,聊天机器人实在是太傻了,来回就那么几句。这是因为我们给聊天机器人的数据太少,他只能在我们给的训练集中找它认为最合适的。那么,如何导入更多的训练数据呢? 我能想到最简单的方法是找对话的数据,然后把这些数据作为训练数据训练机器人。 感谢 candlewill 已经收集好了大量的训练数据,dialog_corpus https://github.com/candlewill/Dialog_Corpus 。 HUGOMORE42 这个库中包含电影台词、中英文短信息、自然语言处理相关的数据集、小黄鸡语料等。这里我选择电影台词语料。 语料地址为:dgk_lost_conv:https://github.com/rustch3n/dgk_lost_conv chatterbot 训练逻辑处理模块 这个模块提供训练机器人的方法,chatterbot自带了通过输入list来训练([“你好”, “你好啊”] 后者是前者的回答)以及通过导入Corpus格式文件来训练的方式。 这里我们选择使用第一种,通过输入list来训练机器人。 处理训练数据 首先下载数据集: wget https://codeload.github.com/rustch3n/dgk_lost_conv/zip/master # 解压 $ unzip dgk_lost_conv-master.zip 我们先打开一个文件看下数据结构: E M 你得想想办法 我弟弟是无辜的 M 他可是美国公民啊 M 对此我也无能为力 M 你当然能 M 再去犯罪现场看看 定能证实清白 M 你看 我不过是个夜间办事员而已 M 你若真想解决问题 M 最好等领事来 M 他早上才上班 M 我很抱歉 E M 那我自己来搞定 M 你兄弟 M 关在哪个监狱? M 索纳监狱 E M 怎么了? M 那里关的都是最穷凶极恶的罪犯 M 别的监狱都不收 .conv 语料文件中:E 是分隔符 M 表示会话。因为我是使用输入list 的方式训练数据,这时我可以以分隔符E为分隔,将一段对话放入一个list中,那么上述例子中的训练数据应该被格式化为: ...

2017-07-08 · 3 min · 571 words