象解析實(shí)戰(zhàn):從字符串到 List,再到 Document 內(nèi)容提取)
在實(shí)際開(kāi)發(fā)中我們經(jīng)常會(huì)遇到這樣的數(shù)據(jù)看起來(lái)像list實(shí)際上是str看起來(lái)像對(duì)象實(shí)際上只是對(duì)象的字符串描述從日志、數(shù)據(jù)庫(kù)、接口返回的數(shù)據(jù)中恢復(fù) Python 對(duì)象今天通過(guò)兩個(gè)真實(shí)案例講解如何正確處理。一、問(wèn)題一字符串形式的 List 如何轉(zhuǎn)換成真正 List1.1 場(chǎng)景例如raw_str [qwen-turbo,qwen-plus,deepseek]很多初學(xué)者會(huì)認(rèn)為print(type(raw_str))應(yīng)該是class list但是實(shí)際print(type(raw_str))結(jié)果class str原因它只是一個(gè)字符串。里面的[qwen-turbo,qwen-plus,deepseek]只是字符。1.2 錯(cuò)誤方式eval()很多人第一反應(yīng)result eval(raw_str)結(jié)果[qwen-turbo, qwen-plus, deepseek]看起來(lái)可以。但是不推薦原因eval()會(huì)執(zhí)行 Python 代碼。例如data __import__(os).system(rm -rf /) eval(data)如果數(shù)據(jù)來(lái)自用戶輸入網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)庫(kù)可能造成安全問(wèn)題。1.3 推薦方式ast.literal_eval()Python 提供安全解析import ast raw_str [qwen-turbo,qwen-plus,deepseek] models ast.literal_eval(raw_str) print(models) print(type(models))輸出[qwen-turbo, qwen-plus, deepseek] class list1.4 literal_eval 支持哪些類型例如list[a,b]轉(zhuǎn)換[a,b]dict{name:deepseek}轉(zhuǎn)換{ name:deepseek }tuple(1,2,3)轉(zhuǎn)換(1,2,3)二、問(wèn)題二字符串中的 Document 對(duì)象如何獲取 page這是 RAG 項(xiàng)目中非常常見(jiàn)的問(wèn)題。例如[Document(metadata{pk:12,page:2}, page_contentxxx)]很多人看到Document以為documents[0].page_content可以訪問(wèn)。但是實(shí)際上type(data)結(jié)果class str它不是 Document。只是一個(gè)包含 Document 文本描述的字符串。三、為什么不能直接取 page錯(cuò)誤data [Document(metadata{page:2},page_contenthello)] print(data[0].page)錯(cuò)誤AttributeError因?yàn)閐ata實(shí)際結(jié)構(gòu)String | | 字符 | | [ D o c u m e n t ( ... )Python 不知道Document是什么對(duì)象。四、正確解決方案一不要讓對(duì)象變字符串最佳方案如果數(shù)據(jù)來(lái)源是 LangChain例如from langchain_core.documents import Document docs [ Document( metadata{ pk:12, page:2 }, page_content測(cè)試內(nèi)容 ) ]此時(shí)print(type(docs))輸出list里面元素print(type(docs[0]))輸出Document那么直接for doc in docs: print(doc.metadata[page]) print(doc.page_content)輸出2 測(cè)試內(nèi)容五、真實(shí)場(chǎng)景數(shù)據(jù)庫(kù)里面保存成字符串怎么辦很多項(xiàng)目比如mysql 字段 documents 內(nèi)容 [Document(...)]讀取以后content row.documents變成str怎么辦方法1正則提取 page_content如果只是想獲取文本import re text Document(metadata{page:2}, page_contenthello world) contents re.findall( rpage_content(.*?), text, re.S ) print(contents)結(jié)果[ hello world ]然后result \n.join(contents)得到hello world六、方法2重新構(gòu)造 Document 對(duì)象推薦在 RAG 項(xiàng)目中使用。假設(shè)字符串doc_str里面metadata page_content解析from langchain_core.documents import Document docs[] for item in解析后的數(shù)據(jù): docs.append( Document( metadataitem[metadata], page_contentitem[page_content] ) )之后for doc in docs: print(doc.metadata[page]) print(doc.page_content)七、如果字符串格式固定可以使用 ast 轉(zhuǎn)換例如text [ { metadata:{ page:2 }, page_content:hello } ] 轉(zhuǎn)換import ast data ast.literal_eval(text) for item in data: print(item[metadata][page]) print(item[page_content])輸出2 hello八、RAG項(xiàng)目中的實(shí)際應(yīng)用在知識(shí)庫(kù)系統(tǒng)里面流程PDF | ↓ Document對(duì)象 Document( metadata{ page:1 }, page_content政策內(nèi)容 ) | ↓ Embedding | ↓ Vector DB檢索回來(lái)應(yīng)該保持List[Document]例如docs vector_store.similarity_search(question)返回[ Document(...), Document(...) ]然后for doc in docs: page doc.metadata[page] content doc.page_content生成引用來(lái)源 政策文件.pdf 第2頁(yè) 內(nèi)容 xxxx九、兩個(gè)問(wèn)題總結(jié)問(wèn)題本質(zhì)解決方案字符串 List 轉(zhuǎn) Liststr → listast.literal_eval()字符串 Document 轉(zhuǎn)對(duì)象str → Document不要序列化或者重新構(gòu)造獲取 page讀取 metadatadoc.metadata[page]獲取文本讀取 page_contentdoc.page_content十、最終記憶開(kāi)發(fā)中遇到看起來(lái)像對(duì)象先問(wèn)print(type(data))如果str不要直接data.xxx先恢復(fù)對(duì)象。RAG 開(kāi)發(fā)尤其注意Document對(duì)象 ↓ 不要變成字符串 ↓ 保持 List[Document] ↓ metadata 保存來(lái)源 ↓ page_content 保存正文這也是為什么 LangChain、LlamaIndex 等框架一直強(qiáng)調(diào)檢索階段保持 Document 對(duì)象結(jié)構(gòu)不要提前轉(zhuǎn)成字符串。