langchain-references


Namelangchain-references JSON
Version 0.2.31 PyPI version JSON
download
home_pagehttps://www.github.com/pprados/langchain-references
SummaryComponent to manager referenced documents with LLM.
upload_time2024-08-13 10:16:14
maintainerNone
docs_urlNone
authorPhilippe PRADOS
requires_python<4.0,>=3.9
licenseApache 2.0
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            Langchain-Reference
===================

[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/pprados/langchain-references?quickstart=1)

**"Ask a question, get a comprehensive answer and directly access the sources 
used to develop that answer."**

It's a very difficult goal to achieve.

**Now that's not a problem!**

[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/pprados/langchain-references/blob/master/langchain_reference.ipynb)

## Introduction
With a RAG project, when publishing the LLM's response, it can be helpful to include 
links to the documents used to produce the answer. This way, the user can learn more 
or verify that the response is accurate.

The list of documents is retrieved from the vector database. Each fragment carries 
metadata that allows for precise identification of its origin (the URL, the page, 
the title, the position of the first character, etc.).

To be clearer, let's position ourselves in a typical scenario. A question is posed in 
a prompt, which is then processed with five documents:

- `a.html#chap1`: The fragment relates to Chapter 1 of the `a.html` file.
- `a.html#chap2`: The fragment relates to Chapter 2 of the `a.html` file.
- `b.pdf`: The fragment comes from the `b.pdf` file.
- `b.pdf`: Another fragment also comes from the `b.pdf` file.
- `c.csv`: The fragment is a line from the `c.csv` file.

Given the scenario where the LLM uses multiple fragments from different documents to 
generate a response, the references should be formatted as footnotes, reflecting 
the different sources. For example, the LLM answers several questions using the 
referenced fragments:
```markdown
Yes[1](id=3), certainly[2](id=2), no[3](id=4), yes[4](id=1)
```
In this situation, the first four fragments are used, but not the last one. 
The first two have different URLs, even though they come from the same document. 
The next two share the same URL but refer to different fragments.

The naive approach is to list all the injected documents after the response and, 
if possible, extract a specific title for each fragment.  
```markdown
Yes, certainly, no, yes

- [a chap1](a.html#chap1)
- [a chap2](a.html#chap2)
- [b frag1](b.pdf)
- [b frag2](b.pdf)
- [c](c.csv)
```
---
Yes, certainly, no, yes

- [a chap1](a.html#chap1)
- [a chap2](a.html#chap2)
- [b frag1](b.pdf)
- [b frag2](b.pdf)
- [c](c.csv)
---

Optionally, duplicates can be filtered out.

We observe that the result is not satisfactory. First, the user will be disappointed 
when reading the file `c.csv` to find that it doesn’t contain any information 
supporting the response. This file should be excluded from the reference list since 
it provides no useful information and was not used by the LLM to generate the answer. 
There are also two different links leading to the same document, which could confuse 
the user as to why this is the case.

What should be produced is closer to this:
```markdown
Yes<sup>[1]</sup>, certainly<sup>[2]</sup>, no<sup>[3]</sup>, yes<sup>[4]</sup>

- [1],[3] [b](b.pdf)
- [2]     [a chap2](a.html#chap2)
- [4]     [a chap1](a.html#chap1)
```
---
Yes<sup>[1]</sup>, certainly<sup>[2]</sup>, no<sup>[3]</sup>, yes<sup>[4]</sup>

- [1],[3] [b](b.pdf)
- [2]     [a chap2](a.html#chap2)
- [4]     [a chap1](a.html#chap1)
---
We identify fragments sharing the same URL to combine reference numbers and avoid 
unreferenced documents.

The best solution is to adjust the reference numbers when they share the same URL. 
This adjustment should be made during the LLM’s response generation to achieve the 
following:
```markdown
Yes<sup>[1]</sup>, certainly<sup>[2]</sup>, no<sup>[1]</sup>, yes<sup>[4]</sup>

- [1] [b](b.pdf)
- [2] [a chap2](a.html#chap2)
- [3] [a chap1](a.html#chap1)
```
---
Yes<sup>[1]</sup>, certainly<sup>[2]</sup>, no<sup>[1]</sup>, yes<sup>[4]</sup>

- [1] [b](b.pdf)
- [2] [a chap2](a.html#chap2)
- [3] [a chap1](a.html#chap1)
---

Note that the reference numbers have been adjusted. As a result, 
we have a reference list that resembles what a human would have created.

**This complexity cannot reasonably be delegated to the LLM.** It would require providing 
the URLs for each fragment and crafting a prompt in the hope that it would always 
calculate correctly. Since handling URLs is not its strength, it’s better to relieve 
it of this responsibility and implement deterministic code capable of consistently 
performing the necessary calculations and adjustments. In the process, links can be 
directly embedded in the references.

```markdown
yes<sup>[[1](b.pdf)]</sup>, certainly<sup>[[2](a.html#chap2)]</sup>, 
no<sup>[[1](b.pdf)]</sup>, yes<sup>[[3](a.html#chap1)]</sup>

- [1] [b](b.pdf)
- [2] [a chap2](a.html#chap2)
- [3] [a chap1](a.html#chap1)
```
---
yes<sup>[[1](b.pdf)]</sup>, certainly<sup>[[2](a.html#chap2)]</sup>, 
no<sup>[[1](b.pdf)]</sup>, yes<sup>[[3](a.html#chap1)]</sup>

- [1] [b](b.pdf)
- [2] [a chap2](a.html#chap2)
- [3] [a chap1](a.html#chap1)
---

## Usage:
**You can't ask too much of an LLM.** Imperative code is often the best solution. 
To manage document references correctly, we'll separate responsibilities into two 
parts. The first is not too complex for an LLM: indicate a reference number, 
followed by the identifier of the fragment from which the answer is extracted. The 
second part is the responsibility of a Python code: adjusting reference numbers if 
there are duplicates, injecting URLs to the original documents if necessary, then 
concluding the prompt with the list of all references.

Having the list of documents that have been injected into the prompt, it is possible 
to add an identifier (the position of each document in the list), so that LLM can 
respond with the unique number of the injected document. In this way, it is possible 
to retrieve each original document and use the metadata to build a URL, for example. 
The following prompt asks LLM to handle references simply, in the form : 
`[<number_of_reference>](id=<index_of_fragment>)`.

### Chain without retriever
To use a chain without a retriever, you need to apply some similar steps.

Get the format of the references.
```python
from langchain_references import FORMAT_REFERENCES
print(f{FORMAT_REFERENCES=})
```
```text
FORMAT_REFERENCES='When referencing the documents, add a citation right after.' 
'Use "[NUMBER](id=ID_NUMBER)" for the citation (e.g. "The Space Needle is in '
'Seattle [1](id=55)[2](id=12).").'
```
And create a prompt:
```python
prompt=ChatPromptTemplate.from_template(
"""
Here, the context: 
{context}

{format_references}

Question : {question}
""")
```
The context must be built up by adding a reference to each document.
```python
def format_docs(docs):
    return "\n".join(
        # Add a document id so that LLM can reference it 
        [f"<document id={i+1}>\n{doc.page_content}\n</document>\n" 
         for i,doc in enumerate(docs)]
    )
context = RunnablePassthrough.assign(
    context=lambda input: format_docs(input["documents"]),
    format_references=lambda _: FORMAT_REFERENCES,
)
```
Then, thanks to `langchain-references`, to modify the tokens produced by the LLM.
Encapsulate the invocation of the model with `manage_references()` to adjust the
reference numbers and inject the URLs of the original documents.
```python
from langchain_references import manage_references
chain = context | manage_references(rag_prompt| model) | StrOutputParser()
```
Now, invoke the chain with the documents and the question.
```python
question = "What is the difference kind of games and competition of mathematics?"

docs = vectorstore.similarity_search(question)

# Run
print(chain.invoke({"documents": docs, "question": question}))
```
The response from the LLM will be:
```text
Mathematical games are structured activities defined by clear mathematical parameters, 
focusing on strategy and skills without requiring deep mathematical knowledge, such as 
tic-tac-toe or chess [1](id=1). In contrast, mathematics competitions, like the 
International Mathematical Olympiad, involve participants solving complex mathematical 
problems, often requiring proof or detailed solutions [2](id=2). Essentially, games 
are for enjoyment and skill development, while competitions test and challenge 
mathematical understanding and problem-solving abilities.'
```

The response with `manage_reference()` will be:
```markdown
Mathematical games are structured activities defined by clear mathematical parameters, 
focusing on strategy and skills without requiring deep mathematical knowledge, such as 
tic-tac-toe or chess <sup>[[1](https://en.wikipedia.org/wiki/Mathematics)]</sup>. In contrast, mathematics competitions, like 
the International Mathematical Olympiad, involve participants solving complex 
mathematical  problems, often requiring proof or detailed solutions 
<sup>[[2](https://en.wikipedia.org/wiki/Mathematical_game)]</sup>. Essentially, games are for enjoyment and skill development, 
while competitions test and challenge mathematical understanding and problem-solving 
abilities..

- **1** [Mathematics](https://en.wikipedia.org/wiki/Mathematics)
- **2** [Mathematical game](https://en.wikipedia.org/wiki/Mathematical_game)
```
---
Mathematical games are structured activities defined by clear mathematical parameters, 
focusing on strategy and skills without requiring deep mathematical knowledge, such as 
tic-tac-toe or chess <sup>[[1](https://en.wikipedia.org/wiki/Mathematics)]</sup>. In contrast, mathematics competitions, like 
the International Mathematical Olympiad, involve participants solving complex 
mathematical  problems, often requiring proof or detailed solutions 
<sup>[[2](https://en.wikipedia.org/wiki/Mathematical_game)]</sup>. Essentially, games are for enjoyment and skill development, 
while competitions test and challenge mathematical understanding and problem-solving 
abilities..

- **1** [Mathematics](https://en.wikipedia.org/wiki/Mathematics)
- **2** [Mathematical game](https://en.wikipedia.org/wiki/Mathematical_game)
---

The `manage_references()` take a `Runnable[LanguageModelInput, LanguageModelOutput]` as 
an argument and return a `Runnable[LanguageModelInput, LanguageModelOutput]`.

The input for this `Runnable` must be a dictionary with the keys *documents_key* 
(default is `documents`) and whatever the runnable requires as a parameter.

### Chain with retriever
With a chain with a retriever, the process is the same, but the documents are retrieved
by the retriever. The retriever must be added to the chain before the LLM.

```python
retriever = vectorstore.as_retriever(search_kwargs={"k": 6})
context = (
    RunnableParallel(
        # Get list of documents, necessary for reference analysis
        documents=(itemgetter("question") | retriever),
        # and question
        question=itemgetter("question"),
    ).assign(
        context=lambda input: format_docs(input["documents"]),
        format_references=lambda _: FORMAT_REFERENCES,
    )
)
```
And invoke with:
```python
answer = (context | manage_references(rag_prompt | model) | StrOutputParser() ).invoke({"question": question})
pprint(answer)
```
## Style
Different styles can be used to display the references. The default style is:
Markdown, but you can use:
- `EmptyReferenceStyle` : no references are produced
- `TextReferenceStyle` : for console output
- `MarkdownReferenceStyle` : format markdown output
- `HTMLReferenceStyle` : format html output

You can adjust the style or how to retrieve the URL from the metadata.
```python
from langchain_references import ReferenceStyle
from langchain_core.documents.base import BaseMedia
from typing import List, Tuple
def my_source_id(media: BaseMedia) -> str:
    # Documents loaded from a CSV file with 'row' in metadata
    return f'{media.metadata["source"]}#{media.metadata["row"]}'

class MyReferenceStyle(ReferenceStyle):
    source_id_key = my_source_id

    def format_reference(self, ref: int, media: BaseMedia) -> str:
        return f"[{media.metadata['title']}]"

    def format_all_references(self, refs: List[Tuple[int, BaseMedia]]) -> str:
        if not refs:
            return ""
        result = []
        for ref, media in refs:
            source = self.source_id_key.__func__(media)  # type: ignore
            result.append(f"- [{ref}] {source}\n")
        if not result:
            return ""
        return "\n\n" + "".join(result)
```

## How does it work?
On the fly, each token is captured to identify the pattern of references. As soon as 
the beginning of a text seems to match, tokens are accumulated until references are 
identified or the capture is abandoned, as this is a false detection. The accumulated 
tokens are then produced, before the analysis is resumed.
As soon as a token appears, it is assigned an identifier, in relation to the various 
documents present. Then `format_reference()` is invoked. 
When there are no more tokens, the list of documents used for the response is 
constructed and added as the final fragment, via `format_all_references()`.

            

Raw data

            {
    "_id": null,
    "home_page": "https://www.github.com/pprados/langchain-references",
    "name": "langchain-references",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.9",
    "maintainer_email": null,
    "keywords": null,
    "author": "Philippe PRADOS",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/c5/08/106faab97c0bc2de062ae2b463b11f6756fa8d1abea2efce8c1d643a8883/langchain_references-0.2.31.tar.gz",
    "platform": null,
    "description": "Langchain-Reference\n===================\n\n[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/pprados/langchain-references?quickstart=1)\n\n**\"Ask a question, get a comprehensive answer and directly access the sources \nused to develop that answer.\"**\n\nIt's a very difficult goal to achieve.\n\n**Now that's not a problem!**\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/pprados/langchain-references/blob/master/langchain_reference.ipynb)\n\n## Introduction\nWith a RAG project, when publishing the LLM's response, it can be helpful to include \nlinks to the documents used to produce the answer. This way, the user can learn more \nor verify that the response is accurate.\n\nThe list of documents is retrieved from the vector database. Each fragment carries \nmetadata that allows for precise identification of its origin (the URL, the page, \nthe title, the position of the first character, etc.).\n\nTo be clearer, let's position ourselves in a typical scenario. A question is posed in \na prompt, which is then processed with five documents:\n\n- `a.html#chap1`: The fragment relates to Chapter 1 of the `a.html` file.\n- `a.html#chap2`: The fragment relates to Chapter 2 of the `a.html` file.\n- `b.pdf`: The fragment comes from the `b.pdf` file.\n- `b.pdf`: Another fragment also comes from the `b.pdf` file.\n- `c.csv`: The fragment is a line from the `c.csv` file.\n\nGiven the scenario where the LLM uses multiple fragments from different documents to \ngenerate a response, the references should be formatted as footnotes, reflecting \nthe different sources. For example, the LLM answers several questions using the \nreferenced fragments:\n```markdown\nYes[1](id=3), certainly[2](id=2), no[3](id=4), yes[4](id=1)\n```\nIn this situation, the first four fragments are used, but not the last one. \nThe first two have different URLs, even though they come from the same document. \nThe next two share the same URL but refer to different fragments.\n\nThe naive approach is to list all the injected documents after the response and, \nif possible, extract a specific title for each fragment.  \n```markdown\nYes, certainly, no, yes\n\n- [a chap1](a.html#chap1)\n- [a chap2](a.html#chap2)\n- [b frag1](b.pdf)\n- [b frag2](b.pdf)\n- [c](c.csv)\n```\n---\nYes, certainly, no, yes\n\n- [a chap1](a.html#chap1)\n- [a chap2](a.html#chap2)\n- [b frag1](b.pdf)\n- [b frag2](b.pdf)\n- [c](c.csv)\n---\n\nOptionally, duplicates can be filtered out.\n\nWe observe that the result is not satisfactory. First, the user will be disappointed \nwhen reading the file `c.csv` to find that it doesn\u2019t contain any information \nsupporting the response. This file should be excluded from the reference list since \nit provides no useful information and was not used by the LLM to generate the answer. \nThere are also two different links leading to the same document, which could confuse \nthe user as to why this is the case.\n\nWhat should be produced is closer to this:\n```markdown\nYes<sup>[1]</sup>, certainly<sup>[2]</sup>, no<sup>[3]</sup>, yes<sup>[4]</sup>\n\n- [1],[3] [b](b.pdf)\n- [2]     [a chap2](a.html#chap2)\n- [4]     [a chap1](a.html#chap1)\n```\n---\nYes<sup>[1]</sup>, certainly<sup>[2]</sup>, no<sup>[3]</sup>, yes<sup>[4]</sup>\n\n- [1],[3] [b](b.pdf)\n- [2]     [a chap2](a.html#chap2)\n- [4]     [a chap1](a.html#chap1)\n---\nWe identify fragments sharing the same URL to combine reference numbers and avoid \nunreferenced documents.\n\nThe best solution is to adjust the reference numbers when they share the same URL. \nThis adjustment should be made during the LLM\u2019s response generation to achieve the \nfollowing:\n```markdown\nYes<sup>[1]</sup>, certainly<sup>[2]</sup>, no<sup>[1]</sup>, yes<sup>[4]</sup>\n\n- [1] [b](b.pdf)\n- [2] [a chap2](a.html#chap2)\n- [3] [a chap1](a.html#chap1)\n```\n---\nYes<sup>[1]</sup>, certainly<sup>[2]</sup>, no<sup>[1]</sup>, yes<sup>[4]</sup>\n\n- [1] [b](b.pdf)\n- [2] [a chap2](a.html#chap2)\n- [3] [a chap1](a.html#chap1)\n---\n\nNote that the reference numbers have been adjusted. As a result, \nwe have a reference list that resembles what a human would have created.\n\n**This complexity cannot reasonably be delegated to the LLM.** It would require providing \nthe URLs for each fragment and crafting a prompt in the hope that it would always \ncalculate correctly. Since handling URLs is not its strength, it\u2019s better to relieve \nit of this responsibility and implement deterministic code capable of consistently \nperforming the necessary calculations and adjustments. In the process, links can be \ndirectly embedded in the references.\n\n```markdown\nyes<sup>[[1](b.pdf)]</sup>, certainly<sup>[[2](a.html#chap2)]</sup>, \nno<sup>[[1](b.pdf)]</sup>, yes<sup>[[3](a.html#chap1)]</sup>\n\n- [1] [b](b.pdf)\n- [2] [a chap2](a.html#chap2)\n- [3] [a chap1](a.html#chap1)\n```\n---\nyes<sup>[[1](b.pdf)]</sup>, certainly<sup>[[2](a.html#chap2)]</sup>, \nno<sup>[[1](b.pdf)]</sup>, yes<sup>[[3](a.html#chap1)]</sup>\n\n- [1] [b](b.pdf)\n- [2] [a chap2](a.html#chap2)\n- [3] [a chap1](a.html#chap1)\n---\n\n## Usage:\n**You can't ask too much of an LLM.** Imperative code is often the best solution. \nTo manage document references correctly, we'll separate responsibilities into two \nparts. The first is not too complex for an LLM: indicate a reference number, \nfollowed by the identifier of the fragment from which the answer is extracted. The \nsecond part is the responsibility of a Python code: adjusting reference numbers if \nthere are duplicates, injecting URLs to the original documents if necessary, then \nconcluding the prompt with the list of all references.\n\nHaving the list of documents that have been injected into the prompt, it is possible \nto add an identifier (the position of each document in the list), so that LLM can \nrespond with the unique number of the injected document. In this way, it is possible \nto retrieve each original document and use the metadata to build a URL, for example. \nThe following prompt asks LLM to handle references simply, in the form : \n`[<number_of_reference>](id=<index_of_fragment>)`.\n\n### Chain without retriever\nTo use a chain without a retriever, you need to apply some similar steps.\n\nGet the format of the references.\n```python\nfrom langchain_references import FORMAT_REFERENCES\nprint(f{FORMAT_REFERENCES=})\n```\n```text\nFORMAT_REFERENCES='When referencing the documents, add a citation right after.' \n'Use \"[NUMBER](id=ID_NUMBER)\" for the citation (e.g. \"The Space Needle is in '\n'Seattle [1](id=55)[2](id=12).\").'\n```\nAnd create a prompt:\n```python\nprompt=ChatPromptTemplate.from_template(\n\"\"\"\nHere, the context: \n{context}\n\n{format_references}\n\nQuestion : {question}\n\"\"\")\n```\nThe context must be built up by adding a reference to each document.\n```python\ndef format_docs(docs):\n    return \"\\n\".join(\n        # Add a document id so that LLM can reference it \n        [f\"<document id={i+1}>\\n{doc.page_content}\\n</document>\\n\" \n         for i,doc in enumerate(docs)]\n    )\ncontext = RunnablePassthrough.assign(\n    context=lambda input: format_docs(input[\"documents\"]),\n    format_references=lambda _: FORMAT_REFERENCES,\n)\n```\nThen, thanks to `langchain-references`, to modify the tokens produced by the LLM.\nEncapsulate the invocation of the model with `manage_references()` to adjust the\nreference numbers and inject the URLs of the original documents.\n```python\nfrom langchain_references import manage_references\nchain = context | manage_references(rag_prompt| model) | StrOutputParser()\n```\nNow, invoke the chain with the documents and the question.\n```python\nquestion = \"What is the difference kind of games and competition of mathematics?\"\n\ndocs = vectorstore.similarity_search(question)\n\n# Run\nprint(chain.invoke({\"documents\": docs, \"question\": question}))\n```\nThe response from the LLM will be:\n```text\nMathematical games are structured activities defined by clear mathematical parameters, \nfocusing on strategy and skills without requiring deep mathematical knowledge, such as \ntic-tac-toe or chess [1](id=1). In contrast, mathematics competitions, like the \nInternational Mathematical Olympiad, involve participants solving complex mathematical \nproblems, often requiring proof or detailed solutions [2](id=2). Essentially, games \nare for enjoyment and skill development, while competitions test and challenge \nmathematical understanding and problem-solving abilities.'\n```\n\nThe response with `manage_reference()` will be:\n```markdown\nMathematical games are structured activities defined by clear mathematical parameters, \nfocusing on strategy and skills without requiring deep mathematical knowledge, such as \ntic-tac-toe or chess <sup>[[1](https://en.wikipedia.org/wiki/Mathematics)]</sup>. In contrast, mathematics competitions, like \nthe International Mathematical Olympiad, involve participants solving complex \nmathematical  problems, often requiring proof or detailed solutions \n<sup>[[2](https://en.wikipedia.org/wiki/Mathematical_game)]</sup>. Essentially, games are for enjoyment and skill development, \nwhile competitions test and challenge mathematical understanding and problem-solving \nabilities..\n\n- **1** [Mathematics](https://en.wikipedia.org/wiki/Mathematics)\n- **2** [Mathematical game](https://en.wikipedia.org/wiki/Mathematical_game)\n```\n---\nMathematical games are structured activities defined by clear mathematical parameters, \nfocusing on strategy and skills without requiring deep mathematical knowledge, such as \ntic-tac-toe or chess <sup>[[1](https://en.wikipedia.org/wiki/Mathematics)]</sup>. In contrast, mathematics competitions, like \nthe International Mathematical Olympiad, involve participants solving complex \nmathematical  problems, often requiring proof or detailed solutions \n<sup>[[2](https://en.wikipedia.org/wiki/Mathematical_game)]</sup>. Essentially, games are for enjoyment and skill development, \nwhile competitions test and challenge mathematical understanding and problem-solving \nabilities..\n\n- **1** [Mathematics](https://en.wikipedia.org/wiki/Mathematics)\n- **2** [Mathematical game](https://en.wikipedia.org/wiki/Mathematical_game)\n---\n\nThe `manage_references()` take a `Runnable[LanguageModelInput, LanguageModelOutput]` as \nan argument and return a `Runnable[LanguageModelInput, LanguageModelOutput]`.\n\nThe input for this `Runnable` must be a dictionary with the keys *documents_key* \n(default is `documents`) and whatever the runnable requires as a parameter.\n\n### Chain with retriever\nWith a chain with a retriever, the process is the same, but the documents are retrieved\nby the retriever. The retriever must be added to the chain before the LLM.\n\n```python\nretriever = vectorstore.as_retriever(search_kwargs={\"k\": 6})\ncontext = (\n    RunnableParallel(\n        # Get list of documents, necessary for reference analysis\n        documents=(itemgetter(\"question\") | retriever),\n        # and question\n        question=itemgetter(\"question\"),\n    ).assign(\n        context=lambda input: format_docs(input[\"documents\"]),\n        format_references=lambda _: FORMAT_REFERENCES,\n    )\n)\n```\nAnd invoke with:\n```python\nanswer = (context | manage_references(rag_prompt | model) | StrOutputParser() ).invoke({\"question\": question})\npprint(answer)\n```\n## Style\nDifferent styles can be used to display the references. The default style is:\nMarkdown, but you can use:\n- `EmptyReferenceStyle` : no references are produced\n- `TextReferenceStyle` : for console output\n- `MarkdownReferenceStyle` : format markdown output\n- `HTMLReferenceStyle` : format html output\n\nYou can adjust the style or how to retrieve the URL from the metadata.\n```python\nfrom langchain_references import ReferenceStyle\nfrom langchain_core.documents.base import BaseMedia\nfrom typing import List, Tuple\ndef my_source_id(media: BaseMedia) -> str:\n    # Documents loaded from a CSV file with 'row' in metadata\n    return f'{media.metadata[\"source\"]}#{media.metadata[\"row\"]}'\n\nclass MyReferenceStyle(ReferenceStyle):\n    source_id_key = my_source_id\n\n    def format_reference(self, ref: int, media: BaseMedia) -> str:\n        return f\"[{media.metadata['title']}]\"\n\n    def format_all_references(self, refs: List[Tuple[int, BaseMedia]]) -> str:\n        if not refs:\n            return \"\"\n        result = []\n        for ref, media in refs:\n            source = self.source_id_key.__func__(media)  # type: ignore\n            result.append(f\"- [{ref}] {source}\\n\")\n        if not result:\n            return \"\"\n        return \"\\n\\n\" + \"\".join(result)\n```\n\n## How does it work?\nOn the fly, each token is captured to identify the pattern of references. As soon as \nthe beginning of a text seems to match, tokens are accumulated until references are \nidentified or the capture is abandoned, as this is a false detection. The accumulated \ntokens are then produced, before the analysis is resumed.\nAs soon as a token appears, it is assigned an identifier, in relation to the various \ndocuments present. Then `format_reference()` is invoked. \nWhen there are no more tokens, the list of documents used for the response is \nconstructed and added as the final fragment, via `format_all_references()`.\n",
    "bugtrack_url": null,
    "license": "Apache 2.0",
    "summary": "Component to manager referenced documents with LLM.",
    "version": "0.2.31",
    "project_urls": {
        "Homepage": "https://www.github.com/pprados/langchain-references",
        "Repository": "https://www.github.com/pprados/langchain-references"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8272bc75b22d21b69ea48117a6eb82abbc6f3075382fd6fbfdc9e7654b0bd4be",
                "md5": "5ab7146aecb7ddeaebe46d37d5e7047f",
                "sha256": "2a7e57d86bb3c4485c405e342fc0d0e1b55e3d588dfc2963f5a9bf1e36c05a0b"
            },
            "downloads": -1,
            "filename": "langchain_references-0.2.31-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "5ab7146aecb7ddeaebe46d37d5e7047f",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.9",
            "size": 14000,
            "upload_time": "2024-08-13T10:16:13",
            "upload_time_iso_8601": "2024-08-13T10:16:13.281918Z",
            "url": "https://files.pythonhosted.org/packages/82/72/bc75b22d21b69ea48117a6eb82abbc6f3075382fd6fbfdc9e7654b0bd4be/langchain_references-0.2.31-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c508106faab97c0bc2de062ae2b463b11f6756fa8d1abea2efce8c1d643a8883",
                "md5": "62146b0a6a7cddb985c2aa4ef5af2ce8",
                "sha256": "2b58da8c823f11bc472494eaf2a62248f0cbe9625de638d9fa01f9bf3a9b62cd"
            },
            "downloads": -1,
            "filename": "langchain_references-0.2.31.tar.gz",
            "has_sig": false,
            "md5_digest": "62146b0a6a7cddb985c2aa4ef5af2ce8",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.9",
            "size": 17539,
            "upload_time": "2024-08-13T10:16:14",
            "upload_time_iso_8601": "2024-08-13T10:16:14.365574Z",
            "url": "https://files.pythonhosted.org/packages/c5/08/106faab97c0bc2de062ae2b463b11f6756fa8d1abea2efce8c1d643a8883/langchain_references-0.2.31.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-08-13 10:16:14",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "pprados",
    "github_project": "langchain-references",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "langchain-references"
}
        
Elapsed time: 0.58784s