Deepset의 Haystack 2.0에 Jina Embeddings가 통합되고 Jina Reranker가 출시된 데 이어, Jina Reranker가 이제 Jina Haystack 확장을 통해서도 사용 가능하게 되었음을 기쁘게 발표합니다.


Haystack은 GenAI 프로젝트 수명 주기의 모든 단계를 지원하는 종단간 프레임워크입니다. 문서 검색, 검색 증강 생성(RAG), 질문 답변 또는 답변 생성을 수행하고자 할 때, Haystack은 최신 임베딩 모델과 LLM을 파이프라인으로 구성하여 종단간 NLP 애플리케이션을 구축하고 사용 사례를 해결할 수 있습니다.

이 글에서는 이들을 사용하여 자체 Jira 티켓 검색 엔진을 만들어 운영을 효율화하고 중복 이슈 생성으로 시간을 낭비하지 않는 방법을 보여드리겠습니다.
이 튜토리얼을 따라하려면 Jina Reranker API 키가 필요합니다. Jina Reranker 웹사이트에서 100만 토큰의 무료 체험판 할당량으로 API 키를 만들 수 있습니다.
tagJira 지원 티켓 검색하기
복잡한 프로젝트를 다루는 모든 팀은 이슈를 등록하고 싶은데 해당 문제에 대한 티켓이 이미 존재하는지 알 수 없어 답답했던 경험이 있을 것입니다.
다음 튜토리얼에서는 Jina Reranker와 Haystack 파이프라인을 사용하여 새로 생성되는 티켓에 대해 가능한 중복 티켓을 제안하는 도구를 직접 만드는 방법을 보여드리겠습니다.
- 기존 모든 티켓과 대조해야 하는 티켓을 입력하면, 파이프라인이 먼저 데이터베이스에서 모든 관련 이슈를 검색합니다.
- 그런 다음 원본 티켓(데이터베이스에 이미 존재하는 경우)과 모든 하위 티켓(원본 티켓의 상위 ID에 해당하는 티켓)을 목록에서 제거합니다.
- 최종 선택은 이제 원본 티켓과 동일한 주제를 다룰 수 있지만 데이터베이스에서 ID를 통해 그렇게 표시되지 않은 이슈들만 포함합니다. 이러한 티켓들은 최대 연관성을 보장하기 위해 재순위가 매겨지며 데이터베이스의 중복 항목을 식별할 수 있게 합니다.
tag데이터셋 가져오기
우리의 솔루션을 구현하기 위해 Apache Zookeeper 프로젝트의 모든 "진행 중" 상태인 Jira 티켓을 선택했습니다. 이것은 분산 애플리케이션의 프로세스를 조정하기 위한 오픈소스 서비스입니다.
더 편리하게 사용할 수 있도록 티켓들을 JSON 파일에 저장했습니다. 파일을 작업 공간에 다운로드하세요.
tag사전 요구사항 설정
요구사항을 설치하려면 다음을 실행하세요:
pip install --q chromadb haystack-ai jina-haystack chroma-haystack
API 키를 입력하려면 환경 변수로 설정하세요:
import os
import getpass
os.environ["JINA_API_KEY"] = getpass.getpass()
getpass.getpass()는 해당 코드 블록 아래에 API 키를 입력하라는 메시지를 표시합니다. 거기에 키를 입력하고 enter를 눌러 튜토리얼을 계속할 수 있습니다. 원하는 경우 getpass.getpass()를 API 키 자체로 대체할 수도 있습니다.tag인덱싱 파이프라인 구축
인덱싱 파이프라인은 티켓을 전처리하고, 벡터로 변환한 후 저장합니다. 벡터 임베딩을 저장하기 위해 Chroma Document Store Haystack 통합을 통해 Chroma DocumentStore를 벡터 데이터베이스로 사용할 것입니다.
from haystack_integrations.document_stores.chroma import ChromaDocumentStore
document_store = ChromaDocumentStore()
먼저 관련 문서 필드만 고려하고 모든 빈 항목을 삭제하는 사용자 정의 데이터 전처리기를 정의하겠습니다:
import json
from typing import List
from haystack import Document, component
relevant_keys = ['Summary', 'Issue key', 'Issue id', 'Parent id', 'Issue type', 'Status', 'Project lead', 'Priority', 'Assignee', 'Reporter', 'Creator', 'Created', 'Updated', 'Last Viewed', 'Due Date', 'Labels',
'Description', 'Comment', 'Comment__1', 'Comment__2', 'Comment__3', 'Comment__4', 'Comment__5', 'Comment__6', 'Comment__7', 'Comment__8', 'Comment__9', 'Comment__10', 'Comment__11', 'Comment__12',
'Comment__13', 'Comment__14', 'Comment__15']
@component
class RemoveKeys:
@component.output_types(documents=List[Document])
def run(self, file_name: str):
with open(file_name, 'r') as file:
tickets = json.load(file)
cleaned_tickets = []
for t in tickets:
t = {k: v for k, v in t.items() if k in relevant_keys and v}
cleaned_tickets.append(t)
return {'documents': cleaned_tickets}
그런 다음 티켓을 Haystack이 이해할 수 있는 Document 객체로 변환하기 위한 사용자 정의 JSON 변환기를 만들어야 합니다:
@component
class JsonConverter:
@component.output_types(documents=List[Document])
def run(self, tickets: List[Document]):
tickets_documents = []
for t in tickets:
if 'Parent id' in t:
t = Document(content=json.dumps(t), meta={'Issue key': t['Issue key'], 'Issue id': t['Issue id'], 'Parent id': t['Parent id']})
else:
t = Document(content=json.dumps(t), meta={'Issue key': t['Issue key'], 'Issue id': t['Issue id'], 'Parent id': ''})
tickets_documents.append(t)
return {'documents': tickets_documents}
마지막으로 Documents를 임베딩하고 이러한 임베딩을 ChromaDocumentStore에 작성합니다:
from haystack import Pipeline
from haystack.components.writers import DocumentWriter
from haystack_integrations.components.retrievers.chroma import ChromaEmbeddingRetriever
from haystack.document_stores.types import DuplicatePolicy
from haystack_integrations.components.embedders.jina import JinaDocumentEmbedder
retriever = ChromaEmbeddingRetriever(document_store=document_store)
retriever_reranker = ChromaEmbeddingRetriever(document_store=document_store)
indexing_pipeline = Pipeline()
indexing_pipeline.add_component('cleaner', RemoveKeys())
indexing_pipeline.add_component('converter', JsonConverter())
indexing_pipeline.add_component('embedder', JinaDocumentEmbedder(model='jina-embeddings-v2-base-en'))
indexing_pipeline.add_component('writer', DocumentWriter(document_store=document_store, policy=DuplicatePolicy.SKIP))
indexing_pipeline.connect('cleaner', 'converter')
indexing_pipeline.connect('converter', 'embedder')
indexing_pipeline.connect('embedder', 'writer')
indexing_pipeline.run({'cleaner': {'file_name': 'tickets.json'}})
이렇게 하면 진행 상태 표시줄이 생성되고 저장된 내용에 대한 간단한 JSON 정보가 출력됩니다:
Calculating embeddings: 100%|██████████| 1/1 [00:01<00:00, 1.21s/it]
{'embedder': {'meta': {'model': 'jina-embeddings-v2-base-en',
'usage': {'total_tokens': 20067, 'prompt_tokens': 20067}}},
'writer': {'documents_written': 31}}tag쿼리 파이프라인 구축하기
티켓을 비교할 수 있도록 쿼리 파이프라인을 만들어보겠습니다. Haystack 2.0에서 Retriever는 DocumentStore와 밀접하게 연결되어 있습니다. 이전에 초기화한 Retriever에 document store를 전달하면, 이 파이프라인은 우리가 생성한 문서에 접근하고 이를 reranker에 전달할 수 있습니다. 그러면 reranker는 이러한 문서들을 질문과 직접 비교하여 관련성에 따라 순위를 매깁니다.
먼저 쿼리로 전달된 이슈와 동일한 이슈 ID 또는 상위 ID를 포함하는 티켓을 제거하는 사용자 정의 클리너를 정의합니다:
from typing import Optional
@component
class RemoveRelated:
@component.output_types(documents=List[Document])
def run(self, tickets: List[Document], query_id: Optional[str]):
retrieved_tickets = []
for t in tickets:
if not t.meta['Issue id'] == query_id and not t.meta['Parent id'] == query_id:
retrieved_tickets.append(t)
return {'documents': retrieved_tickets}
그런 다음 쿼리를 임베딩하고, 관련 문서를 검색하고, 선택 항목을 정리한 후 마지막으로 재순위를 매깁니다:
from haystack_integrations.components.embedders.jina import JinaTextEmbedder
from haystack_integrations.components.rankers.jina import JinaRanker
query_pipeline_reranker = Pipeline()
query_pipeline_reranker.add_component('query_embedder_reranker', JinaTextEmbedder(model='jina-embeddings-v2-base-en'))
query_pipeline_reranker.add_component('query_retriever_reranker', retriever_reranker)
query_pipeline_reranker.add_component('query_cleaner_reranker', RemoveRelated())
query_pipeline_reranker.add_component('query_ranker_reranker', JinaRanker())
query_pipeline_reranker.connect('query_embedder_reranker.embedding', 'query_retriever_reranker.query_embedding')
query_pipeline_reranker.connect('query_retriever_reranker', 'query_cleaner_reranker')
query_pipeline_reranker.connect('query_cleaner_reranker', 'query_ranker_reranker')

reranker로 인한 차이를 강조하기 위해, 최종 재순위 지정 단계가 없는 동일한 파이프라인을 분석했습니다(가독성을 위해 해당 코드는 이 게시물에서 생략되었지만 노트북에서 찾을 수 있습니다):

이 두 파이프라인의 결과를 비교하기 위해, 이제 기존 티켓의 형태로 쿼리를 정의합니다. 여기서는 "ZOOKEEPER-3282"입니다:
query_ticket_key = 'ZOOKEEPER-3282'
with open('tickets.json', 'r') as file:
tickets = json.load(file)
for ticket in tickets:
if ticket['Issue key'] == query_ticket_key:
query = str(ticket)
query_ticket_id = ticket['Issue id']
이것은 "documetations에 대한 대규모 리팩토링"에 관한 것입니다 [sic]. 맞춤법 오류에도 불구하고 Jina Reranker가 유사한 티켓을 정확하게 검색할 것을 보게 될 것입니다.
{
"Summary": "a big refactor for the documetations"
"Issue key": "ZOOKEEPER-3282"
"Issue id:: 13216608
"Parent id": ""
"Issue Type": "Task"
"Status": "In Progress"
"Project lead": "phunt"
"Priority": "Major"
"Assignee": "maoling"
"Reporter": "maoling"
"Creator": "maoling"
"Created": "19/Feb/19 11:50"
"Updated": "04/Aug/19 12:48"
"Last Viewed": "12/Mar/24 11:56"
"Description": "Hi guys: I'am working on doing a big refactor for the documetations.it aims to - 1.make a better reading experiences and help users know more about zookeeper quickly,as good as other projects' doc(e.g redis,hbase). - 2.have less changes to diff with the original docs as far as possible. - 3.solve the problem when we have some new features or improvements,but cannot find a good place to doc it. The new catalog may looks kile this: * is new one added. ** is the one to keep unchanged as far as possible. *** is the one modified. -------------------------------------------------------------- |---Overview |---Welcome ** [1.1] |---Overview ** [1.2] |---Getting Started ** [1.3] |---Release Notes ** [1.4] |---Developer |---API *** [2.1] |---Programmer's Guide ** [2.2] |---Recipes *** [2.3] |---Clients * [2.4] |---Use Cases * [2.5] |---Admin & Ops |---Administrator's Guide ** [3.1] |---Quota Guide ** [3.2] |---JMX ** [3.3] |---Observers Guide ** [3.4] |---Dynamic Reconfiguration ** [3.5] |---Zookeeper CLI * [3.6] |---Shell * [3.7] |---Configuration flags * [3.8] |---Troubleshooting & Tuning * [3.9] |---Contributor Guidelines |---General Guidelines * [4.1] |---ZooKeeper Internals ** [4.2] |---Miscellaneous |---Wiki ** [5.1] |---Mailing Lists ** [5.2] -------------------------------------------------------------- The Roadmap is: 1.(I pick up it : D) 1.1 write API[2.1], which includes the: 1.1.1 original API Docs which is a Auto-generated java doc,just give a link. 1.1.2. Restful-api (the apis under the /zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources) 1.2 write Clients[2.4], which includes the: 1.2.1 C client 1.2.2 zk-python, kazoo 1.2.3 Curator etc....... look at an example from: https://redis.io/clients # write Recipes[2.3], which includes the: - integrate "Java Example" and "Barrier and Queue Tutorial"(Since some bugs in the examples and they are obsolete,we may delete something) into it. - suggest users to use the recipes implements of Curator and link to the Curator's recipes doc. # write Zookeeper CLI[3.6], which includes the: - about how to use the zk command line interface [./zkCli.sh] e.g ls /; get ; rmr;create -e -p etc....... - look at an example from redis: https://redis.io/topics/rediscli # write shell[3.7], which includes the: - list all usages of the shells under the zookeeper/bin. (e.g zkTxnLogToolkit.sh,zkCleanup.sh) # write Configuration flags[3.8], which includes the: - list all usages of configurations properties(e.g zookeeper.snapCount): - move the original Advanced Configuration part of zookeeperAdmin.md into it. look at an example from:https://coreos.com/etcd/docs/latest/op-guide/configuration.html # write Troubleshooting & Tuning[3.9], which includes the: - move the original "Gotchas: Common Problems and Troubleshooting" part of Administrator's Guide.md into it. - move the original "FAQ" into into it. - add some new contents (e.g https://www.yumpu.com/en/document/read/29574266/building-an-impenetrable-zookeeper-pdf-github). look at an example from:https://redis.io/topics/problems https://coreos.com/etcd/docs/latest/tuning.html # write General Guidelines[4.1], which includes the: - move the original "Logging" part of ZooKeeper Internals into it as the logger specification. - write specifications about code, git commit messages,github PR etc ... look at an example from: http://hbase.apache.org/book.html#hbase.commit.msg.format # write Use Cases[2.5], which includes the: - just move the context from: https://cwiki.apache.org/confluence/display/ZOOKEEPER/PoweredBy into it. - add some new contents.(e.g Apache Projects:Spark;Companies:twitter,fb) -------------------------------------------------------------- BTW: - Any insights or suggestions are very welcomed.After the dicussions,I will create a series of tickets(An umbrella) - Since these works can be done parallelly, if you are interested in them, please don't hesitate,just assign to yourself, pick it up. (Notice: give me a ping to avoid the duplicated work)."
}
마지막으로, 쿼리 파이프라인을 실행합니다. 이 경우 20개의 티켓을 검색하고, ID 관련 항목을 제거하고, 재순위를 매긴 후 가장 관련성이 높은 10개 이슈의 최종 선택을 출력합니다.
재순위 지정 단계 이전에는 출력에 17개의 티켓이 포함되어 있습니다:
| Rank | Issue ID | Issue Key | Summary |
|---|---|---|---|
| 1 | 13191544 | ZOOKEEPER-3170 | Umbrella for eliminating ZooKeeper flaky tests |
| 2 | 13400622 | ZOOKEEPER-4375 | Quota cannot limit the specify value when multiply clients create/set znodes |
| 3 | 13249579 | ZOOKEEPER-3499 | [admin server way] Add a complete backup mechanism for zookeeper internal |
| 4 | 13295073 | ZOOKEEPER-3775 | Wrong message in IOException |
| 5 | 13268474 | ZOOKEEPER-3617 | ZK digest ACL permissions gets overridden |
| 6 | 13296971 | ZOOKEEPER-3787 | Apply modernizer-maven-plugin to build |
| 7 | 13265507 | ZOOKEEPER-3600 | support the complete linearizable read and multiply read consistency level |
| 8 | 13222060 | ZOOKEEPER-3318 | [CLI way]Add a complete backup mechanism for zookeeper internal |
| 9 | 13262989 | ZOOKEEPER-3587 | Add a documentation about docker |
| 10 | 13262130 | ZOOKEEPER-3578 | Add a new CLI: multi |
| 11 | 13262828 | ZOOKEEPER-3585 | Add a documentation about RequestProcessors |
| 12 | 13262494 | ZOOKEEPER-3583 | Add new apis to get node type and ttl time info |
| 13 | 12998876 | ZOOKEEPER-2519 | zh->state should not be 0 while handle is active |
| 14 | 13536435 | ZOOKEEPER-4696 | Update for Zookeeper latest version |
| 15 | 13297249 | ZOOKEEPER-3789 | fix the build warnings about @see,@link,@return found by IDEA |
| 16 | 12728973 | ZOOKEEPER-1983 | Append to zookeeper.out (not overwrite) to support logrotation |
| 17 | 12478629 | ZOOKEEPER-915 | Errors that happen during sync() processing at the leader do not get propagated back to the client. |
재순위 지정자를 포함한 후 쿼리 파이프라인을 실행합니다:
result = query_pipeline_reranker.run(data={'query_embedder_reranker':{'text': query},
'query_retriever_reranker': {'top_k': 20},
'query_cleaner_reranker': {'query_id': query_ticket_id},
'query_ranker_reranker': {'query': query, 'top_k': 10}
}
)
for idx, res in enumerate(result['query_ranker_reranker']['documents']):
print('Doc {}:'.format(idx + 1), res)
최종 출력은 가장 관련성이 높은 10개의 티켓입니다:
| Rank | Issue ID | Issue Key | Summary |
|---|---|---|---|
| 1 | 13262989 | ZOOKEEPER-3587 | Add a documentation about docker |
| 2 | 13265507 | ZOOKEEPER-3600 | support the complete linearizable read and multiply read consistency level |
| 3 | 13249579 | ZOOKEEPER-3499 | [admin server way] Add a complete backup mechanism for zookeeper internal |
| 4 | 12478629 | ZOOKEEPER-915 | Errors that happen during sync() processing at the leader do not get propagated back to the client. |
| 5 | 13262828 | ZOOKEEPER-3585 | Add a documentation about RequestProcessors |
| 6 | 13297249 | ZOOKEEPER-3789 | fix the build warnings about @see,@link,@return found by IDEA |
| 7 | 12998876 | ZOOKEEPER-2519 | zh->state should not be 0 while handle is active |
| 8 | 13536435 | ZOOKEEPER-4696 | Update for Zookeeper latest version |
| 9 | 12728973 | ZOOKEEPER-1983 | Append to zookeeper.out (not overwrite) to support logrotation |
| 10 | 13222060 | ZOOKEEPER-3318 | [CLI way]Add a complete backup mechanism for zookeeper internal |
tagJina Embeddings와 Reranker의 장점
이 튜토리얼을 요약하자면, Jina Embeddings, Jina Reranker, 그리고 Haystack 2.0을 기반으로 중복 티켓 식별 도구를 구축했습니다. 위의 결과는 벡터 검색을 통해 관련 문서를 검색하는 Jina Embeddings와 최종적으로 가장 관련성 높은 콘텐츠를 얻기 위한 Jina Reranker의 필요성을 명확히 보여줍니다.
예를 들어, 문서 추가와 관련된 두 이슈인 "ZOOKEEPER-3585"와 "ZOOKEEPER-3587"을 보면, 검색 단계 후에는 각각 11위와 9위에 올바르게 포함되어 있습니다. 문서 재순위 지정 후에는 이들이 각각 5위와 1위로 상위 5개 가장 관련성 높은 문서 안에 포함되어 있어 큰 개선을 보여줍니다.
두 모델을 Haystack의 파이프라인에 통합함으로써 전체 도구를 바로 사용할 수 있게 되었습니다. 이러한 조합으로 Jina Haystack 확장은 여러분의 애플리케이션을 위한 완벽한 솔루션이 됩니다.







