LLM 不只靠模型:搭建可替换 Harness,并用 Scrapy 接入可靠网页数据

2026-07-24 27 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

在 LLM 应用里,模型当然重要,但真正决定开发流程能否稳定复用的,往往是模型外围的 harness(编排与运行框架)。它负责把提示词、工具调用、上下文、权限、重试、日志和人工确认组织成一个可观测的闭环。模型可以替换,流程不能靠临场发挥。

本期话题同时谈到 agentic 开发工作流、Scrapy 网页抓取和 Python 应用自托管。把它们放在一起看,会得到一个很实用的结论:LLM 应该消费经过约束、清洗和追踪的数据,而不是直接面对不可控的网页与生产环境。

模型是推理引擎,Harness 才是操作系统

把一个开发型 agent 想象成一名能快速写代码的工程师。模型决定它能否理解需求、生成代码和分析错误;harness 则规定它:

  • 从哪里读取任务和代码上下文;
  • 哪些命令、文件和网络请求可以执行;
  • 工具失败后如何重试、降级或停止;
  • 什么时候必须请求人工确认;
  • 如何记录输入、工具调用、输出和最终变更。

没有 harness 的聊天式工作流通常很脆弱:一次上下文遗漏、一次命令误执行,或一次工具返回格式变化,都可能让结果失去可重复性。

一个可维护的 harness 不必从复杂框架开始。核心是把不稳定部分隔离出去:模型供应商、提示词模板、工具执行器、状态存储和审核策略应当有明确边界。这样更换模型时,测试和权限控制不需要跟着重写。

Agentic 开发流程应当分层,而不是放开权限

对代码库进行自动化修改时,最危险的设计是让 agent 同时拥有无限上下文、任意 shell 权限和自动提交能力。更稳妥的流程可以分成四步:

  1. 收集:读取 issue、相关源码、测试和运行日志。
  2. 规划:产出结构化修改计划,明确会触碰的文件与验证方式。
  3. 执行:通过受限工具写入工作区,并运行允许列表中的命令。
  4. 验收:执行测试、汇总 diff,交由人或规则引擎决定是否合并。

这里的关键不是让 agent 多做几步,而是让每一步都有可检查的输入和输出。例如,规划阶段输出 JSON,比输出一段自由文本更容易交给后续程序校验。

可以这样实践一个最小任务契约:

{
  "task": "为用户接口补充分页测试",
  "allowed_paths": ["src/api/users.py", "tests/test_users.py"],
  "allowed_commands": ["pytest -q tests/test_users.py"],
  "requires_review": true
}

这个契约不能阻止所有错误,但能把 agent 的活动范围压缩到可审计边界内。尤其在自托管环境中,路径限制、命令白名单和密钥隔离应当被视为基础设施,而不是后补功能。

用 Scrapy 把网页变成可消费的数据源

网页抓取很适合作为 LLM 工作流的上游,但原始 HTML 不应该直接塞进上下文窗口。导航栏、广告、重复模块和过期内容会浪费 token,也会干扰检索与回答。

Scrapy 提供了较清晰的抓取结构:请求调度、CSS/XPath 选择器、限速、导出和中间件可以各自配置。下面是一个最小项目,抓取允许访问的文档页面标题与正文,并导出为 JSON Lines。

先创建虚拟环境并安装依赖:

python -m venv .venv
source .venv/bin/activate
pip install scrapy
scrapy startproject docsbot
cd docsbot

将下面内容保存为 docsbot/spiders/docs.py。运行前将 example.com 和选择器替换为你有权抓取的网站与实际页面结构:

import scrapy


class DocsSpider(scrapy.Spider):
    name = "docs"
    allowed_domains = ["example.com"]
    start_urls = ["https://example.com/docs/"]

    custom_settings = {
        "DOWNLOAD_DELAY": 1.0,
        "ROBOTSTXT_OBEY": True,
        "USER_AGENT": "docsbot/0.1 (+contact@example.com)",
    }

    def parse(self, response):
        for href in response.css("main a::attr(href)").getall():
            yield response.follow(href, callback=self.parse_page)

    def parse_page(self, response):
        title = response.css("h1::text").get(default="").strip()
        paragraphs = response.css("main p::text").getall()
        content = "\n".join(text.strip() for text in paragraphs if text.strip())

        if title and content:
            yield {
                "url": response.url,
                "title": title,
                "content": content,
            }

执行抓取:

scrapy crawl docs -O documents.jsonl

生成的每一行都是一个 JSON 文档,适合后续做去重、分块、嵌入和检索。实际接入 LLM 前,建议额外保留 url、抓取时间、内容哈希和文档版本;当回答需要引用或数据发生变化时,这些字段能帮助定位来源并增量更新索引。

自托管时,把运行边界当作产品能力

自托管 Python 应用的价值不只是省去托管平台费用。对于包含内部代码、业务文档或抓取数据的 agent 系统,它意味着可以控制数据驻留位置、网络出口、日志保留策略和模型调用路径。

但自托管也会把责任带回来:升级、备份、TLS、监控、任务队列和密钥轮换都需要明确负责人。一个常见的务实做法是先拆分状态:Web 服务保持无状态,抓取与索引任务交给 worker,文档和任务状态落到受备份保护的数据库或对象存储。

下面的 Compose 配置展示了一个可改造的本地结构:应用服务负责 API,worker 负责异步任务。示例使用环境变量引用密钥,避免把密钥写进镜像或仓库。

services:
  api:
    image: my-agent-app:latest
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: ${DATABASE_URL}
      LLM_API_KEY: ${LLM_API_KEY}
    restart: unless-stopped

  worker:
    image: my-agent-app:latest
    command: python -m app.worker
    environment:
      DATABASE_URL: ${DATABASE_URL}
      LLM_API_KEY: ${LLM_API_KEY}
    restart: unless-stopped

生产部署还应补上反向代理、健康检查、最小权限运行用户、出站网络策略和备份演练。特别是带有 shell 或浏览器工具的 agent,容器隔离并不等于权限安全,仍需限制挂载目录、系统调用和可访问的凭据。

从一个闭环开始验证

不必一开始就构建全自动开发团队。更合适的起点是一个闭环:Scrapy 抓取一类已授权文档,清洗后写入索引;agent 只能检索这些文档,并通过受限工具生成一个补丁或报告;最后由测试和人工审核决定结果是否可用。

采用时可以检查四件事:

  • 模型替换后,工具接口、任务状态和审计日志是否仍然有效;
  • 每次抓取是否遵守站点规则、频率限制和数据使用边界;
  • 每个自动化动作是否有最小权限、可追踪记录和停止条件;
  • 自托管环境是否能恢复数据、轮换密钥并定位失败任务。

模型能力会持续变化,但可靠的 harness、干净的数据管道和清晰的运行边界,会让这些变化真正转化为可交付的工程效率。


相关推荐