AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


使用Qdrant、Llama 3、LangChain构建法律案例搜索引擎及探索不同过滤技术
发布日期:2024-07-14 08:17:48 浏览次数: 1902


随着GenAI在涉及RAG的实际应用中的使用和适应性不断增加,我们经常会遇到一些限制,例如:

  1. 数据短缺

  2. LLM预测问题

  3. 检索与查询相关的结果,通常使用向量数据库

第三点无疑是构建任何RAG应用时面临的最大限制,因为它关乎我们能根据查询找到的相关结果,这些结果随后有助于为查询创建“上下文”,然后将其发送给LLM。

在本文中,我将尝试重现检索的限制,并向您展示如何克服这一限制。

通过LangChain在向量数据库上进行简单的查询搜索。

问题

本文将构建一个使用Qdrant、Llama3(通过Groq的API)和LangChain的法律案例搜索引擎。

主要议程是专注于“相关结果的筛选”,因为法律案例记录非常详尽。我们有大量的元数据可以利用,因此关键在于:如何使用这些元数据?

另一个可能出现的问题是,法律案例有摘要,你会发现许多案例在性质上非常相似——如果仅将这些摘要输入向量数据库,查询搜索后你将无法控制得到的结果。

为了解决这个问题,我们将介绍三种筛选技术:

  1. LangChain的自查询(Self-Query)

  2. Qdrant的负载过滤器(Payload Filters)

  3. 语义过滤(我们将构建)

在此之前,让我们先看看我们的数据集:https://github.com/sachink1729/legal-cases-search-using-self-query-qdrant-llama3-langchain/blob/main/dataset.json

我使用ChatGPT生成了这个数据集;如果你需要提示,这里是链接:https://chatgpt.com/share/849d5987-1f47-4766-9738-2bcc87430f4b

如你所见,数据集是一个字典列表,每个小字典代表一个法律案例,包含page_content和metadata。我们将在后面详细探讨这个数据集。

开始编码

使用以下命令安装所需的库:

!pip install langchain-huggingface
!pip install qdrant-client
!pip install langchain-qdrant
!pip install langchain-community
!pip install lark

让我们查看一下我们的数据集。

import json
dataset = json.load(open("dataset.json"))['cases_list']
dataset[0]

输出:

{'page_content': "最高法院以4:1的多数支持2016年废钞令,裁定废钞措施与联邦目标成比例且合理实施。",
'metadata': {'year': 2023,
'court': '印度最高法院',
'judges': ['S. 阿卜杜勒·纳泽尔',
'B.R. 加瓦伊',
'A.S. 博帕纳',
'V. 拉马苏布拉马尼亚',
'B.V. 纳加尔萨纳'],
'legal_topics': ['宪法法', '经济政策'],
'relevant_laws': ['印度宪法第370条']}}

元数据中有5个字段:年份、法院、法官、法律主题和相关法律,这些字段一目了然。

该数据集中总共有30个独特的案例。

加载 HF 嵌入模型

使用 langchain-huggingface 集成加载嵌入模型:

from langchain_huggingface import HuggingFaceEmbeddings
model_name = "BAAI/bge-small-en-v1.5"
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': True}
hf = HuggingFaceEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)

启动模型:

hf.embed_query("demonetization case")

这将返回一个向量列表,表示查询的表示:

[-0.08326546847820282,
0.022219663485884666,
-0.017243655398488045,
-0.053167618811130524,
0.05313488468527794,
-0.016739359125494957,

-0.043265871703624725,
0.06921685487031937,
-0.032498423010110855,
-0.01773829013109207,
0.0005499106482602656]

输出被截断,但如果检查此列表的长度,它是 384,这是“BAAI/bge-small-en-v1.5”模型的嵌入大小。

接下来,让我们构建一个自查询检索器!

LangChain 自查询检索器

为了命名方便,我们再次加载数据集并将其重命名为 langchain_dataset。这个变量将用于自查询检索器;这就是为什么我们要将其与原始数据集分开保存。

langchain_dataset = json.load(open("dataset.json"))['cases_list']

我们将对 langchain_dataset 进行预处理。

import re
def remove_punctuation(text):
no_punct_text = re.sub(r'[^\w\s,]', '', text)
return no_punct_text.lower()
现在使用这个函数:
for x in langchain_dataset:
for key, val in x['metadata'].items():
x['metadata'][key] = remove_punctuation(str(val))
for val in x['page_content']:
x['page_content'] = remove_punctuation(str(x['page_content']))

让我们看看结果:

langchain_dataset[0]
{'page_content': 'the supreme court upheld the centres 2016 demonetisation scheme in a 41 majority, ruling that demonetisation was proportionate to the unions objectives and implemented reasonably',
'metadata': {'year': '2023',
'court': 'supreme court of india',
'judges': 's abdul nazeer, br gavai, as bopanna, v ramasubramanian, bv nagarathna',
'legal_topics': 'constitutional law, economic policy',
'relevant_laws': 'article 370 of the indian constitution'}}

接下来,我们初始化 Qdrant 客户端以构建向量存储。

from langchain_core.documents import Document
from langchain_community.vectorstores.qdrant import Qdrant
for i, x in enumerate(langchain_dataset):
langchain_dataset[i] = Document(
page_content=x['page_content'],
metadata=x['metadata'],
)
vectorstore = Qdrant.from_documents(langchain_dataset, hf,
location=":memory:",
collection_name="langchain_legal",)

这里是构建元数据字段信息的主要部分;这是一种告诉检索器可用字段的方式。

from langchain.chains.query_constructor.base import AttributeInfo
from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain_groq import ChatGroq
metadata_field_info = [
AttributeInfo(
name="court",
description="The judiciary court's name.",
type="string",
),
AttributeInfo(
name="year",
description="The year the court decision was given.",
type="string",
),
AttributeInfo(
name="judges",
description="The list of names of the case judges.",
type="string",
),
AttributeInfo(
name="legal_topics",
description="list of the topic names of the case",
type="string",
),
AttributeInfo(
name="relevant_laws",
description="list of relevant laws that applied on the case decision",
type="string",
)
]
document_content_description = "Brief summary of a court case"

最后,我们将所有内容汇总并初始化我们的检索器:

在这个实验中,我们将使用 Groq 的 API 来访问 Llama 3。如果您还没有注册,请注册并获取您的 API 密钥,并将其替换为“your-groq-key”。注册地址:https://console.groq.com/docs/quickstart

from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain_groq import ChatGroq
llm = ChatGroq(temperature=0, api_key="your-groq-key")
retriever = SelfQueryRetriever.from_llm(
llm,
vectorstore,
document_content_description,
metadata_field_info,
)

最后,让我们看看它的表现:

retriever.invoke("demonetization case summary")

返回:

[Document(page_content="The Supreme Court upheld the Centre's 2016 demonetisation scheme in a 4:1 majority, ruling that demonetisation was proportionate to the Union's objectives and implemented reasonably.", metadata={'year': '2023', 'court': 'supreme court of india', 'judges': 's abdul nazeer, br gavai, as bopanna, v ramasubramanian, bv nagarathna', 'legal_topics': 'constitutional law, economic policy', 'relevant_laws': 'article 370 of the indian constitution', '_id': 'aaf5d793903e4a6aa2bcd68ee11ccfad', '_collection_name': 'langchain_legal'}),
Document(page_content='The Supreme Court of the United Kingdom ruled that Uber drivers are workers and entitled to employment rights such as minimum wage and holiday pay.', metadata={'year': '2021', 'court': 'supreme court of the united kingdom', 'judges': 'lord reed, lord hodge, lady arden', 'legal_topics': 'employment law, gig economy', 'relevant_laws': 'employment rights act 1996', '_id': '07dc3ad5dbf243cfa6c67e3bea2589a0', '_collection_name': 'langchain_legal'}),
Document(page_content='The U.S. Supreme Court invalidated a federal law prohibiting sports betting outside Nevada, holding it violated the anti-commandeering rule.', metadata={'year': '2018', 'court': 'supreme court of the united states', 'judges': 'majority opinion by justice samuel alito', 'legal_topics': 'sports law, constitutional law', 'relevant_laws': 'professional and amateur sports protection act paspa', '_id': 'cd0196513549463798f0805851bcad48', '_collection_name': 'langchain_legal'}),
Document(page_content='The Supreme Court of India decriminalized consensual homosexual acts between adults, overturning a colonial-era law under Section 377 of the Indian Penal Code.', metadata={'year': '2018', 'court': 'supreme court of india', 'judges': 'chief justice dipak misra', 'legal_topics': 'human rights, lgbtq rights', 'relevant_laws': 'section 377 of the indian penal code', '_id': 'f6f893a7103f4ec1a355d80691e74cfd', '_collection_name': 'langchain_legal'})]

如果您看到第一个结果,这正是我们想要的。但仔细一看,你会发现这个查询没有任何过滤条件。

让我们尝试一个带过滤条件的查询:

# 这个例子只指定了一个过滤条件
retriever.invoke("cases in year 2020")

返回:

[Document(page_content='The International Court of Justice ruled that Myanmar must take measures to prevent the genocide of the Rohingya people.', metadata={'year': '2020', 'court': 'international court of justice', 'judges': 'president abdulqawi yusuf', 'legal_topics': 'international law, human rights', 'relevant_laws': 'convention on the prevention and punishment of the crime of genocide', '_id': '7829c80e30eb4d31948623f6873ff46f', '_collection_name': 'langchain_legal'}),
Document(page_content='The Constitutional Court of South Africa ruled that domestic workers are entitled to the same compensation benefits as other workers.', metadata={'year': '2020', 'court': 'constitutional court of south africa', 'judges': 'chief justice mogoeng mogoeng', 'legal_topics': 'labor law, equality', 'relevant_laws': 'compensation for occupational injuries and diseases act', '_id': 'bb34eeb9ce5a49f39da01e4ed3764367', '_collection_name': 'langchain_legal'})]

完美,这些案件都是2020年的,看看两个结果中的“year”字段。

再看一个例子:

# 这个例子只指定了一个过滤条件
retriever.invoke("cases with topic Constitutional Law")
[Document(page_content='The U.S. Supreme Court decided that same-sex marriage is a constitutional right under the Fourteenth Amendment.', metadata={'year': '2015', 'court': 'supreme court of the united states', 'judges': 'majority opinion by justice anthony kennedy', 'legal_topics': 'constitutional law, civil rights', 'relevant_laws': 'fourteenth amendment of the us constitution', '_id': '6480fdbd1a8143b383b8fa03b8748cab', '_collection_name': 'langchain_legal'}),
Document(page_content="The U.S. Supreme Court ruled that state laws banning abortion are unconstitutional, recognizing a woman's right to privacy in making medical decisions.", metadata={'year': '1973', 'court': 'supreme court of the united states', 'judges': 'majority opinion by justice harry blackmun', 'legal_topics': 'constitutional law, reproductive rights', 'relevant_laws': 'fourteenth amendment of the us constitution', '_id': '0b193c8b40794f6dac578dfd046fbbd1', '_collection_name': 'langchain_legal'}),
Document(page_content='The U.S. Supreme Court held that the Defense of Marriage Act (DOMA) was unconstitutional as it violated the Fifth Amendment by denying federal recognition of same-sex marriages.', metadata={'year': '2013', 'court': 'supreme court of the united states', 'judges': 'majority opinion by justice anthony kennedy', 'legal_topics': 'constitutional law, lgbtq rights', 'relevant_laws': 'fifth amendment of the us constitution', '_id': '2b7a5bb46e1a4492b88d312fbc335014', '_collection_name': 'langchain_legal'}),
Document(page_content='The Constitutional Court of South Africa ruled that the use of corporal punishment in the home is unconstitutional.', metadata={'year': '2019', 'court': 'constitutional court of south africa', 'judges': 'chief justice mogoeng mogoeng', 'legal_topics': 'family law, human rights', 'relevant_laws': 'south african constitution', '_id': 'b284224c8cbb4878b2b43906ef57073d', '_collection_name': 'langchain_legal'})]

这又是一个自查询工作原理的绝佳例子。

现在,让我们看看它在哪些地方会失败!?

retriever.invoke("cases where Judge Sylvia Steiner was the judge")

这会导致一个错误:

OutputParserException: Parsing text
```json
{
"query": "",
"filter": "eq(contains(judges, \"Sylvia Steiner\"), true)"
}
解释:

- 查询是一个空字符串,因为用户没有在案件内容中寻找任何特定文本。

- 过滤器使用 `eq` 比较语句来检查 `judges` 属性是否包含名称“Sylvia Steiner”。

- `contains` 函数用于检查 `judges` 属性是否是一个包含名称“Sylvia Steiner”的字符串。

- `true` 值用于作为 `eq` 语句的比较值,以检查 `contains` 函数是否返回 `true`

```python
raised following error:
Unexpected token Token('COMMA', ',') at line 1, column 19.
Expected one of:
* LPAR
Previous tokens: [Token('CNAME', 'judges')]

这里还有一个例子,你不会得到任何结果,这很奇怪。

retriever.invoke("demonetization case of Supreme court of India")

你会得到一个空列表。

接下来,让我们看看我们的第二种方法:Qdrant 的 Payload 过滤器。

Qdrant 的负载过滤器

首先,我们使用 Qdrant 的官方绑定创建一个新的集合。

from qdrant_client.models import PointStruct
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams

client = QdrantClient(":memory:")

client.recreate_collection(
collection_name="law_docs",
vectors_config=VectorParams(size=384, distance=Distance.COSINE),
)

# 注意:考虑将数据分成多个块以避免达到服务器负载大小限制
# 或者使用 `upload_collection` 或 `upload_points` 方法,这些方法会自动处理这个问题
# 警告:逐个上传点不推荐,因为请求开销较大
client.upsert(
collection_name="law_docs",
points=[
PointStruct(
id=idx,
vector=hf.embed_query(element['page_content']),
payload=element['metadata']
)
for idx, element in enumerate(dataset)
]
)

要查看 Qdrant 提供的负载过滤选项,请访问:https://qdrant.tech/documentation/concepts/filtering/

我不会涵盖所有内容,但你需要关注以下两点:

  1. 过滤子句

  2. 过滤条件

理想情况下,这种方法需要一些手动工作,因为我们必须扫描整个句子以找到匹配的字段,然后才能使用条件。

为了我们的目的,我们将使用一个示例:

from qdrant_client import models
client.scroll(
collection_name="law_docs",
scroll_filter=models.Filter(
must=[
models.FieldCondition(
key="year",
match=models.MatchValue(value=2023),
),
]
),
)

这将搜索所有年份为 2023 的记录,你将得到:

([Record(id=0, payload={'year': 2023, 'court': 'Supreme Court of India', 'judges': ['S. Abdul Nazeer', 'B.R. Gavai', 'A.S. Bopanna', 'V. Ramasubramanian', 'B.V. Nagarathna'], 'legal_topics': ['Constitutional Law', 'Economic Policy'], 'relevant_laws': ['Article 370 of the Indian Constitution']}, vector=None, shard_key=None),
Record(id=1, payload={'year': 2023, 'court': 'Supreme Court of the United States', 'judges': ['K.M. Joseph', 'Ajay Rastogi', 'Aniruddha Bose', 'Hrishikesh Roy', 'C.T. Ravikumar'], 'legal_topics': ['Election Law', 'Constitutional Law'], 'relevant_laws': ['Election Commission Act']}, vector=None, shard_key=None),
Record(id=4, payload={'year': 2023, 'court': 'Supreme Court of India', 'judges': ['K.M. Joseph', 'Ajay Rastogi', 'Aniruddha Bose', 'Hrishikesh Roy', 'C.T. Ravikumar'], 'legal_topics': ['Health Law', 'Right to Die'], 'relevant_laws': ['Guidelines for Passive Euthanasia']}, vector=None, shard_key=None)],
None)

这是正确的!

作为练习,尝试实现一个涵盖 Qdrant 提供的所有子句和条件的过滤管道——那将是一项不错的挑战!

现在让我们转向语义过滤。

语义过滤

我发现的第三种也是最好的技术是使用一个独立的向量数据库来过滤结果!

具体思路如下:

  1. 主向量数据库保持不变,集合名为“law_docs”;这将是第一层。

  2. 我们将构建一个名为“metadata”的新向量数据库,作为第二层。

  3. 这两者将根据查询找到最佳匹配。

让我们创建新集合,但首先,让我们预处理文本,因为我们必须为每个条目提供完整的元数据作为单个文本片段:

metadata_fields = [x['metadata'] for x in dataset]
metadata_list = []
for elem in metadata_fields:
s = ''
for key in elem:
s = s + f"{key} : {elem[key]}\n"
s = s.strip().lower().replace('.','').replace("'",'').replace('[','').replace(']','')
# s = remove_punctuation(s)
metadata_list.append(s)


metadata_client = QdrantClient(":memory:")
metadata_client.recreate_collection(
collection_name="metadata",
vectors_config=VectorParams(size=384, distance=Distance.COSINE),
)


metadata_client.upsert(
collection_name="metadata",
points=[
PointStruct(
id=idx,
vector=hf.embed_query(element),
)
for idx, element in enumerate(metadata_list)
]
)

接下来定义我们的主函数,该函数将驱动结果:

如前所述,将进行两级向量查询搜索,第一级使用原始向量数据库,第二级检查第一级的命中是否与第二级匹配。然后,我们将根据与查询的分数对这些命中进行排序!

def find_hit_details(hit_list, hit):
for i, x in enumerate(hit_list):
if hit == x.id:
return i
return -1


def semantic_filtering(text):
first_level = set()
second_level = set()
matching_hits = {}


query_vector = hf.embed_query(text)
hits = client.search("law_docs", query_vector, limit=5)
for h in hits:
first_level.add(h.id)

filter_hits = metadata_client.search("metadata", query_vector, limit=5)
filter_hits_dict = {fh.id: fh for fh in filter_hits}
for fh in filter_hits:
second_level.add(fh.id)

common_hits = first_level & second_level
for hit in common_hits:
filter_hit_detail = filter_hits_dict[hit]
if filter_hit_detail.score > 0.65:
matching_hits[filter_hit_detail.score] = hit


sorted_matching_hits = sorted(matching_hits.items(), reverse=True)

if sorted_matching_hits:
print("semantic_filtering")
return [dataset[hit] for score, hit in sorted_matching_hits]
else:
print("No filter found")
return [dataset[hit] for hit in first_level]

如果你看到这部分“if filter_hit_detail.score > 0.65”,注意这是我们控制阈值的地方,它对噪声非常敏感。因此,你必须仔细选择这个分数,或者尝试找到更好的计算分数的方法,可能是将我们从向量数据库得到的余弦分数与特定于问题陈述的自定义指标结合起来。

最后,让我们运行语义过滤器。

# 示例用法
text = "gay marriage cases"
results = semantic_filtering(text)
results

结果如下:

[{'page_content': '美国最高法院认为《婚姻保护法》(DOMA)因违反第五修正案而不符合宪法,因为它拒绝承认同性婚姻的联邦地位。',
'metadata': {'year': 2013,
'court': '美国最高法院',
'judges': ['安东尼·肯尼迪大法官多数意见'],
'legal_topics': ['宪法', 'LGBTQ+权利'],
'relevant_laws': ['美国宪法第五修正案']}},
{'page_content': '日本最高法院维持了一项要求已婚夫妇共用一个姓氏的法律,裁定该法律不违反宪法规定的平等权。',
'metadata': {'year': 2015,
'court': '日本最高法院',
'judges': ['寺田逸郎首席法官'],
'legal_topics': ['家庭法', '平等'],
'relevant_laws': ['日本民法典']}}]

再举一个例子:

semantic_filtering("demonetisation case judge as boppana".lower())

结果如下:

semantic_filtering

[{'page_content': '最高法院以4:1的多数支持2016年废钞计划,裁定废钞与联邦目标成比例且合理实施。',
'metadata': {'year': 2023,
'court': '印度最高法院',
'judges': ['纳泽尔',
'加瓦伊',
'博帕纳',
'拉马斯瓦米安',
'纳拉萨纳'],
'legal_topics': ['宪法', '经济政策'],
'relevant_laws': ['印度宪法第370条']}},
{'page_content': '南非宪法法院裁定,家政工人有权享有与其他工人相同的补偿福利。',
'metadata': {'year': 2020,
'court': '南非宪法法院',
'judges': ['莫戈恩首席法官'],
'legal_topics': ['劳动法', '平等'],
'relevant_laws': ['职业伤害和疾病补偿法']}},
{'page_content': '南非宪法法院裁定,在家中使用体罚违反宪法。',
'metadata': {'year': 2019,
'court': '南非宪法法院',
'judges': ['莫戈恩首席法官'],
'legal_topics': ['家庭法', '人权'],
'relevant_laws': ['南非宪法']}}]

在从文本中找不到过滤器的情况下,我们只返回第一级结果。

semantic_filtering("cases about money".lower())

结果如下:

No filter found

[{'page_content': '最高法院以4:1的多数支持2016年废钞计划,裁定废钞与联邦目标成比例且合理实施。',
'metadata': {'year': 2023,
'court': '印度最高法院',
'judges': ['纳泽尔',
'加瓦伊',
'博帕纳',
'拉马斯瓦米安',
'纳拉萨纳'],
'legal_topics': ['宪法', '经济政策'],
'relevant_laws': ['印度宪法第370条']}},
{'page_content': '美国司法部在涉及残疾学生权利的案件中得出结论,阿拉巴马州的寄养系统歧视情绪和行为障碍学生。',
'metadata': {'year': 2022,
'court': '美国司法部',
'judges': ['民权司调查'],
'legal_topics': ['残疾法', '教育法'],
'relevant_laws': ['美国残疾人法案第二章']}},
{'page_content': '美国最高法院裁定,除非州外卖家在该州有显著存在,否则州不能要求他们征收销售税。',
'metadata': {'year': 2018,
'court': '美国最高法院',
'judges': ['肯尼迪大法官多数意见'],
'legal_topics': ['税法', '商业条款'],
'relevant_laws': ['美国宪法商业条款']}},
{'page_content': '英国最高法院裁定,Uber司机是工人,有权享有最低工资和假期工资等就业权利。',
'metadata': {'year': 2021,
'court': '英国最高法院',
'judges': ['里德勋爵', '霍奇勋爵', '阿登女爵'],
'legal_topics': ['劳动法', '零工经济'],
'relevant_laws': ['1996年就业权利法']}},
{'page_content': '欧盟法院裁定,要求电信公司无差别保留用户数据的数据保留法无效。',
'metadata': {'year': 2016,
'court': '欧盟法院',
'judges': ['莱纳茨法官'],
'legal_topics': ['数据隐私', '电信法'],
'relevant_laws': ['2006/24/EC指令']}}]

GitHub

完整代码和数据集请访问:

https://github.com/sachink1729/legal-cases-search-using-self-query-qdrant-llama3-langchain

结论

在本文中,我们探讨了构建智能RAG时元数据过滤的重要性!我们构建了一个法律案例搜索系统。我们看到了三种不同的存储和使用元数据的技术:

  1. LangChain的自查询(Self-Query)

  2. Qdrant的有效负载过滤器(Payload Filters)

  3. 语义过滤(Semantic Filtering)



53AI,企业落地应用大模型首选服务商

产品:大模型应用平台+智能体定制开发+落地咨询服务

承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

与创始人交个朋友

回到顶部

 
扫码咨询