LangChain
使用 Easysearch 打造企业内部知识问答系统
Easysearch • INFINI Labs 小助手 发表了文章 • 0 个评论 • 2344 次浏览 • 2024-07-29 11:08
大家可能都有这样的经历,刚入职一家企业时,同事往往会给你分享一些文档资料,有可能是产品信息、规章制度等等。这些文档有的过于冗长,很难第一时间找到想要的内容。有的已经有了新版本,但员工使用的还是老版本。
基于这种背景,我们可以利用 Easysearch 加 LLM 实现一个内部知识的 QA 问答系统。这个系统将利用 LangChain 框架调用本地部署的大模型和 Easysearch,实现理解员工的提问,并基于最新的文档,给出精准答案。
开发框架
整个框架分为四个部分:
- 数据源:数据可以有很多种,可以是非结构化的,比如 PDF、docx、txt 等。也可以是结构化的数据,甚至代码也行。在本次示例中,我们使用 PDF 的非结构化数据。
- 大模型应用:应用与大模型交互,生成我们需要的答案。
- 大模型:系统执行相关任务需要用到的大模型,可以有多个。
- Q&A 场景:基于大模型为引擎的 QA 场景,使用 web 框架,构建一个交互界面。
数据准备
本次我们使用的资料是 "INFINI 产品安装手册.pdf" ,文档部分内容展示如下:
首先我们使用 LangChain 的 document_loaders 来加载文件。document_loaders 集成了数百种数据源格式,可以很方便的加载数据。我们的数据的 pdf 格式的,导入 PyPDFLoader 类来进行处理。代码如下:
import os
# 导入 Document Loaders
from langchain_community.document_loaders import PyPDFLoader
# Load Pdf
base_dir = '.\\easysearch' # 文档的存放目录
docs = []
for file in os.listdir(base_dir):
file_path = os.path.join(base_dir, file)
if file.endswith('.pdf'):
loader = PyPDFLoader(file_path)
documents.extend(loader.load())
上面的代码将 pdf 文件的内容存储在 docs 这个列表中,以便后续进行处理。
文本分割
一个文件的文本内容可能很大,无法适应许多模型的上下文窗口,也不利于检索和存储。因此,通常我们会将文本内容分割成更小的块,这将帮助我们在运行时只检索文档中最相关的部分。LangChain 提供了工具来进行处理文本分割,非常方便。 我们将把文档分割成 1000 个字符的块,每个块之间有 200 个重叠字符。这种重叠有助于减少将语句与相关的重要上下文分离的可能性。
# 2.将Documents切分成块
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=20)
chunked_documents = text_splitter.split_documents(docs)
上面的代码将 docs 的内容按 1000 字符大小进行切分,存储在 chunked_documents 中,以便后续进行处理。
注意,实际运行中,切分及重叠的大小,都会影响应用效果,需自行调试。
向量库 Easysearch
接下来,我们将这些文本块转换成向量的形式,并存储在一个向量数据库中。在本示例中,我们使用 mxbai-embed-large 模型来生成向量,然后将向量和原始内容存入 easysearch 。
本地部署模型,我使用的是 ollama ,大家可以使用自己喜欢的工具。
# 3. 定义embedding模型
from langchain_community.embeddings import OllamaEmbeddings
ollama_emb = OllamaEmbeddings(
model="mxbai-embed-large",
)
# 4. 定义 easysearch 集群的信息,以及存放向量的索引名称 infini
from langchain_community.vectorstores import EcloudESVectorStore
ES_URL = "https://192.168.56.3:9200"
USER = "admin"
PASSWORD = "e5ac1b537785ae27c187"
indexname = "infini"
docsearch = EcloudESVectorStore.from_documents(
chunked_documents,
ollama_emb,
es_url=ES_URL,
user=USER,
password=PASSWORD,
index_name=indexname,
verify_certs=False,
)
通过上面的步骤,我们成功将文本块转换成了向量,并存入到了 easysearch 集群的 infini 索引中。 我们看看 infini 索引内容是怎样的 text 字段存放了文本块的原始内容,vector 字段存放着对应的向量表示。
检索及生成答案
在这一步,我们会定义一个生成式大模型。然后创建一个 RetrievalQA 链,它是一个检索式问答模型,用于生成问题的答案。
在 RetrievalQA 链中有下面两大重要组成部分。
- LLM 是大模型,负责回答问题。
- retriever(vectorstore.as_retriever())负责根据用户的问题检索相关的信息。先是找最近似的“向量块”,再把”向量块“对应的“文档块”作为知识信息,和问题一起传递进入大模型。之所以要先检索,是因为从互联网信息训练而来的大模型不可能拥有一个私营企业的内部知识。
# 5. Retrieval 准备模型和Retrieval链
import logging
# MultiQueryRetriever工具
from langchain.retrievers.multi_query import MultiQueryRetriever
# RetrievalQA链
from langchain.chains import RetrievalQA
# # 设置Logging
logging.basicConfig()
logging.getLogger('langchain.retrievers.multi_query').setLevel(logging.INFO)
# # 实例化一个大模型工具
from langchain_community.chat_models import ChatOllama
llm = ChatOllama(model="qwen2:latest")
from langchain.prompts import PromptTemplate
my_template = PromptTemplate(
input_variables=["question"],
template="""You are an AI language model assistant. Your task is
to generate 3 different versions of the given user
question in Chinese to retrieve relevant documents from a vector database.
By generating multiple perspectives on the user question,
your goal is to help the user overcome some of the limitations
of distance-based similarity search. Provide these alternative
questions separated by newlines. Original question: {question}""",
)
# # 实例化一个MultiQueryRetriever
retriever_from_llm = MultiQueryRetriever.from_llm(retriever=docsearch.as_retriever(), llm=llm,prompt=my_template,include_original=True)
# # 实例化一个RetrievalQA链
qa_chain = RetrievalQA.from_chain_type(llm,retriever=retriever_from_llm)
这里我们使用 ollama 在本地部署一个 qwen2 大模型,负责问题改写和生成答案。
启动 qwen2 大模型:ollama run qwen2
我们获取到用户问题后,先通过 MultiQueryRetriever 类调用大模型 qwen2 进行改写,生成 3 个同样语义的问题,然后再调用 easyearch 进行向量检索,搜索相关内容。
最后把所有相关内容,合并、去重后,与原始问题一起提交给大模型 qwen2,进行答案生成。
虽然这里使用的是向量检索,但实际上我们可以同时使用全文检索和向量检索。这也是使用 easysearch 作为检索库的优势之一。
前端展示
这一步我们创建一个 Flask 应用(需要安装 Flask 包)来接收用户的问题,并生成相应的答案,最后通过 index.html 对答案进行渲染和呈现。
在这个步骤中,我们使用了之前创建的 RetrievalQA 链来获取相关的文档和生成答案。然后,将这些信息返回给用户,显示在网页上。
# 6. Q&A系统的UI实现
from flask import Flask, request, render_template
app = Flask(__name__) # Flask APP
@app.route('/', methods=['GET', 'POST'])
def home():
if request.method == 'POST':
# 接收用户输入作为问题
question = request.form.get('question')
# RetrievalQA链 - 读入问题,生成答案
result = qa_chain({"query": question})
# 把大模型的回答结果返回网页进行渲染
return render_template('index.html', result=result)
return render_template('index.html')
if __name__ == "__main__":
app.run(host='0.0.0.0',debug=True,port=5000)
效果演示
我们模仿用户进行提问。 Q&A 系统进行回答,回答速度取决于本地的计算资源。 内容校验,在原始文档内用 ctrl+F 搜索关键字 LOGGING_ES_ENDPOINT 得到如下内容。 嗯,回答的还不错,达到预期目的。如果还有其他要求,可修改 my_template 中的提示词或者替换成别的大模型也是可以的。
小结
通过这次示例,我们演示了如何基于 LangChain 和 easysearch 以及大模型,快速开发出一个内部知识问答系统。怎么样,是不是觉得整个流程特别简单易懂?
如有任何问题,请随时联系我,期待与您交流!
关于 Easysearch 有奖征文活动
无论你是 Easysearch 的老用户,还是第一次听说这个名字,只要你对 INFINI Labs 旗下的 Easysearch 产品感兴趣,或者是希望了解 Easysearch,都可以参加这次活动。
详情查看:Easysearch 征文活动
【第1期】2024 搜索客 Meetup | Easysearch 结合大模型实现 RAG
活动 • searchkit 发表了文章 • 0 个评论 • 2838 次浏览 • 2024-07-25 16:57
2024 搜索客社区 Meetup 首期线上活动正式启动,本次活动由 搜索客社区、极限科技(INFINI Labs)联合举办,诚邀广大搜索技术开发者和爱好者参加交流学习。
活动时间:2024 年 7 月 31 日 19:00-20:00 (周三)
活动形式:微信视频号(极限实验室)直播
报名方式:关注或扫码海报中的二维码进行预约
活动简介
在这个人工智能飞速发展的时代,ChatGPT 和 GPT-4 的出现无疑为人类带来了前所未有的震撼。我们不禁思考:通用人工智能的奇点是否真的即将来临?而最前沿的 AI 技术与最实用的落地应用之间的距离,又该如何缩短?
为了深入探讨这些问题,我们特别邀请到了极限科技(INFINI Labs)高级解决方案架构师、《老杨玩搜索》栏目 B 站 UP 主——杨帆先生,为我们带来一场主题为 “Easysearch 结合大模型实现 RAG” 的精彩演讲。
嘉宾介绍
杨帆,拥有十余年金融行业服务工作经验,熟悉 Linux、数据库、网络等领域。目前主要从事 Easysearch、Elasticsearch 等搜索引擎的技术支持工作,服务国内私有化部署的客户。他的丰富经验和深刻见解,将为我们揭开 AI 技术与实际应用之间的神秘面纱。
演讲主题
《Easysearch 结合大模型实现 RAG》
主题摘要
在本次演讲中,杨帆将跟大家分享和探讨以下几个方面:
- LangChain 简介:LangChain 的作用是什么?它由哪些组件构成,优势是什么。
- RAG 的背景及其局限性:RAG 出现以前的我们是如何获取信息的,RAG 解决了什么问题?它就是最终的答案了吗?
- LangChain 下的 RAG 工作流:在 LangChain 的框架下,实现 RAG 的步骤是怎样的。
- RAG Demo:使用 ollama 部署本地模型,利用 LangChain 集成 Easysearch 和 LLM , 开发 QA 问答系统
活动亮点
- 前沿技术分享: 深入了解当前 AI 领域的最新动态和发展趋势。
- 实战经验交流: 学习如何在实际工作中应用这些先进技术。
- 互动问答环节: 与演讲嘉宾直接对话,解答你的疑惑。
参与有奖
本次直播活动中设置了随机抽奖环节,奖品为 INFINI Labs 周边纪念品,包括 T 恤、鸭舌帽、咖啡杯、指甲刀套件等等(图片仅供参考,款式、颜色与尺码随机)。
活动交流
活动交流群二维码 7 天内(8 月 1 日前)有效,如过期请添加小助手微信拉群。活动最新消息也会在群内及时同步,欢迎大家参与,记得先预约,精彩内容不错过!
讲师招募
搜索客社区 Meetup 讲师持续招募中...
这是一个由搜索客社区精心组织策划的线下线上技术交流活动,我们诚挚邀请各位技术大咖、行业精英踊跃提交演讲议题。Meetup 活动将聚焦 AI 与搜索领域的最新动态,以及数据实时搜索分析、向量检索、技术实践与案例分析、日志分析、安全等领域的深度探讨。详情参见:http://cfp.searchkit.cn 。我们热切期待您的精彩分享!
关于 搜索客(SearchKit)社区
搜索客社区由 Elasticsearch 中文社区进行全新的品牌升级,以新的 Slogan:“搜索人自己的社区” 为宣言。汇集搜索领域最新动态、精选干货文章、精华讨论、文档资料、翻译与版本发布等,为广大搜索领域从业者提供更为丰富便捷的学习和交流平台。社区官网:https://searchkit.cn 。
Easysearch 有奖征文活动推荐
无论你是 Easysearch 的老用户,还是第一次听说这个名字,只要你对 INFINI Labs 旗下的 Easysearch 产品感兴趣,或者是希望了解 Easysearch,都可以参加这次活动。
详情查看:Easysearch 征文活动
【搜索客社区日报】第1863期 (2024-07-19)
社区日报 • Fred2000 发表了文章 • 0 个评论 • 2571 次浏览 • 2024-07-19 11:32
【搜索客社区日报】 第1824期 (2024-05-24)
社区日报 • searchkit 发表了文章 • 0 个评论 • 2598 次浏览 • 2024-05-24 13:17
使用 Easysearch 打造企业内部知识问答系统
Easysearch • INFINI Labs 小助手 发表了文章 • 0 个评论 • 2344 次浏览 • 2024-07-29 11:08
大家可能都有这样的经历,刚入职一家企业时,同事往往会给你分享一些文档资料,有可能是产品信息、规章制度等等。这些文档有的过于冗长,很难第一时间找到想要的内容。有的已经有了新版本,但员工使用的还是老版本。
基于这种背景,我们可以利用 Easysearch 加 LLM 实现一个内部知识的 QA 问答系统。这个系统将利用 LangChain 框架调用本地部署的大模型和 Easysearch,实现理解员工的提问,并基于最新的文档,给出精准答案。
开发框架
整个框架分为四个部分:
- 数据源:数据可以有很多种,可以是非结构化的,比如 PDF、docx、txt 等。也可以是结构化的数据,甚至代码也行。在本次示例中,我们使用 PDF 的非结构化数据。
- 大模型应用:应用与大模型交互,生成我们需要的答案。
- 大模型:系统执行相关任务需要用到的大模型,可以有多个。
- Q&A 场景:基于大模型为引擎的 QA 场景,使用 web 框架,构建一个交互界面。
数据准备
本次我们使用的资料是 "INFINI 产品安装手册.pdf" ,文档部分内容展示如下:
首先我们使用 LangChain 的 document_loaders 来加载文件。document_loaders 集成了数百种数据源格式,可以很方便的加载数据。我们的数据的 pdf 格式的,导入 PyPDFLoader 类来进行处理。代码如下:
import os
# 导入 Document Loaders
from langchain_community.document_loaders import PyPDFLoader
# Load Pdf
base_dir = '.\\easysearch' # 文档的存放目录
docs = []
for file in os.listdir(base_dir):
file_path = os.path.join(base_dir, file)
if file.endswith('.pdf'):
loader = PyPDFLoader(file_path)
documents.extend(loader.load())
上面的代码将 pdf 文件的内容存储在 docs 这个列表中,以便后续进行处理。
文本分割
一个文件的文本内容可能很大,无法适应许多模型的上下文窗口,也不利于检索和存储。因此,通常我们会将文本内容分割成更小的块,这将帮助我们在运行时只检索文档中最相关的部分。LangChain 提供了工具来进行处理文本分割,非常方便。 我们将把文档分割成 1000 个字符的块,每个块之间有 200 个重叠字符。这种重叠有助于减少将语句与相关的重要上下文分离的可能性。
# 2.将Documents切分成块
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=20)
chunked_documents = text_splitter.split_documents(docs)
上面的代码将 docs 的内容按 1000 字符大小进行切分,存储在 chunked_documents 中,以便后续进行处理。
注意,实际运行中,切分及重叠的大小,都会影响应用效果,需自行调试。
向量库 Easysearch
接下来,我们将这些文本块转换成向量的形式,并存储在一个向量数据库中。在本示例中,我们使用 mxbai-embed-large 模型来生成向量,然后将向量和原始内容存入 easysearch 。
本地部署模型,我使用的是 ollama ,大家可以使用自己喜欢的工具。
# 3. 定义embedding模型
from langchain_community.embeddings import OllamaEmbeddings
ollama_emb = OllamaEmbeddings(
model="mxbai-embed-large",
)
# 4. 定义 easysearch 集群的信息,以及存放向量的索引名称 infini
from langchain_community.vectorstores import EcloudESVectorStore
ES_URL = "https://192.168.56.3:9200"
USER = "admin"
PASSWORD = "e5ac1b537785ae27c187"
indexname = "infini"
docsearch = EcloudESVectorStore.from_documents(
chunked_documents,
ollama_emb,
es_url=ES_URL,
user=USER,
password=PASSWORD,
index_name=indexname,
verify_certs=False,
)
通过上面的步骤,我们成功将文本块转换成了向量,并存入到了 easysearch 集群的 infini 索引中。 我们看看 infini 索引内容是怎样的 text 字段存放了文本块的原始内容,vector 字段存放着对应的向量表示。
检索及生成答案
在这一步,我们会定义一个生成式大模型。然后创建一个 RetrievalQA 链,它是一个检索式问答模型,用于生成问题的答案。
在 RetrievalQA 链中有下面两大重要组成部分。
- LLM 是大模型,负责回答问题。
- retriever(vectorstore.as_retriever())负责根据用户的问题检索相关的信息。先是找最近似的“向量块”,再把”向量块“对应的“文档块”作为知识信息,和问题一起传递进入大模型。之所以要先检索,是因为从互联网信息训练而来的大模型不可能拥有一个私营企业的内部知识。
# 5. Retrieval 准备模型和Retrieval链
import logging
# MultiQueryRetriever工具
from langchain.retrievers.multi_query import MultiQueryRetriever
# RetrievalQA链
from langchain.chains import RetrievalQA
# # 设置Logging
logging.basicConfig()
logging.getLogger('langchain.retrievers.multi_query').setLevel(logging.INFO)
# # 实例化一个大模型工具
from langchain_community.chat_models import ChatOllama
llm = ChatOllama(model="qwen2:latest")
from langchain.prompts import PromptTemplate
my_template = PromptTemplate(
input_variables=["question"],
template="""You are an AI language model assistant. Your task is
to generate 3 different versions of the given user
question in Chinese to retrieve relevant documents from a vector database.
By generating multiple perspectives on the user question,
your goal is to help the user overcome some of the limitations
of distance-based similarity search. Provide these alternative
questions separated by newlines. Original question: {question}""",
)
# # 实例化一个MultiQueryRetriever
retriever_from_llm = MultiQueryRetriever.from_llm(retriever=docsearch.as_retriever(), llm=llm,prompt=my_template,include_original=True)
# # 实例化一个RetrievalQA链
qa_chain = RetrievalQA.from_chain_type(llm,retriever=retriever_from_llm)
这里我们使用 ollama 在本地部署一个 qwen2 大模型,负责问题改写和生成答案。
启动 qwen2 大模型:ollama run qwen2
我们获取到用户问题后,先通过 MultiQueryRetriever 类调用大模型 qwen2 进行改写,生成 3 个同样语义的问题,然后再调用 easyearch 进行向量检索,搜索相关内容。
最后把所有相关内容,合并、去重后,与原始问题一起提交给大模型 qwen2,进行答案生成。
虽然这里使用的是向量检索,但实际上我们可以同时使用全文检索和向量检索。这也是使用 easysearch 作为检索库的优势之一。
前端展示
这一步我们创建一个 Flask 应用(需要安装 Flask 包)来接收用户的问题,并生成相应的答案,最后通过 index.html 对答案进行渲染和呈现。
在这个步骤中,我们使用了之前创建的 RetrievalQA 链来获取相关的文档和生成答案。然后,将这些信息返回给用户,显示在网页上。
# 6. Q&A系统的UI实现
from flask import Flask, request, render_template
app = Flask(__name__) # Flask APP
@app.route('/', methods=['GET', 'POST'])
def home():
if request.method == 'POST':
# 接收用户输入作为问题
question = request.form.get('question')
# RetrievalQA链 - 读入问题,生成答案
result = qa_chain({"query": question})
# 把大模型的回答结果返回网页进行渲染
return render_template('index.html', result=result)
return render_template('index.html')
if __name__ == "__main__":
app.run(host='0.0.0.0',debug=True,port=5000)
效果演示
我们模仿用户进行提问。 Q&A 系统进行回答,回答速度取决于本地的计算资源。 内容校验,在原始文档内用 ctrl+F 搜索关键字 LOGGING_ES_ENDPOINT 得到如下内容。 嗯,回答的还不错,达到预期目的。如果还有其他要求,可修改 my_template 中的提示词或者替换成别的大模型也是可以的。
小结
通过这次示例,我们演示了如何基于 LangChain 和 easysearch 以及大模型,快速开发出一个内部知识问答系统。怎么样,是不是觉得整个流程特别简单易懂?
如有任何问题,请随时联系我,期待与您交流!
关于 Easysearch 有奖征文活动
无论你是 Easysearch 的老用户,还是第一次听说这个名字,只要你对 INFINI Labs 旗下的 Easysearch 产品感兴趣,或者是希望了解 Easysearch,都可以参加这次活动。
详情查看:Easysearch 征文活动
【第1期】2024 搜索客 Meetup | Easysearch 结合大模型实现 RAG
活动 • searchkit 发表了文章 • 0 个评论 • 2838 次浏览 • 2024-07-25 16:57
2024 搜索客社区 Meetup 首期线上活动正式启动,本次活动由 搜索客社区、极限科技(INFINI Labs)联合举办,诚邀广大搜索技术开发者和爱好者参加交流学习。
活动时间:2024 年 7 月 31 日 19:00-20:00 (周三)
活动形式:微信视频号(极限实验室)直播
报名方式:关注或扫码海报中的二维码进行预约
活动简介
在这个人工智能飞速发展的时代,ChatGPT 和 GPT-4 的出现无疑为人类带来了前所未有的震撼。我们不禁思考:通用人工智能的奇点是否真的即将来临?而最前沿的 AI 技术与最实用的落地应用之间的距离,又该如何缩短?
为了深入探讨这些问题,我们特别邀请到了极限科技(INFINI Labs)高级解决方案架构师、《老杨玩搜索》栏目 B 站 UP 主——杨帆先生,为我们带来一场主题为 “Easysearch 结合大模型实现 RAG” 的精彩演讲。
嘉宾介绍
杨帆,拥有十余年金融行业服务工作经验,熟悉 Linux、数据库、网络等领域。目前主要从事 Easysearch、Elasticsearch 等搜索引擎的技术支持工作,服务国内私有化部署的客户。他的丰富经验和深刻见解,将为我们揭开 AI 技术与实际应用之间的神秘面纱。
演讲主题
《Easysearch 结合大模型实现 RAG》
主题摘要
在本次演讲中,杨帆将跟大家分享和探讨以下几个方面:
- LangChain 简介:LangChain 的作用是什么?它由哪些组件构成,优势是什么。
- RAG 的背景及其局限性:RAG 出现以前的我们是如何获取信息的,RAG 解决了什么问题?它就是最终的答案了吗?
- LangChain 下的 RAG 工作流:在 LangChain 的框架下,实现 RAG 的步骤是怎样的。
- RAG Demo:使用 ollama 部署本地模型,利用 LangChain 集成 Easysearch 和 LLM , 开发 QA 问答系统
活动亮点
- 前沿技术分享: 深入了解当前 AI 领域的最新动态和发展趋势。
- 实战经验交流: 学习如何在实际工作中应用这些先进技术。
- 互动问答环节: 与演讲嘉宾直接对话,解答你的疑惑。
参与有奖
本次直播活动中设置了随机抽奖环节,奖品为 INFINI Labs 周边纪念品,包括 T 恤、鸭舌帽、咖啡杯、指甲刀套件等等(图片仅供参考,款式、颜色与尺码随机)。
活动交流
活动交流群二维码 7 天内(8 月 1 日前)有效,如过期请添加小助手微信拉群。活动最新消息也会在群内及时同步,欢迎大家参与,记得先预约,精彩内容不错过!
讲师招募
搜索客社区 Meetup 讲师持续招募中...
这是一个由搜索客社区精心组织策划的线下线上技术交流活动,我们诚挚邀请各位技术大咖、行业精英踊跃提交演讲议题。Meetup 活动将聚焦 AI 与搜索领域的最新动态,以及数据实时搜索分析、向量检索、技术实践与案例分析、日志分析、安全等领域的深度探讨。详情参见:http://cfp.searchkit.cn 。我们热切期待您的精彩分享!
关于 搜索客(SearchKit)社区
搜索客社区由 Elasticsearch 中文社区进行全新的品牌升级,以新的 Slogan:“搜索人自己的社区” 为宣言。汇集搜索领域最新动态、精选干货文章、精华讨论、文档资料、翻译与版本发布等,为广大搜索领域从业者提供更为丰富便捷的学习和交流平台。社区官网:https://searchkit.cn 。
Easysearch 有奖征文活动推荐
无论你是 Easysearch 的老用户,还是第一次听说这个名字,只要你对 INFINI Labs 旗下的 Easysearch 产品感兴趣,或者是希望了解 Easysearch,都可以参加这次活动。
详情查看:Easysearch 征文活动
【搜索客社区日报】第1863期 (2024-07-19)
社区日报 • Fred2000 发表了文章 • 0 个评论 • 2571 次浏览 • 2024-07-19 11:32
【搜索客社区日报】 第1824期 (2024-05-24)
社区日报 • searchkit 发表了文章 • 0 个评论 • 2598 次浏览 • 2024-05-24 13:17