news 2026/8/11 3:56:24

# 如何将包含 Document 对象的字符串转换为 List[Document]?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
# 如何将包含 Document 对象的字符串转换为 List[Document]?

如何将包含 Document 对象的字符串转换为 List[Document]?

问题背景

在 LangChain 开发中,我们经常需要处理Document对象。但有时候你会拿到一个"看起来像列表、实际上是字符串"的东西——比如从数据库读取、从 API 返回、或从日志中提取的内容:

# 你拿到的可能是这样一个字符串,而不是真正的 listdoc_string="[Document(metadata={'pk': 12, 'page': 2}, page_content='费用报销内容...'), Document(metadata={'pk': 27, 'page': 2}, page_content='...')]"

你的目标是把它变成真正的list[Document]。本文介绍3 种方案,从最简单到最健壮,覆盖不同场景。


先认识 Document 对象

在 LangChain 中,Document的结构非常简单:

fromlangchain_core.documentsimportDocument doc=Document(metadata={"pk":12,"page":2},page_content="这是文档的文本内容")

它只有两个核心字段:

  • metadata:字典,存储元数据(主键、页码等)
  • page_content:字符串,存储实际文本内容

当它被str()repr()转成字符串时,输出格式是Document(metadata={...}, page_content='...')


方案一:eval() —— 最直接(需注意安全风险)

如果字符串中引用的是真实的Document类,且你的环境中已经导入了该类,最简单的方法就是直接eval()

fromlangchain_core.documentsimportDocument doc_string="[Document(metadata={'pk': 12, 'page': 2}, page_content='...')]"# 直接 evaldocs=eval(doc_string)print(type(docs))# <class 'list'>print(type(docs[0]))# <class 'langchain_core.documents.base.Document'>print(docs[0].metadata)# {'pk': 12, 'page': 2}

优点

  • 一行代码搞定,简单粗暴
  • 完整还原对象类型

缺点

  • eval()会执行字符串中的任意 Python 代码,存在安全风险
  • 仅适用于完全可信的数据来源

安全提示:如果字符串来自用户输入、网络请求等不可信来源,绝对不要使用 eval()


方案二:正则 + ast.literal_eval —— 安全且通用

ast.literal_eval是 Python 标准库提供的安全解析函数,它只解析 Python 字面量(字符串、数字、字典、列表等),不会执行任何代码。

但问题是:ast.literal_eval无法直接解析Document(...)这种自定义类的构造调用。所以我们需要分两步

  1. 用正则提取每个Document(...)块中的metadatapage_content
  2. ast.literal_eval安全解析提取出的字面量
importreimportastfromlangchain_core.documentsimportDocumentdefparse_documents_from_string(s:str)->list[Document]:""" 将包含 Document(...) 的字符串解析为 list[Document] 使用正则提取 + ast.literal_eval 安全解析 """results=[]idx=0whileTrue:start=s.find("Document(",idx)ifstart==-1:break# 通过括号深度匹配,找到完整的 Document(...)depth=1i=start+len("Document(")whilei<len(s)anddepth>0:ifs[i]=='(':depth+=1elifs[i]==')':depth-=1i+=1doc_str=s[start:i]# 提取 metadata(字典格式)meta_match=re.search(r"metadata=(\{.*?\})",doc_str,re.DOTALL)# 提取 page_content(引号包裹的字符串,到块结尾)content_match=re.search(r"page_content=(.*?)(?:\)\s*$)",doc_str,re.DOTALL)ifmeta_matchandcontent_match:metadata=ast.literal_eval(meta_match.group(1))page_content_str=content_match.group(1).strip().rstrip(')')page_content=ast.literal_eval(page_content_str)results.append(Document(metadata=metadata,page_content=page_content))idx=ireturnresults

核心原理解析

为什么要用"括号深度匹配"?

page_content的值本身可能包含括号,比如page_content='费用报销(凭发票)'。如果用简单的正则Document\(([^)]*)\),遇到第一个)就会提前截断。

括号深度匹配的原理是:遍历字符,遇到(加一,遇到)减一,当深度归零时就找到了完整的闭合位置:

Document(metadata={'pk': 12}, page_content='(USD)') ^ ^ start end (depth=0)

为什么用 ast.literal_eval 而不是 eval?

特性eval()ast.literal_eval()
执行代码
解析字面量
安全性低(可执行任意代码)高(仅解析字面量)
适用场景可信数据任意数据

使用示例

doc_string="""[Document(metadata={'pk': 12, 'page': 2}, page_content='费用报销内容...'), Document(metadata={'pk': 27, 'page': 2}, page_content='奖惩制度...')]"""docs=parse_documents_from_string(doc_string)fordocindocs:print(f"pk={doc.metadata['pk']}, page={doc.metadata['page']}")print(f"content:{doc.page_content[:50]}...")print()

输出:

pk=12, page=2 content: 费用报销内容... pk=27, page=2 content: 奖惩制度...

方案三:处理无依赖场景(自定义 Document 类)

如果你的环境中没有安装 LangChain,但你需要解析这种字符串并重建类似结构,可以自定义一个等价的 Document 类:

importreimportastfromdataclassesimportdataclass@dataclassclassDocument:"""模拟 langchain Document 的简化版本"""metadata:dictpage_content:strdef__repr__(self):returnf"Document(metadata={self.metadata}, page_content={repr(self.page_content)})"defparse_documents_from_string(s:str,doc_class=Document)->list:"""解析字符串为 list[Document],支持自定义 Document 类"""results=[]idx=0whileTrue:start=s.find("Document(",idx)ifstart==-1:breakdepth=1i=start+len("Document(")whilei<len(s)anddepth>0:ifs[i]=='(':depth+=1elifs[i]==')':depth-=1i+=1doc_str=s[start:i]meta_match=re.search(r"metadata=(\{.*?\})",doc_str,re.DOTALL)content_match=re.search(r"page_content=(.*?)(?:\)\s*$)",doc_str,re.DOTALL)ifmeta_matchandcontent_match:metadata=ast.literal_eval(meta_match.group(1))page_content_str=content_match.group(1).strip().rstrip(')')page_content=ast.literal_eval(page_content_str)results.append(doc_class(metadata=metadata,page_content=page_content))idx=ireturnresults

这个版本通过doc_class参数支持传入任意 Document 类,无论你用的是 LangChain 的 Document 还是自定义的。


方案对比

方案安全性健壮性复杂度适用场景
eval()极低数据完全可信,且 Document 类已在作用域中
正则 + ast.literal_eval任意数据来源,生产环境推荐
自定义类 + 正则无 LangChain 环境,或需要自定义结构

实际测试验证

用用户提供的真实数据测试方案二:

raw_string=r"""[Document(metadata={'pk': 12, 'page': 2}, page_content='2. 费用报销:...'), Document(metadata={'pk': 27, 'page': 2}, page_content='2. 费用报销:...'), Document(metadata={'pk': 14, 'page': 3}, page_content='3. 最后警告:...')]"""docs=parse_documents_from_string(raw_string)print(len(docs))# 3

测试结果:

  • 3 个 Document 全部正确解析
  • metadata 中pkpage字段完整保留
  • page_content 中包含\n换行符、中文、特殊标点均正确还原
  • 含括号(连续三天或月累计五天)的内容也被正确处理

常见陷阱

1. page_content 中包含括号

page_content='金额($100)'

错误做法:用简单正则Document\(([^)]*)\)会在$100后的)处提前截断。

正确做法:使用括号深度匹配。

2. page_content 中包含引号

page_content='He said "hello"'

ast.literal_eval能正确处理嵌套引号(单引号包裹的字符串中包含双引号),无需特殊处理。

3. 转义字符 \n

字符串中的\nast.literal_eval解析后会被正确转换为换行符,不需要手动替换。


完整代码

importreimportastfromlangchain_core.documentsimportDocumentdefparse_documents_from_string(s:str)->list[Document]:""" 将包含 Document(...) 的字符串安全地解析为 list[Document]。 Args: s: 包含 Document(...) 表示的字符串 Returns: list[Document]: 解析后的 Document 对象列表 """results=[]idx=0whileTrue:start=s.find("Document(",idx)ifstart==-1:break# 括号深度匹配,处理 page_content 中含括号的情况depth=1i=start+len("Document(")whilei<len(s)anddepth>0:ifs[i]=='(':depth+=1elifs[i]==')':depth-=1i+=1doc_str=s[start:i]meta_match=re.search(r"metadata=(\{.*?\})",doc_str,re.DOTALL)content_match=re.search(r"page_content=(.*?)(?:\)\s*$)",doc_str,re.DOTALL)ifmeta_matchandcontent_match:metadata=ast.literal_eval(meta_match.group(1))page_content_str=content_match.group(1).strip().rstrip(')')page_content=ast.literal_eval(page_content_str)results.append(Document(metadata=metadata,page_content=page_content))idx=ireturnresults# ===== 使用示例 =====if__name__=="__main__":doc_string="[Document(metadata={'pk': 12, 'page': 2}, page_content='示例内容')]"docs=parse_documents_from_string(doc_string)print(f"解析到{len(docs)}个文档")print(docs[0].metadata)# {'pk': 12, 'page': 2}print(docs[0].page_content)# 示例内容

总结

需求推荐方案
快速验证、数据完全可信eval()
生产环境、数据来源不可控正则 +ast.literal_eval
无 LangChain 依赖自定义 Document 类 + 正则

核心原则:能用ast.literal_eval就不用eval,安全第一。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/11 3:56:12

每一步都合理,但结果是错的——企业AI落地的真实困境

我见过一次很典型的失败。一个团队做了一个采购助手&#xff0c;让采购员用自然语言提交补货需求。背后接了公司的库存系统&#xff0c;用Function Calling让模型决定调哪个接口。测试阶段跑了几十个case&#xff0c;结果看起来都对&#xff0c;上线了。上线三天之后&#xff0…

作者头像 李华
网站建设 2026/8/11 3:52:45

知网维普AIGC检测新标准怎么过?2026论文降AI合规改写指南

一、前言&#xff1a;新版AIGC检测下&#xff0c;所有学生都面临的写作困境随着高校学术规范管控升级&#xff0c;知网AIGC 4.0检测系统、维普全新重构版AI检测算法已全面落地应用。和旧版只检测文字重复率不同&#xff0c;新算法主打深层语义识别、行文风格研判、逻辑结构筛查…

作者头像 李华
网站建设 2026/8/11 3:52:36

初识机器学习(SVM)

一、机器学习十大常见算法本篇主要介绍支持向量机&#xff08;Support Vector Machine&#xff0c;简称 SVM&#xff09;&#xff0c;是一种监督学习算法&#xff0c;主要用于分类和回归问题。SVM 的核心思想是找到一个最优的超平面&#xff0c;将不同类别的数据分开。这个超平…

作者头像 李华
网站建设 2026/8/11 3:52:29

从Visual Studio迁移到VSCode:配置指南与避坑经验

1. 一个老派开发者的“断舍离”从Visual Studio 2015&#xff08;VS2015&#xff09;转向Visual Studio Code&#xff08;VSCode&#xff09;&#xff0c;这听起来像是一次简单的工具切换&#xff0c;但对于像我这样在Windows平台上&#xff0c;用着Visual Studio从6.0版本一路…

作者头像 李华
网站建设 2026/8/11 3:50:01

智能充电桩选购指南:核心指标与避坑策略

1. 智能充电桩选购的核心痛点解析第一次给电动车选充电桩时&#xff0c;我对着电商平台五花八门的参数彻底懵了。从3kW到22kW的功率跨度&#xff0c;从基础款到带4G联网的"智能中控"&#xff0c;价格能从千元档直接跳到五位数。经过三个月的实测对比和与行业人士的深…

作者头像 李华
网站建设 2026/8/11 3:49:52

HEIF Utility:Windows上处理iPhone照片的终极免费解决方案

HEIF Utility&#xff1a;Windows上处理iPhone照片的终极免费解决方案 【免费下载链接】HEIF-Utility HEIF Utility - View/Convert Apple HEIF images on Windows. 项目地址: https://gitcode.com/gh_mirrors/he/HEIF-Utility 还在为iPhone照片在Windows电脑上无法打开…

作者头像 李华