品味无法通过单元测试来验证
速览
文章指出,虽然单元测试能有效验证代码的逻辑正确性和功能完整性,但无法衡量代码的优雅程度或设计品味。代码品味涉及主观判断、架构直觉和经验积累,属于难以形式化的领域。这一观点提醒开发者,自动化测试虽重要,但不能完全替代人工审查和对代码质量的审美追求。
AI 深度解读
你无法用单元测试来测试“品味”
背景
作者正在开发一款名为 In the Long Run 的应用程序。该应用的核心理念是“生活是一场马拉松,而非短跑”,旨在通过虚拟跑步为跑者提供长期的激励和动力。用户可以将 Strava 上的跑步里程同步到应用中,并在以国家或大陆为单位的著名路线上进行虚拟穿越。即使某个月或某个季度的表现不佳,用户依然可以在虚拟环游世界的进程中取得进展。
应用通过交互式地图展示用户的进度,允许用户自行探索。然而,作者一直希望丰富地图内容,增加有趣的景点或历史遗迹。对于作者熟悉的路线,他可以手动构建列表,但这无法扩展到他不熟悉的大国路线。因此,作者试图寻找一个数据源,以构建一个提取“兴趣点”(Points of Interest, POIs)的管道。在这个过程中,他不仅与数据和算法搏斗,还不得不面对“品味”和偏见的问题,甚至还要对抗产生幻觉的大语言模型(LLM)。起初作者认为 AI 将是核心功能,但最终 AI 仅作为辅助角色,与其他信号和数据处理流程共同发挥作用。
核心内容
数据集与工具链的选择
作者选择 GeoNames 作为起点,这是一个包含位置、类别和链接的广泛数据源,拥有 Creative Commons 许可证。作者与 Claude 合作,旨在从原始数据转储中构建一个管道,为 In the Long Run 的用户提供相关的兴趣点。
- 技术栈:使用 Python 进行编程,因其库支持良好;使用 Apache Parquet 文件在本地存储处理后的数据;使用 DuckDB 作为查询层。
- 学习策略:这是作者首次使用 Parquet 和 DuckDB。作者认为,在项目中引入一两项新技术是学习的最佳方式,因为如果整个技术栈都是全新的,学习曲线会过于陡峭,可能导致放弃项目。
- AI 辅助开发:虽然 AI 编码代理改变了这一计算方式,但作者发现,掌握大部分所用技术能让他更好地引导代理,做出明智的决策,而不是盲目跟随。
- 工作流:在实施前,作者与 Claude 制定了项目计划。随着进展,为每个步骤构建规范/计划,以便在从早期工作中获得更多见解后进行迭代。这种分阶段的方法允许为每个里程碑启动新的代理会话。将前一个里程碑的结果浓缩为简短的上下文和指令,能获得更快、更好的响应,因为过大的上下文会迅速降低代理工作的质量。
显著性与偏见
数据预处理
- 数据下载:下载并解压 GeoNames 所需文件,由于数据量过大,数据文件被加入
.gitignore。 - 过滤与连接:
- 排除行政区域(国家、州、地区等)。
- 选择特定的功能代码,如公园、历史遗址、城堡、纪念碑、山脉等。
- 对居民点添加人口过滤器,对山脉添加海拔过滤器。
- 虽然这可能导致一些假阴性,但目标是获得一个粗略的初稿。
- 利用 Wikipedia 链接:GeoNames 数据集中的
alternateNames.txt包含 Wikipedia 链接(当isolanguage=link且alternate_name包含%en.wikipedia.org%时)。这被视为显著性/相关性信号,同时也提供了可用于生成简介的文本(Wikipedia 摘要也拥有 Creative Commons 许可证)。 - ** sanity check(健全性检查)**:
- 初稿中出现了澳大利亚农村地名 Stonehenge,而非著名的史前巨石阵。
- 通过检查别名和语言,确保使用相关的 Wikipedia URL 作为交叉引用(GeoNames 将规范名称存储在当地语言中)。
- 结果:全球兴趣点数据从最初的 1300 万行减少到约 72.5 万行的 Parquet 文件。
路线匹配
- 空间过滤:
- 获取路线的 GeoJSON 文件,构建边界框以快速过滤掉距离路线较远的点。
- 遍历路线坐标,检查边界框内的点是否距离路线本身在特定范围内(默认为 50 公里)。
- 使用 Shapely 和 Pyproj 进行地理计算,并计算“沿路线距离”属性,以决定何时向跑者展示该兴趣点。
- 结果与偏见显现:
- 冰岛环岛路线(1,321 公里):511 个 POIs。
- 开普敦到马加丹路线(23,257 公里,应用中最长):10,000 个 POIs。
- 66 号公路(3,787 公里):14,181 个 POIs。
- 洞察:这一早期迹象表明,基于英语 Wikipedia 的信号实际上反映了“英语使用者居住和编辑维基百科的地方”这一偏见。
LLM 的谎言与品味
数据丰富化
- 获取摘要:为每个兴趣点获取 Wikipedia 摘要。
- Wikidata 数据:对于每个 Wikipedia URL,查询有多少种语言的 Wikipedia 拥有该主题的文章。如果页面存在于多种语言中,其重要性可能高于仅存在于英语 Wikipedia 中的页面。Wiki 数据可以全局缓存,以避免后续路线重复抓取。
LLM 评分与幻觉
- 模型选择:使用 Anthropic 的 Haiku 模型,因其速度和价格优势(尽管其“兄弟”模型 Opus 通过 Claude Code 推荐了它)。为了进一步节省成本,作者对调用进行了批处理(输入和输出令牌打 5 折)。
- 技术挑战:
- 这是作者首次以编程方式调用 LLM。API 逻辑清晰,但输出不完全一致。
- 有时会出现奇怪的 Anthropic Markup Language (antml) 变体泄漏到工具调用结果字符串中,需要清理。
- 批处理调用可能需要数小时完成,较大路线的成本约为 10 美元。作者计划尝试本地或更便宜的模型以权衡利弊。
- 幻觉问题:
- 第一次尝试:未充分“接地”(grounding)LLM 丰富化数据,提示词中未施加限制。结果,Haiku 将伊利诺伊州迪凯特的中央公园误判为曼哈顿更著名的同名公园,并大幅提升了其显著性评分。
- 第二次尝试:将位置和行政元数据(国家、城市等)作为输入加入 LLM,并在系统提示词中更仔细地 grounding。
- 残留问题:即使经过改进,抽样检查仍发现几处幻觉。例如,Haiku 改变了城镇的人口规模,并将山脉夸大到不切实际的大小(如同 90 年代经典电影《抓狂老师》中的休·格兰特那样夸张)。
作者最终决定仅使用 LLM 生成的评分作为参考信号之一,而非完全依赖其生成的摘要或事实数据,因为手动验证和修正幻觉的成本过高,且 LLM 在事实准确性上仍不可靠。
关键要点
- AI 的辅助角色:在构建数据管道时,AI(如 Claude 和 Haiku)主要作为辅助工具,用于代码生成、数据丰富和评分,而非完全替代传统的数据处理和验证流程。
- 数据偏见的不可避免性:基于英语 Wikipedia 的数据源天然带有“英语使用者地理分布”的偏见,这在兴趣点的选择上表现明显。
- LLM 的幻觉风险:大语言模型在缺乏充分上下文和约束的情况下,容易产生事实性错误(如混淆同名地点、夸大地理特征)。必须通过引入元数据(位置、行政信息)和严格的系统提示词来减轻这一问题。
- 技术选型策略:在项目中逐步引入新技术(如 DuckDB、Parquet)并结合 AI 代理,比一次性学习整个新栈更高效。掌握现有技术有助于更好地引导 AI
