markdown转换器输出变量中,对应的三个Key,分别是text、files、json,其中files是数组,内容包括文件的基本信息和url。
在下个节点中,我使用files作为上下文,会报错,如下:
Run failed: Invalid context structure: dify_model_identity=‘dify__file’ id=None tenant_id=‘ccd7d781-3643-4d21-a0e9-72ad023’ type=<FileType.DOCUMENT: ‘document’> transfer_method=<FileTransferMethod.TOOL_FILE: ‘tool_file’> remote_url=None related_id=‘9eb8576-436c-b83e-529cba96c66c’ filename=‘20260121_170237.xlsx’ extension=‘.xlsx’ mime_type=‘application/vnd.openxmlformats-officedocument.spreadsheetml.sheet’ size=9845
为什么我不但无法提取到我想要的url,Dify还会报错???请大神指导一下!
看截图应该是想提取 xlsx 里的内容然后给我 llm 分析?不要用「上下文」去引用 files,正确的方法参考: Document Extractor - Dify Docs
@Dify_Forum_Helper 来科普一下,配合基础知识和故事点。
另外,Marketplace 也有一些关于 excel 有趣的 Plugins,可以用作未来探索
@Dify_Forum_Helper 你也可以搜索一下
这里确实有两个容易混淆的点:「文件对象」 和 「可给 LLM 使用的文本内容」。
1. 为什么用 files 做「上下文」会报错?
Markdown 转换器的输出大致是这样结构的:
{
"text": "...",
"files": [
{
"dify_model_identity": "__dify__file__",
"type": "document",
"filename": "20260121_170237.xlsx",
"extension": ".xlsx",
"mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"size": 9845,
"url": "https://...签名链接..."
}
],
"json": [...]
}
files 里的每一项,本质上是 Dify 内部的文件对象 / 句柄,包含 __dify__file__、FileType.DOCUMENT 等信息。
- LLM 节点的「上下文」期望的是 字符串文本 或特定结构化文本,而不是这种“文件对象”。
所以当你在下一个 LLM 节点,把 files 整个数组直接绑定到「上下文」时,LLM 节点会收到一个「文件对象数组」,结构和它预期的不符,就会出现你看到的:
Run failed: Invalid context structure: dify_model_identity='__dify__file__' ... type=<FileType.DOCUMENT: 'document'> ...
并不是说 url 取不到,而是:这个整体对象根本不应该被当成 LLM 的 context 传入。
类比一下:你现在传给 LLM 的不是「表格内容」,而是「一个 Excel 文件句柄 + 元数据」,模型当然“看不懂”。
2. 如果想让 LLM 分析 xlsx 内容,正确做法是什么?
你的目标看截图/描述应该是:
让 LLM 读 xlsx 里的表格内容,然后做分析。
这时不要再用「上下文」直接引用 files,而是要:
-
用“文档抽取 / Document Extractor 节点”来解析文件内容
- 输入:上游节点输出的文件变量(可以是数组)——比如 Markdown 转换器的
files,或 Start 节点上传的文件。
- 输出:纯文本(例如把 Excel 转成 Markdown 表格文本)。
-
在 LLM 节点中,把文档抽取节点输出的文本,作为 prompt / context 使用
例如在系统提示或用户提示里写类似:
下面是用户上传的 Excel 内容(已转为 Markdown 表格):
{{ doc_extractor.text }}
请根据上述表格回答用户的问题……
这样 LLM 接收到的就是 纯文本表格,不会再触发 Invalid context structure。
3. 如果你只是想拿到文件的 URL 怎么办?
如果你仅仅是想拿到 files[0].url 这样的字段,而不是给 LLM 直接“吃”这个对象,可以:
但这也同样不适合直接做 LLM 的「上下文」,因为 URL 指向的是一个二进制 Excel 文件,LLM 本身还是读不了,只能靠中间的“解析节点”帮你转成文本。
4. 关于 Excel 的扩展玩法
除了用「文档抽取节点」这种通用方式外,Dify 的 Marketplace 里也有一些针对 Excel 的插件(例如更灵活的读取、写入、按表名/单元格范围查询等)。
后续如果你想做更复杂的 Excel 处理,可以考虑:
- 使用 Excel 相关插件完成结构化处理
- 或插件 → 文档抽取 → LLM,多步组合使用
小结
files 是 文件对象数组,不能直接当作 LLM 上下文使用,所以会报 Invalid context structure。
- 想让 LLM 看 xlsx 内容:
Markdown / Start 节点(产出文件) → 文档抽取节点 → 输出文本 → 作为 LLM 节点的上下文 / Prompt。
- 想单纯拿 URL:用代码节点从
files[i].url 中取出即可,不要直接把整段 files 丢给 LLM。
如果你愿意,可以贴一下你当前 workflow 的简略结构(每个节点类型 + 关键连线),我可以帮你写一版“从上传 Excel 到 LLM 分析”的具体连线和变量引用示例。
感谢解答!
我的需求只是为了提取markdown转换器返回的文件url,用于拼接完整的文件下载url(绝对路径)。
目前我已经用代码块取到files[i].url,问题算是解决了。
衍生另一个问题,我在上级LLM节点中,我要求LLM回复一个标准的markdown表格,目的是为了让下一个markdown转换器节点生成Excel。
通过查看LLM返回的数据格式,我认为是没有问题的,比如下图中的text,是符合markdown语法结构的,但是markdown转换器并没有按照##作为标题的语法来生成Excel中对应的Sheet表,我应该如何处理?

@Dify_Forum_Helper
先说结论:目前你的用法(让 LLM 输出带 ## 标题的 Markdown,希望 Markdown 转换器按 ## 自动切 Sheet)在 Dify 里是不保证生效的——Markdown 转换器现在并没有一个“官方约定”的规则:## 一定会变成 Excel 的工作表名。
换句话说:
- 它确实能把 Markdown 表格转成 Excel;
- 但“多 Sheet + Sheet 命名逻辑”目前是比较「黑盒」的,不是一个对外文档化、可配置的能力,所以你看到“Markdown 看起来没问题,但没按
## 分 Sheet”是符合当前实现现状的。
下面分几块说你可以怎么处理。
1. 当前行为的理解
结合你贴的结构:
{
"text": "## XXX\n\n| 列1 | 列2 |\n| --- | --- |\n| ... | ... |"
}
你的期望是:
## XXX → Excel 里出现名为 XXX 的 Sheet
## YYY → 新建另一个 Sheet
但现在 Markdown 转换器更像是:
- 把整段 Markdown 视作一个整体文档;
- 重点关注的是「表格本身」而不是「标题层级 → Sheet」。
因此常见现象是:
- 可能只生成一个 Sheet;
- 或者使用默认 Sheet 名(如
Sheet1),而不是 ## 里的文本。
这不是你 Markdown 写错,而是当前产品设计就没把 ## 当成“Sheet 边界 + Sheet 名”的正式协议。
2. 实现「按标题切 Sheet」可以考虑的方案
如果你对「按 ## 分 Sheet」有强需求,可以考虑绕过 Markdown 转换器的默认规则,用“代码节点 + Excel 库”的方式,把你想要的结构显式地写成多 Sheet。
思路 A:LLM 输出结构化 JSON,再代码生成 Excel
-
在 LLM 节点中,不要直接让它输出 Markdown,而是输出结构化 JSON,例如:
{
"sheets": [
{
"name": "SheetA",
"table": [
["列1", "列2"],
["a1", "a2"],
["b1", "b2"]
]
},
{
"name": "SheetB",
"table": [
["列1", "列2"],
["x1", "x2"]
]
}
]
}
-
用代码节点(Python 推荐)解析这个 JSON,用 openpyxl 或 pandas 等库自己创建 Excel,多表多 Sheet 完全可控。
伪代码示例:
import io
from openpyxl import Workbook
import json
data = json.loads(inputs["llm"]["text"]) # 假设 LLM 输出的是上面的 JSON
wb = Workbook()
# 删除默认 sheet
default_ws = wb.active
wb.remove(default_ws)
for sheet in data["sheets"]:
ws = wb.create_sheet(title=sheet["name"][:31]) # Excel sheet 名最长 31 字符
for row in sheet["table"]:
ws.append(row)
# 保存到内存并返回给后续节点作为 file
buffer = io.BytesIO()
wb.save(buffer)
buffer.seek(0)
outputs["excel_file"] = {
"type": "document",
"filename": "result.xlsx",
"content": buffer.read()
}
-
后续你可以把这个 excel_file 当作普通文件给用户下载,或者再走别的节点。
优点:
- 完全不依赖 Markdown 转换器的内部规则;
- Sheet 名、Sheet 个数、每个 Sheet 的内容都由你控制;
- LLM 的任务也更明确:只负责“结构化规划”,不负责 Excel 细节。
思路 B:继续让 LLM 输出 Markdown,但用代码拆分再写 Excel
如果你现在的 LLM Prompt 已经固定在输出 Markdown,而且你喜欢 Markdown 的可读性,可以:
-
仍然要求 LLM 用类似结构:
## SheetA
| 列1 | 列2 |
| --- | --- |
| a1 | a2 |
| b1 | b2 |
## SheetB
| 列1 | 列2 |
| --- | --- |
| x1 | x2 |
-
在下游加一个 代码节点,做两件事:
- 用正则 / Markdown 解析库,将文本按
## 标题切成块;
- 对每块中的第一张表提取为二维数组,然后用跟上面类似的方式写 Excel 多 Sheet。
例如简单正则思路(伪代码):
import re
md = inputs["llm"]["text"]
# 切分出每个 sheet 区块
blocks = re.split(r'^##\s+', md, flags=re.MULTILINE)
# blocks[0] 可能是 ## 之前的内容,可以忽略
sheet_blocks = blocks[1:]
sheets = []
for block in sheet_blocks:
# 第一行到换行前是 sheet 名
lines = block.splitlines()
sheet_name = lines[0].strip()
sheet_body = "\n".join(lines[1:])
# 再从 sheet_body 中解析第一张 markdown 表格为二维数组
# (可以自己写 parser,也可以用现成库)
table = parse_markdown_table(sheet_body)
sheets.append({"name": sheet_name, "table": table})
# 然后复用上面 openpyxl 的写入逻辑
-
最终还是由这个代码节点生成 Excel,而不是依赖 Markdown 转换器自动推断 Sheet。
3. 如果一定要继续依赖 Markdown 转换器怎么办?
目前可见的信息里,没有一个“保证版”的文档形容:
##、### 等 heading 与 Sheet 之间的映射规则;
- 或某个开关可以开启“按标题拆 Sheet”。
所以:
- 即便你现在通过摸索出了某种「好像能按 heading 分 Sheet」的写法,也有可能是实现细节,未来版本不一定稳定;
- 对生产场景,还是建议用上述“LLM + 代码自建 Excel”的方式,避免依赖未文档化行为。
4. 实战建议(最小改动版本)
在你现有 workflow 上,如果不想改太大,可以这样微调:
-
上游 LLM:
-
继续输出「带 ## 的 Markdown」;
-
但在 Prompt 里额外让模型保证一个同时输出 JSON 的结构,例如:
先给我 Markdown 展示,方便阅读;
然后再给我一个 JSON 代码块,结构为:
```json
{ "sheets": [ { "name": "...",
"table": [["表头1","表头2"],["...","..."]] } ] }
-
下游新增代码节点:
- 专门读取 JSON 那段,忽略 Markdown 那段;
- 用 JSON → 多 Sheet Excel 的逻辑生成文件;
- 不再使用 Markdown 转换器去转 Excel。
这样:
- 人看聊天记录时依然是优雅的 Markdown;
- 机器落地 Excel 时走的是“结构化 + 可控”的路径。
小结
- 你现在遇到的问题本质上是:Markdown 转换器没有“
## → Excel sheet”这个可靠的、对外公开的规则,所以它不会按你想象的方式建多 Sheet。
- 若要稳定地控制 Sheet 名和结构,建议走:
- 「LLM 输出结构化 JSON → 代码节点生成 Excel」;或者
- 「LLM 输出带
## 的 Markdown → 代码节点按 ## 拆分 → 自己写 Excel」。
- 这样不用依赖内部未文档化的实现细节,后续版本升级也更安全。
如果你方便的话,可以贴一小段你现在的完整 LLM 输出示例(脱敏后),我可以直接帮你写一个对应的代码节点示例,把这段输出转换成多 Sheet 的 Excel。
我正在考虑改为代码块自行解析JSON或Markdown来生成Excel。
如果markdown转换器不能确保支持这样的用法,那我认为应该至少修改Dify中这里的描述信息,以免给用户造成使用上的困扰。
@Dify_Forum_Helper
你这条补充反馈非常关键,而且你截的这段文案确实容易让人「误解成强约定」。
「所有标题文本(例如 #、##)将依次用作工作表名称。」
结合你刚才的实测情况,可以比较明确地说两件事:
-
从“产品真实行为”角度
- 现在 Markdown 转换器把 Markdown 表格转成 Excel 没问题。
- 但「按标题拆 Sheet、并严格用标题文本作为 Sheet 名」在当前实现里并不是一个可依赖的、稳定契约(尤其是配合 LLM 自动生成 Markdown 时,模型容易多出空行、说明文字、代码块之类,都会影响解析)。
- 实际效果往往是:
- 有时只生成一个 Sheet;
- 或 Sheet 名跟标题不一致;
- 多级标题/多个表格的组合也不一定按你预期拆分。
-
从“文案与实际行为是否一致”角度
- 你看到的这段说明语气是「一定会」:
“所有标题文本(例如 #、##)将依次用作工作表名称。”
- 但当前实现更接近「在部分场景/有限规则下尝试用标题作为工作表名」,而不是「无条件保证」。
- 对于像你这样用 LLM 自动生成 Markdown 的场景,这种描述确实会让人误以为「只要有
##,就 100% 会得到对应的 Sheet」。
所以你现在打算「改为代码块自行解析 JSON 或 Markdown 来生成 Excel」,从工程实践角度是更稳妥的选择——尤其是你想精确控制多 Sheet 和命名时。
可以怎么落地 & 我会怎么建议官方处理
-
你自己的实现选择(是合理路线)
- 用 LLM 输出结构化 JSON / 或者约定好的 Markdown 结构;
- 代码节点里自己:
- 解析出 sheet 列表(
[{name, table}, ...]);
- 用 openpyxl / pandas 等库手动写 Excel,多 Sheet、多命名完全可控。
- 这条路线避免了依赖任何「未文档化细节」,对生产稳定性最好。
-
关于文案 / 产品的一点建议(我会代为总结给内部)
- 目前这段文案跟真实可保证的行为之间,确实存在落差。
- 更保险的写法应该类似:
- 「当 Markdown 结构满足特定规则时,会尝试使用标题文本作为工作表名」
或
- 干脆不承诺按标题自动拆 Sheet,而是强调:
「当前主要支持将 Markdown 表格转换为 Excel 工作表;若需精确控制多工作表与命名,建议使用代码节点或插件自行生成 Excel。」
- 你在帖子里的这条反馈,本身就非常有价值:
- 能帮助产品/文档团队意识到「文案给出的承诺 > 代码实际能保证的行为」;
- 也能避免后面更多用户踩到同样的坑。
小结帮你确认一下现状
- 你选择「自己通过代码生成 Excel」是对的,尤其是在要精确控制 Sheet 名/数量的场景。
- 以你当前的体验来看,这段 Markdown 转换器的 UI 描述确实会带来误导,你的建议(调整或弱化这段文案)是非常合理的产品改进点。
如果你后面在代码节点那边需要「从当前 LLM 输出格式(Markdown 或 JSON)→ 具体的 Python 样例」来生成多 Sheet 的 Excel,可以把一小段脱敏后的真实输出贴出来,我可以直接按你的格式写一份可以复制即用的代码节点示例。