为什么 AI 编程如此艰难

2025 年 03 月 21 日

前几天看到一个项目,叫 ai-trend-publish,思路很有意思,从一些数据源去获取信息,扩写之后生成公众号文章,然后发布到微信公众号。全部自动化完成。

这个思路让我产生了一个想法,我经常看一些国外的技术文章,如果中间加一个翻译流程的话,是不是也可以做到:给出一个 URL,自动提取其内容,然后翻译,再自动发布到微信公众号?

想法有了,我打算试一下。

参考现有工程

先看看人家的代码,确定我这个需求能不能直接用 ai-trend-publish。看起来,大概思路是以下几点:

  • 解决信息源的问题。原始工程核心是:
    • 通过 x API 获取推特上的信息。
    • 通过 firecrawler 获取任意网页信息。
    • 其他数据源方式,但似乎没有看到具体实现方法。
  • 文章生成的问题。做法是结合一系列 prompt,将获取到的信息源的数据提交给 LLM 进行扩写,然后按照特定的排版方式生成文章。
  • 发布的问题。调用公众号 API 去发布。

大概看明白之后,我发现有几个原因导致我没办法直接使用:

  • X API 的限制。免费 API 额度有限,我简单算了一下,很容易就会用尽。
  • firecrawler 服务同样是收费的。原作者似乎计划增加自动化注册并获取免费使用额度的功能,但这种风格我不太喜欢,主要是觉得太容易受到 firecrawler 的策略影响。
  • 公众号 API 也有各种限制,不过在当时我还没发现。

说白了还是穷,不想花钱,那就自己动手吧。另外我手上有自建的 LLM,因此推理这部分不用考虑费用问题。

模块规划和遇到的问题

需求其实很简单,跟参考项目其实很接近。那么就是分成下面几个模块,最后整合:

  • 数据抓取,把网页转换成结构化数据。
  • 翻译转换,翻译网页内容,尽量保持其原有格式不变。
  • 上传发布,调用公众号 API,提交。

数据抓取

本来的想法是把 URL 直接丢给 LLM 去抓取的,但 LLM 不支持访问网页。我在这个时间点还没搞定抓网页功能,因此先用普通的 http client 和 puppeteer 方式搞定网页抓取。不过后来发现,即使 LLM 能抓取网页也不行。

会失败的原因是:网页太大了。随便一个网页几万个字符,LLM 的上下文长度一般都不够。所以就算 LLM 能通过 MCP 或 Tool 方式抓取网页,也会因为上下文被截断而无法完成任务,更不要说 token 可能会爆掉的问题了。

那就先洗数据吧。把明显不需要的部分去掉。我打算用各种 sanitize 库处理了一下抓到的网页文本,将数据清洗到上下文限制之内,再丢给 LLM。然后我发现,洗数据这个过程,其实也很难保证精确性。这部分没有什么万金油算法。

不说洗数据过程中的问题,洗过的数据总还算是包含目标文章的正文的。但不管我的 prompt 怎么写,LLM 都无法准确的从 html snippet 中,结构化的提取出文章的正文。不仅如此,LLM 经常会在最终的输出考虑 html 标签,这样就导致输出内容的主题都产生了变化。

我的 prompt 能力还有欠缺,即使是在各种 AI 的辅助下,写出来的 prompt 也无法准确达成我的目的。既然如此,我又换了个思路,不再考虑通用网页抓取,而是通过特定网站的 API,来获取内容。

虽然跟一开始的目标已经有了一些差距,但不管怎么说,我总算是获取到了结构化的数据。

公众号上传发布

抓取到了结构化数据后,文章还是 html 格式的。我觉得这点正好,因为公众号支持 html 格式。

本来应该做翻译模块,不过由于文章已经准备好了,我想先调试一下公众号发布,于是开始做上传发布模块。

然后就发现了一堆问题:

  1. 调用公众号接口前,需要先获取 access token,这里有个 IP 限制问题。家用宽带 IP 每天都会变更,因此需要每天去公众号修改允许的 IP。虽然可以搞个公网 IP 来处理,不过这样对调试来说,可真够麻烦的。
  2. 直接上传 html 时,可能会因为包含了不支持的标签导致上传失败。但问题是 API 文档中,并没有详细说明对于 html 的限制有哪些。虽然我尝试把所有的链接都清洗掉之后,偶尔能上传成功,但这样也太不靠谱了。
  3. 公众号文章要求必须有个 cover image,但这个图片无法在上传 html 草稿时同时提交,而是必须单独调用上传资源接口,然后再将获得的资源 ID 放在上传草稿的时候提交。这样就导致整个的过程多了几步中间操作。如果最后一步失败的话,还要考虑已经上传的 cover image 如何处理。这样业务逻辑就变得复杂。
  4. 如果文章内文有图片,也需要特殊的上传处理,使得业务逻辑进一步复杂化。

上面一堆问题虽然不是完全不能解决,但为了处理这些问题,需要付出的努力和最终可能的收益完全不成正比。因此,我放弃了自动化上传。

注:ai-trend-publish 实际上规避了我遇到的问题,因为它的文章是自己生成的,结构由自己控制。所以直接规避了问题 2/4,但它无法规避问题 1。至于问题 3 本身,倒还不算太复杂,但需要大量调试。

翻译服务

放弃自动化上传功能后,本来我有点想放弃了,但又觉得做了一小半有点可惜。即使不能发布,好歹也可以自己用不是?于是我打算把翻译服务做完。至少,先把生成中文文章的过程自动化,能简化我的发布流程也是好的。

本来翻译服务也是打算用 LLM 的,但没想到这个过程也出幺蛾子。这次问题不是出在 prompt 上了,毕竟现成的 prompt 很多。

问题出在结果上。简单来说就是,整篇文章丢进去,翻译完成后,在不固定的位置,会丢掉一些内容。

比如说文章内部有一段列表,分了六七段。但翻译完后的输出,可能只剩下三四段。另外,列表中每一句话,后面都有一小节来强化这一句话。翻译完后的输出,可能就会丢掉这一小节。

这种问题在推理型 LLM 上,比如 deepseek-r1:14b,感觉更明显。在普通 LLM 上,比如 qwen2.5:7b,感觉会略好一点,但也绝不是没有问题。

尝试了多次,这个问题也是无解。最终我还是找了个免费的 deeplx 项目,把转换成 markdown 的文档逐句翻译,总算最终结果差强人意。

体会

整个开发过程给我的感受有以下几点:

  1. LLM 永远不可能精确输出。但这并不是说 LLM 不好,只是说它需要特定的应用场景。
  2. 现实世界的问题,不自己尝试一下,是想不全的。比如上传公众号的问题,我以为别人都做过了,没什么难的。但实际看起来,API 的限制比想象中多。别人的用例能绕过,但我自己的用例没法绕过。

说到底,LLM 输出的模糊性,以及开发所需要的精确性,是难以糅合的。灵活的需求、以及客户的容忍度,可以在一定程度上减轻这两者适配的难度,但你不可能把所有的希望都寄托在这上面。

Top