Simon Willison 博客 2026-02-26 中文逐字稿
原文来源:simonwillison.net | 翻译日期:2026-02-27
篇一:囤积你知道怎么做的事情
原文标题:Hoard Things You Know How to Do
原文链接:https://simonwillison.net/guides/agentic-engineering-patterns/hoard-things-you-know-how-to-do/
发布日期:2026年2月26日
标签:coding-agents, ai-assisted-programming, generative-ai, agentic-engineering, ai, llms
我关于如何高效使用编程代理的许多建议,其实是我在没有编程代理时就觉得很有用的职业经验的延伸。这里就有一个很好的例子:囤积你知道怎么做的事情。
软件开发这门技艺的很大一部分,在于理解什么是可能的、什么是不可能的,以及对如何实现这些事情至少有一个大致的概念。
这些问题可以是很宽泛的,也可以是相当冷门的。一个网页能不能仅用 JavaScript 来运行 OCR 操作?一个 iPhone 应用能不能在不运行的时候与蓝牙设备配对?我们能不能在 Python 中处理一个 100GB 的 JSON 文件而不必把整个文件加载到内存中?
你掌握的这类问题的答案越多,你就越有可能发现利用技术解决问题的机会——而且这些方式可能是其他人尚未想到的。
知道某件事在理论上是可能的,和亲眼见过它被实现是不一样的。作为软件专业人士,一个关键的资产是积累大量这类问题的答案,最好是配有可运行代码的示例。
我通过多种方式囤积这类解决方案。我的博客和 TIL(Today I Learned,今日所学)博客里塞满了我摸索出来的各种方法笔记。我有超过一千个 GitHub 仓库,收集了我为不同项目编写的代码,其中许多是演示某个关键思路的小型概念验证项目。
最近,我还利用大语言模型来帮助扩展我的代码解决方案集合,去解决更多有趣的问题。
tools.simonwillison.net 是我最大的 LLM 辅助工具和原型集合。我用它来收集我所说的 HTML 工具——单个 HTML 页面,嵌入了 JavaScript 和 CSS,解决一个特定的问题。
我的 simonw/research 仓库里有更大、更复杂的示例,在那里我挑战编程代理去研究一个问题,然后带回可运行的代码和一份详细说明发现的书面报告。
从你的囤积中重新组合
为什么要收集所有这些东西?除了帮助你构建和扩展自己的能力之外,你在这个过程中产生的资产会成为你的编程代理极其强大的输入。
我最喜欢的提示模式之一,就是告诉代理通过组合两个或更多现有的可运行示例来构建新的东西。
有一个项目帮助我深刻认识到这种方式有多高效——那就是我添加到工具集合中的第一个项目:一个基于浏览器的 OCR 工具,更详细的描述在这里。
我想要一个简单的、基于浏览器的工具,用于对 PDF 文件的页面进行 OCR——特别是那些完全由扫描图像组成、根本没有文本版本的 PDF。
我之前曾经试验过在浏览器中运行 Tesseract.js OCR 库,发现它非常能干。这个库提供了成熟的 Tesseract OCR 引擎的 WebAssembly 构建版本,让你可以从 JavaScript 调用它来从图像中提取文本。
但我不想处理图像,我想处理 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() {
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...";
for (const img of images) {
const altTextarea = img.parentNode.querySelector(".textarea-alt");
if (altTextarea.value === "") {
const imageUrl = img.src;
var { data: { text } } = await worker.recognize(imageUrl);
altTextarea.value = text;
progressBar.value += 1;
}
}
await worker.terminate();
ocrButton.innerText = "OCR complete";
};
}
利用这些示例,组合成一个单独的 HTML 页面,嵌入 HTML、CSS 和 JavaScript,提供一个大的正方形区域,用户可以将 PDF 文件拖放到上面,然后 PDF 的每一页都会被转换为 JPEG 并显示在页面下方,接着使用 Tesseract 进行 OCR,结果显示在每张图片下方的文本框中。
这完美地运作了!模型输出了一个概念验证页面,完全实现了我需要的功能。
后来我又和它来回迭代了几次才得到最终结果,但整个过程只花了几分钟就构建了一个真正有用的工具,从那以后我一直在受益。
编程代理让这一切更加强大
我在 2024 年 3 月构建了那个 OCR 示例,距离 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 页面。
这里的核心思想是,编程代理意味着我们只需要弄清楚一个有用的技巧一次。如果这个技巧随后被记录在某个地方,并附有可运行的代码示例,我们的代理就可以参考该示例,并用它来解决未来任何类似形状的项目。
篇二:引用 Andrej Karpathy
原文标题:A quote from Andrej Karpathy
原文链接:https://simonwillison.net/2026/Feb/26/andrej-karpathy/
发布日期:2026年2月26日
标签:ai, andrej-karpathy, generative-ai, llms, ai-assisted-programming, coding-agents, agentic-engineering
Andrej Karpathy 说道:
“很难传达 AI 在最近两个月内让编程发生了多大的变化:不是以’一切照常的进步’那种渐进方式随着时间推移的变化,而是具体就在去年 12 月。虽然有一些附加条件,但在我看来,编程代理在 12 月之前基本不能用,12 月之后基本能用了——模型的质量、长期连贯性和韧性有了显著提升,它们能够坚持完成大型和长期的任务,足以对默认的编程工作流程产生极大的颠覆性影响。”
——Andrej Karpathy
篇三:Google API 密钥本不是秘密,但 Gemini 改变了规则
原文标题:Google API Keys Weren’t Secrets. But then Gemini Changed the Rules
原文链接:https://simonwillison.net/2026/Feb/26/google-api-keys/
发布日期:2026年2月26日
Simon Willison 报道了 Truffle Security 发现的一个重大安全问题:原本设计为可公开使用的 Google API 密钥,在 Gemini API 启用后变得危险了。
核心问题
这个问题的本质是一个权限提升序列。开发者历史上将 Google Maps API 密钥直接嵌入网站——因为这些密钥就是被设计为公共标识符的。然而,当 Google 在相同的项目上启用了 Gemini API 时,那些相同的密钥获得了访问敏感端点和计费服务的权限——而开发者没有收到任何通知。
正如文章所解释的:「让这成为权限提升而非配置错误的,是事件发生的顺序。」开发者创建了用于 Maps 的公共 API 密钥,Gemini API 随后在这些项目上被启用,然而「开发者从未被警告过密钥的权限在其不知情的情况下发生了变化。」
问题规模
Truffle Security 在 2025 年 11 月的 Common Crawl 数据中发现了 2,863 个暴露的 API 密钥,这些密钥能够访问 Gemini。值得注意的是,其中一些属于 Google 自己,有一个从 2023 年 2 月就已部署——比 Gemini API 早了好几年。
建议
Google 正在努力撤销受影响的密钥,但用户应该验证自己的 API 密钥是否受到这种漏洞的影响。
篇四:引用 Benedict Evans
原文标题:A quote from Benedict Evans
原文链接:https://simonwillison.net/2026/Feb/26/benedict-evans/
发布日期:2026年2月26日
标签:ai, openai, chatgpt, benedict-evans
Benedict Evans 说道:
“如果人们最多每周只使用这个东西几次,而且在普通的日子里想不出用它做什么,那它就没有改变他们的生活。OpenAI 自己也承认这个问题,谈到模型能做什么和人们实际用它们做什么之间存在一个’能力差距’,在我看来,这是一种回避说出你没有清晰的产品市场契合度的方式。因此,OpenAI 的广告项目一方面只是为了覆盖服务那 90% 或更多不付费用户的成本(并在广告商中获得早期优势,以及在这可能如何运作方面积累早期学习经验),但更具战略意义的是,它也是为了让给那些用户提供最新、最强大(即最昂贵的)模型成为可能,希望这能加深他们的参与度。”
——Benedict Evans,摘自《OpenAI 将如何竞争?》(How will OpenAI compete?)