Package: paper-qa
Paper QA-Paper QAPaper QA- Paper QAOutput ExampleReferencesHugging Face DemoInstallUsageAdding DocumentsChoosing ModelAdjusting number of sourcesUsing Code or HTMLVersion 3 ChangesNew FeaturesNamingBreaking ChangesNotebooksWhere do I get papers?ZoteroPaper ScraperPDF Reading OptionsTypewriter ViewLLM/Embedding CachingCaching EmbeddingsCustomizing PromptsPre and Post PromptsFAQHow is this different from LlamaIndex?How is this different from LangChain?Can I use different LLMs?Where do the documents come from?Can I save or load?This is a minimal package for doing question and answering from
PDFs or text files (which can be raw HTML). It strives to give very good answers, with no hallucinations, by grounding responses with in-text citations.By default, it usesOpenAI Embeddingswith a vector DB calledFAISSto embed and search documents. However, vialangchainyou can use open-source models or embeddings (see details below).PaperQA uses the process shown below:embed docs into vectorsembed query into vectorsearch for top k passages in docscreate summary of each passage relevant to queryput summaries into promptgenerate answer with promptOutput ExampleQuestion: How can carbon nanotubes be manufactured at a large scale?Carbon nanotubes can be manufactured at a large scale using the electric-arc technique (Journet6644). This technique involves creating an arc between two electrodes in a reactor under a helium atmosphere and using a mixture of a metallic catalyst and graphite powder in the anode. Yields of 80% of entangled carbon filaments can be achieved, which consist of smaller aligned SWNTs self-organized into bundle-like crystallites (Journet6644). Additionally, carbon nanotubes can be synthesized and self-assembled using various methods such as DNA-mediated self-assembly, nanoparticle-assisted alignment, chemical self-assembly, and electro-addressed functionalization (Tulevski2007). These methods have been used to fabricate large-area nanostructured arrays, high-density integration, and freestanding networks (Tulevski2007). 98% semiconducting CNT network solution can also be used and is separated from metallic nanotubes using a density gradient ultracentrifugation approach (Chen2014). The substrate is incubated in the solution and then rinsed with deionized water and dried with N2 air gun, leaving a uniform carbon network (Chen2014).ReferencesJournet6644: Journet, Catherine, et al. "Large-scale production of single-walled carbon nanotubes by the electric-arc technique." nature 388.6644 (1997): 756-758.Tulevski2007: Tulevski, George S., et al. "Chemically assisted directed assembly of carbon nanotubes for the fabrication of large-scale device arrays." Journal of the American Chemical Society 129.39 (2007): 11964-11968.Chen2014: Chen, Haitian, et al. "Large-scale complementary macroelectronics using hybrid integration of carbon nanotubes and IGZO thin-film transistors." Nature communications 5.1 (2014): 4097.Hugging Face DemoHugging Face DemoInstallInstall with pip:pipinstallpaper-qaUsageMake sure you have set your OPENAI_API_KEY environment variable to youropenai api keyTo use paper-qa, you need to have a list of paths (valid extensions include: .pdf, .txt) and a list of citations (strings) that correspond to the paths. You can then use theDocsclass to add the documents and then query them. If you don't have citations,Docswill try to guess them from the first page of your docs.frompaperqaimportDocs# get a list of pathsdocs=Docs()fordinmy_docs:docs.add(d)answer=docs.query("What manufacturing challenges are unique to bispecific antibodies?")print(answer.formatted_answer)The answer object has the following attributes:formatted_answer,answer(answer alone),question,context(the summaries of passages found for answer),references(the docs from which the passages came), andpassageswhich contain the raw text of the passages as a dictionary.Adding Documentsaddwill add from paths. You can also useadd_file(expects a file object) oradd_urlto work with other sources.Choosing ModelBy default, it uses a hybrid ofgpt-3.5-turboandgpt-4. If you don't have gpt-4 access or would like to save money, you can adjust:docs=Docs(llm='gpt-3.5-turbo')or you can use any other model available inlangchain:fromlangchain.chat_modelsimportChatAnthropic,ChatOpenAImodel=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)Locally HostedYou can also use any other models (or embeddings) available inlangchain. Here's an example of usingllama.cppto have locally hosted paper-qa:importpaperscraperfrompaperqaimportDocsfromlangchain.llmsimportLlamaCppfromlangchainimportPromptTemplate,LLMChainfromlangchain.callbacks.managerimportCallbackManagerfromlangchain.embeddingsimportLlamaCppEmbeddingsfromlangchain.callbacks.streaming_stdoutimportStreamingStdOutCallbackHandler# 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)forpath,datainpapers.items():try:docs.add(path,chunk_chars=500)exceptValueErrorase:print('Could not read',path,e)answer=docs.query("What manufacturing challenges are unique to bispecific antibodies?")print(answer)Adjusting number of sourcesYou can adjust the numbers of sources (passages of text) to reduce token usage or add more context.krefers to the top k most relevant and diverse (may from different sources) passages. Each passage is sent to the LLM to summarize, or determine if it is irrelevant. After this step, a limit ofmax_sourcesis applied so that the final answer can fit into the LLM context window. Thus,k>max_sourcesandmax_sourcesis the number of sources used in the final answer.docs.query("What manufacturing challenges are unique to bispecific antibodies?",k=5,max_sources=2)Using Code or HTMLYou do not need to use papers -- you can use code or raw HTML. Note that this tool is focused on answering questions, so it won't do well at writing code. One note is that the tool cannot infer citations from code, so you will need to provide them yourself.importglobsource_files=glob.glob('**/*.js')docs=Docs()forfinsource_files:# this assumes the file names are unique in codedocs.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)Version 3 ChangesVersion 3 includes many changes to type the code, make it more focused/modular, and enable performance to very large numbers of documents. The major breaking changes are documented below:New FeaturesThe following new features are in v3:Memory is now possible inqueryby settingDocs(memory=True)- this means follow-up questions will have a record of the previous question and answer.add_urlandadd_fileare now supported for adding from URLs and file objectsPrompts can be customized, and now can be executed pre and post queryConsistent use ofdockeyanddocnamefor unique and natural language names enable better tracking with external databasesTexts and embeddings are no longer required to be part ofDocsobject, so you can use external databases or other strategies to manage themVarious simplifications, bug fixes, and performance improvementsNamingThe following table shows the old names and the new names:Old NameNew NameExplanationkeynameName is a natural language name for text.dockeydocnameDocname is a natural language name for a document.hashdockeyDockey is a unique identifier for the document.Breaking ChangesPickled objectsThe pickled objects are not compatible with the new version.AgentsThe agent functionality has been removed, as it's not a core focus of the libraryCachingCaching has been removed because it's not a core focus of the library. See FAQ below for how to use caching.AnswersAnswers will not include passages, but instead return dockeys that can be used to retrieve the passages. Tokens/cost will also not be counted since that is built into langchain by default (see below for an example).Search QueryThe search query chain has been removed. You can use langchain directly to do this.NotebooksIf you want to use this in an jupyter notebook or colab, you need to run the following command:importnest_asyncionest_asyncio.apply()Also - if you know how to make this automated, please let me know!Where do I get papers?Well that's a really good question! It's probably best to just download PDFs of papers you think will help answer your question and start from there.ZoteroIf you useZoteroto organize your personal bibliography,
you can use thepaperqa.contrib.ZoteroDBto query papers from your library,
which relies onpyzotero.Installpyzoteroto use this feature:pipinstallpyzoteroFirst, note thatpaperqaparses the PDFs of papers to store in the database,
so all relevant papers should have PDFs stored inside your database.
You can get Zotero to automatically do this by highlighting the references
you wish to retrieve, right clicking, and selecting"Find Available PDFs".
You can also manually drag-and-drop PDFs onto each reference.To download papers, you need to get an API key for your account.Get your library ID, and set it as the environment variableZOTERO_USER_ID.For personal libraries, this ID is givenhereat the part "Your userID for use in API calls is XXXXXX".For group libraries, go to your group pagehttps://www.zotero.org/groups/groupname, and hover over the settings link. The ID is the integer after /groups/. (h/t pyzotero!)Create a new API keyhereand set it as the environment variableZOTERO_API_KEY.The key will need read access to the library.With this, we can download papers from our library and add them topaperqa:frompaperqa.contribimportZoteroDBdocs=paperqa.Docs()zotero=ZoteroDB(library_type="user")# "group" if group libraryforiteminzotero.iterate(limit=20):ifitem.num_pages>30:continue# skip long papersdocs.add(item.pdf,docname=item.key)which will download the first 20 papers in your Zotero database and add
them to theDocsobject.We can also do specific queries of our Zotero library and iterate over the results:foriteminzotero.iterate(q="large language models",qmode="everything",sort="date",direction="desc",limit=100,):print("Adding",item.title)docs.add(item.pdf,docname=item.key)You can read more about the search syntax by typingzotero.iterate?in IPython.Paper ScraperIf you want to search for papers outside of your own collection, I've found an unrelated project calledpaper-scraperthat looks
like it might help. But beware, this project looks like it uses some scraping tools that may violate publisher's rights or be in a gray area of legality.keyword_search='bispecific antibody manufacture'papers=paperscraper.search_papers(keyword_search)docs=paperqa.Docs()forpath,datainpapers.items():try:docs.add(path)exceptValueErrorase:# sometimes this happens if PDFs aren't downloaded or readableprint('Could not read',path,e)answer=docs.query("What manufacturing challenges are unique to bispecific antibodies?")print(answer)PDF Reading OptionsBy defaultPyPDFis used since it's pure python and easy to install. For faster PDF reading, paper-qa will detect and usePymuPDF (fitz):pipinstallpymupdfTypewriter ViewTo stream the completions as they occur (giving that ChatGPT typewriter look), you can simply instantiate models with those properties:frompaperqaimportDocsfromlangchain.callbacks.managerimportCallbackManagerfromlangchain.chat_modelsimportChatOpenAIfromlangchain.callbacks.streaming_stdoutimportStreamingStdOutCallbackHandlermy_llm=ChatOpenAI(callbacks=[StreamingStdOutCallbackHandler()],streaming=True)docs=Docs(llm=my_llm)LLM/Embedding CachingYou can using the builtin langchain caching capabilities. Just run this code at the top of yours:fromlangchain.cacheimportInMemoryCachelangchain.llm_cache=InMemoryCache()Caching EmbeddingsIn general, embeddings are cached when you pickle aDocsregardless of what vector store you use. If you would like to manage caching embeddings via an external database or other strategy,
you can populate aDocsobject directly via
theadd_textsobject. That can take chunked texts and documents, which are serializable objects, to populateDocs.You also can simply use a separate vector database by setting thedoc_indexandtexts_indexexplicitly when building theDocsobject.Customizing PromptsYou can customize any of the prompts, using thePromptCollectionclass. For example, if you want to change the prompt for the question, you can do:frompaperqaimportDocs,Answer,PromptCollectionfromlangchain.promptsimportPromptTemplatemy_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)Pre and Post PromptsFollowing the syntax above, you can also include prompts that
are executed after the query and before the query. For example, you can use this to critique the answer.FAQHow is this different from LlamaIndex?It's not that different! This is similar to the tree response method in LlamaIndex. I just have included some prompts I find useful, readers that give page numbers/line numbers, and am focused on one task - answering technical questions with cited sources.How is this different from LangChain?It's not! We use langchain to abstract the LLMS, and the process is very similar to themap_reducechain in LangChain.Can I use different LLMs?Yes, you can use any LLMs fromlangchainby passing thellmargument to theDocsclass. You can use different LLMs for summarization and for question answering too.Where do the documents come from?You can provide your own. I use some of my own code to pull papers from Google Scholar. This code is not included because it may enable people to violate Google's terms of service and publisher's terms of service.Can I save or load?TheDocsclass can be pickled and unpickled. This is useful if you want to save the embeddings of the documents and then load them later.importpickle# savewithopen("my_docs.pkl","wb")asf:pickle.dump(docs,f)# loadwithopen("my_docs.pkl","rb")asf:docs=pickle.load(f)