开源项目,让 Zotero 研究库支持 LLM 驱动的智能问答,适合学者和需要文献管理的开发者。
Paper QA——对 PDF 或文本文件(也可以是原始 HTML)进行问答的极简工具包。
pip install paper-qa
请确保已将 OPENAI_API_KEY 环境变量设置为你的 OpenAI API key。
要使用 paper-qa,你需要准备一组文件路径(支持的扩展名包括 .pdf、.txt),以及与这些路径一一对应的引用信息(字符串)。然后,可以使用 Docs 类添加文档并进行查询。如果没有提供引用信息,Docs 会尝试从文档第一页推断。
from paperqa import Docs
# get a list of paths
docs = Docs()
for d in my_docs:
docs.add(d)
answer = docs.query("What manufacturing challenges are unique to bispecific antibodies?")
print(answer.formatted_answer)
answer 对象包含以下属性:formatted_answer、answer(仅答案本身)、question、context(为生成答案而找到的段落摘要)、references(这些段落所来自的文档),以及 passages(以字典形式保存段落原始文本)。
add 会从路径添加文档。你也可以使用 add_file(接收文件对象)或 add_url 来处理其他来源。
默认情况下,它会混合使用 gpt-3.5-turbo 和 gpt-4。如果你没有 gpt-4 的访问权限,或者希望节省费用,可以这样调整:
docs = Docs(llm='gpt-3.5-turbo')
你也可以使用 langchain 中提供的任何其他模型:
from langchain.chat_models import ChatAnthropic, ChatOpenAI
model = ChatOpenAI(model='gpt-4')
summary_model = ChatAnthropic(model="claude-instant-v1-100k", anthropic_api_key="my-api-key")
docs = Docs(llm=model, summary_llm=summary_model)
你还可以使用 langchain 中提供的其他任意模型或 Embeddings。下面这个例子使用 llama.cpp 在本地运行 paper-qa:
import paperscraper
from paperqa import Docs
from langchain.llms import LlamaCpp
from langchain import PromptTemplate, LLMChain
from langchain.callbacks.manager import CallbackManager
from langchain.embeddings import LlamaCppEmbeddings
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
# Make sure the model path is correct for your system!
llm = LlamaCpp(
model_path="./ggml-model-q4_0.bin", callbacks=[StreamingStdOutCallbackHandler()]
)
embeddings = LlamaCppEmbeddings(model_path="./ggml-model-q4_0.bin")
docs = Docs(llm=llm, embeddings=embeddings)
keyword_search = 'bispecific antibody manufacture'
papers = paperscraper.search_papers(keyword_search, limit=2)
for path,data in papers.items():
try:
docs.add(path,chunk_chars=500)
except ValueError as e:
print('Could not read', path, e)
answer = docs.query("What manufacturing challenges are unique to bispecific antibodies?")
print(answer)
你可以调整来源(文本段落)的数量,以减少 token 用量或提供更多上下文。k 表示相关性最高且彼此具有多样性的前 k 个段落,这些段落可能来自不同来源。每个段落都会发送给 LLM,由它进行摘要或判断该段落是否无关。完成这一步后,系统会应用 max_sources 限制,确保最终答案可以放入 LLM 的上下文窗口。因此,k > max_sources,而 max_sources 是最终答案实际使用的来源数量。
docs.query("What manufacturing challenges are unique to bispecific antibodies?", k = 5, max_sources = 2)
你不一定非要使用论文,也可以使用代码或原始 HTML。需要注意的是,这个工具专注于回答问题,因此并不擅长编写代码。另外,它无法从代码中推断引用信息,所以你需要自行提供。
import glob
source_files = glob.glob('**/*.js')
docs = Docs()
for f in source_files:
# this assumes the file names are unique in code
docs.add(f, citation='File ' + os.path.name(f), docname=os.path.name(f))
answer = docs.query("Where is the search bar in the header defined?")
print(answer)
PaperQA 使用下面所示的流程:
问题:如何大规模制造碳纳米管?
可以使用电弧技术大规模制造碳纳米管(Journet6644)。该技术是在氦气环境的反应器中,让两个电极之间产生电弧,并在阳极中使用金属催化剂与石墨粉末的混合物。通过这种方式,可以获得产率达 80% 的缠结碳丝;这些碳丝由尺寸更小、排列整齐的单壁碳纳米管(SWNT)组成,并能自组织成束状微晶(Journet6644)。此外,还可以通过多种方法合成并自组装碳纳米管,例如 DNA 介导的自组装、纳米颗粒辅助排列、化学自组装以及电寻址功能化(Tulevski2007)。这些方法已被用于制造大面积纳米结构阵列、高密度集成结构和自支撑网络(Tulevski2007)。还可以使用半导体纯度为 98% 的 CNT 网络溶液,该溶液通过密度梯度超速离心法与金属性纳米管分离(Chen2014)。将基底浸入该溶液中,随后用去离子水冲洗,再使用 N2 气枪吹干,即可留下均匀的碳网络(Chen2014)。
Journet6644:Journet, Catherine 等,《通过电弧技术大规模生产单壁碳纳米管》,《Nature》388.6644(1997):756–758。
Tulevski2007:Tulevski, George S. 等,《用于制造大规模器件阵列的化学辅助碳纳米管定向组装》,《Journal of the American Chemical Society》129.39(2007):11964–11968。
Chen2014:Chen, Haitian 等,《利用碳纳米管与 IGZO 薄膜晶体管混合集成实现大规模互补宏电子器件》,《Nature Communications》5.1(2014):4097。
Version 3 包含大量改动,旨在完善代码类型标注、提升专注度与模块化程度,并支持高效处理数量极其庞大的文档。主要的破坏性变更记录如下。
v3 包含以下新功能:
Docs(memory=True) 在查询中启用记忆。这意味着后续问题能够访问上一个问题及其答案的记录。add_url 和 add_file,分别从 URL 和文件对象添加内容。dockey 和 docname 作为唯一名称和自然语言名称,以便更好地与外部数据库协同追踪数据。Docs 对象中,因此可以使用外部数据库或其他策略进行管理。下表展示了旧名称与新名称:
dockey。此外,也不再统计 token 或费用,因为 langchain 默认已经内置了这项功能,示例见下文。如果想在 Jupyter Notebook 或 Colab 中使用它,需要运行以下命令:
import nest_asyncio
nest_asyncio.apply()
另外,如果你知道如何实现自动化,请告诉我!
这是个非常好的问题!最好的起点,可能就是下载那些你认为有助于回答问题的论文 PDF,然后从这些文件开始。
如果你使用 Zotero 管理个人参考文献,可以通过 paperqa.contrib.ZoteroDB 查询 Zotero 文库中的论文。该功能依赖 pyzotero。
请安装 pyzotero 以使用这项功能:
pip install pyzotero
首先需要注意,paperqa 会解析论文 PDF 并将其存入数据库,因此所有相关论文都应该在你的数据库中附带 PDF。你可以让 Zotero 自动执行这项操作:选中希望获取的参考文献,右键单击,然后选择 “Find Available PDFs”。你也可以手动把 PDF 拖放到对应的参考文献上。
要下载论文,你需要获取自己账户的 API key。
取得你的 library ID,并将其设置为环境变量 ZOTERO_USER_ID。对于个人文库,可以在 “Your userID for use in API calls is XXXXXX” 这部分找到该 ID。对于群组文库,请打开群组页面 https://www.zotero.org/groups/groupname,然后将鼠标悬停在设置链接上。ID 就是 /groups/ 后面的整数。(感谢 pyzotero!)
对于个人文库,可以在 “Your userID for use in API calls is XXXXXX” 这部分找到该 ID。
对于群组文库,请打开群组页面 https://www.zotero.org/groups/groupname,然后将鼠标悬停在设置链接上。ID 就是 /groups/ 后面的整数。(感谢 pyzotero!)
创建一个新的 API key,并将其设置为环境变量 ZOTERO_API_KEY。这个 key 需要具备文库的读取权限。
这个 key 需要具备文库的读取权限。
完成这些设置后,我们便可以从自己的文库下载论文,并将其添加到 paperqa:
from paperqa.contrib import ZoteroDB
docs = paperqa.Docs()
zotero = ZoteroDB(library_type="user") # "group" if group library
for item in zotero.iterate(limit=20):
if item.num_pages > 30:
continue # skip long papers
docs.add(item.pdf, docname=item.key)
这段代码会下载 Zotero 数据库中的前 20 篇论文,并将它们添加到 Docs 对象。
我们也可以对 Zotero 文库执行特定查询,并遍历查询结果:
for item in zotero.iterate(
q="large language models",
qmode="everything",
sort="date",
direction="desc",
limit=100,
):
print("Adding", item.title)
docs.add(item.pdf, docname=item.key)
你可以在 IPython 中输入 zotero.iterate?,进一步了解搜索语法。
如果想搜索个人收藏以外的论文,我发现了一个与本项目无关、名为 paper-scraper 的项目,看起来可能会有所帮助。不过请注意,这个项目似乎使用了一些爬取工具,可能侵犯出版商的权利,或者处于法律灰色地带。
keyword_search = 'bispecific antibody manufacture'
papers = paperscraper.search_papers(keyword_search)
docs = paperqa.Docs()
for path,data in papers.items():
try:
docs.add(path)
except ValueError as e:
# sometimes this happens if PDFs aren't downloaded or readable
print('Could not read', path, e)
answer = docs.query("What manufacturing challenges are unique to bispecific antibodies?")
print(answer)
默认使用 PyPDF,因为它是纯 Python 实现,而且容易安装。为了更快地读取 PDF,paper-qa 会检测并使用 PymuPDF(fitz):
pip install pymupdf
要在 completion 生成时实时流式输出,营造类似 ChatGPT 的打字机效果,只需在实例化模型时设置相应属性:
from paperqa import Docs
from langchain.callbacks.manager import CallbackManager
from langchain.chat_models import ChatOpenAI
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
my_llm = ChatOpenAI(callbacks=[StreamingStdOutCallbackHandler()], streaming=True)
docs = Docs(llm=my_llm)
你可以使用 langchain 内置的缓存功能。只需在代码开头运行:
from langchain.cache import InMemoryCache
langchain.llm_cache = InMemoryCache()
通常来说,无论使用哪一种向量存储,只要对 Docs 进行 pickle 序列化,Embeddings 就会一并缓存。如果想通过外部数据库或其他策略管理 Embeddings 缓存,可以使用 add_texts 对象直接填充 Docs 对象。它可以接收分块后的文本和文档;这些都是可序列化对象,可用于填充 Docs。
你也可以在构建 Docs 对象时显式设置 doc_index 和 texts_index,直接使用独立的向量数据库。
你可以使用 PromptCollection 类自定义任意 prompt。例如,如果想修改问题使用的 prompt,可以这样做:
from paperqa import Docs, Answer, PromptCollection
from langchain.prompts import PromptTemplate
my_qaprompt = PromptTemplate(
input_variables=["context", "question"],
template="Answer the question '{question}' "
"Use the context below if helpful. "
"You can cite the context using the key "
"like (Example2012). "
"If there is insufficient context, write a poem "
"about how you cannot answer.\n\n"
"Context: {context}\n\n")
prompts=PromptCollection(qa=my_qaprompt)
docs = Docs(prompts=prompts)
按照上面的语法,你还可以加入在查询前后执行的 prompt。例如,可以利用这一功能对答案进行批判性审查。
其实差别并不大!它与 LlamaIndex 中的 tree response 方法类似。我只是加入了一些自己认为实用的 prompt、能够提供页码和行号的读取器,并专注于一项任务:使用带引用的来源回答技术问题。
并没有不同!我们使用 langchain 对 LLM 进行抽象,整个过程也与 LangChain 中的 map_reduce chain 非常相似。
可以。只需向 Docs 类传入 llm 参数,就能使用 langchain 中的任意 LLM。你还可以分别为摘要和问答使用不同的 LLM。
你可以提供自己的文档。我会使用自己编写的一些代码从 Google Scholar 拉取论文。这些代码没有包含在项目中,因为它们可能让人违反 Google 和出版商的服务条款。
Docs 类可以进行 pickle 序列化与反序列化。如果想保存文档的 Embeddings,之后再重新加载,这项功能会很有用。
import pickle
# save
with open("my_docs.pkl", "wb") as f:
pickle.dump(docs, f)
# load
with open("my_docs.pkl", "rb") as f:
docs = pickle.load(f)