第 03 篇
积累你已经掌握的实现方法
Simon Willison 介绍如何积累经过运行验证的代码示例,并让编程智能体组合和复用这些示例,以浏览器中的 PDF OCR 工具为例。
Hex4C59 5 分钟阅读 更新于 2026-10-17
软件工程·译文
译文信息
- 原文:Hoard things you know how to do
- 作者:Simon Willison
- 中文翻译与整理:Hex4C59(AI 辅助)
- 翻译整理:2026-10-11
本文为非官方中文译文,依据提供的原文正文翻译。正文中的「我」、项目经历和效果判断均指原作者。示例代码保留原文版本,译者未执行;第二段代码依赖原页面中的元素和变量,不能独立运行。提示词中的本地路径属于作者的示例环境。
我分享的许多与编程智能体高效协作的技巧,都来自过去职业生涯中行之有效的做法;如今,我把这些做法用在了智能体上。下面就是一个很好的例子:积累你已经掌握的实现方法。
开发软件的一项重要能力,是知道什么能做到、什么做不到,并且至少大致了解怎样才能做到。
这类问题既可能很宽泛,也可能相当冷门。网页能只用 JavaScript 完成光学字符识别(Optical Character Recognition,OCR)吗?iPhone 应用即使没有运行,也能与蓝牙设备配对吗?能不能用 Python 处理一个 100 GB 的 JSON 文件,而不先把整个文件载入内存?
你知道的这类答案越多,就越可能发现别人尚未想到的技术用法,用它们解决问题。
要对这些答案有把握,最好的方法就是亲眼看到代码运行起来,证明它们可行。知道一件事在理论上做得到,与亲眼见过它被实现,并不是一回事。对软件专业人士来说,这些问题的答案和相应实例都是值得积累的重要资产。
我用几种不同的方式收集这样的解决方案。我的博客和 TIL 博客里,记录了许多我摸索出来的实现方法。TIL 是 Today I Learned 的缩写,也就是「今天学到了什么」。我还有一千多个 GitHub 仓库,收集了为不同项目编写的代码,其中许多都是演示某个关键想法的小型概念验证。
最近,我也开始用大语言模型(Large Language Models,LLM)帮助自己积累更多代码方案,解决有趣的问题。
tools.simonwillison.net 是我最大的 LLM 辅助工具与原型集合。我用它收集自己所说的 HTML 工具:在单个 HTML 页面中嵌入 JavaScript 和 CSS,用来解决一个具体问题。
我的 simonw/research 仓库则包含更大、更复杂的案例。在这些案例中,我让编程智能体研究一个问题,拿出能够运行的代码,并写一份报告,详细说明它发现了什么。
重新组合已有积累 #
为什么要收集所有这些东西?除了帮助你培养和拓展自己的能力,这些材料还可以交给编程智能体,供它参考。
我最喜欢的一种提示词用法,就是让智能体把两个或更多已经能正常工作的示例组合起来,做出一个新东西。
有一个项目让我清楚地认识到这种做法有多有效:它是我最早加入工具集的项目,一个在浏览器中运行的 OCR 工具。我在这篇文章中更详细地介绍了它。
我想要一个简单易用的浏览器工具,能够识别 PDF 页面中的文字,尤其是那些完全由扫描图像组成、根本没有文本版本的 PDF。
此前,我尝试过在浏览器中运行 Tesseract.js OCR 库,发现它的能力很强。这个库提供了成熟的 Tesseract OCR 引擎的 WebAssembly 版本,让你可以从 JavaScript 中调用它,提取图像里的文字。WebAssembly 是一种可在浏览器中执行的二进制格式,使这类引擎能够在网页中运行。
但我想处理的是 PDF,而不是图像。这时,我想起自己也用过 Mozilla 的 PDF.js 库。它的功能之一,就是把 PDF 的每一页渲染成图像。
我的笔记中,恰好保存了使用这两个库的 JavaScript 代码片段。
下面是我交给模型的完整提示词,当时使用的是 Claude 3 Opus。它把两个示例放在一起,并描述了我想要的结果:
以下代码演示如何打开一个 PDF,并把每一页转换成一张图像:
<!DOCTYPE html>
<html>
<head>
<title>PDF to Images</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.9.359/pdf.min.js"></script>
<style>
.image-container img {
margin-bottom: 10px;
}
.image-container p {
margin: 0;
font-size: 14px;
color: #888;
}
</style>
</head>
<body>
<input type="file" id="fileInput" accept=".pdf" />
<div class="image-container"></div>
<script>
const desiredWidth = 800;
const fileInput = document.getElementById('fileInput');
const imageContainer = document.querySelector('.image-container');
fileInput.addEventListener('change', handleFileUpload);
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.9.359/pdf.worker.min.js';
async function handleFileUpload(event) {
const file = event.target.files[0];
const imageIterator = convertPDFToImages(file);
for await (const { imageURL, size } of imageIterator) {
const imgElement = document.createElement('img');
imgElement.src = imageURL;
imageContainer.appendChild(imgElement);
const sizeElement = document.createElement('p');
sizeElement.textContent = `Size: ${formatSize(size)}`;
imageContainer.appendChild(sizeElement);
}
}
async function* convertPDFToImages(file) {
try {
const pdf = await pdfjsLib.getDocument(URL.createObjectURL(file)).promise;
const numPages = pdf.numPages;
for (let i = 1; i <= numPages; i++) {
const page = await pdf.getPage(i);
const viewport = page.getViewport({ scale: 1 });
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = desiredWidth;
canvas.height = (desiredWidth / viewport.width) * viewport.height;
const renderContext = {
canvasContext: context,
viewport: page.getViewport({ scale: desiredWidth / viewport.width }),
};
await page.render(renderContext).promise;
const imageURL = canvas.toDataURL('image/jpeg', 0.8);
const size = calculateSize(imageURL);
yield { imageURL, size };
}
} catch (error) {
console.error('Error:', error);
}
}
function calculateSize(imageURL) {
const base64Length = imageURL.length - 'data:image/jpeg;base64,'.length;
const sizeInBytes = Math.ceil(base64Length * 0.75);
return sizeInBytes;
}
function formatSize(size) {
const sizeInKB = (size / 1024).toFixed(2);
return `${sizeInKB} KB`;
}
</script>
</body>
</html>以下代码演示如何对图像进行 OCR:
async function ocrMissingAltText() {
// Load Tesseract
var s = document.createElement("script");
s.src = "https://unpkg.com/tesseract.js@v2.1.0/dist/tesseract.min.js";
document.head.appendChild(s);
s.onload = async () => {
const images = document.getElementsByTagName("img");
const worker = Tesseract.createWorker();
await worker.load();
await worker.loadLanguage("eng");
await worker.initialize("eng");
ocrButton.innerText = "Running OCR...";
// Iterate through all the images in the output div
for (const img of images) {
const altTextarea = img.parentNode.querySelector(".textarea-alt");
// Check if the alt textarea is empty
if (altTextarea.value === "") {
const imageUrl = img.src;
var {
data: { text },
} = await worker.recognize(imageUrl);
altTextarea.value = text; // Set the OCR result to the alt textarea
progressBar.value += 1;
}
}
await worker.terminate();
ocrButton.innerText = "OCR complete";
};
}请利用这些示例,组合出一个内嵌 HTML、CSS 和 JavaScript 的单页工具。页面上要有一个大的正方形区域,用户可以把 PDF 文件拖放进去。文件放入后,把 PDF 的每一页转换成 JPEG,并显示在页面下方;随后使用 Tesseract 进行 OCR,把识别结果放在每张图像下方的文本框(textarea)中。
这次效果非常好!模型生成了一个概念验证页面,完全满足了我的需求。
之后,我又和模型迭代了几次,才得到最终版本。不过,只用了几分钟,我就做出了一个实用的工具,此后一直受益于它。
编程智能体让这种方法更加强大 #
这个 OCR 示例是我在 2024 年 3 月做的,距离 Claude Code 首次发布还有将近一年。编程智能体的出现,让收集可运行示例这件事变得更有价值。
如果你的编程智能体可以访问互联网,就可以让它做这样的事情:
使用 curl 获取 https://tools.simonwillison.net/ocr 和 https://tools.simonwillison.net/gemini-bbox 的源代码,然后构建一个新工具:让我从 PDF 中选择一页,把它传给 Gemini,并返回这一页中插图的边界框,也就是标出插图位置的矩形区域。
(我在这里明确指定 curl,是因为 Claude Code 默认会使用 WebFetch 工具,它会总结页面内容,而不是返回原始 HTML。)
编程智能体很擅长搜索,因此你可以让它在自己的计算机上运行,并告诉它去哪里寻找你希望它参考的示例:
为
~/dev/ecosystem/datasette-oauth项目添加使用模拟 HTTP 请求的测试,参考~/dev/ecosystem/llm-mistral中的做法。
通常这样就够了。智能体会让一个负责搜索的子智能体查找示例,再由它带回完成任务所需的具体信息。
由于我的许多研究代码都是公开的,我经常让编程智能体把仓库克隆到 /tmp,再作为输入参考:
从 GitHub 把 simonw/research 克隆到
/tmp,找到将 Rust 编译为 WebAssembly 的示例,再参考这些示例,为当前项目构建一个演示用的 HTML 页面。
这里的核心想法是:有了编程智能体,一项有用的技巧只需要摸索成功一次。只要把做法记录下来,并附上能够运行的代码示例,智能体就能查阅它,再将这种做法用于今后任何类似的项目。
一起讨论
选中正文中的文字,可以针对这段内容发起讨论。
正在读取讨论…
在 GitHub 查看与管理讨论 ↗