工作流中同时上传了文件和一个问题,发现llm在思考时说未收到任何上传的文档

下面按「从报错到跑通」的时间线,把这次 Unstructured 插件接入的配置过程梳理成一份可复用的排查流程,后来的同学可以照着一步步对。


一、问题起点:LLM 说「没收到文件」

  • 场景:工作流里用户上传了扫描版 PDF + 问题,前面用了内置「文档提取器(Doc Extractor)」。
  • 现象:LLM 在思考时提示「未收到任何上传的文档」。
  • 根因:扫描 PDF 只有图片,没有文本层;内置 Doc Extractor 不做 OCR,输出 text 为空;Prompt 引用的是空文本,LLM 当然说「没看到文件」。

结论:要解决扫描 PDF 场景,必须换成支持 OCR 的工具节点(如 Unstructured 插件、其他 OCR 插件),而不是只指望内置文档提取器。


二、第一阶段:部署 Unstructured 服务 & 插件接入

1. 本地启动 Unstructured 服务

  • 用户在本地通过 Docker 起了一个 unstructured 容器(端口 8000)。
  • 这个服务和 Dify 其它容器在同一个 Docker 网络里。

这里的关键点:
后面在插件里配置的“服务地址”,要能在容器网络内部被访问,而不是只在宿主机访问。

2. 在 Dify 中安装 Unstructured 插件

  • 在「插件 / Marketplace」里安装官方的 Unstructured 插件。
  • 在插件的 API 配置弹窗中,需要填:
    • 一个「Unstructured 服务 API URL」;
    • 选择「服务类型」(本地部署);
    • 如果服务需要额外认证,再填 Token(本例中用的是本地开放服务,可为空)。

三、第二阶段:URL / FILES_URL 配置相关的坑

1. API URL 拼错导致 404

一开始的配置类似:

Unstructured 服务 API URL: http://unstructured:8000/general/v0/general

问题在于:
插件内部会自己再拼接路径,比如再加上 /general/v0/general,于是最终请求 URL 变成了

http://unstructured:8000/general/v0/general/general/v0/general

Unstructured 容器日志里能看到大量 404,说明路径被重复拼接。

修正方式:

只保留服务根地址

http://unstructured:8000

后续 path 由插件自己处理。修正之后,404 就消失了,开始进入参数校验阶段。

提示点:以后如果看到类似 /xxx/xxx/xxx/xxx/xxx 被重复拼接的路径、且返回 404,可以第一时间检查是不是在插件里把完整 path 写死了。

2. FILES_URL / 文件访问的思路

虽然这次主要问题集中在 API URL,但从你的反馈可以看出另一个常见坑:在 .env 里给 FILES_URL 写 localhost

在多容器部署里:

  • localhost 对每个容器来说都是“自己”,指的不是宿主机,也不是 Dify Web 容器;
  • 结果是:插件所在的容器去访问 http://localhost:xxx/... 时,根本访问不到 Dify 暴露的文件下载地址。

比较稳妥的做法是:

  • .env 里把 FILES_URL 设置为容器网络中可解析的服务名,例如(视你的 compose 而定):
FILES_URL=http://web:3000
# 或 http://nginx:80 等,看你实际暴露的是哪个服务

只要在 Unstructured 容器里能 curl 通这个地址并拿到上传文件,插件就能正常工作。


四、第三阶段:Unstructured 参数(chunking_strategy)错误

URL 修正后,下一个报错变成了参数校验:

An error occurred in ... Partition request failed.
msg:{
  "detail":[
    {
      "type":"literal_error",
      "loc":["body","chunking_strategy"],
      "msg":"Input should be 'by_title'",
      "input":"by_page",
      "ctx":{"expected":"'by_title'"}
    }
  ]
}

含义很直接:

  • 你在插件节点里把 分块策略 填成了 by_page
  • 当前使用的这个 Unstructured 接口只接受 'by_title'(或有限的一些值),于是返回 422 / 400。

修正方式:

  • chunking_strategy 改成接口实际支持的值(例如 by_title),或者干脆先留空使用默认值;
  • 先保证「不报错,能出结果」,再逐步调高级参数。

调试顺序建议:

  1. 先只填最少参数(OCR 策略、语言、文件输入),让节点能成功跑通;
  2. 再按官方文档一个个加上 chunking_strategychunk_sizeoverlap 等;
  3. 每加一项就 test run 一次,确保没有 4xx/5xx 错误。

五、第四阶段:输出结果 & 节点链路的最终形态

修正 URL + 参数后:

  • Unstructured partition 节点已经能成功解析扫描 PDF

  • 输出结构中,你看到的是一个 JSON 对象,大致包含:

    • text:一整段 / 多段拼接好的纯文本(OCR 后的内容);
    • files: [](空列表);
    • images: 可能为空或包含图片引用;
    • elements:结构化元素列表;
    • json:更原始的结构化结果。

1. 为什么 files 是空的?

  • 这是为了兼容“压缩包、多文件、带附件文档”等更复杂场景的统一 schema 字段
  • 你现在传的是单个 PDF 且无嵌套附件,自然没有子文件可输出,所以是空列表
  • 跟 OCR 是否成功无关——真正有用的是 text / elements / json

2. 是否还需要后面的「文档提取器」节点?

在你的这个场景下:

  • Unstructured 已经完成了「文件 → OCR → 结构化元素 → 拼接文本」全过程;
  • 再接一个内置「文档提取器」节点,只是对文本结果再做一次无意义处理,对扫描 PDF 没额外帮助。

推荐的最终工作流:

  1. 用户输入节点:上传扫描 PDF(user_files)。
  2. Unstructured partition 节点:
    • 输入文件:引用 {{ user_files[0].file }}(或你当前用的变量)。
    • 配置 OCR + 分块策略(确保参数合法)。
  3. LLM 节点:
    • 在系统 / 用户提示词里直接引用 {{ partition.text }}(用你节点的输出名)。
  4. 若有进阶需求:
    • elements / json 做更细粒度筛选(例如只取某几页、某些 element type)。

六、给后续同学的「快速自查 Checklist」

遇到类似「扫描 PDF + Unstructured 插件」问题时,可以按这个顺序排查:

  1. 服务连通性

    • 本地 Unstructured 容器在 8000 端口正常运行。
    • 在同一网络的其它容器中,curl http://unstructured:8000 能通。
  2. 插件 API URL

    • 只填根地址:http://unstructured:8000
    • 不要手动拼 /general/v0/general 这类 path,否则会被重复拼接导致 404。
  3. FILES_URL / 文件访问

    • .env 里的 FILES_URL 不用 localhost,而是像 http://web:3000 这样可以从别的容器访问到的服务名;
    • 在 Unstructured 容器里用该 URL 能下载到 Dify 上传的文件。
  4. 参数配置

    • 初次测试时先不指定或少指定高级参数,确保接口不报 4xx;
    • chunking_strategy 之类字段时,对照官方文档使用受支持的值(如 by_title),遇到错误看返回的 expected 提示。
  5. 工作流使用方式

    • 扫描 PDF 场景里,直接用 Unstructured 节点输出的 text 到 LLM;
    • 内置文档提取器在这条链路中可以省略;
    • files 为空不必纠结,重点看 text 是否有内容。

这样整体复盘下来,从「LLM 看不到文件」到「Unstructured OCR 跑通」的路径和中间坑点基本都覆盖到了,后面大家只要照着 URL→网络→参数→输出这几步顺序检查,基本都能比较快定位问题。

1 个赞