我关于如何高效使用编程智能体的许多建议,其实都是我职业生涯中那些行之有效的经验之谈,即便没有 AI 参与时也同样适用。一个绝佳的例子就是:囤积那些你“知道怎么做”的事情。
构建软件的一项核心能力,在于理解什么是可能的,什么是不可能的,并且至少对如何实现这些目标有一个大致的思路。
这些问题可能宏大,也可能非常冷门。例如:网页能否仅靠 JavaScript 运行 OCR(光学字符识别)?iPhone 应用在未运行时能否与蓝牙设备配对?我们能否在 Python 中处理 100GB 的 JSON 文件而不将其全部加载到内存?
你掌握这类问题的答案越多,就越有可能发现别人尚未想到的、利用技术解决问题的机会。
要对这些答案充满信心,最好的办法就是亲眼看到运行中的代码。知道某事在理论上可行,与亲眼见证它被实现完全是两回事。作为软件专业人士,一项关键资产就是收集大量此类问题的答案,并附带相关的证明代码。
我通过多种方式“囤积”这些方案。我的博客和“TIL(今天学到了)”博客里塞满了关于各种技术实现的研究笔记。我在 GitHub 上有一千多个仓库,收集了为不同项目编写的代码,其中很多都是演示核心理念的小型原型(PoC)。
最近,我开始利用 LLM 来帮助我扩展这一代码方案库。
-
tools.simonwillison.net:这是我最大的 LLM 辅助工具和原型集合。我用它来收集我所谓的“HTML 工具”——即嵌入了 JS 和 CSS、解决特定问题的单 HTML 页面。
-
simonw/research 仓库:这里存放着更庞大、更复杂的示例。我会要求编程智能体研究某个问题,并返回可运行的代码以及一份详细说明其发现的报告。
重新组合你的“宝库”
为什么要收集这些东西?除了提升个人能力外,你在这个过程中生成的资产会成为编程智能体的强大输入。
我最喜欢的提示词模式之一,就是让智能体通过组合两个或多个现有的运行示例来构建新东西。
一个让我深刻意识到这种方式多么有效的项目,是我工具集里的第一个成员:一个基于浏览器的 OCR 工具。
当时,我想做一个简单的浏览器工具,用来对 PDF 页面进行 OCR 处理——尤其是那些完全由扫描图像组成、没有文本层的 PDF。
我之前尝试过在浏览器中运行 Tesseract.js 库,发现它非常强大。该库提供了成熟的 Tesseract OCR 引擎的 WebAssembly 版本,允许你从 JS 调用它来提取图像文本。
但我不想处理单张图像,我想处理 PDF。接着我想到,我以前用过 Mozilla 的 PDF.js 库,它可以将 PDF 的每一页渲染成图像。
在我的笔记里,正好有这两个库的 JavaScript 代码片段。于是,我把这两个示例结合起来,向模型(当时是 Claude 3 Opus)发出了如下提示词:
这段代码展示了如何打开 PDF 并将其转换为每页一张的图像:
```html
<!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:
```javascript
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 和 JS 的单页面。提供一个大方框,用户可以将 PDF 文件拖放进去。操作后,将 PDF 的每一页转换为 JPEG 显示在页面上,然后使用 Tesseract 运行 OCR,并将结果显示在每个图像下方的文本框中。
结果非常完美!模型直接吐出了一个完全符合我要求的原型页面。我只进行了几次简单的迭代就完成了最终版本。这只花了几分钟时间,却打造出了一个让我受益至今的实用工具。
编程智能体让这一模式更强大
我构建那个 OCR 示例是在 2024 年 3 月,比 Claude Code 发布早了近一年。而编程智能体的出现,让囤积运行示例的价值翻倍了。
如果你的编程智能体可以访问网络,你可以告诉它
使用 `curl` 获取 `https://tools.simonwillison.net/ocr` 和 `https://tools.simonwillison.net/gemini-bbox` 的源码,构建一个新工具,让我能选择 PDF 的一页并传给 Gemini,返回该页插图的检测框。” _
(这里指定 curl 是因为 Claude Code 默认会总结页面,而我们需要原始 HTML。)
编程智能体也非常擅长搜索,这意味着你可以在本地运行它们,并告诉它们去哪里找示例:
参考 `~/dev/ecosystem/llm-mistral` 的实现方式,为 `~/dev/ecosystem/datasette-oauth` 项目添加模拟 HTTP 请求的测试。
通常这就足够了——智能体会启动一个搜索子智能体去调研,并提取完成任务所需的细节。
由于我的研究代码大多是公开的,我经常让智能体将仓库克隆到 /tmp 目录作为输入:
从 GitHub 克隆 `simonw/research` 到 `/tmp`,找到将 Rust 编译为 WebAssembly 的示例,并以此为本项目构建一个演示 HTML 页面。
核心理念在于:有了编程智能体,任何有用的技巧我们都只需要解决一次。 只要这个技巧被记录在某处并配有可运行的代码示例,我们的智能体就能在未来查阅它,并用它来解决任何类似的问题
原文:Agentic Engineering Patterns by Simon Willison
关注公众号「Python之禅」,回复「1024」免费获取Python资源