将自然语言理解添加到任何应用程序

[

David Mezzetti

](https://.com/@davidmezzetti?source=post_page—–c721f4ff91ad——————————–)[

NeuML

](https://.com/neuml?source=post_page—–c721f4ff91ad——————————–)

这是原始文章的更新版本

搜索是许多应用程序的基础。一旦数据开始堆积,用户希望能够找到它。这是互联网的基础,也是一个从未解决或完成的日益增长的挑战。

自然语言处理(NLP)领域正在迅速发展,出现了许多新的发展。大规模通用语言模型是一种令人兴奋的新功能,允许我们添加令人惊叹的功能。
创新继续与新的模式和进步来在什么似乎每周的基础。

本文介绍了txtai,这是一个一体化的嵌入式数据库,可以在任何应用程序中实现基于自然语言理解(NLU)的搜索。

 关于txtai

txtai是一个用于语义搜索,LLM编排和语言模型工作流的一体化嵌入数据库。

嵌入式数据库是向量索引(稀疏和密集)、图网络和关系数据库的结合。这使得向量搜索与SQL,主题建模,检索增强生成和更多。

嵌入式数据库可以独立存在和/或作为大型语言模型(LLM)提示的强大知识源。

以下是主要功能的摘要:

  • 🔎使用SQL的向量搜索、对象存储、主题建模、图分析和多模式索引
  • 📄为文本、文档、音频、图像和视频创建嵌入
  • 💡由语言模型驱动的管道,运行LLM提示,问答,标记,转录,翻译,摘要等
  • ️️将管道连接在一起并聚合业务逻辑的工作流程。txtai流程可以是简单的微服务或多模型工作流。
  • 使用Python或YAML编译。️API绑定可用于JavaScriptJavaRustGo
  • ️使用容器编排运行本地或横向扩展

txtai使用Python 3.9+、Hugging Face TransformersSentence TransformersFastAPI构建。txtai是根据Apache 2.0许可证开源的。

 安装并运行txtai

txtai可以通过pipDocker安装。下面介绍如何通过pip安装。

<span id="2b16" data-selectable-paragraph="">pip install txtai

 语义搜索

嵌入式数据库是提供语义搜索的引擎。数据被转换为嵌入向量,其中相似的概念将产生相似的向量。大的和小的索引都是用这些向量构建的。
索引用于查找具有相同含义的结果,而不一定是相同的关键字。

嵌入式数据库的基本用例是构建用于语义搜索的近似最近邻(ANN)索引。下面的示例对少量文本条目进行索引,以演示语义搜索的价值。

<span id="b370" data-selectable-paragraph="">from txtai import Embeddings  
  
  
data = [  
  "US tops 5 million confirmed virus cases",  
  "Canada's last fully intact ice shelf has suddenly collapsed, forming a Manhattan-sized iceberg",  
  "Beijing mobilises invasion craft along coast as Taiwan tensions escalate",  
  "The National Park Service warns against sacrificing slower friends in a bear attack",  
  "Maine man wins $1M from $25 lottery ticket",  
  "Make huge profits without work, earn up to $100,000 a day"  
]  
  
  
embeddings = Embeddings(path="sentence-transformers/nli-mpnet-base-v2")  
  
  
embeddings.index(data)  
  
print("%-20s %s" % ("Query", "Best Match"))  
print("-" * 50)  
  
  
for query in ("feel good story", "climate change",   
    "public health story", "war", "wildlife", "asia",  
    "lucky", "dishonest junk"):  
    
    
  uid = embeddings.search(query, 1)[0][0]  
  
    
  print("%-20s %s" % (query, data[uid]))

上面的例子表明,对于所有查询,查询文本都不在数据中。这是transformers模型在基于令牌的搜索上的真正力量。你从盒子里拿出来的是什么?🔥🔥🔥

 更新和删除

嵌入支持更新和删除。upsert操作将插入新数据并更新现有数据

下一节运行一个查询,然后更新一个值,更改顶部结果,最后删除更新后的值以恢复到原始查询结果。

<span id="6691" data-selectable-paragraph="">  
uid = embeddings.search("feel good story", 1)[0][0]  
print("Initial: ", data[uid])  
  
  
udata = data.copy()  
  
  
udata[0] = "See it: baby panda born"  
embeddings.upsert([(0, udata[0], None)])  
  
uid = embeddings.search("feel good story", 1)[0][0]  
print("After update: ", udata[uid])  
  
  
embeddings.delete([0])  
  
  
uid = embeddings.search("feel good story", 1)[0][0]  
print("After delete: ", udata[uid])
<span id="c9e4" data-selectable-paragraph="">Initial:  Maine man wins $1M from $25 lottery ticket  
After update:  See it: baby panda born  
After delete:  Maine man wins $1M from $25 lottery ticket

 持久性

嵌入可以保存到存储中并重新加载。

<span id="cb1e" data-selectable-paragraph="">embeddings.save("index")  
  
embeddings = Embeddings()  
embeddings.load("index")  
  
uid = embeddings.search("climate change", 1)[0][0]  
print(data[uid])
<span id="c76e" data-selectable-paragraph="">Canada's last fully intact ice shelf has suddenly collapsed, forming a  
Manhattan-sized iceberg

 混合搜索

虽然密集向量索引是语义搜索系统的最佳选择,但稀疏关键字索引仍然可以增加价值。在某些情况下,找到精确的匹配很重要。

混合搜索结合了稀疏和密集向量索引的结果,以达到两全其美。

<span id="ac46" data-selectable-paragraph="">  
embeddings = Embeddings(  
  hybrid=True,  
  path="sentence-transformers/nli-mpnet-base-v2"  
)  
  
  
embeddings.index(data)  
  
print("%-20s %s" % ("Query", "Best Match"))  
print("-" * 50)  
  
  
for query in ("feel good story", "climate change",   
    "public health story", "war", "wildlife", "asia",  
    "lucky", "dishonest junk"):  
    
    
  uid = embeddings.search(query, 1)[0][0]  
  
    
  print("%-20s %s" % (query, data[uid]))

结果与语义搜索相同。让我们运行相同的示例,只使用关键字索引来查看这些结果。

<span id="4864" data-selectable-paragraph="">  
embeddings = Embeddings(keyword=True)  
  
  
embeddings.index(data)  
  
print(embeddings.search("feel good story"))  
print(embeddings.search("lottery"))
<span id="c1f1" data-selectable-paragraph="">[]  
[(4, 0.5234998733628726)]

请注意,当嵌入实例只使用关键字索引时,它无法找到语义匹配,只能找到关键字匹配。

 内容存储

到目前为止,所有示例都引用原始数据数组来检索输入文本。这对于演示来说很好,但如果你有数百万个文档呢?在这种情况下,需要使用id从外部数据库检索文本。

内容存储添加了一个关联的数据库(即SQLite,DuckDB),该数据库存储与矢量索引相关联的元数据。文档文本、附加元数据和附加对象可以与索引向量一起存储和检索。

<span id="00f0" data-selectable-paragraph="">  
  
embeddings = Embeddings(  
  path="sentence-transformers/nli-mpnet-base-v2",  
  content=True,  
  objects=True  
)  
  
  
embeddings.index(data)  
  
print(embeddings.search("feel good story", 1)[0]["text"])
<span id="1c0a" data-selectable-paragraph="">Maine man wins $1M from $25 lottery ticket

上面唯一的变化是将_内容_标志设置为True。这样就可以在索引旁边存储文本和元数据内容(如果提供)。注意文本是如何从查询结果中直接提取出来的!

 让我们添加一些元数据。

 使用SQL查询

启用内容后,整个词典将被存储并可以查询。除了向量查询,txtai还接受SQL查询。这使得可以使用矢量索引和存储在数据库后端的内容进行组合查询。

<span id="6e49" data-selectable-paragraph="">  
embeddings.index([{"text": text, "length": len(text)} for text in data])  
  
  
print(embeddings.search("select text, score from txtai where similar('hiking danger') and score &gt;= 0.15"))  
  
  
print(embeddings.search("select text, length, score from txtai where similar('feel good story') and score &gt;= 0.05 and length &gt;= 40"))  
  
  
print(embeddings.search("select count(*), min(length), max(length), sum(length) from txtai"))
<span id="647c" data-selectable-paragraph="">[{'text': 'The National Park Service warns against sacrificing slower friends in a bear attack', 'score': 0.3151373863220215}]  
[{'text': 'Maine man wins $1M from $25 lottery ticket', 'length': 42, 'score': 0.08329027891159058}]  
[{'count(*)': 6, 'min(length)': 39, 'max(length)': 94, 'sum(length)': 387}]

上面的这个例子添加了一个简单的附加字段,文本长度。

请注意,第二个查询是根据元数据字段长度进行过滤的,沿着带有一个类似的查询子句。这提供了矢量搜索与传统过滤的完美结合,以帮助识别最佳结果。

 对象存储

除了元数据之外,二进制内容也可以与文档相关联。下面的例子下载了一个图像,并将其与相关的文本一起沿着到嵌入索引中。

<span id="5021" data-selectable-paragraph="">import urllib  
  
from IPython.display import Image  
  
  
request = urllib.request.urlopen("https://raw.githubusercontent.com/neuml/txtai/master/demo.gif")  
  
  
embeddings.upsert([("txtai", {"text": "txtai executes machine-learning workflows to transform data and build AI-powered semantic search applications.", "object": request.read()}, None)])  
  
  
result = embeddings.search("select object from txtai where similar('machine learning') limit 1")[0]["object"]  
  
  
Image(result.getvalue(), width=600)

 主题建模

通过语义图启用主题建模。语义图,也称为知识图或语义网络,构建具有连接节点的语义关系的图网络。
在txtai中,它们可以利用嵌入索引中固有的关系。

<span id="b31b" data-selectable-paragraph="">  
embeddings = Embeddings(  
  path="sentence-transformers/nli-mpnet-base-v2",  
  content=True,  
  functions=[  
    {"name": "graph", "function": "graph.attribute"},  
  ],  
  expressions=[  
    {"name": "category", "expression": "graph(indexid, 'category')"},  
    {"name": "topic", "expression": "graph(indexid, 'topic')"},  
  ],  
  graph={  
    "topics": {  
      "categories": ["health", "climate", "finance", "world politics"]  
    }  
  }  
)  
  
embeddings.index(data)  
embeddings.search("select topic, category, text from txtai")
<span id="d0fb" data-selectable-paragraph="">[{'topic': 'confirmed_cases_us_5',  
  'category': 'health',  
  'text': 'US tops 5 million confirmed virus cases'},  
 {'topic': 'collapsed_iceberg_ice_intact',  
  'category': 'climate',  
  'text': "Canada's last fully intact ice shelf has suddenly collapsed, forming a Manhattan-sized iceberg"},  
 {'topic': 'beijing_along_craft_tensions',  
  'category': 'world politics',  
  'text': 'Beijing mobilises invasion craft along coast as Taiwan tensions escalate'}]

当启用图形索引时,将为嵌入实例中的每个条目分配主题。主题是动态创建的,使用稀疏索引在社区检测算法分组的图节点上。

主题类别也可以如上所示导出。

 子索引

可以为嵌入配置子索引。一个嵌入实例可以有多个子索引,每个子索引都有不同的配置。

我们将构建一个同时包含关键字和密集索引的嵌入索引来演示。

<span id="20bf" data-selectable-paragraph="">  
embeddings = Embeddings(  
  content=True,  
  defaults=False,  
  indexes={  
    "keyword": {  
      "keyword": True  
    },  
    "dense": {  
      "path": "sentence-transformers/nli-mpnet-base-v2"  
    }  
  }  
)  
embeddings.index(data)
<span id="04dc" data-selectable-paragraph="">embeddings.search("feel good story", limit=1, index="keyword")
<span id="aa57" data-selectable-paragraph="">[]
<span id="7015" data-selectable-paragraph="">embeddings.search("feel good story", limit=1, index="dense")
<span id="63a1" data-selectable-paragraph="">[{'id': '4',  
  'text': 'Maine man wins $1M from $25 lottery ticket',  
  'score': 0.08329027891159058}]

这个例子再次展示了关键字搜索和语义搜索的区别。第一个搜索调用使用定义的关键字索引,第二个使用密集向量索引。

 LLMorchestration

txtai是一个多合一的嵌入式数据库。它是唯一一个还支持稀疏索引、图网络和具有内联SQL支持的关系数据库的矢量数据库。除此之外,txtai还支持LLM编排。

RAG管道是txtai的检索增强生成(RAG)。该管道通过将提示、上下文数据存储和生成模型结合在一起来从内容中提取知识。

下面的示例展示了大型语言模型(LLM)如何使用嵌入数据库作为上下文。

<span id="899f" data-selectable-paragraph="">import torch  
from txtai import RAG  
  
def prompt(question):  
  return [{  
    "query": question,  
    "question": f"""  
Answer the following question using the context below.  
Question: {question}  
Context:  
"""  
}]  
  
  
embeddings = Embeddings(  
  path="sentence-transformers/nli-mpnet-base-v2",  
  content=True,  
  autoid="uuid5"  
)  
  
  
embeddings.index(data)  
  
  
rag = RAG(  
  embeddings,  
  "google/flan-t5-large",   
  torch_dtype=torch.bfloat16,   
  output="reference"  
)  
rag(prompt("What country is having issues with climate change?"))[0]
<span id="124c" data-selectable-paragraph="">{'answer': 'Canada', 'reference': 'da633124-33ff-58d6-8ecb-14f7a44c042a'}

上面的逻辑首先构建一个嵌入索引。然后加载LLM并使用嵌入索引驱动LLM提示符。

RAG流水线可以选择性地返回对与答案最匹配的记录的id的引用。该id可用于解析完整的答案引用。请注意,上面的嵌入使用了uuid自动序列

<span id="6af4" data-selectable-paragraph="">uid = rag(prompt("What country is having issues with climate change?"))[0]["reference"]  
embeddings.search(f"select id, text from txtai where id = '{uid}'")
<span id="6c50" data-selectable-paragraph="">[{'id': 'da633124-33ff-58d6-8ecb-14f7a44c042a',  
  'text': "Canada's last fully intact ice shelf has suddenly collapsed, forming a Manhattan-sized iceberg"}]

LLM推理也可以独立运行。

<span id="780e" data-selectable-paragraph="">from txtai import LLM  
  
llm = LLM("google/flan-t5-large", torch_dtype=torch.bfloat16)  
llm("Where is one place you'd go in Washington, DC?")
<span id="e49a" data-selectable-paragraph="">national museum of american history

 语言模型工作流

语言模型工作流,也称为语义工作流,将语言模型连接在一起以构建智能应用程序。

工作流可以与嵌入实例一起运行,类似于关系数据库中的存储过程。工作流程可以用Python或YAML编写。我们将演示如何使用YAML编写工作流。

<span id="e323" data-selectable-paragraph="">  
writable: true  
embeddings:  
  path: sentence-transformers/nli-mpnet-base-v2  
  content: true  
  functions:  
    - {name: translation, argcount: 2, function: translation}  
  
  
translation:  
  
  
workflow:  
  search:  
    tasks:  
      - search  
      - action: translation  
        args:  
          target: fr  
        task: template  
        template: "{text}"

上面的工作流加载嵌入索引并定义搜索工作流。搜索工作流运行搜索,然后将结果传递到翻译管道。翻译管道将结果翻译为法语。

<span id="bf54" data-selectable-paragraph="">from txtai import Application  
  
  
app = Application("embeddings.yml")  
app.add(data)  
app.index()  
  
  
list(app.workflow(  
  "search",   
  ["select text from txtai where similar('feel good story') limit 1"]  
))
<span id="d91c" data-selectable-paragraph="">['Maine homme gagne $1M à partir de $25 billet de loterie']

在某些情况下,SQL函数可以完成与工作流相同的任务。下面的函数将翻译管道作为一个函数运行。

<span id="a27a" data-selectable-paragraph="">app.search("select translation(text, 'fr') text from txtai where similar('feel good story') limit 1")
<span id="7692" data-selectable-paragraph="">[{'text': 'Maine homme gagne $1M à partir de $25 billet de loterie'}]

带有模板LLM链也可以使用工作流。工作流是自包含的,它们可以在有或没有相关嵌入实例的情况下运行。以下工作流程使用LLM条件地将文本翻译为法语,然后检测文本的语言。

<span id="7980" data-selectable-paragraph="">sequences:  
  path: google/flan-t5-large  
  torch_dtype: torch.bfloat16  
  
workflow:  
  chain:  
    tasks:  
      - task: template  
        template: Translate '{statement}' to {language} if it's English  
        action: sequences  
      - task: template  
        template: What language is the following text? {text}  
        action: sequences
<span id="0a8b" data-selectable-paragraph="">inputs = [  
  {"statement": "Hello, how are you", "language": "French"},  
  {"statement": "Hallo, wie geht's dir", "language": "French"}  
]  
  
app = Application("workflow.yml")  
list(app.workflow("chain", inputs))
<span id="2459" data-selectable-paragraph="">['French', 'German']

 结束了

NLP正在快速发展。一年前不可能的事情,现在已经成为可能。本文介绍了txtai,一个一体化的嵌入式数据库。可能性是无限的,我们很高兴看到什么可以建立在txtai之上!

请访问下面的链接了解更多信息。

GitHub|文件|示例